]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
3mo xeno archeology (first phase) (#33370)
authorFildrance <fildrance@gmail.com>
Tue, 15 Apr 2025 00:34:53 +0000 (03:34 +0300)
committerGitHub <noreply@github.com>
Tue, 15 Apr 2025 00:34:53 +0000 (10:34 +1000)
* DAG Adjacency Matrix & Tests

* Fix sandbox type errors

* First pass on procgen

* Procgen adjustments

* Networking

* Cruft and god and beauty and analysis console

* convert to data types that dont make me want to kill myself

* starting work on console UI

* drawing nodes n shit

* damn that ui FUCKS

* XAT

* Add a bunch of basic triggers

* Fix trigger gen

* Add node info into the analysis console UI

* Add node unlocking

* more trigger cuz thats pretty cool

* final triggers + incorporate gnostic faith

* some ui changes, mostly

* Fix orphaned procgen segments

* its not random dipshit

* yeah... this one will make pjb happy....

* we call it a day for the UI

* imagine... shared power code...

* extraction WIP but we gotta sidequest momentarily

* oh hey would you look at that its the actual functionality

* distrotrased

* Small departure for randomness.

* ok yep yep indeed that is an effect very cool.

* thanos snap oldcode

* fuck it we ball

* feat: node scanner now displays triggered nodes. Removed unused old artifact systems and related code (most of it). xml-doc and minor fixups.

* refactor: most of preparations, cleanup and groundwork. also segment-related tests

* feature: all basic effects returning

* feat: finished effects lits, created weight lists for struct and handheld artifacts, fixed throw trigger and music ApplyComponent artifact effects not working

* feat: prevent non-first-time-predicted calls in shared artifact effect systems

* fix: remove gun effect from artifact effects - as it interferes with 'activate artefact' action

* fix: foam reagent selection, neat ApplyComponents art effect scenarios, handheld art is RadiationReceiver again

* fix: moved spawn/ pry&throw effect systems back to server part of code - entity duplication bugs were not quite fun

* refactor: fix protos

* refactor: fix linter

* fix: fix old artifact component names in yml

* fix: no more throwing error on artifact spawn with empty XAEFoamComponent.Reagents

* fix: removed old component usage in maps

* fix: remove more deleted components from map

* fix: ContainerContainer is now part of initial artifact entity, it won't be affecting UninitializedSaveTest

* refactor: fix tests, add loc description to toolshed commands

* Changed node scanner to tell the whole story about current artifact state

* refactor: remove excessive get of EntityCoordinates in XAE systems, removed Value access in NodeScannerDisplay

* fix: turned off TriggerInteraction, removed XAESpawn usage and system, EmpSystem now can use EntityCoordinates,

* fix: moved SharedXenoArtifactSystem.CancelUnlockingOnGraphStructureChange into RebuildXenoArtifactMetaData to lessen code coupling

* fix: XenoArtifactEffectJunkSpawn moved invalid rolls declaration

* refactor: set default value for XenoArtifactComponent.EffectsTable for tests

* fix: now explosions XAE can be activated for effect

* refactor: added some usedelay so artifactuse would'nt be spammed

* refactor: artifact-related hints improvements

* fix: artifact no longer spawns fauna into itself

* refactor: xml-doc and minor refactoring

* refactor: xml-doc for Xeno Artifact systems, renaming of questionable XAT systems

* map for playtest, TODO REVERT THIS

* fix: magboots trigger art from a mile

* refactor: bind artifact animation to unlocking state

* feat: radiation dmg now have reference to source (and artifacts won't irradiate themselves)

* fix: random artifact node durability now is rolled for max and not current value

* refactor: gas effects are more rare, hand-held artifact effects are filtered properly now, rad dmg trigger now requires only 20 dmg for activation

* feat: animations and sound effects for artifact force-use and failed finish of unlocking phase

* use only 1 file with art use animation

* refactor: minor artifact dmg triggers tuning

* feat: now nodes that CAN be unlocked are displayed with specific color in console.

* feat: now unlocking stage time is dynamic and it depends on amount of triggers player activated correctly. Failed one stops incrementing

* feat: now non-active unlocked nodes return more points if durability was not wasted

* feat: now puddle/foam effects change description of node

* fix: fix test failure

* refactor: renamed phasing effect, fixed failing test for elkridge

* minor balance changes

* refactor: split rare materials into separate effects

* feat: unlocked nodes without successor wont listen to unlocks, node unlock is not activating node

* fix: removed OnIrradiatedEvent duplicate c-tor

* revert changes of reach for playtest

* revert last row empty line removal on reach.yml

* fix: fix PVS bug, born from attempt to relay event to art nodes that were not synced yet to the client

* fix: fix elkridge for tests (again)

* refactor: xml-doc, more stuff predicted, allocation optimization in XAE/XAT systems

* refactor: naming

* refactor: extract variable refactor for XAEApplyComponentsSystem.OnActivated insides

* fix: duplicate xeno artifact unlocking sound fixed

* feat: CreatePuddle xeno artifact effect now can have min and max borders for chamicals to be drafted, minor XAECreatePuddleSystem refactor

* feat: networking for shared XAE components + xml-doc leftovers

* refactor: more xml-doc, fix XAEApplyComponentsComponent.Components not being serializable but trying to be

* refactor: xml-docs and XAEThrowThingsAroundSystem now uses circle and not box for prying tiles

* refactor: xml-docs, minor refactors

* revert XenoArtifactCommand.ArtifactPrototype being PrototId

* refactor: simplify the way ExtractionResearchLabel works

---------

Co-authored-by: EmoGarbage404 <retron404@gmail.com>
Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
233 files changed:
Content.Client/Xenoarchaeology/Artifact/XenoArtifactSystem.cs [new file with mode: 0644]
Content.Client/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs [new file with mode: 0644]
Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs
Content.Client/Xenoarchaeology/Equipment/NodeScannerSystem.cs [new file with mode: 0644]
Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs
Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml
Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs
Content.Client/Xenoarchaeology/Ui/NodeScannerBoundUserInterface.cs [new file with mode: 0644]
Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml [new file with mode: 0644]
Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml.cs [new file with mode: 0644]
Content.Client/Xenoarchaeology/Ui/XenoArtifactGraphControl.xaml [new file with mode: 0644]
Content.Client/Xenoarchaeology/Ui/XenoArtifactGraphControl.xaml.cs [new file with mode: 0644]
Content.Client/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs
Content.IntegrationTests/Tests/XenoArtifactTest.cs [new file with mode: 0644]
Content.Server/Administration/Systems/AdminVerbSystem.cs
Content.Server/Emp/EmpSystem.cs
Content.Server/EntityEffects/Effects/ActivateArtifact.cs [deleted file]
Content.Server/Instruments/InstrumentComponent.cs
Content.Server/PAI/PAISystem.cs
Content.Server/Radiation/Systems/RadiationSystem.cs
Content.Server/StationEvents/Events/VentClogRule.cs
Content.Server/Xenoarchaeology/Artifact/RandomArtifactSpriteSystem.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs with 64% similarity]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEChargeBatteryComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAECreateGasComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAECreatePuddleComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEEmpInAreaComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEFoamComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEIgniteComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAELightFlickerComponent.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs with 51% similarity]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEPolymorphComponent.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PolyOthersArtifactComponent.cs with 62% similarity]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETelepathicComponent.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs with 85% similarity]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETemperatureComponent.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs with 75% similarity]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEThrowThingsAroundComponent.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs with 75% similarity]
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAECreateGasSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAECreatePuddleSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAEEmpInAreaSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAEFoamSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAEIgniteSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAEPolymorphSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAETelepathicSystem.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs with 53% similarity]
Content.Server/Xenoarchaeology/Artifact/XAE/XAETemperatureSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAEThrowThingsAroundSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAE/XAETriggerExplosivesSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATGasComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATMagnetComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATPressureComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATTemperatureComponent.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/XATGasSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/XATMagnetSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/XATPressureSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XAT/XATTemperatureSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XenoArtifactSystem.ProcGen.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XenoArtifactSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Artifact/XenoArtifactUnlockNodeCommand.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs [new file with mode: 0644]
Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Components/BiasedArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs
Content.Server/Xenoarchaeology/Equipment/Systems/NodeScannerSystem.cs
Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Actions.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Commands.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ChargeBatteryArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ChemicalPuddleArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/EmpArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/IgniteArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PhasingArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PortalArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomInstrumentArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TriggerArtifactComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ChargeBatteryArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ChemicalPuddleArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/EmpArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/IgniteArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PhasingArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PolyOthersArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomInstrumentArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TriggerArtifactSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactElectricityTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactHeatTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactInteractionTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactLandTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMicrowaveTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactTimerTriggerComponent.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactLandSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMicrowaveTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs [deleted file]
Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs [deleted file]
Content.Shared.Database/LogType.cs
Content.Shared/Chemistry/Reaction/ChemicalReactionSystem.cs
Content.Shared/Chemistry/ReactiveSystem.cs
Content.Shared/Damage/Systems/DamageableSystem.cs
Content.Shared/Instruments/SharedInstrumentComponent.cs
Content.Shared/Radiation/Events/OnIrradiatedEvent.cs
Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactNodeComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactUnlockingComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/Prototypes/XenoArchTriggerPrototype.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Graph.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAE.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/BaseXAESystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEApplyComponentsComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEDamageInAreaComponent.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs with 60% similarity]
Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEKnockComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEPortalComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAERandomTeleportInvokerComponent.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs with 51% similarity]
Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAERemoveCollisionComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEShuffleComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/XAEApplyComponentsSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/XAEDamageInAreaSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/XAEKnockSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/XAEPortalSystem.cs [moved from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs with 51% similarity]
Content.Shared/Xenoarchaeology/Artifact/XAE/XAERandomTeleportInvokerSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/XAERemoveCollisionSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAE/XAEShuffleSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/BaseQueryUpdateXATSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/BaseXATSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATCompNearbyComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATDamageThresholdReachedComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATDeathComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATExaminableTextComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATExamineComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATInteractionComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATItemLandComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATReactiveComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATTimerComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATToolUseComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATCompNearbySystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATDamageThresholdReachedSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATDeathSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATExaminableTextSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATExamineSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATInteractionSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATItemLandSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATReactiveSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATTimerSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Artifact/XAT/XATToolUseSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Equipment/Components/ArtifactCrusherComponent.cs [moved from Content.Shared/Xenoarchaeology/Equipment/ArtifactCrusherComponent.cs with 98% similarity]
Content.Shared/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs [deleted file]
Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzerSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Equipment/SharedArtifactCrusherSystem.cs
Content.Shared/Xenoarchaeology/Equipment/SharedNodeScannerSystem.cs [new file with mode: 0644]
Content.Shared/Xenoarchaeology/Equipment/SuppressArtifactContainerSystem.cs [moved from Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs with 54% similarity]
Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs [deleted file]
Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs [deleted file]
Content.Shared/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteComponent.cs
Content.Shared/Xenoarchaeology/XenoArtifacts/SharedArtifact.cs
Resources/Audio/Items/Artifact/artifact-activation-fail1.ogg [new file with mode: 0644]
Resources/Audio/Items/Artifact/artifact-force-activated1.ogg [new file with mode: 0644]
Resources/Audio/Items/Artifact/attributions.yml
Resources/Locale/en-US/commands/toolshed-commands.ftl
Resources/Locale/en-US/guidebook/chemistry/effects.ftl
Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl
Resources/Locale/en-US/xenoarchaeology/artifact-component.ftl
Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl
Resources/Locale/en-US/xenoarchaeology/node-scanner.ftl
Resources/Maps/Shuttles/ShuttleEvent/incorporation.yml
Resources/Prototypes/Actions/types.yml
Resources/Prototypes/Catalog/Bounties/bounties.yml
Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml
Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml [moved from Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_artifacts.yml with 73% similarity]
Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/node_scanner.yml
Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_artifacts.yml [deleted file]
Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml
Resources/Prototypes/Reagents/chemicals.yml
Resources/Prototypes/SoundCollections/artifact.yml
Resources/Prototypes/XenoArch/Effects/normal_effects.yml [deleted file]
Resources/Prototypes/XenoArch/Effects/utility_effects.yml [deleted file]
Resources/Prototypes/XenoArch/artifact_triggers.yml [deleted file]
Resources/Prototypes/XenoArch/effects.yml [new file with mode: 0644]
Resources/Prototypes/XenoArch/triggers.yml [new file with mode: 0644]
Resources/Prototypes/name_identifier_groups.yml
Resources/Textures/Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi/artifact-activation.png [new file with mode: 0644]
Resources/Textures/Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi/meta.json

diff --git a/Content.Client/Xenoarchaeology/Artifact/XenoArtifactSystem.cs b/Content.Client/Xenoarchaeology/Artifact/XenoArtifactSystem.cs
new file mode 100644 (file)
index 0000000..0e8680e
--- /dev/null
@@ -0,0 +1,6 @@
+using Content.Shared.Xenoarchaeology.Artifact;
+
+namespace Content.Client.Xenoarchaeology.Artifact;
+
+/// <inheritdoc/>
+public sealed class XenoArtifactSystem : SharedXenoArtifactSystem;
diff --git a/Content.Client/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs b/Content.Client/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs
new file mode 100644 (file)
index 0000000..557d46f
--- /dev/null
@@ -0,0 +1,40 @@
+using Content.Client.Xenoarchaeology.Ui;
+using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Xenoarchaeology.Equipment;
+
+/// <inheritdoc />
+public sealed class ArtifactAnalyzerSystem : SharedArtifactAnalyzerSystem
+{
+    [Dependency] private readonly UserInterfaceSystem _ui = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<AnalysisConsoleComponent, AfterAutoHandleStateEvent>(OnAnalysisConsoleAfterAutoHandleState);
+        SubscribeLocalEvent<ArtifactAnalyzerComponent, AfterAutoHandleStateEvent>(OnAnalyzerAfterAutoHandleState);
+    }
+
+    private void OnAnalysisConsoleAfterAutoHandleState(Entity<AnalysisConsoleComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        UpdateBuiIfCanGetAnalysisConsoleUi(ent);
+    }
+
+    private void OnAnalyzerAfterAutoHandleState(Entity<ArtifactAnalyzerComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        if (!TryGetAnalysisConsole(ent, out var analysisConsole))
+            return;
+
+        UpdateBuiIfCanGetAnalysisConsoleUi(analysisConsole.Value);
+    }
+
+    private void UpdateBuiIfCanGetAnalysisConsoleUi(Entity<AnalysisConsoleComponent> analysisConsole)
+    {
+        if (_ui.TryGetOpenUi<AnalysisConsoleBoundUserInterface>(analysisConsole.Owner, ArtifactAnalyzerUiKey.Key, out var bui))
+            bui.Update(analysisConsole);
+    }
+}
index a57ef55574755baa50561b00ad85c38d637cab5b..d4a88cdd0378d47c1e5201e83f8c99af25213cb7 100644 (file)
@@ -6,4 +6,4 @@ namespace Content.Client.Xenoarchaeology.Equipment;
 public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
 {
 
-}
+}
\ No newline at end of file
diff --git a/Content.Client/Xenoarchaeology/Equipment/NodeScannerSystem.cs b/Content.Client/Xenoarchaeology/Equipment/NodeScannerSystem.cs
new file mode 100644 (file)
index 0000000..9b517c0
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Client.Xenoarchaeology.Ui;
+using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Xenoarchaeology.Equipment;
+
+/// <inheritdoc cref="SharedNodeScannerSystem"/>
+public sealed class NodeScannerSystem : SharedNodeScannerSystem
+{
+    [Dependency] private readonly UserInterfaceSystem _ui = default!;
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<NodeScannerComponent, AfterAutoHandleStateEvent>(OnAnalysisConsoleAfterAutoHandleState);
+    }
+
+    protected override void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor)
+    {
+        _ui.TryOpenUi(device.Owner, NodeScannerUiKey.Key, actor, true);
+    }
+
+    private void OnAnalysisConsoleAfterAutoHandleState(Entity<NodeScannerComponent> ent, ref AfterAutoHandleStateEvent args)
+    {
+        if (_ui.TryGetOpenUi<NodeScannerBoundUserInterface>(ent.Owner, NodeScannerUiKey.Key, out var bui))
+            bui.Update(ent);
+    }
+}
index c7a74815b6b84ee0b2db29502c7304eeccdf334a..a4ecff753016f1fa189ef878c52a1a9bf2bb1e0c 100644 (file)
@@ -1,66 +1,50 @@
-using Content.Shared.Xenoarchaeology.Equipment;
-using JetBrains.Annotations;
-using Robust.Client.GameObjects;
+using Content.Shared.Research.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
 using Robust.Client.UserInterface;
+using JetBrains.Annotations;
 
 namespace Content.Client.Xenoarchaeology.Ui;
 
+/// <summary>
+/// BUI for artifact analysis console, proxies server-provided UI updates
+/// (related to device, connected artifact analyzer, and artifact lying on it).
+/// </summary>
 [UsedImplicitly]
-public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
+public sealed class AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
 {
     [ViewVariables]
     private AnalysisConsoleMenu? _consoleMenu;
 
-    public AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
-    {
-    }
-
+    /// <inheritdoc />
     protected override void Open()
     {
         base.Open();
 
         _consoleMenu = this.CreateWindow<AnalysisConsoleMenu>();
+        _consoleMenu.SetOwner(owner);
+
+        _consoleMenu.OnClose += Close;
+        _consoleMenu.OpenCentered();
 
         _consoleMenu.OnServerSelectionButtonPressed += () =>
         {
-            SendMessage(new AnalysisConsoleServerSelectionMessage());
-        };
-        _consoleMenu.OnScanButtonPressed += () =>
-        {
-            SendMessage(new AnalysisConsoleScanButtonPressedMessage());
-        };
-        _consoleMenu.OnPrintButtonPressed += () =>
-        {
-            SendMessage(new AnalysisConsolePrintButtonPressedMessage());
+            SendMessage(new ConsoleServerSelectionMessage());
         };
         _consoleMenu.OnExtractButtonPressed += () =>
         {
             SendMessage(new AnalysisConsoleExtractButtonPressedMessage());
         };
-        _consoleMenu.OnUpBiasButtonPressed += () =>
-        {
-            SendMessage(new AnalysisConsoleBiasButtonPressedMessage(false));
-        };
-        _consoleMenu.OnDownBiasButtonPressed += () =>
-        {
-            SendMessage(new AnalysisConsoleBiasButtonPressedMessage(true));
-        };
     }
 
-    protected override void UpdateState(BoundUserInterfaceState state)
+    /// <summary>
+    /// Update UI state based on corresponding component.
+    /// </summary>
+    public void Update(Entity<AnalysisConsoleComponent> ent)
     {
-        base.UpdateState(state);
-
-        switch (state)
-        {
-            case AnalysisConsoleUpdateState msg:
-                _consoleMenu?.SetButtonsDisabled(msg);
-                _consoleMenu?.UpdateInformationDisplay(msg);
-                _consoleMenu?.UpdateProgressBar(msg);
-                break;
-        }
+        _consoleMenu?.Update(ent);
     }
 
+    /// <inheritdoc />
     protected override void Dispose(bool disposing)
     {
         base.Dispose(disposing);
index 29f4a548479094e8d45731a3c4b66bb0987bfd36..14db2bdf60d49d9f96169c931121d31f94a0759b 100644 (file)
@@ -1,80 +1,91 @@
-<controls:FancyWindow xmlns="https://spacestation14.io"
+<controls:FancyWindow xmlns="https://spacestation14.io"
     xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
     xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
     xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+    xmlns:ui="clr-namespace:Content.Client.Xenoarchaeology.Ui"
     Title="{Loc 'analysis-console-menu-title'}"
-    MinSize="620 280"
-    SetSize="620 280">
+    MinSize="700 350"
+    SetSize="980 550">
     <BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
-        <BoxContainer Margin="10 10 10 10" MinWidth="150" Orientation="Vertical"
-            VerticalExpand="True" SizeFlagsStretchRatio="1">
-            <BoxContainer Orientation="Vertical" VerticalExpand="True">
-                <Button Name="ServerSelectionButton"
-                    Text="{Loc 'analysis-console-server-list-button'}"></Button>
-                <BoxContainer MinHeight="5"></BoxContainer>
-                <Button Name="ScanButton"
-                    Text="{Loc 'analysis-console-scan-button'}"
-                    ToolTip="{Loc 'analysis-console-scan-tooltip-info'}">
-                </Button>
-                <BoxContainer MinHeight="5"></BoxContainer>
-                <Button Name="PrintButton"
-                    Text="{Loc 'analysis-console-print-button'}"
-                    ToolTip="{Loc 'analysis-console-print-tooltip-info'}">
-                </Button>
-                <BoxContainer MinHeight="5"></BoxContainer>
-                <BoxContainer Orientation="Horizontal">
-                    <Button Name="UpBiasButton"
-                        Text="{Loc 'analysis-console-bias-up'}"
-                        ToolTip="{Loc 'analysis-console-bias-button-info-up'}"
-                        HorizontalExpand="True"
-                        StyleClasses="OpenRight">
-                    </Button>
-                    <Button Name="DownBiasButton"
-                        Text="{Loc 'analysis-console-bias-down'}"
-                        ToolTip="{Loc 'analysis-console-bias-button-info-down'}"
-                        HorizontalExpand="True"
-                        StyleClasses="OpenLeft">
-                    </Button>
+        <BoxContainer Margin="10 10 10 10" MaxWidth="240" SetWidth="240" Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
+            <PanelContainer Name="BackPanel" HorizontalAlignment="Center">
+                <PanelContainer.PanelOverride>
+                    <gfx:StyleBoxTexture Modulate="#1B1B1E" PatchMarginBottom="10" PatchMarginLeft="10" PatchMarginRight="10" PatchMarginTop="10"/>
+                </PanelContainer.PanelOverride>
+                <BoxContainer HorizontalExpand="True" VerticalExpand="True" MinSize="128 128">
+                    <SpriteView Name="ArtifactView" Scale="4 4" HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalExpand="True" VerticalExpand="True"/>
                 </BoxContainer>
-                <BoxContainer MinHeight="15"></BoxContainer>
-                <Button Name="ExtractButton"
-                    Text="{Loc 'analysis-console-extract-button'}"
-                    ToolTip="{Loc 'analysis-console-extract-button-info'}">
-                </Button>
+            </PanelContainer>
+            <customControls:HSeparator StyleClasses="HighDivider" Margin="0 15 0 10"/>
+            <BoxContainer Name="ExtractContainer" Orientation="Vertical" VerticalExpand="True" Visible="False">
+                <PanelContainer HorizontalExpand="True" VerticalExpand="True" RectClipContent="True">
+                    <PanelContainer.PanelOverride>
+                        <gfx:StyleBoxFlat BackgroundColor="#000000FF" />
+                    </PanelContainer.PanelOverride>
+                    <BoxContainer Margin="10 10 10 5" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
+                        <ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
+                            <BoxContainer HorizontalExpand="True" VerticalExpand="True">
+                                <RichTextLabel Name="ExtractionResearchLabel" VerticalAlignment="Top" HorizontalAlignment="Left"/>
+                            </BoxContainer>
+                        </ScrollContainer>
+                        <Control MinHeight="5"/>
+                        <RichTextLabel Name="ExtractionSumLabel" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
+                    </BoxContainer>
+                </PanelContainer>
             </BoxContainer>
-            <BoxContainer Orientation="Vertical">
-                <Label Name="ProgressLabel"></Label>
-                <ProgressBar
-                    Name="ProgressBar"
-                    MinValue="0"
-                    MaxValue="1"
-                    SetHeight="20">
-                </ProgressBar>
+            <BoxContainer Name="NodeViewContainer" Orientation="Vertical" VerticalExpand="True">
+                <ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
+                    <BoxContainer Orientation="Vertical" HorizontalExpand="False" VerticalExpand="True">
+                        <Label Name="NoneSelectedLabel" Text="{Loc 'analysis-console-no-node'}" HorizontalAlignment="Center" VerticalAlignment="Center" VerticalExpand="True" Visible="False"/>
+                        <BoxContainer Name="InfoContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
+                            <BoxContainer HorizontalExpand="True">
+                                <RichTextLabel Name="IDLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-id'}"/>
+                                <RichTextLabel Name="IDValueLabel" HorizontalAlignment="Right"/>
+                            </BoxContainer>
+                            <BoxContainer HorizontalExpand="True">
+                                <RichTextLabel Name="ClassLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-class'}"/>
+                                <RichTextLabel Name="ClassValueLabel" HorizontalAlignment="Right"/>
+                            </BoxContainer>
+                            <BoxContainer HorizontalExpand="True">
+                                <RichTextLabel Name="LockedLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-locked'}"/>
+                                <RichTextLabel Name="LockedValueLabel" HorizontalAlignment="Right"/>
+                            </BoxContainer>
+                            <BoxContainer HorizontalExpand="True">
+                                <RichTextLabel Name="DurabilityLabel" HorizontalExpand="True" Text="{Loc 'analysis-console-info-durability'}"/>
+                                <RichTextLabel Name="DurabilityValueLabel" HorizontalAlignment="Right"/>
+                            </BoxContainer>
+                            <Control MinHeight="20"/>
+                            <RichTextLabel Name="EffectLabel" Text="{Loc 'analysis-console-info-effect'}"/>
+                            <RichTextLabel Name="EffectValueLabel" HorizontalExpand="True"/>
+                            <RichTextLabel Name="TriggerLabel" Text="{Loc 'analysis-console-info-trigger'}"/>
+                            <RichTextLabel Name="TriggerValueLabel" HorizontalExpand="True"/>
+                        </BoxContainer>
+                    </BoxContainer>
+                </ScrollContainer>
+                <Control MinHeight="5"/>
+                <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+                    <Button Name="ServerButton" Text="{Loc 'analysis-console-server-list-button'}" StyleClasses="OpenRight" HorizontalExpand="True" MinHeight="35"/>
+                    <Button Name="ExtractButton" Text="{Loc 'analysis-console-extract-button'}" StyleClasses="OpenLeft" HorizontalExpand="True" MinHeight="35"/>
+                </BoxContainer>
             </BoxContainer>
         </BoxContainer>
         <customControls:VSeparator StyleClasses="LowDivider" />
-        <PanelContainer Margin="10 10 10 10" HorizontalExpand="True" SizeFlagsStretchRatio="3">
-            <PanelContainer.PanelOverride>
-                <gfx:StyleBoxFlat BackgroundColor="#000000FF" />
-            </PanelContainer.PanelOverride>
-            <BoxContainer Margin="10 10 10 10" Orientation="Horizontal">
-                <BoxContainer Orientation="Vertical" HorizontalExpand="True">
-                    <BoxContainer VerticalExpand="True">
-                        <RichTextLabel Name="Information"> </RichTextLabel>
-                    </BoxContainer>
+        <BoxContainer HorizontalExpand="True" VerticalExpand="True">
+            <PanelContainer Margin="10 10 10 10" HorizontalExpand="True" RectClipContent="True">
+                <PanelContainer.PanelOverride>
+                    <gfx:StyleBoxFlat BackgroundColor="#000000FF" />
+                </PanelContainer.PanelOverride>
+                <BoxContainer Margin="10 10 10 10" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
+                    <ui:XenoArtifactGraphControl Name="GraphControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
+                        <Label Name="NoArtiLabel"
+                               Text="{Loc 'analysis-console-info-no-artifact'}"
+                               HorizontalExpand="True"
+                               VerticalExpand="True"
+                               HorizontalAlignment="Center"
+                               VerticalAlignment="Center"/>
+                    </ui:XenoArtifactGraphControl>
                 </BoxContainer>
-                <BoxContainer VerticalExpand="False" Orientation="Vertical" MaxSize="64 64">
-                    <SpriteView
-                        Name="ArtifactDisplay"
-                        OverrideDirection="South"
-                        VerticalExpand="False"
-                        SetSize="64 64"
-                        MaxSize="64 64"
-                        Scale="2 2">
-                    </SpriteView>
-                </BoxContainer>
-                <BoxContainer VerticalExpand="True"></BoxContainer>
-            </BoxContainer>
-        </PanelContainer>
+            </PanelContainer>
+        </BoxContainer>
     </BoxContainer>
 </controls:FancyWindow>
index 40a6c6a1d9a81285ab530a7c4da4e9a641afaede..0a0885a3a29827b69eb1de1e60981501b43a7ce9 100644 (file)
@@ -1,11 +1,18 @@
-using Content.Client.Stylesheets;
+using System.Text;
+using Content.Client.Message;
+using Content.Client.Resources;
 using Content.Client.UserInterface.Controls;
-using Content.Shared.Xenoarchaeology.Equipment;
-using Microsoft.VisualBasic;
+using Content.Client.Xenoarchaeology.Artifact;
+using Content.Client.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.Audio;
 using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Audio;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
@@ -14,170 +21,213 @@ namespace Content.Client.Xenoarchaeology.Ui;
 [GenerateTypedNameReferences]
 public sealed partial class AnalysisConsoleMenu : FancyWindow
 {
+    private static readonly TimeSpan ExtractInfoDisplayForDuration = TimeSpan.FromSeconds(3);
+
     [Dependency] private readonly IEntityManager _ent = default!;
+    [Dependency] private readonly IResourceCache _resCache = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
 
-    public event Action? OnServerSelectionButtonPressed;
-    public event Action? OnScanButtonPressed;
-    public event Action? OnPrintButtonPressed;
-    public event Action? OnExtractButtonPressed;
-    public event Action? OnUpBiasButtonPressed;
-    public event Action? OnDownBiasButtonPressed;
+    private readonly ArtifactAnalyzerSystem _artifactAnalyzer;
+    private readonly XenoArtifactSystem _xenoArtifact;
+    private readonly AudioSystem _audio;
+    private readonly MetaDataSystem _meta = default!;
 
-    // For rendering the progress bar, updated from BUI state
-    private TimeSpan? _startTime;
-    private TimeSpan? _totalTime;
-    private TimeSpan? _accumulatedRunTime;
+    private Entity<AnalysisConsoleComponent> _owner;
+    private Entity<XenoArtifactNodeComponent>? _currentNode;
 
-    private bool _paused;
+    private TimeSpan? _hideExtractInfoIn;
+    private int _extractionSum;
+
+    public event Action? OnServerSelectionButtonPressed;
+    public event Action? OnExtractButtonPressed;
 
     public AnalysisConsoleMenu()
     {
         RobustXamlLoader.Load(this);
         IoCManager.InjectDependencies(this);
 
-        ServerSelectionButton.OnPressed += _ => OnServerSelectionButtonPressed?.Invoke();
-        ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke();
-        PrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke();
-        ExtractButton.OnPressed += _ => OnExtractButtonPressed?.Invoke();
-        UpBiasButton.OnPressed += _ => OnUpBiasButtonPressed?.Invoke();
-        DownBiasButton.OnPressed += _ => OnDownBiasButtonPressed?.Invoke();
+        _xenoArtifact = _ent.System<XenoArtifactSystem>();
+        _artifactAnalyzer = _ent.System<ArtifactAnalyzerSystem>();
+        _audio = _ent.System<AudioSystem>();
+        _meta = _ent.System<MetaDataSystem>();
+
+        if (BackPanel.PanelOverride is StyleBoxTexture tex)
+            tex.Texture = _resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
 
-        var buttonGroup = new ButtonGroup(false);
-        UpBiasButton.Group = buttonGroup;
-        DownBiasButton.Group = buttonGroup;
+        GraphControl.OnNodeSelected += node =>
+        {
+            _currentNode = node;
+            SetSelectedNode(node);
+        };
+
+        ServerButton.OnPressed += _ =>
+        {
+            OnServerSelectionButtonPressed?.Invoke();
+        };
+
+        ExtractButton.OnPressed += StartExtract;
     }
 
-    protected override void FrameUpdate(FrameEventArgs args)
+    /// <summary>
+    /// Set entity that corresponds analysis console, for which window is opened.
+    /// Closes window if <see cref="AnalysisConsoleComponent"/> is not present on entity.
+    /// </summary>
+    public void SetOwner(EntityUid owner)
     {
-        base.FrameUpdate(args);
+        if (!_ent.TryGetComponent<AnalysisConsoleComponent>(owner, out var comp))
+        {
+            Close();
+            return;
+        }
+
+        _owner = (owner, comp);
+        Update(_owner);
+    }
 
-        if (_startTime is not { } start || _totalTime is not { } total || _accumulatedRunTime is not { } accumulated)
+    private void StartExtract(BaseButton.ButtonEventArgs obj)
+    {
+        if (!_artifactAnalyzer.TryGetArtifactFromConsole(_owner, out var artifact))
             return;
 
-        var remaining = total - accumulated;
-        if (!_paused)
+        ExtractContainer.Visible = true;
+        NodeViewContainer.Visible = false;
+
+        _extractionSum = 0;
+        var extractionMessage = new FormattedMessage();
+
+        var nodes = _xenoArtifact.GetAllNodes(artifact.Value);
+
+        var count = 0;
+        foreach (var node in nodes)
         {
-            // If the analyzer is running, its remaining time is further discounted by the time it's been running for.
-            remaining += start - _timing.CurTime;
+            var pointValue = _xenoArtifact.GetResearchValue(node);
+            if (pointValue <= 0)
+                continue;
+
+            count++;
+
+            var nodeId = _xenoArtifact.GetNodeId(node);
+
+            var text = Loc.GetString("analysis-console-extract-value", ("id", nodeId), ("value", pointValue));
+            extractionMessage.AddMarkupOrThrow(text);
+            extractionMessage.PushNewline();
         }
-        var secsText = Math.Max((int) remaining.TotalSeconds, 0);
 
-        ProgressLabel.Text = Loc.GetString("analysis-console-progress-text",
-            ("seconds", secsText));
+        if (count == 0)
+            extractionMessage.AddMarkupOrThrow(Loc.GetString("analysis-console-extract-none"));
+
+        _hideExtractInfoIn = _timing.CurTime + ExtractInfoDisplayForDuration;
+
+        ExtractionResearchLabel.SetMessage(extractionMessage);
+
+        ExtractionSumLabel.SetMarkup(Loc.GetString("analysis-console-extract-sum", ("value", _extractionSum)));
 
-        // 1.0 - div because we want it to tick up not down
-        ProgressBar.Value = Math.Clamp(1.0f - (float) remaining.Divide(total), 0.0f, 1.0f);
+        _audio.PlayGlobal(_owner.Comp.ScanFinishedSound, _owner, AudioParams.Default.WithVolume(1f));
+        OnExtractButtonPressed?.Invoke();
     }
 
-    public void SetButtonsDisabled(AnalysisConsoleUpdateState state)
+    protected override void FrameUpdate(FrameEventArgs args)
     {
-        ScanButton.Disabled = !state.CanScan;
-        PrintButton.Disabled = !state.CanPrint;
-        if (state.IsTraversalDown)
-            DownBiasButton.Pressed = true;
-        else
-            UpBiasButton.Pressed = true;
+        base.FrameUpdate(args);
 
-        ExtractButton.Disabled = false;
-        if (!state.ServerConnected)
-        {
-            ExtractButton.Disabled = true;
-            ExtractButton.ToolTip = Loc.GetString("analysis-console-no-server-connected");
-        }
-        else if (!state.CanScan)
-        {
-            ExtractButton.Disabled = true;
-
-            // CanScan can be false if either there's no analyzer connected or if there's
-            // no entity on the scanner. The `Information` text will always tell the user
-            // of the former case, but in the latter, it'll only show a message if a scan
-            // has never been performed, so add a tooltip to indicate that the artifact
-            // is gone.
-            if (state.AnalyzerConnected)
-            {
-                ExtractButton.ToolTip = Loc.GetString("analysis-console-no-artifact-placed");
-            }
-            else
-            {
-                ExtractButton.ToolTip = null;
-            }
-        }
-        else if (state.PointAmount <= 0)
-        {
-            ExtractButton.Disabled = true;
-            ExtractButton.ToolTip = Loc.GetString("analysis-console-no-points-to-extract");
-        }
+        if (_hideExtractInfoIn == null || _timing.CurTime + _meta.GetPauseTime(_owner) < _hideExtractInfoIn)
+            return;
 
-        if (ExtractButton.Disabled)
-        {
-            ExtractButton.RemoveStyleClass("ButtonColorGreen");
-        }
-        else
-        {
-            ExtractButton.AddStyleClass("ButtonColorGreen");
-            ExtractButton.ToolTip = null;
-        }
+        ExtractContainer.Visible = false;
+        NodeViewContainer.Visible = true;
+        _hideExtractInfoIn = null;
     }
-    private void UpdateArtifactIcon(EntityUid? uid)
+
+    public void Update(Entity<AnalysisConsoleComponent> ent)
     {
-        if (uid == null)
+        _artifactAnalyzer.TryGetArtifactFromConsole(ent, out var arti);
+        ArtifactView.SetEntity(arti);
+        GraphControl.SetArtifact(arti);
+
+        ExtractButton.Disabled = arti == null;
+
+        if (arti == null)
+            NoneSelectedLabel.Visible = false;
+
+        NoArtiLabel.Visible = true;
+        if (!_artifactAnalyzer.TryGetAnalyzer(ent, out _))
+            NoArtiLabel.Text = Loc.GetString("analysis-console-info-no-scanner");
+        else if (arti == null)
+            NoArtiLabel.Text = Loc.GetString("analysis-console-info-no-artifact");
+        else
+            NoArtiLabel.Visible = false;
+
+        if (_currentNode == null
+            || arti == null
+            || !_xenoArtifact.TryGetIndex((arti.Value, arti.Value), _currentNode.Value, out _))
         {
-            ArtifactDisplay.Visible = false;
-            return;
+            SetSelectedNode(null);
         }
-
-        ArtifactDisplay.Visible = true;
-        ArtifactDisplay.SetEntity(uid);
     }
 
-    public void UpdateInformationDisplay(AnalysisConsoleUpdateState state)
+    public void SetSelectedNode(Entity<XenoArtifactNodeComponent>? node)
     {
-        var message = new FormattedMessage();
+        InfoContainer.Visible = node != null;
+        if (!_artifactAnalyzer.TryGetArtifactFromConsole(_owner, out var artifact))
+            return;
 
-        if (state.Scanning)
-        {
-            if (state.Paused)
-            {
-                message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-scanner-paused"));
-            }
-            else
-            {
-                message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-scanner"));
-            }
-            Information.SetMessage(message);
-            UpdateArtifactIcon(null); //set it to blank
+        NoneSelectedLabel.Visible = node == null;
+
+        if (node == null)
             return;
-        }
 
-        UpdateArtifactIcon(_ent.GetEntity(state.Artifact));
+        var nodeId = _xenoArtifact.GetNodeId(node.Value);
+        IDValueLabel.SetMarkup(Loc.GetString("analysis-console-info-id-value", ("id", nodeId)));
+
+        // If active, state is 2. else, it is 0 or 1 based on whether it is unlocked, or not.
+        int lockedState;
+        if (_xenoArtifact.IsNodeActive(artifact.Value, node.Value))
+            lockedState = 2;
+        else
+            lockedState = node.Value.Comp.Locked ? 0 : 1;
+
+        LockedValueLabel.SetMarkup(Loc.GetString("analysis-console-info-locked-value", ("state", lockedState)));
 
-        if (state.ScanReport == null)
+        var percent = (float) node.Value.Comp.Durability / node.Value.Comp.MaxDurability;
+        var color = percent switch
+        {
+            >= 0.75f => Color.Lime,
+            >= 0.50f => Color.Yellow,
+            _ => Color.Red
+        };
+        DurabilityValueLabel.SetMarkup(Loc.GetString("analysis-console-info-durability-value",
+            ("color", color),
+            ("current", node.Value.Comp.Durability),
+            ("max", node.Value.Comp.MaxDurability)));
+
+        var hasInfo = _xenoArtifact.HasUnlockedPredecessor(artifact.Value, node.Value);
+
+        EffectValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value",
+            ("state", hasInfo),
+            ("info", _ent.GetComponentOrNull<MetaDataComponent>(node.Value)?.EntityDescription ?? string.Empty)));
+
+        var predecessorNodes = _xenoArtifact.GetPredecessorNodes(artifact.Value.Owner, node.Value);
+        if (!hasInfo)
         {
-            if (!state.AnalyzerConnected) //no analyzer connected
-                message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-scanner"));
-            else if (!state.CanScan) //no artifact
-                message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-no-artifact"));
-            else if (state.Artifact == null) //ready to go
-                message.AddMarkupOrThrow(Loc.GetString("analysis-console-info-ready"));
+            TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-effect-value", ("state", false)));
         }
         else
         {
-            message.AddMessage(state.ScanReport);
-        }
-
-        Information.SetMessage(message);
-    }
+            var triggerStr = new StringBuilder();
+            triggerStr.Append("- ");
+            triggerStr.Append(Loc.GetString(node.Value.Comp.TriggerTip!));
 
-    public void UpdateProgressBar(AnalysisConsoleUpdateState state)
-    {
-        ProgressBar.Visible = state.Scanning;
-        ProgressLabel.Visible = state.Scanning;
+            foreach (var predecessor in predecessorNodes)
+            {
+                triggerStr.AppendLine();
+                triggerStr.Append("- ");
+                triggerStr.Append(Loc.GetString(predecessor.Comp.TriggerTip!));
+            }
+            TriggerValueLabel.SetMarkup(Loc.GetString("analysis-console-info-triggered-value", ("triggers", triggerStr.ToString())));
+        }
 
-        _startTime = state.StartTime;
-        _totalTime = state.TotalTime;
-        _accumulatedRunTime = state.AccumulatedRunTime;
-        _paused = state.Paused;
+        ClassValueLabel.SetMarkup(Loc.GetString("analysis-console-info-class-value",
+            ("class", Loc.GetString($"artifact-node-class-{Math.Min(6, predecessorNodes.Count + 1)}"))));
     }
 }
 
diff --git a/Content.Client/Xenoarchaeology/Ui/NodeScannerBoundUserInterface.cs b/Content.Client/Xenoarchaeology/Ui/NodeScannerBoundUserInterface.cs
new file mode 100644 (file)
index 0000000..3ed937c
--- /dev/null
@@ -0,0 +1,42 @@
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+/// <summary>
+/// BUI for hand-held xeno artifact scanner,  server-provided UI updates.
+/// </summary>
+public sealed class NodeScannerBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
+{
+    [ViewVariables]
+    private NodeScannerDisplay? _scannerDisplay;
+
+    /// <inheritdoc />
+    protected override void Open()
+    {
+        base.Open();
+
+        _scannerDisplay = this.CreateWindow<NodeScannerDisplay>();
+        _scannerDisplay.SetOwner(Owner);
+        _scannerDisplay.OnClose += Close;
+    }
+
+    /// <summary>
+    /// Update UI state based on corresponding component.
+    /// </summary>
+    public void Update(Entity<NodeScannerComponent> ent)
+    {
+        _scannerDisplay?.Update(ent);
+    }
+
+    /// <inheritdoc />
+    protected override void Dispose(bool disposing)
+    {
+        base.Dispose(disposing);
+
+        if (!disposing)
+            return;
+
+        _scannerDisplay?.Dispose();
+    }
+}
diff --git a/Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml b/Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml
new file mode 100644 (file)
index 0000000..448acd0
--- /dev/null
@@ -0,0 +1,20 @@
+<controls:FancyWindow xmlns="https://spacestation14.io"
+    xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+    Title="{Loc 'node-scan-display-title'}"
+    MinSize="305 180"
+    SetSize="305 180"
+    Resizable="False"
+    >
+    <BoxContainer Orientation="Vertical" >
+        <controls:StripeBack>
+            <Label Name="NodeScannerState" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
+        </controls:StripeBack>
+        <BoxContainer Orientation="Horizontal">
+            <Label Name="NoActiveNodeDataLabel" Text="{Loc 'node-scan-no-data'}" Margin="45 25 0 0" MinHeight="47" />
+            <GridContainer Name="ActiveNodesList" Columns="4" Rows="2" Visible="True" MinHeight="47" />
+        </BoxContainer>
+        <controls:StripeBack>
+            <Label Name="ArtifactStateLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 0 0 4" />
+        </controls:StripeBack>
+    </BoxContainer>
+</controls:FancyWindow>
diff --git a/Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml.cs b/Content.Client/Xenoarchaeology/Ui/NodeScannerDisplay.xaml.cs
new file mode 100644 (file)
index 0000000..8ef94c4
--- /dev/null
@@ -0,0 +1,86 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class NodeScannerDisplay : FancyWindow
+{
+    [Dependency] private readonly IEntityManager _ent = default!;
+
+    public NodeScannerDisplay()
+    {
+        RobustXamlLoader.Load(this);
+
+        IoCManager.InjectDependencies(this);
+    }
+
+    /// <summary>
+    /// Sets entity that represents hand-held xeno artifact node scanner for which window is opened.
+    /// Closes window if <see cref="NodeScannerComponent"/> is not present on entity.
+    /// </summary>
+    public void SetOwner(EntityUid scannerEntityUid)
+    {
+        if (!_ent.TryGetComponent<NodeScannerComponent>(scannerEntityUid, out var scannerComponent))
+        {
+            Close();
+            return;
+        }
+
+        Update((scannerEntityUid, scannerComponent));
+    }
+
+    /// <summary>
+    /// Updates labels with scanned artifact data and list of triggered nodes from component.
+    /// </summary>
+    public void Update(Entity<NodeScannerComponent> ent)
+    {
+        ArtifactStateLabel.Text = GetState(ent);
+        var scannedAt = ent.Comp.ScannedAt;
+        NodeScannerState.Text = scannedAt > TimeSpan.Zero
+            ? Loc.GetString("node-scanner-artifact-scanned-time", ("time", scannedAt.Value.ToString(@"hh\:mm\:ss")))
+            : Loc.GetString("node-scanner-artifact-scanned-time-none");
+
+        ActiveNodesList.Children.Clear();
+
+        var triggeredNodesSnapshot = ent.Comp.TriggeredNodesSnapshot;
+        if (triggeredNodesSnapshot.Count > 0)
+        {
+            // show list of triggered nodes instead of 'no data' placeholder
+            NoActiveNodeDataLabel.Visible = false;
+            ActiveNodesList.Visible = true;
+
+            foreach (var nodeId in triggeredNodesSnapshot)
+            {
+                var nodeLabel = new Button
+                {
+                    Text = nodeId,
+                    Margin = new Thickness(15, 5, 0, 0),
+                    MaxHeight = 40,
+                    Disabled = true
+                };
+                ActiveNodesList.Children.Add(nodeLabel);
+            }
+        }
+        else
+        {
+            // clear list of activated nodes (done previously), show 'no data' placeholder
+            NoActiveNodeDataLabel.Visible = true;
+            ActiveNodesList.Visible = false;
+        }
+    }
+
+    private string GetState(Entity<NodeScannerComponent> ent)
+    {
+        return ent.Comp.ArtifactState switch
+        {
+            ArtifactState.None => "\u2800", // placeholder for line to not be squeezed
+            ArtifactState.Ready => Loc.GetString("node-scanner-artifact-state-ready"),
+            ArtifactState.Unlocking => Loc.GetString("node-scanner-artifact-state-unlocking"),
+            ArtifactState.Cooldown => Loc.GetString("node-scanner-artifact-state-cooldown")
+        };
+    }
+}
diff --git a/Content.Client/Xenoarchaeology/Ui/XenoArtifactGraphControl.xaml b/Content.Client/Xenoarchaeology/Ui/XenoArtifactGraphControl.xaml
new file mode 100644 (file)
index 0000000..5413387
--- /dev/null
@@ -0,0 +1,6 @@
+<controls:XenoArtifactGraphControl
+    xmlns="https://spacestation14.io"
+    xmlns:controls="clr-namespace:Content.Client.Xenoarchaeology.Ui"
+    HorizontalExpand="True"
+    VerticalExpand="True"
+    MouseFilter="Stop"/>
diff --git a/Content.Client/Xenoarchaeology/Ui/XenoArtifactGraphControl.xaml.cs b/Content.Client/Xenoarchaeology/Ui/XenoArtifactGraphControl.xaml.cs
new file mode 100644 (file)
index 0000000..fd45653
--- /dev/null
@@ -0,0 +1,208 @@
+using System.Linq;
+using System.Numerics;
+using Content.Client.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Input;
+
+namespace Content.Client.Xenoarchaeology.Ui;
+
+[GenerateTypedNameReferences]
+public sealed partial class XenoArtifactGraphControl : BoxContainer
+{
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+
+    private readonly XenoArtifactSystem _artifactSystem;
+
+    private Entity<XenoArtifactComponent>? _artifact;
+
+    private Entity<XenoArtifactNodeComponent>? _hoveredNode;
+
+    private readonly Font _font;
+
+    public event Action<Entity<XenoArtifactNodeComponent>>? OnNodeSelected;
+
+    private float NodeRadius => 25 * UIScale;
+    private float NodeDiameter => NodeRadius * 2;
+    private float MinYSpacing => NodeDiameter * 0.75f;
+    private float MaxYSpacing => NodeDiameter * 1.5f;
+    private float MinXSpacing => NodeDiameter * 0.33f;
+    private float MaxXSpacing => NodeDiameter * 1f;
+    private float MinXSegmentSpacing => NodeDiameter * 0.5f;
+    private float MaxXSegmentSpacing => NodeDiameter * 3f;
+
+    public XenoArtifactGraphControl()
+    {
+        IoCManager.InjectDependencies(this);
+        RobustXamlLoader.Load(this);
+
+        _artifactSystem = _entityManager.System<XenoArtifactSystem>();
+
+        var fontResource = IoCManager.Resolve<IResourceCache>()
+                                     .GetResource<FontResource>("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf");
+        _font = new VectorFont(fontResource, 16);
+    }
+
+    public Color LockedNodeColor { get; set; } = Color.FromHex("#777777");
+    public Color ActiveNodeColor { get; set; } = Color.Plum;
+    public Color UnlockedNodeColor { get; set; } = Color.White;
+    public Color HoveredNodeColor { get; set; } = Color.DimGray;
+    public Color UnlockableNodeColor { get; set; } = Color.LightSlateGray;
+
+    public void SetArtifact(Entity<XenoArtifactComponent>? artifact)
+    {
+        _artifact = artifact;
+    }
+
+    protected override void KeyBindDown(GUIBoundKeyEventArgs args)
+    {
+        base.KeyBindDown(args);
+
+        if (args.Handled || args.Function != EngineKeyFunctions.UIClick)
+            return;
+
+        if (_hoveredNode == null)
+            return;
+
+        OnNodeSelected?.Invoke(_hoveredNode.Value);
+        UserInterfaceManager.ClickSound();
+    }
+
+    /// <summary>
+    /// Renders artifact node graph control, consisting of nodes and edges connecting them.
+    /// </summary>
+    protected override void Draw(DrawingHandleScreen handle)
+    {
+        base.Draw(handle);
+
+        _hoveredNode = null;
+        if (_artifact == null)
+            return;
+        var artifact = _artifact.Value;
+
+        var maxDepth = _artifactSystem.GetAllNodes(artifact)
+                                      .Max(s => s.Comp.Depth);
+        var segments = _artifactSystem.GetSegments(artifact);
+
+        var bottomLeft = Position // the position
+                         + new Vector2(0, Size.Y * UIScale) // the scaled height of the control
+                         + new Vector2(NodeRadius, -NodeRadius); // offset half a node so we don't render off screen
+
+        var controlHeight = bottomLeft.Y;
+        var controlWidth = Size.X * UIScale - NodeRadius;
+
+        // select y spacing based on max number of nodes we have on Y axis - that is max depth of artifact graph node
+        var ySpacing = 0f;
+        if (maxDepth != 0)
+            ySpacing = Math.Clamp((controlHeight - ((maxDepth + 1) * NodeDiameter)) / maxDepth, MinYSpacing, MaxYSpacing);
+
+        // gets settings for visualizing segments (groups of interconnected nodes - there may be 1 or more per artifact).
+        var segmentWidths = segments.Sum(GetBiggestWidth);
+        var segmentSpacing = Math.Clamp((controlWidth - segmentWidths) / (segments.Count - 1), MinXSegmentSpacing, MaxXSegmentSpacing);
+        var segmentOffset = Math.Max((controlWidth - (segmentWidths) - (segmentSpacing * (segments.Count - 1))) / 2, 0);
+
+        bottomLeft.X += segmentOffset;
+        bottomLeft.Y -= (controlHeight - (ySpacing * maxDepth) - (NodeDiameter * (maxDepth + 1))) / 2;
+
+        var cursor = (UserInterfaceManager.MousePositionScaled.Position * UIScale) - GlobalPixelPosition;
+
+        foreach (var segment in segments)
+        {
+            // For each segment we draw nodes in order of depth. Method returns List of nodes for each depth level.
+            var orderedNodes = _artifactSystem.GetDepthOrderedNodes(segment);
+            foreach (var (_, nodes) in orderedNodes)
+            {
+                for (var i = 0; i < nodes.Count; i++)
+                {
+                    // selecting color for node based on its state
+                    var node = nodes[i];
+                    var color = LockedNodeColor;
+                    if (_artifactSystem.IsNodeActive(artifact, node))
+                    {
+                        color = ActiveNodeColor;
+                    }
+                    else if (!node.Comp.Locked)
+                    {
+                        color = UnlockedNodeColor;
+                    }
+                    else
+                    {
+                        var directPredecessorNodes = _artifactSystem.GetDirectPredecessorNodes((artifact, artifact), node);
+                        if (directPredecessorNodes.Count == 0 || directPredecessorNodes.All(x => !x.Comp.Locked))
+                        {
+                            color = UnlockableNodeColor;
+                        }
+                    }
+
+                    var pos = GetNodePos(node, ySpacing, segments, ref bottomLeft);
+                    var hovered = (cursor - pos).LengthSquared() <= NodeRadius * NodeRadius;
+                    if (hovered)
+                    {
+                        // render hovered node if we have one
+                        _hoveredNode = node;
+                        handle.DrawCircle(pos, NodeRadius, HoveredNodeColor);
+                    }
+
+                    // render circle and text with node id inside
+                    handle.DrawCircle(pos, NodeRadius, Color.ToSrgb(color), false);
+
+                    var text = _artifactSystem.GetNodeId(node);
+                    var dimensions = handle.GetDimensions(_font, text, 1);
+                    handle.DrawString(_font, pos - new Vector2(dimensions.X / 2, dimensions.Y / 2), text, color);
+                }
+            }
+
+            // draw edges for each segment and each node that have successors
+            foreach (var node in segment)
+            {
+                var fromNode = GetNodePos(node, ySpacing, segments, ref bottomLeft) + new Vector2(0, -NodeRadius);
+                var successorNodes = _artifactSystem.GetDirectSuccessorNodes((artifact, artifact), node);
+                foreach (var successorNode in successorNodes)
+                {
+                    var color = node.Comp.Locked
+                        ? LockedNodeColor
+                        : UnlockedNodeColor;
+
+                    var toNode = GetNodePos(successorNode, ySpacing, segments, ref bottomLeft) + new Vector2(0, NodeRadius);
+                    handle.DrawLine(fromNode, toNode, color);
+                }
+            }
+
+            bottomLeft.X += GetBiggestWidth(segment) + segmentSpacing;
+        }
+    }
+
+    private Vector2 GetNodePos(Entity<XenoArtifactNodeComponent> node, float ySpacing, List<List<Entity<XenoArtifactNodeComponent>>> segments, ref Vector2 bottomLeft)
+    {
+        var yPos = -(NodeDiameter + ySpacing) * node.Comp.Depth;
+
+        var segment = segments.First(s => s.Contains(node));
+        var depthOrderedNodes = _artifactSystem.GetDepthOrderedNodes(segment);
+        var biggestTier = depthOrderedNodes.Max(s => s.Value.Count);
+        var nodesInLayer = depthOrderedNodes.GetValueOrDefault(node.Comp.Depth)!.Count;
+        var biggestWidth = (NodeDiameter + MinXSpacing) * biggestTier;
+
+        var xSpacing = Math.Clamp((biggestWidth - (NodeDiameter * nodesInLayer)) / (nodesInLayer - 1), MinXSpacing, MaxXSpacing);
+        var layerXOffset = (biggestWidth - (xSpacing * (nodesInLayer - 1)) - (NodeDiameter * nodesInLayer)) / 2;
+
+        // get index of node in current segment's row (row per depth level)
+        var index = depthOrderedNodes.GetValueOrDefault(node.Comp.Depth)!.IndexOf(node);
+
+        var xPos = NodeDiameter * index + (xSpacing * index) + layerXOffset;
+
+        return bottomLeft + new Vector2(xPos, yPos);
+    }
+
+    private float GetBiggestWidth(List<Entity<XenoArtifactNodeComponent>> nodes)
+    {
+        var num = _artifactSystem.GetDepthOrderedNodes(nodes)
+                                 .Max(p => p.Value.Count);
+        return (NodeDiameter * num) + MinXSpacing * (num - 1);
+    }
+}
+
index 3be9b027484b3521aeb2eeaae9de9cee205ba312..b02c44f920c743d7ba838a48cea3e85503356c36 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
+using Content.Shared.Xenoarchaeology.XenoArtifacts;
 using Robust.Client.GameObjects;
 
 namespace Content.Client.Xenoarchaeology.XenoArtifacts;
@@ -13,19 +13,28 @@ public sealed class RandomArtifactSpriteSystem : VisualizerSystem<RandomArtifact
         if (!AppearanceSystem.TryGetData<int>(uid, SharedArtifactsVisuals.SpriteIndex, out var spriteIndex, args.Component))
             return;
 
+        if (!AppearanceSystem.TryGetData<bool>(uid, SharedArtifactsVisuals.IsUnlocking, out var isUnlocking, args.Component))
+            isUnlocking = false;
+
         if (!AppearanceSystem.TryGetData<bool>(uid, SharedArtifactsVisuals.IsActivated, out var isActivated, args.Component))
             isActivated = false;
 
         var spriteIndexStr = spriteIndex.ToString("D2");
-        var spritePrefix = isActivated ? "_on" : "";
+        var spritePrefix = isUnlocking ? "_on" : "";
 
         // layered artifact sprite
-        if (args.Sprite.LayerMapTryGet(ArtifactsVisualLayers.Effect, out var layer))
+        if (args.Sprite.LayerMapTryGet(ArtifactsVisualLayers.UnlockingEffect, out var layer))
         {
             var spriteState = "ano" + spriteIndexStr;
             args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
             args.Sprite.LayerSetState(layer, spriteState + "_on");
-            args.Sprite.LayerSetVisible(layer, isActivated);
+            args.Sprite.LayerSetVisible(layer, isUnlocking);
+
+            if (args.Sprite.LayerMapTryGet(ArtifactsVisualLayers.ActivationEffect, out var activationEffectLayer))
+            {
+                args.Sprite.LayerSetState(activationEffectLayer, "artifact-activation");
+                args.Sprite.LayerSetVisible(activationEffectLayer, isActivated);
+            }
         }
         // non-layered
         else
@@ -33,12 +42,12 @@ public sealed class RandomArtifactSpriteSystem : VisualizerSystem<RandomArtifact
             var spriteState = "ano" + spriteIndexStr + spritePrefix;
             args.Sprite.LayerSetState(ArtifactsVisualLayers.Base, spriteState);
         }
-
     }
 }
 
 public enum ArtifactsVisualLayers : byte
 {
     Base,
-    Effect // doesn't have to use this
+    UnlockingEffect, // doesn't have to use this
+    ActivationEffect
 }
diff --git a/Content.IntegrationTests/Tests/XenoArtifactTest.cs b/Content.IntegrationTests/Tests/XenoArtifactTest.cs
new file mode 100644 (file)
index 0000000..ac4c58c
--- /dev/null
@@ -0,0 +1,419 @@
+using System.Linq;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests;
+
+[TestFixture]
+public sealed class XenoArtifactTest
+{
+    [TestPrototypes]
+    private const string Prototypes = @"
+- type: entity
+  id: TestArtifact
+  parent: BaseXenoArtifact
+  name: artifact
+  components:
+  - type: XenoArtifact
+    isGenerationRequired: false
+    effectsTable: !type:NestedSelector
+      tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+  id: TestGenArtifactFlat
+  parent: BaseXenoArtifact
+  name: artifact
+  components:
+  - type: XenoArtifact
+    isGenerationRequired: true
+    nodeCount:
+      min: 2
+      max: 2
+    segmentSize:
+      min: 1
+      max: 1
+    nodesPerSegmentLayer:
+      min: 1
+      max: 1
+    effectsTable: !type:NestedSelector
+      tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+  id: TestGenArtifactTall
+  parent: BaseXenoArtifact
+  name: artifact
+  components:
+  - type: XenoArtifact
+    isGenerationRequired: true
+    nodeCount:
+      min: 2
+      max: 2
+    segmentSize:
+      min: 2
+      max: 2
+    nodesPerSegmentLayer:
+      min: 1
+      max: 1
+    effectsTable: !type:NestedSelector
+      tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+  id: TestGenArtifactFull
+  name: artifact
+  components:
+  - type: XenoArtifact
+    isGenerationRequired: true
+    nodeCount:
+      min: 6
+      max: 6
+    segmentSize:
+      min: 6
+      max: 6
+    nodesPerSegmentLayer:
+      min: 2
+      max: 2
+    effectsTable: !type:NestedSelector
+      tableId: XenoArtifactEffectsDefaultTable
+
+- type: entity
+  id: TestArtifactNode
+  name: artifact node
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 3
+";
+
+    /// <summary>
+    /// Checks that adding nodes and edges properly adds them into the adjacency matrix
+    /// </summary>
+    [Test]
+    public async Task XenoArtifactAddNodeTest()
+    {
+        await using var pair = await PoolManager.GetServerClient();
+        var server = pair.Server;
+
+        var entManager = server.ResolveDependency<IEntityManager>();
+        var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+        await server.WaitPost(() =>
+        {
+            var artifactUid = entManager.Spawn("TestArtifact");
+            var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+            // Create 3 nodes
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+
+            Assert.That(artifactSystem.GetAllNodeIndices(artifactEnt).Count(), Is.EqualTo(3));
+
+            // Add connection from 1 -> 2 and 2-> 3
+            artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+
+            // Assert that successors and direct successors are counted correctly for node 1.
+            Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node1!.Value).Count, Is.EqualTo(1));
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1!.Value).Count, Is.EqualTo(2));
+            // Assert that we didn't somehow get predecessors on node 1.
+            Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node1!.Value), Is.Empty);
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node1!.Value), Is.Empty);
+
+            // Assert that successors and direct successors are counted correctly for node 2.
+            Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+            // Assert that predecessors and direct predecessors are counted correctly for node 2.
+            Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
+
+            // Assert that successors and direct successors are counted correctly for node 3.
+            Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node3!.Value), Is.Empty);
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node3!.Value), Is.Empty);
+            // Assert that predecessors and direct predecessors are counted correctly for node 3.
+            Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(1));
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(2));
+        });
+        await server.WaitRunTicks(1);
+
+        await pair.CleanReturnAsync();
+    }
+
+    /// <summary>
+    /// Checks to make sure that removing nodes properly cleans up all connections.
+    /// </summary>
+    [Test]
+    public async Task XenoArtifactRemoveNodeTest()
+    {
+        await using var pair = await PoolManager.GetServerClient();
+        var server = pair.Server;
+
+        var entManager = server.ResolveDependency<IEntityManager>();
+        var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+        await server.WaitPost(() =>
+        {
+            var artifactUid = entManager.Spawn("TestArtifact");
+            var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+            // Create 3 nodes
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node5, false));
+
+            Assert.That(artifactSystem.GetAllNodeIndices(artifactEnt).Count(), Is.EqualTo(5));
+
+            // Add connection: 1 -> 2 -> 3 -> 4 -> 5
+            artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node3!.Value, node4!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node4!.Value, node5!.Value, false);
+
+            // Make sure we have a continuous connection between the two ends of the graph.
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Has.Count.EqualTo(4));
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node5.Value), Has.Count.EqualTo(4));
+
+            // Remove the node and make sure it's no longer in the artifact.
+            Assert.That(artifactSystem.RemoveNode(artifactEnt, node3!.Value, false));
+            Assert.That(artifactSystem.TryGetIndex(artifactEnt, node3!.Value, out _), Is.False, "Node 3 still present in artifact.");
+
+            // Check to make sure that we got rid of all the connections.
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node2!.Value), Is.Empty);
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
+        });
+        await server.WaitRunTicks(1);
+
+        await pair.CleanReturnAsync();
+    }
+
+    /// <summary>
+    /// Sets up series of linked nodes and ensures that resizing the adjacency matrix doesn't disturb the connections
+    /// </summary>
+    [Test]
+    public async Task XenoArtifactResizeTest()
+    {
+        await using var pair = await PoolManager.GetServerClient();
+        var server = pair.Server;
+
+        var entManager = server.ResolveDependency<IEntityManager>();
+        var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+        await server.WaitPost(() =>
+        {
+            var artifactUid = entManager.Spawn("TestArtifact");
+            var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+            // Create 3 nodes
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+
+            // Add connection: 1 -> 2 -> 3
+            artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+
+            // Make sure our connection is set up
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node1.Value), Is.False);
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node2.Value), Is.False);
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node3.Value), Is.False);
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node1.Value), Is.False);
+
+            Assert.That(artifactSystem.GetIndex(artifactEnt, node1!.Value), Is.EqualTo(0));
+            Assert.That(artifactSystem.GetIndex(artifactEnt, node2!.Value), Is.EqualTo(1));
+            Assert.That(artifactSystem.GetIndex(artifactEnt, node3!.Value), Is.EqualTo(2));
+
+            // Add a new node, resizing the original adjacency matrix and array.
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4));
+
+            // Check that our connections haven't changed.
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node1.Value), Is.False);
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node2.Value), Is.False);
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node3.Value), Is.False);
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node1.Value), Is.False);
+
+            // Has our array shifted any when we resized?
+            Assert.That(artifactSystem.GetIndex(artifactEnt, node1!.Value), Is.EqualTo(0));
+            Assert.That(artifactSystem.GetIndex(artifactEnt, node2!.Value), Is.EqualTo(1));
+            Assert.That(artifactSystem.GetIndex(artifactEnt, node3!.Value), Is.EqualTo(2));
+
+            // Check that 4 didn't somehow end up with connections
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node4!.Value), Is.Empty);
+        });
+        await server.WaitRunTicks(1);
+
+        await pair.CleanReturnAsync();
+    }
+
+    /// <summary>
+    /// Checks if removing a node and adding a new node into its place in the adjacency matrix doesn't accidentally retain extra data.
+    /// </summary>
+    [Test]
+    public async Task XenoArtifactReplaceTest()
+    {
+        await using var pair = await PoolManager.GetServerClient();
+        var server = pair.Server;
+
+        var entManager = server.ResolveDependency<IEntityManager>();
+        var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+        await server.WaitPost(() =>
+        {
+            var artifactUid = entManager.Spawn("TestArtifact");
+            var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+            // Create 3 nodes
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+
+            // Add connection: 1 -> 2 -> 3
+            artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
+
+            // Make sure our connection is set up
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
+            Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
+
+            // Remove middle node, severing connections
+            artifactSystem.RemoveNode(artifactEnt, node2!.Value, false);
+
+            // Make sure our connection are properly severed.
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Is.Empty);
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3.Value), Is.Empty);
+
+            // Make sure our matrix is 3x3
+            Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixRows, Is.EqualTo(3));
+            Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixColumns, Is.EqualTo(3));
+
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
+
+            // Make sure that adding in a new node didn't add a new slot but instead re-used the middle slot.
+            Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixRows, Is.EqualTo(3));
+            Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixColumns, Is.EqualTo(3));
+
+            // Ensure that all connections are still severed
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Is.Empty);
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3.Value), Is.Empty);
+            Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node4!.Value), Is.Empty);
+            Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
+
+        });
+        await server.WaitRunTicks(1);
+
+        await pair.CleanReturnAsync();
+    }
+
+    /// <summary>
+    /// Checks if the active nodes are properly detected.
+    /// </summary>
+    [Test]
+    public async Task XenoArtifactBuildActiveNodesTest()
+    {
+        await using var pair = await PoolManager.GetServerClient();
+        var server = pair.Server;
+
+        var entManager = server.ResolveDependency<IEntityManager>();
+        var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+        await server.WaitPost(() =>
+        {
+            var artifactUid = entManager.Spawn("TestArtifact");
+            Entity<XenoArtifactComponent> artifactEnt = (artifactUid, entManager.GetComponent<XenoArtifactComponent>(artifactUid));
+
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node5, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node6, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node7, false));
+            Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node8, false));
+
+            //                       /----( 6 )
+            //           /----[*3 ]-/----( 7 )----( 8 )
+            //          /
+            //         /           /----[*5 ]
+            // [ 1 ]--/----[ 2 ]--/----( 4 )
+            // Diagram of the example generation. Nodes in [brackets] are unlocked, nodes in (braces) are locked
+            // and nodes with an *asterisk are supposed to be active.
+            artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node1!.Value, node3!.Value, false);
+
+            artifactSystem.AddEdge(artifactEnt, node2!.Value, node4!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node2!.Value, node5!.Value, false);
+
+            artifactSystem.AddEdge(artifactEnt, node3!.Value, node6!.Value, false);
+            artifactSystem.AddEdge(artifactEnt, node3!.Value, node7!.Value, false);
+
+            artifactSystem.AddEdge(artifactEnt, node7!.Value, node8!.Value, false);
+
+            artifactSystem.SetNodeUnlocked(node1!.Value);
+            artifactSystem.SetNodeUnlocked(node2!.Value);
+            artifactSystem.SetNodeUnlocked(node3!.Value);
+            artifactSystem.SetNodeUnlocked(node5!.Value);
+
+            NetEntity[] expectedActiveNodes =
+            [
+                entManager.GetNetEntity(node3!.Value.Owner),
+                entManager.GetNetEntity(node5!.Value.Owner)
+            ];
+            Assert.That(artifactEnt.Comp.CachedActiveNodes, Is.SupersetOf(expectedActiveNodes));
+            Assert.That(artifactEnt.Comp.CachedActiveNodes, Has.Count.EqualTo(expectedActiveNodes.Length));
+
+        });
+        await server.WaitRunTicks(1);
+
+        await pair.CleanReturnAsync();
+    }
+
+    [Test]
+    public async Task XenoArtifactGenerateSegmentsTest()
+    {
+        await using var pair = await PoolManager.GetServerClient();
+        var server = pair.Server;
+
+        var entManager = server.ResolveDependency<IEntityManager>();
+        var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
+
+        await server.WaitPost(() =>
+        {
+            var artifact1Uid = entManager.Spawn("TestGenArtifactFlat");
+            Entity<XenoArtifactComponent> artifact1Ent = (artifact1Uid, entManager.GetComponent<XenoArtifactComponent>(artifact1Uid));
+
+            var segments1 = artifactSystem.GetSegments(artifact1Ent);
+            Assert.That(segments1.Count, Is.EqualTo(2));
+            Assert.That(segments1[0].Count, Is.EqualTo(1));
+            Assert.That(segments1[1].Count, Is.EqualTo(1));
+
+            var artifact2Uid = entManager.Spawn("TestGenArtifactTall");
+            Entity<XenoArtifactComponent> artifact2Ent = (artifact2Uid, entManager.GetComponent<XenoArtifactComponent>(artifact2Uid));
+
+            var segments2 = artifactSystem.GetSegments(artifact2Ent);
+            Assert.That(segments2.Count, Is.EqualTo(1));
+            Assert.That(segments2[0].Count, Is.EqualTo(2));
+
+            var artifact3Uid = entManager.Spawn("TestGenArtifactFull");
+            Entity<XenoArtifactComponent> artifact3Ent = (artifact3Uid, entManager.GetComponent<XenoArtifactComponent>(artifact3Uid));
+
+            var segments3 = artifactSystem.GetSegments(artifact3Ent);
+            Assert.That(segments3.Count, Is.EqualTo(1));
+            Assert.That(segments3.Sum(x => x.Count), Is.EqualTo(6));
+            var nodesDepths = segments3[0].Select(x => x.Comp.Depth).ToArray();
+            Assert.That(nodesDepths.Distinct().Count(), Is.EqualTo(3));
+            var grouped = nodesDepths.ToLookup(x => x);
+            Assert.That(grouped[0].Count(), Is.EqualTo(2));
+            Assert.That(grouped[1].Count(), Is.GreaterThanOrEqualTo(2)); // tree is attempting sometimes to get wider (so it will look like a tree)
+            Assert.That(grouped[2].Count(), Is.LessThanOrEqualTo(2)); // maintain same width or, if we used 3 nodes on previous layer - we only have 1 left!
+
+        });
+        await server.WaitRunTicks(1);
+
+        await pair.CleanReturnAsync();
+    }
+}
index c8f19aadbe09100781ce1df4924e6d293860bad4..c953277aad609f31e6bf4754508aa853773e113c 100644 (file)
@@ -10,8 +10,6 @@ using Content.Server.Mind;
 using Content.Server.Mind.Commands;
 using Content.Server.Prayer;
 using Content.Server.Station.Systems;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
 using Content.Shared.Administration;
 using Content.Shared.Chemistry.Components.SolutionManager;
 using Content.Shared.Chemistry.EntitySystems;
@@ -60,7 +58,6 @@ namespace Content.Server.Administration.Systems
         [Dependency] private readonly EuiManager _euiManager = default!;
         [Dependency] private readonly GameTicker _ticker = default!;
         [Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
-        [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
         [Dependency] private readonly PrayerSystem _prayerSystem = default!;
         [Dependency] private readonly MindSystem _mindSystem = default!;
@@ -454,29 +451,6 @@ namespace Content.Server.Administration.Systems
                 args.Verbs.Add(verb);
             }
 
-            // XenoArcheology
-            if (_adminManager.IsAdmin(player) && TryComp<ArtifactComponent>(args.Target, out var artifact))
-            {
-                // make artifact always active (by adding timer trigger)
-                args.Verbs.Add(new Verb()
-                {
-                    Text = Loc.GetString("artifact-verb-make-always-active"),
-                    Category = VerbCategory.Debug,
-                    Act = () => EntityManager.AddComponent<ArtifactTimerTriggerComponent>(args.Target),
-                    Disabled = EntityManager.HasComponent<ArtifactTimerTriggerComponent>(args.Target),
-                    Impact = LogImpact.High
-                });
-
-                // force to activate artifact ignoring timeout
-                args.Verbs.Add(new Verb()
-                {
-                    Text = Loc.GetString("artifact-verb-activate"),
-                    Category = VerbCategory.Debug,
-                    Act = () => _artifactSystem.ForceActivateArtifact(args.Target, component: artifact),
-                    Impact = LogImpact.High
-                });
-            }
-
             // Make Sentient verb
             if (_groupController.CanCommand(player, "makesentient") &&
                 args.User != args.Target &&
index d8eab0c5d1f1a720ad4a7b24a5c368dcc1d38b2f..4b5143aa40027605d5b09e4e1a3f9fa99bdfc714 100644 (file)
@@ -44,6 +44,22 @@ public sealed class EmpSystem : SharedEmpSystem
         Spawn(EmpPulseEffectPrototype, coordinates);
     }
 
+    /// <summary>
+    ///   Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
+    /// </summary>
+    /// <param name="coordinates">The location to trigger the EMP pulse at.</param>
+    /// <param name="range">The range of the EMP pulse.</param>
+    /// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
+    /// <param name="duration">The duration of the EMP effects.</param>
+    public void EmpPulse(EntityCoordinates coordinates, float range, float energyConsumption, float duration)
+    {
+        foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
+        {
+            TryEmpEffects(uid, energyConsumption, duration);
+        }
+        Spawn(EmpPulseEffectPrototype, coordinates);
+    }
+
     /// <summary>
     ///    Attempts to apply the effects of an EMP pulse onto an entity by first raising an <see cref="EmpAttemptEvent"/>, followed by raising a <see cref="EmpPulseEvent"/> on it.
     /// </summary>
diff --git a/Content.Server/EntityEffects/Effects/ActivateArtifact.cs b/Content.Server/EntityEffects/Effects/ActivateArtifact.cs
deleted file mode 100644 (file)
index 8540478..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-using Content.Shared.EntityEffects;
-
-namespace Content.Server.EntityEffects.Effects;
-
-public sealed partial class ActivateArtifact : EntityEffect
-{
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var artifact = args.EntityManager.EntitySysManager.GetEntitySystem<ArtifactSystem>();
-        artifact.TryActivateArtifact(args.TargetEntity, logMissing: false);
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
-        Loc.GetString("reagent-effect-guidebook-activate-artifact", ("chance", Probability));
-}
index db9dbb375bcaf87cdb1b77e46f689ef504738cc6..ab1fd2ce04698211c0c59b85d3902dffbedeafeb 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.UserInterface;
 using Content.Shared.Instruments;
 using Robust.Shared.Player;
 using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent;
@@ -21,8 +20,3 @@ public sealed partial class InstrumentComponent : SharedInstrumentComponent
         _entMan.GetComponentOrNull<ActivatableUIComponent>(Owner)?.CurrentSingleUser
         ?? _entMan.GetComponentOrNull<ActorComponent>(Owner)?.PlayerSession.AttachedEntity;
 }
-
-[RegisterComponent]
-public sealed partial class ActiveInstrumentComponent : Component
-{
-}
index b0f4f2476d8a76c936d3eb5236be0c4731d7cccb..fd581f40e43cb2dc12c3713ab36a1dac6405e6f7 100644 (file)
@@ -8,6 +8,7 @@ using Content.Shared.PAI;
 using Content.Shared.Popups;
 using Robust.Shared.Random;
 using System.Text;
+using Content.Shared.Instruments;
 using Robust.Shared.Player;
 
 namespace Content.Server.PAI;
index 3ba393959c2568f832ea1f127621adda0bbd4201..7402a72701015e41ec92311ef5cde021d79f60e1 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Server.Radiation.Components;
+using Content.Server.Radiation.Components;
 using Content.Shared.Radiation.Components;
 using Content.Shared.Radiation.Events;
 using Content.Shared.Stacks;
@@ -50,7 +50,7 @@ public sealed partial class RadiationSystem : EntitySystem
 
     public void IrradiateEntity(EntityUid uid, float radsPerSecond, float time)
     {
-        var msg = new OnIrradiatedEvent(time, radsPerSecond);
+        var msg = new OnIrradiatedEvent(time, radsPerSecond, uid);
         RaiseLocalEvent(uid, msg);
     }
 
index f14958631d1c964bb35a811a4cf61d64142dd25c..3b6eeebe8515e39a098e2d0fa04521c778cc7929 100644 (file)
@@ -8,6 +8,7 @@ using Content.Shared.Station.Components;
 using JetBrains.Annotations;
 using Robust.Shared.Random;
 using System.Linq;
+using Content.Shared.Chemistry.Reaction;
 
 namespace Content.Server.StationEvents.Events;
 
@@ -47,7 +48,7 @@ public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
             var quantity = weak ? component.WeakReagentQuantity : component.ReagentQuantity;
             solution.AddReagent(reagent, quantity);
 
-            var foamEnt = Spawn("Foam", transform.Coordinates);
+            var foamEnt = Spawn(ChemicalReactionSystem.FoamReaction, transform.Coordinates);
             var spreadAmount = weak ? component.WeakSpread : component.Spread;
             _smoke.StartSmoke(foamEnt, solution, component.Time, spreadAmount);
             Audio.PlayPvs(component.Sound, transform.Coordinates);
similarity index 64%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs
rename to Content.Server/Xenoarchaeology/Artifact/RandomArtifactSpriteSystem.cs
index 091441df21a79e07f8d07eb4c2ee44c5d1bc3cd2..645dac24e207458e7aa2f6355a77d66489d5dcd9 100644 (file)
@@ -1,11 +1,11 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
 using Content.Shared.Item;
+using Content.Shared.Xenoarchaeology.Artifact;
 using Content.Shared.Xenoarchaeology.XenoArtifacts;
 using Robust.Server.GameObjects;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
+namespace Content.Server.Xenoarchaeology.Artifact;
 
 public sealed class RandomArtifactSpriteSystem : EntitySystem
 {
@@ -17,8 +17,11 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem
     public override void Initialize()
     {
         base.Initialize();
+
         SubscribeLocalEvent<RandomArtifactSpriteComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<RandomArtifactSpriteComponent, ArtifactActivatedEvent>(OnActivated);
+        SubscribeLocalEvent<RandomArtifactSpriteComponent, ArtifactUnlockingStartedEvent>(UnlockingStageStarted);
+        SubscribeLocalEvent<RandomArtifactSpriteComponent, ArtifactUnlockingFinishedEvent>(UnlockingStageFinished);
+        SubscribeLocalEvent<RandomArtifactSpriteComponent, XenoArtifactActivatedEvent>(ArtifactActivated);
     }
 
     public override void Update(float frameTime)
@@ -47,9 +50,19 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem
         _item.SetHeldPrefix(uid, "ano" + randomSprite.ToString("D2")); //set item artifact inhands
     }
 
-    private void OnActivated(EntityUid uid, RandomArtifactSpriteComponent component, ArtifactActivatedEvent args)
+    private void UnlockingStageStarted(Entity<RandomArtifactSpriteComponent> ent, ref ArtifactUnlockingStartedEvent args)
+    {
+        _appearance.SetData(ent, SharedArtifactsVisuals.IsUnlocking, true);
+    }
+
+    private void UnlockingStageFinished(Entity<RandomArtifactSpriteComponent> ent, ref ArtifactUnlockingFinishedEvent args)
+    {
+        _appearance.SetData(ent, SharedArtifactsVisuals.IsUnlocking, false);
+    }
+
+    private void ArtifactActivated(Entity<RandomArtifactSpriteComponent> ent, ref XenoArtifactActivatedEvent args)
     {
-        _appearance.SetData(uid, SharedArtifactsVisuals.IsActivated, true);
-        component.ActivationStart = _time.CurTime;
+        _appearance.SetData(ent, SharedArtifactsVisuals.IsActivated, true);
+        ent.Comp.ActivationStart = _time.CurTime;
     }
 }
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEChargeBatteryComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEChargeBatteryComponent.cs
new file mode 100644 (file)
index 0000000..d63a52f
--- /dev/null
@@ -0,0 +1,14 @@
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// This is used for recharging all nearby batteries when activated.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEChargeBatterySystem))]
+public sealed partial class XAEChargeBatteryComponent : Component
+{
+    /// <summary>
+    /// The radius of entities that will be affected.
+    /// </summary>
+    [DataField("radius")]
+    public float Radius = 15f;
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAECreateGasComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAECreateGasComponent.cs
new file mode 100644 (file)
index 0000000..ba0893d
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Shared.Atmos;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// XenoArtifact effect that creates gas in atmosphere.
+/// </summary>
+[RegisterComponent, Access(typeof(XAECreateGasSystem))]
+public sealed partial class XAECreateGasComponent : Component
+{
+    /// <summary>
+    /// The gases and how many moles will be created of each.
+    /// </summary>
+    [DataField]
+    public Dictionary<Gas, float> Gases = new();
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAECreatePuddleComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAECreatePuddleComponent.cs
new file mode 100644 (file)
index 0000000..f77fc96
--- /dev/null
@@ -0,0 +1,46 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Destructible.Thresholds;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// This is used for an artifact that creates a puddle of
+/// random chemicals upon being triggered.
+/// </summary>
+[RegisterComponent, Access(typeof(XAECreatePuddleSystem))]
+public sealed partial class XAECreatePuddleComponent : Component
+{
+    /// <summary>
+    /// The solution where all the chemicals are stored.
+    /// </summary>
+    [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+    public Solution ChemicalSolution = default!;
+
+    /// <summary>
+    /// The different chemicals that can be spawned by this effect.
+    /// </summary>
+    [DataField]
+    public List<ProtoId<ReagentPrototype>> PossibleChemicals = new();
+
+    /// <summary>
+    /// The number of chemicals in the puddle.
+    /// </summary>
+    [DataField]
+    public MinMax ChemAmount = new MinMax(1, 3);
+
+    /// <summary>
+    /// List of reagents selected for this node. Selected ones are chosen on first activation
+    /// and are picked from <see cref="PossibleChemicals"/> and is calculated separately for each node.
+    /// </summary>
+    [DataField]
+    public List<ProtoId<ReagentPrototype>>? SelectedChemicals;
+
+    /// <summary>
+    /// Marker, if entity where this component is placed should have description replaced with selected chemicals
+    /// on component init.
+    /// </summary>
+    [DataField]
+    public bool ReplaceDescription;
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEEmpInAreaComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEEmpInAreaComponent.cs
new file mode 100644 (file)
index 0000000..1e9489e
--- /dev/null
@@ -0,0 +1,26 @@
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Effect of EMP on activation.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEEmpInAreaSystem))]
+public sealed partial class XAEEmpInAreaComponent : Component
+{
+    /// <summary>
+    /// Range of EMP effect.
+    /// </summary>
+    [DataField]
+    public float Range = 4f;
+
+    /// <summary>
+    /// Energy to be consumed from energy containers.
+    /// </summary>
+    [DataField]
+    public float EnergyConsumption = 1000000;
+
+    /// <summary>
+    /// Duration (in seconds) for which devices going to be disabled.
+    /// </summary>
+    [DataField]
+    public float DisableDuration = 60f;
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEFoamComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEFoamComponent.cs
new file mode 100644 (file)
index 0000000..d216275
--- /dev/null
@@ -0,0 +1,56 @@
+using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Generates foam from the artifact when activated.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEFoamSystem))]
+public sealed partial class XAEFoamComponent : Component
+{
+    /// <summary>
+    /// The list of reagents that will randomly be picked from
+    /// to choose the foam reagent.
+    /// </summary>
+    [DataField(required: true)]
+    public List<ProtoId<ReagentPrototype>> Reagents = new();
+
+    /// <summary>
+    /// The foam reagent.
+    /// </summary>
+    [DataField]
+    public string? SelectedReagent;
+
+    /// <summary>
+    /// How long does the foam last?
+    /// </summary>
+    [DataField]
+    public float Duration = 10f;
+
+    /// <summary>
+    /// How much reagent is in the foam?
+    /// </summary>
+    [DataField]
+    public float ReagentAmount = 100f;
+
+    /// <summary>
+    /// Minimum radius of foam spawned.
+    /// </summary>
+    [DataField]
+    public int MinFoamAmount = 15;
+
+    /// <summary>
+    /// Maximum radius of foam spawned.
+    /// </summary>
+    [DataField]
+    public int MaxFoamAmount = 20;
+
+    /// <summary>
+    /// Marker, if entity where this component is placed should have description replaced with selected chemicals
+    /// on component init.
+    /// </summary>
+    [DataField]
+    public bool ReplaceDescription;
+}
+
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEIgniteComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEIgniteComponent.cs
new file mode 100644 (file)
index 0000000..a8d591e
--- /dev/null
@@ -0,0 +1,22 @@
+using Content.Shared.Destructible.Thresholds;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Artifact that ignites surrounding entities when triggered.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEIgniteSystem))]
+public sealed partial class XAEIgniteComponent : Component
+{
+    /// <summary>
+    /// Range, inside which all entities going be set on fire.
+    /// </summary>
+    [DataField]
+    public float Range = 2f;
+
+    /// <summary>
+    /// Amount of fire stacks to apply
+    /// </summary>
+    [DataField]
+    public MinMax FireStack = new(2, 5);
+}
similarity index 51%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs
rename to Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAELightFlickerComponent.cs
index 24578d722d5e4a8df16ada7f9c7ee46fab5f3df0..b7fe332dcfe00a8bd80cf3405bdda6416fbb6b3f 100644 (file)
@@ -1,20 +1,20 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
 /// Flickers all the lights within a certain radius.
 /// </summary>
-[RegisterComponent]
-public sealed partial class LightFlickerArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAELightFlickerSystem))]
+public sealed partial class XAELightFlickerComponent : Component
 {
     /// <summary>
-    /// Lights within this radius will be flickered on activation
+    /// Lights within this radius will be flickered on activation.
     /// </summary>
-    [DataField("radius")]
+    [DataField]
     public float Radius = 4;
 
     /// <summary>
-    /// The chance that the light will flicker
+    /// The chance that the light will flicker.
     /// </summary>
-    [DataField("flickerChance")]
+    [DataField]
     public float FlickerChance = 0.75f;
 }
similarity index 62%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PolyOthersArtifactComponent.cs
rename to Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEPolymorphComponent.cs
index 2611d78b088ad618f218bd6fb9fb223b6bdce07b..02aaa3f4ad2a0bb45a2a4bff3949e7644d4551ee 100644 (file)
@@ -1,16 +1,14 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Robust.Shared.Audio;
 using Content.Shared.Polymorph;
+using Robust.Shared.Audio;
 using Robust.Shared.Prototypes;
 
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
-/// Artifact polymorphs surrounding entities when triggered.
+/// Artifact polymorphs entities when triggered.
 /// </summary>
-[RegisterComponent]
-[Access(typeof(PolyOthersArtifactSystem))]
-public sealed partial class PolyOthersArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAEPolymorphSystem))]
+public sealed partial class XAEPolymorphComponent : Component
 {
     /// <summary>
     /// The polymorph effect to trigger.
@@ -19,7 +17,7 @@ public sealed partial class PolyOthersArtifactComponent : Component
     public ProtoId<PolymorphPrototype> PolymorphPrototypeName = "ArtifactMonkey";
 
     /// <summary>
-    /// range of the effect.
+    /// Range of the effect.
     /// </summary>
     [DataField]
     public float Range = 2f;
similarity index 85%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs
rename to Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETelepathicComponent.cs
index a7073a743a52a94c31eecaf2b1d3be93ff62c9ca..2354d2f16bd2aae739865efda7293a6cd4d12262 100644 (file)
@@ -1,11 +1,11 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
 ///     Harmless artifact that broadcast "thoughts" to players nearby.
 ///     Thoughts are shown as popups and unique for each player.
 /// </summary>
-[RegisterComponent]
-public sealed partial class TelepathicArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAETelepathicSystem))]
+public sealed partial class XAETelepathicComponent : Component
 {
     /// <summary>
     ///     Loc string ids of telepathic messages.
similarity index 75%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs
rename to Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETemperatureComponent.cs
index 0988617cdc7480b7cc68be13799a315471c04213..00d3775a8dff12883582869c1b793851120003d3 100644 (file)
@@ -1,12 +1,12 @@
 using Content.Shared.Atmos;
 
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
 ///     Change atmospherics temperature until it reach target.
 /// </summary>
-[RegisterComponent]
-public sealed partial class TemperatureArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAETemperatureSystem))]
+public sealed partial class XAETemperatureComponent : Component
 {
     [DataField("targetTemp"), ViewVariables(VVAccess.ReadWrite)]
     public float TargetTemperature = Atmospherics.T0C;
similarity index 75%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs
rename to Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAEThrowThingsAroundComponent.cs
index 0c2739220820bc7eae143bbefcdfe50c9a9b9436..674bfd32519d019d40d39bf2a4b0357a066821c1 100644 (file)
@@ -1,11 +1,11 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
 /// Throws all nearby entities backwards.
 /// Also pries nearby tiles.
 /// </summary>
-[RegisterComponent]
-public sealed partial class ThrowArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAEThrowThingsAroundSystem))]
+public sealed partial class XAEThrowThingsAroundComponent : Component
 {
     /// <summary>
     /// How close do you have to be to get yeeted?
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs
new file mode 100644 (file)
index 0000000..b9e23f0
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.Explosion.Components.OnTrigger;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Activates 'trigger' for <see cref="ExplodeOnTriggerComponent"/>.
+/// </summary>
+[RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))]
+public sealed partial class XAETriggerExplosivesComponent : Component;
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs
new file mode 100644 (file)
index 0000000..5e9bf73
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Server.Power.Components;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that is fully charging batteries in certain range.
+/// </summary>
+public sealed class XAEChargeBatterySystem : BaseXAESystem<XAEChargeBatteryComponent>
+{
+    [Dependency] private readonly BatterySystem _battery = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<Entity<BatteryComponent>> _batteryEntities = new();
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEChargeBatteryComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        var chargeBatteryComponent = ent.Comp;
+        _batteryEntities.Clear();
+        _lookup.GetEntitiesInRange(args.Coordinates, chargeBatteryComponent.Radius, _batteryEntities);
+        foreach (var battery in _batteryEntities)
+        {
+            _battery.SetCharge(battery, battery.Comp.MaxCharge, battery);
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAECreateGasSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAECreateGasSystem.cs
new file mode 100644 (file)
index 0000000..517e214
--- /dev/null
@@ -0,0 +1,52 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Atmos;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Server.GameObjects;
+using Robust.Shared.Collections;
+using Robust.Shared.Map.Components;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that creates certain atmospheric gas on artifact tile / adjacent tiles.
+/// </summary>
+public sealed class XAECreateGasSystem : BaseXAESystem<XAECreateGasComponent>
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+    [Dependency] private readonly TransformSystem _transform = default!;
+    [Dependency] private readonly MapSystem _map = default!;
+
+    protected override void OnActivated(Entity<XAECreateGasComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        var grid = _transform.GetGrid(args.Coordinates);
+        var map = _transform.GetMap(args.Coordinates);
+        if (map == null || !TryComp<MapGridComponent>(grid, out var gridComp))
+            return;
+
+        var tile = _map.LocalToTile(grid.Value, gridComp, args.Coordinates);
+
+        var mixtures = new ValueList<GasMixture>();
+        if (_atmosphere.GetTileMixture(grid.Value, map.Value, tile, excite: true) is { } localMixture)
+            mixtures.Add(localMixture);
+
+        if (_atmosphere.GetAdjacentTileMixtures(grid.Value, tile, excite: true) is var adjacentTileMixtures)
+        {
+            while (adjacentTileMixtures.MoveNext(out var adjacentMixture))
+            {
+                mixtures.Add(adjacentMixture);
+            }
+        }
+
+        foreach (var (gas, moles) in ent.Comp.Gases)
+        {
+            var molesPerMixture = moles / mixtures.Count;
+
+            foreach (var mixture in mixtures)
+            {
+                mixture.AdjustMoles(gas, molesPerMixture);
+            }
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAECreatePuddleSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAECreatePuddleSystem.cs
new file mode 100644 (file)
index 0000000..7f99626
--- /dev/null
@@ -0,0 +1,77 @@
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that creates puddle of chemical reagents under artifact.
+/// </summary>
+public sealed class XAECreatePuddleSystem: BaseXAESystem<XAECreatePuddleComponent>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly PuddleSystem _puddle = default!;
+    [Dependency] private readonly MetaDataSystem _metaData= default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager= default!;
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<XAECreatePuddleComponent, MapInitEvent>(OnInit);
+    }
+
+    private void OnInit(EntityUid uid, XAECreatePuddleComponent component, MapInitEvent _)
+    {
+        if (component.PossibleChemicals == null || component.PossibleChemicals.Count == 0)
+            return;
+
+        if (component.SelectedChemicals == null)
+        {
+            var chemicalList = new List<ProtoId<ReagentPrototype>>();
+            var chemAmount = component.ChemAmount.Next(_random);
+            for (var i = 0; i < chemAmount; i++)
+            {
+                var chemProto = _random.Pick(component.PossibleChemicals);
+                chemicalList.Add(chemProto);
+            }
+
+            component.SelectedChemicals = chemicalList;
+        }
+
+        if (component.ReplaceDescription)
+        {
+            var reagentNames = new HashSet<string>();
+            foreach (var chemProtoId in component.SelectedChemicals)
+            {
+                var reagent = _prototypeManager.Index(chemProtoId);
+                reagentNames.Add(reagent.LocalizedName);
+            }
+
+            var reagentNamesStr = string.Join(", ", reagentNames);
+            var newEntityDescription = Loc.GetString("xenoarch-effect-puddle", ("reagent", reagentNamesStr));
+            _metaData.SetEntityDescription(uid, newEntityDescription);
+        }
+    }
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAECreatePuddleComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        var component = ent.Comp;
+        if (component.SelectedChemicals == null)
+            return;
+
+        var amountPerChem = component.ChemicalSolution.MaxVolume / component.SelectedChemicals.Count;
+        foreach (var reagent in component.SelectedChemicals)
+        {
+            component.ChemicalSolution.AddReagent(reagent, amountPerChem);
+        }
+
+        _puddle.TrySpillAt(ent, component.ChemicalSolution, out _);
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEEmpInAreaSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEEmpInAreaSystem.cs
new file mode 100644 (file)
index 0000000..43a8ed9
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Server.Emp;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that creates EMP on use.
+/// </summary>
+public sealed class XAEEmpInAreaSystem : BaseXAESystem<XAEEmpInAreaComponent>
+{
+    [Dependency] private readonly EmpSystem _emp = default!;
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEEmpInAreaComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        _emp.EmpPulse(args.Coordinates, ent.Comp.Range, ent.Comp.EnergyConsumption, ent.Comp.DisableDuration);
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEFoamSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEFoamSystem.cs
new file mode 100644 (file)
index 0000000..d5190ce
--- /dev/null
@@ -0,0 +1,63 @@
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that starts Foam chemical reaction with random-ish reagents inside.
+/// </summary>
+public sealed class XAEFoamSystem : BaseXAESystem<XAEFoamComponent>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly SmokeSystem _smoke = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager= default!;
+    [Dependency] private readonly MetaDataSystem _metaData = default!;
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<XAEFoamComponent, MapInitEvent>(OnMapInit);
+    }
+
+    private void OnMapInit(EntityUid uid, XAEFoamComponent component, MapInitEvent args)
+    {
+        if (component.SelectedReagent != null)
+            return;
+
+        if (component.Reagents.Count == 0)
+            return;
+
+        component.SelectedReagent = _random.Pick(component.Reagents);
+
+        if (component.ReplaceDescription)
+        {
+            var reagent = _prototypeManager.Index<ReagentPrototype>(component.SelectedReagent);
+            var newEntityDescription = Loc.GetString("xenoarch-effect-foam", ("reagent", reagent.LocalizedName));
+            _metaData.SetEntityDescription(uid, newEntityDescription);
+        }
+    }
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEFoamComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        var component = ent.Comp;
+        if (component.SelectedReagent == null)
+            return;
+
+        var sol = new Solution();
+        var range = (int)MathF.Round(MathHelper.Lerp(component.MinFoamAmount, component.MaxFoamAmount, _random.NextFloat(0, 1f)));
+        sol.AddReagent(component.SelectedReagent, component.ReagentAmount);
+        var foamEnt = Spawn(ChemicalReactionSystem.FoamReaction, args.Coordinates);
+        var spreadAmount = range * 4;
+        _smoke.StartSmoke(foamEnt, sol, component.Duration, spreadAmount);
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEIgniteSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEIgniteSystem.cs
new file mode 100644 (file)
index 0000000..14270fb
--- /dev/null
@@ -0,0 +1,47 @@
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that ignites any flammable entity in range.
+/// </summary>
+public sealed class XAEIgniteSystem : BaseXAESystem<XAEIgniteComponent>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly FlammableSystem _flammable = default!;
+
+    private EntityQuery<FlammableComponent> _flammables;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<EntityUid> _entities = new();
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _flammables = GetEntityQuery<FlammableComponent>();
+    }
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEIgniteComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        var component = ent.Comp;
+        _entities.Clear();
+        _lookup.GetEntitiesInRange(ent.Owner, component.Range, _entities);
+        foreach (var target in _entities)
+        {
+            if (!_flammables.TryGetComponent(target, out var fl))
+                continue;
+
+            fl.FireStacks += component.FireStack.Next(_random);
+            _flammable.Ignite(target, ent.Owner, fl);
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAELightFlickerSystem.cs
new file mode 100644 (file)
index 0000000..4c4073b
--- /dev/null
@@ -0,0 +1,49 @@
+using Content.Server.Ghost;
+using Content.Server.Light.Components;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that flickers light on and off.
+/// </summary>
+public sealed class XAELightFlickerSystem : BaseXAESystem<XAELightFlickerComponent>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly GhostSystem _ghost = default!;
+
+    private EntityQuery<PoweredLightComponent> _lights;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<EntityUid> _entities = new();
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _lights = GetEntityQuery<PoweredLightComponent>();
+    }
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAELightFlickerComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        _entities.Clear();
+        _lookup.GetEntitiesInRange(ent.Owner, ent.Comp.Radius, _entities, LookupFlags.StaticSundries);
+        foreach (var light in _entities)
+        {
+            if (!_lights.HasComponent(light))
+                continue;
+
+            if (!_random.Prob(ent.Comp.FlickerChance))
+                continue;
+
+            //todo: extract effect from ghost system, update power system accordingly
+            _ghost.DoGhostBooEvent(light);
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEPolymorphSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEPolymorphSystem.cs
new file mode 100644 (file)
index 0000000..8418a3b
--- /dev/null
@@ -0,0 +1,39 @@
+using Content.Server.Polymorph.Systems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Humanoid;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that is polymorphing all humanoid entities in range.
+/// </summary>
+public sealed class XAEPolymorphSystem : BaseXAESystem<XAEPolymorphComponent>
+{
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly MobStateSystem _mob = default!;
+    [Dependency] private readonly PolymorphSystem _poly = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<Entity<HumanoidAppearanceComponent>> _humanoids = new();
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEPolymorphComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        _humanoids.Clear();
+        _lookup.GetEntitiesInRange(args.Coordinates, ent.Comp.Range, _humanoids);
+        foreach (var comp in _humanoids)
+        {
+            var target = comp.Owner;
+            if (!_mob.IsAlive(target))
+                continue;
+
+            _poly.PolymorphEntity(target, ent.Comp.PolymorphPrototypeName);
+            _audio.PlayPvs(ent.Comp.PolySound, ent);
+        }
+    }
+}
similarity index 53%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs
rename to Content.Server/Xenoarchaeology/Artifact/XAE/XAETelepathicSystem.cs
index df06d4ab01754b9e1bdba4ee5db5dca0e8da9417..1755248a5b620ecc095aed3a8cf9a57ce0e8a077 100644 (file)
@@ -1,31 +1,34 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
 using Content.Shared.Popups;
-using Robust.Server.GameObjects;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
 using Robust.Shared.Player;
 using Robust.Shared.Random;
 
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
 
-public sealed class TelepathicArtifactSystem : EntitySystem
+/// <summary>
+/// System for xeno artifact activation effect that sends sublime telepathic messages.
+/// </summary>
+public sealed class XAETelepathicSystem : BaseXAESystem<XAETelepathicComponent>
 {
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
     [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
 
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<TelepathicArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<EntityUid> _entities = new();
 
-    private void OnActivate(EntityUid uid, TelepathicArtifactComponent component, ArtifactActivatedEvent args)
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAETelepathicComponent> ent, ref XenoArtifactNodeActivatedEvent args)
     {
+        var component = ent.Comp;
         // try to find victims nearby
-        var victims = _lookup.GetEntitiesInRange(uid, component.Range);
-        foreach (var victimUid in victims)
+        _entities.Clear();
+        _lookup.GetEntitiesInRange(ent, component.Range, _entities);
+        foreach (var victimUid in _entities)
         {
-            if (!EntityManager.HasComponent<ActorComponent>(victimUid))
+            if (!HasComp<ActorComponent>(victimUid))
                 continue;
 
             // roll if msg should be usual or drastic
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAETemperatureSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAETemperatureSystem.cs
new file mode 100644 (file)
index 0000000..01a11b9
--- /dev/null
@@ -0,0 +1,49 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Atmos;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that changes atmospheric temperature on adjacent tiles.
+/// </summary>
+public sealed class XAETemperatureSystem : BaseXAESystem<XAETemperatureComponent>
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+    [Dependency] private readonly TransformSystem _transformSystem = default!;
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAETemperatureComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        var component = ent.Comp;
+        var transform = Transform(ent);
+
+        var center = _atmosphereSystem.GetContainingMixture(ent.Owner, false, true);
+        if (center == null)
+            return;
+
+        UpdateTileTemperature(component, center);
+
+        if (component.AffectAdjacentTiles && transform.GridUid != null)
+        {
+            var position = _transformSystem.GetGridOrMapTilePosition(ent, transform);
+            var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value, position, excite: true);
+
+            while (enumerator.MoveNext(out var mixture))
+            {
+                UpdateTileTemperature(component, mixture);
+            }
+        }
+    }
+
+    private void UpdateTileTemperature(XAETemperatureComponent component, GasMixture environment)
+    {
+        var dif = component.TargetTemperature - environment.Temperature;
+        var absDif = Math.Abs(dif);
+        var step = Math.Min(absDif, component.SpawnTemperature);
+        environment.Temperature += dif > 0 ? step : -step;
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEThrowThingsAroundSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEThrowThingsAroundSystem.cs
new file mode 100644 (file)
index 0000000..e73fc86
--- /dev/null
@@ -0,0 +1,72 @@
+using System.Numerics;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Maps;
+using Content.Shared.Physics;
+using Content.Shared.Throwing;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact activation effect that pries tiles and throws stuff around.
+/// </summary>
+public sealed class XAEThrowThingsAroundSystem : BaseXAESystem<XAEThrowThingsAroundComponent>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly ThrowingSystem _throwing = default!;
+    [Dependency] private readonly TileSystem _tile = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedMapSystem _map = default!;
+
+    private EntityQuery<PhysicsComponent> _physQuery;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<EntityUid> _entities = new();
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _physQuery = GetEntityQuery<PhysicsComponent>();
+    }
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEThrowThingsAroundComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        var component = ent.Comp;
+        var xform = Transform(ent);
+        if (TryComp<MapGridComponent>(xform.GridUid, out var grid))
+        {
+            var areaForTilesPry = new Circle(_transform.GetWorldPosition(xform), component.Range);
+            var tiles = _map.GetTilesIntersecting(xform.GridUid.Value, grid, areaForTilesPry, true);
+
+            foreach (var tile in tiles)
+            {
+                if (!_random.Prob(component.TilePryChance))
+                    continue;
+
+                _tile.PryTile(tile);
+            }
+        }
+
+        _entities.Clear();
+        _lookup.GetEntitiesInRange(ent, component.Range, _entities, LookupFlags.Dynamic | LookupFlags.Sundries);
+        foreach (var entity in _entities)
+        {
+            if (_physQuery.TryGetComponent(entity, out var phys)
+                && (phys.CollisionMask & (int)CollisionGroup.GhostImpassable) != 0)
+                continue;
+
+            var tempXform = Transform(entity);
+
+            var foo = _transform.GetWorldPosition(tempXform) - _transform.GetWorldPosition(xform);
+            _throwing.TryThrow(entity, foo * 2, component.ThrowStrength, ent, 0);
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAETriggerExplosivesSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAETriggerExplosivesSystem.cs
new file mode 100644 (file)
index 0000000..8767023
--- /dev/null
@@ -0,0 +1,24 @@
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
+using Content.Shared.Explosion.Components;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect of triggering explosion.
+/// </summary>
+public sealed class XAETriggerExplosivesSystem : BaseXAESystem<XAETriggerExplosivesComponent>
+{
+    [Dependency] private readonly ExplosionSystem _explosion = default!;
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAETriggerExplosivesComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        if(!TryComp<ExplosiveComponent>(ent, out var explosiveComp))
+            return;
+
+        _explosion.TriggerExplosive(ent, explosiveComp);
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATGasComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATGasComponent.cs
new file mode 100644 (file)
index 0000000..a5a9a44
--- /dev/null
@@ -0,0 +1,28 @@
+using Content.Shared.Atmos;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated by having a certain amount of gas around it.
+/// </summary>
+[RegisterComponent, Access(typeof(XATGasSystem))]
+public sealed partial class XATGasComponent : Component
+{
+    /// <summary>
+    /// The gas that is related to trigger.
+    /// </summary>
+    [DataField]
+    public Gas TargetGas;
+
+    /// <summary>
+    /// The amount of gas needed.
+    /// </summary>
+    [DataField]
+    public float Moles = Atmospherics.MolesCellStandard * 0.1f;
+
+    /// <summary>
+    /// Marker, if mentioned gas should be present in entity tile for trigger to activate, or it should not.
+    /// </summary>
+    [DataField]
+    public bool ShouldBePresent = true;
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATMagnetComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATMagnetComponent.cs
new file mode 100644 (file)
index 0000000..0126a2f
--- /dev/null
@@ -0,0 +1,21 @@
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// Component for triggering node on getting activated by powerful magnets.
+/// </summary>
+[RegisterComponent, Access(typeof(XATMagnetSystem))]
+public sealed partial class XATMagnetComponent : Component
+{
+    /// <summary>
+    /// How close to the magnet do you have to be?
+    /// </summary>
+    [DataField]
+    public float MagnetRange = 40f;
+
+    /// <summary>
+    /// How close do active magboots have to be?
+    /// This is smaller because they are weaker magnets
+    /// </summary>
+    [DataField]
+    public float MagbootsRange = 2f;
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATPressureComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATPressureComponent.cs
new file mode 100644 (file)
index 0000000..bdd2f21
--- /dev/null
@@ -0,0 +1,20 @@
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that activates when above or below a certain pressure.
+/// </summary>
+[RegisterComponent, Access(typeof(XATPressureSystem))]
+public sealed partial class XATPressureComponent : Component
+{
+    /// <summary>
+    /// The lower-end pressure threshold. Is not considered when null.
+    /// </summary>
+    [DataField]
+    public float? MinPressureThreshold;
+
+    /// <summary>
+    /// The higher-end pressure threshold. Is not considered when null.
+    /// </summary>
+    [DataField]
+    public float? MaxPressureThreshold;
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATTemperatureComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/Components/XATTemperatureComponent.cs
new file mode 100644 (file)
index 0000000..b8e080c
--- /dev/null
@@ -0,0 +1,20 @@
+namespace Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated by having a certain temperature near it.
+/// </summary>
+[RegisterComponent, Access(typeof(XATTemperatureSystem))]
+public sealed partial class XATTemperatureComponent : Component
+{
+    /// <summary>
+    /// Threshold temperature for trigger activation.
+    /// </summary>
+    [DataField]
+    public float TargetTemperature;
+
+    /// <summary>
+    /// Marker, if temp needs to be above or below the target.
+    /// </summary>
+    [DataField]
+    public bool TriggerOnHigherTemp = true;
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/XATGasSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/XATGasSystem.cs
new file mode 100644 (file)
index 0000000..49f24ac
--- /dev/null
@@ -0,0 +1,36 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger, which gets activated from some gas being on the same time as artifact with certain concentration.
+/// </summary>
+public sealed class XATGasSystem : BaseQueryUpdateXATSystem<XATGasComponent>
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+    protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATGasComponent, XenoArtifactNodeComponent> node, float frameTime)
+    {
+        var xform = Transform(artifact);
+
+        if (_atmosphere.GetTileMixture((artifact, xform)) is not { } mixture)
+            return;
+
+        var gasTrigger = node.Comp1;
+        var moles = mixture.GetMoles(gasTrigger.TargetGas);
+
+        if (gasTrigger.ShouldBePresent)
+        {
+            if (moles >= gasTrigger.Moles)
+                Trigger(artifact, node);
+        }
+        else
+        {
+            if (moles <= gasTrigger.Moles)
+                Trigger(artifact, node);
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/XATMagnetSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/XATMagnetSystem.cs
new file mode 100644 (file)
index 0000000..d045682
--- /dev/null
@@ -0,0 +1,67 @@
+using Content.Server.Salvage;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Clothing;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for checking if magnets-related xeno artifact node should be triggered.
+/// Works with magboots and salvage magnet, salvage magnet triggers only upon pulsing on activation.
+/// </summary>
+public sealed class XATMagnetSystem : BaseQueryUpdateXATSystem<XATMagnetComponent>
+{
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private HashSet<Entity<MagbootsComponent>> _magbootEntities = new();
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<SalvageMagnetActivatedEvent>(OnMagnetActivated);
+    }
+
+    /// <inheritdoc />
+    protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATMagnetComponent, XenoArtifactNodeComponent> node, float frameTime)
+    {
+        var coords = Transform(artifact.Owner).Coordinates;
+
+        _magbootEntities.Clear();
+        _lookup.GetEntitiesInRange(coords, node.Comp1.MagbootsRange, _magbootEntities);
+        foreach (var ent in _magbootEntities)
+        {
+            if(!TryComp<ItemToggleComponent>(ent, out var itemToggle) || !itemToggle.Activated)
+                continue;
+
+            Trigger(artifact, node);
+            break;
+        }
+    }
+
+    private void OnMagnetActivated(ref SalvageMagnetActivatedEvent args)
+    {
+        var magnetCoordinates = Transform(args.Magnet).Coordinates;
+
+        var query = EntityQueryEnumerator<XATMagnetComponent, XenoArtifactNodeComponent>();
+        while (query.MoveNext(out var uid, out var comp, out var node))
+        {
+            if (node.Attached == null)
+                continue;
+
+            var artifact = _xenoArtifactQuery.Get(GetEntity(node.Attached.Value));
+
+            if (!CanTrigger(artifact, (uid, node)))
+                continue;
+
+            var artifactCoordinates = Transform(artifact).Coordinates;
+            if (_transform.InRange(magnetCoordinates, artifactCoordinates, comp.MagnetRange))
+                Trigger(artifact, (uid, comp, node));
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/XATPressureSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/XATPressureSystem.cs
new file mode 100644 (file)
index 0000000..571bbee
--- /dev/null
@@ -0,0 +1,29 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for checking if pressure-related xeno artifact node should be triggered.
+/// </summary>
+public sealed class XATPressureSystem : BaseQueryUpdateXATSystem<XATPressureComponent>
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+    /// <inheritdoc />
+    protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATPressureComponent, XenoArtifactNodeComponent> node, float frameTime)
+    {
+        var xform = Transform(artifact);
+
+        if (_atmosphere.GetTileMixture((artifact, xform)) is not { } mixture)
+            return;
+
+        var pressure = mixture.Pressure;
+        if (pressure >= node.Comp1.MaxPressureThreshold || pressure <= node.Comp1.MinPressureThreshold)
+        {
+            Trigger(artifact, node);
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAT/XATTemperatureSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAT/XATTemperatureSystem.cs
new file mode 100644 (file)
index 0000000..99e2b72
--- /dev/null
@@ -0,0 +1,37 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Xenoarchaeology.Artifact.XAT.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+namespace Content.Server.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for checking if temperature-related xeno artifact node should be triggered.
+/// </summary>
+public sealed class XATTemperatureSystem : BaseQueryUpdateXATSystem<XATTemperatureComponent>
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+    /// <inheritdoc />
+    protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATTemperatureComponent, XenoArtifactNodeComponent> node, float frameTime)
+    {
+        var xform = Transform(artifact);
+
+        if (_atmosphere.GetTileMixture((artifact, xform)) is not { } mixture)
+            return;
+
+        var curTemp = mixture.Temperature;
+
+        var temperatureTriggerComponent = node.Comp1;
+        if (temperatureTriggerComponent.TriggerOnHigherTemp)
+        {
+            if (curTemp >= temperatureTriggerComponent.TargetTemperature)
+                Trigger(artifact, node);
+        }
+        else
+        {
+            if (curTemp <= temperatureTriggerComponent.TargetTemperature)
+                Trigger(artifact, node);
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs b/Content.Server/Xenoarchaeology/Artifact/XenoArtifactCommands.cs
new file mode 100644 (file)
index 0000000..31d079e
--- /dev/null
@@ -0,0 +1,125 @@
+using System.Text;
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+/// <summary>
+/// Toolshed commands for manipulating xeno artifact.
+/// </summary>
+[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
+public sealed class XenoArtifactCommand : ToolshedCommand
+{
+    [ValidatePrototypeId<EntityPrototype>]
+    public const string ArtifactPrototype = "BaseXenoArtifact";
+
+    /// <summary> List existing artifacts. </summary>
+    [CommandImplementation("list")]
+    public IEnumerable<EntityUid> List()
+    {
+        var query = EntityManager.EntityQueryEnumerator<XenoArtifactComponent>();
+        while (query.MoveNext(out var uid, out _))
+        {
+            yield return uid;
+        }
+    }
+
+    /// <summary>
+    /// Output matrix of artifact nodes and how they are connected.
+    /// </summary>
+    [CommandImplementation("printMatrix")]
+    public string PrintMatrix([PipedArgument] EntityUid artifactEntitUid)
+    {
+        var comp = EntityManager.GetComponent<XenoArtifactComponent>(artifactEntitUid);
+
+        var nodeCount = comp.NodeVertices.Length;
+
+        var sb = new StringBuilder("\n  |");
+        for (var i = 0; i < nodeCount; i++)
+        {
+            sb.Append($" {i:D2}|");
+        }
+
+        AddHorizontalFiller(sb);
+
+        for (var i = 0; i < nodeCount; i++)
+        {
+            sb.Append($"\n{i:D2}|");
+            for (var j = 0; j < nodeCount; j++)
+            {
+                var value = comp.NodeAdjacencyMatrix[i][j]
+                    ? "X"
+                    : " ";
+                sb.Append($" {value} |");
+            }
+            AddHorizontalFiller(sb);
+        }
+
+        return sb.ToString();
+
+        void AddHorizontalFiller(StringBuilder builder)
+        {
+            builder.AppendLine();
+            builder.Append("--+");
+            for (var i = 0; i < nodeCount; i++)
+            {
+                builder.Append($"---+");
+            }
+        }
+    }
+
+    /// <summary> Output total research points artifact contains. </summary>
+    [CommandImplementation("totalResearch")]
+    public int TotalResearch([PipedArgument] EntityUid artifactEntityUid)
+    {
+        var artiSys = EntityManager.System<XenoArtifactSystem>();
+        var comp = EntityManager.GetComponent<XenoArtifactComponent>(artifactEntityUid);
+
+        var sum = 0;
+
+        var nodes = artiSys.GetAllNodes((artifactEntityUid, comp));
+        foreach (var node in nodes)
+        {
+            sum += node.Comp.ResearchValue;
+        }
+
+        return sum;
+    }
+
+    /// <summary>
+    /// Spawns a bunch of artifacts and gets average total research points they can yield.
+    /// </summary>
+    [CommandImplementation("averageResearch")]
+    public float AverageResearch()
+    {
+        const int n = 100;
+        var sum = 0;
+
+        for (var i = 0; i < n; i++)
+        {
+            var ent = Spawn(ArtifactPrototype, MapCoordinates.Nullspace);
+            sum += TotalResearch(ent);
+            Del(ent);
+        }
+
+        return (float) sum / n;
+    }
+
+    /// <summary> Unlocks all nodes of artifact. </summary>
+    [CommandImplementation("unlockAllNodes")]
+    public void UnlockAllNodes([PipedArgument] EntityUid artifactEntityUid)
+    {
+        var artiSys = EntityManager.System<XenoArtifactSystem>();
+        var comp = EntityManager.GetComponent<XenoArtifactComponent>(artifactEntityUid);
+
+        var nodes = artiSys.GetAllNodes((artifactEntityUid, comp));
+        foreach (var node in nodes)
+        {
+            artiSys.SetNodeUnlocked((node, node.Comp));
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XenoArtifactSystem.ProcGen.cs b/Content.Server/Xenoarchaeology/Artifact/XenoArtifactSystem.ProcGen.cs
new file mode 100644 (file)
index 0000000..9da98dc
--- /dev/null
@@ -0,0 +1,219 @@
+using System.Linq;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Whitelist;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+public sealed partial class XenoArtifactSystem
+{
+    [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
+
+    private void GenerateArtifactStructure(Entity<XenoArtifactComponent> ent)
+    {
+        var nodeCount = ent.Comp.NodeCount.Next(RobustRandom);
+        var triggerPool = CreateTriggerPool(ent, nodeCount);
+        // trigger pool could be smaller, then requested node count
+        nodeCount = triggerPool.Count;
+        ResizeNodeGraph(ent, nodeCount);
+        while (nodeCount > 0)
+        {
+            GenerateArtifactSegment(ent, triggerPool, ref nodeCount);
+        }
+
+        RebuildXenoArtifactMetaData((ent, ent));
+    }
+
+    /// <summary>
+    /// Creates pool from all node triggers that current artifact can support.
+    /// As artifact cannot re-use triggers, pool will be growing smaller
+    /// and smaller with each node generated.
+    /// </summary>
+    /// <param name="ent">Artifact for which pool should be created.</param>
+    /// <param name="size">
+    /// Max size of pool. Resulting pool is not guaranteed to be exactly as large, but it will 100% won't be bigger.
+    /// </param>
+    private List<XenoArchTriggerPrototype> CreateTriggerPool(Entity<XenoArtifactComponent> ent, int size)
+    {
+        var triggerPool = new List<XenoArchTriggerPrototype>(size);
+        var weightsProto = PrototypeManager.Index(ent.Comp.TriggerWeights);
+        var weightsByTriggersLeft = new Dictionary<string, float>(weightsProto.Weights);
+
+        while (triggerPool.Count < size)
+        {
+            // OOPS! We ran out of triggers.
+            if (weightsByTriggersLeft.Count == 0)
+            {
+                Log.Error($"Insufficient triggers for generating {ToPrettyString(ent)}! Needed {size} but had {triggerPool.Count}");
+                return triggerPool;
+            }
+
+            var triggerId = RobustRandom.Pick(weightsByTriggersLeft);
+            weightsByTriggersLeft.Remove(triggerId);
+            var trigger = PrototypeManager.Index<XenoArchTriggerPrototype>(triggerId);
+            if (_entityWhitelist.IsWhitelistFail(trigger.Whitelist, ent))
+                continue;
+
+            triggerPool.Add(trigger);
+        }
+
+        return triggerPool;
+    }
+
+    /// <summary>
+    /// Generates segment of artifact - isolated graph, nodes inside which are interconnected.
+    /// As size of segment is randomized - it is subtracted from node count.
+    /// </summary>
+    private void GenerateArtifactSegment(
+        Entity<XenoArtifactComponent> ent,
+        List<XenoArchTriggerPrototype> triggerPool,
+        ref int nodeCount
+    )
+    {
+        var segmentSize = GetArtifactSegmentSize(ent, nodeCount);
+        nodeCount -= segmentSize;
+        var populatedNodes = PopulateArtifactSegmentRecursive(ent, triggerPool, ref segmentSize);
+
+        var segments = GetSegmentsFromNodes(ent, populatedNodes).ToList();
+
+        // We didn't connect all of our nodes: do extra work to make sure there's a connection.
+        if (segments.Count > 1)
+        {
+            var parent = segments.MaxBy(s => s.Count)!;
+            var minP = parent.Min(n => n.Comp.Depth);
+            var maxP = parent.Max(n => n.Comp.Depth);
+
+            segments.Remove(parent);
+            foreach (var segment in segments)
+            {
+                // calculate the range of the depth of the nodes in the segment
+                var minS = segment.Min(n => n.Comp.Depth);
+                var maxS = segment.Max(n => n.Comp.Depth);
+
+                // Figure out the range of depths that allows for a connection between these two.
+                // The range is essentially the lower values + 1 on each side.
+                var min = Math.Max(minS, minP) - 1;
+                var max = Math.Min(maxS, maxP) + 1;
+
+                // how the fuck did you do this? you don't even deserve to get a parent. fuck you.
+                if (min > max || min == max)
+                    continue;
+
+                var node1Options = segment.Where(n => n.Comp.Depth >= min && n.Comp.Depth <= max)
+                                          .ToList();
+                if (node1Options.Count == 0)
+                {
+                    continue;
+                }
+
+                var node1 = RobustRandom.Pick(node1Options);
+                var node1Depth = node1.Comp.Depth;
+
+                var node2Options = parent.Where(n => n.Comp.Depth >= node1Depth - 1 && n.Comp.Depth <= node1Depth + 1 && n.Comp.Depth != node1Depth)
+                                         .ToList();
+                if (node2Options.Count == 0)
+                {
+                    continue;
+                }
+
+                var node2 = RobustRandom.Pick(node2Options);
+
+                if (node1.Comp.Depth < node2.Comp.Depth)
+                {
+                    AddEdge((ent, ent.Comp), node1, node2, false);
+                }
+                else
+                {
+                    AddEdge((ent, ent.Comp), node2, node1, false);
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// Recursively populate layers of artifact segment - isolated graph, nodes inside which are interconnected.
+    /// Each next iteration is going to have more chances to have more nodes (so it goes 'from top to bottom' of
+    /// the tree, creating its peak nodes first, and then making layers with more and more branches).
+    /// </summary>
+    private List<Entity<XenoArtifactNodeComponent>> PopulateArtifactSegmentRecursive(
+        Entity<XenoArtifactComponent> ent,
+        List<XenoArchTriggerPrototype> triggerPool,
+        ref int segmentSize,
+        int iteration = 0
+    )
+    {
+        if (segmentSize == 0)
+            return new();
+
+        // Try and get larger as we create more layers. Prevents excessive layers.
+        var mod = RobustRandom.Next((int) (iteration / 1.5f), iteration + 1);
+
+        var layerMin = Math.Min(ent.Comp.NodesPerSegmentLayer.Min + mod, segmentSize);
+        var layerMax = Math.Min(ent.Comp.NodesPerSegmentLayer.Max + mod, segmentSize);
+
+        // Default to one node if we had shenanigans and ended up with weird layer counts.
+        var nodeCount = 1;
+        if (layerMax >= layerMin)
+            nodeCount = RobustRandom.Next(layerMin, layerMax + 1); // account for non-inclusive max
+
+        segmentSize -= nodeCount;
+        var nodes = new List<Entity<XenoArtifactNodeComponent>>();
+        for (var i = 0; i < nodeCount; i++)
+        {
+            var trigger = RobustRandom.PickAndTake(triggerPool);
+            nodes.Add(CreateNode(ent, trigger, iteration));
+        }
+
+        var successors = PopulateArtifactSegmentRecursive(
+            ent,
+            triggerPool,
+            ref segmentSize,
+            iteration: iteration + 1
+        );
+
+        if (successors.Count == 0)
+            return nodes;
+
+        foreach (var successor in successors)
+        {
+            var node = RobustRandom.Pick(nodes);
+            AddEdge((ent, ent), node, successor, dirty: false);
+        }
+
+        // randomly add in some extra edges for variance.
+        var scatterCount = ent.Comp.ScatterPerLayer.Next(RobustRandom);
+        for (var i = 0; i < scatterCount; i++)
+        {
+            var node = RobustRandom.Pick(nodes);
+            var successor = RobustRandom.Pick(successors);
+            AddEdge((ent, ent), node, successor, dirty: false);
+        }
+
+        return nodes;
+    }
+
+    /// <summary>
+    /// Rolls segment size, based on amount of nodes left and XenoArtifactComponent settings.
+    /// </summary>
+    private int GetArtifactSegmentSize(Entity<XenoArtifactComponent> ent, int nodeCount)
+    {
+        // Make sure we can't generate a single segment artifact.
+        // We always want to have at least 2 segments. For variety.
+        var segmentMin = ent.Comp.SegmentSize.Min;
+        var segmentMax = Math.Min(ent.Comp.SegmentSize.Max, Math.Max(nodeCount / 2, segmentMin));
+
+        var segmentSize = RobustRandom.Next(segmentMin, segmentMax + 1); // account for non-inclusive max
+        var remainder = nodeCount - segmentSize;
+
+        // If our next segment is going to be undersized, then we just absorb it into this segment.
+        if (remainder < ent.Comp.SegmentSize.Min)
+            segmentSize += remainder;
+
+        // Sanity check to make sure we don't exceed the node count. (it shouldn't happen prior anyway but oh well)
+        segmentSize = Math.Min(nodeCount, segmentSize);
+
+        return segmentSize;
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XenoArtifactSystem.cs b/Content.Server/Xenoarchaeology/Artifact/XenoArtifactSystem.cs
new file mode 100644 (file)
index 0000000..fbea515
--- /dev/null
@@ -0,0 +1,35 @@
+using Content.Server.Cargo.Systems;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+/// <inheritdoc cref="SharedXenoArtifactSystem"/>
+public sealed partial class XenoArtifactSystem : SharedXenoArtifactSystem
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<XenoArtifactComponent, MapInitEvent>(OnArtifactMapInit);
+        SubscribeLocalEvent<XenoArtifactComponent, PriceCalculationEvent>(OnCalculatePrice);
+    }
+
+    private void OnArtifactMapInit(Entity<XenoArtifactComponent> ent, ref MapInitEvent args)
+    {
+        if (ent.Comp.IsGenerationRequired)
+            GenerateArtifactStructure(ent);
+    }
+
+    private void OnCalculatePrice(Entity<XenoArtifactComponent> ent, ref PriceCalculationEvent args)
+    {
+        foreach (var node in GetAllNodes(ent))
+        {
+            if (node.Comp.Locked)
+                continue;
+
+            args.Price += node.Comp.ResearchValue * ent.Comp.PriceMultiplier;
+        }
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Artifact/XenoArtifactUnlockNodeCommand.cs b/Content.Server/Xenoarchaeology/Artifact/XenoArtifactUnlockNodeCommand.cs
new file mode 100644 (file)
index 0000000..1782619
--- /dev/null
@@ -0,0 +1,84 @@
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Console;
+
+namespace Content.Server.Xenoarchaeology.Artifact;
+
+/// <summary> Command for unlocking specific node of xeno artifact. </summary>
+[AdminCommand(AdminFlags.Debug)]
+public sealed class XenoArtifactUnlockNodeCommand : LocalizedCommands
+{
+    [Dependency] private readonly EntityManager _entities = default!;
+
+    /// <inheritdoc />
+    public override string Command => "unlocknode";
+
+    /// <inheritdoc />
+    public override string Description => Loc.GetString("cmd-unlocknode-desc");
+
+    /// <inheritdoc />
+    public override string Help => Loc.GetString("cmd-unlocknode-help");
+
+    /// <inheritdoc />
+    public override void Execute(IConsoleShell shell, string argStr, string[] args)
+    {
+        if (args.Length != 2)
+        {
+            shell.WriteError(Loc.GetString("cmd-parse-failure-unlocknode-arg-num"));
+            return;
+        }
+
+        if (!NetEntity.TryParse(args[1], out var netNode))
+        {
+            shell.WriteError(Loc.GetString("cmd-parse-failure-unlocknode-invalid-entity"));
+            return;
+        }
+
+        if (!_entities.TryGetEntity(netNode, out var entityUid))
+        {
+            shell.WriteError(Loc.GetString("cmd-parse-failure-unlocknode-invalid-entity"));
+            return;
+        }
+        _entities.System<XenoArtifactSystem>()
+                 .SetNodeUnlocked(entityUid.Value);
+    }
+
+    /// <inheritdoc />
+    public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+    {
+        if (args.Length == 1)
+        {
+            var query = _entities.EntityQueryEnumerator<XenoArtifactComponent>();
+            var completionOptions = new List<CompletionOption>();
+            while (query.MoveNext(out var uid, out _))
+            {
+                completionOptions.Add(new CompletionOption(uid.ToString()));
+            }
+
+            return CompletionResult.FromHintOptions(completionOptions, "<artifact uid>");
+        }
+
+        if (args.Length == 2 &&
+            NetEntity.TryParse(args[0], out var netEnt) &&
+            _entities.TryGetEntity(netEnt, out var artifactUid) &&
+            _entities.TryGetComponent<XenoArtifactComponent>(artifactUid, out var comp))
+        {
+            var artifactSystem = _entities.System<XenoArtifactSystem>();
+
+            var result = new List<CompletionOption>();
+            foreach (var node in artifactSystem.GetAllNodes((artifactUid.Value, comp)))
+            {
+                var metaData = _entities.MetaQuery.Comp(artifactUid.Value);
+                var entityUidStr = _entities.GetNetEntity(node)
+                                            .ToString();
+                var completionOption = new CompletionOption(entityUidStr, metaData.EntityName);
+                result.Add(completionOption);
+            }
+
+            return CompletionResult.FromHintOptions(result, "<node uid>");
+        }
+
+        return CompletionResult.Empty;
+    }
+}
diff --git a/Content.Server/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs
new file mode 100644 (file)
index 0000000..2d5aa45
--- /dev/null
@@ -0,0 +1,50 @@
+using Content.Server.Research.Systems;
+using Content.Server.Xenoarchaeology.Artifact;
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Server.Xenoarchaeology.Equipment;
+
+/// <inheritdoc />
+public sealed class ArtifactAnalyzerSystem : SharedArtifactAnalyzerSystem
+{
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly ResearchSystem _research = default!;
+    [Dependency] private readonly XenoArtifactSystem _xenoArtifact = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleExtractButtonPressedMessage>(OnExtractButtonPressed);
+    }
+
+    private void OnExtractButtonPressed(Entity<AnalysisConsoleComponent> ent, ref AnalysisConsoleExtractButtonPressedMessage args)
+    {
+        if (!TryGetArtifactFromConsole(ent, out var artifact))
+            return;
+
+        if (!_research.TryGetClientServer(ent, out var server, out var serverComponent))
+            return;
+
+        var sumResearch = 0;
+        foreach (var node in _xenoArtifact.GetAllNodes(artifact.Value))
+        {
+            var research = _xenoArtifact.GetResearchValue(node);
+            _xenoArtifact.SetConsumedResearchValue(node, node.Comp.ConsumedResearchValue + research);
+            sumResearch += research;
+        }
+
+        if (sumResearch == 0)
+            return;
+
+        _research.ModifyServerPoints(server.Value, sumResearch, serverComponent);
+        _audio.PlayPvs(ent.Comp.ExtractSound, artifact.Value);
+        _popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"), artifact.Value, PopupType.Large);
+    }
+}
+
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs
deleted file mode 100644 (file)
index 7c1fe28..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-using Robust.Shared.Serialization.TypeSerializers.Implementations;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// Activecomp used for tracking artifact analyzers that are currently
-/// in the process of scanning an artifact.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveArtifactAnalyzerComponent : Component
-{
-    /// <summary>
-    /// When did the scanning start or last resume?
-    /// </summary>
-    [DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))]
-    public TimeSpan StartTime;
-
-    /// <summary>
-    /// When pausing, this will store the duration the scan has already been running for.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan AccumulatedRunTime;
-
-    /// <summary>
-    /// Is analysis paused?
-    /// It could be when the Artifact Analyzer has no power, for example.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    public bool AnalysisPaused = false;
-
-    /// <summary>
-    /// What is being scanned?
-    /// </summary>
-    [DataField]
-    public EntityUid Artifact;
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs
deleted file mode 100644 (file)
index da6b35d..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-using Robust.Shared.Audio;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// This is used for tracking artifacts that are currently
-/// being scanned by <see cref="ActiveArtifactAnalyzerComponent"/>
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveScannedArtifactComponent : Component
-{
-    /// <summary>
-    /// The scanner that is scanning this artifact
-    /// </summary>
-    [ViewVariables]
-    public EntityUid Scanner;
-
-    /// <summary>
-    /// The sound that plays when the scan fails
-    /// </summary>
-    public readonly SoundSpecifier ScanFailureSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs
deleted file mode 100644 (file)
index 892e244..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// The console that is used for artifact analysis
-/// </summary>
-[RegisterComponent]
-public sealed partial class AnalysisConsoleComponent : Component
-{
-    /// <summary>
-    /// The analyzer entity the console is linked.
-    /// Can be null if not linked.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    public EntityUid? AnalyzerEntity;
-
-    /// <summary>
-    /// The machine linking port for the analyzer
-    /// </summary>
-    [DataField("linkingPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
-    public string LinkingPort = "ArtifactAnalyzerSender";
-
-    /// <summary>
-    /// The sound played when an artifact has points extracted.
-    /// </summary>
-    [DataField("extractSound")]
-    public SoundSpecifier ExtractSound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg");
-
-    /// <summary>
-    /// The entity spawned by a report.
-    /// </summary>
-    [DataField("reportEntityId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string ReportEntityId = "Paper";
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs
deleted file mode 100644 (file)
index 9497176..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Shared.Construction.Prototypes;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// A machine that is combined and linked to the <see cref="AnalysisConsoleComponent"/>
-/// in order to analyze artifacts and extract points.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactAnalyzerComponent : Component
-{
-    /// <summary>
-    /// How long it takes to analyze an artifact
-    /// </summary>
-    [DataField("analysisDuration", customTypeSerializer: typeof(TimespanSerializer))]
-    public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(30);
-
-    /// <summary>
-    /// The corresponding console entity.
-    /// Can be null if not linked.
-    /// </summary>
-    [ViewVariables]
-    public EntityUid? Console;
-
-    [ViewVariables(VVAccess.ReadWrite)]
-    public bool ReadyToPrint = false;
-
-    [DataField("scanFinishedSound")]
-    public SoundSpecifier ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
-
-    #region Analysis Data
-    [DataField]
-    public EntityUid? LastAnalyzedArtifact;
-
-    [ViewVariables]
-    public ArtifactNode? LastAnalyzedNode;
-
-    [ViewVariables(VVAccess.ReadWrite)]
-    public int? LastAnalyzerPointValue;
-    #endregion
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/BiasedArtifactComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/BiasedArtifactComponent.cs
deleted file mode 100644 (file)
index 16bd34d..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// This is used for artifacts that are biased to move
-/// in a particular direction via the <see cref="TraversalDistorterComponent"/>
-/// </summary>
-[RegisterComponent]
-public sealed partial class BiasedArtifactComponent : Component
-{
-    [ViewVariables]
-    public EntityUid Provider;
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs
deleted file mode 100644 (file)
index 36cc5f8..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-[RegisterComponent]
-public sealed partial class NodeScannerComponent : Component
-{
-
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs
deleted file mode 100644 (file)
index c555f31..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-///     Suppress artifact activation, when entity is placed inside this container.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SuppressArtifactContainerComponent : Component
-{
-
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs
deleted file mode 100644 (file)
index fe95114..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace Content.Server.Xenoarchaeology.Equipment.Components;
-
-/// <summary>
-/// This is used for a machine that biases
-/// an artifact placed on it to move up/down
-/// </summary>
-[RegisterComponent]
-public sealed partial class TraversalDistorterComponent : Component
-{
-    [ViewVariables(VVAccess.ReadWrite)]
-    public BiasDirection BiasDirection = BiasDirection.Up;
-
-    public TimeSpan NextActivation = default!;
-    public TimeSpan ActivationDelay = TimeSpan.FromSeconds(1);
-}
-
-public enum BiasDirection : byte
-{
-    Up, //Towards depth 0
-    Down, //Away from depth 0
-}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs
deleted file mode 100644 (file)
index a5670ec..0000000
+++ /dev/null
@@ -1,519 +0,0 @@
-using System.Linq;
-using Content.Server.Power.Components;
-using Content.Server.Research.Systems;
-using Content.Shared.UserInterface;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Audio;
-using Content.Shared.DeviceLinking;
-using Content.Shared.DeviceLinking.Events;
-using Content.Shared.Paper;
-using Content.Shared.Placeable;
-using Content.Shared.Popups;
-using Content.Shared.Power;
-using Content.Shared.Power.EntitySystems;
-using Content.Shared.Research.Components;
-using Content.Shared.Xenoarchaeology.Equipment;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using JetBrains.Annotations;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Systems;
-
-/// <summary>
-/// This system is used for managing the artifact analyzer as well as the analysis console.
-/// It also hanadles scanning and ui updates for both systems.
-/// </summary>
-public sealed class ArtifactAnalyzerSystem : EntitySystem
-{
-    [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-    [Dependency] private readonly MetaDataSystem _metaSystem = default!;
-    [Dependency] private readonly PaperSystem _paper = default!;
-    [Dependency] private readonly ResearchSystem _research = default!;
-    [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
-    [Dependency] private readonly SharedPopupSystem _popup = default!;
-    [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
-    [Dependency] private readonly TraversalDistorterSystem _traversalDistorter = default!;
-    [Dependency] private readonly UserInterfaceSystem _ui = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ActiveScannedArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
-
-        SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentStartup>(OnAnalyzeStart);
-        SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentShutdown>(OnAnalyzeEnd);
-        SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, PowerChangedEvent>(OnPowerChanged);
-
-        SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced);
-        SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved);
-
-        SubscribeLocalEvent<ArtifactAnalyzerComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
-        SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
-
-        SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleServerSelectionMessage>(OnServerSelectionMessage);
-        SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleScanButtonPressedMessage>(OnScanButton);
-        SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsolePrintButtonPressedMessage>(OnPrintButton);
-        SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleExtractButtonPressedMessage>(OnExtractButton);
-        SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleBiasButtonPressedMessage>(OnBiasButton);
-
-        SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerSelectedMessage>((e, c, _) => UpdateUserInterface(e, c),
-            after: new[] { typeof(ResearchSystem) });
-        SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerDeselectedMessage>((e, c, _) => UpdateUserInterface(e, c),
-            after: new[] { typeof(ResearchSystem) });
-        SubscribeLocalEvent<AnalysisConsoleComponent, BeforeActivatableUIOpenEvent>((e, c, _) => UpdateUserInterface(e, c));
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        var query = EntityQueryEnumerator<ActiveArtifactAnalyzerComponent, ArtifactAnalyzerComponent>();
-        while (query.MoveNext(out var uid, out var active, out var scan))
-        {
-            if (active.AnalysisPaused)
-                continue;
-
-            if (_timing.CurTime - active.StartTime < scan.AnalysisDuration - active.AccumulatedRunTime)
-                continue;
-
-            FinishScan(uid, scan, active);
-        }
-    }
-
-    /// <summary>
-    /// Resets the current scan on the artifact analyzer
-    /// </summary>
-    /// <param name="uid">The analyzer being reset</param>
-    /// <param name="component"></param>
-    [PublicAPI]
-    public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-
-        component.LastAnalyzedArtifact = null;
-        component.ReadyToPrint = false;
-        UpdateAnalyzerInformation(uid, component);
-    }
-
-    /// <summary>
-    /// Goes through the current entities on
-    /// the analyzer and returns a valid artifact
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="placer"></param>
-    /// <returns></returns>
-    private EntityUid? GetArtifactForAnalysis(EntityUid? uid, ItemPlacerComponent? placer = null)
-    {
-        if (uid == null || !Resolve(uid.Value, ref placer))
-            return null;
-
-        return placer.PlacedEntities.FirstOrNull();
-    }
-
-    /// <summary>
-    /// Updates the current scan information based on
-    /// the last artifact that was scanned.
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="component"></param>
-    private void UpdateAnalyzerInformation(EntityUid uid, ArtifactAnalyzerComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-
-        if (component.LastAnalyzedArtifact == null)
-        {
-            component.LastAnalyzerPointValue = null;
-            component.LastAnalyzedNode = null;
-        }
-        else if (TryComp<ArtifactComponent>(component.LastAnalyzedArtifact, out var artifact))
-        {
-            var lastNode = artifact.CurrentNodeId == null
-                ? null
-                : (ArtifactNode?) _artifact.GetNodeFromId(artifact.CurrentNodeId.Value, artifact).Clone();
-            component.LastAnalyzedNode = lastNode;
-            component.LastAnalyzerPointValue = _artifact.GetResearchPointValue(component.LastAnalyzedArtifact.Value, artifact);
-        }
-    }
-
-    private void OnMapInit(EntityUid uid, ArtifactAnalyzerComponent component, MapInitEvent args)
-    {
-        if (!TryComp<DeviceLinkSinkComponent>(uid, out var sink))
-            return;
-
-        foreach (var source in sink.LinkedSources)
-        {
-            if (!TryComp<AnalysisConsoleComponent>(source, out var analysis))
-                continue;
-            component.Console = source;
-            analysis.AnalyzerEntity = uid;
-            return;
-        }
-    }
-
-    private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args)
-    {
-        if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
-            return;
-
-        component.AnalyzerEntity = args.Sink;
-        analyzer.Console = uid;
-
-        UpdateUserInterface(uid, component);
-    }
-
-    private void OnPortDisconnected(EntityUid uid, AnalysisConsoleComponent component, PortDisconnectedEvent args)
-    {
-        if (args.Port == component.LinkingPort && component.AnalyzerEntity != null)
-        {
-            if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzezr))
-                analyzezr.Console = null;
-            component.AnalyzerEntity = null;
-        }
-
-        UpdateUserInterface(uid, component);
-    }
-
-    private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? component = null)
-    {
-        if (!Resolve(uid, ref component, false))
-            return;
-
-        EntityUid? artifact = null;
-        FormattedMessage? msg = null;
-        TimeSpan? totalTime = null;
-        var canScan = false;
-        var canPrint = false;
-        var points = 0;
-
-        if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer))
-        {
-            artifact = analyzer.LastAnalyzedArtifact;
-            msg = GetArtifactScanMessage(analyzer);
-            totalTime = analyzer.AnalysisDuration;
-            if (TryComp<ItemPlacerComponent>(component.AnalyzerEntity, out var placer))
-                canScan = placer.PlacedEntities.Any();
-            canPrint = analyzer.ReadyToPrint;
-
-            // the artifact that's actually on the scanner right now.
-            if (GetArtifactForAnalysis(component.AnalyzerEntity, placer) is { } current)
-                points = _artifact.GetResearchPointValue(current);
-        }
-
-        var analyzerConnected = component.AnalyzerEntity != null;
-        var serverConnected = TryComp<ResearchClientComponent>(uid, out var client) && client.ConnectedToServer;
-
-        var scanning = TryComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity, out var active);
-        var paused = active != null ? active.AnalysisPaused : false;
-
-        var biasDirection = BiasDirection.Up;
-
-        if (TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
-            biasDirection = trav.BiasDirection;
-
-        var state = new AnalysisConsoleUpdateState(GetNetEntity(artifact), analyzerConnected, serverConnected,
-            canScan, canPrint, msg, scanning, paused, active?.StartTime, active?.AccumulatedRunTime, totalTime, points, biasDirection == BiasDirection.Down);
-
-        _ui.SetUiState(uid, ArtifactAnalzyerUiKey.Key, state);
-    }
-
-    /// <summary>
-    /// opens the server selection menu.
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="component"></param>
-    /// <param name="args"></param>
-    private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args)
-    {
-        _ui.OpenUi(uid, ResearchClientUiKey.Key, args.Actor);
-    }
-
-    /// <summary>
-    /// Starts scanning the artifact.
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="component"></param>
-    /// <param name="args"></param>
-    private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args)
-    {
-        if (component.AnalyzerEntity == null)
-            return;
-
-        if (HasComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity))
-            return;
-
-        var ent = GetArtifactForAnalysis(component.AnalyzerEntity);
-        if (ent == null)
-            return;
-
-        var activeComp = EnsureComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity.Value);
-        activeComp.StartTime = _timing.CurTime;
-        activeComp.AccumulatedRunTime = TimeSpan.Zero;
-        activeComp.Artifact = ent.Value;
-
-        if (TryComp<ApcPowerReceiverComponent>(component.AnalyzerEntity.Value, out var powa))
-            activeComp.AnalysisPaused = !powa.Powered;
-
-        var activeArtifact = EnsureComp<ActiveScannedArtifactComponent>(ent.Value);
-        activeArtifact.Scanner = component.AnalyzerEntity.Value;
-        UpdateUserInterface(uid, component);
-    }
-
-    private void OnPrintButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsolePrintButtonPressedMessage args)
-    {
-        if (component.AnalyzerEntity == null)
-            return;
-
-        if (!TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer) ||
-            analyzer.LastAnalyzedNode == null ||
-            analyzer.LastAnalyzerPointValue == null ||
-            !analyzer.ReadyToPrint)
-        {
-            return;
-        }
-        analyzer.ReadyToPrint = false;
-
-        var report = Spawn(component.ReportEntityId, Transform(uid).Coordinates);
-        _metaSystem.SetEntityName(report, Loc.GetString("analysis-report-title", ("id", analyzer.LastAnalyzedNode.Id)));
-
-        var msg = GetArtifactScanMessage(analyzer);
-        if (msg == null)
-            return;
-
-        _popup.PopupEntity(Loc.GetString("analysis-console-print-popup"), uid);
-        if (TryComp<PaperComponent>(report, out var paperComp))
-            _paper.SetContent((report, paperComp), msg.ToMarkup());
-        UpdateUserInterface(uid, component);
-    }
-
-    private FormattedMessage? GetArtifactScanMessage(ArtifactAnalyzerComponent component)
-    {
-        var msg = new FormattedMessage();
-        if (component.LastAnalyzedNode == null)
-            return null;
-
-        var n = component.LastAnalyzedNode;
-
-        msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-id", ("id", n.Id)));
-        msg.PushNewline();
-        msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-depth", ("depth", n.Depth)));
-        msg.PushNewline();
-
-        var activated = n.Triggered
-            ? "analysis-console-info-triggered-true"
-            : "analysis-console-info-triggered-false";
-        msg.AddMarkupOrThrow(Loc.GetString(activated));
-        msg.PushNewline();
-
-        msg.PushNewline();
-        var needSecondNewline = false;
-
-        var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(n.Trigger);
-        if (triggerProto.TriggerHint != null)
-        {
-            msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-trigger",
-                ("trigger", Loc.GetString(triggerProto.TriggerHint))) + "\n");
-            needSecondNewline = true;
-        }
-
-        var effectproto = _prototype.Index<ArtifactEffectPrototype>(n.Effect);
-        if (effectproto.EffectHint != null)
-        {
-            msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-effect",
-                ("effect", Loc.GetString(effectproto.EffectHint))) + "\n");
-            needSecondNewline = true;
-        }
-
-        if (needSecondNewline)
-            msg.PushNewline();
-
-        msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-edges", ("edges", n.Edges.Count)));
-        msg.PushNewline();
-
-        if (component.LastAnalyzerPointValue != null)
-            msg.AddMarkupOrThrow(Loc.GetString("analysis-console-info-value", ("value", component.LastAnalyzerPointValue)));
-
-        return msg;
-    }
-
-    /// <summary>
-    /// Extracts points from the artifact and updates the server points
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="component"></param>
-    /// <param name="args"></param>
-    private void OnExtractButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleExtractButtonPressedMessage args)
-    {
-        if (component.AnalyzerEntity == null)
-            return;
-
-        if (!_research.TryGetClientServer(uid, out var server, out var serverComponent))
-            return;
-
-        var artifact = GetArtifactForAnalysis(component.AnalyzerEntity);
-        if (artifact == null)
-            return;
-
-        var pointValue = _artifact.GetResearchPointValue(artifact.Value);
-
-        // no new nodes triggered so nothing to add
-        if (pointValue == 0)
-            return;
-
-        _research.ModifyServerPoints(server.Value, pointValue, serverComponent);
-        _artifact.AdjustConsumedPoints(artifact.Value, pointValue);
-
-        _audio.PlayPvs(component.ExtractSound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
-
-        _popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"),
-            component.AnalyzerEntity.Value, PopupType.Large);
-
-        UpdateUserInterface(uid, component);
-    }
-
-    private void OnBiasButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleBiasButtonPressedMessage args)
-    {
-        if (component.AnalyzerEntity == null)
-            return;
-
-        if (!TryComp<TraversalDistorterComponent>(component.AnalyzerEntity, out var trav))
-            return;
-
-        if (!_traversalDistorter.SetState(component.AnalyzerEntity.Value, trav, args.IsDown))
-            return;
-
-        UpdateUserInterface(uid, component);
-    }
-
-    /// <summary>
-    /// Cancels scans if the artifact changes nodes (is activated) during the scan.
-    /// </summary>
-    private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        CancelScan(uid);
-    }
-
-    /// <summary>
-    /// Stops the current scan
-    /// </summary>
-    [PublicAPI]
-    public void CancelScan(EntityUid artifact, ActiveScannedArtifactComponent? component = null, ArtifactAnalyzerComponent? analyzer = null)
-    {
-        if (!Resolve(artifact, ref component, false))
-            return;
-
-        if (!Resolve(component.Scanner, ref analyzer))
-            return;
-
-        _audio.PlayPvs(component.ScanFailureSound, component.Scanner, AudioParams.Default.WithVolume(3f));
-
-        RemComp<ActiveArtifactAnalyzerComponent>(component.Scanner);
-        if (analyzer.Console != null)
-            UpdateUserInterface(analyzer.Console.Value);
-
-        RemCompDeferred(artifact, component);
-    }
-
-    /// <summary>
-    /// Finishes the current scan.
-    /// </summary>
-    [PublicAPI]
-    public void FinishScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
-    {
-        if (!Resolve(uid, ref component, ref active))
-            return;
-
-        component.ReadyToPrint = true;
-        _audio.PlayPvs(component.ScanFinishedSound, uid);
-        component.LastAnalyzedArtifact = active.Artifact;
-        UpdateAnalyzerInformation(uid, component);
-
-        RemComp<ActiveScannedArtifactComponent>(active.Artifact);
-        RemComp(uid, active);
-        if (component.Console != null)
-            UpdateUserInterface(component.Console.Value);
-    }
-
-    [PublicAPI]
-    public void PauseScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
-    {
-        if (!Resolve(uid, ref component, ref active) || active.AnalysisPaused)
-            return;
-
-        active.AnalysisPaused = true;
-        // As we pause, we store what was already completed.
-        active.AccumulatedRunTime = (_timing.CurTime - active.StartTime) + active.AccumulatedRunTime;
-
-        if (Exists(component.Console))
-            UpdateUserInterface(component.Console.Value);
-    }
-
-    [PublicAPI]
-    public void ResumeScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null)
-    {
-        if (!Resolve(uid, ref component, ref active) || !active.AnalysisPaused)
-            return;
-
-        active.StartTime = _timing.CurTime;
-        active.AnalysisPaused = false;
-
-        if (Exists(component.Console))
-            UpdateUserInterface(component.Console.Value);
-    }
-
-    private void OnItemPlaced(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemPlacedEvent args)
-    {
-        if (component.Console != null && Exists(component.Console))
-            UpdateUserInterface(component.Console.Value);
-    }
-
-    private void OnItemRemoved(EntityUid uid, ArtifactAnalyzerComponent component, ref ItemRemovedEvent args)
-    {
-        // Scanners shouldn't give permanent remove vision to an artifact, and the scanned artifact doesn't have any
-        // component to track analyzers that have scanned it for removal if the artifact gets deleted.
-        // So we always clear this on removal.
-        component.LastAnalyzedArtifact = null;
-
-        // cancel the scan if the artifact moves off the analyzer
-        CancelScan(args.OtherEntity);
-        if (Exists(component.Console))
-            UpdateUserInterface(component.Console.Value);
-    }
-
-    private void OnAnalyzeStart(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentStartup args)
-    {
-        _receiver.SetNeedsPower(uid, true);
-        _ambientSound.SetAmbience(uid, true);
-    }
-
-    private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
-    {
-        _receiver.SetNeedsPower(uid, false);
-        _ambientSound.SetAmbience(uid, false);
-    }
-
-    private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent active, ref PowerChangedEvent args)
-    {
-        if (!args.Powered)
-        {
-            PauseScan(uid, null, active);
-        }
-        else
-        {
-            ResumeScan(uid, null, active);
-        }
-    }
-}
-
index f841ea910e7759bcef4416c5558640f4679fd4c5..8bc87cb37860d3cd255dd93e2c54575dbd91904d 100644 (file)
@@ -1,16 +1,15 @@
 using Content.Server.Body.Systems;
 using Content.Server.Popups;
-using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
 using Content.Server.Stack;
 using Content.Server.Storage.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
 using Content.Shared.Body.Components;
 using Content.Shared.Damage;
 using Content.Shared.Power;
 using Content.Shared.Verbs;
 using Content.Shared.Whitelist;
 using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
 using Robust.Shared.Collections;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
@@ -22,7 +21,6 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
 {
     [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
     [Dependency] private readonly BodySystem _body = default!;
     [Dependency] private readonly DamageableSystem _damageable = default!;
     [Dependency] private readonly StackSystem _stack = default!;
@@ -103,7 +101,6 @@ public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
                 {
                     ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
                 }
-                _artifact.ForceActivateArtifact(contained);
             }
 
             if (!TryComp<BodyComponent>(contained, out var body))
index b388f3a6d49ae1f531297b6962d137a407002079..2f8cd667367c648a71199ce457aaf95d26053a89 100644 (file)
@@ -1,63 +1,13 @@
-using Content.Server.Popups;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
-using Content.Shared.Interaction;
-using Content.Shared.Timing;
-using Content.Shared.Verbs;
+using Content.Shared.Xenoarchaeology.Equipment;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
 
 namespace Content.Server.Xenoarchaeology.Equipment.Systems;
 
-public sealed class NodeScannerSystem : EntitySystem
+/// <inheritdoc cref="SharedNodeScannerSystem"/>
+public sealed class NodeScannerSystem : SharedNodeScannerSystem
 {
-    [Dependency] private readonly UseDelaySystem _useDelay = default!;
-    [Dependency] private readonly PopupSystem _popupSystem = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
-        SubscribeLocalEvent<NodeScannerComponent, GetVerbsEvent<UtilityVerb>>(AddScanVerb);
-    }
-
-    private void OnBeforeRangedInteract(EntityUid uid, NodeScannerComponent component, BeforeRangedInteractEvent args)
-    {
-        if (args.Handled || !args.CanReach || args.Target is not {} target)
-            return;
-
-        if (!TryComp<ArtifactComponent>(target, out var artifact) || artifact.CurrentNodeId == null)
-            return;
-
-        CreatePopup(uid, target, artifact);
-        args.Handled = true;
-    }
-
-    private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent<UtilityVerb> args)
-    {
-        if (!args.CanAccess)
-            return;
-
-        if (!TryComp<ArtifactComponent>(args.Target, out var artifact) || artifact.CurrentNodeId == null)
-            return;
-
-        var verb = new UtilityVerb()
-        {
-            Act = () =>
-            {
-                CreatePopup(uid, args.Target, artifact);
-            },
-            Text = Loc.GetString("node-scan-tooltip")
-        };
-
-        args.Verbs.Add(verb);
-    }
-
-    private void CreatePopup(EntityUid uid, EntityUid target, ArtifactComponent artifact)
+    protected override void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor)
     {
-        if (TryComp(uid, out UseDelayComponent? useDelay)
-            && !_useDelay.TryResetDelay((uid, useDelay), true))
-            return;
-
-        _popupSystem.PopupEntity(Loc.GetString("node-scan-popup",
-            ("id", $"{artifact.CurrentNodeId}")), target);
+        // no-op
     }
 }
diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs
deleted file mode 100644 (file)
index d277792..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-using Content.Server.Popups;
-using Content.Server.Power.EntitySystems;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Shared.Examine;
-using Content.Shared.Interaction;
-using Content.Shared.Placeable;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Xenoarchaeology.Equipment.Systems;
-
-public sealed class TraversalDistorterSystem : EntitySystem
-{
-    [Dependency] private readonly IGameTiming _timing = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<TraversalDistorterComponent, MapInitEvent>(OnInit);
-        SubscribeLocalEvent<TraversalDistorterComponent, ExaminedEvent>(OnExamine);
-
-        SubscribeLocalEvent<TraversalDistorterComponent, ItemPlacedEvent>(OnItemPlaced);
-        SubscribeLocalEvent<TraversalDistorterComponent, ItemRemovedEvent>(OnItemRemoved);
-    }
-
-    private void OnInit(EntityUid uid, TraversalDistorterComponent component, MapInitEvent args)
-    {
-        component.NextActivation = _timing.CurTime;
-    }
-
-    /// <summary>
-    /// Switches the state of the traversal distorter between up and down.
-    /// </summary>
-    /// <param name="uid">The distorter's entity</param>
-    /// <param name="component">The component on the entity</param>
-    /// <returns>If the distorter changed state</returns>
-    public bool SetState(EntityUid uid, TraversalDistorterComponent component, bool isDown)
-    {
-        if (!this.IsPowered(uid, EntityManager))
-            return false;
-
-        if (_timing.CurTime < component.NextActivation)
-            return false;
-
-        component.NextActivation = _timing.CurTime + component.ActivationDelay;
-
-        component.BiasDirection = isDown ? BiasDirection.Down : BiasDirection.Up;
-
-        return true;
-    }
-
-    private void OnExamine(EntityUid uid, TraversalDistorterComponent component, ExaminedEvent args)
-    {
-        string examine = string.Empty;
-        switch (component.BiasDirection)
-        {
-            case BiasDirection.Up:
-                examine = Loc.GetString("traversal-distorter-desc-up");
-                break;
-            case BiasDirection.Down:
-                examine = Loc.GetString("traversal-distorter-desc-down");
-                break;
-        }
-
-        args.PushMarkup(examine);
-    }
-
-    private void OnItemPlaced(EntityUid uid, TraversalDistorterComponent component, ref ItemPlacedEvent args)
-    {
-        var bias = EnsureComp<BiasedArtifactComponent>(args.OtherEntity);
-        bias.Provider = uid;
-    }
-
-    private void OnItemRemoved(EntityUid uid, TraversalDistorterComponent component, ref ItemRemovedEvent args)
-    {
-        var otherEnt = args.OtherEntity;
-        if (TryComp<BiasedArtifactComponent>(otherEnt, out var bias) && bias.Provider == uid)
-            RemComp(otherEnt, bias);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs
deleted file mode 100644 (file)
index 4afd8af..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-[RegisterComponent, Access(typeof(ArtifactSystem))]
-public sealed partial class ArtifactComponent : Component
-{
-    /// <summary>
-    /// Every node contained in the tree
-    /// </summary>
-    [DataField("nodeTree"), ViewVariables]
-    public List<ArtifactNode> NodeTree = new();
-
-    /// <summary>
-    /// The current node the artifact is on.
-    /// </summary>
-    [DataField("currentNodeId"), ViewVariables]
-    public int? CurrentNodeId;
-
-    #region Node Tree Gen
-    /// <summary>
-    /// Minimum number of nodes to generate, inclusive
-    /// </summary>
-    [DataField("nodesMin")]
-    public int NodesMin = 3;
-
-    /// <summary>
-    /// Maximum number of nodes to generate, exclusive
-    /// </summary>
-    [DataField("nodesMax")]
-    public int NodesMax = 9;
-    #endregion
-
-    /// <summary>
-    /// Cooldown time between artifact activations (in seconds).
-    /// </summary>
-    [DataField("timer"), ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan CooldownTime = TimeSpan.FromSeconds(5);
-
-    /// <summary>
-    /// Is this artifact under some suppression device?
-    /// f true, will ignore all trigger activations attempts.
-    /// </summary>
-    [DataField("isSuppressed"), ViewVariables(VVAccess.ReadWrite)]
-    public bool IsSuppressed;
-
-    /// <summary>
-    /// The last time the artifact was activated.
-    /// </summary>
-    [DataField("lastActivationTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
-    public TimeSpan LastActivationTime;
-
-    /// <summary>
-    /// A multiplier applied to the calculated point value
-    /// to determine the monetary value of the artifact
-    /// </summary>
-    [DataField("priceMultiplier"), ViewVariables(VVAccess.ReadWrite)]
-    public float PriceMultiplier = 0.05f;
-
-    /// <summary>
-    /// The base amount of research points for each artifact node.
-    /// </summary>
-    [DataField("pointsPerNode"), ViewVariables(VVAccess.ReadWrite)]
-    public int PointsPerNode = 6500;
-
-    /// <summary>
-    /// Research points which have been "consumed" from the theoretical max value of the artifact.
-    /// </summary>
-    [DataField("consumedPoints"), ViewVariables(VVAccess.ReadWrite)]
-    public int ConsumedPoints;
-
-    /// <summary>
-    /// A multiplier that is raised to the power of the average depth of a node.
-    /// Used for calculating the research point value of an artifact node.
-    /// </summary>
-    [DataField("pointDangerMultiplier"), ViewVariables(VVAccess.ReadWrite)]
-    public float PointDangerMultiplier = 1.35f;
-
-    /// <summary>
-    /// The sound that plays when an artifact is activated
-    /// </summary>
-    [DataField("activationSound")]
-    public SoundSpecifier ActivationSound = new SoundCollectionSpecifier("ArtifactActivation")
-    {
-        Params = new()
-        {
-            Variation = 0.1f,
-            Volume = 3f
-        }
-    };
-
-    [DataField("activateActionEntity")] public EntityUid? ActivateActionEntity;
-}
-
-/// <summary>
-/// A single "node" of an artifact that contains various data about it.
-/// </summary>
-[DataDefinition]
-public sealed partial class ArtifactNode : ICloneable
-{
-    /// <summary>
-    /// A numeric id corresponding to each node.
-    /// </summary>
-    [DataField("id"), ViewVariables]
-    public int Id;
-
-    /// <summary>
-    /// how "deep" into the node tree. used for generation and price/value calculations
-    /// </summary>
-    [DataField("depth"), ViewVariables]
-    public int Depth;
-
-    /// <summary>
-    /// A list of surrounding nodes. Used for tree traversal
-    /// </summary>
-    [DataField("edges"), ViewVariables]
-    public HashSet<int> Edges = new();
-
-    /// <summary>
-    /// Whether or not the node has been entered
-    /// </summary>
-    [DataField("discovered"), ViewVariables(VVAccess.ReadWrite)]
-    public bool Discovered;
-
-    /// <summary>
-    /// The trigger for the node
-    /// </summary>
-    [DataField("trigger", customTypeSerializer: typeof(PrototypeIdSerializer<ArtifactTriggerPrototype>), required: true), ViewVariables]
-    public string Trigger = default!;
-
-    /// <summary>
-    /// Whether or not the node has been triggered
-    /// </summary>
-    [DataField("triggered"), ViewVariables(VVAccess.ReadWrite)]
-    public bool Triggered;
-
-    /// <summary>
-    /// The effect when the node is activated
-    /// </summary>
-    [DataField("effect", customTypeSerializer: typeof(PrototypeIdSerializer<ArtifactEffectPrototype>), required: true), ViewVariables]
-    public string Effect = default!;
-
-    /// <summary>
-    /// Used for storing cumulative information about nodes
-    /// </summary>
-    [DataField("nodeData"), ViewVariables]
-    public Dictionary<string, object> NodeData = new();
-
-    public object Clone()
-    {
-        return new ArtifactNode
-        {
-            Id = Id,
-            Depth = Depth,
-            Edges = Edges,
-            Discovered = Discovered,
-            Trigger = Trigger,
-            Triggered = Triggered,
-            Effect = Effect,
-            NodeData = NodeData
-        };
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Actions.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Actions.cs
deleted file mode 100644 (file)
index 6c4089a..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-using Content.Server.Actions;
-using Content.Server.Popups;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public partial class ArtifactSystem
-{
-    [Dependency] private readonly ActionsSystem _actions = default!;
-    [Dependency] private readonly PopupSystem _popup = default!;
-
-    [ValidatePrototypeId<EntityPrototype>] private const string ArtifactActivateActionId = "ActionArtifactActivate";
-
-    /// <summary>
-    ///     Used to add the artifact activation action (hehe), which lets sentient artifacts activate themselves,
-    ///     either through admemery or the sentience effect.
-    /// </summary>
-    public void InitializeActions()
-    {
-        SubscribeLocalEvent<ArtifactComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<ArtifactComponent, ComponentRemove>(OnRemove);
-
-        SubscribeLocalEvent<ArtifactComponent, ArtifactSelfActivateEvent>(OnSelfActivate);
-    }
-
-    private void OnMapInit(EntityUid uid, ArtifactComponent component, MapInitEvent args)
-    {
-        RandomizeArtifact(uid, component);
-        _actions.AddAction(uid, ref component.ActivateActionEntity, ArtifactActivateActionId);
-    }
-
-    private void OnRemove(EntityUid uid, ArtifactComponent component, ComponentRemove args)
-    {
-        _actions.RemoveAction(uid, component.ActivateActionEntity);
-    }
-
-    private void OnSelfActivate(EntityUid uid, ArtifactComponent component, ArtifactSelfActivateEvent args)
-    {
-        if (component.CurrentNodeId == null)
-            return;
-
-        var curNode = GetNodeFromId(component.CurrentNodeId.Value, component).Id;
-        _popup.PopupEntity(Loc.GetString("activate-artifact-popup-self", ("node", curNode)), uid, uid);
-        TryActivateArtifact(uid, uid, component);
-
-        args.Handled = true;
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Commands.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Commands.cs
deleted file mode 100644 (file)
index d840a1f..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-using System.Linq;
-using Content.Server.Administration;
-using Content.Shared.Administration;
-using Robust.Shared.Console;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public partial class ArtifactSystem
-{
-    [Dependency] private readonly IConsoleHost _conHost = default!;
-
-    public void InitializeCommands()
-    {
-        _conHost.RegisterCommand("forceartifactnode", "Forces an artifact to traverse to a given node", "forceartifacteffect <uid> <node ID>",
-            ForceArtifactNode,
-            ForceArtifactNodeCompletions);
-
-        _conHost.RegisterCommand("getartifactmaxvalue", "Reports the maximum research point value for a given artifact", "forceartifacteffect <uid>",
-            GetArtifactMaxValue);
-    }
-
-    [AdminCommand(AdminFlags.Fun)]
-    private void ForceArtifactNode(IConsoleShell shell, string argstr, string[] args)
-    {
-        if (args.Length != 2)
-        {
-            shell.WriteError("Argument length must be 2");
-            return;
-        }
-
-        if (!NetEntity.TryParse(args[0], out var uidNet) || !TryGetEntity(uidNet, out var uid) || !int.TryParse(args[1], out var id))
-            return;
-
-        if (!TryComp<ArtifactComponent>(uid, out var artifact))
-            return;
-
-        if (artifact.NodeTree.FirstOrDefault(n => n.Id == id) is { } node)
-        {
-            EnterNode(uid.Value, ref node);
-        }
-    }
-
-    private CompletionResult ForceArtifactNodeCompletions(IConsoleShell shell, string[] args)
-    {
-        if (args.Length == 2 && NetEntity.TryParse(args[0], out var uidNet) && TryGetEntity(uidNet, out var uid))
-        {
-            if (TryComp<ArtifactComponent>(uid, out var artifact))
-            {
-                return CompletionResult.FromHintOptions(artifact.NodeTree.Select(s => s.Id.ToString()), "<node id>");
-            }
-        }
-
-        return CompletionResult.Empty;
-    }
-
-    [AdminCommand(AdminFlags.Debug)]
-    private void GetArtifactMaxValue(IConsoleShell shell, string argstr, string[] args)
-    {
-        if (args.Length != 1)
-            shell.WriteError("Argument length must be 1");
-
-        if (!NetEntity.TryParse(args[0], out var uidNet) || !TryGetEntity(uidNet, out var uid))
-            return;
-
-        if (!TryComp<ArtifactComponent>(uid, out var artifact))
-            return;
-
-        var pointSum = GetResearchPointValue(uid.Value, artifact, true);
-        shell.WriteLine($"Max point value for {ToPrettyString(uid.Value)} with {artifact.NodeTree.Count} nodes: {pointSum}");
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs
deleted file mode 100644 (file)
index a0e8420..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-using System.Linq;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Whitelist;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using JetBrains.Annotations;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public sealed partial class ArtifactSystem
-{
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-
-    private const int MaxEdgesPerNode = 4;
-
-    private readonly HashSet<int> _usedNodeIds = new();
-
-    /// <summary>
-    /// Generate an Artifact tree with fully developed nodes.
-    /// </summary>
-    /// <param name="artifact"></param>
-    /// <param name="allNodes"></param>
-    /// <param name="nodesToCreate">The amount of nodes it has.</param>
-    private void GenerateArtifactNodeTree(EntityUid artifact, List<ArtifactNode> allNodes, int nodesToCreate)
-    {
-        if (nodesToCreate < 1)
-        {
-            Log.Error($"nodesToCreate {nodesToCreate} is less than 1. Aborting artifact tree generation.");
-            return;
-        }
-
-        _usedNodeIds.Clear();
-
-        var uninitializedNodes = new List<ArtifactNode> { new(){ Id = GetValidNodeId() } };
-        var createdNodes = 1;
-
-        while (uninitializedNodes.Count > 0)
-        {
-            var node = uninitializedNodes[0];
-            uninitializedNodes.Remove(node);
-
-            node.Trigger = GetRandomTrigger(artifact, ref node);
-            node.Effect = GetRandomEffect(artifact, ref node);
-
-            var maxChildren = _random.Next(1, MaxEdgesPerNode - 1);
-
-            for (var i = 0; i < maxChildren; i++)
-            {
-                if (nodesToCreate <= createdNodes)
-                {
-                    break;
-                }
-
-                var child = new ArtifactNode {Id = GetValidNodeId(), Depth = node.Depth + 1};
-                node.Edges.Add(child.Id);
-                child.Edges.Add(node.Id);
-
-                uninitializedNodes.Add(child);
-                createdNodes++;
-            }
-
-            allNodes.Add(node);
-        }
-    }
-
-    private int GetValidNodeId()
-    {
-        var id = _random.Next(100, 1000);
-        while (_usedNodeIds.Contains(id))
-        {
-            id = _random.Next(100, 1000);
-        }
-
-        _usedNodeIds.Add(id);
-
-        return id;
-    }
-
-    //yeah these two functions are near duplicates but i don't
-    //want to implement an interface or abstract parent
-
-    private string GetRandomTrigger(EntityUid artifact, ref ArtifactNode node)
-    {
-        var allTriggers = _prototype.EnumeratePrototypes<ArtifactTriggerPrototype>()
-            .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) &&
-            _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).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).ID;
-    }
-
-    private string GetRandomEffect(EntityUid artifact, ref ArtifactNode node)
-    {
-        var allEffects = _prototype.EnumeratePrototypes<ArtifactEffectPrototype>()
-            .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) &&
-            _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).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).ID;
-    }
-
-    /// <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)
-    {
-        // this function is just a normal distribution with a
-        // mean of target depth and standard deviation of 0.75
-        var weights = new Dictionary<int, float>();
-        foreach (var d in depths)
-        {
-            var w = 10f / (0.75f * MathF.Sqrt(2 * MathF.PI)) * MathF.Pow(MathF.E, -MathF.Pow((d - targetDepth) / 0.75f, 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.CurrentNodeId != null)
-        {
-            ExitNode(uid, component);
-        }
-
-        component.CurrentNodeId = node.Id;
-
-        var trigger = _prototype.Index<ArtifactTriggerPrototype>(node.Trigger);
-        var effect = _prototype.Index<ArtifactEffectPrototype>(node.Effect);
-
-        var allComponents = effect.Components.Concat(effect.PermanentComponents).Concat(trigger.Components);
-        foreach (var (name, entry) in allComponents)
-        {
-            var reg = _componentFactory.GetRegistration(name);
-
-            if (node.Discovered && EntityManager.HasComponent(uid, reg.Type))
-            {
-                // Don't re-add permanent components unless this is the first time you've entered this node
-                if (effect.PermanentComponents.ContainsKey(name))
-                    continue;
-
-                EntityManager.RemoveComponent(uid, reg.Type);
-            }
-
-            var comp = (Component)_componentFactory.GetComponent(reg);
-
-            var temp = (object)comp;
-            _serialization.CopyTo(entry.Component, ref temp);
-            EntityManager.RemoveComponent(uid, temp!.GetType());
-            EntityManager.AddComponent(uid, (Component)temp!);
-        }
-
-        node.Discovered = true;
-        RaiseLocalEvent(uid, new ArtifactNodeEnteredEvent(component.CurrentNodeId.Value));
-    }
-
-    /// <summary>
-    /// Exit a node: remove the relevant components.
-    /// </summary>
-    private void ExitNode(EntityUid uid, ArtifactComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-
-        if (component.CurrentNodeId == null)
-            return;
-        var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
-        var trigger = _prototype.Index<ArtifactTriggerPrototype>(currentNode.Trigger);
-        var effect = _prototype.Index<ArtifactEffectPrototype>(currentNode.Effect);
-
-        var entityPrototype = MetaData(uid).EntityPrototype;
-        var toRemove = effect.Components.Keys.Concat(trigger.Components.Keys).ToList();
-
-        foreach (var name in toRemove)
-        {
-            // if the entity prototype contained the component originally
-            if (entityPrototype?.Components.TryGetComponent(name, out var entry) ?? false)
-            {
-                var comp = (Component)_componentFactory.GetComponent(name);
-                var temp = (object)comp;
-                _serialization.CopyTo(entry, ref temp);
-                EntityManager.RemoveComponent(uid, temp!.GetType());
-                EntityManager.AddComponent(uid, (Component)temp);
-                continue;
-            }
-
-            EntityManager.RemoveComponentDeferred(uid, _componentFactory.GetRegistration(name).Type);
-        }
-        component.CurrentNodeId = null;
-    }
-
-    [PublicAPI]
-    public ArtifactNode GetNodeFromId(int id, ArtifactComponent component)
-    {
-        return component.NodeTree.First(x => x.Id == id);
-    }
-
-    [PublicAPI]
-    public ArtifactNode GetNodeFromId(int id, IEnumerable<ArtifactNode> nodes)
-    {
-        return nodes.First(x => x.Id == id);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs
deleted file mode 100644 (file)
index 6ddcd56..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using Content.Server.Cargo.Systems;
-using Content.Server.GameTicking;
-using Content.Server.Power.EntitySystems;
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.CCVar;
-using Content.Shared.Xenoarchaeology.XenoArtifacts;
-using JetBrains.Annotations;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Serialization.Manager;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts;
-
-public sealed partial class ArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IComponentFactory _componentFactory = default!;
-    [Dependency] private readonly IGameTiming _gameTiming = default!;
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly ISerializationManager _serialization = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ArtifactComponent, PriceCalculationEvent>(GetPrice);
-
-        InitializeCommands();
-        InitializeActions();
-    }
-
-    /// <summary>
-    /// Calculates the price of an artifact based on
-    /// how many nodes have been unlocked/triggered
-    /// </summary>
-    /// <remarks>
-    /// General balancing (for fully unlocked artifacts):
-    /// Simple (1-2 Nodes): 1-2K
-    /// Medium (5-8 Nodes): 6-7K
-    /// Complex (7-12 Nodes): 10-11K
-    /// </remarks>
-    private void GetPrice(EntityUid uid, ArtifactComponent component, ref PriceCalculationEvent args)
-    {
-        args.Price += (GetResearchPointValue(uid, component) + component.ConsumedPoints) * component.PriceMultiplier;
-    }
-
-    /// <summary>
-    /// Calculates how many research points the artifact is worth
-    /// </summary>
-    /// <remarks>
-    /// General balancing (for fully unlocked artifacts):
-    /// Simple (1-2 Nodes): ~10K
-    /// Medium (5-8 Nodes): ~30-40K
-    /// Complex (7-12 Nodes): ~60-80K
-    ///
-    /// Simple artifacts should be enough to unlock a few techs.
-    /// Medium should get you partway through a tree.
-    /// Complex should get you through a full tree and then some.
-    /// </remarks>
-    public int GetResearchPointValue(EntityUid uid, ArtifactComponent? component = null, bool getMaxPrice = false)
-    {
-        if (!Resolve(uid, ref component))
-            return 0;
-
-        var sumValue = component.NodeTree.Sum(n => GetNodePointValue(n, component, getMaxPrice));
-        var fullyExploredBonus = component.NodeTree.All(x => x.Triggered) || getMaxPrice ? 1.25f : 1;
-
-        return (int) (sumValue * fullyExploredBonus) - component.ConsumedPoints;
-    }
-
-    /// <summary>
-    /// Adjusts how many points on the artifact have been consumed
-    /// </summary>
-    public void AdjustConsumedPoints(EntityUid uid, int amount, ArtifactComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-
-        component.ConsumedPoints += amount;
-    }
-
-    /// <summary>
-    /// Sets whether or not the artifact is suppressed,
-    /// preventing it from activating
-    /// </summary>
-    public void SetIsSuppressed(EntityUid uid, bool suppressed, ArtifactComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-
-        component.IsSuppressed = suppressed;
-    }
-
-    /// <summary>
-    /// Gets the point value for an individual node
-    /// </summary>
-    private float GetNodePointValue(ArtifactNode node, ArtifactComponent component, bool getMaxPrice = false)
-    {
-        var valueDeduction = 1f;
-        if (!getMaxPrice)
-        {
-            if (!node.Discovered)
-                return 0;
-
-            valueDeduction = !node.Triggered ? 0.25f : 1;
-        }
-
-        var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(node.Trigger);
-        var effectProto = _prototype.Index<ArtifactEffectPrototype>(node.Effect);
-
-        var nodeDanger = (node.Depth + effectProto.TargetDepth + triggerProto.TargetDepth) / 3;
-        return component.PointsPerNode * MathF.Pow(component.PointDangerMultiplier, nodeDanger) * valueDeduction;
-    }
-
-    /// <summary>
-    /// Randomize a given artifact.
-    /// </summary>
-    [PublicAPI]
-    public void RandomizeArtifact(EntityUid uid, ArtifactComponent component)
-    {
-        var nodeAmount = _random.Next(component.NodesMin, component.NodesMax);
-
-        GenerateArtifactNodeTree(uid, component.NodeTree, nodeAmount);
-        var firstNode = GetRootNode(component.NodeTree);
-        EnterNode(uid, ref firstNode, component);
-    }
-
-    /// <summary>
-    /// Tries to activate the artifact
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="user"></param>
-    /// <param name="component"></param>
-    /// <param name="logMissing">Set this to false if you don't know if the entity is an artifact.</param>
-    /// <returns></returns>
-    public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null, bool logMissing = true)
-    {
-        if (!Resolve(uid, ref component, logMissing))
-            return false;
-
-        // check if artifact is under suppression field
-        if (component.IsSuppressed)
-            return false;
-
-        // check if artifact isn't under cooldown
-        var timeDif = _gameTiming.CurTime - component.LastActivationTime;
-        if (timeDif < component.CooldownTime)
-            return false;
-
-        ForceActivateArtifact(uid, user, component);
-        return true;
-    }
-
-    /// <summary>
-    /// Forces an artifact to activate
-    /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="user"></param>
-    /// <param name="component"></param>
-    public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-        if (component.CurrentNodeId == null)
-            return;
-
-        _audio.PlayPvs(component.ActivationSound, uid);
-        component.LastActivationTime = _gameTiming.CurTime;
-
-        var ev = new ArtifactActivatedEvent
-        {
-            Activator = user
-        };
-        RaiseLocalEvent(uid, ev, true);
-
-        var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
-        currentNode.Triggered = true;
-        if (currentNode.Edges.Count == 0)
-            return;
-
-        var newNode = GetNewNode(uid, component);
-        if (newNode == null)
-            return;
-
-        EnterNode(uid, ref newNode, component);
-    }
-
-    private ArtifactNode? GetNewNode(EntityUid uid, ArtifactComponent component)
-    {
-        if (component.CurrentNodeId == null)
-            return null;
-
-        var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
-        var allNodes = currentNode.Edges;
-        Log.Debug($"our node: {currentNode.Id}");
-        Log.Debug($"other nodes: {string.Join(", ", allNodes)}");
-
-        if (TryComp<BiasedArtifactComponent>(uid, out var bias) &&
-            TryComp<TraversalDistorterComponent>(bias.Provider, out var trav) &&
-            this.IsPowered(bias.Provider, EntityManager))
-        {
-            switch (trav.BiasDirection)
-            {
-                case BiasDirection.Up:
-                    var upNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth < currentNode.Depth).ToHashSet();
-                    if (upNodes.Count != 0)
-                        allNodes = upNodes;
-                    break;
-                case BiasDirection.Down:
-                    var downNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth > currentNode.Depth).ToHashSet();
-                    if (downNodes.Count != 0)
-                        allNodes = downNodes;
-                    break;
-            }
-        }
-
-        var undiscoveredNodes = allNodes.Where(x => !GetNodeFromId(x, component).Discovered).ToList();
-        Log.Debug($"Undiscovered nodes: {string.Join(", ", undiscoveredNodes)}");
-        var newNode = _random.Pick(allNodes);
-
-        if (undiscoveredNodes.Count != 0 && _random.Prob(0.75f))
-        {
-            newNode = _random.Pick(undiscoveredNodes);
-        }
-
-        Log.Debug($"Going to node {newNode}");
-
-        return GetNodeFromId(newNode, component);
-    }
-
-    /// <summary>
-    /// Try and get a data object from a node
-    /// </summary>
-    /// <param name="uid">The entity you're getting the data from</param>
-    /// <param name="key">The data's key</param>
-    /// <param name="data">The data you are trying to get.</param>
-    /// <param name="component"></param>
-    /// <typeparam name="T"></typeparam>
-    /// <returns></returns>
-    public bool TryGetNodeData<T>(EntityUid uid, string key, [NotNullWhen(true)] out T? data, ArtifactComponent? component = null)
-    {
-        data = default;
-
-        if (!Resolve(uid, ref component))
-            return false;
-
-        if (component.CurrentNodeId == null)
-            return false;
-        var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
-        if (currentNode.NodeData.TryGetValue(key, out var dat) && dat is T value)
-        {
-            data = value;
-            return true;
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    /// Sets the node data to a certain value
-    /// </summary>
-    /// <param name="uid">The artifact</param>
-    /// <param name="key">The key being set</param>
-    /// <param name="value">The value it's being set to</param>
-    /// <param name="component"></param>
-    public void SetNodeData(EntityUid uid, string key, object value, ArtifactComponent? component = null)
-    {
-        if (!Resolve(uid, ref component))
-            return;
-
-        if (component.CurrentNodeId == null)
-            return;
-        var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
-
-        currentNode.NodeData[key] = value;
-    }
-
-    /// <summary>
-    /// Gets the base node (depth 0) of an artifact's node graph
-    /// </summary>
-    /// <param name="allNodes"></param>
-    /// <returns></returns>
-    public ArtifactNode GetRootNode(List<ArtifactNode> allNodes)
-    {
-        return allNodes.First(n => n.Depth == 0);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ChargeBatteryArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ChargeBatteryArtifactComponent.cs
deleted file mode 100644 (file)
index a46dd9e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for recharging all nearby batteries when activated
-/// </summary>
-[RegisterComponent]
-public sealed partial class ChargeBatteryArtifactComponent : Component
-{
-    /// <summary>
-    /// The radius of entities that will be affected
-    /// </summary>
-    [DataField("radius")]
-    public float Radius = 15f;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ChemicalPuddleArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ChemicalPuddleArtifactComponent.cs
deleted file mode 100644 (file)
index 3065b1c..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for an artifact that creates a puddle of
-/// random chemicals upon being triggered.
-/// </summary>
-[RegisterComponent, Access(typeof(ChemicalPuddleArtifactSystem))]
-public sealed partial class ChemicalPuddleArtifactComponent : Component
-{
-    /// <summary>
-    /// The solution where all the chemicals are stored
-    /// </summary>
-    [DataField("chemicalSolution", required: true), ViewVariables(VVAccess.ReadWrite)]
-    public Solution ChemicalSolution = default!;
-
-    /// <summary>
-    /// The different chemicals that can be spawned by this effect
-    /// </summary>
-    [DataField("possibleChemicals", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
-    public List<string> PossibleChemicals = default!;
-
-    /// <summary>
-    /// The number of chemicals in the puddle
-    /// </summary>
-    [DataField("chemAmount")]
-    public int ChemAmount = 3;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/EmpArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/EmpArtifactComponent.cs
deleted file mode 100644 (file)
index d00e65e..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Artifact that EMP
-/// </summary>
-[RegisterComponent]
-[Access(typeof(EmpArtifactSystem))]
-public sealed partial class EmpArtifactComponent : Component
-{
-    [DataField("range"), ViewVariables(VVAccess.ReadWrite)]
-    public float Range = 4f;
-
-    [DataField("energyConsumption"), ViewVariables(VVAccess.ReadWrite)]
-    public float EnergyConsumption = 1000000;
-
-    [DataField("disableDuration"), ViewVariables(VVAccess.ReadWrite)]
-    public float DisableDuration = 60f;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs
deleted file mode 100644 (file)
index dc6fd06..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Generates foam from the artifact when activated
-/// </summary>
-[RegisterComponent, Access(typeof(FoamArtifactSystem))]
-public sealed partial class FoamArtifactComponent : Component
-{
-    /// <summary>
-    /// The list of reagents that will randomly be picked from
-    /// to choose the foam reagent
-    /// </summary>
-    [DataField("reagents", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
-    public List<string> Reagents = new();
-
-    /// <summary>
-    /// The foam reagent
-    /// </summary>
-    [DataField("selectedReagent"), ViewVariables(VVAccess.ReadWrite)]
-    public string? SelectedReagent;
-
-    /// <summary>
-    /// How long does the foam last?
-    /// </summary>
-    [DataField("duration"), ViewVariables(VVAccess.ReadWrite)]
-    public float Duration = 10;
-
-    /// <summary>
-    /// How much reagent is in the foam?
-    /// </summary>
-    [DataField("reagentAmount"), ViewVariables(VVAccess.ReadWrite)]
-    public float ReagentAmount = 100;
-
-    /// <summary>
-    /// Minimum radius of foam spawned
-    /// </summary>
-    [DataField("minFoamAmount"), ViewVariables(VVAccess.ReadWrite)]
-    public int MinFoamAmount = 15;
-
-    /// <summary>
-    /// Maximum radius of foam spawned
-    /// </summary>
-    [DataField("maxFoamAmount"), ViewVariables(VVAccess.ReadWrite)]
-    public int MaxFoamAmount = 20;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs
deleted file mode 100644 (file)
index ee12326..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-using Content.Shared.Atmos;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-///     Spawn a random gas with random temperature when artifact activated.
-/// </summary>
-[RegisterComponent]
-public sealed partial class GasArtifactComponent : Component
-{
-    /// <summary>
-    ///     Gas that will be spawned when artifact activated.
-    ///     If null it will be picked on startup from <see cref="PossibleGases"/>.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("spawnGas")]
-    public Gas? SpawnGas;
-
-    /// <summary>
-    ///     List of possible activation gases to pick on startup.
-    /// </summary>
-    [DataField("possibleGas")]
-    public List<Gas> PossibleGases = new()
-    {
-        Gas.Oxygen,
-        Gas.Plasma,
-        Gas.Nitrogen,
-        Gas.CarbonDioxide,
-        Gas.Tritium,
-        Gas.Ammonia,
-        Gas.NitrousOxide,
-        Gas.Frezon
-    };
-
-    /// <summary>
-    ///     Temperature of spawned gas. If null it will be picked on startup from range from
-    ///     <see cref="MinRandomTemperature"/> to <see cref="MaxRandomTemperature"/>.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("spawnTemperature")]
-    public float? SpawnTemperature;
-
-    [DataField("minRandomTemp")]
-    public float MinRandomTemperature = 100;
-
-    [DataField("maxRandomTemp")]
-    public float MaxRandomTemperature = 400;
-
-    /// <summary>
-    ///     Max allowed external atmospheric pressure.
-    ///     Artifact will stop spawn gas.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("maxExternalPressure")]
-    public float MaxExternalPressure = Atmospherics.GasMinerDefaultMaxExternalPressure;
-
-    /// <summary>
-    ///     Moles of gas to spawn each time when artifact activated.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("spawnAmount")]
-    public float SpawnAmount = Atmospherics.MolesCellStandard * 3;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/IgniteArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/IgniteArtifactComponent.cs
deleted file mode 100644 (file)
index 436790a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// Artifact that ignites surrounding entities when triggered.
-/// </summary>
-[RegisterComponent]
-public sealed partial class IgniteArtifactComponent : Component
-{
-    [DataField("range")]
-    public float Range = 2f;
-
-    [DataField("minFireStack")]
-    public int MinFireStack = 2;
-
-    [DataField("maxFireStack")]
-    public int MaxFireStack = 5;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs
deleted file mode 100644 (file)
index 4db2f8e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for using the "knock" spell when the artifact is activated
-/// </summary>
-[RegisterComponent]
-public sealed partial class KnockArtifactComponent : Component
-{
-    /// <summary>
-    /// The range of the spell
-    /// </summary>
-    [DataField("knockRange")]
-    public float KnockRange = 4f;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PhasingArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PhasingArtifactComponent.cs
deleted file mode 100644 (file)
index 4857233..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-///     Removes the masks/layers of hard fixtures from the artifact when added, allowing it to pass through walls
-///     and such.
-/// </summary>
-[RegisterComponent]
-public sealed partial class PhasingArtifactComponent : Component
-{
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PortalArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/PortalArtifactComponent.cs
deleted file mode 100644 (file)
index f64b95d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-///     When activated artifact will spawn an pair portals. First - right in artifact, Second - at random point of station.
-/// </summary>
-[RegisterComponent, Access(typeof(PortalArtifactSystem))]
-public sealed partial class PortalArtifactComponent : Component
-{
-    [DataField]
-    public EntProtoId PortalProto = "PortalArtifact";
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomInstrumentArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomInstrumentArtifactComponent.cs
deleted file mode 100644 (file)
index 28e9158..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-[RegisterComponent]
-public sealed partial class RandomInstrumentArtifactComponent : Component
-{
-
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs
deleted file mode 100644 (file)
index 4b71213..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// When activated, will shuffle the position of all players
-/// within a certain radius.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ShuffleArtifactComponent : Component
-{
-    [DataField("radius")]
-    public float Radius = 7.5f;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs
deleted file mode 100644 (file)
index 6fcd8b3..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-using Content.Shared.Storage;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-///     When activated artifact will spawn an entity from prototype.
-///     It could be an angry mob or some random item.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SpawnArtifactComponent : Component
-{
-    [DataField("spawns")]
-    public List<EntitySpawnEntry>? Spawns;
-
-    /// <summary>
-    /// The range around the artifact that it will spawn the entity
-    /// </summary>
-    [DataField("range")]
-    public float Range = 0.5f;
-
-    /// <summary>
-    /// The maximum number of times the spawn will occur
-    /// </summary>
-    [DataField("maxSpawns")]
-    public int MaxSpawns = 10;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TriggerArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TriggerArtifactComponent.cs
deleted file mode 100644 (file)
index 9194cde..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-
-/// <summary>
-/// This is used for an artifact that triggers when activated.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerArtifactComponent : Component
-{
-
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ChargeBatteryArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ChargeBatteryArtifactSystem.cs
deleted file mode 100644 (file)
index 778c672..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-using Content.Server.Power.Components;
-using Content.Server.Power.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Server.GameObjects;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles <see cref="ChargeBatteryArtifactComponent"/>
-/// </summary>
-public sealed class ChargeBatteryArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly BatterySystem _battery = default!;
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
-    [Dependency] private readonly TransformSystem _transform = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ChargeBatteryArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnActivated(EntityUid uid, ChargeBatteryArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        foreach (var battery in _lookup.GetEntitiesInRange<BatteryComponent>(_transform.GetMapCoordinates(uid), component.Radius))
-        {
-            _battery.SetCharge(battery, battery.Comp.MaxCharge, battery);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ChemicalPuddleArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ChemicalPuddleArtifactSystem.cs
deleted file mode 100644 (file)
index 542d8bb..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles <see cref="ChemicalPuddleArtifactComponent"/>
-/// </summary>
-public sealed class ChemicalPuddleArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-    [Dependency] private readonly PuddleSystem _puddle = default!;
-
-    /// <summary>
-    /// The key for the node data entry containing
-    /// the chemicals that the puddle is made of.
-    /// </summary>
-    public const string NodeDataChemicalList = "nodeDataChemicalList";
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ChemicalPuddleArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnActivated(EntityUid uid, ChemicalPuddleArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        if (!TryComp<ArtifactComponent>(uid, out var artifact))
-            return;
-
-        if (!_artifact.TryGetNodeData(uid, NodeDataChemicalList, out List<string>? chemicalList, artifact))
-        {
-            chemicalList = new();
-            for (var i = 0; i < component.ChemAmount; i++)
-            {
-                var chemProto = _random.Pick(component.PossibleChemicals);
-                chemicalList.Add(chemProto);
-            }
-
-            _artifact.SetNodeData(uid, NodeDataChemicalList, chemicalList, artifact);
-        }
-
-        var amountPerChem = component.ChemicalSolution.MaxVolume / component.ChemAmount;
-        foreach (var reagent in chemicalList)
-        {
-            component.ChemicalSolution.AddReagent(reagent, amountPerChem);
-        }
-
-        _puddle.TrySpillAt(uid, component.ChemicalSolution, out _);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs
deleted file mode 100644 (file)
index f231120..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Damage;
-using Content.Shared.Whitelist;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class BreakWindowArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
-    [Dependency] private readonly DamageableSystem _damageable = default!;
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<DamageNearbyArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnActivated(EntityUid uid, DamageNearbyArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var ents = _lookup.GetEntitiesInRange(uid, component.Radius);
-        if (args.Activator != null)
-            ents.Add(args.Activator.Value);
-        foreach (var ent in ents)
-        {
-            if (_whitelistSystem.IsWhitelistFail(component.Whitelist, ent))
-                continue;
-
-            if (!_random.Prob(component.DamageChance))
-                return;
-
-            _damageable.TryChangeDamage(ent, component.Damage, component.IgnoreResistances);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/EmpArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/EmpArtifactSystem.cs
deleted file mode 100644 (file)
index 970743f..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using Content.Server.Emp;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Server.GameObjects;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class EmpArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly EmpSystem _emp = default!;
-    [Dependency] private readonly TransformSystem _transform = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<EmpArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    private void OnActivate(EntityUid uid, EmpArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        _emp.EmpPulse(_transform.GetMapCoordinates(uid), component.Range, component.EnergyConsumption, component.DisableDuration);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs
deleted file mode 100644 (file)
index 2d2c230..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-using System.Linq;
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Chemistry.Components;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class FoamArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly SmokeSystem _smoke = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<FoamArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
-        SubscribeLocalEvent<FoamArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnNodeEntered(EntityUid uid, FoamArtifactComponent component, ArtifactNodeEnteredEvent args)
-    {
-        if (!component.Reagents.Any())
-            return;
-
-        component.SelectedReagent = component.Reagents[args.RandomSeed % component.Reagents.Count];
-    }
-
-    private void OnActivated(EntityUid uid, FoamArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        if (component.SelectedReagent == null)
-            return;
-
-        var sol = new Solution();
-        var xform = Transform(uid);
-        var range = (int) MathF.Round(MathHelper.Lerp(component.MinFoamAmount, component.MaxFoamAmount, _random.NextFloat(0, 1f)));
-        sol.AddReagent(component.SelectedReagent, component.ReagentAmount);
-        var foamEnt = Spawn("Foam", xform.Coordinates);
-        var spreadAmount = range * 4;
-        _smoke.StartSmoke(foamEnt, sol, component.Duration, spreadAmount);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs
deleted file mode 100644 (file)
index dc054d2..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-using Content.Server.Atmos;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Atmos;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class GasArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<GasArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
-        SubscribeLocalEvent<GasArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    private void OnNodeEntered(EntityUid uid, GasArtifactComponent component, ArtifactNodeEnteredEvent args)
-    {
-        if (component.SpawnGas == null && component.PossibleGases.Count != 0)
-        {
-            var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count];
-            component.SpawnGas = gas;
-        }
-
-        if (component.SpawnTemperature == null)
-        {
-            var temp = args.RandomSeed % component.MaxRandomTemperature - component.MinRandomTemperature +
-                       component.MinRandomTemperature;
-            component.SpawnTemperature = temp;
-        }
-    }
-
-    private void OnActivate(EntityUid uid, GasArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        if (component.SpawnGas == null || component.SpawnTemperature == null)
-            return;
-
-        var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
-        if (environment == null)
-            return;
-
-        if (environment.Pressure >= component.MaxExternalPressure)
-            return;
-
-        var merger = new GasMixture(1) { Temperature = component.SpawnTemperature.Value };
-        merger.SetMoles(component.SpawnGas.Value, component.SpawnAmount);
-
-        _atmosphereSystem.Merge(environment, merger);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/IgniteArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/IgniteArtifactSystem.cs
deleted file mode 100644 (file)
index 0d6480a..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Linq;
-using Content.Server.Atmos.Components;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class IgniteArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
-    [Dependency] private readonly FlammableSystem _flammable = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<IgniteArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    private void OnActivate(EntityUid uid, IgniteArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var flammable = GetEntityQuery<FlammableComponent>();
-        foreach (var target in _lookup.GetEntitiesInRange(uid, component.Range))
-        {
-            if (!flammable.TryGetComponent(target, out var fl))
-                continue;
-            fl.FireStacks += _random.Next(component.MinFireStack, component.MaxFireStack);
-            _flammable.Ignite(target, uid, fl);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs
deleted file mode 100644 (file)
index 0245b22..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Magic.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class KnockArtifactSystem : EntitySystem
-{
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<KnockArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnActivated(EntityUid uid, KnockArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var ev = new KnockSpellEvent
-        {
-            Performer = uid,
-            Range = component.KnockRange
-        };
-        RaiseLocalEvent(ev);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs
deleted file mode 100644 (file)
index 52d9fb0..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-using Content.Server.Ghost;
-using Content.Server.Light.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles...
-/// </summary>
-public sealed class LightFlickerArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
-    [Dependency] private readonly GhostSystem _ghost = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<LightFlickerArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnActivated(EntityUid uid, LightFlickerArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var lights = GetEntityQuery<PoweredLightComponent>();
-        foreach (var light in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.StaticSundries ))
-        {
-            if (!lights.HasComponent(light))
-                continue;
-
-            if (!_random.Prob(component.FlickerChance))
-                continue;
-
-            _ghost.DoGhostBooEvent(light);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PhasingArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PhasingArtifactSystem.cs
deleted file mode 100644 (file)
index d07da6a..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Physics;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Physics.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-///     Handles allowing activated artifacts to phase through walls.
-/// </summary>
-public sealed class PhasingArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<PhasingArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    private void OnActivate(EntityUid uid, PhasingArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        if (!TryComp<FixturesComponent>(uid, out var fixtures))
-            return;
-
-        foreach (var fixture in fixtures.Fixtures.Values)
-        {
-            _physics.SetHard(uid, fixture, false, fixtures);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PolyOthersArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PolyOthersArtifactSystem.cs
deleted file mode 100644 (file)
index 3831bea..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Server.Polymorph.Systems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Humanoid;
-using Content.Shared.Mobs.Systems;
-using Robust.Shared.Audio.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class PolyOthersArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
-    [Dependency] private readonly MobStateSystem _mob = default!;
-    [Dependency] private readonly PolymorphSystem _poly = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
-
-    /// <summary>
-    /// On effect trigger polymorphs targets in range.
-    /// </summary>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<PolyOthersArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    /// <summary>
-    /// Provided target is alive and is not a zombie, polymorphs the target.
-    /// </summary>
-    private void OnActivate(Entity<PolyOthersArtifactComponent> ent, ref ArtifactActivatedEvent args)
-    {
-        var xform = Transform(ent);
-        var humanoids = new HashSet<Entity<HumanoidAppearanceComponent>>();
-        _lookup.GetEntitiesInRange(xform.Coordinates, ent.Comp.Range, humanoids);
-
-        foreach (var comp in humanoids)
-        {
-            var target = comp.Owner;
-            if (_mob.IsAlive(target))
-            {
-                _poly.PolymorphEntity(target, ent.Comp.PolymorphPrototypeName);
-                _audio.PlayPvs(ent.Comp.PolySound, ent);
-            }
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomInstrumentArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomInstrumentArtifactSystem.cs
deleted file mode 100644 (file)
index 118bc39..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using Content.Server.Instruments;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Shared.Instruments;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class RandomInstrumentArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly InstrumentSystem _instrument = default!;
-    [Dependency] private readonly IRobustRandom _random = default!;
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<RandomInstrumentArtifactComponent, ComponentStartup>(OnStartup);
-    }
-
-    private void OnStartup(EntityUid uid, RandomInstrumentArtifactComponent component, ComponentStartup args)
-    {
-        var instrument = EnsureComp<InstrumentComponent>(uid);
-        _instrument.SetInstrumentProgram(uid, instrument, (byte) _random.Next(0, 127), 0);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs
deleted file mode 100644 (file)
index 7e3ecb8..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Popups;
-using Robust.Shared.Player;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class RandomTeleportArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly SharedPopupSystem _popup = default!;
-    [Dependency] private readonly SharedTransformSystem _xform = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<RandomTeleportArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    private void OnActivate(EntityUid uid, RandomTeleportArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var xform = Transform(uid);
-        _popup.PopupCoordinates(Loc.GetString("blink-artifact-popup"), xform.Coordinates, PopupType.Medium);
-
-        _xform.SetCoordinates(uid, xform, xform.Coordinates.Offset(_random.NextVector2(component.MinRange, component.MaxRange)));
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs
deleted file mode 100644 (file)
index c396278..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Mobs.Components;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class ShuffleArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly SharedTransformSystem _xform = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ShuffleArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var mobState = GetEntityQuery<MobStateComponent>();
-
-        List<Entity<TransformComponent>> toShuffle = new();
-
-        foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.Dynamic | LookupFlags.Sundries))
-        {
-            if (!mobState.HasComponent(ent))
-                continue;
-
-            var xform = Transform(ent);
-
-            toShuffle.Add((ent, xform));
-        }
-
-        _random.Shuffle(toShuffle);
-
-        while (toShuffle.Count > 1)
-        {
-            var ent1 = _random.PickAndTake(toShuffle);
-            var ent2 = _random.PickAndTake(toShuffle);
-            _xform.SwapPositions((ent1, ent1), (ent2, ent2));
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs
deleted file mode 100644 (file)
index c262283..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Numerics;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Storage;
-using Robust.Server.GameObjects;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class SpawnArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-    [Dependency] private readonly TransformSystem _transform = default!;
-
-    public const string NodeDataSpawnAmount = "nodeDataSpawnAmount";
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<SpawnArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    private void OnActivate(EntityUid uid, SpawnArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        if (!_artifact.TryGetNodeData(uid, NodeDataSpawnAmount, out int amount))
-            amount = 0;
-
-        if (amount >= component.MaxSpawns)
-            return;
-
-        if (component.Spawns is not {} spawns)
-            return;
-
-        var artifactCord = _transform.GetMapCoordinates(uid);
-        foreach (var spawn in EntitySpawnCollection.GetSpawns(spawns, _random))
-        {
-            var dx = _random.NextFloat(-component.Range, component.Range);
-            var dy = _random.NextFloat(-component.Range, component.Range);
-            var spawnCord = artifactCord.Offset(new Vector2(dx, dy));
-            var ent = Spawn(spawn, spawnCord);
-            _transform.AttachToGridOrMap(ent);
-        }
-        _artifact.SetNodeData(uid, NodeDataSpawnAmount, amount + 1);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs
deleted file mode 100644 (file)
index e62ce36..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-using Content.Server.Atmos;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Atmos;
-using Robust.Server.GameObjects;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class TemperatureArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-    [Dependency] private readonly TransformSystem _transformSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<TemperatureArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
-
-    private void OnActivate(EntityUid uid, TemperatureArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var transform = Transform(uid);
-
-        var center = _atmosphereSystem.GetContainingMixture(uid, false, true);
-        if (center == null)
-            return;
-        UpdateTileTemperature(component, center);
-
-        if (component.AffectAdjacentTiles && transform.GridUid != null)
-        {
-            var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value,
-                _transformSystem.GetGridOrMapTilePosition(uid, transform), excite: true);
-
-            while (enumerator.MoveNext(out var mixture))
-            {
-                UpdateTileTemperature(component, mixture);
-            }
-        }
-    }
-
-    private void UpdateTileTemperature(TemperatureArtifactComponent component, GasMixture environment)
-    {
-        var dif = component.TargetTemperature - environment.Temperature;
-        var absDif = Math.Abs(dif);
-        var step = Math.Min(absDif, component.SpawnTemperature);
-        environment.Temperature += dif > 0 ? step : -step;
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs
deleted file mode 100644 (file)
index 7b8430d..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-using System.Numerics;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Shared.Maps;
-using Content.Shared.Physics;
-using Content.Shared.Throwing;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Random;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-public sealed class ThrowArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
-    [Dependency] private readonly ThrowingSystem _throwing = default!;
-    [Dependency] private readonly TileSystem _tile = default!;
-    [Dependency] private readonly SharedTransformSystem _transform = default!;
-    [Dependency] private readonly SharedMapSystem _mapSystem = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ThrowArtifactComponent, ArtifactActivatedEvent>(OnActivated);
-    }
-
-    private void OnActivated(EntityUid uid, ThrowArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        var xform = Transform(uid);
-        if (TryComp<MapGridComponent>(xform.GridUid, out var grid))
-        {
-            var tiles = _mapSystem.GetTilesIntersecting(
-                xform.GridUid.Value,
-                grid,
-                Box2.CenteredAround(_transform.GetWorldPosition(xform), new Vector2(component.Range * 2, component.Range)));
-
-            foreach (var tile in tiles)
-            {
-                if (!_random.Prob(component.TilePryChance))
-                    continue;
-
-                _tile.PryTile(tile);
-            }
-        }
-
-        var lookup = _lookup.GetEntitiesInRange(uid, component.Range, LookupFlags.Dynamic | LookupFlags.Sundries);
-        var physQuery = GetEntityQuery<PhysicsComponent>();
-        foreach (var ent in lookup)
-        {
-            if (physQuery.TryGetComponent(ent, out var phys)
-                && (phys.CollisionMask & (int) CollisionGroup.GhostImpassable) != 0)
-                continue;
-
-            var tempXform = Transform(ent);
-
-            var foo = _transform.GetMapCoordinates(ent, xform: tempXform).Position - _transform.GetMapCoordinates(uid, xform: xform).Position;
-            _throwing.TryThrow(ent, foo*2, component.ThrowStrength, uid, 0);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TriggerArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TriggerArtifactSystem.cs
deleted file mode 100644 (file)
index f70c8d7..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
-
-/// <summary>
-/// This handles <see cref="TriggerArtifactComponent"/>
-/// </summary>
-public sealed class TriggerArtifactSystem : EntitySystem
-{
-    [Dependency] private readonly TriggerSystem _trigger = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<TriggerArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
-    }
-
-    private void OnArtifactActivated(EntityUid uid, TriggerArtifactComponent component, ArtifactActivatedEvent args)
-    {
-        _trigger.Trigger(uid, args.Activator);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs
deleted file mode 100644 (file)
index 57f5089..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-
-/// <summary>
-///     Invokes when artifact was successfully activated.
-///     Used to start attached effects.
-/// </summary>
-public sealed class ArtifactActivatedEvent : EntityEventArgs
-{
-    /// <summary>
-    ///     Entity that activate this artifact.
-    ///     Usually player, but can also be another object.
-    /// </summary>
-    public EntityUid? Activator;
-}
-
-/// <summary>
-///     Force to randomize artifact triggers.
-/// </summary>
-public sealed class ArtifactNodeEnteredEvent : EntityEventArgs
-{
-    /// <summary>
-    /// An entity-specific seed that can be used to
-    /// generate random values.
-    /// </summary>
-    public readonly int RandomSeed;
-
-    public ArtifactNodeEnteredEvent(int randomSeed)
-    {
-        RandomSeed = randomSeed;
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs
deleted file mode 100644 (file)
index 3b2190d..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when an artifact is anchored
-/// </summary>
-/// <remarks>
-/// Not every trigger can be a winner
-/// </remarks>
-[RegisterComponent]
-public sealed partial class ArtifactAnchorTriggerComponent : Component
-{
-
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs
deleted file mode 100644 (file)
index 2f5ac6d..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-using Content.Shared.Damage.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when a certain threshold of damage of certain types is reached
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactDamageTriggerComponent : Component
-{
-    /// <summary>
-    /// What damage types are accumulated for the trigger?
-    /// </summary>
-    [DataField("damageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
-    public List<string>? DamageTypes;
-
-    /// <summary>
-    /// What threshold has to be reached before it is activated?
-    /// </summary>
-    [DataField("damageThreshold", required: true)]
-    public float DamageThreshold;
-
-    /// <summary>
-    /// How much damage has been accumulated on the artifact so far
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    public float AccumulatedDamage = 0;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs
deleted file mode 100644 (file)
index a9d4d1a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when a nearby entity dies
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactDeathTriggerComponent : Component
-{
-    /// <summary>
-    /// How close to the death the artifact has to be for it to trigger.
-    /// </summary>
-    [DataField("range")]
-    public float Range = 15f;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactElectricityTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactElectricityTriggerComponent.cs
deleted file mode 100644 (file)
index 4046d8c..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-///     Activate artifact when it contacted with an electricity source.
-///     It could be connected MV cables, stun baton or multi tool.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactElectricityTriggerComponent : Component
-{
-    /// <summary>
-    ///     How much power should artifact receive to operate.
-    /// </summary>
-    [DataField("minPower")]
-    public float MinPower = 400;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs
deleted file mode 100644 (file)
index ac66997..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when the artifact is examined.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactExamineTriggerComponent : Component
-{
-
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs
deleted file mode 100644 (file)
index 77cb86f..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-using Content.Shared.Atmos;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-///     Activates artifact when it surrounded by certain gas.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactGasTriggerComponent : Component
-{
-    /// <summary>
-    ///     List of possible activation gases to pick on startup.
-    /// </summary>
-    [DataField("possibleGas")]
-    public List<Gas> PossibleGases = new()
-    {
-        Gas.Oxygen,
-        Gas.Plasma,
-        Gas.Nitrogen,
-        Gas.CarbonDioxide,
-        Gas.Ammonia,
-        Gas.NitrousOxide
-    };
-
-    /// <summary>
-    ///     Gas id that will activate artifact.
-    /// </summary>
-    [DataField("gas")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public Gas? ActivationGas;
-
-    /// <summary>
-    ///     How many moles of gas should be present in room to activate artifact.
-    /// </summary>
-    [DataField("moles")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public float ActivationMoles = Atmospherics.MolesCellStandard * 0.1f;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactHeatTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactHeatTriggerComponent.cs
deleted file mode 100644 (file)
index 0af4200..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-// TODO: This should probably be generalized for cold temperature too,
-// but right now there is no sane way to make a freezer.
-
-/// <summary>
-///     Triggers artifact if its in hot environment or
-///     has contacted with a hot object (lit welder, lighter, etc).
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactHeatTriggerComponent : Component
-{
-    /// <summary>
-    ///     Minimal surrounding gas temperature to trigger artifact.
-    ///     Around 100 degrees celsius by default.
-    ///     Doesn't affect hot items temperature.
-    /// </summary>
-    [DataField("activationTemperature")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public float ActivationTemperature = 373;
-
-    /// <summary>
-    ///     Should artifact be activated by hot items (welders, lighter, etc)?
-    /// </summary>
-    [DataField("activateHot")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public bool ActivateHotItems = true;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactInteractionTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactInteractionTriggerComponent.cs
deleted file mode 100644 (file)
index e2f00f9..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-///     Activate artifact by touching, attacking or pulling it.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactInteractionTriggerComponent : Component
-{
-    /// <summary>
-    ///     Should artifact be activated just by touching with empty hand?
-    /// </summary>
-    [DataField("emptyHandActivation")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public bool EmptyHandActivation = true;
-
-    /// <summary>
-    ///     Should artifact be activated by melee attacking?
-    /// </summary>
-    [DataField("attackActivation")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public bool AttackActivation = true;
-
-    /// <summary>
-    ///     Should artifact be activated by starting pulling it?
-    /// </summary>
-    [DataField("pullActivation")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public bool PullActivation = true;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactLandTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactLandTriggerComponent.cs
deleted file mode 100644 (file)
index b8be1b1..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-///     Triggers when the artifact lands after being thrown.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactLandTriggerComponent : Component
-{
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs
deleted file mode 100644 (file)
index 6e7bb43..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when the salvage magnet is activated
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactMagnetTriggerComponent : Component
-{
-    /// <summary>
-    /// how close to the magnet do you have to be?
-    /// </summary>
-    [DataField("range")]
-    public float Range = 40f;
-
-    /// <summary>
-    /// How close do active magboots have to be?
-    /// This is smaller because they are weaker magnets
-    /// </summary>
-    [DataField("magbootRange")]
-    public float MagbootRange = 2f;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMicrowaveTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMicrowaveTriggerComponent.cs
deleted file mode 100644 (file)
index 6270e4d..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-///     Triggers when an item artifact is microwaved.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactMicrowaveTriggerComponent : Component
-{
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs
deleted file mode 100644 (file)
index 1a67d5e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when an instrument is played nearby
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactMusicTriggerComponent : Component
-{
-    /// <summary>
-    /// how close does the artifact have to be to the instrument to activate
-    /// </summary>
-    [DataField("range")]
-    public float Range = 5;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs
deleted file mode 100644 (file)
index 8a7d37c..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-/// Triggers when a certain pressure threshold is hit
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactPressureTriggerComponent : Component
-{
-    /// <summary>
-    /// The lower-end pressure threshold
-    /// </summary>
-    [DataField("minPressureThreshold")]
-    public float? MinPressureThreshold;
-
-    /// <summary>
-    /// The higher-end pressure threshold
-    /// </summary>
-    [DataField("maxPressureThreshold")]
-    public float? MaxPressureThreshold;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactTimerTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactTimerTriggerComponent.cs
deleted file mode 100644 (file)
index bab364a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-/// <summary>
-///     Will try to activate artifact periodically.
-///     Doesn't used for random artifacts, can be spawned by admins.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ArtifactTimerTriggerComponent : Component
-{
-    /// <summary>
-    ///     Time between artifact activation attempts.
-    /// </summary>
-    [DataField("rate")]
-    [ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan ActivationRate = TimeSpan.FromSeconds(5.0f);
-
-    /// <summary>
-    ///     Last time when artifact was activated.
-    /// </summary>
-    public TimeSpan LastActivation;
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs
deleted file mode 100644 (file)
index 568273e..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactAnchorTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ArtifactAnchorTriggerComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
-    }
-
-    private void OnAnchorStateChanged(EntityUid uid, ArtifactAnchorTriggerComponent component, ref AnchorStateChangedEvent args)
-    {
-        if (args.Detaching)
-            return;
-
-        _artifact.TryActivateArtifact(uid);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs
deleted file mode 100644 (file)
index aa7c707..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Damage;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactDamageTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ArtifactDamageTriggerComponent, DamageChangedEvent>(OnDamageChanged);
-    }
-
-    private void OnDamageChanged(EntityUid uid, ArtifactDamageTriggerComponent component, DamageChangedEvent args)
-    {
-        if (!args.DamageIncreased)
-            return;
-
-        if (args.DamageDelta == null)
-            return;
-
-        foreach (var (type, amount) in args.DamageDelta.DamageDict)
-        {
-            if (component.DamageTypes != null && !component.DamageTypes.Contains(type))
-                continue;
-
-            component.AccumulatedDamage += (float) amount;
-        }
-
-        if (component.AccumulatedDamage >= component.DamageThreshold)
-            _artifact.TryActivateArtifact(uid, args.Origin);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs
deleted file mode 100644 (file)
index a924120..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Mobs;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactDeathTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
-    }
-
-    private void OnMobStateChanged(MobStateChangedEvent ev)
-    {
-        if (ev.NewMobState != MobState.Dead)
-            return;
-
-        var deathXform = Transform(ev.Target);
-
-        var toActivate = new List<Entity<ArtifactDeathTriggerComponent>>();
-        var query = EntityQueryEnumerator<ArtifactDeathTriggerComponent, TransformComponent>();
-        while (query.MoveNext(out var uid, out var trigger, out var xform))
-        {
-            if (!deathXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
-                continue;
-
-            if (distance > trigger.Range)
-                continue;
-
-            toActivate.Add((uid, trigger));
-        }
-
-        foreach (var a in toActivate)
-        {
-            _artifact.TryActivateArtifact(a);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs
deleted file mode 100644 (file)
index 9d2fd58..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-using Content.Server.Power.Components;
-using Content.Server.Power.Events;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Tools.Components;
-using Content.Shared.Tools.Systems;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactElectricityTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-    [Dependency] private readonly SharedToolSystem _toolSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<ArtifactElectricityTriggerComponent, InteractUsingEvent>(OnInteractUsing);
-        SubscribeLocalEvent<ArtifactElectricityTriggerComponent, PowerPulseEvent>(OnPowerPulse);
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        List<Entity<ArtifactComponent>> toUpdate = new();
-        var query = EntityQueryEnumerator<ArtifactElectricityTriggerComponent, PowerConsumerComponent, ArtifactComponent>();
-        while (query.MoveNext(out var uid, out var trigger, out var power, out var artifact))
-        {
-            if (power.ReceivedPower <= trigger.MinPower)
-                continue;
-
-            toUpdate.Add((uid, artifact));
-        }
-
-        foreach (var a in toUpdate)
-        {
-            _artifactSystem.TryActivateArtifact(a, null, a);
-        }
-    }
-
-    private void OnInteractUsing(EntityUid uid, ArtifactElectricityTriggerComponent component, InteractUsingEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality))
-            return;
-
-        args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);
-    }
-
-    private void OnPowerPulse(EntityUid uid, ArtifactElectricityTriggerComponent component, PowerPulseEvent args)
-    {
-        _artifactSystem.TryActivateArtifact(uid, args.User);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs
deleted file mode 100644 (file)
index b7afbcf..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Examine;
-using Content.Shared.Ghost;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactExamineTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ArtifactExamineTriggerComponent, ExaminedEvent>(OnExamine);
-    }
-
-    private void OnExamine(EntityUid uid, ArtifactExamineTriggerComponent component, ExaminedEvent args)
-    {
-        // Prevent ghosts from activating this trigger unless they have CanGhostInteract
-        if (TryComp<GhostComponent>(args.Examiner, out var ghost) && !ghost.CanGhostInteract)
-            return;
-
-        _artifact.TryActivateArtifact(uid);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs
deleted file mode 100644 (file)
index 00f409f..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactGasTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-    [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<ArtifactGasTriggerComponent, ArtifactNodeEnteredEvent>(OnRandomizeTrigger);
-    }
-
-    private void OnRandomizeTrigger(EntityUid uid, ArtifactGasTriggerComponent component, ArtifactNodeEnteredEvent args)
-    {
-        if (component.ActivationGas != null)
-            return;
-
-        var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count];
-        component.ActivationGas = gas;
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        List<Entity<ArtifactComponent>> toUpdate = new();
-        var query = EntityQueryEnumerator<ArtifactGasTriggerComponent, ArtifactComponent, TransformComponent>();
-        while (query.MoveNext(out var uid, out var trigger, out var artifact, out var transform))
-        {
-            if (trigger.ActivationGas == null)
-                continue;
-
-            var environment = _atmosphereSystem.GetTileMixture((uid, transform));
-            if (environment == null)
-                continue;
-
-            // check if outside there is enough moles to activate artifact
-            var moles = environment.GetMoles(trigger.ActivationGas.Value);
-            if (moles < trigger.ActivationMoles)
-                continue;
-
-            toUpdate.Add((uid, artifact));
-        }
-
-        foreach (var a in toUpdate)
-        {
-            _artifactSystem.TryActivateArtifact(a, null, a);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs
deleted file mode 100644 (file)
index 5525cdf..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Temperature;
-using Content.Shared.Weapons.Melee.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactHeatTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-    [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<ArtifactHeatTriggerComponent, AttackedEvent>(OnAttacked);
-        SubscribeLocalEvent<ArtifactHeatTriggerComponent, InteractUsingEvent>(OnUsing);
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        List<Entity<ArtifactComponent>> toUpdate = new();
-        var query = EntityQueryEnumerator<ArtifactHeatTriggerComponent, TransformComponent, ArtifactComponent>();
-        while (query.MoveNext(out var uid, out var trigger, out var transform, out var artifact))
-        {
-            var environment = _atmosphereSystem.GetTileMixture((uid, transform));
-            if (environment == null)
-                continue;
-
-            if (environment.Temperature < trigger.ActivationTemperature)
-                continue;
-
-            toUpdate.Add((uid, artifact));
-        }
-
-        foreach (var a in toUpdate)
-        {
-            _artifactSystem.TryActivateArtifact(a, null, a);
-        }
-    }
-
-    private void OnAttacked(EntityUid uid, ArtifactHeatTriggerComponent component, AttackedEvent args)
-    {
-        if (!component.ActivateHotItems || !CheckHot(args.Used))
-            return;
-        _artifactSystem.TryActivateArtifact(uid, args.User);
-    }
-
-    private void OnUsing(EntityUid uid, ArtifactHeatTriggerComponent component, InteractUsingEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        if (!component.ActivateHotItems || !CheckHot(args.Used))
-            return;
-        args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);
-    }
-
-    private bool CheckHot(EntityUid usedUid)
-    {
-        var hotEvent = new IsHotEvent();
-        RaiseLocalEvent(usedUid, hotEvent);
-        return hotEvent.IsHot;
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs
deleted file mode 100644 (file)
index 9976d56..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Movement.Pulling.Events;
-using Content.Shared.Weapons.Melee.Events;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactInteractionTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<ArtifactInteractionTriggerComponent, PullStartedMessage>(OnPull);
-        SubscribeLocalEvent<ArtifactInteractionTriggerComponent, AttackedEvent>(OnAttack);
-        SubscribeLocalEvent<ArtifactInteractionTriggerComponent, InteractHandEvent>(OnInteract);
-    }
-
-    private void OnPull(EntityUid uid, ArtifactInteractionTriggerComponent component, PullStartedMessage args)
-    {
-        if (!component.PullActivation)
-            return;
-
-        _artifactSystem.TryActivateArtifact(uid, args.PullerUid);
-    }
-
-    private void OnAttack(EntityUid uid, ArtifactInteractionTriggerComponent component, AttackedEvent args)
-    {
-        if (!component.AttackActivation)
-            return;
-
-        _artifactSystem.TryActivateArtifact(uid, args.User);
-    }
-
-    private void OnInteract(EntityUid uid, ArtifactInteractionTriggerComponent component, InteractHandEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        if (!component.EmptyHandActivation)
-            return;
-
-        args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactLandSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactLandSystem.cs
deleted file mode 100644 (file)
index f92e4f8..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Throwing;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactLandSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ArtifactLandTriggerComponent, LandEvent>(OnLand);
-    }
-
-    private void OnLand(EntityUid uid, ArtifactLandTriggerComponent component, ref LandEvent args)
-    {
-        _artifact.TryActivateArtifact(uid, args.User);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs
deleted file mode 100644 (file)
index a585a9e..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Linq;
-using Content.Server.Salvage;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Content.Shared.Clothing;
-using Content.Shared.Item.ItemToggle.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-/// <summary>
-/// This handles artifacts that are activated by magnets, both salvage and magboots.
-/// </summary>
-public sealed class ArtifactMagnetTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    private readonly List<EntityUid> _toActivate = new();
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<SalvageMagnetActivatedEvent>(OnMagnetActivated);
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        if (!EntityQuery<ArtifactMagnetTriggerComponent>().Any())
-            return;
-
-        _toActivate.Clear();
-
-        //assume that there's more magboots than artifacts
-        var query = EntityQueryEnumerator<MagbootsComponent, TransformComponent, ItemToggleComponent>();
-        while (query.MoveNext(out _, out var magboot, out var magXform, out var toggle))
-        {
-            if (!toggle.Activated)
-                continue;
-
-            var artiQuery = EntityQueryEnumerator<ArtifactMagnetTriggerComponent, TransformComponent>();
-            while (artiQuery.MoveNext(out var artifactUid, out var trigger, out var xform))
-            {
-                if (!magXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
-                    continue;
-
-                if (distance > trigger.MagbootRange)
-                    continue;
-
-                _toActivate.Add(artifactUid);
-            }
-        }
-
-        foreach (var a in _toActivate)
-        {
-            _artifact.TryActivateArtifact(a);
-        }
-    }
-
-    private void OnMagnetActivated(ref SalvageMagnetActivatedEvent ev)
-    {
-        var magXform = Transform(ev.Magnet);
-
-        var query = EntityQueryEnumerator<ArtifactMagnetTriggerComponent, TransformComponent>();
-        while (query.MoveNext(out var uid, out var artifact, out var xform))
-        {
-            if (!magXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
-                continue;
-
-            if (distance > artifact.Range)
-                continue;
-
-            _toActivate.Add(uid);
-        }
-
-        foreach (var a in _toActivate)
-        {
-            _artifact.TryActivateArtifact(a);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMicrowaveTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMicrowaveTriggerSystem.cs
deleted file mode 100644 (file)
index ca61c8f..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-using Content.Server.Kitchen.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactMicrowaveTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    /// <inheritdoc/>
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<ArtifactMicrowaveTriggerComponent, BeingMicrowavedEvent>(OnMicrowaved);
-    }
-
-    private void OnMicrowaved(EntityUid uid, ArtifactMicrowaveTriggerComponent component, BeingMicrowavedEvent args)
-    {
-        _artifact.TryActivateArtifact(uid);
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs
deleted file mode 100644 (file)
index c62ed58..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-using System.Linq;
-using Content.Server.Instruments;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-/// <summary>
-/// This handles activating an artifact when music is playing nearby
-/// </summary>
-public sealed class ArtifactMusicTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
-
-    private readonly List<Entity<ArtifactMusicTriggerComponent, TransformComponent>> _artifacts = new();
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        _artifacts.Clear();
-        var artifactQuery = EntityQueryEnumerator<ArtifactMusicTriggerComponent, TransformComponent>();
-        while (artifactQuery.MoveNext(out var uid, out var trigger, out var xform))
-        {
-            _artifacts.Add((uid, trigger, xform));
-        }
-
-        if (!_artifacts.Any())
-            return;
-
-        List<EntityUid> toActivate = new();
-        var query = EntityQueryEnumerator<ActiveInstrumentComponent, TransformComponent>();
-
-        //assume that there's more instruments than artifacts
-        while (query.MoveNext(out _, out var instXform))
-        {
-            foreach (var (uid, trigger, xform) in _artifacts)
-            {
-                if (!instXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance))
-                    continue;
-
-                if (distance > trigger.Range)
-                    continue;
-
-                toActivate.Add(uid);
-            }
-        }
-
-        foreach (var a in toActivate)
-        {
-            _artifact.TryActivateArtifact(a);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs
deleted file mode 100644 (file)
index 8777ab0..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-/// <summary>
-/// This handles activation upon certain pressure thresholds.
-/// </summary>
-public sealed class ArtifactPressureTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-    [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        List<Entity<ArtifactComponent>> toUpdate = new();
-        var query = EntityQueryEnumerator<ArtifactPressureTriggerComponent, ArtifactComponent, TransformComponent>();
-        while (query.MoveNext(out var uid, out var trigger, out var artifact, out var transform))
-        {
-            var environment = _atmosphereSystem.GetTileMixture((uid, transform));
-            if (environment == null)
-                continue;
-
-            var pressure = environment.Pressure;
-            if (pressure >= trigger.MaxPressureThreshold || pressure <= trigger.MinPressureThreshold)
-                toUpdate.Add((uid, artifact));
-        }
-
-        foreach (var a in toUpdate)
-        {
-            _artifactSystem.TryActivateArtifact(a, null, a);
-        }
-    }
-}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs
deleted file mode 100644 (file)
index c6ea745..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
-
-public sealed class ArtifactTimerTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly IGameTiming _time = default!;
-    [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ArtifactTimerTriggerComponent, ComponentStartup>(OnStartup);
-    }
-
-    private void OnStartup(EntityUid uid, ArtifactTimerTriggerComponent component, ComponentStartup args)
-    {
-        component.LastActivation = _time.CurTime;
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        List<Entity<ArtifactComponent>> toUpdate = new();
-        var query = EntityQueryEnumerator<ArtifactTimerTriggerComponent, ArtifactComponent>();
-        while (query.MoveNext(out var uid, out var trigger, out var artifact))
-        {
-            var timeDif = _time.CurTime - trigger.LastActivation;
-            if (timeDif <= trigger.ActivationRate)
-                continue;
-
-            toUpdate.Add((uid, artifact));
-            trigger.LastActivation = _time.CurTime;
-        }
-
-        foreach (var a in toUpdate)
-        {
-            _artifactSystem.TryActivateArtifact(a, null, a);
-        }
-    }
-}
index 321a89d591f26a8c74bdce35533f1dd65e736941..0042ba8f721b4b906d0279115f6a7d4195dc2e96 100644 (file)
@@ -464,4 +464,8 @@ public enum LogType
     /// Logs related to botany, such as planting and harvesting crops
     /// </summary>
     Botany = 100,
+    /// <summary>
+    /// Artifact node got activated.
+    /// </summary>
+    ArtifactNode = 101
 }
index 534b6ba9f6b31ba3860f61cd0ffc114e2965173a..25781d2966a0fa5b06ee71b68b1bb945551d8169 100644 (file)
@@ -15,6 +15,11 @@ namespace Content.Shared.Chemistry.Reaction
 {
     public sealed class ChemicalReactionSystem : EntitySystem
     {
+        /// <summary>
+        /// Foam reaction protoId.
+        /// </summary>
+        public static readonly ProtoId<ReactionPrototype> FoamReaction = "Foam";
+
         /// <summary>
         ///     The maximum number of reactions that may occur when a solution is changed.
         /// </summary>
index edd75bb0b4c54cc49d931ee9f15b9d28f1a5492f..6306537324efc154e76a0c9cd5f454845c383942 100644 (file)
@@ -38,6 +38,10 @@ public sealed class ReactiveSystem : EntitySystem
         if (!TryComp(uid, out ReactiveComponent? reactive))
             return;
 
+        // custom event for bypassing reactivecomponent stuff
+        var ev = new ReactionEntityEvent(method, proto, reagentQuantity, source);
+        RaiseLocalEvent(uid, ref ev);
+
         // If we have a source solution, use the reagent quantity we have left. Otherwise, use the reaction volume specified.
         var args = new EntityEffectReagentArgs(uid, EntityManager, null, source, source?.GetReagentQuantity(reagentQuantity.Reagent) ?? reagentQuantity.Quantity, proto, method, 1f);
 
@@ -107,3 +111,11 @@ Touch,
 Injection,
 Ingestion,
 }
+
+[ByRefEvent]
+public readonly record struct ReactionEntityEvent(
+    ReactionMethod Method,
+    ReagentPrototype Reagent,
+    ReagentQuantity ReagentQuantity,
+    Solution? Source
+);
index fb55a6184ee335ed8af27b279d644788d90661d6..31426f6bd075ada0bc0eefe0cb13eec28b752e9d 100644 (file)
@@ -329,7 +329,7 @@ namespace Content.Shared.Damage
                 damage.DamageDict.Add(typeId, damageValue);
             }
 
-            TryChangeDamage(uid, damage, interruptsDoAfters: false);
+            TryChangeDamage(uid, damage, interruptsDoAfters: false, origin: args.Origin);
         }
 
         private void OnRejuvenate(EntityUid uid, DamageableComponent component, RejuvenateEvent args)
index 73500f3869d27c3dbd1e8bb6a89fc2546b170978..da64bf8cd7d6cefa55cfb78d4ac233a16cac30c3 100644 (file)
@@ -34,6 +34,12 @@ public abstract partial class SharedInstrumentComponent : Component
     public BitArray FilteredChannels { get; set; } = new(RobustMidiEvent.MaxChannels, true);
 }
 
+/// <summary>
+/// Component that indicates that musical instrument was activated (ui opened).
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveInstrumentComponent : Component;
+
 [Serializable, NetSerializable]
 public sealed class InstrumentComponentState : ComponentState
 {
index 3112e1eb471ecdf9da612045509fc909ccbfd34a..ec9988fffbfd82303d85d0d5da300658b0163b50 100644 (file)
@@ -1,14 +1,16 @@
-namespace Content.Shared.Radiation.Events;
+namespace Content.Shared.Radiation.Events;
 
 /// <summary>
 ///     Raised on entity when it was irradiated
 ///     by some radiation source.
 /// </summary>
-public readonly record struct OnIrradiatedEvent(float FrameTime, float RadsPerSecond)
+public readonly record struct OnIrradiatedEvent(float FrameTime, float RadsPerSecond, EntityUid Origin)
 {
     public readonly float FrameTime = FrameTime;
 
     public readonly float RadsPerSecond = RadsPerSecond;
 
+    public readonly EntityUid Origin = Origin;
+
     public float TotalRads => RadsPerSecond * FrameTime;
 }
diff --git a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs
new file mode 100644 (file)
index 0000000..fb11860
--- /dev/null
@@ -0,0 +1,161 @@
+using Content.Shared.Destructible.Thresholds;
+using Content.Shared.EntityTable.EntitySelectors;
+using Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+using Robust.Shared.Audio;
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Components;
+
+/// <summary>
+/// This is used for handling interactions with artifacts as well as
+/// storing data about artifact node graphs.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedXenoArtifactSystem)), AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class XenoArtifactComponent : Component
+{
+    public static string NodeContainerId = "node-container";
+
+    /// <summary>
+    /// Marker, if nodes graph should be generated for artifact.
+    /// </summary>
+    [DataField]
+    public bool IsGenerationRequired = true;
+
+    /// <summary>
+    /// Container for artifact graph node entities.
+    /// </summary>
+    [ViewVariables]
+    public Container NodeContainer = default!;
+
+    /// <summary>
+    /// The nodes in this artifact that are currently "active."
+    /// This is cached and updated when nodes are removed, added, or unlocked.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public List<NetEntity> CachedActiveNodes = new();
+
+    /// <summary>
+    /// Cache of interconnected node chunks - segments.
+    /// This is cached and updated when nodes are removed, added, or unlocked.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public List<List<NetEntity>> CachedSegments = new();
+
+    /// <summary>
+    /// Marker, if true - node activations should not happen.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Suppressed;
+
+    /// <summary>
+    /// A multiplier applied to the calculated point value
+    /// to determine the monetary value of the artifact.
+    /// </summary>
+    [DataField]
+    public float PriceMultiplier = 0.10f;
+
+    #region Unlocking
+    /// <summary>
+    /// How long does the unlocking state last by default.
+    /// </summary>
+    [DataField]
+    public TimeSpan UnlockStateDuration = TimeSpan.FromSeconds(6);
+
+    /// <summary>
+    /// By how much unlocking state should be prolonged for each node that was unlocked.
+    /// </summary>
+    [DataField]
+    public TimeSpan UnlockStateIncrementPerNode = TimeSpan.FromSeconds(5);
+
+    /// <summary>
+    /// Minimum waiting time between unlock states.
+    /// </summary>
+    [DataField]
+    public TimeSpan UnlockStateRefractory = TimeSpan.FromSeconds(10);
+
+    /// <summary>
+    /// When next unlock session can be triggered.
+    /// </summary>
+    [DataField, AutoPausedField]
+    public TimeSpan NextUnlockTime;
+    #endregion
+
+    // NOTE: you should not be accessing any of these values directly. Use the methods in SharedXenoArtifactSystem.Graph
+    #region Graph
+    /// <summary>
+    /// List of all nodes currently on this artifact.
+    /// Indexes are used as a lookup table for <see cref="NodeAdjacencyMatrix"/>.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public NetEntity?[] NodeVertices = [];
+
+    /// <summary>
+    /// Adjacency matrix that stores connections between this artifact's nodes.
+    /// A value of "true" denotes an directed edge from node1 to node2, where the location of the vertex is (node1, node2)
+    /// A value of "false" denotes no edge.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public List<List<bool>> NodeAdjacencyMatrix = new();
+
+    public int NodeAdjacencyMatrixRows => NodeAdjacencyMatrix.Count;
+    public int NodeAdjacencyMatrixColumns => NodeAdjacencyMatrix.TryGetValue(0, out var value) ? value.Count : 0;
+    #endregion
+
+    #region GenerationInfo
+
+    /// <summary>
+    /// The total number of nodes that make up this artifact.
+    /// </summary>
+    [DataField]
+    public MinMax NodeCount = new(10, 16);
+
+    /// <summary>
+    /// The amount of nodes that go in each segment.
+    /// A segment is an interconnected series of nodes.
+    /// </summary>
+    [DataField]
+    public MinMax SegmentSize = new(5, 8);
+
+    /// <summary>
+    /// For each "layer" in a segment (set of nodes with equal depth), how many will we generate?
+    /// </summary>
+    [DataField]
+    public MinMax NodesPerSegmentLayer = new(1, 3);
+
+    /// <summary>
+    /// How man nodes can be randomly added on top of usual distribution (per layer).
+    /// </summary>
+    [DataField]
+    public MinMax ScatterPerLayer = new(0, 2);
+
+    /// <summary>
+    /// Effects that can be used during this artifact generation.
+    /// </summary>
+    [DataField]
+    public EntityTableSelector EffectsTable = new NestedSelector
+    {
+        TableId = "XenoArtifactEffectsDefaultTable"
+    };
+
+    /// <summary>
+    /// Triggers that can be used during this artefact generation.
+    /// </summary>
+    [DataField]
+    public ProtoId<WeightedRandomXenoArchTriggerPrototype> TriggerWeights = "DefaultTriggers";
+    #endregion
+
+    /// <summary>
+    /// Sound effect to be played when artifact node is force-activated.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? ForceActivationSoundSpecifier = new SoundCollectionSpecifier("ArtifactForceActivation")
+    {
+        Params = new()
+        {
+            Variation = 0.1f
+        }
+    };
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactNodeComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactNodeComponent.cs
new file mode 100644 (file)
index 0000000..c4c1673
--- /dev/null
@@ -0,0 +1,81 @@
+using Content.Shared.Destructible.Thresholds;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Components;
+
+/// <summary>
+/// Stores metadata about a particular artifact node
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedXenoArtifactSystem)), AutoGenerateComponentState]
+public sealed partial class XenoArtifactNodeComponent : Component
+{
+    /// <summary>
+    /// Depth within the graph generation.
+    /// Used for sorting.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int Depth;
+
+    /// <summary>
+    /// Denotes whether an artifact node has been activated at least once (through the required triggers).
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Locked = true;
+
+    /// <summary>
+    /// List of trigger descriptions that this node require for activation.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId? TriggerTip;
+
+    /// <summary>
+    /// The entity whose graph this node is a part of.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public NetEntity? Attached;
+
+    #region Durability
+    /// <summary>
+    /// Marker, is durability of node degraded or not.
+    /// </summary>
+    public bool Degraded => Durability <= 0;
+
+    /// <summary>
+    /// The amount of generic activations a node has left before becoming fully degraded and useless.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int Durability;
+
+    /// <summary>
+    /// The maximum amount of times a node can be generically activated before becoming useless
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int MaxDurability = 5;
+
+    /// <summary>
+    /// The variance from MaxDurability present when a node is created.
+    /// </summary>
+    [DataField]
+    public MinMax MaxDurabilityCanDecreaseBy = new(0, 2);
+    #endregion
+
+    #region Research
+    /// <summary>
+    /// The amount of points a node is worth with no scaling
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float BasePointValue = 5000;
+
+    /// <summary>
+    /// Amount of points available currently for extracting.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int ResearchValue;
+
+    /// <summary>
+    /// Amount of points already extracted from node.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int ConsumedResearchValue;
+    #endregion
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactUnlockingComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactUnlockingComponent.cs
new file mode 100644 (file)
index 0000000..13b07f4
--- /dev/null
@@ -0,0 +1,48 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Components;
+
+/// <summary>
+/// This is used for tracking the nodes which have been triggered during a particular unlocking state.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class XenoArtifactUnlockingComponent : Component
+{
+    /// <summary>
+    /// Indexes corresponding to all of the nodes that have been triggered
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public HashSet<int> TriggeredNodeIndexes = new();
+
+    /// <summary>
+    /// The time at which the unlocking state ends.
+    /// </summary>
+    [DataField, AutoNetworkedField, AutoPausedField]
+    public TimeSpan EndTime;
+
+    /// <summary>
+    /// The sound that plays when an artifact finishes unlocking successfully (with node unlocked).
+    /// </summary>
+    [DataField]
+    public SoundSpecifier UnlockActivationSuccessfulSound = new SoundCollectionSpecifier("ArtifactUnlockingActivationSuccess")
+    {
+        Params = new()
+        {
+            Variation = 0.1f,
+            Volume = 3f
+        }
+    };
+
+    /// <summary>
+    /// The sound that plays when artifact finishes unlocking non-successfully.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? UnlockActivationFailedSound = new SoundCollectionSpecifier("ArtifactUnlockActivationFailure")
+    {
+        Params = new()
+        {
+            Variation = 0.1f
+        }
+    };
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/Prototypes/XenoArchTriggerPrototype.cs b/Content.Shared/Xenoarchaeology/Artifact/Prototypes/XenoArchTriggerPrototype.cs
new file mode 100644 (file)
index 0000000..74d3895
--- /dev/null
@@ -0,0 +1,46 @@
+using Content.Shared.Random;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.Prototypes;
+
+/// <summary> Proto for xeno artifact triggers - markers, which event could trigger node to unlock it. </summary>
+[Prototype]
+public sealed partial class XenoArchTriggerPrototype : IPrototype
+{
+    /// <inheritdoc/>
+    [IdDataField]
+    public string ID { get; } = default!;
+
+    /// <summary>
+    /// Tip for user on how to activate this trigger.
+    /// </summary>
+    [DataField]
+    public LocId Tip;
+
+    /// <summary>
+    /// Whitelist, describing for which subtype of artifacts this trigger could be used.
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? Whitelist;
+
+    /// <summary>
+    /// List of components that represent ways to trigger node.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry Components = new();
+}
+
+/// <summary>
+/// Container for list of xeno artifact triggers and their respective weights to be used in case randomly rolling trigger is required.
+/// </summary>
+[Prototype]
+public sealed partial class WeightedRandomXenoArchTriggerPrototype : IWeightedRandomPrototype
+{
+    [IdDataField]
+    public string ID { get; private set; } = default!;
+
+    [DataField(customTypeSerializer: typeof(PrototypeIdDictionarySerializer<float, XenoArchTriggerPrototype>))]
+    public Dictionary<string, float> Weights { get; private set; } = new();
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Graph.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Graph.cs
new file mode 100644 (file)
index 0000000..fca5baf
--- /dev/null
@@ -0,0 +1,615 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+/// <summary>
+/// User-friendly API for viewing and modifying the complex graph relationship in XenoArtifacts
+/// </summary>
+public abstract partial class SharedXenoArtifactSystem
+{
+    /// <summary>
+    /// Gets the index, corresponding to a given node, throwing if the node is not present.
+    /// </summary>
+    public int GetIndex(Entity<XenoArtifactComponent> ent, EntityUid node)
+    {
+        if (TryGetIndex((ent, ent), node, out var index))
+        {
+            return index.Value;
+        }
+
+        throw new ArgumentException($"node {ToPrettyString(node)} is not present in {ToPrettyString(ent)}");
+    }
+
+    /// <summary>
+    /// Tries to get index inside nodes collection, corresponding to a given node EntityUid.
+    /// </summary>
+    public bool TryGetIndex(Entity<XenoArtifactComponent?> ent, EntityUid node, [NotNullWhen(true)] out int? index)
+    {
+        index = null;
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        for (var i = 0; i < ent.Comp.NodeVertices.Length; i++)
+        {
+            if (!TryGetNode(ent, i, out var iNode))
+                continue;
+
+            if (node != iNode.Value.Owner)
+                continue;
+
+            index = i;
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    /// Gets node entity with node component from artifact by index of node inside artifact nodes collection.
+    /// </summary>
+    /// <exception cref="ArgumentException">Throws if requested index doesn't exist on artifact. </exception>
+    public Entity<XenoArtifactNodeComponent> GetNode(Entity<XenoArtifactComponent> ent, int index)
+    {
+        if (ent.Comp.NodeVertices[index] is { } netUid && GetEntity(netUid) is var uid)
+            return (uid, XenoArtifactNode(uid));
+
+        throw new ArgumentException($"index {index} does not correspond to an existing node in {ToPrettyString(ent)}");
+    }
+
+    /// <summary>
+    /// Tries to get node entity with node component from artifact by index of node inside artifact nodes collection.
+    /// </summary>
+    public bool TryGetNode(Entity<XenoArtifactComponent?> ent, int index, [NotNullWhen(true)] out Entity<XenoArtifactNodeComponent>? node)
+    {
+        node = null;
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        if (index < 0 || index >= ent.Comp.NodeVertices.Length)
+            return false;
+
+        if (ent.Comp.NodeVertices[index] is { } netUid && GetEntity(netUid) is var uid)
+            node = (uid, XenoArtifactNode(uid));
+
+        return node != null;
+    }
+
+    /// <summary>
+    /// Gets the index of the first empty spot in the NodeVertices array.
+    /// If there is none, resizes both arrays and returns the new index.
+    /// </summary>
+    public int GetFreeNodeIndex(Entity<XenoArtifactComponent> ent)
+    {
+        var length = ent.Comp.NodeVertices.Length;
+        for (var i = 0; i < length; i++)
+        {
+            if (ent.Comp.NodeVertices[i] == null)
+                return i;
+        }
+
+        ResizeNodeGraph(ent, length + 1);
+        return length;
+    }
+
+    /// <summary>
+    /// Extracts node entities from artifact container
+    /// (uses pre-cached <see cref="XenoArtifactComponent.NodeVertices"/> and mapping from NetEntity).
+    /// </summary>
+    public IEnumerable<Entity<XenoArtifactNodeComponent>> GetAllNodes(Entity<XenoArtifactComponent> ent)
+    {
+        foreach (var netNode in ent.Comp.NodeVertices)
+        {
+            if (TryGetEntity(netNode, out var node))
+                yield return (node.Value, XenoArtifactNode(node.Value));
+        }
+    }
+
+    /// <summary>
+    /// Extracts enumeration of all indices that artifact node container have.
+    /// </summary>
+    public IEnumerable<int> GetAllNodeIndices(Entity<XenoArtifactComponent> ent)
+    {
+        for (var i = 0; i < ent.Comp.NodeVertices.Length; i++)
+        {
+            if (ent.Comp.NodeVertices[i] is not null)
+                yield return i;
+        }
+    }
+
+    /// <summary>
+    /// Adds edge between artifact nodes - <see cref="from"/> and <see cref="to"/>
+    /// </summary>
+    /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+    /// <param name="from">Node from which we need to draw edge. </param>
+    /// <param name="to">Node to which we need to draw edge. </param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    /// <returns>True if adding edge was successful, false otherwise.</returns>
+    public bool AddEdge(Entity<XenoArtifactComponent?> ent, EntityUid from, EntityUid to, bool dirty = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        if (!TryGetIndex(ent, from, out var fromIdx) ||
+            !TryGetIndex(ent, to, out var toIdx))
+            return false;
+
+        return AddEdge(ent, fromIdx.Value, toIdx.Value, dirty: dirty);
+    }
+
+    /// <summary>
+    /// Adds edge between artifact nodes by indices inside node container - <see cref="fromIdx"/> and <see cref="toIdx"/>
+    /// </summary>
+    /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+    /// <param name="fromIdx">Node index inside artifact node container, from which we need to draw edge. </param>
+    /// <param name="toIdx">Node index inside artifact node container, to which we need to draw edge. </param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    /// <returns>True if adding edge was successful, false otherwise.</returns>
+    public bool AddEdge(Entity<XenoArtifactComponent?> ent, int fromIdx, int toIdx, bool dirty = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        DebugTools.Assert(fromIdx >= 0 && fromIdx < ent.Comp.NodeVertices.Length, $"fromIdx is out of bounds for fromIdx {fromIdx}");
+        DebugTools.Assert(toIdx >= 0 && toIdx < ent.Comp.NodeVertices.Length, $"toIdx is out of bounds for toIdx {toIdx}");
+
+        if (ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx])
+            return false; //Edge already exists
+
+        // TODO: add a safety check to prohibit cyclic paths.
+
+        ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx] = true;
+        if (dirty)
+        {
+            RebuildXenoArtifactMetaData(ent);
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Removes edge between artifact nodes.
+    /// </summary>
+    /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+    /// <param name="from">Entity of node from which edge to remove is connected.</param>
+    /// <param name="to">Entity of node to which edge to remove is connected.</param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    /// <returns>True if removed edge was successfully, false otherwise.</returns>
+    public bool RemoveEdge(Entity<XenoArtifactComponent?> ent, EntityUid from, EntityUid to, bool dirty = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        if (!TryGetIndex(ent, from, out var fromIdx) ||
+            !TryGetIndex(ent, to, out var toIdx))
+            return false;
+
+        return RemoveEdge(ent, fromIdx.Value, toIdx.Value, dirty);
+    }
+
+    /// <summary>
+    /// Removes edge between artifact nodes.
+    /// </summary>
+    /// <param name="ent">Artifact entity that contains 'from' and 'to' node entities.</param>
+    /// <param name="fromIdx"> First node index inside artifact node container, from which we need to remove connecting edge. </param>
+    /// <param name="toIdx"> Other node index inside artifact node container, from which we need to remove connecting edge. </param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    /// <returns>True if removed edge was successfully, false otherwise.</returns>
+    public bool RemoveEdge(Entity<XenoArtifactComponent?> ent, int fromIdx, int toIdx, bool dirty = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        DebugTools.Assert(fromIdx >= 0 && fromIdx < ent.Comp.NodeVertices.Length, $"fromIdx is out of bounds for fromIdx {fromIdx}");
+        DebugTools.Assert(toIdx >= 0 && toIdx < ent.Comp.NodeVertices.Length, $"toIdx is out of bounds for toIdx {toIdx}");
+
+        if (!ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx])
+            return false; //Edge doesn't exist
+
+        ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx] = false;
+
+        if (dirty)
+        {
+            RebuildXenoArtifactMetaData(ent);
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Creates node entity (spawns) and adds node into artifact node container.
+    /// </summary>
+    /// <param name="ent">Artifact entity, to container of which node should be added.</param>
+    /// <param name="entProtoId">EntProtoId of node to be added.</param>
+    /// <param name="node">Created node or null.</param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    /// <returns>True if node creation and adding was successful, false otherwise.</returns>
+    public bool AddNode(
+        Entity<XenoArtifactComponent?> ent,
+        EntProtoId entProtoId,
+        [NotNullWhen(true)] out Entity<XenoArtifactNodeComponent>? node,
+        bool dirty = true
+    )
+    {
+        node = null;
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        var uid = Spawn(entProtoId);
+        node = (uid, XenoArtifactNode(uid));
+        return AddNode(ent, (node.Value, node.Value.Comp), dirty: dirty);
+    }
+
+    /// <summary>
+    /// Adds node entity to artifact node container.
+    /// </summary>
+    /// <param name="ent">Artifact entity, to container of which node should be added.</param>
+    /// <param name="node">Node entity to add.</param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    /// <returns>True if node adding was successful, false otherwise.</returns>
+    public bool AddNode(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent?> node, bool dirty = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        node.Comp ??= XenoArtifactNode(node);
+        node.Comp.Attached = GetNetEntity(ent);
+
+        var nodeIdx = GetFreeNodeIndex((ent, ent.Comp));
+        _container.Insert(node.Owner, ent.Comp.NodeContainer);
+        ent.Comp.NodeVertices[nodeIdx] = GetNetEntity(node);
+
+        Dirty(node);
+        if (dirty)
+        {
+            RebuildXenoArtifactMetaData(ent);
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Removes artifact node from artifact node container.
+    /// </summary>
+    /// <param name="ent">Artifact from container of which node should be removed</param>
+    /// <param name="node">Node entity to be removed.</param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    /// <returns>True if node was removed successfully, false otherwise.</returns>
+    public bool RemoveNode(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent?> node, bool dirty = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        node.Comp ??= XenoArtifactNode(node);
+
+        if (!TryGetIndex(ent, node, out var idx))
+            return false; // node isn't attached to this entity.
+
+        RemoveAllNodeEdges(ent, idx.Value, dirty: false);
+
+        _container.Remove(node.Owner, ent.Comp.NodeContainer);
+        node.Comp.Attached = null;
+        ent.Comp.NodeVertices[idx.Value] = null;
+        if (dirty)
+        {
+            RebuildXenoArtifactMetaData(ent);
+        }
+
+        Dirty(node);
+
+        return true;
+    }
+
+    /// <summary>
+    /// Remove edges, connected to passed artifact node.
+    /// </summary>
+    /// <param name="ent">Entity of artifact, in node container of which node resides.</param>
+    /// <param name="nodeIdx">Index of node (inside node container), for which all edges should be removed.</param>
+    /// <param name="dirty">
+    /// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
+    /// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
+    /// </param>
+    public void RemoveAllNodeEdges(Entity<XenoArtifactComponent?> ent, int nodeIdx, bool dirty = true)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return;
+
+        var predecessors = GetDirectPredecessorNodes(ent, nodeIdx);
+        foreach (var p in predecessors)
+        {
+            RemoveEdge(ent, p, nodeIdx, dirty: false);
+        }
+
+        var successors = GetDirectSuccessorNodes(ent, nodeIdx);
+        foreach (var s in successors)
+        {
+            RemoveEdge(ent, nodeIdx, s, dirty: false);
+        }
+
+        if (dirty)
+        {
+            RebuildXenoArtifactMetaData(ent);
+        }
+    }
+
+    /// <summary>
+    /// Gets set of node entities, that are direct predecessors to passed node entity.
+    /// </summary>
+    /// <remarks>
+    /// Direct predecessors are nodes, which are connected by edges directly to target node,
+    /// and are on outgoing ('FROM') side of edge connection.
+    /// </remarks>
+    public HashSet<Entity<XenoArtifactNodeComponent>> GetDirectPredecessorNodes(Entity<XenoArtifactComponent?> ent, EntityUid node)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        if (!TryGetIndex(ent, node, out var index))
+            return new();
+
+        var indices = GetDirectPredecessorNodes(ent, index.Value);
+        var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+        foreach (var i in indices)
+        {
+            if (TryGetNode(ent, i, out var predecessor))
+                output.Add(predecessor.Value);
+        }
+
+        return output;
+    }
+
+    /// <summary>
+    /// Gets set of node indices (in artifact node container) which are direct predecessors to node with passed node index.
+    /// </summary>
+    /// <remarks>
+    /// Direct predecessors are nodes, which are connected by edges directly to target node,
+    /// and are on outgoing ('FROM') side of edge connection.
+    /// </remarks>
+    public HashSet<int> GetDirectPredecessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        DebugTools.Assert(nodeIdx >= 0 && nodeIdx < ent.Comp.NodeVertices.Length, $"node index {nodeIdx} is out of bounds!");
+
+        var indices = new HashSet<int>();
+        for (var i = 0; i < ent.Comp.NodeAdjacencyMatrixRows; i++)
+        {
+            if (ent.Comp.NodeAdjacencyMatrix[i][nodeIdx])
+                indices.Add(i);
+        }
+
+        return indices;
+    }
+
+    /// <summary>
+    /// Gets set of node entities, that are direct successors to passed node entity.
+    /// </summary>
+    /// <remarks>
+    /// Direct successors are nodes, which are connected by edges
+    /// directly to target node, and are on incoming ('TO') side of edge connection.
+    /// </remarks>
+    public HashSet<Entity<XenoArtifactNodeComponent>> GetDirectSuccessorNodes(Entity<XenoArtifactComponent?> ent, EntityUid node)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        if (!TryGetIndex(ent, node, out var index))
+            return new();
+
+        var indices = GetDirectSuccessorNodes(ent, index.Value);
+        var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+        foreach (var i in indices)
+        {
+            if (TryGetNode(ent, i, out var successor))
+                output.Add(successor.Value);
+        }
+
+        return output;
+    }
+
+    /// <summary>
+    /// Gets set of node indices (in artifact node container) which are direct successors to node with passed node index.
+    /// </summary>
+    /// <remarks>
+    /// Direct successors are nodes, which are connected by edges
+    /// directly to target node, and are on incoming ('TO') side of edge connection.
+    /// </remarks>
+    public HashSet<int> GetDirectSuccessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+        DebugTools.Assert(nodeIdx >= 0 && nodeIdx < ent.Comp.NodeVertices.Length, "node index is out of bounds!");
+
+        var indices = new HashSet<int>();
+        for (var i = 0; i < ent.Comp.NodeAdjacencyMatrixColumns; i++)
+        {
+            if (ent.Comp.NodeAdjacencyMatrix[nodeIdx][i])
+                indices.Add(i);
+        }
+
+        return indices;
+    }
+
+    /// <summary>
+    /// Gets set of node entities, that are predecessors to passed node entity.
+    /// </summary>
+    /// <remarks>
+    /// Predecessors are nodes, which are connected by edges directly to target node on 'FROM' side of edge,
+    /// or connected to such node on 'FROM' side of edge, etc recursively.
+    /// </remarks>
+    public HashSet<Entity<XenoArtifactNodeComponent>> GetPredecessorNodes(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent> node)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        var predecessors = GetPredecessorNodes(ent, GetIndex((ent, ent.Comp), node));
+        var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+        foreach (var p in predecessors)
+        {
+            output.Add(GetNode((ent, ent.Comp), p));
+        }
+
+        return output;
+    }
+
+    /// <summary>
+    /// Gets set of node indices inside artifact node container, that are predecessors to entity with passed node index.
+    /// </summary>
+    /// <remarks>
+    /// Predecessors are nodes, which are connected by edges directly to target node on 'FROM' side of edge,
+    /// or connected to such node on 'FROM' side of edge, etc recursively.
+    /// </remarks>
+    public HashSet<int> GetPredecessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        var predecessors = GetDirectPredecessorNodes(ent, nodeIdx);
+        if (predecessors.Count == 0)
+            return new();
+
+        var output = new HashSet<int>();
+        foreach (var p in predecessors)
+        {
+            output.Add(p);
+            var recursivePredecessors = GetPredecessorNodes(ent, p);
+            foreach (var rp in recursivePredecessors)
+            {
+                output.Add(rp);
+            }
+        }
+
+        return output;
+    }
+
+    /// <summary>
+    /// Gets set of node entities, that are successors to passed node entity.
+    /// </summary>
+    /// <remarks>
+    /// Successors are nodes, which are connected by edges directly to target node on 'TO' side of edge,
+    /// or connected to such node on 'TO' side of edge, etc recursively.
+    /// </remarks>
+    public HashSet<Entity<XenoArtifactNodeComponent>> GetSuccessorNodes(Entity<XenoArtifactComponent?> ent, Entity<XenoArtifactNodeComponent> node)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        var successors = GetSuccessorNodes(ent, GetIndex((ent, ent.Comp), node));
+        var output = new HashSet<Entity<XenoArtifactNodeComponent>>();
+        foreach (var s in successors)
+        {
+            output.Add(GetNode((ent, ent.Comp), s));
+        }
+
+        return output;
+    }
+
+    /// <summary>
+    /// Gets set of node indices inside artifact node container, that are successors to entity with passed node index.
+    /// </summary>
+    /// <remarks>
+    /// Successors are nodes, which are connected by edges directly to target node on 'TO' side of edge,
+    /// or connected to such node on 'TO' side of edge, etc recursively.
+    /// </remarks>
+    public HashSet<int> GetSuccessorNodes(Entity<XenoArtifactComponent?> ent, int nodeIdx)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        var successors = GetDirectSuccessorNodes(ent, nodeIdx);
+        if (successors.Count == 0)
+            return new();
+
+        var output = new HashSet<int>();
+        foreach (var s in successors)
+        {
+            output.Add(s);
+            var recursiveSuccessors = GetSuccessorNodes(ent, s);
+            foreach (var rs in recursiveSuccessors)
+            {
+                output.Add(rs);
+            }
+        }
+
+        return output;
+    }
+
+    /// <summary>
+    /// Determines, if there is an edge (directed link) FROM one node TO other in passed artifact.
+    /// </summary>
+    /// <param name="ent">Artifact, inside which node container nodes are.</param>
+    /// <param name="from">Node FROM which existence of edge should be checked.</param>
+    /// <param name="to">Node TO which existance of edge should be checked.</param>
+    public bool NodeHasEdge(
+        Entity<XenoArtifactComponent?> ent,
+        Entity<XenoArtifactNodeComponent?> from,
+        Entity<XenoArtifactNodeComponent?> to
+    )
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return new();
+
+        var fromIdx = GetIndex((ent, ent.Comp), from);
+        var toIdx = GetIndex((ent, ent.Comp), to);
+
+        return ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx];
+    }
+
+    /// <summary>
+    /// Resizes the adjacency matrix and vertices array to <paramref name="newSize"/>,
+    /// or at least what it WOULD do if i wasn't forced to use shitty lists.
+    /// </summary>
+    protected void ResizeNodeGraph(Entity<XenoArtifactComponent> ent, int newSize)
+    {
+        Array.Resize(ref ent.Comp.NodeVertices, newSize);
+
+        while (ent.Comp.NodeAdjacencyMatrix.Count < newSize)
+        {
+            ent.Comp.NodeAdjacencyMatrix.Add(new());
+        }
+
+        foreach (var row in ent.Comp.NodeAdjacencyMatrix)
+        {
+            while (row.Count < newSize)
+            {
+                row.Add(false);
+            }
+        }
+
+        Dirty(ent);
+    }
+
+    /// <summary> Removes unlocking state from artifact. </summary>
+    private void CancelUnlockingOnGraphStructureChange(Entity<XenoArtifactComponent> ent)
+    {
+        if (!TryComp<XenoArtifactUnlockingComponent>(ent, out var unlockingComponent))
+            return;
+
+        Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> artifactEnt = (ent, unlockingComponent, ent.Comp);
+        CancelUnlockingState(artifactEnt);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Node.cs
new file mode 100644 (file)
index 0000000..e4c1a1b
--- /dev/null
@@ -0,0 +1,399 @@
+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);
+    }
+
+    /// <summary> Gets node component by node entity uid. </summary>
+    public XenoArtifactNodeComponent XenoArtifactNode(EntityUid uid)
+    {
+        return _nodeQuery.Get(uid);
+    }
+
+    public void SetNodeUnlocked(Entity<XenoArtifactNodeComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return;
+
+        if (ent.Comp.Attached is not { } netArtifact)
+            return;
+
+        var artifact = GetEntity(netArtifact);
+        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 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);
+                outSegment.Add((node, XenoArtifactNode(node)));
+            }
+
+            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(GetEntity(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, predecessorNodes.Count) * nodeComponent.BasePointValue * durabilityMultiplier);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs
new file mode 100644 (file)
index 0000000..3238bed
--- /dev/null
@@ -0,0 +1,167 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+public abstract partial class SharedXenoArtifactSystem
+{
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+    private EntityQuery<XenoArtifactUnlockingComponent> _unlockingQuery;
+
+    private void InitializeUnlock()
+    {
+        _unlockingQuery = GetEntityQuery<XenoArtifactUnlockingComponent>();
+
+        SubscribeLocalEvent<XenoArtifactUnlockingComponent, MapInitEvent>(OnUnlockingStarted);
+    }
+
+    /// <summary> Finish unlocking phase when the time is up. </summary>
+    private void UpdateUnlock(float _)
+    {
+        var query = EntityQueryEnumerator<XenoArtifactUnlockingComponent, XenoArtifactComponent>();
+        while (query.MoveNext(out var uid, out var unlock, out var comp))
+        {
+            if (_timing.CurTime < unlock.EndTime)
+                continue;
+
+            FinishUnlockingState((uid, unlock, comp));
+        }
+    }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    public bool CanUnlockNode(Entity<XenoArtifactNodeComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        var artifact = GetEntity(ent.Comp.Attached);
+        if (!TryComp<XenoArtifactComponent>(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;
+    }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    public void FinishUnlockingState(Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent)
+    {
+        string unlockAttemptResultMsg;
+        XenoArtifactComponent artifactComponent = ent;
+        XenoArtifactUnlockingComponent unlockingComponent = ent;
+
+        SoundSpecifier? soundEffect;
+        if (TryGetNodeFromUnlockState(ent, out var node))
+        {
+            SetNodeUnlocked((ent, artifactComponent), node.Value);
+            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);
+        RiseUnlockingFinished(ent, node);
+        artifactComponent.NextUnlockTime = _timing.CurTime + artifactComponent.UnlockStateRefractory;
+    }
+
+    public void CancelUnlockingState(Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent)
+    {
+        RemComp(ent, ent.Comp1);
+        RiseUnlockingFinished(ent, null);
+    }
+
+    /// <summary>
+    /// Gets first locked node that can be unlocked (it is locked and all predecessor are unlocked).
+    /// </summary>
+    public bool TryGetNodeFromUnlockState(
+        Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent,
+        [NotNullWhen(true)] out Entity<XenoArtifactNodeComponent>? node
+    )
+    {
+        node = null;
+
+        var artifactUnlockingComponent = ent.Comp1;
+        foreach (var nodeIndex in artifactUnlockingComponent.TriggeredNodeIndexes)
+        {
+            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);
+
+            // Make sure the two sets are identical
+            if (requiredIndices.Count != artifactUnlockingComponent.TriggeredNodeIndexes.Count
+                || !artifactUnlockingComponent.TriggeredNodeIndexes.All(requiredIndices.Contains))
+                continue;
+
+            node = curNode;
+            return true;
+        }
+
+        return node != null;
+    }
+
+    private void OnUnlockingStarted(Entity<XenoArtifactUnlockingComponent> ent, ref MapInitEvent args)
+    {
+        var unlockingStartedEvent = new ArtifactUnlockingStartedEvent();
+        RaiseLocalEvent(ent.Owner, ref unlockingStartedEvent);
+    }
+
+    private void RiseUnlockingFinished(
+        Entity<XenoArtifactUnlockingComponent, XenoArtifactComponent> ent,
+        Entity<XenoArtifactNodeComponent>? node
+    )
+    {
+        var unlockingFinishedEvent = new ArtifactUnlockingFinishedEvent(node);
+        RaiseLocalEvent(ent.Owner, ref unlockingFinishedEvent);
+    }
+
+}
+
+/// <summary>
+/// Event for starting artifact unlocking stage.
+/// </summary>
+[ByRefEvent]
+public record struct ArtifactUnlockingStartedEvent;
+
+/// <summary>
+/// Event for finishing artifact unlocking stage.
+/// </summary>
+/// <param name="UnlockedNode">Node which were unlocked. Null if stage was finished without new unlocks.</param>
+[ByRefEvent]
+public record struct ArtifactUnlockingFinishedEvent(EntityUid? UnlockedNode);
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAE.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAE.cs
new file mode 100644 (file)
index 0000000..fb592a0
--- /dev/null
@@ -0,0 +1,160 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Timing;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Map;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+public abstract partial class SharedXenoArtifactSystem
+{
+    [Dependency] private readonly UseDelaySystem _useDelay = default!;
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+
+    private void InitializeXAE()
+    {
+        SubscribeLocalEvent<XenoArtifactComponent, UseInHandEvent>(OnUseInHand);
+        SubscribeLocalEvent<XenoArtifactComponent, AfterInteractEvent>(OnAfterInteract);
+        SubscribeLocalEvent<XenoArtifactComponent, ActivateInWorldEvent>(OnActivateInWorld);
+    }
+
+    private void OnUseInHand(Entity<XenoArtifactComponent> ent, ref UseInHandEvent args)
+    {
+        if (args.Handled)
+            return;
+
+        args.Handled = TryActivateXenoArtifact(ent, args.User, args.User, Transform(args.User).Coordinates);
+    }
+
+    private void OnAfterInteract(Entity<XenoArtifactComponent> ent, ref AfterInteractEvent args)
+    {
+        if (args.Handled || !args.CanReach)
+            return;
+
+        args.Handled = TryActivateXenoArtifact(ent, args.User, args.Target, args.ClickLocation);
+    }
+
+    private void OnActivateInWorld(Entity<XenoArtifactComponent> ent, ref ActivateInWorldEvent args)
+    {
+        if (args.Handled || !args.Complex)
+            return;
+
+        args.Handled = TryActivateXenoArtifact(ent, args.User, args.Target, Transform(args.Target).Coordinates);
+    }
+
+    /// <summary>
+    /// Attempts to activate artifact nodes. 'active' are nodes that are marked as 'unlocked' and have no other successors, marked as 'unlocked'.
+    /// </summary>
+    /// <param name="artifact">Artifact entity, for which attempt to activate was made.</param>
+    /// <param name="user">Character that attempted to activate artifact.</param>
+    /// <param name="target">Target, on which artifact activation attempt was used (for hand-held artifact - it can be 'clicked' over someone).</param>
+    /// <param name="coordinates">Coordinates of <paramref name="target"/> entity.</param>
+    /// <returns>True, if activation was successful, false otherwise.</returns>
+    public bool TryActivateXenoArtifact(
+        Entity<XenoArtifactComponent> artifact,
+        EntityUid? user,
+        EntityUid? target,
+        EntityCoordinates coordinates
+    )
+    {
+        XenoArtifactComponent xenoArtifactComponent = artifact;
+        if (xenoArtifactComponent.Suppressed)
+            return false;
+
+        if (TryComp<UseDelayComponent>(artifact, out var delay) && !_useDelay.TryResetDelay((artifact, delay), true))
+            return false;
+
+        var success = false;
+        foreach (var node in GetActiveNodes(artifact))
+        {
+            success |= ActivateNode(artifact, node, user, target, coordinates);
+        }
+
+        if (!success)
+        {
+            _popup.PopupClient(Loc.GetString("artifact-activation-fail"), artifact, user);
+            return false;
+        }
+
+        // we raised event for each node activation,
+        // now we raise event for artifact itself. For animations and stuff.
+        var ev = new XenoArtifactActivatedEvent(
+            artifact,
+            user,
+            target,
+            coordinates
+        );
+        RaiseLocalEvent(artifact, ref ev);
+
+        if (user.HasValue)
+            _audio.PlayPredicted(xenoArtifactComponent.ForceActivationSoundSpecifier, artifact, user);
+        else
+            _audio.PlayPvs(xenoArtifactComponent.ForceActivationSoundSpecifier, artifact);
+
+        return true;
+    }
+
+    /// <summary>
+    /// Pushes node activation event and updates durability for activated node.
+    /// </summary>
+    /// <param name="artifact">Artifact entity, for which attempt to activate was made.</param>
+    /// <param name="node">Node entity, effect of which should be activated.</param>
+    /// <param name="user">Character that attempted to activate artifact.</param>
+    /// <param name="target">Target, on which artifact activation attempt was used (for hand-held artifact - it can be 'clicked' over someone).</param>
+    /// <param name="coordinates">Coordinates of <paramref name="target"/> entity.</param>
+    /// <param name="consumeDurability">Marker, if node durability should be adjusted as a result of activation.</param>
+    /// <returns>True, if activation was successful, false otherwise.</returns>
+    public bool ActivateNode(
+        Entity<XenoArtifactComponent> artifact,
+        Entity<XenoArtifactNodeComponent> node,
+        EntityUid? user,
+        EntityUid? target,
+        EntityCoordinates coordinates,
+        bool consumeDurability = true
+    )
+    {
+        if (node.Comp.Degraded)
+            return false;
+
+        _adminLogger.Add(
+            LogType.ArtifactNode,
+            LogImpact.Low,
+            $"{ToPrettyString(artifact.Owner)} node {ToPrettyString(node)} got activated at {coordinates}"
+        );
+        if (consumeDurability)
+        {
+            AdjustNodeDurability((node, node.Comp), -1);
+        }
+
+        var ev = new XenoArtifactNodeActivatedEvent(artifact, node, user, target, coordinates);
+        RaiseLocalEvent(node, ref ev);
+        return true;
+    }
+}
+
+/// <summary>
+/// Event of node activation. Should lead to node effect being activated.
+/// </summary>
+/// <param name="Artifact">Artifact entity, for which attempt to activate was made.</param>
+/// <param name="Node">Node entity, effect of which should be activated.</param>
+/// <param name="User">Character that attempted to activate artifact.</param>
+/// <param name="Target">Target, on which artifact activation attempt was used (for hand-held artifact - it can be 'clicked' over someone).</param>
+/// <param name="Coordinates">Coordinates of <paramref name="Target"/> entity.</param>
+[ByRefEvent]
+public readonly record struct XenoArtifactNodeActivatedEvent(
+    Entity<XenoArtifactComponent> Artifact,
+    Entity<XenoArtifactNodeComponent> Node,
+    EntityUid? User,
+    EntityUid? Target,
+    EntityCoordinates Coordinates
+);
+
+[ByRefEvent]
+public readonly record struct XenoArtifactActivatedEvent(
+    Entity<XenoArtifactComponent> Artifact,
+    EntityUid? User,
+    EntityUid? Target,
+    EntityCoordinates Coordinates
+);
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs
new file mode 100644 (file)
index 0000000..863f561
--- /dev/null
@@ -0,0 +1,112 @@
+using System.Linq;
+using Content.Shared.Chemistry;
+using Content.Shared.Damage;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Movement.Pulling.Events;
+using Content.Shared.Throwing;
+using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+public abstract partial class SharedXenoArtifactSystem
+{
+    private void InitializeXAT()
+    {
+        XATRelayLocalEvent<DamageChangedEvent>();
+        XATRelayLocalEvent<InteractUsingEvent>();
+        XATRelayLocalEvent<PullStartedMessage>();
+        XATRelayLocalEvent<AttackedEvent>();
+        XATRelayLocalEvent<XATToolUseDoAfterEvent>();
+        XATRelayLocalEvent<InteractHandEvent>();
+        XATRelayLocalEvent<ReactionEntityEvent>();
+        XATRelayLocalEvent<LandEvent>();
+
+        // special case this one because we need to order the messages
+        SubscribeLocalEvent<XenoArtifactComponent, ExaminedEvent>(OnExamined);
+    }
+
+    /// <summary> Relays artifact events for artifact nodes. </summary>
+    protected void XATRelayLocalEvent<T>() where T : notnull
+    {
+        SubscribeLocalEvent<XenoArtifactComponent, T>(RelayEventToNodes);
+    }
+
+    private void OnExamined(Entity<XenoArtifactComponent> ent, ref ExaminedEvent args)
+    {
+        using (args.PushGroup(nameof(XenoArtifactComponent)))
+        {
+            RelayEventToNodes(ent, ref args);
+        }
+    }
+
+    protected void RelayEventToNodes<T>(Entity<XenoArtifactComponent> ent, ref T args) where T : notnull
+    {
+        var ev = new XenoArchNodeRelayedEvent<T>(ent, args);
+
+        var nodes = GetAllNodes(ent);
+        foreach (var node in nodes)
+        {
+            RaiseLocalEvent(node, ref ev);
+        }
+    }
+
+    /// <summary>
+    /// Attempts to shift artifact into unlocking state, in which it is going to listen to interactions, that could trigger nodes.
+    /// </summary>
+    public void TriggerXenoArtifact(Entity<XenoArtifactComponent> ent, Entity<XenoArtifactNodeComponent> node)
+    {
+        // limits spontaneous chain activations, also prevents spamming every triggering tool to activate nodes
+        // without real knowledge about triggers
+        if (_timing.CurTime < ent.Comp.NextUnlockTime)
+            return;
+
+        var index = GetIndex(ent, node);
+
+        if (!_unlockingQuery.TryGetComponent(ent, out var unlockingComp))
+        {
+            unlockingComp = EnsureComp<XenoArtifactUnlockingComponent>(ent);
+            unlockingComp.EndTime = _timing.CurTime + ent.Comp.UnlockStateDuration;
+            Log.Debug($"{ToPrettyString(ent)} entered unlocking state");
+
+            if (_net.IsServer)
+                _popup.PopupEntity(Loc.GetString("artifact-unlock-state-begin"), ent);
+        }
+        else
+        {
+            var predecessorNodeIndices = GetPredecessorNodes((ent, ent), index);
+            var successorNodeIndices = GetSuccessorNodes((ent, ent), index);
+            if(unlockingComp.TriggeredNodeIndexes.Count == 0
+               || unlockingComp.TriggeredNodeIndexes.All(
+                   x => predecessorNodeIndices.Contains(x) || successorNodeIndices.Contains(x)
+                )
+               )
+                // we add time on each new trigger, if it is not going to fail us
+                unlockingComp.EndTime += ent.Comp.UnlockStateIncrementPerNode;
+        }
+
+        if (unlockingComp.TriggeredNodeIndexes.Add(index))
+        {
+            Dirty(ent, unlockingComp);
+        }
+    }
+}
+
+/// <summary>
+/// Event wrapper for XenoArch Trigger events.
+/// </summary>
+[ByRefEvent]
+public record struct XenoArchNodeRelayedEvent<TEvent>(Entity<XenoArtifactComponent> Artifact, TEvent Args)
+{
+    /// <summary>
+    /// Original event.
+    /// </summary>
+    public TEvent Args = Args;
+
+    /// <summary>
+    /// Artifact entity, that received original event.
+    /// </summary>
+    public Entity<XenoArtifactComponent> Artifact = Artifact;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.cs
new file mode 100644 (file)
index 0000000..aad2f51
--- /dev/null
@@ -0,0 +1,56 @@
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact;
+
+/// <summary>
+/// Handles all logic for generating and facilitating interactions with XenoArtifacts
+/// </summary>
+public abstract partial class SharedXenoArtifactSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+    [Dependency] protected readonly IRobustRandom RobustRandom = default!;
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<XenoArtifactComponent, ComponentStartup>(OnStartup);
+
+        InitializeNode();
+        InitializeUnlock();
+        InitializeXAT();
+        InitializeXAE();
+    }
+
+    /// <inheritdoc />
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        UpdateUnlock(frameTime);
+    }
+
+    /// <summary> As all artifacts have to contain nodes - we ensure that they are containers. </summary>
+    private void OnStartup(Entity<XenoArtifactComponent> ent, ref ComponentStartup args)
+    {
+        ent.Comp.NodeContainer = _container.EnsureContainer<Container>(ent, XenoArtifactComponent.NodeContainerId);
+    }
+
+    public void SetSuppressed(Entity<XenoArtifactComponent> ent, bool val)
+    {
+        if (ent.Comp.Suppressed == val)
+            return;
+
+        ent.Comp.Suppressed = val;
+        Dirty(ent);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/BaseXAESystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/BaseXAESystem.cs
new file mode 100644 (file)
index 0000000..6943782
--- /dev/null
@@ -0,0 +1,21 @@
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// Base class for 
+/// </summary>
+/// <typeparam name="T"></typeparam>
+public abstract class BaseXAESystem<T> : EntitySystem where T : Component
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<T, XenoArtifactNodeActivatedEvent>(OnActivated);
+    }
+
+    /// <summary>
+    /// Handler for node activation.
+    /// </summary>
+    /// <param name="ent">Entity (node) that got activated.</param>
+    /// <param name="args">Activation event (containing artifact and other useful info).</param>
+    protected abstract void OnActivated(Entity<T> ent, ref XenoArtifactNodeActivatedEvent args);
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEApplyComponentsComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEApplyComponentsComponent.cs
new file mode 100644 (file)
index 0000000..6f2eb81
--- /dev/null
@@ -0,0 +1,29 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// Applies components when effect is activated.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEApplyComponentsSystem))]
+public sealed partial class XAEApplyComponentsComponent : Component
+{
+    /// <summary>
+    /// Components that are permanently added to an entity when the effect's node is entered.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry Components = new();
+
+    /// <summary>
+    /// Does adding components need to be done only on first activation.
+    /// </summary>
+    [DataField]
+    public bool ApplyIfAlreadyHave { get; set; }
+
+    /// <summary>
+    /// Does component need to be restored when activated 2nd or more times.
+    /// </summary>
+    [DataField]
+    public bool RefreshOnReactivate { get; set; }
+}
similarity index 60%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs
rename to Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEDamageInAreaComponent.cs
index fb85446400c625404c93eb9d28a9c2bdff28a452..3725c1ba1f731e245af91608058b16068f3fc503 100644 (file)
@@ -1,44 +1,42 @@
-using Content.Shared.Damage;
+using Content.Shared.Damage;
 using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
 
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
 /// When activated, damages nearby entities.
 /// </summary>
-[RegisterComponent]
-public sealed partial class DamageNearbyArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAEDamageInAreaSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEDamageInAreaComponent : Component
 {
     /// <summary>
     /// The radius of entities that will be affected
     /// </summary>
-    [DataField("radius")]
+    [DataField, AutoNetworkedField]
     public float Radius = 3f;
 
     /// <summary>
     /// A whitelist for filtering certain damage.
     /// </summary>
-    /// <remarks>
-    /// TODO: The component portion, since it uses an array, does not work currently.
-    /// </remarks>
-    [DataField("whitelist")]
+    [DataField, AutoNetworkedField]
     public EntityWhitelist? Whitelist;
 
     /// <summary>
     /// The damage that is applied
     /// </summary>
-    [DataField("damage", required: true)]
+    [DataField(required: true), AutoNetworkedField]
     public DamageSpecifier Damage = default!;
 
     /// <summary>
     /// The chance that damage is applied to each individual entity
     /// </summary>
-    [DataField("damageChance")]
+    [DataField, AutoNetworkedField]
     public float DamageChance = 1f;
 
     /// <summary>
     /// Whether or not this should ignore resistances for the damage
     /// </summary>
-    [DataField("ignoreResistances")]
+    [DataField, AutoNetworkedField]
     public bool IgnoreResistances;
 }
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEKnockComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEKnockComponent.cs
new file mode 100644 (file)
index 0000000..72080e0
--- /dev/null
@@ -0,0 +1,16 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// This is used for using the "knock" spell when the artifact is activated
+/// </summary>
+[RegisterComponent, Access(typeof(XAEKnockSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEKnockComponent : Component
+{
+    /// <summary>
+    /// The range of the spell
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float KnockRange = 4f;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEPortalComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEPortalComponent.cs
new file mode 100644 (file)
index 0000000..2e922bb
--- /dev/null
@@ -0,0 +1,17 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+///     When activated artifact will spawn a pair of portals. First - right in artifact, Second - at random point of station.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEPortalSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEPortalComponent : Component
+{
+    /// <summary>
+    /// Entity that should be spawned as portal.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntProtoId PortalProto = "PortalArtifact";
+}
similarity index 51%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs
rename to Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAERandomTeleportInvokerComponent.cs
index d2be32e95c86b43e927bb600d1c05f9be294fc19..a9d9ba1299035be5574707fc8b18b65b7c9aa9ac 100644 (file)
@@ -1,21 +1,23 @@
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
 /// When activated, will teleport the artifact
 /// to a random position within a certain radius
 /// </summary>
-[RegisterComponent]
-public sealed partial class RandomTeleportArtifactComponent : Component
+[RegisterComponent, Access(typeof(XAERandomTeleportInvokerSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAERandomTeleportInvokerComponent : Component
 {
     /// <summary>
     /// The max distance that the artifact will teleport.
     /// </summary>
-    [DataField("maxRange")]
+    [DataField, AutoNetworkedField]
     public float MaxRange = 15f;
 
     /// <summary>
     /// The min distance that the artifact will teleport.
     /// </summary>
-    [DataField("minRange")]
+    [DataField, AutoNetworkedField]
     public float MinRange = 6f;
 }
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAERemoveCollisionComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAERemoveCollisionComponent.cs
new file mode 100644 (file)
index 0000000..cbc6ee1
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+///     Removes the masks/layers of hard fixtures from the artifact when added, allowing it to pass through walls
+///     and such.
+/// </summary>
+[RegisterComponent, Access(typeof(XAERemoveCollisionSystem)), NetworkedComponent]
+public sealed partial class XAERemoveCollisionComponent : Component;
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEShuffleComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/Components/XAEShuffleComponent.cs
new file mode 100644 (file)
index 0000000..38858e5
--- /dev/null
@@ -0,0 +1,17 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+
+/// <summary>
+/// When activated, will shuffle the position of all players
+/// within a certain radius.
+/// </summary>
+[RegisterComponent, Access(typeof(XAEShuffleSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class XAEShuffleComponent : Component
+{
+    /// <summary>
+    /// Radius, within which mobs would be switched.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Radius = 7.5f;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEApplyComponentsSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEApplyComponentsSystem.cs
new file mode 100644 (file)
index 0000000..e5bd33f
--- /dev/null
@@ -0,0 +1,38 @@
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for applying component-registry when artifact effect is activated.
+/// </summary>
+public sealed class XAEApplyComponentsSystem : BaseXAESystem<XAEApplyComponentsComponent>
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEApplyComponentsComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        if (!_timing.IsFirstTimePredicted)
+            return;
+
+        var artifact = args.Artifact;
+
+        foreach (var registry in ent.Comp.Components)
+        {
+            var componentType = registry.Value.Component.GetType();
+            if (!ent.Comp.ApplyIfAlreadyHave && EntityManager.HasComponent(artifact, componentType))
+            {
+                continue;
+            }
+
+            if (ent.Comp.RefreshOnReactivate)
+            {
+                EntityManager.RemoveComponent(artifact, componentType);
+            }
+
+            var clone = EntityManager.ComponentFactory.GetComponent(registry.Value);
+            EntityManager.AddComponent(artifact, clone);
+        }
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEDamageInAreaSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEDamageInAreaSystem.cs
new file mode 100644 (file)
index 0000000..405fa33
--- /dev/null
@@ -0,0 +1,43 @@
+using Content.Shared.Damage;
+using Content.Shared.Whitelist;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that damages entities from whitelist in area.
+/// </summary>
+public sealed class XAEDamageInAreaSystem : BaseXAESystem<XAEDamageInAreaComponent>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly DamageableSystem _damageable = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<EntityUid> _entitiesInRange = new();
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEDamageInAreaComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        if (!_timing.IsFirstTimePredicted)
+            return;
+
+        var damageInAreaComponent = ent.Comp;
+        _entitiesInRange.Clear();
+        _lookup.GetEntitiesInRange(ent.Owner, damageInAreaComponent.Radius, _entitiesInRange);
+        foreach (var entityInRange in _entitiesInRange)
+        {
+            if (!_random.Prob(damageInAreaComponent.DamageChance))
+                continue;
+
+            if (_whitelistSystem.IsWhitelistFail(damageInAreaComponent.Whitelist, entityInRange))
+                continue;
+
+            _damageable.TryChangeDamage(entityInRange, damageInAreaComponent.Damage, damageInAreaComponent.IgnoreResistances);
+        }
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEKnockSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEKnockSystem.cs
new file mode 100644 (file)
index 0000000..9cc9efe
--- /dev/null
@@ -0,0 +1,27 @@
+using Content.Shared.Magic.Events;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that opens doors in some area around.
+/// </summary>
+public sealed class XAEKnockSystem : BaseXAESystem<XAEKnockComponent>
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEKnockComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        if (!_timing.IsFirstTimePredicted)
+            return;
+
+        var ev = new KnockSpellEvent
+        {
+            Performer = ent.Owner,
+            Range = ent.Comp.KnockRange
+        };
+        RaiseLocalEvent(ev);
+    }
+}
similarity index 51%
rename from Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs
rename to Content.Shared/Xenoarchaeology/Artifact/XAE/XAEPortalSystem.cs
index 810d56e89bc2e2488cd4b5e84a2d76c0b1b73556..3d5355b5fd08ec05cde87d5b1642ac321dcf5a14 100644 (file)
@@ -1,30 +1,32 @@
-using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
 using Content.Shared.Mind.Components;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Teleportation.Systems;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
 using Robust.Shared.Collections;
 using Robust.Shared.Containers;
 using Robust.Shared.Random;
+using Robust.Shared.Timing;
 
-namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems;
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
 
-public sealed class PortalArtifactSystem : EntitySystem
+/// <summary>
+/// System for xeno artifact effect that creates temporary portal between places on station.
+/// </summary>
+public sealed class XAEPortalSystem : BaseXAESystem<XAEPortalComponent>
 {
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly LinkedEntitySystem _link = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
     [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
 
-    public override void Initialize()
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEPortalComponent> ent, ref XenoArtifactNodeActivatedEvent args)
     {
-        base.Initialize();
-        SubscribeLocalEvent<PortalArtifactComponent, ArtifactActivatedEvent>(OnActivate);
-    }
+        if (!_timing.IsFirstTimePredicted)
+            return;
 
-    private void OnActivate(Entity<PortalArtifactComponent> artifact, ref ArtifactActivatedEvent args)
-    {
-        var map = Transform(artifact).MapID;
+        var map = Transform(ent).MapID;
         var validMinds = new ValueList<EntityUid>();
         var mindQuery = EntityQueryEnumerator<MindContainerComponent, MobStateComponent, TransformComponent, MetaDataComponent>();
         while (mindQuery.MoveNext(out var uid, out var mc, out _, out var xform, out var meta))
@@ -35,18 +37,20 @@ public sealed class PortalArtifactSystem : EntitySystem
                 validMinds.Add(uid);
             }
         }
-        //this would only be 0 if there were a station full of AIs and no one else, in that case just stop this function
+        // this would only be 0 if there were a station full of AIs and no one else, in that case just stop this function
         if (validMinds.Count == 0)
             return;
 
-        var firstPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(artifact));
+        var offset = _random.NextVector2(2, 3);
+        var originWithOffset = args.Coordinates.Offset(offset);
+        var firstPortal = Spawn(ent.Comp.PortalProto, originWithOffset);
 
         var target = _random.Pick(validMinds);
 
-        var secondPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(target));
+        var secondPortal = Spawn(ent.Comp.PortalProto, _transform.GetMapCoordinates(target));
 
-        //Manual position swapping, because the portal that opens doesn't trigger a collision, and doesn't teleport targets the first time.
-        _transform.SwapPositions(target, artifact.Owner);
+        // Manual position swapping, because the portal that opens doesn't trigger a collision, and doesn't teleport targets the first time.
+        _transform.SwapPositions(target, ent.Owner);
 
         _link.TryLink(firstPortal, secondPortal, true);
     }
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/XAERandomTeleportInvokerSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/XAERandomTeleportInvokerSystem.cs
new file mode 100644 (file)
index 0000000..24563cb
--- /dev/null
@@ -0,0 +1,29 @@
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+public sealed class XAERandomTeleportInvokerSystem : BaseXAESystem<XAERandomTeleportInvokerComponent>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SharedTransformSystem _xform = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAERandomTeleportInvokerComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        if (!_timing.IsFirstTimePredicted)
+            return;
+        // todo: teleport person who activated artifact with artifact itself
+        var component = ent.Comp;
+
+        var xform = Transform(ent.Owner);
+        _popup.PopupCoordinates(Loc.GetString("blink-artifact-popup"), xform.Coordinates, PopupType.Medium);
+
+        var offsetTo = _random.NextVector2(component.MinRange, component.MaxRange);
+        _xform.SetCoordinates(ent.Owner, xform, xform.Coordinates.Offset(offsetTo));
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/XAERemoveCollisionSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/XAERemoveCollisionSystem.cs
new file mode 100644 (file)
index 0000000..5b557a7
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Systems;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System for xeno artifact effect that make artifact pass through other objects.
+/// </summary>
+public sealed class XAERemoveCollisionSystem : BaseXAESystem<XAERemoveCollisionComponent>
+{
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAERemoveCollisionComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        if (!TryComp<FixturesComponent>(ent.Owner, out var fixtures))
+            return;
+
+        foreach (var fixture in fixtures.Fixtures.Values)
+        {
+            _physics.SetHard(ent.Owner, fixture, false, fixtures);
+        }
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEShuffleSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAE/XAEShuffleSystem.cs
new file mode 100644 (file)
index 0000000..9eb627b
--- /dev/null
@@ -0,0 +1,59 @@
+using Content.Shared.Mobs.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAE.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAE;
+
+/// <summary>
+/// System that handles mob entities spacial shuffling effect.
+/// </summary>
+public sealed class XAEShuffleSystem : BaseXAESystem<XAEShuffleComponent>
+{
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly SharedTransformSystem _xform = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    private EntityQuery<MobStateComponent> _mobState;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<EntityUid> _entities= new();
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _mobState = GetEntityQuery<MobStateComponent>();
+    }
+
+    /// <inheritdoc />
+    protected override void OnActivated(Entity<XAEShuffleComponent> ent, ref XenoArtifactNodeActivatedEvent args)
+    {
+        if(!_timing.IsFirstTimePredicted)
+            return;
+
+        List<Entity<TransformComponent>> toShuffle = new();
+        _entities.Clear();
+        _lookup.GetEntitiesInRange(ent.Owner, ent.Comp.Radius, _entities, LookupFlags.Dynamic | LookupFlags.Sundries);
+        foreach (var entity in _entities)
+        {
+            if (!_mobState.HasComponent(entity))
+                continue;
+
+            var xform = Transform(entity);
+
+            toShuffle.Add((entity, xform));
+        }
+
+        _random.Shuffle(toShuffle);
+
+        while (toShuffle.Count > 1)
+        {
+            var ent1 = _random.PickAndTake(toShuffle);
+            var ent2 = _random.PickAndTake(toShuffle);
+            _xform.SwapPositions((ent1, ent1), (ent2, ent2));
+        }
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/BaseQueryUpdateXATSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/BaseQueryUpdateXATSystem.cs
new file mode 100644 (file)
index 0000000..5a7fc53
--- /dev/null
@@ -0,0 +1,51 @@
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// Base type for xeno artifact trigger systems, that are relied on updating loop.
+/// </summary>
+/// <typeparam name="T">Type of XAT component that system will work with.</typeparam>
+public abstract class BaseQueryUpdateXATSystem<T> : BaseXATSystem<T> where T : Component
+{
+    protected EntityQuery<XenoArtifactComponent> _xenoArtifactQuery;
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _xenoArtifactQuery = GetEntityQuery<XenoArtifactComponent>();
+    }
+
+    /// <inheritdoc />
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        // TODO: add a way to defer triggering artifacts to the end of the Update loop
+
+        var query = EntityQueryEnumerator<T, XenoArtifactNodeComponent>();
+        while (query.MoveNext(out var uid, out var comp, out var node))
+        {
+            if (node.Attached == null)
+                continue;
+
+            var artifact = _xenoArtifactQuery.Get(GetEntity(node.Attached.Value));
+
+            if (!CanTrigger(artifact, (uid, node)))
+                continue;
+
+            UpdateXAT(artifact, (uid, comp, node), frameTime);
+        }
+    }
+
+    /// <summary>
+    /// Handles update logic that is related to trigger component.
+    /// </summary>
+    protected abstract void UpdateXAT(
+        Entity<XenoArtifactComponent> artifact,
+        Entity<T, XenoArtifactNodeComponent> node,
+        float frameTime
+    );
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/BaseXATSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/BaseXATSystem.cs
new file mode 100644 (file)
index 0000000..d995a12
--- /dev/null
@@ -0,0 +1,88 @@
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// Base type for xeno artifact trigger systems. Each system should work with 1 trigger mechanics.
+/// </summary>
+/// <typeparam name="T">Type of XAT component that system will work with.</typeparam>
+public abstract class BaseXATSystem<T> : EntitySystem where T : Component
+{
+    [Dependency] protected readonly IGameTiming Timing = default!;
+    [Dependency] protected readonly SharedXenoArtifactSystem XenoArtifact = default!;
+
+    private EntityQuery<XenoArtifactUnlockingComponent> _unlockingQuery;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _unlockingQuery = GetEntityQuery<XenoArtifactUnlockingComponent>();
+    }
+
+    /// <summary>
+    /// Subscribes to event occurring on artifact (and by relaying - on node).
+    /// </summary>
+    /// <typeparam name="TEvent">Type of event to sub for.</typeparam>
+    /// <param name="eventHandler">Delegate that handles event.</param>
+    protected void XATSubscribeDirectEvent<TEvent>(XATEventHandler<TEvent> eventHandler) where TEvent : notnull
+    {
+        SubscribeLocalEvent<T, XenoArchNodeRelayedEvent<TEvent>>((uid, component, args) =>
+        {
+            var nodeComp = Comp<XenoArtifactNodeComponent>(uid);
+
+            if (!CanTrigger(args.Artifact, (uid, nodeComp)))
+                return;
+
+            var node = new Entity<T, XenoArtifactNodeComponent>(uid, component, nodeComp);
+            eventHandler.Invoke(args.Artifact, node, ref args.Args);
+        });
+    }
+
+    /// <summary>
+    /// Checks if node can be triggered.
+    /// </summary>
+    /// <param name="artifact">Artifact entity.</param>
+    /// <param name="node">Node from <see cref="artifact"/>.</param>
+    protected bool CanTrigger(Entity<XenoArtifactComponent> artifact, Entity<XenoArtifactNodeComponent> node)
+    {
+        if (Timing.CurTime < artifact.Comp.NextUnlockTime)
+            return false;
+
+        if (_unlockingQuery.TryComp(artifact, out var unlocking) &&
+            unlocking.TriggeredNodeIndexes.Contains(XenoArtifact.GetIndex(artifact, node)))
+            return false;
+
+        if (!XenoArtifact.CanUnlockNode((node, node)))
+            return false;
+
+        return true;
+    }
+
+    /// <summary>
+    /// Triggers node. Triggered nodes participate in node unlocking.
+    /// </summary>
+    protected void Trigger(Entity<XenoArtifactComponent> artifact, Entity<T, XenoArtifactNodeComponent> node)
+    {
+        if (!Timing.IsFirstTimePredicted)
+            return;
+
+        Log.Debug($"Activated trigger {typeof(T).Name} on node {ToPrettyString(node)} for {ToPrettyString(artifact)}");
+        XenoArtifact.TriggerXenoArtifact(artifact, (node.Owner, node.Comp2));
+    }
+
+    /// <summary>
+    /// Delegate for handling relayed artifact trigger events.
+    /// </summary>
+    /// <typeparam name="TEvent">Event type to be handled.</typeparam>
+    /// <param name="artifact">Artifact, on which event occurred.</param>
+    /// <param name="node">Node which for which event were relayed.</param>
+    /// <param name="args">Event data.</param>
+    protected delegate void XATEventHandler<TEvent>(
+        Entity<XenoArtifactComponent> artifact,
+        Entity<T, XenoArtifactNodeComponent> node,
+        ref TEvent args
+    ) where TEvent : notnull;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATCompNearbyComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATCompNearbyComponent.cs
new file mode 100644 (file)
index 0000000..90c697a
--- /dev/null
@@ -0,0 +1,30 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used a XAT that activates when an entity fulfilling the given whitelist is nearby the artifact.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATCompNearbyComponent)), AutoGenerateComponentState]
+public sealed partial class XATCompNearbyComponent : Component
+{
+    /// <summary>
+    /// Component name that is required to activate trigger.
+    /// Is spelled without 'Component' suffix.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(ComponentNameSerializer)), AutoNetworkedField]
+    public string RequireComponentWithName = "Item";
+
+    /// <summary>
+    /// Radius, in which trigger going to search for entity with component.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Radius = 5;
+
+    /// <summary>
+    /// Required entities count.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int Count = 1;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATDamageThresholdReachedComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATDamageThresholdReachedComponent.cs
new file mode 100644 (file)
index 0000000..4f5ceb1
--- /dev/null
@@ -0,0 +1,38 @@
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated after a certain amount of damage is dealt.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(XATDamageThresholdReachedSystem))]
+public sealed partial class XATDamageThresholdReachedComponent : Component
+{
+    /// <summary>
+    /// Damage, accumulated by artifact so far. Is cleared on node activation.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public DamageSpecifier AccumulatedDamage = new();
+
+    /// <summary>
+    /// Damage that is required to activate trigger, grouped by damage type.
+    /// Only one damage type is required, amount of damage must exceed set limit.
+    /// <see cref="GroupsNeeded"/> is not required to activate trigger if this
+    /// requirement is satisfied.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public Dictionary<ProtoId<DamageTypePrototype>, FixedPoint2> TypesNeeded = new();
+
+    /// <summary>
+    /// Damage that is required to activate trigger, grouped by damage group.
+    /// Only one damage type is required, amount of damage must exceed set limit.
+    /// <see cref="TypesNeeded"/> is not required to activate trigger if this
+    /// requirement is satisfied.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> GroupsNeeded = new();
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATDeathComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATDeathComponent.cs
new file mode 100644 (file)
index 0000000..48a0916
--- /dev/null
@@ -0,0 +1,16 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that activates when something dies nearby.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATDeathSystem)), AutoGenerateComponentState]
+public sealed partial class XATDeathComponent : Component
+{
+    /// <summary>
+    /// Range within which artifact going to listen to death event.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Range = 15;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATExaminableTextComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATExaminableTextComponent.cs
new file mode 100644 (file)
index 0000000..b097389
--- /dev/null
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact node that puts examine text on the artifact itself. Useful for flavor
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedXenoArtifactSystem)), AutoGenerateComponentState]
+public sealed partial class XATExaminableTextComponent : Component
+{
+    /// <summary> Text to display. </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public LocId ExamineText;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATExamineComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATExamineComponent.cs
new file mode 100644 (file)
index 0000000..1b00141
--- /dev/null
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact that is activated when someone examines it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATExamineSystem))]
+public sealed partial class XATExamineComponent : Component;
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATInteractionComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATInteractionComponent.cs
new file mode 100644 (file)
index 0000000..988738f
--- /dev/null
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that activates after any type of physical interaction.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATInteractionSystem))]
+public sealed partial class XATInteractionComponent : Component;
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATItemLandComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATItemLandComponent.cs
new file mode 100644 (file)
index 0000000..153e532
--- /dev/null
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for an artifact trigger that activates when a thrown item lands on the ground.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATItemLandSystem))]
+public sealed partial class XATItemLandComponent : Component;
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATReactiveComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATReactiveComponent.cs
new file mode 100644 (file)
index 0000000..1af168b
--- /dev/null
@@ -0,0 +1,38 @@
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that activates when a reaction occurs on the artifact.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATReactiveSystem)), AutoGenerateComponentState]
+public sealed partial class XATReactiveComponent : Component
+{
+    [DataField, AutoNetworkedField]
+    public List<ReactionMethod> ReactionMethods = new() { ReactionMethod.Touch };
+
+    /// <summary>
+    /// Reagents that are required in quantity <see cref="MinQuantity"/> to activate trigger.
+    /// If any of them are present in required amount - activation will be triggered.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public HashSet<ProtoId<ReagentPrototype>> Reagents = new();
+
+    /// <summary>
+    /// ReagentGroups that are required in quantity <see cref="MinQuantity"/> to activate trigger.
+    /// If any of them are present in required amount - activation will be triggered.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public HashSet<ProtoId<ReactiveGroupPrototype>> ReactiveGroups = new();
+
+    /// <summary>
+    /// Min amount of reagent to trigger.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public FixedPoint2 MinQuantity = 5f;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATTimerComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATTimerComponent.cs
new file mode 100644 (file)
index 0000000..d23b222
--- /dev/null
@@ -0,0 +1,23 @@
+using Content.Shared.Destructible.Thresholds;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that self-activates at a regular interval
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATTimerSystem)), AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class XATTimerComponent : Component
+{
+    /// <summary>
+    /// Next time timer going to activate.
+    /// </summary>
+    [DataField, AutoNetworkedField, AutoPausedField]
+    public TimeSpan NextActivation;
+
+    /// <summary>
+    /// Delay between activations.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public MinMax PossibleDelayInSeconds;
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATToolUseComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/Components/XATToolUseComponent.cs
new file mode 100644 (file)
index 0000000..f07e6ae
--- /dev/null
@@ -0,0 +1,49 @@
+using Content.Shared.DoAfter;
+using Content.Shared.Tools;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+/// <summary>
+/// This is used for a xenoarch trigger that is activated by a tool being used on it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(XATToolUseSystem)), AutoGenerateComponentState]
+public sealed partial class XATToolUseComponent : Component
+{
+    /// <summary>
+    /// Tool to be used.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public ProtoId<ToolQualityPrototype> RequiredTool;
+
+    /// <summary>
+    /// Time that using tool on artifact will take.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Delay = 3;
+
+    /// <summary>
+    /// Amount of fuel using tool will take (for devices such as Welding tool).
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Fuel;
+}
+
+/// <summary> Do after that will be used if proper tool was used on artifact with <see cref="XATToolUseComponent"/>. </summary>
+[Serializable, NetSerializable]
+public sealed partial class XATToolUseDoAfterEvent : DoAfterEvent
+{
+    public NetEntity Node;
+
+    public XATToolUseDoAfterEvent(NetEntity node)
+    {
+        Node = node;
+    }
+
+    public override DoAfterEvent Clone()
+    {
+        return this;
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATCompNearbySystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATCompNearbySystem.cs
new file mode 100644 (file)
index 0000000..8036ec0
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires some entity/entities with certain component on them nearby.
+/// </summary>
+public sealed class XATCompNearbySystem : BaseQueryUpdateXATSystem<XATCompNearbyComponent>
+{
+    [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+    /// <summary> Pre-allocated and re-used collection.</summary>
+    private readonly HashSet<Entity<IComponent>> _entities = new();
+
+    /// <inheritdoc />
+    protected override void UpdateXAT(
+        Entity<XenoArtifactComponent> artifact,
+        Entity<XATCompNearbyComponent, XenoArtifactNodeComponent> node,
+        float frameTime
+    )
+    {
+        var compNearbyComponent = node.Comp1;
+
+        var pos = _transform.GetMapCoordinates(artifact);
+        var comp = EntityManager.ComponentFactory.GetRegistration(compNearbyComponent.RequireComponentWithName);
+
+        _entities.Clear();
+        _entityLookup.GetEntitiesInRange(comp.Type, pos, compNearbyComponent.Radius, _entities);
+        if (_entities.Count >= compNearbyComponent.Count)
+            Trigger(artifact, node);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATDamageThresholdReachedSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATDamageThresholdReachedSystem.cs
new file mode 100644 (file)
index 0000000..eab832c
--- /dev/null
@@ -0,0 +1,65 @@
+using Content.Shared.Damage;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires certain damage to be applied to artifact within a timeframe.
+/// </summary>
+public sealed class XATDamageThresholdReachedSystem : BaseXATSystem<XATDamageThresholdReachedComponent>
+{
+    [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        XATSubscribeDirectEvent<DamageChangedEvent>(OnDamageChanged);
+    }
+
+    private void OnDamageChanged(Entity<XenoArtifactComponent> artifact, Entity<XATDamageThresholdReachedComponent, XenoArtifactNodeComponent> node, ref DamageChangedEvent args)
+    {
+        if (!args.DamageIncreased || args.DamageDelta == null || args.Origin == artifact.Owner)
+            return;
+
+        var damageTriggerComponent = node.Comp1;
+        if (Timing.IsFirstTimePredicted)
+            damageTriggerComponent.AccumulatedDamage += args.DamageDelta;
+
+        foreach (var (type, needed) in damageTriggerComponent.TypesNeeded)
+        {
+            if (damageTriggerComponent.AccumulatedDamage.DamageDict.GetValueOrDefault(type) >= needed)
+            {
+                InvokeTrigger(artifact, node);
+                return; // intentional. Do not continue checks
+            }
+        }
+
+        foreach (var (group, needed) in damageTriggerComponent.GroupsNeeded)
+        {
+            var damageGroupPrototype = _prototype.Index(group);
+            if (!damageTriggerComponent.AccumulatedDamage.TryGetDamageInGroup(damageGroupPrototype, out var damage))
+                continue;
+
+            if (damage >= needed)
+            {
+                InvokeTrigger(artifact, node);
+                return; // intentional. Do not continue checks
+            }
+        }
+    }
+
+    private void InvokeTrigger(
+        Entity<XenoArtifactComponent> artifact,
+        Entity<XATDamageThresholdReachedComponent, XenoArtifactNodeComponent> node
+    )
+    {
+        var damageTriggerComponent = node.Comp1;
+        damageTriggerComponent.AccumulatedDamage.DamageDict.Clear();
+        Dirty(node, damageTriggerComponent);
+        Trigger(artifact, node);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATDeathSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATDeathSystem.cs
new file mode 100644 (file)
index 0000000..747bd82
--- /dev/null
@@ -0,0 +1,49 @@
+using Content.Shared.Mobs;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires death of some mob near artifact.
+/// </summary>
+public sealed class XATDeathSystem : BaseXATSystem<XATDeathComponent>
+{
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+    private EntityQuery<XenoArtifactComponent> _xenoArtifactQuery;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        _xenoArtifactQuery = GetEntityQuery<XenoArtifactComponent>();
+
+        SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
+    }
+
+    private void OnMobStateChanged(MobStateChangedEvent args)
+    {
+        if (args.NewMobState != MobState.Dead)
+            return;
+
+        var targetCoords = Transform(args.Target).Coordinates;
+
+        var query = EntityQueryEnumerator<XATDeathComponent, XenoArtifactNodeComponent>();
+        while (query.MoveNext(out var uid, out var comp, out var node))
+        {
+            if (node.Attached == null)
+                continue;
+
+            var artifact = _xenoArtifactQuery.Get(GetEntity(node.Attached.Value));
+
+            if (!CanTrigger(artifact, (uid, node)))
+                continue;
+
+            var artifactCoords = Transform(artifact).Coordinates;
+            if (_transform.InRange(targetCoords, artifactCoords, comp.Range))
+                Trigger(artifact, (uid, comp, node));
+        }
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATExaminableTextSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATExaminableTextSystem.cs
new file mode 100644 (file)
index 0000000..affb850
--- /dev/null
@@ -0,0 +1,28 @@
+using Content.Shared.Examine;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <remarks>
+/// System for marking xeno artifact with certain text.
+/// </remarks>
+/// <remarks> Not actually a trigger but nice and easy to use. </remarks>
+public sealed class XATExaminableTextSystem : BaseXATSystem<XATExaminableTextComponent>
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        XATSubscribeDirectEvent<ExaminedEvent>(OnExamined);
+    }
+
+    private void OnExamined(Entity<XenoArtifactComponent> artifact, Entity<XATExaminableTextComponent, XenoArtifactNodeComponent> node, ref ExaminedEvent args)
+    {
+        if (!args.IsInDetailsRange)
+            return;
+
+        args.PushMarkup(Loc.GetString(node.Comp1.ExamineText));
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATExamineSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATExamineSystem.cs
new file mode 100644 (file)
index 0000000..44616ee
--- /dev/null
@@ -0,0 +1,32 @@
+using Content.Shared.Examine;
+using Content.Shared.Ghost;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires player to examine details of artifact.
+/// </summary>
+public sealed class XATExamineSystem : BaseXATSystem<XATExamineComponent>
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        XATSubscribeDirectEvent<ExaminedEvent>(OnExamine);
+    }
+
+    private void OnExamine(Entity<XenoArtifactComponent> artifact, Entity<XATExamineComponent, XenoArtifactNodeComponent> node, ref ExaminedEvent args)
+    {
+        if (!args.IsInDetailsRange)
+            return;
+
+        if (HasComp<GhostComponent>(args.Examiner))
+            return;
+
+        Trigger(artifact, node);
+        args.PushMarkup(Loc.GetString("artifact-examine-trigger-desc"));
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATInteractionSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATInteractionSystem.cs
new file mode 100644 (file)
index 0000000..940e461
--- /dev/null
@@ -0,0 +1,42 @@
+using Content.Shared.Interaction;
+using Content.Shared.Movement.Pulling.Events;
+using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires some way of 'using' (with default action) an artifact entity.
+/// </summary>
+public sealed class XATInteractionSystem : BaseXATSystem<XATInteractionComponent>
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        XATSubscribeDirectEvent<PullStartedMessage>(OnPullStart);
+        XATSubscribeDirectEvent<AttackedEvent>(OnAttacked);
+        XATSubscribeDirectEvent<InteractHandEvent>(OnInteractHand);
+    }
+
+    private void OnPullStart(Entity<XenoArtifactComponent> artifact, Entity<XATInteractionComponent, XenoArtifactNodeComponent> node, ref PullStartedMessage args)
+    {
+        Trigger(artifact, node);
+    }
+
+    private void OnAttacked(Entity<XenoArtifactComponent> artifact, Entity<XATInteractionComponent, XenoArtifactNodeComponent> node, ref AttackedEvent args)
+    {
+        Trigger(artifact, node);
+    }
+
+    private void OnInteractHand(Entity<XenoArtifactComponent> artifact, Entity<XATInteractionComponent, XenoArtifactNodeComponent> node, ref InteractHandEvent args)
+    {
+        if (args.Handled)
+            return;
+
+        args.Handled = true;
+        Trigger(artifact, node);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATItemLandSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATItemLandSystem.cs
new file mode 100644 (file)
index 0000000..943d1ef
--- /dev/null
@@ -0,0 +1,24 @@
+using Content.Shared.Throwing;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires hand-held artifact to be thrown (and land).
+/// </summary>
+public sealed class XATItemLandSystem : BaseXATSystem<XATItemLandComponent>
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        XATSubscribeDirectEvent<LandEvent>(OnLand);
+    }
+
+    private void OnLand(Entity<XenoArtifactComponent> artifact, Entity<XATItemLandComponent, XenoArtifactNodeComponent> node, ref LandEvent args)
+    {
+        Trigger(artifact, node);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATReactiveSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATReactiveSystem.cs
new file mode 100644 (file)
index 0000000..6b0c425
--- /dev/null
@@ -0,0 +1,58 @@
+using Content.Shared.Chemistry;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that requires some chemical reagent.
+/// </summary>
+public sealed class XATReactiveSystem : BaseXATSystem<XATReactiveComponent>
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        XATSubscribeDirectEvent<ReactionEntityEvent>(OnReaction);
+    }
+
+    private void OnReaction(Entity<XenoArtifactComponent> artifact, Entity<XATReactiveComponent, XenoArtifactNodeComponent> node, ref ReactionEntityEvent args)
+    {
+        var reactiveTriggerComponent = node.Comp1;
+        if (!reactiveTriggerComponent.ReactionMethods.Contains(args.Method))
+            return;
+
+        if (args.ReagentQuantity.Quantity < reactiveTriggerComponent.MinQuantity)
+            return;
+
+        if (!reactiveTriggerComponent.Reagents.Contains(args.Reagent.ID))
+            return;
+
+        if (reactiveTriggerComponent.ReactiveGroups?.Count > 0 && !ReagentHaveReactiveGroup(args, reactiveTriggerComponent))
+            return;
+
+        Trigger(artifact, node);
+    }
+
+    private static bool ReagentHaveReactiveGroup(ReactionEntityEvent args, XATReactiveComponent reactiveTriggerComponent)
+    {
+        var reactiveReagentEffectEntries = args.Reagent.ReactiveEffects;
+        if (reactiveReagentEffectEntries == null)
+        {
+            return false;
+        }
+
+        var reactiveGroups = reactiveTriggerComponent.ReactiveGroups;
+        foreach(var reactiveGroup in reactiveGroups)
+        {
+            if (reactiveReagentEffectEntries.TryGetValue(reactiveGroup, out var effectEntry)
+                && effectEntry.Methods?.Contains(args.Method) == true)
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATTimerSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATTimerSystem.cs
new file mode 100644 (file)
index 0000000..af77711
--- /dev/null
@@ -0,0 +1,68 @@
+using Content.Shared.Examine;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+using Robust.Shared.Random;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// System for xeno artifact trigger that activates from time to time on schedule.
+/// </summary>
+public sealed class XATTimerSystem : BaseQueryUpdateXATSystem<XATTimerComponent>
+{
+    [Dependency] private readonly IRobustRandom _robustRandom = default!;
+
+    /// <inheritdoc />
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<XATTimerComponent, MapInitEvent>(OnMapInit);
+        XATSubscribeDirectEvent<ExaminedEvent>(OnExamine);
+    }
+
+    // We handle the timer resetting here because we need to keep it updated even if the node isn't able to unlock.
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var timerQuery = EntityQueryEnumerator<XATTimerComponent>();
+        while (timerQuery.MoveNext(out var uid, out var timer))
+        {
+            if (Timing.CurTime < timer.NextActivation)
+                continue;
+            timer.NextActivation += GetNextDelay(timer);
+            Dirty(uid, timer);
+        }
+    }
+
+    /// <inheritdoc />
+    protected override void UpdateXAT(Entity<XenoArtifactComponent> artifact, Entity<XATTimerComponent, XenoArtifactNodeComponent> node, float frameTime)
+    {
+        if (Timing.CurTime > node.Comp1.NextActivation)
+            Trigger(artifact, node);
+    }
+
+    private void OnMapInit(Entity<XATTimerComponent> ent, ref MapInitEvent args)
+    {
+        var delay = GetNextDelay(ent);
+        ent.Comp.NextActivation = Timing.CurTime + delay;
+        Dirty(ent);
+    }
+
+    private void OnExamine(Entity<XenoArtifactComponent> artifact, Entity<XATTimerComponent, XenoArtifactNodeComponent> node, ref ExaminedEvent args)
+    {
+        if (!args.IsInDetailsRange)
+            return;
+
+        args.PushMarkup(
+            Loc.GetString("xenoarch-trigger-examine-timer",
+            ("time", MathF.Ceiling((float) (node.Comp1.NextActivation - Timing.CurTime).TotalSeconds)))
+        );
+    }
+
+    private TimeSpan GetNextDelay(XATTimerComponent comp)
+    {
+        return TimeSpan.FromSeconds(comp.PossibleDelayInSeconds.Next(_robustRandom));
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/XAT/XATToolUseSystem.cs b/Content.Shared/Xenoarchaeology/Artifact/XAT/XATToolUseSystem.cs
new file mode 100644 (file)
index 0000000..ca2692a
--- /dev/null
@@ -0,0 +1,52 @@
+using Content.Shared.Interaction;
+using Content.Shared.Tools.Components;
+using Content.Shared.Tools.Systems;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Artifact.XAT.Components;
+
+namespace Content.Shared.Xenoarchaeology.Artifact.XAT;
+
+/// <summary>
+/// This handles <see cref="XATToolUseComponent"/>
+/// </summary>
+public sealed class XATToolUseSystem : BaseXATSystem<XATToolUseComponent>
+{
+    [Dependency] private readonly SharedToolSystem _tool = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        XATSubscribeDirectEvent<InteractUsingEvent>(OnInteractUsing);
+        XATSubscribeDirectEvent<XATToolUseDoAfterEvent>(OnToolUseComplete);
+    }
+
+    private void OnToolUseComplete(Entity<XenoArtifactComponent> artifact, Entity<XATToolUseComponent, XenoArtifactNodeComponent> node, ref XATToolUseDoAfterEvent args)
+    {
+        if (args.Cancelled)
+            return;
+
+        if (GetEntity(args.Node) != node.Owner)
+            return;
+
+        Trigger(artifact, node);
+        args.Handled = true;
+    }
+
+    private void OnInteractUsing(Entity<XenoArtifactComponent> artifact, Entity<XATToolUseComponent, XenoArtifactNodeComponent> node, ref InteractUsingEvent args)
+    {
+        if (!TryComp<ToolComponent>(args.Used, out var tool))
+            return;
+
+        var toolUseTriggerComponent = node.Comp1;
+        args.Handled = _tool.UseTool(args.Used,
+            args.User,
+            artifact,
+            toolUseTriggerComponent.Delay,
+            toolUseTriggerComponent.RequiredTool,
+            new XATToolUseDoAfterEvent(GetNetEntity(node)),
+            fuel: toolUseTriggerComponent.Fuel,
+            tool);
+    }
+}
diff --git a/Content.Shared/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs b/Content.Shared/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs
new file mode 100644 (file)
index 0000000..3247e33
--- /dev/null
@@ -0,0 +1,52 @@
+using Content.Shared.DeviceLinking;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
+
+/// <summary>
+/// The console that is used for artifact analysis
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+public sealed partial class AnalysisConsoleComponent : Component
+{
+    /// <summary>
+    /// The analyzer entity the console is linked.
+    /// Can be null if not linked.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public NetEntity? AnalyzerEntity;
+
+    [DataField]
+    public SoundSpecifier? ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
+
+    /// <summary>
+    /// The sound played when an artifact has points extracted.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? ExtractSound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg")
+    {
+        Params = new AudioParams
+        {
+            Volume = 4,
+        }
+    };
+
+    /// <summary>
+    /// The machine linking port for the analyzer
+    /// </summary>
+    [DataField]
+    public ProtoId<SourcePortPrototype> LinkingPort = "ArtifactAnalyzerSender";
+}
+
+[Serializable, NetSerializable]
+public enum ArtifactAnalyzerUiKey : byte
+{
+    Key
+}
+
+[Serializable, NetSerializable]
+public sealed class AnalysisConsoleExtractButtonPressedMessage : BoundUserInterfaceMessage;
+
diff --git a/Content.Shared/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs b/Content.Shared/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs
new file mode 100644 (file)
index 0000000..05d17bb
--- /dev/null
@@ -0,0 +1,38 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
+
+/// <summary>
+/// A machine that is combined and linked to the <see cref="AnalysisConsoleComponent"/>
+/// in order to analyze artifacts and extract points.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+public sealed partial class ArtifactAnalyzerComponent : Component
+{
+    /// <summary>
+    /// How long it takes to analyze an artifact
+    /// </summary>
+    [DataField]
+    public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(30);
+
+    /// <summary>
+    /// The current artifact placed on this analyzer.
+    /// Can be null if none are present.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? CurrentArtifact;
+
+    /// <summary>
+    /// The corresponding console entity.
+    /// Can be null if not linked.
+    /// </summary>
+    [ViewVariables, AutoNetworkedField]
+    public EntityUid? Console;
+
+    /// <summary>
+    /// Marker, if artifact graph data is ready for printing.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool ReadyToPrint = false;
+}
similarity index 98%
rename from Content.Shared/Xenoarchaeology/Equipment/ArtifactCrusherComponent.cs
rename to Content.Shared/Xenoarchaeology/Equipment/Components/ArtifactCrusherComponent.cs
index e21cedb6f9a7a9b262e05e2854427fe8ed0af922..69a682b6602087385f9ffbafc0f0f3353b09ca97 100644 (file)
@@ -9,7 +9,7 @@ using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Shared.Xenoarchaeology.Equipment;
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
 
 /// <summary>
 /// This is an entity storage that, when activated, crushes the artifact inside of it and gives artifact fragments.
diff --git a/Content.Shared/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs b/Content.Shared/Xenoarchaeology/Equipment/Components/NodeScannerComponent.cs
new file mode 100644 (file)
index 0000000..89403ee
--- /dev/null
@@ -0,0 +1,58 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
+
+/// <summary>
+/// Component for managing data stored on NodeScanner hand-held device.
+/// Can store snapshot list of currently triggered artifact nodes.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedNodeScannerSystem))]
+public sealed partial class NodeScannerComponent : Component
+{
+    /// <summary>
+    /// Identity-names (3-digit codes) of nodes that are triggered on scanned artifact.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public HashSet<string> TriggeredNodesSnapshot = new();
+
+    /// <summary>
+    /// State of artifact on the moment of scanning.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public ArtifactState ArtifactState;
+
+    /// <summary>
+    /// Time until next unlocking of scanned artifact can be started.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan? WaitTime;
+
+    /// <summary>
+    /// Moment of gametime, at which last artifact scanning was done.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan? ScannedAt;
+}
+
+/// <summary>
+/// Displayable to player artifact states.
+/// </summary>
+[Serializable, NetSerializable]
+public enum ArtifactState
+{
+    /// <summary> Unused default. </summary>
+    None,
+    /// <summary> Artifact is ready to start unlocking. </summary>
+    Ready,
+    /// <summary> Artifact is in unlocking state, listening to any additional trigger. </summary>
+    Unlocking,
+    /// <summary> Artifact unlocking is on cooldown, nodes could not be triggered. </summary>
+    Cooldown
+}
+
+[Serializable, NetSerializable]
+public enum NodeScannerUiKey : byte
+{
+    Key
+}
diff --git a/Content.Shared/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs b/Content.Shared/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs
new file mode 100644 (file)
index 0000000..831813b
--- /dev/null
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Xenoarchaeology.Equipment.Components;
+
+/// <summary>
+///     Suppress artifact activation, when entity is placed inside this container.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SuppressArtifactContainerSystem))]
+public sealed partial class SuppressArtifactContainerComponent : Component;
diff --git a/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs
deleted file mode 100644 (file)
index 07f2a60..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-using Robust.Shared.Serialization;
-using Robust.Shared.Utility;
-
-namespace Content.Shared.Xenoarchaeology.Equipment;
-
-[Serializable, NetSerializable]
-public enum ArtifactAnalzyerUiKey : byte
-{
-    Key
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleServerSelectionMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleScanButtonPressedMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsolePrintButtonPressedMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleExtractButtonPressedMessage : BoundUserInterfaceMessage
-{
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleBiasButtonPressedMessage(bool isDown) : BoundUserInterfaceMessage
-{
-    public bool IsDown = isDown;
-}
-
-[Serializable, NetSerializable]
-public sealed class AnalysisConsoleUpdateState(
-    NetEntity? artifact,
-    bool analyzerConnected,
-    bool serverConnected,
-    bool canScan,
-    bool canPrint,
-    FormattedMessage? scanReport,
-    bool scanning,
-    bool paused,
-    TimeSpan? startTime,
-    TimeSpan? accumulatedRunTime,
-    TimeSpan? totalTime,
-    int pointAmount,
-    bool isTraversalDown
-)
-    : BoundUserInterfaceState
-{
-    public NetEntity? Artifact = artifact;
-    public bool AnalyzerConnected = analyzerConnected;
-    public bool ServerConnected = serverConnected;
-    public bool CanScan = canScan;
-    public bool CanPrint = canPrint;
-    public FormattedMessage? ScanReport = scanReport;
-    public bool Scanning = scanning;
-    public bool Paused = paused;
-    public TimeSpan? StartTime = startTime;
-    public TimeSpan? AccumulatedRunTime = accumulatedRunTime;
-    public TimeSpan? TotalTime = totalTime;
-    public int PointAmount = pointAmount;
-    public bool IsTraversalDown = isTraversalDown;
-}
diff --git a/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzerSystem.cs b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzerSystem.cs
new file mode 100644 (file)
index 0000000..e47e964
--- /dev/null
@@ -0,0 +1,136 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Placeable;
+using Content.Shared.Power.EntitySystems;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+
+namespace Content.Shared.Xenoarchaeology.Equipment;
+
+/// <summary>
+/// This system is used for managing the artifact analyzer as well as the analysis console.
+/// It also handles scanning and ui updates for both systems.
+/// </summary>
+public abstract class SharedArtifactAnalyzerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemPlacedEvent>(OnItemPlaced);
+        SubscribeLocalEvent<ArtifactAnalyzerComponent, ItemRemovedEvent>(OnItemRemoved);
+        SubscribeLocalEvent<ArtifactAnalyzerComponent, MapInitEvent>(OnMapInit);
+
+        SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
+        SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
+    }
+
+    private void OnItemPlaced(Entity<ArtifactAnalyzerComponent> ent, ref ItemPlacedEvent args)
+    {
+        ent.Comp.CurrentArtifact = args.OtherEntity;
+        Dirty(ent);
+    }
+
+    private void OnItemRemoved(Entity<ArtifactAnalyzerComponent> ent, ref ItemRemovedEvent args)
+    {
+        if (args.OtherEntity != ent.Comp.CurrentArtifact)
+            return;
+
+        ent.Comp.CurrentArtifact = null;
+        Dirty(ent);
+    }
+
+    private void OnMapInit(Entity<ArtifactAnalyzerComponent> ent, ref MapInitEvent args)
+    {
+        if (!TryComp<DeviceLinkSinkComponent>(ent, out var sink))
+            return;
+
+        foreach (var source in sink.LinkedSources)
+        {
+            if (!TryComp<AnalysisConsoleComponent>(source, out var analysis))
+                continue;
+
+            analysis.AnalyzerEntity = GetNetEntity(ent);
+            ent.Comp.Console = source;
+            Dirty(source, analysis);
+            Dirty(ent);
+            break;
+        }
+    }
+
+    private void OnNewLink(Entity<AnalysisConsoleComponent> ent, ref NewLinkEvent args)
+    {
+        if (!TryComp<ArtifactAnalyzerComponent>(args.Sink, out var analyzer))
+            return;
+
+        ent.Comp.AnalyzerEntity = GetNetEntity(args.Sink);
+        analyzer.Console = ent;
+        Dirty(args.Sink, analyzer);
+        Dirty(ent);
+    }
+
+    private void OnPortDisconnected(Entity<AnalysisConsoleComponent> ent, ref PortDisconnectedEvent args)
+    {
+        var analyzerNetEntity = ent.Comp.AnalyzerEntity;
+        if (args.Port != ent.Comp.LinkingPort || analyzerNetEntity == null)
+            return;
+
+        var analyzerEntityUid = GetEntity(analyzerNetEntity);
+        if (TryComp<ArtifactAnalyzerComponent>(analyzerEntityUid, out var analyzer))
+        {
+            analyzer.Console = null;
+            Dirty(analyzerEntityUid.Value, analyzer);
+        }
+
+        ent.Comp.AnalyzerEntity = null;
+        Dirty(ent);
+    }
+
+    public bool TryGetAnalyzer(Entity<AnalysisConsoleComponent> ent, [NotNullWhen(true)] out Entity<ArtifactAnalyzerComponent>? analyzer)
+    {
+        analyzer = null;
+
+        var consoleEnt = ent.Owner;
+        if (!_powerReceiver.IsPowered(consoleEnt))
+            return false;
+
+        var analyzerUid = GetEntity(ent.Comp.AnalyzerEntity);
+        if (!TryComp<ArtifactAnalyzerComponent>(analyzerUid, out var analyzerComp))
+            return false;
+
+        if (!_powerReceiver.IsPowered(analyzerUid.Value))
+            return false;
+
+        analyzer = (analyzerUid.Value, analyzerComp);
+        return true;
+    }
+
+    public bool TryGetArtifactFromConsole(Entity<AnalysisConsoleComponent> ent, [NotNullWhen(true)] out Entity<XenoArtifactComponent>? artifact)
+    {
+        artifact = null;
+
+        if (!TryGetAnalyzer(ent, out var analyzer))
+            return false;
+
+        if (!TryComp<XenoArtifactComponent>(analyzer.Value.Comp.CurrentArtifact, out var comp))
+            return false;
+
+        artifact = (analyzer.Value.Comp.CurrentArtifact.Value, comp);
+        return true;
+    }
+
+    public bool TryGetAnalysisConsole(Entity<ArtifactAnalyzerComponent> ent, [NotNullWhen(true)] out Entity<AnalysisConsoleComponent>? analysisConsole)
+    {
+        analysisConsole = null;
+
+        if (!TryComp<AnalysisConsoleComponent>(ent.Comp.Console, out var consoleComp))
+            return false;
+
+        analysisConsole = (ent.Comp.Console.Value, consoleComp);
+        return true;
+    }
+}
index 2d7d86822f9ef37a896c920565dcb2ec838b3ea7..64f31262977336834395823c0ec99ce934a34eb5 100644 (file)
@@ -3,6 +3,7 @@ using Content.Shared.Storage.Components;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Containers;
 using Content.Shared.Emag.Systems;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
 
 namespace Content.Shared.Xenoarchaeology.Equipment;
 
diff --git a/Content.Shared/Xenoarchaeology/Equipment/SharedNodeScannerSystem.cs b/Content.Shared/Xenoarchaeology/Equipment/SharedNodeScannerSystem.cs
new file mode 100644 (file)
index 0000000..7589cc8
--- /dev/null
@@ -0,0 +1,120 @@
+using Content.Shared.Interaction;
+using Content.Shared.NameIdentifier;
+using Content.Shared.Timing;
+using Content.Shared.Verbs;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Xenoarchaeology.Equipment;
+
+/// <summary> Controls behaviour of artifact node scanner device. </summary>
+public abstract class SharedNodeScannerSystem : EntitySystem
+{
+    [Dependency] private readonly UseDelaySystem _useDelay = default!;
+    [Dependency] private readonly SharedXenoArtifactSystem _artifact = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
+        SubscribeLocalEvent<NodeScannerComponent, GetVerbsEvent<UtilityVerb>>(AddScanVerb);
+    }
+
+    private void OnBeforeRangedInteract(EntityUid uid, NodeScannerComponent component, BeforeRangedInteractEvent args)
+    {
+        if (args.Handled || !args.CanReach || args.Target is not { } target)
+            return;
+
+        Entity<XenoArtifactUnlockingComponent?> unlockingEnt = TryComp<XenoArtifactUnlockingComponent>(target, out var unlockingComponent)
+            ? (target, unlockingComponent)
+            : (target, null);
+
+        TryMakeActiveNodesSnapshot((uid, component), unlockingEnt, args.User);
+
+        args.Handled = true;
+    }
+
+    private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent<UtilityVerb> args)
+    {
+        if (!args.CanAccess)
+            return;
+
+        if (!TryComp<XenoArtifactUnlockingComponent>(args.Target, out var unlockingComponent))
+            return;
+
+        var verb = new UtilityVerb
+        {
+            Act = () =>
+            {
+                TryMakeActiveNodesSnapshot((uid, component), (args.Target, unlockingComponent), args.User);
+            },
+            Text = Loc.GetString("node-scan-tooltip")
+        };
+
+        args.Verbs.Add(verb);
+    }
+
+    private void TryMakeActiveNodesSnapshot(
+        Entity<NodeScannerComponent> device,
+        Entity<XenoArtifactUnlockingComponent?> unlockingEnt,
+        EntityUid actor
+    )
+    {
+        if (!_timing.IsFirstTimePredicted)
+            return;
+
+        if (TryComp(device, out UseDelayComponent? useDelay)
+            && !_useDelay.TryResetDelay((device, useDelay), true))
+            return;
+
+        if (!TryComp<XenoArtifactComponent>(unlockingEnt.Owner, out var artifactComponent))
+            return;
+
+        TryOpenUi(device, actor);
+
+        TimeSpan? waitTime = null;
+        HashSet<string> triggeredNodeNames;
+        ArtifactState artifactState;
+        if (unlockingEnt.Comp == null)
+        {
+            triggeredNodeNames = new HashSet<string>();
+            var timeToUnlockAvailable = artifactComponent.NextUnlockTime - _timing.CurTime;
+            if (timeToUnlockAvailable > TimeSpan.Zero)
+            {
+                artifactState = ArtifactState.Cooldown;
+                waitTime = timeToUnlockAvailable;
+            }
+            else
+            {
+                artifactState = ArtifactState.Ready;
+            }
+        }
+        else
+        {
+            var triggeredIndexes = unlockingEnt.Comp.TriggeredNodeIndexes;
+            triggeredNodeNames = new HashSet<string>(triggeredIndexes.Count);
+
+            foreach (var triggeredIndex in triggeredIndexes)
+            {
+                var node = _artifact.GetNode((unlockingEnt.Owner, artifactComponent), triggeredIndex);
+                var triggeredNodeName = (CompOrNull<NameIdentifierComponent>(node)?.Identifier ?? 0).ToString("D3");
+                triggeredNodeNames.Add(triggeredNodeName);
+            }
+
+            artifactState = ArtifactState.Unlocking;
+            waitTime = _timing.CurTime - unlockingEnt.Comp.EndTime;
+        }
+
+        device.Comp.ArtifactState = artifactState;
+        device.Comp.WaitTime = waitTime;
+        device.Comp.TriggeredNodesSnapshot = triggeredNodeNames;
+        device.Comp.ScannedAt = _timing.CurTime;
+
+        Dirty(device);
+    }
+
+    protected abstract void TryOpenUi(Entity<NodeScannerComponent> device, EntityUid actor);
+}
similarity index 54%
rename from Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs
rename to Content.Shared/Xenoarchaeology/Equipment/SuppressArtifactContainerSystem.cs
index 4278af409a8cd68abdf48502f3be859cd398228f..4076274ca2c0b64da2d729431c75c9a2eee448d0 100644 (file)
@@ -1,12 +1,13 @@
-using Content.Server.Xenoarchaeology.Equipment.Components;
-using Content.Server.Xenoarchaeology.XenoArtifacts;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Content.Shared.Xenoarchaeology.Equipment.Components;
 using Robust.Shared.Containers;
 
-namespace Content.Server.Xenoarchaeology.Equipment.Systems;
+namespace Content.Shared.Xenoarchaeology.Equipment;
 
 public sealed class SuppressArtifactContainerSystem : EntitySystem
 {
-    [Dependency] private readonly ArtifactSystem _artifact = default!;
+    [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!;
 
     public override void Initialize()
     {
@@ -17,17 +18,17 @@ public sealed class SuppressArtifactContainerSystem : EntitySystem
 
     private void OnInserted(EntityUid uid, SuppressArtifactContainerComponent component, EntInsertedIntoContainerMessage args)
     {
-        if (!TryComp<ArtifactComponent>(args.Entity, out var artifact))
+        if (!TryComp<XenoArtifactComponent>(args.Entity, out var artifact))
             return;
 
-        _artifact.SetIsSuppressed(args.Entity, true, artifact);
+        _xenoArtifact.SetSuppressed((args.Entity, artifact), true);
     }
 
     private void OnRemoved(EntityUid uid, SuppressArtifactContainerComponent component, EntRemovedFromContainerMessage args)
     {
-        if (!TryComp<ArtifactComponent>(args.Entity, out var artifact))
+        if (!TryComp<XenoArtifactComponent>(args.Entity, out var artifact))
             return;
 
-        _artifact.SetIsSuppressed(args.Entity, false, artifact);
+        _xenoArtifact.SetSuppressed((args.Entity, artifact), false);
     }
 }
diff --git a/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs
deleted file mode 100644 (file)
index 4ba4d0d..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Shared.Item;
-using Content.Shared.Whitelist;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
-
-/// <summary>
-/// This is a prototype for...
-/// </summary>
-[Prototype]
-[DataDefinition]
-public sealed partial class ArtifactEffectPrototype : IPrototype
-{
-    /// <inheritdoc/>
-    [IdDataField]
-    public string ID { get; private set; } = default!;
-
-    /// <summary>
-    /// Components that are added to the artifact when the specfic effect is active.
-    /// These are removed after the node is exited and the effect is changed.
-    /// </summary>
-    [DataField("components", serverOnly: true)]
-    public ComponentRegistry Components = new();
-
-    /// <summary>
-    /// Components that are permanently added to an entity when the effect's node is entered.
-    /// </summary>
-    [DataField("permanentComponents")]
-    public ComponentRegistry PermanentComponents = new();
-
-    //TODO: make this a list so we can have multiple target depths
-    [DataField("targetDepth")]
-    public int TargetDepth = 0;
-
-    [DataField("effectHint")]
-    public string? EffectHint;
-
-    [DataField("whitelist")]
-    public EntityWhitelist? Whitelist;
-
-    [DataField("blacklist")]
-    public EntityWhitelist? Blacklist;
-}
diff --git a/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs
deleted file mode 100644 (file)
index 53ade6a..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Shared.Item;
-using Content.Shared.Whitelist;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
-
-/// <summary>
-/// This is a prototype for...
-/// </summary>
-[Prototype]
-[DataDefinition]
-public sealed partial class ArtifactTriggerPrototype : IPrototype
-{
-    /// <inheritdoc/>
-    [IdDataField]
-    public string ID { get; private set; } = default!;
-
-    [DataField("components", serverOnly: true)]
-    public ComponentRegistry Components = new();
-
-    [DataField("targetDepth")]
-    public int TargetDepth = 0;
-
-    [DataField("triggerHint")]
-    public string? TriggerHint;
-
-    [DataField("whitelist")]
-    public EntityWhitelist? Whitelist;
-
-    [DataField("blacklist")]
-    public EntityWhitelist? Blacklist;
-}
index 77b381b0b51fa87e23a36d6d9c0ff943d094b197..ff10ec628b95ed89e5b1d3fd6599d75a9449387f 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
+namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
 
 [RegisterComponent]
 public sealed partial class RandomArtifactSpriteComponent : Component
@@ -10,7 +10,7 @@ public sealed partial class RandomArtifactSpriteComponent : Component
     public int MaxSprite = 14;
 
     [DataField("activationTime")]
-    public double ActivationTime = 2.0;
+    public double ActivationTime = 0.4;
 
     public TimeSpan? ActivationStart;
 }
index f7a69eade34389c24b7924e1b3478227cf603817..a434f468ebb22213fa8393a208072b627ac93004 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Shared.Actions;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
@@ -7,12 +6,6 @@ namespace Content.Shared.Xenoarchaeology.XenoArtifacts;
 public enum SharedArtifactsVisuals : byte
 {
     SpriteIndex,
-    IsActivated
-}
-
-/// <summary>
-///     Raised as an instant action event when a sentient artifact activates itself using an action.
-/// </summary>
-public sealed partial class ArtifactSelfActivateEvent : InstantActionEvent
-{
+    IsActivated,
+    IsUnlocking
 }
diff --git a/Resources/Audio/Items/Artifact/artifact-activation-fail1.ogg b/Resources/Audio/Items/Artifact/artifact-activation-fail1.ogg
new file mode 100644 (file)
index 0000000..fb9b7e1
Binary files /dev/null and b/Resources/Audio/Items/Artifact/artifact-activation-fail1.ogg differ
diff --git a/Resources/Audio/Items/Artifact/artifact-force-activated1.ogg b/Resources/Audio/Items/Artifact/artifact-force-activated1.ogg
new file mode 100644 (file)
index 0000000..9ad8cb1
Binary files /dev/null and b/Resources/Audio/Items/Artifact/artifact-force-activated1.ogg differ
index ff50796dfd2e18612c23f7b2eb19935ef8ec43ce..a49afc574946bc538ea26f69a20dc956ddf2fdc8 100644 (file)
@@ -2,3 +2,11 @@
   license: "CC-BY-4.0"
   copyright: "Created by Garuda1982, split into individual files and converted to OGG and Mono by EmoGarbage404 (github)"
   source: "https://freesound.org/people/Garuda1982/sounds/560310/"
+- files: ["artifact-activation-fail1.ogg"]
+  license: "CC-BY-NC-3.0"
+  copyright: "Created by GabrieleKkj, split and converted by fildrance"
+  source: "https://freesound.org/people/GabrieleKkj/sounds/264370/"
+- files: ["artifact-force-activated1.ogg"]
+  license: "CC0-1.0"
+  copyright: "Created by brunoboselli, split and converted by fildrance"
+  source: "https://freesound.org/people/brunoboselli/sounds/457294/"
index c53c825eb3e0937138faeee540be7c9b8ca7e758..51f0745b3dd8d94dd0b2469735c622b90d62a3ee 100644 (file)
@@ -84,3 +84,13 @@ command-description-addaccesslog =
     Adds an access log to this entity. Do note that this bypasses the log's default limit and pause check.
 command-description-stationevent-simulate =
     Simulates N number of rounds in which events will occur and prints the occurrences of every event after.
+command-description-xenoartifact-list =
+    List all EntityUids of spawned artifacts.
+command-description-xenoartifact-printMatrix =
+    Prints out matrix that displays all edges between nodes.
+command-description-xenoartifact-totalResearch =
+    Gets all research points that can be extracted from artifact currently.
+command-description-xenoartifact-averageResearch =
+    Calculates amount of research points average generated xeno artifact will output when fully activated.
+command-description-xenoartifact-unlockAllNodes =
+    Unlocks all nodes of artifact.
index 642555b237eaa2fa1e397e98fe6aa6090b59fce0..098403c84c2838a7f67aa68f98b4929c52051801 100644 (file)
@@ -103,12 +103,6 @@ reagent-effect-guidebook-status-effect =
                 } {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
     }
 
-reagent-effect-guidebook-activate-artifact =
-    { $chance ->
-        [1] Attempts
-        *[other] attempt
-    } to activate an artifact
-
 reagent-effect-guidebook-set-solution-temperature-effect =
     { $chance ->
         [1] Sets
index 35dd42167f63cd483c4d5934a447b99e054bb30a..8e9cd073d97ea5e21af6bf9ad6e35f6b3d025ef2 100644 (file)
@@ -1,42 +1,40 @@
-analysis-console-menu-title = analysis console
-analysis-console-server-list-button = Server List
-analysis-console-scan-button = Scan
-analysis-console-scan-tooltip-info = Scan artifacts to learn information about their structure.
-analysis-console-print-button = Print
-analysis-console-print-tooltip-info = Print out the current information about the artifact.
-analysis-console-extract-button = Extract
-analysis-console-extract-button-info = Extract points from an artifact based on the newly explored nodes.
-analysis-console-bias-up = Up
-analysis-console-bias-down = Down
-analysis-console-bias-button-info-up = Toggles the bias an artifact has in moving between its nodes. Up heads toward zero depth.
-analysis-console-bias-button-info-down = Toggles the bias an artifact has in moving between its nodes. Down heads toward ever-higher depths.
+analysis-console-menu-title = Broad-Spectrum Mark 3 Analysis Console
+analysis-console-server-list-button = Server
+analysis-console-extract-button = Extract points
 
 analysis-console-info-no-scanner = No analyzer connected! Please connect one using a multitool.
-analysis-console-info-no-artifact = No artifact present! Place one on the pad then scan for information.
+analysis-console-info-no-artifact = No artifact present! Place one on the pad to view node information.
 analysis-console-info-ready = Systems operational. Ready to scan.
 
-analysis-console-info-id = NODE_ID: {$id}
-analysis-console-info-depth = DEPTH: {$depth}
-analysis-console-info-triggered-true = ACTIVATED: TRUE
-analysis-console-info-triggered-false = ACTIVATED: FALSE
-analysis-console-info-effect = REACTION: {$effect}
-analysis-console-info-trigger = STIMULUS: {$trigger}
-analysis-console-info-edges = EDGES: {$edges}
-analysis-console-info-value = UNEXTRACTED_VALUE: {$value}
-
+analysis-console-no-node = Select node to view
+analysis-console-info-id = [font="Monospace" size=11]ID:[/font]
+analysis-console-info-id-value = [font="Monospace" size=11][color=yellow]{$id}[/color][/font]
+analysis-console-info-class = [font="Monospace" size=11]Class:[/font]
+analysis-console-info-class-value = [font="Monospace" size=11]{$class}[/font]
+analysis-console-info-locked = [font="Monospace" size=11]Status:[/font]
+analysis-console-info-locked-value = [font="Monospace" size=11][color={ $state ->
+    [0] red]Locked
+    [1] lime]Unlocked
+    *[2] plum]Active
+}[/color][/font]
+analysis-console-info-durability = [font="Monospace" size=11]Durability:[/font]
+analysis-console-info-durability-value = [font="Monospace" size=11][color={$color}]{$current}/{$max}[/color][/font]
+analysis-console-info-effect = [font="Monospace" size=11]Effect:[/font]
+analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
+    [true] {$info}
+    *[false] Unlock nodes to gain info
+}[/color][/font]
+analysis-console-info-trigger = [font="Monospace" size=11]Triggers:[/font]
+analysis-console-info-triggered-value = [font="Monospace" size=11][color=gray]{$triggers}[/color][/font]
 analysis-console-info-scanner = Scanning...
 analysis-console-info-scanner-paused = Paused.
 analysis-console-progress-text = {$seconds ->
     [one] T-{$seconds} second
     *[other] T-{$seconds} seconds
 }
-analysis-console-no-server-connected = Cannot extract. No server connected.
-analysis-console-no-artifact-placed = No artifact on scanner.
-analysis-console-no-points-to-extract = No points to extract.
 
-analyzer-artifact-component-upgrade-analysis = analysis duration
+analysis-console-extract-value = [font="Monospace" size=11][color=orange]Node {$id} (+{$value})[/color][/font]
+analysis-console-extract-none = [font="Monospace" size=11][color=orange] No unlocked nodes have any points left to extract [/color][/font]
+analysis-console-extract-sum = [font="Monospace" size=11][color=orange]Total Research: {$value}[/color][/font]
 
-analysis-console-print-popup = The console printed out a report.
 analyzer-artifact-extract-popup = Energy shimmers on the artifact's surface!
-
-analysis-report-title = Artifact Report: Node {$id}
index debb3653042146395d9544370d53b26e16c9bbb4..b083d33f359ec5b3fbcdfed95e8e482731af102b 100644 (file)
@@ -1,4 +1,27 @@
-### Verbs
+### Commands
+cmd-unlocknode-desc = Unlocks a node on a given artifact
+cmd-unlocknode-help = unlocknode <artifact uid> <node uid>
+cmd-parse-failure-unlocknode-arg-num = Incorrect number of args
+cmd-parse-failure-unlocknode-invalid-entity = Provided netEntity is not valid node
 
+### Verbs
 artifact-verb-make-always-active = Make artifact always active
 artifact-verb-activate = Activate artifact
+
+### Unlocking
+artifact-unlock-state-begin = It begins to shift in strange ways...
+artifact-unlock-state-end-success = It slows down, visibly changed.
+artifact-unlock-state-end-failure = It slows down before uneventfully stopping.
+
+### Activation
+artifact-activation-fail = Nothing happens...
+
+### Misc.
+artifact-examine-trigger-desc = [color=gray][italic]Am I on your mind?[/italic][/color]
+
+artifact-node-class-1 = [color=#ff2bb1]Hylic[/color]
+artifact-node-class-2 = [color=#ff8b2b]Psychic[/color]
+artifact-node-class-3 = [color=#a9ff38]Pneumatic[/color]
+artifact-node-class-4 = [color=#2bfff8]Archon[/color]
+artifact-node-class-5 = [color=#7883ff]Luminary[/color]
+artifact-node-class-6 = [color=#be78ff]Demiurge[/color]
index c38016b1ca6cf5f6fe43dbdc1e95a5dbf447bdab..23abad3383b1d1b65794b8cca62f55062a91c2ee 100644 (file)
@@ -42,3 +42,40 @@ artifact-trigger-hint-land = Active deceleration
 artifact-trigger-hint-examine = Examination
 artifact-trigger-hint-medical = Therapeutic chemicals
 
+xenoarch-trigger-tip-music = Harmonical sound vibrations
+xenoarch-trigger-tip-heat = High temperature gas
+xenoarch-trigger-tip-cold = Low temperature gas
+xenoarch-trigger-tip-no-oxygen = Oxygen-free environment
+xenoarch-trigger-tip-water = Water
+xenoarch-trigger-tip-co2 = Carbon dioxide
+xenoarch-trigger-tip-plasma = Non-solid plasma
+xenoarch-trigger-tip-tritium = Tritium
+xenoarch-trigger-tip-ammonia = Ammonia
+xenoarch-trigger-tip-n2o = Nitrous oxide
+xenoarch-trigger-tip-frezon = Frezon
+xenoarch-trigger-tip-radiation = Radiation
+xenoarch-trigger-tip-brute-damage = Physical damage
+xenoarch-trigger-tip-interaction = Physical interaction
+xenoarch-trigger-tip-wrenching = Tightening
+xenoarch-trigger-tip-prying = Prying
+xenoarch-trigger-tip-screwing = Screwing
+xenoarch-trigger-tip-pulsing = Pulsing
+xenoarch-trigger-tip-pressure-low = Low pressure
+xenoarch-trigger-tip-pressure-high = High pressure
+xenoarch-trigger-tip-examine = Close inspection
+xenoarch-trigger-tip-timer = Regular self-activation
+xenoarch-trigger-tip-blood = Blood
+xenoarch-trigger-tip-throw = Being thrown
+xenoarch-trigger-tip-death = Death
+xenoarch-trigger-tip-magnet = Magnetic waves
+
+### Description hints
+xenoarch-trigger-examine-wrenching = There's a loose bit spinning around.
+xenoarch-trigger-examine-prying = There's a panel coming up from the surface.
+xenoarch-trigger-examine-screwing = There's a raised section with a small inset on it.
+xenoarch-trigger-examine-pulsing = An exposed diode pokes out of the artifact's surface.
+xenoarch-trigger-examine-timer = Carvings and scratches cover the surface... You can just barely make out a number: [italic]{$time}[/italic]
+
+### Effects hints
+xenoarch-effect-puddle = Produces puddle of following reagents: {$reagent}
+xenoarch-effect-foam = Produces foam of following reagents: {$reagent}
index 4a05414c46020fc99e8ac1bcba4bcc83fd1fe49a..f43843203e3cd9f541add973530ea6dc46c51e76 100644 (file)
@@ -1,2 +1,9 @@
-node-scan-popup = The node ID is {$id}
-node-scan-tooltip = Scan artifact
+node-scan-tooltip = Scan active nodes
+node-scan-no-data = No active node data found
+node-scan-display-title = Node scanner
+
+node-scanner-artifact-scanned-time = Artifact last scanned at {$time}
+node-scanner-artifact-state-ready = Artifact is ready for interaction
+node-scanner-artifact-state-unlocking = Artifact is resonating with your actions
+node-scanner-artifact-state-cooldown = Artifact is resting
+node-scanner-artifact-scanned-time-none = Scan artifact to see current state
index 75362b414ed3197e19a6a8f5bf451b4d8b4cbab8..bbcef8f9330c40512e7609689b0b3576a4af967e 100644 (file)
@@ -5292,8 +5292,7 @@ entities:
     components:
     - type: Transform
       parent: 766
-    - type: Artifact
-      isSuppressed: True
+    - type: XenoArtifact
     - type: Physics
       canCollide: False
     - type: InsideEntityStorage
@@ -5301,8 +5300,7 @@ entities:
     components:
     - type: Transform
       parent: 768
-    - type: Artifact
-      isSuppressed: True
+    - type: XenoArtifact
     - type: Physics
       canCollide: False
     - type: InsideEntityStorage
@@ -5312,14 +5310,12 @@ entities:
       rot: 1.5707963267948966 rad
       pos: -5.5242987,-12.5
       parent: 1
-    - type: BiasedArtifact
   - uid: 789
     components:
     - type: Transform
       rot: 1.5707963267948966 rad
       pos: 6.506952,-12.5
       parent: 1
-    - type: BiasedArtifact
 - proto: VendingMachineMedical
   entities:
   - uid: 230
index aa378d8861f801e542dfc1db29ed09649a680003..b1c68f9d0eeba3d3f8743a180ba0acfd2c5753bd 100644 (file)
     useDelay: 30
     event: !type:VendingMachineSelfDispenseEvent
 
-- type: entity
-  id: ActionArtifactActivate
-  name: Activate Artifact
-  description: Immediately activates your current artifact node.
-  components:
-  - type: InstantAction
-    icon:
-      sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
-      state: ano01
-    useDelay: 60
-    event: !type:ArtifactSelfActivateEvent
-
 - type: entity
   id: ActionToggleBlock
   name: Block
index b96ce86df373dc193801730206dd19922c28951a..450d8ec618c3622b94cba3aea8da5dba1f8b7584 100644 (file)
@@ -7,7 +7,7 @@
     amount: 1
     whitelist:
       components:
-      - Artifact
+      - XenoArtifact
 
 - type: cargoBounty
   id: BountyBaseballBat
index 7053eda22660d60d838138f5f00a27dabf5868cb..27084003ee158270865ac60b80a9f2f887a57453 100644 (file)
@@ -46,7 +46,7 @@
       capacity: 1
       whitelist:
         components:
-        - Artifact
+        - XenoArtifact
     - type: Weldable
     - type: SuppressArtifactContainer
     - type: RadiationBlockingContainer
similarity index 73%
rename from Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_artifacts.yml
rename to Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml
index 2241cdd4aa09b14900e3d1b234c83ba024ca0e40..00c6abf362fbe4fd67a2e476cdfbea28bfe0c7c4 100644 (file)
@@ -1,27 +1,29 @@
 - type: entity
-  parent: BaseItem
+  parent: [BaseItem, BaseXenoArtifact]
   id: BaseXenoArtifactItem
-  name: alien artifact
-  description: A strange handheld alien device.
+  name: artifact
+  description: A strange artifact from time unknown. Looks like a good time. Fits in hand perfectly.
   abstract: true
+  noSpawn: true
   components:
+  # Visual
   - type: Sprite
     sprite: Objects/Specific/Xenoarchaeology/item_artifacts.rsi
     layers:
     - state: ano01
       map: [ "enum.ArtifactsVisualLayers.Base" ]
     - state: ano01_on
-      map: [ "enum.ArtifactsVisualLayers.Effect" ]
+      map: [ "enum.ArtifactsVisualLayers.UnlockingEffect" ]
+      visible: false
+    - state: artifact-activation
+      sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
+      map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ]
       visible: false
-  - type: Damageable
   - type: Physics
     bodyType: Dynamic
   - type: CollisionWake
     enabled: false
   - type: InteractionOutline
-  - type: Reactive
-    groups:
-      Acidic: [Touch]
   - type: Fixtures
     fixtures:
       fix1:
         - Opaque
         restitution: 0.3  # fite me
         friction: 0.2
-  - type: Artifact
   - type: RandomArtifactSprite
     maxSprite: 11
     activationTime: 2.4
   - type: RandomSprite
     available:
-    - enum.ArtifactsVisualLayers.Effect:
+    - enum.ArtifactsVisualLayers.UnlockingEffect:
         ano01_on: Rainbow
-  - type: UserInterface #needs to be here for certain effects
-    interfaces:
-      enum.StorageUiKey.Key:
-        type: StorageBoundUserInterface
-      enum.TransferAmountUiKey.Key:
-        type: TransferAmountBoundUserInterface
-      enum.InstrumentUiKey.Key:
-        type: InstrumentBoundUserInterface
-      enum.IntercomUiKey.Key:
-        type: IntercomBoundUserInterface
-  - type: Appearance
+  # gameplay interactions
   - type: Item
     size: Normal
     sprite: Objects/Specific/Xenoarchaeology/item_artifacts.rsi
     heldPrefix: ano01
-  - type: Actions
   - type: Construction
     graph: Artifact
     node: done
-  - type: GuideHelp
-    guides:
-    - Xenoarchaeology
+  - type: XenoArtifact
+    effectsTable: !type:GroupSelector
+      children:
+      - !type:NestedSelector
+        tableId: XenoArtifactEffectsDefaultTable
+        weight: 54
+      - !type:NestedSelector
+        tableId: XenoArtifactEffectsHandheldOnlyTable
+        weight: 2
 
 - type: entity
   parent: BaseXenoArtifactItem
   id: SimpleXenoArtifactItem
-  suffix: Simple
+  suffix: hand-sized Simple
   components:
-  - type: Artifact
-    nodesMin: 2
-    nodesMax: 5
+  - type: XenoArtifact
+    nodeCount:
+      min: 2
+      max: 5
 
 - type: entity
   parent: BaseXenoArtifactItem
   id: MediumXenoArtifactItem
-  suffix: Medium
+  suffix: hand-sized Medium
   components:
-  - type: Artifact
-    nodesMin: 5
-    nodesMax: 9
+  - type: XenoArtifact
+    nodeCount:
+      min: 5
+      max: 9
 
 - type: entity
   parent: BaseXenoArtifactItem
   id: ComplexXenoArtifactItem
-  suffix: Complex
+  suffix: hand-sized Complex
   components:
-  - type: Artifact
-    nodesMin: 9
-    nodesMax: 13
+  - type: XenoArtifact
+    nodeCount:
+      min: 9
+      max: 13
 
 # this exists for crafting item artifacts so that the final result can be simple, medium, or complex.
 - type: entity
   parent: BaseXenoArtifactItem
   id: VariedXenoArtifactItem
-  suffix: Varied
+  suffix: hand-sized Varied
   components:
-  - type: Artifact
-    nodesMin: 2
-    nodesMax: 13
+  - type: XenoArtifact
+    nodeCount:
+      min: 2
+      max: 13
 
 - type: entity
   id: ArtifactFragment
index 7406555fe32704c027ad86902d8deeff939b0457..788374618cc3089a2ae43e824e63d0d6a44077c3 100644 (file)
       sprite: Objects/Specific/Xenoarchaeology/node_scanner.rsi
     - type: NodeScanner
     - type: UseDelay
-      delay: 3
+      delay: 1
     - type: GuideHelp
       guides:
       - ArtifactReports
+    - type: ActivatableUI
+      key: enum.NodeScannerUiKey.Key
+      singleUser: true
+    - type: UserInterface #needs to be here for certain effects
+      interfaces:
+        enum.NodeScannerUiKey.Key:
+          type: NodeScannerBoundUserInterface
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_artifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_artifacts.yml
deleted file mode 100644 (file)
index 68947c0..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-- type: entity
-  parent: BaseStructureDynamic
-  id: BaseXenoArtifact
-  name: alien artifact
-  description: A strange alien device.
-  abstract: true
-  components:
-  - type: Sprite
-    drawdepth: SmallObjects
-    sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
-    noRot: true
-    layers:
-    - state: ano30
-      map: [ "enum.ArtifactsVisualLayers.Base" ]
-    - state: ano30_on
-      map: [ "enum.ArtifactsVisualLayers.Effect" ]
-      visible: false
-  - type: Damageable
-  - type: Physics
-    bodyType: Dynamic
-  - type: Transform
-    noRot: true
-  - type: UserInterface #needs to be here for certain effects
-    interfaces:
-      enum.StorageUiKey.Key:
-        type: StorageBoundUserInterface
-      enum.TransferAmountUiKey.Key:
-        type: TransferAmountBoundUserInterface
-      enum.InstrumentUiKey.Key:
-        type: InstrumentBoundUserInterface
-      enum.IntercomUiKey.Key:
-        type: IntercomBoundUserInterface
-  - type: Reactive
-    groups:
-      Acidic: [Touch]
-  - type: Fixtures
-    fixtures:
-      fix1:
-        shape:
-          !type:PhysShapeCircle
-          radius: 0.45
-        density: 75
-        layer: # doesn't collide with artifact storage
-        - Opaque
-        mask:
-        - MachineMask
-  - type: InteractionOutline
-  - type: Artifact
-  - type: RandomArtifactSprite
-    maxSprite: 36
-  - type: RandomSprite
-    available:
-    - enum.ArtifactsVisualLayers.Effect:
-        ano01_on: Rainbow
-  - type: Appearance
-  - type: Actions
-  - type: GuideHelp
-    guides:
-    - Xenoarchaeology
-  - type: StealTarget
-    stealGroup: XenoArtifact
-  - type: ContainerContainer
-    containers:
-      storagebase: !type:Container # needed for the EffectStorage artifactEffect
-        ents: [ ]
-      revolver-ammo: !type:Container # needed for the EffectBigIron artifactEffect
-
-- type: entity
-  parent: BaseXenoArtifact
-  id: SimpleXenoArtifact
-  suffix: Simple
-  components:
-  - type: Artifact
-    nodesMin: 2
-    nodesMax: 5
-
-- type: entity
-  parent: BaseXenoArtifact
-  id: MediumXenoArtifact
-  suffix: Medium
-  components:
-  - type: Artifact
-    nodesMin: 5
-    nodesMax: 9
-
-- type: entity
-  parent: BaseXenoArtifact
-  id: ComplexXenoArtifact
-  suffix: Complex
-  components:
-  - type: Artifact
-    nodesMin: 9
-    nodesMax: 13
-
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml
new file mode 100644 (file)
index 0000000..0b3d957
--- /dev/null
@@ -0,0 +1,71 @@
+- type: entity
+  parent: [BaseStructureDynamic, BaseXenoArtifact]
+  id: BaseXenoArtifactStructure
+  name: artifact
+  abstract: true
+  noSpawn: true
+  components:
+  # Visual
+  - type: Sprite
+    drawdepth: SmallObjects
+    sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi
+    noRot: true
+    layers:
+    - state: ano30
+      map: [ "enum.ArtifactsVisualLayers.Base" ]
+    - state: ano30_on
+      map: [ "enum.ArtifactsVisualLayers.UnlockingEffect" ]
+      visible: false
+    - state: artifact-activation
+      map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ]
+      visible: false
+  - type: RandomArtifactSprite
+    maxSprite: 36
+  - type: RandomSprite
+    available:
+    - enum.ArtifactsVisualLayers.UnlockingEffect:
+        ano01_on: Rainbow
+  - type: Transform
+    noRot: true
+  - type: Fixtures
+    fixtures:
+      fix1:
+        shape:
+          !type:PhysShapeCircle
+          radius: 0.45
+        density: 75
+        layer: # doesn't collide with artifact storage
+        - Opaque
+        mask:
+        - MachineMask
+
+- type: entity
+  parent: BaseXenoArtifactStructure
+  id: SimpleXenoArtifact
+  suffix: Simple
+  components:
+  - type: XenoArtifact
+    nodeCount:
+      min: 2
+      max: 5
+
+- type: entity
+  parent: BaseXenoArtifactStructure
+  id: MediumXenoArtifact
+  suffix: Medium
+  components:
+  - type: XenoArtifact
+    nodeCount:
+      min: 5
+      max: 9
+
+- type: entity
+  parent: BaseXenoArtifactStructure
+  id: ComplexXenoArtifact
+  suffix: Complex
+  components:
+  - type: XenoArtifact
+    nodeCount:
+      min: 9
+      max: 13
+
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml
new file mode 100644 (file)
index 0000000..b9db84b
--- /dev/null
@@ -0,0 +1,43 @@
+- type: entity
+  id: BaseXenoArtifact
+  name: artifact
+  description: A strange artifact from time unknown. Looks like a good time.
+  abstract: true
+  noSpawn: true
+  components:
+  # Visual
+  - type: Appearance
+  - type: InteractionOutline
+  - type: UserInterface #needs to be here for certain effects
+    interfaces:
+      enum.StorageUiKey.Key:
+        type: StorageBoundUserInterface
+      enum.TransferAmountUiKey.Key:
+        type: TransferAmountBoundUserInterface
+      enum.InstrumentUiKey.Key:
+        type: InstrumentBoundUserInterface
+      enum.IntercomUiKey.Key:
+        type: IntercomBoundUserInterface
+  - type: GuideHelp
+    guides:
+    - Xenoarchaeology
+  # gameplay interactions
+  - type: XenoArtifact
+    effectsTable: !type:NestedSelector
+      tableId: XenoArtifactEffectsDefaultTable
+  - type: Damageable
+  - type: Actions
+  - type: Physics
+    bodyType: Dynamic
+  - type: UseDelay
+  - type: StealTarget
+    stealGroup: XenoArtifact
+  - type: ContainerContainer
+    containers:
+      node-container: !type:Container
+        showEnts: False
+        occludes: True
+        ents: []
+  # These components are needed for certain triggers to work.
+  - type: RadiationReceiver
+  - type: Reactive
index 0f502e83f0af88ccf408d0c89a210048a5d7d085..93125455bc2429fc95f67f3a6755cfecbf2d7230 100644 (file)
       state: generic_panel_open
   - type: ResearchClient
   - type: AnalysisConsole
-    reportEntityId: PaperArtifactAnalyzer
   - type: DeviceList
   - type: DeviceNetwork
     deviceNetId: Wired
     ports:
       - ArtifactAnalyzerSender
   - type: ActivatableUI
-    key: enum.ArtifactAnalzyerUiKey.Key
+    key: enum.ArtifactAnalyzerUiKey.Key
   - type: UserInterface
     interfaces:
-      enum.ArtifactAnalzyerUiKey.Key:
+      enum.ArtifactAnalyzerUiKey.Key:
         type: AnalysisConsoleBoundUserInterface
       enum.ResearchClientUiKey.Key:
         type: ResearchClientBoundUserInterface
index b9efefdf75ef2090f22bdf47525b7fb55a0dab49..d4fc8263a8424112eb36168ad77046f4412f837e 100644 (file)
     powerLoad: 12000
     needsPower: false #only turns on when scanning
   - type: ArtifactAnalyzer
-  - type: TraversalDistorter
   - type: ItemPlacer
     whitelist:
       components:
-      - Artifact
+      - XenoArtifact
   - type: DeviceNetwork
     deviceNetId: Wired
     receiveFrequencyId: BasicDevice
@@ -90,7 +89,7 @@
   - type: ArtifactCrusher
     crushingWhitelist:
       components:
-      - Artifact
+      - XenoArtifact
     crushingDamage:
       types:
         Blunt: 10
     capacity: 1
     whitelist:
       components:
-      - Artifact
+      - XenoArtifact
       tags:
       - CanPilot # People
       - VimPilot # Pets
index 769b7748f306f43b7a825d3c706871e31d4c797a..be5473fb8c2222ef1c29ba725fc7cb0981960513 100644 (file)
         damage:
           types:
             Caustic: 2
-  reactiveEffects:
-    Acidic:
-      methods: [ Touch ]
-      effects:
-      - !type:ActivateArtifact
-        conditions:
-        - !type:ReagentThreshold
-          min: 5
 
 - type: reagent
   id: Benzene
index 9584a019a5790ec35e95c4cc6a77a02ce9e9d739..d6a65487d9f5c2eabb576e6573b0c7559b5c3b4e 100644 (file)
@@ -1,5 +1,5 @@
 - type: soundCollection
-  id: ArtifactActivation
+  id: ArtifactUnlockingActivationSuccess
   files:
     - /Audio/Items/Artifact/artifact1.ogg
     - /Audio/Items/Artifact/artifact2.ogg
@@ -8,3 +8,13 @@
     - /Audio/Items/Artifact/artifact5.ogg
     - /Audio/Items/Artifact/artifact6.ogg
     - /Audio/Items/Artifact/artifact7.ogg
+
+- type: soundCollection
+  id: ArtifactUnlockActivationFailure
+  files:
+    - /Audio/Items/Artifact/artifact-activation-fail1.ogg
+
+- type: soundCollection
+  id: ArtifactForceActivation
+  files:
+    - /Audio/Items/Artifact/artifact-force-activated1.ogg
diff --git a/Resources/Prototypes/XenoArch/Effects/normal_effects.yml b/Resources/Prototypes/XenoArch/Effects/normal_effects.yml
deleted file mode 100644 (file)
index f2723e5..0000000
+++ /dev/null
@@ -1,679 +0,0 @@
-- type: artifactEffect
-  id: EffectBadFeeling
-  targetDepth: 0
-  effectHint: artifact-effect-hint-mental
-  components:
-  - type: TelepathicArtifact
-    messages:
-    - badfeeling-artifact-1
-    - badfeeling-artifact-2
-    - badfeeling-artifact-3
-    - badfeeling-artifact-4
-    - badfeeling-artifact-5
-    - badfeeling-artifact-6
-    - badfeeling-artifact-7
-    - badfeeling-artifact-8
-    - badfeeling-artifact-9
-    - badfeeling-artifact-10
-    - badfeeling-artifact-11
-    - badfeeling-artifact-12
-    - badfeeling-artifact-13
-    - badfeeling-artifact-14
-    - badfeeling-artifact-15
-    drastic:
-    - badfeeling-artifact-drastic-1
-    - badfeeling-artifact-drastic-2
-    - badfeeling-artifact-drastic-3
-    - badfeeling-artifact-drastic-4
-    - badfeeling-artifact-drastic-5
-    - badfeeling-artifact-drastic-6
-
-- type: artifactEffect
-  id: EffectGoodFeeling
-  targetDepth: 0
-  effectHint: artifact-effect-hint-mental
-  components:
-  - type: TelepathicArtifact
-    messages:
-    - goodfeeling-artifact-1
-    - goodfeeling-artifact-2
-    - goodfeeling-artifact-3
-    - goodfeeling-artifact-4
-    - goodfeeling-artifact-5
-    - goodfeeling-artifact-6
-    - goodfeeling-artifact-7
-    - goodfeeling-artifact-8
-    - goodfeeling-artifact-9
-    - goodfeeling-artifact-10
-    - goodfeeling-artifact-11
-    - goodfeeling-artifact-12
-    - goodfeeling-artifact-13
-    - goodfeeling-artifact-14
-    drastic:
-    - goodfeeling-artifact-drastic-1
-    - goodfeeling-artifact-drastic-2
-    - goodfeeling-artifact-drastic-3
-    - goodfeeling-artifact-drastic-4
-    - goodfeeling-artifact-drastic-5
-    - goodfeeling-artifact-drastic-6
-
-- type: artifactEffect
-  id: EffectJunkSpawn
-  targetDepth: 0
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 10
-    spawns:
-    - id: FoodPacketSyndiTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketSemkiTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketBoritosTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketCheesieTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketChipsTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketChocolateTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketEnergyTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketPopcornTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: FoodPacketRaisinsTrash
-      prob: 0.1
-      orGroup: Trash
-    - id: ToySpawner
-      prob: 0.1
-      orGroup: Trash
-
-- type: artifactEffect
-  id: EffectLightFlicker
-  targetDepth: 0
-  effectHint: artifact-effect-hint-electrical-interference
-  components:
-  - type: LightFlickerArtifact
-
-- type: artifactEffect
-  id: EffectPointLight
-  targetDepth: 0
-  components:
-  - type: PointLight
-    radius: 8
-    energy: 10
-    color: "#daa3fd"
-  - type: TriggerArtifact
-  - type: FlashOnTrigger
-    range: 8
-
-- type: artifactEffect #bornana
-  id: EffectBananaSpawn
-  targetDepth: 0
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 20
-    spawns:
-    - id: FoodBanana
-      amount: 3
-      maxAmount: 6
-  - type: ChemicalPuddleArtifact
-    chemicalSolution:
-      maxVol: 100
-      canReact: false
-    possibleChemicals:
-    - Potassium
-
-- type: artifactEffect
-  id: EffectFloraSpawn
-  targetDepth: 1
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 3
-    spawns:
-    - id: RandomFloraTree
-
-- type: artifactEffect
-  id: EffectThrow
-  targetDepth: 0
-  effectHint: artifact-effect-hint-environment
-  components:
-    - type: ThrowArtifact
-
-- type: artifactEffect
-  id: EffectChemicalPuddle
-  targetDepth: 0
-  effectHint: artifact-effect-hint-biochemical
-  components:
-  - type: ChemicalPuddleArtifact
-    chemicalSolution:
-      maxVol: 500
-      canReact: false
-    possibleChemicals:
-    - Aluminium
-    - Carbon
-    - Chlorine
-    - Copper
-    - Ethanol
-    - Fluorine
-    - Sugar
-    - Hydrogen
-    - Iodine
-    - Iron
-    - Lithium
-    - Mercury
-    - Nitrogen
-    - Oxygen
-    - Phosphorus
-    - Potassium
-    - Radium
-    - Silicon
-    - Sodium
-    - Water
-    - Sulfur
-
-- type: artifactEffect
-  id: EffectCold
-  targetDepth: 1
-  effectHint: artifact-effect-hint-consumption
-  components:
-  - type: TemperatureArtifact
-    targetTemp: 50
-
-- type: artifactEffect
-  id: EffectHeat
-  targetDepth: 1
-  effectHint: artifact-effect-hint-release
-  components:
-    - type: TemperatureArtifact
-      targetTemp: 500
-
-- type: artifactEffect
-  id: EffectFoamMild
-  targetDepth: 1
-  effectHint: artifact-effect-hint-biochemical
-  components:
-  - type: FoamArtifact
-    reagents:
-    - Oxygen
-    - Plasma
-    - Blood
-    - SpaceCleaner
-    - Nutriment
-    - SpaceLube
-    - Ethanol
-    - Mercury
-    - VentCrud
-    - WeldingFuel
-    - JuiceThatMakesYouWeh
-
-- type: artifactEffect
-  id: EffectInstrumentSpawn
-  targetDepth: 1
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 5
-    spawns:
-    - id: RandomInstruments
-
-- type: artifactEffect
-  id: EffectMonkeySpawn
-  targetDepth: 1
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    spawns:
-    - id: MobMonkey
-      orGroup: monkey
-      prob: 0.95
-    - id: MobGorilla #harambe
-      orGroup: monkey
-      prob: 0.05
-
-- type: artifactEffect
-  id: EffectChargeBatteries
-  targetDepth: 1
-  effectHint: artifact-effect-hint-release
-  components:
-  - type: ChargeBatteryArtifact
-  - type: TelepathicArtifact
-    messages:
-    - charge-artifact-popup
-
-- type: artifactEffect
-  id: EffectRadiate
-  targetDepth: 1
-  effectHint: artifact-effect-hint-release
-  components:
-  - type: RadiationSource
-    intensity: 1
-    slope: 0.3
-
-- type: artifactEffect
-  id: EffectKnock
-  targetDepth: 1
-  effectHint: artifact-effect-hint-electrical-interference
-  components:
-    - type: KnockArtifact
-
-- type: artifactEffect
-  id: EffectMagnet
-  targetDepth: 1
-  effectHint: artifact-effect-hint-magnet
-  components:
-  - type: GravityWell
-    maxRange: 3
-    baseRadialAcceleration: 1
-    baseTangentialAcceleration: 3
-
-- type: artifactEffect
-  id: EffectAntiMagnet
-  targetDepth: 1
-  effectHint: artifact-effect-hint-magnet
-  components:
-  - type: GravityWell
-    maxRange: 3
-    baseRadialAcceleration: -1
-    baseTangentialAcceleration: -3
-
-- type: artifactEffect
-  id: EffectInvisibility
-  targetDepth: 2
-  effectHint: artifact-effect-hint-visual
-  components:
-  - type: Stealth
-    hadOutline: true
-  - type: StealthOnMove
-    passiveVisibilityRate: -0.10
-    movementVisibilityRate: 0.10
-
-- type: artifactEffect
-  id: EffectExplosionScary
-  targetDepth: 2
-  effectHint: artifact-effect-hint-environment
-  components:
-  - type: TriggerArtifact
-  - type: ExplodeOnTrigger
-  - type: Explosive
-    deleteAfterExplosion: false
-    explosionType: Radioactive
-    totalIntensity: 300
-    intensitySlope: 2
-    maxIntensity: 1.5
-    canCreateVacuum: false
-
-- type: artifactEffect
-  id: EffectRareMaterialSpawn
-  targetDepth: 2
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    spawns:
-    - id: SilverOre1
-      prob: 0.3
-      maxAmount: 3
-    - id: PlasmaOre1
-      prob: 0.3
-      maxAmount: 3
-    - id: GoldOre1
-      prob: 0.3
-      maxAmount: 3
-    - id: UraniumOre1
-      prob: 0.3
-      maxAmount: 3
-
-- type: artifactEffect
-  id: EffectAngryCarpSpawn
-  targetDepth: 2
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 5
-    spawns:
-    - id: MobCarpHolo
-      orGroup: carp
-    - id: MobCarpMagic
-      orGroup: carp
-
-- type: artifactEffect
-  id: EffectFaunaSpawn
-  targetDepth: 2
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 5
-    spawns:
-    - id: MobAdultSlimesYellowAngry
-      orGroup: fauna
-    - id: MobAngryBee
-      orGroup: fauna
-    - id: MobBearSpace
-      orGroup: fauna
-    - id: MobBee
-      orGroup: fauna
-      maxAmount: 5
-    - id: MobCat
-      orGroup: fauna
-      maxAmount: 2
-    - id: MobCatKitten
-      orGroup: fauna
-      maxAmount: 2
-    - id: MobCorgiPuppy
-      orGroup: fauna
-      maxAmount: 2
-    - id: MobFox
-      orGroup: fauna
-      maxAmount: 1
-    - id: MobGoat
-      orGroup: fauna
-      maxAmount: 3
-    - id: MobKangaroo
-      orGroup: fauna
-      maxAmount: 1
-    - id: MobKangarooSpace
-      orGroup: fauna
-    - id: MobMothroach
-      orGroup: fauna
-      maxAmount: 2
-    - id: MobMonkeySyndicateAgent #so lucky
-      orGroup: fauna
-      maxAmount: 1
-      prob: 0.03
-    - id: MobMouse
-      orGroup: fauna
-    - id: MobParrot
-      orGroup: fauna
-      maxAmount: 1
-    - id: MobPenguin
-      orGroup: fauna
-      maxAmount: 2
-    - id: MobPig
-      orGroup: fauna
-      maxAmount: 1
-    - id: MobPurpleSnake
-      orGroup: fauna
-    - id: MobSpiderSpace
-      orGroup: fauna
-    - id: MobTick
-      orGroup: fauna
-    - id: MobXenoRavager
-      orGroup: fauna
-
-- type: artifactEffect
-  id: EffectCashSpawn
-  targetDepth: 2
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 10
-    spawns:
-    - id: SpaceCash10
-      maxAmount: 5
-      prob: 0.75
-    - id: SpaceCash100
-      maxAmount: 2
-      prob: 0.5
-    - id: SpaceCash500
-      prob: 0.25
-    - id: SpaceCash1000
-      prob: 0.1
-
-- type: artifactEffect
-  id: EffectShatterWindows
-  targetDepth: 2
-  effectHint: artifact-effect-hint-environment
-  components:
-  - type: DamageNearbyArtifact
-    damageChance: 0.75
-    whitelist:
-      tags:
-      - Window
-    damage:
-      types:
-        Structural: 200
-
-- type: artifactEffect
-  id: EffectGas
-  targetDepth: 2
-  effectHint: artifact-effect-hint-environment
-  components:
-  - type: GasArtifact
-    possibleGas:
-    - CarbonDioxide
-    - Plasma
-    - Tritium
-    - Ammonia
-    - NitrousOxide
-    - Frezon
-
-- type: artifactEffect
-  id: EffectBlink
-  targetDepth: 2
-  effectHint: artifact-effect-hint-displacement
-  components:
-  - type: RandomTeleportArtifact
-
-- type: artifactEffect
-  id: EffectFoamGood
-  targetDepth: 2
-  effectHint: artifact-effect-hint-biochemical
-  components:
-  - type: FoamArtifact
-    reagents:
-    - Dermaline
-    - Arithrazine
-    - Bicaridine
-    - Inaprovaline
-    - Kelotane
-    - Dexalin
-    - Omnizine
-
-- type: artifactEffect
-  id: EffectChemicalPuddleRare
-  targetDepth: 2
-  effectHint: artifact-effect-hint-biochemical
-  components:
-  - type: ChemicalPuddleArtifact
-    chemicalSolution:
-      maxVol: 500
-      canReact: false
-    possibleChemicals:
-    - Dermaline
-    - Arithrazine
-    - Bicaridine
-    - Inaprovaline
-    - Kelotane
-    - Dexalin
-    - Omnizine
-    - Napalm
-    - Toxin
-    - Epinephrine
-    - Cognizine
-    - Ultravasculine
-    - Desoxyephedrine
-    - Pax
-    - Siderlac
-
-- type: artifactEffect
-  id: EffectEmp
-  targetDepth: 2
-  effectHint: artifact-effect-hint-electrical-interference
-  components:
-  - type: EmpArtifact
-
-- type: artifactEffect
-  id: EffectPolyMonkey
-  targetDepth: 2
-  effectHint: artifact-effect-hint-polymorph
-  components:
-  - type: PolyOthersArtifact
-
-- type: artifactEffect
-  id: EffectPolyLizard
-  targetDepth: 2
-  effectHint: artifact-effect-hint-polymorph
-  components:
-  - type: PolyOthersArtifact
-    polymorphPrototypeName: ArtifactLizard
-
-- type: artifactEffect
-  id: EffectPolyLuminous
-  targetDepth: 3
-  effectHint: artifact-effect-hint-polymorph
-  components:
-  - type: PolyOthersArtifact
-    polymorphPrototypeName: ArtifactLuminous
-
-- type: artifactEffect
-  id: EffectHealAll
-  targetDepth: 3
-  effectHint: artifact-effect-hint-environment
-  components:
-  - type: DamageNearbyArtifact
-    damageChance: 1
-    radius: 8
-    whitelist:
-      components:
-      - MobState
-    damage:
-      groups:
-        Brute: -300
-        Burn: -300
-
-- type: artifactEffect
-  id: EffectRadiateStrong
-  targetDepth: 3
-  effectHint: artifact-effect-hint-release
-  components:
-  - type: RadiationSource
-    intensity: 2
-    slope: 0.3
-
-- type: artifactEffect
-  id: EffectMaterialSpawn
-  targetDepth: 3
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 5
-    spawns:
-    - id: SheetSteel
-      orGroup: materials
-    - id: SheetGlass
-      orGroup: materials
-    - id: SheetPlastic
-      orGroup: materials
-
-- type: artifactEffect
-  id: EffectShuffle
-  targetDepth: 3
-  effectHint: artifact-effect-hint-displacement
-  components:
-  - type: ShuffleArtifact
-  - type: TelepathicArtifact
-    range: 7.5
-    messages:
-    - shuffle-artifact-popup
-
-- type: artifactEffect
-  id: EffectFoamDangerous
-  targetDepth: 3
-  effectHint: artifact-effect-hint-biochemical
-  components:
-  - type: FoamArtifact
-    minFoamAmount: 20
-    maxFoamAmount: 30
-    reagents:
-    - Tritium
-    - Plasma
-    - SulfuricAcid
-    - SpaceDrugs
-    - Nocturine
-    - MuteToxin
-    - Napalm
-    - CarpoToxin
-    - ChloralHydrate
-    - Mold
-    - Amatoxin
-
-- type: artifactEffect
-  id: EffectIgnite
-  targetDepth: 3
-  effectHint: artifact-effect-hint-release
-  components:
-  - type: IgniteArtifact
-    range: 7
-    minFireStack: 3
-    maxFireStack: 6
-
-- type: artifactEffect
-  id: EffectMitosis
-  targetDepth: 3
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 1
-    spawns:
-    - id: RandomArtifactSpawner
-
-- type: artifactEffect
-  id: EffectAnomaly
-  targetDepth: 3
-  effectHint: artifact-effect-hint-creation
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 1
-    spawns:
-    - id: RandomAnomalySpawner
-
-- type: artifactEffect
-  id: EffectBoom
-  targetDepth: 3
-  effectHint: artifact-effect-hint-environment
-  components:
-  - type: TriggerArtifact
-  - type: ExplodeOnTrigger
-  - type: Explosive
-    deleteAfterExplosion: false
-    explosionType: Default
-    totalIntensity: 500
-    intensitySlope: 2.5
-    maxIntensity: 50
-
-- type: artifactEffect
-  id: EffectPortal
-  targetDepth: 3
-  effectHint: artifact-effect-hint-displacement
-  components:
-  - type: PortalArtifact
-
-- type: artifactEffect
-  id: EffectSingulo
-  targetDepth: 10
-  effectHint: artifact-effect-hint-destruction
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 1
-    spawns:
-    - id: Singularity
-
-- type: artifactEffect
-  id: EffectTesla
-  targetDepth: 10
-  effectHint: artifact-effect-hint-destruction
-  components:
-  - type: SpawnArtifact
-    maxSpawns: 1
-    spawns:
-    - id: TeslaEnergyBall
diff --git a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml
deleted file mode 100644 (file)
index 2258106..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-# Utility effects permanently modify the entity in some way when triggered, and they generally make it 'useful' for some purpose,
-# like turning the artifact into a tool, or gun, or whatever.
-- type: artifactEffect
-  id: EffectIntercom
-  targetDepth: 2
-  effectHint: artifact-effect-hint-communication
-  permanentComponents:
-  - type: RadioMicrophone
-    powerRequired: false
-    toggleOnInteract: false
-    listenRange: 3
-  - type: Speech
-  - type: RadioSpeaker
-    toggleOnInteract: false
-  - type: ActivatableUI
-    key: enum.IntercomUiKey.Key
-  - type: Intercom
-    requiresPower: false
-    supportedChannels:
-    - Common
-    - CentCom
-    - Command
-    - Engineering
-    - Medical
-    - Science
-    - Security
-    - Service
-    - Supply
-
-- type: artifactEffect
-  id: EffectRandomInstrument
-  targetDepth: 2
-  effectHint: artifact-effect-hint-mental
-  permanentComponents:
-  - type: Instrument
-  - type: ActivatableUI
-    singleUser: true
-    verbText: verb-instrument-openui
-    key: enum.InstrumentUiKey.Key
-  - type: RandomInstrumentArtifact
-
-- type: artifactEffect
-  id: EffectStorage
-  targetDepth: 2
-  effectHint: artifact-effect-hint-storage
-  whitelist:
-    components:
-    - Item # it doesnt necessarily have to be restricted from structures, but i think it'll be better that way
-  permanentComponents:
-  - type: Item
-    size: Huge
-  - type: Storage
-    maxItemSize: Huge
-    grid:
-    - 0,0,10,5
-
-- type: artifactEffect
-  id: EffectPhasing
-  targetDepth: 2
-  effectHint: artifact-effect-hint-phasing
-  permanentComponents:
-  - type: PhasingArtifact
-
-- type: artifactEffect
-  id: EffectWandering
-  targetDepth: 2
-  effectHint: artifact-effect-hint-displacement
-  blacklist:
-    components:
-    - Item # item artifacts can't be anchored, so wanderers can't really be scanned properly
-  permanentComponents:
-  - type: RandomWalk
-    minSpeed: 12
-    maxSpeed: 20
-    minStepCooldown: 1
-    maxStepCooldown: 3
-
-- type: artifactEffect
-  id: EffectSolutionStorage
-  targetDepth: 2
-  effectHint: artifact-effect-hint-storage
-  whitelist:
-    components:
-    - Item
-  permanentComponents:
-  - type: SolutionContainerManager
-    solutions:
-      beaker:
-        maxVol: 150
-  - type: FitsInDispenser
-    solution: beaker
-  - type: RefillableSolution
-    solution: beaker
-  - type: DrainableSolution
-    solution: beaker
-  - type: ExaminableSolution
-    solution: beaker
-  - type: DrawableSolution
-    solution: beaker
-  - type: InjectableSolution
-    solution: beaker
-  - type: SolutionTransfer
-    canChangeTransferAmount: true
-  - type: Drink
-    solution: beaker
-
-- type: artifactEffect
-  id: EffectSpeedUp
-  targetDepth: 2
-  effectHint: artifact-effect-hint-displacement
-  whitelist:
-    components:
-    - Item
-  permanentComponents:
-  - type: HeldSpeedModifier
-    walkModifier: 1.2
-    sprintModifier: 1.3
-
-- type: artifactEffect
-  id: EffectDrill
-  targetDepth: 3
-  effectHint: artifact-effect-hint-drill
-  whitelist:
-    components:
-    - Item
-  permanentComponents:
-  - type: UseDelay
-  - type: MeleeWeapon
-    damage:
-      types:
-        Piercing: 18
-        Blunt: 4
-    soundHit:
-      path: /Audio/Weapons/bladeslice.ogg
-  - type: Sharp
-
-- type: artifactEffect
-  id: EffectPowerGen20K
-  targetDepth: 3
-  effectHint: artifact-effect-hint-release
-  blacklist:
-    components:
-    - Item
-  permanentComponents:
-  - type: PowerSupplier
-    supplyRate: 20000
-  - type: NodeContainer
-    examinable: true
-    nodes:
-      output_hv:
-        !type:CableDeviceNode
-        nodeGroupID: HVPower
-
-- type: artifactEffect
-  id: EffectBigIron
-  targetDepth: 3
-  effectHint: artifact-effect-hint-gun
-  whitelist:
-    components:
-    - Item
-  permanentComponents:
-  - type: RevolverAmmoProvider
-    whitelist:
-      tags:
-      - CartridgeMagnum
-      - SpeedLoaderMagnum
-    proto: CartridgeMagnum
-    capacity: 7
-    chambers: [ True, True, True, True, True, True, True ]
-    ammoSlots: [ null, null, null, null, null, null, null ]
-    soundEject:
-      path: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg
-    soundInsert:
-      path: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg
-  - type: Gun
-    selectedMode: SemiAuto
-    fireRate: 2
-    availableModes:
-    - SemiAuto
-    - FullAuto # no alien revolver in buildings
-    soundGunshot:
-      path: /Audio/Weapons/Guns/Gunshots/revolver.ogg
-
-- type: artifactEffect
-  id: EffectSentience
-  targetDepth: 3
-  effectHint: artifact-effect-hint-sentience
-  permanentComponents:
-  - type: GhostRole
-    allowMovement: true
-    allowSpeech: true
-    makeSentient: true
-    name: ghost-role-information-artifact-name
-    description: ghost-role-information-artifact-description
-    rules: ghost-role-information-freeagent-rules
-    mindRoles:
-    - MindRoleGhostRoleFreeAgent
-    raffle:
-      settings: default
-  - type: GhostTakeoverAvailable
-  - type: MovementSpeedModifier
-    baseWalkSpeed: 0.25
-    baseSprintSpeed: 0.5
-
-- type: artifactEffect
-  id: EffectMultitool
-  targetDepth: 3
-  effectHint: artifact-effect-hint-multitool
-  whitelist:
-    components:
-    - Item
-  permanentComponents:
-  - type: UserInterface
-    interfaces:
-        enum.SignalLinkerUiKey.Key:
-          type: SignalPortSelectorBoundUserInterface
-  - type: ToolTileCompatible
-  - type: Tool
-    qualities:
-    - Screwing
-    speedModifier: 2 # Very powerful multitool to balance out the desire to sell or scrap for points
-    useSound: /Audio/Items/drill_use.ogg
-  - type: Tag
-    tags:
-      - Multitool
-  - type: MultipleTool
-    statusShowBehavior: true
-    entries:
-    - behavior: Screwing
-      useSound:
-        path: /Audio/Items/drill_use.ogg
-      changeSound:
-        path: /Audio/Items/change_drill.ogg
-    - behavior: Prying
-      useSound:
-        path: /Audio/Items/jaws_pry.ogg
-      changeSound:
-        path: /Audio/Items/change_drill.ogg
-    - behavior: Anchoring
-      useSound:
-        path: /Audio/Items/ratchet.ogg
-      changeSound:
-        path: /Audio/Items/change_drill.ogg
-    - behavior: Cutting
-      useSound:
-        path: /Audio/Items/jaws_cut.ogg
-      changeSound:
-        path: /Audio/Items/change_drill.ogg
-    - behavior: Pulsing
-      changeSound:
-        path: /Audio/Items/change_drill.ogg
diff --git a/Resources/Prototypes/XenoArch/artifact_triggers.yml b/Resources/Prototypes/XenoArch/artifact_triggers.yml
deleted file mode 100644 (file)
index ea2ea90..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-- type: artifactTrigger
-  id: TriggerInteraction
-  targetDepth: 0
-  triggerHint: artifact-trigger-hint-physical
-  components:
-  - type: ArtifactInteractionTrigger
-
-- type: artifactTrigger
-  id: TriggerTimer
-  targetDepth: 0
-  components:
-  - type: ArtifactTimerTrigger
-
-- type: artifactTrigger
-  id: TriggerExamine
-  targetDepth: 0
-  triggerHint: artifact-trigger-hint-examine
-  components:
-  - type: ArtifactExamineTrigger
-
-- type: artifactTrigger
-  id: TriggerAnchor
-  targetDepth: 0
-  triggerHint: artifact-trigger-hint-tool
-  blacklist:
-    components:
-    - Item
-  components:
-  - type: ArtifactAnchorTrigger
-
-- type: artifactTrigger
-  id: TriggerElectricity
-  targetDepth: 0
-  triggerHint: artifact-trigger-hint-electricity
-  blacklist:
-    components:
-    - Item
-  components:
-  - type: ArtifactElectricityTrigger
-  - type: PowerConsumer
-    voltage: Medium
-    drawRate: 500
-  - type: Electrified
-    requirePower: true
-    noWindowInTile: true
-    highVoltageNode: high
-    mediumVoltageNode: medium
-    lowVoltageNode: low
-  - type: NodeContainer
-    nodes:
-      medium:
-        !type:CableDeviceNode
-        nodeGroupID: MVPower
-        # sadly, HVPower and Apc cables doesn't work right now
-
-- type: artifactTrigger
-  id: TriggerMusic
-  targetDepth: 1
-  triggerHint: artifact-trigger-hint-music
-  components:
-  - type: ArtifactMusicTrigger
-
-- type: artifactTrigger
-  id: TriggerBruteDamage
-  targetDepth: 1
-  triggerHint: artifact-trigger-hint-physical
-  components:
-  - type: ArtifactDamageTrigger
-    damageTypes:
-    - Blunt
-    - Slash
-    - Piercing
-    damageThreshold: 50
-
-- type: artifactTrigger
-  id: TriggerItemLanded
-  targetDepth: 1
-  triggerHint: artifact-trigger-hint-land
-  whitelist:
-    components:
-    - Item
-  components:
-  - type: ArtifactLandTrigger
-
-- type: artifactTrigger
-  id: TriggerHeat
-  targetDepth: 1
-  triggerHint: artifact-trigger-hint-heat
-  components:
-  - type: ArtifactHeatTrigger
-
-- type: artifactTrigger
-  id: TriggerWater
-  targetDepth: 1
-  triggerHint: artifact-trigger-hint-water
-  components:
-  - type: Reactive
-    groups:
-      Acidic: [ Touch ]
-    reactions:
-    - reagents: [ Water ]
-      methods: [ Touch ]
-      effects:
-      - !type:ActivateArtifact
-
-- type: artifactTrigger
-  id: TriggerBlood
-  targetDepth: 1
-  triggerHint: artifact-trigger-hint-blood
-  components:
-  - type: Reactive
-    groups:
-      Acidic: [ Touch ]
-    reactions:
-    - reagents: [ Blood, CopperBlood, InsectBlood, Slime, AmmoniaBlood, ZombieBlood ]
-      methods: [ Touch ]
-      effects:
-      - !type:ActivateArtifact
-
-- type: artifactTrigger
-  id: TriggerMedical
-  targetDepth: 2
-  triggerHint: artifact-trigger-hint-medical
-  components:
-  - type: Reactive
-    groups:
-      Acidic: [ Touch ]
-    reactions:
-    - reagents: [ Dylovene, Diphenhydramine, Arithrazine, Bicaridine, Dermaline, Dexalin, DexalinPlus, Tricordrazine, Leporazine, Bruizine, Lacerinol, Puncturase, Pyrazine, Insuzine, Kelotane, Hyronalin, Inaprovaline, Epinephrine ]
-      methods: [ Touch ]
-      effects:
-      - !type:ActivateArtifact
-
-- type: artifactTrigger
-  id: TriggerGas
-  targetDepth: 2
-  triggerHint: artifact-trigger-hint-regular-gases
-  components:
-  - type: ArtifactGasTrigger
-    possibleGas:
-    - Oxygen
-    - Nitrogen
-    - CarbonDioxide
-
-- type: artifactTrigger
-  id: TriggerDeath
-  targetDepth: 2
-  triggerHint: artifact-trigger-hint-death
-  components:
-  - type: ArtifactDeathTrigger
-
-- type: artifactTrigger
-  id: TriggerMagnet
-  targetDepth: 2
-  triggerHint: artifact-trigger-hint-magnet
-  components:
-  - type: ArtifactMagnetTrigger
-
-- type: artifactTrigger
-  id: TriggerLowPressure
-  targetDepth: 2
-  triggerHint: artifact-trigger-hint-pressure
-  components:
-  - type: ArtifactPressureTrigger
-    minPressureThreshold: 50
-
-- type: artifactTrigger
-  id: TriggerHighDamage
-  targetDepth: 3
-  triggerHint: artifact-trigger-hint-physical
-  components:
-  - type: ArtifactDamageTrigger
-    damageThreshold: 500 #make it go boom or w/e
-
-- type: artifactTrigger
-  id: TriggerRadiation
-  targetDepth: 3
-  triggerHint: artifact-trigger-hint-radiation
-  components:
-  - type: ArtifactMicrowaveTrigger
-  - type: ArtifactDamageTrigger
-    damageTypes:
-    - Radiation
-    damageThreshold: 50
-  - type: RadiationReceiver
-
-- type: artifactTrigger
-  id: TriggerHighPressure
-  targetDepth: 3
-  triggerHint: artifact-trigger-hint-pressure
-  components:
-  - type: ArtifactPressureTrigger
-    maxPressureThreshold: 385
-
-- type: artifactTrigger
-  id: TriggerPlasma
-  targetDepth: 3
-  triggerHint: artifact-trigger-hint-plasma
-  components:
-  - type: ArtifactGasTrigger
-    possibleGas:
-    - Plasma
-
-#don't add in new targetdepth values until you have a few
-#or else it will skew heavily towards a few options.
diff --git a/Resources/Prototypes/XenoArch/effects.yml b/Resources/Prototypes/XenoArch/effects.yml
new file mode 100644 (file)
index 0000000..01a316d
--- /dev/null
@@ -0,0 +1,1359 @@
+- type: entityTable
+  id: XenoArtifactEffectsDefaultTable
+  table: !type:GroupSelector
+    children:
+    # hijacks use key, prevents from using artifact, sadly
+    #- id: XenoArtifactEffectUniversalIntercom
+    #  weight: 10.0
+    - id: XenoArtifactSolutionStorage
+      weight: 10.0
+    - id: XenoArtifactPhasing
+      weight: 2.0
+    - id: XenoArtifactWandering
+      weight: 4.0
+    - id: XenoArtifactSpeedUp
+      weight: 4.0
+    - id: XenoArtifactGhost
+      weight: 2.0
+    - id: XenoArtifactEffectBadFeeling
+      weight: 10.0
+    - id: XenoArtifactEffectGoodFeeling
+      weight: 10.0
+    - id: XenoArtifactEffectJunkSpawn
+      weight: 10.0
+    - id: XenoArtifactEffectLightFlicker
+      weight: 10.0
+    - id: XenoArtifactPotassiumWave
+      weight: 7.0
+    - id: XenoArtifactFloraSpawn
+      weight: 10.0
+    - id: XenoArtifactChemicalPuddle
+      weight: 10.0
+    - id: XenoArtifactThrowThingsAround
+      weight: 10.0
+    - id: XenoArtifactColdWave
+      weight: 10.0
+    - id: XenoArtifactHeatWave
+      weight: 4.0
+    - id: XenoArtifactFoamMild
+      weight: 8.0
+    - id: XenoArtifactRandomInstrumentSpawn
+      weight: 10.0
+    - id: XenoArtifactMonkeySpawn
+      weight: 10.0
+    - id: XenoArtifactRadioactive
+      weight: 8.0
+    - id: XenoArtifactChargeBattery
+      weight: 10.0
+    - id: XenoArtifactKnock
+      weight: 4.0
+    - id: XenoArtifactMagnet
+      weight: 2.0
+    - id: XenoArtifactMagnetNegative
+      weight: 2.0
+    - id: XenoArtifactStealth
+      weight: 1.0
+    - id: XenoArtifactRareMaterialSpawnSilver
+      weight: 1.8 # amount is laughable
+    - id: XenoArtifactRareMaterialSpawnPlasma
+      weight: 2.0 # amount is laughable
+    - id: XenoArtifactRareMaterialSpawnGold
+      weight: 1.8 # amount is laughable
+    - id: XenoArtifactRareMaterialSpawnUranium
+      weight: 1.0 # amount is laughable
+    - id: XenoArtifactAngryCarpSpawn
+      weight: 4.0
+    - id: XenoArtifactFaunaSpawn
+      weight: 10.0
+    - id: XenoArtifactCashSpawn
+      weight: 10.0
+    - id: XenoArtifactShatterWindows
+      weight: 8.0
+    - id: XenoArtifactFoamGood
+      weight: 4.0
+    - id: XenoArtifactFoamDangerous
+      weight: 2.0
+    - id: XenoArtifactPuddleRare
+      weight: 2.0
+    - id: XenoArtifactAnomalySpawn
+      weight: 10.0
+    - id: XenoArtifactIgnite
+      weight: 2.0
+    - id: XenoArtifactTeleport
+      weight: 2.0
+    - id: XenoArtifactEmp
+      weight: 2.0
+    - id: XenoArtifactPolyMonkey
+      weight: 2.0
+    - id: XenoArtifactPolyLuminous
+      weight: 2.0
+    - id: XenoArtifactPolyLizard
+      weight: 2.0
+    - id: XenoArtifactRadioactiveStrong
+      weight: 3.0
+    - id: XenoArtifactMaterialSpawnGlass
+      weight: 3.3
+    - id: XenoArtifactMaterialSpawnSteel
+      weight: 3.3
+    - id: XenoArtifactMaterialSpawnPlastic
+      weight: 3.3
+    - id: XenoArtifactPortal
+      weight: 2.0
+    - id: XenoArtifactArtifactSpawn
+      weight: 0.5
+    - id: XenoArtifactShuffle
+      weight: 3.0
+    - id: XenoArtifactHealAll
+      weight: 1.0
+    #- id: XenoArtifactTesla
+    #  weight: 10.0
+    #- id: XenoArtifactSingularity
+    #  weight: 10.0
+    - id: XenoArtifactExplosionScary
+      weight: 1.0
+    - id: XenoArtifactBoom
+      weight: 5.0
+    - id: XenoArtifactEffectCreationGasPlasma
+      weight: 2.0
+    - id: XenoArtifactEffectCreationGasTritium
+      weight: 2.0
+    - id: XenoArtifactEffectCreationGasAmmonia
+      weight: 3.0
+    - id: XenoArtifactEffectCreationGasFrezon
+      weight: 1.0
+    - id: XenoArtifactEffectCreationGasNitrousOxide
+      weight: 4.0
+    - id: XenoArtifactEffectCreationGasCarbonDioxide
+      weight: 4.0
+
+- type: entityTable
+  id: XenoArtifactEffectsHandheldOnlyTable
+  table: !type:GroupSelector
+    children:
+    #- id: XenoArtifactBecomeRandomInstrument
+    #  weight 10.0 # removed until we have value-based system
+    #- id: XenoArtifactGun
+    #  weight 4.0 #it conflicts with default interaction - it should activate artifact nodes
+    - id: XenoArtifactOmnitool
+      weight: 10.0
+    - id: XenoArtifactDrill
+      weight: 10.0
+
+- type: entity
+  id: BaseXenoArtifactEffect
+  name: effect
+  description: Unknown
+  categories: [ HideSpawnMenu ]
+  abstract: true
+  components:
+  - type: XenoArtifactNode
+  - type: NameIdentifier
+    group: XenoArtifactNode
+
+- type: entity
+  id: BaseOneTimeXenoArtifactEffect
+  parent: BaseXenoArtifactEffect
+  name: one-time-effect
+  description: Unknown
+  categories: [ HideSpawnMenu ]
+  abstract: true
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 1
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 0
+  - type: NameIdentifier
+    group: XenoArtifactNode
+
+- type: entity
+  id: XenoArtifactEffectUniversalIntercom
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Obtains ability of long-distance communication device
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: RadioMicrophone
+      powerRequired: false
+      toggleOnInteract: false
+      listenRange: 3
+    - type: Speech
+    - type: RadioSpeaker
+      toggleOnInteract: false
+    - type: ActivatableUI
+      key: enum.IntercomUiKey.Key
+    - type: Intercom
+      requiresPower: false
+      supportedChannels:
+      - Common
+      - CentCom
+      - Command
+      - Engineering
+      - Medical
+      - Science
+      - Security
+      - Service
+      - Supply
+
+- type: entity
+  id: XenoArtifactBecomeRandomInstrument
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Obtains ability of musical instrument
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: Instrument
+    - type: ActivatableUI
+      singleUser: true
+      verbText: verb-instrument-openui
+      key: enum.InstrumentUiKey.Key
+
+- type: entity
+  id: XenoArtifactStorage
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Obtains ability of hidden storage
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: Item
+      size: Huge
+    - type: Storage
+      maxItemSize: Huge
+      grid:
+      - 0,0,10,5
+
+- type: entity
+  id: XenoArtifactPhasing
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Becomes phased
+  components:
+  - type: XAERemoveCollision
+
+- type: entity
+  id: XenoArtifactWandering
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Starts to move sporadically
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: RandomWalk
+      minSpeed: 12
+      maxSpeed: 20
+      minStepCooldown: 1
+      maxStepCooldown: 3
+
+- type: entity
+  id: XenoArtifactSolutionStorage
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Obtains ability of container for chemical solutions
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: SolutionContainerManager
+      solutions:
+        beaker:
+          maxVol: 150
+    - type: FitsInDispenser
+      solution: beaker
+    - type: RefillableSolution
+      solution: beaker
+    - type: DrainableSolution
+      solution: beaker
+    - type: ExaminableSolution
+      solution: beaker
+    - type: DrawableSolution
+      solution: beaker
+    - type: InjectableSolution
+      solution: beaker
+    - type: SolutionTransfer
+      canChangeTransferAmount: true
+    - type: Drink
+      solution: beaker
+
+- type: entity
+  id: XenoArtifactSpeedUp
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Improves holder movement speed
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: HeldSpeedModifier
+      walkModifier: 1.2
+      sprintModifier: 1.3
+
+- type: entity
+  id: XenoArtifactDrill
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Obtains ability of drill
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: MeleeWeapon
+      damage:
+        types:
+          Piercing: 18
+          Blunt: 4
+      soundHit:
+        path: /Audio/Weapons/bladeslice.ogg
+    - type: Sharp
+
+- type: entity
+  id: XenoArtifactGenerateEnergy
+  parent: BaseOneTimeXenoArtifactEffect # todo - increment power, but only once per node
+  description: Produces power
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: PowerSupplier
+      supplyRate: 20000
+    - type: NodeContainer
+      examinable: true
+      nodes:
+        output_hv:
+          !type:CableDeviceNode
+          nodeGroupID: HVPower
+
+- type: entity
+  id: XenoArtifactGun
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Obtains ability of firearm
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: RevolverAmmoProvider
+      whitelist:
+        tags:
+        - CartridgeMagnum
+        - SpeedLoaderMagnum
+      proto: CartridgeMagnum
+      capacity: 7
+      chambers: [ True, True, True, True, True, True, True ]
+      ammoSlots: [ null, null, null, null, null, null, null ]
+      soundEject:
+        path: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg
+      soundInsert:
+        path: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg
+    - type: Gun
+      selectedMode: SemiAuto
+      fireRate: 2
+      availableModes:
+      - SemiAuto
+      - FullAuto # no alien revolver in buildings
+      soundGunshot:
+        path: /Audio/Weapons/Guns/Gunshots/revolver.ogg
+
+- type: entity
+  id: XenoArtifactGhost
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Becomes sentient
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: GhostRole
+      allowMovement: true
+      allowSpeech: true
+      makeSentient: true
+      name: ghost-role-information-artifact-name
+      description: ghost-role-information-artifact-description
+      rules: ghost-role-information-freeagent-rules
+      raffle:
+        settings: default
+      mindRoles:
+      - MindRoleGhostRoleFreeAgent
+    - type: GhostTakeoverAvailable
+    - type: MovementSpeedModifier
+      baseWalkSpeed: 0.25
+      baseSprintSpeed: 0.5
+
+- type: entity
+  id: XenoArtifactOmnitool
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Obtains ability of omnitool
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: UserInterface
+      interfaces:
+          enum.SignalLinkerUiKey.Key:
+            type: SignalPortSelectorBoundUserInterface
+    - type: ToolTileCompatible
+    - type: Tool
+      qualities:
+      - Screwing
+      speedModifier: 2 # Very powerful multitool to balance out the desire to sell or scrap for points
+      useSound: /Audio/Items/drill_use.ogg
+    - type: Tag
+      tags:
+        - Multitool
+    - type: MultipleTool
+      statusShowBehavior: true
+      entries:
+      - behavior: Screwing
+        useSound:
+          path: /Audio/Items/drill_use.ogg
+        changeSound:
+          path: /Audio/Items/change_drill.ogg
+      - behavior: Prying
+        useSound:
+          path: /Audio/Items/jaws_pry.ogg
+        changeSound:
+          path: /Audio/Items/change_drill.ogg
+      - behavior: Anchoring
+        useSound:
+          path: /Audio/Items/ratchet.ogg
+        changeSound:
+          path: /Audio/Items/change_drill.ogg
+      - behavior: Cutting
+        useSound:
+          path: /Audio/Items/jaws_cut.ogg
+        changeSound:
+          path: /Audio/Items/change_drill.ogg
+      - behavior: Pulsing
+        changeSound:
+          path: /Audio/Items/change_drill.ogg
+
+- type: entity
+  id: XenoArtifactEffectBadFeeling
+  parent: BaseXenoArtifactEffect
+  description: Broadcasts sublime message
+  components:
+  - type: XAETelepathic
+    messages:
+    - badfeeling-artifact-1
+    - badfeeling-artifact-2
+    - badfeeling-artifact-3
+    - badfeeling-artifact-4
+    - badfeeling-artifact-5
+    - badfeeling-artifact-6
+    - badfeeling-artifact-7
+    - badfeeling-artifact-8
+    - badfeeling-artifact-9
+    - badfeeling-artifact-10
+    - badfeeling-artifact-11
+    - badfeeling-artifact-12
+    - badfeeling-artifact-13
+    - badfeeling-artifact-14
+    - badfeeling-artifact-15
+    drastic:
+    - badfeeling-artifact-drastic-1
+    - badfeeling-artifact-drastic-2
+    - badfeeling-artifact-drastic-3
+    - badfeeling-artifact-drastic-4
+    - badfeeling-artifact-drastic-5
+    - badfeeling-artifact-drastic-6
+
+- type: entity
+  id: XenoArtifactEffectGoodFeeling
+  parent: BaseXenoArtifactEffect
+  description: Broadcasts sublime message
+  components:
+  - type: XAETelepathic
+    messages:
+    - goodfeeling-artifact-1
+    - goodfeeling-artifact-2
+    - goodfeeling-artifact-3
+    - goodfeeling-artifact-4
+    - goodfeeling-artifact-5
+    - goodfeeling-artifact-6
+    - goodfeeling-artifact-7
+    - goodfeeling-artifact-8
+    - goodfeeling-artifact-9
+    - goodfeeling-artifact-10
+    - goodfeeling-artifact-11
+    - goodfeeling-artifact-12
+    - goodfeeling-artifact-13
+    - goodfeeling-artifact-14
+    drastic:
+    - goodfeeling-artifact-drastic-1
+    - goodfeeling-artifact-drastic-2
+    - goodfeeling-artifact-drastic-3
+    - goodfeeling-artifact-drastic-4
+    - goodfeeling-artifact-drastic-5
+    - goodfeeling-artifact-drastic-6
+
+- type: entity
+  id: XenoArtifactEffectJunkSpawn
+  parent: BaseXenoArtifactEffect
+  description: Create recyclable junk
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        rolls: !type:RangeNumberSelector
+          range: 1, 4
+        children:
+        - !type:NestedSelector
+          tableId: GenericTrashItems
+          weight: 35
+        - !type:AllSelector
+          weight: 1
+          children:
+          - id: ToySpawner
+
+- type: entity
+  id: XenoArtifactEffectLightFlicker
+  parent: BaseXenoArtifactEffect
+  description: Minor electromagnetic interference
+  components:
+  - type: XAELightFlicker
+
+- type: entity
+  id: XenoArtifactPotassiumWave
+  parent: BaseXenoArtifactEffect
+  description: Produces potassium
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: FoodBanana
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.5
+  - type: XAECreatePuddle
+    chemAmount:
+      min: 1
+      max: 1
+    chemicalSolution:
+      maxVol: 100
+      canReact: false
+    possibleChemicals:
+    - Potassium
+
+- type: entity
+  id: XenoArtifactFloraSpawn
+  parent: BaseXenoArtifactEffect
+  description: Produces flora
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: RandomFloraTree
+
+- type: entity
+  id: XenoArtifactChemicalPuddle
+  parent: BaseXenoArtifactEffect
+  description: Produces puddle of chemical mixture # todo: make description say what exact chemical is produced, maybe add mixes into possible chemicals
+  components:
+  - type: XAECreatePuddle
+    chemAmount:
+      min: 1
+      max: 3
+    replaceDescription: true
+    chemicalSolution:
+      maxVol: 500
+      canReact: false
+    possibleChemicals:
+    - Aluminium
+    - Carbon
+    - Chlorine
+    - Copper
+    - Ethanol
+    - Fluorine
+    - Sugar
+    - Hydrogen
+    - Iodine
+    - Iron
+    - Lithium
+    - Mercury
+    - Nitrogen
+    - Oxygen
+    - Phosphorus
+    - Potassium
+    - Radium
+    - Silicon
+    - Sodium
+    - Water
+    - Sulfur
+
+- type: entity
+  id: XenoArtifactThrowThingsAround
+  parent: BaseXenoArtifactEffect
+  description: Minor implosion
+  components:
+  - type: XAEThrowThingsAround
+
+- type: entity
+  id: XenoArtifactColdWave
+  parent: BaseXenoArtifactEffect
+  description: Cools down surrounding gas
+  components:
+  - type: XAETemperature
+    targetTemp: 50
+
+- type: entity
+  id: XenoArtifactHeatWave
+  parent: BaseXenoArtifactEffect
+  description: Heats up surrounding gas greatly
+  components:
+  - type: XAETemperature
+    targetTemp: 500
+
+- type: entity
+  id: XenoArtifactFoamMild
+  parent: BaseXenoArtifactEffect
+  description: Produces chemical foam # todo: separate in 1 for each chemical for description? actually sounds like a very good idea
+  components:
+  - type: XAEFoam
+    replaceDescription: true
+    reagents:
+    - Oxygen
+    - Plasma
+    - Blood
+    - SpaceCleaner
+    - Nutriment
+    - SpaceLube
+    - Ethanol
+    - Mercury
+    - VentCrud
+    - WeldingFuel
+    - JuiceThatMakesYouWeh
+
+- type: entity
+  id: XenoArtifactRandomInstrumentSpawn
+  parent: BaseXenoArtifactEffect
+  description: Creates musical instrument
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 2
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 1
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: RandomInstruments
+
+- type: entity
+  id: XenoArtifactMonkeySpawn
+  parent: BaseXenoArtifactEffect
+  description: Creates primate
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 3
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 2
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        children:
+        - id: MobMonkey
+          weight: 95.0
+        - id: MobGorilla
+          weight: 5.0
+
+- type: entity
+  id: XenoArtifactRadioactive
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Becomes mildly radioactive
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: RadiationSource
+      intensity: 1
+      slope: 0.3
+
+- type: entity
+  id: XenoArtifactChargeBattery
+  parent: BaseXenoArtifactEffect
+  description: Charges up batteries
+  components:
+  - type: XAEChargeBattery
+  - type: XAETelepathic
+    messages:
+    - charge-artifact-popup
+
+- type: entity
+  id: XenoArtifactKnock
+  parent: BaseXenoArtifactEffect
+  description: Mild electromagnetic interference
+  components:
+  - type: XAEKnock
+  - type: XAELightFlicker
+
+- type: entity
+  id: XenoArtifactMagnet
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Create small gravity well
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: GravityWell
+      maxRange: 3
+      baseRadialAcceleration: 1
+      baseTangentialAcceleration: 3
+
+- type: entity
+  id: XenoArtifactMagnetNegative
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Create small gravity well
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: GravityWell
+      maxRange: 3
+      baseRadialAcceleration: -1
+      baseTangentialAcceleration: -3
+
+- type: entity
+  id: XenoArtifactStealth
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Create light interference
+  components:
+  - type: XAEApplyComponents
+    components:
+    - type: Stealth
+      hadOutline: true
+    - type: StealthOnMove
+      passiveVisibilityRate: -0.10
+      movementVisibilityRate: 0.10
+
+- type: entity
+  id: XenoArtifactRareMaterialSpawn
+  parent: BaseXenoArtifactEffect # todo: splice into different well-named effects, amounts should reflect how rare material is
+  description: Create rare materials
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: SilverOre1
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.3
+        - id: PlasmaOre1
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.3
+        - id: GoldOre1
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.3
+        - id: UraniumOre1
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.3
+
+- type: entity
+  id: XenoArtifactRareMaterialSpawnSilver
+  parent: BaseXenoArtifactEffect
+  description: Create rare materials
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 4
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 2
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: SilverOre1
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.3
+
+- type: entity
+  id: XenoArtifactRareMaterialSpawnPlasma
+  parent: BaseXenoArtifactEffect
+  description: Create plasma
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 4
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 2
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: PlasmaOre1
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.3
+
+- type: entity
+  id: XenoArtifactRareMaterialSpawnGold
+  parent: BaseXenoArtifactEffect
+  description: Create gold
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 3
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 1
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: GoldOre1
+          rolls: !type:ConstantNumberSelector
+            value: 6
+          prob: 0.3
+
+- type: entity
+  id: XenoArtifactRareMaterialSpawnUranium
+  parent: BaseXenoArtifactEffect
+  description: Create uranium
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 4
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 2
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: UraniumOre1
+          rolls: !type:ConstantNumberSelector
+            value: 3
+          prob: 0.3
+
+- type: entity
+  id: XenoArtifactAngryCarpSpawn
+  parent: BaseXenoArtifactEffect
+  description: Create hostile fish
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 3
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 2
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        children:
+        - id: MobCarpMagic
+          weight: 1.0
+        - id: MobCarpHolo
+          weight: 1.0
+
+- type: entity
+  id: XenoArtifactFaunaSpawn
+  parent: BaseXenoArtifactEffect
+  description: Create friendly fauna
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 4
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 3
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        children:
+        - id: MobAdultSlimesYellowAngry
+        - id: MobAngryBee
+        - id: MobBearSpace
+        - id: MobXenoRavager
+        - id: MobTick
+        - id: MobSpiderSpace
+        - id: MobPurpleSnake
+        - id: MobMouse
+        - id: MobKangarooSpace
+        - id: MobPig
+        - id: MobParrot
+        - id: MobKangaroo
+        - id: MobFox
+        - id: MobPenguin
+          amount: !type:RangeNumberSelector
+            range: 1, 2
+        - id: MobMothroach
+          amount: !type:RangeNumberSelector
+            range: 1, 2
+        - id: MobCorgiPuppy
+          amount: !type:RangeNumberSelector
+            range: 1, 2
+        - id: MobCatKitten
+          amount: !type:RangeNumberSelector
+            range: 1, 2
+        - id: MobCat
+          amount: !type:RangeNumberSelector
+            range: 1, 2
+        - id: MobBee
+          amount: !type:RangeNumberSelector
+            range: 2, 5
+        - id: MobGoat
+          amount: !type:RangeNumberSelector
+            range: 1, 3
+        - id: MobMonkeySyndicateAgent #so lucky
+          prob: 0.03
+
+- type: entity
+  id: XenoArtifactCashSpawn
+  parent: BaseXenoArtifactEffect
+  description: Create money
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 2
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 1
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        rolls: !type:RangeNumberSelector
+          range: 2, 4
+        children:
+        - id: SpaceCash10
+          weight: 0.75
+        - id: SpaceCash100
+          weight: 0.5
+        - id: SpaceCash500
+          weight: 0.25
+        - id: SpaceCash1000
+          weight: 0.1
+
+- type: entity
+  id: XenoArtifactShatterWindows
+  parent: BaseXenoArtifactEffect
+  description: Break windows
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 3
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 2
+  - type: XAEDamageInArea
+    damageChance: 0.75
+    whitelist:
+      tags:
+      - Window
+    damage:
+      types:
+        Structural: 200
+
+- type: entity
+  id: XenoArtifactFoamGood
+  parent: BaseXenoArtifactEffect
+  description: Creates wave of helpful foam
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 7
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 5
+  - type: XAEFoam
+    replaceDescription: true
+    reagents:
+    - Dermaline
+    - Arithrazine
+    - Bicaridine
+    - Inaprovaline
+    - Kelotane
+    - Dexalin
+    - Omnizine
+
+- type: entity
+  id: XenoArtifactFoamDangerous
+  parent: BaseXenoArtifactEffect
+  description: Creates wave of harmful foam
+  components:
+  - type: XAEFoam
+    minFoamAmount: 20
+    maxFoamAmount: 30
+    replaceDescription: true
+    reagents:
+    - Tritium
+    - Plasma
+    - SulfuricAcid
+    - SpaceDrugs
+    - Nocturine
+    - MuteToxin
+    - Napalm
+    - CarpoToxin
+    - ChloralHydrate
+    - Mold
+    - Amatoxin
+
+- type: entity
+  id: XenoArtifactPuddleRare
+  parent: BaseXenoArtifactEffect
+  description: Creates puddle of helpful chemicals
+  components:
+  - type: XAECreatePuddle
+    chemAmount:
+      min: 1
+      max: 3
+    replaceDescription: true
+    chemicalSolution:
+      maxVol: 500
+      canReact: false
+    possibleChemicals:
+    - Dermaline
+    - Arithrazine
+    - Bicaridine
+    - Inaprovaline
+    - Kelotane
+    - Dexalin
+    - Omnizine
+    - Napalm
+    - Toxin
+    - Epinephrine
+    - Cognizine
+    - Ultravasculine
+    - Desoxyephedrine
+    - Pax
+    - Siderlac
+
+- type: entity
+  id: XenoArtifactAnomalySpawn
+  parent: BaseXenoArtifactEffect
+  description: Creates anomaly
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 2
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 1
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: RandomAnomalySpawner
+
+- type: entity
+  id: XenoArtifactIgnite
+  parent: BaseXenoArtifactEffect
+  description: Pyrokinesis
+  components:
+  - type: XAEIgnite
+    range: 7
+    fireStack:
+      min: 3
+      max: 6
+
+- type: entity
+  id: XenoArtifactTeleport
+  parent: BaseXenoArtifactEffect
+  description: Teleportation
+  components:
+  - type: XAERandomTeleportInvoker
+
+- type: entity
+  id: XenoArtifactEmp
+  parent: BaseXenoArtifactEffect
+  description: Dangerous electromagnetic interference
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 5
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 3
+  - type: XAEEmpInArea
+
+- type: entity
+  id: XenoArtifactPolyMonkey
+  parent: BaseXenoArtifactEffect
+  description: Temporarily reshape flesh to fur
+  components:
+  - type: XAEPolymorph
+
+- type: entity
+  id: XenoArtifactPolyLizard
+  parent: BaseXenoArtifactEffect
+  description: Temporarily reshape flesh to scale
+  components:
+  - type: XAEPolymorph
+    polymorphPrototypeName: ArtifactLizard
+
+- type: entity
+  id: XenoArtifactPolyLuminous
+  parent: BaseXenoArtifactEffect
+  description: Temporarily reshape flesh to light
+  components:
+  - type: XAEPolymorph
+    polymorphPrototypeName: ArtifactLuminous
+
+- type: entity
+  id: XenoArtifactRadioactiveStrong
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Becomes highly radioactive
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: RadiationSource
+      intensity: 2
+      slope: 0.3
+
+- type: entity
+  id: XenoArtifactMaterialSpawnGlass
+  parent: BaseXenoArtifactEffect
+  description: Create glass
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        children:
+        - id: SheetGlass
+
+- type: entity
+  id: XenoArtifactMaterialSpawnSteel
+  parent: BaseXenoArtifactEffect
+  description: Create steel
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        children:
+        - id: SheetSteel
+
+- type: entity
+  id: XenoArtifactMaterialSpawnPlastic
+  parent: BaseXenoArtifactEffect
+  description: Create plastic
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:GroupSelector
+        children:
+        - id: SheetPlastic
+
+- type: entity
+  id: XenoArtifactPortal
+  parent: BaseXenoArtifactEffect
+  description: Create short-living bluespace portal
+  components:
+  - type: XAEPortal
+
+- type: entity
+  id: XenoArtifactArtifactSpawn
+  parent: BaseXenoArtifactEffect
+  description: Create artifact
+  components:
+  - type: XenoArtifactNode
+    maxDurability: 2
+    maxDurabilityCanDecreaseBy:
+      min: 0
+      max: 1
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: RandomArtifactSpawner
+
+- type: entity
+  id: XenoArtifactShuffle
+  parent: BaseXenoArtifactEffect
+  description: Switch places of sentient beings #not ALL beings, but oh well...
+  components:
+  - type: XAEShuffle
+  - type: XAETelepathic
+    range: 7.5
+    messages:
+    - shuffle-artifact-popup
+
+- type: entity
+  id: XenoArtifactHealAll
+  parent: BaseXenoArtifactEffect
+  description: Miraclous healing
+  components:
+  - type: XAEDamageInArea
+    damageChance: 1
+    radius: 8
+    whitelist:
+      components:
+      - MobState
+    damage:
+      groups:
+        Brute: -300
+        Burn: -300
+
+- type: entity
+  id: XenoArtifactTesla
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Mass destruction
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: Singularity
+
+- type: entity
+  id: XenoArtifactSingularity
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Imminent doom
+  components:
+  - type: XAEApplyComponents
+    applyIfAlreadyHave: true
+    refreshOnReactivate: true
+    components:
+    - type: EntityTableSpawner
+      deleteSpawnerAfterSpawn: false
+      table: !type:AllSelector
+        children:
+        - id: TeslaEnergyBall
+
+- type: entity
+  id: XenoArtifactExplosionScary
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Small scale high-speed nuclear reaction
+  components:
+  - type: XAETriggerExplosives
+  - type: Explosive
+    deleteAfterExplosion: false
+    explosionType: Radioactive
+    totalIntensity: 300
+    intensitySlope: 2
+    maxIntensity: 1.5
+    canCreateVacuum: false
+    repeatable: true
+
+- type: entity
+  id: XenoArtifactBoom
+  parent: BaseOneTimeXenoArtifactEffect
+  description: Explosion
+  components:
+  - type: XAETriggerExplosives
+  - type: Explosive
+    deleteAfterExplosion: false
+    explosionType: Default
+    totalIntensity: 500
+    intensitySlope: 2.5
+    maxIntensity: 50
+    repeatable: true
+
+- type: entity
+  id: XenoArtifactEffectCreationGasPlasma
+  parent: BaseXenoArtifactEffect
+  description: Expels plasma
+  components:
+  - type: XAECreateGas
+    gases:
+      Plasma: 300
+
+- type: entity
+  id: XenoArtifactEffectCreationGasTritium
+  parent: BaseXenoArtifactEffect
+  description: Expels tritium
+  components:
+  - type: XAECreateGas
+    gases:
+      Tritium: 300
+
+- type: entity
+  id: XenoArtifactEffectCreationGasAmmonia
+  parent: BaseXenoArtifactEffect
+  description: Expels ammonia
+  components:
+  - type: XAECreateGas
+    gases:
+      Ammonia: 300
+
+- type: entity
+  id: XenoArtifactEffectCreationGasFrezon
+  parent: BaseXenoArtifactEffect
+  description: Expels frezon
+  components:
+  - type: XAECreateGas
+    gases:
+      Frezon: 300
+
+- type: entity
+  id: XenoArtifactEffectCreationGasNitrousOxide
+  parent: BaseXenoArtifactEffect
+  description: Expels nitrous oxide
+  components:
+  - type: XAECreateGas
+    gases:
+      NitrousOxide: 300
+
+- type: entity
+  id: XenoArtifactEffectCreationGasCarbonDioxide
+  parent: BaseXenoArtifactEffect
+  description: Expels carbon dioxide
+  components:
+  - type: XAECreateGas
+    gases:
+      CarbonDioxide: 300
diff --git a/Resources/Prototypes/XenoArch/triggers.yml b/Resources/Prototypes/XenoArch/triggers.yml
new file mode 100644 (file)
index 0000000..5e70843
--- /dev/null
@@ -0,0 +1,265 @@
+- type: weightedRandomXenoArchTrigger
+  id: DefaultTriggers
+  weights:
+    TriggerMusic: 1
+    TriggerHeat: 1
+    TriggerCold: 0.5
+    TriggerNoOxygen: 1
+    TriggerWater: 1
+    TriggerCO2: 0.5
+    TriggerPlasma: 0.5
+    TriggerN2O: 0.5
+    TriggerTritium: 0.1
+    TriggerAmmonia: 0.1
+    TriggerFrezon: 0.1
+    TriggerRadiation: 1
+    TriggerPressureHigh: 0.5
+    TriggerPressureLow: 1
+    TriggerExamine: 1
+    TriggerBruteDamage: 1
+    #TriggerInteraction: 1 #this one interferes with activating artifact AND brute dmg!
+    TriggerWrenching: 0.5
+    TriggerPrying: 0.5
+    TriggerScrewing: 0.5
+    TriggerPulsing: 0.5
+    TriggerTimer: 0.25
+    TriggerBlood: 1
+    TriggerThrow: 1
+    TriggerDeath: 1
+    TriggerMagnet: 1
+
+- type: xenoArchTrigger
+  id: TriggerMusic
+  tip: xenoarch-trigger-tip-music
+  components:
+  - type: XATCompNearby
+    requireComponentWithName: ActiveInstrument
+    radius: 2
+
+- type: xenoArchTrigger
+  id: TriggerHeat
+  tip: xenoarch-trigger-tip-heat
+  components:
+  - type: XATTemperature
+    targetTemperature: 373
+    triggerOnHigherTemp: true
+  - type: XATDamageThresholdReached
+    typesNeeded:
+      Heat: 20
+# This kinda trivializes the difficulty.
+#  - type: XATToolUse
+#    requiredTool: Welding
+#    delay: 5
+#    fuel: 10
+
+- type: xenoArchTrigger
+  id: TriggerCold
+  tip: xenoarch-trigger-tip-cold
+  components:
+  - type: XATTemperature
+    targetTemperature: 255
+    triggerOnHigherTemp: false
+  - type: XATDamageThresholdReached
+    typesNeeded:
+      Cold: 20
+
+- type: xenoArchTrigger
+  id: TriggerNoOxygen
+  tip: xenoarch-trigger-tip-no-oxygen
+  components:
+  - type: XATGas
+    targetGas: Oxygen
+    moles: 10
+    shouldBePresent: false
+
+- type: xenoArchTrigger
+  id: TriggerWater
+  tip: xenoarch-trigger-tip-water
+  components:
+  - type: XATGas
+    targetGas: WaterVapor
+  - type: XATReactive
+    reagents:
+    - Water
+
+- type: xenoArchTrigger
+  id: TriggerCO2
+  tip: xenoarch-trigger-tip-co2
+  components:
+  - type: XATGas
+    targetGas: CarbonDioxide
+  - type: XATReactive
+    reagents:
+    - CarbonDioxide
+
+- type: xenoArchTrigger
+  id: TriggerPlasma
+  tip: xenoarch-trigger-tip-plasma
+  components:
+  - type: XATGas
+    targetGas: Plasma
+  - type: XATReactive
+    reagents:
+    - Plasma
+
+- type: xenoArchTrigger
+  id: TriggerTritium
+  tip: xenoarch-trigger-tip-tritium
+  components:
+  - type: XATGas
+    targetGas: Tritium
+  - type: XATReactive
+    reagents:
+    - Tritium
+
+- type: xenoArchTrigger
+  id: TriggerAmmonia
+  tip: xenoarch-trigger-tip-ammonia
+  components:
+  - type: XATGas
+    targetGas: Ammonia
+  - type: XATReactive
+    reagents:
+    - Ammonia
+
+- type: xenoArchTrigger
+  id: TriggerN2O
+  tip: xenoarch-trigger-tip-n2o
+  components:
+  - type: XATGas
+    targetGas: NitrousOxide
+  - type: XATReactive
+    reagents:
+    - NitrousOxide
+
+- type: xenoArchTrigger
+  id: TriggerFrezon
+  tip: xenoarch-trigger-tip-frezon
+  components:
+  - type: XATGas
+    targetGas: Frezon
+  - type: XATReactive
+    reagents:
+    - Frezon
+
+- type: xenoArchTrigger
+  id: TriggerRadiation
+  tip: xenoarch-trigger-tip-radiation
+  components:
+  - type: XATDamageThresholdReached
+    typesNeeded:
+      Radiation: 20
+  # TODO: legendary microwave trigger
+
+- type: xenoArchTrigger
+  id: TriggerPressureHigh
+  tip: xenoarch-trigger-tip-pressure-high
+  components:
+  - type: XATPressure
+    maxPressureThreshold: 385
+
+- type: xenoArchTrigger
+  id: TriggerPressureLow
+  tip: xenoarch-trigger-tip-pressure-low
+  components:
+  - type: XATPressure
+    minPressureThreshold: 50
+
+- type: xenoArchTrigger
+  id: TriggerExamine
+  tip: xenoarch-trigger-tip-examine
+  components:
+  - type: XATExamine
+
+- type: xenoArchTrigger
+  id: TriggerBruteDamage
+  tip: xenoarch-trigger-tip-brute-damage
+  components:
+  - type: XATDamageThresholdReached
+    groupsNeeded:
+      Brute: 20
+
+- type: xenoArchTrigger
+  id: TriggerInteraction
+  tip: xenoarch-trigger-tip-interaction
+  components:
+  - type: XATInteraction
+
+- type: xenoArchTrigger
+  id: TriggerWrenching
+  tip: xenoarch-trigger-tip-wrenching
+  components:
+  - type: XATToolUse
+    requiredTool: Anchoring
+  - type: XATExaminableText
+    examineText: xenoarch-trigger-examine-wrenching
+
+- type: xenoArchTrigger
+  id: TriggerPrying
+  tip: xenoarch-trigger-tip-prying
+  components:
+  - type: XATToolUse
+    requiredTool: Prying
+  - type: XATExaminableText
+    examineText: xenoarch-trigger-examine-prying
+
+- type: xenoArchTrigger
+  id: TriggerScrewing
+  tip: xenoarch-trigger-tip-screwing
+  components:
+  - type: XATToolUse
+    requiredTool: Screwing
+  - type: XATExaminableText
+    examineText: xenoarch-trigger-examine-screwing
+
+- type: xenoArchTrigger
+  id: TriggerPulsing
+  tip: xenoarch-trigger-tip-pulsing
+  components:
+  - type: XATToolUse
+    requiredTool: Pulsing
+  - type: XATExaminableText
+    examineText: xenoarch-trigger-examine-pulsing
+
+- type: xenoArchTrigger
+  id: TriggerTimer
+  tip: xenoarch-trigger-tip-timer
+  components:
+  - type: XATTimer
+    possibleDelayInSeconds:
+      min: 80
+      max: 120
+
+- type: xenoArchTrigger
+  id: TriggerBlood
+  tip: xenoarch-trigger-tip-blood
+  components:
+  - type: XATReactive
+    reagents:
+    - Blood
+    - CopperBlood
+    - InsectBlood
+    - Slime
+    - AmmoniaBlood
+    - ZombieBlood
+
+- type: xenoArchTrigger
+  id: TriggerThrow
+  tip: xenoarch-trigger-tip-throw
+  whitelist:
+    components:
+    - Item
+  components:
+  - type: XATItemLand
+
+- type: xenoArchTrigger
+  id: TriggerDeath
+  tip: xenoarch-trigger-tip-death
+  components:
+  - type: XATDeath
+
+- type: xenoArchTrigger
+  id: TriggerMagnet
+  tip: xenoarch-trigger-tip-magnet
+  components:
+  - type: XATMagnet
index 4823e31f55d059029f54fd1583c1ad73b60b541a..f175058e9ac225f92f3e925244529b113ba1616d 100644 (file)
   minValue: 1000
   maxValue: 9999
 
+- type: nameIdentifierGroup
+  id: XenoArtifactNode
+  minValue: 0
+  maxValue: 999
+
 # Used to suffix a number to any mob to identify player controlled mob griefing.
 - type: nameIdentifierGroup
   id: GenericNumber
diff --git a/Resources/Textures/Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi/artifact-activation.png b/Resources/Textures/Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi/artifact-activation.png
new file mode 100644 (file)
index 0000000..fd45f20
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi/artifact-activation.png differ
index 9550a7217d852fa3d7256f84aa6f3a3bf8811e60..797abd80eea61dd88efeeddd024daa9b6860367d 100644 (file)
           0.10599999
         ]
       ]
+    },
+    {
+     "name": "artifact-activation",
+     "delays": [
+        [
+            0.100,
+            0.100,
+            0.100,
+            0.100,
+            0.100,
+            0.100,
+            0.100,
+            0.100
+        ]
+     ]
     }
   ]
 }