From d439c5a9620844ae2e0e495822eea1cc0b43e7ce Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 28 May 2024 22:04:53 -0400 Subject: [PATCH 01/24] Revert "Merge branch 'VMSolidus-Psionic-Power-Refactor'" This reverts commit 15fc457e8f1ffa3a05966566dff93dbe6994310a, reversing changes made to c62f777aee0c5e28df70953f77de950009e42c13. --- .github/workflows/build-docfx.yml | 2 +- .../Atmos/Overlays/GasTileOverlay.cs | 70 ++-- Content.Client/Mapping/MappingSystem.cs | 2 +- .../Cartridges}/GlimmerMonitorUi.cs | 3 +- .../Cartridges}/GlimmerMonitorUiFragment.xaml | 2 +- .../GlimmerMonitorUiFragment.xaml.cs | 4 +- .../Chat}/PsionicChatUpdateSystem.cs | 4 +- .../Glimmer/GlimmerReactiveVisuals.cs | 0 .../Psionics/UI}/AcceptPsionicsEUI.cs | 0 .../Psionics/UI}/AcceptPsionicsWindow.cs | 0 .../UserInterface/GlimmerGraph.cs | 2 +- .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 2 +- .../Systems/Chat/ChatUIController.cs | 9 +- .../Tests/Body/LungTest.cs | 2 +- .../Interaction/InteractionTest.Helpers.cs | 7 +- .../Abilities/Mime/MimePowersSystem.cs | 2 +- .../Managers/AdminManager.Metrics.cs | 98 ------ .../Administration/Managers/AdminManager.cs | 9 +- .../Systems/AdminVerbSystem.Smites.cs | 12 +- .../Anomaly/AnomalySystem.Psionics.cs | 4 +- .../Atmos/Commands/SetMapAtmosCommand.cs | 94 ----- .../Atmos/Components/AirtightComponent.cs | 4 +- .../Components/GridAtmosphereComponent.cs | 8 +- .../Components/IgniteOnCollideComponent.cs | 4 +- .../Components/MapAtmosphereComponent.cs | 8 +- .../Atmos/EntitySystems/AirtightSystem.cs | 18 +- .../EntitySystems/AtmosDebugOverlaySystem.cs | 11 +- .../AtmosObstructionEnumerator.cs | 37 ++ .../EntitySystems/AtmosphereSystem.API.cs | 45 ++- .../AtmosphereSystem.ExcitedGroup.cs | 28 +- .../AtmosphereSystem.GridAtmosphere.cs | 247 +++++++++----- .../AtmosphereSystem.HighPressureDelta.cs | 10 +- .../EntitySystems/AtmosphereSystem.LINDA.cs | 18 +- .../EntitySystems/AtmosphereSystem.Map.cs | 108 +----- .../AtmosphereSystem.Monstermos.cs | 110 +++--- .../AtmosphereSystem.Processing.cs | 323 ++++++------------ .../AtmosphereSystem.Superconductivity.cs | 8 +- .../EntitySystems/AtmosphereSystem.Utils.cs | 62 ++-- .../Atmos/EntitySystems/AtmosphereSystem.cs | 7 - .../EntitySystems/AutomaticAtmosSystem.cs | 8 +- .../Atmos/EntitySystems/GasAnalyzerSystem.cs | 4 +- .../EntitySystems/GasTileOverlaySystem.cs | 6 +- Content.Server/Atmos/GasMixture.cs | 57 +--- .../Unary/EntitySystems/GasCanisterSystem.cs | 2 +- .../Unary/EntitySystems/GasCondenserSystem.cs | 2 +- .../TileAtmosCollectionSerializer.cs | 8 +- Content.Server/Atmos/TileAtmosphere.cs | 53 +-- Content.Server/Body/Systems/LungSystem.cs | 2 +- Content.Server/Chat/Systems/ChatSystem.cs | 2 +- .../Chemistry/EntitySystems/VaporSystem.cs | 4 +- .../Chemistry/ReagentEffects/ModifyLungGas.cs | 13 +- .../Conditions/ComponentInTile.cs | 11 +- .../Events/GlimmerMobSpawnRule.cs | 2 +- .../ExplosionSystem.Processing.cs | 4 +- .../ImmovableRod/ImmovableRodSystem.cs | 6 +- .../Psionics/Abilities/DispelPowerSystem.cs | 31 +- .../Abilities/MetapsionicPowerSystem.cs | 74 ++++ .../Psionics/Abilities/MindSwapPowerSystem.cs | 62 ++-- .../Abilities/MindSwappedComponent.cs | 5 +- .../Abilities/NoosphericZapPowerSystem.cs | 44 ++- .../PsionicInvisibilityPowerSystem.cs | 94 ++--- .../PsionicRegenerationPowerSystem.cs | 128 +++++++ .../Abilities/PyrokinesisPowerSystem.cs | 66 ++++ .../Abilities/TelegnosisPowerSystem.cs | 67 ++++ .../Psionics}/PsionicAbilitiesSystem.cs | 89 +++-- .../Audio/GlimmerSoundComponent.cs | 6 +- .../Chat/NyanoChatSystem.cs} | 18 +- .../Chat}/TSayCommand.cs | 3 +- .../Chat}/TelepathicRepeaterComponent.cs | 2 +- .../Chemistry/Effects/ChemRemovePsionic.cs | 2 +- .../BecomePsionicConditionComponent.cs | 11 + .../Systems/BecomePsionicConditionSystem.cs | 32 ++ .../Psionics/AcceptPsionicsEui.cs | 2 +- .../Psionics/AntiPsychicWeaponComponent.cs | 0 .../Psionics/Dreams/DreamSystem.cs | 3 + .../Psionics/Glimmer/GlimmerCommands.cs | 0 .../Psionics/Glimmer/GlimmerReactiveSystem.cs | 5 +- .../Glimmer/PassiveGlimmerReductionSystem.cs | 2 + .../Structures/GlimmerSourceComponent.cs | 0 .../Structures/GlimmerStructuresSystem.cs | 0 .../Invisibility/PsionicInvisibilitySystem.cs | 25 +- .../PsionicInvisibleContactsComponent.cs | 1 + .../PsionicInvisibleContactsSystem.cs | 2 + .../PsionicallyInvisibleComponent.cs | 0 .../Psionics/PotentialPsionicComponent.cs | 14 + .../PsionicAwaitingPlayerComponent.cs | 0 .../Psionics/PsionicBonusChanceComponent.cs | 0 .../Psionics/PsionicsCommands.cs | 6 +- .../Psionics/PsionicsSystem.cs | 23 +- .../Research/Oracle/OracleSystem.cs | 2 +- .../SophicScribe/SophicScribeSystem.cs | 2 +- .../Events/GlimmerWispSpawnRule.cs | 2 +- .../StationEvents/Events/MassMindSwapRule.cs | 5 +- .../StationEvents/Events/NoosphericFryRule.cs | 2 +- .../Events/NoosphericStormRule.cs | 4 +- .../StationEvents/Events/NoosphericZapRule.cs | 2 +- .../Events/PsionicCatGotYourTongueRule.cs | 2 +- Content.Server/Parallax/BiomeSystem.cs | 11 +- .../ParticleAcceleratorSystem.Emitter.cs | 2 +- .../Physics/Controllers/ChasingWalkSystem.cs | 2 +- .../Power/Generator/GasPowerReceiverSystem.cs | 2 +- .../Abilities/MetapsionicPowerSystem.cs | 188 ---------- .../PsionicRegenerationPowerSystem.cs | 179 ---------- .../Abilities/PyrokinesisPowerSystem.cs | 93 ----- .../RegenerativeStasisPowerSystem.cs | 76 ----- .../Abilities/TelegnosisPowerSystem.cs | 106 ------ .../Psionics/PotentialPsionicComponent.cs | 21 -- .../Salvage/SpawnSalvageMissionJob.cs | 6 +- .../Systems/ShuttleSystem.FasterThanLight.cs | 8 +- .../Shuttles/Systems/ShuttleSystem.cs | 8 +- .../EntitySystems/EventHorizonSystem.cs | 4 +- Content.Server/Spreader/SpreaderSystem.cs | 69 ++-- .../StationEvents/Events/MeteorSwarmRule.cs | 6 +- .../Zombies/ZombieSystem.Transform.cs | 4 +- Content.Shared/Atmos/Atmospherics.cs | 7 +- .../SharedGasTileOverlaySystem.cs | 3 - .../Buckle/SharedBuckleSystem.Buckle.cs | 2 +- Content.Shared/Maps/ContentTileDefinition.cs | 6 +- Content.Shared/Maps/TurfHelpers.cs | 27 +- .../Movement/Systems/SharedJetpackSystem.cs | 4 +- .../Abilities/AcceptPsionicsEuiMessage.cs | 0 .../Dispel/DamageOnDispelComponent.cs | 4 +- .../Abilities/Dispel/DispelPowerComponent.cs | 8 +- .../Abilities/Dispel/DispellableComponent.cs | 2 +- .../MassSleep/MassSleepPowerComponent.cs | 18 + .../MassSleep/MassSleepPowerSystem.cs | 59 ++++ .../Metapsionics/MetapsionicPowerComponent.cs | 21 ++ .../MindSwap/MindSwapPowerComponent.cs | 5 +- .../NoosphericZapPowerComponent.cs | 5 +- .../PsionicInvisibilityPowerComponent.cs | 8 +- .../PsionicInvisibilityUsedComponent.cs | 3 +- .../PsionicRegenerationPowerComponent.cs | 10 +- .../Pyrokinesis/PyrokinesisPowerComponent.cs | 5 +- .../Telegnosis/TelegnosisPowerComponent.cs | 10 +- .../TelegnosticProjectionComponent.cs | 6 + .../ClothingGrantPsionicPowerComponent.cs | 2 +- .../Psionics/Items/HeadCageComponent.cs | 2 +- .../Psionics/Items/HeadCagedComponent.cs | 2 +- .../Psionics/Items/PsionicItemsSystem.cs | 2 +- .../Psionics/Items/TinfoilHatComponent.cs | 2 +- .../Abilities}/Psionics/PsionicComponent.cs | 16 +- .../Psionics/PsionicInsulationComponent.cs | 2 +- .../Psionics/PsionicsDisabledComponent.cs | 2 +- .../Psionics/SharedPsionicAbilitiesSystem.cs | 4 +- .../Events/MassSleepPowerActionEvent.cs | 2 + .../Events/MetapsionicPowerActionEvent.cs | 3 +- .../Events/PyrokinesisPowerActionEvent.cs | 4 +- .../RegenerativeStasisPowerActionEvent.cs | 2 - Content.Shared/Nyanotrasen/Psionics/Events.cs | 28 ++ .../Psionics/Glimmer/GlimmerSystem.cs | 2 +- .../Glimmer/SharedGlimmerReactiveComponent.cs | 0 .../Glimmer/SharedGlimmerReactiveVisuals.cs | 0 .../Metapsionics/MetapsionicPowerComponent.cs | 38 --- .../RegenerativeStasisPowerComponent.cs | 20 -- .../TelegnosticProjectionComponent.cs | 8 - Content.Shared/Psionics/Events.cs | 64 ---- .../Psionics/SharedPsionicSystem.Insulated.cs | 4 - Content.Shared/Throwing/ThrowingSystem.cs | 6 +- Content.Shared/Throwing/ThrownItemSystem.cs | 2 +- .../Weapons/Melee/MeleeThrowOnHitSystem.cs | 4 +- .../Weapons/Misc/SharedTetherGunSystem.cs | 10 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 +- .../Audio/Nyanotrasen/heartbeat_fast.ogg | Bin 39983 -> 0 bytes Resources/Changelog/Changelog.yml | 14 - Resources/Credits/GitHub.txt | 2 +- Resources/Locale/en-US/atmos/commands.ftl | 8 - .../en-US/nyanotrasen/abilities/psionic.ftl | 18 +- .../nyanotrasen/psionics/psychic-feedback.ftl | 21 -- .../Catalog/Cargo/cargo_vending.yml | 2 +- .../Inventories/clothesmate.yml | 5 - .../DeltaV/Entities/Mobs/NPCs/familiars.yml | 7 +- .../DeltaV/Entities/Mobs/Player/harpy.yml | 1 + .../DeltaV/Entities/Mobs/Player/vulpkanin.yml | 1 + .../Prototypes/DeltaV/GameRules/events.yml | 5 +- .../Clothing/OuterClothing/wintercoats.yml | 55 --- .../Entities/Mobs/Player/arachnid.yml | 1 + .../Prototypes/Entities/Mobs/Player/diona.yml | 1 + .../Prototypes/Entities/Mobs/Player/dwarf.yml | 2 + .../Prototypes/Entities/Mobs/Player/human.yml | 2 + .../Prototypes/Entities/Mobs/Player/moth.yml | 2 + .../Entities/Mobs/Player/reptilian.yml | 2 + .../Prototypes/Entities/Mobs/Player/slime.yml | 2 + .../Entities/Mobs/Species/human.yml | 1 + .../Objects/Specific/Hydroponics/leaves.yml | 8 +- .../Specific/Robotics/borg_modules.yml | 2 +- .../Prototypes/Nyanotrasen/Actions/types.yml | 34 +- .../Nyanotrasen/Entities/Mobs/Player/Oni.yml | 1 + .../Entities/Mobs/Player/felinid.yml | 2 + .../Machines/metempsychoticMachine.yml | 3 - .../Entities/Structures/Research/oracle.yml | 3 - .../Structures/Research/sophicscribe.yml | 2 - .../Nyanotrasen/Objectives/traitor.yml | 23 ++ .../Roles/Jobs/Epistemics/forensicmantis.yml | 15 +- .../Nyanotrasen/Traits/psionics.yml | 6 - .../Prototypes/Nyanotrasen/psionicPowers.yml | 6 +- .../Prototypes/Objectives/objectiveGroups.yml | 1 + .../Objectives/stealTargetGroups.yml | 7 +- Resources/Prototypes/Objectives/thief.yml | 15 +- .../Roles/Jobs/Science/research_director.yml | 28 +- .../equipped-OUTERCLOTHING.png | Bin 8610 -> 0 bytes .../WinterCoats/cs_corpo_jacket.rsi/icon.png | Bin 5930 -> 0 bytes .../WinterCoats/cs_corpo_jacket.rsi/meta.json | 18 - .../equipped-OUTERCLOTHING.png | Bin 8514 -> 0 bytes .../WinterCoats/ee_corpo_jacket.rsi/icon.png | Bin 6063 -> 0 bytes .../WinterCoats/ee_corpo_jacket.rsi/meta.json | 18 - .../equipped-OUTERCLOTHING.png | Bin 8310 -> 0 bytes .../WinterCoats/hi_corpo_jacket.rsi/icon.png | Bin 5980 -> 0 bytes .../WinterCoats/hi_corpo_jacket.rsi/meta.json | 18 - .../equipped-OUTERCLOTHING.png | Bin 9341 -> 0 bytes .../WinterCoats/hm_corpo_jacket.rsi/icon.png | Bin 6169 -> 0 bytes .../WinterCoats/hm_corpo_jacket.rsi/meta.json | 18 - .../equipped-OUTERCLOTHING.png | Bin 9025 -> 0 bytes .../WinterCoats/id_corpo_jacket.rsi/icon.png | Bin 5955 -> 0 bytes .../WinterCoats/id_corpo_jacket.rsi/meta.json | 18 - Resources/keybinds.yml | 1 - global.json | 2 +- 216 files changed, 1588 insertions(+), 2450 deletions(-) rename Content.Client/{Psionics/GlimmerMonitor => Nyanotrasen/CartridgeLoader/Cartridges}/GlimmerMonitorUi.cs (92%) rename Content.Client/{Psionics/GlimmerMonitor => Nyanotrasen/CartridgeLoader/Cartridges}/GlimmerMonitorUiFragment.xaml (93%) rename Content.Client/{Psionics/GlimmerMonitor => Nyanotrasen/CartridgeLoader/Cartridges}/GlimmerMonitorUiFragment.xaml.cs (96%) rename Content.Client/{Psionics/Telepathy => Nyanotrasen/Chat}/PsionicChatUpdateSystem.cs (92%) rename Content.Client/{ => Nyanotrasen}/Psionics/Glimmer/GlimmerReactiveVisuals.cs (100%) rename Content.Client/{Psionics/UserInterface => Nyanotrasen/Psionics/UI}/AcceptPsionicsEUI.cs (100%) rename Content.Client/{Psionics/UserInterface => Nyanotrasen/Psionics/UI}/AcceptPsionicsWindow.cs (100%) rename Content.Client/{Psionics => Nyanotrasen}/UserInterface/GlimmerGraph.cs (97%) delete mode 100644 Content.Server/Administration/Managers/AdminManager.Metrics.cs delete mode 100644 Content.Server/Atmos/Commands/SetMapAtmosCommand.cs create mode 100644 Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs rename Content.Server/{ => Nyanotrasen/Abilities}/Psionics/Abilities/DispelPowerSystem.cs (83%) create mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs rename Content.Server/{ => Nyanotrasen/Abilities}/Psionics/Abilities/MindSwapPowerSystem.cs (78%) rename Content.Server/{ => Nyanotrasen/Abilities}/Psionics/Abilities/MindSwappedComponent.cs (79%) rename Content.Server/{ => Nyanotrasen/Abilities}/Psionics/Abilities/NoosphericZapPowerSystem.cs (54%) rename Content.Server/{ => Nyanotrasen/Abilities}/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs (57%) create mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs create mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs create mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs rename Content.Server/{Psionics/Abilities => Nyanotrasen/Abilities/Psionics}/PsionicAbilitiesSystem.cs (54%) rename Content.Server/{Psionics => Nyanotrasen}/Audio/GlimmerSoundComponent.cs (80%) rename Content.Server/{Psionics/Telepathy/TelepathyChatSystem.cs => Nyanotrasen/Chat/NyanoChatSystem.cs} (85%) rename Content.Server/{Psionics/Telepathy => Nyanotrasen/Chat}/TSayCommand.cs (95%) rename Content.Server/{Psionics/Telepathy => Nyanotrasen/Chat}/TelepathicRepeaterComponent.cs (82%) create mode 100644 Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs create mode 100644 Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs rename Content.Server/{ => Nyanotrasen}/Psionics/AcceptPsionicsEui.cs (95%) rename Content.Server/{ => Nyanotrasen}/Psionics/AntiPsychicWeaponComponent.cs (100%) rename Content.Server/{ => Nyanotrasen}/Psionics/Dreams/DreamSystem.cs (93%) rename Content.Server/{ => Nyanotrasen}/Psionics/Glimmer/GlimmerCommands.cs (100%) rename Content.Server/{ => Nyanotrasen}/Psionics/Glimmer/GlimmerReactiveSystem.cs (99%) rename Content.Server/{ => Nyanotrasen}/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs (94%) rename Content.Server/{ => Nyanotrasen}/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs (100%) rename Content.Server/{ => Nyanotrasen}/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs (100%) rename Content.Server/{ => Nyanotrasen}/Psionics/Invisibility/PsionicInvisibilitySystem.cs (88%) rename Content.Server/{ => Nyanotrasen}/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs (95%) rename Content.Server/{ => Nyanotrasen}/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs (95%) rename Content.Server/{ => Nyanotrasen}/Psionics/Invisibility/PsionicallyInvisibleComponent.cs (100%) create mode 100644 Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs rename Content.Server/{ => Nyanotrasen}/Psionics/PsionicAwaitingPlayerComponent.cs (100%) rename Content.Server/{ => Nyanotrasen}/Psionics/PsionicBonusChanceComponent.cs (100%) rename Content.Server/{ => Nyanotrasen}/Psionics/PsionicsCommands.cs (84%) rename Content.Server/{ => Nyanotrasen}/Psionics/PsionicsSystem.cs (91%) delete mode 100644 Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs delete mode 100644 Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs delete mode 100644 Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs delete mode 100644 Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs delete mode 100644 Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs delete mode 100644 Content.Server/Psionics/PotentialPsionicComponent.cs rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/AcceptPsionicsEuiMessage.cs (100%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs (77%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/Dispel/DispelPowerComponent.cs (79%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/Dispel/DispellableComponent.cs (69%) create mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs create mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs create mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs (76%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs (77%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs (71%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs (94%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs (76%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs (79%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs (73%) create mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Items/ClothingGrantPsionicPowerComponent.cs (84%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Items/HeadCageComponent.cs (96%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Items/HeadCagedComponent.cs (81%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Items/PsionicItemsSystem.cs (98%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/Items/TinfoilHatComponent.cs (90%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/PsionicComponent.cs (51%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/PsionicInsulationComponent.cs (82%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/PsionicsDisabledComponent.cs (84%) rename Content.Shared/{ => Nyanotrasen/Abilities}/Psionics/SharedPsionicAbilitiesSystem.cs (97%) create mode 100644 Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs delete mode 100644 Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs create mode 100644 Content.Shared/Nyanotrasen/Psionics/Events.cs rename Content.Shared/{ => Nyanotrasen}/Psionics/Glimmer/GlimmerSystem.cs (98%) rename Content.Shared/{ => Nyanotrasen}/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs (100%) rename Content.Shared/{ => Nyanotrasen}/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs (100%) delete mode 100644 Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs delete mode 100644 Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs delete mode 100644 Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs delete mode 100644 Content.Shared/Psionics/Events.cs delete mode 100644 Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs delete mode 100644 Resources/Audio/Nyanotrasen/heartbeat_fast.ogg delete mode 100644 Resources/Locale/en-US/atmos/commands.ftl delete mode 100644 Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl delete mode 100644 Resources/Prototypes/Nyanotrasen/Traits/psionics.yml delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/equipped-OUTERCLOTHING.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/icon.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/equipped-OUTERCLOTHING.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/icon.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/equipped-OUTERCLOTHING.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/icon.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/meta.json delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/equipped-OUTERCLOTHING.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/icon.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/meta.json delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/equipped-OUTERCLOTHING.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/icon.png delete mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json diff --git a/.github/workflows/build-docfx.yml b/.github/workflows/build-docfx.yml index d37e37026d7..ca1a6f0af12 100644 --- a/.github/workflows/build-docfx.yml +++ b/.github/workflows/build-docfx.yml @@ -21,7 +21,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 8.0.100 + dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index f4dc274a4e5..ef65d43fe85 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -8,6 +8,7 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Enums; +using Robust.Shared.Graphics; using Robust.Shared.Graphics.RSI; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -22,7 +23,7 @@ public sealed class GasTileOverlay : Overlay private readonly IEntityManager _entManager; private readonly IMapManager _mapManager; - public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld; + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; private readonly ShaderInstance _shader; // Gas overlays @@ -78,8 +79,7 @@ public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IR var rsi = resourceCache.GetResource(animated.RsiPath).RSI; var stateId = animated.RsiState; - if (!rsi.TryGetState(stateId, out var state)) - continue; + if (!rsi.TryGetState(stateId, out var state)) continue; _frames[i] = state.GetFrames(RsiDirection.South); _frameDelays[i] = state.GetDelays(); @@ -111,8 +111,7 @@ protected override void FrameUpdate(FrameEventArgs args) for (var i = 0; i < _gasCount; i++) { var delays = _frameDelays[i]; - if (delays.Length == 0) - continue; + if (delays.Length == 0) continue; var frameCount = _frameCounter[i]; _timer[i] += args.DeltaSeconds; @@ -128,8 +127,7 @@ protected override void FrameUpdate(FrameEventArgs args) for (var i = 0; i < FireStates; i++) { var delays = _fireFrameDelays[i]; - if (delays.Length == 0) - continue; + if (delays.Length == 0) continue; var frameCount = _fireFrameCounter[i]; _fireTimer[i] += args.DeltaSeconds; @@ -163,10 +161,26 @@ protected override void Draw(in OverlayDrawArgs args) var mapUid = _mapManager.GetMapEntityId(args.MapId); if (_entManager.TryGetComponent(mapUid, out var atmos)) - DrawMapOverlay(drawHandle, args, mapUid, atmos); + { + var bottomLeft = args.WorldAABB.BottomLeft.Floored(); + var topRight = args.WorldAABB.TopRight.Ceiled(); - if (args.Space != OverlaySpace.WorldSpaceEntities) - return; + for (var x = bottomLeft.X; x <= topRight.X; x++) + { + for (var y = bottomLeft.Y; y <= topRight.Y; y++) + { + var tilePosition = new Vector2(x, y); + + for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++) + { + var opacity = atmos.OverlayData.Opacity[i]; + + if (opacity > 0) + args.WorldHandle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity)); + } + } + } + } // TODO: WorldBounds callback. _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState, @@ -251,41 +265,5 @@ protected override void Draw(in OverlayDrawArgs args) drawHandle.UseShader(null); drawHandle.SetTransform(Matrix3.Identity); } - - private void DrawMapOverlay( - DrawingHandleWorld handle, - OverlayDrawArgs args, - EntityUid map, - MapAtmosphereComponent atmos) - { - var mapGrid = _entManager.HasComponent(map); - - // map-grid atmospheres get drawn above grids - if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities) - return; - - // Normal map atmospheres get drawn below grids - if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld) - return; - - var bottomLeft = args.WorldAABB.BottomLeft.Floored(); - var topRight = args.WorldAABB.TopRight.Ceiled(); - - for (var x = bottomLeft.X; x <= topRight.X; x++) - { - for (var y = bottomLeft.Y; y <= topRight.Y; y++) - { - var tilePosition = new Vector2(x, y); - - for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++) - { - var opacity = atmos.OverlayData.Opacity[i]; - - if (opacity > 0) - handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity)); - } - } - } - } } } diff --git a/Content.Client/Mapping/MappingSystem.cs b/Content.Client/Mapping/MappingSystem.cs index 8daf193dfeb..4456be36a65 100644 --- a/Content.Client/Mapping/MappingSystem.cs +++ b/Content.Client/Mapping/MappingSystem.cs @@ -83,7 +83,7 @@ private void OnFillActionSlot(FillActionSlotEvent ev) if (tileDef is not ContentTileDefinition contentTileDef) return; - var tileIcon = contentTileDef.MapAtmosphere + var tileIcon = contentTileDef.IsSpace ? _spaceIcon : new Texture(contentTileDef.Sprite!.Value); diff --git a/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUi.cs b/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs similarity index 92% rename from Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUi.cs rename to Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs index 0d8accb9f86..0b5fc7ad38c 100644 --- a/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUi.cs +++ b/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs @@ -1,11 +1,10 @@ using Robust.Client.GameObjects; using Robust.Client.UserInterface; -using Content.Client.Psionics.UI; using Content.Client.UserInterface.Fragments; using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.CartridgeLoader; -namespace Content.Client.Psionics.GlimmerMonitor; +namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges; public sealed partial class GlimmerMonitorUi : UIFragment { diff --git a/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml b/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml similarity index 93% rename from Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml rename to Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml index 3044680e27b..119a1831e6e 100644 --- a/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml +++ b/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml @@ -1,4 +1,4 @@ - diff --git a/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml.cs b/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs similarity index 96% rename from Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml.cs rename to Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs index 58bbee38a2f..43d9202aa45 100644 --- a/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml.cs +++ b/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs @@ -1,12 +1,12 @@ using System.Linq; using System.Numerics; -using Content.Client.Psionics.UI; +using Content.Client.Nyanotrasen.UserInterface; using Robust.Client.AutoGenerated; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -namespace Content.Client.Psionics.GlimmerMonitor; +namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges; [GenerateTypedNameReferences] public sealed partial class GlimmerMonitorUiFragment : BoxContainer diff --git a/Content.Client/Psionics/Telepathy/PsionicChatUpdateSystem.cs b/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs similarity index 92% rename from Content.Client/Psionics/Telepathy/PsionicChatUpdateSystem.cs rename to Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs index 7bb88764a1f..84602052fe7 100644 --- a/Content.Client/Psionics/Telepathy/PsionicChatUpdateSystem.cs +++ b/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs @@ -1,8 +1,8 @@ -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Client.Chat.Managers; using Robust.Client.Player; -namespace Content.Client.Psionics.Chat +namespace Content.Client.Nyanotrasen.Chat { public sealed class PsionicChatUpdateSystem : EntitySystem { diff --git a/Content.Client/Psionics/Glimmer/GlimmerReactiveVisuals.cs b/Content.Client/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveVisuals.cs similarity index 100% rename from Content.Client/Psionics/Glimmer/GlimmerReactiveVisuals.cs rename to Content.Client/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveVisuals.cs diff --git a/Content.Client/Psionics/UserInterface/AcceptPsionicsEUI.cs b/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs similarity index 100% rename from Content.Client/Psionics/UserInterface/AcceptPsionicsEUI.cs rename to Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs diff --git a/Content.Client/Psionics/UserInterface/AcceptPsionicsWindow.cs b/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs similarity index 100% rename from Content.Client/Psionics/UserInterface/AcceptPsionicsWindow.cs rename to Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs diff --git a/Content.Client/Psionics/UserInterface/GlimmerGraph.cs b/Content.Client/Nyanotrasen/UserInterface/GlimmerGraph.cs similarity index 97% rename from Content.Client/Psionics/UserInterface/GlimmerGraph.cs rename to Content.Client/Nyanotrasen/UserInterface/GlimmerGraph.cs index 111c810acb1..c4a9109dcd8 100644 --- a/Content.Client/Psionics/UserInterface/GlimmerGraph.cs +++ b/Content.Client/Nyanotrasen/UserInterface/GlimmerGraph.cs @@ -4,7 +4,7 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; -namespace Content.Client.Psionics.UI; +namespace Content.Client.Nyanotrasen.UserInterface; public sealed class GlimmerGraph : Control { diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index f0537079b97..ce5cf421aef 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -403,7 +403,7 @@ private void InputManagerOnFirstChanceOnKeyEvent(KeyEventArgs keyEvent, KeyEvent Mod1 = mods[0], Mod2 = mods[1], Mod3 = mods[2], - Priority = _currentlyRebinding.Binding?.Priority ?? 0, + Priority = 0, Type = bindType, CanFocus = key == Keyboard.Key.MouseLeft || key == Keyboard.Key.MouseRight diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 79c1909ebaf..ff4972d9d08 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -20,6 +20,7 @@ using Content.Shared.Examine; using Content.Shared.Input; using Content.Shared.Radio; +using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; @@ -36,7 +37,7 @@ using Robust.Shared.Replays; using Robust.Shared.Timing; using Robust.Shared.Utility; -using Content.Client.Psionics.Chat; +using Content.Client.Nyanotrasen.Chat; //Nyano - Summary: chat namespace. namespace Content.Client.UserInterface.Systems.Chat; @@ -60,7 +61,7 @@ public sealed class ChatUIController : UIController [UISystemDependency] private readonly GhostSystem? _ghost = default; [UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default; [UISystemDependency] private readonly ChatSystem? _chatSys = default; - [UISystemDependency] private readonly PsionicChatUpdateSystem? _psionic = default!; //EE - Summary: makes the psionic chat available. + [UISystemDependency] private readonly PsionicChatUpdateSystem? _psionic = default!; //Nyano - Summary: makes the psionic chat available. [ValidatePrototypeId] private const string ChatNamePalette = "ChatNames"; @@ -81,7 +82,7 @@ public sealed class ChatUIController : UIController {SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin}, {SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio}, {SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead}, - {SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} //EE - Summary: adds the telepathic prefix =. + {SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} //Nyano - Summary: adds the telepathic prefix =. }; public static readonly Dictionary ChannelPrefixes = new() @@ -95,7 +96,7 @@ public sealed class ChatUIController : UIController {ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix}, {ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix}, {ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix}, - {ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix } //EE - Summary: associates telepathic with =. + {ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix } //Nyano - Summary: associates telepathic with =. }; /// diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index f2e19849b00..d0325480acd 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -128,7 +128,7 @@ await server.WaitAssertion(() => metaSys.Update(1.0f); metaSys.Update(1.0f); respSys.Update(2.0f); - Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002)); + Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0001)); }); } diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 88448e7b800..84e1afaf45c 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -1006,10 +1006,15 @@ protected async Task AddAtmosphere(EntityUid? uid = null) await Server.WaitPost(() => { var atmosSystem = SEntMan.System(); + var atmos = SEntMan.EnsureComponent(target); var moles = new float[Atmospherics.AdjustedNumberOfGases]; moles[(int) Gas.Oxygen] = 21.824779f; moles[(int) Gas.Nitrogen] = 82.10312f; - atmosSystem.SetMapAtmosphere(target, false, new GasMixture(moles, Atmospherics.T20C)); + atmosSystem.SetMapAtmosphere(target, false, new GasMixture(2500) + { + Temperature = 293.15f, + Moles = moles, + }, atmos); }); } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index b3bd3392434..c1d2643d6fa 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -10,7 +10,7 @@ using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Timing; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; //Nyano - Summary: Makes Mime psionic. using Content.Shared.Speech.Muting; namespace Content.Server.Abilities.Mime diff --git a/Content.Server/Administration/Managers/AdminManager.Metrics.cs b/Content.Server/Administration/Managers/AdminManager.Metrics.cs deleted file mode 100644 index 2fea931f1b9..00000000000 --- a/Content.Server/Administration/Managers/AdminManager.Metrics.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Diagnostics.Metrics; -using System.Runtime.InteropServices; -using Content.Server.Afk; -using Robust.Server.DataMetrics; - -namespace Content.Server.Administration.Managers; - -// Handles metrics reporting for active admin count and such. - -public sealed partial class AdminManager -{ - private Dictionary? _adminOnlineCounts; - - private const int SentinelRankId = -1; - - [Dependency] private readonly IMetricsManager _metrics = default!; - [Dependency] private readonly IAfkManager _afkManager = default!; - [Dependency] private readonly IMeterFactory _meterFactory = default!; - - private void InitializeMetrics() - { - _metrics.UpdateMetrics += MetricsOnUpdateMetrics; - - var meter = _meterFactory.Create("SS14.AdminManager"); - - meter.CreateObservableGauge( - "admins_online_count", - MeasureAdminCount, - null, - "The count of online admins"); - } - - private void MetricsOnUpdateMetrics() - { - _sawmill.Verbose("Updating metrics"); - - var dict = new Dictionary(); - - foreach (var (session, reg) in _admins) - { - var rankId = reg.RankId ?? SentinelRankId; - - ref var counts = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, rankId, out _); - - if (reg.Data.Active) - { - if (_afkManager.IsAfk(session)) - counts.afk += 1; - else - counts.active += 1; - } - else - { - counts.deadminned += 1; - } - } - - // Neither prometheus-net nor dotnet-counters seem to handle stuff well if we STOP returning measurements. - // i.e. if the last admin with a rank disconnects. - // So if we have EVER reported a rank, always keep reporting it. - if (_adminOnlineCounts != null) - { - foreach (var rank in _adminOnlineCounts.Keys) - { - CollectionsMarshal.GetValueRefOrAddDefault(dict, rank, out _); - } - } - - // Make sure "no rank" is always available. Avoid "no data". - CollectionsMarshal.GetValueRefOrAddDefault(dict, SentinelRankId, out _); - - _adminOnlineCounts = dict; - } - - private IEnumerable> MeasureAdminCount() - { - if (_adminOnlineCounts == null) - yield break; - - foreach (var (rank, (active, afk, deadminned)) in _adminOnlineCounts) - { - yield return new Measurement( - active, - new KeyValuePair("state", "active"), - new KeyValuePair("rank", rank == SentinelRankId ? "none" : rank.ToString())); - - yield return new Measurement( - afk, - new KeyValuePair("state", "afk"), - new KeyValuePair("rank", rank == SentinelRankId ? "none" : rank.ToString())); - - yield return new Measurement( - deadminned, - new KeyValuePair("state", "deadminned"), - new KeyValuePair("rank", rank == SentinelRankId ? "none" : rank.ToString())); - } - } -} diff --git a/Content.Server/Administration/Managers/AdminManager.cs b/Content.Server/Administration/Managers/AdminManager.cs index b1cca46e63f..4eaa08fe9dd 100644 --- a/Content.Server/Administration/Managers/AdminManager.cs +++ b/Content.Server/Administration/Managers/AdminManager.cs @@ -23,7 +23,7 @@ namespace Content.Server.Administration.Managers { - public sealed partial class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation + public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation { [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IServerDbManager _dbManager = default!; @@ -34,7 +34,6 @@ public sealed partial class AdminManager : IAdminManager, IPostInjectInit, IConG [Dependency] private readonly IServerConsoleHost _consoleHost = default!; [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly ToolshedManager _toolshed = default!; - [Dependency] private readonly ILogManager _logManager = default!; private readonly Dictionary _admins = new(); private readonly HashSet _promotedPlayers = new(); @@ -50,8 +49,6 @@ public sealed partial class AdminManager : IAdminManager, IPostInjectInit, IConG private readonly AdminCommandPermissions _commandPermissions = new(); private readonly AdminCommandPermissions _toolshedCommandPermissions = new(); - private ISawmill _sawmill = default!; - public bool IsAdmin(ICommonSession session, bool includeDeAdmin = false) { return GetAdminData(session, includeDeAdmin) != null; @@ -184,8 +181,6 @@ public void ReloadAdminsWithRank(int rankId) public void Initialize() { - _sawmill = _logManager.GetSawmill("admin"); - _netMgr.RegisterNetMessage(); // Cache permissions for loaded console commands with the requisite attributes. @@ -239,8 +234,6 @@ public void Initialize() } _toolshed.ActivePermissionController = this; - - InitializeMetrics(); } public void PromoteHost(ICommonSession player) diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 8ee52ad03e7..8a819f59420 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -409,7 +409,7 @@ private void AddSmiteVerbs(GetVerbsEvent args) var fixtures = Comp(args.Target); xform.Anchored = false; // Just in case. _physics.SetBodyType(args.Target, BodyType.Dynamic, manager: fixtures, body: physics); - _physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir); + _physics.SetBodyStatus(physics, BodyStatus.InAir); _physics.WakeBody(args.Target, manager: fixtures, body: physics); foreach (var fixture in fixtures.Fixtures.Values) @@ -424,8 +424,8 @@ private void AddSmiteVerbs(GetVerbsEvent args) _physics.SetLinearVelocity(args.Target, _random.NextVector2(1.5f, 1.5f), manager: fixtures, body: physics); _physics.SetAngularVelocity(args.Target, MathF.PI * 12, manager: fixtures, body: physics); - _physics.SetLinearDamping(args.Target, physics, 0f); - _physics.SetAngularDamping(args.Target, physics, 0f); + _physics.SetLinearDamping(physics, 0f); + _physics.SetAngularDamping(physics, 0f); }, Impact = LogImpact.Extreme, Message = Loc.GetString("admin-smite-pinball-description") @@ -444,7 +444,7 @@ private void AddSmiteVerbs(GetVerbsEvent args) xform.Anchored = false; // Just in case. _physics.SetBodyType(args.Target, BodyType.Dynamic, body: physics); - _physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir); + _physics.SetBodyStatus(physics, BodyStatus.InAir); _physics.WakeBody(args.Target, manager: fixtures, body: physics); foreach (var fixture in fixtures.Fixtures.Values) @@ -454,8 +454,8 @@ private void AddSmiteVerbs(GetVerbsEvent args) _physics.SetLinearVelocity(args.Target, _random.NextVector2(8.0f, 8.0f), manager: fixtures, body: physics); _physics.SetAngularVelocity(args.Target, MathF.PI * 12, manager: fixtures, body: physics); - _physics.SetLinearDamping(args.Target, physics, 0f); - _physics.SetAngularDamping(args.Target, physics, 0f); + _physics.SetLinearDamping(physics, 0f); + _physics.SetAngularDamping(physics, 0f); }, Impact = LogImpact.Extreme, Message = Loc.GetString("admin-smite-yeet-description") diff --git a/Content.Server/Anomaly/AnomalySystem.Psionics.cs b/Content.Server/Anomaly/AnomalySystem.Psionics.cs index 84f200f47ba..95fda1d5035 100644 --- a/Content.Server/Anomaly/AnomalySystem.Psionics.cs +++ b/Content.Server/Anomaly/AnomalySystem.Psionics.cs @@ -1,4 +1,4 @@ -using Content.Server.Psionics.Abilities; +using Content.Server.Abilities.Psionics; //Nyano - Summary: the psniocs bin where dispel is located. using Content.Shared.Anomaly; using Content.Shared.Anomaly.Components; using Robust.Shared.Random; @@ -14,6 +14,8 @@ private void InitializePsionics() { SubscribeLocalEvent(OnDispelled); } + + //Nyano - Summary: gives dispellable behavior to Anomalies. private void OnDispelled(EntityUid uid, AnomalyComponent component, DispelledEvent args) { _dispel.DealDispelDamage(uid); diff --git a/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs b/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs deleted file mode 100644 index 6f04cfb2da6..00000000000 --- a/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Content.Server.Administration; -using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Administration; -using Content.Shared.Atmos; -using Robust.Shared.Console; -using Robust.Shared.Map; - -namespace Content.Server.Atmos.Commands; - -[AdminCommand(AdminFlags.Admin)] -public sealed class AddMapAtmosCommand : LocalizedCommands -{ - [Dependency] private readonly IEntityManager _entities = default!; - [Dependency] private readonly IMapManager _map = default!; - - private const string _cmd = "cmd-set-map-atmos"; - public override string Command => "setmapatmos"; - public override string Description => Loc.GetString($"{_cmd}-desc"); - public override string Help => Loc.GetString($"{_cmd}-help"); - - public override void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (args.Length < 2) - { - shell.WriteLine(Help); - return; - } - - int.TryParse(args[0], out var id); - var map = _map.GetMapEntityId(new MapId(id)); - if (!map.IsValid()) - { - shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", args[0]))); - return; - } - - if (!bool.TryParse(args[1], out var space)) - { - shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[1]))); - return; - } - - if (space || args.Length < 4) - { - _entities.RemoveComponent(map); - shell.WriteLine(Loc.GetString($"{_cmd}-removed", ("map", id))); - return; - } - - if (!float.TryParse(args[2], out var temp)) - { - shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2]))); - return; - } - - var mix = new GasMixture(Atmospherics.CellVolume) {Temperature = Math.Min(temp, Atmospherics.TCMB)}; - for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) - { - if (args.Length == 3 + i) - break; - - if (!float.TryParse(args[3+i], out var moles)) - { - shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3+i]))); - return; - } - - mix.AdjustMoles(i, moles); - } - - var atmos = _entities.EntitySysManager.GetEntitySystem(); - atmos.SetMapAtmosphere(map, space, mix); - shell.WriteLine(Loc.GetString($"{_cmd}-updated", ("map", id))); - } - - public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) - { - if (args.Length == 1) - return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entities), Loc.GetString($"{_cmd}-hint-map")); - - if (args.Length == 2) - return CompletionResult.FromHintOptions(new[]{ "false", "true"}, Loc.GetString($"{_cmd}-hint-space")); - - if (!bool.TryParse(args[1], out var space) || space) - return CompletionResult.Empty; - - if (args.Length == 3) - return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-temp")); - - var gas = (Gas) args.Length - 4; - return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-gas" , ("gas", gas.ToString()))); - } -} diff --git a/Content.Server/Atmos/Components/AirtightComponent.cs b/Content.Server/Atmos/Components/AirtightComponent.cs index ca107eafbe8..897981724c9 100644 --- a/Content.Server/Atmos/Components/AirtightComponent.cs +++ b/Content.Server/Atmos/Components/AirtightComponent.cs @@ -1,10 +1,9 @@ -using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.Atmos.Components { - [RegisterComponent, Access(typeof(AirtightSystem))] + [RegisterComponent] public sealed partial class AirtightComponent : Component { public (EntityUid Grid, Vector2i Tile) LastPosition { get; set; } @@ -30,7 +29,6 @@ public sealed partial class AirtightComponent : Component [DataField("noAirWhenFullyAirBlocked")] public bool NoAirWhenFullyAirBlocked { get; set; } = true; - [Access(Other = AccessPermissions.ReadWriteExecute)] public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection; } } diff --git a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs index e682fd09644..7fcd63bc5d8 100644 --- a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs @@ -28,9 +28,6 @@ public sealed partial class GridAtmosphereComponent : Component [IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))] public Dictionary Tiles = new(1000); - [ViewVariables] - public HashSet MapTiles = new(1000); - [ViewVariables] public readonly HashSet ActiveTiles = new(1000); @@ -83,10 +80,7 @@ public sealed partial class GridAtmosphereComponent : Component public readonly HashSet InvalidatedCoords = new(1000); [ViewVariables] - public readonly Queue CurrentRunInvalidatedTiles = new(); - - [ViewVariables] - public readonly List PossiblyDisconnectedTiles = new(100); + public readonly Queue CurrentRunInvalidatedCoordinates = new(); [ViewVariables] public int InvalidatedCoordsCount => InvalidatedCoords.Count; diff --git a/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs b/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs index 710c37b62fe..a58d3a3c122 100644 --- a/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs +++ b/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs @@ -1,6 +1,8 @@ +using Content.Server.Atmos.EntitySystems; + namespace Content.Server.Atmos.Components; -[RegisterComponent] +[RegisterComponent, Access(typeof(FlammableSystem))] public sealed partial class IgniteOnCollideComponent : Component { /// diff --git a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs index 6bdef901d44..bbf5ea6403e 100644 --- a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs @@ -12,14 +12,12 @@ public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponen /// /// The default GasMixture a map will have. Space mixture by default. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public GasMixture Mixture = GasMixture.SpaceGas; + [DataField("mixture"), ViewVariables(VVAccess.ReadWrite)] + public GasMixture? Mixture = GasMixture.SpaceGas; /// /// Whether empty tiles will be considered space or not. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField("space"), ViewVariables(VVAccess.ReadWrite)] public bool Space = true; - - public SharedGasTileOverlaySystem.GasOverlayData Overlay; } diff --git a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs index 548d6a36926..97dccbaabb7 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Explosion.EntitySystems; using Content.Shared.Atmos; using JetBrains.Annotations; +using Robust.Shared.Map; using Robust.Shared.Map.Components; namespace Content.Server.Atmos.EntitySystems @@ -9,7 +10,7 @@ namespace Content.Server.Atmos.EntitySystems [UsedImplicitly] public sealed class AirtightSystem : EntitySystem { - [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly ExplosionSystem _explosionSystem = default!; @@ -120,16 +121,19 @@ public void UpdatePosition(Entity ent, TransformComponent? xf if (!xform.Anchored || !TryComp(xform.GridUid, out MapGridComponent? grid)) return; - var indices = _transform.GetGridTilePositionOrDefault((ent, xform), grid); - airtight.LastPosition = (xform.GridUid.Value, indices); - InvalidatePosition((xform.GridUid.Value, grid), indices); + airtight.LastPosition = (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates)); + InvalidatePosition(airtight.LastPosition.Item1, airtight.LastPosition.Item2, airtight.FixVacuum && !airtight.AirBlocked); } - public void InvalidatePosition(Entity grid, Vector2i pos) + public void InvalidatePosition(EntityUid gridId, Vector2i pos, bool fixVacuum = false) { + if (!TryComp(gridId, out MapGridComponent? grid)) + return; + var query = EntityManager.GetEntityQuery(); - _explosionSystem.UpdateAirtightMap(grid, pos, grid, query); - _atmosphereSystem.InvalidateTile(grid.Owner, pos); + _explosionSystem.UpdateAirtightMap(gridId, pos, grid, query); + // TODO make atmos system use query + _atmosphereSystem.InvalidateTile(gridId, pos); } private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle) diff --git a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs index c0284f26c90..4af32fce58f 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs @@ -97,19 +97,22 @@ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) } } - private AtmosDebugOverlayData? ConvertTileToData(TileAtmosphere tile) + private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile) { + if (tile == null) + return default; + return new AtmosDebugOverlayData( tile.GridIndices, tile.Air?.Temperature ?? default, tile.Air?.Moles, tile.PressureDirection, tile.LastPressureDirection, - tile.AirtightData.BlockedDirections, + tile.BlockedAirflow, tile.ExcitedGroup?.GetHashCode(), tile.Space, - tile.MapAtmosphere, - tile.NoGridTile); + false, + false); } public override void Update(float frameTime) diff --git a/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs b/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs new file mode 100644 index 00000000000..aed009e9a12 --- /dev/null +++ b/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.Atmos.Components; +using Robust.Shared.Map; +using Robust.Shared.Map.Enumerators; + +namespace Content.Server.Atmos.EntitySystems; + +public struct AtmosObstructionEnumerator +{ + private AnchoredEntitiesEnumerator _enumerator; + private EntityQuery _query; + + public AtmosObstructionEnumerator(AnchoredEntitiesEnumerator enumerator, EntityQuery query) + { + _enumerator = enumerator; + _query = query; + } + + public bool MoveNext([NotNullWhen(true)] out AirtightComponent? airtight) + { + if (!_enumerator.MoveNext(out var uid)) + { + airtight = null; + return false; + } + + // No rider, it makes it uglier. + // ReSharper disable once ConvertIfStatementToReturnStatement + if (!_query.TryGetComponent(uid.Value, out airtight)) + { + // ReSharper disable once TailRecursiveCall + return MoveNext(out airtight); + } + + return true; + } +} diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs index cece99cacf6..310e602336a 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -85,10 +85,10 @@ public IEnumerable GetAllMixtures(EntityUid gridUid, bool excite = f return ev.Mixtures!; } - public void InvalidateTile(Entity entity, Vector2i tile) + public void InvalidateTile(EntityUid gridUid, Vector2i tile) { - if (_atmosQuery.Resolve(entity.Owner, ref entity.Comp, false)) - entity.Comp.InvalidatedCoords.Add(tile); + var ev = new InvalidateTileMethodEvent(gridUid, tile); + RaiseLocalEvent(gridUid, ref ev); } public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List tiles, bool excite = false) @@ -176,11 +176,11 @@ public ReactionResult ReactTile(EntityUid gridId, Vector2i tile) public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null) { - if (!Resolve(gridUid, ref mapGridComp)) - return false; + var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp); + RaiseLocalEvent(gridUid, ref ev); - var data = GetAirtightData(gridUid, mapGridComp, tile); - return data.BlockedDirections.IsFlagSet(directions); + // If nothing handled the event, it'll default to true. + return ev.Result; } public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null) @@ -231,6 +231,12 @@ public IEnumerable GetAdjacentTileMixtures(EntityUid gridUid, Vector return ev.Result ?? Enumerable.Empty(); } + public void UpdateAdjacent(EntityUid gridUid, Vector2i tile, MapGridComponent? mapGridComp = null) + { + var ev = new UpdateAdjacentMethodEvent(gridUid, tile, mapGridComp); + RaiseLocalEvent(gridUid, ref ev); + } + public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume, EntityUid? sparkSourceUid = null, bool soh = false) { @@ -253,6 +259,12 @@ public bool IsHotspotActive(EntityUid gridUid, Vector2i tile) return ev.Result; } + public void FixTileVacuum(EntityUid gridUid, Vector2i tile) + { + var ev = new FixTileVacuumMethodEvent(gridUid, tile); + RaiseLocalEvent(gridUid, ref ev); + } + public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet) { var ev = new AddPipeNetMethodEvent(gridUid, pipeNet); @@ -295,6 +307,9 @@ [ByRefEvent] private record struct IsSimulatedGridMethodEvent [ByRefEvent] private record struct GetAllMixturesMethodEvent (EntityUid Grid, bool Excite = false, IEnumerable? Mixtures = null, bool Handled = false); + [ByRefEvent] private record struct InvalidateTileMethodEvent + (EntityUid Grid, Vector2i Tile, bool Handled = false); + [ByRefEvent] private record struct GetTileMixturesMethodEvent (EntityUid? GridUid, EntityUid? MapUid, List Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false); @@ -304,6 +319,16 @@ [ByRefEvent] private record struct GetTileMixtureMethodEvent [ByRefEvent] private record struct ReactTileMethodEvent (EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false); + [ByRefEvent] private record struct IsTileAirBlockedMethodEvent + (EntityUid Grid, Vector2i Tile, AtmosDirection Direction = AtmosDirection.All, MapGridComponent? MapGridComponent = null, bool Result = false, bool Handled = false) + { + /// + /// True if one of the enabled blockers has . Note + /// that this does not actually check if all directions are blocked. + /// + public bool NoAir = false; + } + [ByRefEvent] private record struct IsTileSpaceMethodEvent (EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false); @@ -314,6 +339,9 @@ [ByRefEvent] private record struct GetAdjacentTileMixturesMethodEvent (EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite, IEnumerable? Result = null, bool Handled = false); + [ByRefEvent] private record struct UpdateAdjacentMethodEvent + (EntityUid Grid, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Handled = false); + [ByRefEvent] private record struct HotspotExposeMethodEvent (EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false); @@ -323,6 +351,9 @@ [ByRefEvent] private record struct HotspotExtinguishMethodEvent [ByRefEvent] private record struct IsHotspotActiveMethodEvent (EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false); + [ByRefEvent] private record struct FixTileVacuumMethodEvent + (EntityUid Grid, Vector2i Tile, bool Handled = false); + [ByRefEvent] private record struct AddPipeNetMethodEvent (EntityUid Grid, PipeNet PipeNet, bool Handled = false); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs index de4c9199cf7..1d809dcd032 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs @@ -72,8 +72,7 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E var tileSize = excitedGroup.Tiles.Count; - if (excitedGroup.Disposed) - return; + if (excitedGroup.Disposed) return; if (tileSize == 0) { @@ -99,9 +98,7 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E foreach (var tile in excitedGroup.Tiles) { - if (tile?.Air == null) - continue; - + if (tile?.Air == null) continue; tile.Air.CopyFromMutable(combined); InvalidateVisuals(tile.GridIndex, tile.GridIndices); } @@ -109,23 +106,21 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E excitedGroup.BreakdownCooldown = 0; } - /// - /// This de-activates and removes all tiles in an excited group. - /// - private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup) + private void ExcitedGroupDismantle(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup, bool unexcite = true) { foreach (var tile in excitedGroup.Tiles) { tile.ExcitedGroup = null; + + if (!unexcite) + continue; + RemoveActiveTile(gridAtmosphere, tile); } excitedGroup.Tiles.Clear(); } - /// - /// This removes an excited group without de-activating its tiles. - /// private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup) { if (excitedGroup.Disposed) @@ -134,14 +129,9 @@ private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, Excited DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!"); excitedGroup.Disposed = true; - gridAtmosphere.ExcitedGroups.Remove(excitedGroup); - - foreach (var tile in excitedGroup.Tiles) - { - tile.ExcitedGroup = null; - } - excitedGroup.Tiles.Clear(); + gridAtmosphere.ExcitedGroups.Remove(excitedGroup); + ExcitedGroupDismantle(gridAtmosphere, excitedGroup, false); } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index d43cc81b0f8..1f1a208b24b 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -14,7 +14,6 @@ public sealed partial class AtmosphereSystem private void InitializeGridAtmosphere() { SubscribeLocalEvent(OnGridAtmosphereInit); - SubscribeLocalEvent(OnGridAtmosphereStartup); SubscribeLocalEvent(OnAtmosphereRemove); SubscribeLocalEvent(OnGridSplit); @@ -23,15 +22,19 @@ private void InitializeGridAtmosphere() SubscribeLocalEvent(GridHasAtmosphere); SubscribeLocalEvent(GridIsSimulated); SubscribeLocalEvent(GridGetAllMixtures); + SubscribeLocalEvent(GridInvalidateTile); SubscribeLocalEvent(GridGetTileMixture); SubscribeLocalEvent(GridGetTileMixtures); SubscribeLocalEvent(GridReactTile); + SubscribeLocalEvent(GridIsTileAirBlocked); SubscribeLocalEvent(GridIsTileSpace); SubscribeLocalEvent(GridGetAdjacentTiles); SubscribeLocalEvent(GridGetAdjacentTileMixtures); + SubscribeLocalEvent(GridUpdateAdjacent); SubscribeLocalEvent(GridHotspotExpose); SubscribeLocalEvent(GridHotspotExtinguish); SubscribeLocalEvent(GridIsHotspotActive); + SubscribeLocalEvent(GridFixTileVacuum); SubscribeLocalEvent(GridAddPipeNet); SubscribeLocalEvent(GridRemovePipeNet); SubscribeLocalEvent(GridAddAtmosDevice); @@ -53,23 +56,22 @@ private void OnAtmosphereRemove(EntityUid uid, GridAtmosphereComponent component } } - private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args) + private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args) { base.Initialize(); + if (!TryComp(uid, out MapGridComponent? mapGrid)) + return; + EnsureComp(uid); - foreach (var tile in component.Tiles.Values) + + foreach (var (indices, tile) in gridAtmosphere.Tiles) { + gridAtmosphere.InvalidatedCoords.Add(indices); tile.GridIndex = uid; } - } - - private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args) - { - if (!TryComp(uid, out MapGridComponent? mapGrid)) - return; - InvalidateAllTiles((uid, mapGrid, component)); + GridRepopulateTiles((uid, mapGrid, gridAtmosphere)); } private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args) @@ -102,7 +104,8 @@ private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmo continue; // Copy a bunch of data over... Not great, maybe put this in TileAtmosphere? - newTileAtmosphere.Air = tileAtmosphere.Air?.Clone(); + newTileAtmosphere.Air = tileAtmosphere.Air?.Clone() ?? null; + newTileAtmosphere.MolesArchived = newTileAtmosphere.Air == null ? null : new float[Atmospherics.AdjustedNumberOfGases]; newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot; newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity; newTileAtmosphere.Temperature = tileAtmosphere.Temperature; @@ -167,6 +170,15 @@ IEnumerable EnumerateMixtures(EntityUid gridUid, GridAtmosphereCompo args.Handled = true; } + private void GridInvalidateTile(EntityUid uid, GridAtmosphereComponent component, ref InvalidateTileMethodEvent args) + { + if (args.Handled) + return; + + component.InvalidatedCoords.Add(args.Tile); + args.Handled = true; + } + private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component, ref GetTileMixtureMethodEvent args) { @@ -221,6 +233,43 @@ private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref args.Handled = true; } + private void GridIsTileAirBlocked(EntityUid uid, GridAtmosphereComponent component, + ref IsTileAirBlockedMethodEvent args) + { + if (args.Handled) + return; + + var mapGridComp = args.MapGridComponent; + + if (!Resolve(uid, ref mapGridComp)) + return; + + var directions = AtmosDirection.Invalid; + + var enumerator = GetObstructingComponentsEnumerator(mapGridComp, args.Tile); + + while (enumerator.MoveNext(out var obstructingComponent)) + { + if (!obstructingComponent.AirBlocked) + continue; + + // We set the directions that are air-blocked so far, + // as you could have a full obstruction with only 4 directional air blockers. + directions |= obstructingComponent.AirBlockedDirection; + args.NoAir |= obstructingComponent.NoAirWhenFullyAirBlocked; + + if (directions.IsFlagSet(args.Direction)) + { + args.Result = true; + args.Handled = true; + return; + } + } + + args.Result = false; + args.Handled = true; + } + private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args) { if (args.Handled) @@ -282,58 +331,71 @@ IEnumerable EnumerateAdjacent(GridAtmosphereComponent grid, TileAtmo args.Handled = true; } - /// - /// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies. - /// - private void UpdateAdjacentTiles( - Entity ent, - TileAtmosphere tile, - bool activate = false) + private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component, + ref UpdateAdjacentMethodEvent args) { - var uid = ent.Owner; - var atmos = ent.Comp1; - var blockedDirs = tile.AirtightData.BlockedDirections; - if (activate) - AddActiveTile(atmos, tile); + if (args.Handled) + return; + + var mapGridComp = args.MapGridComponent; + + if (!Resolve(uid, ref mapGridComp)) + return; + + var xform = Transform(uid); + EntityUid? mapUid = _mapManager.MapExists(xform.MapID) ? _mapManager.GetMapEntityId(xform.MapID) : null; + + if (!component.Tiles.TryGetValue(args.Tile, out var tile)) + return; tile.AdjacentBits = AtmosDirection.Invalid; + tile.BlockedAirflow = GetBlockedDirections(mapGridComp, tile.GridIndices); + for (var i = 0; i < Atmospherics.Directions; i++) { var direction = (AtmosDirection) (1 << i); - var adjacentIndices = tile.GridIndices.Offset(direction); - TileAtmosphere? adjacent; - if (!tile.NoGridTile) - { - adjacent = GetOrNewTile(uid, atmos, adjacentIndices); - } - else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent)) + var otherIndices = tile.GridIndices.Offset(direction); + + if (!component.Tiles.TryGetValue(otherIndices, out var adjacent)) { - tile.AdjacentBits &= ~direction; - tile.AdjacentTiles[i] = null; - continue; + adjacent = new TileAtmosphere(tile.GridIndex, otherIndices, + GetTileMixture(null, mapUid, otherIndices), + space: IsTileSpace(null, mapUid, otherIndices, mapGridComp)); } - var adjBlockDirs = adjacent.AirtightData.BlockedDirections; - if (activate) - AddActiveTile(atmos, adjacent); - var oppositeDirection = direction.GetOpposite(); - if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction)) + + adjacent.BlockedAirflow = GetBlockedDirections(mapGridComp, adjacent.GridIndices); + + // Pass in MapGridComponent so we don't have to resolve it for every adjacent direction. + var tileBlockedEv = new IsTileAirBlockedMethodEvent(uid, tile.GridIndices, direction, mapGridComp); + GridIsTileAirBlocked(uid, component, ref tileBlockedEv); + + var adjacentBlockedEv = + new IsTileAirBlockedMethodEvent(uid, adjacent.GridIndices, oppositeDirection, mapGridComp); + GridIsTileAirBlocked(uid, component, ref adjacentBlockedEv); + + if (!adjacent.BlockedAirflow.IsFlagSet(oppositeDirection) && !tileBlockedEv.Result) + { + adjacent.AdjacentBits |= oppositeDirection; + adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile; + } + else { - // Adjacency is blocked by some airtight entity. - tile.AdjacentBits &= ~direction; adjacent.AdjacentBits &= ~oppositeDirection; - tile.AdjacentTiles[i] = null; adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null; } - else + + if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result) { - // No airtight entity in the way. tile.AdjacentBits |= direction; - adjacent.AdjacentBits |= oppositeDirection; - tile.AdjacentTiles[i] = adjacent; - adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile; + tile.AdjacentTiles[direction.ToIndex()] = adjacent; + } + else + { + tile.AdjacentBits &= ~direction; + tile.AdjacentTiles[direction.ToIndex()] = null; } DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^ @@ -347,16 +409,6 @@ private void UpdateAdjacentTiles( tile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid; } - private (GasMixture Air, bool IsSpace) GetDefaultMapAtmosphere(MapAtmosphereComponent? map) - { - if (map == null) - return (GasMixture.SpaceGas, true); - - var air = map.Mixture; - DebugTools.Assert(air.Immutable); - return (air, map.Space); - } - private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args) { if (args.Handled) @@ -399,50 +451,54 @@ private void GridIsHotspotActive(EntityUid uid, GridAtmosphereComponent componen args.Handled = true; } - private void GridFixTileVacuum( - Entity ent, - TileAtmosphere tile, - float volume) + private void GridFixTileVacuum(EntityUid uid, GridAtmosphereComponent component, ref FixTileVacuumMethodEvent args) { - DebugTools.AssertNotNull(tile.Air); - DebugTools.Assert(tile.Air?.Immutable == false ); - Array.Clear(tile.MolesArchived); - tile.ArchivedCycle = 0; + if (args.Handled) + return; + + var adjEv = new GetAdjacentTileMixturesMethodEvent(uid, args.Tile, false, true); + GridGetAdjacentTileMixtures(uid, component, ref adjEv); + + if (!adjEv.Handled || !component.Tiles.TryGetValue(args.Tile, out var tile)) + return; + + if (!TryComp(uid, out var mapGridComp)) + return; - var count = 0; - foreach (var adj in tile.AdjacentTiles) + var adjacent = adjEv.Result!.ToArray(); + + // Return early, let's not cause any funny NaNs or needless vacuums. + if (adjacent.Length == 0) + return; + + tile.Air = new GasMixture { - if (adj?.Air != null) - count++; - } + Volume = GetVolumeForTiles(mapGridComp, 1), + Temperature = Atmospherics.T20C + }; + + tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; + tile.ArchivedCycle = 0; - var ratio = 1f / count; + var ratio = 1f / adjacent.Length; var totalTemperature = 0f; - foreach (var adj in tile.AdjacentTiles) + foreach (var adj in adjacent) { - if (adj?.Air == null) - continue; - totalTemperature += adj.Temperature; - // TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles? - // Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary. - // if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather - // than having to iterate over adjacent tiles twice. - // Remove a bit of gas from the adjacent ratio... - var mix = adj.Air.RemoveRatio(ratio); + var mix = adj.RemoveRatio(ratio); // And merge it to the new tile air. Merge(tile.Air, mix); // Return removed gas to its original mixture. - Merge(adj.Air, mix); + Merge(adj, mix); } // New temperature is the arithmetic mean of the sum of the adjacent temperatures... - tile.Air.Temperature = totalTemperature / count; + tile.Air.Temperature = totalTemperature / adjacent.Length; } private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args) @@ -491,21 +547,30 @@ private void GridRemoveAtmosDevice(EntityUid uid, GridAtmosphereComponent compon /// /// Repopulates all tiles on a grid atmosphere. /// - public void InvalidateAllTiles(Entity entity) + /// The grid where to get all valid tiles from. + /// The grid atmosphere where the tiles will be repopulated. + private void GridRepopulateTiles(Entity grid) { - var (uid, grid, atmos) = entity; - if (!Resolve(uid, ref grid, ref atmos)) - return; + var (uid, mapGrid, gridAtmosphere) = grid; + var volume = GetVolumeForTiles(mapGrid, 1); - foreach (var indices in atmos.Tiles.Keys) + foreach (var tile in mapGrid.GetAllTiles()) { - atmos.InvalidatedCoords.Add(indices); + if (!gridAtmosphere.Tiles.ContainsKey(tile.GridIndices)) + gridAtmosphere.Tiles[tile.GridIndices] = new TileAtmosphere(tile.GridUid, tile.GridIndices, + new GasMixture(volume) { Temperature = Atmospherics.T20C }); + + gridAtmosphere.InvalidatedCoords.Add(tile.GridIndices); } - var enumerator = _map.GetAllTilesEnumerator(uid, grid); - while (enumerator.MoveNext(out var tile)) + TryComp(uid, out GasTileOverlayComponent? overlay); + + // Gotta do this afterwards so we can properly update adjacent tiles. + foreach (var (position, _) in gridAtmosphere.Tiles.ToArray()) { - atmos.InvalidatedCoords.Add(tile.Value.GridIndices); + var ev = new UpdateAdjacentMethodEvent(uid, position); + GridUpdateAdjacent(uid, gridAtmosphere, ref ev); + InvalidateVisuals(uid, position, overlay); } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs index 77b5bf18af2..53035e1ed3c 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs @@ -1,11 +1,13 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos; +using Content.Shared.Audio; using Content.Shared.Mobs.Components; using Content.Shared.Physics; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; +using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -52,7 +54,7 @@ private void UpdateHighPressure(float frameTime) if (HasComp(uid) && TryComp(uid, out var body)) { - _physics.SetBodyStatus(uid, body, BodyStatus.OnGround); + _physics.SetBodyStatus(body, BodyStatus.OnGround); } if (TryComp(uid, out var fixtures)) @@ -75,7 +77,7 @@ private void AddMobMovedByPressure(EntityUid uid, MovedByPressureComponent compo if (!TryComp(uid, out var fixtures)) return; - _physics.SetBodyStatus(uid, body, BodyStatus.InAir); + _physics.SetBodyStatus(body, BodyStatus.InAir); foreach (var (id, fixture) in fixtures.Fixtures) { @@ -94,9 +96,9 @@ private void HighPressureMovements(Entity gridAtmospher // TODO ATMOS finish this // Don't play the space wind sound on tiles that are on fire... - if (tile.PressureDifference > 15 && !tile.Hotspot.Valid) + if(tile.PressureDifference > 15 && !tile.Hotspot.Valid) { - if (_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound)) + if(_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound)) { var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices); _audio.PlayPvs(SpaceWindSound, coordinates, AudioParams.Default.WithVariation(0.125f).WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100))); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index c27e18b55b0..795c6e0547e 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -1,13 +1,12 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; -using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems { public sealed partial class AtmosphereSystem { - private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent visuals) + private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent? visuals) { // Can't process a tile without air if (tile.Air == null) @@ -117,9 +116,15 @@ private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere private void Archive(TileAtmosphere tile, int fireCount) { if (tile.Air != null) + { tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan()); + tile.TemperatureArchived = tile.Air.Temperature; + } + else + { + tile.TemperatureArchived = tile.Temperature; + } - tile.TemperatureArchived = tile.Temperature; tile.ArchivedCycle = fireCount; } @@ -161,12 +166,6 @@ private void AddActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmospher /// Whether to dispose of the tile's private void RemoveActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, bool disposeExcitedGroup = true) { - DebugTools.Assert(tile.Excited == gridAtmosphere.ActiveTiles.Contains(tile)); - DebugTools.Assert(tile.Excited || tile.ExcitedGroup == null); - - if (!tile.Excited) - return; - tile.Excited = false; gridAtmosphere.ActiveTiles.Remove(tile); @@ -187,6 +186,7 @@ public float GetHeatCapacityArchived(TileAtmosphere tile) if (tile.Air == null) return tile.HeatCapacity; + // Moles archived is not null if air is not null. return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs index ed105c8d33f..916191cb050 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs @@ -1,8 +1,6 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos.Components; using Robust.Shared.GameStates; -using Robust.Shared.Map.Components; -using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems; @@ -10,25 +8,10 @@ public partial class AtmosphereSystem { private void InitializeMap() { - SubscribeLocalEvent(OnMapStartup); - SubscribeLocalEvent(OnMapRemove); SubscribeLocalEvent(MapIsTileSpace); SubscribeLocalEvent(MapGetTileMixture); SubscribeLocalEvent(MapGetTileMixtures); SubscribeLocalEvent(OnMapGetState); - SubscribeLocalEvent(OnGridParentChanged); - } - - private void OnMapStartup(EntityUid uid, MapAtmosphereComponent component, ComponentInit args) - { - component.Mixture.MarkImmutable(); - component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture); - } - - private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, ComponentRemove args) - { - if (!TerminatingOrDeleted(uid)) - RefreshAllGridMapAtmospheres(uid); } private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args) @@ -45,115 +28,54 @@ private void MapGetTileMixture(EntityUid uid, MapAtmosphereComponent component, if (args.Handled) return; - args.Mixture = component.Mixture; + // Clone the mixture, if possible. + args.Mixture = component.Mixture?.Clone(); args.Handled = true; } private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args) { - if (args.Handled) + if (args.Handled || component.Mixture == null) return; args.Handled = true; args.Mixtures ??= new GasMixture?[args.Tiles.Count]; for (var i = 0; i < args.Tiles.Count; i++) { - args.Mixtures[i] ??= component.Mixture; + args.Mixtures[i] ??= component.Mixture.Clone(); } } private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args) { - args.State = new MapAtmosphereComponentState(component.Overlay); - } - - public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture) - { - DebugTools.Assert(HasComp(uid)); - var component = EnsureComp(uid); - SetMapGasMixture(uid, mixture, component, false); - SetMapSpace(uid, space, component, false); - RefreshAllGridMapAtmospheres(uid); + args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture)); } - public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true) + public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null) { if (!Resolve(uid, ref component)) return; - if (!mixture.Immutable) - { - mixture = mixture.Clone(); - mixture.MarkImmutable(); - } - + component.Space = space; component.Mixture = mixture; - component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture); - Dirty(uid, component); - if (updateTiles) - RefreshAllGridMapAtmospheres(uid); + Dirty(component); } - public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true) + public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null) { if (!Resolve(uid, ref component)) return; - if (component.Space == space) - return; - - component.Space = space; - - if (updateTiles) - RefreshAllGridMapAtmospheres(uid); - } - - /// - /// Forces a refresh of all MapAtmosphere tiles on every grid on a map. - /// - public void RefreshAllGridMapAtmospheres(EntityUid map) - { - DebugTools.Assert(HasComp(map)); - var enumerator = AllEntityQuery(); - while (enumerator.MoveNext(out var grid, out var atmos, out var xform)) - { - if (xform.MapUid == map) - RefreshMapAtmosphereTiles((grid, atmos)); - } - } - - /// - /// Forces a refresh of all MapAtmosphere tiles on a given grid. - /// - private void RefreshMapAtmosphereTiles(Entity grid) - { - if (!Resolve(grid.Owner, ref grid.Comp)) - return; - - var atmos = grid.Comp; - foreach (var tile in atmos.MapTiles) - { - RemoveMapAtmos(atmos, tile); - atmos.InvalidatedCoords.Add(tile.GridIndices); - } - atmos.MapTiles.Clear(); + component.Mixture = mixture; + Dirty(component); } - /// - /// Handles updating map-atmospheres when grids move across maps. - /// - private void OnGridParentChanged(Entity grid, ref EntParentChangedMessage args) + public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null) { - // Do nothing if detaching to nullspace - if (!args.Transform.ParentUid.IsValid()) + if (!Resolve(uid, ref component)) return; - // Avoid doing work if moving from a space-map to another space-map. - if (args.OldParent == null - || HasComp(args.OldParent) - || HasComp(args.Transform.ParentUid)) - { - RefreshMapAtmosphereTiles((grid, grid)); - } + component.Space = space; + Dirty(component); } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index f156125b0ff..aceda3cd332 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -5,6 +5,7 @@ using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Database; +using Content.Shared.Doors.Components; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Random; @@ -26,10 +27,7 @@ public sealed partial class AtmosphereSystem private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2]; - private void EqualizePressureInZone( - Entity ent, - TileAtmosphere tile, - int cycleNum) + private void EqualizePressureInZone(Entity ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) { if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum)) return; // Already done. @@ -58,7 +56,7 @@ private void EqualizePressureInZone( return; } - var gridAtmosphere = ent.Comp1; + var (_, mapGrid, gridAtmosphere) = ent; var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl; var totalMoles = 0f; _equalizeTiles[0] = tile; @@ -93,7 +91,7 @@ private void EqualizePressureInZone( { // Looks like someone opened an airlock to space! - ExplosivelyDepressurize(ent, tile, cycleNum); + ExplosivelyDepressurize(ent, tile, cycleNum, visuals); return; } } @@ -218,13 +216,9 @@ private void EqualizePressureInZone( for (var k = 0; k < Atmospherics.Directions; k++) { var direction = (AtmosDirection) (1 << k); - if (!otherTile.AdjacentBits.IsFlagSet(direction)) - continue; - - if (giver.MonstermosInfo.MoleDelta <= 0) - break; // We're done here now. Let's not do more work than needed. - + if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; var otherTile2 = otherTile.AdjacentTiles[k]; + if (giver.MonstermosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed. if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue; DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue; @@ -338,7 +332,7 @@ private void EqualizePressureInZone( for (var i = 0; i < tileCount; i++) { var otherTile = _equalizeTiles[i]!; - FinalizeEq(gridAtmosphere, otherTile, ent); + FinalizeEq(gridAtmosphere, otherTile, visuals); } for (var i = 0; i < tileCount; i++) @@ -347,17 +341,12 @@ private void EqualizePressureInZone( for (var j = 0; j < Atmospherics.Directions; j++) { var direction = (AtmosDirection) (1 << j); - if (!otherTile.AdjacentBits.IsFlagSet(direction)) - continue; - + if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; var otherTile2 = otherTile.AdjacentTiles[j]!; if (otherTile2.AdjacentBits == 0) continue; - DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); - if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) - continue; - + if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) continue; AddActiveTile(gridAtmosphere, otherTile2); break; } @@ -370,10 +359,7 @@ private void EqualizePressureInZone( Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit); } - private void ExplosivelyDepressurize( - Entity ent, - TileAtmosphere tile, - int cycleNum) + private void ExplosivelyDepressurize(Entity ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) { // Check if explosive depressurization is enabled and if the tile is valid. if (!MonstermosDepressurization || tile.Air == null) @@ -382,7 +368,7 @@ private void ExplosivelyDepressurize( const int limit = Atmospherics.MonstermosHardTileLimit; var totalMolesRemoved = 0f; - var (owner, gridAtmosphere, visuals, mapGrid, _) = ent; + var (owner, mapGrid, gridAtmosphere) = ent; var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl; var tileCount = 0; @@ -402,27 +388,20 @@ private void ExplosivelyDepressurize( { for (var j = 0; j < Atmospherics.Directions; j++) { - var otherTile2 = otherTile.AdjacentTiles[j]; - if (otherTile2?.Air == null) - continue; - - if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) - continue; - var direction = (AtmosDirection) (1 << j); - DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction)); + if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; + var otherTile2 = otherTile.AdjacentTiles[j]; + if (otherTile2?.Air == null) continue; DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); + if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue; - ConsiderFirelocks(ent, otherTile, otherTile2); + ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid); // The firelocks might have closed on us. - if (!otherTile.AdjacentBits.IsFlagSet(direction)) - continue; - + if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle }; _depressurizeTiles[tileCount++] = otherTile2; - if (tileCount >= limit) - break; + if (tileCount >= limit) break; } } else @@ -458,21 +437,13 @@ private void ExplosivelyDepressurize( // Flood fill into this new direction var direction = (AtmosDirection) (1 << j); // Tiles in _depressurizeProgressionOrder cannot have null air. - if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space) - continue; - + if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space) continue; var tile2 = otherTile.AdjacentTiles[j]; - if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) - continue; - + if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue; DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); // If flood fill has already reached this tile, continue. - if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) - continue; - - if(tile2.Space) - continue; - + if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue; + if(tile2.Space) continue; tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite(); tile2.MonstermosInfo.CurrentTransferAmount = 0.0f; tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget; @@ -564,7 +535,7 @@ private void ExplosivelyDepressurize( _physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics); } - if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10) + if(tileCount > 10 && (totalMolesRemoved / tileCount) > 10) _adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High, $"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}"); @@ -573,33 +544,36 @@ private void ExplosivelyDepressurize( Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2); } - private void ConsiderFirelocks( - Entity ent, - TileAtmosphere tile, - TileAtmosphere other) + private void ConsiderFirelocks(Entity ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid) { var reconsiderAdjacent = false; - var mapGrid = ent.Comp3; - foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices)) + foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices)) { - if (_firelockQuery.TryGetComponent(entity, out var firelock)) - reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); + if (!TryComp(entity, out FirelockComponent? firelock)) + continue; + + reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); } - foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices)) + foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices)) { - if (_firelockQuery.TryGetComponent(entity, out var firelock)) - reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); + if (!TryComp(entity, out FirelockComponent? firelock)) + continue; + + reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); } if (!reconsiderAdjacent) return; - UpdateAdjacentTiles(ent, tile); - UpdateAdjacentTiles(ent, other); - InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent); - InvalidateVisuals(other.GridIndex, other.GridIndices, ent); + var (owner, gridAtmosphere) = ent; + var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices); + var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices); + GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv); + GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv); + InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals); + InvalidateVisuals(other.GridIndex, other.GridIndices, visuals); } private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals) @@ -668,7 +642,7 @@ private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, flo if (adj == null) { var nonNull = tile.AdjacentTiles.Where(x => x != null).Count(); - Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}"); + Logger.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: {tile.Tile}, non-null adj count: {nonNull}, Trace: {Environment.StackTrace}"); return; } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 1f3ca2145b9..4f8df0af670 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.Components; using Content.Server.Atmos.Piping.Components; +using Content.Server.NodeContainer.NodeGroups; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Maps; @@ -7,7 +8,6 @@ using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems { @@ -30,249 +30,131 @@ public sealed partial class AtmosphereSystem private int _currentRunAtmosphereIndex; private bool _simulationPaused; - private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index) - { - var tile = atmosphere.Tiles.GetOrNew(index, out var existing); - if (existing) - return tile; - - atmosphere.InvalidatedCoords.Add(index); - tile.GridIndex = owner; - tile.GridIndices = index; - return tile; - } - - private readonly List> _currentRunAtmosphere = new(); + private readonly List> _currentRunAtmosphere = new(); /// /// Revalidates all invalid coordinates in a grid atmosphere. - /// I.e., process any tiles that have had their airtight blockers modified. /// /// The grid atmosphere in question. /// Whether the process succeeded or got paused due to time constrains. - private bool ProcessRevalidate(Entity ent) + private bool ProcessRevalidate(Entity ent, GasTileOverlayComponent? visuals) { - if (ent.Comp4.MapUid == null) - { - Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}"); - return true; - } - - var (uid, atmosphere, visuals, grid, xform) = ent; - var volume = GetVolumeForTiles(grid); - TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos); - + var (owner, atmosphere) = ent; if (!atmosphere.ProcessingPaused) { - atmosphere.CurrentRunInvalidatedTiles.Clear(); - atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count); - foreach (var indices in atmosphere.InvalidatedCoords) + atmosphere.CurrentRunInvalidatedCoordinates.Clear(); + atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count); + foreach (var tile in atmosphere.InvalidatedCoords) { - var tile = GetOrNewTile(uid, atmosphere, indices); - atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile); - - // Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData. - UpdateTileData(ent, mapAtmos, tile); + atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile); } atmosphere.InvalidatedCoords.Clear(); - - if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) - return false; } - var number = 0; - while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile)) - { - DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile); - UpdateAdjacentTiles(ent, tile, activate: true); - UpdateTileAir(ent, tile, volume); - InvalidateVisuals(uid, tile.GridIndices, visuals); - - if (number++ < InvalidCoordinatesLagCheckIterations) - continue; - - number = 0; - // Process the rest next time. - if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) - return false; - } + if (!TryComp(owner, out MapGridComponent? mapGridComp)) + return true; - TrimDisconnectedMapTiles(ent); - return true; - } + var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID); - /// - /// This method queued a tile and all of its neighbours up for processing by . - /// - public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile) - { - if (!tile.TrimQueued) - { - tile.TrimQueued = true; - atmos.PossiblyDisconnectedTiles.Add(tile); - } + var volume = GetVolumeForTiles(mapGridComp); - for (var i = 0; i < Atmospherics.Directions; i++) + var number = 0; + while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices)) { - var direction = (AtmosDirection) (1 << i); - var indices = tile.GridIndices.Offset(direction); - if (atmos.Tiles.TryGetValue(indices, out var adj) - && adj.NoGridTile - && !adj.TrimQueued) + if (!atmosphere.Tiles.TryGetValue(indices, out var tile)) { - adj.TrimQueued = true; - atmos.PossiblyDisconnectedTiles.Add(adj); + tile = new TileAtmosphere(owner, indices, + new GasMixture(volume) { Temperature = Atmospherics.T20C }); + atmosphere.Tiles[indices] = tile; } - } - } - /// - /// Tiles in a are either grid-tiles, or they they should be are tiles - /// adjacent to grid-tiles that represent the map's atmosphere. This method trims any map-tiles that are no longer - /// adjacent to any grid-tiles. - /// - private void TrimDisconnectedMapTiles( - Entity ent) - { - var atmos = ent.Comp1; + var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp); + GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv); + var isAirBlocked = airBlockedEv.Result; - foreach (var tile in atmos.PossiblyDisconnectedTiles) - { - tile.TrimQueued = false; - if (!tile.NoGridTile) - continue; + var oldBlocked = tile.BlockedAirflow; + var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp); + GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv); - var connected = false; - for (var i = 0; i < Atmospherics.Directions; i++) + // Blocked airflow changed, rebuild excited groups! + if (tile.Excited && tile.BlockedAirflow != oldBlocked) { - var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i)); - if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty) - { - connected = true; - break; - } + RemoveActiveTile(atmosphere, tile); } - if (!connected) + // Call this instead of the grid method as the map has a say on whether the tile is space or not. + if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked) { - RemoveActiveTile(atmos, tile); - atmos.Tiles.Remove(tile.GridIndices); + tile.Air = GetTileMixture(null, mapUid, indices); + tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null; + tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp); } - } - - atmos.PossiblyDisconnectedTiles.Clear(); - } - - /// - /// Checks whether a tile has a corresponding grid-tile, or whether it is a "map" tile. Also checks whether the - /// tile should be considered "space" - /// - private void UpdateTileData( - Entity ent, - MapAtmosphereComponent? mapAtmos, - TileAtmosphere tile) - { - var idx = tile.GridIndices; - bool mapAtmosphere; - if (_map.TryGetTile(ent.Comp3, idx, out var gTile) && !gTile.IsEmpty) - { - var contentDef = (ContentTileDefinition) _tileDefinitionManager[gTile.TypeId]; - mapAtmosphere = contentDef.MapAtmosphere; - tile.ThermalConductivity = contentDef.ThermalConductivity; - tile.HeatCapacity = contentDef.HeatCapacity; - tile.NoGridTile = false; - } - else - { - mapAtmosphere = true; - tile.ThermalConductivity = 0.5f; - tile.HeatCapacity = float.PositiveInfinity; - - if (!tile.NoGridTile) + else if (isAirBlocked) { - tile.NoGridTile = true; - - // This tile just became a non-grid atmos tile. - // It, or one of its neighbours, might now be completely disconnected from the grid. - QueueTileTrim(ent.Comp1, tile); + if (airBlockedEv.NoAir) + { + tile.Air = null; + tile.MolesArchived = null; + tile.ArchivedCycle = 0; + tile.LastShare = 0f; + tile.Hotspot = new Hotspot(); + } } - } + else + { + if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices)) + { + var vacuumEv = new FixTileVacuumMethodEvent(owner, indices); + GridFixTileVacuum(owner, atmosphere, ref vacuumEv); + } - UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile); + // Tile used to be space, but isn't anymore. + if (tile.Space || (tile.Air?.Immutable ?? false)) + { + tile.Air = null; + tile.MolesArchived = null; + tile.ArchivedCycle = 0; + tile.LastShare = 0f; + tile.Space = false; + } - if (mapAtmosphere) - { - if (!tile.MapAtmosphere) - { - (tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos); - tile.MapAtmosphere = true; - ent.Comp1.MapTiles.Add(tile); + tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C}; + tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases]; } - DebugTools.AssertNotNull(tile.Air); - DebugTools.Assert(tile.Air?.Immutable ?? false); - return; - } + // We activate the tile. + AddActiveTile(atmosphere, tile); - if (!tile.MapAtmosphere) - return; + // TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity + var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef) + ? tileRef.GetContentTileDefinition(_tileDefinitionManager) + : null; - // Tile used to be exposed to the map's atmosphere, but isn't anymore. - RemoveMapAtmos(ent.Comp1, tile); - } + tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f; + tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity; + InvalidateVisuals(owner, indices, visuals); - private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile) - { - DebugTools.Assert(tile.MapAtmosphere); - DebugTools.AssertNotNull(tile.Air); - DebugTools.Assert(tile.Air?.Immutable ?? false); - tile.MapAtmosphere = false; - atmos.MapTiles.Remove(tile); - tile.Air = null; - Array.Clear(tile.MolesArchived); - tile.ArchivedCycle = 0; - tile.LastShare = 0f; - tile.Space = false; - } + for (var i = 0; i < Atmospherics.Directions; i++) + { + var direction = (AtmosDirection) (1 << i); + var otherIndices = indices.Offset(direction); - /// - /// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one. - /// - private void UpdateTileAir( - Entity ent, - TileAtmosphere tile, - float volume) - { - if (tile.MapAtmosphere) - { - DebugTools.AssertNotNull(tile.Air); - DebugTools.Assert(tile.Air?.Immutable ?? false); - return; - } + if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile)) + AddActiveTile(atmosphere, otherTile); + } - var data = tile.AirtightData; - var fullyBlocked = data.BlockedDirections == AtmosDirection.All; + if (number++ < InvalidCoordinatesLagCheckIterations) + continue; - if (fullyBlocked && data.NoAirWhenBlocked) - { - if (tile.Air == null) - return; - - tile.Air = null; - Array.Clear(tile.MolesArchived); - tile.ArchivedCycle = 0; - tile.LastShare = 0f; - tile.Hotspot = new Hotspot(); - return; + number = 0; + // Process the rest next time. + if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) + { + return false; + } } - if (tile.Air != null) - return; - - tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C}; - - if (data.FixVacuum) - GridFixTileVacuum(ent, tile, volume); + return true; } private void QueueRunTiles( @@ -288,16 +170,19 @@ private void QueueRunTiles( } } - private bool ProcessTileEqualize(Entity ent) + private bool ProcessTileEqualize(Entity ent, GasTileOverlayComponent? visuals) { - var atmosphere = ent.Comp1; + var (uid, atmosphere) = ent; if (!atmosphere.ProcessingPaused) QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles); + if (!TryComp(uid, out MapGridComponent? mapGridComp)) + throw new Exception("Tried to process a grid atmosphere on an entity that isn't a grid!"); + var number = 0; while (atmosphere.CurrentRunTiles.TryDequeue(out var tile)) { - EqualizePressureInZone(ent, tile, atmosphere.UpdateCounter); + EqualizePressureInZone((uid, mapGridComp, atmosphere), tile, atmosphere.UpdateCounter, visuals); if (number++ < LagCheckIterations) continue; @@ -313,7 +198,7 @@ private bool ProcessTileEqualize(Entity Atmospherics.ExcitedGroupBreakdownCycles) + if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles) ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup); - else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles) - DeactivateGroupTiles(gridAtmosphere, excitedGroup); - // TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it? + + else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles) + ExcitedGroupDismantle(gridAtmosphere, excitedGroup); if (number++ < LagCheckIterations) continue; @@ -550,10 +435,10 @@ private void UpdateProcessing(float frameTime) _currentRunAtmosphereIndex = 0; _currentRunAtmosphere.Clear(); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var atmos, out var overlay, out var grid, out var xform )) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var grid)) { - _currentRunAtmosphere.Add((uid, atmos, overlay, grid, xform)); + _currentRunAtmosphere.Add((uid, grid)); } } @@ -563,7 +448,8 @@ private void UpdateProcessing(float frameTime) for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++) { var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex]; - var (owner, atmosphere, visuals, grid, xform) = ent; + var (owner, atmosphere) = ent; + TryComp(owner, out GasTileOverlayComponent? visuals); if (!TryComp(owner, out TransformComponent? x) || x.MapUid == null @@ -588,14 +474,13 @@ private void UpdateProcessing(float frameTime) switch (atmosphere.State) { case AtmosphereProcessingState.Revalidate: - if (!ProcessRevalidate(ent)) + if (!ProcessRevalidate(ent, visuals)) { atmosphere.ProcessingPaused = true; return; } atmosphere.ProcessingPaused = false; - // Next state depends on whether monstermos equalization is enabled or not. // Note: We do this here instead of on the tile equalization step to prevent ending it early. // Therefore, a change to this CVar might only be applied after that step is over. @@ -604,7 +489,7 @@ private void UpdateProcessing(float frameTime) : AtmosphereProcessingState.ActiveTiles; continue; case AtmosphereProcessingState.TileEqualize: - if (!ProcessTileEqualize(ent)) + if (!ProcessTileEqualize(ent, visuals)) { atmosphere.ProcessingPaused = true; return; @@ -614,7 +499,7 @@ private void UpdateProcessing(float frameTime) atmosphere.State = AtmosphereProcessingState.ActiveTiles; continue; case AtmosphereProcessingState.ActiveTiles: - if (!ProcessActiveTiles(ent, ent)) + if (!ProcessActiveTiles(atmosphere, visuals)) { atmosphere.ProcessingPaused = true; return; @@ -635,7 +520,7 @@ private void UpdateProcessing(float frameTime) atmosphere.State = AtmosphereProcessingState.HighPressureDelta; continue; case AtmosphereProcessingState.HighPressureDelta: - if (!ProcessHighPressureDelta((ent, ent))) + if (!ProcessHighPressureDelta(ent)) { atmosphere.ProcessingPaused = true; return; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs index 5c73cf11246..33fa16a6c68 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs @@ -1,6 +1,5 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos; -using Robust.Shared.Map.Components; namespace Content.Server.Atmos.EntitySystems { @@ -13,8 +12,7 @@ private void Superconduct(GridAtmosphereComponent gridAtmosphere, TileAtmosphere for(var i = 0; i < Atmospherics.Directions; i++) { var direction = (AtmosDirection) (1 << i); - if (!directions.IsFlagSet(direction)) - continue; + if (!directions.IsFlagSet(direction)) continue; var adjacent = tile.AdjacentTiles[direction.ToIndex()]; @@ -94,9 +92,7 @@ public void NeighborConductWithSource(GridAtmosphereComponent gridAtmosphere, Ti { if (tile.Air == null) { - // TODO ATMOS: why does this need to check if a tile exists if it doesn't use the tile? - if (TryComp(other.GridIndex, out var grid) - && _mapSystem.TryGetTileRef(other.GridIndex, grid, other.GridIndices, out var _)) + if (other.Tile != null) { TemperatureShareOpenToSolid(other, tile); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs index 9b0d0d9670d..699c2a70aef 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs @@ -41,6 +41,20 @@ public void InvalidateVisuals(EntityUid gridUid, Vector2i tile, GasTileOverlayCo _gasTileOverlaySystem.Invalidate(gridUid, tile, comp); } + public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices) + { + var value = false; + + var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices); + + while (enumerator.MoveNext(out var airtight)) + { + value |= airtight.FixVacuum; + } + + return value; + } + /// /// Gets the volume in liters for a number of tiles, on a specific grid. /// @@ -52,44 +66,34 @@ private float GetVolumeForTiles(MapGridComponent mapGrid, int tiles = 1) return Atmospherics.CellVolume * mapGrid.TileSize * tiles; } - public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked, - bool FixVacuum); - - private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile) + /// + /// Gets all obstructing instances in a specific tile. + /// + /// The grid where to get the tile. + /// The indices of the tile. + /// The enumerator for the airtight components. + public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile) { - var oldBlocked = tile.AirtightData.BlockedDirections; - - tile.AirtightData = tile.NoGridTile - ? default - : GetAirtightData(uid, grid, tile.GridIndices); + var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile); + var airQuery = GetEntityQuery(); - if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null) - ExcitedGroupDispose(atmos, tile.ExcitedGroup); + var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery); + return enumerator; } - private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile) + private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices) { - var blockedDirs = AtmosDirection.Invalid; - var noAirWhenBlocked = false; - var fixVacuum = false; + var value = AtmosDirection.Invalid; - foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile)) - { - if (!_airtightQuery.TryGetComponent(ent, out var airtight)) - continue; - - if(!airtight.AirBlocked) - continue; + var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices); - blockedDirs |= airtight.AirBlockedDirection; - noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked; - fixVacuum |= airtight.FixVacuum; - - if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum) - break; + while (enumerator.MoveNext(out var airtight)) + { + if(airtight.AirBlocked) + value |= airtight.AirBlockedDirection; } - return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum); + return value; } /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index d2f40e77169..dd2a9675591 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Fluids.EntitySystems; using Content.Server.NodeContainer.EntitySystems; using Content.Shared.Atmos.EntitySystems; -using Content.Shared.Doors.Components; using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Server.GameObjects; @@ -41,9 +40,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem private const float ExposedUpdateDelay = 1f; private float _exposedTimer = 0f; - private EntityQuery _atmosQuery; - private EntityQuery _airtightQuery; - private EntityQuery _firelockQuery; private HashSet _entSet = new(); public override void Initialize() @@ -59,9 +55,6 @@ public override void Initialize() InitializeGridAtmosphere(); InitializeMap(); - _atmosQuery = GetEntityQuery(); - _airtightQuery = GetEntityQuery(); - _firelockQuery = GetEntityQuery(); SubscribeLocalEvent(OnTileChanged); diff --git a/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs b/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs index 70e3eef3c44..203c747e29e 100644 --- a/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs @@ -27,12 +27,8 @@ private void OnTileChanged(ref TileChangedEvent ev) // Also, these calls are surprisingly slow. // TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into // TODO: a simple array lookup, as tile IDs are likely contiguous, and there's at most 2^16 possibilities anyway. - - var oldSpace = ev.OldTile.IsSpace(_tileDefinitionManager); - var newSpace = ev.NewTile.IsSpace(_tileDefinitionManager); - - if (!(oldSpace && !newSpace || - !oldSpace && newSpace) || + if (!((ev.OldTile.IsSpace(_tileDefinitionManager) && !ev.NewTile.IsSpace(_tileDefinitionManager)) || + (!ev.OldTile.IsSpace(_tileDefinitionManager) && ev.NewTile.IsSpace(_tileDefinitionManager))) || _atmosphereSystem.HasAtmosphere(ev.Entity)) return; diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs index 552ee142329..6a2c8f0a7e5 100644 --- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs @@ -260,13 +260,13 @@ private GasEntry[] GenerateGasEntryArray(GasMixture? mixture) { var gas = _atmo.GetGas(i); - if (mixture?[i] <= UIMinMoles) + if (mixture?.Moles[i] <= UIMinMoles) continue; if (mixture != null) { var gasName = Loc.GetString(gas.Name); - gases.Add(new GasEntry(gasName, mixture[i], gas.Color)); + gases.Add(new GasEntry(gasName, mixture.Moles[i], gas.Color)); } } diff --git a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs index 397f0167a9b..8ae9517379e 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -171,7 +171,7 @@ public GasOverlayData GetOverlayData(GasMixture? mixture) { var id = VisibleGasId[i]; var gas = _atmosphereSystem.GetGas(id); - var moles = mixture?[id] ?? 0f; + var moles = mixture?.Moles[id] ?? 0f; ref var opacity = ref data.Opacity[i]; if (moles < gas.GasMolesVisible) @@ -216,13 +216,13 @@ private bool UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayC oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity); } - if (tile is {Air: not null, NoGridTile: false}) + if (tile.Air != null) { for (var i = 0; i < VisibleGasId.Length; i++) { var id = VisibleGasId[i]; var gas = _atmosphereSystem.GetGas(id); - var moles = tile.Air[id]; + var moles = tile.Air.Moles[id]; ref var oldOpacity = ref oldData.Opacity[i]; if (moles < gas.GasMolesVisible) diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index 77fd7018333..0a2ef235a71 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; using Content.Server.Atmos.Reactions; using Content.Shared.Atmos; -using Content.Shared.Atmos.EntitySystems; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -18,13 +17,11 @@ public sealed partial class GasMixture : IEquatable, ISerializationH { public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true}; - // No access, to ensure immutable mixtures are never accidentally mutated. - [Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)] - [DataField] + // This must always have a length that is a multiple of 4 for SIMD acceleration. + [DataField("moles")] + [ViewVariables(VVAccess.ReadWrite)] public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases]; - public float this[int gas] => Moles[gas]; - [DataField("temperature")] [ViewVariables(VVAccess.ReadWrite)] private float _temperature = Atmospherics.TCMB; @@ -83,19 +80,6 @@ public GasMixture(float volume = 0f) Volume = volume; } - public GasMixture(float[] moles, float temp, float volume = Atmospherics.CellVolume) - { - if (moles.Length != Atmospherics.AdjustedNumberOfGases) - throw new InvalidOperationException($"Invalid mole array length"); - - if (volume < 0) - volume = 0; - - _temperature = temp; - Moles = moles; - Volume = volume; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MarkImmutable() { @@ -133,16 +117,15 @@ public void SetMoles(Gas gas, float quantity) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AdjustMoles(int gasId, float quantity) { - if (Immutable) - return; - - if (!float.IsFinite(quantity)) - throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity)); + if (!Immutable) + { + if (!float.IsFinite(quantity)) + throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity)); - // Clamping is needed because x - x can be negative with floating point numbers. If we don't - // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles(). - ref var moles = ref Moles[gasId]; - moles = MathF.Max(moles + quantity, 0); + // Clamping is needed because x - x can be negative with floating point numbers. If we don't + // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles(). + Moles[gasId] = MathF.Max(Moles[gasId] + quantity, 0); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -180,8 +163,7 @@ public GasMixture RemoveRatio(float ratio) { var moles = Moles[i]; var otherMoles = removed.Moles[i]; - - if ((moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) && !Immutable) + if (moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) Moles[i] = 0; if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles)) @@ -220,9 +202,6 @@ public void Multiply(float multiplier) void ISerializationHooks.AfterDeserialization() { - // ISerializationHooks is obsolete. - // TODO add fixed-length-array serializer - // The arrays MUST have a specific length. Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases); } @@ -250,12 +229,8 @@ public override bool Equals(object? obj) public bool Equals(GasMixture? other) { - if (ReferenceEquals(this, other)) - return true; - - if (ReferenceEquals(null, other)) - return false; - + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; return Moles.SequenceEqual(other.Moles) && _temperature.Equals(other._temperature) && ReactionResults.SequenceEqual(other.ReactionResults) @@ -283,13 +258,11 @@ public override int GetHashCode() public GasMixture Clone() { - if (Immutable) - return this; - var newMixture = new GasMixture() { Moles = (float[])Moles.Clone(), _temperature = _temperature, + Immutable = Immutable, Volume = Volume, }; return newMixture; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index ad647fad1b8..64d02d793bc 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -136,7 +136,7 @@ private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent ca for (int i = 0; i < containedGasArray.Length; i++) { - containedGasDict.Add((Gas)i, canister.Air[i]); + containedGasDict.Add((Gas)i, canister.Air.Moles[i]); } _adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]"); diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs index 852542ec6cd..491bf600627 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs @@ -45,7 +45,7 @@ private void OnCondenserUpdated(Entity entity, ref AtmosD var removed = inlet.Air.Remove(molesToConvert); for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - var moles = removed[i]; + var moles = removed.Moles[i]; if (moles <= 0) continue; diff --git a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs index 71e4c2d0def..7d385c530a9 100644 --- a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs +++ b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs @@ -183,7 +183,13 @@ public void CopyTo(ISerializationManager serializationManager, Dictionary - /// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the - /// unblocked directions on adjacent tiles. - /// [ViewVariables] public AtmosDirection AdjacentBits = AtmosDirection.Invalid; @@ -77,7 +72,10 @@ public sealed class TileAtmosphere : IGasMixtureHolder public EntityUid GridIndex { get; set; } [ViewVariables] - public Vector2i GridIndices; + public TileRef? Tile => GridIndices.GetTileRef(GridIndex); + + [ViewVariables] + public Vector2i GridIndices { get; } [ViewVariables] public ExcitedGroup? ExcitedGroup { get; set; } @@ -94,7 +92,7 @@ public sealed class TileAtmosphere : IGasMixtureHolder public float LastShare; [ViewVariables] - public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; + public float[]? MolesArchived; GasMixture IGasMixtureHolder.Air { @@ -105,31 +103,8 @@ GasMixture IGasMixtureHolder.Air [ViewVariables] public float MaxFireTemperatureSustained { get; set; } - /// - /// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at - /// this position, or because the tile type is not airtight. - /// - [ViewVariables] - public bool MapAtmosphere; - - /// - /// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for - /// adjacent grid tiles. - /// [ViewVariables] - public bool NoGridTile; - - /// - /// If true, this tile is queued for processing in - /// - [ViewVariables] - public bool TrimQueued; - - /// - /// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated - /// (i.e., gets added to ). - /// - public AtmosphereSystem.AirtightData AirtightData; + public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid; public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false) { @@ -137,24 +112,10 @@ public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mix GridIndices = gridIndices; Air = mixture; Space = space; + MolesArchived = Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null; if(immutable) Air?.MarkImmutable(); } - - public TileAtmosphere(TileAtmosphere other) - { - GridIndex = other.GridIndex; - GridIndices = other.GridIndices; - Space = other.Space; - NoGridTile = other.NoGridTile; - MapAtmosphere = other.MapAtmosphere; - Air = other.Air?.Clone(); - Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length); - } - - public TileAtmosphere() - { - } } } diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index 4b60f8814b5..b5bac507391 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -77,7 +77,7 @@ public void GasToReagent(EntityUid uid, LungComponent lung) foreach (var gas in Enum.GetValues()) { var i = (int) gas; - var moles = lung.Air[i]; + var moles = lung.Air.Moles[i]; if (moles <= 0) continue; var reagent = _atmosphereSystem.GasReagents[i]; diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 6cdb088dbb0..be6e8b78fef 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -7,7 +7,7 @@ using Content.Server.GameTicking; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; -using Content.Server.Psionics.Telepathy; +using Content.Server.Nyanotrasen.Chat; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; using Content.Server.Station.Components; diff --git a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs index 7d3f70bc0d8..d945c1f818b 100644 --- a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs @@ -64,8 +64,8 @@ public void Start(Entity vapor, TransformComponent vaporXform, V // Set Move if (EntityManager.TryGetComponent(vapor, out PhysicsComponent? physics)) { - _physics.SetLinearDamping(vapor, physics, 0f); - _physics.SetAngularDamping(vapor, physics, 0f); + _physics.SetLinearDamping(physics, 0f); + _physics.SetAngularDamping(physics, 0f); _throwing.TryThrow(vapor, dir, speed, user: user); diff --git a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs index e7466fbc85d..e12cab80a91 100644 --- a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs +++ b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs @@ -16,15 +16,12 @@ public sealed partial class ModifyLungGas : ReagentEffect public override void Effect(ReagentEffectArgs args) { - if (!args.EntityManager.TryGetComponent(args.OrganEntity, out var lung)) - return; - - foreach (var (gas, ratio) in _ratios) + if (args.EntityManager.TryGetComponent(args.OrganEntity, out var lung)) { - var quantity = ratio * args.Quantity.Float() / Atmospherics.BreathMolesToReagentMultiplier; - if (quantity < 0) - quantity = Math.Max(quantity, -lung.Air[(int)gas]); - lung.Air.AdjustMoles(gas, quantity); + foreach (var (gas, ratio) in _ratios) + { + lung.Air.Moles[(int) gas] += (ratio * args.Quantity.Float()) / Atmospherics.BreathMolesToReagentMultiplier; + } } } } diff --git a/Content.Server/Construction/Conditions/ComponentInTile.cs b/Content.Server/Construction/Conditions/ComponentInTile.cs index 36705e4c0ee..8ab4046a721 100644 --- a/Content.Server/Construction/Conditions/ComponentInTile.cs +++ b/Content.Server/Construction/Conditions/ComponentInTile.cs @@ -3,7 +3,6 @@ using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Utility; namespace Content.Server.Construction.Conditions @@ -49,15 +48,7 @@ public bool Condition(EntityUid uid, IEntityManager entityManager) var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve()); var lookup = entityManager.EntitySysManager.GetEntitySystem(); - - - if (!entityManager.TryGetComponent(transform.GridUid.Value, out var grid)) - return !HasEntity; - - if (!entityManager.System().TryGetTileRef(transform.GridUid.Value, grid, indices, out var tile)) - return !HasEntity; - - var entities = tile.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static, lookup); + var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Static, lookup); foreach (var ent in entities) { diff --git a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs index 22d96a54146..ec9ec770313 100644 --- a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs +++ b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs @@ -5,7 +5,7 @@ using Content.Server.Psionics.Glimmer; using Content.Server.StationEvents.Components; using Content.Shared.Psionics.Glimmer; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 4a9477e7443..31451c425f5 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -475,7 +475,7 @@ public void DamageFloorTile(TileRef tileRef, if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef) return; - if (tileDef.MapAtmosphere) + if (tileDef.IsSpace) canCreateVacuum = true; // is already a vacuum. int tileBreakages = 0; @@ -491,7 +491,7 @@ public void DamageFloorTile(TileRef tileRef, if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef) break; - if (newDef.MapAtmosphere && !canCreateVacuum) + if (newDef.IsSpace && !canCreateVacuum) break; tileDef = newDef; diff --git a/Content.Server/ImmovableRod/ImmovableRodSystem.cs b/Content.Server/ImmovableRod/ImmovableRodSystem.cs index 31aa39cf03a..0fa8f7d292f 100644 --- a/Content.Server/ImmovableRod/ImmovableRodSystem.cs +++ b/Content.Server/ImmovableRod/ImmovableRodSystem.cs @@ -53,9 +53,9 @@ private void OnMapInit(EntityUid uid, ImmovableRodComponent component, MapInitEv { if (EntityManager.TryGetComponent(uid, out PhysicsComponent? phys)) { - _physics.SetLinearDamping(uid, phys, 0f); - _physics.SetFriction(uid, phys, 0f); - _physics.SetBodyStatus(uid, phys, BodyStatus.InAir); + _physics.SetLinearDamping(phys, 0f); + _physics.SetFriction(phys, 0f); + _physics.SetBodyStatus(phys, BodyStatus.InAir); if (!component.RandomizeVelocity) return; diff --git a/Content.Server/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs similarity index 83% rename from Content.Server/Psionics/Abilities/DispelPowerSystem.cs rename to Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs index cb7ef8313cd..d338a5a5bcb 100644 --- a/Content.Server/Psionics/Abilities/DispelPowerSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs @@ -1,20 +1,24 @@ using Content.Shared.Actions; using Content.Shared.StatusEffect; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Damage; using Content.Shared.Revenant.Components; using Content.Server.Guardian; using Content.Server.Bible.Components; using Content.Server.Popups; +using Robust.Shared.Prototypes; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Timing; +using Content.Shared.Mind; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; -namespace Content.Server.Psionics.Abilities +namespace Content.Server.Abilities.Psionics { public sealed class DispelPowerSystem : EntitySystem { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; @@ -23,6 +27,8 @@ public sealed class DispelPowerSystem : EntitySystem [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() @@ -46,13 +52,10 @@ private void OnInit(EntityUid uid, DispelPowerComponent component, ComponentInit _actions.TryGetActionData( component.DispelActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.DispelActionEntity); - if (TryComp(uid, out var psionic)) + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) { + psionic.PsionicAbility = component.DispelActionEntity; psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.DispelFeedback); - //It's fully intended that Dispel doesn't increase Amplification, and instead heavily spikes Dampening - //Antimage archetype. - psionic.Dampening += 1f; } } @@ -63,16 +66,12 @@ private void OnShutdown(EntityUid uid, DispelPowerComponent component, Component if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.DispelFeedback); - psionic.Dampening -= 1f; } } private void OnPowerUsed(DispelPowerActionEvent args) { - if (HasComp(args.Target) || HasComp(args.Performer)) - return; - if (!TryComp(args.Performer, out var psionic) || !HasComp(args.Target)) + if (HasComp(args.Target)) return; var ev = new DispelledEvent(); @@ -81,9 +80,7 @@ private void OnPowerUsed(DispelPowerActionEvent args) if (ev.Handled) { args.Handled = true; - _psionics.LogPowerUsed(args.Performer, "dispel", - (int) MathF.Round(-2 * psionic.Dampening + psionic.Amplification), - (int) MathF.Round(-4 * psionic.Dampening + psionic.Amplification)); + _psionics.LogPowerUsed(args.Performer, "dispel"); } } @@ -99,7 +96,7 @@ private void OnDispelled(EntityUid uid, DispellableComponent component, Dispelle private void OnDmgDispelled(EntityUid uid, DamageOnDispelComponent component, DispelledEvent args) { var damage = component.Damage; - var modifier = 1 + component.Variance - _random.NextFloat(0, component.Variance * 2); + var modifier = (1 + component.Variance) - (_random.NextFloat(0, component.Variance * 2)); damage *= modifier; DealDispelDamage(uid, damage); @@ -148,3 +145,5 @@ public void DealDispelDamage(EntityUid uid, DamageSpecifier? damage = null) } public sealed class DispelledEvent : HandledEntityEventArgs {} } + + diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs new file mode 100644 index 00000000000..b775117b716 --- /dev/null +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -0,0 +1,74 @@ +using Content.Shared.Actions; +using Content.Shared.Abilities.Psionics; +using Content.Shared.StatusEffect; +using Content.Shared.Popups; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Content.Shared.Mind; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics +{ + public sealed class MetapsionicPowerSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.MetapsionicActionEntity, component.MetapsionicActionId ); + _actions.TryGetActionData( component.MetapsionicActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.MetapsionicActionEntity); + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + { + psionic.PsionicAbility = component.MetapsionicActionEntity; + psionic.ActivePowers.Add(component); + } + + } + + private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.MetapsionicActionEntity); + + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + } + } + + private void OnPowerUsed(EntityUid uid, MetapsionicPowerComponent component, MetapsionicPowerActionEvent args) + { + foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range)) + { + if (HasComp(entity) && entity != uid && !HasComp(entity) && + !(HasComp(entity) && Transform(entity).ParentUid == uid)) + { + _popups.PopupEntity(Loc.GetString("metapsionic-pulse-success"), uid, uid, PopupType.LargeCaution); + args.Handled = true; + return; + } + } + _popups.PopupEntity(Loc.GetString("metapsionic-pulse-failure"), uid, uid, PopupType.Large); + _psionics.LogPowerUsed(uid, "metapsionic pulse", 2, 4); + + args.Handled = true; + } + } +} diff --git a/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs similarity index 78% rename from Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs rename to Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs index 1e50a586b4f..b23224cab48 100644 --- a/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs @@ -1,6 +1,5 @@ using Content.Shared.Actions; -using Content.Shared.Psionics.Abilities; -using Content.Shared.Psionics; +using Content.Shared.Abilities.Psionics; using Content.Shared.Speech; using Content.Shared.Stealth.Components; using Content.Shared.Mobs.Components; @@ -11,15 +10,19 @@ using Content.Server.Popups; using Content.Server.Psionics; using Content.Server.GameTicking; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; using Content.Shared.Mind; using Content.Shared.Actions.Events; -namespace Content.Server.Psionics.Abilities +namespace Content.Server.Abilities.Psionics { public sealed class MindSwapPowerSystem : EntitySystem { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly MindSystem _mindSystem = default!; @@ -35,22 +38,20 @@ public override void Initialize() SubscribeLocalEvent(OnDispelled); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnGhostAttempt); + // SubscribeLocalEvent(OnSwapInit); - SubscribeLocalEvent(OnSwapShutdown); - SubscribeLocalEvent(OnInsulated); } private void OnInit(EntityUid uid, MindSwapPowerComponent component, ComponentInit args) { - _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId); - _actions.TryGetActionData( component.MindSwapActionEntity, out var actionData); + _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId ); + _actions.TryGetActionData( component.MindSwapActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.MindSwapActionEntity); - if (TryComp(uid, out var psionic)) + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) { + psionic.PsionicAbility = component.MindSwapActionEntity; psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.MindSwapFeedback); - psionic.Amplification += 1f; } } @@ -60,26 +61,20 @@ private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, Compone if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.MindSwapFeedback); - psionic.Amplification -= 1f; } } private void OnPowerUsed(MindSwapPowerActionEvent args) { - if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological")) return; if (HasComp(args.Target)) return; - if (!TryComp(args.Performer, out var psionic)) - return; - Swap(args.Performer, args.Target); - _psionics.LogPowerUsed(args.Performer, "mind swap", (int) MathF.Round(psionic.Amplification / psionic.Dampening * 8), (int) MathF.Round(psionic.Amplification / psionic.Dampening * 12)); + _psionics.LogPowerUsed(args.Performer, "mind swap"); args.Handled = true; } @@ -130,8 +125,8 @@ private void OnDispelled(EntityUid uid, MindSwappedComponent component, Dispelle private void OnMobStateChanged(EntityUid uid, MindSwappedComponent component, MobStateChangedEvent args) { - if (args.NewMobState == MobState.Dead || args.NewMobState == MobState.Critical) - Swap(uid, component.OriginalEntity, true); + if (args.NewMobState == MobState.Dead) + RemComp(uid); } private void OnGhostAttempt(GhostAttemptHandleEvent args) @@ -156,21 +151,8 @@ private void OnSwapInit(EntityUid uid, MindSwappedComponent component, Component _actions.TryGetActionData( component.MindSwapReturnActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.MindSwapReturnActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.MindSwappedFeedback); - } - } - - private void OnSwapShutdown(EntityUid uid, MindSwappedComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MindSwapReturnActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.MindSwappedFeedback); - } + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.MindSwapReturnActionEntity; } public void Swap(EntityUid performer, EntityUid target, bool end = false) @@ -183,13 +165,11 @@ public void Swap(EntityUid performer, EntityUid target, bool end = false) MindComponent? targetMind = null; // This is here to prevent missing MindContainerComponent Resolve errors. - if (!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)) - { + if(!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)){ performerMind = null; }; - if (!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)) - { + if(!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)){ targetMind = null; }; //This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say @@ -222,7 +202,7 @@ public void Swap(EntityUid performer, EntityUid target, bool end = false) perfComp.OriginalEntity = target; targetComp.OriginalEntity = performer; } - //It shouldn't actually be possible anymore to get trapped under most circumstances, but for niche edge cases, I am leaving this here + public void GetTrapped(EntityUid uid) { @@ -240,9 +220,5 @@ public void GetTrapped(EntityUid uid) _metaDataSystem.SetEntityDescription(uid, Loc.GetString("telegnostic-trapped-entity-desc")); } } - public void OnInsulated(EntityUid uid, MindSwappedComponent component, PsionicInsulationEvent args) - { - Swap(uid, component.OriginalEntity, true); - } } } diff --git a/Content.Server/Psionics/Abilities/MindSwappedComponent.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwappedComponent.cs similarity index 79% rename from Content.Server/Psionics/Abilities/MindSwappedComponent.cs rename to Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwappedComponent.cs index 82c0313bca6..72cd6a66ef9 100644 --- a/Content.Server/Psionics/Abilities/MindSwappedComponent.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwappedComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Psionics.Abilities +namespace Content.Server.Abilities.Psionics { [RegisterComponent] public sealed partial class MindSwappedComponent : Component @@ -14,8 +14,5 @@ public sealed partial class MindSwappedComponent : Component [DataField("mindSwapReturnActionEntity")] public EntityUid? MindSwapReturnActionEntity; - - [DataField("mindSwappedFeedback")] - public string MindSwappedFeedback = "mindswapped-feedback"; } } diff --git a/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs similarity index 54% rename from Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs rename to Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs index c935bc0123d..0fd261ef12f 100644 --- a/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs @@ -1,21 +1,26 @@ using Content.Shared.Actions; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; +using Content.Server.Psionics; using Content.Shared.StatusEffect; -using Content.Server.Electrocution; using Content.Server.Stunnable; using Content.Server.Beam; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Content.Server.Mind; using Content.Shared.Actions.Events; -namespace Content.Server.Psionics.Abilities +namespace Content.Server.Abilities.Psionics { public sealed class NoosphericZapPowerSystem : EntitySystem { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly BeamSystem _beam = default!; - [Dependency] private readonly ElectrocutionSystem _electrocution = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() @@ -32,11 +37,10 @@ private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, Compon _actions.TryGetActionData( component.NoosphericZapActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic)) + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) { + psionic.PsionicAbility = component.NoosphericZapActionEntity; psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.NoosphericZapFeedback); - psionic.Amplification += 1f; } } @@ -46,34 +50,24 @@ private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, Co if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.NoosphericZapFeedback); - psionic.Amplification -= 1f; } } private void OnPowerUsed(NoosphericZapPowerActionEvent args) { - if (!TryComp(args.Performer, out var psionic)) + if (!HasComp(args.Target)) return; - if (!HasComp(args.Target) && !HasComp(args.Performer)) - { - _beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric"); - _stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(1 * psionic.Amplification), false); + if (HasComp(args.Target)) + return; - _electrocution.TryDoElectrocution(args.Target, null, - (int) MathF.Round(5f * psionic.Amplification), - new TimeSpan((long) MathF.Round(1f * psionic.Amplification)), - true, - ignoreInsulation: true); + _beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric"); - _statusEffectsSystem.TryAddStatusEffect(args.Target, "Stutter", TimeSpan.FromSeconds(2 * psionic.Amplification), false, "StutteringAccent"); + _stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(5), false); + _statusEffectsSystem.TryAddStatusEffect(args.Target, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent"); - _psionics.LogPowerUsed(args.Performer, "noopsheric zap", - (int) MathF.Round(6 * psionic.Amplification - psionic.Dampening), - (int) MathF.Round(8 * psionic.Amplification - psionic.Dampening)); - args.Handled = true; - } + _psionics.LogPowerUsed(args.Performer, "noospheric zap"); + args.Handled = true; } } } diff --git a/Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs similarity index 57% rename from Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs rename to Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs index 0c50efb5cf3..5ca1dc7a6dc 100644 --- a/Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs @@ -1,31 +1,31 @@ -using Content.Server.DoAfter; using Content.Shared.Actions; -using Content.Shared.Psionics.Abilities; +using Content.Shared.CombatMode.Pacification; +using Content.Shared.Abilities.Psionics; using Content.Shared.Damage; -using Content.Shared.DoAfter; using Content.Shared.Stunnable; using Content.Shared.Stealth; using Content.Shared.Stealth.Components; -using Content.Shared.Psionics.Events; +using Content.Server.Psionics; +using Robust.Shared.Prototypes; +using Robust.Shared.Player; +using Robust.Shared.Audio; +using Robust.Shared.Timing; +using Content.Server.Mind; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; -using Content.Shared.Interaction.Events; -using Content.Shared.Weapons.Ranged.Events; -using Content.Shared.Throwing; -using Robust.Shared.Timing; -using Content.Shared.Psionics; -namespace Content.Server.Psionics.Abilities +namespace Content.Server.Abilities.Psionics { public sealed class PsionicInvisibilityPowerSystem : EntitySystem { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedStealthSystem _stealth = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { @@ -37,61 +37,43 @@ public override void Initialize() SubscribeLocalEvent(OnStart); SubscribeLocalEvent(OnEnd); SubscribeLocalEvent(OnDamageChanged); - SubscribeLocalEvent(OnAttackAttempt); - SubscribeLocalEvent(OnShootAttempt); - SubscribeLocalEvent(OnThrowAttempt); - SubscribeLocalEvent(OnInsulated); } private void OnInit(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentInit args) { - _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId); - _actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData); + _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId ); + _actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic)) + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) { + psionic.PsionicAbility = component.PsionicInvisibilityActionEntity; psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.InvisibilityFeedback); - psionic.Amplification += 0.5f; } } private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args) { - RemComp(uid); - RemComp(uid); _actions.RemoveAction(uid, component.PsionicInvisibilityActionEntity); if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.InvisibilityFeedback); - psionic.Amplification -= 0.5f; } } private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args) { - if (!TryComp(uid, out var psionic)) - return; - - if (HasComp(uid)) + if (HasComp(uid)) return; - var ev = new PsionicInvisibilityTimerEvent(_gameTiming.CurTime); - var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseTimer, ev, uid) { Hidden = true }; - _doAfterSystem.TryStartDoAfter(doAfterArgs); - ToggleInvisibility(args.Performer); var action = Spawn(PsionicInvisibilityUsedComponent.PsionicInvisibilityUsedActionPrototype); _actions.AddAction(uid, action, action); - _actions.TryGetActionData(action, out var actionData); + _actions.TryGetActionData( action, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(action); - _psionics.LogPowerUsed(uid, "psionic invisibility", - (int) MathF.Round(8 * psionic.Amplification - 2 * psionic.Dampening), - (int) MathF.Round(12 * psionic.Amplification - 2 * psionic.Dampening)); + _psionics.LogPowerUsed(uid, "psionic invisibility"); args.Handled = true; } @@ -107,6 +89,7 @@ private void OnPowerOff(RemovePsionicInvisibilityOffPowerActionEvent args) private void OnStart(EntityUid uid, PsionicInvisibilityUsedComponent component, ComponentInit args) { EnsureComp(uid); + EnsureComp(uid); var stealth = EnsureComp(uid); _stealth.SetVisibility(uid, 0.66f, stealth); _audio.PlayPvs("/Audio/Effects/toss.ogg", uid); @@ -119,37 +102,24 @@ private void OnEnd(EntityUid uid, PsionicInvisibilityUsedComponent component, Co return; RemComp(uid); + RemComp(uid); RemComp(uid); _audio.PlayPvs("/Audio/Effects/toss.ogg", uid); + //Pretty sure this DOESN'T work as intended. _actions.RemoveAction(uid, component.PsionicInvisibilityUsedActionEntity); - DirtyEntity(uid); - } - private void OnAttackAttempt(EntityUid uid, PsionicInvisibilityUsedComponent component, AttackAttemptEvent args) - { - RemComp(uid); - } - - private void OnShootAttempt(EntityUid uid, PsionicInvisibilityUsedComponent component, ShotAttemptedEvent args) - { - RemComp(uid); + _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(8), false); + DirtyEntity(uid); } - private void OnThrowAttempt(EntityUid uid, PsionicInvisibilityUsedComponent component, ThrowAttemptEvent args) - { - RemComp(uid); - } private void OnDamageChanged(EntityUid uid, PsionicInvisibilityUsedComponent component, DamageChangedEvent args) { - if (!TryComp(uid, out var psionic)) - return; - if (!args.DamageIncreased) return; ToggleInvisibility(uid); - _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(4f / psionic.Dampening + psionic.Amplification), false); } + public void ToggleInvisibility(EntityUid uid) { if (!HasComp(uid)) @@ -160,19 +130,5 @@ public void ToggleInvisibility(EntityUid uid) RemComp(uid); } } - - public void OnDoAfter(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityTimerEvent args) - { - RemComp(uid); - } - - private void OnInsulated(EntityUid uid, PsionicInvisibilityUsedComponent component, PsionicInsulationEvent args) - { - if (!TryComp(uid, out var psionic)) - return; - - RemComp(uid); - _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(4f / psionic.Dampening + psionic.Amplification), false); - } } } diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs new file mode 100644 index 00000000000..2eca3173b6d --- /dev/null +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -0,0 +1,128 @@ +using Robust.Shared.Audio; +using Robust.Server.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.DoAfter; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions; +using Content.Shared.Chemistry.Components; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.Popups; +using Content.Shared.Psionics.Events; +using Content.Shared.Tag; +using Content.Shared.Examine; +using static Content.Shared.Examine.ExamineSystemShared; +using Robust.Shared.Timing; +using Content.Server.Mind; +using Content.Shared.Actions.Events; +using Content.Shared.Chemistry.EntitySystems; +using Robust.Server.Audio; + +namespace Content.Server.Abilities.Psionics +{ + public sealed class PsionicRegenerationPowerSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); + _actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.PsionicRegenerationActionEntity); + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + { + psionic.PsionicAbility = component.PsionicRegenerationActionEntity; + psionic.ActivePowers.Add(component); + } + } + + private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) + { + var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime); + var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid); + + _doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId); + + component.DoAfter = doAfterId; + + _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)), + uid, + // TODO: Use LoS-based Filter when one is available. + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + PopupType.Medium); + + _audioSystem.PlayPvs(component.SoundUse, component.Owner, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); + _psionics.LogPowerUsed(uid, "psionic regeneration"); + args.Handled = true; + } + + private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.PsionicRegenerationActionEntity); + + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + } + } + + private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args) + { + if (component.DoAfter == null) + return; + + _doAfterSystem.Cancel(component.DoAfter); + component.DoAfter = null; + + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationDoAfterEvent args) + { + component.DoAfter = null; + + if (!TryComp(uid, out var stream)) + return; + + // DoAfter has no way to run a callback during the process to give + // small doses of the reagent, so we wait until either the action + // is cancelled (by being dispelled) or complete to give the + // appropriate dose. A timestamp delta is used to accomplish this. + var percentageComplete = Math.Min(1f, (_gameTiming.CurTime - args.StartedAt).TotalSeconds / component.UseDelay); + + var solution = new Solution(); + solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(component.EssenceAmount * percentageComplete)); + _bloodstreamSystem.TryAddToChemicals(uid, solution, stream); + } + } +} + diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs new file mode 100644 index 00000000000..407b72c6b58 --- /dev/null +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.Actions; +using Content.Shared.Abilities.Psionics; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Popups; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Content.Server.Mind; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics +{ + public sealed class PyrokinesisPowerSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly FlammableSystem _flammableSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId ); + _actions.TryGetActionData( component.PyrokinesisActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.PyrokinesisActionEntity); + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + { + psionic.PsionicAbility = component.PyrokinesisActionEntity; + psionic.ActivePowers.Add(component); + } + } + + private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.PyrokinesisActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + } + } + + private void OnPowerUsed(PyrokinesisPowerActionEvent args) + { + if (!TryComp(args.Target, out var flammableComponent)) + return; + + flammableComponent.FireStacks += 5; + _flammableSystem.Ignite(args.Target, args.Target); + _popupSystem.PopupEntity(Loc.GetString("pyrokinesis-power-used", ("target", args.Target)), args.Target, Shared.Popups.PopupType.LargeCaution); + + _psionics.LogPowerUsed(args.Performer, "pyrokinesis"); + args.Handled = true; + } + } +} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs new file mode 100644 index 00000000000..f7ae04b61ea --- /dev/null +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs @@ -0,0 +1,67 @@ +using Content.Shared.Actions; +using Content.Shared.StatusEffect; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Mind.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Content.Server.Mind; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics +{ + public sealed class TelegnosisPowerSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnMindRemoved); + } + + private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId ); + _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.TelegnosisActionEntity); + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + { + psionic.PsionicAbility = component.TelegnosisActionEntity; + psionic.ActivePowers.Add(component); + } + } + + private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.TelegnosisActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + } + } + + private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) + { + var projection = Spawn(component.Prototype, Transform(uid).Coordinates); + Transform(projection).AttachToGridOrMap(); + _mindSwap.Swap(uid, projection); + + _psionics.LogPowerUsed(uid, "telegnosis"); + args.Handled = true; + } + private void OnMindRemoved(EntityUid uid, TelegnosticProjectionComponent component, MindRemovedMessage args) + { + QueueDel(uid); + } + } +} diff --git a/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs similarity index 54% rename from Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs rename to Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs index 915abd12224..ee16aaccfb6 100644 --- a/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -1,41 +1,77 @@ -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Actions; using Content.Shared.Psionics.Glimmer; using Content.Shared.Random; using Content.Shared.Random.Helpers; +using Content.Server.EUI; +using Content.Server.Psionics; +using Content.Server.Mind; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; using Content.Shared.StatusEffect; using Robust.Shared.Random; using Robust.Shared.Prototypes; +using Robust.Server.GameObjects; +using Robust.Server.Player; using Robust.Shared.Player; -using Content.Shared.Examine; -using Content.Shared.Popups; -using static Content.Shared.Examine.ExamineSystemShared; -namespace Content.Server.Psionics.Abilities +namespace Content.Server.Abilities.Psionics { public sealed class PsionicAbilitiesSystem : EntitySystem { [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly EuiManager _euiManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnPlayerAttached); } - /// - /// Adds a psychic power once a character rolls one. This used to be a system you have to select for. However the opt-in is no longer the text window, but is now done at character creation. - /// TODO: This is going to get removed when I reach Part 3 of my reworks, when I touch upon the GlimmerSystem itself and overhaul how players get powers. - /// - /// - /// - /// - public void AddPsionics(EntityUid uid) + private void OnPlayerAttached(EntityUid uid, PsionicAwaitingPlayerComponent component, PlayerAttachedEvent args) + { + if (TryComp(uid, out var bonus) && bonus.Warn == true) + _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), args.Player); + else + AddRandomPsionicPower(uid); + RemCompDeferred(uid); + } + + public void AddPsionics(EntityUid uid, bool warn = true) + { + if (Deleted(uid)) + return; + + if (HasComp(uid)) + return; + + //Don't know if this will work. New mind state vs old. + if (!TryComp(uid, out var mindContainer) || + !_mindSystem.TryGetMind(uid, out _, out var mind )) + //|| + //!_mindSystem.TryGetMind(uid, out var mind, mindContainer)) + { + EnsureComp(uid); + return; + } + + if (!_mindSystem.TryGetSession(mind, out var client)) + return; + + if (warn && TryComp(uid, out var actor)) + _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), client); + else + AddRandomPsionicPower(uid); + } + + public void AddPsionics(EntityUid uid, string powerComp) { if (Deleted(uid)) return; @@ -43,11 +79,17 @@ public void AddPsionics(EntityUid uid) if (HasComp(uid)) return; - AddRandomPsionicPower(uid); + AddComp(uid); + + var newComponent = (Component) _componentFactory.GetComponent(powerComp); + newComponent.Owner = uid; + + EntityManager.AddComponent(uid, newComponent); } + public void AddRandomPsionicPower(EntityUid uid) { - EnsureComp(uid, out var psionic); + AddComp(uid); if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) { @@ -61,21 +103,11 @@ public void AddRandomPsionicPower(EntityUid uid) EntityManager.AddComponent(uid, newComponent); - _glimmerSystem.Glimmer += _random.Next((int) MathF.Round(psionic.Amplification * psionic.Dampening * 1), (int) MathF.Round(psionic.Amplification * psionic.Dampening * 5)); + _glimmerSystem.Glimmer += _random.Next(1, 5); } public void RemovePsionics(EntityUid uid) { - if (RemComp(uid)) - { - _popups.PopupEntity(Loc.GetString("mindbreaking-feedback", ("entity", uid)), - uid, - // TODO: Use LoS-based Filter when one is available. - Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), - true, - PopupType.Medium); - } - if (!TryComp(uid, out var psionic)) return; @@ -98,15 +130,14 @@ public void RemovePsionics(EntityUid uid) if (psionic.PsionicAbility != null){ _actionsSystem.TryGetActionData( psionic.PsionicAbility, out var psiAbility ); if (psiAbility != null){ + var owner = psiAbility.Owner; _actionsSystem.RemoveAction(uid, psiAbility.Owner); } } _statusEffectsSystem.TryAddStatusEffect(uid, "Stutter", TimeSpan.FromMinutes(5), false, "StutteringAccent"); - _glimmerSystem.Glimmer += _random.Next((int) MathF.Round(psionic.Amplification * psionic.Dampening * -10), (int) MathF.Round(psionic.Amplification * psionic.Dampening * -5)); RemComp(uid); - RemComp(uid); } } } diff --git a/Content.Server/Psionics/Audio/GlimmerSoundComponent.cs b/Content.Server/Nyanotrasen/Audio/GlimmerSoundComponent.cs similarity index 80% rename from Content.Server/Psionics/Audio/GlimmerSoundComponent.cs rename to Content.Server/Nyanotrasen/Audio/GlimmerSoundComponent.cs index 9a6c62381be..850be3e831c 100644 --- a/Content.Server/Psionics/Audio/GlimmerSoundComponent.cs +++ b/Content.Server/Nyanotrasen/Audio/GlimmerSoundComponent.cs @@ -2,8 +2,12 @@ using Content.Shared.Audio; using Content.Shared.Psionics.Glimmer; using Robust.Shared.Audio; +using Robust.Shared.ComponentTrees; +using Robust.Shared.GameStates; +using Robust.Shared.Physics; +using Robust.Shared.Serialization; -namespace Content.Server.Psionics.Audio +namespace Content.Server.Audio { [RegisterComponent] [Access(typeof(SharedAmbientSoundSystem), typeof(GlimmerReactiveSystem))] diff --git a/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs b/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs similarity index 85% rename from Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs rename to Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs index ad49075e65a..58ed1782741 100644 --- a/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs +++ b/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs @@ -2,7 +2,7 @@ using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Bed.Sleep; using Content.Shared.Chat; using Content.Shared.Database; @@ -16,10 +16,10 @@ using System.Linq; using System.Text; -namespace Content.Server.Psionics.Telepathy +namespace Content.Server.Nyanotrasen.Chat { /// - /// Extensions for Telepathic Chat + /// Extensions for nyano's chat stuff /// public sealed class NyanoChatSystem : EntitySystem @@ -47,9 +47,7 @@ private IEnumerable GetAdminClients() private List GetDreamers(IEnumerable removeList) { var filtered = Filter.Empty() - .AddWhereAttachedEntity(entity => HasComp(entity) - || HasComp(entity) && !HasComp(entity) && !HasComp(entity) - || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) + .AddWhereAttachedEntity(entity => HasComp(entity) || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) .Recipients .Select(p => p.ConnectedClient); @@ -63,7 +61,7 @@ private List GetDreamers(IEnumerable removeList) private bool IsEligibleForTelepathy(EntityUid entity) { - return TryComp(entity, out var psionic) && psionic.Telepath + return HasComp(entity) && !HasComp(entity) && !HasComp(entity) && (!TryComp(entity, out var mobstate) || mobstate.CurrentState == MobState.Alive); @@ -94,9 +92,9 @@ public void SendTelepathicChat(EntityUid source, string message, bool hideChat) if (_random.Prob(0.1f)) _glimmerSystem.Glimmer++; - if (_random.Prob(Math.Min(0.33f + (float) _glimmerSystem.Glimmer / 1500, 1))) + if (_random.Prob(Math.Min(0.33f + ((float) _glimmerSystem.Glimmer / 1500), 1))) { - float obfuscation = 0.25f + (float) _glimmerSystem.Glimmer / 2000; + float obfuscation = (0.25f + (float) _glimmerSystem.Glimmer / 2000); var obfuscated = ObfuscateMessageReadability(message, obfuscation); _chatManager.ChatMessageToMany(ChatChannel.Telepathic, obfuscated, messageWrap, source, hideChat, false, GetDreamers(clients), Color.PaleVioletRed); } @@ -113,7 +111,7 @@ private string ObfuscateMessageReadability(string message, float chance) for (var i = 0; i < message.Length; i++) { - if (char.IsWhiteSpace(modifiedMessage[i])) + if (char.IsWhiteSpace((modifiedMessage[i]))) { continue; } diff --git a/Content.Server/Psionics/Telepathy/TSayCommand.cs b/Content.Server/Nyanotrasen/Chat/TSayCommand.cs similarity index 95% rename from Content.Server/Psionics/Telepathy/TSayCommand.cs rename to Content.Server/Nyanotrasen/Chat/TSayCommand.cs index 8fbaa5e17b2..9ba27b65d71 100644 --- a/Content.Server/Psionics/Telepathy/TSayCommand.cs +++ b/Content.Server/Nyanotrasen/Chat/TSayCommand.cs @@ -1,10 +1,11 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.Player; -namespace Content.Server.Psionics.Telepathy +namespace Content.Server.Chat.Commands { [AnyCommand] internal sealed class TSayCommand : IConsoleCommand diff --git a/Content.Server/Psionics/Telepathy/TelepathicRepeaterComponent.cs b/Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs similarity index 82% rename from Content.Server/Psionics/Telepathy/TelepathicRepeaterComponent.cs rename to Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs index 6e194f76c8f..fc199f4332a 100644 --- a/Content.Server/Psionics/Telepathy/TelepathicRepeaterComponent.cs +++ b/Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Psionics.Telepathy +namespace Content.Server.Nyanotrasen.Chat { /// /// Repeats whatever is happening in telepathic chat. diff --git a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs index 0ce3f9d7c64..a23a5b3d77d 100644 --- a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs +++ b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs @@ -1,5 +1,5 @@ using Content.Shared.Chemistry.Reagent; -using Content.Server.Psionics.Abilities; +using Content.Server.Abilities.Psionics; using JetBrains.Annotations; using Robust.Shared.Prototypes; diff --git a/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs b/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs new file mode 100644 index 00000000000..3b677bab2d4 --- /dev/null +++ b/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs @@ -0,0 +1,11 @@ +using Content.Server.Objectives.Systems; + +namespace Content.Server.Objectives.Components; + +/// +/// Requires that the player dies to be complete. +/// +[RegisterComponent, Access(typeof(BecomePsionicConditionSystem))] +public sealed partial class BecomePsionicConditionComponent : Component +{ +} \ No newline at end of file diff --git a/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs b/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs new file mode 100644 index 00000000000..d090c320a41 --- /dev/null +++ b/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared.Abilities.Psionics; +using Content.Server.Objectives.Components; +using Content.Shared.Mind; +using Content.Shared.Objectives.Components; + +namespace Content.Server.Objectives.Systems +{ + public sealed class BecomePsionicConditionSystem : EntitySystem + { + private EntityQuery _metaQuery; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetProgress); + } + + private void OnGetProgress(EntityUid uid, BecomePsionicConditionComponent comp, ref ObjectiveGetProgressEvent args) + { + args.Progress = GetProgress(args.Mind); + } + + private float GetProgress(MindComponent mind) + { + var entMan = IoCManager.Resolve(); + if (HasComp(mind.CurrentEntity)) + return 1; + return 0; + } + } +} diff --git a/Content.Server/Psionics/AcceptPsionicsEui.cs b/Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs similarity index 95% rename from Content.Server/Psionics/AcceptPsionicsEui.cs rename to Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs index 7c652664c64..80fd8946f28 100644 --- a/Content.Server/Psionics/AcceptPsionicsEui.cs +++ b/Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs @@ -1,7 +1,7 @@ using Content.Shared.Psionics; using Content.Shared.Eui; using Content.Server.EUI; -using Content.Server.Psionics.Abilities; +using Content.Server.Abilities.Psionics; namespace Content.Server.Psionics { diff --git a/Content.Server/Psionics/AntiPsychicWeaponComponent.cs b/Content.Server/Nyanotrasen/Psionics/AntiPsychicWeaponComponent.cs similarity index 100% rename from Content.Server/Psionics/AntiPsychicWeaponComponent.cs rename to Content.Server/Nyanotrasen/Psionics/AntiPsychicWeaponComponent.cs diff --git a/Content.Server/Psionics/Dreams/DreamSystem.cs b/Content.Server/Nyanotrasen/Psionics/Dreams/DreamSystem.cs similarity index 93% rename from Content.Server/Psionics/Dreams/DreamSystem.cs rename to Content.Server/Nyanotrasen/Psionics/Dreams/DreamSystem.cs index 1731c7a9bf5..d6067717c94 100644 --- a/Content.Server/Psionics/Dreams/DreamSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Dreams/DreamSystem.cs @@ -1,14 +1,17 @@ using Content.Shared.Dataset; using Content.Shared.Bed.Sleep; +using Content.Server.Chat.Systems; using Content.Server.Chat.Managers; using Robust.Shared.Random; using Robust.Shared.Prototypes; +using Robust.Server.GameObjects; using Robust.Shared.Player; namespace Content.Server.Psionics.Dreams { public sealed class DreamsSystem : EntitySystem { + [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; diff --git a/Content.Server/Psionics/Glimmer/GlimmerCommands.cs b/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerCommands.cs similarity index 100% rename from Content.Server/Psionics/Glimmer/GlimmerCommands.cs rename to Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerCommands.cs diff --git a/Content.Server/Psionics/Glimmer/GlimmerReactiveSystem.cs b/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs similarity index 99% rename from Content.Server/Psionics/Glimmer/GlimmerReactiveSystem.cs rename to Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs index c0802c8b670..da3b07d6dab 100644 --- a/Content.Server/Psionics/Glimmer/GlimmerReactiveSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs @@ -1,12 +1,14 @@ -using Content.Server.Psionics.Audio; +using Content.Server.Audio; using Content.Server.Power.Components; using Content.Server.Electrocution; using Content.Server.Lightning; using Content.Server.Explosion.EntitySystems; +using Content.Server.Construction; using Content.Server.Ghost; using Content.Server.Revenant.EntitySystems; using Content.Shared.Audio; using Content.Shared.Construction.EntitySystems; +using Content.Shared.Coordinates.Helpers; using Content.Shared.GameTicking; using Content.Shared.Psionics.Glimmer; using Content.Shared.Verbs; @@ -14,6 +16,7 @@ using Content.Shared.Damage; using Content.Shared.Destructible; using Content.Shared.Construction.Components; +using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Weapons.Melee.Components; using Robust.Shared.Audio; diff --git a/Content.Server/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs b/Content.Server/Nyanotrasen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs similarity index 94% rename from Content.Server/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs rename to Content.Server/Nyanotrasen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs index 57c74398b08..f0da85ce453 100644 --- a/Content.Server/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.CCVar; using Content.Shared.Psionics.Glimmer; using Content.Shared.GameTicking; +using Content.Server.CartridgeLoader.Cartridges; namespace Content.Server.Psionics.Glimmer { @@ -16,6 +17,7 @@ public sealed class PassiveGlimmerReductionSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly GlimmerMonitorCartridgeSystem _cartridgeSys = default!; /// List of glimmer values spaced by minute. public List GlimmerValues = new(); diff --git a/Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs b/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs similarity index 100% rename from Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs rename to Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs diff --git a/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs b/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs similarity index 100% rename from Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs rename to Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs similarity index 88% rename from Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs rename to Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs index 9583f45fdc9..31e6b89f13d 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs @@ -1,6 +1,5 @@ -using Content.Shared.Psionics.Abilities; -using Content.Shared.Psionics; -using Content.Server.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; +using Content.Server.Abilities.Psionics; using Content.Shared.Eye; using Content.Server.NPC.Systems; using Robust.Shared.Containers; @@ -21,6 +20,7 @@ public override void Initialize() SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnInsulInit); SubscribeLocalEvent(OnInsulShutdown); + SubscribeLocalEvent(OnEyeInit); /// Layer SubscribeLocalEvent(OnInvisInit); @@ -36,16 +36,10 @@ private void OnInit(EntityUid uid, PotentialPsionicComponent component, Componen SetCanSeePsionicInvisiblity(uid, false); } - /// - /// Being able to see invisible by default is no longer tracked by "Not having Potential Psionic". - /// Anything intended to be immune to invisibility(and mind magic in general) should instead have PsionicInsulation as a built-in component - /// - /// - /// - /// private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, ComponentInit args) { - RaiseLocalEvent(uid, new PsionicInsulationEvent()); + if (!HasComp(uid)) + return; if (HasComp(uid)) _invisSystem.ToggleInvisibility(uid); @@ -67,6 +61,9 @@ private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, Co private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component, ComponentShutdown args) { + if (!HasComp(uid)) + return; + SetCanSeePsionicInvisiblity(uid, false); if (!HasComp(uid)) @@ -102,6 +99,10 @@ private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent compon } } + private void OnEyeInit(EntityUid uid, EyeComponent component, ComponentInit args) + { + //SetCanSeePsionicInvisiblity(uid, true); //JJ Comment - Not allowed to modifies .yml on spawn any longer. See UninitializedSaveTest. + } private void OnEntInserted(EntityUid uid, PsionicallyInvisibleComponent component, EntInsertedIntoContainerMessage args) { DirtyEntity(args.Entity); @@ -124,7 +125,7 @@ public void SetCanSeePsionicInvisiblity(EntityUid uid, bool set) { if (EntityManager.TryGetComponent(uid, out EyeComponent? eye)) { - _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~(int) VisibilityFlags.PsionicInvisibility, eye); + _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~ (int) VisibilityFlags.PsionicInvisibility, eye); } } } diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs similarity index 95% rename from Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs rename to Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs index 268deddf6d9..859ceb7b83a 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs +++ b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Whitelist; +using Robust.Shared.Timing; namespace Content.Server.Psionics { diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs similarity index 95% rename from Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs rename to Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs index 403e0592617..cec755e3260 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Stealth.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; namespace Content.Server.Psionics { @@ -11,6 +12,7 @@ namespace Content.Server.Psionics public sealed class PsionicInvisibleContactsSystem : EntitySystem { [Dependency] private readonly SharedStealthSystem _stealth = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; public override void Initialize() { diff --git a/Content.Server/Psionics/Invisibility/PsionicallyInvisibleComponent.cs b/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicallyInvisibleComponent.cs similarity index 100% rename from Content.Server/Psionics/Invisibility/PsionicallyInvisibleComponent.cs rename to Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicallyInvisibleComponent.cs diff --git a/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs b/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs new file mode 100644 index 00000000000..9499497cd1d --- /dev/null +++ b/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Psionics +{ + [RegisterComponent] + public sealed partial class PotentialPsionicComponent : Component + { + [DataField("chance")] + public float Chance = 0.04f; + + /// + /// YORO (you only reroll once) + /// + public bool Rerolled = false; + } +} diff --git a/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs b/Content.Server/Nyanotrasen/Psionics/PsionicAwaitingPlayerComponent.cs similarity index 100% rename from Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs rename to Content.Server/Nyanotrasen/Psionics/PsionicAwaitingPlayerComponent.cs diff --git a/Content.Server/Psionics/PsionicBonusChanceComponent.cs b/Content.Server/Nyanotrasen/Psionics/PsionicBonusChanceComponent.cs similarity index 100% rename from Content.Server/Psionics/PsionicBonusChanceComponent.cs rename to Content.Server/Nyanotrasen/Psionics/PsionicBonusChanceComponent.cs diff --git a/Content.Server/Psionics/PsionicsCommands.cs b/Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs similarity index 84% rename from Content.Server/Psionics/PsionicsCommands.cs rename to Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs index 3f9ee794b38..959251d1fb7 100644 --- a/Content.Server/Psionics/PsionicsCommands.cs +++ b/Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs @@ -1,8 +1,9 @@ using Content.Server.Administration; using Content.Shared.Administration; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Mobs.Components; using Robust.Shared.Console; +using Robust.Server.GameObjects; using Content.Shared.Actions; using Robust.Shared.Player; @@ -18,8 +19,7 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) { SharedActionsSystem actions = default!; var entMan = IoCManager.Resolve(); - foreach (var (actor, psionic, meta) in entMan.EntityQuery()) - { + foreach (var (actor, mob, psionic, meta) in entMan.EntityQuery()){ // filter out xenos, etc, with innate telepathy actions.TryGetActionData( psionic.PsionicAbility, out var actionData ); if (actionData == null || actionData.ToString() == null) diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs similarity index 91% rename from Content.Server/Psionics/PsionicsSystem.cs rename to Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs index bf829477609..5a96af2e96b 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs @@ -1,14 +1,19 @@ -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.StatusEffect; +using Content.Shared.Mobs; using Content.Shared.Psionics.Glimmer; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Damage.Events; +using Content.Shared.IdentityManagement; using Content.Shared.CCVar; -using Content.Server.Psionics.Abilities; +using Content.Server.Abilities.Psionics; +using Content.Server.Chat.Systems; using Content.Server.Electrocution; using Content.Server.NPC.Components; using Content.Server.NPC.Systems; +using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; using Robust.Shared.Configuration; using Robust.Shared.Random; @@ -22,6 +27,7 @@ public sealed class PsionicsSystem : EntitySystem [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -48,7 +54,6 @@ public override void Initialize() SubscribeLocalEvent(OnStamHit); SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnRemove); } @@ -82,11 +87,7 @@ private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, Mel _electrocutionSystem.TryDoElectrocution(args.User, null, 20, TimeSpan.FromSeconds(5), false); } } - private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) - { - component.Amplification = _random.NextFloat(0.3f, 1.1f); - component.Dampening = _random.NextFloat(0.3f, 1.1f); - } + private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args) { if (!component.Removable) @@ -103,7 +104,7 @@ private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit arg private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) { - if (!HasComp(uid)) + if (!TryComp(uid, out var factions)) return; _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); @@ -143,14 +144,14 @@ public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, boo } if (applyGlimmer) - chance += (float) _glimmerSystem.Glimmer / 1000; + chance += ((float) _glimmerSystem.Glimmer / 1000); chance *= multiplier; chance = Math.Clamp(chance, 0, 1); if (_random.Prob(chance)) - _psionicAbilitiesSystem.AddPsionics(uid); + _psionicAbilitiesSystem.AddPsionics(uid, warn); } public void RerollPsionics(EntityUid uid, PotentialPsionicComponent? psionic = null, float bonusMuliplier = 1f) diff --git a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs index 24459d29e22..148598fe2c3 100644 --- a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs @@ -5,7 +5,7 @@ using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Fluids.EntitySystems; using Content.Server.Psionics; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Chat; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; diff --git a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs index b8cdcb56d47..bc3c22cc350 100644 --- a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs +++ b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Psionics.Abilities; +using Content.Server.Abilities.Psionics; using Content.Server.Chat.Systems; using Content.Server.Radio.Components; using Content.Server.Radio.EntitySystems; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs index 89b5a176f24..66eea988aeb 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs @@ -5,7 +5,7 @@ using Content.Server.Psionics.Glimmer; using Content.Server.StationEvents.Components; using Content.Shared.Psionics.Glimmer; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs index 89f3bc97501..63944563269 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs @@ -1,9 +1,10 @@ +using Robust.Server.GameObjects; using Robust.Shared.Random; -using Content.Server.Psionics.Abilities; +using Content.Server.Abilities.Psionics; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics; using Content.Server.StationEvents.Components; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Robust.Shared.Player; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs index 6a2c1c3ba7d..c04543d2195 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs @@ -10,7 +10,7 @@ using Content.Server.Power.EntitySystems; using Content.Server.Psionics.Glimmer; using Content.Server.StationEvents.Components; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Damage; using Content.Shared.Inventory; using Content.Shared.Mobs.Components; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs index 8812ed1fe37..175318e15bd 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs @@ -1,9 +1,9 @@ using Robust.Shared.Random; -using Content.Server.Psionics.Abilities; +using Content.Server.Abilities.Psionics; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Psionics; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Mobs.Systems; using Content.Shared.Psionics.Glimmer; using Content.Shared.Zombies; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs index 3672d317d9e..82c3d72b139 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs @@ -3,7 +3,7 @@ using Content.Server.Psionics; using Content.Server.StationEvents.Components; using Content.Server.Stunnable; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.StatusEffect; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs index 753b2e25729..63e0a435cb0 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs @@ -4,7 +4,7 @@ using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Shared.Mobs.Components; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.StatusEffect; using Content.Shared.Mobs.Systems; using Robust.Shared.Audio.Systems; diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs index 83cf9b9cb2d..c4c2300870d 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -1001,13 +1001,20 @@ public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, light.AmbientLightColor = Color.FromHex("#D8B059"); Dirty(mapUid, light, metadata); + // Atmos + var atmos = EnsureComp(mapUid); + var moles = new float[Atmospherics.AdjustedNumberOfGases]; moles[(int) Gas.Oxygen] = 21.824779f; moles[(int) Gas.Nitrogen] = 82.10312f; - var mixture = new GasMixture(moles, Atmospherics.T20C); + var mixture = new GasMixture(2500) + { + Temperature = 293.15f, + Moles = moles, + }; - _atmos.SetMapAtmosphere(mapUid, false, mixture); + _atmos.SetMapAtmosphere(mapUid, false, mixture, atmos); } /// diff --git a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs index 06f1b6b154c..46b25163cc0 100644 --- a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs +++ b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs @@ -28,7 +28,7 @@ private void FireEmitter(EntityUid uid, ParticleAcceleratorPowerState strength, if (TryComp(emitted, out var particlePhys)) { var angle = _transformSystem.GetWorldRotation(uid, xformQuery); - _physicsSystem.SetBodyStatus(emitted, particlePhys, BodyStatus.InAir); + _physicsSystem.SetBodyStatus(particlePhys, BodyStatus.InAir); var velocity = angle.ToWorldVec() * 20f; if (TryComp(uid, out var phys)) diff --git a/Content.Server/Physics/Controllers/ChasingWalkSystem.cs b/Content.Server/Physics/Controllers/ChasingWalkSystem.cs index 618dd4156fc..215e7e3124e 100644 --- a/Content.Server/Physics/Controllers/ChasingWalkSystem.cs +++ b/Content.Server/Physics/Controllers/ChasingWalkSystem.cs @@ -97,6 +97,6 @@ private void ForceImpulse(EntityUid uid, ChasingWalkComponent component) var speed = delta.Length() > 0 ? delta.Normalized() * component.Speed : Vector2.Zero; _physics.SetLinearVelocity(uid, speed); - _physics.SetBodyStatus(uid, physics, BodyStatus.InAir); //If this is not done, from the explosion up close, the tesla will "Fall" to the ground, and almost stop moving. + _physics.SetBodyStatus(physics, BodyStatus.InAir); //If this is not done, from the explosion up close, the tesla will "Fall" to the ground, and almost stop moving. } } diff --git a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs index 76cf90c3693..c61599edfc9 100644 --- a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs +++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs @@ -37,7 +37,7 @@ private void OnDeviceUpdated(EntityUid uid, GasPowerReceiverComponent component, if (pipe.Air.Temperature <= component.MaxTemperature) { // we have enough gas, so we consume it and are powered - if (pipe.Air[(int) component.TargetGas] > component.MolesConsumedSec * timeDelta) + if (pipe.Air.Moles[(int) component.TargetGas] > component.MolesConsumedSec * timeDelta) { pipe.Air.AdjustMoles(component.TargetGas, -component.MolesConsumedSec * timeDelta); SetPowered(uid, component, true); diff --git a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs deleted file mode 100644 index 7b3a417c53f..00000000000 --- a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs +++ /dev/null @@ -1,188 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Actions.Events; -using Content.Shared.Psionics.Abilities; -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using static Content.Shared.Examine.ExamineSystemShared; -using Content.Shared.Popups; -using Robust.Server.Audio; -using Robust.Shared.Audio; -using Robust.Shared.Timing; -using Robust.Shared.Player; -using Content.Server.DoAfter; -using Content.Shared.Psionics.Events; -using Content.Server.Psionics; - -namespace Content.Server.Psionics.Abilities -{ - public sealed class MetapsionicPowerSystem : EntitySystem - { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly AudioSystem _audioSystem = default!; - - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnWidePowerUsed); - SubscribeLocalEvent(OnFocusedPowerUsed); - SubscribeLocalEvent(OnDoAfter); - } - - private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) - { - if (!TryComp(uid, out ActionsComponent? comp)) - return; - _actions.AddAction(uid, ref component.ActionWideMetapsionicEntity, component.ActionWideMetapsionic, component: comp); - _actions.AddAction(uid, ref component.ActionFocusedMetapsionicEntity, component.ActionFocusedMetapsionic, component: comp); - _actions.TryGetActionData(component.ActionWideMetapsionicEntity, out var actionData); - if (actionData is { UseDelay: not null }) - { - _actions.StartUseDelay(component.ActionWideMetapsionicEntity); - _actions.StartUseDelay(component.ActionFocusedMetapsionicEntity); - } - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.MetapsionicFeedback); - psionic.Amplification += 0.1f; - psionic.Dampening += 0.5f; - } - - } - - private void UpdateActions(EntityUid uid, MetapsionicPowerComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - _actions.StartUseDelay(component.ActionWideMetapsionicEntity); - _actions.StartUseDelay(component.ActionFocusedMetapsionicEntity); - } - - private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.ActionWideMetapsionicEntity); - _actions.RemoveAction(uid, component.ActionFocusedMetapsionicEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.MetapsionicFeedback); - psionic.Amplification -= 0.1f; - psionic.Dampening -= 0.5f; - } - } - - private void OnWidePowerUsed(EntityUid uid, MetapsionicPowerComponent component, WideMetapsionicPowerActionEvent args) - { - if (HasComp(uid)) - return; - - if (!TryComp(uid, out var psionic)) - return; - - foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range)) - { - if (HasComp(entity) && entity != uid && !HasComp(entity) && - !(HasComp(entity) && Transform(entity).ParentUid == uid)) - { - _popups.PopupEntity(Loc.GetString("metapsionic-pulse-success"), uid, uid, PopupType.LargeCaution); - args.Handled = true; - return; - } - } - _popups.PopupEntity(Loc.GetString("metapsionic-pulse-failure"), uid, uid, PopupType.Large); - _psionics.LogPowerUsed(uid, "metapsionic pulse", - (int) MathF.Round(2 * psionic.Amplification - psionic.Dampening), - (int) MathF.Round(4 * psionic.Amplification - psionic.Dampening)); - UpdateActions(uid, component); - args.Handled = true; - } - - private void OnFocusedPowerUsed(FocusedMetapsionicPowerActionEvent args) - { - if (!TryComp(args.Performer, out var psionic)) - return; - - if (HasComp(args.Target)) - return; - - if (!TryComp(args.Performer, out var component)) - return; - - var ev = new FocusedMetapsionicDoAfterEvent(_gameTiming.CurTime); - - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, component.UseDelay - psionic.Amplification, ev, args.Performer, args.Target, args.Performer) - { - BlockDuplicate = true, - BreakOnUserMove = true, - BreakOnTargetMove = true, - BreakOnDamage = true, - }, out var doAfterId); - - component.DoAfter = doAfterId; - - _popups.PopupEntity(Loc.GetString("focused-metapsionic-pulse-begin", ("entity", args.Target)), - args.Performer, - // TODO: Use LoS-based Filter when one is available. - Filter.Pvs(args.Performer).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(args.Performer, entity, ExamineRange, null)), - true, - PopupType.Medium); - - _audioSystem.PlayPvs(component.SoundUse, args.Performer, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); - _psionics.LogPowerUsed(args.Performer, "focused metapsionic pulse", - (int) MathF.Round(3 * psionic.Amplification - psionic.Dampening), - (int) MathF.Round(6 * psionic.Amplification - psionic.Dampening)); - args.Handled = true; - - UpdateActions(args.Performer, component); - } - - private void OnDoAfter(EntityUid uid, MetapsionicPowerComponent component, FocusedMetapsionicDoAfterEvent args) - { - if (!TryComp(args.Target, out var psychic)) - return; - - component.DoAfter = null; - - if (args.Target == null) return; - - if (TryComp(args.Target, out var swapped)) - { - _popups.PopupEntity(Loc.GetString(swapped.MindSwappedFeedback, ("entity", args.Target)), uid, uid, PopupType.LargeCaution); - return; - } - - if (args.Target == uid) - { - _popups.PopupEntity(Loc.GetString("metapulse-self", ("entity", args.Target)), uid, uid, PopupType.LargeCaution); - return; - } - - if (!HasComp(args.Target)) - { - _popups.PopupEntity(Loc.GetString("no-powers", ("entity", args.Target)), uid, uid, PopupType.LargeCaution); - return; - } - - if (HasComp(args.Target) & !HasComp(args.Target)) - { - _popups.PopupEntity(Loc.GetString("psychic-potential", ("entity", args.Target)), uid, uid, PopupType.LargeCaution); - return; - } - - foreach (var psychicFeedback in psychic.PsychicFeedback) - { - _popups.PopupEntity(Loc.GetString(psychicFeedback, ("entity", args.Target)), uid, uid, PopupType.LargeCaution); - } - - } - } -} diff --git a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs deleted file mode 100644 index 6958170a5c2..00000000000 --- a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ /dev/null @@ -1,179 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Player; -using Content.Server.Body.Components; -using Content.Server.Body.Systems; -using Content.Server.DoAfter; -using Content.Shared.Psionics.Abilities; -using Content.Shared.Actions; -using Content.Shared.Chemistry.Components; -using Content.Shared.DoAfter; -using Content.Shared.FixedPoint; -using Content.Shared.Mobs; -using Content.Shared.Popups; -using Content.Shared.Psionics.Events; -using Content.Shared.Examine; -using Robust.Shared.Timing; -using Content.Shared.Actions.Events; -using Robust.Server.Audio; - -namespace Content.Server.Psionics.Abilities -{ - public sealed class PsionicRegenerationPowerSystem : EntitySystem - { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; - [Dependency] private readonly AudioSystem _audioSystem = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly ExamineSystemShared _examine = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - SubscribeLocalEvent(OnMobStateChangedEvent); - SubscribeLocalEvent(OnDispelled); - SubscribeLocalEvent(OnDoAfter); - } - - private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); - _actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.RegenerationFeedback); - psionic.Amplification += 0.5f; - psionic.Dampening += 0.5f; - } - } - - private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) - { - if (!TryComp(uid, out var psionic)) - return; - - var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime); - var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid); - - //Prevent the power from ignoring its own cooldown - _actions.TryGetActionData(component.PsionicRegenerationActionEntity, out var actionData); - var curTime = _gameTiming.CurTime; - if (actionData != null && actionData.Cooldown.HasValue && actionData.Cooldown.Value.End > curTime) - return; - - _doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId); - - component.DoAfter = doAfterId; - - _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)), uid, PopupType.Medium); - _audioSystem.PlayPvs(component.SoundUse, uid, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); - - _psionics.LogPowerUsed(uid, "psionic regeneration", - (int) Math.Round(6 * psionic.Amplification - psionic.Dampening), - (int) Math.Round(8 * psionic.Amplification - psionic.Dampening)); - - args.Handled = true; - } - - /// - /// Regenerators automatically activate upon crit, provided the power was off cooldown at that exact point in time. - /// Self-rescusitation is also far more costly, and extremely obvious - /// - /// - /// - /// - private void OnMobStateChangedEvent(EntityUid uid, PsionicRegenerationPowerComponent component, MobStateChangedEvent args) - { - if (!TryComp(uid, out var psionic)) - return; - - if (HasComp(uid)) - return; - - if (args.NewMobState is MobState.Critical) - { - _actions.TryGetActionData(component.PsionicRegenerationActionEntity, out var actionData); - var curTime = _gameTiming.CurTime; - if (actionData != null && actionData.Cooldown.HasValue && actionData.Cooldown.Value.End > curTime) - return; - - if (actionData is { UseDelay: not null }) - { - component.SelfRevive = true; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, uid, component.UseDelay, new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime), uid, args.Target, uid) - { - BreakOnUserMove = false, - BreakOnTargetMove = false, - BreakOnWeightlessMove = false, - BreakOnDamage = false, - RequireCanInteract = false, - }); - _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-self-revive", ("entity", uid)), uid, PopupType.MediumCaution); - _audioSystem.PlayPvs(component.SoundUse, uid, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); - - _psionics.LogPowerUsed(uid, "psionic regeneration", - (int) Math.Round(10 * psionic.Amplification - 2 * psionic.Dampening), - (int) Math.Round(20 * psionic.Amplification - 2 * psionic.Dampening)); - - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); - } - } - } - - private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PsionicRegenerationActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.RegenerationFeedback); - psionic.Amplification -= 0.5f; - psionic.Dampening -= 0.5f; - } - } - - private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args) - { - if (component.DoAfter == null) - return; - - _doAfterSystem.Cancel(component.DoAfter); - component.DoAfter = null; - - args.Handled = true; - } - - private void OnDoAfter(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationDoAfterEvent args) - { - component.DoAfter = null; - - if (!TryComp(uid, out var psionic)) - return; - - if (!TryComp(uid, out var stream)) - return; - - var percentageComplete = Math.Min(1f, (_gameTiming.CurTime - args.StartedAt).TotalSeconds / component.UseDelay); - - var solution = new Solution(); - solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(Math.Min(component.EssenceAmount * percentageComplete + 10f * psionic.Dampening, 15f))); - _bloodstreamSystem.TryAddToChemicals(uid, solution, stream); - if (component.SelfRevive == true) - { - var critSolution = new Solution(); - critSolution.AddReagent("Epinephrine", MathF.Min(5 + 5 * psionic.Dampening, 15)); - _bloodstreamSystem.TryAddToChemicals(uid, critSolution, stream); - component.SelfRevive = false; - } - } - } -} diff --git a/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs deleted file mode 100644 index 77075dab206..00000000000 --- a/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Psionics.Abilities; -using Content.Server.Atmos.Components; -using Content.Server.Weapons.Ranged.Systems; -using Robust.Server.GameObjects; -using Content.Shared.Actions.Events; -using Content.Server.Explosion.Components; -using Content.Shared.Mobs.Components; -using Robust.Shared.Map; - -namespace Content.Server.Psionics.Abilities -{ - public sealed class PyrokinesisPowerSystem : EntitySystem - { - [Dependency] private readonly TransformSystem _xform = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly GunSystem _gunSystem = default!; - [Dependency] private readonly SharedTransformSystem _transformSystem = default!; - [Dependency] private readonly PhysicsSystem _physics = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId); - _actions.TryGetActionData( component.PyrokinesisActionEntity, out var actionData); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.PyrokinesisFeedback); - psionic.Amplification += 1f; - } - } - - private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.PyrokinesisFeedback); - psionic.Amplification -= 1f; - } - } - - private void OnPowerUsed(PyrokinesisPowerActionEvent args) - { - if (!TryComp(args.Performer, out var psionic)) - return; - - if (!HasComp(args.Performer)) - { - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(args.Performer); - - var mapPos = xform.Coordinates.ToMap(EntityManager, _xform); - var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _) - ? xform.Coordinates.WithEntityId(gridUid, EntityManager) - : new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position); - - var ent = Spawn("ProjectileAnomalyFireball", spawnCoords); - - if (TryComp(ent, out var fireball)) - { - fireball.MaxIntensity = (int) MathF.Round(20 * psionic.Amplification - 10 * psionic.Dampening); - - if (psionic.Amplification > 5 && EnsureComp(ent, out var ignite)) - { - ignite.FireStacks = 0.2f * psionic.Amplification - 0.1f * psionic.Dampening; - } - } - - var direction = args.Target.Position; - - _gunSystem.ShootProjectile(ent, direction, new System.Numerics.Vector2(0, 0), args.Performer, args.Performer, 20f); - - _psionics.LogPowerUsed(args.Performer, "pyrokinesis", - (int) MathF.Round(6f * psionic.Amplification - psionic.Dampening), - (int) MathF.Round(8f * psionic.Amplification - psionic.Dampening)); - args.Handled = true; - } - } - } -} diff --git a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs deleted file mode 100644 index e184b19396b..00000000000 --- a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Content.Server.Body.Systems; -using Content.Server.Body.Components; -using Content.Shared.Actions; -using Content.Shared.Chemistry.Components; -using Content.Shared.Bed.Sleep; -using Content.Shared.Psionics.Abilities; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; -using Content.Shared.Actions.Events; -using Content.Shared.FixedPoint; - -namespace Content.Server.Psionics.Abilities -{ - public sealed class MassSleepPowerSystem : EntitySystem - { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, RegenerativeStasisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.RegenerativeStasisActionEntity, component.RegenerativeStasisActionId); - _actions.TryGetActionData(component.RegenerativeStasisActionEntity, out var actionData); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.RegenerativeStasisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.RegenerativeStasisFeedback); - psionic.Amplification += 0.5f; - psionic.Dampening += 0.5f; - } - } - - private void OnShutdown(EntityUid uid, RegenerativeStasisPowerComponent component, ComponentShutdown args) - { - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.RegenerativeStasisFeedback); - psionic.Amplification -= 0.5f; - psionic.Dampening -= 0.5f; - } - _actions.RemoveAction(uid, component.RegenerativeStasisActionEntity); - } - - private void OnPowerUsed(EntityUid uid, RegenerativeStasisPowerComponent component, RegenerativeStasisPowerActionEvent args) - { - if (TryComp(uid, out var psionic) - && !HasComp(uid) - && !HasComp(args.Target) - && TryComp(args.Target, out var stream)) - { - var solution = new Solution(); - solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(MathF.Min(2.5f * psionic.Amplification + psionic.Dampening, 15f))); - solution.AddReagent("Epinephrine", FixedPoint2.New(MathF.Min(2.5f * psionic.Dampening + psionic.Amplification, 15f))); - _bloodstreamSystem.TryAddToChemicals(args.Target, solution, stream); - EnsureComp(args.Target); - - _psionics.LogPowerUsed(uid, "regenerative stasis", - (int) Math.Round(4 * psionic.Amplification - psionic.Dampening), - (int) Math.Round(6 * psionic.Amplification - psionic.Dampening)); - args.Handled = true; - } - } - } -} diff --git a/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs deleted file mode 100644 index f03b001fc70..00000000000 --- a/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Psionics.Abilities; -using Content.Shared.Mind.Components; -using Content.Shared.Actions.Events; -using Content.Shared.Mobs; -using Content.Shared.Storage.Components; - -namespace Content.Server.Psionics.Abilities -{ - public sealed class TelegnosisPowerSystem : EntitySystem - { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - SubscribeLocalEvent(OnMindRemoved); - SubscribeLocalEvent(OnDispelled); - SubscribeLocalEvent(OnMobstateChanged); - SubscribeLocalEvent(OnStorageInsertAttempt); - } - - private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId ); - _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Add(component); - psionic.PsychicFeedback.Add(component.TelegnosisFeedback); - psionic.Amplification += 0.3f; - psionic.Dampening += 0.3f; - } - } - - private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - psionic.PsychicFeedback.Remove(component.TelegnosisFeedback); - psionic.Amplification -= 0.3f; - psionic.Dampening -= 0.3f; - } - } - - private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) - { - if (!TryComp(uid, out var psionic)) - return; - - if (HasComp(uid)) - return; - - var projection = Spawn(component.Prototype, Transform(uid).Coordinates); - Transform(projection).AttachToGridOrMap(); - component.OriginalEntity = uid; - component.IsProjecting = true; - component.ProjectionUid = projection; - _mindSwap.Swap(uid, projection); - - if (EnsureComp(projection, out var projectionComponent)) - projectionComponent.OriginalEntity = uid; - - _psionics.LogPowerUsed(uid, "telegnosis", - (int) Math.Round(8f * psionic.Amplification - psionic.Dampening), - (int) Math.Round(12f * psionic.Amplification - psionic.Dampening)); - - args.Handled = true; - } - private void OnMindRemoved(EntityUid uid, TelegnosticProjectionComponent component, MindRemovedMessage args) - { - if (TryComp(component.OriginalEntity, out var originalEntity)) - originalEntity.IsProjecting = false; - - QueueDel(uid); - } - - private void OnDispelled(EntityUid uid, TelegnosisPowerComponent component, DispelledEvent args) - { - if (component.IsProjecting) - _mindSwap.Swap(uid, component.ProjectionUid); - } - - private void OnMobstateChanged(EntityUid uid, TelegnosisPowerComponent component, MobStateChangedEvent args) - { - if (component.IsProjecting && args.NewMobState is MobState.Critical - || component.IsProjecting && args.NewMobState is MobState.Dead) - _mindSwap.Swap(uid, component.ProjectionUid); - } - - private void OnStorageInsertAttempt(EntityUid uid, TelegnosisPowerComponent component, InsertIntoEntityStorageAttemptEvent args) - { - if (component.IsProjecting) - _mindSwap.Swap(uid, component.ProjectionUid); - } - } -} diff --git a/Content.Server/Psionics/PotentialPsionicComponent.cs b/Content.Server/Psionics/PotentialPsionicComponent.cs deleted file mode 100644 index e874296a4c3..00000000000 --- a/Content.Server/Psionics/PotentialPsionicComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PotentialPsionicComponent : Component - { - /// - /// The base chance of an entity rolling psychic powers, which is increased by other modifiers such as glimmer. - /// - /// - /// I have increased this to 10% up from its original value of 2%, because I estimate that most people won't take the Latent Psychic trait - /// Simply because they might not even know it exists - /// - [DataField("chance")] - public float Chance = 0.10f; - - /// - /// YORO (you only reroll once) - /// - public bool Rerolled = false; - } -} diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index e2b17b58724..2776db2283a 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -125,7 +125,11 @@ protected override async Task Process() air.Gases.CopyTo(moles, 0); var atmos = _entManager.EnsureComponent(mapUid); _entManager.System().SetMapSpace(mapUid, air.Space, atmos); - _entManager.System().SetMapGasMixture(mapUid, new GasMixture(moles, mission.Temperature), atmos); + _entManager.System().SetMapGasMixture(mapUid, new GasMixture(2500) + { + Temperature = mission.Temperature, + Moles = moles, + }, atmos); if (mission.Color != null) { diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index e4e4534b0c5..f9ceab8f7b1 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -371,8 +371,8 @@ private void UpdateFTLStarting(Entity entity) Enable(uid, component: body); _physics.SetLinearVelocity(uid, new Vector2(0f, 20f), body: body); _physics.SetAngularVelocity(uid, 0f, body: body); - _physics.SetLinearDamping(uid, body, 0f); - _physics.SetAngularDamping(uid, body, 0f); + _physics.SetLinearDamping(body, 0f); + _physics.SetAngularDamping(body, 0f); _dockSystem.SetDockBolts(uid, true); _console.RefreshShuttleConsoles(uid); @@ -426,8 +426,8 @@ private void UpdateFTLArriving(Entity entity) _physics.SetLinearVelocity(uid, Vector2.Zero, body: body); _physics.SetAngularVelocity(uid, 0f, body: body); - _physics.SetLinearDamping(uid, body, entity.Comp2.LinearDamping); - _physics.SetAngularDamping(uid, body, entity.Comp2.AngularDamping); + _physics.SetLinearDamping(body, entity.Comp2.LinearDamping); + _physics.SetAngularDamping(body, entity.Comp2.AngularDamping); var target = entity.Comp1.TargetCoordinates; diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 7c42753a7de..8d44dae4b2e 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -129,10 +129,10 @@ public void Enable(EntityUid uid, FixturesComponent? manager = null, PhysicsComp return; _physics.SetBodyType(uid, BodyType.Dynamic, manager: manager, body: component); - _physics.SetBodyStatus(uid, component, BodyStatus.InAir); + _physics.SetBodyStatus(component, BodyStatus.InAir); _physics.SetFixedRotation(uid, false, manager: manager, body: component); - _physics.SetLinearDamping(uid, component, shuttle.LinearDamping); - _physics.SetAngularDamping(uid, component, shuttle.AngularDamping); + _physics.SetLinearDamping(component, shuttle.LinearDamping); + _physics.SetAngularDamping(component, shuttle.AngularDamping); } public void Disable(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? component = null) @@ -141,7 +141,7 @@ public void Disable(EntityUid uid, FixturesComponent? manager = null, PhysicsCom return; _physics.SetBodyType(uid, BodyType.Static, manager: manager, body: component); - _physics.SetBodyStatus(uid, component, BodyStatus.OnGround); + _physics.SetBodyStatus(component, BodyStatus.OnGround); _physics.SetFixedRotation(uid, true, manager: manager, body: component); } diff --git a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs index ba07375699b..7784db015d3 100644 --- a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs +++ b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs @@ -13,7 +13,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Timing; -using Content.Shared.Psionics.Abilities; //EE - Summary: for the telegnostic projection. +using Content.Shared.Abilities.Psionics; //Nyano - Summary: for the telegnostic projection. namespace Content.Server.Singularity.EntitySystems; @@ -39,7 +39,7 @@ public override void Initialize() SubscribeLocalEvent(PreventConsume); SubscribeLocalEvent(PreventConsume); - SubscribeLocalEvent(PreventConsume); ///EE - Summary: the telegnositic projection has the same trait as ghosts. + SubscribeLocalEvent(PreventConsume); ///Nyano - Summary: the telegnositic projection has the same trait as ghosts. SubscribeLocalEvent(PreventConsume); SubscribeLocalEvent(OnHorizonMapInit); SubscribeLocalEvent(OnStartCollide); diff --git a/Content.Server/Spreader/SpreaderSystem.cs b/Content.Server/Spreader/SpreaderSystem.cs index 5b2f3298a2b..89951718236 100644 --- a/Content.Server/Spreader/SpreaderSystem.cs +++ b/Content.Server/Spreader/SpreaderSystem.cs @@ -18,7 +18,6 @@ namespace Content.Server.Spreader; /// public sealed class SpreaderSystem : EntitySystem { - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly SharedMapSystem _map = default!; @@ -34,8 +33,6 @@ public sealed class SpreaderSystem : EntitySystem // TODO PERFORMANCE Assign each prototype to an index and convert dictionary to array private readonly Dictionary> _gridUpdates = []; - private EntityQuery _query; - public const float SpreadCooldownSeconds = 1; [ValidatePrototypeId] @@ -50,8 +47,6 @@ public override void Initialize() SubscribeLocalEvent(OnTerminating); SetupPrototypes(); - - _query = GetEntityQuery(); } private void OnPrototypeReload(PrototypesReloadedEventArgs obj) @@ -71,7 +66,13 @@ private void SetupPrototypes() private void OnAirtightChanged(ref AirtightChanged ev) { - ActivateSpreadableNeighbors(ev.Entity, ev.Position); + var neighbors = GetSpreadableNeighbors(ev.Entity, ev.Airtight, ev.Position); + + foreach (var neighbor in neighbors) + { + if (!TerminatingOrDeleted(neighbor)) + EnsureComp(neighbor); + } } private void OnGridInit(GridInitializeEvent ev) @@ -81,7 +82,13 @@ private void OnGridInit(GridInitializeEvent ev) private void OnTerminating(Entity entity, ref EntityTerminatingEvent args) { - ActivateSpreadableNeighbors(entity); + var neighbors = GetSpreadableNeighbors(entity); + + foreach (var neighbor in neighbors) + { + if (!TerminatingOrDeleted(neighbor)) + EnsureComp(neighbor); + } } /// @@ -247,7 +254,8 @@ public void GetNeighbors(EntityUid uid, TransformComponent comp, ProtoId - /// This function activates all spreaders that are adjacent to a given entity. This also activates other spreaders - /// on the same tile as the current entity (for thin airtight entities like windoors). + /// Given an entity, this returns a list of all adjacent entities with a . /// - public void ActivateSpreadableNeighbors(EntityUid uid, (EntityUid Grid, Vector2i Tile)? position = null) + public List GetSpreadableNeighbors(EntityUid uid, AirtightComponent? comp = null, + (EntityUid Grid, Vector2i Tile)? position = null) { + Resolve(uid, ref comp, false); + var neighbors = new List(); + Vector2i tile; EntityUid ent; MapGridComponent? grid; @@ -303,40 +315,37 @@ public void ActivateSpreadableNeighbors(EntityUid uid, (EntityUid Grid, Vector2i { var transform = Transform(uid); if (!TryComp(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value)) - return; - + return neighbors; tile = _map.TileIndicesFor(transform.GridUid.Value, grid, transform.Coordinates); ent = transform.GridUid.Value; } else { if (!TryComp(position.Value.Grid, out grid)) - return; - (ent, tile) = position.Value; + return neighbors; + tile = position.Value.Tile; + ent = position.Value.Grid; } - var anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, tile); - while (anchored.MoveNext(out var entity)) - { - if (entity == ent) - continue; - DebugTools.Assert(Transform(entity.Value).Anchored); - if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value)) - EnsureComp(entity.Value); - } + var spreaderQuery = GetEntityQuery(); for (var i = 0; i < Atmospherics.Directions; i++) { var direction = (AtmosDirection) (1 << i); - var adjacentTile = SharedMapSystem.GetDirection(tile, direction.ToDirection()); - anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, adjacentTile); + if (comp != null && !comp.AirBlockedDirection.IsFlagSet(direction)) + continue; - while (anchored.MoveNext(out var entity)) + var directionEnumerator = + _map.GetAnchoredEntitiesEnumerator(ent, grid, SharedMapSystem.GetDirection(tile, direction.ToDirection())); + + while (directionEnumerator.MoveNext(out var entity)) { DebugTools.Assert(Transform(entity.Value).Anchored); - if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value)) - EnsureComp(entity.Value); + if (spreaderQuery.HasComponent(entity) && !TerminatingOrDeleted(entity.Value)) + neighbors.Add(entity.Value); } } + + return neighbors; } } diff --git a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs index ad56479b379..192a620c9fc 100644 --- a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs +++ b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs @@ -68,9 +68,9 @@ protected override void ActiveTick(EntityUid uid, MeteorSwarmRuleComponent compo var spawnPosition = new MapCoordinates(center + offset, mapId); var meteor = Spawn("MeteorLarge", spawnPosition); var physics = EntityManager.GetComponent(meteor); - _physics.SetBodyStatus(meteor, physics, BodyStatus.InAir); - _physics.SetLinearDamping(meteor, physics, 0f); - _physics.SetAngularDamping(meteor, physics, 0f); + _physics.SetBodyStatus(physics, BodyStatus.InAir); + _physics.SetLinearDamping(physics, 0f); + _physics.SetAngularDamping(physics, 0f); _physics.ApplyLinearImpulse(meteor, -offset.Normalized() * component.MeteorVelocity * physics.Mass, body: physics); _physics.ApplyAngularImpulse( meteor, diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 53128aade31..daadd4b518b 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -16,7 +16,7 @@ using Content.Server.Roles; using Content.Server.Speech.Components; using Content.Server.Temperature.Components; -using Content.Shared.Psionics.Abilities; +using Content.Shared.Abilities.Psionics; using Content.Shared.CombatMode; using Content.Shared.CombatMode.Pacification; using Content.Shared.Damage; @@ -59,7 +59,9 @@ public sealed partial class ZombieSystem [Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly ActionsSystem _actions = default!; // DeltaV - No psionic zombies /// /// Handles an entity turning into a zombie when they die or go into crit diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 6a8587ca239..7460e08e46f 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -8,6 +8,11 @@ namespace Content.Shared.Atmos /// public static class Atmospherics { + static Atmospherics() + { + AdjustedNumberOfGases = MathHelper.NextMultipleOf(TotalNumberOfGases, 4); + } + #region ATMOS /// /// The universal gas constant, in kPa*L/(K*mol) @@ -178,7 +183,7 @@ public static class Atmospherics /// This is the actual length of the gases arrays in mixtures. /// Set to the closest multiple of 4 relative to for SIMD reasons. /// - public const int AdjustedNumberOfGases = ((TotalNumberOfGases + 3) / 4) * 4; + public static readonly int AdjustedNumberOfGases; /// /// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope) diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index f468724db33..eb0079eb358 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -66,10 +66,7 @@ public static Vector2i GetGasChunkIndices(Vector2i indices) [Serializable, NetSerializable] public readonly struct GasOverlayData : IEquatable { - [ViewVariables] public readonly byte FireState; - - [ViewVariables] public readonly byte[] Opacity; // TODO change fire color based on temps diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 8172947a039..b58bdf83e49 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -358,7 +358,7 @@ public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid if (TryComp(buckleUid, out var physics)) { - _physics.ResetDynamics(buckleUid, physics); + _physics.ResetDynamics(physics); } if (!buckleComp.PullStrap && TryComp(strapUid, out var toPullable)) diff --git a/Content.Shared/Maps/ContentTileDefinition.cs b/Content.Shared/Maps/ContentTileDefinition.cs index 32f5db0e821..ce7980509eb 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -81,11 +81,7 @@ public sealed partial class ContentTileDefinition : IPrototype, IInheritingProto [DataField("itemDrop", customTypeSerializer:typeof(PrototypeIdSerializer))] public string ItemDropPrototypeName { get; private set; } = "FloorTileItemSteel"; - // TODO rename data-field in yaml - /// - /// Whether or not the tile is exposed to the map's atmosphere. - /// - [DataField("isSpace")] public bool MapAtmosphere { get; private set; } + [DataField("isSpace")] public bool IsSpace { get; private set; } /// /// Friction override for mob mover in diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index 58a5d133b55..a87b8c97d15 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -11,6 +11,22 @@ namespace Content.Shared.Maps // That, or make the interface arguments non-optional so people stop failing to pass them in. public static class TurfHelpers { + /// + /// Attempts to get the turf at map indices with grid id or null if no such turf is found. + /// + public static TileRef GetTileRef(this Vector2i vector2i, EntityUid gridId, IMapManager? mapManager = null) + { + mapManager ??= IoCManager.Resolve(); + + if (!mapManager.TryGetGrid(gridId, out var grid)) + return default; + + if (!grid.TryGetTileRef(vector2i, out var tile)) + return default; + + return tile; + } + /// /// Attempts to get the turf at a certain coordinates or null if no such turf is found. /// @@ -51,7 +67,7 @@ public static ContentTileDefinition GetContentTileDefinition(this Tile tile, ITi /// public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null) { - return tile.GetContentTileDefinition(tileDefinitionManager).MapAtmosphere; + return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace; } /// @@ -99,6 +115,15 @@ public static IEnumerable GetEntitiesInTile(this EntityCoordinates co return GetEntitiesInTile(turf.Value, flags, lookupSystem); } + /// + /// Helper that returns all entities in a turf. + /// + [Obsolete("Use the lookup system")] + public static IEnumerable GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null) + { + return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem); + } + /// /// Checks if a turf has something dense on it. /// diff --git a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs index 8c42511f846..abe12b79d1a 100644 --- a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs +++ b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs @@ -92,7 +92,7 @@ private void SetupUser(EntityUid user, EntityUid jetpackUid) _mover.SetRelay(user, jetpackUid); if (TryComp(user, out var physics)) - _physics.SetBodyStatus(user, physics, BodyStatus.InAir); + _physics.SetBodyStatus(physics, BodyStatus.InAir); userComp.Jetpack = jetpackUid; } @@ -103,7 +103,7 @@ private void RemoveUser(EntityUid uid) return; if (TryComp(uid, out var physics)) - _physics.SetBodyStatus(uid, physics, BodyStatus.OnGround); + _physics.SetBodyStatus(physics, BodyStatus.OnGround); RemComp(uid); } diff --git a/Content.Shared/Psionics/Abilities/AcceptPsionicsEuiMessage.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/AcceptPsionicsEuiMessage.cs similarity index 100% rename from Content.Shared/Psionics/Abilities/AcceptPsionicsEuiMessage.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/AcceptPsionicsEuiMessage.cs diff --git a/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs similarity index 77% rename from Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs index c3702880375..ce86111fc4b 100644 --- a/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs @@ -1,6 +1,6 @@ using Content.Shared.Damage; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { /// /// Takes damage when dispelled. @@ -9,7 +9,7 @@ namespace Content.Shared.Psionics.Abilities public sealed partial class DamageOnDispelComponent : Component { [DataField("damage", required: true)] - public DamageSpecifier Damage = default!; + public DamageSpecifier Damage = default!; [DataField("variance")] public float Variance = 0.5f; diff --git a/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs similarity index 79% rename from Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs index 518a28b0967..cd887866364 100644 --- a/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs @@ -1,22 +1,20 @@ +using Content.Shared.Actions; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class DispelPowerComponent : Component { [DataField("range")] public float Range = 10f; - + [DataField("dispelActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? DispelActionId = "ActionDispel"; [DataField("dispelActionEntity")] public EntityUid? DispelActionEntity; - - [DataField("dispelFeedback")] - public string DispelFeedback = "dispel-feedback"; } } diff --git a/Content.Shared/Psionics/Abilities/Dispel/DispellableComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs similarity index 69% rename from Content.Shared/Psionics/Abilities/Dispel/DispellableComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs index 4bb5ee653d2..40352004187 100644 --- a/Content.Shared/Psionics/Abilities/Dispel/DispellableComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class DispellableComponent : Component diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs new file mode 100644 index 00000000000..7d611c63dac --- /dev/null +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class MassSleepPowerComponent : Component + { + public float Radius = 1.25f; + [DataField("massSleepActionId", + customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? MassSleepActionId = "ActionMassSleep"; + + [DataField("massSleepActionEntity")] + public EntityUid? MassSleepActionEntity; + } +} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs new file mode 100644 index 00000000000..e36a3c70e8a --- /dev/null +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs @@ -0,0 +1,59 @@ +using Content.Shared.Actions; +using Content.Shared.Bed.Sleep; +using Content.Shared.Magic.Events; +using Content.Shared.Damage; +using Content.Shared.Mobs.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Content.Shared.Mind; +using Content.Shared.Actions.Events; + +namespace Content.Shared.Abilities.Psionics +{ + public sealed class MassSleepPowerSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, MassSleepPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.MassSleepActionEntity, component.MassSleepActionId ); + _actions.TryGetActionData( component.MassSleepActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.MassSleepActionEntity); + if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + psionic.PsionicAbility = component.MassSleepActionEntity; + } + + private void OnShutdown(EntityUid uid, MassSleepPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.MassSleepActionEntity); + } + + private void OnPowerUsed(EntityUid uid, MassSleepPowerComponent component, MassSleepPowerActionEvent args) + { + foreach (var entity in _lookup.GetEntitiesInRange(args.Target, component.Radius)) + { + if (HasComp(entity) && entity != uid && !HasComp(entity)) + { + if (TryComp(entity, out var damageable) && damageable.DamageContainerID == "Biological") + EnsureComp(entity); + } + } + _psionics.LogPowerUsed(uid, "mass sleep"); + args.Handled = true; + } + } +} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs new file mode 100644 index 00000000000..c9d0130221a --- /dev/null +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class MetapsionicPowerComponent : Component + { + [DataField("range")] + public float Range = 5f; + + public InstantActionComponent? MetapsionicPowerAction = null; + [DataField("metapsionicActionId", + customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? MetapsionicActionId = "ActionMetapsionic"; + + [DataField("metapsionicActionEntity")] + public EntityUid? MetapsionicActionEntity; + } +} diff --git a/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs similarity index 76% rename from Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs index 94b73c41e38..6a3fc811c89 100644 --- a/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class MindSwapPowerComponent : Component @@ -12,8 +12,5 @@ public sealed partial class MindSwapPowerComponent : Component [DataField("mindSwapActionEntity")] public EntityUid? MindSwapActionEntity; - - [DataField("mindSwapFeedback")] - public string MindSwapFeedback = "mind-swap-feedback"; } } diff --git a/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs similarity index 77% rename from Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs index 997db65e1b1..0e91894b1dc 100644 --- a/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class NoosphericZapPowerComponent : Component @@ -13,8 +13,5 @@ public sealed partial class NoosphericZapPowerComponent : Component [DataField("noosphericZapActionEntity")] public EntityUid? NoosphericZapActionEntity; - - [DataField("noosphericZapFeedback")] - public string NoosphericZapFeedback = "noospheric-zap-feedback"; } } diff --git a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs similarity index 71% rename from Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs index d9c36f5b22a..3e198aa9303 100644 --- a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class PsionicInvisibilityPowerComponent : Component @@ -12,11 +12,5 @@ public sealed partial class PsionicInvisibilityPowerComponent : Component [DataField("psionicInvisibilityActionEntity")] public EntityUid? PsionicInvisibilityActionEntity; - - [DataField("InvisibilityFeedback")] - public string InvisibilityFeedback = "invisibility-feedback"; - - [DataField("UseTimer")] - public float UseTimer = 30f; } } diff --git a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs similarity index 94% rename from Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs index 2a9dd7642ba..9037b8bcdfe 100644 --- a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs @@ -1,7 +1,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class PsionicInvisibilityUsedComponent : Component diff --git a/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs similarity index 76% rename from Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs index 3184bf7de5b..4a62e84d191 100644 --- a/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs @@ -3,7 +3,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class PsionicRegenerationPowerComponent : Component @@ -12,7 +12,7 @@ public sealed partial class PsionicRegenerationPowerComponent : Component public DoAfterId? DoAfter; [DataField("essence")] - public float EssenceAmount = 10; + public float EssenceAmount = 20; [DataField("useDelay")] public float UseDelay = 8f; @@ -26,12 +26,6 @@ public sealed partial class PsionicRegenerationPowerComponent : Component [DataField("psionicRegenerationActionEntity")] public EntityUid? PsionicRegenerationActionEntity; - - [DataField("regenerationFeedback")] - public string RegenerationFeedback = "regeneration-feedback"; - - [DataField("selfRevive")] - public bool SelfRevive { get; set; } = false; } } diff --git a/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs similarity index 79% rename from Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs index 1f88741b9a9..28425afdb4c 100644 --- a/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class PyrokinesisPowerComponent : Component @@ -14,8 +14,5 @@ public sealed partial class PyrokinesisPowerComponent : Component [DataField("pyrokinesisActionEntity")] public EntityUid? PyrokinesisActionEntity; - - [DataField("pyrokinesisFeedback")] - public string PyrokinesisFeedback = "pyrokinesis-feedback"; } } diff --git a/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs similarity index 73% rename from Content.Shared/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs index f1a71332b18..51958822a41 100644 --- a/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs @@ -3,7 +3,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class TelegnosisPowerComponent : Component @@ -19,11 +19,5 @@ public sealed partial class TelegnosisPowerComponent : Component [DataField("telegnosisActionEntity")] public EntityUid? TelegnosisActionEntity; - - [DataField("telegnosisFeedback")] - public string TelegnosisFeedback = "telegnosis-feedback"; - public EntityUid OriginalEntity = default!; - public EntityUid ProjectionUid = default!; - public bool IsProjecting = false; } -} +} \ No newline at end of file diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs new file mode 100644 index 00000000000..9d627cb42d8 --- /dev/null +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class TelegnosticProjectionComponent : Component + {} +} \ No newline at end of file diff --git a/Content.Shared/Psionics/Items/ClothingGrantPsionicPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs similarity index 84% rename from Content.Shared/Psionics/Items/ClothingGrantPsionicPowerComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs index f09efc3064c..4cbb05c8395 100644 --- a/Content.Shared/Psionics/Items/ClothingGrantPsionicPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class ClothingGrantPsionicPowerComponent : Component diff --git a/Content.Shared/Psionics/Items/HeadCageComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCageComponent.cs similarity index 96% rename from Content.Shared/Psionics/Items/HeadCageComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCageComponent.cs index c03241e47c7..acaa832860f 100644 --- a/Content.Shared/Psionics/Items/HeadCageComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCageComponent.cs @@ -1,7 +1,7 @@ using System.Threading; using Robust.Shared.Audio; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class HeadCageComponent : Component diff --git a/Content.Shared/Psionics/Items/HeadCagedComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCagedComponent.cs similarity index 81% rename from Content.Shared/Psionics/Items/HeadCagedComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCagedComponent.cs index 0f826f7a05e..f8af46b8878 100644 --- a/Content.Shared/Psionics/Items/HeadCagedComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCagedComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] /// diff --git a/Content.Shared/Psionics/Items/PsionicItemsSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs similarity index 98% rename from Content.Shared/Psionics/Items/PsionicItemsSystem.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs index 950353c5dfb..f88acf61f3c 100644 --- a/Content.Shared/Psionics/Items/PsionicItemsSystem.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs @@ -2,7 +2,7 @@ using Content.Shared.Clothing.Components; using Content.Shared.StatusEffect; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { public sealed class PsionicItemsSystem : EntitySystem { diff --git a/Content.Shared/Psionics/Items/TinfoilHatComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs similarity index 90% rename from Content.Shared/Psionics/Items/TinfoilHatComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs index 6ef7bdc823b..5086b9f4977 100644 --- a/Content.Shared/Psionics/Items/TinfoilHatComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class TinfoilHatComponent : Component diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs similarity index 51% rename from Content.Shared/Psionics/PsionicComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs index 9a06e54cb31..9091e03cfc3 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs @@ -1,6 +1,7 @@ +using Content.Shared.Actions; using Robust.Shared.GameStates; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent, NetworkedComponent] public sealed partial class PsionicComponent : Component @@ -14,17 +15,6 @@ public sealed partial class PsionicComponent : Component public bool Removable = true; [DataField("activePowers")] - public List ActivePowers = new(); - - [DataField("psychicFeedback")] - public List PsychicFeedback = new(); - - [DataField("amplification")] - public float Amplification = 0.1f; - - [DataField("dampening")] - public float Dampening = 0.1f; - public bool Telepath = false; - public bool InnatePsiChecked = false; + public HashSet ActivePowers = new(); } } diff --git a/Content.Shared/Psionics/PsionicInsulationComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs similarity index 82% rename from Content.Shared/Psionics/PsionicInsulationComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs index 2ab054b1f8f..12370da5ae4 100644 --- a/Content.Shared/Psionics/PsionicInsulationComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class PsionicInsulationComponent : Component diff --git a/Content.Shared/Psionics/PsionicsDisabledComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs similarity index 84% rename from Content.Shared/Psionics/PsionicsDisabledComponent.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs index 00cf5506523..28e7157a9d2 100644 --- a/Content.Shared/Psionics/PsionicsDisabledComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs @@ -1,6 +1,6 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { /// /// Only use this for the status effect, please. diff --git a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs similarity index 97% rename from Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs rename to Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs index 603c5188a52..2739d5ba31a 100644 --- a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs @@ -7,7 +7,7 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; -namespace Content.Shared.Psionics.Abilities +namespace Content.Shared.Abilities.Psionics { public sealed class SharedPsionicAbilitiesSystem : EntitySystem { @@ -73,7 +73,7 @@ public void SetPsionicsThroughEligibility(EntityUid uid) if (actionData == null) return; - _actions.SetEnabled(uid, IsEligibleForPsionics(uid)); + _actions.SetEnabled(actionData.Owner, IsEligibleForPsionics(uid)); } private bool IsEligibleForPsionics(EntityUid uid) diff --git a/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs new file mode 100644 index 00000000000..6666ee48d6c --- /dev/null +++ b/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs @@ -0,0 +1,2 @@ +namespace Content.Shared.Actions.Events; +public sealed partial class MassSleepPowerActionEvent : WorldTargetActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs index b7c3c8ad2d6..b28801efe74 100644 --- a/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs +++ b/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs @@ -1,3 +1,2 @@ namespace Content.Shared.Actions.Events; -public sealed partial class WideMetapsionicPowerActionEvent : InstantActionEvent { } -public sealed partial class FocusedMetapsionicPowerActionEvent : EntityTargetActionEvent { } +public sealed partial class MetapsionicPowerActionEvent : InstantActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs index 4639aadd55b..896ec0bb63d 100644 --- a/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs +++ b/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs @@ -1,4 +1,2 @@ namespace Content.Shared.Actions.Events; -public sealed partial class PyrokinesisPowerActionEvent : WorldTargetActionEvent {} - - +public sealed partial class PyrokinesisPowerActionEvent : EntityTargetActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs deleted file mode 100644 index 4435f475a44..00000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class RegenerativeStasisPowerActionEvent : EntityTargetActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Psionics/Events.cs b/Content.Shared/Nyanotrasen/Psionics/Events.cs new file mode 100644 index 00000000000..cf9a50c6e18 --- /dev/null +++ b/Content.Shared/Nyanotrasen/Psionics/Events.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Psionics.Events +{ + [Serializable, NetSerializable] + public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent + { + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + private PsionicRegenerationDoAfterEvent() + { + } + + public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) + { + StartedAt = startedAt; + } + + public override DoAfterEvent Clone() => this; + } + + [Serializable, NetSerializable] + public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent + { + } +} diff --git a/Content.Shared/Psionics/Glimmer/GlimmerSystem.cs b/Content.Shared/Nyanotrasen/Psionics/Glimmer/GlimmerSystem.cs similarity index 98% rename from Content.Shared/Psionics/Glimmer/GlimmerSystem.cs rename to Content.Shared/Nyanotrasen/Psionics/Glimmer/GlimmerSystem.cs index 8be02f936a9..31af85bbb51 100644 --- a/Content.Shared/Psionics/Glimmer/GlimmerSystem.cs +++ b/Content.Shared/Nyanotrasen/Psionics/Glimmer/GlimmerSystem.cs @@ -40,7 +40,7 @@ public GlimmerTier GetGlimmerTier(int? glimmer = null) if (glimmer == null) glimmer = Glimmer; - return glimmer switch + return (glimmer) switch { <= 49 => GlimmerTier.Minimal, >= 50 and <= 99 => GlimmerTier.Low, diff --git a/Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs b/Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs similarity index 100% rename from Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs rename to Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs diff --git a/Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs b/Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs similarity index 100% rename from Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs rename to Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs diff --git a/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs deleted file mode 100644 index 2fbfe18327e..00000000000 --- a/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Psionics.Abilities -{ - [RegisterComponent] - public sealed partial class MetapsionicPowerComponent : Component - { - [DataField("doAfter")] - public DoAfterId? DoAfter; - - [DataField("useDelay")] - public float UseDelay = 8f; - [DataField("soundUse")] - - public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Nyanotrasen/heartbeat_fast.ogg"); - - [DataField("range")] - public float Range = 5f; - - [DataField("actionWideMetapsionic", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ActionWideMetapsionic = "ActionWideMetapsionic"; - - [DataField("actionWideMetapsionicEntity")] - public EntityUid? ActionWideMetapsionicEntity; - - [DataField("actionFocusedMetapsionic", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ActionFocusedMetapsionic = "ActionFocusedMetapsionic"; - - [DataField("actionFocusedMetapsionicEntity")] - public EntityUid? ActionFocusedMetapsionicEntity; - - [DataField("metapsionicFeedback")] - public string MetapsionicFeedback = "metapsionic-feedback"; - } -} diff --git a/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs b/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs deleted file mode 100644 index 27a0903e224..00000000000 --- a/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Psionics.Abilities -{ - [RegisterComponent] - public sealed partial class RegenerativeStasisPowerComponent : Component - { - [DataField("regenerativeStasisActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? RegenerativeStasisActionId = "ActionRegenerativeStasis"; - - [DataField("regenerativeStasisActionEntity")] - public EntityUid? RegenerativeStasisActionEntity; - - [DataField("regenerativeStasisFeedback")] - public string RegenerativeStasisFeedback = "regenerative-stasis-feedback"; - } -} diff --git a/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs b/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs deleted file mode 100644 index bc18ff9f3c2..00000000000 --- a/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Content.Shared.Psionics.Abilities -{ - [RegisterComponent] - public sealed partial class TelegnosticProjectionComponent : Component - { - public EntityUid OriginalEntity = default!; - } -} diff --git a/Content.Shared/Psionics/Events.cs b/Content.Shared/Psionics/Events.cs deleted file mode 100644 index 45a00b5f048..00000000000 --- a/Content.Shared/Psionics/Events.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Robust.Shared.Serialization; -using Content.Shared.DoAfter; - -namespace Content.Shared.Psionics.Events -{ - [Serializable, NetSerializable] - public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent - { - [DataField("startedAt", required: true)] - public TimeSpan StartedAt; - - private PsionicRegenerationDoAfterEvent() - { - } - - public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) - { - StartedAt = startedAt; - } - - public override DoAfterEvent Clone() => this; - } - - [Serializable, NetSerializable] - public sealed partial class PsionicInvisibilityTimerEvent : DoAfterEvent - { - [DataField("startedAt", required: true)] - public TimeSpan StartedAt; - - private PsionicInvisibilityTimerEvent() - { - } - - public PsionicInvisibilityTimerEvent(TimeSpan startedAt) - { - StartedAt = startedAt; - } - - public override DoAfterEvent Clone() => this; - } - - [Serializable, NetSerializable] - public sealed partial class FocusedMetapsionicDoAfterEvent : DoAfterEvent - { - [DataField("startedAt", required: true)] - public TimeSpan StartedAt; - - private FocusedMetapsionicDoAfterEvent() - { - } - - public FocusedMetapsionicDoAfterEvent(TimeSpan startedAt) - { - StartedAt = startedAt; - } - - public override DoAfterEvent Clone() => this; - } - - [Serializable, NetSerializable] - public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent - { - } -} diff --git a/Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs b/Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs deleted file mode 100644 index 5c89f39354c..00000000000 --- a/Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Content.Shared.Psionics -{ - public readonly record struct PsionicInsulationEvent; -} diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index 01682863389..54294318315 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -160,7 +160,7 @@ public void TryThrow(EntityUid uid, } else { - _physics.SetBodyStatus(uid, physics, BodyStatus.InAir); + _physics.SetBodyStatus(physics, BodyStatus.InAir); } if (user == null) @@ -176,10 +176,10 @@ public void TryThrow(EntityUid uid, { var msg = new ThrowPushbackAttemptEvent(); RaiseLocalEvent(uid, msg); - const float massLimit = 5f; + const float MassLimit = 5f; if (!msg.Cancelled) - _physics.ApplyLinearImpulse(user.Value, -impulseVector / physics.Mass * pushbackRatio * MathF.Min(massLimit, physics.Mass), body: userPhysics); + _physics.ApplyLinearImpulse(user.Value, -impulseVector / physics.Mass * pushbackRatio * MathF.Min(MassLimit, physics.Mass), body: userPhysics); } } } diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index cc50094e3dd..b3b5bcf7870 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -93,7 +93,7 @@ public void StopThrow(EntityUid uid, ThrownItemComponent thrownItemComponent) { if (TryComp(uid, out var physics)) { - _physics.SetBodyStatus(uid, physics, BodyStatus.OnGround); + _physics.SetBodyStatus(physics, BodyStatus.OnGround); if (physics.Awake) _broadphase.RegenerateContacts(uid, physics); diff --git a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs index e0696022606..e840bd1ddd5 100644 --- a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs +++ b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs @@ -74,7 +74,7 @@ private void OnThrownStartup(Entity ent, ref ComponentStar comp.PreviousStatus = body.BodyStatus; comp.ThrownEndTime = _timing.CurTime + TimeSpan.FromSeconds(comp.Lifetime); comp.MinLifetimeTime = _timing.CurTime + TimeSpan.FromSeconds(comp.MinLifetime); - _physics.SetBodyStatus(ent, body, BodyStatus.InAir); + _physics.SetBodyStatus(body, BodyStatus.InAir); _physics.SetLinearVelocity(ent, Vector2.Zero, body: body); _physics.ApplyLinearImpulse(ent, comp.Velocity * body.Mass, body: body); Dirty(ent, ent.Comp); @@ -83,7 +83,7 @@ private void OnThrownStartup(Entity ent, ref ComponentStar private void OnThrownShutdown(Entity ent, ref ComponentShutdown args) { if (TryComp(ent, out var body)) - _physics.SetBodyStatus(ent, body, ent.Comp.PreviousStatus); + _physics.SetBodyStatus(body,ent.Comp.PreviousStatus); var ev = new MeleeThrowOnHitEndEvent(); RaiseLocalEvent(ent, ref ev); } diff --git a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs index 3a950bcd29e..177cb310d18 100644 --- a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs +++ b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs @@ -207,12 +207,12 @@ protected virtual void StartTether(EntityUid gunUid, BaseForceGunComponent compo TransformSystem.Unanchor(target, targetXform); component.Tethered = target; var tethered = EnsureComp(target); - _physics.SetBodyStatus(target, targetPhysics, BodyStatus.InAir, false); + _physics.SetBodyStatus(targetPhysics, BodyStatus.InAir, false); _physics.SetSleepingAllowed(target, targetPhysics, false); tethered.Tetherer = gunUid; tethered.OriginalAngularDamping = targetPhysics.AngularDamping; - _physics.SetAngularDamping(target, targetPhysics, 0f); - _physics.SetLinearDamping(target, targetPhysics, 0f); + _physics.SetAngularDamping(targetPhysics, 0f); + _physics.SetLinearDamping(targetPhysics, 0f); _physics.SetAngularVelocity(target, SpinVelocity, body: targetPhysics); _physics.WakeBody(target, body: targetPhysics); var thrown = EnsureComp(component.Tethered.Value); @@ -264,9 +264,9 @@ protected virtual void StopTether(EntityUid gunUid, BaseForceGunComponent compon _thrown.StopThrow(component.Tethered.Value, thrown); } - _physics.SetBodyStatus(component.Tethered.Value, targetPhysics, BodyStatus.OnGround); + _physics.SetBodyStatus(targetPhysics, BodyStatus.OnGround); _physics.SetSleepingAllowed(component.Tethered.Value, targetPhysics, true); - _physics.SetAngularDamping(component.Tethered.Value, targetPhysics, Comp(component.Tethered.Value).OriginalAngularDamping); + _physics.SetAngularDamping(targetPhysics, Comp(component.Tethered.Value).OriginalAngularDamping); } if (!transfer) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index d3aee5a48e9..36b5bbd1927 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -388,7 +388,7 @@ public abstract void Shoot( public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocity, EntityUid gunUid, EntityUid? user = null, float speed = 20f) { var physics = EnsureComp(uid); - Physics.SetBodyStatus(uid, physics, BodyStatus.InAir); + Physics.SetBodyStatus(physics, BodyStatus.InAir); var targetMapVelocity = gunVelocity + direction.Normalized() * speed; var currentMapVelocity = Physics.GetMapLinearVelocity(uid, physics); diff --git a/Resources/Audio/Nyanotrasen/heartbeat_fast.ogg b/Resources/Audio/Nyanotrasen/heartbeat_fast.ogg deleted file mode 100644 index 85a034d9bd0017dafea7df4128235c815babac0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39983 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 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ba49523cfde..954f86aa044 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -4080,17 +4080,3 @@ Entries: you join when someone leaves. id: 6110 time: '2024-05-13T04:58:39.0000000+00:00' -- author: LovelyLophi - changes: - - type: Add - message: Added a number of new corporate coats to the Clothesmate. - id: 6111 - time: '2024-05-22T03:35:11.0000000+00:00' -- author: VMSolidus - changes: - - type: Tweak - message: >- - Syndicate Listening Posts can now appear any time in a round, and can - appear on low-pop servers. - id: 6112 - time: '2024-05-28T23:44:39.0000000+00:00' diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index db58663a2b2..3aea674fd94 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, casperr04, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, clement-or, Clyybber, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, KaiShibaa, kalane15, kalanosh, KEEYNy, Keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Kmc2000, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, LightVillet, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LovelyLophi, LudwigVonChesterfield, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, M3739, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, matthst, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, SignalWalker, SimpleStation14, Simyon264, SirDragooon, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, Slava0135, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, Stealthbomber16, stellar-novas, StrawberryMoses, Subversionary, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, tom-leys, tomasalves8, Tomeno, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, waylon531, weaversam8, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem +0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, casperr04, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, clement-or, Clyybber, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, KaiShibaa, kalane15, kalanosh, KEEYNy, Keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Kmc2000, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, LightVillet, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LudwigVonChesterfield, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, M3739, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, matthst, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, SignalWalker, SimpleStation14, Simyon264, SirDragooon, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, Slava0135, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, Stealthbomber16, stellar-novas, StrawberryMoses, Subversionary, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, tom-leys, tomasalves8, Tomeno, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, waylon531, weaversam8, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem diff --git a/Resources/Locale/en-US/atmos/commands.ftl b/Resources/Locale/en-US/atmos/commands.ftl deleted file mode 100644 index 692908d42ad..00000000000 --- a/Resources/Locale/en-US/atmos/commands.ftl +++ /dev/null @@ -1,8 +0,0 @@ -cmd-set-map-atmos-desc = Sets a map's atmosphere -cmd-set-map-atmos-help = setmapatmos [ [moles...]] -cmd-set-map-atmos-removed = Atmosphere removed from map {$map} -cmd-set-map-atmos-updated = Atmosphere set for map {$map} -cmd-set-map-atmos-hint-map = -cmd-set-map-atmos-hint-space = -cmd-set-map-atmos-hint-temp = (float) -cmd-set-map-atmos-hint-gas = <{$gas} moles> (float) diff --git a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl index 5b368e822f1..91ae21233a3 100644 --- a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl +++ b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl @@ -3,11 +3,8 @@ cage-resist-third-person = {CAPITALIZE(THE($user))} starts removing {POSS-ADJ($u cage-uncage-verb = Uncage -action-name-widemetapsionic = Wide Metapsionic Pulse -action-description-widemetapsionic = Send a mental pulse through the area to see if there are any psychics nearby. - -action-name-focusedmetapsionic = Focused Metapsionic Pulse -action-description-focusedmetapsionic = Probe an entity at close range to glean metaphorical information about any powers they may have +action-name-metapsionic = Metapsionic Pulse +action-description-metapsionic = Send a mental pulse through the area to see if there are any psychics nearby. metapsionic-pulse-success = You detect psychic presence nearby. metapsionic-pulse-failure = You don't detect any psychic presence nearby. @@ -16,8 +13,8 @@ metapsionic-pulse-power = You detect that {$power} was used nearby. action-name-dispel = Dispel action-description-dispel = Dispel summoned entities such as familiars or forcewalls. -action-name-regenerative-stasis = Regenerative Stasis -action-description-regenerative-stasis = Puts the target into a brief stasis, during which time their wounds rapidly heal. +action-name-mass-sleep = Mass Sleep +action-description-mass-sleep = Put targets in a small area to sleep. accept-psionics-window-title = Psionic! accept-psionics-window-prompt-text-part = You rolled a psionic power! @@ -66,14 +63,11 @@ action-name-noospheric-zap = Noospheric Zap action-description-noospheric-zap = Shocks the conciousness of the target and leaves them stunned and stuttering. action-name-pyrokinesis = Pyrokinesis -action-description-pyrokinesis = Hurl a small gateway to the plane of Gehenna at your target. +action-description-pyrokinesis = Light a flammable target on fire. +pyrokinesis-power-used = A wisp of flame engulfs {THE($target)}, igniting {OBJECT($target)}! action-name-psychokinesis = Psychokinesis action-description-psychokinesis = Bend the fabric of space to instantly move across it. action-name-rf-sensitivity = Toggle RF Sensitivity action-desc-rf-sensitivity = Toggle your ability to interpret radio waves on and off. - -trait-latent-psychic-desc = Your mind and soul are open to the noosphere, allowing for a limited use of Telepathy. - Thus, you are eligible for potentially receiving psychic powers. - It is possible that you may be hunted by otherworldly forces, so consider keeping your powers a secret. diff --git a/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl b/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl deleted file mode 100644 index 26d2acb87cd..00000000000 --- a/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl +++ /dev/null @@ -1,21 +0,0 @@ -# Feedback messages for Focused Metapsionic Pulse -metapulse-self = I AM. -no-powers = {CAPITALIZE($entity)} will never awaken from the dream in this life -psychic-potential = {CAPITALIZE($entity)} has a slim chance of awakening from the dream -dispel-feedback = {CAPITALIZE($entity)} is a mighty stone, standing against the currents of fate -metapsionic-feedback = {CAPITALIZE($entity)} gazes back upon thee -mind-swap-feedback = {CAPITALIZE($entity)}'s vessel seems fit for other souls -mindswapped-feedback = Cursed flesh! {CAPITALIZE($entity)} dwells within the wrong vessel! -noospheric-zap-feedback = {CAPITALIZE($entity)}'s soul writhes with thunder from beyond the veil -pyrokinesis-feedback = The Secret of Fire dwells within {CAPITALIZE($entity)} -invisibility-feedback = {CAPITALIZE($entity)}'s wyrd seeks to hide from thine gaze -telegnosis-feedback = {CAPITALIZE($entity)}'s soul travels across bridges composed of dreamlight -sophic-grammateus-feedback = SEEKER, YOU NEED ONLY ASK FOR MY WISDOM. -oracle-feedback = WHY DO YOU BOTHER ME SEEKER? HAVE I NOT MADE MY DESIRES CLEAR? -metempsychotic-machine-feedback = The sea of fate flows through this machine -ifrit-feedback = A spirit of Gehenna, bound by the will of a powerful psychic - -# Power PVS Messages -focused-metapsionic-pulse-begin = The air around {CAPITALIZE($entity)} begins to shimmer faintly -psionic-regeneration-self-revive = {CAPITALIZE($entity)} begins to visibly regenerate -mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml index d0ae444bf77..2165fb4e585 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml @@ -33,7 +33,7 @@ sprite: Objects/Specific/Service/vending_machine_restock.rsi state: base product: CrateVendingMachineRestockClothesFilled - cost: 8050 + cost: 7245 category: cargoproduct-category-name-service group: market diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml index 04cc2e3e19d..1dbc412a9c8 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml @@ -85,11 +85,6 @@ ClothingOuterCoatLettermanRed: 2 # Nyano - Clothing addition ClothingOuterDenimJacket: 2 # DeltaV - Clothing addition ClothingOuterCorporateJacket: 2 # DeltaV - Clothing addition - ClothingOuterCsCorporateJacket: 2 # Einstein Engines - Clothing addition - ClothingOuterEECorporateJacket: 2 # Einstein Engines - Clothing addition - ClothingOuterHICorporateJacket: 2 # Einstein Engines - Clothing addition - ClothingOuterHMCorporateJacket: 2 # Einstein Engines - Clothing addition - ClothingOuterIdCorporateJacket: 2 # Einstein Engines - Clothing addition ClothingShoesBootsFishing: 2 # Nyano - Clothing addition ClothingHeadTinfoil: 2 # Nyano - Clothing addition ClothingHeadFishCap: 2 diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml index 4f255cad211..771da36719f 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml @@ -84,10 +84,7 @@ - type: PotentialPsionic - type: Psionic removable: false - amplification: 4 - psychicFeedback: - - "ifrit-feedback" - - type: PyrokinesisPower + # - type: PyrokinesisPower # Pending psionic rework - type: Grammar attributes: proper: true @@ -106,7 +103,7 @@ requirements: - !type:DepartmentTimeRequirement department: Epistemics - time: 14400 + time: 14400 # DeltaV - 4 hours - type: entity parent: WelderExperimental diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml index e2541def035..1f4eb696c65 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml @@ -26,3 +26,4 @@ - type: NpcFactionMember factions: - NanoTrasen + - type: PotentialPsionic diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml index ea2357a5c06..06abe8c45fa 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml @@ -24,6 +24,7 @@ - type: NpcFactionMember factions: - NanoTrasen + - type: PotentialPsionic - type: Respirator damage: types: diff --git a/Resources/Prototypes/DeltaV/GameRules/events.yml b/Resources/Prototypes/DeltaV/GameRules/events.yml index 9391756492b..73b0ca6549c 100644 --- a/Resources/Prototypes/DeltaV/GameRules/events.yml +++ b/Resources/Prototypes/DeltaV/GameRules/events.yml @@ -52,8 +52,9 @@ noSpawn: true components: - type: StationEvent - weight: 7.5 - minimumPlayers: 10 + earliestStart: 15 + weight: 5 + minimumPlayers: 25 maxOccurrences: 1 duration: 1 - type: PirateRadioSpawnRule diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml index d45e8c3f3c2..4f0a0d0aafa 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml @@ -541,58 +541,3 @@ Quantity: 20 - type: ToggleableClothing clothingPrototype: ClothingHeadHatHoodWinterWeb - -- type: entity - parent: ClothingOuterWinterCoat - id: ClothingOuterCsCorporateJacket - name: Cybersun Corporate Jacket - description: A cozy jacket with the Cybersun logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. - components: - - type: Sprite - sprite: Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi - - type: Clothing - sprite: Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi - -- type: entity - parent: ClothingOuterWinterCoat - id: ClothingOuterEECorporateJacket - name: Einstein Engines Corporate Jacket - description: A cozy jacket with the Einstein Engines logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. - components: - - type: Sprite - sprite: Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi - - type: Clothing - sprite: Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi - -- type: entity - parent: ClothingOuterWinterCoat - id: ClothingOuterHICorporateJacket - name: Hephaestus Industries Corporate Jacket - description: A cozy jacket with the Hephaestus Industries logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. - components: - - type: Sprite - sprite: Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi - - type: Clothing - sprite: Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi - -- type: entity - parent: ClothingOuterWinterCoat - id: ClothingOuterHMCorporateJacket - name: Hawkmoon Acquisitions Corporate Jacket - description: A cozy jacket with the Hawkmoon Acquisitions logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. - components: - - type: Sprite - sprite: Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi - - type: Clothing - sprite: Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi - -- type: entity - parent: ClothingOuterWinterCoat - id: ClothingOuterIdCorporateJacket - name: Interdyne Corporate Jacket - description: A cozy jacket with the Interdyne logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. - components: - - type: Sprite - sprite: Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi - - type: Clothing - sprite: Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml index d9dea3c18d9..5ebd43ddf48 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml @@ -11,3 +11,4 @@ damageRecovery: types: Asphyxiation: -0.5 + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. diff --git a/Resources/Prototypes/Entities/Mobs/Player/diona.yml b/Resources/Prototypes/Entities/Mobs/Player/diona.yml index dfd5e9a1be7..28687c68bfc 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/diona.yml @@ -11,6 +11,7 @@ damageRecovery: types: Asphyxiation: -1.0 + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. # Reformed Diona - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml index d1de65df012..fb84ad3650f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml @@ -3,3 +3,5 @@ name: Urist McHands The Dwarf parent: BaseMobDwarf id: MobDwarf + components: + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 9a7c2ee65ec..6197c82c021 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -3,6 +3,8 @@ name: Urist McHands parent: BaseMobHuman id: MobHuman + components: + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Syndie - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/moth.yml b/Resources/Prototypes/Entities/Mobs/Player/moth.yml index 72feba958ab..ffdb36d86bd 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/moth.yml @@ -3,3 +3,5 @@ name: Urist McFluff parent: BaseMobMoth id: MobMoth + components: + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml index b9f265e0bcf..71d74222979 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml @@ -3,5 +3,7 @@ name: Urisst' Mzhand parent: BaseMobReptilian id: MobReptilian + components: + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Weh diff --git a/Resources/Prototypes/Entities/Mobs/Player/slime.yml b/Resources/Prototypes/Entities/Mobs/Player/slime.yml index 4e5974b3084..79669a8fe2a 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/slime.yml @@ -2,3 +2,5 @@ save: false parent: BaseMobSlimePerson id: MobSlimePerson + components: + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 900de77712e..7bf96efe2cc 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -16,6 +16,7 @@ spawned: - id: FoodMeatHuman amount: 5 + - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml index b847416211d..e87fec22acc 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml @@ -17,7 +17,8 @@ reagents: - ReagentId: THC Quantity: 15 - + - type: StealTarget + stealGroup: Cannabis - type: entity name: dried cannabis leaves @@ -37,6 +38,8 @@ - type: Sprite sprite: Objects/Specific/Hydroponics/tobacco.rsi state: dried + - type: StealTarget + stealGroup: Cannabis - type: entity name: ground cannabis @@ -65,7 +68,8 @@ - Smokable - type: Item size: Tiny - + - type: StealTarget + stealGroup: Cannabis - type: entity name: tobacco leaves diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index e14f29746dc..8d195a25bea 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -177,7 +177,7 @@ - Screwdriver - Wirecutter - Multitool - - WelderIndustrial + - Welder # cargo modules - type: entity diff --git a/Resources/Prototypes/Nyanotrasen/Actions/types.yml b/Resources/Prototypes/Nyanotrasen/Actions/types.yml index 55dd48e5470..e6e4bdc5a75 100644 --- a/Resources/Prototypes/Nyanotrasen/Actions/types.yml +++ b/Resources/Prototypes/Nyanotrasen/Actions/types.yml @@ -36,18 +36,18 @@ event: !type:DispelPowerActionEvent - type: entity - id: ActionRegenerativeStasis - name: action-name-regenerative-stasis - description: action-description-regenerative-stasis + id: ActionMassSleep + name: action-name-mass-sleep + description: action-description-mass-sleep noSpawn: true components: - - type: EntityTargetAction + - type: WorldTargetAction icon: Nyanotrasen/Interface/VerbIcons/mass_sleep.png useDelay: 60 checkCanAccess: false range: 8 itemIconStyle: BigAction - event: !type:RegenerativeStasisPowerActionEvent + event: !type:MassSleepPowerActionEvent - type: entity id: ActionMindSwap @@ -94,7 +94,7 @@ description: action-description-pyrokinesis noSpawn: true components: - - type: WorldTargetAction + - type: EntityTargetAction icon: Nyanotrasen/Interface/VerbIcons/pyrokinesis.png useDelay: 50 range: 6 @@ -103,29 +103,15 @@ event: !type:PyrokinesisPowerActionEvent - type: entity - id: ActionWideMetapsionic - name: action-name-widemetapsionic - description: action-description-widemetapsionic + id: ActionMetapsionic + name: action-name-metapsionic + description: action-description-metapsionic noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/metapsionic.png useDelay: 45 - event: !type:WideMetapsionicPowerActionEvent - -- type: entity - id: ActionFocusedMetapsionic - name: action-name-focusedmetapsionic - description: action-description-focusedmetapsionic - noSpawn: true - components: - - type: EntityTargetAction - icon: Nyanotrasen/Interface/VerbIcons/metapsionic.png - useDelay: 45 - range: 3 - checkCanAccess: false - itemIconStyle: BigAction - event: !type:FocusedMetapsionicPowerActionEvent + event: !type:MetapsionicPowerActionEvent - type: entity id: ActionPsionicRegeneration diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml index 1166d8a29f5..562b9c564ec 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml @@ -32,3 +32,4 @@ - type: NpcFactionMember factions: - NanoTrasen + - type: PotentialPsionic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml index 94ac8403adc..db7936cc5b4 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml @@ -49,3 +49,5 @@ - type: NpcFactionMember factions: - NanoTrasen + - type: PotentialPsionic + diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml index d773cf87c76..d8e791af1ed 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml @@ -22,7 +22,4 @@ NoMind: { state: pod_1 } Gore: { state: pod_1 } Idle: { state: pod_0 } - - type: PotentialPsionic - type: Psionic - psychicFeedback: - - "metempsychotic-machine-feedback" diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml index 58189e49cec..f7481abf1ed 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml @@ -16,10 +16,7 @@ - type: Oracle - type: Speech speechSounds: Tenor - - type: PotentialPsionic - type: Psionic - psychicFeedback: - - "oracle-feedback" - type: SolutionContainerManager solutions: fountain: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml index 8e34a07ea5e..ae85cd25e03 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml @@ -29,8 +29,6 @@ - Science - type: PotentialPsionic #this makes her easier to access for glimmer events, dw about it - type: Psionic - psychicFeedback: - - "sophic-grammateus-feedback" - type: Grammar attributes: gender: female diff --git a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml index e6e497003d5..53910a54a92 100644 --- a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml +++ b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml @@ -9,6 +9,29 @@ stealGroup: AntiPsychicKnife owner: job-name-mantis +- type: entity + id: BecomePsionicObjective + parent: BaseTraitorObjective + name: Become psionic + description: We need you to acquire psionics and keep them until your mission is complete. + noSpawn: true + components: + - type: NotJobsRequirement + jobs: + - Mime + - ForensicMantis + - type: Objective + difficulty: 2.5 + #unique: false + icon: + sprite: Nyanotrasen/Icons/psi.rsi + state: psi + - type: ObjectiveBlacklistRequirement + blacklist: + components: + - BecomeGolemCondition + - type: BecomePsionicCondition + #- type: entity # id: BecomeGolemObjective # parent: BaseTraitorObjective diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index 15b2cdd4fa7..c3e682e02a9 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -7,24 +7,22 @@ - !type:OverallPlaytimeRequirement time: 18000 - !type:DepartmentTimeRequirement - department: Epistemics + department: Epistemics # DeltaV - Epistemics Department replacing Science time: 3600 startingGear: ForensicMantisGear icon: "JobIconForensicMantis" supervisors: job-supervisors-rd - antagAdvantage: 5 - canBeAntag: true + antagAdvantage: 5 # DeltaV - From 4 to 5 + canBeAntag: true # DeltaV - Mantis is no longer a Detective + # whitelistRequired: true access: - Research - Maintenance - - Mantis + - Mantis # DeltaV - Psionic Mantis, see Resources/Prototypes/DeltaV/Access/epistemics.yml special: - !type:AddComponentSpecial components: - - type: PotentialPsionic - type: Psionic - amplification: 0.3 - dampening: 0.3 - type: MetapsionicPower - type: startingGear @@ -36,10 +34,11 @@ head: ClothingHeadHatFezMantis id: ForensicMantisPDA eyes: ClothingEyesGlassesSunglasses - ears: ClothingHeadsetScience + ears: ClothingHeadsetScience # DeltaV - Mantis is part of Epistemics gloves: ClothingHandsGlovesColorWhite outerClothing: ClothingOuterCoatMantis belt: ClothingBeltMantis + # pocket2: ForensicScanner # DeltaV - Mantis is no longer a Detective innerClothingSkirt: ClothingUniformSkirtMantis satchel: ClothingBackpackSatchelMantisFilled duffelbag: ClothingBackpackDuffelMantisFilled diff --git a/Resources/Prototypes/Nyanotrasen/Traits/psionics.yml b/Resources/Prototypes/Nyanotrasen/Traits/psionics.yml deleted file mode 100644 index 5fef3427703..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Traits/psionics.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: trait - id: LatentPsychic - name: Latent Psychic - description: trait-latent-psychic-desc - components: - - type: PotentialPsionic diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml index ca1764e204c..f40b688fd18 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml @@ -5,8 +5,6 @@ DispelPower: 1 TelegnosisPower: 1 PsionicRegenerationPower: 1 - RegenerativeStasisPower: 0.3 - PsionicInvisibilityPower: 0.15 + MassSleepPower: 0.3 +# PsionicInvisibilityPower: 0.15 MindSwapPower: 0.15 - NoosphericZapPower: 0.15 - PyrokinesisPower: 0.15 diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index d711e9a1b13..fba2c4cc172 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -39,6 +39,7 @@ EscapeShuttleObjective: 1 # DieObjective: 0.05 # DeltaV - Disable the lrp objective aka murderbone justification #HijackShuttleObjective: 0.02 + BecomePsionicObjective: 1 # Nyanotrasen - Become Psionic objective, see Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml #BecomeGolemObjective: 0.5 # Nyanotrasen - Become a golem objective, see Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml - type: weightedRandom diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml index 006c061666b..fb1e5e76fc0 100644 --- a/Resources/Prototypes/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml @@ -142,7 +142,12 @@ sprite: Objects/Misc/id_cards.rsi state: default - +- type: stealTargetGroup + id: Cannabis + name: cannabis + sprite: + sprite: Objects/Specific/Hydroponics/cannabis.rsi + state: produce - type: stealTargetGroup id: LAMP diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index 18154850973..66258870d61 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -142,7 +142,20 @@ - type: Objective difficulty: 0.7 - +- type: entity + noSpawn: true + parent: BaseThiefStealCollectionObjective + id: CannabisStealCollectionObjective + components: + - type: NotJobRequirement + job: Botanist + - type: StealCondition + stealGroup: Cannabis + minCollectionSize: 20 + maxCollectionSize: 30 + verifyMapExistence: false + - type: Objective + difficulty: 0.5 - type: entity noSpawn: true diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index ddb779669eb..19cf1419111 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -3,13 +3,13 @@ name: job-name-rd description: job-description-rd playTimeTracker: JobResearchDirector - antagAdvantage: 6 + antagAdvantage: 6 # DeltaV - Reduced TC: Head of Staff requirements: - !type:DepartmentTimeRequirement - department: Epistemics - time: 54000 + department: Epistemics # DeltaV - Epistemics Department replacing Science + time: 54000 # DeltaV - 15 hours - !type:OverallPlaytimeRequirement - time: 72000 + time: 72000 # DeltaV - 20 hours weight: 10 startingGear: ResearchDirectorGear icon: "JobIconResearchDirector" @@ -21,20 +21,20 @@ - Command - Maintenance - ResearchDirector - - Mantis - - Chapel + - Mantis # DeltaV - Psionic Mantis, see Resources/Prototypes/DeltaV/Access/epistemics.yml + - Chapel # DeltaV - Chaplain is in Epistemics - Cryogenics - special: + special: # Nyanotrasen - Mystagogue can use the Bible - !type:AddComponentSpecial components: - - type: BibleUser - - type: PotentialPsionic - - type: Psionic - dampening: 1 #Mystagogue gets a significant buff to his antimage abilities, making him better at dispelling than other people - - type: DispelPower - - type: CommandStaff + - type: BibleUser # Nyano - Lets them heal with bibles + - type: Psionic # Nyano - They start with telepathic chat + - type: DispelPower # Nyano - They get the Dispel psionic power on spawn - !type:AddImplantSpecial implants: [ MindShieldImplant ] + - !type:AddComponentSpecial + components: + - type: CommandStaff - type: startingGear id: ResearchDirectorGear @@ -44,7 +44,7 @@ shoes: ClothingShoesColorBrown id: RnDPDA ears: ClothingHeadsetRD - belt: BibleMystagogue + belt: BibleMystagogue # Nyanotrasen - Mystagogue book for their Ifrit innerClothingSkirt: ClothingUniformJumpskirtResearchDirector satchel: ClothingBackpackSatchelResearchDirectorFilled duffelbag: ClothingBackpackDuffelResearchDirectorFilled diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/equipped-OUTERCLOTHING.png deleted file mode 100644 index 73e230e8a5f2088cbea061e045059ab6e5c52140..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8610 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 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/icon.png deleted file mode 100644 index f78a89945c3732976cfac3674fea17d416a0bbe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5930 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 Y6YmP{KCGw>f(VDXJ9|4FUK=U<7ye`^m;e9( diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json deleted file mode 100644 index 42d21c3d8ab..00000000000 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "leonardo_dabepis on discord / @leonardo-dabepis on Tumblr, Edited by heartparkyheart on Discord", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - } - ] -} diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/equipped-OUTERCLOTHING.png deleted file mode 100644 index 983c2534f4ceffdb575394b6b2077e250a9a07e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8514 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 C=v7$& diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/icon.png deleted file mode 100644 index ba72253098481f5f357ad1a0fd752a9c1b23a2d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6063 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 NoE=>4&)9BC_#YSooM`|6 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json deleted file mode 100644 index 42d21c3d8ab..00000000000 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "leonardo_dabepis on discord / @leonardo-dabepis on Tumblr, Edited by heartparkyheart on Discord", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - } - ] -} diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/equipped-OUTERCLOTHING.png deleted file mode 100644 index f18a64ab9c0c4012305f51ab70dc2710c1131824..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8310 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 B7?S`1 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/icon.png deleted file mode 100644 index d80b56558a23c7c523173d543ae370b835f48ac8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6169 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 G68{IREn~+3 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json deleted file mode 100644 index 42d21c3d8ab..00000000000 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "leonardo_dabepis on discord / @leonardo-dabepis on Tumblr, Edited by heartparkyheart on Discord", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - } - ] -} diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 346156159a7..b8cfc40c1c4 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -167,7 +167,6 @@ binds: type: State key: MouseLeft canFocus: true - priority: 10 - function: RotateStoredItem type: State key: MouseRight diff --git a/global.json b/global.json index 2244195a209..391ba3c2a30 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "8.0.100", - "rollForward": "disable" + "rollForward": "latestFeature" } } From 3f44f9d1af13c3fd3216f1d87091eddee040e721 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 28 May 2024 22:05:40 -0400 Subject: [PATCH 02/24] Update PsionicRegenerationPowerSystem.cs --- .../PsionicRegenerationPowerSystem.cs | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs diff --git a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs new file mode 100644 index 00000000000..882eb9e0b14 --- /dev/null +++ b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -0,0 +1,190 @@ +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Server.DoAfter; +using Content.Shared.Psionics.Abilities; +using Content.Shared.Actions; +using Content.Shared.Chemistry.Components; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs; +using Content.Shared.Popups; +using Content.Shared.Psionics.Events; +using Content.Shared.Examine; +using static Content.Shared.Examine.ExamineSystemShared; +using Robust.Shared.Timing; +using Content.Shared.Actions.Events; +using Robust.Server.Audio; + +namespace Content.Server.Psionics.Abilities +{ + public sealed class PsionicRegenerationPowerSystem : EntitySystem + { + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnMobStateChangedEvent); + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); + _actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.PsionicRegenerationActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.RegenerationFeedback); + psionic.Amplification += 0.5f; + psionic.Dampening += 0.5f; + } + } + + private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) + { + if (!TryComp(uid, out var psionic)) + return; + + var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime); + var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid); + + //Prevent the power from ignoring its own cooldown + _actions.TryGetActionData(component.PsionicRegenerationActionEntity, out var actionData); + var curTime = _gameTiming.CurTime; + if (actionData != null && actionData.Cooldown.HasValue && actionData.Cooldown.Value.End > curTime) + return; + + _doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId); + + component.DoAfter = doAfterId; + + _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)), + uid, + // TODO: Use LoS-based Filter when one is available. + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + PopupType.Medium); + + _audioSystem.PlayPvs(component.SoundUse, uid, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); + + _psionics.LogPowerUsed(uid, "psionic regeneration", + (int) Math.Round(6 * psionic.Amplification - psionic.Dampening), + (int) Math.Round(8 * psionic.Amplification - psionic.Dampening)); + + args.Handled = true; + } + + /// + /// Regenerators automatically activate upon crit, provided the power was off cooldown at that exact point in time. + /// Self-rescusitation is also far more costly, and extremely obvious + /// + /// + /// + /// + private void OnMobStateChangedEvent(EntityUid uid, PsionicRegenerationPowerComponent component, MobStateChangedEvent args) + { + if (!TryComp(uid, out var psionic)) + return; + + if (HasComp(uid)) + return; + + if (args.NewMobState is MobState.Critical) + { + _actions.TryGetActionData(component.PsionicRegenerationActionEntity, out var actionData); + var curTime = _gameTiming.CurTime; + if (actionData != null && actionData.Cooldown.HasValue && actionData.Cooldown.Value.End > curTime) + return; + + if (actionData is { UseDelay: not null }) + { + component.SelfRevive = true; + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, uid, component.UseDelay, new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime), uid, args.Target, uid) + { + BreakOnUserMove = false, + BreakOnTargetMove = false, + BreakOnWeightlessMove = false, + BreakOnDamage = false, + RequireCanInteract = false, + }); + _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-self-revive", ("entity", uid)), + uid, + // TODO: Use LoS-based Filter when one is available. + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + PopupType.MediumCaution); + _audioSystem.PlayPvs(component.SoundUse, uid, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); + + _psionics.LogPowerUsed(uid, "psionic regeneration", + (int) Math.Round(10 * psionic.Amplification - 2 * psionic.Dampening), + (int) Math.Round(20 * psionic.Amplification - 2 * psionic.Dampening)); + + _actions.StartUseDelay(component.PsionicRegenerationActionEntity); + } + } + } + + private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.PsionicRegenerationActionEntity); + + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.RegenerationFeedback); + psionic.Amplification -= 0.5f; + psionic.Dampening -= 0.5f; + } + } + + private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args) + { + if (component.DoAfter == null) + return; + + _doAfterSystem.Cancel(component.DoAfter); + component.DoAfter = null; + + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationDoAfterEvent args) + { + component.DoAfter = null; + + if (!TryComp(uid, out var psionic)) + return; + + if (!TryComp(uid, out var stream)) + return; + + var percentageComplete = Math.Min(1f, (_gameTiming.CurTime - args.StartedAt).TotalSeconds / component.UseDelay); + + var solution = new Solution(); + solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(Math.Min(component.EssenceAmount * percentageComplete + 10f * psionic.Dampening, 15f))); + _bloodstreamSystem.TryAddToChemicals(uid, solution, stream); + if (component.SelfRevive == true) + { + var critSolution = new Solution(); + critSolution.AddReagent("Epinephrine", MathF.Min(5 + 5 * psionic.Dampening, 15)); + _bloodstreamSystem.TryAddToChemicals(uid, critSolution, stream); + component.SelfRevive = false; + } + } + } +} From 462e91c2cceee24954d4c7570ef5328994f251bd Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 28 May 2024 22:07:31 -0400 Subject: [PATCH 03/24] aaaaaaaaa --- .github/workflows/build-docfx.yml | 2 +- .../Atmos/Overlays/GasTileOverlay.cs | 70 ++-- Content.Client/Mapping/MappingSystem.cs | 2 +- .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 2 +- .../Glimmer/GlimmerReactiveVisuals.cs | 0 .../GlimmerMonitor}/GlimmerMonitorUi.cs | 3 +- .../GlimmerMonitorUiFragment.xaml | 2 +- .../GlimmerMonitorUiFragment.xaml.cs | 4 +- .../Telepathy}/PsionicChatUpdateSystem.cs | 4 +- .../UserInterface}/AcceptPsionicsEUI.cs | 0 .../UserInterface}/AcceptPsionicsWindow.cs | 0 .../UserInterface/GlimmerGraph.cs | 2 +- .../Systems/Chat/ChatUIController.cs | 9 +- .../Tests/Body/LungTest.cs | 2 +- .../Interaction/InteractionTest.Helpers.cs | 7 +- .../Abilities/Mime/MimePowersSystem.cs | 2 +- .../Managers/AdminManager.Metrics.cs | 98 ++++++ .../Administration/Managers/AdminManager.cs | 9 +- .../Systems/AdminVerbSystem.Smites.cs | 12 +- .../Anomaly/AnomalySystem.Psionics.cs | 4 +- .../Atmos/Commands/SetMapAtmosCommand.cs | 94 +++++ .../Atmos/Components/AirtightComponent.cs | 4 +- .../Components/GridAtmosphereComponent.cs | 8 +- .../Components/IgniteOnCollideComponent.cs | 4 +- .../Components/MapAtmosphereComponent.cs | 8 +- .../Atmos/EntitySystems/AirtightSystem.cs | 18 +- .../EntitySystems/AtmosDebugOverlaySystem.cs | 11 +- .../AtmosObstructionEnumerator.cs | 37 -- .../EntitySystems/AtmosphereSystem.API.cs | 45 +-- .../AtmosphereSystem.ExcitedGroup.cs | 28 +- .../AtmosphereSystem.GridAtmosphere.cs | 247 +++++--------- .../AtmosphereSystem.HighPressureDelta.cs | 10 +- .../EntitySystems/AtmosphereSystem.LINDA.cs | 18 +- .../EntitySystems/AtmosphereSystem.Map.cs | 108 +++++- .../AtmosphereSystem.Monstermos.cs | 110 +++--- .../AtmosphereSystem.Processing.cs | 323 ++++++++++++------ .../AtmosphereSystem.Superconductivity.cs | 8 +- .../EntitySystems/AtmosphereSystem.Utils.cs | 62 ++-- .../Atmos/EntitySystems/AtmosphereSystem.cs | 7 + .../EntitySystems/AutomaticAtmosSystem.cs | 8 +- .../Atmos/EntitySystems/GasAnalyzerSystem.cs | 4 +- .../EntitySystems/GasTileOverlaySystem.cs | 6 +- Content.Server/Atmos/GasMixture.cs | 57 +++- .../Unary/EntitySystems/GasCanisterSystem.cs | 2 +- .../Unary/EntitySystems/GasCondenserSystem.cs | 2 +- .../TileAtmosCollectionSerializer.cs | 8 +- Content.Server/Atmos/TileAtmosphere.cs | 53 ++- Content.Server/Body/Systems/LungSystem.cs | 2 +- Content.Server/Chat/Systems/ChatSystem.cs | 2 +- .../Chemistry/EntitySystems/VaporSystem.cs | 4 +- .../Chemistry/ReagentEffects/ModifyLungGas.cs | 13 +- .../Conditions/ComponentInTile.cs | 11 +- .../Events/GlimmerMobSpawnRule.cs | 2 +- .../ExplosionSystem.Processing.cs | 4 +- .../ImmovableRod/ImmovableRodSystem.cs | 6 +- .../Abilities/MetapsionicPowerSystem.cs | 74 ---- .../PsionicRegenerationPowerSystem.cs | 128 ------- .../Abilities/PyrokinesisPowerSystem.cs | 66 ---- .../Abilities/TelegnosisPowerSystem.cs | 67 ---- .../Chemistry/Effects/ChemRemovePsionic.cs | 2 +- .../BecomePsionicConditionComponent.cs | 11 - .../Systems/BecomePsionicConditionSystem.cs | 32 -- .../Psionics/PotentialPsionicComponent.cs | 14 - .../Research/Oracle/OracleSystem.cs | 2 +- .../SophicScribe/SophicScribeSystem.cs | 2 +- .../Events/GlimmerWispSpawnRule.cs | 2 +- .../StationEvents/Events/MassMindSwapRule.cs | 5 +- .../StationEvents/Events/NoosphericFryRule.cs | 2 +- .../Events/NoosphericStormRule.cs | 4 +- .../StationEvents/Events/NoosphericZapRule.cs | 2 +- .../Events/PsionicCatGotYourTongueRule.cs | 2 +- Content.Server/Parallax/BiomeSystem.cs | 11 +- .../ParticleAcceleratorSystem.Emitter.cs | 2 +- .../Physics/Controllers/ChasingWalkSystem.cs | 2 +- .../Power/Generator/GasPowerReceiverSystem.cs | 2 +- .../Psionics/Abilities/DispelPowerSystem.cs | 31 +- .../Abilities/MetapsionicPowerSystem.cs | 188 ++++++++++ .../Psionics/Abilities/MindSwapPowerSystem.cs | 62 ++-- .../Abilities/MindSwappedComponent.cs | 5 +- .../Abilities/NoosphericZapPowerSystem.cs | 44 +-- .../Abilities}/PsionicAbilitiesSystem.cs | 89 ++--- .../PsionicInvisibilityPowerSystem.cs | 94 +++-- .../Abilities/PyrokinesisPowerSystem.cs | 93 +++++ .../RegenerativeStasisPowerSystem.cs | 76 +++++ .../Abilities/TelegnosisPowerSystem.cs | 106 ++++++ .../Psionics/AcceptPsionicsEui.cs | 2 +- .../Psionics/AntiPsychicWeaponComponent.cs | 0 .../Audio/GlimmerSoundComponent.cs | 6 +- .../Psionics/Dreams/DreamSystem.cs | 3 - .../Psionics/Glimmer/GlimmerCommands.cs | 0 .../Psionics/Glimmer/GlimmerReactiveSystem.cs | 5 +- .../Glimmer/PassiveGlimmerReductionSystem.cs | 2 - .../Structures/GlimmerSourceComponent.cs | 0 .../Structures/GlimmerStructuresSystem.cs | 0 .../Invisibility/PsionicInvisibilitySystem.cs | 25 +- .../PsionicInvisibleContactsComponent.cs | 1 - .../PsionicInvisibleContactsSystem.cs | 2 - .../PsionicallyInvisibleComponent.cs | 0 .../Psionics/PotentialPsionicComponent.cs | 21 ++ .../PsionicAwaitingPlayerComponent.cs | 0 .../Psionics/PsionicBonusChanceComponent.cs | 0 .../Psionics/PsionicsCommands.cs | 6 +- .../Psionics/PsionicsSystem.cs | 23 +- .../Telepathy}/TSayCommand.cs | 3 +- .../Telepathy}/TelepathicRepeaterComponent.cs | 2 +- .../Telepathy/TelepathyChatSystem.cs} | 18 +- .../Salvage/SpawnSalvageMissionJob.cs | 6 +- .../Systems/ShuttleSystem.FasterThanLight.cs | 8 +- .../Shuttles/Systems/ShuttleSystem.cs | 8 +- .../EntitySystems/EventHorizonSystem.cs | 4 +- Content.Server/Spreader/SpreaderSystem.cs | 69 ++-- .../StationEvents/Events/MeteorSwarmRule.cs | 6 +- .../Zombies/ZombieSystem.Transform.cs | 4 +- Content.Shared/Atmos/Atmospherics.cs | 7 +- .../SharedGasTileOverlaySystem.cs | 3 + .../Buckle/SharedBuckleSystem.Buckle.cs | 2 +- Content.Shared/Maps/ContentTileDefinition.cs | 6 +- Content.Shared/Maps/TurfHelpers.cs | 27 +- .../Movement/Systems/SharedJetpackSystem.cs | 4 +- .../MassSleep/MassSleepPowerComponent.cs | 18 - .../MassSleep/MassSleepPowerSystem.cs | 59 ---- .../Metapsionics/MetapsionicPowerComponent.cs | 21 -- .../TelegnosticProjectionComponent.cs | 6 - .../Events/MassSleepPowerActionEvent.cs | 2 - .../Events/MetapsionicPowerActionEvent.cs | 3 +- .../Events/PyrokinesisPowerActionEvent.cs | 4 +- .../RegenerativeStasisPowerActionEvent.cs | 2 + Content.Shared/Nyanotrasen/Psionics/Events.cs | 28 -- .../Abilities/AcceptPsionicsEuiMessage.cs | 0 .../Dispel/DamageOnDispelComponent.cs | 4 +- .../Abilities/Dispel/DispelPowerComponent.cs | 8 +- .../Abilities/Dispel/DispellableComponent.cs | 2 +- .../Metapsionics/MetapsionicPowerComponent.cs | 38 +++ .../MindSwap/MindSwapPowerComponent.cs | 5 +- .../NoosphericZapPowerComponent.cs | 5 +- .../PsionicInvisibilityPowerComponent.cs | 8 +- .../PsionicInvisibilityUsedComponent.cs | 3 +- .../PsionicRegenerationPowerComponent.cs | 10 +- .../Pyrokinesis/PyrokinesisPowerComponent.cs | 5 +- .../RegenerativeStasisPowerComponent.cs | 20 ++ .../Telegnosis/TelegnosisPowerComponent.cs | 10 +- .../TelegnosticProjectionComponent.cs | 8 + Content.Shared/Psionics/Events.cs | 64 ++++ .../Psionics/Glimmer/GlimmerSystem.cs | 2 +- .../Glimmer/SharedGlimmerReactiveComponent.cs | 0 .../Glimmer/SharedGlimmerReactiveVisuals.cs | 0 .../ClothingGrantPsionicPowerComponent.cs | 2 +- .../Psionics/Items/HeadCageComponent.cs | 2 +- .../Psionics/Items/HeadCagedComponent.cs | 2 +- .../Psionics/Items/PsionicItemsSystem.cs | 2 +- .../Psionics/Items/TinfoilHatComponent.cs | 2 +- .../Psionics/PsionicComponent.cs | 16 +- .../Psionics/PsionicInsulationComponent.cs | 2 +- .../Psionics/PsionicsDisabledComponent.cs | 2 +- .../Psionics/SharedPsionicAbilitiesSystem.cs | 4 +- .../Psionics/SharedPsionicSystem.Insulated.cs | 4 + Content.Shared/Throwing/ThrowingSystem.cs | 6 +- Content.Shared/Throwing/ThrownItemSystem.cs | 2 +- .../Weapons/Melee/MeleeThrowOnHitSystem.cs | 4 +- .../Weapons/Misc/SharedTetherGunSystem.cs | 10 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 +- .../Audio/Nyanotrasen/heartbeat_fast.ogg | Bin 0 -> 39983 bytes Resources/Changelog/Changelog.yml | 14 + Resources/Credits/GitHub.txt | 2 +- Resources/Locale/en-US/atmos/commands.ftl | 8 + .../en-US/nyanotrasen/abilities/psionic.ftl | 18 +- .../nyanotrasen/psionics/psychic-feedback.ftl | 21 ++ .../Catalog/Cargo/cargo_vending.yml | 2 +- .../Inventories/clothesmate.yml | 5 + .../DeltaV/Entities/Mobs/NPCs/familiars.yml | 7 +- .../DeltaV/Entities/Mobs/Player/harpy.yml | 1 - .../DeltaV/Entities/Mobs/Player/vulpkanin.yml | 1 - .../Prototypes/DeltaV/GameRules/events.yml | 5 +- .../Clothing/OuterClothing/wintercoats.yml | 55 +++ .../Entities/Mobs/Player/arachnid.yml | 1 - .../Prototypes/Entities/Mobs/Player/diona.yml | 1 - .../Prototypes/Entities/Mobs/Player/dwarf.yml | 2 - .../Prototypes/Entities/Mobs/Player/human.yml | 2 - .../Prototypes/Entities/Mobs/Player/moth.yml | 2 - .../Entities/Mobs/Player/reptilian.yml | 2 - .../Prototypes/Entities/Mobs/Player/slime.yml | 2 - .../Entities/Mobs/Species/human.yml | 1 - .../Objects/Specific/Hydroponics/leaves.yml | 8 +- .../Specific/Robotics/borg_modules.yml | 2 +- .../Prototypes/Nyanotrasen/Actions/types.yml | 34 +- .../Nyanotrasen/Entities/Mobs/Player/Oni.yml | 1 - .../Entities/Mobs/Player/felinid.yml | 2 - .../Machines/metempsychoticMachine.yml | 3 + .../Entities/Structures/Research/oracle.yml | 3 + .../Structures/Research/sophicscribe.yml | 2 + .../Nyanotrasen/Objectives/traitor.yml | 23 -- .../Roles/Jobs/Epistemics/forensicmantis.yml | 15 +- .../Nyanotrasen/Traits/psionics.yml | 6 + .../Prototypes/Nyanotrasen/psionicPowers.yml | 6 +- .../Prototypes/Objectives/objectiveGroups.yml | 1 - .../Objectives/stealTargetGroups.yml | 7 +- Resources/Prototypes/Objectives/thief.yml | 15 +- .../Roles/Jobs/Science/research_director.yml | 28 +- .../equipped-OUTERCLOTHING.png | Bin 0 -> 8610 bytes .../WinterCoats/cs_corpo_jacket.rsi/icon.png | Bin 0 -> 5930 bytes .../WinterCoats/cs_corpo_jacket.rsi/meta.json | 18 + .../equipped-OUTERCLOTHING.png | Bin 0 -> 8514 bytes .../WinterCoats/ee_corpo_jacket.rsi/icon.png | Bin 0 -> 6063 bytes .../WinterCoats/ee_corpo_jacket.rsi/meta.json | 18 + .../equipped-OUTERCLOTHING.png | Bin 0 -> 8310 bytes .../WinterCoats/hi_corpo_jacket.rsi/icon.png | Bin 0 -> 5980 bytes .../WinterCoats/hi_corpo_jacket.rsi/meta.json | 18 + .../equipped-OUTERCLOTHING.png | Bin 0 -> 9341 bytes .../WinterCoats/hm_corpo_jacket.rsi/icon.png | Bin 0 -> 6169 bytes .../WinterCoats/hm_corpo_jacket.rsi/meta.json | 18 + .../equipped-OUTERCLOTHING.png | Bin 0 -> 9025 bytes .../WinterCoats/id_corpo_jacket.rsi/icon.png | Bin 0 -> 5955 bytes .../WinterCoats/id_corpo_jacket.rsi/meta.json | 18 + Resources/keybinds.yml | 1 + global.json | 2 +- 215 files changed, 2271 insertions(+), 1588 deletions(-) rename Content.Client/{Nyanotrasen => }/Psionics/Glimmer/GlimmerReactiveVisuals.cs (100%) rename Content.Client/{Nyanotrasen/CartridgeLoader/Cartridges => Psionics/GlimmerMonitor}/GlimmerMonitorUi.cs (92%) rename Content.Client/{Nyanotrasen/CartridgeLoader/Cartridges => Psionics/GlimmerMonitor}/GlimmerMonitorUiFragment.xaml (93%) rename Content.Client/{Nyanotrasen/CartridgeLoader/Cartridges => Psionics/GlimmerMonitor}/GlimmerMonitorUiFragment.xaml.cs (96%) rename Content.Client/{Nyanotrasen/Chat => Psionics/Telepathy}/PsionicChatUpdateSystem.cs (92%) rename Content.Client/{Nyanotrasen/Psionics/UI => Psionics/UserInterface}/AcceptPsionicsEUI.cs (100%) rename Content.Client/{Nyanotrasen/Psionics/UI => Psionics/UserInterface}/AcceptPsionicsWindow.cs (100%) rename Content.Client/{Nyanotrasen => Psionics}/UserInterface/GlimmerGraph.cs (97%) create mode 100644 Content.Server/Administration/Managers/AdminManager.Metrics.cs create mode 100644 Content.Server/Atmos/Commands/SetMapAtmosCommand.cs delete mode 100644 Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs delete mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs delete mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs delete mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs delete mode 100644 Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs delete mode 100644 Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs delete mode 100644 Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs delete mode 100644 Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs rename Content.Server/{Nyanotrasen/Abilities => }/Psionics/Abilities/DispelPowerSystem.cs (83%) create mode 100644 Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs rename Content.Server/{Nyanotrasen/Abilities => }/Psionics/Abilities/MindSwapPowerSystem.cs (78%) rename Content.Server/{Nyanotrasen/Abilities => }/Psionics/Abilities/MindSwappedComponent.cs (79%) rename Content.Server/{Nyanotrasen/Abilities => }/Psionics/Abilities/NoosphericZapPowerSystem.cs (54%) rename Content.Server/{Nyanotrasen/Abilities/Psionics => Psionics/Abilities}/PsionicAbilitiesSystem.cs (54%) rename Content.Server/{Nyanotrasen/Abilities => }/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs (57%) create mode 100644 Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs create mode 100644 Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs create mode 100644 Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs rename Content.Server/{Nyanotrasen => }/Psionics/AcceptPsionicsEui.cs (95%) rename Content.Server/{Nyanotrasen => }/Psionics/AntiPsychicWeaponComponent.cs (100%) rename Content.Server/{Nyanotrasen => Psionics}/Audio/GlimmerSoundComponent.cs (80%) rename Content.Server/{Nyanotrasen => }/Psionics/Dreams/DreamSystem.cs (93%) rename Content.Server/{Nyanotrasen => }/Psionics/Glimmer/GlimmerCommands.cs (100%) rename Content.Server/{Nyanotrasen => }/Psionics/Glimmer/GlimmerReactiveSystem.cs (99%) rename Content.Server/{Nyanotrasen => }/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs (94%) rename Content.Server/{Nyanotrasen => }/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs (100%) rename Content.Server/{Nyanotrasen => }/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs (100%) rename Content.Server/{Nyanotrasen => }/Psionics/Invisibility/PsionicInvisibilitySystem.cs (88%) rename Content.Server/{Nyanotrasen => }/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs (95%) rename Content.Server/{Nyanotrasen => }/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs (95%) rename Content.Server/{Nyanotrasen => }/Psionics/Invisibility/PsionicallyInvisibleComponent.cs (100%) create mode 100644 Content.Server/Psionics/PotentialPsionicComponent.cs rename Content.Server/{Nyanotrasen => }/Psionics/PsionicAwaitingPlayerComponent.cs (100%) rename Content.Server/{Nyanotrasen => }/Psionics/PsionicBonusChanceComponent.cs (100%) rename Content.Server/{Nyanotrasen => }/Psionics/PsionicsCommands.cs (84%) rename Content.Server/{Nyanotrasen => }/Psionics/PsionicsSystem.cs (91%) rename Content.Server/{Nyanotrasen/Chat => Psionics/Telepathy}/TSayCommand.cs (95%) rename Content.Server/{Nyanotrasen/Chat => Psionics/Telepathy}/TelepathicRepeaterComponent.cs (82%) rename Content.Server/{Nyanotrasen/Chat/NyanoChatSystem.cs => Psionics/Telepathy/TelepathyChatSystem.cs} (85%) delete mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs delete mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs delete mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs delete mode 100644 Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs delete mode 100644 Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs create mode 100644 Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs delete mode 100644 Content.Shared/Nyanotrasen/Psionics/Events.cs rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/AcceptPsionicsEuiMessage.cs (100%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs (77%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/Dispel/DispelPowerComponent.cs (79%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/Dispel/DispellableComponent.cs (69%) create mode 100644 Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs (76%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs (77%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs (71%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs (94%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs (76%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs (79%) create mode 100644 Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs (73%) create mode 100644 Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs create mode 100644 Content.Shared/Psionics/Events.cs rename Content.Shared/{Nyanotrasen => }/Psionics/Glimmer/GlimmerSystem.cs (98%) rename Content.Shared/{Nyanotrasen => }/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs (100%) rename Content.Shared/{Nyanotrasen => }/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs (100%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Items/ClothingGrantPsionicPowerComponent.cs (84%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Items/HeadCageComponent.cs (96%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Items/HeadCagedComponent.cs (81%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Items/PsionicItemsSystem.cs (98%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/Items/TinfoilHatComponent.cs (90%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/PsionicComponent.cs (51%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/PsionicInsulationComponent.cs (82%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/PsionicsDisabledComponent.cs (84%) rename Content.Shared/{Nyanotrasen/Abilities => }/Psionics/SharedPsionicAbilitiesSystem.cs (97%) create mode 100644 Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs create mode 100644 Resources/Audio/Nyanotrasen/heartbeat_fast.ogg create mode 100644 Resources/Locale/en-US/atmos/commands.ftl create mode 100644 Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl create mode 100644 Resources/Prototypes/Nyanotrasen/Traits/psionics.yml create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/meta.json create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/meta.json create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json diff --git a/.github/workflows/build-docfx.yml b/.github/workflows/build-docfx.yml index ca1a6f0af12..d37e37026d7 100644 --- a/.github/workflows/build-docfx.yml +++ b/.github/workflows/build-docfx.yml @@ -21,7 +21,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 8.0.x + dotnet-version: 8.0.100 - name: Install dependencies run: dotnet restore diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index ef65d43fe85..f4dc274a4e5 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -8,7 +8,6 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Enums; -using Robust.Shared.Graphics; using Robust.Shared.Graphics.RSI; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -23,7 +22,7 @@ public sealed class GasTileOverlay : Overlay private readonly IEntityManager _entManager; private readonly IMapManager _mapManager; - public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld; private readonly ShaderInstance _shader; // Gas overlays @@ -79,7 +78,8 @@ public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IR var rsi = resourceCache.GetResource(animated.RsiPath).RSI; var stateId = animated.RsiState; - if (!rsi.TryGetState(stateId, out var state)) continue; + if (!rsi.TryGetState(stateId, out var state)) + continue; _frames[i] = state.GetFrames(RsiDirection.South); _frameDelays[i] = state.GetDelays(); @@ -111,7 +111,8 @@ protected override void FrameUpdate(FrameEventArgs args) for (var i = 0; i < _gasCount; i++) { var delays = _frameDelays[i]; - if (delays.Length == 0) continue; + if (delays.Length == 0) + continue; var frameCount = _frameCounter[i]; _timer[i] += args.DeltaSeconds; @@ -127,7 +128,8 @@ protected override void FrameUpdate(FrameEventArgs args) for (var i = 0; i < FireStates; i++) { var delays = _fireFrameDelays[i]; - if (delays.Length == 0) continue; + if (delays.Length == 0) + continue; var frameCount = _fireFrameCounter[i]; _fireTimer[i] += args.DeltaSeconds; @@ -161,26 +163,10 @@ protected override void Draw(in OverlayDrawArgs args) var mapUid = _mapManager.GetMapEntityId(args.MapId); if (_entManager.TryGetComponent(mapUid, out var atmos)) - { - var bottomLeft = args.WorldAABB.BottomLeft.Floored(); - var topRight = args.WorldAABB.TopRight.Ceiled(); - - for (var x = bottomLeft.X; x <= topRight.X; x++) - { - for (var y = bottomLeft.Y; y <= topRight.Y; y++) - { - var tilePosition = new Vector2(x, y); - - for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++) - { - var opacity = atmos.OverlayData.Opacity[i]; + DrawMapOverlay(drawHandle, args, mapUid, atmos); - if (opacity > 0) - args.WorldHandle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity)); - } - } - } - } + if (args.Space != OverlaySpace.WorldSpaceEntities) + return; // TODO: WorldBounds callback. _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState, @@ -265,5 +251,41 @@ protected override void Draw(in OverlayDrawArgs args) drawHandle.UseShader(null); drawHandle.SetTransform(Matrix3.Identity); } + + private void DrawMapOverlay( + DrawingHandleWorld handle, + OverlayDrawArgs args, + EntityUid map, + MapAtmosphereComponent atmos) + { + var mapGrid = _entManager.HasComponent(map); + + // map-grid atmospheres get drawn above grids + if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities) + return; + + // Normal map atmospheres get drawn below grids + if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld) + return; + + var bottomLeft = args.WorldAABB.BottomLeft.Floored(); + var topRight = args.WorldAABB.TopRight.Ceiled(); + + for (var x = bottomLeft.X; x <= topRight.X; x++) + { + for (var y = bottomLeft.Y; y <= topRight.Y; y++) + { + var tilePosition = new Vector2(x, y); + + for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++) + { + var opacity = atmos.OverlayData.Opacity[i]; + + if (opacity > 0) + handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity)); + } + } + } + } } } diff --git a/Content.Client/Mapping/MappingSystem.cs b/Content.Client/Mapping/MappingSystem.cs index 4456be36a65..8daf193dfeb 100644 --- a/Content.Client/Mapping/MappingSystem.cs +++ b/Content.Client/Mapping/MappingSystem.cs @@ -83,7 +83,7 @@ private void OnFillActionSlot(FillActionSlotEvent ev) if (tileDef is not ContentTileDefinition contentTileDef) return; - var tileIcon = contentTileDef.IsSpace + var tileIcon = contentTileDef.MapAtmosphere ? _spaceIcon : new Texture(contentTileDef.Sprite!.Value); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index ce5cf421aef..f0537079b97 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -403,7 +403,7 @@ private void InputManagerOnFirstChanceOnKeyEvent(KeyEventArgs keyEvent, KeyEvent Mod1 = mods[0], Mod2 = mods[1], Mod3 = mods[2], - Priority = 0, + Priority = _currentlyRebinding.Binding?.Priority ?? 0, Type = bindType, CanFocus = key == Keyboard.Key.MouseLeft || key == Keyboard.Key.MouseRight diff --git a/Content.Client/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveVisuals.cs b/Content.Client/Psionics/Glimmer/GlimmerReactiveVisuals.cs similarity index 100% rename from Content.Client/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveVisuals.cs rename to Content.Client/Psionics/Glimmer/GlimmerReactiveVisuals.cs diff --git a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs b/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUi.cs similarity index 92% rename from Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs rename to Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUi.cs index 0b5fc7ad38c..0d8accb9f86 100644 --- a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs +++ b/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUi.cs @@ -1,10 +1,11 @@ using Robust.Client.GameObjects; using Robust.Client.UserInterface; +using Content.Client.Psionics.UI; using Content.Client.UserInterface.Fragments; using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.CartridgeLoader; -namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges; +namespace Content.Client.Psionics.GlimmerMonitor; public sealed partial class GlimmerMonitorUi : UIFragment { diff --git a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml b/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml similarity index 93% rename from Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml rename to Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml index 119a1831e6e..3044680e27b 100644 --- a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml +++ b/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml @@ -1,4 +1,4 @@ - diff --git a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs b/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml.cs similarity index 96% rename from Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs rename to Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml.cs index 43d9202aa45..58bbee38a2f 100644 --- a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs +++ b/Content.Client/Psionics/GlimmerMonitor/GlimmerMonitorUiFragment.xaml.cs @@ -1,12 +1,12 @@ using System.Linq; using System.Numerics; -using Content.Client.Nyanotrasen.UserInterface; +using Content.Client.Psionics.UI; using Robust.Client.AutoGenerated; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges; +namespace Content.Client.Psionics.GlimmerMonitor; [GenerateTypedNameReferences] public sealed partial class GlimmerMonitorUiFragment : BoxContainer diff --git a/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs b/Content.Client/Psionics/Telepathy/PsionicChatUpdateSystem.cs similarity index 92% rename from Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs rename to Content.Client/Psionics/Telepathy/PsionicChatUpdateSystem.cs index 84602052fe7..7bb88764a1f 100644 --- a/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs +++ b/Content.Client/Psionics/Telepathy/PsionicChatUpdateSystem.cs @@ -1,8 +1,8 @@ -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Client.Chat.Managers; using Robust.Client.Player; -namespace Content.Client.Nyanotrasen.Chat +namespace Content.Client.Psionics.Chat { public sealed class PsionicChatUpdateSystem : EntitySystem { diff --git a/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs b/Content.Client/Psionics/UserInterface/AcceptPsionicsEUI.cs similarity index 100% rename from Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs rename to Content.Client/Psionics/UserInterface/AcceptPsionicsEUI.cs diff --git a/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs b/Content.Client/Psionics/UserInterface/AcceptPsionicsWindow.cs similarity index 100% rename from Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs rename to Content.Client/Psionics/UserInterface/AcceptPsionicsWindow.cs diff --git a/Content.Client/Nyanotrasen/UserInterface/GlimmerGraph.cs b/Content.Client/Psionics/UserInterface/GlimmerGraph.cs similarity index 97% rename from Content.Client/Nyanotrasen/UserInterface/GlimmerGraph.cs rename to Content.Client/Psionics/UserInterface/GlimmerGraph.cs index c4a9109dcd8..111c810acb1 100644 --- a/Content.Client/Nyanotrasen/UserInterface/GlimmerGraph.cs +++ b/Content.Client/Psionics/UserInterface/GlimmerGraph.cs @@ -4,7 +4,7 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; -namespace Content.Client.Nyanotrasen.UserInterface; +namespace Content.Client.Psionics.UI; public sealed class GlimmerGraph : Control { diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index ff4972d9d08..79c1909ebaf 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -20,7 +20,6 @@ using Content.Shared.Examine; using Content.Shared.Input; using Content.Shared.Radio; -using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; @@ -37,7 +36,7 @@ using Robust.Shared.Replays; using Robust.Shared.Timing; using Robust.Shared.Utility; -using Content.Client.Nyanotrasen.Chat; //Nyano - Summary: chat namespace. +using Content.Client.Psionics.Chat; namespace Content.Client.UserInterface.Systems.Chat; @@ -61,7 +60,7 @@ public sealed class ChatUIController : UIController [UISystemDependency] private readonly GhostSystem? _ghost = default; [UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default; [UISystemDependency] private readonly ChatSystem? _chatSys = default; - [UISystemDependency] private readonly PsionicChatUpdateSystem? _psionic = default!; //Nyano - Summary: makes the psionic chat available. + [UISystemDependency] private readonly PsionicChatUpdateSystem? _psionic = default!; //EE - Summary: makes the psionic chat available. [ValidatePrototypeId] private const string ChatNamePalette = "ChatNames"; @@ -82,7 +81,7 @@ public sealed class ChatUIController : UIController {SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin}, {SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio}, {SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead}, - {SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} //Nyano - Summary: adds the telepathic prefix =. + {SharedChatSystem.TelepathicPrefix, ChatSelectChannel.Telepathic} //EE - Summary: adds the telepathic prefix =. }; public static readonly Dictionary ChannelPrefixes = new() @@ -96,7 +95,7 @@ public sealed class ChatUIController : UIController {ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix}, {ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix}, {ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix}, - {ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix } //Nyano - Summary: associates telepathic with =. + {ChatSelectChannel.Telepathic, SharedChatSystem.TelepathicPrefix } //EE - Summary: associates telepathic with =. }; /// diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index d0325480acd..f2e19849b00 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -128,7 +128,7 @@ await server.WaitAssertion(() => metaSys.Update(1.0f); metaSys.Update(1.0f); respSys.Update(2.0f); - Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0001)); + Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002)); }); } diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 84e1afaf45c..88448e7b800 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -1006,15 +1006,10 @@ protected async Task AddAtmosphere(EntityUid? uid = null) await Server.WaitPost(() => { var atmosSystem = SEntMan.System(); - var atmos = SEntMan.EnsureComponent(target); var moles = new float[Atmospherics.AdjustedNumberOfGases]; moles[(int) Gas.Oxygen] = 21.824779f; moles[(int) Gas.Nitrogen] = 82.10312f; - atmosSystem.SetMapAtmosphere(target, false, new GasMixture(2500) - { - Temperature = 293.15f, - Moles = moles, - }, atmos); + atmosSystem.SetMapAtmosphere(target, false, new GasMixture(moles, Atmospherics.T20C)); }); } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index c1d2643d6fa..b3bd3392434 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -10,7 +10,7 @@ using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Timing; -using Content.Shared.Abilities.Psionics; //Nyano - Summary: Makes Mime psionic. +using Content.Shared.Psionics.Abilities; using Content.Shared.Speech.Muting; namespace Content.Server.Abilities.Mime diff --git a/Content.Server/Administration/Managers/AdminManager.Metrics.cs b/Content.Server/Administration/Managers/AdminManager.Metrics.cs new file mode 100644 index 00000000000..2fea931f1b9 --- /dev/null +++ b/Content.Server/Administration/Managers/AdminManager.Metrics.cs @@ -0,0 +1,98 @@ +using System.Diagnostics.Metrics; +using System.Runtime.InteropServices; +using Content.Server.Afk; +using Robust.Server.DataMetrics; + +namespace Content.Server.Administration.Managers; + +// Handles metrics reporting for active admin count and such. + +public sealed partial class AdminManager +{ + private Dictionary? _adminOnlineCounts; + + private const int SentinelRankId = -1; + + [Dependency] private readonly IMetricsManager _metrics = default!; + [Dependency] private readonly IAfkManager _afkManager = default!; + [Dependency] private readonly IMeterFactory _meterFactory = default!; + + private void InitializeMetrics() + { + _metrics.UpdateMetrics += MetricsOnUpdateMetrics; + + var meter = _meterFactory.Create("SS14.AdminManager"); + + meter.CreateObservableGauge( + "admins_online_count", + MeasureAdminCount, + null, + "The count of online admins"); + } + + private void MetricsOnUpdateMetrics() + { + _sawmill.Verbose("Updating metrics"); + + var dict = new Dictionary(); + + foreach (var (session, reg) in _admins) + { + var rankId = reg.RankId ?? SentinelRankId; + + ref var counts = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, rankId, out _); + + if (reg.Data.Active) + { + if (_afkManager.IsAfk(session)) + counts.afk += 1; + else + counts.active += 1; + } + else + { + counts.deadminned += 1; + } + } + + // Neither prometheus-net nor dotnet-counters seem to handle stuff well if we STOP returning measurements. + // i.e. if the last admin with a rank disconnects. + // So if we have EVER reported a rank, always keep reporting it. + if (_adminOnlineCounts != null) + { + foreach (var rank in _adminOnlineCounts.Keys) + { + CollectionsMarshal.GetValueRefOrAddDefault(dict, rank, out _); + } + } + + // Make sure "no rank" is always available. Avoid "no data". + CollectionsMarshal.GetValueRefOrAddDefault(dict, SentinelRankId, out _); + + _adminOnlineCounts = dict; + } + + private IEnumerable> MeasureAdminCount() + { + if (_adminOnlineCounts == null) + yield break; + + foreach (var (rank, (active, afk, deadminned)) in _adminOnlineCounts) + { + yield return new Measurement( + active, + new KeyValuePair("state", "active"), + new KeyValuePair("rank", rank == SentinelRankId ? "none" : rank.ToString())); + + yield return new Measurement( + afk, + new KeyValuePair("state", "afk"), + new KeyValuePair("rank", rank == SentinelRankId ? "none" : rank.ToString())); + + yield return new Measurement( + deadminned, + new KeyValuePair("state", "deadminned"), + new KeyValuePair("rank", rank == SentinelRankId ? "none" : rank.ToString())); + } + } +} diff --git a/Content.Server/Administration/Managers/AdminManager.cs b/Content.Server/Administration/Managers/AdminManager.cs index 4eaa08fe9dd..b1cca46e63f 100644 --- a/Content.Server/Administration/Managers/AdminManager.cs +++ b/Content.Server/Administration/Managers/AdminManager.cs @@ -23,7 +23,7 @@ namespace Content.Server.Administration.Managers { - public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation + public sealed partial class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation { [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IServerDbManager _dbManager = default!; @@ -34,6 +34,7 @@ public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupCont [Dependency] private readonly IServerConsoleHost _consoleHost = default!; [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly ToolshedManager _toolshed = default!; + [Dependency] private readonly ILogManager _logManager = default!; private readonly Dictionary _admins = new(); private readonly HashSet _promotedPlayers = new(); @@ -49,6 +50,8 @@ public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupCont private readonly AdminCommandPermissions _commandPermissions = new(); private readonly AdminCommandPermissions _toolshedCommandPermissions = new(); + private ISawmill _sawmill = default!; + public bool IsAdmin(ICommonSession session, bool includeDeAdmin = false) { return GetAdminData(session, includeDeAdmin) != null; @@ -181,6 +184,8 @@ public void ReloadAdminsWithRank(int rankId) public void Initialize() { + _sawmill = _logManager.GetSawmill("admin"); + _netMgr.RegisterNetMessage(); // Cache permissions for loaded console commands with the requisite attributes. @@ -234,6 +239,8 @@ public void Initialize() } _toolshed.ActivePermissionController = this; + + InitializeMetrics(); } public void PromoteHost(ICommonSession player) diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 8a819f59420..8ee52ad03e7 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -409,7 +409,7 @@ private void AddSmiteVerbs(GetVerbsEvent args) var fixtures = Comp(args.Target); xform.Anchored = false; // Just in case. _physics.SetBodyType(args.Target, BodyType.Dynamic, manager: fixtures, body: physics); - _physics.SetBodyStatus(physics, BodyStatus.InAir); + _physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir); _physics.WakeBody(args.Target, manager: fixtures, body: physics); foreach (var fixture in fixtures.Fixtures.Values) @@ -424,8 +424,8 @@ private void AddSmiteVerbs(GetVerbsEvent args) _physics.SetLinearVelocity(args.Target, _random.NextVector2(1.5f, 1.5f), manager: fixtures, body: physics); _physics.SetAngularVelocity(args.Target, MathF.PI * 12, manager: fixtures, body: physics); - _physics.SetLinearDamping(physics, 0f); - _physics.SetAngularDamping(physics, 0f); + _physics.SetLinearDamping(args.Target, physics, 0f); + _physics.SetAngularDamping(args.Target, physics, 0f); }, Impact = LogImpact.Extreme, Message = Loc.GetString("admin-smite-pinball-description") @@ -444,7 +444,7 @@ private void AddSmiteVerbs(GetVerbsEvent args) xform.Anchored = false; // Just in case. _physics.SetBodyType(args.Target, BodyType.Dynamic, body: physics); - _physics.SetBodyStatus(physics, BodyStatus.InAir); + _physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir); _physics.WakeBody(args.Target, manager: fixtures, body: physics); foreach (var fixture in fixtures.Fixtures.Values) @@ -454,8 +454,8 @@ private void AddSmiteVerbs(GetVerbsEvent args) _physics.SetLinearVelocity(args.Target, _random.NextVector2(8.0f, 8.0f), manager: fixtures, body: physics); _physics.SetAngularVelocity(args.Target, MathF.PI * 12, manager: fixtures, body: physics); - _physics.SetLinearDamping(physics, 0f); - _physics.SetAngularDamping(physics, 0f); + _physics.SetLinearDamping(args.Target, physics, 0f); + _physics.SetAngularDamping(args.Target, physics, 0f); }, Impact = LogImpact.Extreme, Message = Loc.GetString("admin-smite-yeet-description") diff --git a/Content.Server/Anomaly/AnomalySystem.Psionics.cs b/Content.Server/Anomaly/AnomalySystem.Psionics.cs index 95fda1d5035..84f200f47ba 100644 --- a/Content.Server/Anomaly/AnomalySystem.Psionics.cs +++ b/Content.Server/Anomaly/AnomalySystem.Psionics.cs @@ -1,4 +1,4 @@ -using Content.Server.Abilities.Psionics; //Nyano - Summary: the psniocs bin where dispel is located. +using Content.Server.Psionics.Abilities; using Content.Shared.Anomaly; using Content.Shared.Anomaly.Components; using Robust.Shared.Random; @@ -14,8 +14,6 @@ private void InitializePsionics() { SubscribeLocalEvent(OnDispelled); } - - //Nyano - Summary: gives dispellable behavior to Anomalies. private void OnDispelled(EntityUid uid, AnomalyComponent component, DispelledEvent args) { _dispel.DealDispelDamage(uid); diff --git a/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs b/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs new file mode 100644 index 00000000000..6f04cfb2da6 --- /dev/null +++ b/Content.Server/Atmos/Commands/SetMapAtmosCommand.cs @@ -0,0 +1,94 @@ +using Content.Server.Administration; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Administration; +using Content.Shared.Atmos; +using Robust.Shared.Console; +using Robust.Shared.Map; + +namespace Content.Server.Atmos.Commands; + +[AdminCommand(AdminFlags.Admin)] +public sealed class AddMapAtmosCommand : LocalizedCommands +{ + [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IMapManager _map = default!; + + private const string _cmd = "cmd-set-map-atmos"; + public override string Command => "setmapatmos"; + public override string Description => Loc.GetString($"{_cmd}-desc"); + public override string Help => Loc.GetString($"{_cmd}-help"); + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length < 2) + { + shell.WriteLine(Help); + return; + } + + int.TryParse(args[0], out var id); + var map = _map.GetMapEntityId(new MapId(id)); + if (!map.IsValid()) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", args[0]))); + return; + } + + if (!bool.TryParse(args[1], out var space)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[1]))); + return; + } + + if (space || args.Length < 4) + { + _entities.RemoveComponent(map); + shell.WriteLine(Loc.GetString($"{_cmd}-removed", ("map", id))); + return; + } + + if (!float.TryParse(args[2], out var temp)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2]))); + return; + } + + var mix = new GasMixture(Atmospherics.CellVolume) {Temperature = Math.Min(temp, Atmospherics.TCMB)}; + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) + { + if (args.Length == 3 + i) + break; + + if (!float.TryParse(args[3+i], out var moles)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3+i]))); + return; + } + + mix.AdjustMoles(i, moles); + } + + var atmos = _entities.EntitySysManager.GetEntitySystem(); + atmos.SetMapAtmosphere(map, space, mix); + shell.WriteLine(Loc.GetString($"{_cmd}-updated", ("map", id))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entities), Loc.GetString($"{_cmd}-hint-map")); + + if (args.Length == 2) + return CompletionResult.FromHintOptions(new[]{ "false", "true"}, Loc.GetString($"{_cmd}-hint-space")); + + if (!bool.TryParse(args[1], out var space) || space) + return CompletionResult.Empty; + + if (args.Length == 3) + return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-temp")); + + var gas = (Gas) args.Length - 4; + return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-gas" , ("gas", gas.ToString()))); + } +} diff --git a/Content.Server/Atmos/Components/AirtightComponent.cs b/Content.Server/Atmos/Components/AirtightComponent.cs index 897981724c9..ca107eafbe8 100644 --- a/Content.Server/Atmos/Components/AirtightComponent.cs +++ b/Content.Server/Atmos/Components/AirtightComponent.cs @@ -1,9 +1,10 @@ +using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.Atmos.Components { - [RegisterComponent] + [RegisterComponent, Access(typeof(AirtightSystem))] public sealed partial class AirtightComponent : Component { public (EntityUid Grid, Vector2i Tile) LastPosition { get; set; } @@ -29,6 +30,7 @@ public sealed partial class AirtightComponent : Component [DataField("noAirWhenFullyAirBlocked")] public bool NoAirWhenFullyAirBlocked { get; set; } = true; + [Access(Other = AccessPermissions.ReadWriteExecute)] public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection; } } diff --git a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs index 7fcd63bc5d8..e682fd09644 100644 --- a/Content.Server/Atmos/Components/GridAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/GridAtmosphereComponent.cs @@ -28,6 +28,9 @@ public sealed partial class GridAtmosphereComponent : Component [IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))] public Dictionary Tiles = new(1000); + [ViewVariables] + public HashSet MapTiles = new(1000); + [ViewVariables] public readonly HashSet ActiveTiles = new(1000); @@ -80,7 +83,10 @@ public sealed partial class GridAtmosphereComponent : Component public readonly HashSet InvalidatedCoords = new(1000); [ViewVariables] - public readonly Queue CurrentRunInvalidatedCoordinates = new(); + public readonly Queue CurrentRunInvalidatedTiles = new(); + + [ViewVariables] + public readonly List PossiblyDisconnectedTiles = new(100); [ViewVariables] public int InvalidatedCoordsCount => InvalidatedCoords.Count; diff --git a/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs b/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs index a58d3a3c122..710c37b62fe 100644 --- a/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs +++ b/Content.Server/Atmos/Components/IgniteOnCollideComponent.cs @@ -1,8 +1,6 @@ -using Content.Server.Atmos.EntitySystems; - namespace Content.Server.Atmos.Components; -[RegisterComponent, Access(typeof(FlammableSystem))] +[RegisterComponent] public sealed partial class IgniteOnCollideComponent : Component { /// diff --git a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs index bbf5ea6403e..6bdef901d44 100644 --- a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs @@ -12,12 +12,14 @@ public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponen /// /// The default GasMixture a map will have. Space mixture by default. /// - [DataField("mixture"), ViewVariables(VVAccess.ReadWrite)] - public GasMixture? Mixture = GasMixture.SpaceGas; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public GasMixture Mixture = GasMixture.SpaceGas; /// /// Whether empty tiles will be considered space or not. /// - [DataField("space"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public bool Space = true; + + public SharedGasTileOverlaySystem.GasOverlayData Overlay; } diff --git a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs index 97dccbaabb7..548d6a36926 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Explosion.EntitySystems; using Content.Shared.Atmos; using JetBrains.Annotations; -using Robust.Shared.Map; using Robust.Shared.Map.Components; namespace Content.Server.Atmos.EntitySystems @@ -10,7 +9,7 @@ namespace Content.Server.Atmos.EntitySystems [UsedImplicitly] public sealed class AirtightSystem : EntitySystem { - [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly ExplosionSystem _explosionSystem = default!; @@ -121,19 +120,16 @@ public void UpdatePosition(Entity ent, TransformComponent? xf if (!xform.Anchored || !TryComp(xform.GridUid, out MapGridComponent? grid)) return; - airtight.LastPosition = (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates)); - InvalidatePosition(airtight.LastPosition.Item1, airtight.LastPosition.Item2, airtight.FixVacuum && !airtight.AirBlocked); + var indices = _transform.GetGridTilePositionOrDefault((ent, xform), grid); + airtight.LastPosition = (xform.GridUid.Value, indices); + InvalidatePosition((xform.GridUid.Value, grid), indices); } - public void InvalidatePosition(EntityUid gridId, Vector2i pos, bool fixVacuum = false) + public void InvalidatePosition(Entity grid, Vector2i pos) { - if (!TryComp(gridId, out MapGridComponent? grid)) - return; - var query = EntityManager.GetEntityQuery(); - _explosionSystem.UpdateAirtightMap(gridId, pos, grid, query); - // TODO make atmos system use query - _atmosphereSystem.InvalidateTile(gridId, pos); + _explosionSystem.UpdateAirtightMap(grid, pos, grid, query); + _atmosphereSystem.InvalidateTile(grid.Owner, pos); } private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle) diff --git a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs index 4af32fce58f..c0284f26c90 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs @@ -97,22 +97,19 @@ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) } } - private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile) + private AtmosDebugOverlayData? ConvertTileToData(TileAtmosphere tile) { - if (tile == null) - return default; - return new AtmosDebugOverlayData( tile.GridIndices, tile.Air?.Temperature ?? default, tile.Air?.Moles, tile.PressureDirection, tile.LastPressureDirection, - tile.BlockedAirflow, + tile.AirtightData.BlockedDirections, tile.ExcitedGroup?.GetHashCode(), tile.Space, - false, - false); + tile.MapAtmosphere, + tile.NoGridTile); } public override void Update(float frameTime) diff --git a/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs b/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs deleted file mode 100644 index aed009e9a12..00000000000 --- a/Content.Server/Atmos/EntitySystems/AtmosObstructionEnumerator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Server.Atmos.Components; -using Robust.Shared.Map; -using Robust.Shared.Map.Enumerators; - -namespace Content.Server.Atmos.EntitySystems; - -public struct AtmosObstructionEnumerator -{ - private AnchoredEntitiesEnumerator _enumerator; - private EntityQuery _query; - - public AtmosObstructionEnumerator(AnchoredEntitiesEnumerator enumerator, EntityQuery query) - { - _enumerator = enumerator; - _query = query; - } - - public bool MoveNext([NotNullWhen(true)] out AirtightComponent? airtight) - { - if (!_enumerator.MoveNext(out var uid)) - { - airtight = null; - return false; - } - - // No rider, it makes it uglier. - // ReSharper disable once ConvertIfStatementToReturnStatement - if (!_query.TryGetComponent(uid.Value, out airtight)) - { - // ReSharper disable once TailRecursiveCall - return MoveNext(out airtight); - } - - return true; - } -} diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs index 310e602336a..cece99cacf6 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -85,10 +85,10 @@ public IEnumerable GetAllMixtures(EntityUid gridUid, bool excite = f return ev.Mixtures!; } - public void InvalidateTile(EntityUid gridUid, Vector2i tile) + public void InvalidateTile(Entity entity, Vector2i tile) { - var ev = new InvalidateTileMethodEvent(gridUid, tile); - RaiseLocalEvent(gridUid, ref ev); + if (_atmosQuery.Resolve(entity.Owner, ref entity.Comp, false)) + entity.Comp.InvalidatedCoords.Add(tile); } public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List tiles, bool excite = false) @@ -176,11 +176,11 @@ public ReactionResult ReactTile(EntityUid gridId, Vector2i tile) public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null) { - var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp); - RaiseLocalEvent(gridUid, ref ev); + if (!Resolve(gridUid, ref mapGridComp)) + return false; - // If nothing handled the event, it'll default to true. - return ev.Result; + var data = GetAirtightData(gridUid, mapGridComp, tile); + return data.BlockedDirections.IsFlagSet(directions); } public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null) @@ -231,12 +231,6 @@ public IEnumerable GetAdjacentTileMixtures(EntityUid gridUid, Vector return ev.Result ?? Enumerable.Empty(); } - public void UpdateAdjacent(EntityUid gridUid, Vector2i tile, MapGridComponent? mapGridComp = null) - { - var ev = new UpdateAdjacentMethodEvent(gridUid, tile, mapGridComp); - RaiseLocalEvent(gridUid, ref ev); - } - public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume, EntityUid? sparkSourceUid = null, bool soh = false) { @@ -259,12 +253,6 @@ public bool IsHotspotActive(EntityUid gridUid, Vector2i tile) return ev.Result; } - public void FixTileVacuum(EntityUid gridUid, Vector2i tile) - { - var ev = new FixTileVacuumMethodEvent(gridUid, tile); - RaiseLocalEvent(gridUid, ref ev); - } - public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet) { var ev = new AddPipeNetMethodEvent(gridUid, pipeNet); @@ -307,9 +295,6 @@ [ByRefEvent] private record struct IsSimulatedGridMethodEvent [ByRefEvent] private record struct GetAllMixturesMethodEvent (EntityUid Grid, bool Excite = false, IEnumerable? Mixtures = null, bool Handled = false); - [ByRefEvent] private record struct InvalidateTileMethodEvent - (EntityUid Grid, Vector2i Tile, bool Handled = false); - [ByRefEvent] private record struct GetTileMixturesMethodEvent (EntityUid? GridUid, EntityUid? MapUid, List Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false); @@ -319,16 +304,6 @@ [ByRefEvent] private record struct GetTileMixtureMethodEvent [ByRefEvent] private record struct ReactTileMethodEvent (EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false); - [ByRefEvent] private record struct IsTileAirBlockedMethodEvent - (EntityUid Grid, Vector2i Tile, AtmosDirection Direction = AtmosDirection.All, MapGridComponent? MapGridComponent = null, bool Result = false, bool Handled = false) - { - /// - /// True if one of the enabled blockers has . Note - /// that this does not actually check if all directions are blocked. - /// - public bool NoAir = false; - } - [ByRefEvent] private record struct IsTileSpaceMethodEvent (EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false); @@ -339,9 +314,6 @@ [ByRefEvent] private record struct GetAdjacentTileMixturesMethodEvent (EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite, IEnumerable? Result = null, bool Handled = false); - [ByRefEvent] private record struct UpdateAdjacentMethodEvent - (EntityUid Grid, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Handled = false); - [ByRefEvent] private record struct HotspotExposeMethodEvent (EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false); @@ -351,9 +323,6 @@ [ByRefEvent] private record struct HotspotExtinguishMethodEvent [ByRefEvent] private record struct IsHotspotActiveMethodEvent (EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false); - [ByRefEvent] private record struct FixTileVacuumMethodEvent - (EntityUid Grid, Vector2i Tile, bool Handled = false); - [ByRefEvent] private record struct AddPipeNetMethodEvent (EntityUid Grid, PipeNet PipeNet, bool Handled = false); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs index 1d809dcd032..de4c9199cf7 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs @@ -72,7 +72,8 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E var tileSize = excitedGroup.Tiles.Count; - if (excitedGroup.Disposed) return; + if (excitedGroup.Disposed) + return; if (tileSize == 0) { @@ -98,7 +99,9 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E foreach (var tile in excitedGroup.Tiles) { - if (tile?.Air == null) continue; + if (tile?.Air == null) + continue; + tile.Air.CopyFromMutable(combined); InvalidateVisuals(tile.GridIndex, tile.GridIndices); } @@ -106,21 +109,23 @@ private void ExcitedGroupSelfBreakdown(GridAtmosphereComponent gridAtmosphere, E excitedGroup.BreakdownCooldown = 0; } - private void ExcitedGroupDismantle(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup, bool unexcite = true) + /// + /// This de-activates and removes all tiles in an excited group. + /// + private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup) { foreach (var tile in excitedGroup.Tiles) { tile.ExcitedGroup = null; - - if (!unexcite) - continue; - RemoveActiveTile(gridAtmosphere, tile); } excitedGroup.Tiles.Clear(); } + /// + /// This removes an excited group without de-activating its tiles. + /// private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup) { if (excitedGroup.Disposed) @@ -129,9 +134,14 @@ private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, Excited DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!"); excitedGroup.Disposed = true; - gridAtmosphere.ExcitedGroups.Remove(excitedGroup); - ExcitedGroupDismantle(gridAtmosphere, excitedGroup, false); + + foreach (var tile in excitedGroup.Tiles) + { + tile.ExcitedGroup = null; + } + + excitedGroup.Tiles.Clear(); } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index 1f1a208b24b..d43cc81b0f8 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -14,6 +14,7 @@ public sealed partial class AtmosphereSystem private void InitializeGridAtmosphere() { SubscribeLocalEvent(OnGridAtmosphereInit); + SubscribeLocalEvent(OnGridAtmosphereStartup); SubscribeLocalEvent(OnAtmosphereRemove); SubscribeLocalEvent(OnGridSplit); @@ -22,19 +23,15 @@ private void InitializeGridAtmosphere() SubscribeLocalEvent(GridHasAtmosphere); SubscribeLocalEvent(GridIsSimulated); SubscribeLocalEvent(GridGetAllMixtures); - SubscribeLocalEvent(GridInvalidateTile); SubscribeLocalEvent(GridGetTileMixture); SubscribeLocalEvent(GridGetTileMixtures); SubscribeLocalEvent(GridReactTile); - SubscribeLocalEvent(GridIsTileAirBlocked); SubscribeLocalEvent(GridIsTileSpace); SubscribeLocalEvent(GridGetAdjacentTiles); SubscribeLocalEvent(GridGetAdjacentTileMixtures); - SubscribeLocalEvent(GridUpdateAdjacent); SubscribeLocalEvent(GridHotspotExpose); SubscribeLocalEvent(GridHotspotExtinguish); SubscribeLocalEvent(GridIsHotspotActive); - SubscribeLocalEvent(GridFixTileVacuum); SubscribeLocalEvent(GridAddPipeNet); SubscribeLocalEvent(GridRemovePipeNet); SubscribeLocalEvent(GridAddAtmosDevice); @@ -56,22 +53,23 @@ private void OnAtmosphereRemove(EntityUid uid, GridAtmosphereComponent component } } - private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args) + private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args) { base.Initialize(); - if (!TryComp(uid, out MapGridComponent? mapGrid)) - return; - EnsureComp(uid); - - foreach (var (indices, tile) in gridAtmosphere.Tiles) + foreach (var tile in component.Tiles.Values) { - gridAtmosphere.InvalidatedCoords.Add(indices); tile.GridIndex = uid; } + } + + private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args) + { + if (!TryComp(uid, out MapGridComponent? mapGrid)) + return; - GridRepopulateTiles((uid, mapGrid, gridAtmosphere)); + InvalidateAllTiles((uid, mapGrid, component)); } private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args) @@ -104,8 +102,7 @@ private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmo continue; // Copy a bunch of data over... Not great, maybe put this in TileAtmosphere? - newTileAtmosphere.Air = tileAtmosphere.Air?.Clone() ?? null; - newTileAtmosphere.MolesArchived = newTileAtmosphere.Air == null ? null : new float[Atmospherics.AdjustedNumberOfGases]; + newTileAtmosphere.Air = tileAtmosphere.Air?.Clone(); newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot; newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity; newTileAtmosphere.Temperature = tileAtmosphere.Temperature; @@ -170,15 +167,6 @@ IEnumerable EnumerateMixtures(EntityUid gridUid, GridAtmosphereCompo args.Handled = true; } - private void GridInvalidateTile(EntityUid uid, GridAtmosphereComponent component, ref InvalidateTileMethodEvent args) - { - if (args.Handled) - return; - - component.InvalidatedCoords.Add(args.Tile); - args.Handled = true; - } - private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component, ref GetTileMixtureMethodEvent args) { @@ -233,43 +221,6 @@ private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref args.Handled = true; } - private void GridIsTileAirBlocked(EntityUid uid, GridAtmosphereComponent component, - ref IsTileAirBlockedMethodEvent args) - { - if (args.Handled) - return; - - var mapGridComp = args.MapGridComponent; - - if (!Resolve(uid, ref mapGridComp)) - return; - - var directions = AtmosDirection.Invalid; - - var enumerator = GetObstructingComponentsEnumerator(mapGridComp, args.Tile); - - while (enumerator.MoveNext(out var obstructingComponent)) - { - if (!obstructingComponent.AirBlocked) - continue; - - // We set the directions that are air-blocked so far, - // as you could have a full obstruction with only 4 directional air blockers. - directions |= obstructingComponent.AirBlockedDirection; - args.NoAir |= obstructingComponent.NoAirWhenFullyAirBlocked; - - if (directions.IsFlagSet(args.Direction)) - { - args.Result = true; - args.Handled = true; - return; - } - } - - args.Result = false; - args.Handled = true; - } - private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args) { if (args.Handled) @@ -331,71 +282,58 @@ IEnumerable EnumerateAdjacent(GridAtmosphereComponent grid, TileAtmo args.Handled = true; } - private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component, - ref UpdateAdjacentMethodEvent args) + /// + /// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies. + /// + private void UpdateAdjacentTiles( + Entity ent, + TileAtmosphere tile, + bool activate = false) { - if (args.Handled) - return; - - var mapGridComp = args.MapGridComponent; - - if (!Resolve(uid, ref mapGridComp)) - return; - - var xform = Transform(uid); - EntityUid? mapUid = _mapManager.MapExists(xform.MapID) ? _mapManager.GetMapEntityId(xform.MapID) : null; - - if (!component.Tiles.TryGetValue(args.Tile, out var tile)) - return; + var uid = ent.Owner; + var atmos = ent.Comp1; + var blockedDirs = tile.AirtightData.BlockedDirections; + if (activate) + AddActiveTile(atmos, tile); tile.AdjacentBits = AtmosDirection.Invalid; - tile.BlockedAirflow = GetBlockedDirections(mapGridComp, tile.GridIndices); - for (var i = 0; i < Atmospherics.Directions; i++) { var direction = (AtmosDirection) (1 << i); + var adjacentIndices = tile.GridIndices.Offset(direction); - var otherIndices = tile.GridIndices.Offset(direction); - - if (!component.Tiles.TryGetValue(otherIndices, out var adjacent)) + TileAtmosphere? adjacent; + if (!tile.NoGridTile) { - adjacent = new TileAtmosphere(tile.GridIndex, otherIndices, - GetTileMixture(null, mapUid, otherIndices), - space: IsTileSpace(null, mapUid, otherIndices, mapGridComp)); + adjacent = GetOrNewTile(uid, atmos, adjacentIndices); } - - var oppositeDirection = direction.GetOpposite(); - - adjacent.BlockedAirflow = GetBlockedDirections(mapGridComp, adjacent.GridIndices); - - // Pass in MapGridComponent so we don't have to resolve it for every adjacent direction. - var tileBlockedEv = new IsTileAirBlockedMethodEvent(uid, tile.GridIndices, direction, mapGridComp); - GridIsTileAirBlocked(uid, component, ref tileBlockedEv); - - var adjacentBlockedEv = - new IsTileAirBlockedMethodEvent(uid, adjacent.GridIndices, oppositeDirection, mapGridComp); - GridIsTileAirBlocked(uid, component, ref adjacentBlockedEv); - - if (!adjacent.BlockedAirflow.IsFlagSet(oppositeDirection) && !tileBlockedEv.Result) + else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent)) { - adjacent.AdjacentBits |= oppositeDirection; - adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile; + tile.AdjacentBits &= ~direction; + tile.AdjacentTiles[i] = null; + continue; } - else + + var adjBlockDirs = adjacent.AirtightData.BlockedDirections; + if (activate) + AddActiveTile(atmos, adjacent); + + var oppositeDirection = direction.GetOpposite(); + if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction)) { + // Adjacency is blocked by some airtight entity. + tile.AdjacentBits &= ~direction; adjacent.AdjacentBits &= ~oppositeDirection; + tile.AdjacentTiles[i] = null; adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null; } - - if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result) - { - tile.AdjacentBits |= direction; - tile.AdjacentTiles[direction.ToIndex()] = adjacent; - } else { - tile.AdjacentBits &= ~direction; - tile.AdjacentTiles[direction.ToIndex()] = null; + // No airtight entity in the way. + tile.AdjacentBits |= direction; + adjacent.AdjacentBits |= oppositeDirection; + tile.AdjacentTiles[i] = adjacent; + adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile; } DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^ @@ -409,6 +347,16 @@ private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component tile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid; } + private (GasMixture Air, bool IsSpace) GetDefaultMapAtmosphere(MapAtmosphereComponent? map) + { + if (map == null) + return (GasMixture.SpaceGas, true); + + var air = map.Mixture; + DebugTools.Assert(air.Immutable); + return (air, map.Space); + } + private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args) { if (args.Handled) @@ -451,54 +399,50 @@ private void GridIsHotspotActive(EntityUid uid, GridAtmosphereComponent componen args.Handled = true; } - private void GridFixTileVacuum(EntityUid uid, GridAtmosphereComponent component, ref FixTileVacuumMethodEvent args) + private void GridFixTileVacuum( + Entity ent, + TileAtmosphere tile, + float volume) { - if (args.Handled) - return; - - var adjEv = new GetAdjacentTileMixturesMethodEvent(uid, args.Tile, false, true); - GridGetAdjacentTileMixtures(uid, component, ref adjEv); - - if (!adjEv.Handled || !component.Tiles.TryGetValue(args.Tile, out var tile)) - return; - - if (!TryComp(uid, out var mapGridComp)) - return; - - var adjacent = adjEv.Result!.ToArray(); - - // Return early, let's not cause any funny NaNs or needless vacuums. - if (adjacent.Length == 0) - return; + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable == false ); + Array.Clear(tile.MolesArchived); + tile.ArchivedCycle = 0; - tile.Air = new GasMixture + var count = 0; + foreach (var adj in tile.AdjacentTiles) { - Volume = GetVolumeForTiles(mapGridComp, 1), - Temperature = Atmospherics.T20C - }; - - tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; - tile.ArchivedCycle = 0; + if (adj?.Air != null) + count++; + } - var ratio = 1f / adjacent.Length; + var ratio = 1f / count; var totalTemperature = 0f; - foreach (var adj in adjacent) + foreach (var adj in tile.AdjacentTiles) { + if (adj?.Air == null) + continue; + totalTemperature += adj.Temperature; + // TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles? + // Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary. + // if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather + // than having to iterate over adjacent tiles twice. + // Remove a bit of gas from the adjacent ratio... - var mix = adj.RemoveRatio(ratio); + var mix = adj.Air.RemoveRatio(ratio); // And merge it to the new tile air. Merge(tile.Air, mix); // Return removed gas to its original mixture. - Merge(adj, mix); + Merge(adj.Air, mix); } // New temperature is the arithmetic mean of the sum of the adjacent temperatures... - tile.Air.Temperature = totalTemperature / adjacent.Length; + tile.Air.Temperature = totalTemperature / count; } private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args) @@ -547,30 +491,21 @@ private void GridRemoveAtmosDevice(EntityUid uid, GridAtmosphereComponent compon /// /// Repopulates all tiles on a grid atmosphere. /// - /// The grid where to get all valid tiles from. - /// The grid atmosphere where the tiles will be repopulated. - private void GridRepopulateTiles(Entity grid) + public void InvalidateAllTiles(Entity entity) { - var (uid, mapGrid, gridAtmosphere) = grid; - var volume = GetVolumeForTiles(mapGrid, 1); + var (uid, grid, atmos) = entity; + if (!Resolve(uid, ref grid, ref atmos)) + return; - foreach (var tile in mapGrid.GetAllTiles()) + foreach (var indices in atmos.Tiles.Keys) { - if (!gridAtmosphere.Tiles.ContainsKey(tile.GridIndices)) - gridAtmosphere.Tiles[tile.GridIndices] = new TileAtmosphere(tile.GridUid, tile.GridIndices, - new GasMixture(volume) { Temperature = Atmospherics.T20C }); - - gridAtmosphere.InvalidatedCoords.Add(tile.GridIndices); + atmos.InvalidatedCoords.Add(indices); } - TryComp(uid, out GasTileOverlayComponent? overlay); - - // Gotta do this afterwards so we can properly update adjacent tiles. - foreach (var (position, _) in gridAtmosphere.Tiles.ToArray()) + var enumerator = _map.GetAllTilesEnumerator(uid, grid); + while (enumerator.MoveNext(out var tile)) { - var ev = new UpdateAdjacentMethodEvent(uid, position); - GridUpdateAdjacent(uid, gridAtmosphere, ref ev); - InvalidateVisuals(uid, position, overlay); + atmos.InvalidatedCoords.Add(tile.Value.GridIndices); } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs index 53035e1ed3c..77b5bf18af2 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.HighPressureDelta.cs @@ -1,13 +1,11 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos; -using Content.Shared.Audio; using Content.Shared.Mobs.Components; using Content.Shared.Physics; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; -using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -54,7 +52,7 @@ private void UpdateHighPressure(float frameTime) if (HasComp(uid) && TryComp(uid, out var body)) { - _physics.SetBodyStatus(body, BodyStatus.OnGround); + _physics.SetBodyStatus(uid, body, BodyStatus.OnGround); } if (TryComp(uid, out var fixtures)) @@ -77,7 +75,7 @@ private void AddMobMovedByPressure(EntityUid uid, MovedByPressureComponent compo if (!TryComp(uid, out var fixtures)) return; - _physics.SetBodyStatus(body, BodyStatus.InAir); + _physics.SetBodyStatus(uid, body, BodyStatus.InAir); foreach (var (id, fixture) in fixtures.Fixtures) { @@ -96,9 +94,9 @@ private void HighPressureMovements(Entity gridAtmospher // TODO ATMOS finish this // Don't play the space wind sound on tiles that are on fire... - if(tile.PressureDifference > 15 && !tile.Hotspot.Valid) + if (tile.PressureDifference > 15 && !tile.Hotspot.Valid) { - if(_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound)) + if (_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound)) { var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices); _audio.PlayPvs(SpaceWindSound, coordinates, AudioParams.Default.WithVariation(0.125f).WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100))); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index 795c6e0547e..c27e18b55b0 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -1,12 +1,13 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; +using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems { public sealed partial class AtmosphereSystem { - private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent? visuals) + private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent visuals) { // Can't process a tile without air if (tile.Air == null) @@ -116,15 +117,9 @@ private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere private void Archive(TileAtmosphere tile, int fireCount) { if (tile.Air != null) - { tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan()); - tile.TemperatureArchived = tile.Air.Temperature; - } - else - { - tile.TemperatureArchived = tile.Temperature; - } + tile.TemperatureArchived = tile.Temperature; tile.ArchivedCycle = fireCount; } @@ -166,6 +161,12 @@ private void AddActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmospher /// Whether to dispose of the tile's private void RemoveActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, bool disposeExcitedGroup = true) { + DebugTools.Assert(tile.Excited == gridAtmosphere.ActiveTiles.Contains(tile)); + DebugTools.Assert(tile.Excited || tile.ExcitedGroup == null); + + if (!tile.Excited) + return; + tile.Excited = false; gridAtmosphere.ActiveTiles.Remove(tile); @@ -186,7 +187,6 @@ public float GetHeatCapacityArchived(TileAtmosphere tile) if (tile.Air == null) return tile.HeatCapacity; - // Moles archived is not null if air is not null. return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs index 916191cb050..ed105c8d33f 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs @@ -1,6 +1,8 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos.Components; using Robust.Shared.GameStates; +using Robust.Shared.Map.Components; +using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems; @@ -8,10 +10,25 @@ public partial class AtmosphereSystem { private void InitializeMap() { + SubscribeLocalEvent(OnMapStartup); + SubscribeLocalEvent(OnMapRemove); SubscribeLocalEvent(MapIsTileSpace); SubscribeLocalEvent(MapGetTileMixture); SubscribeLocalEvent(MapGetTileMixtures); SubscribeLocalEvent(OnMapGetState); + SubscribeLocalEvent(OnGridParentChanged); + } + + private void OnMapStartup(EntityUid uid, MapAtmosphereComponent component, ComponentInit args) + { + component.Mixture.MarkImmutable(); + component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture); + } + + private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, ComponentRemove args) + { + if (!TerminatingOrDeleted(uid)) + RefreshAllGridMapAtmospheres(uid); } private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args) @@ -28,54 +45,115 @@ private void MapGetTileMixture(EntityUid uid, MapAtmosphereComponent component, if (args.Handled) return; - // Clone the mixture, if possible. - args.Mixture = component.Mixture?.Clone(); + args.Mixture = component.Mixture; args.Handled = true; } private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args) { - if (args.Handled || component.Mixture == null) + if (args.Handled) return; args.Handled = true; args.Mixtures ??= new GasMixture?[args.Tiles.Count]; for (var i = 0; i < args.Tiles.Count; i++) { - args.Mixtures[i] ??= component.Mixture.Clone(); + args.Mixtures[i] ??= component.Mixture; } } private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args) { - args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture)); + args.State = new MapAtmosphereComponentState(component.Overlay); + } + + public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture) + { + DebugTools.Assert(HasComp(uid)); + var component = EnsureComp(uid); + SetMapGasMixture(uid, mixture, component, false); + SetMapSpace(uid, space, component, false); + RefreshAllGridMapAtmospheres(uid); } - public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null) + public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true) { if (!Resolve(uid, ref component)) return; - component.Space = space; + if (!mixture.Immutable) + { + mixture = mixture.Clone(); + mixture.MarkImmutable(); + } + component.Mixture = mixture; - Dirty(component); + component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture); + Dirty(uid, component); + if (updateTiles) + RefreshAllGridMapAtmospheres(uid); } - public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null) + public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true) { if (!Resolve(uid, ref component)) return; - component.Mixture = mixture; - Dirty(component); + if (component.Space == space) + return; + + component.Space = space; + + if (updateTiles) + RefreshAllGridMapAtmospheres(uid); } - public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null) + /// + /// Forces a refresh of all MapAtmosphere tiles on every grid on a map. + /// + public void RefreshAllGridMapAtmospheres(EntityUid map) { - if (!Resolve(uid, ref component)) + DebugTools.Assert(HasComp(map)); + var enumerator = AllEntityQuery(); + while (enumerator.MoveNext(out var grid, out var atmos, out var xform)) + { + if (xform.MapUid == map) + RefreshMapAtmosphereTiles((grid, atmos)); + } + } + + /// + /// Forces a refresh of all MapAtmosphere tiles on a given grid. + /// + private void RefreshMapAtmosphereTiles(Entity grid) + { + if (!Resolve(grid.Owner, ref grid.Comp)) return; - component.Space = space; - Dirty(component); + var atmos = grid.Comp; + foreach (var tile in atmos.MapTiles) + { + RemoveMapAtmos(atmos, tile); + atmos.InvalidatedCoords.Add(tile.GridIndices); + } + atmos.MapTiles.Clear(); + } + + /// + /// Handles updating map-atmospheres when grids move across maps. + /// + private void OnGridParentChanged(Entity grid, ref EntParentChangedMessage args) + { + // Do nothing if detaching to nullspace + if (!args.Transform.ParentUid.IsValid()) + return; + + // Avoid doing work if moving from a space-map to another space-map. + if (args.OldParent == null + || HasComp(args.OldParent) + || HasComp(args.Transform.ParentUid)) + { + RefreshMapAtmosphereTiles((grid, grid)); + } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index aceda3cd332..f156125b0ff 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -5,7 +5,6 @@ using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Database; -using Content.Shared.Doors.Components; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Random; @@ -27,7 +26,10 @@ public sealed partial class AtmosphereSystem private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2]; - private void EqualizePressureInZone(Entity ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) + private void EqualizePressureInZone( + Entity ent, + TileAtmosphere tile, + int cycleNum) { if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum)) return; // Already done. @@ -56,7 +58,7 @@ private void EqualizePressureInZone(Entity ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals) + private void ExplosivelyDepressurize( + Entity ent, + TileAtmosphere tile, + int cycleNum) { // Check if explosive depressurization is enabled and if the tile is valid. if (!MonstermosDepressurization || tile.Air == null) @@ -368,7 +382,7 @@ private void ExplosivelyDepressurize(Entity= limit) break; + if (tileCount >= limit) + break; } } else @@ -437,13 +458,21 @@ private void ExplosivelyDepressurize(Entity 10 && (totalMolesRemoved / tileCount) > 10) + if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10) _adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High, $"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}"); @@ -544,36 +573,33 @@ private void ExplosivelyDepressurize(Entity ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid) + private void ConsiderFirelocks( + Entity ent, + TileAtmosphere tile, + TileAtmosphere other) { var reconsiderAdjacent = false; - foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices)) + var mapGrid = ent.Comp3; + foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices)) { - if (!TryComp(entity, out FirelockComponent? firelock)) - continue; - - reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); + if (_firelockQuery.TryGetComponent(entity, out var firelock)) + reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); } - foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices)) + foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices)) { - if (!TryComp(entity, out FirelockComponent? firelock)) - continue; - - reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); + if (_firelockQuery.TryGetComponent(entity, out var firelock)) + reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock); } if (!reconsiderAdjacent) return; - var (owner, gridAtmosphere) = ent; - var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices); - var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices); - GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv); - GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv); - InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals); - InvalidateVisuals(other.GridIndex, other.GridIndices, visuals); + UpdateAdjacentTiles(ent, tile); + UpdateAdjacentTiles(ent, other); + InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent); + InvalidateVisuals(other.GridIndex, other.GridIndices, ent); } private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals) @@ -642,7 +668,7 @@ private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, flo if (adj == null) { var nonNull = tile.AdjacentTiles.Where(x => x != null).Count(); - Logger.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: {tile.Tile}, non-null adj count: {nonNull}, Trace: {Environment.StackTrace}"); + Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}"); return; } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 4f8df0af670..1f3ca2145b9 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -1,6 +1,5 @@ using Content.Server.Atmos.Components; using Content.Server.Atmos.Piping.Components; -using Content.Server.NodeContainer.NodeGroups; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Maps; @@ -8,6 +7,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Atmos.EntitySystems { @@ -30,131 +30,249 @@ public sealed partial class AtmosphereSystem private int _currentRunAtmosphereIndex; private bool _simulationPaused; - private readonly List> _currentRunAtmosphere = new(); + private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index) + { + var tile = atmosphere.Tiles.GetOrNew(index, out var existing); + if (existing) + return tile; + + atmosphere.InvalidatedCoords.Add(index); + tile.GridIndex = owner; + tile.GridIndices = index; + return tile; + } + + private readonly List> _currentRunAtmosphere = new(); /// /// Revalidates all invalid coordinates in a grid atmosphere. + /// I.e., process any tiles that have had their airtight blockers modified. /// /// The grid atmosphere in question. /// Whether the process succeeded or got paused due to time constrains. - private bool ProcessRevalidate(Entity ent, GasTileOverlayComponent? visuals) + private bool ProcessRevalidate(Entity ent) { - var (owner, atmosphere) = ent; + if (ent.Comp4.MapUid == null) + { + Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}"); + return true; + } + + var (uid, atmosphere, visuals, grid, xform) = ent; + var volume = GetVolumeForTiles(grid); + TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos); + if (!atmosphere.ProcessingPaused) { - atmosphere.CurrentRunInvalidatedCoordinates.Clear(); - atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count); - foreach (var tile in atmosphere.InvalidatedCoords) + atmosphere.CurrentRunInvalidatedTiles.Clear(); + atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count); + foreach (var indices in atmosphere.InvalidatedCoords) { - atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile); + var tile = GetOrNewTile(uid, atmosphere, indices); + atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile); + + // Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData. + UpdateTileData(ent, mapAtmos, tile); } atmosphere.InvalidatedCoords.Clear(); + + if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) + return false; } - if (!TryComp(owner, out MapGridComponent? mapGridComp)) - return true; + var number = 0; + while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile)) + { + DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile); + UpdateAdjacentTiles(ent, tile, activate: true); + UpdateTileAir(ent, tile, volume); + InvalidateVisuals(uid, tile.GridIndices, visuals); - var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID); + if (number++ < InvalidCoordinatesLagCheckIterations) + continue; - var volume = GetVolumeForTiles(mapGridComp); + number = 0; + // Process the rest next time. + if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) + return false; + } - var number = 0; - while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices)) + TrimDisconnectedMapTiles(ent); + return true; + } + + /// + /// This method queued a tile and all of its neighbours up for processing by . + /// + public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile) + { + if (!tile.TrimQueued) { - if (!atmosphere.Tiles.TryGetValue(indices, out var tile)) + tile.TrimQueued = true; + atmos.PossiblyDisconnectedTiles.Add(tile); + } + + for (var i = 0; i < Atmospherics.Directions; i++) + { + var direction = (AtmosDirection) (1 << i); + var indices = tile.GridIndices.Offset(direction); + if (atmos.Tiles.TryGetValue(indices, out var adj) + && adj.NoGridTile + && !adj.TrimQueued) { - tile = new TileAtmosphere(owner, indices, - new GasMixture(volume) { Temperature = Atmospherics.T20C }); - atmosphere.Tiles[indices] = tile; + adj.TrimQueued = true; + atmos.PossiblyDisconnectedTiles.Add(adj); } + } + } - var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp); - GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv); - var isAirBlocked = airBlockedEv.Result; + /// + /// Tiles in a are either grid-tiles, or they they should be are tiles + /// adjacent to grid-tiles that represent the map's atmosphere. This method trims any map-tiles that are no longer + /// adjacent to any grid-tiles. + /// + private void TrimDisconnectedMapTiles( + Entity ent) + { + var atmos = ent.Comp1; - var oldBlocked = tile.BlockedAirflow; - var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp); - GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv); + foreach (var tile in atmos.PossiblyDisconnectedTiles) + { + tile.TrimQueued = false; + if (!tile.NoGridTile) + continue; - // Blocked airflow changed, rebuild excited groups! - if (tile.Excited && tile.BlockedAirflow != oldBlocked) + var connected = false; + for (var i = 0; i < Atmospherics.Directions; i++) { - RemoveActiveTile(atmosphere, tile); + var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i)); + if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty) + { + connected = true; + break; + } } - // Call this instead of the grid method as the map has a say on whether the tile is space or not. - if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked) + if (!connected) { - tile.Air = GetTileMixture(null, mapUid, indices); - tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null; - tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp); + RemoveActiveTile(atmos, tile); + atmos.Tiles.Remove(tile.GridIndices); } - else if (isAirBlocked) + } + + atmos.PossiblyDisconnectedTiles.Clear(); + } + + /// + /// Checks whether a tile has a corresponding grid-tile, or whether it is a "map" tile. Also checks whether the + /// tile should be considered "space" + /// + private void UpdateTileData( + Entity ent, + MapAtmosphereComponent? mapAtmos, + TileAtmosphere tile) + { + var idx = tile.GridIndices; + bool mapAtmosphere; + if (_map.TryGetTile(ent.Comp3, idx, out var gTile) && !gTile.IsEmpty) + { + var contentDef = (ContentTileDefinition) _tileDefinitionManager[gTile.TypeId]; + mapAtmosphere = contentDef.MapAtmosphere; + tile.ThermalConductivity = contentDef.ThermalConductivity; + tile.HeatCapacity = contentDef.HeatCapacity; + tile.NoGridTile = false; + } + else + { + mapAtmosphere = true; + tile.ThermalConductivity = 0.5f; + tile.HeatCapacity = float.PositiveInfinity; + + if (!tile.NoGridTile) { - if (airBlockedEv.NoAir) - { - tile.Air = null; - tile.MolesArchived = null; - tile.ArchivedCycle = 0; - tile.LastShare = 0f; - tile.Hotspot = new Hotspot(); - } + tile.NoGridTile = true; + + // This tile just became a non-grid atmos tile. + // It, or one of its neighbours, might now be completely disconnected from the grid. + QueueTileTrim(ent.Comp1, tile); } - else - { - if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices)) - { - var vacuumEv = new FixTileVacuumMethodEvent(owner, indices); - GridFixTileVacuum(owner, atmosphere, ref vacuumEv); - } + } - // Tile used to be space, but isn't anymore. - if (tile.Space || (tile.Air?.Immutable ?? false)) - { - tile.Air = null; - tile.MolesArchived = null; - tile.ArchivedCycle = 0; - tile.LastShare = 0f; - tile.Space = false; - } + UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile); - tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C}; - tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases]; + if (mapAtmosphere) + { + if (!tile.MapAtmosphere) + { + (tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos); + tile.MapAtmosphere = true; + ent.Comp1.MapTiles.Add(tile); } - // We activate the tile. - AddActiveTile(atmosphere, tile); + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable ?? false); + return; + } - // TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity - var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef) - ? tileRef.GetContentTileDefinition(_tileDefinitionManager) - : null; + if (!tile.MapAtmosphere) + return; - tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f; - tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity; - InvalidateVisuals(owner, indices, visuals); + // Tile used to be exposed to the map's atmosphere, but isn't anymore. + RemoveMapAtmos(ent.Comp1, tile); + } - for (var i = 0; i < Atmospherics.Directions; i++) - { - var direction = (AtmosDirection) (1 << i); - var otherIndices = indices.Offset(direction); + private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile) + { + DebugTools.Assert(tile.MapAtmosphere); + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable ?? false); + tile.MapAtmosphere = false; + atmos.MapTiles.Remove(tile); + tile.Air = null; + Array.Clear(tile.MolesArchived); + tile.ArchivedCycle = 0; + tile.LastShare = 0f; + tile.Space = false; + } - if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile)) - AddActiveTile(atmosphere, otherTile); - } + /// + /// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one. + /// + private void UpdateTileAir( + Entity ent, + TileAtmosphere tile, + float volume) + { + if (tile.MapAtmosphere) + { + DebugTools.AssertNotNull(tile.Air); + DebugTools.Assert(tile.Air?.Immutable ?? false); + return; + } - if (number++ < InvalidCoordinatesLagCheckIterations) - continue; + var data = tile.AirtightData; + var fullyBlocked = data.BlockedDirections == AtmosDirection.All; - number = 0; - // Process the rest next time. - if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime) - { - return false; - } + if (fullyBlocked && data.NoAirWhenBlocked) + { + if (tile.Air == null) + return; + + tile.Air = null; + Array.Clear(tile.MolesArchived); + tile.ArchivedCycle = 0; + tile.LastShare = 0f; + tile.Hotspot = new Hotspot(); + return; } - return true; + if (tile.Air != null) + return; + + tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C}; + + if (data.FixVacuum) + GridFixTileVacuum(ent, tile, volume); } private void QueueRunTiles( @@ -170,19 +288,16 @@ private void QueueRunTiles( } } - private bool ProcessTileEqualize(Entity ent, GasTileOverlayComponent? visuals) + private bool ProcessTileEqualize(Entity ent) { - var (uid, atmosphere) = ent; + var atmosphere = ent.Comp1; if (!atmosphere.ProcessingPaused) QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles); - if (!TryComp(uid, out MapGridComponent? mapGridComp)) - throw new Exception("Tried to process a grid atmosphere on an entity that isn't a grid!"); - var number = 0; while (atmosphere.CurrentRunTiles.TryDequeue(out var tile)) { - EqualizePressureInZone((uid, mapGridComp, atmosphere), tile, atmosphere.UpdateCounter, visuals); + EqualizePressureInZone(ent, tile, atmosphere.UpdateCounter); if (number++ < LagCheckIterations) continue; @@ -198,7 +313,7 @@ private bool ProcessTileEqualize(Entity ent, GasTileOve return true; } - private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals) + private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals) { if(!atmosphere.ProcessingPaused) QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles); @@ -240,11 +355,11 @@ private bool ProcessExcitedGroups(GridAtmosphereComponent gridAtmosphere) excitedGroup.BreakdownCooldown++; excitedGroup.DismantleCooldown++; - if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles) + if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles) ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup); - - else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles) - ExcitedGroupDismantle(gridAtmosphere, excitedGroup); + else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles) + DeactivateGroupTiles(gridAtmosphere, excitedGroup); + // TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it? if (number++ < LagCheckIterations) continue; @@ -435,10 +550,10 @@ private void UpdateProcessing(float frameTime) _currentRunAtmosphereIndex = 0; _currentRunAtmosphere.Clear(); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var grid)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var atmos, out var overlay, out var grid, out var xform )) { - _currentRunAtmosphere.Add((uid, grid)); + _currentRunAtmosphere.Add((uid, atmos, overlay, grid, xform)); } } @@ -448,8 +563,7 @@ private void UpdateProcessing(float frameTime) for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++) { var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex]; - var (owner, atmosphere) = ent; - TryComp(owner, out GasTileOverlayComponent? visuals); + var (owner, atmosphere, visuals, grid, xform) = ent; if (!TryComp(owner, out TransformComponent? x) || x.MapUid == null @@ -474,13 +588,14 @@ private void UpdateProcessing(float frameTime) switch (atmosphere.State) { case AtmosphereProcessingState.Revalidate: - if (!ProcessRevalidate(ent, visuals)) + if (!ProcessRevalidate(ent)) { atmosphere.ProcessingPaused = true; return; } atmosphere.ProcessingPaused = false; + // Next state depends on whether monstermos equalization is enabled or not. // Note: We do this here instead of on the tile equalization step to prevent ending it early. // Therefore, a change to this CVar might only be applied after that step is over. @@ -489,7 +604,7 @@ private void UpdateProcessing(float frameTime) : AtmosphereProcessingState.ActiveTiles; continue; case AtmosphereProcessingState.TileEqualize: - if (!ProcessTileEqualize(ent, visuals)) + if (!ProcessTileEqualize(ent)) { atmosphere.ProcessingPaused = true; return; @@ -499,7 +614,7 @@ private void UpdateProcessing(float frameTime) atmosphere.State = AtmosphereProcessingState.ActiveTiles; continue; case AtmosphereProcessingState.ActiveTiles: - if (!ProcessActiveTiles(atmosphere, visuals)) + if (!ProcessActiveTiles(ent, ent)) { atmosphere.ProcessingPaused = true; return; @@ -520,7 +635,7 @@ private void UpdateProcessing(float frameTime) atmosphere.State = AtmosphereProcessingState.HighPressureDelta; continue; case AtmosphereProcessingState.HighPressureDelta: - if (!ProcessHighPressureDelta(ent)) + if (!ProcessHighPressureDelta((ent, ent))) { atmosphere.ProcessingPaused = true; return; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs index 33fa16a6c68..5c73cf11246 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.Components; using Content.Shared.Atmos; +using Robust.Shared.Map.Components; namespace Content.Server.Atmos.EntitySystems { @@ -12,7 +13,8 @@ private void Superconduct(GridAtmosphereComponent gridAtmosphere, TileAtmosphere for(var i = 0; i < Atmospherics.Directions; i++) { var direction = (AtmosDirection) (1 << i); - if (!directions.IsFlagSet(direction)) continue; + if (!directions.IsFlagSet(direction)) + continue; var adjacent = tile.AdjacentTiles[direction.ToIndex()]; @@ -92,7 +94,9 @@ public void NeighborConductWithSource(GridAtmosphereComponent gridAtmosphere, Ti { if (tile.Air == null) { - if (other.Tile != null) + // TODO ATMOS: why does this need to check if a tile exists if it doesn't use the tile? + if (TryComp(other.GridIndex, out var grid) + && _mapSystem.TryGetTileRef(other.GridIndex, grid, other.GridIndices, out var _)) { TemperatureShareOpenToSolid(other, tile); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs index 699c2a70aef..9b0d0d9670d 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs @@ -41,20 +41,6 @@ public void InvalidateVisuals(EntityUid gridUid, Vector2i tile, GasTileOverlayCo _gasTileOverlaySystem.Invalidate(gridUid, tile, comp); } - public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices) - { - var value = false; - - var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices); - - while (enumerator.MoveNext(out var airtight)) - { - value |= airtight.FixVacuum; - } - - return value; - } - /// /// Gets the volume in liters for a number of tiles, on a specific grid. /// @@ -66,34 +52,44 @@ private float GetVolumeForTiles(MapGridComponent mapGrid, int tiles = 1) return Atmospherics.CellVolume * mapGrid.TileSize * tiles; } - /// - /// Gets all obstructing instances in a specific tile. - /// - /// The grid where to get the tile. - /// The indices of the tile. - /// The enumerator for the airtight components. - public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile) + public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked, + bool FixVacuum); + + private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile) { - var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile); - var airQuery = GetEntityQuery(); + var oldBlocked = tile.AirtightData.BlockedDirections; + + tile.AirtightData = tile.NoGridTile + ? default + : GetAirtightData(uid, grid, tile.GridIndices); - var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery); - return enumerator; + if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null) + ExcitedGroupDispose(atmos, tile.ExcitedGroup); } - private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices) + private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile) { - var value = AtmosDirection.Invalid; + var blockedDirs = AtmosDirection.Invalid; + var noAirWhenBlocked = false; + var fixVacuum = false; - var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices); - - while (enumerator.MoveNext(out var airtight)) + foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile)) { - if(airtight.AirBlocked) - value |= airtight.AirBlockedDirection; + if (!_airtightQuery.TryGetComponent(ent, out var airtight)) + continue; + + if(!airtight.AirBlocked) + continue; + + blockedDirs |= airtight.AirBlockedDirection; + noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked; + fixVacuum |= airtight.FixVacuum; + + if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum) + break; } - return value; + return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum); } /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index dd2a9675591..d2f40e77169 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Fluids.EntitySystems; using Content.Server.NodeContainer.EntitySystems; using Content.Shared.Atmos.EntitySystems; +using Content.Shared.Doors.Components; using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Server.GameObjects; @@ -40,6 +41,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem private const float ExposedUpdateDelay = 1f; private float _exposedTimer = 0f; + private EntityQuery _atmosQuery; + private EntityQuery _airtightQuery; + private EntityQuery _firelockQuery; private HashSet _entSet = new(); public override void Initialize() @@ -55,6 +59,9 @@ public override void Initialize() InitializeGridAtmosphere(); InitializeMap(); + _atmosQuery = GetEntityQuery(); + _airtightQuery = GetEntityQuery(); + _firelockQuery = GetEntityQuery(); SubscribeLocalEvent(OnTileChanged); diff --git a/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs b/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs index 203c747e29e..70e3eef3c44 100644 --- a/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AutomaticAtmosSystem.cs @@ -27,8 +27,12 @@ private void OnTileChanged(ref TileChangedEvent ev) // Also, these calls are surprisingly slow. // TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into // TODO: a simple array lookup, as tile IDs are likely contiguous, and there's at most 2^16 possibilities anyway. - if (!((ev.OldTile.IsSpace(_tileDefinitionManager) && !ev.NewTile.IsSpace(_tileDefinitionManager)) || - (!ev.OldTile.IsSpace(_tileDefinitionManager) && ev.NewTile.IsSpace(_tileDefinitionManager))) || + + var oldSpace = ev.OldTile.IsSpace(_tileDefinitionManager); + var newSpace = ev.NewTile.IsSpace(_tileDefinitionManager); + + if (!(oldSpace && !newSpace || + !oldSpace && newSpace) || _atmosphereSystem.HasAtmosphere(ev.Entity)) return; diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs index 6a2c8f0a7e5..552ee142329 100644 --- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs @@ -260,13 +260,13 @@ private GasEntry[] GenerateGasEntryArray(GasMixture? mixture) { var gas = _atmo.GetGas(i); - if (mixture?.Moles[i] <= UIMinMoles) + if (mixture?[i] <= UIMinMoles) continue; if (mixture != null) { var gasName = Loc.GetString(gas.Name); - gases.Add(new GasEntry(gasName, mixture.Moles[i], gas.Color)); + gases.Add(new GasEntry(gasName, mixture[i], gas.Color)); } } diff --git a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs index 8ae9517379e..397f0167a9b 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -171,7 +171,7 @@ public GasOverlayData GetOverlayData(GasMixture? mixture) { var id = VisibleGasId[i]; var gas = _atmosphereSystem.GetGas(id); - var moles = mixture?.Moles[id] ?? 0f; + var moles = mixture?[id] ?? 0f; ref var opacity = ref data.Opacity[i]; if (moles < gas.GasMolesVisible) @@ -216,13 +216,13 @@ private bool UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayC oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity); } - if (tile.Air != null) + if (tile is {Air: not null, NoGridTile: false}) { for (var i = 0; i < VisibleGasId.Length; i++) { var id = VisibleGasId[i]; var gas = _atmosphereSystem.GetGas(id); - var moles = tile.Air.Moles[id]; + var moles = tile.Air[id]; ref var oldOpacity = ref oldData.Opacity[i]; if (moles < gas.GasMolesVisible) diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index 0a2ef235a71..77fd7018333 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using Content.Server.Atmos.Reactions; using Content.Shared.Atmos; +using Content.Shared.Atmos.EntitySystems; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -17,11 +18,13 @@ public sealed partial class GasMixture : IEquatable, ISerializationH { public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true}; - // This must always have a length that is a multiple of 4 for SIMD acceleration. - [DataField("moles")] - [ViewVariables(VVAccess.ReadWrite)] + // No access, to ensure immutable mixtures are never accidentally mutated. + [Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)] + [DataField] public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases]; + public float this[int gas] => Moles[gas]; + [DataField("temperature")] [ViewVariables(VVAccess.ReadWrite)] private float _temperature = Atmospherics.TCMB; @@ -80,6 +83,19 @@ public GasMixture(float volume = 0f) Volume = volume; } + public GasMixture(float[] moles, float temp, float volume = Atmospherics.CellVolume) + { + if (moles.Length != Atmospherics.AdjustedNumberOfGases) + throw new InvalidOperationException($"Invalid mole array length"); + + if (volume < 0) + volume = 0; + + _temperature = temp; + Moles = moles; + Volume = volume; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MarkImmutable() { @@ -117,15 +133,16 @@ public void SetMoles(Gas gas, float quantity) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AdjustMoles(int gasId, float quantity) { - if (!Immutable) - { - if (!float.IsFinite(quantity)) - throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity)); + if (Immutable) + return; - // Clamping is needed because x - x can be negative with floating point numbers. If we don't - // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles(). - Moles[gasId] = MathF.Max(Moles[gasId] + quantity, 0); - } + if (!float.IsFinite(quantity)) + throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity)); + + // Clamping is needed because x - x can be negative with floating point numbers. If we don't + // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles(). + ref var moles = ref Moles[gasId]; + moles = MathF.Max(moles + quantity, 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -163,7 +180,8 @@ public GasMixture RemoveRatio(float ratio) { var moles = Moles[i]; var otherMoles = removed.Moles[i]; - if (moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) + + if ((moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) && !Immutable) Moles[i] = 0; if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles)) @@ -202,6 +220,9 @@ public void Multiply(float multiplier) void ISerializationHooks.AfterDeserialization() { + // ISerializationHooks is obsolete. + // TODO add fixed-length-array serializer + // The arrays MUST have a specific length. Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases); } @@ -229,8 +250,12 @@ public override bool Equals(object? obj) public bool Equals(GasMixture? other) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + if (ReferenceEquals(this, other)) + return true; + + if (ReferenceEquals(null, other)) + return false; + return Moles.SequenceEqual(other.Moles) && _temperature.Equals(other._temperature) && ReactionResults.SequenceEqual(other.ReactionResults) @@ -258,11 +283,13 @@ public override int GetHashCode() public GasMixture Clone() { + if (Immutable) + return this; + var newMixture = new GasMixture() { Moles = (float[])Moles.Clone(), _temperature = _temperature, - Immutable = Immutable, Volume = Volume, }; return newMixture; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index 64d02d793bc..ad647fad1b8 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -136,7 +136,7 @@ private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent ca for (int i = 0; i < containedGasArray.Length; i++) { - containedGasDict.Add((Gas)i, canister.Air.Moles[i]); + containedGasDict.Add((Gas)i, canister.Air[i]); } _adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]"); diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs index 491bf600627..852542ec6cd 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCondenserSystem.cs @@ -45,7 +45,7 @@ private void OnCondenserUpdated(Entity entity, ref AtmosD var removed = inlet.Air.Remove(molesToConvert); for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - var moles = removed.Moles[i]; + var moles = removed[i]; if (moles <= 0) continue; diff --git a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs index 7d385c530a9..71e4c2d0def 100644 --- a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs +++ b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs @@ -183,13 +183,7 @@ public void CopyTo(ISerializationManager serializationManager, Dictionary + /// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the + /// unblocked directions on adjacent tiles. + /// [ViewVariables] public AtmosDirection AdjacentBits = AtmosDirection.Invalid; @@ -72,10 +77,7 @@ public sealed class TileAtmosphere : IGasMixtureHolder public EntityUid GridIndex { get; set; } [ViewVariables] - public TileRef? Tile => GridIndices.GetTileRef(GridIndex); - - [ViewVariables] - public Vector2i GridIndices { get; } + public Vector2i GridIndices; [ViewVariables] public ExcitedGroup? ExcitedGroup { get; set; } @@ -92,7 +94,7 @@ public sealed class TileAtmosphere : IGasMixtureHolder public float LastShare; [ViewVariables] - public float[]? MolesArchived; + public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; GasMixture IGasMixtureHolder.Air { @@ -103,8 +105,31 @@ GasMixture IGasMixtureHolder.Air [ViewVariables] public float MaxFireTemperatureSustained { get; set; } + /// + /// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at + /// this position, or because the tile type is not airtight. + /// + [ViewVariables] + public bool MapAtmosphere; + + /// + /// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for + /// adjacent grid tiles. + /// [ViewVariables] - public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid; + public bool NoGridTile; + + /// + /// If true, this tile is queued for processing in + /// + [ViewVariables] + public bool TrimQueued; + + /// + /// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated + /// (i.e., gets added to ). + /// + public AtmosphereSystem.AirtightData AirtightData; public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false) { @@ -112,10 +137,24 @@ public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mix GridIndices = gridIndices; Air = mixture; Space = space; - MolesArchived = Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null; if(immutable) Air?.MarkImmutable(); } + + public TileAtmosphere(TileAtmosphere other) + { + GridIndex = other.GridIndex; + GridIndices = other.GridIndices; + Space = other.Space; + NoGridTile = other.NoGridTile; + MapAtmosphere = other.MapAtmosphere; + Air = other.Air?.Clone(); + Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length); + } + + public TileAtmosphere() + { + } } } diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index b5bac507391..4b60f8814b5 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -77,7 +77,7 @@ public void GasToReagent(EntityUid uid, LungComponent lung) foreach (var gas in Enum.GetValues()) { var i = (int) gas; - var moles = lung.Air.Moles[i]; + var moles = lung.Air[i]; if (moles <= 0) continue; var reagent = _atmosphereSystem.GasReagents[i]; diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index be6e8b78fef..6cdb088dbb0 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -7,7 +7,7 @@ using Content.Server.GameTicking; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; -using Content.Server.Nyanotrasen.Chat; +using Content.Server.Psionics.Telepathy; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; using Content.Server.Station.Components; diff --git a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs index d945c1f818b..7d3f70bc0d8 100644 --- a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs @@ -64,8 +64,8 @@ public void Start(Entity vapor, TransformComponent vaporXform, V // Set Move if (EntityManager.TryGetComponent(vapor, out PhysicsComponent? physics)) { - _physics.SetLinearDamping(physics, 0f); - _physics.SetAngularDamping(physics, 0f); + _physics.SetLinearDamping(vapor, physics, 0f); + _physics.SetAngularDamping(vapor, physics, 0f); _throwing.TryThrow(vapor, dir, speed, user: user); diff --git a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs index e12cab80a91..e7466fbc85d 100644 --- a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs +++ b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs @@ -16,12 +16,15 @@ public sealed partial class ModifyLungGas : ReagentEffect public override void Effect(ReagentEffectArgs args) { - if (args.EntityManager.TryGetComponent(args.OrganEntity, out var lung)) + if (!args.EntityManager.TryGetComponent(args.OrganEntity, out var lung)) + return; + + foreach (var (gas, ratio) in _ratios) { - foreach (var (gas, ratio) in _ratios) - { - lung.Air.Moles[(int) gas] += (ratio * args.Quantity.Float()) / Atmospherics.BreathMolesToReagentMultiplier; - } + var quantity = ratio * args.Quantity.Float() / Atmospherics.BreathMolesToReagentMultiplier; + if (quantity < 0) + quantity = Math.Max(quantity, -lung.Air[(int)gas]); + lung.Air.AdjustMoles(gas, quantity); } } } diff --git a/Content.Server/Construction/Conditions/ComponentInTile.cs b/Content.Server/Construction/Conditions/ComponentInTile.cs index 8ab4046a721..36705e4c0ee 100644 --- a/Content.Server/Construction/Conditions/ComponentInTile.cs +++ b/Content.Server/Construction/Conditions/ComponentInTile.cs @@ -3,6 +3,7 @@ using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Utility; namespace Content.Server.Construction.Conditions @@ -48,7 +49,15 @@ public bool Condition(EntityUid uid, IEntityManager entityManager) var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve()); var lookup = entityManager.EntitySysManager.GetEntitySystem(); - var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Static, lookup); + + + if (!entityManager.TryGetComponent(transform.GridUid.Value, out var grid)) + return !HasEntity; + + if (!entityManager.System().TryGetTileRef(transform.GridUid.Value, grid, indices, out var tile)) + return !HasEntity; + + var entities = tile.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static, lookup); foreach (var ent in entities) { diff --git a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs index ec9ec770313..22d96a54146 100644 --- a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs +++ b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs @@ -5,7 +5,7 @@ using Content.Server.Psionics.Glimmer; using Content.Server.StationEvents.Components; using Content.Shared.Psionics.Glimmer; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 31451c425f5..4a9477e7443 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -475,7 +475,7 @@ public void DamageFloorTile(TileRef tileRef, if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef) return; - if (tileDef.IsSpace) + if (tileDef.MapAtmosphere) canCreateVacuum = true; // is already a vacuum. int tileBreakages = 0; @@ -491,7 +491,7 @@ public void DamageFloorTile(TileRef tileRef, if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef) break; - if (newDef.IsSpace && !canCreateVacuum) + if (newDef.MapAtmosphere && !canCreateVacuum) break; tileDef = newDef; diff --git a/Content.Server/ImmovableRod/ImmovableRodSystem.cs b/Content.Server/ImmovableRod/ImmovableRodSystem.cs index 0fa8f7d292f..31aa39cf03a 100644 --- a/Content.Server/ImmovableRod/ImmovableRodSystem.cs +++ b/Content.Server/ImmovableRod/ImmovableRodSystem.cs @@ -53,9 +53,9 @@ private void OnMapInit(EntityUid uid, ImmovableRodComponent component, MapInitEv { if (EntityManager.TryGetComponent(uid, out PhysicsComponent? phys)) { - _physics.SetLinearDamping(phys, 0f); - _physics.SetFriction(phys, 0f); - _physics.SetBodyStatus(phys, BodyStatus.InAir); + _physics.SetLinearDamping(uid, phys, 0f); + _physics.SetFriction(uid, phys, 0f); + _physics.SetBodyStatus(uid, phys, BodyStatus.InAir); if (!component.RandomizeVelocity) return; diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs deleted file mode 100644 index b775117b716..00000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Abilities.Psionics; -using Content.Shared.StatusEffect; -using Content.Shared.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; -using Content.Shared.Actions.Events; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class MetapsionicPowerSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MetapsionicActionEntity, component.MetapsionicActionId ); - _actions.TryGetActionData( component.MetapsionicActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MetapsionicActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.MetapsionicActionEntity; - psionic.ActivePowers.Add(component); - } - - } - - private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MetapsionicActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnPowerUsed(EntityUid uid, MetapsionicPowerComponent component, MetapsionicPowerActionEvent args) - { - foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range)) - { - if (HasComp(entity) && entity != uid && !HasComp(entity) && - !(HasComp(entity) && Transform(entity).ParentUid == uid)) - { - _popups.PopupEntity(Loc.GetString("metapsionic-pulse-success"), uid, uid, PopupType.LargeCaution); - args.Handled = true; - return; - } - } - _popups.PopupEntity(Loc.GetString("metapsionic-pulse-failure"), uid, uid, PopupType.Large); - _psionics.LogPowerUsed(uid, "metapsionic pulse", 2, 4); - - args.Handled = true; - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs deleted file mode 100644 index 2eca3173b6d..00000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Server.GameObjects; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Content.Server.Body.Components; -using Content.Server.Body.Systems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.DoAfter; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; -using Content.Shared.Chemistry.Components; -using Content.Shared.DoAfter; -using Content.Shared.FixedPoint; -using Content.Shared.Popups; -using Content.Shared.Psionics.Events; -using Content.Shared.Tag; -using Content.Shared.Examine; -using static Content.Shared.Examine.ExamineSystemShared; -using Robust.Shared.Timing; -using Content.Server.Mind; -using Content.Shared.Actions.Events; -using Content.Shared.Chemistry.EntitySystems; -using Robust.Server.Audio; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class PsionicRegenerationPowerSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; - [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; - [Dependency] private readonly AudioSystem _audioSystem = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - - SubscribeLocalEvent(OnDispelled); - SubscribeLocalEvent(OnDoAfter); - } - - private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); - _actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PsionicRegenerationActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) - { - var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime); - var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid); - - _doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId); - - component.DoAfter = doAfterId; - - _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)), - uid, - // TODO: Use LoS-based Filter when one is available. - Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), - true, - PopupType.Medium); - - _audioSystem.PlayPvs(component.SoundUse, component.Owner, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); - _psionics.LogPowerUsed(uid, "psionic regeneration"); - args.Handled = true; - } - - private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PsionicRegenerationActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args) - { - if (component.DoAfter == null) - return; - - _doAfterSystem.Cancel(component.DoAfter); - component.DoAfter = null; - - args.Handled = true; - } - - private void OnDoAfter(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationDoAfterEvent args) - { - component.DoAfter = null; - - if (!TryComp(uid, out var stream)) - return; - - // DoAfter has no way to run a callback during the process to give - // small doses of the reagent, so we wait until either the action - // is cancelled (by being dispelled) or complete to give the - // appropriate dose. A timestamp delta is used to accomplish this. - var percentageComplete = Math.Min(1f, (_gameTiming.CurTime - args.StartedAt).TotalSeconds / component.UseDelay); - - var solution = new Solution(); - solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(component.EssenceAmount * percentageComplete)); - _bloodstreamSystem.TryAddToChemicals(uid, solution, stream); - } - } -} - diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs deleted file mode 100644 index 407b72c6b58..00000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Abilities.Psionics; -using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; -using Content.Shared.Actions.Events; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class PyrokinesisPowerSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly FlammableSystem _flammableSystem = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId ); - _actions.TryGetActionData( component.PyrokinesisActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PyrokinesisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnPowerUsed(PyrokinesisPowerActionEvent args) - { - if (!TryComp(args.Target, out var flammableComponent)) - return; - - flammableComponent.FireStacks += 5; - _flammableSystem.Ignite(args.Target, args.Target); - _popupSystem.PopupEntity(Loc.GetString("pyrokinesis-power-used", ("target", args.Target)), args.Target, Shared.Popups.PopupType.LargeCaution); - - _psionics.LogPowerUsed(args.Performer, "pyrokinesis"); - args.Handled = true; - } - } -} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs deleted file mode 100644 index f7ae04b61ea..00000000000 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.StatusEffect; -using Content.Shared.Abilities.Psionics; -using Content.Shared.Mind.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; -using Content.Shared.Actions.Events; - -namespace Content.Server.Abilities.Psionics -{ - public sealed class TelegnosisPowerSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - SubscribeLocalEvent(OnMindRemoved); - } - - private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId ); - _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.TelegnosisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - - private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) - { - var projection = Spawn(component.Prototype, Transform(uid).Coordinates); - Transform(projection).AttachToGridOrMap(); - _mindSwap.Swap(uid, projection); - - _psionics.LogPowerUsed(uid, "telegnosis"); - args.Handled = true; - } - private void OnMindRemoved(EntityUid uid, TelegnosticProjectionComponent component, MindRemovedMessage args) - { - QueueDel(uid); - } - } -} diff --git a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs index a23a5b3d77d..0ce3f9d7c64 100644 --- a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs +++ b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs @@ -1,5 +1,5 @@ using Content.Shared.Chemistry.Reagent; -using Content.Server.Abilities.Psionics; +using Content.Server.Psionics.Abilities; using JetBrains.Annotations; using Robust.Shared.Prototypes; diff --git a/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs b/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs deleted file mode 100644 index 3b677bab2d4..00000000000 --- a/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Server.Objectives.Systems; - -namespace Content.Server.Objectives.Components; - -/// -/// Requires that the player dies to be complete. -/// -[RegisterComponent, Access(typeof(BecomePsionicConditionSystem))] -public sealed partial class BecomePsionicConditionComponent : Component -{ -} \ No newline at end of file diff --git a/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs b/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs deleted file mode 100644 index d090c320a41..00000000000 --- a/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Shared.Abilities.Psionics; -using Content.Server.Objectives.Components; -using Content.Shared.Mind; -using Content.Shared.Objectives.Components; - -namespace Content.Server.Objectives.Systems -{ - public sealed class BecomePsionicConditionSystem : EntitySystem - { - private EntityQuery _metaQuery; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnGetProgress); - } - - private void OnGetProgress(EntityUid uid, BecomePsionicConditionComponent comp, ref ObjectiveGetProgressEvent args) - { - args.Progress = GetProgress(args.Mind); - } - - private float GetProgress(MindComponent mind) - { - var entMan = IoCManager.Resolve(); - if (HasComp(mind.CurrentEntity)) - return 1; - return 0; - } - } -} diff --git a/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs b/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs deleted file mode 100644 index 9499497cd1d..00000000000 --- a/Content.Server/Nyanotrasen/Psionics/PotentialPsionicComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PotentialPsionicComponent : Component - { - [DataField("chance")] - public float Chance = 0.04f; - - /// - /// YORO (you only reroll once) - /// - public bool Rerolled = false; - } -} diff --git a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs index 148598fe2c3..24459d29e22 100644 --- a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs @@ -5,7 +5,7 @@ using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Fluids.EntitySystems; using Content.Server.Psionics; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Chat; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; diff --git a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs index bc3c22cc350..b8cdcb56d47 100644 --- a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs +++ b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Abilities.Psionics; +using Content.Server.Psionics.Abilities; using Content.Server.Chat.Systems; using Content.Server.Radio.Components; using Content.Server.Radio.EntitySystems; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs index 66eea988aeb..89b5a176f24 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs @@ -5,7 +5,7 @@ using Content.Server.Psionics.Glimmer; using Content.Server.StationEvents.Components; using Content.Shared.Psionics.Glimmer; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs index 63944563269..89f3bc97501 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs @@ -1,10 +1,9 @@ -using Robust.Server.GameObjects; using Robust.Shared.Random; -using Content.Server.Abilities.Psionics; +using Content.Server.Psionics.Abilities; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics; using Content.Server.StationEvents.Components; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Robust.Shared.Player; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs index c04543d2195..6a2c1c3ba7d 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs @@ -10,7 +10,7 @@ using Content.Server.Power.EntitySystems; using Content.Server.Psionics.Glimmer; using Content.Server.StationEvents.Components; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Damage; using Content.Shared.Inventory; using Content.Shared.Mobs.Components; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs index 175318e15bd..8812ed1fe37 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs @@ -1,9 +1,9 @@ using Robust.Shared.Random; -using Content.Server.Abilities.Psionics; +using Content.Server.Psionics.Abilities; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Psionics; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Mobs.Systems; using Content.Shared.Psionics.Glimmer; using Content.Shared.Zombies; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs index 82c3d72b139..3672d317d9e 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs @@ -3,7 +3,7 @@ using Content.Server.Psionics; using Content.Server.StationEvents.Components; using Content.Server.Stunnable; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.StatusEffect; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs index 63e0a435cb0..753b2e25729 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs @@ -4,7 +4,7 @@ using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Shared.Mobs.Components; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.StatusEffect; using Content.Shared.Mobs.Systems; using Robust.Shared.Audio.Systems; diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs index c4c2300870d..83cf9b9cb2d 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -1001,20 +1001,13 @@ public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, light.AmbientLightColor = Color.FromHex("#D8B059"); Dirty(mapUid, light, metadata); - // Atmos - var atmos = EnsureComp(mapUid); - var moles = new float[Atmospherics.AdjustedNumberOfGases]; moles[(int) Gas.Oxygen] = 21.824779f; moles[(int) Gas.Nitrogen] = 82.10312f; - var mixture = new GasMixture(2500) - { - Temperature = 293.15f, - Moles = moles, - }; + var mixture = new GasMixture(moles, Atmospherics.T20C); - _atmos.SetMapAtmosphere(mapUid, false, mixture, atmos); + _atmos.SetMapAtmosphere(mapUid, false, mixture); } /// diff --git a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs index 46b25163cc0..06f1b6b154c 100644 --- a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs +++ b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Emitter.cs @@ -28,7 +28,7 @@ private void FireEmitter(EntityUid uid, ParticleAcceleratorPowerState strength, if (TryComp(emitted, out var particlePhys)) { var angle = _transformSystem.GetWorldRotation(uid, xformQuery); - _physicsSystem.SetBodyStatus(particlePhys, BodyStatus.InAir); + _physicsSystem.SetBodyStatus(emitted, particlePhys, BodyStatus.InAir); var velocity = angle.ToWorldVec() * 20f; if (TryComp(uid, out var phys)) diff --git a/Content.Server/Physics/Controllers/ChasingWalkSystem.cs b/Content.Server/Physics/Controllers/ChasingWalkSystem.cs index 215e7e3124e..618dd4156fc 100644 --- a/Content.Server/Physics/Controllers/ChasingWalkSystem.cs +++ b/Content.Server/Physics/Controllers/ChasingWalkSystem.cs @@ -97,6 +97,6 @@ private void ForceImpulse(EntityUid uid, ChasingWalkComponent component) var speed = delta.Length() > 0 ? delta.Normalized() * component.Speed : Vector2.Zero; _physics.SetLinearVelocity(uid, speed); - _physics.SetBodyStatus(physics, BodyStatus.InAir); //If this is not done, from the explosion up close, the tesla will "Fall" to the ground, and almost stop moving. + _physics.SetBodyStatus(uid, physics, BodyStatus.InAir); //If this is not done, from the explosion up close, the tesla will "Fall" to the ground, and almost stop moving. } } diff --git a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs index c61599edfc9..76cf90c3693 100644 --- a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs +++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs @@ -37,7 +37,7 @@ private void OnDeviceUpdated(EntityUid uid, GasPowerReceiverComponent component, if (pipe.Air.Temperature <= component.MaxTemperature) { // we have enough gas, so we consume it and are powered - if (pipe.Air.Moles[(int) component.TargetGas] > component.MolesConsumedSec * timeDelta) + if (pipe.Air[(int) component.TargetGas] > component.MolesConsumedSec * timeDelta) { pipe.Air.AdjustMoles(component.TargetGas, -component.MolesConsumedSec * timeDelta); SetPowered(uid, component, true); diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Psionics/Abilities/DispelPowerSystem.cs similarity index 83% rename from Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs rename to Content.Server/Psionics/Abilities/DispelPowerSystem.cs index d338a5a5bcb..cb7ef8313cd 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/DispelPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/DispelPowerSystem.cs @@ -1,24 +1,20 @@ using Content.Shared.Actions; using Content.Shared.StatusEffect; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Damage; using Content.Shared.Revenant.Components; using Content.Server.Guardian; using Content.Server.Bible.Components; using Content.Server.Popups; -using Robust.Shared.Prototypes; using Robust.Shared.Player; using Robust.Shared.Random; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; -namespace Content.Server.Abilities.Psionics +namespace Content.Server.Psionics.Abilities { public sealed class DispelPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; @@ -27,8 +23,6 @@ public sealed class DispelPowerSystem : EntitySystem [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() @@ -52,10 +46,13 @@ private void OnInit(EntityUid uid, DispelPowerComponent component, ComponentInit _actions.TryGetActionData( component.DispelActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.DispelActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + if (TryComp(uid, out var psionic)) { - psionic.PsionicAbility = component.DispelActionEntity; psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.DispelFeedback); + //It's fully intended that Dispel doesn't increase Amplification, and instead heavily spikes Dampening + //Antimage archetype. + psionic.Dampening += 1f; } } @@ -66,12 +63,16 @@ private void OnShutdown(EntityUid uid, DispelPowerComponent component, Component if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.DispelFeedback); + psionic.Dampening -= 1f; } } private void OnPowerUsed(DispelPowerActionEvent args) { - if (HasComp(args.Target)) + if (HasComp(args.Target) || HasComp(args.Performer)) + return; + if (!TryComp(args.Performer, out var psionic) || !HasComp(args.Target)) return; var ev = new DispelledEvent(); @@ -80,7 +81,9 @@ private void OnPowerUsed(DispelPowerActionEvent args) if (ev.Handled) { args.Handled = true; - _psionics.LogPowerUsed(args.Performer, "dispel"); + _psionics.LogPowerUsed(args.Performer, "dispel", + (int) MathF.Round(-2 * psionic.Dampening + psionic.Amplification), + (int) MathF.Round(-4 * psionic.Dampening + psionic.Amplification)); } } @@ -96,7 +99,7 @@ private void OnDispelled(EntityUid uid, DispellableComponent component, Dispelle private void OnDmgDispelled(EntityUid uid, DamageOnDispelComponent component, DispelledEvent args) { var damage = component.Damage; - var modifier = (1 + component.Variance) - (_random.NextFloat(0, component.Variance * 2)); + var modifier = 1 + component.Variance - _random.NextFloat(0, component.Variance * 2); damage *= modifier; DealDispelDamage(uid, damage); @@ -145,5 +148,3 @@ public void DealDispelDamage(EntityUid uid, DamageSpecifier? damage = null) } public sealed class DispelledEvent : HandledEntityEventArgs {} } - - diff --git a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs new file mode 100644 index 00000000000..7b3a417c53f --- /dev/null +++ b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -0,0 +1,188 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.Events; +using Content.Shared.Psionics.Abilities; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using static Content.Shared.Examine.ExamineSystemShared; +using Content.Shared.Popups; +using Robust.Server.Audio; +using Robust.Shared.Audio; +using Robust.Shared.Timing; +using Robust.Shared.Player; +using Content.Server.DoAfter; +using Content.Shared.Psionics.Events; +using Content.Server.Psionics; + +namespace Content.Server.Psionics.Abilities +{ + public sealed class MetapsionicPowerSystem : EntitySystem + { + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnWidePowerUsed); + SubscribeLocalEvent(OnFocusedPowerUsed); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) + { + if (!TryComp(uid, out ActionsComponent? comp)) + return; + _actions.AddAction(uid, ref component.ActionWideMetapsionicEntity, component.ActionWideMetapsionic, component: comp); + _actions.AddAction(uid, ref component.ActionFocusedMetapsionicEntity, component.ActionFocusedMetapsionic, component: comp); + _actions.TryGetActionData(component.ActionWideMetapsionicEntity, out var actionData); + if (actionData is { UseDelay: not null }) + { + _actions.StartUseDelay(component.ActionWideMetapsionicEntity); + _actions.StartUseDelay(component.ActionFocusedMetapsionicEntity); + } + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.MetapsionicFeedback); + psionic.Amplification += 0.1f; + psionic.Dampening += 0.5f; + } + + } + + private void UpdateActions(EntityUid uid, MetapsionicPowerComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + _actions.StartUseDelay(component.ActionWideMetapsionicEntity); + _actions.StartUseDelay(component.ActionFocusedMetapsionicEntity); + } + + private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.ActionWideMetapsionicEntity); + _actions.RemoveAction(uid, component.ActionFocusedMetapsionicEntity); + + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.MetapsionicFeedback); + psionic.Amplification -= 0.1f; + psionic.Dampening -= 0.5f; + } + } + + private void OnWidePowerUsed(EntityUid uid, MetapsionicPowerComponent component, WideMetapsionicPowerActionEvent args) + { + if (HasComp(uid)) + return; + + if (!TryComp(uid, out var psionic)) + return; + + foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range)) + { + if (HasComp(entity) && entity != uid && !HasComp(entity) && + !(HasComp(entity) && Transform(entity).ParentUid == uid)) + { + _popups.PopupEntity(Loc.GetString("metapsionic-pulse-success"), uid, uid, PopupType.LargeCaution); + args.Handled = true; + return; + } + } + _popups.PopupEntity(Loc.GetString("metapsionic-pulse-failure"), uid, uid, PopupType.Large); + _psionics.LogPowerUsed(uid, "metapsionic pulse", + (int) MathF.Round(2 * psionic.Amplification - psionic.Dampening), + (int) MathF.Round(4 * psionic.Amplification - psionic.Dampening)); + UpdateActions(uid, component); + args.Handled = true; + } + + private void OnFocusedPowerUsed(FocusedMetapsionicPowerActionEvent args) + { + if (!TryComp(args.Performer, out var psionic)) + return; + + if (HasComp(args.Target)) + return; + + if (!TryComp(args.Performer, out var component)) + return; + + var ev = new FocusedMetapsionicDoAfterEvent(_gameTiming.CurTime); + + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, component.UseDelay - psionic.Amplification, ev, args.Performer, args.Target, args.Performer) + { + BlockDuplicate = true, + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnDamage = true, + }, out var doAfterId); + + component.DoAfter = doAfterId; + + _popups.PopupEntity(Loc.GetString("focused-metapsionic-pulse-begin", ("entity", args.Target)), + args.Performer, + // TODO: Use LoS-based Filter when one is available. + Filter.Pvs(args.Performer).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(args.Performer, entity, ExamineRange, null)), + true, + PopupType.Medium); + + _audioSystem.PlayPvs(component.SoundUse, args.Performer, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); + _psionics.LogPowerUsed(args.Performer, "focused metapsionic pulse", + (int) MathF.Round(3 * psionic.Amplification - psionic.Dampening), + (int) MathF.Round(6 * psionic.Amplification - psionic.Dampening)); + args.Handled = true; + + UpdateActions(args.Performer, component); + } + + private void OnDoAfter(EntityUid uid, MetapsionicPowerComponent component, FocusedMetapsionicDoAfterEvent args) + { + if (!TryComp(args.Target, out var psychic)) + return; + + component.DoAfter = null; + + if (args.Target == null) return; + + if (TryComp(args.Target, out var swapped)) + { + _popups.PopupEntity(Loc.GetString(swapped.MindSwappedFeedback, ("entity", args.Target)), uid, uid, PopupType.LargeCaution); + return; + } + + if (args.Target == uid) + { + _popups.PopupEntity(Loc.GetString("metapulse-self", ("entity", args.Target)), uid, uid, PopupType.LargeCaution); + return; + } + + if (!HasComp(args.Target)) + { + _popups.PopupEntity(Loc.GetString("no-powers", ("entity", args.Target)), uid, uid, PopupType.LargeCaution); + return; + } + + if (HasComp(args.Target) & !HasComp(args.Target)) + { + _popups.PopupEntity(Loc.GetString("psychic-potential", ("entity", args.Target)), uid, uid, PopupType.LargeCaution); + return; + } + + foreach (var psychicFeedback in psychic.PsychicFeedback) + { + _popups.PopupEntity(Loc.GetString(psychicFeedback, ("entity", args.Target)), uid, uid, PopupType.LargeCaution); + } + + } + } +} diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs similarity index 78% rename from Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs rename to Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs index b23224cab48..1e50a586b4f 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Actions; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; +using Content.Shared.Psionics; using Content.Shared.Speech; using Content.Shared.Stealth.Components; using Content.Shared.Mobs.Components; @@ -10,19 +11,15 @@ using Content.Server.Popups; using Content.Server.Psionics; using Content.Server.GameTicking; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; using Content.Shared.Mind; using Content.Shared.Actions.Events; -namespace Content.Server.Abilities.Psionics +namespace Content.Server.Psionics.Abilities { public sealed class MindSwapPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly MindSystem _mindSystem = default!; @@ -38,20 +35,22 @@ public override void Initialize() SubscribeLocalEvent(OnDispelled); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnGhostAttempt); - // SubscribeLocalEvent(OnSwapInit); + SubscribeLocalEvent(OnSwapShutdown); + SubscribeLocalEvent(OnInsulated); } private void OnInit(EntityUid uid, MindSwapPowerComponent component, ComponentInit args) { - _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId ); - _actions.TryGetActionData( component.MindSwapActionEntity, out var actionData ); + _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId); + _actions.TryGetActionData( component.MindSwapActionEntity, out var actionData); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.MindSwapActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + if (TryComp(uid, out var psionic)) { - psionic.PsionicAbility = component.MindSwapActionEntity; psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.MindSwapFeedback); + psionic.Amplification += 1f; } } @@ -61,20 +60,26 @@ private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, Compone if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.MindSwapFeedback); + psionic.Amplification -= 1f; } } private void OnPowerUsed(MindSwapPowerActionEvent args) { + if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological")) return; if (HasComp(args.Target)) return; + if (!TryComp(args.Performer, out var psionic)) + return; + Swap(args.Performer, args.Target); - _psionics.LogPowerUsed(args.Performer, "mind swap"); + _psionics.LogPowerUsed(args.Performer, "mind swap", (int) MathF.Round(psionic.Amplification / psionic.Dampening * 8), (int) MathF.Round(psionic.Amplification / psionic.Dampening * 12)); args.Handled = true; } @@ -125,8 +130,8 @@ private void OnDispelled(EntityUid uid, MindSwappedComponent component, Dispelle private void OnMobStateChanged(EntityUid uid, MindSwappedComponent component, MobStateChangedEvent args) { - if (args.NewMobState == MobState.Dead) - RemComp(uid); + if (args.NewMobState == MobState.Dead || args.NewMobState == MobState.Critical) + Swap(uid, component.OriginalEntity, true); } private void OnGhostAttempt(GhostAttemptHandleEvent args) @@ -151,8 +156,21 @@ private void OnSwapInit(EntityUid uid, MindSwappedComponent component, Component _actions.TryGetActionData( component.MindSwapReturnActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.MindSwapReturnActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.MindSwapReturnActionEntity; + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.MindSwappedFeedback); + } + } + + private void OnSwapShutdown(EntityUid uid, MindSwappedComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.MindSwapReturnActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.MindSwappedFeedback); + } } public void Swap(EntityUid performer, EntityUid target, bool end = false) @@ -165,11 +183,13 @@ public void Swap(EntityUid performer, EntityUid target, bool end = false) MindComponent? targetMind = null; // This is here to prevent missing MindContainerComponent Resolve errors. - if(!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)){ + if (!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)) + { performerMind = null; }; - if(!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)){ + if (!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)) + { targetMind = null; }; //This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say @@ -202,7 +222,7 @@ public void Swap(EntityUid performer, EntityUid target, bool end = false) perfComp.OriginalEntity = target; targetComp.OriginalEntity = performer; } - + //It shouldn't actually be possible anymore to get trapped under most circumstances, but for niche edge cases, I am leaving this here public void GetTrapped(EntityUid uid) { @@ -220,5 +240,9 @@ public void GetTrapped(EntityUid uid) _metaDataSystem.SetEntityDescription(uid, Loc.GetString("telegnostic-trapped-entity-desc")); } } + public void OnInsulated(EntityUid uid, MindSwappedComponent component, PsionicInsulationEvent args) + { + Swap(uid, component.OriginalEntity, true); + } } } diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwappedComponent.cs b/Content.Server/Psionics/Abilities/MindSwappedComponent.cs similarity index 79% rename from Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwappedComponent.cs rename to Content.Server/Psionics/Abilities/MindSwappedComponent.cs index 72cd6a66ef9..82c0313bca6 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MindSwappedComponent.cs +++ b/Content.Server/Psionics/Abilities/MindSwappedComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Abilities.Psionics +namespace Content.Server.Psionics.Abilities { [RegisterComponent] public sealed partial class MindSwappedComponent : Component @@ -14,5 +14,8 @@ public sealed partial class MindSwappedComponent : Component [DataField("mindSwapReturnActionEntity")] public EntityUid? MindSwapReturnActionEntity; + + [DataField("mindSwappedFeedback")] + public string MindSwappedFeedback = "mindswapped-feedback"; } } diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs similarity index 54% rename from Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs rename to Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs index 0fd261ef12f..c935bc0123d 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/NoosphericZapPowerSystem.cs @@ -1,26 +1,21 @@ using Content.Shared.Actions; -using Content.Shared.Abilities.Psionics; -using Content.Server.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.StatusEffect; +using Content.Server.Electrocution; using Content.Server.Stunnable; using Content.Server.Beam; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; -namespace Content.Server.Abilities.Psionics +namespace Content.Server.Psionics.Abilities { public sealed class NoosphericZapPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly BeamSystem _beam = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly ElectrocutionSystem _electrocution = default!; public override void Initialize() @@ -37,10 +32,11 @@ private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, Compon _actions.TryGetActionData( component.NoosphericZapActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + if (TryComp(uid, out var psionic)) { - psionic.PsionicAbility = component.NoosphericZapActionEntity; psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.NoosphericZapFeedback); + psionic.Amplification += 1f; } } @@ -50,24 +46,34 @@ private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, Co if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.NoosphericZapFeedback); + psionic.Amplification -= 1f; } } private void OnPowerUsed(NoosphericZapPowerActionEvent args) { - if (!HasComp(args.Target)) + if (!TryComp(args.Performer, out var psionic)) return; - if (HasComp(args.Target)) - return; + if (!HasComp(args.Target) && !HasComp(args.Performer)) + { + _beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric"); + _stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(1 * psionic.Amplification), false); - _beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric"); + _electrocution.TryDoElectrocution(args.Target, null, + (int) MathF.Round(5f * psionic.Amplification), + new TimeSpan((long) MathF.Round(1f * psionic.Amplification)), + true, + ignoreInsulation: true); - _stunSystem.TryParalyze(args.Target, TimeSpan.FromSeconds(5), false); - _statusEffectsSystem.TryAddStatusEffect(args.Target, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent"); + _statusEffectsSystem.TryAddStatusEffect(args.Target, "Stutter", TimeSpan.FromSeconds(2 * psionic.Amplification), false, "StutteringAccent"); - _psionics.LogPowerUsed(args.Performer, "noospheric zap"); - args.Handled = true; + _psionics.LogPowerUsed(args.Performer, "noopsheric zap", + (int) MathF.Round(6 * psionic.Amplification - psionic.Dampening), + (int) MathF.Round(8 * psionic.Amplification - psionic.Dampening)); + args.Handled = true; + } } } } diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs similarity index 54% rename from Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs rename to Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs index ee16aaccfb6..915abd12224 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs @@ -1,77 +1,41 @@ -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Actions; using Content.Shared.Psionics.Glimmer; using Content.Shared.Random; using Content.Shared.Random.Helpers; -using Content.Server.EUI; -using Content.Server.Psionics; -using Content.Server.Mind; -using Content.Shared.Mind; -using Content.Shared.Mind.Components; using Content.Shared.StatusEffect; using Robust.Shared.Random; using Robust.Shared.Prototypes; -using Robust.Server.GameObjects; -using Robust.Server.Player; using Robust.Shared.Player; +using Content.Shared.Examine; +using Content.Shared.Popups; +using static Content.Shared.Examine.ExamineSystemShared; -namespace Content.Server.Abilities.Psionics +namespace Content.Server.Psionics.Abilities { public sealed class PsionicAbilitiesSystem : EntitySystem { [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly EuiManager _euiManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayerAttached); } - private void OnPlayerAttached(EntityUid uid, PsionicAwaitingPlayerComponent component, PlayerAttachedEvent args) - { - if (TryComp(uid, out var bonus) && bonus.Warn == true) - _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), args.Player); - else - AddRandomPsionicPower(uid); - RemCompDeferred(uid); - } - - public void AddPsionics(EntityUid uid, bool warn = true) - { - if (Deleted(uid)) - return; - - if (HasComp(uid)) - return; - - //Don't know if this will work. New mind state vs old. - if (!TryComp(uid, out var mindContainer) || - !_mindSystem.TryGetMind(uid, out _, out var mind )) - //|| - //!_mindSystem.TryGetMind(uid, out var mind, mindContainer)) - { - EnsureComp(uid); - return; - } - - if (!_mindSystem.TryGetSession(mind, out var client)) - return; - - if (warn && TryComp(uid, out var actor)) - _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), client); - else - AddRandomPsionicPower(uid); - } - - public void AddPsionics(EntityUid uid, string powerComp) + /// + /// Adds a psychic power once a character rolls one. This used to be a system you have to select for. However the opt-in is no longer the text window, but is now done at character creation. + /// TODO: This is going to get removed when I reach Part 3 of my reworks, when I touch upon the GlimmerSystem itself and overhaul how players get powers. + /// + /// + /// + /// + public void AddPsionics(EntityUid uid) { if (Deleted(uid)) return; @@ -79,17 +43,11 @@ public void AddPsionics(EntityUid uid, string powerComp) if (HasComp(uid)) return; - AddComp(uid); - - var newComponent = (Component) _componentFactory.GetComponent(powerComp); - newComponent.Owner = uid; - - EntityManager.AddComponent(uid, newComponent); + AddRandomPsionicPower(uid); } - public void AddRandomPsionicPower(EntityUid uid) { - AddComp(uid); + EnsureComp(uid, out var psionic); if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) { @@ -103,11 +61,21 @@ public void AddRandomPsionicPower(EntityUid uid) EntityManager.AddComponent(uid, newComponent); - _glimmerSystem.Glimmer += _random.Next(1, 5); + _glimmerSystem.Glimmer += _random.Next((int) MathF.Round(psionic.Amplification * psionic.Dampening * 1), (int) MathF.Round(psionic.Amplification * psionic.Dampening * 5)); } public void RemovePsionics(EntityUid uid) { + if (RemComp(uid)) + { + _popups.PopupEntity(Loc.GetString("mindbreaking-feedback", ("entity", uid)), + uid, + // TODO: Use LoS-based Filter when one is available. + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + PopupType.Medium); + } + if (!TryComp(uid, out var psionic)) return; @@ -130,14 +98,15 @@ public void RemovePsionics(EntityUid uid) if (psionic.PsionicAbility != null){ _actionsSystem.TryGetActionData( psionic.PsionicAbility, out var psiAbility ); if (psiAbility != null){ - var owner = psiAbility.Owner; _actionsSystem.RemoveAction(uid, psiAbility.Owner); } } _statusEffectsSystem.TryAddStatusEffect(uid, "Stutter", TimeSpan.FromMinutes(5), false, "StutteringAccent"); + _glimmerSystem.Glimmer += _random.Next((int) MathF.Round(psionic.Amplification * psionic.Dampening * -10), (int) MathF.Round(psionic.Amplification * psionic.Dampening * -5)); RemComp(uid); + RemComp(uid); } } } diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs similarity index 57% rename from Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs rename to Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs index 5ca1dc7a6dc..0c50efb5cf3 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs @@ -1,31 +1,31 @@ +using Content.Server.DoAfter; using Content.Shared.Actions; -using Content.Shared.CombatMode.Pacification; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Damage; +using Content.Shared.DoAfter; using Content.Shared.Stunnable; using Content.Shared.Stealth; using Content.Shared.Stealth.Components; -using Content.Server.Psionics; -using Robust.Shared.Prototypes; -using Robust.Shared.Player; -using Robust.Shared.Audio; -using Robust.Shared.Timing; -using Content.Server.Mind; +using Content.Shared.Psionics.Events; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; +using Content.Shared.Interaction.Events; +using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Throwing; +using Robust.Shared.Timing; +using Content.Shared.Psionics; -namespace Content.Server.Abilities.Psionics +namespace Content.Server.Psionics.Abilities { public sealed class PsionicInvisibilityPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedStealthSystem _stealth = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; public override void Initialize() { @@ -37,43 +37,61 @@ public override void Initialize() SubscribeLocalEvent(OnStart); SubscribeLocalEvent(OnEnd); SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnAttackAttempt); + SubscribeLocalEvent(OnShootAttempt); + SubscribeLocalEvent(OnThrowAttempt); + SubscribeLocalEvent(OnInsulated); } private void OnInit(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentInit args) { - _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId ); - _actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData ); + _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId); + _actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) + if (TryComp(uid, out var psionic)) { - psionic.PsionicAbility = component.PsionicInvisibilityActionEntity; psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.InvisibilityFeedback); + psionic.Amplification += 0.5f; } } private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args) { + RemComp(uid); + RemComp(uid); _actions.RemoveAction(uid, component.PsionicInvisibilityActionEntity); if (TryComp(uid, out var psionic)) { psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.InvisibilityFeedback); + psionic.Amplification -= 0.5f; } } private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args) { - if (HasComp(uid)) + if (!TryComp(uid, out var psionic)) + return; + + if (HasComp(uid)) return; + var ev = new PsionicInvisibilityTimerEvent(_gameTiming.CurTime); + var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseTimer, ev, uid) { Hidden = true }; + _doAfterSystem.TryStartDoAfter(doAfterArgs); + ToggleInvisibility(args.Performer); var action = Spawn(PsionicInvisibilityUsedComponent.PsionicInvisibilityUsedActionPrototype); _actions.AddAction(uid, action, action); - _actions.TryGetActionData( action, out var actionData ); + _actions.TryGetActionData(action, out var actionData); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(action); - _psionics.LogPowerUsed(uid, "psionic invisibility"); + _psionics.LogPowerUsed(uid, "psionic invisibility", + (int) MathF.Round(8 * psionic.Amplification - 2 * psionic.Dampening), + (int) MathF.Round(12 * psionic.Amplification - 2 * psionic.Dampening)); args.Handled = true; } @@ -89,7 +107,6 @@ private void OnPowerOff(RemovePsionicInvisibilityOffPowerActionEvent args) private void OnStart(EntityUid uid, PsionicInvisibilityUsedComponent component, ComponentInit args) { EnsureComp(uid); - EnsureComp(uid); var stealth = EnsureComp(uid); _stealth.SetVisibility(uid, 0.66f, stealth); _audio.PlayPvs("/Audio/Effects/toss.ogg", uid); @@ -102,24 +119,37 @@ private void OnEnd(EntityUid uid, PsionicInvisibilityUsedComponent component, Co return; RemComp(uid); - RemComp(uid); RemComp(uid); _audio.PlayPvs("/Audio/Effects/toss.ogg", uid); - //Pretty sure this DOESN'T work as intended. _actions.RemoveAction(uid, component.PsionicInvisibilityUsedActionEntity); - - _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(8), false); DirtyEntity(uid); } + private void OnAttackAttempt(EntityUid uid, PsionicInvisibilityUsedComponent component, AttackAttemptEvent args) + { + RemComp(uid); + } + + private void OnShootAttempt(EntityUid uid, PsionicInvisibilityUsedComponent component, ShotAttemptedEvent args) + { + RemComp(uid); + } + + private void OnThrowAttempt(EntityUid uid, PsionicInvisibilityUsedComponent component, ThrowAttemptEvent args) + { + RemComp(uid); + } private void OnDamageChanged(EntityUid uid, PsionicInvisibilityUsedComponent component, DamageChangedEvent args) { + if (!TryComp(uid, out var psionic)) + return; + if (!args.DamageIncreased) return; ToggleInvisibility(uid); + _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(4f / psionic.Dampening + psionic.Amplification), false); } - public void ToggleInvisibility(EntityUid uid) { if (!HasComp(uid)) @@ -130,5 +160,19 @@ public void ToggleInvisibility(EntityUid uid) RemComp(uid); } } + + public void OnDoAfter(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityTimerEvent args) + { + RemComp(uid); + } + + private void OnInsulated(EntityUid uid, PsionicInvisibilityUsedComponent component, PsionicInsulationEvent args) + { + if (!TryComp(uid, out var psionic)) + return; + + RemComp(uid); + _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(4f / psionic.Dampening + psionic.Amplification), false); + } } } diff --git a/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs new file mode 100644 index 00000000000..77075dab206 --- /dev/null +++ b/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs @@ -0,0 +1,93 @@ +using Content.Shared.Actions; +using Content.Shared.Psionics.Abilities; +using Content.Server.Atmos.Components; +using Content.Server.Weapons.Ranged.Systems; +using Robust.Server.GameObjects; +using Content.Shared.Actions.Events; +using Content.Server.Explosion.Components; +using Content.Shared.Mobs.Components; +using Robust.Shared.Map; + +namespace Content.Server.Psionics.Abilities +{ + public sealed class PyrokinesisPowerSystem : EntitySystem + { + [Dependency] private readonly TransformSystem _xform = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly GunSystem _gunSystem = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId); + _actions.TryGetActionData( component.PyrokinesisActionEntity, out var actionData); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.PyrokinesisActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.PyrokinesisFeedback); + psionic.Amplification += 1f; + } + } + + private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.PyrokinesisActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.PyrokinesisFeedback); + psionic.Amplification -= 1f; + } + } + + private void OnPowerUsed(PyrokinesisPowerActionEvent args) + { + if (!TryComp(args.Performer, out var psionic)) + return; + + if (!HasComp(args.Performer)) + { + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(args.Performer); + + var mapPos = xform.Coordinates.ToMap(EntityManager, _xform); + var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _) + ? xform.Coordinates.WithEntityId(gridUid, EntityManager) + : new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position); + + var ent = Spawn("ProjectileAnomalyFireball", spawnCoords); + + if (TryComp(ent, out var fireball)) + { + fireball.MaxIntensity = (int) MathF.Round(20 * psionic.Amplification - 10 * psionic.Dampening); + + if (psionic.Amplification > 5 && EnsureComp(ent, out var ignite)) + { + ignite.FireStacks = 0.2f * psionic.Amplification - 0.1f * psionic.Dampening; + } + } + + var direction = args.Target.Position; + + _gunSystem.ShootProjectile(ent, direction, new System.Numerics.Vector2(0, 0), args.Performer, args.Performer, 20f); + + _psionics.LogPowerUsed(args.Performer, "pyrokinesis", + (int) MathF.Round(6f * psionic.Amplification - psionic.Dampening), + (int) MathF.Round(8f * psionic.Amplification - psionic.Dampening)); + args.Handled = true; + } + } + } +} diff --git a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs new file mode 100644 index 00000000000..e184b19396b --- /dev/null +++ b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs @@ -0,0 +1,76 @@ +using Content.Server.Body.Systems; +using Content.Server.Body.Components; +using Content.Shared.Actions; +using Content.Shared.Chemistry.Components; +using Content.Shared.Bed.Sleep; +using Content.Shared.Psionics.Abilities; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Content.Shared.Mind; +using Content.Shared.Actions.Events; +using Content.Shared.FixedPoint; + +namespace Content.Server.Psionics.Abilities +{ + public sealed class MassSleepPowerSystem : EntitySystem + { + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnInit(EntityUid uid, RegenerativeStasisPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.RegenerativeStasisActionEntity, component.RegenerativeStasisActionId); + _actions.TryGetActionData(component.RegenerativeStasisActionEntity, out var actionData); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.RegenerativeStasisActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.RegenerativeStasisFeedback); + psionic.Amplification += 0.5f; + psionic.Dampening += 0.5f; + } + } + + private void OnShutdown(EntityUid uid, RegenerativeStasisPowerComponent component, ComponentShutdown args) + { + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.RegenerativeStasisFeedback); + psionic.Amplification -= 0.5f; + psionic.Dampening -= 0.5f; + } + _actions.RemoveAction(uid, component.RegenerativeStasisActionEntity); + } + + private void OnPowerUsed(EntityUid uid, RegenerativeStasisPowerComponent component, RegenerativeStasisPowerActionEvent args) + { + if (TryComp(uid, out var psionic) + && !HasComp(uid) + && !HasComp(args.Target) + && TryComp(args.Target, out var stream)) + { + var solution = new Solution(); + solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(MathF.Min(2.5f * psionic.Amplification + psionic.Dampening, 15f))); + solution.AddReagent("Epinephrine", FixedPoint2.New(MathF.Min(2.5f * psionic.Dampening + psionic.Amplification, 15f))); + _bloodstreamSystem.TryAddToChemicals(args.Target, solution, stream); + EnsureComp(args.Target); + + _psionics.LogPowerUsed(uid, "regenerative stasis", + (int) Math.Round(4 * psionic.Amplification - psionic.Dampening), + (int) Math.Round(6 * psionic.Amplification - psionic.Dampening)); + args.Handled = true; + } + } + } +} diff --git a/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs new file mode 100644 index 00000000000..f03b001fc70 --- /dev/null +++ b/Content.Server/Psionics/Abilities/TelegnosisPowerSystem.cs @@ -0,0 +1,106 @@ +using Content.Shared.Actions; +using Content.Shared.Psionics.Abilities; +using Content.Shared.Mind.Components; +using Content.Shared.Actions.Events; +using Content.Shared.Mobs; +using Content.Shared.Storage.Components; + +namespace Content.Server.Psionics.Abilities +{ + public sealed class TelegnosisPowerSystem : EntitySystem + { + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnMindRemoved); + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnMobstateChanged); + SubscribeLocalEvent(OnStorageInsertAttempt); + } + + private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) + { + _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId ); + _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); + if (actionData is { UseDelay: not null }) + _actions.StartUseDelay(component.TelegnosisActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.TelegnosisFeedback); + psionic.Amplification += 0.3f; + psionic.Dampening += 0.3f; + } + } + + private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) + { + _actions.RemoveAction(uid, component.TelegnosisActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.ActivePowers.Remove(component); + psionic.PsychicFeedback.Remove(component.TelegnosisFeedback); + psionic.Amplification -= 0.3f; + psionic.Dampening -= 0.3f; + } + } + + private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) + { + if (!TryComp(uid, out var psionic)) + return; + + if (HasComp(uid)) + return; + + var projection = Spawn(component.Prototype, Transform(uid).Coordinates); + Transform(projection).AttachToGridOrMap(); + component.OriginalEntity = uid; + component.IsProjecting = true; + component.ProjectionUid = projection; + _mindSwap.Swap(uid, projection); + + if (EnsureComp(projection, out var projectionComponent)) + projectionComponent.OriginalEntity = uid; + + _psionics.LogPowerUsed(uid, "telegnosis", + (int) Math.Round(8f * psionic.Amplification - psionic.Dampening), + (int) Math.Round(12f * psionic.Amplification - psionic.Dampening)); + + args.Handled = true; + } + private void OnMindRemoved(EntityUid uid, TelegnosticProjectionComponent component, MindRemovedMessage args) + { + if (TryComp(component.OriginalEntity, out var originalEntity)) + originalEntity.IsProjecting = false; + + QueueDel(uid); + } + + private void OnDispelled(EntityUid uid, TelegnosisPowerComponent component, DispelledEvent args) + { + if (component.IsProjecting) + _mindSwap.Swap(uid, component.ProjectionUid); + } + + private void OnMobstateChanged(EntityUid uid, TelegnosisPowerComponent component, MobStateChangedEvent args) + { + if (component.IsProjecting && args.NewMobState is MobState.Critical + || component.IsProjecting && args.NewMobState is MobState.Dead) + _mindSwap.Swap(uid, component.ProjectionUid); + } + + private void OnStorageInsertAttempt(EntityUid uid, TelegnosisPowerComponent component, InsertIntoEntityStorageAttemptEvent args) + { + if (component.IsProjecting) + _mindSwap.Swap(uid, component.ProjectionUid); + } + } +} diff --git a/Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs b/Content.Server/Psionics/AcceptPsionicsEui.cs similarity index 95% rename from Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs rename to Content.Server/Psionics/AcceptPsionicsEui.cs index 80fd8946f28..7c652664c64 100644 --- a/Content.Server/Nyanotrasen/Psionics/AcceptPsionicsEui.cs +++ b/Content.Server/Psionics/AcceptPsionicsEui.cs @@ -1,7 +1,7 @@ using Content.Shared.Psionics; using Content.Shared.Eui; using Content.Server.EUI; -using Content.Server.Abilities.Psionics; +using Content.Server.Psionics.Abilities; namespace Content.Server.Psionics { diff --git a/Content.Server/Nyanotrasen/Psionics/AntiPsychicWeaponComponent.cs b/Content.Server/Psionics/AntiPsychicWeaponComponent.cs similarity index 100% rename from Content.Server/Nyanotrasen/Psionics/AntiPsychicWeaponComponent.cs rename to Content.Server/Psionics/AntiPsychicWeaponComponent.cs diff --git a/Content.Server/Nyanotrasen/Audio/GlimmerSoundComponent.cs b/Content.Server/Psionics/Audio/GlimmerSoundComponent.cs similarity index 80% rename from Content.Server/Nyanotrasen/Audio/GlimmerSoundComponent.cs rename to Content.Server/Psionics/Audio/GlimmerSoundComponent.cs index 850be3e831c..9a6c62381be 100644 --- a/Content.Server/Nyanotrasen/Audio/GlimmerSoundComponent.cs +++ b/Content.Server/Psionics/Audio/GlimmerSoundComponent.cs @@ -2,12 +2,8 @@ using Content.Shared.Audio; using Content.Shared.Psionics.Glimmer; using Robust.Shared.Audio; -using Robust.Shared.ComponentTrees; -using Robust.Shared.GameStates; -using Robust.Shared.Physics; -using Robust.Shared.Serialization; -namespace Content.Server.Audio +namespace Content.Server.Psionics.Audio { [RegisterComponent] [Access(typeof(SharedAmbientSoundSystem), typeof(GlimmerReactiveSystem))] diff --git a/Content.Server/Nyanotrasen/Psionics/Dreams/DreamSystem.cs b/Content.Server/Psionics/Dreams/DreamSystem.cs similarity index 93% rename from Content.Server/Nyanotrasen/Psionics/Dreams/DreamSystem.cs rename to Content.Server/Psionics/Dreams/DreamSystem.cs index d6067717c94..1731c7a9bf5 100644 --- a/Content.Server/Nyanotrasen/Psionics/Dreams/DreamSystem.cs +++ b/Content.Server/Psionics/Dreams/DreamSystem.cs @@ -1,17 +1,14 @@ using Content.Shared.Dataset; using Content.Shared.Bed.Sleep; -using Content.Server.Chat.Systems; using Content.Server.Chat.Managers; using Robust.Shared.Random; using Robust.Shared.Prototypes; -using Robust.Server.GameObjects; using Robust.Shared.Player; namespace Content.Server.Psionics.Dreams { public sealed class DreamsSystem : EntitySystem { - [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; diff --git a/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerCommands.cs b/Content.Server/Psionics/Glimmer/GlimmerCommands.cs similarity index 100% rename from Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerCommands.cs rename to Content.Server/Psionics/Glimmer/GlimmerCommands.cs diff --git a/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs b/Content.Server/Psionics/Glimmer/GlimmerReactiveSystem.cs similarity index 99% rename from Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs rename to Content.Server/Psionics/Glimmer/GlimmerReactiveSystem.cs index da3b07d6dab..c0802c8b670 100644 --- a/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs +++ b/Content.Server/Psionics/Glimmer/GlimmerReactiveSystem.cs @@ -1,14 +1,12 @@ -using Content.Server.Audio; +using Content.Server.Psionics.Audio; using Content.Server.Power.Components; using Content.Server.Electrocution; using Content.Server.Lightning; using Content.Server.Explosion.EntitySystems; -using Content.Server.Construction; using Content.Server.Ghost; using Content.Server.Revenant.EntitySystems; using Content.Shared.Audio; using Content.Shared.Construction.EntitySystems; -using Content.Shared.Coordinates.Helpers; using Content.Shared.GameTicking; using Content.Shared.Psionics.Glimmer; using Content.Shared.Verbs; @@ -16,7 +14,6 @@ using Content.Shared.Damage; using Content.Shared.Destructible; using Content.Shared.Construction.Components; -using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Weapons.Melee.Components; using Robust.Shared.Audio; diff --git a/Content.Server/Nyanotrasen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs b/Content.Server/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs similarity index 94% rename from Content.Server/Nyanotrasen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs rename to Content.Server/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs index f0da85ce453..57c74398b08 100644 --- a/Content.Server/Nyanotrasen/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs +++ b/Content.Server/Psionics/Glimmer/PassiveGlimmerReductionSystem.cs @@ -4,7 +4,6 @@ using Content.Shared.CCVar; using Content.Shared.Psionics.Glimmer; using Content.Shared.GameTicking; -using Content.Server.CartridgeLoader.Cartridges; namespace Content.Server.Psionics.Glimmer { @@ -17,7 +16,6 @@ public sealed class PassiveGlimmerReductionSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly GlimmerMonitorCartridgeSystem _cartridgeSys = default!; /// List of glimmer values spaced by minute. public List GlimmerValues = new(); diff --git a/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs b/Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs similarity index 100% rename from Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs rename to Content.Server/Psionics/Glimmer/Structures/GlimmerSourceComponent.cs diff --git a/Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs b/Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs similarity index 100% rename from Content.Server/Nyanotrasen/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs rename to Content.Server/Psionics/Glimmer/Structures/GlimmerStructuresSystem.cs diff --git a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs similarity index 88% rename from Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs rename to Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs index 31e6b89f13d..9583f45fdc9 100644 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibilitySystem.cs +++ b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs @@ -1,5 +1,6 @@ -using Content.Shared.Abilities.Psionics; -using Content.Server.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; +using Content.Shared.Psionics; +using Content.Server.Psionics.Abilities; using Content.Shared.Eye; using Content.Server.NPC.Systems; using Robust.Shared.Containers; @@ -20,7 +21,6 @@ public override void Initialize() SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnInsulInit); SubscribeLocalEvent(OnInsulShutdown); - SubscribeLocalEvent(OnEyeInit); /// Layer SubscribeLocalEvent(OnInvisInit); @@ -36,10 +36,16 @@ private void OnInit(EntityUid uid, PotentialPsionicComponent component, Componen SetCanSeePsionicInvisiblity(uid, false); } + /// + /// Being able to see invisible by default is no longer tracked by "Not having Potential Psionic". + /// Anything intended to be immune to invisibility(and mind magic in general) should instead have PsionicInsulation as a built-in component + /// + /// + /// + /// private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, ComponentInit args) { - if (!HasComp(uid)) - return; + RaiseLocalEvent(uid, new PsionicInsulationEvent()); if (HasComp(uid)) _invisSystem.ToggleInvisibility(uid); @@ -61,9 +67,6 @@ private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, Co private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component, ComponentShutdown args) { - if (!HasComp(uid)) - return; - SetCanSeePsionicInvisiblity(uid, false); if (!HasComp(uid)) @@ -99,10 +102,6 @@ private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent compon } } - private void OnEyeInit(EntityUid uid, EyeComponent component, ComponentInit args) - { - //SetCanSeePsionicInvisiblity(uid, true); //JJ Comment - Not allowed to modifies .yml on spawn any longer. See UninitializedSaveTest. - } private void OnEntInserted(EntityUid uid, PsionicallyInvisibleComponent component, EntInsertedIntoContainerMessage args) { DirtyEntity(args.Entity); @@ -125,7 +124,7 @@ public void SetCanSeePsionicInvisiblity(EntityUid uid, bool set) { if (EntityManager.TryGetComponent(uid, out EyeComponent? eye)) { - _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~ (int) VisibilityFlags.PsionicInvisibility, eye); + _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~(int) VisibilityFlags.PsionicInvisibility, eye); } } } diff --git a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs b/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs similarity index 95% rename from Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs rename to Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs index 859ceb7b83a..268deddf6d9 100644 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs +++ b/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs @@ -1,5 +1,4 @@ using Content.Shared.Whitelist; -using Robust.Shared.Timing; namespace Content.Server.Psionics { diff --git a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs b/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs similarity index 95% rename from Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs rename to Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs index cec755e3260..403e0592617 100644 --- a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs +++ b/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs @@ -2,7 +2,6 @@ using Content.Shared.Stealth.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; -using Robust.Shared.Timing; namespace Content.Server.Psionics { @@ -12,7 +11,6 @@ namespace Content.Server.Psionics public sealed class PsionicInvisibleContactsSystem : EntitySystem { [Dependency] private readonly SharedStealthSystem _stealth = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; public override void Initialize() { diff --git a/Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicallyInvisibleComponent.cs b/Content.Server/Psionics/Invisibility/PsionicallyInvisibleComponent.cs similarity index 100% rename from Content.Server/Nyanotrasen/Psionics/Invisibility/PsionicallyInvisibleComponent.cs rename to Content.Server/Psionics/Invisibility/PsionicallyInvisibleComponent.cs diff --git a/Content.Server/Psionics/PotentialPsionicComponent.cs b/Content.Server/Psionics/PotentialPsionicComponent.cs new file mode 100644 index 00000000000..e874296a4c3 --- /dev/null +++ b/Content.Server/Psionics/PotentialPsionicComponent.cs @@ -0,0 +1,21 @@ +namespace Content.Server.Psionics +{ + [RegisterComponent] + public sealed partial class PotentialPsionicComponent : Component + { + /// + /// The base chance of an entity rolling psychic powers, which is increased by other modifiers such as glimmer. + /// + /// + /// I have increased this to 10% up from its original value of 2%, because I estimate that most people won't take the Latent Psychic trait + /// Simply because they might not even know it exists + /// + [DataField("chance")] + public float Chance = 0.10f; + + /// + /// YORO (you only reroll once) + /// + public bool Rerolled = false; + } +} diff --git a/Content.Server/Nyanotrasen/Psionics/PsionicAwaitingPlayerComponent.cs b/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs similarity index 100% rename from Content.Server/Nyanotrasen/Psionics/PsionicAwaitingPlayerComponent.cs rename to Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs diff --git a/Content.Server/Nyanotrasen/Psionics/PsionicBonusChanceComponent.cs b/Content.Server/Psionics/PsionicBonusChanceComponent.cs similarity index 100% rename from Content.Server/Nyanotrasen/Psionics/PsionicBonusChanceComponent.cs rename to Content.Server/Psionics/PsionicBonusChanceComponent.cs diff --git a/Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs b/Content.Server/Psionics/PsionicsCommands.cs similarity index 84% rename from Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs rename to Content.Server/Psionics/PsionicsCommands.cs index 959251d1fb7..3f9ee794b38 100644 --- a/Content.Server/Nyanotrasen/Psionics/PsionicsCommands.cs +++ b/Content.Server/Psionics/PsionicsCommands.cs @@ -1,9 +1,8 @@ using Content.Server.Administration; using Content.Shared.Administration; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Mobs.Components; using Robust.Shared.Console; -using Robust.Server.GameObjects; using Content.Shared.Actions; using Robust.Shared.Player; @@ -19,7 +18,8 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) { SharedActionsSystem actions = default!; var entMan = IoCManager.Resolve(); - foreach (var (actor, mob, psionic, meta) in entMan.EntityQuery()){ + foreach (var (actor, psionic, meta) in entMan.EntityQuery()) + { // filter out xenos, etc, with innate telepathy actions.TryGetActionData( psionic.PsionicAbility, out var actionData ); if (actionData == null || actionData.ToString() == null) diff --git a/Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs similarity index 91% rename from Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs rename to Content.Server/Psionics/PsionicsSystem.cs index 5a96af2e96b..bf829477609 100644 --- a/Content.Server/Nyanotrasen/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -1,19 +1,14 @@ -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.StatusEffect; -using Content.Shared.Mobs; using Content.Shared.Psionics.Glimmer; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Damage.Events; -using Content.Shared.IdentityManagement; using Content.Shared.CCVar; -using Content.Server.Abilities.Psionics; -using Content.Server.Chat.Systems; +using Content.Server.Psionics.Abilities; using Content.Server.Electrocution; using Content.Server.NPC.Components; using Content.Server.NPC.Systems; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; using Robust.Shared.Configuration; using Robust.Shared.Random; @@ -27,7 +22,6 @@ public sealed class PsionicsSystem : EntitySystem [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -54,6 +48,7 @@ public override void Initialize() SubscribeLocalEvent(OnStamHit); SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnRemove); } @@ -87,7 +82,11 @@ private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, Mel _electrocutionSystem.TryDoElectrocution(args.User, null, 20, TimeSpan.FromSeconds(5), false); } } - + private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) + { + component.Amplification = _random.NextFloat(0.3f, 1.1f); + component.Dampening = _random.NextFloat(0.3f, 1.1f); + } private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args) { if (!component.Removable) @@ -104,7 +103,7 @@ private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit arg private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) { - if (!TryComp(uid, out var factions)) + if (!HasComp(uid)) return; _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); @@ -144,14 +143,14 @@ public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, boo } if (applyGlimmer) - chance += ((float) _glimmerSystem.Glimmer / 1000); + chance += (float) _glimmerSystem.Glimmer / 1000; chance *= multiplier; chance = Math.Clamp(chance, 0, 1); if (_random.Prob(chance)) - _psionicAbilitiesSystem.AddPsionics(uid, warn); + _psionicAbilitiesSystem.AddPsionics(uid); } public void RerollPsionics(EntityUid uid, PotentialPsionicComponent? psionic = null, float bonusMuliplier = 1f) diff --git a/Content.Server/Nyanotrasen/Chat/TSayCommand.cs b/Content.Server/Psionics/Telepathy/TSayCommand.cs similarity index 95% rename from Content.Server/Nyanotrasen/Chat/TSayCommand.cs rename to Content.Server/Psionics/Telepathy/TSayCommand.cs index 9ba27b65d71..8fbaa5e17b2 100644 --- a/Content.Server/Nyanotrasen/Chat/TSayCommand.cs +++ b/Content.Server/Psionics/Telepathy/TSayCommand.cs @@ -1,11 +1,10 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; -using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.Player; -namespace Content.Server.Chat.Commands +namespace Content.Server.Psionics.Telepathy { [AnyCommand] internal sealed class TSayCommand : IConsoleCommand diff --git a/Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs b/Content.Server/Psionics/Telepathy/TelepathicRepeaterComponent.cs similarity index 82% rename from Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs rename to Content.Server/Psionics/Telepathy/TelepathicRepeaterComponent.cs index fc199f4332a..6e194f76c8f 100644 --- a/Content.Server/Nyanotrasen/Chat/TelepathicRepeaterComponent.cs +++ b/Content.Server/Psionics/Telepathy/TelepathicRepeaterComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Nyanotrasen.Chat +namespace Content.Server.Psionics.Telepathy { /// /// Repeats whatever is happening in telepathic chat. diff --git a/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs b/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs similarity index 85% rename from Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs rename to Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs index 58ed1782741..ad49075e65a 100644 --- a/Content.Server/Nyanotrasen/Chat/NyanoChatSystem.cs +++ b/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs @@ -2,7 +2,7 @@ using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.Bed.Sleep; using Content.Shared.Chat; using Content.Shared.Database; @@ -16,10 +16,10 @@ using System.Linq; using System.Text; -namespace Content.Server.Nyanotrasen.Chat +namespace Content.Server.Psionics.Telepathy { /// - /// Extensions for nyano's chat stuff + /// Extensions for Telepathic Chat /// public sealed class NyanoChatSystem : EntitySystem @@ -47,7 +47,9 @@ private IEnumerable GetAdminClients() private List GetDreamers(IEnumerable removeList) { var filtered = Filter.Empty() - .AddWhereAttachedEntity(entity => HasComp(entity) || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) + .AddWhereAttachedEntity(entity => HasComp(entity) + || HasComp(entity) && !HasComp(entity) && !HasComp(entity) + || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) .Recipients .Select(p => p.ConnectedClient); @@ -61,7 +63,7 @@ private List GetDreamers(IEnumerable removeList) private bool IsEligibleForTelepathy(EntityUid entity) { - return HasComp(entity) + return TryComp(entity, out var psionic) && psionic.Telepath && !HasComp(entity) && !HasComp(entity) && (!TryComp(entity, out var mobstate) || mobstate.CurrentState == MobState.Alive); @@ -92,9 +94,9 @@ public void SendTelepathicChat(EntityUid source, string message, bool hideChat) if (_random.Prob(0.1f)) _glimmerSystem.Glimmer++; - if (_random.Prob(Math.Min(0.33f + ((float) _glimmerSystem.Glimmer / 1500), 1))) + if (_random.Prob(Math.Min(0.33f + (float) _glimmerSystem.Glimmer / 1500, 1))) { - float obfuscation = (0.25f + (float) _glimmerSystem.Glimmer / 2000); + float obfuscation = 0.25f + (float) _glimmerSystem.Glimmer / 2000; var obfuscated = ObfuscateMessageReadability(message, obfuscation); _chatManager.ChatMessageToMany(ChatChannel.Telepathic, obfuscated, messageWrap, source, hideChat, false, GetDreamers(clients), Color.PaleVioletRed); } @@ -111,7 +113,7 @@ private string ObfuscateMessageReadability(string message, float chance) for (var i = 0; i < message.Length; i++) { - if (char.IsWhiteSpace((modifiedMessage[i]))) + if (char.IsWhiteSpace(modifiedMessage[i])) { continue; } diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index 2776db2283a..e2b17b58724 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -125,11 +125,7 @@ protected override async Task Process() air.Gases.CopyTo(moles, 0); var atmos = _entManager.EnsureComponent(mapUid); _entManager.System().SetMapSpace(mapUid, air.Space, atmos); - _entManager.System().SetMapGasMixture(mapUid, new GasMixture(2500) - { - Temperature = mission.Temperature, - Moles = moles, - }, atmos); + _entManager.System().SetMapGasMixture(mapUid, new GasMixture(moles, mission.Temperature), atmos); if (mission.Color != null) { diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index f9ceab8f7b1..e4e4534b0c5 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -371,8 +371,8 @@ private void UpdateFTLStarting(Entity entity) Enable(uid, component: body); _physics.SetLinearVelocity(uid, new Vector2(0f, 20f), body: body); _physics.SetAngularVelocity(uid, 0f, body: body); - _physics.SetLinearDamping(body, 0f); - _physics.SetAngularDamping(body, 0f); + _physics.SetLinearDamping(uid, body, 0f); + _physics.SetAngularDamping(uid, body, 0f); _dockSystem.SetDockBolts(uid, true); _console.RefreshShuttleConsoles(uid); @@ -426,8 +426,8 @@ private void UpdateFTLArriving(Entity entity) _physics.SetLinearVelocity(uid, Vector2.Zero, body: body); _physics.SetAngularVelocity(uid, 0f, body: body); - _physics.SetLinearDamping(body, entity.Comp2.LinearDamping); - _physics.SetAngularDamping(body, entity.Comp2.AngularDamping); + _physics.SetLinearDamping(uid, body, entity.Comp2.LinearDamping); + _physics.SetAngularDamping(uid, body, entity.Comp2.AngularDamping); var target = entity.Comp1.TargetCoordinates; diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 8d44dae4b2e..7c42753a7de 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -129,10 +129,10 @@ public void Enable(EntityUid uid, FixturesComponent? manager = null, PhysicsComp return; _physics.SetBodyType(uid, BodyType.Dynamic, manager: manager, body: component); - _physics.SetBodyStatus(component, BodyStatus.InAir); + _physics.SetBodyStatus(uid, component, BodyStatus.InAir); _physics.SetFixedRotation(uid, false, manager: manager, body: component); - _physics.SetLinearDamping(component, shuttle.LinearDamping); - _physics.SetAngularDamping(component, shuttle.AngularDamping); + _physics.SetLinearDamping(uid, component, shuttle.LinearDamping); + _physics.SetAngularDamping(uid, component, shuttle.AngularDamping); } public void Disable(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? component = null) @@ -141,7 +141,7 @@ public void Disable(EntityUid uid, FixturesComponent? manager = null, PhysicsCom return; _physics.SetBodyType(uid, BodyType.Static, manager: manager, body: component); - _physics.SetBodyStatus(component, BodyStatus.OnGround); + _physics.SetBodyStatus(uid, component, BodyStatus.OnGround); _physics.SetFixedRotation(uid, true, manager: manager, body: component); } diff --git a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs index 7784db015d3..ba07375699b 100644 --- a/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs +++ b/Content.Server/Singularity/EntitySystems/EventHorizonSystem.cs @@ -13,7 +13,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Timing; -using Content.Shared.Abilities.Psionics; //Nyano - Summary: for the telegnostic projection. +using Content.Shared.Psionics.Abilities; //EE - Summary: for the telegnostic projection. namespace Content.Server.Singularity.EntitySystems; @@ -39,7 +39,7 @@ public override void Initialize() SubscribeLocalEvent(PreventConsume); SubscribeLocalEvent(PreventConsume); - SubscribeLocalEvent(PreventConsume); ///Nyano - Summary: the telegnositic projection has the same trait as ghosts. + SubscribeLocalEvent(PreventConsume); ///EE - Summary: the telegnositic projection has the same trait as ghosts. SubscribeLocalEvent(PreventConsume); SubscribeLocalEvent(OnHorizonMapInit); SubscribeLocalEvent(OnStartCollide); diff --git a/Content.Server/Spreader/SpreaderSystem.cs b/Content.Server/Spreader/SpreaderSystem.cs index 89951718236..5b2f3298a2b 100644 --- a/Content.Server/Spreader/SpreaderSystem.cs +++ b/Content.Server/Spreader/SpreaderSystem.cs @@ -18,6 +18,7 @@ namespace Content.Server.Spreader; /// public sealed class SpreaderSystem : EntitySystem { + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly SharedMapSystem _map = default!; @@ -33,6 +34,8 @@ public sealed class SpreaderSystem : EntitySystem // TODO PERFORMANCE Assign each prototype to an index and convert dictionary to array private readonly Dictionary> _gridUpdates = []; + private EntityQuery _query; + public const float SpreadCooldownSeconds = 1; [ValidatePrototypeId] @@ -47,6 +50,8 @@ public override void Initialize() SubscribeLocalEvent(OnTerminating); SetupPrototypes(); + + _query = GetEntityQuery(); } private void OnPrototypeReload(PrototypesReloadedEventArgs obj) @@ -66,13 +71,7 @@ private void SetupPrototypes() private void OnAirtightChanged(ref AirtightChanged ev) { - var neighbors = GetSpreadableNeighbors(ev.Entity, ev.Airtight, ev.Position); - - foreach (var neighbor in neighbors) - { - if (!TerminatingOrDeleted(neighbor)) - EnsureComp(neighbor); - } + ActivateSpreadableNeighbors(ev.Entity, ev.Position); } private void OnGridInit(GridInitializeEvent ev) @@ -82,13 +81,7 @@ private void OnGridInit(GridInitializeEvent ev) private void OnTerminating(Entity entity, ref EntityTerminatingEvent args) { - var neighbors = GetSpreadableNeighbors(entity); - - foreach (var neighbor in neighbors) - { - if (!TerminatingOrDeleted(neighbor)) - EnsureComp(neighbor); - } + ActivateSpreadableNeighbors(entity); } /// @@ -254,8 +247,7 @@ public void GetNeighbors(EntityUid uid, TransformComponent comp, ProtoId - /// Given an entity, this returns a list of all adjacent entities with a . + /// This function activates all spreaders that are adjacent to a given entity. This also activates other spreaders + /// on the same tile as the current entity (for thin airtight entities like windoors). /// - public List GetSpreadableNeighbors(EntityUid uid, AirtightComponent? comp = null, - (EntityUid Grid, Vector2i Tile)? position = null) + public void ActivateSpreadableNeighbors(EntityUid uid, (EntityUid Grid, Vector2i Tile)? position = null) { - Resolve(uid, ref comp, false); - var neighbors = new List(); - Vector2i tile; EntityUid ent; MapGridComponent? grid; @@ -315,37 +303,40 @@ public List GetSpreadableNeighbors(EntityUid uid, AirtightComponent? { var transform = Transform(uid); if (!TryComp(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value)) - return neighbors; + return; + tile = _map.TileIndicesFor(transform.GridUid.Value, grid, transform.Coordinates); ent = transform.GridUid.Value; } else { if (!TryComp(position.Value.Grid, out grid)) - return neighbors; - tile = position.Value.Tile; - ent = position.Value.Grid; + return; + (ent, tile) = position.Value; } - var spreaderQuery = GetEntityQuery(); + var anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, tile); + while (anchored.MoveNext(out var entity)) + { + if (entity == ent) + continue; + DebugTools.Assert(Transform(entity.Value).Anchored); + if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value)) + EnsureComp(entity.Value); + } for (var i = 0; i < Atmospherics.Directions; i++) { var direction = (AtmosDirection) (1 << i); - if (comp != null && !comp.AirBlockedDirection.IsFlagSet(direction)) - continue; + var adjacentTile = SharedMapSystem.GetDirection(tile, direction.ToDirection()); + anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, adjacentTile); - var directionEnumerator = - _map.GetAnchoredEntitiesEnumerator(ent, grid, SharedMapSystem.GetDirection(tile, direction.ToDirection())); - - while (directionEnumerator.MoveNext(out var entity)) + while (anchored.MoveNext(out var entity)) { DebugTools.Assert(Transform(entity.Value).Anchored); - if (spreaderQuery.HasComponent(entity) && !TerminatingOrDeleted(entity.Value)) - neighbors.Add(entity.Value); + if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value)) + EnsureComp(entity.Value); } } - - return neighbors; } } diff --git a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs index 192a620c9fc..ad56479b379 100644 --- a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs +++ b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs @@ -68,9 +68,9 @@ protected override void ActiveTick(EntityUid uid, MeteorSwarmRuleComponent compo var spawnPosition = new MapCoordinates(center + offset, mapId); var meteor = Spawn("MeteorLarge", spawnPosition); var physics = EntityManager.GetComponent(meteor); - _physics.SetBodyStatus(physics, BodyStatus.InAir); - _physics.SetLinearDamping(physics, 0f); - _physics.SetAngularDamping(physics, 0f); + _physics.SetBodyStatus(meteor, physics, BodyStatus.InAir); + _physics.SetLinearDamping(meteor, physics, 0f); + _physics.SetAngularDamping(meteor, physics, 0f); _physics.ApplyLinearImpulse(meteor, -offset.Normalized() * component.MeteorVelocity * physics.Mass, body: physics); _physics.ApplyAngularImpulse( meteor, diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index daadd4b518b..53128aade31 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -16,7 +16,7 @@ using Content.Server.Roles; using Content.Server.Speech.Components; using Content.Server.Temperature.Components; -using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Abilities; using Content.Shared.CombatMode; using Content.Shared.CombatMode.Pacification; using Content.Shared.Damage; @@ -59,9 +59,7 @@ public sealed partial class ZombieSystem [Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; - [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly ActionsSystem _actions = default!; // DeltaV - No psionic zombies /// /// Handles an entity turning into a zombie when they die or go into crit diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 7460e08e46f..6a8587ca239 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -8,11 +8,6 @@ namespace Content.Shared.Atmos /// public static class Atmospherics { - static Atmospherics() - { - AdjustedNumberOfGases = MathHelper.NextMultipleOf(TotalNumberOfGases, 4); - } - #region ATMOS /// /// The universal gas constant, in kPa*L/(K*mol) @@ -183,7 +178,7 @@ static Atmospherics() /// This is the actual length of the gases arrays in mixtures. /// Set to the closest multiple of 4 relative to for SIMD reasons. /// - public static readonly int AdjustedNumberOfGases; + public const int AdjustedNumberOfGases = ((TotalNumberOfGases + 3) / 4) * 4; /// /// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope) diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index eb0079eb358..f468724db33 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -66,7 +66,10 @@ public static Vector2i GetGasChunkIndices(Vector2i indices) [Serializable, NetSerializable] public readonly struct GasOverlayData : IEquatable { + [ViewVariables] public readonly byte FireState; + + [ViewVariables] public readonly byte[] Opacity; // TODO change fire color based on temps diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index b58bdf83e49..8172947a039 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -358,7 +358,7 @@ public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid if (TryComp(buckleUid, out var physics)) { - _physics.ResetDynamics(physics); + _physics.ResetDynamics(buckleUid, physics); } if (!buckleComp.PullStrap && TryComp(strapUid, out var toPullable)) diff --git a/Content.Shared/Maps/ContentTileDefinition.cs b/Content.Shared/Maps/ContentTileDefinition.cs index ce7980509eb..32f5db0e821 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -81,7 +81,11 @@ public sealed partial class ContentTileDefinition : IPrototype, IInheritingProto [DataField("itemDrop", customTypeSerializer:typeof(PrototypeIdSerializer))] public string ItemDropPrototypeName { get; private set; } = "FloorTileItemSteel"; - [DataField("isSpace")] public bool IsSpace { get; private set; } + // TODO rename data-field in yaml + /// + /// Whether or not the tile is exposed to the map's atmosphere. + /// + [DataField("isSpace")] public bool MapAtmosphere { get; private set; } /// /// Friction override for mob mover in diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index a87b8c97d15..58a5d133b55 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -11,22 +11,6 @@ namespace Content.Shared.Maps // That, or make the interface arguments non-optional so people stop failing to pass them in. public static class TurfHelpers { - /// - /// Attempts to get the turf at map indices with grid id or null if no such turf is found. - /// - public static TileRef GetTileRef(this Vector2i vector2i, EntityUid gridId, IMapManager? mapManager = null) - { - mapManager ??= IoCManager.Resolve(); - - if (!mapManager.TryGetGrid(gridId, out var grid)) - return default; - - if (!grid.TryGetTileRef(vector2i, out var tile)) - return default; - - return tile; - } - /// /// Attempts to get the turf at a certain coordinates or null if no such turf is found. /// @@ -67,7 +51,7 @@ public static ContentTileDefinition GetContentTileDefinition(this Tile tile, ITi /// public static bool IsSpace(this Tile tile, ITileDefinitionManager? tileDefinitionManager = null) { - return tile.GetContentTileDefinition(tileDefinitionManager).IsSpace; + return tile.GetContentTileDefinition(tileDefinitionManager).MapAtmosphere; } /// @@ -115,15 +99,6 @@ public static IEnumerable GetEntitiesInTile(this EntityCoordinates co return GetEntitiesInTile(turf.Value, flags, lookupSystem); } - /// - /// Helper that returns all entities in a turf. - /// - [Obsolete("Use the lookup system")] - public static IEnumerable GetEntitiesInTile(this Vector2i indices, EntityUid gridId, LookupFlags flags = LookupFlags.Static, EntityLookupSystem? lookupSystem = null) - { - return GetEntitiesInTile(indices.GetTileRef(gridId), flags, lookupSystem); - } - /// /// Checks if a turf has something dense on it. /// diff --git a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs index abe12b79d1a..8c42511f846 100644 --- a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs +++ b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs @@ -92,7 +92,7 @@ private void SetupUser(EntityUid user, EntityUid jetpackUid) _mover.SetRelay(user, jetpackUid); if (TryComp(user, out var physics)) - _physics.SetBodyStatus(physics, BodyStatus.InAir); + _physics.SetBodyStatus(user, physics, BodyStatus.InAir); userComp.Jetpack = jetpackUid; } @@ -103,7 +103,7 @@ private void RemoveUser(EntityUid uid) return; if (TryComp(uid, out var physics)) - _physics.SetBodyStatus(physics, BodyStatus.OnGround); + _physics.SetBodyStatus(uid, physics, BodyStatus.OnGround); RemComp(uid); } diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs deleted file mode 100644 index 7d611c63dac..00000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class MassSleepPowerComponent : Component - { - public float Radius = 1.25f; - [DataField("massSleepActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MassSleepActionId = "ActionMassSleep"; - - [DataField("massSleepActionEntity")] - public EntityUid? MassSleepActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs deleted file mode 100644 index e36a3c70e8a..00000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Content.Shared.Actions; -using Content.Shared.Bed.Sleep; -using Content.Shared.Magic.Events; -using Content.Shared.Damage; -using Content.Shared.Mobs.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; -using Content.Shared.Actions.Events; - -namespace Content.Shared.Abilities.Psionics -{ - public sealed class MassSleepPowerSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnPowerUsed); - } - - private void OnInit(EntityUid uid, MassSleepPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MassSleepActionEntity, component.MassSleepActionId ); - _actions.TryGetActionData( component.MassSleepActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MassSleepActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.MassSleepActionEntity; - } - - private void OnShutdown(EntityUid uid, MassSleepPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MassSleepActionEntity); - } - - private void OnPowerUsed(EntityUid uid, MassSleepPowerComponent component, MassSleepPowerActionEvent args) - { - foreach (var entity in _lookup.GetEntitiesInRange(args.Target, component.Radius)) - { - if (HasComp(entity) && entity != uid && !HasComp(entity)) - { - if (TryComp(entity, out var damageable) && damageable.DamageContainerID == "Biological") - EnsureComp(entity); - } - } - _psionics.LogPowerUsed(uid, "mass sleep"); - args.Handled = true; - } - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs deleted file mode 100644 index c9d0130221a..00000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class MetapsionicPowerComponent : Component - { - [DataField("range")] - public float Range = 5f; - - public InstantActionComponent? MetapsionicPowerAction = null; - [DataField("metapsionicActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MetapsionicActionId = "ActionMetapsionic"; - - [DataField("metapsionicActionEntity")] - public EntityUid? MetapsionicActionEntity; - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs deleted file mode 100644 index 9d627cb42d8..00000000000 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Shared.Abilities.Psionics -{ - [RegisterComponent] - public sealed partial class TelegnosticProjectionComponent : Component - {} -} \ No newline at end of file diff --git a/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs deleted file mode 100644 index 6666ee48d6c..00000000000 --- a/Content.Shared/Nyanotrasen/Actions/Events/MassSleepPowerActionEvent.cs +++ /dev/null @@ -1,2 +0,0 @@ -namespace Content.Shared.Actions.Events; -public sealed partial class MassSleepPowerActionEvent : WorldTargetActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs index b28801efe74..b7c3c8ad2d6 100644 --- a/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs +++ b/Content.Shared/Nyanotrasen/Actions/Events/MetapsionicPowerActionEvent.cs @@ -1,2 +1,3 @@ namespace Content.Shared.Actions.Events; -public sealed partial class MetapsionicPowerActionEvent : InstantActionEvent {} +public sealed partial class WideMetapsionicPowerActionEvent : InstantActionEvent { } +public sealed partial class FocusedMetapsionicPowerActionEvent : EntityTargetActionEvent { } diff --git a/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs index 896ec0bb63d..4639aadd55b 100644 --- a/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs +++ b/Content.Shared/Nyanotrasen/Actions/Events/PyrokinesisPowerActionEvent.cs @@ -1,2 +1,4 @@ namespace Content.Shared.Actions.Events; -public sealed partial class PyrokinesisPowerActionEvent : EntityTargetActionEvent {} +public sealed partial class PyrokinesisPowerActionEvent : WorldTargetActionEvent {} + + diff --git a/Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs b/Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs new file mode 100644 index 00000000000..4435f475a44 --- /dev/null +++ b/Content.Shared/Nyanotrasen/Actions/Events/RegenerativeStasisPowerActionEvent.cs @@ -0,0 +1,2 @@ +namespace Content.Shared.Actions.Events; +public sealed partial class RegenerativeStasisPowerActionEvent : EntityTargetActionEvent {} diff --git a/Content.Shared/Nyanotrasen/Psionics/Events.cs b/Content.Shared/Nyanotrasen/Psionics/Events.cs deleted file mode 100644 index cf9a50c6e18..00000000000 --- a/Content.Shared/Nyanotrasen/Psionics/Events.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Robust.Shared.Serialization; -using Content.Shared.DoAfter; - -namespace Content.Shared.Psionics.Events -{ - [Serializable, NetSerializable] - public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent - { - [DataField("startedAt", required: true)] - public TimeSpan StartedAt; - - private PsionicRegenerationDoAfterEvent() - { - } - - public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) - { - StartedAt = startedAt; - } - - public override DoAfterEvent Clone() => this; - } - - [Serializable, NetSerializable] - public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent - { - } -} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/AcceptPsionicsEuiMessage.cs b/Content.Shared/Psionics/Abilities/AcceptPsionicsEuiMessage.cs similarity index 100% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/AcceptPsionicsEuiMessage.cs rename to Content.Shared/Psionics/Abilities/AcceptPsionicsEuiMessage.cs diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs b/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs similarity index 77% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs rename to Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs index ce86111fc4b..c3702880375 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs +++ b/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs @@ -1,6 +1,6 @@ using Content.Shared.Damage; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { /// /// Takes damage when dispelled. @@ -9,7 +9,7 @@ namespace Content.Shared.Abilities.Psionics public sealed partial class DamageOnDispelComponent : Component { [DataField("damage", required: true)] - public DamageSpecifier Damage = default!; + public DamageSpecifier Damage = default!; [DataField("variance")] public float Variance = 0.5f; diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs b/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs similarity index 79% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs rename to Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs index cd887866364..518a28b0967 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispelPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs @@ -1,20 +1,22 @@ -using Content.Shared.Actions; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class DispelPowerComponent : Component { [DataField("range")] public float Range = 10f; - + [DataField("dispelActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? DispelActionId = "ActionDispel"; [DataField("dispelActionEntity")] public EntityUid? DispelActionEntity; + + [DataField("dispelFeedback")] + public string DispelFeedback = "dispel-feedback"; } } diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs b/Content.Shared/Psionics/Abilities/Dispel/DispellableComponent.cs similarity index 69% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs rename to Content.Shared/Psionics/Abilities/Dispel/DispellableComponent.cs index 40352004187..4bb5ee653d2 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Dispel/DispellableComponent.cs +++ b/Content.Shared/Psionics/Abilities/Dispel/DispellableComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class DispellableComponent : Component diff --git a/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs new file mode 100644 index 00000000000..2fbfe18327e --- /dev/null +++ b/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Psionics.Abilities +{ + [RegisterComponent] + public sealed partial class MetapsionicPowerComponent : Component + { + [DataField("doAfter")] + public DoAfterId? DoAfter; + + [DataField("useDelay")] + public float UseDelay = 8f; + [DataField("soundUse")] + + public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Nyanotrasen/heartbeat_fast.ogg"); + + [DataField("range")] + public float Range = 5f; + + [DataField("actionWideMetapsionic", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionWideMetapsionic = "ActionWideMetapsionic"; + + [DataField("actionWideMetapsionicEntity")] + public EntityUid? ActionWideMetapsionicEntity; + + [DataField("actionFocusedMetapsionic", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ActionFocusedMetapsionic = "ActionFocusedMetapsionic"; + + [DataField("actionFocusedMetapsionicEntity")] + public EntityUid? ActionFocusedMetapsionicEntity; + + [DataField("metapsionicFeedback")] + public string MetapsionicFeedback = "metapsionic-feedback"; + } +} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs b/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs similarity index 76% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs rename to Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs index 6a3fc811c89..94b73c41e38 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class MindSwapPowerComponent : Component @@ -12,5 +12,8 @@ public sealed partial class MindSwapPowerComponent : Component [DataField("mindSwapActionEntity")] public EntityUid? MindSwapActionEntity; + + [DataField("mindSwapFeedback")] + public string MindSwapFeedback = "mind-swap-feedback"; } } diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs b/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs similarity index 77% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs rename to Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs index 0e91894b1dc..997db65e1b1 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class NoosphericZapPowerComponent : Component @@ -13,5 +13,8 @@ public sealed partial class NoosphericZapPowerComponent : Component [DataField("noosphericZapActionEntity")] public EntityUid? NoosphericZapActionEntity; + + [DataField("noosphericZapFeedback")] + public string NoosphericZapFeedback = "noospheric-zap-feedback"; } } diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs similarity index 71% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs rename to Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs index 3e198aa9303..d9c36f5b22a 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class PsionicInvisibilityPowerComponent : Component @@ -12,5 +12,11 @@ public sealed partial class PsionicInvisibilityPowerComponent : Component [DataField("psionicInvisibilityActionEntity")] public EntityUid? PsionicInvisibilityActionEntity; + + [DataField("InvisibilityFeedback")] + public string InvisibilityFeedback = "invisibility-feedback"; + + [DataField("UseTimer")] + public float UseTimer = 30f; } } diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs similarity index 94% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs rename to Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs index 9037b8bcdfe..2a9dd7642ba 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs @@ -1,6 +1,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics + +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class PsionicInvisibilityUsedComponent : Component diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs b/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs similarity index 76% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs rename to Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs index 4a62e84d191..3184bf7de5b 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs @@ -3,7 +3,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class PsionicRegenerationPowerComponent : Component @@ -12,7 +12,7 @@ public sealed partial class PsionicRegenerationPowerComponent : Component public DoAfterId? DoAfter; [DataField("essence")] - public float EssenceAmount = 20; + public float EssenceAmount = 10; [DataField("useDelay")] public float UseDelay = 8f; @@ -26,6 +26,12 @@ public sealed partial class PsionicRegenerationPowerComponent : Component [DataField("psionicRegenerationActionEntity")] public EntityUid? PsionicRegenerationActionEntity; + + [DataField("regenerationFeedback")] + public string RegenerationFeedback = "regeneration-feedback"; + + [DataField("selfRevive")] + public bool SelfRevive { get; set; } = false; } } diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs b/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs similarity index 79% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs rename to Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs index 28425afdb4c..1f88741b9a9 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class PyrokinesisPowerComponent : Component @@ -14,5 +14,8 @@ public sealed partial class PyrokinesisPowerComponent : Component [DataField("pyrokinesisActionEntity")] public EntityUid? PyrokinesisActionEntity; + + [DataField("pyrokinesisFeedback")] + public string PyrokinesisFeedback = "pyrokinesis-feedback"; } } diff --git a/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs b/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs new file mode 100644 index 00000000000..27a0903e224 --- /dev/null +++ b/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Psionics.Abilities +{ + [RegisterComponent] + public sealed partial class RegenerativeStasisPowerComponent : Component + { + [DataField("regenerativeStasisActionId", + customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? RegenerativeStasisActionId = "ActionRegenerativeStasis"; + + [DataField("regenerativeStasisActionEntity")] + public EntityUid? RegenerativeStasisActionEntity; + + [DataField("regenerativeStasisFeedback")] + public string RegenerativeStasisFeedback = "regenerative-stasis-feedback"; + } +} diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs b/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs similarity index 73% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs rename to Content.Shared/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs index 51958822a41..f1a71332b18 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosisPowerComponent.cs @@ -3,7 +3,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class TelegnosisPowerComponent : Component @@ -19,5 +19,11 @@ public sealed partial class TelegnosisPowerComponent : Component [DataField("telegnosisActionEntity")] public EntityUid? TelegnosisActionEntity; + + [DataField("telegnosisFeedback")] + public string TelegnosisFeedback = "telegnosis-feedback"; + public EntityUid OriginalEntity = default!; + public EntityUid ProjectionUid = default!; + public bool IsProjecting = false; } -} \ No newline at end of file +} diff --git a/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs b/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs new file mode 100644 index 00000000000..bc18ff9f3c2 --- /dev/null +++ b/Content.Shared/Psionics/Abilities/Telegnosis/TelegnosticProjectionComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Psionics.Abilities +{ + [RegisterComponent] + public sealed partial class TelegnosticProjectionComponent : Component + { + public EntityUid OriginalEntity = default!; + } +} diff --git a/Content.Shared/Psionics/Events.cs b/Content.Shared/Psionics/Events.cs new file mode 100644 index 00000000000..45a00b5f048 --- /dev/null +++ b/Content.Shared/Psionics/Events.cs @@ -0,0 +1,64 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Psionics.Events +{ + [Serializable, NetSerializable] + public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent + { + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + private PsionicRegenerationDoAfterEvent() + { + } + + public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) + { + StartedAt = startedAt; + } + + public override DoAfterEvent Clone() => this; + } + + [Serializable, NetSerializable] + public sealed partial class PsionicInvisibilityTimerEvent : DoAfterEvent + { + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + private PsionicInvisibilityTimerEvent() + { + } + + public PsionicInvisibilityTimerEvent(TimeSpan startedAt) + { + StartedAt = startedAt; + } + + public override DoAfterEvent Clone() => this; + } + + [Serializable, NetSerializable] + public sealed partial class FocusedMetapsionicDoAfterEvent : DoAfterEvent + { + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + private FocusedMetapsionicDoAfterEvent() + { + } + + public FocusedMetapsionicDoAfterEvent(TimeSpan startedAt) + { + StartedAt = startedAt; + } + + public override DoAfterEvent Clone() => this; + } + + [Serializable, NetSerializable] + public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent + { + } +} diff --git a/Content.Shared/Nyanotrasen/Psionics/Glimmer/GlimmerSystem.cs b/Content.Shared/Psionics/Glimmer/GlimmerSystem.cs similarity index 98% rename from Content.Shared/Nyanotrasen/Psionics/Glimmer/GlimmerSystem.cs rename to Content.Shared/Psionics/Glimmer/GlimmerSystem.cs index 31af85bbb51..8be02f936a9 100644 --- a/Content.Shared/Nyanotrasen/Psionics/Glimmer/GlimmerSystem.cs +++ b/Content.Shared/Psionics/Glimmer/GlimmerSystem.cs @@ -40,7 +40,7 @@ public GlimmerTier GetGlimmerTier(int? glimmer = null) if (glimmer == null) glimmer = Glimmer; - return (glimmer) switch + return glimmer switch { <= 49 => GlimmerTier.Minimal, >= 50 and <= 99 => GlimmerTier.Low, diff --git a/Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs b/Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs similarity index 100% rename from Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs rename to Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveComponent.cs diff --git a/Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs b/Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs similarity index 100% rename from Content.Shared/Nyanotrasen/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs rename to Content.Shared/Psionics/Glimmer/SharedGlimmerReactiveVisuals.cs diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs b/Content.Shared/Psionics/Items/ClothingGrantPsionicPowerComponent.cs similarity index 84% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs rename to Content.Shared/Psionics/Items/ClothingGrantPsionicPowerComponent.cs index 4cbb05c8395..f09efc3064c 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/ClothingGrantPsionicPowerComponent.cs +++ b/Content.Shared/Psionics/Items/ClothingGrantPsionicPowerComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class ClothingGrantPsionicPowerComponent : Component diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCageComponent.cs b/Content.Shared/Psionics/Items/HeadCageComponent.cs similarity index 96% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCageComponent.cs rename to Content.Shared/Psionics/Items/HeadCageComponent.cs index acaa832860f..c03241e47c7 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCageComponent.cs +++ b/Content.Shared/Psionics/Items/HeadCageComponent.cs @@ -1,7 +1,7 @@ using System.Threading; using Robust.Shared.Audio; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class HeadCageComponent : Component diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCagedComponent.cs b/Content.Shared/Psionics/Items/HeadCagedComponent.cs similarity index 81% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCagedComponent.cs rename to Content.Shared/Psionics/Items/HeadCagedComponent.cs index f8af46b8878..0f826f7a05e 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/HeadCagedComponent.cs +++ b/Content.Shared/Psionics/Items/HeadCagedComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] /// diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs b/Content.Shared/Psionics/Items/PsionicItemsSystem.cs similarity index 98% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs rename to Content.Shared/Psionics/Items/PsionicItemsSystem.cs index f88acf61f3c..950353c5dfb 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/PsionicItemsSystem.cs +++ b/Content.Shared/Psionics/Items/PsionicItemsSystem.cs @@ -2,7 +2,7 @@ using Content.Shared.Clothing.Components; using Content.Shared.StatusEffect; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { public sealed class PsionicItemsSystem : EntitySystem { diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs b/Content.Shared/Psionics/Items/TinfoilHatComponent.cs similarity index 90% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs rename to Content.Shared/Psionics/Items/TinfoilHatComponent.cs index 5086b9f4977..6ef7bdc823b 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Items/TinfoilHatComponent.cs +++ b/Content.Shared/Psionics/Items/TinfoilHatComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class TinfoilHatComponent : Component diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs similarity index 51% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs rename to Content.Shared/Psionics/PsionicComponent.cs index 9091e03cfc3..9a06e54cb31 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -1,7 +1,6 @@ -using Content.Shared.Actions; using Robust.Shared.GameStates; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent, NetworkedComponent] public sealed partial class PsionicComponent : Component @@ -15,6 +14,17 @@ public sealed partial class PsionicComponent : Component public bool Removable = true; [DataField("activePowers")] - public HashSet ActivePowers = new(); + public List ActivePowers = new(); + + [DataField("psychicFeedback")] + public List PsychicFeedback = new(); + + [DataField("amplification")] + public float Amplification = 0.1f; + + [DataField("dampening")] + public float Dampening = 0.1f; + public bool Telepath = false; + public bool InnatePsiChecked = false; } } diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs b/Content.Shared/Psionics/PsionicInsulationComponent.cs similarity index 82% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs rename to Content.Shared/Psionics/PsionicInsulationComponent.cs index 12370da5ae4..2ab054b1f8f 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicInsulationComponent.cs +++ b/Content.Shared/Psionics/PsionicInsulationComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { [RegisterComponent] public sealed partial class PsionicInsulationComponent : Component diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs b/Content.Shared/Psionics/PsionicsDisabledComponent.cs similarity index 84% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs rename to Content.Shared/Psionics/PsionicsDisabledComponent.cs index 28e7157a9d2..00cf5506523 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/PsionicsDisabledComponent.cs +++ b/Content.Shared/Psionics/PsionicsDisabledComponent.cs @@ -1,6 +1,6 @@ using Robust.Shared.GameStates; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { /// /// Only use this for the status effect, please. diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs similarity index 97% rename from Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs rename to Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs index 2739d5ba31a..603c5188a52 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/SharedPsionicAbilitiesSystem.cs +++ b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs @@ -7,7 +7,7 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Psionics.Abilities { public sealed class SharedPsionicAbilitiesSystem : EntitySystem { @@ -73,7 +73,7 @@ public void SetPsionicsThroughEligibility(EntityUid uid) if (actionData == null) return; - _actions.SetEnabled(actionData.Owner, IsEligibleForPsionics(uid)); + _actions.SetEnabled(uid, IsEligibleForPsionics(uid)); } private bool IsEligibleForPsionics(EntityUid uid) diff --git a/Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs b/Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs new file mode 100644 index 00000000000..5c89f39354c --- /dev/null +++ b/Content.Shared/Psionics/SharedPsionicSystem.Insulated.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Psionics +{ + public readonly record struct PsionicInsulationEvent; +} diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index 54294318315..01682863389 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -160,7 +160,7 @@ public void TryThrow(EntityUid uid, } else { - _physics.SetBodyStatus(physics, BodyStatus.InAir); + _physics.SetBodyStatus(uid, physics, BodyStatus.InAir); } if (user == null) @@ -176,10 +176,10 @@ public void TryThrow(EntityUid uid, { var msg = new ThrowPushbackAttemptEvent(); RaiseLocalEvent(uid, msg); - const float MassLimit = 5f; + const float massLimit = 5f; if (!msg.Cancelled) - _physics.ApplyLinearImpulse(user.Value, -impulseVector / physics.Mass * pushbackRatio * MathF.Min(MassLimit, physics.Mass), body: userPhysics); + _physics.ApplyLinearImpulse(user.Value, -impulseVector / physics.Mass * pushbackRatio * MathF.Min(massLimit, physics.Mass), body: userPhysics); } } } diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index b3b5bcf7870..cc50094e3dd 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -93,7 +93,7 @@ public void StopThrow(EntityUid uid, ThrownItemComponent thrownItemComponent) { if (TryComp(uid, out var physics)) { - _physics.SetBodyStatus(physics, BodyStatus.OnGround); + _physics.SetBodyStatus(uid, physics, BodyStatus.OnGround); if (physics.Awake) _broadphase.RegenerateContacts(uid, physics); diff --git a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs index e840bd1ddd5..e0696022606 100644 --- a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs +++ b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs @@ -74,7 +74,7 @@ private void OnThrownStartup(Entity ent, ref ComponentStar comp.PreviousStatus = body.BodyStatus; comp.ThrownEndTime = _timing.CurTime + TimeSpan.FromSeconds(comp.Lifetime); comp.MinLifetimeTime = _timing.CurTime + TimeSpan.FromSeconds(comp.MinLifetime); - _physics.SetBodyStatus(body, BodyStatus.InAir); + _physics.SetBodyStatus(ent, body, BodyStatus.InAir); _physics.SetLinearVelocity(ent, Vector2.Zero, body: body); _physics.ApplyLinearImpulse(ent, comp.Velocity * body.Mass, body: body); Dirty(ent, ent.Comp); @@ -83,7 +83,7 @@ private void OnThrownStartup(Entity ent, ref ComponentStar private void OnThrownShutdown(Entity ent, ref ComponentShutdown args) { if (TryComp(ent, out var body)) - _physics.SetBodyStatus(body,ent.Comp.PreviousStatus); + _physics.SetBodyStatus(ent, body, ent.Comp.PreviousStatus); var ev = new MeleeThrowOnHitEndEvent(); RaiseLocalEvent(ent, ref ev); } diff --git a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs index 177cb310d18..3a950bcd29e 100644 --- a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs +++ b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs @@ -207,12 +207,12 @@ protected virtual void StartTether(EntityUid gunUid, BaseForceGunComponent compo TransformSystem.Unanchor(target, targetXform); component.Tethered = target; var tethered = EnsureComp(target); - _physics.SetBodyStatus(targetPhysics, BodyStatus.InAir, false); + _physics.SetBodyStatus(target, targetPhysics, BodyStatus.InAir, false); _physics.SetSleepingAllowed(target, targetPhysics, false); tethered.Tetherer = gunUid; tethered.OriginalAngularDamping = targetPhysics.AngularDamping; - _physics.SetAngularDamping(targetPhysics, 0f); - _physics.SetLinearDamping(targetPhysics, 0f); + _physics.SetAngularDamping(target, targetPhysics, 0f); + _physics.SetLinearDamping(target, targetPhysics, 0f); _physics.SetAngularVelocity(target, SpinVelocity, body: targetPhysics); _physics.WakeBody(target, body: targetPhysics); var thrown = EnsureComp(component.Tethered.Value); @@ -264,9 +264,9 @@ protected virtual void StopTether(EntityUid gunUid, BaseForceGunComponent compon _thrown.StopThrow(component.Tethered.Value, thrown); } - _physics.SetBodyStatus(targetPhysics, BodyStatus.OnGround); + _physics.SetBodyStatus(component.Tethered.Value, targetPhysics, BodyStatus.OnGround); _physics.SetSleepingAllowed(component.Tethered.Value, targetPhysics, true); - _physics.SetAngularDamping(targetPhysics, Comp(component.Tethered.Value).OriginalAngularDamping); + _physics.SetAngularDamping(component.Tethered.Value, targetPhysics, Comp(component.Tethered.Value).OriginalAngularDamping); } if (!transfer) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 36b5bbd1927..d3aee5a48e9 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -388,7 +388,7 @@ public abstract void Shoot( public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocity, EntityUid gunUid, EntityUid? user = null, float speed = 20f) { var physics = EnsureComp(uid); - Physics.SetBodyStatus(physics, BodyStatus.InAir); + Physics.SetBodyStatus(uid, physics, BodyStatus.InAir); var targetMapVelocity = gunVelocity + direction.Normalized() * speed; var currentMapVelocity = Physics.GetMapLinearVelocity(uid, physics); diff --git a/Resources/Audio/Nyanotrasen/heartbeat_fast.ogg b/Resources/Audio/Nyanotrasen/heartbeat_fast.ogg new file mode 100644 index 0000000000000000000000000000000000000000..85a034d9bd0017dafea7df4128235c815babac0c GIT binary patch literal 39983 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 literal 0 HcmV?d00001 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 954f86aa044..ba49523cfde 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -4080,3 +4080,17 @@ Entries: you join when someone leaves. id: 6110 time: '2024-05-13T04:58:39.0000000+00:00' +- author: LovelyLophi + changes: + - type: Add + message: Added a number of new corporate coats to the Clothesmate. + id: 6111 + time: '2024-05-22T03:35:11.0000000+00:00' +- author: VMSolidus + changes: + - type: Tweak + message: >- + Syndicate Listening Posts can now appear any time in a round, and can + appear on low-pop servers. + id: 6112 + time: '2024-05-28T23:44:39.0000000+00:00' diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index 3aea674fd94..db58663a2b2 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, casperr04, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, clement-or, Clyybber, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, KaiShibaa, kalane15, kalanosh, KEEYNy, Keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Kmc2000, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, LightVillet, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LudwigVonChesterfield, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, M3739, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, matthst, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, SignalWalker, SimpleStation14, Simyon264, SirDragooon, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, Slava0135, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, Stealthbomber16, stellar-novas, StrawberryMoses, Subversionary, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, tom-leys, tomasalves8, Tomeno, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, waylon531, weaversam8, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem +0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, casperr04, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, clement-or, Clyybber, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, KaiShibaa, kalane15, kalanosh, KEEYNy, Keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Kmc2000, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, LightVillet, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LovelyLophi, LudwigVonChesterfield, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, M3739, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, matthst, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, SignalWalker, SimpleStation14, Simyon264, SirDragooon, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, Slava0135, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, Stealthbomber16, stellar-novas, StrawberryMoses, Subversionary, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, tom-leys, tomasalves8, Tomeno, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, waylon531, weaversam8, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem diff --git a/Resources/Locale/en-US/atmos/commands.ftl b/Resources/Locale/en-US/atmos/commands.ftl new file mode 100644 index 00000000000..692908d42ad --- /dev/null +++ b/Resources/Locale/en-US/atmos/commands.ftl @@ -0,0 +1,8 @@ +cmd-set-map-atmos-desc = Sets a map's atmosphere +cmd-set-map-atmos-help = setmapatmos [ [moles...]] +cmd-set-map-atmos-removed = Atmosphere removed from map {$map} +cmd-set-map-atmos-updated = Atmosphere set for map {$map} +cmd-set-map-atmos-hint-map = +cmd-set-map-atmos-hint-space = +cmd-set-map-atmos-hint-temp = (float) +cmd-set-map-atmos-hint-gas = <{$gas} moles> (float) diff --git a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl index 91ae21233a3..5b368e822f1 100644 --- a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl +++ b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl @@ -3,8 +3,11 @@ cage-resist-third-person = {CAPITALIZE(THE($user))} starts removing {POSS-ADJ($u cage-uncage-verb = Uncage -action-name-metapsionic = Metapsionic Pulse -action-description-metapsionic = Send a mental pulse through the area to see if there are any psychics nearby. +action-name-widemetapsionic = Wide Metapsionic Pulse +action-description-widemetapsionic = Send a mental pulse through the area to see if there are any psychics nearby. + +action-name-focusedmetapsionic = Focused Metapsionic Pulse +action-description-focusedmetapsionic = Probe an entity at close range to glean metaphorical information about any powers they may have metapsionic-pulse-success = You detect psychic presence nearby. metapsionic-pulse-failure = You don't detect any psychic presence nearby. @@ -13,8 +16,8 @@ metapsionic-pulse-power = You detect that {$power} was used nearby. action-name-dispel = Dispel action-description-dispel = Dispel summoned entities such as familiars or forcewalls. -action-name-mass-sleep = Mass Sleep -action-description-mass-sleep = Put targets in a small area to sleep. +action-name-regenerative-stasis = Regenerative Stasis +action-description-regenerative-stasis = Puts the target into a brief stasis, during which time their wounds rapidly heal. accept-psionics-window-title = Psionic! accept-psionics-window-prompt-text-part = You rolled a psionic power! @@ -63,11 +66,14 @@ action-name-noospheric-zap = Noospheric Zap action-description-noospheric-zap = Shocks the conciousness of the target and leaves them stunned and stuttering. action-name-pyrokinesis = Pyrokinesis -action-description-pyrokinesis = Light a flammable target on fire. -pyrokinesis-power-used = A wisp of flame engulfs {THE($target)}, igniting {OBJECT($target)}! +action-description-pyrokinesis = Hurl a small gateway to the plane of Gehenna at your target. action-name-psychokinesis = Psychokinesis action-description-psychokinesis = Bend the fabric of space to instantly move across it. action-name-rf-sensitivity = Toggle RF Sensitivity action-desc-rf-sensitivity = Toggle your ability to interpret radio waves on and off. + +trait-latent-psychic-desc = Your mind and soul are open to the noosphere, allowing for a limited use of Telepathy. + Thus, you are eligible for potentially receiving psychic powers. + It is possible that you may be hunted by otherworldly forces, so consider keeping your powers a secret. diff --git a/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl b/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl new file mode 100644 index 00000000000..26d2acb87cd --- /dev/null +++ b/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl @@ -0,0 +1,21 @@ +# Feedback messages for Focused Metapsionic Pulse +metapulse-self = I AM. +no-powers = {CAPITALIZE($entity)} will never awaken from the dream in this life +psychic-potential = {CAPITALIZE($entity)} has a slim chance of awakening from the dream +dispel-feedback = {CAPITALIZE($entity)} is a mighty stone, standing against the currents of fate +metapsionic-feedback = {CAPITALIZE($entity)} gazes back upon thee +mind-swap-feedback = {CAPITALIZE($entity)}'s vessel seems fit for other souls +mindswapped-feedback = Cursed flesh! {CAPITALIZE($entity)} dwells within the wrong vessel! +noospheric-zap-feedback = {CAPITALIZE($entity)}'s soul writhes with thunder from beyond the veil +pyrokinesis-feedback = The Secret of Fire dwells within {CAPITALIZE($entity)} +invisibility-feedback = {CAPITALIZE($entity)}'s wyrd seeks to hide from thine gaze +telegnosis-feedback = {CAPITALIZE($entity)}'s soul travels across bridges composed of dreamlight +sophic-grammateus-feedback = SEEKER, YOU NEED ONLY ASK FOR MY WISDOM. +oracle-feedback = WHY DO YOU BOTHER ME SEEKER? HAVE I NOT MADE MY DESIRES CLEAR? +metempsychotic-machine-feedback = The sea of fate flows through this machine +ifrit-feedback = A spirit of Gehenna, bound by the will of a powerful psychic + +# Power PVS Messages +focused-metapsionic-pulse-begin = The air around {CAPITALIZE($entity)} begins to shimmer faintly +psionic-regeneration-self-revive = {CAPITALIZE($entity)} begins to visibly regenerate +mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml index 2165fb4e585..d0ae444bf77 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml @@ -33,7 +33,7 @@ sprite: Objects/Specific/Service/vending_machine_restock.rsi state: base product: CrateVendingMachineRestockClothesFilled - cost: 7245 + cost: 8050 category: cargoproduct-category-name-service group: market diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml index 1dbc412a9c8..04cc2e3e19d 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml @@ -85,6 +85,11 @@ ClothingOuterCoatLettermanRed: 2 # Nyano - Clothing addition ClothingOuterDenimJacket: 2 # DeltaV - Clothing addition ClothingOuterCorporateJacket: 2 # DeltaV - Clothing addition + ClothingOuterCsCorporateJacket: 2 # Einstein Engines - Clothing addition + ClothingOuterEECorporateJacket: 2 # Einstein Engines - Clothing addition + ClothingOuterHICorporateJacket: 2 # Einstein Engines - Clothing addition + ClothingOuterHMCorporateJacket: 2 # Einstein Engines - Clothing addition + ClothingOuterIdCorporateJacket: 2 # Einstein Engines - Clothing addition ClothingShoesBootsFishing: 2 # Nyano - Clothing addition ClothingHeadTinfoil: 2 # Nyano - Clothing addition ClothingHeadFishCap: 2 diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml index 771da36719f..4f255cad211 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml @@ -84,7 +84,10 @@ - type: PotentialPsionic - type: Psionic removable: false - # - type: PyrokinesisPower # Pending psionic rework + amplification: 4 + psychicFeedback: + - "ifrit-feedback" + - type: PyrokinesisPower - type: Grammar attributes: proper: true @@ -103,7 +106,7 @@ requirements: - !type:DepartmentTimeRequirement department: Epistemics - time: 14400 # DeltaV - 4 hours + time: 14400 - type: entity parent: WelderExperimental diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml index 1f4eb696c65..e2541def035 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/harpy.yml @@ -26,4 +26,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml index 06abe8c45fa..ea2357a5c06 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml @@ -24,7 +24,6 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic - type: Respirator damage: types: diff --git a/Resources/Prototypes/DeltaV/GameRules/events.yml b/Resources/Prototypes/DeltaV/GameRules/events.yml index 73b0ca6549c..9391756492b 100644 --- a/Resources/Prototypes/DeltaV/GameRules/events.yml +++ b/Resources/Prototypes/DeltaV/GameRules/events.yml @@ -52,9 +52,8 @@ noSpawn: true components: - type: StationEvent - earliestStart: 15 - weight: 5 - minimumPlayers: 25 + weight: 7.5 + minimumPlayers: 10 maxOccurrences: 1 duration: 1 - type: PirateRadioSpawnRule diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml index 4f0a0d0aafa..d45e8c3f3c2 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml @@ -541,3 +541,58 @@ Quantity: 20 - type: ToggleableClothing clothingPrototype: ClothingHeadHatHoodWinterWeb + +- type: entity + parent: ClothingOuterWinterCoat + id: ClothingOuterCsCorporateJacket + name: Cybersun Corporate Jacket + description: A cozy jacket with the Cybersun logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. + components: + - type: Sprite + sprite: Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi + - type: Clothing + sprite: Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi + +- type: entity + parent: ClothingOuterWinterCoat + id: ClothingOuterEECorporateJacket + name: Einstein Engines Corporate Jacket + description: A cozy jacket with the Einstein Engines logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. + components: + - type: Sprite + sprite: Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi + - type: Clothing + sprite: Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi + +- type: entity + parent: ClothingOuterWinterCoat + id: ClothingOuterHICorporateJacket + name: Hephaestus Industries Corporate Jacket + description: A cozy jacket with the Hephaestus Industries logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. + components: + - type: Sprite + sprite: Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi + - type: Clothing + sprite: Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi + +- type: entity + parent: ClothingOuterWinterCoat + id: ClothingOuterHMCorporateJacket + name: Hawkmoon Acquisitions Corporate Jacket + description: A cozy jacket with the Hawkmoon Acquisitions logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. + components: + - type: Sprite + sprite: Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi + - type: Clothing + sprite: Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi + +- type: entity + parent: ClothingOuterWinterCoat + id: ClothingOuterIdCorporateJacket + name: Interdyne Corporate Jacket + description: A cozy jacket with the Interdyne logo printed on the back. Merchandise rewarded to stations with a safety factor of uhh... seven. + components: + - type: Sprite + sprite: Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi + - type: Clothing + sprite: Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml index 5ebd43ddf48..d9dea3c18d9 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml @@ -11,4 +11,3 @@ damageRecovery: types: Asphyxiation: -0.5 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. diff --git a/Resources/Prototypes/Entities/Mobs/Player/diona.yml b/Resources/Prototypes/Entities/Mobs/Player/diona.yml index 28687c68bfc..dfd5e9a1be7 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/diona.yml @@ -11,7 +11,6 @@ damageRecovery: types: Asphyxiation: -1.0 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. # Reformed Diona - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml index fb84ad3650f..d1de65df012 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml @@ -3,5 +3,3 @@ name: Urist McHands The Dwarf parent: BaseMobDwarf id: MobDwarf - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 6197c82c021..9a7c2ee65ec 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -3,8 +3,6 @@ name: Urist McHands parent: BaseMobHuman id: MobHuman - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Syndie - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/moth.yml b/Resources/Prototypes/Entities/Mobs/Player/moth.yml index ffdb36d86bd..72feba958ab 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/moth.yml @@ -3,5 +3,3 @@ name: Urist McFluff parent: BaseMobMoth id: MobMoth - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml index 71d74222979..b9f265e0bcf 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml @@ -3,7 +3,5 @@ name: Urisst' Mzhand parent: BaseMobReptilian id: MobReptilian - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Weh diff --git a/Resources/Prototypes/Entities/Mobs/Player/slime.yml b/Resources/Prototypes/Entities/Mobs/Player/slime.yml index 79669a8fe2a..4e5974b3084 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/slime.yml @@ -2,5 +2,3 @@ save: false parent: BaseMobSlimePerson id: MobSlimePerson - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 7bf96efe2cc..900de77712e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -16,7 +16,6 @@ spawned: - id: FoodMeatHuman amount: 5 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml index e87fec22acc..b847416211d 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml @@ -17,8 +17,7 @@ reagents: - ReagentId: THC Quantity: 15 - - type: StealTarget - stealGroup: Cannabis + - type: entity name: dried cannabis leaves @@ -38,8 +37,6 @@ - type: Sprite sprite: Objects/Specific/Hydroponics/tobacco.rsi state: dried - - type: StealTarget - stealGroup: Cannabis - type: entity name: ground cannabis @@ -68,8 +65,7 @@ - Smokable - type: Item size: Tiny - - type: StealTarget - stealGroup: Cannabis + - type: entity name: tobacco leaves diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 8d195a25bea..e14f29746dc 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -177,7 +177,7 @@ - Screwdriver - Wirecutter - Multitool - - Welder + - WelderIndustrial # cargo modules - type: entity diff --git a/Resources/Prototypes/Nyanotrasen/Actions/types.yml b/Resources/Prototypes/Nyanotrasen/Actions/types.yml index e6e4bdc5a75..55dd48e5470 100644 --- a/Resources/Prototypes/Nyanotrasen/Actions/types.yml +++ b/Resources/Prototypes/Nyanotrasen/Actions/types.yml @@ -36,18 +36,18 @@ event: !type:DispelPowerActionEvent - type: entity - id: ActionMassSleep - name: action-name-mass-sleep - description: action-description-mass-sleep + id: ActionRegenerativeStasis + name: action-name-regenerative-stasis + description: action-description-regenerative-stasis noSpawn: true components: - - type: WorldTargetAction + - type: EntityTargetAction icon: Nyanotrasen/Interface/VerbIcons/mass_sleep.png useDelay: 60 checkCanAccess: false range: 8 itemIconStyle: BigAction - event: !type:MassSleepPowerActionEvent + event: !type:RegenerativeStasisPowerActionEvent - type: entity id: ActionMindSwap @@ -94,7 +94,7 @@ description: action-description-pyrokinesis noSpawn: true components: - - type: EntityTargetAction + - type: WorldTargetAction icon: Nyanotrasen/Interface/VerbIcons/pyrokinesis.png useDelay: 50 range: 6 @@ -103,15 +103,29 @@ event: !type:PyrokinesisPowerActionEvent - type: entity - id: ActionMetapsionic - name: action-name-metapsionic - description: action-description-metapsionic + id: ActionWideMetapsionic + name: action-name-widemetapsionic + description: action-description-widemetapsionic noSpawn: true components: - type: InstantAction icon: Nyanotrasen/Interface/VerbIcons/metapsionic.png useDelay: 45 - event: !type:MetapsionicPowerActionEvent + event: !type:WideMetapsionicPowerActionEvent + +- type: entity + id: ActionFocusedMetapsionic + name: action-name-focusedmetapsionic + description: action-description-focusedmetapsionic + noSpawn: true + components: + - type: EntityTargetAction + icon: Nyanotrasen/Interface/VerbIcons/metapsionic.png + useDelay: 45 + range: 3 + checkCanAccess: false + itemIconStyle: BigAction + event: !type:FocusedMetapsionicPowerActionEvent - type: entity id: ActionPsionicRegeneration diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml index 562b9c564ec..1166d8a29f5 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml @@ -32,4 +32,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml index db7936cc5b4..94ac8403adc 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml @@ -49,5 +49,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic - diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml index d8e791af1ed..d773cf87c76 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml @@ -22,4 +22,7 @@ NoMind: { state: pod_1 } Gore: { state: pod_1 } Idle: { state: pod_0 } + - type: PotentialPsionic - type: Psionic + psychicFeedback: + - "metempsychotic-machine-feedback" diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml index f7481abf1ed..58189e49cec 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/oracle.yml @@ -16,7 +16,10 @@ - type: Oracle - type: Speech speechSounds: Tenor + - type: PotentialPsionic - type: Psionic + psychicFeedback: + - "oracle-feedback" - type: SolutionContainerManager solutions: fountain: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml index ae85cd25e03..8e34a07ea5e 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml @@ -29,6 +29,8 @@ - Science - type: PotentialPsionic #this makes her easier to access for glimmer events, dw about it - type: Psionic + psychicFeedback: + - "sophic-grammateus-feedback" - type: Grammar attributes: gender: female diff --git a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml index 53910a54a92..e6e497003d5 100644 --- a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml +++ b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml @@ -9,29 +9,6 @@ stealGroup: AntiPsychicKnife owner: job-name-mantis -- type: entity - id: BecomePsionicObjective - parent: BaseTraitorObjective - name: Become psionic - description: We need you to acquire psionics and keep them until your mission is complete. - noSpawn: true - components: - - type: NotJobsRequirement - jobs: - - Mime - - ForensicMantis - - type: Objective - difficulty: 2.5 - #unique: false - icon: - sprite: Nyanotrasen/Icons/psi.rsi - state: psi - - type: ObjectiveBlacklistRequirement - blacklist: - components: - - BecomeGolemCondition - - type: BecomePsionicCondition - #- type: entity # id: BecomeGolemObjective # parent: BaseTraitorObjective diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index c3e682e02a9..15b2cdd4fa7 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -7,22 +7,24 @@ - !type:OverallPlaytimeRequirement time: 18000 - !type:DepartmentTimeRequirement - department: Epistemics # DeltaV - Epistemics Department replacing Science + department: Epistemics time: 3600 startingGear: ForensicMantisGear icon: "JobIconForensicMantis" supervisors: job-supervisors-rd - antagAdvantage: 5 # DeltaV - From 4 to 5 - canBeAntag: true # DeltaV - Mantis is no longer a Detective - # whitelistRequired: true + antagAdvantage: 5 + canBeAntag: true access: - Research - Maintenance - - Mantis # DeltaV - Psionic Mantis, see Resources/Prototypes/DeltaV/Access/epistemics.yml + - Mantis special: - !type:AddComponentSpecial components: + - type: PotentialPsionic - type: Psionic + amplification: 0.3 + dampening: 0.3 - type: MetapsionicPower - type: startingGear @@ -34,11 +36,10 @@ head: ClothingHeadHatFezMantis id: ForensicMantisPDA eyes: ClothingEyesGlassesSunglasses - ears: ClothingHeadsetScience # DeltaV - Mantis is part of Epistemics + ears: ClothingHeadsetScience gloves: ClothingHandsGlovesColorWhite outerClothing: ClothingOuterCoatMantis belt: ClothingBeltMantis - # pocket2: ForensicScanner # DeltaV - Mantis is no longer a Detective innerClothingSkirt: ClothingUniformSkirtMantis satchel: ClothingBackpackSatchelMantisFilled duffelbag: ClothingBackpackDuffelMantisFilled diff --git a/Resources/Prototypes/Nyanotrasen/Traits/psionics.yml b/Resources/Prototypes/Nyanotrasen/Traits/psionics.yml new file mode 100644 index 00000000000..5fef3427703 --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/Traits/psionics.yml @@ -0,0 +1,6 @@ +- type: trait + id: LatentPsychic + name: Latent Psychic + description: trait-latent-psychic-desc + components: + - type: PotentialPsionic diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml index f40b688fd18..ca1764e204c 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml @@ -5,6 +5,8 @@ DispelPower: 1 TelegnosisPower: 1 PsionicRegenerationPower: 1 - MassSleepPower: 0.3 -# PsionicInvisibilityPower: 0.15 + RegenerativeStasisPower: 0.3 + PsionicInvisibilityPower: 0.15 MindSwapPower: 0.15 + NoosphericZapPower: 0.15 + PyrokinesisPower: 0.15 diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index fba2c4cc172..d711e9a1b13 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -39,7 +39,6 @@ EscapeShuttleObjective: 1 # DieObjective: 0.05 # DeltaV - Disable the lrp objective aka murderbone justification #HijackShuttleObjective: 0.02 - BecomePsionicObjective: 1 # Nyanotrasen - Become Psionic objective, see Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml #BecomeGolemObjective: 0.5 # Nyanotrasen - Become a golem objective, see Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml - type: weightedRandom diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml index fb1e5e76fc0..006c061666b 100644 --- a/Resources/Prototypes/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml @@ -142,12 +142,7 @@ sprite: Objects/Misc/id_cards.rsi state: default -- type: stealTargetGroup - id: Cannabis - name: cannabis - sprite: - sprite: Objects/Specific/Hydroponics/cannabis.rsi - state: produce + - type: stealTargetGroup id: LAMP diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index 66258870d61..18154850973 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -142,20 +142,7 @@ - type: Objective difficulty: 0.7 -- type: entity - noSpawn: true - parent: BaseThiefStealCollectionObjective - id: CannabisStealCollectionObjective - components: - - type: NotJobRequirement - job: Botanist - - type: StealCondition - stealGroup: Cannabis - minCollectionSize: 20 - maxCollectionSize: 30 - verifyMapExistence: false - - type: Objective - difficulty: 0.5 + - type: entity noSpawn: true diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index 19cf1419111..ddb779669eb 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -3,13 +3,13 @@ name: job-name-rd description: job-description-rd playTimeTracker: JobResearchDirector - antagAdvantage: 6 # DeltaV - Reduced TC: Head of Staff + antagAdvantage: 6 requirements: - !type:DepartmentTimeRequirement - department: Epistemics # DeltaV - Epistemics Department replacing Science - time: 54000 # DeltaV - 15 hours + department: Epistemics + time: 54000 - !type:OverallPlaytimeRequirement - time: 72000 # DeltaV - 20 hours + time: 72000 weight: 10 startingGear: ResearchDirectorGear icon: "JobIconResearchDirector" @@ -21,20 +21,20 @@ - Command - Maintenance - ResearchDirector - - Mantis # DeltaV - Psionic Mantis, see Resources/Prototypes/DeltaV/Access/epistemics.yml - - Chapel # DeltaV - Chaplain is in Epistemics + - Mantis + - Chapel - Cryogenics - special: # Nyanotrasen - Mystagogue can use the Bible + special: - !type:AddComponentSpecial components: - - type: BibleUser # Nyano - Lets them heal with bibles - - type: Psionic # Nyano - They start with telepathic chat - - type: DispelPower # Nyano - They get the Dispel psionic power on spawn + - type: BibleUser + - type: PotentialPsionic + - type: Psionic + dampening: 1 #Mystagogue gets a significant buff to his antimage abilities, making him better at dispelling than other people + - type: DispelPower + - type: CommandStaff - !type:AddImplantSpecial implants: [ MindShieldImplant ] - - !type:AddComponentSpecial - components: - - type: CommandStaff - type: startingGear id: ResearchDirectorGear @@ -44,7 +44,7 @@ shoes: ClothingShoesColorBrown id: RnDPDA ears: ClothingHeadsetRD - belt: BibleMystagogue # Nyanotrasen - Mystagogue book for their Ifrit + belt: BibleMystagogue innerClothingSkirt: ClothingUniformJumpskirtResearchDirector satchel: ClothingBackpackSatchelResearchDirectorFilled duffelbag: ClothingBackpackDuffelResearchDirectorFilled diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..73e230e8a5f2088cbea061e045059ab6e5c52140 GIT binary patch literal 8610 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f78a89945c3732976cfac3674fea17d416a0bbe7 GIT binary patch literal 5930 zcmeHKX;f3!77m7ak_svokr1#9k~twHGE|vC<{*P0+$1*;$wU$$L=X*wNjV4z=1)lf&+@xB7z902v`)}4O*+~y|vzQt@r+!tef1k&)(nJ`#XE@d(Tc^ zZ&xk#Z`ENin3lVnlRxy2kYB3G(62};oq)lV!=mcBj`E7fQ` zy=(Ow#M2X$>@l;EpCe~XpWZRw?oyuaAiEn=GNxvk6ZmYj-4mRdJZo0k%j^qyQEacR zr{sao-TQOU!K6P53H=5G)0k8Hb7H!p3`?X19ctm1}0Xgw)?!9+nTGS)!!ercWJVsB&ca9 z#9badk$G@>Y2P!pR_Ea@UUfsgXO!YsB+c3uO;W%6zpul;JZZ-Zzdx`bkxu1xREdZC&9+VNA7uOiidsU0i1!=b7x=xA*RQm zA*<2sS8Hxh=rhC3@ouA%QyYf$m)X8FcXTnNa_X;}^+gp}9QWR5eQ%_z#IU@zwzh~o z_QR4e_Kz!T(Oy^AZ%97h8~*^jdU!*_^OG}0C$`@*x{IlESs0V_Q)b*>EUo=D-P+nV9U8eS6g{^IX=@yU!gH#*Wm&h}+KjvR`pw?0$F}_9*|T&(`8z{qMo*9Pk&&Lw3&>jE{FHYv zo@{h=al(w{>9yH~Q_(v_=BtnOv}SQ`2Nrbty~KsQoz$e6m^~@j)skJW)5TWWMq_IC zQnt5{$<|^)gC%lV43vncNEvyQ|#KM5NwyZBYc^nFH?)D5}2=GK;quY*4vr2Xt9AyF!y4$ zzG7)`h1=m&*qZ+D^{p#2`RW{4&;@0K=76fZCQS+jI)64Fs_0QJsroH>b#RA-w@PIx zJWS=CV%X8L`#&6Aee<5{)}a!`f*em)WGBPQCR4^Y+nI_{^mu&X&=jsYjFW$2bo@d@ zTj^HDKwfUqsWIyxbWJlSbvp|-UKA96leE@Y(&ay{+4r{I_N5X%ehhmvb|fm)KKBl} zxkblA1C8C%)pV}@@uRBXylYDZ=j;}AKOSV1QB&V;^^fiF->zk_GB4Fj4`+JRhx5qh zph&@r6VQLJUWY8y*5RuzyyvJ3C zfJ#5f+$Z#$s~&=FBQdwoy3Q%++;MS{TB?V^e!HRa+Yx`1%ogf9N4IocFZaLd=AkNW z2tzSO!_=3RF1; zO^P~d8&qR1x;Cg-Ecd2S8Z_)LJf_y6)UHLgUJfZ#Qz~uKuzA{W@0Nysw!!Y{(KiE; zFTuLG#S`7SY38Yyq~Sv;2DtKeqcTSZ45q+iJ39KZ8PKhyI4-Yqm6M--TMEIEpdV6Z zI)KiwGWR=n)7<(zot59p8YLyTbcQu-j{2Qh%dyH&zdao~z3{f>1`iAW-TRu3WQ&{H ztA4(4YTwMPv@7vJSEBYbBXR%39<8;lmed!QtE`I(Z^(SoqRaO2WbE@N4lUAkpEVpK zPt%%K_%_YTZnn%wjcV?bhX=ty(!$n71jjBoE^gGBCT^r+4{^8u3Ec^4Zo` z8>NUwrP893sn~~!rQ3BHw-aiIBMORL`knfJ*_3A&9g@1|Rn0;E`5~&f?+)AS9ZHG$ zmzQoyPB}&C*Pv@Q5toOf_U+%`cKC{J__;3h#Fg2Cbj&q))Wk*HJ_YE}AJ2v!@xfl} zs0=}W?=hS}5ktrph}nD|LXHX0`Eg=9BodlO{OuoC;N|rZo+tXi z0>lSK0thfTG#10 zkTV|+lYs#_AQw^ehyqCjG6cm0P;?s#14Sm%an?3u0vY=O zipik5@P%9e+D=a>A)=eBJGkcr^AOi7y8bvmgVg2G~3%KSA`*6phUV z1H^!wPaGLXB;koR4n;$C z6oP=5FQoDL96O}kDTLhe$EqR4kE7z|!zoDhW@8c!Y-7PxSdr zHY@SJY0Jw8Vf%j5-Pj^%{lt0E`W8aV74>|1lVnQJ1%Yq6p-iIIp;z8!TpAgpj zDMmEFivpqk@gZUVmb3puGOz%U!o-sCP%05vC?b?8f!6JQ*F569w@IaRnvlJXZ+w4_jsNxqd=4DBlN&GKeG8N6JW8B<4e~7JsKJ3H)aRmta>mpyo?^n9M()C3Qe3A0k?D|UA z7cuZf%3rhVe@2)3KZhxh2fYC$Ku4u;{t_8<%u=R%x;nw;DGDZq$$5;tB)hvA8V;!;UL!n-vA>v5QT4B(HaFgGNq(tJCxm&3XMIR22!qL2g9)0hhz;6%U2nL?swT}& z!vl1d82VmaXk^oh<+q0R4adS<&Qxz)|Jnf#hoA559@B3;<+QAM%jV9O&Q)o?<9nBl z!or42?3+8?esr3ixTEE=sU}rnNmBKSlJQwQA!&NN0I1!g497UAgk=wQ9n!oP{%3B5 zM^>x!zMnD=?We2_*zce$|7*_ZLGvZIWANFeckX`WN;iBiEnngHyIgCXCS3DUe9QKwWWQ*kf`&zA|NS$aEyiuz&86iNDj7xQ^m+FDum@nU Y6YmP{KCGw>f(VDXJ9|4FUK=U<7ye`^m;e9( literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json new file mode 100644 index 00000000000..42d21c3d8ab --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/cs_corpo_jacket.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "leonardo_dabepis on discord / @leonardo-dabepis on Tumblr, Edited by heartparkyheart on Discord", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..983c2534f4ceffdb575394b6b2077e250a9a07e1 GIT binary patch literal 8514 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 C=v7$& literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ba72253098481f5f357ad1a0fd752a9c1b23a2d5 GIT binary patch literal 6063 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 NoE=>4&)9BC_#YSooM`|6 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json new file mode 100644 index 00000000000..42d21c3d8ab --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/ee_corpo_jacket.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "leonardo_dabepis on discord / @leonardo-dabepis on Tumblr, Edited by heartparkyheart on Discord", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/hi_corpo_jacket.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..f18a64ab9c0c4012305f51ab70dc2710c1131824 GIT binary patch literal 8310 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 B7?S`1 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/hm_corpo_jacket.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d80b56558a23c7c523173d543ae370b835f48ac8 GIT binary patch literal 6169 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 G68{IREn~+3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json new file mode 100644 index 00000000000..42d21c3d8ab --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/id_corpo_jacket.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "leonardo_dabepis on discord / @leonardo-dabepis on Tumblr, Edited by heartparkyheart on Discord", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index b8cfc40c1c4..346156159a7 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -167,6 +167,7 @@ binds: type: State key: MouseLeft canFocus: true + priority: 10 - function: RotateStoredItem type: State key: MouseRight diff --git a/global.json b/global.json index 391ba3c2a30..2244195a209 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "8.0.100", - "rollForward": "latestFeature" + "rollForward": "disable" } } From c4db83104568f47794f79ea9e9a0896e092a7442 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 28 May 2024 22:10:05 -0400 Subject: [PATCH 04/24] Update PsionicRegenerationPowerSystem.cs --- .../Abilities/PsionicRegenerationPowerSystem.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs index 882eb9e0b14..893d1bc4cfa 100644 --- a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -73,13 +73,7 @@ private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent compon component.DoAfter = doAfterId; - _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)), - uid, - // TODO: Use LoS-based Filter when one is available. - Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), - true, - PopupType.Medium); - + _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-begin", ("entity", uid)), uid, PopupType.Medium); _audioSystem.PlayPvs(component.SoundUse, uid, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); _psionics.LogPowerUsed(uid, "psionic regeneration", @@ -122,12 +116,7 @@ private void OnMobStateChangedEvent(EntityUid uid, PsionicRegenerationPowerCompo BreakOnDamage = false, RequireCanInteract = false, }); - _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-self-revive", ("entity", uid)), - uid, - // TODO: Use LoS-based Filter when one is available. - Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), - true, - PopupType.MediumCaution); + _popupSystem.PopupEntity(Loc.GetString("psionic-regeneration-self-revive", ("entity", uid)), uid, PopupType.MediumCaution); _audioSystem.PlayPvs(component.SoundUse, uid, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); _psionics.LogPowerUsed(uid, "psionic regeneration", From cefe228d3e4401e9811aa35c64ebf46e01fd70c9 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Tue, 28 May 2024 22:13:59 -0400 Subject: [PATCH 05/24] fun pvs fixes --- .../Psionics/Abilities/MetapsionicPowerSystem.cs | 7 +------ .../Psionics/Abilities/PsionicAbilitiesSystem.cs | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs index 7b3a417c53f..78842c6443d 100644 --- a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -129,12 +129,7 @@ private void OnFocusedPowerUsed(FocusedMetapsionicPowerActionEvent args) component.DoAfter = doAfterId; - _popups.PopupEntity(Loc.GetString("focused-metapsionic-pulse-begin", ("entity", args.Target)), - args.Performer, - // TODO: Use LoS-based Filter when one is available. - Filter.Pvs(args.Performer).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(args.Performer, entity, ExamineRange, null)), - true, - PopupType.Medium); + _popups.PopupEntity(Loc.GetString("focused-metapsionic-pulse-begin", ("entity", args.Target)), args.Performer, PopupType.Medium); _audioSystem.PlayPvs(component.SoundUse, args.Performer, AudioParams.Default.WithVolume(8f).WithMaxDistance(1.5f).WithRolloffFactor(3.5f)); _psionics.LogPowerUsed(args.Performer, "focused metapsionic pulse", diff --git a/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs b/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs index 915abd12224..c81d3d7a0b0 100644 --- a/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs +++ b/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs @@ -68,12 +68,7 @@ public void RemovePsionics(EntityUid uid) { if (RemComp(uid)) { - _popups.PopupEntity(Loc.GetString("mindbreaking-feedback", ("entity", uid)), - uid, - // TODO: Use LoS-based Filter when one is available. - Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), - true, - PopupType.Medium); + _popups.PopupEntity(Loc.GetString("mindbreaking-feedback", ("entity", uid)), uid, PopupType.Medium); } if (!TryComp(uid, out var psionic)) From c416d4063e61bcf687ca171fa59319cddcebea74 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 29 May 2024 12:49:44 -0400 Subject: [PATCH 06/24] Update DispelPowerSystem.cs --- Content.Server/Psionics/Abilities/DispelPowerSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Psionics/Abilities/DispelPowerSystem.cs index cb7ef8313cd..67176c3a3d2 100644 --- a/Content.Server/Psionics/Abilities/DispelPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/DispelPowerSystem.cs @@ -82,8 +82,8 @@ private void OnPowerUsed(DispelPowerActionEvent args) { args.Handled = true; _psionics.LogPowerUsed(args.Performer, "dispel", - (int) MathF.Round(-2 * psionic.Dampening + psionic.Amplification), - (int) MathF.Round(-4 * psionic.Dampening + psionic.Amplification)); + (int) MathF.Round(-4 * psionic.Dampening + psionic.Amplification), + (int) MathF.Round(-2 * psionic.Dampening + psionic.Amplification)); } } From a90fe7f90ff39e257c17754f281cd2d25e58bd67 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 12:05:22 -0400 Subject: [PATCH 07/24] Start of Arachne Port --- Content.Server/Arachne/ArachneSystem.cs | 345 ++++++++++++++++++ Content.Server/Arachne/CocoonComponent.cs | 13 + .../Vampire/BloodSuckedComponent.cs | 9 + .../Vampire/BloodSuckerComponent.cs | 44 +++ Content.Server/Vampire/BloodSuckerSystem.cs | 221 +++++++++++ .../BloodSuckerGlandInjectorComponent.cs | 23 ++ .../BloodSuckerGlandInjectorSystem.cs | 40 ++ Content.Shared/Arachne/ArachneComponent.cs | 22 ++ Content.Shared/Arachne/Events.cs | 29 ++ Content.Shared/Arachne/WebComponent.cs | 6 + .../Vampiric/BloodSuckDoAfterEvent.cs | 10 + 11 files changed, 762 insertions(+) create mode 100644 Content.Server/Arachne/ArachneSystem.cs create mode 100644 Content.Server/Arachne/CocoonComponent.cs create mode 100644 Content.Server/Vampire/BloodSuckedComponent.cs create mode 100644 Content.Server/Vampire/BloodSuckerComponent.cs create mode 100644 Content.Server/Vampire/BloodSuckerSystem.cs create mode 100644 Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs create mode 100644 Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs create mode 100644 Content.Shared/Arachne/ArachneComponent.cs create mode 100644 Content.Shared/Arachne/Events.cs create mode 100644 Content.Shared/Arachne/WebComponent.cs create mode 100644 Content.Shared/Vampiric/BloodSuckDoAfterEvent.cs diff --git a/Content.Server/Arachne/ArachneSystem.cs b/Content.Server/Arachne/ArachneSystem.cs new file mode 100644 index 00000000000..6d57774304b --- /dev/null +++ b/Content.Server/Arachne/ArachneSystem.cs @@ -0,0 +1,345 @@ +using Content.Shared.Arachne; +using Content.Shared.Actions; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.IdentityManagement; +using Content.Shared.Verbs; +using Content.Shared.Buckle.Components; +using Content.Shared.Maps; +using Content.Shared.DoAfter; +using Content.Shared.Physics; +using Content.Shared.Stunnable; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.Doors.Components; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Damage; +using Content.Shared.Inventory; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Examine; +using Content.Shared.Humanoid; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Content.Server.Buckle.Systems; +using Content.Server.Nutrition.EntitySystems; +using Content.Server.Nutrition.Components; +using Content.Server.Popups; +using Content.Server.DoAfter; +using Content.Server.Body.Components; +using Content.Server.Vampiric; +using Content.Server.Speech.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Player; +using Robust.Shared.Physics.Components; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Utility; +using Robust.Server.Console; +using static Content.Shared.Examine.ExamineSystemShared; + +namespace Content.Server.Arachne +{ + public sealed class ArachneSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly HungerSystem _hungerSystem = default!; + [Dependency] private readonly ThirstSystem _thirstSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly BuckleSystem _buckleSystem = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; + [Dependency] private readonly BlindableSystem _blindableSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + + [Dependency] private readonly IServerConsoleHost _host = default!; + [Dependency] private readonly BloodSuckerSystem _bloodSuckerSystem = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + + private const string BodySlot = "body_slot"; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent>(AddCocoonVerb); + + SubscribeLocalEvent(OnCocEntInserted); + SubscribeLocalEvent(OnCocEntRemoved); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent>(AddSuccVerb); + + SubscribeLocalEvent(OnSpinWeb); + + SubscribeLocalEvent(OnWebDoAfter); + SubscribeLocalEvent(OnCocoonDoAfter); + } + + private void OnInit(EntityUid uid, ArachneComponent component, ComponentInit args) + { + if (_prototypeManager.TryIndex("SpinWeb", out var spinWeb)) + _actions.AddAction(uid, new WorldTargetAction(spinWeb), null); + } + + private void AddCocoonVerb(EntityUid uid, ArachneComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (args.Target == uid) + return; + + if (!TryComp(args.Target, out var bloodstream)) + return; + + if (bloodstream.BloodReagent != component.WebBloodReagent) + return; + + InnateVerb verb = new() + { + Act = () => + { + StartCocooning(uid, component, args.Target); + }, + Text = Loc.GetString("cocoon"), + Priority = 2 + }; + args.Verbs.Add(verb); + } + + private void OnCocEntInserted(EntityUid uid, CocoonComponent component, EntInsertedIntoContainerMessage args) + { + _blindableSystem.UpdateIsBlind(args.Entity); + EnsureComp(args.Entity); + + if (TryComp(args.Entity, out var currentAccent)) + { + component.WasReplacementAccent = true; + component.OldAccent = currentAccent.Accent; + currentAccent.Accent = "mumble"; + } else + { + component.WasReplacementAccent = false; + var replacement = EnsureComp(args.Entity); + replacement.Accent = "mumble"; + } + } + + private void OnCocEntRemoved(EntityUid uid, CocoonComponent component, EntRemovedFromContainerMessage args) + { + if (component.WasReplacementAccent && TryComp(args.Entity, out var replacement)) + { + replacement.Accent = component.OldAccent; + } else + { + RemComp(args.Entity); + } + + RemComp(args.Entity); + _blindableSystem.UpdateIsBlind(args.Entity); + } + + private void OnDamageChanged(EntityUid uid, CocoonComponent component, DamageChangedEvent args) + { + if (!args.DamageIncreased) + return; + + if (args.DamageDelta == null) + return; + + var body = _itemSlots.GetItemOrNull(uid, BodySlot); + + if (body == null) + return; + + var damage = args.DamageDelta * component.DamagePassthrough; + _damageableSystem.TryChangeDamage(body, damage); + } + + private void AddSuccVerb(EntityUid uid, CocoonComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!TryComp(args.User, out var sucker)) + return; + + if (!sucker.WebRequired) + return; + + var victim = _itemSlots.GetItemOrNull(uid, BodySlot); + + if (victim == null) + return; + + if (!TryComp(victim, out var stream)) + return; + + AlternativeVerb verb = new() + { + Act = () => + { + _bloodSuckerSystem.StartSuccDoAfter(args.User, victim.Value, sucker, stream, false); // start doafter + }, + Text = Loc.GetString("action-name-suck-blood"), + Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), + Priority = 2 + }; + args.Verbs.Add(verb); + } + + private void OnEntRemoved(EntityUid uid, WebComponent web, EntRemovedFromContainerMessage args) + { + if (!TryComp(uid, out var strap)) + return; + + if (HasComp(args.Entity)) + _buckleSystem.StrapSetEnabled(uid, false, strap); + } + + private void OnSpinWeb(SpinWebActionEvent args) + { + if (!TryComp(args.Performer, out var arachne)) + return; + + if (_containerSystem.IsEntityInContainer(args.Performer)) + return; + + TryComp(args.Performer, out var hunger); + TryComp(args.Performer, out var thirst); + + if (hunger != null && thirst != null) + { + if (hunger.CurrentThreshold <= Shared.Nutrition.Components.HungerThreshold.Peckish) + { + _popupSystem.PopupEntity(Loc.GetString("spin-web-action-hungry"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); + return; + } + if (thirst.CurrentThirstThreshold <= ThirstThreshold.Thirsty) + { + _popupSystem.PopupEntity(Loc.GetString("spin-web-action-thirsty"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); + return; + } + } + + var coords = args.Target; + if (!_mapManager.TryGetGrid(coords.GetGridUid(EntityManager), out var grid)) + { + _popupSystem.PopupEntity(Loc.GetString("action-name-spin-web-space"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); + return; + } + + foreach (var entity in coords.GetEntitiesInTile()) + { + PhysicsComponent? physics = null; // We use this to check if it's impassable + if ((HasComp(entity)) || // Is there already a web there? + ((Resolve(entity, ref physics, false) && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0) // Is it impassable? + && !(TryComp(entity, out var door) && door.State != DoorState.Closed))) // Is it a door that's open and so not actually impassable? + { + _popupSystem.PopupEntity(Loc.GetString("action-name-spin-web-blocked"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); + return; + } + } + + _popupSystem.PopupEntity(Loc.GetString("spin-web-start-third-person", ("spider", Identity.Entity(args.Performer, EntityManager))), args.Performer, + Filter.PvsExcept(args.Performer).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(args.Performer, entity, ExamineRange, null)), + true, + Shared.Popups.PopupType.MediumCaution); + _popupSystem.PopupEntity(Loc.GetString("spin-web-start-second-person"), args.Performer, args.Performer, Shared.Popups.PopupType.Medium); + + var ev = new ArachneWebDoAfterEvent(coords); + var doAfterArgs = new DoAfterArgs(args.Performer, arachne.WebDelay, ev, args.Performer) + { + BreakOnUserMove = true, + }; + + _doAfter.TryStartDoAfter(doAfterArgs); + } + + private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid target) + { + _popupSystem.PopupEntity(Loc.GetString("cocoon-start-third-person", ("target", Identity.Entity(target, EntityManager)), ("spider", Identity.Entity(uid, EntityManager))), uid, + // TODO: We need popup occlusion lmao + Filter.PvsExcept(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + Shared.Popups.PopupType.MediumCaution); + + _popupSystem.PopupEntity(Loc.GetString("cocoon-start-second-person", ("target", Identity.Entity(target, EntityManager))), uid, uid, Shared.Popups.PopupType.Medium); + + var delay = component.CocoonDelay; + + if (HasComp(target)) + delay *= component.CocoonKnockdownMultiplier; + + // Is it good practice to use empty data just to disambiguate doafters + // Who knows, there's no docs! + var ev = new ArachneCocoonDoAfterEvent(); + + var args = new DoAfterArgs(uid, delay, ev, uid, target: target) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + }; + + _doAfter.TryStartDoAfter(args); + } + + private void OnWebDoAfter(EntityUid uid, ArachneComponent component, ArachneWebDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + _hungerSystem.ModifyHunger(uid, -8); + if (TryComp(uid, out var thirst)) + _thirstSystem.UpdateThirst(thirst, -20); + + Spawn("ArachneWeb", args.Coords.SnapToGrid()); + _popupSystem.PopupEntity(Loc.GetString("spun-web-third-person", ("spider", Identity.Entity(uid, EntityManager))), uid, + Filter.PvsExcept(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + Shared.Popups.PopupType.MediumCaution); + _popupSystem.PopupEntity(Loc.GetString("spun-web-second-person"), uid, uid, Shared.Popups.PopupType.Medium); + args.Handled = true; + } + + private void OnCocoonDoAfter(EntityUid uid, ArachneComponent component, ArachneCocoonDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + var spawnProto = HasComp(args.Args.Target) ? "CocoonedHumanoid" : "CocoonSmall"; + Transform(args.Args.Target.Value).AttachToGridOrMap(); + var cocoon = Spawn(spawnProto, Transform(args.Args.Target.Value).Coordinates); + + if (!TryComp(cocoon, out var slots)) + return; + + // todo: our species should use scale visuals probably... + // TODO: We need a client-accessible notion of scale influence here. + /* if (spawnProto == "CocoonedHumanoid" && TryComp(args.Args.Target.Value, out var sprite)) */ + /* { */ + /* // why the fuck is this only available as a console command. */ + /* _host.ExecuteCommand(null, "scale " + cocoon + " " + sprite.Scale.Y); */ + if (TryComp(args.Args.Target.Value, out var physics)) + { + var scale = Math.Clamp(1 / (35 / physics.FixturesMass), 0.35, 2.5); + _host.ExecuteCommand(null, "scale " + cocoon + " " + scale); + } + + _inventorySystem.TryUnequip(args.Args.Target.Value, "ears", true, true); + + _itemSlots.SetLock(cocoon, BodySlot, false, slots); + _itemSlots.TryInsert(cocoon, BodySlot, args.Args.Target.Value, args.Args.User); + _itemSlots.SetLock(cocoon, BodySlot, true, slots); + + var impact = (spawnProto == "CocoonedHumanoid") ? LogImpact.High : LogImpact.Medium; + + _adminLogger.Add(LogType.Action, impact, $"{ToPrettyString(args.Args.User):player} cocooned {ToPrettyString(args.Args.Target.Value):target}"); + args.Handled = true; + } + } + + public sealed partial class SpinWebActionEvent : WorldTargetActionEvent {} +} diff --git a/Content.Server/Arachne/CocoonComponent.cs b/Content.Server/Arachne/CocoonComponent.cs new file mode 100644 index 00000000000..d7c8aea1296 --- /dev/null +++ b/Content.Server/Arachne/CocoonComponent.cs @@ -0,0 +1,13 @@ +namespace Content.Server.Arachne +{ + [RegisterComponent] + public sealed class CocoonComponent : Component + { + public bool WasReplacementAccent = false; + + public string OldAccent = ""; + + [DataField("damagePassthrough")] + public float DamagePassthrough = 0.5f; + } +} diff --git a/Content.Server/Vampire/BloodSuckedComponent.cs b/Content.Server/Vampire/BloodSuckedComponent.cs new file mode 100644 index 00000000000..2a0ced3cdfd --- /dev/null +++ b/Content.Server/Vampire/BloodSuckedComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Server.Vampiric +{ + /// + /// For entities who have been succed. + /// + [RegisterComponent] + public sealed class BloodSuckedComponent : Component + {} +} diff --git a/Content.Server/Vampire/BloodSuckerComponent.cs b/Content.Server/Vampire/BloodSuckerComponent.cs new file mode 100644 index 00000000000..53908e2fa60 --- /dev/null +++ b/Content.Server/Vampire/BloodSuckerComponent.cs @@ -0,0 +1,44 @@ +namespace Content.Server.Vampiric +{ + [RegisterComponent] + public sealed class BloodSuckerComponent : Component + { + /// + /// How much to succ each time we succ. + /// + [DataField("unitsToSucc")] + public float UnitsToSucc = 20f; + + /// + /// The time (in seconds) that it takes to succ an entity. + /// + [DataField("succDelay")] + public long SuccDelay = 4; + + // ***INJECT WHEN SUCC*** + + /// + /// Whether to inject chems into a chemstream when we suck something. + /// + [DataField("injectWhenSucc")] + public bool InjectWhenSucc = false; + + /// + /// How many units of our injected chem to inject. + /// + [DataField("unitsToInject")] + public float UnitsToInject = 5; + + /// + /// Which reagent to inject. + /// + [DataField("injectReagent")] + public string InjectReagent = ""; + + /// + /// Whether we need to web the thing up first... + /// + [DataField("webRequired")] + public bool WebRequired = false; + } +} diff --git a/Content.Server/Vampire/BloodSuckerSystem.cs b/Content.Server/Vampire/BloodSuckerSystem.cs new file mode 100644 index 00000000000..d987bbe911a --- /dev/null +++ b/Content.Server/Vampire/BloodSuckerSystem.cs @@ -0,0 +1,221 @@ +using Content.Shared.Verbs; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Administration.Logs; +using Content.Shared.Vampiric; +using Content.Server.Atmos.Components; +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Shared.Chemistry.EntitySystems; +using Content.Server.Popups; +using Content.Server.HealthExaminable; +using Content.Server.DoAfter; +using Content.Server.Nutrition.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Player; +using Robust.Shared.Audio; +using Robust.Shared.Utility; + +namespace Content.Server.Vampiric +{ + public sealed class BloodSuckerSystem : EntitySystem + { + [Dependency] private readonly BodySystem _bodySystem = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly PopupSystem _popups = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly StomachSystem _stomachSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(AddSuccVerb); + SubscribeLocalEvent(OnHealthExamined); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnDoAfter); + } + + private void AddSuccVerb(EntityUid uid, BloodSuckerComponent component, GetVerbsEvent args) + { + if (args.User == args.Target) + return; + if (component.WebRequired) + return; // handled elsewhere + if (!TryComp(args.Target, out var bloodstream)) + return; + if (!args.CanAccess) + return; + + InnateVerb verb = new() + { + Act = () => + { + StartSuccDoAfter(uid, args.Target, component, bloodstream); // start doafter + }, + Text = Loc.GetString("action-name-suck-blood"), + Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), + Priority = 2 + }; + args.Verbs.Add(verb); + } + + private void OnHealthExamined(EntityUid uid, BloodSuckedComponent component, HealthBeingExaminedEvent args) + { + args.Message.PushNewline(); + args.Message.AddMarkup(Loc.GetString("bloodsucked-health-examine", ("target", uid))); + } + + private void OnDamageChanged(EntityUid uid, BloodSuckedComponent component, DamageChangedEvent args) + { + if (args.DamageIncreased) + return; + + if (_prototypeManager.TryIndex("Brute", out var brute) && args.Damageable.Damage.TryGetDamageInGroup(brute, out var bruteTotal) + && _prototypeManager.TryIndex("Airloss", out var airloss) && args.Damageable.Damage.TryGetDamageInGroup(airloss, out var airlossTotal)) + { + if (bruteTotal == 0 && airlossTotal == 0) + RemComp(uid); + } + } + + private void OnDoAfter(EntityUid uid, BloodSuckerComponent component, BloodSuckDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Args.Target == null) + return; + + args.Handled = TrySucc(uid, args.Args.Target.Value); + } + + public void StartSuccDoAfter(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponent? bloodSuckerComponent = null, BloodstreamComponent? stream = null, bool doChecks = true) + { + if (!Resolve(bloodsucker, ref bloodSuckerComponent)) + return; + + if (!Resolve(victim, ref stream)) + return; + + if (doChecks) + { + if (!_interactionSystem.InRangeUnobstructed(bloodsucker, victim)) + { + return; + } + + if (_inventorySystem.TryGetSlotEntity(victim, "head", out var headUid) && HasComp(headUid)) + { + _popups.PopupEntity(Loc.GetString("bloodsucker-fail-helmet", ("helmet", headUid)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + return; + } + + if (_inventorySystem.TryGetSlotEntity(bloodsucker, "mask", out var maskUid) && + EntityManager.TryGetComponent(maskUid, out var blocker) && + blocker.Enabled) + { + _popups.PopupEntity(Loc.GetString("bloodsucker-fail-mask", ("mask", maskUid)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + return; + } + } + + if (stream.BloodReagent != "Blood") + { + _popups.PopupEntity(Loc.GetString("bloodsucker-fail-not-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + return; + } + + if (_bloodstreamSystem.GetBloodLevelPercentage(victim, stream) <= 1) + { + if (HasComp(victim)) + _popups.PopupEntity(Loc.GetString("bloodsucker-fail-no-blood-bloodsucked", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + else + _popups.PopupEntity(Loc.GetString("bloodsucker-fail-no-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + + return; + } + + _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start-victim", ("sucker", bloodsucker)), victim, victim, Shared.Popups.PopupType.LargeCaution); + _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + + var ev = new BloodSuckDoAfterEvent(); + var args = new DoAfterArgs(EntityManager, bloodsucker, new TimeSpan(bloodSuckerComponent.SuccDelay), ev, bloodsucker, target: victim) + { + BreakOnTargetMove = true, + BreakOnUserMove = false, + DistanceThreshold = 2f, + NeedHand = false + }; + + _doAfter.TryStartDoAfter(args); + } + + public bool TrySucc(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponent? bloodsuckerComp = null, BloodstreamComponent? bloodstream = null) + { + // Is bloodsucker a bloodsucker? + if (!Resolve(bloodsucker, ref bloodsuckerComp)) + return false; + + // Does victim have a bloodstream? + if (!Resolve(victim, ref bloodstream)) + return false; + + // No blood left, yikes. + if (_bloodstreamSystem.GetBloodLevelPercentage(victim, bloodstream) == 0.0f) + return false; + + // Does bloodsucker have a stomach? + var stomachList = _bodySystem.GetBodyOrganComponents(bloodsucker); + if (stomachList.Count == 0) + return false; + + if (!_solutionSystem.TryGetSolution(stomachList[0].Comp.Owner, StomachSystem.DefaultSolutionName, out var stomachSolution)) + return false; + + // Are we too full? + var unitsToDrain = bloodsuckerComp.UnitsToSucc; + + if (_solutionSystem.TryGetDrainableSolution(stomachList) < unitsToDrain) + unitsToDrain = (float) stomachSolution.AvailableVolume; + + if (unitsToDrain <= 2) + { + _popups.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), bloodsucker, bloodsucker, Shared.Popups.PopupType.MediumCaution); + return false; + } + + _adminLogger.Add(Shared.Database.LogType.MeleeHit, Shared.Database.LogImpact.Medium, $"{ToPrettyString(bloodsucker):player} sucked blood from {ToPrettyString(victim):target}"); + + // All good, succ time. + SoundSystem.Play("/Audio/Items/drink.ogg", Filter.Pvs(bloodsucker), bloodsucker); + _popups.PopupEntity(Loc.GetString("bloodsucker-blood-sucked-victim", ("sucker", bloodsucker)), victim, victim, Shared.Popups.PopupType.LargeCaution); + _popups.PopupEntity(Loc.GetString("bloodsucker-blood-sucked", ("target", victim)), bloodsucker, bloodsucker, Shared.Popups.PopupType.Medium); + EnsureComp(victim); + + // Make everything actually ingest. + var temp = _solutionSystem.SplitSolution(victim, bloodstream.BloodSolution, unitsToDrain); + temp.DoEntityReaction(bloodsucker, Shared.Chemistry.Reagent.ReactionMethod.Ingestion); + _stomachSystem.TryTransferSolution(stomachList[0].Comp.Owner, temp, stomachList[0].Comp); + + // Add a little pierce + DamageSpecifier damage = new(); + damage.DamageDict.Add("Piercing", 1); // Slowly accumulate enough to gib after like half an hour + + _damageableSystem.TryChangeDamage(victim, damage, true, true); + + if (bloodsuckerComp.InjectWhenSucc && _solutionSystem.TryGetInjectableSolution(victim, out var injectable)) + { + _solutionSystem.TryAddReagent(victim, injectable, bloodsuckerComp.InjectReagent, bloodsuckerComp.UnitsToInject, out var acceptedQuantity); + } + return true; + } + + private record struct BloodSuckData() + {} + } +} diff --git a/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs new file mode 100644 index 00000000000..6e97daf6a84 --- /dev/null +++ b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs @@ -0,0 +1,23 @@ +namespace Content.Server.Vampiric +{ + [RegisterComponent] + /// + /// Item that gives a bloodsucker injection glands (for poison, usually) + /// + public sealed class BloodSuckerGlandInjectorComponent : Component + { + public bool Used = false; + + /// + /// How many units of our injected chem to inject. + /// + [DataField("unitsToInject")] + public float UnitsToInject = 5; + + /// + /// Which reagent to inject. + /// + [DataField("injectReagent")] + public string InjectReagent = ""; + } +} diff --git a/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs new file mode 100644 index 00000000000..fd9d6fc15ed --- /dev/null +++ b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs @@ -0,0 +1,40 @@ +using Content.Server.Popups; +using Content.Shared.Interaction; +using Robust.Shared.Player; + +namespace Content.Server.Vampiric +{ + public sealed class BloodSuckerGlandInjectorSystem : EntitySystem + { + [Dependency] private readonly PopupSystem _popupSystem = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAfterInteract); + } + + private void OnAfterInteract(EntityUid uid, BloodSuckerGlandInjectorComponent component, AfterInteractEvent args) + { + if (component.Used) + return; + + if (!args.CanReach) + return; + + if (!TryComp(args.Target, out var bloodSuckerComponent)) + return; + + // They already have one. + if (bloodSuckerComponent.InjectWhenSucc) + return; + + bloodSuckerComponent.InjectWhenSucc = true; + bloodSuckerComponent.InjectReagent = component.InjectReagent; + bloodSuckerComponent.UnitsToInject = component.UnitsToInject; + component.Used = true; + QueueDel(uid); + + _popupSystem.PopupEntity(Loc.GetString("bloodsucker-glands-throb"), args.Target.Value, args.Target.Value); + } + } +} diff --git a/Content.Shared/Arachne/ArachneComponent.cs b/Content.Shared/Arachne/ArachneComponent.cs new file mode 100644 index 00000000000..ea774992c76 --- /dev/null +++ b/Content.Shared/Arachne/ArachneComponent.cs @@ -0,0 +1,22 @@ +namespace Content.Shared.Arachne +{ + [RegisterComponent] + public sealed partial class ArachneComponent : Component + { + [DataField("webDelay")] + public float WebDelay = 5f; + + [DataField("cocoonDelay")] + public float CocoonDelay = 12f; + + [DataField("cocoonKnockdownMultiplier")] + public float CocoonKnockdownMultiplier = 0.5f; + + /// + /// Blood reagent required to web up a mob. + /// + + [DataField("webBloodReagent")] + public string WebBloodReagent = "Blood"; + } +} diff --git a/Content.Shared/Arachne/Events.cs b/Content.Shared/Arachne/Events.cs new file mode 100644 index 00000000000..1adc08eda5c --- /dev/null +++ b/Content.Shared/Arachne/Events.cs @@ -0,0 +1,29 @@ +using Robust.Shared.Map; +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Arachne +{ + [Serializable, NetSerializable] + public sealed partial class ArachneWebDoAfterEvent : DoAfterEvent + { + [DataField("coords", required: true)] + public EntityCoordinates Coords = default!; + + private ArachneWebDoAfterEvent() + { + } + + public ArachneWebDoAfterEvent(EntityCoordinates coords) + { + Coords = coords; + } + + public override DoAfterEvent Clone() => this; + } + + [Serializable, NetSerializable] + public sealed partial class ArachneCocoonDoAfterEvent : SimpleDoAfterEvent + { + } +} diff --git a/Content.Shared/Arachne/WebComponent.cs b/Content.Shared/Arachne/WebComponent.cs new file mode 100644 index 00000000000..b35e911bfde --- /dev/null +++ b/Content.Shared/Arachne/WebComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Arachne +{ + [RegisterComponent] + public sealed partial class WebComponent : Component + {} +} diff --git a/Content.Shared/Vampiric/BloodSuckDoAfterEvent.cs b/Content.Shared/Vampiric/BloodSuckDoAfterEvent.cs new file mode 100644 index 00000000000..6aadc258d73 --- /dev/null +++ b/Content.Shared/Vampiric/BloodSuckDoAfterEvent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Vampiric +{ + [Serializable, NetSerializable] + public sealed partial class BloodSuckDoAfterEvent : SimpleDoAfterEvent + { + } +} From 8432e2cfb5d3d31566c7d06558b020346c64a898 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 12:37:52 -0400 Subject: [PATCH 08/24] Finishing arachne system --- Content.Server/Arachne/ArachneSystem.cs | 16 ++++------------ Content.Shared/Arachne/ArachneComponent.cs | 10 ++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Content.Server/Arachne/ArachneSystem.cs b/Content.Server/Arachne/ArachneSystem.cs index 6d57774304b..cf79a4dd8bf 100644 --- a/Content.Server/Arachne/ArachneSystem.cs +++ b/Content.Server/Arachne/ArachneSystem.cs @@ -79,8 +79,7 @@ public override void Initialize() private void OnInit(EntityUid uid, ArachneComponent component, ComponentInit args) { - if (_prototypeManager.TryIndex("SpinWeb", out var spinWeb)) - _actions.AddAction(uid, new WorldTargetAction(spinWeb), null); + _actions.AddAction(uid, ref component.WebActionEntity, component.WebActionId); } private void AddCocoonVerb(EntityUid uid, ArachneComponent component, GetVerbsEvent args) @@ -244,13 +243,11 @@ private void OnSpinWeb(SpinWebActionEvent args) } _popupSystem.PopupEntity(Loc.GetString("spin-web-start-third-person", ("spider", Identity.Entity(args.Performer, EntityManager))), args.Performer, - Filter.PvsExcept(args.Performer).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(args.Performer, entity, ExamineRange, null)), - true, Shared.Popups.PopupType.MediumCaution); _popupSystem.PopupEntity(Loc.GetString("spin-web-start-second-person"), args.Performer, args.Performer, Shared.Popups.PopupType.Medium); var ev = new ArachneWebDoAfterEvent(coords); - var doAfterArgs = new DoAfterArgs(args.Performer, arachne.WebDelay, ev, args.Performer) + var doAfterArgs = new DoAfterArgs(EntityManager, args.Performer, arachne.WebDelay, ev, args.Performer) { BreakOnUserMove = true, }; @@ -261,9 +258,6 @@ private void OnSpinWeb(SpinWebActionEvent args) private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid target) { _popupSystem.PopupEntity(Loc.GetString("cocoon-start-third-person", ("target", Identity.Entity(target, EntityManager)), ("spider", Identity.Entity(uid, EntityManager))), uid, - // TODO: We need popup occlusion lmao - Filter.PvsExcept(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), - true, Shared.Popups.PopupType.MediumCaution); _popupSystem.PopupEntity(Loc.GetString("cocoon-start-second-person", ("target", Identity.Entity(target, EntityManager))), uid, uid, Shared.Popups.PopupType.Medium); @@ -277,7 +271,7 @@ private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid // Who knows, there's no docs! var ev = new ArachneCocoonDoAfterEvent(); - var args = new DoAfterArgs(uid, delay, ev, uid, target: target) + var args = new DoAfterArgs(EntityManager, uid, delay, ev, uid, target: target) { BreakOnUserMove = true, BreakOnTargetMove = true, @@ -293,12 +287,10 @@ private void OnWebDoAfter(EntityUid uid, ArachneComponent component, ArachneWebD _hungerSystem.ModifyHunger(uid, -8); if (TryComp(uid, out var thirst)) - _thirstSystem.UpdateThirst(thirst, -20); + _thirstSystem.ModifyThirst(uid, thirst, -20); Spawn("ArachneWeb", args.Coords.SnapToGrid()); _popupSystem.PopupEntity(Loc.GetString("spun-web-third-person", ("spider", Identity.Entity(uid, EntityManager))), uid, - Filter.PvsExcept(uid).RemoveWhereAttachedEntity(entity => !ExamineSystemShared.InRangeUnOccluded(uid, entity, ExamineRange, null)), - true, Shared.Popups.PopupType.MediumCaution); _popupSystem.PopupEntity(Loc.GetString("spun-web-second-person"), uid, uid, Shared.Popups.PopupType.Medium); args.Handled = true; diff --git a/Content.Shared/Arachne/ArachneComponent.cs b/Content.Shared/Arachne/ArachneComponent.cs index ea774992c76..1b19a3e1de6 100644 --- a/Content.Shared/Arachne/ArachneComponent.cs +++ b/Content.Shared/Arachne/ArachneComponent.cs @@ -1,3 +1,6 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + namespace Content.Shared.Arachne { [RegisterComponent] @@ -18,5 +21,12 @@ public sealed partial class ArachneComponent : Component [DataField("webBloodReagent")] public string WebBloodReagent = "Blood"; + + [DataField("webActionId", + customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? WebActionId = "SpinWeb"; + + [DataField("dispelActionEntity")] + public EntityUid? WebActionEntity; } } From 79e1110073bb0e71af59668f89edf56eb643cfe7 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 16:00:21 -0400 Subject: [PATCH 09/24] Oneirophage complete --- Content.Server/Arachne/ArachneSystem.cs | 95 +------ Content.Server/Arachne/CocoonComponent.cs | 2 +- .../Vampire/BloodSuckedComponent.cs | 2 +- .../Vampire/BloodSuckerComponent.cs | 6 +- Content.Server/Vampire/BloodSuckerSystem.cs | 42 ++- .../BloodSuckerGlandInjectorComponent.cs | 2 +- Content.Shared/Arachne/ArachneComponent.cs | 14 +- Content.Shared/Arachne/Events.cs | 18 -- Content.Shared/Arachne/WebComponent.cs | 5 +- Resources/Locale/en-US/abilities/arachne.ftl | 13 + .../Locale/en-US/abilities/bloodsucker.ftl | 19 ++ Resources/Prototypes/GameRules/events.yml | 16 ++ .../Entities/Body/Mechanisms/vampiric.yml | 22 ++ .../Entities/Body/Parts/spider.yml | 24 +- .../Entities/Body/Prototypes/arachne.yml | 63 +++++ .../Body/Prototypes/vampiricanimal.yml | 43 +++ .../Entities/Markers/Spawners/ghost_roles.yml | 40 +-- .../Entities/Mobs/NPCs/mutants.yml | 259 +++++++++--------- .../Entities/Structures/Webbing/webs.yml | 93 +++++++ Resources/Prototypes/tags.yml | 6 + .../Structures/cocoon.rsi/cocoon1.png | Bin 0 -> 669 bytes .../Structures/cocoon.rsi/cocoon2.png | Bin 0 -> 636 bytes .../Structures/cocoon.rsi/cocoon3.png | Bin 0 -> 578 bytes .../Structures/cocoon.rsi/cocoon_large1.png | Bin 0 -> 762 bytes .../Structures/cocoon.rsi/cocoon_large2.png | Bin 0 -> 683 bytes .../Structures/cocoon.rsi/cocoon_large3.png | Bin 0 -> 876 bytes .../Structures/cocoon.rsi/meta.json | 35 +++ 27 files changed, 507 insertions(+), 312 deletions(-) create mode 100644 Resources/Locale/en-US/abilities/arachne.ftl create mode 100644 Resources/Locale/en-US/abilities/bloodsucker.ftl create mode 100644 Resources/Prototypes/Nyanotrasen/Entities/Body/Mechanisms/vampiric.yml create mode 100644 Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/arachne.yml create mode 100644 Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/vampiricanimal.yml create mode 100644 Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml create mode 100644 Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon1.png create mode 100644 Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon2.png create mode 100644 Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon3.png create mode 100644 Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon_large1.png create mode 100644 Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon_large2.png create mode 100644 Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon_large3.png create mode 100644 Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/meta.json diff --git a/Content.Server/Arachne/ArachneSystem.cs b/Content.Server/Arachne/ArachneSystem.cs index cf79a4dd8bf..338ecc21a1a 100644 --- a/Content.Server/Arachne/ArachneSystem.cs +++ b/Content.Server/Arachne/ArachneSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Arachne; using Content.Shared.Actions; +using Content.Shared.Actions.Events; using Content.Shared.Coordinates.Helpers; using Content.Shared.IdentityManagement; using Content.Shared.Verbs; @@ -15,32 +16,26 @@ using Content.Shared.Inventory; using Content.Shared.Administration.Logs; using Content.Shared.Database; -using Content.Shared.Examine; using Content.Shared.Humanoid; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Server.Buckle.Systems; -using Content.Server.Nutrition.EntitySystems; -using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Server.DoAfter; using Content.Server.Body.Components; using Content.Server.Vampiric; using Content.Server.Speech.Components; using Robust.Shared.Prototypes; -using Robust.Shared.Player; using Robust.Shared.Physics.Components; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Utility; using Robust.Server.Console; -using static Content.Shared.Examine.ExamineSystemShared; namespace Content.Server.Arachne { public sealed class ArachneSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly HungerSystem _hungerSystem = default!; [Dependency] private readonly ThirstSystem _thirstSystem = default!; @@ -63,25 +58,15 @@ public sealed class ArachneSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); SubscribeLocalEvent>(AddCocoonVerb); SubscribeLocalEvent(OnCocEntInserted); SubscribeLocalEvent(OnCocEntRemoved); SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent>(AddSuccVerb); - - SubscribeLocalEvent(OnSpinWeb); - - SubscribeLocalEvent(OnWebDoAfter); SubscribeLocalEvent(OnCocoonDoAfter); } - private void OnInit(EntityUid uid, ArachneComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.WebActionEntity, component.WebActionId); - } - private void AddCocoonVerb(EntityUid uid, ArachneComponent component, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract) @@ -198,63 +183,6 @@ private void OnEntRemoved(EntityUid uid, WebComponent web, EntRemovedFromContain _buckleSystem.StrapSetEnabled(uid, false, strap); } - private void OnSpinWeb(SpinWebActionEvent args) - { - if (!TryComp(args.Performer, out var arachne)) - return; - - if (_containerSystem.IsEntityInContainer(args.Performer)) - return; - - TryComp(args.Performer, out var hunger); - TryComp(args.Performer, out var thirst); - - if (hunger != null && thirst != null) - { - if (hunger.CurrentThreshold <= Shared.Nutrition.Components.HungerThreshold.Peckish) - { - _popupSystem.PopupEntity(Loc.GetString("spin-web-action-hungry"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); - return; - } - if (thirst.CurrentThirstThreshold <= ThirstThreshold.Thirsty) - { - _popupSystem.PopupEntity(Loc.GetString("spin-web-action-thirsty"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); - return; - } - } - - var coords = args.Target; - if (!_mapManager.TryGetGrid(coords.GetGridUid(EntityManager), out var grid)) - { - _popupSystem.PopupEntity(Loc.GetString("action-name-spin-web-space"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); - return; - } - - foreach (var entity in coords.GetEntitiesInTile()) - { - PhysicsComponent? physics = null; // We use this to check if it's impassable - if ((HasComp(entity)) || // Is there already a web there? - ((Resolve(entity, ref physics, false) && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0) // Is it impassable? - && !(TryComp(entity, out var door) && door.State != DoorState.Closed))) // Is it a door that's open and so not actually impassable? - { - _popupSystem.PopupEntity(Loc.GetString("action-name-spin-web-blocked"), args.Performer, args.Performer, Shared.Popups.PopupType.MediumCaution); - return; - } - } - - _popupSystem.PopupEntity(Loc.GetString("spin-web-start-third-person", ("spider", Identity.Entity(args.Performer, EntityManager))), args.Performer, - Shared.Popups.PopupType.MediumCaution); - _popupSystem.PopupEntity(Loc.GetString("spin-web-start-second-person"), args.Performer, args.Performer, Shared.Popups.PopupType.Medium); - - var ev = new ArachneWebDoAfterEvent(coords); - var doAfterArgs = new DoAfterArgs(EntityManager, args.Performer, arachne.WebDelay, ev, args.Performer) - { - BreakOnUserMove = true, - }; - - _doAfter.TryStartDoAfter(doAfterArgs); - } - private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid target) { _popupSystem.PopupEntity(Loc.GetString("cocoon-start-third-person", ("target", Identity.Entity(target, EntityManager)), ("spider", Identity.Entity(uid, EntityManager))), uid, @@ -280,22 +208,6 @@ private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid _doAfter.TryStartDoAfter(args); } - private void OnWebDoAfter(EntityUid uid, ArachneComponent component, ArachneWebDoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - _hungerSystem.ModifyHunger(uid, -8); - if (TryComp(uid, out var thirst)) - _thirstSystem.ModifyThirst(uid, thirst, -20); - - Spawn("ArachneWeb", args.Coords.SnapToGrid()); - _popupSystem.PopupEntity(Loc.GetString("spun-web-third-person", ("spider", Identity.Entity(uid, EntityManager))), uid, - Shared.Popups.PopupType.MediumCaution); - _popupSystem.PopupEntity(Loc.GetString("spun-web-second-person"), uid, uid, Shared.Popups.PopupType.Medium); - args.Handled = true; - } - private void OnCocoonDoAfter(EntityUid uid, ArachneComponent component, ArachneCocoonDoAfterEvent args) { if (args.Handled || args.Cancelled || args.Args.Target == null) @@ -319,9 +231,6 @@ private void OnCocoonDoAfter(EntityUid uid, ArachneComponent component, ArachneC var scale = Math.Clamp(1 / (35 / physics.FixturesMass), 0.35, 2.5); _host.ExecuteCommand(null, "scale " + cocoon + " " + scale); } - - _inventorySystem.TryUnequip(args.Args.Target.Value, "ears", true, true); - _itemSlots.SetLock(cocoon, BodySlot, false, slots); _itemSlots.TryInsert(cocoon, BodySlot, args.Args.Target.Value, args.Args.User); _itemSlots.SetLock(cocoon, BodySlot, true, slots); @@ -332,6 +241,4 @@ private void OnCocoonDoAfter(EntityUid uid, ArachneComponent component, ArachneC args.Handled = true; } } - - public sealed partial class SpinWebActionEvent : WorldTargetActionEvent {} } diff --git a/Content.Server/Arachne/CocoonComponent.cs b/Content.Server/Arachne/CocoonComponent.cs index d7c8aea1296..42ecf27971a 100644 --- a/Content.Server/Arachne/CocoonComponent.cs +++ b/Content.Server/Arachne/CocoonComponent.cs @@ -1,7 +1,7 @@ namespace Content.Server.Arachne { [RegisterComponent] - public sealed class CocoonComponent : Component + public sealed partial class CocoonComponent : Component { public bool WasReplacementAccent = false; diff --git a/Content.Server/Vampire/BloodSuckedComponent.cs b/Content.Server/Vampire/BloodSuckedComponent.cs index 2a0ced3cdfd..d7e402cd98a 100644 --- a/Content.Server/Vampire/BloodSuckedComponent.cs +++ b/Content.Server/Vampire/BloodSuckedComponent.cs @@ -4,6 +4,6 @@ namespace Content.Server.Vampiric /// For entities who have been succed. /// [RegisterComponent] - public sealed class BloodSuckedComponent : Component + public sealed partial class BloodSuckedComponent : Component {} } diff --git a/Content.Server/Vampire/BloodSuckerComponent.cs b/Content.Server/Vampire/BloodSuckerComponent.cs index 53908e2fa60..f5619d1cb49 100644 --- a/Content.Server/Vampire/BloodSuckerComponent.cs +++ b/Content.Server/Vampire/BloodSuckerComponent.cs @@ -1,7 +1,7 @@ namespace Content.Server.Vampiric { [RegisterComponent] - public sealed class BloodSuckerComponent : Component + public sealed partial class BloodSuckerComponent : Component { /// /// How much to succ each time we succ. @@ -12,8 +12,8 @@ public sealed class BloodSuckerComponent : Component /// /// The time (in seconds) that it takes to succ an entity. /// - [DataField("succDelay")] - public long SuccDelay = 4; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan Delay = TimeSpan.FromSeconds(4); // ***INJECT WHEN SUCC*** diff --git a/Content.Server/Vampire/BloodSuckerSystem.cs b/Content.Server/Vampire/BloodSuckerSystem.cs index d987bbe911a..35f4743e645 100644 --- a/Content.Server/Vampire/BloodSuckerSystem.cs +++ b/Content.Server/Vampire/BloodSuckerSystem.cs @@ -17,6 +17,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Player; using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; using Robust.Shared.Utility; namespace Content.Server.Vampiric @@ -34,6 +35,7 @@ public sealed class BloodSuckerSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { base.Initialize(); @@ -130,21 +132,14 @@ public void StartSuccDoAfter(EntityUid bloodsucker, EntityUid victim, BloodSucke return; } - if (_bloodstreamSystem.GetBloodLevelPercentage(victim, stream) <= 1) - { - if (HasComp(victim)) - _popups.PopupEntity(Loc.GetString("bloodsucker-fail-no-blood-bloodsucked", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); - else - _popups.PopupEntity(Loc.GetString("bloodsucker-fail-no-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); - - return; - } + if (_solutionSystem.PercentFull(stream.Owner) != 0) + _popups.PopupEntity(Loc.GetString("bloodsucker-fail-no-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start-victim", ("sucker", bloodsucker)), victim, victim, Shared.Popups.PopupType.LargeCaution); _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); var ev = new BloodSuckDoAfterEvent(); - var args = new DoAfterArgs(EntityManager, bloodsucker, new TimeSpan(bloodSuckerComponent.SuccDelay), ev, bloodsucker, target: victim) + var args = new DoAfterArgs(EntityManager, bloodsucker, bloodSuckerComponent.Delay, ev, bloodsucker, target: victim) { BreakOnTargetMove = true, BreakOnUserMove = false, @@ -155,14 +150,14 @@ public void StartSuccDoAfter(EntityUid bloodsucker, EntityUid victim, BloodSucke _doAfter.TryStartDoAfter(args); } - public bool TrySucc(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponent? bloodsuckerComp = null, BloodstreamComponent? bloodstream = null) + public bool TrySucc(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponent? bloodsuckerComp = null) { // Is bloodsucker a bloodsucker? if (!Resolve(bloodsucker, ref bloodsuckerComp)) return false; // Does victim have a bloodstream? - if (!Resolve(victim, ref bloodstream)) + if (!TryComp(victim, out var bloodstream)) return false; // No blood left, yikes. @@ -178,12 +173,8 @@ public bool TrySucc(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponen return false; // Are we too full? - var unitsToDrain = bloodsuckerComp.UnitsToSucc; - - if (_solutionSystem.TryGetDrainableSolution(stomachList) < unitsToDrain) - unitsToDrain = (float) stomachSolution.AvailableVolume; - if (unitsToDrain <= 2) + if (_solutionSystem.PercentFull(bloodsucker) >= 1) { _popups.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), bloodsucker, bloodsucker, Shared.Popups.PopupType.MediumCaution); return false; @@ -192,14 +183,16 @@ public bool TrySucc(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponen _adminLogger.Add(Shared.Database.LogType.MeleeHit, Shared.Database.LogImpact.Medium, $"{ToPrettyString(bloodsucker):player} sucked blood from {ToPrettyString(victim):target}"); // All good, succ time. - SoundSystem.Play("/Audio/Items/drink.ogg", Filter.Pvs(bloodsucker), bloodsucker); + _audio.PlayPvs("/Audio/Items/drink.ogg", bloodsucker); _popups.PopupEntity(Loc.GetString("bloodsucker-blood-sucked-victim", ("sucker", bloodsucker)), victim, victim, Shared.Popups.PopupType.LargeCaution); _popups.PopupEntity(Loc.GetString("bloodsucker-blood-sucked", ("target", victim)), bloodsucker, bloodsucker, Shared.Popups.PopupType.Medium); EnsureComp(victim); // Make everything actually ingest. - var temp = _solutionSystem.SplitSolution(victim, bloodstream.BloodSolution, unitsToDrain); - temp.DoEntityReaction(bloodsucker, Shared.Chemistry.Reagent.ReactionMethod.Ingestion); + if (bloodstream.BloodSolution == null) + return false; + + var temp = _solutionSystem.SplitSolution(bloodstream.BloodSolution.Value, bloodsuckerComp.UnitsToSucc); _stomachSystem.TryTransferSolution(stomachList[0].Comp.Owner, temp, stomachList[0].Comp); // Add a little pierce @@ -208,10 +201,11 @@ public bool TrySucc(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponen _damageableSystem.TryChangeDamage(victim, damage, true, true); - if (bloodsuckerComp.InjectWhenSucc && _solutionSystem.TryGetInjectableSolution(victim, out var injectable)) - { - _solutionSystem.TryAddReagent(victim, injectable, bloodsuckerComp.InjectReagent, bloodsuckerComp.UnitsToInject, out var acceptedQuantity); - } + //I'm not porting the nocturine gland, this code is deprecated, and will be reworked at a later date. + //if (bloodsuckerComp.InjectWhenSucc && _solutionSystem.TryGetInjectableSolution(victim, out var injectable)) + //{ + // _solutionSystem.TryAddReagent(victim, injectable, bloodsuckerComp.InjectReagent, bloodsuckerComp.UnitsToInject, out var acceptedQuantity); + //} return true; } diff --git a/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs index 6e97daf6a84..1a3c9b1588a 100644 --- a/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs +++ b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorComponent.cs @@ -4,7 +4,7 @@ namespace Content.Server.Vampiric /// /// Item that gives a bloodsucker injection glands (for poison, usually) /// - public sealed class BloodSuckerGlandInjectorComponent : Component + public sealed partial class BloodSuckerGlandInjectorComponent : Component { public bool Used = false; diff --git a/Content.Shared/Arachne/ArachneComponent.cs b/Content.Shared/Arachne/ArachneComponent.cs index 1b19a3e1de6..8e1dd03b200 100644 --- a/Content.Shared/Arachne/ArachneComponent.cs +++ b/Content.Shared/Arachne/ArachneComponent.cs @@ -1,14 +1,13 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Arachne { - [RegisterComponent] + [RegisterComponent, NetworkedComponent] public sealed partial class ArachneComponent : Component { - [DataField("webDelay")] - public float WebDelay = 5f; - [DataField("cocoonDelay")] public float CocoonDelay = 12f; @@ -21,12 +20,5 @@ public sealed partial class ArachneComponent : Component [DataField("webBloodReagent")] public string WebBloodReagent = "Blood"; - - [DataField("webActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? WebActionId = "SpinWeb"; - - [DataField("dispelActionEntity")] - public EntityUid? WebActionEntity; } } diff --git a/Content.Shared/Arachne/Events.cs b/Content.Shared/Arachne/Events.cs index 1adc08eda5c..02001286ac6 100644 --- a/Content.Shared/Arachne/Events.cs +++ b/Content.Shared/Arachne/Events.cs @@ -4,24 +4,6 @@ namespace Content.Shared.Arachne { - [Serializable, NetSerializable] - public sealed partial class ArachneWebDoAfterEvent : DoAfterEvent - { - [DataField("coords", required: true)] - public EntityCoordinates Coords = default!; - - private ArachneWebDoAfterEvent() - { - } - - public ArachneWebDoAfterEvent(EntityCoordinates coords) - { - Coords = coords; - } - - public override DoAfterEvent Clone() => this; - } - [Serializable, NetSerializable] public sealed partial class ArachneCocoonDoAfterEvent : SimpleDoAfterEvent { diff --git a/Content.Shared/Arachne/WebComponent.cs b/Content.Shared/Arachne/WebComponent.cs index b35e911bfde..95c03e5a4a2 100644 --- a/Content.Shared/Arachne/WebComponent.cs +++ b/Content.Shared/Arachne/WebComponent.cs @@ -1,6 +1,9 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + namespace Content.Shared.Arachne { - [RegisterComponent] + [RegisterComponent, NetworkedComponent] public sealed partial class WebComponent : Component {} } diff --git a/Resources/Locale/en-US/abilities/arachne.ftl b/Resources/Locale/en-US/abilities/arachne.ftl new file mode 100644 index 00000000000..6f6348d7212 --- /dev/null +++ b/Resources/Locale/en-US/abilities/arachne.ftl @@ -0,0 +1,13 @@ +action-name-spin-web = Spin Web +action-desc-spin-web = Use your spinnerets to make a spider web in the current tile. Makes you hungrier and thirstier. +action-name-spin-web-space = You can't spin a web in space! +action-name-spin-web-blocked = There's no room for a web here. +spin-web-action-hungry = You're too hungry to spin a web! +spin-web-action-thirsty = You're too thirsty to spin a web! +spin-web-start-second-person = You start spinning a web. +spin-web-start-third-person = {CAPITALIZE(THE($spider))} starts spinning a web! +cocoon-start-second-person = You start cocooning {THE($target)}. +cocoon-start-third-person = {CAPITALIZE(THE($spider))} starts cocooning {THE($target)}. +spun-web-second-person = You spin up a web. +spun-web-third-person = {CAPITALIZE(THE($spider))} spins up a web! +cocoon = Cocoon diff --git a/Resources/Locale/en-US/abilities/bloodsucker.ftl b/Resources/Locale/en-US/abilities/bloodsucker.ftl new file mode 100644 index 00000000000..d956eaff84e --- /dev/null +++ b/Resources/Locale/en-US/abilities/bloodsucker.ftl @@ -0,0 +1,19 @@ +action-name-suck-blood = Suck Blood +action-description-suck-blood = Suck the blood of the victim in your hand. + +bloodsucker-fail-helmet = You'd need to remove {THE($helmet)}. +bloodsucker-fail-mask = You'd need to remove your mask! + +bloodsucker-fail-not-blood = { CAPITALIZE(SUBJECT($target)) } doesn't have delicious, nourishing mortal blood. +bloodsucker-fail-no-blood = { CAPITALIZE(SUBJECT($target)) } has no blood in { POSS-ADJ($target) } body. +bloodsucker-fail-no-blood-bloodsucked = { CAPITALIZE(SUBJECT($target)) } has been sucked dry. + +bloodsucker-blood-sucked = You suck some blood from {$target}. +bloodsucker-doafter-start = You try to suck blood from {$target}. + +bloodsucker-doafter-start-victim = {CAPITALIZE(THE($sucker))} is trying to bite your neck! +bloodsucker-blood-sucked-victim = {CAPITALIZE(THE($sucker))} sucks some of your blood! + +bloodsucked-health-examine = [color=red]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } bite marks on { POSS-ADJ($target) } neck.[/color] + +bloodsucker-glands-throb = The glands behind your fangs feel a bit sore. diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 6812ca14f70..44f10bd9307 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -395,6 +395,22 @@ # - id: MobClownSpider # prob: 0.05 +- type: entity + id: OneirophageSpawn + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + id: VentCritters + earliestStart: 15 + minimumPlayers: 15 + weight: 4 + duration: 60 + - type: VentCrittersRule + entries: + - id: MobGiantSpiderVampireAngry + prob: 0.01 + - type: entity id: ZombieOutbreak parent: BaseGameRule diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Body/Mechanisms/vampiric.yml b/Resources/Prototypes/Nyanotrasen/Entities/Body/Mechanisms/vampiric.yml new file mode 100644 index 00000000000..23934b3ebcc --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/Entities/Body/Mechanisms/vampiric.yml @@ -0,0 +1,22 @@ +- type: entity + id: OrganVampiricHumanoidStomach + parent: OrganHumanStomach + components: + - type: Metabolizer + # mm yummy + maxReagents: 3 + metabolizerTypes: [Vampiric] + groups: + - id: Food + - id: Drink + +- type: entity + id: OrganVampiricStomach + parent: OrganAnimalStomach + components: + - type: Metabolizer + maxReagents: 3 + metabolizerTypes: [Vampiric] + groups: + - id: Food + - id: Drink diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Body/Parts/spider.yml b/Resources/Prototypes/Nyanotrasen/Entities/Body/Parts/spider.yml index a900f7524e7..7e71227dbcb 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Body/Parts/spider.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Body/Parts/spider.yml @@ -24,13 +24,33 @@ - ReagentId: DemonsBlood Quantity: 10 +- type: entity + id: ThoraxSpider + name: "spider thorax" #for arachne, actual spiders should get a cephalothorax that combines with head. + parent: PartSpider + components: + - type: Sprite + sprite: Mobs/Species/Moth/parts.rsi # placeholder sprite + state: "torso_m" + - type: Icon + sprite: Mobs/Species/Moth/parts.rsi + state: "torso_m" + - type: BodyPart #"Other" type + - type: Extractable + juiceSolution: + reagents: + - ReagentId: Fat + Quantity: 10 + - ReagentId: DemonsBlood + Quantity: 20 + - type: entity id: RightLegSpider name: "right spider leg" parent: PartSpider components: - type: Sprite - sprite: Objects/Consumable/Food/meat.rsi + sprite: Objects/Consumable/Food/meat.rsi # placeholder sprite state: spiderleg - type: Icon sprite: Objects/Consumable/Food/meat.rsi @@ -48,7 +68,7 @@ parent: PartSpider components: - type: Sprite - sprite: Objects/Consumable/Food/meat.rsi + sprite: Objects/Consumable/Food/meat.rsi # placeholder sprite state: spiderleg - type: Icon sprite: Objects/Consumable/Food/meat.rsi diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/arachne.yml b/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/arachne.yml new file mode 100644 index 00000000000..553391484e2 --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/arachne.yml @@ -0,0 +1,63 @@ +- type: body + id: Arachne + name: "arachne" + root: torso + slots: + head: + part: HeadHuman + connections: + - torso + organs: + brain: OrganHumanBrain + eyes: OrganHumanEyes + torso: + part: TorsoHuman + connections: + - left arm + - right arm + - thorax + organs: + heart: OrganHumanHeart + lungs: OrganHumanLungs + stomach: OrganVampiricHumanoidStomach + liver: OrganHumanLiver + kidneys: OrganHumanKidneys + right arm: + part: RightArmHuman + connections: + - right hand + left arm: + part: LeftArmHuman + connections: + - left hand + right hand: + part: RightHandHuman + left hand: + part: LeftHandHuman + thorax: + part: ThoraxSpider + connections: + - left foreleg + - left second leg + - left third leg + - left hind leg + - right foreleg + - right second leg + - right third leg + - right hind leg + left foreleg: + part: LeftLegSpider + left second leg: + part: LeftLegSpider + left third leg: + part: LeftLegSpider + left hind leg: + part: LeftLegSpider + right foreleg: + part: RightLegSpider + right second leg: + part: RightLegSpider + right third leg: + part: RightLegSpider + right hind leg: + part: RightLegSpider diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/vampiricanimal.yml b/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/vampiricanimal.yml new file mode 100644 index 00000000000..3f4cdb06de1 --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/Entities/Body/Prototypes/vampiricanimal.yml @@ -0,0 +1,43 @@ +- type: body + id: VampiricAnimal + name: "vampiric animal" + root: torso + slots: + torso: + part: TorsoAnimal + connections: + - legs + organs: + lungs: OrganAnimalLungs + stomach: OrganVampiricStomach + liver: OrganAnimalLiver + heart: OrganAnimalHeart + kidneys: OrganAnimalKidneys + legs: + part: LegsAnimal + connections: + - feet + feet: + part: FeetAnimal + +- type: body + id: VampiricAnimalLarge + name: "large vampiric animal" + root: torso + slots: + torso: + part: TorsoAnimal + connections: + - legs + organs: + lungs: OrganAnimalLungs + stomach: OrganVampiricHumanoidStomach + liver: OrganAnimalLiver + heart: OrganAnimalHeart + kidneys: OrganAnimalKidneys + legs: + part: LegsAnimal + connections: + - feet + feet: + part: FeetAnimal diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml index 2652a89127e..046a324e6f6 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml @@ -51,23 +51,23 @@ - state: prisoner # - type: MidRoundAntagSpawnLocation # When MidRoundAntag? -# - type: entity -# id: SpawnPointGhostVampSpider -# name: ghost role spawn point -# suffix: Vampire spider -# parent: MarkerBase -# noSpawn: true -# components: -# - type: GhostRoleMobSpawner -# prototype: MobGiantSpiderVampireAngry -# - type: GhostRole -# makeSentient: true -# name: ghost-role-information-giant-spider-vampire-name -# description: ghost-role-information-giant-spider-vampire-description -# rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. -# - type: Sprite -# sprite: Markers/jobs.rsi -# layers: -# - state: green -# - sprite: Mobs/Animals/bat.rsi -# state: bat +- type: entity + id: SpawnPointGhostVampSpider + name: ghost role spawn point + suffix: Vampire spider + parent: MarkerBase + noSpawn: true + components: + - type: GhostRoleMobSpawner + prototype: MobGiantSpiderVampireAngry + - type: GhostRole + makeSentient: true + name: ghost-role-information-giant-spider-vampire-name + description: ghost-role-information-giant-spider-vampire-description + rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. + - type: Sprite + sprite: Markers/jobs.rsi + layers: + - state: green + - sprite: Mobs/Animals/bat.rsi + state: bat diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml index 5daf2e15e56..462b3254f1e 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml @@ -70,143 +70,130 @@ - type: Produce - type: NoSlip -# - type: entity -# name: oneirophage -# parent: SimpleMobBase -# id: MobGiantSpiderVampire -# description: The 'dream-eater' spider, rumored to be one of the potential genetic sources for arachne. -# components: -# - type: Sprite -# drawdepth: Mobs -# layers: -# - map: ["enum.DamageStateVisualLayers.Base"] -# state: viper -# sprite: Mobs/Animals/spider.rsi -# - type: Physics -# - type: Fixtures -# fixtures: -# fix1: -# shape: -# !type:PhysShapeCircle -# radius: 0.35 -# density: 130 -# mask: -# - SmallMobMask -# layer: -# - SmallMobLayer -# - type: Appearance -# - type: DamageStateVisuals -# states: -# Alive: -# Base: viper -# Critical: -# Base: viper_dead -# Dead: -# Base: viper_dead -# - type: Butcherable -# spawned: -# - id: FoodMeatSpider -# amount: 2 -# - type: CombatMode -# - type: ReplacementAccent -# accent: xeno -# - type: InteractionPopup -# successChance: 0.5 -# interactSuccessString: petting-success-tarantula -# interactFailureString: petting-failure-generic -# - type: Puller -# needsHands: false -# - type: Arachne -# cocoonDelay: 8 -# - type: SolutionContainerManager -# solutions: -# melee: -# reagents: -# - ReagentId: Nocturine -# Quantity: 20 -# - type: MeleeChemicalInjector -# solution: melee -# transferAmount: 3.5 -# - type: SolutionRegeneration -# solution: melee -# generated: -# reagents: -# - ReagentId: Nocturine -# Quantity: 0.15 -# - type: BloodSucker -# unitsToSucc: 35 -# injectWhenSucc: true -# injectReagent: Cryptobiolin -# unitsToInject: 10 -# webRequired: true -# - type: Bloodstream -# bloodReagent: DemonsBlood -# - type: Body -# prototype: VampiricAnimalLarge -# - type: PotentialPsionic -# - type: Psionic -# removable: false -# - type: MetapsionicPower -# - type: MeleeWeapon -# hidden: true -# angle: 0 -# animation: WeaponArcBite -# damage: -# types: -# Piercing: 8 -# - type: AntiPsionicWeapon -# punish: false -# modifiers: -# coefficients: -# Piercing: 2.25 -# - type: Damageable -# damageContainer: HalfSpirit -# damageModifierSet: HalfSpirit -# - type: StatusEffects -# allowed: -# - Stun -# - KnockedDown -# - SlowedDown -# - Stutter -# - SeeingRainbows -# - Electrocution -# - Drunk -# - SlurredSpeech -# - PressureImmunity -# - Muted -# - ForcedSleep -# - TemporaryBlindness -# - Pacified -# - PsionicsDisabled -# - PsionicallyInsulated -# - type: Tag -# tags: -# - Oneirophage -# - type: MovementAlwaysTouching -# - type: PsionicInvisibleContacts -# whitelist: -# tags: -# - ArachneWeb +- type: entity + name: oneirophage + parent: MobGiantSpider + id: MobGiantSpiderVampire + description: The 'dream-eater' spider, rumored to be one of the potential genetic sources for arachne. + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base", "movement"] + state: viper + sprite: Mobs/Animals/spider.rsi + - type: SpriteMovement + movementLayers: + movement: + state: viper-moving + noMovementLayers: + movement: + state: viper + - type: Appearance + - type: DamageStateVisuals + states: + Alive: + Base: viper + Critical: + Base: viper_dead + Dead: + Base: viper_dead + - type: ReplacementAccent + accent: xeno + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-tarantula + interactFailureString: petting-failure-generic + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/snake_hiss.ogg + - type: Puller + needsHands: false + - type: Arachne + cocoonDelay: 8 + - type: SolutionContainerManager + solutions: + melee: + reagents: + - ReagentId: Nocturine + Quantity: 20 + - type: MeleeChemicalInjector + solution: melee + transferAmount: 3.5 + - type: SolutionRegeneration + solution: melee + generated: + reagents: + - ReagentId: Nocturine + Quantity: 0.15 + - type: BloodSucker + unitsToSucc: 35 + injectWhenSucc: true + injectReagent: Cryptobiolin + unitsToInject: 10 + webRequired: true + - type: Bloodstream + bloodReagent: DemonsBlood + - type: Body + prototype: VampiricAnimalLarge + - type: PotentialPsionic + - type: Psionic + removable: false + - type: MetapsionicPower + - type: AntiPsionicWeapon + punish: false + modifiers: + coefficients: + Piercing: 2.25 + - type: Damageable + damageContainer: HalfSpirit + damageModifierSet: HalfSpirit + - type: StatusEffects + allowed: + - Stun + - KnockedDown + - SlowedDown + - Stutter + - SeeingRainbows + - Electrocution + - Drunk + - SlurredSpeech + - PressureImmunity + - Muted + - ForcedSleep + - TemporaryBlindness + - Pacified + - PsionicsDisabled + - PsionicallyInsulated + - type: Tag + tags: + - Oneirophage + - type: MovementAlwaysTouching + - type: PsionicInvisibleContacts + whitelist: + tags: + - ArachneWeb -# - type: entity -# name: oneirophage -# parent: MobGiantSpiderVampire -# id: MobGiantSpiderVampireAngry -# suffix: Angry -# components: -# - type: NpcFactionMember -# factions: -# - SimpleHostile -# - type: InputMover -# - type: MobMover -# - type: HTN -# rootTask: SimpleHostileCompound -# - type: GhostRole -# makeSentient: true -# name: ghost-role-information-giant-spider-vampire-name -# description: ghost-role-information-giant-spider-vampire-description -# rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. -# - type: GhostTakeoverAvailable +- type: entity + name: oneirophage + parent: MobGiantSpiderVampire + id: MobGiantSpiderVampireAngry + suffix: Angry + components: + - type: NpcFactionMember + factions: + - SimpleHostile + - type: InputMover + - type: MobMover + - type: HTN + rootTask: + task: SimpleHostileCompound + - type: GhostRole + makeSentient: true + name: ghost-role-information-giant-spider-vampire-name + description: ghost-role-information-giant-spider-vampire-description + rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. + - type: GhostTakeoverAvailable - type: entity parent: SimpleMobBase diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml new file mode 100644 index 00000000000..e483ea5da71 --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml @@ -0,0 +1,93 @@ +- type: entity + id: CocoonedHumanoid + name: cocooned humanoid + description: Unlucky. + placement: + mode: SnapgridCenter + snap: + - Wall + components: + - type: Sprite + layers: + - sprite: Nyanotrasen/Structures/cocoon.rsi + state: cocoon_large1 + map: [ "enum.DamageStateVisualLayers.Base" ] + - type: RandomSprite + available: + - enum.DamageStateVisualLayers.Base: + cocoon_large1: "" + - enum.DamageStateVisualLayers.Base: #your guess for why randomsprite requires an arbitrary layer is as good as mine friend + cocoon_large2: "" + - enum.DamageStateVisualLayers.Base: + cocoon_large3: "" + - type: Cocoon + - type: Clickable + - type: InteractionOutline + - type: Transform + noRot: true + - type: Damageable + damageModifierSet: Web + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 40 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.4,0.25,0.1" + density: 20 + mask: + - SmallMobMask + layer: + - SmallMobLayer + - type: Physics + bodyType: Dynamic + - type: Pullable + - type: AntiRottingContainer + - type: ItemSlots + slots: + body_slot: + name: Body + locked: true + ejectOnBreak: true + - type: Butcherable + butcheringType: Knife + butcherDelay: 12 + spawned: + - id: MaterialCloth1 + amount: 1 + prob: 0.5 #This doesn't cost hunger so should at least make it not worth it time-wise + - type: Appearance + - type: ContainerContainer + containers: + body_slot: !type:ContainerSlot + +- type: entity + id: CocoonSmall + parent: CocoonedHumanoid + name: cocoon + description: What could be inside...? + placement: + mode: SnapgridCenter + snap: + - Wall + components: + - type: Sprite + layers: + - sprite: Nyanotrasen/Structures/cocoon.rsi + state: cocoon1 + map: [ "enum.DamageStateVisualLayers.Base" ] + - type: RandomSprite + available: + - enum.DamageStateVisualLayers.Base: + cocoon1: "" + - enum.DamageStateVisualLayers.Base: #your guess for why randomsprite requires an arbitrary layer is as good as mine friend + cocoon2: "" + - enum.DamageStateVisualLayers.Base: + cocoon3: "" diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index c6a0ab3f8fd..1bf002d254e 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -16,6 +16,9 @@ - type: Tag id: AppraisalTool +- type: Tag + id: ArachneWeb + - type: Tag id: ArtifactFragment @@ -889,6 +892,9 @@ - type: Tag id: Ointment +- type: Tag + id: Oneirophage + - type: Tag id: Ore diff --git a/Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon1.png b/Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon1.png new file mode 100644 index 0000000000000000000000000000000000000000..27741fdf314bc38be7153151d603479000af1891 GIT binary patch literal 669 zcmV;O0%HA%P)$5Dx(m zFBXfsFs^ZhFvIKhs_Q@?a2Eu|Znw+%d={V2CxgL2(&@BRt5s77QvgGJv)QP)!{Lxf zBqH5zS3;qX0suChPIdoDARG?Ma5&VIZq5}t7z~=4e3$?qrcx<&lF#R5GMOk0#n$Wf zbgvNx%x1I6<#LhTZYQJB=)MNvY>Qb6p90{%P$;MnB8HP#EGF%CTX=>j_YedE$8-R| z2pY9PD*b+6(|S1pqs>SHF&0TClhWyQ6o&J|;UM$*{N4f71zSRDY#+4%AZuXAG5}6) zJ*{FSF&>X~3xL8MAVy2#m^3I#?&oqjg$5#%$!M*-lmLq%x>zjq8dr$n%mCybVgTLx zUR_f8)C2?Ph&LJyX|-C`y5LqQR|<#N5hY3%&StYJXt<{%0O37mT984J0?BUyP@joE zOaO?F549B$#R;MmAA^~AnE_;<*&=3=1DK@06q*z%=`4(1e*jw4~ z0qlJMWovU6H!CGJl-MY-xwmdtq%iY)ns=JT%y?aMo6Y-_-gBJS`Tw5(^PJ}DNTn6v)$^dz}l z?&{uG0e*DwrC2OVzu#ADv6;^I`F#40Q3}HmU~mHGDA`>A1U8Z_U5_|(ZouJi__HaQ zb4ntSh&q9ItyYt6w|oCZ)ai881#Y-_Jg%Y$7#{#)v6!9+#DQ$JTE_40cff%7Zs&#L z)@(La1XswMBT4plyIm@kirRiH^P2&PUpCc0o8Q&ftnh~bj4)&Ervm)TumCIo3*Z}` WA1sKIggnat0000>)WwzxBKr~01&TMs}c%@WV_wUYPFI?BEi)4t02Yzh&P)}+3)w-zg#X- zDwPz3@mqWv#0bC=C*w>eBcstsa=Dy%JRWspHk*mt?Us7It}P&@5XKH5PW1czd*+CH zy=I<#amf@8*aR#6tkGuT&~3hV6jRB0QN)BpQv%Znx93ryd@e$n4R$MNZwy{|3K8G+z z!Wb@C4gm4&)`v%71mO4kWiS|M;ZcBOY1xfC6eHr)fzRi=uS=y;5BGiuKv5kI2Xz3D zbULj^5ex=jy*HoFRS*~g7kH2Lwg7s)9`;=ujfVCSW>Y>rs(-WDs2hPmKqp|B26zX=cKp8o0}g-#@aF*j10gEyIQQql Q8~^|S07*qoM6N<$f*=3`Hvj+t literal 0 HcmV?d00001 diff --git a/Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon_large1.png b/Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon_large1.png new file mode 100644 index 0000000000000000000000000000000000000000..f9431f6428586fe569344878961bd645cbc04fc6 GIT binary patch literal 762 zcmVgc z)HGVt5GrUW(MCfaUs*`R6?PUu8&3| zNvG3tb#*1>a@o1Oyxf|EOMu{L0LW@nhDS$7QYw{P@YQN1KA%r^cX!o}?e_MT;npOS z0IYMloFtP;d3$?vgMWN{xLH0wKdZ(L0YI@wA`ywlP)iTC$+wedl~_5y~WPfkvBj3y>__7w^Rr`>L^ z5zz`*DNQVxW%cy*B;jyavm+Di%jHt5z#cw`quGZf5((9|PBO$gJv}9kQ>j#T)_MSj znM}ql8BVj=Oq~EE3y3gB=JUBr#MYRT2ni7n!3I&PNX3w1u}F_;pC|#GC@tUs6Lhgy zXaidIcHUNUn{!$Y^}?1i+)CY#N=3DoO#J%UXS&qe~DNc>iKw1&f>5L*PT;*8~QGffs!} zpU=Pki~!)>Zr7AbC3$_n-??}$BvKPl@Or&&PN$QpR;%WCJR0t~kVr)!dh+Mb%Vi;e0@UO25G7E>wo((o??RzquGg!nR4V3jxrFbX&*%3O z0E81G`|WlU5)_17DiwjiTW>rbi}%=(&*ww%*=%N(%jNq**lxEagrpwNU$2*_ffB3L z${IWs0l-J2k@(Mp0$hEk3tY;H0N}k|Po8sfE##*a31Y2Q6cTGIE(O#1Pj;9&toXiW$VheODDlgT8zK?E?T*O80l5Ya3g z4hJEF1z2KRP8L9d0(`w*ixt?Qz^Fn+hf<~lIGK9w2T~;8?RMdb)e4s!I1vDV(_;Z@ zaAJsPK?p0bYO~p7OCV?~nE~2@bb^MDJU|djc%Q%;i;ZYaz@K~`sb}CY@i-+99Y(s8 zAhX?W!`+Zni39-V>bIfvJK>}Bq&oirjJ71z@AtXHst`+{)9H8sBQF4W&yONKK#(DT zyoWmEg6Am1XCX43PGd<}SpWz>hyvPj6oG^YkErDOS?<#jTvI6!Y&B08jIW6P@f20a z0#hxX%$l3vJF)B8X%5)#dD~4${QyPKZkme+ R_X7X`002ovPDHLkV1mDMGcW)E literal 0 HcmV?d00001 diff --git a/Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon_large3.png b/Resources/Textures/Nyanotrasen/Structures/cocoon.rsi/cocoon_large3.png new file mode 100644 index 0000000000000000000000000000000000000000..9a033961ffa27e1796d98c10f6fc9d563f13066a GIT binary patch literal 876 zcmV-y1C#uTP)P6p9*9R5b`t4M~gH+uwv|vr7wIu9v%9!kbKXc6WH6-}`4~`Dy;KodM1O zXMq0~pwVbJn!mrlo5#n;AAbb^VFc^-x=E+g=JoZ}j(k4v%;$5{ZnsYYISByqTrOv7 zwVJuOxG?MW+Hj9uUtgQaWWv-r1tK&6;zS*dMolahvjAM5pPy|e7K??sy1FuzN`)+h z4k{FY#EEjXTG^3ICM^VmdolqKFsu)og-`&AM8Z1e${_WsJ@>Nhc5BBe_9ejW?XBZB zm`y#x5`1}ivC#uTHvG^27y#ncYSqq_b|M4;| za|~gkCEcuJOs7*jPO%RF_xJZssZ{bB3Nbl}=HBV1Z zwiJ4&5-<=Js4E;0Z#J9Wx8hhy0swb+ced-*Ob8%O#Hm!ut2qXwj8m{&E;Dt2K-~3Z z-|aHx9D)b{5EUJdai!im;UbZUd3$@a5Tu<>M;N1I7(2#YZ_f>Sf#zjBr>G0;d{&+S z01k;V?-3skhjz|8t_T9m z+}|fW$GGwZB@4Y?FOY=*0ClCCHQ-*^ct8vPUUAI}K#JNiLu*@g_>hbjTa zp9$BgLl%fe;>v~^j7|qqe@X^I;+0IbvLu-NH#yC###c+Y{tIbhlW zM6IJ*< Date: Mon, 10 Jun 2024 16:25:33 -0400 Subject: [PATCH 10/24] PORTING COMPLETE --- Resources/Locale/en-US/species/species.ftl | 1 + .../Chemistry/metabolizer_types.yml | 6 +- .../Entities/Mobs/Player/arachne.yml | 35 +++ .../Entities/Mobs/Species/arachne.yml | 223 ++++++++++++++++++ .../anytaur_inventory_template.yml | 112 +++++++++ Resources/Prototypes/Reagents/biological.yml | 23 ++ Resources/Prototypes/Species/arachne.yml | 50 ++++ .../female_full.png | Bin 0 -> 140 bytes .../female_none.png | Bin 0 -> 112 bytes .../female_top.png | Bin 0 -> 140 bytes .../anytaur_masking_helpers.rsi/full.png | Bin 0 -> 112 bytes .../anytaur_masking_helpers.rsi/male_full.png | Bin 0 -> 112 bytes .../anytaur_masking_helpers.rsi/male_none.png | Bin 0 -> 112 bytes .../anytaur_masking_helpers.rsi/male_top.png | Bin 0 -> 112 bytes .../anytaur_masking_helpers.rsi/meta.json | 59 +++++ .../anytaur_masking_helpers.rsi/none.png | Bin 0 -> 112 bytes .../anytaur_masking_helpers.rsi/top.png | Bin 0 -> 112 bytes .../unisex_full.png | Bin 0 -> 112 bytes .../unisex_none.png | Bin 0 -> 112 bytes .../unisex_top.png | Bin 0 -> 112 bytes .../Customization/spidereyes.rsi/eyes.png | Bin 0 -> 5070 bytes .../Customization/spidereyes.rsi/meta.json | 15 ++ .../Mobs/Species/arachne.rsi/meta.json | 19 ++ .../Mobs/Species/arachne.rsi/spider_body.png | Bin 0 -> 3288 bytes .../Species/arachne.rsi/spider_body_front.png | Bin 0 -> 761 bytes .../Textures/Mobs/Species/eyes.rsi/eyes.png | Bin 0 -> 5553 bytes .../Textures/Mobs/Species/eyes.rsi/meta.json | 15 ++ 27 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 Resources/Prototypes/Entities/Mobs/Player/arachne.yml create mode 100644 Resources/Prototypes/Entities/Mobs/Species/arachne.yml create mode 100644 Resources/Prototypes/InventoryTemplates/anytaur_inventory_template.yml create mode 100644 Resources/Prototypes/Species/arachne.yml create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_full.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_none.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_top.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/full.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_full.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_none.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_top.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/meta.json create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/none.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/top.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_full.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_none.png create mode 100644 Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_top.png create mode 100644 Resources/Textures/Mobs/Customization/spidereyes.rsi/eyes.png create mode 100644 Resources/Textures/Mobs/Customization/spidereyes.rsi/meta.json create mode 100644 Resources/Textures/Mobs/Species/arachne.rsi/meta.json create mode 100644 Resources/Textures/Mobs/Species/arachne.rsi/spider_body.png create mode 100644 Resources/Textures/Mobs/Species/arachne.rsi/spider_body_front.png create mode 100644 Resources/Textures/Mobs/Species/eyes.rsi/eyes.png create mode 100644 Resources/Textures/Mobs/Species/eyes.rsi/meta.json diff --git a/Resources/Locale/en-US/species/species.ftl b/Resources/Locale/en-US/species/species.ftl index f31b1fa0f00..79ce7fea6a1 100644 --- a/Resources/Locale/en-US/species/species.ftl +++ b/Resources/Locale/en-US/species/species.ftl @@ -6,6 +6,7 @@ species-name-reptilian = Reptilian species-name-slime = Slime Person species-name-diona = Diona species-name-arachnid = Arachnid +species-name-arachne = Arachne species-name-moth = Moth Person species-name-skeleton = Skeleton species-name-vox = Vox diff --git a/Resources/Prototypes/Chemistry/metabolizer_types.yml b/Resources/Prototypes/Chemistry/metabolizer_types.yml index 259387b6d5c..4d48dab9925 100644 --- a/Resources/Prototypes/Chemistry/metabolizer_types.yml +++ b/Resources/Prototypes/Chemistry/metabolizer_types.yml @@ -1,4 +1,4 @@ -# If your species wants to metabolize stuff differently, +# If your species wants to metabolize stuff differently, # you'll likely have to tag its metabolizers with something other than Human. - type: metabolizerType @@ -44,3 +44,7 @@ - type: metabolizerType id: Arachnid name: arachnid + +- type: metabolizerType + id: Vampiric + name: vampiric diff --git a/Resources/Prototypes/Entities/Mobs/Player/arachne.yml b/Resources/Prototypes/Entities/Mobs/Player/arachne.yml new file mode 100644 index 00000000000..bebf42f31ba --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Player/arachne.yml @@ -0,0 +1,35 @@ +- type: entity + save: false + name: Urist McArachne + parent: MobArachneBase + id: MobArachne + components: + - type: CombatMode + - type: InteractionPopup + successChance: 1 + interactSuccessString: hugging-success-generic + interactSuccessSound: /Audio/Effects/thudswoosh.ogg + messagePerceivedByOthers: hugging-success-generic-others + - type: MindContainer + showExamineInfo: true + - type: Input + context: "human" + - type: MobMover + - type: InputMover + - type: Respirator + damage: + types: + Asphyxiation: 1.0 + damageRecovery: + types: + Asphyxiation: -1.0 + - type: Alerts + - type: Actions + - type: Eye + - type: CameraRecoil + - type: Examiner + - type: CanHostGuardian + - type: NpcFactionMember + factions: + - NanoTrasen + - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachne.yml b/Resources/Prototypes/Entities/Mobs/Species/arachne.yml new file mode 100644 index 00000000000..991668e444f --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Species/arachne.yml @@ -0,0 +1,223 @@ +- type: entity + save: false + name: Urist McArachne + parent: BaseMobHuman + id: MobArachneBase + abstract: true + components: + - type: Sprite + # Arachne are one of the species that needs a manual visual layers setup. + layers: + - map: [ "enum.HumanoidVisualLayers.LLeg" ] + sprite: Mobs/Species/arachne.rsi + state: spider_body + - map: [ "enum.HumanoidVisualLayers.Chest" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: torso_m + - map: [ "enum.HumanoidVisualLayers.RLeg" ] + sprite: Mobs/Species/arachne.rsi + state: spider_body_front + - map: [ "enum.HumanoidVisualLayers.Head" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + color: "#008800" + sprite: Mobs/Species/eyes.rsi + state: eyes + - map: [ "enum.HumanoidVisualLayers.RArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_arm + - map: [ "enum.HumanoidVisualLayers.LArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_arm + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: [ "enum.HumanoidVisualLayers.StencilMask" ] + sprite: Mobs/Customization/anytaur_masking_helpers.rsi + state: unisex_full + visible: false + - map: [ "jumpsuit" ] + - map: [ "enum.HumanoidVisualLayers.LHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_hand + - map: [ "enum.HumanoidVisualLayers.RHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_hand + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "id" ] + - map: [ "gloves" ] + - map: [ "shoes" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.Hair" ] + state: bald + sprite: Mobs/Customization/human_hair.rsi + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] + - map: [ "enum.HumanoidVisualLayers.Tail" ] + sprite: Mobs/Customization/masking_helpers.rsi + state: none + visible: false + - map: [ "clownedon" ] # Dynamically generated + sprite: "Effects/creampie.rsi" + state: "creampie_human" + visible: false + - type: HumanoidAppearance + species: Arachne + - type: Fixtures + fixtures: # TODO: This needs a second fixture just for mob collisions. + fix1: + shape: + !type:PhysShapeCircle + radius: 0.40 + density: 140 + restitution: 0.0 + mask: + - MobMask + layer: + - MobLayer + - type: Body + prototype: Arachne + requiredLegs: 8 + - type: Damageable + damageContainer: HalfSpirit + damageModifierSet: HalfSpirit + - type: Speech + speechSounds: Alto + - type: Inventory + templateId: anytaur + - type: Tag + tags: + - CanPilot + - ShoesRequiredStepTriggerImmune + - DoorBumpOpener + - type: Bloodstream + bloodReagent: DemonsBlood + - type: BloodSucker + webRequired: true + - type: Arachne + - type: DamageVisuals + thresholds: [ 20, 40, 100 ] + targetLayers: + - "enum.HumanoidVisualLayers.Chest" + - "enum.HumanoidVisualLayers.Head" + - "enum.HumanoidVisualLayers.LArm" + - "enum.HumanoidVisualLayers.RArm" + - type: MovedByPressure + pressureResistance: 4 + - type: Barotrauma + damage: + types: + Blunt: 0.05 #per second, scales with pressure and other constants. Reduced Damage. This allows medicine to heal faster than damage. + - type: MovementAlwaysTouching + - type: MovementSpeedModifier + baseWalkSpeed : 3.0 + baseSprintSpeed : 5.0 + - type: FireVisuals + sprite: Mobs/Effects/onfire.rsi + normalState: Generic_mob_burning + alternateState: arachne_standing + fireStackAlternateState: 3 + - type: Spider + - type: IgnoreSpiderWeb + +- type: entity + save: false + name: Urist McHands + parent: MobHumanDummy + id: MobArachneDummy + noSpawn: true + description: A dummy arachne meant to be used in character setup. + components: + - type: Sprite + layers: + - map: [ "enum.HumanoidVisualLayers.LLeg" ] + sprite: Mobs/Species/arachne.rsi + state: spider_body + - map: [ "enum.HumanoidVisualLayers.Chest" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: torso_m + - map: [ "enum.HumanoidVisualLayers.RLeg" ] + sprite: Mobs/Species/arachne.rsi + state: spider_body_front + - map: [ "enum.HumanoidVisualLayers.Head" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + color: "#008800" + sprite: Mobs/Species/eyes.rsi + state: eyes + - map: [ "enum.HumanoidVisualLayers.RArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_arm + - map: [ "enum.HumanoidVisualLayers.LArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_arm + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: [ "enum.HumanoidVisualLayers.StencilMask" ] + sprite: Mobs/Customization/anytaur_masking_helpers.rsi + state: unisex_full + visible: false + - map: [ "jumpsuit" ] + - map: [ "enum.HumanoidVisualLayers.LHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_hand + - map: [ "enum.HumanoidVisualLayers.RHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_hand + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "id" ] + - map: [ "gloves" ] + - map: [ "shoes" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.Hair" ] + state: bald + sprite: Mobs/Customization/human_hair.rsi + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] + - map: [ "enum.HumanoidVisualLayers.Tail" ] + sprite: Mobs/Customization/masking_helpers.rsi + state: none + visible: false + - type: Inventory + templateId: anytaur + - type: HumanoidAppearance + species: Arachne diff --git a/Resources/Prototypes/InventoryTemplates/anytaur_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/anytaur_inventory_template.yml new file mode 100644 index 00000000000..0dd5961aef3 --- /dev/null +++ b/Resources/Prototypes/InventoryTemplates/anytaur_inventory_template.yml @@ -0,0 +1,112 @@ +- type: inventoryTemplate + id: anytaur + slots: + - name: jumpsuit + slotTexture: uniform + slotFlags: INNERCLOTHING + stripTime: 5 + uiWindowPos: 0,2 + strippingWindowPos: 0,2 + displayName: Jumpsuit + - name: outerClothing + slotTexture: suit + slotFlags: OUTERCLOTHING + slotGroup: MainHotbar + stripTime: 6 + uiWindowPos: 1,2 + strippingWindowPos: 1,2 + displayName: Suit + blacklist: + tags: + - FullBodyOuter + - name: gloves + slotTexture: gloves + slotFlags: GLOVES + uiWindowPos: 2,2 + strippingWindowPos: 2,2 + displayName: Gloves + - name: neck + slotTexture: neck + slotFlags: NECK + uiWindowPos: 0,1 + strippingWindowPos: 0,1 + displayName: Neck + - name: mask + slotTexture: mask + slotFlags: MASK + uiWindowPos: 1,1 + strippingWindowPos: 1,1 + displayName: Mask + - name: eyes + slotTexture: glasses + slotFlags: EYES + stripTime: 2 + uiWindowPos: 0,0 + strippingWindowPos: 0,0 + displayName: Eyes + - name: ears + slotTexture: ears + slotFlags: EARS + stripTime: 2 + uiWindowPos: 2,0 + strippingWindowPos: 2,1 + displayName: Ears + - name: head + slotTexture: head + slotFlags: HEAD + uiWindowPos: 1,0 + strippingWindowPos: 1,0 + displayName: Head + - name: pocket1 + slotTexture: pocket + slotFlags: POCKET + slotGroup: MainHotbar + stripTime: 2 + uiWindowPos: 0,3 + strippingWindowPos: 0,3 + dependsOn: jumpsuit + displayName: Pocket 1 + stripHidden: true + - name: pocket2 + slotTexture: pocket + slotFlags: POCKET + slotGroup: MainHotbar + stripTime: 2 + uiWindowPos: 2,3 + strippingWindowPos: 2,3 + dependsOn: jumpsuit + displayName: Pocket 2 + stripHidden: true + - name: suitstorage + slotTexture: suit_storage + slotFlags: SUITSTORAGE + stripTime: 2 + uiWindowPos: 2,0 + strippingWindowPos: 2,0 + dependsOn: outerClothing + displayName: Suit Storage + - name: id + slotTexture: id + slotFlags: IDCARD + slotGroup: SecondHotbar + stripTime: 6 + uiWindowPos: 2,1 + strippingWindowPos: 2,4 + dependsOn: jumpsuit + displayName: ID + - name: belt + slotTexture: belt + slotFlags: BELT + slotGroup: SecondHotbar + stripTime: 5 + uiWindowPos: 3,1 + strippingWindowPos: 1,3 + displayName: Belt + - name: back + slotTexture: back + slotFlags: BACK + slotGroup: SecondHotbar + stripTime: 5 + uiWindowPos: 3,0 + strippingWindowPos: 0,4 + displayName: Back diff --git a/Resources/Prototypes/Reagents/biological.yml b/Resources/Prototypes/Reagents/biological.yml index c24c073f037..f3c57dccb9e 100644 --- a/Resources/Prototypes/Reagents/biological.yml +++ b/Resources/Prototypes/Reagents/biological.yml @@ -23,6 +23,29 @@ - !type:OrganType type: Human shouldHave: false + - !type:SatiateHunger + factor: 0.5 + conditions: + - !type:OrganType + type: Vampiric + - !type:AdjustReagent + conditions: + - !type:OrganType + type: Vampiric + reagent: Water + amount: 0.15 + - !type:AdjustReagent + conditions: + - !type:OrganType + type: Vampiric + reagent: Protein + amount: 0.15 + - !type:AdjustReagent + conditions: + - !type:OrganType + type: Vampiric + reagent: Omnizine + amount: 0.2 Food: effects: - !type:AdjustReagent diff --git a/Resources/Prototypes/Species/arachne.yml b/Resources/Prototypes/Species/arachne.yml new file mode 100644 index 00000000000..ed494c37cc8 --- /dev/null +++ b/Resources/Prototypes/Species/arachne.yml @@ -0,0 +1,50 @@ +- type: species + id: Arachne + name: species-name-arachne + roundStart: true + prototype: MobArachne + sprites: MobArachneSprites + markingLimits: MobArachneMarkingLimits + dollPrototype: MobArachneDummy + skinColoration: HumanToned + sexes: + - Female + minAge: 60 + youngAge: 150 + oldAge: 400 + maxAge: 666 + +- type: markingPoints + id: MobArachneMarkingLimits + points: + Hair: + points: 1 + required: false + Tail: + points: 1 + required: false + Chest: + points: 1 + required: false + Arms: + points: 2 + required: false + + +- type: speciesBaseSprites + id: MobArachneSprites + sprites: + Head: MobHumanHead + Hair: MobHumanoidAnyMarking + Chest: MobHumanTorso + Eyes: MobArachneEyes + LArm: MobHumanLArm + RArm: MobHumanRArm + LHand: MobHumanLHand + RHand: MobHumanRHand + +- type: humanoidBaseSprite + id: MobArachneEyes + baseSprite: + sprite: Mobs/Species/eyes.rsi + state: eyes diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_full.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_full.png new file mode 100644 index 0000000000000000000000000000000000000000..acb96562e7374cbaba2d9f8eac4f1983c2722946 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pW8&%J7@~1LIYA=F!GWQPv*Eu3gIdO!1&Rrbf?NSjtdA<$*!Y?lS%R69 i8<@M9!xb)?)i4xI-eS7+u8$Z{KZB>MpUXO@geCy^0w-qx literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_none.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_none.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_top.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/female_top.png new file mode 100644 index 0000000000000000000000000000000000000000..acb96562e7374cbaba2d9f8eac4f1983c2722946 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pW8&%J7@~1LIYA=F!GWQPv*Eu3gIdO!1&Rrbf?NSjtdA<$*!Y?lS%R69 i8<@M9!xb)?)i4xI-eS7+u8$Z{KZB>MpUXO@geCy^0w-qx literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/full.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/full.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_full.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_full.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_none.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_none.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_top.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/male_top.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/meta.json b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/meta.json new file mode 100644 index 00000000000..b44be570c4f --- /dev/null +++ b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/meta.json @@ -0,0 +1,59 @@ +{ + "version": 1, + "copyright": "Rane", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "female_none", + "directions": 4 + }, + { + "name": "female_full", + "directions": 4 + }, + { + "name": "female_top", + "directions": 4 + }, + { + "name": "male_none", + "directions": 4 + }, + { + "name": "male_full", + "directions": 4 + }, + { + "name": "male_top", + "directions": 4 + }, + { + "name": "full", + "directions": 4 + }, + { + "name": "none", + "directions": 4 + }, + { + "name": "top", + "directions": 4 + }, + { + "name": "unisex_full", + "directions": 4 + }, + { + "name": "unisex_none", + "directions": 4 + }, + { + "name": "unisex_top", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/none.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/none.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/top.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/top.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_full.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_full.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_none.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_none.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_top.png b/Resources/Textures/Mobs/Customization/anytaur_masking_helpers.rsi/unisex_top.png new file mode 100644 index 0000000000000000000000000000000000000000..20ccfaa8db4caa19d2762bbb84aa2c4a5dff53bc GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxB}__|Nk$&IsYz@#aI&L z7tG-B>_!@pBjM@d7@~1LIYDC41ABo6PATIylLfUu7#QYSZ1zpv!|)iWhQZU-&t;uc GLK6T%$RK0@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/spidereyes.rsi/eyes.png b/Resources/Textures/Mobs/Customization/spidereyes.rsi/eyes.png new file mode 100644 index 0000000000000000000000000000000000000000..0f2534769440310b1f727cde206dfda449257ab0 GIT binary patch literal 5070 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Species/arachne.rsi/spider_body_front.png b/Resources/Textures/Mobs/Species/arachne.rsi/spider_body_front.png new file mode 100644 index 0000000000000000000000000000000000000000..0171f16fe385fe420c6f1b1142b5381e2284ba92 GIT binary patch literal 761 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSoCO|{#S9F5M?jcysy3fA0|V1U zPZ!6KiaBrZ9`qM>6gmF!{+8Vi+G2O6t>rz%CBtGXu=nNv`4z?wm8z7c6lHq3>b})` z_pMSxSHic)aOSLUzju~yJ9%bb<>#{RUsQlrUqp7-u2zqhRC<9m~TO#Ecr>eQ`KzJ5W|QoS~ZL{9TeyS3u#mg`xS{f-Zc zH_f%!X2A2MsQ$i*T>MNm)e}>_W+zHysQ0bC`&KU}RO?vs)5>*@^G?6XTR*qBXwtFA z8^4=YdN(}xOj>-=W3fqaYOe1jv*p+4EHOEo_W!p@d(eyKnVA+ct77#2PhxNl{A2R0 zdpb*n)NwJE$t#b!F0?ssYWp|XHPA;)WQzDUp%86RR>uVfJSFu&(@%YlIrnpaYQAWK zDo<#?QtY?iRXg^r%WppT=GK>T+b$K|ce7vG#C`gA#G7eO`R;X3=3HIuan6#L?fS0z zL&DF`SgJptqxZXZuI_`E?Ms&2+_L26pSR6=nPzVzKYu(_&xt({v4}Q2eYn;6dH3B| zW8+XQ(Y^MT3$0(+uIJ~^HIX`LxtxFb@_R{-x7O}C6S6jJ>n@>JZ!cb4Q~zoA3IjA^m*&&Ubxth`@pA}Idyk3R@{B*k#_1*$lJK{*`{;6U*7GLyP1~j zeE)L{|Ekc^YTMW^zrDXo+~`y4mgbG+Z|7=u2#??YV$uJ~l}VU>`gfN9lgpc9^P48s h0VVlTBRtc5eHpZXY!0a6OTm*tEKgTImvv4FO#o58J(mCg literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Species/eyes.rsi/eyes.png b/Resources/Textures/Mobs/Species/eyes.rsi/eyes.png new file mode 100644 index 0000000000000000000000000000000000000000..b6250e22b38a17c35b2b54789f66bb5cda1db308 GIT binary patch literal 5553 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Species/eyes.rsi/meta.json b/Resources/Textures/Mobs/Species/eyes.rsi/meta.json new file mode 100644 index 00000000000..a98aba406f1 --- /dev/null +++ b/Resources/Textures/Mobs/Species/eyes.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by @Rane#7518", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "eyes", + "directions": 4 + } + ] +} From 90ba45be54fd83701ecabbd22414a7acb7fe15d9 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 16:35:49 -0400 Subject: [PATCH 11/24] Needed tags --- Resources/Prototypes/Damage/containers.yml | 10 ++++++++++ Resources/Prototypes/Damage/modifier_sets.yml | 13 +++++++++++++ .../Entities/Clothing/OuterClothing/armor.yml | 6 ++++++ .../OuterClothing/base_clothingouter.yml | 1 + .../Entities/Clothing/OuterClothing/suits.yml | 16 +++++++++++++++- Resources/Prototypes/tags.yml | 3 +++ 6 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Damage/containers.yml b/Resources/Prototypes/Damage/containers.yml index fb40e9b658f..b01d22df3b7 100644 --- a/Resources/Prototypes/Damage/containers.yml +++ b/Resources/Prototypes/Damage/containers.yml @@ -52,3 +52,13 @@ id: ShadowHaze supportedTypes: - Heat + +- type: damageContainer + id: HalfSpirit + supportedGroups: + - Burn + - Brute + - Airloss + - Immaterial + supportedTypes: + - Poison diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml index 31dd47a9e16..a6798e39cfe 100644 --- a/Resources/Prototypes/Damage/modifier_sets.yml +++ b/Resources/Prototypes/Damage/modifier_sets.yml @@ -336,3 +336,16 @@ flatReductions: # can't punch the endoskeleton to death Blunt: 5 + +- type: damageModifierSet + id: HalfSpirit + coefficients: + Cold: 0.5 + Shock: 0.75 + Blunt: 0.75 + Slash: 0.75 + Piercing: 0.75 + Heat: 1.25 + Holy: 1.5 + flatReductions: + Cold: 3 diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml index fdaee45ccc8..fb9ebacf92f 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml @@ -138,6 +138,9 @@ Radiation: 0 Caustic: 0.75 - type: GroupExamine + - type: Tag + tags: + - FullBodyOuter - type: entity parent: ClothingOuterArmorHeavy @@ -260,6 +263,9 @@ - type: ExplosionResistance damageCoefficient: 0.5 - type: GroupExamine + - type: Tag + tags: + - FullBodyOuter - type: entity parent: ClothingOuterBaseLarge diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml index 13524efa9e6..6dcdeffdd4a 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml @@ -93,6 +93,7 @@ - Hardsuit - WhitelistChameleon - HidesHarpyWings #DeltaV: Used by harpies to help render their hardsuit sprites + - FullBodyOuter - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index 96a97be87ec..bc1a61b0323 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -22,6 +22,7 @@ tags: - Hardsuit - WhitelistChameleon + - FullBodyOuter - type: entity parent: ClothingOuterBaseLarge @@ -44,6 +45,7 @@ tags: - Hardsuit - WhitelistChameleon + - FullBodyOuter - type: entity parent: ClothingOuterBaseLarge @@ -97,6 +99,9 @@ sprintModifier: 0.8 - type: HeldSpeedModifier - type: GroupExamine + - type: Tag + tags: + - FullBodyOuter - type: entity parent: [ClothingOuterBaseLarge, GeigerCounterClothing] @@ -120,7 +125,10 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} - + - type: Tag + tags: + - FullBodyOuter + - type: entity parent: ClothingOuterBaseLarge id: ClothingOuterSuitSpaceNinja @@ -171,6 +179,9 @@ sprite: Clothing/OuterClothing/Suits/chicken.rsi - type: Clothing sprite: Clothing/OuterClothing/Suits/chicken.rsi + - type: Tag + tags: + - FullBodyOuter - type: entity parent: ClothingOuterBase @@ -196,6 +207,9 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + - type: Tag + tags: + - FullBodyOuter - type: entity parent: ClothingOuterBase diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 1bf002d254e..37201d028ad 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -578,6 +578,9 @@ - type: Tag id: Fruit +- type: Tag + id: FullBodyOuter + - type: Tag id: Galaxythistle From 30d319f04544eb8f01250b29373c02fdf3db5191 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 16:40:59 -0400 Subject: [PATCH 12/24] remove unused usings --- Content.Server/Arachne/ArachneSystem.cs | 13 ------------- Content.Server/Vampire/BloodSuckerSystem.cs | 2 -- .../Injector/BloodSuckerGlandInjectorSystem.cs | 1 - Content.Shared/Arachne/ArachneComponent.cs | 3 --- Content.Shared/Arachne/WebComponent.cs | 1 - 5 files changed, 20 deletions(-) diff --git a/Content.Server/Arachne/ArachneSystem.cs b/Content.Server/Arachne/ArachneSystem.cs index 338ecc21a1a..9cdefb441be 100644 --- a/Content.Server/Arachne/ArachneSystem.cs +++ b/Content.Server/Arachne/ArachneSystem.cs @@ -1,23 +1,17 @@ using Content.Shared.Arachne; using Content.Shared.Actions; -using Content.Shared.Actions.Events; -using Content.Shared.Coordinates.Helpers; using Content.Shared.IdentityManagement; using Content.Shared.Verbs; using Content.Shared.Buckle.Components; -using Content.Shared.Maps; using Content.Shared.DoAfter; -using Content.Shared.Physics; using Content.Shared.Stunnable; using Content.Shared.Eye.Blinding.Systems; -using Content.Shared.Doors.Components; using Content.Shared.Containers.ItemSlots; using Content.Shared.Damage; using Content.Shared.Inventory; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Humanoid; -using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Server.Buckle.Systems; using Content.Server.Popups; @@ -25,7 +19,6 @@ using Content.Server.Body.Components; using Content.Server.Vampiric; using Content.Server.Speech.Components; -using Robust.Shared.Prototypes; using Robust.Shared.Physics.Components; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -36,11 +29,7 @@ namespace Content.Server.Arachne { public sealed class ArachneSystem : EntitySystem { - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly HungerSystem _hungerSystem = default!; - [Dependency] private readonly ThirstSystem _thirstSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly BuckleSystem _buckleSystem = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; @@ -49,9 +38,7 @@ public sealed class ArachneSystem : EntitySystem [Dependency] private readonly IServerConsoleHost _host = default!; [Dependency] private readonly BloodSuckerSystem _bloodSuckerSystem = default!; - [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; private const string BodySlot = "body_slot"; diff --git a/Content.Server/Vampire/BloodSuckerSystem.cs b/Content.Server/Vampire/BloodSuckerSystem.cs index 35f4743e645..a63334a8943 100644 --- a/Content.Server/Vampire/BloodSuckerSystem.cs +++ b/Content.Server/Vampire/BloodSuckerSystem.cs @@ -15,8 +15,6 @@ using Content.Server.DoAfter; using Content.Server.Nutrition.Components; using Robust.Shared.Prototypes; -using Robust.Shared.Player; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Utility; diff --git a/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs index fd9d6fc15ed..d2a92f24be6 100644 --- a/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs +++ b/Content.Server/Vampire/Injector/BloodSuckerGlandInjectorSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Popups; using Content.Shared.Interaction; -using Robust.Shared.Player; namespace Content.Server.Vampiric { diff --git a/Content.Shared/Arachne/ArachneComponent.cs b/Content.Shared/Arachne/ArachneComponent.cs index 8e1dd03b200..04c369cc456 100644 --- a/Content.Shared/Arachne/ArachneComponent.cs +++ b/Content.Shared/Arachne/ArachneComponent.cs @@ -1,7 +1,4 @@ using Robust.Shared.GameStates; -using Robust.Shared.Serialization; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Arachne { diff --git a/Content.Shared/Arachne/WebComponent.cs b/Content.Shared/Arachne/WebComponent.cs index 95c03e5a4a2..c8284f39434 100644 --- a/Content.Shared/Arachne/WebComponent.cs +++ b/Content.Shared/Arachne/WebComponent.cs @@ -1,5 +1,4 @@ using Robust.Shared.GameStates; -using Robust.Shared.Serialization; namespace Content.Shared.Arachne { From 82edb60ac6f872c4605010f5198dd71fa6ab3ec9 Mon Sep 17 00:00:00 2001 From: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:48:48 +0200 Subject: [PATCH 13/24] Languages (#43) Resolves https://github.com/Simple-Station/Einstein-Engines/issues/37 # Description This PR adds languages. Every entity who can speak now speaks a specific language (or Universal, for entities that are not supposed to speak, which is understood by everyone). Other entities who do not understand this language will see gibberish (it's possible to learn how certain induvidual words are spelled. But the spelling changes between rounds). This means that certain creatures, like xenos, cats, vulps, can communicate within their species in their own languages. Similarly, it means that xenos, cats and other things cannot understand GalacticCommon speakers without a translator or cognization. An entity may be able to speak multiple languages, or understand a language but be unable to speak it. Thi PR was orignally made for Frontier but is now being ported and will be maintain here. Orignal PR: https://github.com/new-frontiers-14/frontier-station-14/pull/671 This PR was made orignally by Mnemotechnician and FoxxoTrystan. --- # TODO - [x] Language System. (Check Frontier PR for all the compleated todo list) - [x] Port PR from Frontier. - [x] QOL Changes. - [x] Missing Default Languages. (Missing default langauges for some roundstart species) - [x] Animals Languages. ---

Media

![image](https://github.com/Simple-Station/Einstein-Engines/assets/45297731/fc43efd9-612e-4a6d-8ed6-90a26d315c6f) ![image](https://github.com/Simple-Station/Einstein-Engines/assets/45297731/b86616a3-d5fb-408d-865e-90d09096b6d7) ![image](https://github.com/Simple-Station/Einstein-Engines/assets/45297731/ab1e8581-522d-4e7e-95e8-f62575bc5039)

--- # Changelog :cl: FoxxoTrystan / Mnemotechnician - add: All species can now bring their own cultures and languages --------- Signed-off-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Signed-off-by: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com> Co-authored-by: fox Co-authored-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Co-authored-by: Pspritechologist <81725545+Pspritechologist@users.noreply.github.com> Co-authored-by: Lincoln McQueen Co-authored-by: Arkyfloof Co-authored-by: reese1243 Co-authored-by: VMSolidus Co-authored-by: Eagle-0 <114363363+Eagle-0@users.noreply.github.com> Co-authored-by: BlitzDev <145472107+Reese1243@users.noreply.github.com> Co-authored-by: Arkyfloof <161242062+Arkyfloof@users.noreply.github.com> Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> --- Content.Client/Input/ContentContexts.cs | 1 + .../Language/LanguageMenuWindow.xaml | 18 + .../Language/LanguageMenuWindow.xaml.cs | 134 +++++ .../Language/Systems/LanguageSystem.cs | 76 +++ .../Systems/TranslatorImplanterSystem.cs | 8 + .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 1 + .../Systems/Chat/Controls/ChatInputBox.cs | 1 + .../Language/LanguageMenuUIController.cs | 89 ++++ .../MenuBar/GameTopMenuBarUIController.cs | 4 + .../MenuBar/Widgets/GameTopMenuBar.xaml | 10 + Content.Server/Chat/Systems/ChatSystem.cs | 153 ++++-- .../Chemistry/ReagentEffects/MakeSentient.cs | 20 +- .../Language/Commands/ListLanguagesCommand.cs | 39 ++ .../Language/Commands/SayLanguageCommand.cs | 53 ++ .../Commands/SelectLanguageCommand.cs | 48 ++ .../Language/DetermineEntityLanguagesEvent.cs | 29 ++ .../Language/LanguageSystem.Networking.cs | 59 +++ Content.Server/Language/LanguageSystem.cs | 289 ++++++++++ .../Language/TranslatorImplanterSystem.cs | 72 +++ Content.Server/Language/TranslatorSystem.cs | 225 ++++++++ .../Mind/Commands/MakeSentientCommand.cs | 10 + .../Radio/EntitySystems/HeadsetSystem.cs | 16 +- .../Radio/EntitySystems/RadioDeviceSystem.cs | 5 +- .../Radio/EntitySystems/RadioSystem.cs | 66 ++- Content.Server/Radio/RadioEvent.cs | 14 +- .../Speech/EntitySystems/ListeningSystem.cs | 7 +- Content.Shared/Input/ContentKeyFunctions.cs | 1 + .../Components/LanguageSpeakerComponent.cs | 29 ++ .../TranslatorImplanterComponent.cs | 35 ++ .../Translators/BaseTranslatorComponent.cs | 47 ++ .../HandheldTranslatorComponent.cs | 15 + .../Translators/HoldsTranslatorComponent.cs | 11 + .../ImplantedTranslatorComponent.cs | 9 + .../IntrinsicTranslatorComponent.cs | 10 + .../UniversalLanguageSpeakerComponent.cs | 11 + .../Language/Events/LanguagesSetMessage.cs | 13 + .../Language/Events/LanguagesUpdateEvent.cs | 8 + .../Events/LanguagesUpdatedMessage.cs | 15 + .../Events/RequestLanguagesMessage.cs | 10 + Content.Shared/Language/LanguagePrototype.cs | 37 ++ .../Language/Systems/SharedLanguageSystem.cs | 39 ++ .../SharedTranslatorImplanterSystem.cs | 36 ++ .../Systems/SharedTranslatorSystem.cs | 34 ++ Resources/Locale/en-US/language/commands.ftl | 8 + .../Locale/en-US/language/language-menu.ftl | 4 + Resources/Locale/en-US/language/languages.ftl | 71 +++ .../Locale/en-US/language/technologies.ftl | 2 + .../Locale/en-US/language/translator.ftl | 8 + .../DeltaV/Entities/Mobs/NPCs/animals.yml | 13 +- .../DeltaV/Entities/Mobs/NPCs/familiars.yml | 5 + .../DeltaV/Entities/Mobs/NPCs/nukiemouse.yml | 10 +- .../DeltaV/Entities/Mobs/Species/harpy.yml | 13 +- .../Entities/Mobs/Species/vulpkanin.yml | 7 + .../Mobs/Cyborgs/base_borg_chassis.yml | 7 + .../Prototypes/Entities/Mobs/NPCs/animals.yml | 129 ++++- .../Entities/Mobs/NPCs/argocyte.yml | 7 +- .../Prototypes/Entities/Mobs/NPCs/pets.yml | 65 ++- .../Entities/Mobs/NPCs/regalrat.yml | 13 + .../Entities/Mobs/NPCs/revenant.yml | 1 + .../Prototypes/Entities/Mobs/NPCs/shadows.yml | 11 +- .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 7 + .../Prototypes/Entities/Mobs/NPCs/slimes.yml | 7 +- .../Prototypes/Entities/Mobs/NPCs/space.yml | 23 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 12 + .../Entities/Mobs/Player/observer.yml | 1 + .../Entities/Mobs/Player/replay_observer.yml | 1 + .../Prototypes/Entities/Mobs/Species/base.yml | 9 +- .../Entities/Mobs/Species/diona.yml | 7 + .../Entities/Mobs/Species/dwarf.yml | 7 + .../Entities/Mobs/Species/human.yml | 7 + .../Prototypes/Entities/Mobs/Species/moth.yml | 7 + .../Entities/Mobs/Species/reptilian.yml | 7 + .../Entities/Mobs/Species/slime.yml | 7 + .../Objects/Devices/translator_implants.yml | 132 +++++ .../Entities/Objects/Devices/translators.yml | 205 ++++++++ .../Entities/Structures/Machines/lathe.yml | 18 + .../Structures/Machines/vending_machines.yml | 7 + Resources/Prototypes/Language/languages.yml | 493 ++++++++++++++++++ .../Nyanotrasen/Entities/Mobs/Species/Oni.yml | 7 + .../Entities/Mobs/Species/felinid.yml | 9 + .../Prototypes/Recipes/Lathes/language.yml | 190 +++++++ .../Prototypes/Research/civilianservices.yml | 40 ++ Resources/Textures/Interface/language.png | Bin 0 -> 739 bytes .../Objects/Devices/translator.rsi/icon.png | Bin 0 -> 278 bytes .../Objects/Devices/translator.rsi/meta.json | 17 + .../Devices/translator.rsi/translator.png | Bin 0 -> 202 bytes Resources/keybinds.yml | 3 + 87 files changed, 3270 insertions(+), 134 deletions(-) create mode 100644 Content.Client/Language/LanguageMenuWindow.xaml create mode 100644 Content.Client/Language/LanguageMenuWindow.xaml.cs create mode 100644 Content.Client/Language/Systems/LanguageSystem.cs create mode 100644 Content.Client/Language/Systems/TranslatorImplanterSystem.cs create mode 100644 Content.Client/UserInterface/Systems/Language/LanguageMenuUIController.cs create mode 100644 Content.Server/Language/Commands/ListLanguagesCommand.cs create mode 100644 Content.Server/Language/Commands/SayLanguageCommand.cs create mode 100644 Content.Server/Language/Commands/SelectLanguageCommand.cs create mode 100644 Content.Server/Language/DetermineEntityLanguagesEvent.cs create mode 100644 Content.Server/Language/LanguageSystem.Networking.cs create mode 100644 Content.Server/Language/LanguageSystem.cs create mode 100644 Content.Server/Language/TranslatorImplanterSystem.cs create mode 100644 Content.Server/Language/TranslatorSystem.cs create mode 100644 Content.Shared/Language/Components/LanguageSpeakerComponent.cs create mode 100644 Content.Shared/Language/Components/TranslatorImplanterComponent.cs create mode 100644 Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs create mode 100644 Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs create mode 100644 Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs create mode 100644 Content.Shared/Language/Components/Translators/ImplantedTranslatorComponent.cs create mode 100644 Content.Shared/Language/Components/Translators/IntrinsicTranslatorComponent.cs create mode 100644 Content.Shared/Language/Components/UniversalLanguageSpeakerComponent.cs create mode 100644 Content.Shared/Language/Events/LanguagesSetMessage.cs create mode 100644 Content.Shared/Language/Events/LanguagesUpdateEvent.cs create mode 100644 Content.Shared/Language/Events/LanguagesUpdatedMessage.cs create mode 100644 Content.Shared/Language/Events/RequestLanguagesMessage.cs create mode 100644 Content.Shared/Language/LanguagePrototype.cs create mode 100644 Content.Shared/Language/Systems/SharedLanguageSystem.cs create mode 100644 Content.Shared/Language/Systems/SharedTranslatorImplanterSystem.cs create mode 100644 Content.Shared/Language/Systems/SharedTranslatorSystem.cs create mode 100644 Resources/Locale/en-US/language/commands.ftl create mode 100644 Resources/Locale/en-US/language/language-menu.ftl create mode 100644 Resources/Locale/en-US/language/languages.ftl create mode 100644 Resources/Locale/en-US/language/technologies.ftl create mode 100644 Resources/Locale/en-US/language/translator.ftl create mode 100644 Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml create mode 100644 Resources/Prototypes/Entities/Objects/Devices/translators.yml create mode 100644 Resources/Prototypes/Language/languages.yml create mode 100644 Resources/Prototypes/Recipes/Lathes/language.yml create mode 100644 Resources/Textures/Interface/language.png create mode 100644 Resources/Textures/Objects/Devices/translator.rsi/icon.png create mode 100644 Resources/Textures/Objects/Devices/translator.rsi/meta.json create mode 100644 Resources/Textures/Objects/Devices/translator.rsi/translator.png diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 03f4f3f38b7..fa631938100 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -55,6 +55,7 @@ public static void SetupContexts(IInputContextContainer contexts) human.AddFunction(ContentKeyFunctions.UseItemInHand); human.AddFunction(ContentKeyFunctions.AltUseItemInHand); human.AddFunction(ContentKeyFunctions.OpenCharacterMenu); + human.AddFunction(ContentKeyFunctions.OpenLanguageMenu); human.AddFunction(ContentKeyFunctions.ActivateItemInWorld); human.AddFunction(ContentKeyFunctions.ThrowItemInHand); human.AddFunction(ContentKeyFunctions.AltActivateItemInWorld); diff --git a/Content.Client/Language/LanguageMenuWindow.xaml b/Content.Client/Language/LanguageMenuWindow.xaml new file mode 100644 index 00000000000..ff33a6ddf56 --- /dev/null +++ b/Content.Client/Language/LanguageMenuWindow.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/Content.Client/Language/LanguageMenuWindow.xaml.cs b/Content.Client/Language/LanguageMenuWindow.xaml.cs new file mode 100644 index 00000000000..312814aca35 --- /dev/null +++ b/Content.Client/Language/LanguageMenuWindow.xaml.cs @@ -0,0 +1,134 @@ +using Content.Client.Language.Systems; +using Content.Shared.Language; +using Content.Shared.Language.Systems; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Console; +using Robust.Shared.Utility; +using Serilog; +using static Content.Shared.Language.Systems.SharedLanguageSystem; + +namespace Content.Client.Language; + +[GenerateTypedNameReferences] +public sealed partial class LanguageMenuWindow : DefaultWindow +{ + private readonly LanguageSystem _clientLanguageSystem; + private readonly List _entries = new(); + + + public LanguageMenuWindow() + { + RobustXamlLoader.Load(this); + _clientLanguageSystem = IoCManager.Resolve().GetEntitySystem(); + } + + protected override void Opened() + { + // Refresh the window when it gets opened. + // This actually causes two refreshes: one immediately, and one after the server sends a state message. + UpdateState(_clientLanguageSystem.CurrentLanguage, _clientLanguageSystem.SpokenLanguages); + _clientLanguageSystem.RequestStateUpdate(); + } + + + public void UpdateState(string currentLanguage, List spokenLanguages) + { + var langName = Loc.GetString($"language-{currentLanguage}-name"); + CurrentLanguageLabel.Text = Loc.GetString("language-menu-current-language", ("language", langName)); + + OptionsList.RemoveAllChildren(); + _entries.Clear(); + + foreach (var language in spokenLanguages) + { + AddLanguageEntry(language); + } + + // Disable the button for the currently chosen language + foreach (var entry in _entries) + { + if (entry.button != null) + entry.button.Disabled = entry.language == currentLanguage; + } + } + + private void AddLanguageEntry(string language) + { + var proto = _clientLanguageSystem.GetLanguagePrototype(language); + var state = new EntryState { language = language }; + + var container = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical }; + + #region Header + var header = new BoxContainer + { + Orientation = BoxContainer.LayoutOrientation.Horizontal, + HorizontalExpand = true, + SeparationOverride = 2 + }; + + var name = new Label + { + Text = proto?.Name ?? Loc.GetString("generic-error"), + MinWidth = 50, + HorizontalExpand = true + }; + + var button = new Button { Text = "Choose" }; + button.OnPressed += _ => OnLanguageChosen(language); + state.button = button; + + header.AddChild(name); + header.AddChild(button); + + container.AddChild(header); + #endregion + + #region Collapsible description + var body = new CollapsibleBody + { + HorizontalExpand = true, + Margin = new Thickness(4f, 4f) + }; + + var description = new RichTextLabel { HorizontalExpand = true }; + description.SetMessage(proto?.Description ?? Loc.GetString("generic-error")); + body.AddChild(description); + + var collapser = new Collapsible(Loc.GetString("language-menu-description-header"), body) + { + Orientation = BoxContainer.LayoutOrientation.Vertical, + HorizontalExpand = true + }; + + container.AddChild(collapser); + #endregion + + // Before adding, wrap the new container in a PanelContainer to give it a distinct look + var wrapper = new PanelContainer(); + wrapper.StyleClasses.Add("PdaBorderRect"); + + wrapper.AddChild(container); + OptionsList.AddChild(wrapper); + + _entries.Add(state); + } + + + private void OnLanguageChosen(string id) + { + var proto = _clientLanguageSystem.GetLanguagePrototype(id); + if (proto != null) + _clientLanguageSystem.RequestSetLanguage(proto); + } + + + private struct EntryState + { + public string language; + public Button? button; + } +} diff --git a/Content.Client/Language/Systems/LanguageSystem.cs b/Content.Client/Language/Systems/LanguageSystem.cs new file mode 100644 index 00000000000..9714078b2c5 --- /dev/null +++ b/Content.Client/Language/Systems/LanguageSystem.cs @@ -0,0 +1,76 @@ +using Content.Shared.Language; +using Content.Shared.Language.Events; +using Content.Shared.Language.Systems; +using Robust.Client; +using Robust.Shared.Console; + +namespace Content.Client.Language.Systems; + +/// +/// Client-side language system. +/// +/// +/// Unlike the server, the client is not aware of other entities' languages; it's only notified about the entity that it posesses. +/// Due to that, this system stores such information in a static manner. +/// +public sealed class LanguageSystem : SharedLanguageSystem +{ + [Dependency] private readonly IBaseClient _client = default!; + + /// + /// The current language of the entity currently possessed by the player. + /// + public string CurrentLanguage { get; private set; } = default!; + /// + /// The list of languages the currently possessed entity can speak. + /// + public List SpokenLanguages { get; private set; } = new(); + /// + /// The list of languages the currently possessed entity can understand. + /// + public List UnderstoodLanguages { get; private set; } = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnLanguagesUpdated); + _client.RunLevelChanged += OnRunLevelChanged; + } + + private void OnLanguagesUpdated(LanguagesUpdatedMessage message) + { + CurrentLanguage = message.CurrentLanguage; + SpokenLanguages = message.Spoken; + UnderstoodLanguages = message.Understood; + } + + private void OnRunLevelChanged(object? sender, RunLevelChangedEventArgs args) + { + // Request an update when entering a game + if (args.NewLevel == ClientRunLevel.InGame) + RequestStateUpdate(); + } + + /// + /// Sends a network request to the server to update this system's state. + /// The server may ignore the said request if the player is not possessing an entity. + /// + public void RequestStateUpdate() + { + RaiseNetworkEvent(new RequestLanguagesMessage()); + } + + public void RequestSetLanguage(LanguagePrototype language) + { + if (language.ID == CurrentLanguage) + return; + + RaiseNetworkEvent(new LanguagesSetMessage(language.ID)); + + // May cause some minor desync... + // So to reduce the probability of desync, we replicate the change locally too + if (SpokenLanguages.Contains(language.ID)) + CurrentLanguage = language.ID; + } +} diff --git a/Content.Client/Language/Systems/TranslatorImplanterSystem.cs b/Content.Client/Language/Systems/TranslatorImplanterSystem.cs new file mode 100644 index 00000000000..da19b3decf9 --- /dev/null +++ b/Content.Client/Language/Systems/TranslatorImplanterSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Language.Systems; + +namespace Content.Client.Language.Systems; + +public sealed class TranslatorImplanterSystem : SharedTranslatorImplanterSystem +{ + +} diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index f0537079b97..49e8099e0fb 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -212,6 +212,7 @@ void AddCheckBox(string checkBoxName, bool currentState, Action, IOnStateExited +{ + public LanguageMenuWindow? LanguageWindow; + private MenuButton? LanguageButton => UIManager.GetActiveUIWidgetOrNull()?.LanguageButton; + + public override void Initialize() + { + SubscribeNetworkEvent((LanguagesUpdatedMessage message, EntitySessionEventArgs _) => + LanguageWindow?.UpdateState(message.CurrentLanguage, message.Spoken)); + } + + public void OnStateEntered(GameplayState state) + { + DebugTools.Assert(LanguageWindow == null); + + LanguageWindow = UIManager.CreateWindow(); + LayoutContainer.SetAnchorPreset(LanguageWindow, LayoutContainer.LayoutPreset.CenterTop); + + CommandBinds.Builder.Bind(ContentKeyFunctions.OpenLanguageMenu, + InputCmdHandler.FromDelegate(_ => ToggleWindow())).Register(); + } + + public void OnStateExited(GameplayState state) + { + if (LanguageWindow != null) + { + LanguageWindow.Dispose(); + LanguageWindow = null; + } + + CommandBinds.Unregister(); + } + + public void UnloadButton() + { + if (LanguageButton == null) + return; + + LanguageButton.OnPressed -= LanguageButtonPressed; + } + + public void LoadButton() + { + if (LanguageButton == null) + return; + + LanguageButton.OnPressed += LanguageButtonPressed; + + if (LanguageWindow == null) + return; + + LanguageWindow.OnClose += () => LanguageButton.Pressed = false; + LanguageWindow.OnOpen += () => LanguageButton.Pressed = true; + } + + private void LanguageButtonPressed(ButtonEventArgs args) + { + ToggleWindow(); + } + + private void ToggleWindow() + { + if (LanguageWindow == null) + return; + + if (LanguageButton != null) + LanguageButton.SetClickPressed(!LanguageWindow.IsOpen); + + if (LanguageWindow.IsOpen) + LanguageWindow.Close(); + else + LanguageWindow.Open(); + } +} diff --git a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs index 1505db48a79..156fa63884e 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs +++ b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs @@ -9,6 +9,7 @@ using Content.Client.UserInterface.Systems.MenuBar.Widgets; using Content.Client.UserInterface.Systems.Sandbox; using Robust.Client.UserInterface.Controllers; +using Content.Client.UserInterface.Systems.Language; namespace Content.Client.UserInterface.Systems.MenuBar; @@ -22,6 +23,7 @@ public sealed class GameTopMenuBarUIController : UIController [Dependency] private readonly ActionUIController _action = default!; [Dependency] private readonly SandboxUIController _sandbox = default!; [Dependency] private readonly GuidebookUIController _guidebook = default!; + [Dependency] private readonly LanguageMenuUIController _language = default!; private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull(); @@ -44,6 +46,7 @@ public void UnloadButtons() _ahelp.UnloadButton(); _action.UnloadButton(); _sandbox.UnloadButton(); + _language.UnloadButton(); } public void LoadButtons() @@ -56,5 +59,6 @@ public void LoadButtons() _ahelp.LoadButton(); _action.LoadButton(); _sandbox.LoadButton(); + _language.LoadButton(); } } diff --git a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml index 3c8cd1d164f..a76943ace85 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml +++ b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml @@ -63,6 +63,16 @@ HorizontalExpand="True" AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}" /> + (source)) @@ -249,10 +255,10 @@ public void TrySendInGameICMessage( switch (desiredType) { case InGameICChatType.Speak: - SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker); + SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker, languageOverride: languageOverride); break; case InGameICChatType.Whisper: - SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker); + SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker, languageOverride: languageOverride); break; case InGameICChatType.Emote: SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); @@ -382,12 +388,14 @@ private void SendEntitySpeak( ChatTransmitRange range, string? nameOverride, bool hideLog = false, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + LanguagePrototype? languageOverride = null ) { if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) return; + // The original message var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage)); if (message.Length == 0) @@ -411,18 +419,19 @@ private void SendEntitySpeak( speech = proto; } - name = FormattedMessage.EscapeText(name); + var language = languageOverride ?? _language.GetLanguage(source); - var wrappedMessage = Loc.GetString(speech.Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message", - ("entityName", name), - ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), - ("fontType", speech.FontId), - ("fontSize", speech.FontSize), - ("message", FormattedMessage.EscapeText(message))); + name = FormattedMessage.EscapeText(name); + // The chat message wrapped in a "x says y" string + var wrappedMessage = WrapPublicMessage(source, name, message); + // The chat message obfuscated via language obfuscation + var obfuscated = SanitizeInGameICMessage(source, _language.ObfuscateSpeech(message, language), out var emoteStr, true, _configurationManager.GetCVar(CCVars.ChatPunctuation), (!CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Parent.Name == "en") || (CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Name == "en")); + // The language-obfuscated message wrapped in a "x says y" string + var wrappedObfuscated = WrapPublicMessage(source, name, obfuscated); - SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, range); + SendInVoiceRange(ChatChannel.Local, name, message, wrappedMessage, obfuscated, wrappedObfuscated, source, range, languageOverride: language); - var ev = new EntitySpokeEvent(source, message, null, null); + var ev = new EntitySpokeEvent(source, message, null, false, language); RaiseLocalEvent(source, ev, true); // To avoid logging any messages sent by entities that are not players, like vendors, cloning, etc. @@ -455,7 +464,8 @@ private void SendEntityWhisper( RadioChannelPrototype? channel, string? nameOverride, bool hideLog = false, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + LanguagePrototype? languageOverride = null ) { if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) @@ -465,8 +475,6 @@ private void SendEntityWhisper( if (message.Length == 0) return; - var obfuscatedMessage = ObfuscateMessageReadability(message, 0.2f); - // get the entity's name by visual identity (if no override provided). string nameIdentity = FormattedMessage.EscapeText(nameOverride ?? Identity.Name(source, EntityManager)); // get the entity's name by voice (if no override provided). @@ -483,41 +491,57 @@ private void SendEntityWhisper( } name = FormattedMessage.EscapeText(name); - var wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", - ("entityName", name), ("message", FormattedMessage.EscapeText(message))); - - var wrappedobfuscatedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", - ("entityName", nameIdentity), ("message", FormattedMessage.EscapeText(obfuscatedMessage))); - - var wrappedUnknownMessage = Loc.GetString("chat-manager-entity-whisper-unknown-wrap-message", - ("message", FormattedMessage.EscapeText(obfuscatedMessage))); - + var language = languageOverride ?? _language.GetLanguage(source); + var languageObfuscatedMessage = SanitizeInGameICMessage(source, _language.ObfuscateSpeech(message, language), out var emoteStr, true, _configurationManager.GetCVar(CCVars.ChatPunctuation), (!CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Parent.Name == "en") || (CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Name == "en")); foreach (var (session, data) in GetRecipients(source, WhisperMuffledRange)) { - EntityUid listener; - - if (session.AttachedEntity is not { Valid: true } playerEntity) + if (session.AttachedEntity is not { Valid: true } listener) continue; - listener = session.AttachedEntity.Value; if (MessageRangeCheck(session, data, range) != MessageRangeCheckResult.Full) continue; // Won't get logged to chat, and ghosts are too far away to see the pop-up, so we just won't send it to them. + var canUnderstandLanguage = _language.CanUnderstand(listener, language); + // How the entity perceives the message depends on whether it can understand its language + var perceivedMessage = canUnderstandLanguage ? message : languageObfuscatedMessage; + + // Result is the intermediate message derived from the perceived one via obfuscation + // Wrapped message is the result wrapped in an "x says y" string + string result, wrappedMessage; if (data.Range <= WhisperClearRange) - _chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.Channel); - //If listener is too far, they only hear fragments of the message - //Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind - else if (_interactionSystem.InRangeUnobstructed(source, listener, WhisperMuffledRange, Shared.Physics.CollisionGroup.Opaque)) //Shared.Physics.CollisionGroup.Opaque - _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.Channel); - //If listener is too far and has no line of sight, they can't identify the whisperer's identity + { + // Scenario 1: the listener can clearly understand the message + result = perceivedMessage; + wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", + ("entityName", name), + ("message", FormattedMessage.EscapeText(result))); + } + else if (_interactionSystem.InRangeUnobstructed(source, listener, WhisperMuffledRange, Shared.Physics.CollisionGroup.Opaque)) + { + // Scenerio 2: if the listener is too far, they only hear fragments of the message + // Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind + result = ObfuscateMessageReadability(perceivedMessage); + wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", + ("entityName", nameIdentity), ("message", FormattedMessage.EscapeText(result))); + } else - _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedUnknownMessage, source, false, session.Channel); + { + // Scenario 3: If listener is too far and has no line of sight, they can't identify the whisperer's identity + result = ObfuscateMessageReadability(perceivedMessage); + wrappedMessage = Loc.GetString("chat-manager-entity-whisper-unknown-wrap-message", + ("message", FormattedMessage.EscapeText(result))); + } + + _chatManager.ChatMessageToOne(ChatChannel.Whisper, result, wrappedMessage, source, false, session.Channel); } - _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); + var replayWrap = Loc.GetString("chat-manager-entity-whisper-wrap-message", + ("entityName", name), + ("message", FormattedMessage.EscapeText(message))); + _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, replayWrap, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); - var ev = new EntitySpokeEvent(source, message, channel, obfuscatedMessage); + var ev = new EntitySpokeEvent(source, message, channel, true, language); RaiseLocalEvent(source, ev, true); if (!hideLog) if (originalMessage == message) @@ -564,7 +588,7 @@ private void SendEntityEmote( if (checkEmote) TryEmoteChatInput(source, action); - SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author); + SendInVoiceRange(ChatChannel.Emotes, name, action, wrappedMessage, obfuscated: "", obfuscatedWrappedMessage: "", source, range, author); if (!hideLog) if (name != Name(source)) _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user} as {name}: {action}"); @@ -591,7 +615,13 @@ private void SendLOOC(EntityUid source, ICommonSession player, string message, b ("entityName", name), ("message", FormattedMessage.EscapeText(message))); - SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, player.UserId); + SendInVoiceRange(ChatChannel.LOOC, name, message, wrappedMessage, + obfuscated: string.Empty, + obfuscatedWrappedMessage: string.Empty, // will be skipped anyway + source, + hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, + player.UserId, + languageOverride: LanguageSystem.Universal); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}"); } @@ -672,15 +702,29 @@ private MessageRangeCheckResult MessageRangeCheck(ICommonSession session, ICChat /// /// Sends a chat message to the given players in range of the source entity. /// - private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null) + private void SendInVoiceRange(ChatChannel channel, string name, string message, string wrappedMessage, string obfuscated, string obfuscatedWrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null, LanguagePrototype? languageOverride = null) { + var language = languageOverride ?? _language.GetLanguage(source); foreach (var (session, data) in GetRecipients(source, VoiceRange)) { var entRange = MessageRangeCheck(session, data, range); if (entRange == MessageRangeCheckResult.Disallowed) continue; var entHideChat = entRange == MessageRangeCheckResult.HideChat; - _chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.Channel, author: author); + if (session.AttachedEntity is not { Valid: true } playerEntity) + continue; + EntityUid listener = session.AttachedEntity.Value; + + + // If the channel does not support languages, or the entity can understand the message, send the original message, otherwise send the obfuscated version + if (channel == ChatChannel.LOOC || channel == ChatChannel.Emotes || _language.CanUnderstand(listener, language)) + { + _chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.Channel, author: author); + } + else + { + _chatManager.ChatMessageToOne(channel, obfuscated, obfuscatedWrappedMessage, source, entHideChat, session.Channel, author: author); + } } _replay.RecordServerMessage(new ChatMessage(channel, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); @@ -790,6 +834,21 @@ public string SanitizeMessageReplaceWords(string message) return msg; } + /// + /// Wraps a message sent by the specified entity into an "x says y" string. + /// + public string WrapPublicMessage(EntityUid source, string name, string message) + { + var speech = GetSpeechVerb(source, message); + var verbName = Loc.GetString(_random.Pick(speech.SpeechVerbStrings)); + return Loc.GetString(speech.Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message", + ("entityName", name), + ("verb", verbName), + ("fontType", speech.FontId), + ("fontSize", speech.FontSize), + ("message", FormattedMessage.EscapeText(message))); + } + /// /// Returns list of players and ranges for all players withing some range. Also returns observers with a range of -1. /// @@ -836,7 +895,7 @@ public readonly record struct ICChatRecipientData(float Range, bool Observer, bo { } - private string ObfuscateMessageReadability(string message, float chance) + public string ObfuscateMessageReadability(string message, float chance = DefaultObfuscationFactor) { var modifiedMessage = new StringBuilder(message); @@ -925,7 +984,8 @@ public sealed class EntitySpokeEvent : EntityEventArgs { public readonly EntityUid Source; public readonly string Message; - public readonly string? ObfuscatedMessage; // not null if this was a whisper + public readonly bool IsWhisper; + public readonly LanguagePrototype Language; /// /// If the entity was trying to speak into a radio, this was the channel they were trying to access. If a radio @@ -933,12 +993,13 @@ public sealed class EntitySpokeEvent : EntityEventArgs /// public RadioChannelPrototype? Channel; - public EntitySpokeEvent(EntityUid source, string message, RadioChannelPrototype? channel, string? obfuscatedMessage) + public EntitySpokeEvent(EntityUid source, string message, RadioChannelPrototype? channel, bool isWhisper, LanguagePrototype language) { Source = source; Message = message; Channel = channel; - ObfuscatedMessage = obfuscatedMessage; + IsWhisper = isWhisper; + Language = language; } } diff --git a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs index bf7691fe375..da16529d515 100644 --- a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs +++ b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs @@ -1,10 +1,14 @@ +using System.Linq; using Content.Server.Ghost.Roles.Components; using Content.Server.Speech.Components; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Language; +using Content.Shared.Language.Systems; using Content.Shared.Mind.Components; using Robust.Shared.Prototypes; using Content.Server.Psionics; //Nyano - Summary: pulls in the ability for the sentient creature to become psionic. using Content.Shared.Humanoid; //Delta-V - Banning humanoids from becoming ghost roles. +using Content.Shared.Language.Events; namespace Content.Server.Chemistry.ReagentEffects; @@ -24,6 +28,20 @@ public override void Effect(ReagentEffectArgs args) entityManager.RemoveComponent(uid); entityManager.RemoveComponent(uid); + var speaker = entityManager.EnsureComponent(uid); + var fallback = SharedLanguageSystem.FallbackLanguagePrototype; + + if (!speaker.UnderstoodLanguages.Contains(fallback)) + speaker.UnderstoodLanguages.Add(fallback); + + if (!speaker.SpokenLanguages.Contains(fallback)) + { + speaker.CurrentLanguage = fallback; + speaker.SpokenLanguages.Add(fallback); + } + + args.EntityManager.EventBus.RaiseLocalEvent(uid, new LanguagesUpdateEvent(), true); + // Stops from adding a ghost role to things like people who already have a mind if (entityManager.TryGetComponent(uid, out var mindContainer) && mindContainer.HasMind) { @@ -47,7 +65,7 @@ public override void Effect(ReagentEffectArgs args) ghostRole = entityManager.AddComponent(uid); entityManager.EnsureComponent(uid); - entityManager.EnsureComponent(uid); //Nyano - Summary:. Makes the animated body able to get psionics. + entityManager.EnsureComponent(uid); //Nyano - Summary:. Makes the animated body able to get psionics. var entityData = entityManager.GetComponent(uid); ghostRole.RoleName = entityData.EntityName; diff --git a/Content.Server/Language/Commands/ListLanguagesCommand.cs b/Content.Server/Language/Commands/ListLanguagesCommand.cs new file mode 100644 index 00000000000..6698e1b6453 --- /dev/null +++ b/Content.Server/Language/Commands/ListLanguagesCommand.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Language.Commands; + +[AnyCommand] +public sealed class ListLanguagesCommand : IConsoleCommand +{ + public string Command => "languagelist"; + public string Description => Loc.GetString("command-list-langs-desc"); + public string Help => Loc.GetString("command-list-langs-help", ("command", Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server")); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not {} playerEntity) + { + shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity")); + return; + } + + var languages = IoCManager.Resolve().GetEntitySystem(); + + var (spokenLangs, knownLangs) = languages.GetAllLanguages(playerEntity); + + shell.WriteLine("Spoken:\n" + string.Join("\n", spokenLangs)); + shell.WriteLine("Understood:\n" + string.Join("\n", knownLangs)); + } +} diff --git a/Content.Server/Language/Commands/SayLanguageCommand.cs b/Content.Server/Language/Commands/SayLanguageCommand.cs new file mode 100644 index 00000000000..2e4a27b1dcc --- /dev/null +++ b/Content.Server/Language/Commands/SayLanguageCommand.cs @@ -0,0 +1,53 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Language.Commands; + +[AnyCommand] +public sealed class SayLanguageCommand : IConsoleCommand +{ + public string Command => "saylang"; + public string Description => Loc.GetString("command-saylang-desc"); + public string Help => Loc.GetString("command-saylang-help", ("command", Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server")); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not {} playerEntity) + { + shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity")); + return; + } + + if (args.Length < 2) + return; + + var languageId = args[0]; + var message = string.Join(" ", args, startIndex: 1, count: args.Length - 1).Trim(); + + if (string.IsNullOrEmpty(message)) + return; + + var languages = IoCManager.Resolve().GetEntitySystem(); + var chats = IoCManager.Resolve().GetEntitySystem(); + + var language = languages.GetLanguagePrototype(languageId); + if (language == null || !languages.CanSpeak(playerEntity, language.ID)) + { + shell.WriteError($"Language {languageId} is invalid or you cannot speak it!"); + return; + } + + chats.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Speak, ChatTransmitRange.Normal, false, shell, player, languageOverride: language); + } +} diff --git a/Content.Server/Language/Commands/SelectLanguageCommand.cs b/Content.Server/Language/Commands/SelectLanguageCommand.cs new file mode 100644 index 00000000000..e3363846539 --- /dev/null +++ b/Content.Server/Language/Commands/SelectLanguageCommand.cs @@ -0,0 +1,48 @@ +using System.Linq; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Language.Commands; + +[AnyCommand] +public sealed class SelectLanguageCommand : IConsoleCommand +{ + public string Command => "languageselect"; + public string Description => Loc.GetString("command-language-select-desc"); + public string Help => Loc.GetString("command-language-select-help", ("command", Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server")); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not { } playerEntity) + { + shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity")); + return; + } + + if (args.Length < 1) + return; + + var languageId = args[0]; + + var languages = IoCManager.Resolve().GetEntitySystem(); + + var language = languages.GetLanguagePrototype(languageId); + if (language == null || !languages.CanSpeak(playerEntity, language.ID)) + { + shell.WriteError($"Language {languageId} is invalid or you cannot speak it!"); + return; + } + + languages.SetLanguage(playerEntity, language.ID); + } +} diff --git a/Content.Server/Language/DetermineEntityLanguagesEvent.cs b/Content.Server/Language/DetermineEntityLanguagesEvent.cs new file mode 100644 index 00000000000..13ab2cac279 --- /dev/null +++ b/Content.Server/Language/DetermineEntityLanguagesEvent.cs @@ -0,0 +1,29 @@ +namespace Content.Server.Language; + +/// +/// Raised in order to determine the language an entity speaks at the current moment, +/// as well as the list of all languages the entity may speak and understand. +/// +public sealed class DetermineEntityLanguagesEvent : EntityEventArgs +{ + /// + /// The default language of this entity. If empty, remain unchanged. + /// This field has no effect if the entity decides to speak in a concrete language. + /// + public string CurrentLanguage; + /// + /// The list of all languages the entity may speak. Must NOT be held as a reference! + /// + public List SpokenLanguages; + /// + /// The list of all languages the entity may understand. Must NOT be held as a reference! + /// + public List UnderstoodLanguages; + + public DetermineEntityLanguagesEvent(string currentLanguage, List spokenLanguages, List understoodLanguages) + { + CurrentLanguage = currentLanguage; + SpokenLanguages = spokenLanguages; + UnderstoodLanguages = understoodLanguages; + } +} diff --git a/Content.Server/Language/LanguageSystem.Networking.cs b/Content.Server/Language/LanguageSystem.Networking.cs new file mode 100644 index 00000000000..7517b4185e3 --- /dev/null +++ b/Content.Server/Language/LanguageSystem.Networking.cs @@ -0,0 +1,59 @@ +using Content.Server.Mind; +using Content.Shared.Language; +using Content.Shared.Language.Events; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Robust.Shared.Player; + +namespace Content.Server.Language; + +/// +/// LanguageSystem Networking +/// This is used to update client state when mind change entity. +/// + +public sealed partial class LanguageSystem +{ + [Dependency] private readonly MindSystem _mind = default!; + + + public void InitializeNet() + { + // Refresh the client's state when its mind hops to a different entity + SubscribeLocalEvent((uid, _, _) => SendLanguageStateToClient(uid)); + SubscribeLocalEvent((_, _, args) => + { + if (args.Mind.Comp.Session != null) + SendLanguageStateToClient(args.Mind.Comp.Session); + }); + + SubscribeLocalEvent((uid, comp, _) => SendLanguageStateToClient(uid, comp)); + SubscribeNetworkEvent((_, session) => SendLanguageStateToClient(session.SenderSession)); + } + + + private void SendLanguageStateToClient(EntityUid uid, LanguageSpeakerComponent? comp = null) + { + // Try to find a mind inside the entity and notify its session + if (!_mind.TryGetMind(uid, out _, out var mindComp) || mindComp.Session == null) + return; + + SendLanguageStateToClient(uid, mindComp.Session, comp); + } + + private void SendLanguageStateToClient(ICommonSession session, LanguageSpeakerComponent? comp = null) + { + // Try to find an entity associated with the session and resolve the languages from it + if (session.AttachedEntity is not { Valid: true } entity) + return; + + SendLanguageStateToClient(entity, session, comp); + } + + private void SendLanguageStateToClient(EntityUid uid, ICommonSession session, LanguageSpeakerComponent? component = null) + { + var langs = GetLanguages(uid, component); + var message = new LanguagesUpdatedMessage(langs.CurrentLanguage, langs.SpokenLanguages, langs.UnderstoodLanguages); + RaiseNetworkEvent(message, session); + } +} diff --git a/Content.Server/Language/LanguageSystem.cs b/Content.Server/Language/LanguageSystem.cs new file mode 100644 index 00000000000..f1bf44c1f4f --- /dev/null +++ b/Content.Server/Language/LanguageSystem.cs @@ -0,0 +1,289 @@ +using System.Linq; +using System.Text; +using Content.Server.GameTicking.Events; +using Content.Shared.Language; +using Content.Shared.Language.Events; +using Content.Shared.Language.Systems; +using Robust.Shared.Random; +using UniversalLanguageSpeakerComponent = Content.Shared.Language.Components.UniversalLanguageSpeakerComponent; + +namespace Content.Server.Language; + +public sealed partial class LanguageSystem : SharedLanguageSystem +{ + // Static and re-used event instances used to minimize memory allocations during language processing, which can happen many times per tick. + // These are used in the method GetLanguages and returned from it. They should never be mutated outside of that method or returned outside this system. + private readonly DetermineEntityLanguagesEvent + _determineLanguagesEvent = new(string.Empty, new(), new()), + _universalLanguagesEvent = new(UniversalPrototype, [UniversalPrototype], [UniversalPrototype]); // Returned for universal speakers only + + /// + /// A random number added to each pseudo-random number's seed. Changes every round. + /// + public int RandomRoundSeed { get; private set; } + + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(OnClientSetLanguage); + SubscribeLocalEvent(OnInitLanguageSpeaker); + SubscribeLocalEvent(_ => RandomRoundSeed = _random.Next()); + + InitializeNet(); + } + + + #region public api + /// + /// Obfuscate a message using an entity's default language. + /// + public string ObfuscateSpeech(EntityUid source, string message) + { + var language = GetLanguage(source) ?? Universal; + return ObfuscateSpeech(message, language); + } + + /// + /// Obfuscate a message using the given language. + /// + public string ObfuscateSpeech(string message, LanguagePrototype language) + { + var builder = new StringBuilder(); + if (language.ObfuscateSyllables) + ObfuscateSyllables(builder, message, language); + else + ObfuscatePhrases(builder, message, language); + + return builder.ToString(); + } + + public bool CanUnderstand(EntityUid listener, LanguagePrototype language, LanguageSpeakerComponent? listenerLanguageComp = null) + { + if (language.ID == UniversalPrototype || HasComp(listener)) + return true; + + var listenerLanguages = GetLanguages(listener, listenerLanguageComp)?.UnderstoodLanguages; + + return listenerLanguages?.Contains(language.ID, StringComparer.Ordinal) ?? false; + } + + public bool CanSpeak(EntityUid speaker, string language, LanguageSpeakerComponent? speakerComp = null) + { + if (HasComp(speaker)) + return true; + + var langs = GetLanguages(speaker, speakerComp)?.UnderstoodLanguages; + return langs?.Contains(language, StringComparer.Ordinal) ?? false; + } + + /// + /// Returns the current language of the given entity. + /// Assumes Universal if not specified. + /// + public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent? languageComp = null) + { + var id = GetLanguages(speaker, languageComp)?.CurrentLanguage; + if (id == null) + return Universal; // Fallback + + _prototype.TryIndex(id, out LanguagePrototype? proto); + + return proto ?? Universal; + } + + public void SetLanguage(EntityUid speaker, string language, LanguageSpeakerComponent? languageComp = null) + { + if (!CanSpeak(speaker, language) || HasComp(speaker)) + return; + + if (languageComp == null && !TryComp(speaker, out languageComp)) + return; + + if (languageComp.CurrentLanguage == language) + return; + + languageComp.CurrentLanguage = language; + + RaiseLocalEvent(speaker, new LanguagesUpdateEvent(), true); + } + + /// + /// Adds a new language to the lists of understood and/or spoken languages of the given component. + /// + public void AddLanguage(LanguageSpeakerComponent comp, string language, bool addSpoken = true, bool addUnderstood = true) + { + if (addSpoken && !comp.SpokenLanguages.Contains(language)) + comp.SpokenLanguages.Add(language); + + if (addUnderstood && !comp.UnderstoodLanguages.Contains(language)) + comp.UnderstoodLanguages.Add(language); + + RaiseLocalEvent(comp.Owner, new LanguagesUpdateEvent(), true); + } + + public (List spoken, List understood) GetAllLanguages(EntityUid speaker) + { + var languages = GetLanguages(speaker); + // The lists need to be copied because the internal ones are re-used for performance reasons. + return (new List(languages.SpokenLanguages), new List(languages.UnderstoodLanguages)); + } + + /// + /// Ensures the given entity has a valid language as its current language. + /// If not, sets it to the first entry of its SpokenLanguages list, or universal if it's empty. + /// + public void EnsureValidLanguage(EntityUid entity, LanguageSpeakerComponent? comp = null) + { + if (comp == null && !TryComp(entity, out comp)) + return; + + var langs = GetLanguages(entity, comp); + if (!langs.SpokenLanguages.Contains(comp!.CurrentLanguage, StringComparer.Ordinal)) + { + comp.CurrentLanguage = langs.SpokenLanguages.FirstOrDefault(UniversalPrototype); + RaiseLocalEvent(comp.Owner, new LanguagesUpdateEvent(), true); + } + } + #endregion + + #region event handling + private void OnInitLanguageSpeaker(EntityUid uid, LanguageSpeakerComponent component, ComponentInit args) + { + if (string.IsNullOrEmpty(component.CurrentLanguage)) + component.CurrentLanguage = component.SpokenLanguages.FirstOrDefault(UniversalPrototype); + } + #endregion + + #region internal api - obfuscation + private void ObfuscateSyllables(StringBuilder builder, string message, LanguagePrototype language) + { + // Go through each word. Calculate its hash sum and count the number of letters. + // Replicate it with pseudo-random syllables of pseudo-random (but similar) length. Use the hash code as the seed. + // This means that identical words will be obfuscated identically. Simple words like "hello" or "yes" in different langs can be memorized. + var wordBeginIndex = 0; + var hashCode = 0; + for (var i = 0; i < message.Length; i++) + { + var ch = char.ToLower(message[i]); + // A word ends when one of the following is found: a space, a sentence end, or EOM + if (char.IsWhiteSpace(ch) || IsSentenceEnd(ch) || i == message.Length - 1) + { + var wordLength = i - wordBeginIndex; + if (wordLength > 0) + { + var newWordLength = PseudoRandomNumber(hashCode, 1, 4); + + for (var j = 0; j < newWordLength; j++) + { + var index = PseudoRandomNumber(hashCode + j, 0, language.Replacement.Count); + builder.Append(language.Replacement[index]); + } + } + + builder.Append(ch); + hashCode = 0; + wordBeginIndex = i + 1; + } + else + hashCode = hashCode * 31 + ch; + } + } + + private void ObfuscatePhrases(StringBuilder builder, string message, LanguagePrototype language) + { + // In a similar manner, each phrase is obfuscated with a random number of conjoined obfuscation phrases. + // However, the number of phrases depends on the number of characters in the original phrase. + var sentenceBeginIndex = 0; + for (var i = 0; i < message.Length; i++) + { + var ch = char.ToLower(message[i]); + if (IsSentenceEnd(ch) || i == message.Length - 1) + { + var length = i - sentenceBeginIndex; + if (length > 0) + { + var newLength = (int) Math.Clamp(Math.Cbrt(length) - 1, 1, 4); // 27+ chars for 2 phrases, 64+ for 3, 125+ for 4. + + for (var j = 0; j < newLength; j++) + { + var phrase = _random.Pick(language.Replacement); + builder.Append(phrase); + } + } + sentenceBeginIndex = i + 1; + + if (IsSentenceEnd(ch)) + builder.Append(ch).Append(" "); + } + } + } + + private static bool IsSentenceEnd(char ch) + { + return ch is '.' or '!' or '?'; + } + #endregion + + #region internal api - misc + /// + /// Dynamically resolves the current language of the entity and the list of all languages it speaks. + /// + /// If the entity is not a language speaker, or is a universal language speaker, then it's assumed to speak Universal, + /// aka all languages at once and none at the same time. + /// + /// + /// The returned event is reused and thus must not be held as a reference anywhere but inside the caller function. + /// + private DetermineEntityLanguagesEvent GetLanguages(EntityUid speaker, LanguageSpeakerComponent? comp = null) + { + // This is a shortcut for ghosts and entities that should not speak normally (admemes) + if (HasComp(speaker) || !TryComp(speaker, out comp)) + return _universalLanguagesEvent; + + var ev = _determineLanguagesEvent; + ev.SpokenLanguages.Clear(); + ev.UnderstoodLanguages.Clear(); + + ev.CurrentLanguage = comp.CurrentLanguage; + ev.SpokenLanguages.AddRange(comp.SpokenLanguages); + ev.UnderstoodLanguages.AddRange(comp.UnderstoodLanguages); + + RaiseLocalEvent(speaker, ev, true); + + if (ev.CurrentLanguage.Length == 0) + ev.CurrentLanguage = !string.IsNullOrEmpty(comp.CurrentLanguage) ? comp.CurrentLanguage : UniversalPrototype; // Fall back to account for admemes like admins possessing a bread + return ev; + } + + /// + /// Generates a stable pseudo-random number in the range (min, max) for the given seed. + /// Each input seed corresponds to exactly one random number. + /// + private int PseudoRandomNumber(int seed, int min, int max) + { + // This is not a uniform distribution, but it shouldn't matter given there's 2^31 possible random numbers, + // the bias of this function should be so tiny it will never be noticed. + seed += RandomRoundSeed; + var random = ((seed * 1103515245) + 12345) & 0x7fffffff; // Source: http://cs.uccs.edu/~cs591/bufferOverflow/glibc-2.2.4/stdlib/random_r.c + return random % (max - min) + min; + } + + /// + /// Set CurrentLanguage of the client, the client must be able to Understand the language requested. + /// + private void OnClientSetLanguage(LanguagesSetMessage message, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not {Valid: true} speaker) + return; + + var language = GetLanguagePrototype(message.CurrentLanguage); + + if (language == null || !CanSpeak(speaker, language.ID)) + return; + + SetLanguage(speaker, language.ID); + } + #endregion +} diff --git a/Content.Server/Language/TranslatorImplanterSystem.cs b/Content.Server/Language/TranslatorImplanterSystem.cs new file mode 100644 index 00000000000..1e0c13375e4 --- /dev/null +++ b/Content.Server/Language/TranslatorImplanterSystem.cs @@ -0,0 +1,72 @@ +using System.Linq; +using Content.Server.Administration.Logs; +using Content.Server.Popups; +using Content.Shared.Database; +using Content.Shared.Interaction; +using Content.Shared.Language; +using Content.Shared.Language.Components; +using Content.Shared.Language.Events; +using Content.Shared.Language.Systems; +using Content.Shared.Mobs.Components; +using Content.Shared.Language.Components.Translators; + +namespace Content.Server.Language; + +public sealed class TranslatorImplanterSystem : SharedTranslatorImplanterSystem +{ + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly LanguageSystem _language = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnImplant); + } + + + private void OnImplant(EntityUid implanter, TranslatorImplanterComponent component, AfterInteractEvent args) + { + if (component.Used || !args.CanReach || args.Target is not { Valid: true } target) + return; + + if (!TryComp(target, out var speaker)) + return; + + if (component.MobsOnly && !HasComp(target)) + { + _popup.PopupEntity("translator-implanter-refuse", component.Owner); + return; + } + + var understood = _language.GetAllLanguages(target).understood; + if (component.RequiredLanguages.Count > 0 && !component.RequiredLanguages.Any(lang => understood.Contains(lang))) + { + _popup.PopupEntity(Loc.GetString("translator-implanter-refuse", + ("implanter", implanter), ("target", target)), implanter); + return; + } + + var intrinsic = EnsureComp(target); + intrinsic.Enabled = true; + + foreach (var lang in component.SpokenLanguages.Where(lang => !intrinsic.SpokenLanguages.Contains(lang))) + intrinsic.SpokenLanguages.Add(lang); + + foreach (var lang in component.UnderstoodLanguages.Where(lang => !intrinsic.UnderstoodLanguages.Contains(lang))) + intrinsic.UnderstoodLanguages.Add(lang); + + component.Used = true; + _popup.PopupEntity(Loc.GetString("translator-implanter-success", + ("implanter", implanter), ("target", target)), implanter); + + _adminLogger.Add(LogType.Action, LogImpact.Medium, + $"{ToPrettyString(args.User):player} used {ToPrettyString(implanter):implanter} to give {ToPrettyString(target):target} the following languages:" + + $"\nSpoken: {string.Join(", ", component.SpokenLanguages)}; Understood: {string.Join(", ", component.UnderstoodLanguages)}"); + + OnAppearanceChange(implanter, component); + RaiseLocalEvent(target, new LanguagesUpdateEvent(), true); + } +} diff --git a/Content.Server/Language/TranslatorSystem.cs b/Content.Server/Language/TranslatorSystem.cs new file mode 100644 index 00000000000..3b7704b9a71 --- /dev/null +++ b/Content.Server/Language/TranslatorSystem.cs @@ -0,0 +1,225 @@ +using System.Linq; +using Content.Server.Popups; +using Content.Server.PowerCell; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Language; +using Content.Shared.Language.Events; +using Content.Shared.Language.Systems; +using Content.Shared.PowerCell; +using Content.Shared.Language.Components.Translators; + +namespace Content.Server.Language; + +// This does not support holding multiple translators at once. +// That shouldn't be an issue for now, but it needs to be fixed later. +public sealed class TranslatorSystem : SharedTranslatorSystem +{ + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly LanguageSystem _language = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; + + public override void Initialize() + { + base.Initialize(); + + // I wanna die. But my death won't help us discover polymorphism. + SubscribeLocalEvent(OnDetermineLanguages); + SubscribeLocalEvent(OnDetermineLanguages); + SubscribeLocalEvent(OnDetermineLanguages); + + SubscribeLocalEvent(OnTranslatorToggle); + SubscribeLocalEvent(OnPowerCellSlotEmpty); + + // TODO: why does this use InteractHandEvent?? + SubscribeLocalEvent(OnTranslatorInteract); + SubscribeLocalEvent(OnTranslatorDropped); + } + + private void OnDetermineLanguages(EntityUid uid, IntrinsicTranslatorComponent component, + DetermineEntityLanguagesEvent ev) + { + if (!component.Enabled) + return; + + if (!_powerCell.HasActivatableCharge(uid)) + return; + + var addUnderstood = true; + var addSpoken = true; + if (component.RequiredLanguages.Count > 0) + { + if (component.RequiresAllLanguages) + { + // Add langs when the wielder has all of the required languages + foreach (var language in component.RequiredLanguages) + { + if (!ev.SpokenLanguages.Contains(language, StringComparer.Ordinal)) + addSpoken = false; + + if (!ev.UnderstoodLanguages.Contains(language, StringComparer.Ordinal)) + addUnderstood = false; + } + } + else + { + // Add langs when the wielder has at least one of the required languages + addUnderstood = false; + addSpoken = false; + foreach (var language in component.RequiredLanguages) + { + if (ev.SpokenLanguages.Contains(language, StringComparer.Ordinal)) + addSpoken = true; + + if (ev.UnderstoodLanguages.Contains(language, StringComparer.Ordinal)) + addUnderstood = true; + } + } + } + + if (addSpoken) + { + foreach (var language in component.SpokenLanguages) + AddIfNotExists(ev.SpokenLanguages, language); + + if (component.DefaultLanguageOverride != null && ev.CurrentLanguage.Length == 0) + ev.CurrentLanguage = component.DefaultLanguageOverride; + } + + if (addUnderstood) + foreach (var language in component.UnderstoodLanguages) + AddIfNotExists(ev.UnderstoodLanguages, language); + } + + private void OnTranslatorInteract( EntityUid translator, HandheldTranslatorComponent component, InteractHandEvent args) + { + var holder = args.User; + if (!EntityManager.HasComponent(holder)) + return; + + var intrinsic = EnsureComp(holder); + UpdateBoundIntrinsicComp(component, intrinsic, component.Enabled); + + RaiseLocalEvent(holder, new LanguagesUpdateEvent(), true); + } + + private void OnTranslatorDropped(EntityUid translator, HandheldTranslatorComponent component, DroppedEvent args) + { + var holder = args.User; + if (!EntityManager.TryGetComponent(holder, out var intrinsic)) + return; + + if (intrinsic.Issuer == component) + { + intrinsic.Enabled = false; + RemCompDeferred(holder, intrinsic); + } + + _language.EnsureValidLanguage(holder); + + RaiseLocalEvent(holder, new LanguagesUpdateEvent(), true); + } + + private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponent component, ActivateInWorldEvent args) + { + if (!component.ToggleOnInteract) + return; + + var hasPower = _powerCell.HasDrawCharge(translator); + + if (Transform(args.Target).ParentUid is { Valid: true } holder + && EntityManager.HasComponent(holder)) + { + // This translator is held by a language speaker and thus has an intrinsic counterpart bound to it. + // Make sure it's up-to-date. + var intrinsic = EnsureComp(holder); + var isEnabled = !component.Enabled; + if (intrinsic.Issuer != component) + { + // The intrinsic comp wasn't owned by this handheld component, so this comp wasn't the active translator. + // Thus it needs to be turned on regardless of its previous state. + intrinsic.Issuer = component; + isEnabled = true; + } + + isEnabled &= hasPower; + UpdateBoundIntrinsicComp(component, intrinsic, isEnabled); + component.Enabled = isEnabled; + _powerCell.SetPowerCellDrawEnabled(translator, isEnabled); + + _language.EnsureValidLanguage(holder); + RaiseLocalEvent(holder, new LanguagesUpdateEvent(), true); + } + else + { + // This is a standalone translator (e.g. lying on the ground), toggle its state. + component.Enabled = !component.Enabled && hasPower; + _powerCell.SetPowerCellDrawEnabled(translator, !component.Enabled && hasPower); + } + + OnAppearanceChange(translator, component); + + // HasPower shows a popup when there's no power, so we do not proceed in that case + if (hasPower) + { + var message = Loc.GetString( + component.Enabled + ? "translator-component-turnon" + : "translator-component-shutoff", + ("translator", component.Owner)); + _popup.PopupEntity(message, component.Owner, args.User); + } + } + + private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorComponent component, PowerCellSlotEmptyEvent args) + { + component.Enabled = false; + _powerCell.SetPowerCellDrawEnabled(translator, false); + OnAppearanceChange(translator, component); + + if (Transform(translator).ParentUid is { Valid: true } holder + && EntityManager.HasComponent(holder)) + { + if (!EntityManager.TryGetComponent(holder, out var intrinsic)) + return; + + if (intrinsic.Issuer == component) + { + intrinsic.Enabled = false; + EntityManager.RemoveComponent(holder, intrinsic); + } + + _language.EnsureValidLanguage(holder); + RaiseLocalEvent(holder, new LanguagesUpdateEvent(), true); + } + } + + /// + /// Copies the state from the handheld to the intrinsic component + /// + private void UpdateBoundIntrinsicComp(HandheldTranslatorComponent comp, HoldsTranslatorComponent intrinsic, bool isEnabled) + { + if (isEnabled) + { + intrinsic.SpokenLanguages = new List(comp.SpokenLanguages); + intrinsic.UnderstoodLanguages = new List(comp.UnderstoodLanguages); + intrinsic.DefaultLanguageOverride = comp.DefaultLanguageOverride; + } + else + { + intrinsic.SpokenLanguages.Clear(); + intrinsic.UnderstoodLanguages.Clear(); + intrinsic.DefaultLanguageOverride = null; + } + + intrinsic.Enabled = isEnabled; + intrinsic.Issuer = comp; + } + + private static void AddIfNotExists(List list, string item) + { + if (list.Contains(item)) + return; + list.Add(item); + } +} diff --git a/Content.Server/Mind/Commands/MakeSentientCommand.cs b/Content.Server/Mind/Commands/MakeSentientCommand.cs index 5e19d135b6f..cacd499ab8d 100644 --- a/Content.Server/Mind/Commands/MakeSentientCommand.cs +++ b/Content.Server/Mind/Commands/MakeSentientCommand.cs @@ -1,7 +1,10 @@ using Content.Server.Administration; +using Content.Server.Language; using Content.Shared.Administration; using Content.Shared.Emoting; using Content.Shared.Examine; +using Content.Shared.Language; +using Content.Shared.Language.Systems; using Content.Shared.Mind.Components; using Content.Shared.Movement.Components; using Content.Shared.Speech; @@ -55,6 +58,13 @@ public static void MakeSentient(EntityUid uid, IEntityManager entityManager, boo { entityManager.EnsureComponent(uid); entityManager.EnsureComponent(uid); + + var language = IoCManager.Resolve().GetEntitySystem(); + var speaker = entityManager.EnsureComponent(uid); + // If the speaker knows any language (like monkey or robot), they keep those + // Otherwise, we give them the fallback + if (speaker.SpokenLanguages.Count == 0) + language.AddLanguage(speaker, SharedLanguageSystem.FallbackLanguagePrototype); } entityManager.EnsureComponent(uid); diff --git a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs index d18b044205c..53517da6cb4 100644 --- a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs +++ b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs @@ -1,6 +1,9 @@ using Content.Server.Chat.Systems; using Content.Server.Emp; +using Content.Server.Language; using Content.Server.Radio.Components; +using Content.Server.Speech; +using Content.Shared.Chat; using Content.Shared.Inventory.Events; using Content.Shared.Radio; using Content.Shared.Radio.Components; @@ -14,6 +17,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem { [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly LanguageSystem _language = default!; public override void Initialize() { @@ -99,8 +103,16 @@ public void SetEnabled(EntityUid uid, bool value, HeadsetComponent? component = private void OnHeadsetReceive(EntityUid uid, HeadsetComponent component, ref RadioReceiveEvent args) { - if (TryComp(Transform(uid).ParentUid, out ActorComponent? actor)) - _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel); + var parent = Transform(uid).ParentUid; + if (TryComp(parent, out ActorComponent? actor)) + { + var canUnderstand = _language.CanUnderstand(parent, args.Language); + var msg = new MsgChatMessage + { + Message = canUnderstand ? args.OriginalChatMsg : args.LanguageObfuscatedChatMsg + }; + _netMan.ServerSendMessage(msg, actor.PlayerSession.Channel); + } } private void OnEmpPulse(EntityUid uid, HeadsetComponent component, ref EmpPulseEvent args) diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index ace7d8ae31a..fc3f69a3ba2 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Server.Interaction; +using Content.Server.Language; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -29,6 +30,7 @@ public sealed class RadioDeviceSystem : EntitySystem [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly LanguageSystem _language = default!; // Used to prevent a shitter from using a bunch of radios to spam chat. private HashSet<(string, EntityUid)> _recentlySent = new(); @@ -208,7 +210,8 @@ private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref ("originalName", nameEv.Name)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios - _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false); + var message = args.OriginalChatMsg.Message; // The chat system will handle the rest and re-obfuscate if needed. + _chat.TrySendInGameICMessage(uid, message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false, languageOverride: args.Language); } private void OnBeforeIntercomUiOpen(EntityUid uid, IntercomComponent component, BeforeActivatableUIOpenEvent args) diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index e2a61b5022b..60aa7c2f4fb 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -1,10 +1,13 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; +using Content.Server.Language; using Content.Server.Power.Components; using Content.Server.Radio.Components; +using Content.Server.Speech; using Content.Server.VoiceMask; using Content.Shared.Chat; using Content.Shared.Database; +using Content.Shared.Language; using Content.Shared.Radio; using Content.Shared.Radio.Components; using Content.Shared.Speech; @@ -29,6 +32,7 @@ public sealed class RadioSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly LanguageSystem _language = default!; // set used to prevent radio feedback loops. private readonly HashSet _messages = new(); @@ -44,7 +48,7 @@ private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent { if (args.Channel != null && component.Channels.Contains(args.Channel.ID)) { - SendRadioMessage(uid, args.Message, args.Channel, uid); + SendRadioMessage(uid, args.Message, args.Channel, uid, args.Language); args.Channel = null; // prevent duplicate messages from other listeners. } } @@ -52,15 +56,23 @@ private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args) { if (TryComp(uid, out ActorComponent? actor)) - _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel); + { + // Einstein-Engines - languages mechanic + var listener = component.Owner; + var msg = args.OriginalChatMsg; + if (listener != null && !_language.CanUnderstand(listener, args.Language)) + msg = args.LanguageObfuscatedChatMsg; + + _netMan.ServerSendMessage(new MsgChatMessage { Message = msg}, actor.PlayerSession.Channel); + } } /// /// Send radio message to all active radio listeners /// - public void SendRadioMessage(EntityUid messageSource, string message, ProtoId channel, EntityUid radioSource, bool escapeMarkup = true) + public void SendRadioMessage(EntityUid messageSource, string message, ProtoId channel, EntityUid radioSource, LanguagePrototype? language = null, bool escapeMarkup = true) { - SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup); + SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup, language: language); } ///
@@ -68,8 +80,11 @@ public void SendRadioMessage(EntityUid messageSource, string message, ProtoId /// Entity that spoke the message /// Entity that picked up the message and will send it, e.g. headset - public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true) + public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, LanguagePrototype? language = null, bool escapeMarkup = true) { + if (language == null) + language = _language.GetLanguage(messageSource); + // TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this. if (!_messages.Add(message)) return; @@ -84,6 +99,7 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann name = FormattedMessage.EscapeText(name); + // most radios are relayed to chat, so lets parse the chat message beforehand SpeechVerbPrototype speech; if (mask != null && mask.Enabled @@ -99,24 +115,15 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann ? FormattedMessage.EscapeText(message) : message; - var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap", - ("color", channel.Color), - ("fontType", speech.FontId), - ("fontSize", speech.FontSize), - ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), - ("channel", $"\\[{channel.LocalizedName}\\]"), - ("name", name), - ("message", content)); + var wrappedMessage = WrapRadioMessage(messageSource, channel, name, content); + var msg = new ChatMessage(ChatChannel.Radio, content, wrappedMessage, NetEntity.Invalid, null); - // most radios are relayed to chat, so lets parse the chat message beforehand - var chat = new ChatMessage( - ChatChannel.Radio, - message, - wrappedMessage, - NetEntity.Invalid, - null); - var chatMsg = new MsgChatMessage { Message = chat }; - var ev = new RadioReceiveEvent(message, messageSource, channel, chatMsg); + // ... you guess it + var obfuscated = _language.ObfuscateSpeech(content, language); + var obfuscatedWrapped = WrapRadioMessage(messageSource, channel, name, obfuscated); + var notUdsMsg = new ChatMessage(ChatChannel.Radio, obfuscated, obfuscatedWrapped, NetEntity.Invalid, null); + + var ev = new RadioReceiveEvent(messageSource, channel, msg, notUdsMsg, language); var sendAttemptEv = new RadioSendAttemptEvent(channel, radioSource); RaiseLocalEvent(ref sendAttemptEv); @@ -162,10 +169,23 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann else _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} on {channel.LocalizedName}: {message}"); - _replay.RecordServerMessage(chat); + _replay.RecordServerMessage(msg); _messages.Remove(message); } + private string WrapRadioMessage(EntityUid source, RadioChannelPrototype channel, string name, string message) + { + var speech = _chat.GetSpeechVerb(source, message); + return Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap", + ("color", channel.Color), + ("fontType", speech.FontId), + ("fontSize", speech.FontSize), + ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), + ("channel", $"\\[{channel.LocalizedName}\\]"), + ("name", name), + ("message", FormattedMessage.EscapeText(message))); + } + /// private bool HasActiveServer(MapId mapId, string channelId) { diff --git a/Content.Server/Radio/RadioEvent.cs b/Content.Server/Radio/RadioEvent.cs index 69d764ffe67..35220d1d757 100644 --- a/Content.Server/Radio/RadioEvent.cs +++ b/Content.Server/Radio/RadioEvent.cs @@ -1,10 +1,22 @@ using Content.Shared.Chat; +using Content.Shared.Language; using Content.Shared.Radio; namespace Content.Server.Radio; +/// +/// The message to display when the speaker can understand "language" +/// The message to display when the speaker cannot understand "language" +/// [ByRefEvent] -public readonly record struct RadioReceiveEvent(string Message, EntityUid MessageSource, RadioChannelPrototype Channel, MsgChatMessage ChatMsg); +public readonly record struct RadioReceiveEvent( + // Einstein-Engines - languages mechanic + EntityUid MessageSource, + RadioChannelPrototype Channel, + ChatMessage OriginalChatMsg, + ChatMessage LanguageObfuscatedChatMsg, + LanguagePrototype Language +); /// /// Use this event to cancel sending message per receiver diff --git a/Content.Server/Speech/EntitySystems/ListeningSystem.cs b/Content.Server/Speech/EntitySystems/ListeningSystem.cs index ea3569e055c..f2a625600ca 100644 --- a/Content.Server/Speech/EntitySystems/ListeningSystem.cs +++ b/Content.Server/Speech/EntitySystems/ListeningSystem.cs @@ -8,6 +8,7 @@ namespace Content.Server.Speech.EntitySystems; /// public sealed class ListeningSystem : EntitySystem { + [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly SharedTransformSystem _xforms = default!; public override void Initialize() @@ -18,10 +19,10 @@ public override void Initialize() private void OnSpeak(EntitySpokeEvent ev) { - PingListeners(ev.Source, ev.Message, ev.ObfuscatedMessage); + PingListeners(ev.Source, ev.Message, ev.IsWhisper); } - public void PingListeners(EntityUid source, string message, string? obfuscatedMessage) + public void PingListeners(EntityUid source, string message, bool isWhisper) { // TODO whispering / audio volume? Microphone sensitivity? // for now, whispering just arbitrarily reduces the listener's max range. @@ -32,7 +33,7 @@ public void PingListeners(EntityUid source, string message, string? obfuscatedMe var attemptEv = new ListenAttemptEvent(source); var ev = new ListenEvent(message, source); - var obfuscatedEv = obfuscatedMessage == null ? null : new ListenEvent(obfuscatedMessage, source); + var obfuscatedEv = !isWhisper ? null : new ListenEvent(_chat.ObfuscateMessageReadability(message), source); var query = EntityQueryEnumerator(); while(query.MoveNext(out var listenerUid, out var listener, out var xform)) diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index ee4a4e9023b..0b72ac5ee74 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -25,6 +25,7 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction CycleChatChannelBackward = "CycleChatChannelBackward"; public static readonly BoundKeyFunction EscapeContext = "EscapeContext"; public static readonly BoundKeyFunction OpenCharacterMenu = "OpenCharacterMenu"; + public static readonly BoundKeyFunction OpenLanguageMenu = "OpenLanguageMenu"; public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu"; public static readonly BoundKeyFunction OpenGuidebook = "OpenGuidebook"; public static readonly BoundKeyFunction OpenInventoryMenu = "OpenInventoryMenu"; diff --git a/Content.Shared/Language/Components/LanguageSpeakerComponent.cs b/Content.Shared/Language/Components/LanguageSpeakerComponent.cs new file mode 100644 index 00000000000..95232ffe6ff --- /dev/null +++ b/Content.Shared/Language/Components/LanguageSpeakerComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Language; + +[RegisterComponent, AutoGenerateComponentState] +public sealed partial class LanguageSpeakerComponent : Component +{ + /// + /// The current language the entity may use to speak. + /// Other listeners will hear the entity speak in this language. + /// + [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public string CurrentLanguage = default!; + + /// + /// List of languages this entity can speak. + /// + [ViewVariables] + [DataField("speaks", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] + public List SpokenLanguages = new(); + + /// + /// List of languages this entity can understand. + /// + [ViewVariables] + [DataField("understands", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] + public List UnderstoodLanguages = new(); +} diff --git a/Content.Shared/Language/Components/TranslatorImplanterComponent.cs b/Content.Shared/Language/Components/TranslatorImplanterComponent.cs new file mode 100644 index 00000000000..401e8a8b8aa --- /dev/null +++ b/Content.Shared/Language/Components/TranslatorImplanterComponent.cs @@ -0,0 +1,35 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Language.Components; + +/// +/// An item that, when used on a mob, adds an intrinsic translator to it. +/// +[RegisterComponent] +public sealed partial class TranslatorImplanterComponent : Component +{ + [DataField("spoken", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List SpokenLanguages = new(); + + [DataField("understood", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List UnderstoodLanguages = new(); + + /// + /// The list of languages the mob must understand in order for this translator to have effect. + /// Knowing one language is enough. + /// + [DataField("requires", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List RequiredLanguages = new(); + + /// + /// If true, only allows to use this implanter on mobs. + /// + [DataField] + public bool MobsOnly = true; + + /// + /// Whether this implant has been used already. + /// + [DataField] + public bool Used = false; +} diff --git a/Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs new file mode 100644 index 00000000000..a66c9be082e --- /dev/null +++ b/Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs @@ -0,0 +1,47 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Language.Components.Translators; + +public abstract partial class BaseTranslatorComponent : Component +{ + // TODO may need to be removed completely, it's a part of legacy code that never ended up being used. + /// + /// The language this translator changes the speaker's language to when they don't specify one. + /// If null, does not modify the default language. + /// + [DataField("defaultLanguage")] + [ViewVariables(VVAccess.ReadWrite)] + public string? DefaultLanguageOverride = null; + + /// + /// The list of additional languages this translator allows the wielder to speak. + /// + [DataField("spoken", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List SpokenLanguages = new(); + + /// + /// The list of additional languages this translator allows the wielder to understand. + /// + [DataField("understood", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List UnderstoodLanguages = new(); + + /// + /// The languages the wielding MUST know in order for this translator to have effect. + /// The field [RequiresAllLanguages] indicates whether all of them are required, or just one. + /// + [DataField("requires", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List RequiredLanguages = new(); + + /// + /// If true, the wielder must understand all languages in [RequiredLanguages] to speak [SpokenLanguages], + /// and understand all languages in [RequiredLanguages] to understand [UnderstoodLanguages]. + /// + /// Otherwise, at least one language must be known (or the list must be empty). + /// + [DataField("requiresAll")] + [ViewVariables(VVAccess.ReadWrite)] + public bool RequiresAllLanguages = false; + + [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)] + public bool Enabled = true; +} diff --git a/Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs new file mode 100644 index 00000000000..f900603f01d --- /dev/null +++ b/Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Shared.Language.Components.Translators; + +/// +/// A translator that must be held in a hand or a pocket of an entity in order ot have effect. +/// +[RegisterComponent] +public sealed partial class HandheldTranslatorComponent : Translators.BaseTranslatorComponent +{ + /// + /// Whether or not interacting with this translator + /// toggles it on or off. + /// + [DataField("toggleOnInteract")] + public bool ToggleOnInteract = true; +} diff --git a/Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs new file mode 100644 index 00000000000..caea9b9a948 --- /dev/null +++ b/Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Language.Components.Translators; + +/// +/// Applied internally to the holder of [HandheldTranslatorComponent]. +/// Do not use directly. Use [HandheldTranslatorComponent] instead. +/// +[RegisterComponent] +public sealed partial class HoldsTranslatorComponent : IntrinsicTranslatorComponent +{ + public Component? Issuer = null; +} diff --git a/Content.Shared/Language/Components/Translators/ImplantedTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/ImplantedTranslatorComponent.cs new file mode 100644 index 00000000000..d1d72e83ed7 --- /dev/null +++ b/Content.Shared/Language/Components/Translators/ImplantedTranslatorComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Language.Components.Translators; + +/// +/// Applied to entities who were injected with a translator implant. +/// +[RegisterComponent] +public sealed partial class ImplantedTranslatorComponent : IntrinsicTranslatorComponent +{ +} diff --git a/Content.Shared/Language/Components/Translators/IntrinsicTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/IntrinsicTranslatorComponent.cs new file mode 100644 index 00000000000..d8def4ac1de --- /dev/null +++ b/Content.Shared/Language/Components/Translators/IntrinsicTranslatorComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Language.Components.Translators; + +/// +/// A translator attached to an entity that translates its speech. +/// An example is a translator implant that allows the speaker to speak another language. +/// +[RegisterComponent, Virtual] +public partial class IntrinsicTranslatorComponent : Translators.BaseTranslatorComponent +{ +} diff --git a/Content.Shared/Language/Components/UniversalLanguageSpeakerComponent.cs b/Content.Shared/Language/Components/UniversalLanguageSpeakerComponent.cs new file mode 100644 index 00000000000..6f5ad1178b8 --- /dev/null +++ b/Content.Shared/Language/Components/UniversalLanguageSpeakerComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Language.Components; + +// +// Signifies that this entity can speak and understand any language. +// Applies to such entities as ghosts. +// +[RegisterComponent] +public sealed partial class UniversalLanguageSpeakerComponent : Component +{ + +} diff --git a/Content.Shared/Language/Events/LanguagesSetMessage.cs b/Content.Shared/Language/Events/LanguagesSetMessage.cs new file mode 100644 index 00000000000..f7a78210aaf --- /dev/null +++ b/Content.Shared/Language/Events/LanguagesSetMessage.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Language.Events; + +/// +/// Sent from the client to the server when it needs to want to set his currentLangauge. +/// Yeah im using this instead of ExecuteCommand... Better right? +/// +[Serializable, NetSerializable] +public sealed class LanguagesSetMessage(string currentLanguage) : EntityEventArgs +{ + public string CurrentLanguage = currentLanguage; +} diff --git a/Content.Shared/Language/Events/LanguagesUpdateEvent.cs b/Content.Shared/Language/Events/LanguagesUpdateEvent.cs new file mode 100644 index 00000000000..90ce2f4446b --- /dev/null +++ b/Content.Shared/Language/Events/LanguagesUpdateEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Language.Events; + +/// +/// Raised on an entity when its list of languages changes. +/// +public sealed class LanguagesUpdateEvent : EntityEventArgs +{ +} diff --git a/Content.Shared/Language/Events/LanguagesUpdatedMessage.cs b/Content.Shared/Language/Events/LanguagesUpdatedMessage.cs new file mode 100644 index 00000000000..563f036df6d --- /dev/null +++ b/Content.Shared/Language/Events/LanguagesUpdatedMessage.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Language.Events; + +/// +/// Sent to the client when its list of languages changes. +/// The client should in turn update its HUD and relevant systems. +/// +[Serializable, NetSerializable] +public sealed class LanguagesUpdatedMessage(string currentLanguage, List spoken, List understood) : EntityEventArgs +{ + public string CurrentLanguage = currentLanguage; + public List Spoken = spoken; + public List Understood = understood; +} diff --git a/Content.Shared/Language/Events/RequestLanguagesMessage.cs b/Content.Shared/Language/Events/RequestLanguagesMessage.cs new file mode 100644 index 00000000000..aead1f4cd1a --- /dev/null +++ b/Content.Shared/Language/Events/RequestLanguagesMessage.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Language.Events; + +/// +/// Sent from the client to the server when it needs to learn the list of languages its entity knows. +/// This event should always be followed by a , unless the client doesn't have an entity. +/// +[Serializable, NetSerializable] +public sealed class RequestLanguagesMessage : EntityEventArgs; diff --git a/Content.Shared/Language/LanguagePrototype.cs b/Content.Shared/Language/LanguagePrototype.cs new file mode 100644 index 00000000000..801ab8a393b --- /dev/null +++ b/Content.Shared/Language/LanguagePrototype.cs @@ -0,0 +1,37 @@ +using System.Runtime.CompilerServices; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Language; + +[Prototype("language")] +public sealed class LanguagePrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// If true, obfuscated phrases of creatures speaking this language will have their syllables replaced with "replacement" syllables. + /// Otherwise entire sentences will be replaced. + /// + [DataField(required: true)] + public bool ObfuscateSyllables; + + /// + /// Lists all syllables that are used to obfuscate a message a listener cannot understand if obfuscateSyllables is true. + /// Otherwise uses all possible phrases the creature can make when trying to say anything. + /// + [DataField(required: true)] + public List Replacement = []; + + #region utility + /// + /// The in-world name of this language, localized. + /// + public string Name => Loc.GetString($"language-{ID}-name"); + + /// + /// The in-world description of this language, localized. + /// + public string Description => Loc.GetString($"language-{ID}-description"); + #endregion utility +} diff --git a/Content.Shared/Language/Systems/SharedLanguageSystem.cs b/Content.Shared/Language/Systems/SharedLanguageSystem.cs new file mode 100644 index 00000000000..e2eeb8bb493 --- /dev/null +++ b/Content.Shared/Language/Systems/SharedLanguageSystem.cs @@ -0,0 +1,39 @@ +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.Language.Systems; + +public abstract class SharedLanguageSystem : EntitySystem +{ + /// + /// The language used as a fallback in cases where an entity suddenly becomes a language speaker (e.g. the usage of make-sentient) + /// + [ValidatePrototypeId] + public static readonly string FallbackLanguagePrototype = "GalacticCommon"; + + /// + /// The language whose speakers are assumed to understand and speak every language. Should never be added directly. + /// + [ValidatePrototypeId] + public static readonly string UniversalPrototype = "Universal"; + + /// + /// A cached instance of + /// + public static LanguagePrototype Universal { get; private set; } = default!; + + [Dependency] protected readonly IPrototypeManager _prototype = default!; + [Dependency] protected readonly IRobustRandom _random = default!; + + public override void Initialize() + { + Universal = _prototype.Index("Universal"); + } + + public LanguagePrototype? GetLanguagePrototype(string id) + { + _prototype.TryIndex(id, out var proto); + return proto; + } +} diff --git a/Content.Shared/Language/Systems/SharedTranslatorImplanterSystem.cs b/Content.Shared/Language/Systems/SharedTranslatorImplanterSystem.cs new file mode 100644 index 00000000000..a13225378cd --- /dev/null +++ b/Content.Shared/Language/Systems/SharedTranslatorImplanterSystem.cs @@ -0,0 +1,36 @@ +using Content.Shared.Examine; +using Content.Shared.Implants.Components; +using Content.Shared.Language.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Language.Systems; + +public abstract class SharedTranslatorImplanterSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, TranslatorImplanterComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + var text = !component.Used + ? Loc.GetString("translator-implanter-ready") + : Loc.GetString("translator-implanter-used"); + + args.PushText(text); + } + + protected void OnAppearanceChange(EntityUid implanter, TranslatorImplanterComponent component) + { + var used = component.Used; + _appearance.SetData(implanter, ImplanterVisuals.Full, !used); + } +} diff --git a/Content.Shared/Language/Systems/SharedTranslatorSystem.cs b/Content.Shared/Language/Systems/SharedTranslatorSystem.cs new file mode 100644 index 00000000000..08a016efa9c --- /dev/null +++ b/Content.Shared/Language/Systems/SharedTranslatorSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Examine; +using Content.Shared.Toggleable; +using Content.Shared.Language.Components.Translators; + +namespace Content.Shared.Language.Systems; + +public abstract class SharedTranslatorSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, HandheldTranslatorComponent component, ExaminedEvent args) + { + var state = Loc.GetString(component.Enabled + ? "translator-enabled" + : "translator-disabled"); + + args.PushMarkup(state); + } + + protected void OnAppearanceChange(EntityUid translator, HandheldTranslatorComponent? comp = null) + { + if (comp == null && !TryComp(translator, out comp)) + return; + + _appearance.SetData(translator, ToggleVisuals.Toggled, comp.Enabled); + } +} diff --git a/Resources/Locale/en-US/language/commands.ftl b/Resources/Locale/en-US/language/commands.ftl new file mode 100644 index 00000000000..32fa5415b8c --- /dev/null +++ b/Resources/Locale/en-US/language/commands.ftl @@ -0,0 +1,8 @@ +command-list-langs-desc = List languages your current entity can speak at the current moment. +command-list-langs-help = Usage: {$command} + +command-saylang-desc = Send a message in a specific language. +command-saylang-help = Usage: {$command} . Example: {$command} GalacticCommon "Hello World!" + +command-language-select-desc = Select the currently spoken language of your entity. +command-language-select-help = Usage: {$command} . Example: {$command} GalacticCommon diff --git a/Resources/Locale/en-US/language/language-menu.ftl b/Resources/Locale/en-US/language/language-menu.ftl new file mode 100644 index 00000000000..83687d0f1a6 --- /dev/null +++ b/Resources/Locale/en-US/language/language-menu.ftl @@ -0,0 +1,4 @@ +language-menu-window-title = Language Menu +language-menu-current-language = Current Language: {$language} +language-menu-description-header = Description +ui-options-function-open-language-menu = Open language Menu diff --git a/Resources/Locale/en-US/language/languages.ftl b/Resources/Locale/en-US/language/languages.ftl new file mode 100644 index 00000000000..69c5d0a4a76 --- /dev/null +++ b/Resources/Locale/en-US/language/languages.ftl @@ -0,0 +1,71 @@ +language-Universal-name = Universal +language-Universal-description = What are you? + +language-GalacticCommon-name = Galactic common +language-GalacticCommon-description = The standard Galatic language, most commonly used for inter-species communications and legal work. + +language-Bubblish-name = Bubblish +language-Bubblish-description = The language of Slimes. Being a mixture of bubbling noises and pops it's very difficult to speak for humans without the use of mechanical aids. + +language-RootSpeak-name = Rootspeak +language-RootSpeak-description = The strange whistling-style language spoken by the Diona. + +language-Nekomimetic-name = Nekomimetic +language-Nekomimetic-description = To the casual observer, this language is an incomprehensible mess of broken Japanese. To the felinids, it's somehow comprehensible. + +language-Draconic-name = Draconic +language-Draconic-description = The common language of lizard-people, composed of sibilant hisses and rattles. + +language-SolCommon-name = Sol common +language-SolCommon-description = The language common to species from the Sol System. + +language-Canilunzt-name = Canilunzt +language-Canilunzt-description = The guttural language spoken and utilized by the inhabitants of the Vazzend system, composed of growls, barks, yaps, and heavy utilization of ears and tail movements. Vulpkanin speak this language with ease. + +language-Moffic-name = Moffic +language-Moffic-description = The language of the mothpeople borders on complete unintelligibility. + +language-RobotTalk-name = RobotTalk +language-RobotTalk-description = A language consisting of harsh binary chirps, whistles, hisses, and whines. Organic tongues cannot speak it without aid from special translators. + +language-Cat-name = Cat +language-Cat-description = Meow + +language-Dog-name = Dog +language-Dog-description = Bark! + +language-Fox-name = Fox +language-Fox-description = Yeeps! + +language-Xeno-name = Xeno +language-Xeno-description = Sssss! + +language-Monkey-name = Monkey +language-Monkey-description = oooook! + +language-Mouse-name = Mouse +language-Mouse-description = Squeeek! + +language-Chicken-name = Chicken +language-Chicken-description = Coot! + +language-Duck-name = Duck +language-Duck-description = Quack! + +language-Cow-name = Cow +language-Cow-description = Moooo! + +language-Sheep-name = Sheep +language-Sheep-description = Baaah! + +language-Kangaroo-name = Kangaroo +language-Kangaroo-description = Chuu! + +language-Pig-name = Pig +language-Pig-description = Oink! + +language-Crab-name = Crab +language-Crab-description = Click! + +language-Kobold-name = Kobold +language-Kobold-description = Hiss! diff --git a/Resources/Locale/en-US/language/technologies.ftl b/Resources/Locale/en-US/language/technologies.ftl new file mode 100644 index 00000000000..901a48061c5 --- /dev/null +++ b/Resources/Locale/en-US/language/technologies.ftl @@ -0,0 +1,2 @@ +research-technology-basic-translation = Basic Translation +research-technology-advanced-translation = Advanced Translation diff --git a/Resources/Locale/en-US/language/translator.ftl b/Resources/Locale/en-US/language/translator.ftl new file mode 100644 index 00000000000..b2a1e9b2b8c --- /dev/null +++ b/Resources/Locale/en-US/language/translator.ftl @@ -0,0 +1,8 @@ +translator-component-shutoff = The {$translator} shuts off. +translator-component-turnon = The {$translator} turns on. +translator-enabled = It appears to be active. +translator-disabled = It appears to be disabled. +translator-implanter-refuse = The {$implanter} has no effect on {$target}. +translator-implanter-success = The {$implanter} successfully injected {$target}. +translator-implanter-ready = This implanter appears to be ready to use. +translator-implanter-used = This implanter seems empty. diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml index 92615131f05..e932974a0f4 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml @@ -66,6 +66,11 @@ - type: Tag tags: - VimPilot + - type: LanguageSpeaker + speaks: + - Fox + understands: + - Fox - type: entity name: security dog @@ -154,8 +159,6 @@ spawned: - id: FoodMeat amount: 2 - - type: ReplacementAccent - accent: dog - type: InteractionPopup successChance: 0.5 interactSuccessString: petting-success-dog @@ -176,3 +179,9 @@ tags: - DoorBumpOpener - VimPilot + - type: LanguageSpeaker + speaks: + - Dog + understands: + - Dog + - GalacticCommon diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml index 771da36719f..fa51b99325c 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml @@ -95,6 +95,11 @@ factions: - PsionicInterloper - NanoTrasen + - type: LanguageSpeaker + speaks: + - GalacticCommon + understands: + - GalacticCommon - type: GhostTakeoverAvailable - type: GhostRole makeSentient: true diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml index 96950317c1f..c2ae33ec0ba 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml @@ -96,8 +96,12 @@ spawned: - id: FoodMeat amount: 1 - - type: ReplacementAccent - accent: mouse + - type: LanguageSpeaker + speaks: + - Mouse + understands: + - Mouse + - GalacticCommon - type: Tag tags: - VimPilot @@ -163,4 +167,4 @@ interactFailureString: petting-failure-nukie-mouse interactSuccessSpawn: EffectHearts interactSuccessSound: - path: /Audio/Animals/mouse_squeak.ogg \ No newline at end of file + path: /Audio/Animals/mouse_squeak.ogg diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml index a4498299c9a..18437e074dd 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/harpy.yml @@ -122,12 +122,19 @@ - type: MovementSpeedModifier baseWalkSpeed: 2.5 baseSprintSpeed: 5.0 - - type: Inventory + - type: Inventory speciesId: harpy templateId: digitigrade - type: HarpyVisuals - type: UltraVision - + - type: LanguageSpeaker + speaks: + - GalacticCommon + - SolCommon + understands: + - GalacticCommon + - SolCommon + - type: entity save: false name: Urist McHands @@ -138,7 +145,7 @@ components: - type: HumanoidAppearance species: Harpy - - type: Inventory + - type: Inventory speciesId: harpy - type: Sprite scale: 0.9, 0.9 diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml index 4a187d51b33..52853d696a2 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml @@ -97,6 +97,13 @@ Female: FemaleVulpkanin Unsexed: MaleVulpkanin - type: DogVision + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Canilunzt + understands: + - GalacticCommon + - Canilunzt - type: entity save: false diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index dec46df0b53..0645e451af2 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -213,6 +213,13 @@ visMask: - PsionicInvisibility - Normal + - type: LanguageSpeaker + speaks: + - GalacticCommon + - RobotTalk + understands: + - GalacticCommon + - RobotTalk - type: entity id: BaseBorgChassisNT diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 378b3f8a9d8..369544fdc1b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -48,8 +48,11 @@ flavorKind: station-event-random-sentience-flavor-organic - type: Bloodstream bloodMaxVolume: 50 - - type: ReplacementAccent - accent: mouse + - type: LanguageSpeaker + speaks: + - Mouse + understands: + - Mouse - type: MeleeWeapon soundHit: path: /Audio/Effects/bite.ogg @@ -229,8 +232,11 @@ - type: EggLayer eggSpawn: - id: FoodEgg - - type: ReplacementAccent - accent: chicken + - type: LanguageSpeaker + speaks: + - Chicken + understands: + - Chicken - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-organic - type: NpcFactionMember @@ -504,8 +510,11 @@ prob: 0.5 - type: Extractable grindableSolutionName: food - - type: ReplacementAccent - accent: mothroach + - type: LanguageSpeaker + speaks: + - Moffic + understands: + - Moffic - type: ZombieAccentOverride accent: zombieMoth - type: Vocal @@ -601,8 +610,11 @@ - type: EggLayer eggSpawn: - id: FoodEgg - - type: ReplacementAccent - accent: duck + - type: LanguageSpeaker + speaks: + - Duck + understands: + - Duck - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-organic - type: NpcFactionMember @@ -839,8 +851,11 @@ interactSuccessSpawn: EffectHearts interactSuccessSound: path: /Audio/Voice/Arachnid/arachnid_chitter.ogg - - type: ReplacementAccent - accent: crab + - type: LanguageSpeaker + speaks: + - Crab + understands: + - Crab - type: Bloodstream bloodMaxVolume: 50 bloodReagent: CopperBlood @@ -1076,8 +1091,11 @@ - type: Inventory speciesId: kangaroo templateId: kangaroo - - type: ReplacementAccent - accent: kangaroo + - type: LanguageSpeaker + speaks: + - Kangaroo + understands: + - Kangaroo - type: InventorySlots - type: Strippable - type: Butcherable @@ -1266,7 +1284,12 @@ - type: Speech speechSounds: Monkey speechVerb: Monkey - - type: MonkeyAccent + - type: LanguageSpeaker + speaks: + - Monkey + understands: + - Monkey + - Kobold - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-primate - type: AlwaysRevolutionaryConvertible @@ -1300,7 +1323,13 @@ - type: Speech speechSounds: Monkey speechVerb: Monkey - - type: MonkeyAccent + - type: LanguageSpeaker + speaks: + - Monkey + understands: + - Monkey + - Kobold + - GalacticCommon - type: NpcFactionMember factions: - Syndicate @@ -1339,8 +1368,12 @@ - type: NameIdentifier group: Kobold - type: LizardAccent - - type: ReplacementAccent - accent: kobold + - type: LanguageSpeaker + speaks: + - Kobold + understands: + - Kobold + - Monkey - type: Speech speechSounds: Lizard speechVerb: Reptilian @@ -1568,8 +1601,11 @@ spawned: - id: FoodMeatRat amount: 1 - - type: ReplacementAccent - accent: mouse + - type: LanguageSpeaker + speaks: + - Mouse + understands: + - Mouse - type: Tag tags: - Trash @@ -1894,6 +1930,11 @@ path: /Audio/Animals/parrot_raught.ogg - type: Bloodstream bloodMaxVolume: 50 + - type: LanguageSpeaker + speaks: + - GalacticCommon + understands: + - GalacticCommon - type: entity name: penguin @@ -2140,8 +2181,11 @@ - type: MeleeChemicalInjector transferAmount: 0.75 solution: melee - - type: ReplacementAccent - accent: xeno + - type: LanguageSpeaker + speaks: + - Xeno + understands: + - Xeno - type: InteractionPopup successChance: 0.5 interactSuccessString: petting-success-tarantula @@ -2472,6 +2516,11 @@ - type: Tag tags: - VimPilot + - type: LanguageSpeaker + speaks: + - Fox + understands: + - Fox - type: entity name: corgi @@ -2518,8 +2567,11 @@ spawned: - id: FoodMeat amount: 2 - - type: ReplacementAccent - accent: dog + - type: LanguageSpeaker + speaks: + - Dog + understands: + - Dog - type: InteractionPopup interactSuccessString: petting-success-dog interactFailureString: petting-failure-generic @@ -2671,8 +2723,11 @@ spawned: - id: FoodMeat amount: 3 - - type: ReplacementAccent - accent: cat + - type: LanguageSpeaker + speaks: + - Cat + understands: + - Cat - type: InteractionPopup successChance: 0.7 interactSuccessString: petting-success-cat @@ -2739,6 +2794,12 @@ - type: NpcFactionMember factions: - Syndicate + - type: LanguageSpeaker + speaks: + - Xeno + understands: + - Xeno + - GalacticCommon - type: entity name: space cat @@ -3034,8 +3095,11 @@ spawned: - id: FoodMeat amount: 1 - - type: ReplacementAccent - accent: mouse + - type: LanguageSpeaker + speaks: + - Mouse + understands: + - Mouse - type: Tag tags: - VimPilot @@ -3141,8 +3205,11 @@ interactSuccessSpawn: EffectHearts interactSuccessSound: path: /Audio/Animals/pig_oink.ogg - - type: ReplacementAccent - accent: pig + - type: LanguageSpeaker + speaks: + - Pig + understands: + - Pig - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-organic - type: NpcFactionMember @@ -3228,6 +3295,12 @@ reformTime: 10 popupText: diona-reform-attempt reformPrototype: MobDionaReformed + - type: LanguageSpeaker + speaks: + - RootSpeak + understands: + - GalacticCommon + - RootSpeak - type: entity parent: MobDionaNymph diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml index 39e68b63a78..3bcf8e7a16f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml @@ -15,8 +15,11 @@ - type: Sprite sprite: Mobs/Aliens/Argocyte/argocyte_common.rsi - type: SolutionContainerManager - - type: ReplacementAccent - accent: xeno + - type: LanguageSpeaker + speaks: + - Xeno + understands: + - Xeno - type: Bloodstream bloodReagent: FerrochromicAcid bloodMaxVolume: 75 #we don't want the map to become pools of blood diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 9981bb8bd92..8ca1b2d2f0e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -36,6 +36,12 @@ - VimPilot - type: StealTarget stealGroup: AnimalIan + - type: LanguageSpeaker + speaks: + - Dog + understands: + - GalacticCommon + - Dog - type: entity name: Old Ian @@ -121,6 +127,12 @@ tags: - CannotSuicide - VimPilot + - type: LanguageSpeaker + speaks: + - Cat + understands: + - GalacticCommon + - Cat - type: entity name: Exception @@ -139,6 +151,12 @@ tags: - CannotSuicide - VimPilot + - type: LanguageSpeaker + speaks: + - Cat + understands: + - GalacticCommon + - Cat - type: entity name: Floppa @@ -288,8 +306,12 @@ spawned: - id: FoodMeat amount: 2 - - type: ReplacementAccent - accent: dog + - type: LanguageSpeaker + speaks: + - Dog + understands: + - GalacticCommon + - Dog - type: InteractionPopup successChance: 0.5 interactSuccessString: petting-success-dog @@ -387,8 +409,12 @@ spawned: - id: FoodMeat amount: 3 - - type: ReplacementAccent - accent: dog + - type: LanguageSpeaker + speaks: + - Dog + understands: + - GalacticCommon + - Dog - type: InteractionPopup successChance: 0.7 interactSuccessString: petting-success-dog @@ -546,6 +572,12 @@ - VimPilot - type: StealTarget stealGroup: AnimalRenault + - type: LanguageSpeaker + speaks: + - Fox + understands: + - GalacticCommon + - Fox - type: entity name: Hamlet @@ -593,6 +625,12 @@ - CannotSuicide - Hamster - VimPilot + - type: LanguageSpeaker + speaks: + - Mouse + understands: + - GalacticCommon + - Mouse - type: entity name: Shiva @@ -765,6 +803,12 @@ attributes: proper: true gender: female + - type: LanguageSpeaker + speaks: + - Bubblish + understands: + - GalacticCommon + - Bubblish - type: entity name: Pun Pun @@ -799,6 +843,13 @@ attributes: proper: true gender: male + - type: LanguageSpeaker + speaks: + - Monkey + understands: + - GalacticCommon + - Monkey + - Kobold - type: entity name: Tropico @@ -826,3 +877,9 @@ # - type: AlwaysRevolutionaryConvertible - type: StealTarget stealGroup: AnimalTropico + - type: LanguageSpeaker + speaks: + - Crab + understands: + - GalacticCommon + - Crab diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index 89a6f16e525..50fe3b6765e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -119,6 +119,13 @@ attributes: gender: male - type: PotentialPsionic # Nyano + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Mouse + understands: + - GalacticCommon + - Mouse - type: entity id: MobRatKingBuff @@ -289,6 +296,12 @@ - type: Food - type: Item size: Tiny # Delta V - Make them eatable and pickable. + - type: LanguageSpeaker + speaks: + - Mouse + understands: + - GalacticCommon + - Mouse - type: weightedRandomEntity id: RatKingLoot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index ec1ed3a58f6..1316aefc50b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -97,6 +97,7 @@ - RevenantTheme - type: Speech speechVerb: Ghost + - type: UniversalLanguageSpeaker - type: Tag tags: - NoPaint diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/shadows.yml b/Resources/Prototypes/Entities/Mobs/NPCs/shadows.yml index f08fe36544e..9559ae3a0c0 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/shadows.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/shadows.yml @@ -36,7 +36,7 @@ speedModifierThresholds: 60: 0.7 80: 0.5 - + - type: entity name: shadow cat parent: BaseShadowMob @@ -50,8 +50,11 @@ - map: ["enum.DamageStateVisualLayers.Base"] state: cat - type: Physics - - type: ReplacementAccent - accent: cat + - type: LanguageSpeaker + speaks: + - Cat + understands: + - Cat - type: InteractionPopup successChance: 0.01 # you cant pet shadow cat... almost interactSuccessString: petting-success-cat @@ -64,4 +67,4 @@ gender: epicene - type: Tag tags: - - VimPilot \ No newline at end of file + - VimPilot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 42b7ff9e211..e3166c15f6e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -107,6 +107,13 @@ - type: TypingIndicator proto: robot - type: ZombieImmune + - type: LanguageSpeaker + speaks: + - GalacticCommon + - RobotTalk + understands: + - GalacticCommon + - RobotTalk - type: entity parent: MobSiliconBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index c64479369a6..901bf149cbc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -111,8 +111,11 @@ successChance: 0.5 interactSuccessString: petting-success-slimes interactFailureString: petting-failure-generic - - type: ReplacementAccent - accent: slimes + - type: LanguageSpeaker + speaks: + - Bubblish + understands: + - Bubblish - type: GhostTakeoverAvailable - type: GhostRole makeSentient: true diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index 0a294805cfd..9ea2d784dbb 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -165,8 +165,11 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepBounce - - type: ReplacementAccent - accent: kangaroo + - type: LanguageSpeaker + speaks: + - Kangaroo + understands: + - Kangaroo - type: InventorySlots - type: Strippable - type: UserInterface @@ -248,8 +251,11 @@ - type: MeleeChemicalInjector solution: melee transferAmount: 4 - - type: ReplacementAccent - accent: xeno + - type: LanguageSpeaker + speaks: + - Xeno + understands: + - Xeno - type: InteractionPopup successChance: 0.20 interactSuccessString: petting-success-tarantula @@ -351,8 +357,11 @@ - type: MeleeChemicalInjector solution: melee transferAmount: 6 - - type: ReplacementAccent - accent: xeno + - type: LanguageSpeaker + speaks: + - Xeno + understands: + - Xeno - type: InteractionPopup successChance: 0.2 interactSuccessString: petting-success-snake @@ -373,4 +382,4 @@ parent: MobCobraSpace suffix: "Salvage Ruleset" components: - - type: SalvageMobRestrictions \ No newline at end of file + - type: SalvageMobRestrictions diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index d0ac6fc0265..26553a2f1f2 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -125,6 +125,11 @@ chance: -2 - type: Psionic #Nyano - Summary: makes psionic by default. removable: false + - type: LanguageSpeaker + speaks: + - Xeno + understands: + - Xeno - type: entity name: Praetorian @@ -234,6 +239,13 @@ - type: Tag tags: - CannotSuicide + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Xeno + understands: + - GalacticCommon + - Xeno - type: entity name: Ravager diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 8f3e6c13466..0086be81d9a 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -53,6 +53,7 @@ - type: Tag tags: - BypassInteractionRangeChecks + - type: UniversalLanguageSpeaker # Ghosts should understand any language. - type: entity id: ActionGhostBoo diff --git a/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml b/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml index ad9b37f63e1..07deef857c3 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml @@ -7,3 +7,4 @@ - type: MovementSpeedModifier baseSprintSpeed: 24 baseWalkSpeed: 16 + - type: UniversalLanguageSpeaker diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index a610e04d6dd..67212d416fe 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -141,7 +141,7 @@ - Pacified - StaminaModifier - PsionicsDisabled #Nyano - Summary: PCs can have psionics disabled. - - PsionicallyInsulated #Nyano - Summary: PCs can be made insulated from psionic powers. + - PsionicallyInsulated #Nyano - Summary: PCs can be made insulated from psionic powers. - type: Reflect enabled: false reflectProb: 0 @@ -218,7 +218,12 @@ - type: MobPrice price: 1500 # Kidnapping a living person and selling them for cred is a good move. deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. - - type: CanEscapeInventory # Carrying system from nyanotrasen. + - type: CanEscapeInventory # Carrying system from nyanotrasen. + - type: LanguageSpeaker # This is here so all with no LanguageSpeaker at least spawn with the default languages. + speaks: + - GalacticCommon + understands: + - GalacticCommon - type: Tag tags: - CanPilot diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index 3d405c4dd91..5cb3de6f168 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -102,6 +102,13 @@ actionPrototype: DionaGibAction allowedStates: - Dead + - type: LanguageSpeaker + speaks: + - GalacticCommon + - RootSpeak + understands: + - GalacticCommon + - RootSpeak - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml index fe36754b9b5..7afc5cddd70 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -52,6 +52,13 @@ accent: dwarf - type: Speech speechSounds: Bass + - type: LanguageSpeaker + speaks: + - GalacticCommon + - SolCommon + understands: + - GalacticCommon + - SolCommon - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 7bf96efe2cc..7c3f857c001 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -17,6 +17,13 @@ - id: FoodMeatHuman amount: 5 - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. + - type: LanguageSpeaker + speaks: + - GalacticCommon + - SolCommon + understands: + - GalacticCommon + - SolCommon - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/moth.yml b/Resources/Prototypes/Entities/Mobs/Species/moth.yml index 1680dd6cda6..39aa0ab8dea 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/moth.yml @@ -23,6 +23,13 @@ accent: zombieMoth - type: Speech speechVerb: Moth + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Moffic + understands: + - GalacticCommon + - Moffic - type: TypingIndicator proto: moth - type: Butcherable diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index 09e86b19968..bdea4499ed1 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -59,6 +59,13 @@ types: Heat : 1.5 #per second, scales with temperature & other constants - type: Wagging + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Draconic + understands: + - GalacticCommon + - Draconic - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 481afd06a3c..a601010ef94 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -74,6 +74,13 @@ types: Asphyxiation: -1.0 maxSaturation: 15 + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Bubblish + understands: + - GalacticCommon + - Bubblish - type: entity parent: MobHumanDummy diff --git a/Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml b/Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml new file mode 100644 index 00000000000..fc947efe9a3 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml @@ -0,0 +1,132 @@ +- type: entity + abstract: true + id: BaseTranslatorImplanter + parent: [ BaseItem ] + name: basic translator implant + description: Translates speech. + components: + - type: Sprite + sprite: Objects/Specific/Medical/implanter.rsi + state: implanter0 + layers: + - state: implanter1 + map: [ "implantFull" ] + visible: true + - state: implanter0 + map: [ "implantBroken" ] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ImplanterVisuals.Full: + implantFull: + True: {visible: true} + False: {visible: false} + implantBroken: + True: {visible: false} + False: {visible: true} + +- type: entity + id: BasicGalaticCommonTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: basic Galactic Common translator implant + description: An implant giving the ability to understand Galactic Common. + components: + - type: TranslatorImplanter + understood: + - GalacticCommon + +- type: entity + id: AdvancedGalaticCommonTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: advanced Galactic Common translator implant + description: An implant giving the ability to understand and speak Galactic Common. + components: + - type: TranslatorImplanter + spoken: + - GalacticCommon + understood: + - GalacticCommon + +- type: entity + id: BubblishTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Bubblish translator implant + description: An implant giving the ability to understand and speak Bubblish. + components: + - type: TranslatorImplanter + spoken: + - Bubblish + understood: + - Bubblish + +- type: entity + id: NekomimeticTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Nekomimetic translator implant + description: An implant giving the ability to understand and speak Nekomimetic. Nya~! + components: + - type: TranslatorImplanter + spoken: + - Nekomimetic + understood: + - Nekomimetic + +- type: entity + id: DraconicTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Draconic translator implant + description: An implant giving the ability to understand and speak Draconic. + components: + - type: TranslatorImplanter + spoken: + - Draconic + understood: + - Draconic + +- type: entity + id: CanilunztTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Canilunzt translator implant + description: An implant giving the ability to understand and speak Canilunzt. Yeeps! + components: + - type: TranslatorImplanter + spoken: + - Canilunzt + understood: + - Canilunzt + +- type: entity + id: SolCommonTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: SolCommon translator implant + description: An implant giving the ability to understand and speak SolCommon. Raaagh! + components: + - type: TranslatorImplanter + spoken: + - SolCommon + understood: + - SolCommon + +- type: entity + id: RootSpeakTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: RootSpeak translator implant + description: An implant giving the ability to understand and speak RootSpeak. + components: + - type: TranslatorImplanter + spoken: + - RootSpeak + understood: + - RootSpeak + +- type: entity + id: MofficTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Moffic translator implant + description: An implant giving the ability to understand and speak Moffic. + components: + - type: TranslatorImplanter + spoken: + - Moffic + understood: + - Moffic diff --git a/Resources/Prototypes/Entities/Objects/Devices/translators.yml b/Resources/Prototypes/Entities/Objects/Devices/translators.yml new file mode 100644 index 00000000000..e5ad824c5d9 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/translators.yml @@ -0,0 +1,205 @@ +- type: entity + abstract: true + id: TranslatorUnpowered + parent: [ BaseItem ] + name: translator + description: Translates speech. + components: + - type: Sprite + sprite: Objects/Devices/translator.rsi + state: icon + layers: + - state: icon + - state: translator + shader: unshaded + visible: false + map: [ "enum.ToggleVisuals.Layer", "enum.PowerDeviceVisualLayers.Powered" ] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ToggleVisuals.Toggled: + enum.ToggleVisuals.Layer: + True: { visible: true } + False: { visible: false } + - type: HandheldTranslator + enabled: false + +- type: entity + abstract: true + id: Translator + parent: [ TranslatorUnpowered, PowerCellSlotMediumItem ] + suffix: Powered + components: + - type: PowerCellDraw + drawRate: 1 + +- type: entity + abstract: true + id: TranslatorEmpty + parent: [ Translator ] + suffix: Empty + components: + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default + + +- type: entity + id: CanilunztTranslator + parent: [ TranslatorEmpty ] + name: Canilunzt translator + description: Translates speech between Canilunzt and Galactic Common. + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - Canilunzt + understood: + - GalacticCommon + - Canilunzt + requires: + - GalacticCommon + - Canilunzt + +- type: entity + id: BubblishTranslator + parent: [ TranslatorEmpty ] + name: Bubblish translator + description: Translates speech between Bubblish and Galactic Common. + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - Bubblish + understood: + - GalacticCommon + - Bubblish + requires: + - GalacticCommon + - Bubblish + +- type: entity + id: NekomimeticTranslator + parent: [ TranslatorEmpty ] + name: Nekomimetic translator + description: Translates speech between Nekomimetic and Galactic Common. Why would you want that? + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - Nekomimetic + understood: + - GalacticCommon + - Nekomimetic + requires: + - GalacticCommon + - Nekomimetic + +- type: entity + id: DraconicTranslator + parent: [ TranslatorEmpty ] + name: Draconic translator + description: Translates speech between Draconic and Galactic Common. + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - Draconic + understood: + - GalacticCommon + - Draconic + requires: + - GalacticCommon + - Draconic + +- type: entity + id: SolCommonTranslator + parent: [ TranslatorEmpty ] + name: Sol Common translator + description: Translates speech between Sol Common and Galactic Common. Like a true Earthman! + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - SolCommon + understood: + - GalacticCommon + - SolCommon + requires: + - GalacticCommon + - SolCommon + +- type: entity + id: RootSpeakTranslator + parent: [ TranslatorEmpty ] + name: RootSpeak translator + description: Translates speech between RootSpeak and Galactic Common. Like a true plant? + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - RootSpeak + understood: + - GalacticCommon + - RootSpeak + requires: + - GalacticCommon + - RootSpeak + +- type: entity + id: MofficTranslator + parent: [ TranslatorEmpty ] + name: Moffic translator + description: Translates speech between Moffic and Galactic Common. Like a true moth... or bug? + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - Moffic + understood: + - GalacticCommon + - Moffic + requires: + - GalacticCommon + - Moffic + +- type: entity + id: XenoTranslator + parent: [ TranslatorEmpty ] + name: Xeno translator + description: Translates speech between Xeno and Galactic Common. Not sure if that will help. + components: + - type: HandheldTranslator + spoken: + - GalacticCommon + - Xeno + understood: + - GalacticCommon + - Xeno + requires: + - GalacticCommon + +- type: entity + id: AnimalTranslator + parent: [ TranslatorEmpty ] + name: Animal translator + description: Translates all the cutes noises that animals make into a more understandable form! + components: + - type: HandheldTranslator + understood: + - Cat + - Dog + - Fox + - Monkey + - Mouse + - Chicken + - Duck + - Cow + - Sheep + - Kangaroo + - Pig + - Crab + - Kobold + requires: + - GalacticCommon diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 011f2a3b649..7300c0b9ec3 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -341,6 +341,24 @@ - FauxTileAstroSnow - OreBagOfHolding - DeviceQuantumSpinInverter + - CanilunztTranslator + - BubblishTranslator + - NekomimeticTranslator + - DraconicTranslator + - SolCommonTranslator + - RootSpeakTranslator + - XenoTranslator + - BasicGalaticCommonTranslatorImplanter + - AdvancedGalaticCommonTranslatorImplanter + - BubblishTranslatorImplanter + - NekomimeticTranslatorImplanter + - DraconicTranslatorImplanter + - CanilunztTranslatorImplanter + - SolCommonTranslatorImplanter + - RootSpeakTranslatorImplanter + - AnimalTranslator + - MofficTranslatorImplanter + - MofficTranslator - type: EmagLatheRecipes emagDynamicRecipes: - ExplosivePayload diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index 64b6b068c71..6efa5a63711 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -101,6 +101,13 @@ price: 100 - type: Appearance - type: WiresVisuals + - type: LanguageSpeaker + speaks: + - GalacticCommon + - RobotTalk + understands: + - GalacticCommon + - RobotTalk - type: entity parent: VendingMachine diff --git a/Resources/Prototypes/Language/languages.yml b/Resources/Prototypes/Language/languages.yml new file mode 100644 index 00000000000..90bce1baed2 --- /dev/null +++ b/Resources/Prototypes/Language/languages.yml @@ -0,0 +1,493 @@ +# The universal language, assumed if the entity has a UniversalLanguageSpeakerComponent. +# Do not use otherwise. Try to use the respective component instead of this language. +- type: language + id: Universal + obfuscateSyllables: false + replacement: + - "*incomprehensible*" + +# The common galactic tongue. +- type: language + id: GalacticCommon + obfuscateSyllables: true + replacement: + - Blah + - Blah + - Blah + - dingle-doingle + - dingle + - dangle + - jibber-jabber + - jubber + - bleh + - zippity + - zoop + - wibble + - wobble + - wiggle + - yada + - meh + - neh + - nah + - wah + +# Spoken by slimes. +- type: language + id: Bubblish + obfuscateSyllables: true + replacement: + - blob + - plop + - pop + - bop + - boop + +# Spoken by moths. +- type: language + id: Moffic + obfuscateSyllables: true + replacement: + - år + - i + - går + - sek + - mo + - ff + - ok + - gj + - ø + - gå + - la + - le + - lit + - ygg + - van + - dår + - næ + - møt + - idd + - hvo + - ja + - på + - han + - så + - ån + - det + - att + - nå + - gö + - bra + - int + - tyc + - om + - när + - två + - må + - dag + - sjä + - vii + - vuo + - eil + - tun + - käyt + - teh + - vä + - hei + - huo + - suo + - ää + - ten + - ja + - heu + - stu + - uhr + - kön + - we + - hön + + # Spoken by dionas. +- type: language + id: RootSpeak + obfuscateSyllables: true + replacement: + - hs + - zt + - kr + - st + - sh + +# A mess of broken Japanese, spoken by Felinds and Oni +- type: language + id: Nekomimetic + obfuscateSyllables: true + replacement: + - neko + - nyan + - mimi + - moe + - mofu + - fuwa + - kyaa + - kawaii + - poka + - munya + - puni + - munyu + - ufufu + - icha + - doki + - kyun + - kusu + - nya + - nyaa + - desu + - kis + - ama + - chuu + - baka + - hewo + - boop + - gato + - kit + - sune + - yori + - sou + - baka + - chan + - san + - kun + - mahou + - yatta + - suki + - usagi + - domo + - ori + - uwa + - zaazaa + - shiku + - puru + - ira + - heto + - etto + +# Spoken by the Lizard race. +- type: language + id: Draconic + obfuscateSyllables: true + replacement: + - za + - az + - ze + - ez + - zi + - iz + - zo + - oz + - zu + - uz + - zs + - sz + - ha + - ah + - he + - eh + - hi + - ih + - ho + - oh + - hu + - uh + - hs + - sh + - la + - al + - le + - el + - li + - il + - lo + - ol + - lu + - ul + - ls + - sl + - ka + - ak + - ke + - ek + - ki + - ik + - ko + - ok + - ku + - uk + - ks + - sk + - sa + - as + - se + - es + - si + - is + - so + - os + - su + - us + - ss + - ss + - ra + - ar + - re + - er + - ri + - ir + - ro + - or + - ru + - ur + - rs + - sr + - a + - a + - e + - e + - i + - i + - o + - o + - u + - u + - s + - s + +# Spoken by the Vulpkanin race. +- type: language + id: Canilunzt + obfuscateSyllables: true + replacement: + - rur + - ya + - cen + - rawr + - bar + - kuk + - tek + - qat + - uk + - wu + - vuh + - tah + - tch + - schz + - auch + - ist + - ein + - entch + - zwichs + - tut + - mir + - wo + - bis + - es + - vor + - nic + - gro + - lll + - enem + - zandt + - tzch + - noch + - hel + - ischt + - far + - wa + - baram + - iereng + - tech + - lach + - sam + - mak + - lich + - gen + - or + - ag + - eck + - gec + - stag + - onn + - bin + - ket + - jarl + - vulf + - einech + - cresthz + - azunein + - ghzth + +# The common language of the Sol system. +- type: language + id: SolCommon + obfuscateSyllables: true + replacement: + - tao + - shi + - tzu + - yi + - com + - be + - is + - i + - op + - vi + - ed + - lec + - mo + - cle + - te + - dis + - e + +- type: language + id: RobotTalk + obfuscateSyllables: true + replacement: + - 0 + - 1 + - 01 + - 10 + - 001 + - 100 + - 011 + - 110 + - 101 + - 010 + +# Languages spoken by various critters. +- type: language + id: Cat + obfuscateSyllables: true + replacement: + - murr + - meow + - purr + - mrow + +- type: language + id: Dog + obfuscateSyllables: true + replacement: + - woof + - bark + - ruff + - bork + - raff + - garr + +- type: language + id: Fox + obfuscateSyllables: true + replacement: + - bark + - gecker + - ruff + - raff + - garr + +- type: language + id: Xeno + obfuscateSyllables: true + replacement: + - sss + - sSs + - SSS + +- type: language + id: Monkey + obfuscateSyllables: true + replacement: + - ok + - ook + - oook + - ooook + - oooook + +- type: language + id: Mouse + obfuscateSyllables: true + replacement: + - Squeak + - Piep + - Chuu + - Eeee + - Pip + - Fwiep + - Heep + +- type: language + id: Chicken + obfuscateSyllables: true + replacement: + - Coo + - Coot + - Cooot + +- type: language + id: Duck + obfuscateSyllables: true + replacement: + - Quack + - Quack quack + +- type: language + id: Cow + obfuscateSyllables: true + replacement: + - Moo + - Mooo + +- type: language + id: Sheep + obfuscateSyllables: true + replacement: + - Ba + - Baa + - Baaa + +- type: language + id: Kangaroo + obfuscateSyllables: true + replacement: + - Shreak + - Chuu + +- type: language + id: Pig + obfuscateSyllables: true + replacement: + - Oink + - Oink oink + +- type: language + id: Crab + obfuscateSyllables: true + replacement: + - Click + - Click-clack + - Clack + - Tipi-tap + - Clik-tap + - Cliliick + +- type: language + id: Kobold + obfuscateSyllables: true + replacement: + - Yip + - Grrar. + - Yap + - Bip + - Screet + - Gronk + - Hiss + - Eeee + - Yip diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml index e11f1c4165f..8a0e750abd6 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml @@ -35,6 +35,13 @@ - MobLayer - type: Stamina critThreshold: 115 + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Nekomimetic + understands: + - GalacticCommon + - Nekomimetic - type: entity save: false diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml index d9b25c5dd1b..2184926b95a 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml @@ -64,6 +64,15 @@ Unsexed: MaleFelinid - type: Felinid - type: NoShoesSilentFootsteps + - type: LanguageSpeaker + speaks: + - GalacticCommon + - SolCommon + - Nekomimetic + understands: + - GalacticCommon + - SolCommon + - Nekomimetic - type: entity save: false diff --git a/Resources/Prototypes/Recipes/Lathes/language.yml b/Resources/Prototypes/Recipes/Lathes/language.yml new file mode 100644 index 00000000000..6871ed5228d --- /dev/null +++ b/Resources/Prototypes/Recipes/Lathes/language.yml @@ -0,0 +1,190 @@ +- type: latheRecipe + id: CanilunztTranslator + result: CanilunztTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: BubblishTranslator + result: BubblishTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: NekomimeticTranslator + result: NekomimeticTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: DraconicTranslator + result: DraconicTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: SolCommonTranslator + result: SolCommonTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: RootSpeakTranslator + result: RootSpeakTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: MofficTranslator + result: MofficTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: BasicGalaticCommonTranslatorImplanter + result: BasicGalaticCommonTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: XenoTranslator + result: XenoTranslator + completetime: 2 + materials: + Steel: 200 + Plastic: 50 + Gold: 50 + Plasma: 50 + Silver: 50 + +- type: latheRecipe + id: AdvancedGalaticCommonTranslatorImplanter + result: AdvancedGalaticCommonTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: BubblishTranslatorImplanter + result: BubblishTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: NekomimeticTranslatorImplanter + result: NekomimeticTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: DraconicTranslatorImplanter + result: DraconicTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: CanilunztTranslatorImplanter + result: CanilunztTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: SolCommonTranslatorImplanter + result: SolCommonTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: RootSpeakTranslatorImplanter + result: RootSpeakTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: MofficTranslatorImplanter + result: MofficTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: AnimalTranslator + result: AnimalTranslator + completetime: 2 + materials: + Steel: 200 + Plastic: 50 + Gold: 50 + Plasma: 50 + Silver: 5 diff --git a/Resources/Prototypes/Research/civilianservices.yml b/Resources/Prototypes/Research/civilianservices.yml index 61f95894ee6..acb6a2498d4 100644 --- a/Resources/Prototypes/Research/civilianservices.yml +++ b/Resources/Prototypes/Research/civilianservices.yml @@ -227,3 +227,43 @@ recipeUnlocks: - BluespaceBeaker - SyringeBluespace + +- type: technology + id: BasicTranslation + name: research-technology-basic-translation + icon: + sprite: Objects/Devices/translator.rsi + state: icon + discipline: CivilianServices + tier: 2 + cost: 10000 + recipeUnlocks: + - CanilunztTranslator + - BubblishTranslator + - NekomimeticTranslator + - DraconicTranslator + - SolCommonTranslator + - RootSpeakTranslator + - BasicGalaticCommonTranslatorImplanter + - MofficTranslator + +- type: technology + id: AdvancedTranslation + name: research-technology-advanced-translation + icon: + sprite: Objects/Devices/translator.rsi + state: icon + discipline: CivilianServices + tier: 3 + cost: 15000 + recipeUnlocks: + - XenoTranslator + - AdvancedGalaticCommonTranslatorImplanter + - BubblishTranslatorImplanter + - NekomimeticTranslatorImplanter + - DraconicTranslatorImplanter + - CanilunztTranslatorImplanter + - SolCommonTranslatorImplanter + - RootSpeakTranslatorImplanter + - AnimalTranslator + - MofficTranslatorImplanter diff --git a/Resources/Textures/Interface/language.png b/Resources/Textures/Interface/language.png new file mode 100644 index 0000000000000000000000000000000000000000..2b39424d12d3d75421a5f48ab87ec0c4f852129f GIT binary patch literal 739 zcmV<90v!E`P)4Tx04UFukv&MmP!xqvQ>9WWf_4yb$WWc^q9TqZ6^me@v=v%)FnQ@8G-*gu zTpR`0f`dPcRR<0iR+(n?#Q;sW z&2&N%bNN*<@QM(ERKpNuW*N!MVirBu*FAiEzl-uL{BI4a1&aYbkvPT-(0(wkHR4&~h^pz7FXTK{ zId5^+s&&@xlfN)j&{r~Cry55BOGqLG5en+4pb8sN+I3Par0HDh;U9DTDRL>~s(_JW z0X1llT|f9A{GP2--aXJgw?FT=^MmvQjVW@mx*a zR9HvtliLo2APhtW#Q*Z7 zxueKOz!l>drAkVi37b}eoEaHck8hT`Y711a{BAiv=MV89^d&Flsg z;w6 zQhU|Z-a6Cv%8BbI{bv4!1BA+Lo2}0W538WU#pihLZ&zz@(>PT+|s@$ z;f&CupQ;C=PPAHcUOTL%$X6+}V=}kyeUH}1?myQVa~Q{oXWeD^{6ysAU#rzwKqoMG My85}Sb4q9e0CGNE+5i9m literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/translator.rsi/meta.json b/Resources/Textures/Objects/Devices/translator.rsi/meta.json new file mode 100644 index 00000000000..0202c0c39c7 --- /dev/null +++ b/Resources/Textures/Objects/Devices/translator.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 2, + "license": "CC-BY-SA-3.0", + "copyright": "baystation12", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "translator" + } + ] +} diff --git a/Resources/Textures/Objects/Devices/translator.rsi/translator.png b/Resources/Textures/Objects/Devices/translator.rsi/translator.png new file mode 100644 index 0000000000000000000000000000000000000000..6c54a0b86366cef370ee7e2575979bc054a826dd GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!V7#Y`V@QPi+bb724=C`kT-dCARMX0%{FPtn zM7@b>Ggy`WPQ1h1>1a^Uq@bh}Rp#`xG0tlB_rgh+7A^Z3Ffrqd#I~w``;=d`)m1S? uB}Iz&N5B8Xx#z0zhx6*&T(($0U_Q1^;pBD8!#jaiF?hQAxvX Date: Mon, 10 Jun 2024 20:49:11 +0000 Subject: [PATCH 14/24] Automatic Changelog Update (#43) --- Resources/Changelog/Changelog.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 743c0add322..babf5f90782 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -4142,3 +4142,9 @@ Entries: message: Floors Looks Updated/Resprited. id: 6118 time: '2024-06-03T19:23:51.0000000+00:00' +- author: FoxxoTrystan + changes: + - type: Add + message: All species can now bring their own cultures and languages + id: 6119 + time: '2024-06-10T20:48:48.0000000+00:00' From e3fb4543b057a73998c76738c4f3eea90e30e350 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 18:43:47 -0400 Subject: [PATCH 15/24] Fixing invisibility, more feedback messages --- .../Abilities/MetapsionicPowerSystem.cs | 11 ++-- .../RegenerativeStasisPowerSystem.cs | 1 + .../Invisibility/PsionicInvisibilitySystem.cs | 2 + .../PsionicInvisibilityPowerSystem.cs | 42 ++++++------- .../PsionicInvisibilityUsedComponent.cs | 7 ++- .../PsionicInvisibleContactsComponent.cs | 2 +- .../PsionicInvisibleContactsSystem.cs | 2 +- .../PsionicallyInvisibleComponent.cs | 2 +- Content.Shared/Psionics/PsionicComponent.cs | 2 +- .../en-US/nyanotrasen/abilities/psionic.ftl | 7 +-- .../nyanotrasen/psionics/psychic-feedback.ftl | 25 +++++++- .../Entities/Mobs/NPCs/elemental.yml | 29 +++++++++ .../Prototypes/Entities/Mobs/NPCs/flesh.yml | 5 ++ .../Structures/Specific/Anomaly/anomalies.yml | 62 ++++++++++++++++++- .../Structures/Research/glimmer_prober.yml | 10 +++ 15 files changed, 166 insertions(+), 43 deletions(-) rename {Content.Server/Psionics/Abilities => Content.Shared/Psionics/Abilities/PsionicInvisibility}/PsionicInvisibilityPowerSystem.cs (86%) rename {Content.Server/Psionics/Invisibility => Content.Shared/Psionics/Abilities/PsionicInvisibility}/PsionicInvisibleContactsComponent.cs (93%) rename {Content.Server/Psionics/Invisibility => Content.Shared/Psionics/Abilities/PsionicInvisibility}/PsionicInvisibleContactsSystem.cs (98%) rename {Content.Server/Psionics/Invisibility => Content.Shared/Psionics/Abilities/PsionicInvisibility}/PsionicallyInvisibleComponent.cs (76%) diff --git a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs index 78842c6443d..38135e9a977 100644 --- a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -142,9 +142,6 @@ private void OnFocusedPowerUsed(FocusedMetapsionicPowerActionEvent args) private void OnDoAfter(EntityUid uid, MetapsionicPowerComponent component, FocusedMetapsionicDoAfterEvent args) { - if (!TryComp(args.Target, out var psychic)) - return; - component.DoAfter = null; if (args.Target == null) return; @@ -173,11 +170,13 @@ private void OnDoAfter(EntityUid uid, MetapsionicPowerComponent component, Focus return; } - foreach (var psychicFeedback in psychic.PsychicFeedback) + if (TryComp(args.Target, out var psychic)) { - _popups.PopupEntity(Loc.GetString(psychicFeedback, ("entity", args.Target)), uid, uid, PopupType.LargeCaution); + foreach (var psychicFeedback in psychic.PsychicFeedback) + { + _popups.PopupEntity(Loc.GetString(psychicFeedback, ("entity", args.Target)), uid, uid, PopupType.LargeCaution); + } } - } } } diff --git a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs index e184b19396b..8f187b71f33 100644 --- a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs @@ -63,6 +63,7 @@ private void OnPowerUsed(EntityUid uid, RegenerativeStasisPowerComponent compone var solution = new Solution(); solution.AddReagent("PsionicRegenerationEssence", FixedPoint2.New(MathF.Min(2.5f * psionic.Amplification + psionic.Dampening, 15f))); solution.AddReagent("Epinephrine", FixedPoint2.New(MathF.Min(2.5f * psionic.Dampening + psionic.Amplification, 15f))); + solution.AddReagent("Nocturine", 10f + (1 * psionic.Amplification + psionic.Dampening)); _bloodstreamSystem.TryAddToChemicals(args.Target, solution, stream); EnsureComp(args.Target); diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs index 9583f45fdc9..19c1a2c616c 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs +++ b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs @@ -89,6 +89,7 @@ private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.PsionicInvisibility, false); _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); + SetCanSeePsionicInvisiblity(uid, true); } @@ -99,6 +100,7 @@ private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent compon _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.PsionicInvisibility, false); _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); + SetCanSeePsionicInvisiblity(uid, false); } } diff --git a/Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs similarity index 86% rename from Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs rename to Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs index 0c50efb5cf3..28378d363ec 100644 --- a/Content.Server/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs @@ -1,6 +1,4 @@ -using Content.Server.DoAfter; using Content.Shared.Actions; -using Content.Shared.Psionics.Abilities; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Stunnable; @@ -13,9 +11,9 @@ using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Throwing; using Robust.Shared.Timing; -using Content.Shared.Psionics; +using System.Xml; -namespace Content.Server.Psionics.Abilities +namespace Content.Shared.Psionics.Abilities { public sealed class PsionicInvisibilityPowerSystem : EntitySystem { @@ -25,7 +23,7 @@ public sealed class PsionicInvisibilityPowerSystem : EntitySystem [Dependency] private readonly SharedStealthSystem _stealth = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; public override void Initialize() { @@ -80,26 +78,24 @@ private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent compon var ev = new PsionicInvisibilityTimerEvent(_gameTiming.CurTime); var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseTimer, ev, uid) { Hidden = true }; - _doAfterSystem.TryStartDoAfter(doAfterArgs); - - ToggleInvisibility(args.Performer); - var action = Spawn(PsionicInvisibilityUsedComponent.PsionicInvisibilityUsedActionPrototype); - _actions.AddAction(uid, action, action); - _actions.TryGetActionData(action, out var actionData); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(action); - - _psionics.LogPowerUsed(uid, "psionic invisibility", - (int) MathF.Round(8 * psionic.Amplification - 2 * psionic.Dampening), - (int) MathF.Round(12 * psionic.Amplification - 2 * psionic.Dampening)); - args.Handled = true; + if (_doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId)) + { + ToggleInvisibility(args.Performer); + if (TryComp(uid, out var invis)) + { + _actions.AddAction(uid, ref invis.PsionicInvisibilityUsedActionEntity, invis.PsionicInvisibilityUsedActionId); + invis.DoAfter = doAfterId; + } + + _psionics.LogPowerUsed(uid, "psionic invisibility", + (int) MathF.Round(8 * psionic.Amplification - 2 * psionic.Dampening), + (int) MathF.Round(12 * psionic.Amplification - 2 * psionic.Dampening)); + args.Handled = true; + } } private void OnPowerOff(RemovePsionicInvisibilityOffPowerActionEvent args) { - if (!HasComp(args.Performer)) - return; - ToggleInvisibility(args.Performer); args.Handled = true; } @@ -118,6 +114,7 @@ private void OnEnd(EntityUid uid, PsionicInvisibilityUsedComponent component, Co if (Terminating(uid)) return; + _doAfterSystem.Cancel(component.DoAfter); RemComp(uid); RemComp(uid); _audio.PlayPvs("/Audio/Effects/toss.ogg", uid); @@ -163,7 +160,8 @@ public void ToggleInvisibility(EntityUid uid) public void OnDoAfter(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityTimerEvent args) { - RemComp(uid); + if (!args.Cancelled) + RemComp(uid); } private void OnInsulated(EntityUid uid, PsionicInvisibilityUsedComponent component, PsionicInsulationEvent args) diff --git a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs index 2a9dd7642ba..22d1b928553 100644 --- a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityUsedComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.DoAfter; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -6,13 +7,13 @@ namespace Content.Shared.Psionics.Abilities [RegisterComponent] public sealed partial class PsionicInvisibilityUsedComponent : Component { - [ValidatePrototypeId] - public const string PsionicInvisibilityUsedActionPrototype = "ActionPsionicInvisibilityUsed"; - [DataField("psionicInvisibilityUsedActionId", + [DataField("psionicInvisibilityActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? PsionicInvisibilityUsedActionId = "ActionPsionicInvisibilityUsed"; [DataField("psionicInvisibilityUsedActionEntity")] public EntityUid? PsionicInvisibilityUsedActionEntity; + + public DoAfterId? DoAfter; } } diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibleContactsComponent.cs similarity index 93% rename from Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs rename to Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibleContactsComponent.cs index 268deddf6d9..376414eedce 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibleContactsComponent.cs @@ -1,6 +1,6 @@ using Content.Shared.Whitelist; -namespace Content.Server.Psionics +namespace Content.Shared.Psionics { [RegisterComponent] public sealed partial class PsionicInvisibleContactsComponent : Component diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibleContactsSystem.cs similarity index 98% rename from Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs rename to Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibleContactsSystem.cs index 403e0592617..f71605238ae 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibleContactsSystem.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibleContactsSystem.cs @@ -3,7 +3,7 @@ using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; -namespace Content.Server.Psionics +namespace Content.Shared.Psionics { /// /// Allows an entity to become psionically invisible when touching certain entities. diff --git a/Content.Server/Psionics/Invisibility/PsionicallyInvisibleComponent.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicallyInvisibleComponent.cs similarity index 76% rename from Content.Server/Psionics/Invisibility/PsionicallyInvisibleComponent.cs rename to Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicallyInvisibleComponent.cs index 5352f5737f2..87d3f250b1e 100644 --- a/Content.Server/Psionics/Invisibility/PsionicallyInvisibleComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicallyInvisibleComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Psionics +namespace Content.Shared.Psionics { [RegisterComponent] public sealed partial class PsionicallyInvisibleComponent : Component diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 9a06e54cb31..35ceb82c876 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -24,7 +24,7 @@ public sealed partial class PsionicComponent : Component [DataField("dampening")] public float Dampening = 0.1f; - public bool Telepath = false; + public bool Telepath = true; public bool InnatePsiChecked = false; } } diff --git a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl index 5b368e822f1..d46a05e9c98 100644 --- a/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl +++ b/Resources/Locale/en-US/nyanotrasen/abilities/psionic.ftl @@ -26,13 +26,10 @@ accept-psionics-window-prompt-text-part = You rolled a psionic power! Do you still wish to be psionic? action-name-psionic-invisibility = Psionic Invisibility -action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. - -action-name-psionic-invisibility = Psionic Invisibility -action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. +action-description-psionic-invisibility = Telepathically remove yourself from the vision of anyone not psionically insulated. action-name-psionic-invisibility-off = Turn Off Psionic Invisibility -action-description-psionic-invisibility-off = Return to visibility, and receive a stun. +action-description-psionic-invisibility-off = End your invisibility early. action-name-mind-swap = Mind Swap action-description-mind-swap = Swap minds with the target. Either can change back after 20 seconds. diff --git a/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl b/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl index 26d2acb87cd..30dde33451d 100644 --- a/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl +++ b/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl @@ -10,10 +10,31 @@ noospheric-zap-feedback = {CAPITALIZE($entity)}'s soul writhes with thunder from pyrokinesis-feedback = The Secret of Fire dwells within {CAPITALIZE($entity)} invisibility-feedback = {CAPITALIZE($entity)}'s wyrd seeks to hide from thine gaze telegnosis-feedback = {CAPITALIZE($entity)}'s soul travels across bridges composed of dreamlight -sophic-grammateus-feedback = SEEKER, YOU NEED ONLY ASK FOR MY WISDOM. -oracle-feedback = WHY DO YOU BOTHER ME SEEKER? HAVE I NOT MADE MY DESIRES CLEAR? metempsychotic-machine-feedback = The sea of fate flows through this machine +regeneration-feedback = {CAPITALIZE($entity)} possesses an overwhelming will to live +regenerative-stasis-feedback = {CAPITALIZE($entity)} possesses an overwhelming will such that others may live + +# Entity Feedback Messages ifrit-feedback = A spirit of Gehenna, bound by the will of a powerful psychic +prober-feedback = A mirror into the end of time, the screaming of dead stars emanates from this machine +drain-feedback = A mirror into a realm where the stars sit still forever, a cold and distant malevolence stares back +sophic-grammateus-feedback = SEEKER, YOU NEED ONLY ASK FOR MY WISDOM. +oracle-feedback = WHY DO YOU BOTHER ME SEEKER? HAVE I NOT MADE MY DESIRES CLEAR? +orecrab-feedback = Heralds of the Lord of Earth, summoned to this realm from Grome's kingdom +reagent-slime-feedback = Heralds of the Lord of Water, summoned to this realm from Straasha's kingdom. +flesh-golem-feedback = Abominations pulled from dead realms, twisted amalgamations of those fallen to the influence of primordial Chaos + +# Anomaly Feedback Messages +anomaly-pyroclastic-feedback = A small mirror to the plane of Gehenna, truth lies within the Secret of Fire +anomaly-gravity-feedback = Violet and crimson, blue of blue, impossibly dark yet greater than the whitest of white, a black star shines weakly at the end of it all +anomaly-electricity-feedback = A mirror to a realm tiled by silicon, the lifeblood of artificial thought flows from it +anomaly-flesh-feedback = From within it comes the suffering of damned mutants howling for all eternity +anomaly-bluespace-feedback = A bridge of dreamlight, crossing into the space between realms of the multiverse +anomaly-ice-feedback = Walls of blackened stone, ruin and famine wait for those who fall within +anomaly-rock-feedback = A vast old oak dwells high over a plane of stone, it turns to stare back +anomaly-flora-feedback = Musical notes drift around you, playfully beckoning, they wish to feast +anomaly-liquid-feedback = A realm of twisting currents. Its placidity is a lie. The eyes within stare hungrilly +anomaly-shadow-feedback = At the end of time, when all suns have set forever, there amidst the void stands a monument to past sins. # Power PVS Messages focused-metapsionic-pulse-begin = The air around {CAPITALIZE($entity)} begins to shimmer faintly diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml index 01fce382e37..fda467cb32d 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml @@ -101,6 +101,12 @@ - SimpleHostile - type: Damageable damageContainer: StructuralInorganic + - type: PotentialPsionic + - type: Psionic + removable: false + dampening: 1 + psychicFeedback: + - "orecrab-feedback" - type: entity parent: MobOreCrab @@ -292,6 +298,12 @@ solution: bloodstream - type: DrainableSolution solution: bloodstream + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 1 + psychicFeedback: + - "reagent-slime-feedback" - type: entity name: Reagent Slime Spawner @@ -529,3 +541,20 @@ - map: [ "enum.DamageStateVisualLayers.Base" ] state: alive color: "#3e901c" + +- type: entity + id: ReagentSlimeLoto + parent: ReagentSlime + suffix: LotophagoiOil + components: + - type: Bloodstream + bloodReagent: LotophagoiOil + - type: PointLight + color: "#ffbf00" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#ffbf00" diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml index 06ab02dedc9..93081d382a2 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml @@ -54,6 +54,11 @@ Slash: 6 - type: ReplacementAccent accent: genericAggressive + - type: Psionic + removable: false + amplification: 1 + psychicFeedback: + - "flesh-golem-feedback" - type: entity parent: BaseMobFlesh diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml index 43c0bdce12f..9a2bd4af69f 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml @@ -48,7 +48,7 @@ sound: path: /Audio/Effects/teleport_arrival.ogg - type: Psionic #Nyano - Summary: makes psionic on creation. - - type: GlimmerSource #Nyano - Summary: makes this a potential source of Glimmer. + - type: GlimmerSource #Nyano - Summary: makes this a potential source of Glimmer. active: false - type: entity @@ -99,6 +99,12 @@ - type: IgniteOnCollide fixtureId: fix1 fireStacks: 1 + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-pyroclastic-feedback" - type: entity id: AnomalyGravity @@ -131,6 +137,12 @@ - type: SingularityDistortion intensity: 1000 falloffPower: 2.7 + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-gravity-feedback" - type: entity id: AnomalyElectricity @@ -154,6 +166,12 @@ castShadows: false - type: ElectricityAnomaly - type: Electrified + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-electricity-feedback" - type: entity id: AnomalyFlesh @@ -245,6 +263,12 @@ - MobFleshClamp - MobFleshLover - FleshKudzu + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-flesh-feedback" - type: entity id: AnomalyBluespace @@ -295,6 +319,12 @@ anomalyContactDamage: types: Radiation: 10 + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-bluespace-feedback" - type: entity id: AnomalyIce @@ -344,6 +374,12 @@ releasedGas: 8 # Frezon. Please replace if there is a better way to specify this releaseOnMaxSeverity: true spawnRadius: 0 + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-ice-feedback" - type: entity id: AnomalyRockBase @@ -380,6 +416,12 @@ maxAmount: 50 maxRange: 12 floor: FloorAsteroidTile + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-rock-feedback" - type: entity id: AnomalyRockUranium @@ -637,6 +679,12 @@ maxRange: 6 spawns: - KudzuFlowerAngry + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-flora-feedback" - type: entity id: AnomalyFloraBulb @@ -795,6 +843,12 @@ solution: anomaly - type: InjectableSolution solution: beaker + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-liquid-feedback" - type: entity id: AnomalyShadow @@ -849,3 +903,9 @@ - type: Tag tags: - SpookyFog + - type: PotentialPsionic + - type: Psionic + removable: false + amplification: 5 + psychicFeedback: + - "anomaly-shadow-feedback" diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml index e157f8b7ff4..eca5b5e3758 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml @@ -4,7 +4,12 @@ name: glimmer prober description: Probes the noösphere to generate research points. Might be worth turning off if glimmer is a problem. components: + - type: PotentialPsionic - type: Psionic + removable: false + amplification: 10 + psychicFeedback: + - "prober-feedback" - type: GlimmerSource - type: Construction graph: GlimmerDevices @@ -90,7 +95,12 @@ name: glimmer drain description: Uses electricity to try and sort out the noösphere, reducing its level of entropy. components: + - type: PotentialPsionic - type: Psionic + removable: false + dampening: 10 + psychicFeedback: + - "drain-feedback" - type: GlimmerSource addToGlimmer: false - type: Construction From c2a20aae83dbe358230a5a5eb73c022efb7116de Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 18:55:56 -0400 Subject: [PATCH 16/24] Update RegenerativeStasisPowerSystem.cs --- .../Psionics/Abilities/RegenerativeStasisPowerSystem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs index 8f187b71f33..bc93afd7788 100644 --- a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs @@ -65,7 +65,6 @@ private void OnPowerUsed(EntityUid uid, RegenerativeStasisPowerComponent compone solution.AddReagent("Epinephrine", FixedPoint2.New(MathF.Min(2.5f * psionic.Dampening + psionic.Amplification, 15f))); solution.AddReagent("Nocturine", 10f + (1 * psionic.Amplification + psionic.Dampening)); _bloodstreamSystem.TryAddToChemicals(args.Target, solution, stream); - EnsureComp(args.Target); _psionics.LogPowerUsed(uid, "regenerative stasis", (int) Math.Round(4 * psionic.Amplification - psionic.Dampening), From 5c5ad44a4cd5ee3445b2b9c93245c1ec47a40451 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 19:46:29 -0400 Subject: [PATCH 17/24] Refactor mime psionics! --- .../Abilities/Mime/MimePowersComponent.cs | 12 ++++++ .../Abilities/Mime/MimePowersSystem.cs | 37 ++++++++++++++++--- .../Psionics/Telepathy/TelepathyChatSystem.cs | 8 ++++ Content.Shared/Psionics/PsionicComponent.cs | 1 + .../nyanotrasen/psionics/psychic-feedback.ftl | 5 +++ 5 files changed, 57 insertions(+), 6 deletions(-) diff --git a/Content.Server/Abilities/Mime/MimePowersComponent.cs b/Content.Server/Abilities/Mime/MimePowersComponent.cs index fd4fc2c2af9..757528a9cbc 100644 --- a/Content.Server/Abilities/Mime/MimePowersComponent.cs +++ b/Content.Server/Abilities/Mime/MimePowersComponent.cs @@ -47,5 +47,17 @@ public sealed partial class MimePowersComponent : Component /// [DataField("vowCooldown")] public TimeSpan VowCooldown = TimeSpan.FromMinutes(5); + + /// + [DataField("mimeFeedback")] + public string MimeFeedback = "mime-feedback"; + + /// + [DataField("mimeBrokenFeedback")] + public string MimeBrokenFeedback = "mime-broken-feedback"; } } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index b3bd3392434..e37864f2a35 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -58,14 +58,21 @@ private void OnComponentInit(EntityUid uid, MimePowersComponent component, Compo EnsureComp(uid); _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); _actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid); - //Nyano - Summary: Add Psionic Ability to Mime. - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.InvisibleWallActionEntity; + + // Mimes gain their power from a special Vow, but this vow extends to Telepathic speech. + if (EnsureComp(uid, out var psionic)) + { + psionic.TelepathicMute = true; + psionic.ActivePowers.Add(component); + psionic.PsychicFeedback.Add(component.MimeFeedback); + psionic.Dampening += 1f; + } } /// /// Creates an invisible wall in a free space after some checks. /// + // TODO: Consider separating this out from the Mime entirely, and make a standalone "Telekinetic Barricade" power. private void OnInvisibleWall(EntityUid uid, MimePowersComponent component, InvisibleWallActionEvent args) { if (!component.Enabled) @@ -98,9 +105,11 @@ private void OnInvisibleWall(EntityUid uid, MimePowersComponent component, Invis return; } } - // Begin Nyano-code: mime powers are psionic. - _psionics.LogPowerUsed(uid, "invisible wall"); - // End Nyano-code. + if (TryComp(uid, out var psionic)) + _psionics.LogPowerUsed(uid, "invisible wall", + (int) Math.Round(4 * psionic.Amplification - psionic.Dampening), + (int) Math.Round(6 * psionic.Amplification - psionic.Dampening)); + _popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-popup", ("mime", uid)), uid); // Make sure we set the invisible wall to despawn properly Spawn(component.WallPrototype, _turf.GetTileCenter(tile.Value)); @@ -126,6 +135,14 @@ public void BreakVow(EntityUid uid, MimePowersComponent? mimePowers = null) _alertsSystem.ClearAlert(uid, AlertType.VowOfSilence); _alertsSystem.ShowAlert(uid, AlertType.VowBroken); _actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallActionEntity); + if (TryComp(uid, out var psionic)) + { + psionic.TelepathicMute = false; + psionic.ActivePowers.Remove(mimePowers); + psionic.PsychicFeedback.Remove(mimePowers.MimeFeedback); + psionic.PsychicFeedback.Add(mimePowers.MimeBrokenFeedback); + psionic.Dampening -= 1f; + } } /// @@ -149,6 +166,14 @@ public void RetakeVow(EntityUid uid, MimePowersComponent? mimePowers = null) _alertsSystem.ClearAlert(uid, AlertType.VowBroken); _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); _actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid); + if (TryComp(uid, out var psionic)) + { + psionic.TelepathicMute = true; + psionic.ActivePowers.Add(mimePowers); + psionic.PsychicFeedback.Add(mimePowers.MimeFeedback); + psionic.PsychicFeedback.Remove(mimePowers.MimeBrokenFeedback); + psionic.Dampening += 1f; + } } } } diff --git a/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs b/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs index ad49075e65a..2c6b93e2542 100644 --- a/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs +++ b/Content.Server/Psionics/Telepathy/TelepathyChatSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; +using Content.Server.Popups; using Content.Shared.Psionics.Abilities; using Content.Shared.Bed.Sleep; using Content.Shared.Chat; @@ -30,6 +31,7 @@ public sealed class NyanoChatSystem : EntitySystem [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; private IEnumerable GetPsionicChatClients() { return Filter.Empty() @@ -74,6 +76,12 @@ public void SendTelepathicChat(EntityUid source, string message, bool hideChat) if (!IsEligibleForTelepathy(source)) return; + if (TryComp(source, out var psionic) && psionic.TelepathicMute) + { + _popupSystem.PopupEntity(Loc.GetString("telepathic-mute-message"), source, source); + return; + } + var clients = GetPsionicChatClients(); var admins = GetAdminClients(); string messageWrap; diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 35ceb82c876..c955626c53d 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -25,6 +25,7 @@ public sealed partial class PsionicComponent : Component [DataField("dampening")] public float Dampening = 0.1f; public bool Telepath = true; + public bool TelepathicMute = false; public bool InnatePsiChecked = false; } } diff --git a/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl b/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl index 30dde33451d..4af3ae267b3 100644 --- a/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl +++ b/Resources/Locale/en-US/nyanotrasen/psionics/psychic-feedback.ftl @@ -13,6 +13,8 @@ telegnosis-feedback = {CAPITALIZE($entity)}'s soul travels across bridges compos metempsychotic-machine-feedback = The sea of fate flows through this machine regeneration-feedback = {CAPITALIZE($entity)} possesses an overwhelming will to live regenerative-stasis-feedback = {CAPITALIZE($entity)} possesses an overwhelming will such that others may live +mime-feedback = A servant of the Silent Dreamer, {CAPITALIZE($entity)} bears His power in exchange for a vow of silence +mime-broken-feedback = TREASONOUS VERMIN! {CAPITALIZE($entity)} HAS BETRAYED THEIR VOW! THE SILENT DREAMER DEMANDS THEIR HEAD! # Entity Feedback Messages ifrit-feedback = A spirit of Gehenna, bound by the will of a powerful psychic @@ -40,3 +42,6 @@ anomaly-shadow-feedback = At the end of time, when all suns have set forever, th focused-metapsionic-pulse-begin = The air around {CAPITALIZE($entity)} begins to shimmer faintly psionic-regeneration-self-revive = {CAPITALIZE($entity)} begins to visibly regenerate mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience + +# Misc Psionic Messages +telepathic-mute-message = You strain, but are unable to send your thoughts to the Noosphere From efccb4900dd72bbb3040eb7fd02e0aecb20c0c02 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 19:50:05 -0400 Subject: [PATCH 18/24] Update mime.yml --- Resources/Prototypes/Roles/Jobs/Civilian/mime.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml index 8da2c34231b..253b5b89c1a 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml @@ -16,6 +16,7 @@ special: - !type:AddComponentSpecial components: + - type: PotentialPsionic - type: Psionic # Nyano - Summary: Makes the mime psionic. - type: MimePowers - type: FrenchAccent From c1cbaf9aa17c76779d8b3f05481427cb27003c13 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 20:01:56 -0400 Subject: [PATCH 19/24] Purge a bunch of unused usings --- Content.Server/Abilities/Mime/MimePowersSystem.cs | 1 - Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs | 2 -- Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs | 4 ---- Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs | 1 - Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs | 3 --- .../Psionics/Abilities/PsionicRegenerationPowerSystem.cs | 3 --- Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs | 3 --- .../Psionics/Abilities/RegenerativeStasisPowerSystem.cs | 4 ---- .../Psionics/Invisibility/PsionicInvisibilitySystem.cs | 1 - Content.Server/Psionics/PsionicsCommands.cs | 1 - Content.Server/Psionics/PsionicsSystem.cs | 2 -- .../PsionicInvisibility/PsionicInvisibilityPowerSystem.cs | 1 - .../RegenerativeStasis/RegenerativeStasisPowerComponent.cs | 1 - 13 files changed, 27 deletions(-) diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index e37864f2a35..9c108c4bcf2 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Popups; -using Content.Server.Speech.Muting; using Content.Shared.Actions; using Content.Shared.Actions.Events; using Content.Shared.Alert; diff --git a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs index 24459d29e22..3a0e912c08a 100644 --- a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs @@ -8,13 +8,11 @@ using Content.Shared.Psionics.Abilities; using Content.Shared.Chat; using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; using Content.Shared.Interaction; using Content.Shared.Mobs.Components; using Content.Shared.Psionics.Glimmer; using Content.Shared.Research.Prototypes; -using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; diff --git a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs index 38135e9a977..dac38290f22 100644 --- a/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -2,16 +2,12 @@ using Content.Shared.Actions.Events; using Content.Shared.Psionics.Abilities; using Content.Shared.DoAfter; -using Content.Shared.Examine; -using static Content.Shared.Examine.ExamineSystemShared; using Content.Shared.Popups; using Robust.Server.Audio; using Robust.Shared.Audio; using Robust.Shared.Timing; -using Robust.Shared.Player; using Content.Server.DoAfter; using Content.Shared.Psionics.Events; -using Content.Server.Psionics; namespace Content.Server.Psionics.Abilities { diff --git a/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs index 1e50a586b4f..b0e27027ad5 100644 --- a/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/MindSwapPowerSystem.cs @@ -9,7 +9,6 @@ using Content.Server.Mind; using Content.Shared.Mobs.Systems; using Content.Server.Popups; -using Content.Server.Psionics; using Content.Server.GameTicking; using Content.Shared.Mind; using Content.Shared.Actions.Events; diff --git a/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs b/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs index c81d3d7a0b0..480cf561001 100644 --- a/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs +++ b/Content.Server/Psionics/Abilities/PsionicAbilitiesSystem.cs @@ -6,10 +6,7 @@ using Content.Shared.StatusEffect; using Robust.Shared.Random; using Robust.Shared.Prototypes; -using Robust.Shared.Player; -using Content.Shared.Examine; using Content.Shared.Popups; -using static Content.Shared.Examine.ExamineSystemShared; namespace Content.Server.Psionics.Abilities { diff --git a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs index 893d1bc4cfa..715774ae1c7 100644 --- a/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -1,5 +1,4 @@ using Robust.Shared.Audio; -using Robust.Shared.Player; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.DoAfter; @@ -11,8 +10,6 @@ using Content.Shared.Mobs; using Content.Shared.Popups; using Content.Shared.Psionics.Events; -using Content.Shared.Examine; -using static Content.Shared.Examine.ExamineSystemShared; using Robust.Shared.Timing; using Content.Shared.Actions.Events; using Robust.Server.Audio; diff --git a/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs index 77075dab206..e488aac7879 100644 --- a/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/PyrokinesisPowerSystem.cs @@ -5,7 +5,6 @@ using Robust.Server.GameObjects; using Content.Shared.Actions.Events; using Content.Server.Explosion.Components; -using Content.Shared.Mobs.Components; using Robust.Shared.Map; namespace Content.Server.Psionics.Abilities @@ -16,8 +15,6 @@ public sealed class PyrokinesisPowerSystem : EntitySystem [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly GunSystem _gunSystem = default!; - [Dependency] private readonly SharedTransformSystem _transformSystem = default!; - [Dependency] private readonly PhysicsSystem _physics = default!; [Dependency] private readonly IMapManager _mapManager = default!; public override void Initialize() { diff --git a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs index bc93afd7788..be435d1acc5 100644 --- a/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs +++ b/Content.Server/Psionics/Abilities/RegenerativeStasisPowerSystem.cs @@ -2,11 +2,7 @@ using Content.Server.Body.Components; using Content.Shared.Actions; using Content.Shared.Chemistry.Components; -using Content.Shared.Bed.Sleep; using Content.Shared.Psionics.Abilities; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; using Content.Shared.FixedPoint; diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs index 19c1a2c616c..c21444139d1 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs +++ b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs @@ -1,6 +1,5 @@ using Content.Shared.Psionics.Abilities; using Content.Shared.Psionics; -using Content.Server.Psionics.Abilities; using Content.Shared.Eye; using Content.Server.NPC.Systems; using Robust.Shared.Containers; diff --git a/Content.Server/Psionics/PsionicsCommands.cs b/Content.Server/Psionics/PsionicsCommands.cs index 3f9ee794b38..76c9022151e 100644 --- a/Content.Server/Psionics/PsionicsCommands.cs +++ b/Content.Server/Psionics/PsionicsCommands.cs @@ -1,7 +1,6 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Psionics.Abilities; -using Content.Shared.Mobs.Components; using Robust.Shared.Console; using Content.Shared.Actions; using Robust.Shared.Player; diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index bf829477609..7e97af34b80 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -134,12 +134,10 @@ public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, boo return; var chance = component.Chance; - var warn = true; if (TryComp(uid, out var bonus)) { chance *= bonus.Multiplier; chance += bonus.FlatBonus; - warn = bonus.Warn; } if (applyGlimmer) diff --git a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs index 28378d363ec..29ee615dd49 100644 --- a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerSystem.cs @@ -11,7 +11,6 @@ using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Throwing; using Robust.Shared.Timing; -using System.Xml; namespace Content.Shared.Psionics.Abilities { diff --git a/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs b/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs index 27a0903e224..7caa6100806 100644 --- a/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/RegenerativeStasis/RegenerativeStasisPowerComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Actions; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; From c753eec130b3a82c52413ce5be3be2480eb24e0e Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 20:27:53 -0400 Subject: [PATCH 20/24] Update mutants.yml --- Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml index 462b3254f1e..44c5cde0323 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml @@ -140,6 +140,7 @@ - type: Psionic removable: false - type: MetapsionicPower + - type: InvisibilityPower - type: AntiPsionicWeapon punish: false modifiers: From 87850a78f228ff8d3798ca9b5277b3fdfe85dce5 Mon Sep 17 00:00:00 2001 From: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com> Date: Tue, 11 Jun 2024 02:34:02 +0200 Subject: [PATCH 21/24] Misc Resprites! (#435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR add a bunch of Misc Repsrites and others fixes that i have missed. This PR will avoid ALL .yml changes and will focus on only "Sprites" changes. Here is the following list of Changed Sprites: • Rust Solid/Reinforced Walls • Stunbaton • Banners • Radiation Collector • Riot Suit • Bulletproof Vest • Armor Vest • Slim Armor Vest • Fire Extinguisher • Stock Parts • Rack • Screen # TODO - [x] Port/Resprites. ---

Media

![image](https://github.com/Simple-Station/Einstein-Engines/assets/45297731/3ad4521e-093c-4e3a-a088-6e61e8e66a49)

--- # Changelog :cl: - tweak: Rust Walls Sprites. - tweak: Armor, Bulletproof, Riot Suit Resprites. - tweak: New Banners Sprites. - tweak: Stunbaton, Fire Extinguisher New Sprites. - tweak: Rack/WallScreen resprites. - tweak: Stock Parts new sprites! - tweak: Radiation Collector has now a new sprite! --- .../equipped-OUTERCLOTHING.png | Bin 411 -> 622 bytes .../Armor/bulletproof.rsi/icon.png | Bin 244 -> 236 bytes .../Armor/bulletproof.rsi/meta.json | 2 +- .../Armor/riot.rsi/equipped-OUTERCLOTHING.png | Bin 1562 -> 648 bytes .../OuterClothing/Armor/riot.rsi/icon.png | Bin 293 -> 231 bytes .../OuterClothing/Armor/riot.rsi/meta.json | 4 +- .../security.rsi/equipped-OUTERCLOTHING.png | Bin 343 -> 413 bytes .../OuterClothing/Armor/security.rsi/icon.png | Bin 252 -> 193 bytes .../Armor/security.rsi/meta.json | 2 +- .../equipped-OUTERCLOTHING.png | Bin 428 -> 484 bytes .../Armor/security_slim.rsi/icon.png | Bin 277 -> 195 bytes .../Armor/security_slim.rsi/meta.json | 2 +- .../fire_extinguisher_closed.png | Bin 429 -> 303 bytes .../fire_extinguisher_open.png | Bin 420 -> 315 bytes .../Misc/fire_extinguisher.rsi/meta.json | 2 +- .../Misc/stock_parts.rsi/adv_capacitor.png | Bin 420 -> 345 bytes .../Misc/stock_parts.rsi/adv_scan_module.png | Bin 927 -> 725 bytes .../stock_parts.rsi/advanced_matter_bin.png | Bin 426 -> 430 bytes .../stock_parts.rsi/bluespace_matter_bin.png | Bin 627 -> 1283 bytes .../Misc/stock_parts.rsi/capacitor.png | Bin 254 -> 391 bytes .../Misc/stock_parts.rsi/femto_mani.png | Bin 589 -> 416 bytes .../Misc/stock_parts.rsi/high_micro_laser.png | Bin 278 -> 333 bytes .../Misc/stock_parts.rsi/matter_bin.png | Bin 462 -> 457 bytes .../Objects/Misc/stock_parts.rsi/meta.json | 50 ++++++++---------- .../Misc/stock_parts.rsi/micro_laser.png | Bin 263 -> 357 bytes .../Misc/stock_parts.rsi/micro_mani.png | Bin 366 -> 390 bytes .../Misc/stock_parts.rsi/nano_mani.png | Bin 394 -> 428 bytes .../Misc/stock_parts.rsi/pico_mani.png | Bin 373 -> 404 bytes .../stock_parts.rsi/quadratic_capacitor.png | Bin 729 -> 367 bytes .../stock_parts.rsi/quadultra_micro_laser.png | Bin 371 -> 421 bytes .../Misc/stock_parts.rsi/scan_module.png | Bin 491 -> 478 bytes .../Misc/stock_parts.rsi/super_capacitor.png | Bin 419 -> 291 bytes .../Misc/stock_parts.rsi/super_matter_bin.png | Bin 397 -> 523 bytes .../stock_parts.rsi/super_scan_module.png | Bin 1182 -> 564 bytes .../stock_parts.rsi/triphasic_scan_module.png | Bin 631 -> 770 bytes .../ultra_high_micro_laser.png | Bin 304 -> 389 bytes .../Weapons/Melee/stunbaton.rsi/meta.json | 2 +- .../Melee/stunbaton.rsi/stunbaton_nocell.png | Bin 431 -> 253 bytes .../Melee/stunbaton.rsi/stunbaton_off.png | Bin 362 -> 224 bytes .../Melee/stunbaton.rsi/stunbaton_on.png | Bin 1091 -> 1753 bytes .../Decoration/banner.rsi/banner.png | Bin 1577 -> 397 bytes .../Decoration/banner.rsi/banner_cargo.png | Bin 519 -> 415 bytes .../banner.rsi/banner_engineering.png | Bin 724 -> 381 bytes .../Decoration/banner.rsi/banner_medical.png | Bin 428 -> 420 bytes .../Decoration/banner.rsi/banner_science.png | Bin 537 -> 417 bytes .../Decoration/banner.rsi/banner_security.png | Bin 742 -> 402 bytes .../banner.rsi/banner_syndicate.png | Bin 569 -> 394 bytes .../Decoration/banner.rsi/meta.json | 2 +- .../Furniture/furniture.rsi/meta.json | 4 +- .../Furniture/furniture.rsi/rack.png | Bin 250 -> 315 bytes .../Singularity/collector.rsi/ca-o0.png | Bin 1477 -> 152 bytes .../Singularity/collector.rsi/ca-o1.png | Bin 1415 -> 149 bytes .../Singularity/collector.rsi/ca-o2.png | Bin 1383 -> 151 bytes .../Singularity/collector.rsi/ca-o3.png | Bin 1421 -> 148 bytes .../Singularity/collector.rsi/ca-tank.png | Bin 2071 -> 198 bytes .../Singularity/collector.rsi/ca_active.png | Bin 10749 -> 3159 bytes .../Singularity/collector.rsi/ca_deactive.png | Bin 10791 -> 3131 bytes .../Singularity/collector.rsi/ca_off.png | Bin 2755 -> 990 bytes .../Singularity/collector.rsi/ca_on.png | Bin 3212 -> 1521 bytes .../Singularity/collector.rsi/cu.png | Bin 558 -> 990 bytes .../Singularity/collector.rsi/meta.json | 2 +- .../Singularity/collector.rsi/static.png | Bin 664 -> 1608 bytes .../Wallmounts/screen.rsi/meta.json | 2 +- .../Wallmounts/screen.rsi/screen.png | Bin 266 -> 398 bytes .../Structures/Walls/solid_rust.rsi/full.png | Bin 2155 -> 2228 bytes .../Structures/Walls/solid_rust.rsi/meta.json | 2 +- .../solid_rust.rsi/reinf_construct-0.png | Bin 2293 -> 2248 bytes .../solid_rust.rsi/reinf_construct-1.png | Bin 2303 -> 2272 bytes .../solid_rust.rsi/reinf_construct-2.png | Bin 2340 -> 2269 bytes .../solid_rust.rsi/reinf_construct-3.png | Bin 2339 -> 2272 bytes .../solid_rust.rsi/reinf_construct-4.png | Bin 2372 -> 2268 bytes .../solid_rust.rsi/reinf_construct-5.png | Bin 2381 -> 2265 bytes .../Walls/solid_rust.rsi/reinf_over0.png | Bin 3230 -> 3233 bytes .../Walls/solid_rust.rsi/reinf_over1.png | Bin 2990 -> 3206 bytes .../Walls/solid_rust.rsi/reinf_over2.png | Bin 3230 -> 3233 bytes .../Walls/solid_rust.rsi/reinf_over3.png | Bin 2990 -> 3206 bytes .../Walls/solid_rust.rsi/reinf_over4.png | Bin 2903 -> 3191 bytes .../Walls/solid_rust.rsi/reinf_over5.png | Bin 2908 -> 3128 bytes .../Walls/solid_rust.rsi/reinf_over6.png | Bin 2903 -> 3191 bytes .../Walls/solid_rust.rsi/reinf_over7.png | Bin 2840 -> 3083 bytes .../Walls/solid_rust.rsi/rgeneric.png | Bin 2332 -> 2248 bytes .../Walls/solid_rust.rsi/solid0.png | Bin 2943 -> 3281 bytes .../Walls/solid_rust.rsi/solid1.png | Bin 2638 -> 3147 bytes .../Walls/solid_rust.rsi/solid2.png | Bin 2943 -> 3281 bytes .../Walls/solid_rust.rsi/solid3.png | Bin 2638 -> 3147 bytes .../Walls/solid_rust.rsi/solid4.png | Bin 2554 -> 3156 bytes .../Walls/solid_rust.rsi/solid5.png | Bin 2565 -> 3128 bytes .../Walls/solid_rust.rsi/solid6.png | Bin 2531 -> 3156 bytes .../Walls/solid_rust.rsi/solid7.png | Bin 2495 -> 3083 bytes 89 files changed, 34 insertions(+), 42 deletions(-) diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/equipped-OUTERCLOTHING.png index 81512185135281cfe088087f850d732431838ab5..78f7cd2453efbbe1b9cd75a835227c0d872a0a16 100644 GIT binary patch delta 589 zcmV-T0c0004VQb$4nuFf3k00004XF*Lt006O%3;baP z0000FP)t-sAs`|d7#c4wF+xE^00014tKnM!000GaQchF<|Ns90`?d~KkuDd10oO@H zK~z{r?N-~C#2^S$+yDROO^}%af}OLoPkWk&Rt$~tQnj65;t_ZR2rJNLpsy8u$ZK?Y zDNJ%}gAty&LhE3F(JgMUyQg-O7`Ow(E;0Ch-<0E=3Gdx4cN0n1-fy)%gxSzJMW5T18l)qzGUL_f@tjpz+< zvt+?Opb5`=fr(lm;cWm)TB!Qar-C10uf{%<2H1i?qY0la0$NVM`HKmmxu_qo8VF-R zverm>E5HGS%`yMl6>!f*cc^E4TLVRg zb|{yKLP02=pLR!Z4vv9a2oEQ%2@t9ysd9krRBji00VI_I*sVCo)gTBZIfZ~cc!}6J znBCtA$N?+y0=7RJF9f7;EFL9ZAnsWxy7215)5$f&^090n8HMo#DAP+%`a2&~PsR)T_U43i r`>O}hgRHI-ZKL3qQraEx&+`q(U?z<000005AXa0qg;gB!3T3OjJd{z`!9OA}=m685kNuK}1D=0xBL50000< KMNUMnLSTZ)D_7_M delta 216 zcmaFE_=RzTNG(&67Iy3=9mM1s;*b3=G`DAk4@xYmNj^P&&XT#1%*@E2){8 zT7(1#I61ob`uOYV8tUsAN{CB7y?XB)NN-7yUogXeC@^Q5eFZ2O>*?YcQZXkvK_aRl zu%ppaAkyJRpy$O%22TeU6Wvgj$iRk;H*W3}INIapc+gFRF+DxqJ%y2PK^LD0;{q=J zKUyuly{Z9ow32d^8k7{?J?hgs5YhKYPvJ#If=wQdEM+D0I@wryK$ zK|hPNp?5(5Qrud|(x6V60i!+`AVDNhE2gkCKqa=n++-=my3V%|mSS-4^T#j5Whpy{x zp->naWT8-SbzS%B%)$t>azHGIU|E&{ux%U5vQSkO!!WQc3q?`Tb)9y*jiM;{>%M>g z{?7h`ARGX&v$I1km*er{$KLnL%S%ktWOH*90Mj%B%f7RcuIuj0moEW0Iyyp86j4qp zm7>$>U>F7fmVag8uWJ~_#0HqP1Ee5IIF3WJ*#w~1>jBVcG$@zLD2n2h0o3btGMP-s zVUFhL=!mNh==FN&x{hU8=(_GXz%fnX! zCr_Tx?RI(h?j4;@heo5}efK*+)C;OZXanT)c`pL`{XX${9Dr0RMXgqwi2?lb{SI&( z2T779azLOpK+tNnMn*unTn;~~F%VPGFbpI~!f_llO(UPrQ>)cN_jjwMR#sNrjg1We z_V)GwSbtqz9T@=!2L}N7+Z-MqP9++2U3bNR0n+I-nx=VcL95jQ2J6LCBfz$8WLaio zV}qTY9d>tjfx!YOBEYgNPq^5NZQBkx#OvJVfJUQ1zuyNyRaNr&yyt+;%}rOdxyu28 zrhskRUWg|W2@;6}s;cth#S1!}4w59%>-DfKi+{bnJ(ic3#~Sb`*`plr{rh(mMe%eS z#{st*;D_~G-){`VU~zHL1t^!xq|<3ytro|}$KLm=Z6@Wa1H>UrAl8I&91u!D@gKl6 zO(f|*+c}PdII5OPrK#=!D=RD4jJdkH>OOt?)T>jk*Eu*ih~3s^6rpwjRaMb64Zz!O z#DDgr>-x}vWLo$K@%;I7S9rcws}YaK>G%7jQYi|Bg4^kIs8lMkFp(F^01r(*pZ9*N zs>=5Ec5G{F%k`I4DwR-uhB83A-KO1c4;?ndQ8|>pjPv3U0E*-%RUN%KRY2C%!kJCrP)H6k12PssX{qI1%GsQyA3AU-cqt(Cpd%4#|Hp&|eV z*W@2ReB$KfWGE@pKX(H67uW!2XMZ;FdG%WP&kzAuIXerUn4RcWIGL3By|DhE5%6c} z>qPwpUa^<*%kJ5kO-4&oC=>uFl}g@vAXa8smf7FmpUFpp*n8*m-$1@orqu1+Et(9Y?Qpp=U3>@WO6z^iJ zP+ePFa{({Z#q$;5UJgPq{1oP@K4F+x*@cA#SJ!o#%_h}q^1W9#eQdEW(i_39Pd+uNaa2)qXPX}8;C zv)OT)&V+0>OS|13tL<0@P*s)F(^FvZCP=*1y-)mdPft(16nR)CjsP(Q?i;_{Ng`mZ zH9$OsT3A^4DQ<=)d6+fPQTWFS{2)dUQ()3vAR^*_&cCjfnMOT}vJF@O0000Z0_Opc7=Hu<0001iRA;s zhy2}>?OaO#^-8(y@(FA)p9eWCz-`QlwR+B1+qV09D>001^nOjJbx001^NHcCoL78VvNDk?xgKsY!! zA|fJ1MMW|)G9e)$8X6iVCMFjb7cDI)SE4#n2oYy4)5;v}@i)w`mU&-PE2kdblz~OKU_~GCY zU_@qzgNYXqX08ZVw|N-T(jq07*qoM6N<$f`O>_%)r2R7=#&*=dVZs z8qVhF;usR){&w0~K4wLZ7RPUY=kMgHzrnldpGjs_#-Z{E)dv<|0+zh?_FBMlK*6Eo zjd43?LI0e9v%N0j3(cnOQ50i4^s#G4{xrorIXi_+2exkK11d@J6NP{teNtMA%f%Ap@|Ef)8TT*N;hbL@)zu-Wn#WCA+%U;gEsNPifSaso@?YlqL zsmm|txqkKM_BW5twRY(={M^2!VUB7!lRCRQW3BQVnIGIQ-TC(Hy_}zrZF4pJ!SA!& o59e0N{b~YQ^5Hwv^{-3|(`{D8KYFbO3@8Q;Pgg&ebxsLQ019rRi~s-t delta 328 zcmV-O0k{601J?qO7=Hu<00013M{Ml?001gbOjJbx000;l86zSlDJdyBIX*l(KS4l1 zLqbMHMMYCmR#jD1S5{hCSy^swZg}atJpcdz0d!JMQvg8b*k%9#0NY7KK~zY`?bAI9 z!axuO;D;lm_XK)_g;9$I!B$%l4-iO^Dv&}XWsb7l66+MXL4Q~)TfK{=Gw}xmv5>s( z4If(!J3yarW@v!vn~rr1N|*P(%9#(MgB-}oUNlWEU!Wr|6xcVl2G#YVz^OynFgu%u^ceSs7=k2L=K3{k`*B4{y+8kv*_CeV9)BC6UeGVA!i^5W1tIw zK?j^ASUiBpoQ3Ycer6o_mkLF+OFCcxZF;A3E<_5e;KNEk5PWxgzZ aev=zDtvcEW$;fR00000%X=h6_*`Bpy@s#~XKV;b%Dk5f};t TA?!hr00000NkvXXu0mjfnGHSE delta 224 zcmX@e_=j()+*x_<~Rj6z)&xuGThXyT!;%7W6jK_LrI$Y>7XqHrJ zNYUNJpzWf-;K_X=Ktxv|dm^ioBa??;dO7Zm6f1oo`xWD8G908>YsI`;45b=s}P=DNk@J3oITnGT2Lh(kV zMu=gJ080j30~CuT004(I!j?guuO&3epshu&6OhgqqYa%?7Ld*^-E)@|$a48>&kEUF$H3f{X;5R<=G-p|n%%8| z+Gs#$yzRKFk$f#M+Zn;^0d1Uq{74Q-z_h(RnmzFBoXKGjkS*9{;Jtk=U{mj9U^k~1 zz*l?Vd75qH5?oxuq=y-uai4h!UYVW*&6=RcME%YsP9}n_j|j7wrSrQSfg_;&THwAP Xj1pUE|AAcn00000NkvXXu0mjf14_G% delta 372 zcmaFDyoPy#W4&~MPl&6&o}rVYi=u*3NN_+}YNCX=q_UElxtRq61A~^0$ORz9SrX(I z{2vM!g8eImK!ODxk;M!Qe1}1p@p%4<6b1%HR!6cp39*bI#YIya5+2;=Su{mR zL`pbyN$@FMDMz^*d&E-SJWEe=kPELkDSdF8^M-tG#?1mB)F;g6+BE0Dv-^7v{bE_6 zP#0y=q*AeEMG}jJN=7!ndOoYXK$C!hA_t>zQ#^NH^SNzviY^syS-OCAQ(Q@TKzn;) zaBA-4?^$*)PDW|1cx6@jp+|w6{mZdi)2~ce)b%v$Qi5v9i@D){xtkl(SvX~XeR#mV zmdKI;Vst09n8=KmY&$ delta 261 zcmX@iIF)IFL_G&H0|SH0lCEPwim^Dz-HBn{IhmJ04okYDuOkD)#(wTUiL5}rP=HT} ztAx0uzMi3zqf1(9Vn}cRP}tyDaWIf#D+%%o{tp5SoA)o-4;0}n@Q5sCVBk9p!i>lB zSEK+1t2|vCLoEE?P6*^Xs=&cg&RF-p{^@ou`AasJxJwop&gSblTJ=zPQagi9o(!fU*(~tYWo*xpm^e1`L*wa96$4YzXx;%gQu&X%Q~loCIF)) BUr7J} diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json index e0d437b8ece..afdd4120b55 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6665eec76c98a4f3f89bebcd10b34b47dcc0b8ae", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_closed.png b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_closed.png index 7c99615ffec1d6df58698e5dcf25e1fe7e9e4ad1..b84389a06bd18e2ef60cf5e4bc94fde649b421bf 100644 GIT binary patch delta 287 zcmV+)0pR|v1Fr&*8Gi!+002a!ipBr{06b7kR7L9a{d;?R<>lqJwzgSWSvEI1QB+ko zH#Z?6CQktzj0Ga;bac7}1*HH0**G}nO-+f1hlPohyTQri=jy`bCkFrk00DGTPE!Ct z=GbNc005y$L_t(IjkVIz4#OY_1yHCWV%4qs|NlDL?QLR{Hh<;yo?J*2;Gsc0iI*AB zcyazDp5&95h`>9$ok9qV4iU$K0>#Ma{o;7IgF*ygX75mY2IK%DNsXxZzhnop=3Gjt zqKZd1NM6@kYZ2p01ut#L*8#;T*eh-WdK!fH2>{rBU^78GKr?}~)_ZTwTp{@Y_6_s5 l_=^2M*tv?7(ikkiG9QYM3CE9d_ALMa002ovPDHLkV1i3Yeu@A9 delta 414 zcmV;P0b%~H0<8m(8Gi-<0047(dh`GQ010qNS#tmY4#WTe4#WYKD-Ig~00Cb~L_t(| zob8suYJ)Hs$G>QIDfH;*!6JzFb@Y%HpJOKr(s$T16b5^Vflsj)C?1O23kZU+GH)x4 z!Khij&_JOZG&U+_{voJ|`1_d{lOAIX+DxxidMD5c=n&X$2!9(48Nu_SD8!VqEQ>TI zfN+%fGT$LhQ=pWd4A&wM$1xii0He`0hchMkSpvTAi|)?z9G1%nwxtJ<#iCB77b*!L z9Kl(Z3D7+uEaAsT)<6#&!V!#hL(_yC!vJ}$BBCsSa16pEcwCmyrxeDk6+G?RPL)s+ zkmD}j9^o>d9Dm-Q%^>8*2Zd*zJqz&dWv(U@04|`C%l{MpF9A6NS`)BsTf0Q1A#far z-ThMky4Jy;vG3&;(K46cZq-R|hVTK5B0LA40zNEavY1L(hp8>j(KiU0rr07*qo IM6N<$f@^xR4*&oF diff --git a/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_open.png b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_open.png index 7909d14f269cea522d6e0b9033e83d81451f94ca..0cf52827fca440fc3ec9a0d956ea5e53e86f2176 100644 GIT binary patch delta 299 zcmV+`0o4Aa1G@r{8Gi!+002a!ipBr{06b7kR7L9a{d;?R<>lqJwzgSWSvEI1H#av? zR8=7%CQktzj0Ga;bac7}1*HH0**G}nO-+f1hlPohyTQri=jt*%SjGSV00DGTPE!Ct z=GbNc006B?L_t(IjkVI<4#OY_1z@Ry#lNlg{oi$Hcd=xm$$y%0d%oZa(U?CCz#`se zK;q51MLfypv2k{?9T5pE04M^z_XN&umWFSj$9*RvXF>K12>RU^<{*Yed^c%;V@f&a z!lGyhQjDdP%;Sy?JY+)}g4S^XTE`opzn}mwC%$q8Jmth!t^m2PjM2fzEWz;xhYD&3 xRRvt@d7iaSIwW*(qoj!TAM{*A2w|50GA|%_3Obz{x$^)3002ovPDHLkV1j(`fVThu delta 405 zcmV;G0c!rc0;B_w8Gi-<0047(dh`GQ010qNS#tmY4#WTe4#WYKD-Ig~00CA>L_t(| zob8rDYQjJihF{cf+y`8T>}zo`N{$jCc!!=LQ0OHJo+1~JEF^k?Kp_PUj6i z5!>24lZgoVVF;6feBV4~GSib%!adWwE4>wH1?mpcsM{`2pBFF@Npa~D^Y0?BAl{S@cv{1fqK3%zQGo_ zEI-bk@-!L&D1QM?SOQf3I`cTcV%mC*3!MLOFtqoL3tWOtve*5+f zj5KqAp1#Tdz=%i&c{xdjgM0VkQubf{_Y($tPbr4yw|+86sEaoJ4#VpJ zkmWwUK4A94N6#7Tot?0%Q&81pSifR9!@7wV7z6}G7#L14Fn@e_|Av7WKyn%YIRqC7 zQWF7(0th_6B@7G+z5j$lf+z<(xP2Y$OMLQApFLr?3iS29*9h?s+X;sP(IEh`1ewML zU?G4=2Mh$$0bU0{HG|UuCzGd?kaGbAu&wvPl6AY9mLS#n(ts$fa3?4mtz(5Kh vDhi143Atcslp=(q4j6U7r~^hFKuZ7sUu$URzcXAb00000NkvXXu0mjf%t3Z? delta 372 zcmV-)0gL|G0;B_wIe%73L_t(|oMT}aU@+={Q3s4VVAKI*J7CK6)&D{CojZ3JR8&;J zVmXr$uxGr_3Kv{wr)QRb_B!$3}A~v0DmM-Dj+)-z$^m=`PS{I z4y36ADoU&wI(jxR`1%H5$Zg)d0W80B+fv#&00g#gZDa8Cu7dMnzV7diXL$LN#%bW# zu`3Mrj^1F)?u4f?sH~g?r(yCmbwG8^G*BM@9~g)k1?$&ufXUO&0U+@Dy)4+W8;gUSI|2xSrhinJfcuO)cOnQiuPjd$-xIbjX$EUdis0BtHFzSE-cK`sQWjik93kwuy7iazGD-ygRx;rRlt_(|fp{@l-)9NW=R$Obp zxN5Yfy?hYVl@?g=g1cj9(w4R_pLW~tZGSc|Jwqer*Ux*lmQ|K@^PePfbTK7}Ff3+} zU_GG0;Kpdc)ey+g0}+~$!ua`f|F4y|-@U2XBVcO0_NOP)rVmG#KGks8f3Df!?9ZJ{ z@9tG!*l$*DyYKrxQBM1NcG6bWZ{F2v)+*eZFkkz@*HbZnX2kY2um4^6`PkJbj8@!p zca;A2i}|@zuv>qTN$!dBq93x>n*T`ty>ahWS=STW-s`yRx&6vj^wtFXk32vAyuSbb zb+&v`+OvNvZ_CTRJvMpj^8dE`UuhSZ)W3W>`^T3$=F{iD{eQUmcyhYNWT1UNZ@+tx zZmt(QuXj<(;dSn&U`KrT@Zg91iG+sz(-USs6=^%a_Rx+$XRmIV{w4Bh`tSWat$s=@ zc_v<^T=waU(u`w&R2b{^mQK3g5wi13c68k*$$;;EqN!1z_lmP%4I(U|1{3Nq680O^LGn~`E%P+CrZQshTKb_suGpy=M*nTrUH`rfdpKwX)=jNFC_A2>p zYM(x*ZICbU`}CImz)z4F?^zF`M-0pgq_{#6`pY2ko6+DKbHW?;12^~^RzCgB+?M^M z^XpIb^b>ZGd(Zt*+aS*neEK_kPqgy=E5H6+PFtb8pA({Zqdddoy$UGCK~uy1wAg+R U#Ul5ez;w#s>FVdQ&MBb@0PF}o;Q#;t delta 918 zcmV;H18Mx#1)m3y7k@wq1^@s6)-goag_de%+_nZL)K@bE%5Pt+AcqnBe++zqbRg{a1 z!9T;9grF|~>jb#L2T%{DB$UGfpkkE(rA#DUH<-nK@D~8or|Sl@eg>js3`xJW4`_`U zrHnyq%-G%{WP70-fN4-j_1%*>1dfd0pm7`=7{`FtLUM8da~;Oh51nZVf9 z9yWdI{H+H3^y3c$0O|Z60KnJGHUMCCtAIC`&K-IXDoNjNu&psOcRP-r`TLl-a0^;% zI(kOuR=`dVw;+~|aw zZpQHDMc?(km_Nyia2XhQ;KgmM7&-PDss0Zbau(UfK$*W|b=Bv0p_P?RqqWB7W&t}p zzmd!3fPd_UKVJh&^tIOL)6cNNq|Y*c7o|+ZIo-wkA{K4reG!W`kn~ArT-xc*hPSGb zcDh--m{7FQgtu$>li)WcD?g@`F-n=3-#bAm6LF&LMk!;8pZ@Q{Xv4l4N7w}C%m*%O@FAIqE z@f1obqyzaD40vP4u{ffx9~2JN0s;1cy!f>gfXj?4^d?U^vsf$i2G)!tJu^A$^UI+> sp)BA&kO{IAS`kR_1wjx5L9pU4$ev$+sSgitw*UYD07*qoM6N<$f)NM21poj5 diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/advanced_matter_bin.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/advanced_matter_bin.png index 819275417bab0d14b6904c6877cf9435fb74a812..7533331b835f14da64cc2356e30d496d6f8282ab 100644 GIT binary patch delta 382 zcmV-^0fGLi1Fi#*Ie%bDL_t(|oMT}aU@+={Q3s4VU}!qP(=*^dy4d#Z8yIQs0GP#r z5s}!W|C8eoq8$LT9A@#9zHSC-DH#-bBuD)x#Sug~0AzVVSrJ2Od>n&-pa@nq>sKsi zxO(*hLrrrhNsb^p5k7tP1SJ$ed~yKE4*2l?4LlT(Ed_~#z<`jf%%}QKnKvE2S)|WF;sH^3_N`F98AL;f-L^v_H~ByXD=|23MeXwU=9LVjt)Qx zmzab|^^y>l6aE~V$FQ9U^)xApK_Rl`#9N}&(yT7Xun=a*x=3XQfKoUJfJ!}iIZ3Q) zw6zTwmMmI8w0D^#hd`RkKZaRAhk!~(eBjZe2h=MGFI_yt2+Z66LB4x_>nBiz6Q^2G zA|%#wq8$JNP(%M;y$T6tbN~x>QYT)D5l9Vkkb|OzkfVum0CFm}v*HJ*fzP+@g86q8?*J`l09zCq;>)ml&1SG8 zK$e5p*wO{b4gi4%JLUf$+4`K}A;|K)F0dHyq%vR_e}t<6g%k)NTfTGKQoLD^Xa^{` zKLi`(!?g0hu5%VI*mAH#+|$AGAYUVg0!_dfQ3M7OZfj@ zms+rxWm7oA^O=u<5X=vX$A(C zt)4E9Ar*7p&fT9a;wW>}T|`jO)RDV>LHcYDrL4@t8>)_;s<&cgCz*tm9kf6Ah--Di z)I&}px~gv8+@h1NNrbo@oxC6;(c*J#zFzU`zqfAQtlYl&J#*sbn~(3k+4tt$y|>@b zz6oUE;$?7VK5&7-z=VMZ#_Z&kklr8he#_glucz-0Io7i5{&vQwA8&p{Y>E5*Y3<$9 zueR}OuBwY?>)0VyxAD)G$FJSo&%2&}zjgiB-rmx5cEyyxF4GkR<>y6q)Wz%n7uMF@ zpOqKCR?kFwBJ0~P+RDopJuUjW(ERU?Z;x)dZHd`yHaEh)P+sBDuLbVBH~wAZwTK9f z>%4W*g5#22{I5mYRY_Hq&$3Eno=Cj0uT89e9k%GZSIU8I+JG*L}t3CdSmK2ZS*S%I+9aehr-G+JDGds`q>-=QwS57jhe?Ps*@x`xL-U-1gZnO8tE!iuo z(^PlfSm9mGpQ!7dhn?M*Fly;8e69YD`QE=9Yqie5U%m8I(dq1Z`?@9H^7dt~OE2XW z`&(c z-5EN(i-L=b)9)S)XO#Wg8X>*s_3>K?tMk8>me_Akj1-y#j3$$R!i)G-etEAfJF!x0 z#~&q~lE3@TGdvEyTfaRu@@<9S7vcD(8k;W~lkDU@n-(%AWH3lbF|@HY;AGC=XLxNa z&u}f>j$uvyRQ?I89#rJ+3E}v?=KOkz)Q`JjUMkCY2wOarJ-L3-`K z{$Z??-+w>seyE*NsJczY|Rk~i4!_=twE8DRX^`^nJ$ z^`oo$^q&nAPPp1Bq!dl&=a_ouDYH{^zsf;W7t}L`MA$ABW!wNN)fhZo{an^LB{Ts5 D=1N37 delta 617 zcmZqX`plx(8Q|y6%O%Cdz`(%k>ERLtq#Zz*gAGWo$}GG$QBk~}N!8QEF{EP7+gaA$ zhXW+q;ul&}92V1e)#~cFwocNeu<&D(LeSl#t{?3!eks41U!WZtT_|XM<(1GXKP}OG z=K~983lt}9ZOpp-~+= zhlF|kYwxAsq8CWatf^!?S-)gus{RB&`S8+KCq)hQi3VFAWgq@IkLApo+?&h3Gjs0V z`%lbWON?2EacR)Xg}f8a^}8^5J1qV=FWn)1gLv-6jpyCi>ih2hT*qW)w(acm=i(A= zfyH-DohrJzspg;1rWNPPML&q@EsNNG^<96@g4&`#n~j4xg_7q4>&d(;zx$%9K49*9 ztpk;!y=AO#i#J>;Oy16$yx>okg4k?_g$1ekEga(S>#g+nF0$IcF8jdUhZ8Di)f{?f zSzu6_q#u3aT-15<`^iTp3RxU3wS9g%e8H(dyI6S6-@mPWEA#A(2wC0saDi)c-4C9h z8$S1aCR@z=z2U9(3$vbwKChm|c=4sBaR2uDsr=o=YXTfLMr@I1+FyRp^#4Ogt^1eX zH@x1w_|;T>UB~11KP3MP^V6FDpQq76hv7&fBSx?aw6ivVq#piac=_e5Z}4mBoVePC zlzla|4a+?5&Yylx|9fGrLvulG!)=Cj{6F4*Wo)*Mkyn^}{ww2Gd!9$X8lt{G|NQgW vHTDRT?RF05O@YMoUksYaF(=Tzr-47Z!D(qS-{MSQ>S6G7^>bP0l+XkKQFIuz diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/capacitor.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/capacitor.png index 305087c37b634d1fe0fa8962937605c45200cb89..242f217e4afd493c1d23275a445a3abcab026f92 100644 GIT binary patch delta 364 zcmV-y0h9jz0fz&SB!3BTNLh0L01m_e01m_fl`9S#0003wNklEaninmGVuxxStbgRqz+!@fPc z!D3R95)9jS?_cGX zbm)ZeEl?3J00EyVc5F;r=}wah5%D! z4#SVFn;8yF+r;3IxaIK*#(i#UXed05SyR+h2V0e_%c+1Q>w$W_S`#JxC5CDyj&Ez=!v57`UJgfB}#X z(b+JI-@kqbrlHY7&=GhYASb8rUnr{q%!Z`{5Y2{W`PHiz7z6}GkS!(}5S0c%mV(j& zC<+b$`R}Gd9EIjPc#eTN0u*`_IRNAq{Q^f(G&;cek xcmgEFa;i9h980P007}5914bP%>VN@v004BWo|uaA($W9`002ovPDHLkV1hvxnTr4b literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|VoK zPZ!6KiaBp*ZS-aelsUfNDcd=1!1+qbaw>i%WCD6(Zs*tXWK zA*R`C`_GYUMGJ#rLXn_N9fPkw+#sf3BB4=(|z>UY}3`M-m==KtLx7? zmW}}Hzs$}SJ#rEEpRY?_C?xtb_C3Q!j?G+R@25Rca$#NdX~vCpN!7DNZJk`}!kL^F zCjJ!cU}ZU!T(sxgwvWbauhxf3>WO8n-#cl6(*2DMQES;^&u_0;fB#w3xynkNho4^` zp0z!E`-)TVH6HxSxpHEjO``hu???TX>#g~iV$~G$?$Yv1trSXX_v=IT(9f5K|Q z+jjr`^lpRI4mK4>l%)6~S7Q=VAYaa0v<{y*{jdnN@wZid1L z2Fw6UWQ0gHTo&GG&u~Znz+Xlval!2TU&fSQ%=eBrx-7C>__{+(s&U>I_Kuo^Cl-WO yPkg_}USXbj!PNYjpSRg70F`R3JB?va4Rf!${Fei_UkC%!2ZN`ppUXO@geCx_?EK&W diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/high_micro_laser.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/high_micro_laser.png index 2bd8d3837da037939796bf6e9c74730fdc936985..c23c941e150eda9e282659576a6b1707a9b721bc 100644 GIT binary patch delta 306 zcmbQnbe3s?N{ zjpG6(3@0ue=Ket_XbQHguQ7s1CId-y7Tz2ThmcCO@E2W|%gE(bg2#kAG?v_XW7^K&SyeO0}yFj0@#(0{{$TSpXKfqWW<)N6&}@!TFiA!rud#-!A|X zla^Kiyy_kR7<`Y!_fMGFoMycm-alZmSS)YH1sm*JseF8qNdN!<07*qoM6N<$g7!Le A`v3p{ diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/matter_bin.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/matter_bin.png index c4603632dff5c79f7610e6bf724f687f2b1303e1..494582986fd3e448c7b114a3a92ee75d0d323d74 100644 GIT binary patch delta 431 zcmV;g0Z{(V1IYuBB!3BTNLh0L01m_e01m_fl`9S#0004dNkls^-zNcMMmrUSRNabt1_TLoX42c>e|*3V%S*Es5P0$G6@$LAFoQmu7=w|%9+;2pKym;{4mf`J zAcMZ63d6%YH^B~&5EDaj1TygT^7_Ad+cts;k?M&M`N_-%ID*=K4RZBvsVgIEhB{1YbiH<>51%*%b zx&YY`AT}tKg8(e=-@5yjEXzr90FomZ7}l;vX%Fms^eCP_?EwsjPyr0R+HlkXqYj`p Z004NKpP(c3k*oj!002ovPDHLkV1h25y*B^= delta 436 zcmV;l0ZabL1I`1GB!2;OQb$4nuFf3k0004qNklwPye( zr=}I_Pmd1(j2g#;X9^jp7E74zwzk>J7{@1oW(3t@Nyva+NCh}uZcJ87>!K+k*PSl6 z=vZ|TtgU+hIDgm=cdVBymEIdTKRszJ@NR4>YTjOIEv+c3B8bPL#ADI68oA!L2o{%? z0XRC0bky`Eg5NBxtOCs=KT92RnaKcPaX3`Z5{{q~sbqq{_D)y5Ju#q@V8&tVpMi8T zp^JoqLfMup6(XS^>10CaH!pk#eg;awpW~KvaB-byD=p`zkjn`#hc4d(vvc!`?*TQ7 eH8eCdWWE94#e$z+?t)eT0000<_?gPQ}~}@AT+9RRXiOIMFULQIU|#eb_;3|hLFYMwoR!C+~k zhc5h|7)RiB0L=2^hYy1Jfx)3*N1y{KNeQqcKr{@1^bz9-ybk#I;XRz4%OJw=1j>e4 zhHfc2fM67WECGSZ3~v|$k@)EJhc}oZgX{sa9B}E@T~P4-mse3|Fw)m!ICt?9R&@{W z+++Z$K^KGSCqv%>$c_LRhz)%B@s~k>gP8$co*2uCasV-gJz^jq49%i=)B&Ro7kJx?-Don8Cu|EH(M7XOcLU(Yjp+Zwgg zI-3qmUw{2y{m-hm|F^E*{jc4~;E#P-$-DZZvq?(fj~BE1OMn1Z|2fs`9H(RymMa`S z00asO3f2aE4Xp>-`TN2H$~fIy&c(Ha?v4~u5J`BZ)U9rOSFwZG35y#1xyHmj@V h*Hvb)(*q6OFfg>{crW&Cn3l@`1fH&bF6*2UngFhSZ=L`E diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/micro_mani.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/micro_mani.png index 8dca252f688e5ea18df98922c7dc02cf353ee676..c63b141f11d8ad63b1579c59afcc6447d82c91f9 100644 GIT binary patch delta 363 zcmV-x0hIpk0)_*SB!3BTNLh0L01m_e01m_fl`9S#0003vNkl-gv5FAPj40johT|`m- zKNyJva-57_C}e|{o<3L(nK0AvX|fJFnua!ddX^8ZlZgTc9rml*H?oN0i8OaOBP z$oHUV`SSfS*x|U$q)AB#as*n`-~@xSC?0jdr~^hFFzNt$0ssbqdC#va9D4u&002ov JPDHLkV1i%anSlTR delta 339 zcmV-Z0j&Op1MUKlB!2;OQb$4nuFf3k0003fNkl1pw6o$XF^^(Oy2za&+ zUVoAG5(@ezNl5Si^?nH~ z7K_F5WF~((6vwdwq-i>7$L)M%Lc2^;3E8PhZ(KwE84799aw27Co;$axF-|sn{ zPQAyr>weW?OiWpGe#X&fqiq+yJMmC93d>G(ZD?Xt1jCeFEIXJu+xu1@VA;E^mQRS` lbNg`7K2Ny0#bWu#d;u5pZ+MgdwjBTf002ovPDHLkV1lQ?p4I>W diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/nano_mani.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/nano_mani.png index 93b2d06f098388d6a153ab87f3bfb3836c5db4e1..0c3defdefa4fdf1061e71e468e243ede1394a20c 100644 GIT binary patch delta 380 zcmV-?0fYXE1FQp(Ie%VBL_t(|oMT}aU@+={Q3s4VVAKJl4jB9n@bnD$kIdh`eFGyt z^;I0000L%}k1m#z8OlgE z2Y`T~fhL@N{=#*J{rjf-*0b*M)`+xie1_li+b%v|KNeBfw z!C=cl4n(H$If7^h+_`gyVe58O{=xmP!Ipl`>tf)YRK{@Z*cFD??`7fg$TZmwc(7Cc z|B-qd@@VdpL;jf;$44 zrpN)vApim(%Tr+h6yzXFkJqhauxtuvAO{c~0`6W{{$Ia-1x0Scwxw_%ZmF3@8wbF^ sP9)2y35HHxFzSF&2aGyk)B&^t0LO8NgsdPP_W%F@07*qoM6N<$f-iuXKL7v# diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/pico_mani.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/pico_mani.png index f3d1ff54dc93bff2eaf5ba08ed42bc1f64a49508..2da4e416464424d15410f82c64ecc33403fa2200 100644 GIT binary patch delta 356 zcmV-q0h|8y0+a)gIe$h;L_t(|ob8pn4gouj)Q6PHNlPbQhm&ivn;IWy~pA4>r!00p1`6o3>! zG?q3*eYqHUz6yZmsak^9lOaE}n_#>XLMj4iEW}|t?}K;=fPY4~YZ?W(LWsU`XLffq zG;)u|r2sO8l5yDXtwnIb$?nJ@^l}}$ZUHO^FcXTJi=ESjThrBD5;_@60f>cY@`-$Sbq!^& zsq3d$#@vmh-T{#dWB9>SY(acT|M53e017|>d{P%az@Cv??+F3`00003&IrGse`7fUexiQzps z_{jl8hk%Lm`#%3L3vV!70@G*q9*6r7$fk`0kSw6M)T77&v;w0J7EanisyaYV-{gOj?^ht}E0}-a z1UJLFTW=VSF44hIdv@1dhP?L*{~tYifYT9VM?v1A?@-ovxR`a!6_U*%$^jr>BlGJv zb2A7Ca-*p6&V9iEixxt`PLcyafAK9XB?IRRiVA`0_pjgK)ApZOM-UYPPoF(OVe9DY zf!SBDUcgYRt!=0x$jV0@(#2iBL{Q4GJ0x0000DNk~Le0000$0000$2nGNE0IF$m-jN{}e*%k1 zL_t(|ob8%DNE|^JhTj9Ru~4q@k3a+of-AxJ6BH{&L_`PyC4x;PO^T!ou@Xs@V0RWa zq6mUPAx#QF5v&eYQ4ua+5jZRqL&Bv9*9hS<-^eazmlJ20S;6_9M)w=!nR(CC)qN}A zI2-^RhXa7)Z~$-|4gij$9l-GLfAbQ3EEEbzr_*w_x%nl9gfQjT3V`Hwb+zGj@Hx$s z2*IRZIe@7AIyAQ(f@!~U0Q4<)T^MZU;^Iea-3F%pT5o}lj&_`#ormIJBaMv>*xufO zZA|)=1Mp|w0UzctI5-e)fW!pJ<@(4jY~fE$084A-{j~1i-me^h_kI@sf7f;3#mjIO zr0#!ShBrEn9PHuEFT(#cD_?||UxZKlQ}HVYP}A~69#V!7-JF=yI&%B4h4j;lKp{l^ zVy^1f`WetU_8Jg}jq;#p2!DS6gl(i>9M*(iIe=g1c{H~@l`>+ZWgTNDI zfRpSN{f;d)wRXqW^56D?e|2k^_G`Tbs{dX}8S!!DJMx`-;XDaU`n3Wm7K^B^g1iSm z71Ccymw?g!ixH~=^f2LQ)$K7fD5mlkUvD3}lc0000VQ!Pj5=V{0izC}xdZg{P5y)EOBc^D(%k_d z%ePLJV6cw4f>RAa2f%!Q4KPLKF#KCS1%nL(O}<~jmOpy*0ITl_Isjz(hxc#5Y;A1= z20>9FhWD@EAru_kz;Jx#90tqA22?f_t=?lSI;uoI5>eF zfeaF~85rZXE44DlAPoF&j zM*|4ty;t~;Ei~{(!MkY&f)0Q=0=MJ?>})8= zTcq&+=n`dMR>U6vL^*&G0J79NhT%UxH8d}ZDM|zM0HY2Vb-<_tMjZeErzoaF>8JT< P00000NkvXXu0mjfrRSkd literal 371 zcmV-(0gV2MP)(flqAWoXIYj>rVP zAY{I%2i)vDQ>$+PP^)ipzkMf}Ml58$xCvfwT>|j1dn*nvBUHX<1I~y0h47Qt(^89A zX1@3Y9KM_Z(0S^J!^;Sj|L=c=<%@2&J&guTr!W}A`wRRl`AR9Jlv1kVz5vGZr4}K? R+4led002ovPDHLkV1n%7sx<%r diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/scan_module.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/scan_module.png index b95b803d8f7eb81581855ac7bfb9cd74b846b3f2..a05d626eea0e41ee7c45b01db0b5756d1ce45885 100644 GIT binary patch literal 478 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|Vm( zPZ!6KiaBp*8G0XfU}&BH;(}pPho7s4g-GsnrKTV0bN+Di`N{s__WLI(=n@m<5+Jgr zSwUlM@M{Ib=~11RTDB?>zYgU5+(}=P&;GUj9JIUioj;Kg*p%f6lZ%(9++w{y;^Cjm_QrNhUvg zu04(qnK)lFAxprX{le~jKfl&hZ&d6QBY*A^WwR7wC{kg-=5>g~3i$--b#)GYSNH?$ u9sV+WWd<=0@V;Vq_}g%40#azr(Z6Mz^JC}EQ;xt0WAJqKb6Mw<&;$TFE6KkA literal 491 zcmV9(urJYCU!XB+l9oP!gNp^V)X-v6LgH2q61b{MU_(PR8RXO&eSrq27HcT* zINk^s zTW3#20kfW;@qMt}Zby=ISuAR`R?}!SQn_6A9`C>AdlejYyW>?K48~tPKaby^o|LIp z|NKWtQ@&TRJ5rSEbdzs+~fOOO8I@hb546N8(L^BtMa;*czzu7_uc^?ZEqjNF^rev`3pU6-m8?oHmf0I{#l=g z&ZNv@zaM=*1n&|!0KdvFx>NNH^OK%G00000000000000;f?038rl3E68q9ioxV2as zv*3^W0mir=U@Yis{rT2{|Ax+bf4;R??g!AH h4*&oF001x(-T?GNg|&x+);0hD002ovPDHLkV1l)(+F$?x diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/super_capacitor.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/super_capacitor.png index d6d1f9d555a0a98a519049b70c227aaeb774217c..054299f6d7ab343ed10b88b8a50f00e59be60e7f 100644 GIT binary patch delta 243 zcmZ3?yqIZ%MLok+PZ!6KiaE)N5(x&c8UopStP-v;F6NUcWr$q!EE%3v$4oo4s3Bm(~C&vX%n=Sma{EkU+&n_ rG%JPufXqRMWP=^vhv05sz|O!B{Mb$VZP9#A1|aZs^>bP0l+XkKONC)E delta 372 zcmV-)0gL{l0;2uLNYiBPDLoVs6_|C*&)u| z#Koy9s5rT05IVSM2DcVRF^(ySE}=ukAt^#v!OwauN>)--sJZ(i;IVk_$*9Sm?mn3WJ{yt|$34r$=WLNR-5u66@2~Y++1wE^_R!_@? zm{$To_7Z)!4 diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/super_matter_bin.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/super_matter_bin.png index 54678e59150cbdd9b369d147265e3617e8a065e7..b56c920f7be3acb4ab4fe2dea88296c6e2575934 100644 GIT binary patch delta 476 zcmV<20VDp61B(QZIe)!LL_t(|oMT}aU@+={Q3s4VU}!nOH9z4$jQ#)q7p!XbY+A!e z69<4SSJo7Q^Y7wN=i(RgpA<(Bs!rTNhk;R%ha^Xk6a~nZOH0Y1 z$US}bgyH7;0}Sec4x|`Kej)@}i0%k-0Lc#c@cs>$289I35r1@ZfR&6G14C61I2w>0 zf($^x4@xU{aO4n*8~_7eKt~V)FCV|4z5|Xuc?Agt0$}m(8w{iXf)3C%wE2(B2RQ^8 z+`aw|t3Ek7h5wHpJs_AB2|BYMDT6LL}$_kUqvC2fXg?^U-9wOfTTXb>RQ_e47Y=7^7P z75*ES=rG(`bcO*$e_V8h!PpIzCdG1+8~_3!!)5IrfZ4ccQo-QQ1)~n2H2?rhjjn4? SvY`n80000Hl%bEoq*~NOcFm zEPVFhF@uzXEQ6Du8^e-Gb1~F`${*#XF@_?V+1Jn?}N-M&TJ7#QLpav-)-oEO*;Aj?6H!Xr zAAzXi@G$8w?_fE)tHj$MI^gDiu^^NnMdz%(hA6YT&P vsIHktlHpW$04>0%14bP%>VQ!P47dXTHfNF$WMu~G00000NkvXXu0mjfxsTL#T@vAC^Q~)KHf_Ptt1JC4sApv4a!u)2 zmeJeRA*OobXxpKM3*SE5S@k$6>UrGxYTkR#&F>r*{Ay|U{ZHl9biPvy83Xuu8;UX* zrZ_V%m~(?I=Y44H<+%7c>5KkO_)w@B^ICltbv-2;op~bT-k1>u^Non84RQs$fACHB zcoi*qVQ=O`o^Lar?{*2Yzoz!WzFxsf)IHNQzG=5=YfIgm;01r5lvSONvcG7Uu`J@V zinUGMnRRoV>rOjINz`tBJ^Rt|o7^$@U!7J}FW1hI&h z)?<;15KL(!R3#G7L2z{;mrZD_Mogg_noMddeI90JXVYw)$;|A&4E=pz88$ofzQ6bT zy?-IXH^K z;1vw(Xn*gVBU~F)$F*%r1D1Yi0M%*jh6B>a^f{n7g31n|iX%E4V4=tP4sI*dHU#K& zK77qf=jVV)`hiX)-%I8@Rln`WH`K<@oe)Z1n>fYq)Z&>G-22h3K8Q!>hmQVXnVwxt~+uQv1 z<7KKVZkatf)KWEgFWU=8-!#p}XP3UaN?VO22u;ECTYoV`uQtw4Pw#1OcAR?p92KHirWzy%r>HlV<5hph1}|;z z0Jtrwvi#7U^gbLu!>|4D{m{uBr>|T=_2f{utdgT~05JD4-27|jq<4B^0)R_*Uuc~7 z%a^F^IQ6vz+Og_e|DUDZ3euwy#ru%p;aJ%s0-$AkTCxN(=;31 z@A~zNRyVv4nb%1yRY^Si5P&=p4FRPxq@RYlMOWW6O)hM{#50ShxxAKS`#!ulN}@JH zs#`(&8~|&fY_RJ;I0Xxnr2N*uHbW{q4qn<%_%)(9LdBT_9_7f96} z95A=ox}34DAGnlX=G7d9%;H%MlO!rh35dK#L`pl+?}3xYG* zYYBoqIscmibUYAjINh+09S?%r`c&9f7(^R`-t;j`-#+#Rv7z&0R+L12vlUFco3Jab zSfl%8P{5WiM1?qC5ClOG1VIo4K@bE%cx;II!?1As13>@d;FzbU$9IFqLD-)V0iic& zl*-X1U+q`B{}U>}HTxLbAOy!CX>U@{(*q28``-%wKK39GZU}PTf$De{YGCAHMSQqoz&y`MTLpdit*A-k$mSQPal! zeBJzi0Gga%Ds!z=rcx?XcYbb_pTBYimCLxF&=$5iKVLWA!$pe>u;F*87miN2`T5_x z&7aSo;85)|mj7tO5M=j&g-I*jd-yQQ(RcFm`}$s^`tz)-Z}))OjCYE-PQ3RA6xhIE z)&6EJ_YS*1x6IFvnl|R=>n6}D2x787;Bi3r{s7(jVTs~lTm-W*mI9hq8o3Or w`bE5ZLAWtfXMVn5Bg2m%2!bF8g0OG?17)@3(3V13H2?qr07*qoM6N<$g1$RElmGw# diff --git a/Resources/Textures/Objects/Misc/stock_parts.rsi/triphasic_scan_module.png b/Resources/Textures/Objects/Misc/stock_parts.rsi/triphasic_scan_module.png index d3e7d72b57de409a078622ed638518a3d732bf7c..e2fd4e6fbff41f2e89a8c64668a35d7cabe4aaf9 100644 GIT binary patch literal 770 zcmeAS@N?(olHy`uVBq!ia0vp^4nUm1!3HGP9xZtRq&N#aB8wRq_>O=u<5X=vX$A(S zEuJopAr*7p&auxB4wN~*-%IeNLf96uqcd(!+vY63(5s+8vn$)Yy-Q*14$GhGXU}f- zKIqAE_o!)!pt>U8&Ex>j#W$2zTsT;1Lk{C|-czrW?1pO{e0b7|HoUQf?m?0nqOc3Ua=M7ES*hh$cDP7>Sp);*XuugtNY&Mj?tX1y5CPu-n%-n za9wSKspn7j>ERncr`TSuS=_Mo`To80O?~{&x35lUuBm_hRqEO+57(f&r`JOF7;$>c z%oh>(dHCW)PmZMUu!rYQFR%Uk`0e31uF0QPi|Zx7I``M-^Y=YGznWXFKm2IE{^MzB z8^1latA8_G|H*up<15d#90@DQTgC@c?ebd}%5XIlX)v5x$e_{Av;Z3?vG#z%!!`U2 zubCs{5By@NuiqCslmGUq*``~X-oBdiika{Cd+CXlE}C~^HlEtPihn|YQf;Bz`}qZFe1dF?CaBk$63;`D66&X-M+lNT7iYB|rCww&8aI&~fQ1 zub5+ke*9v%F0#!|L7yvHeu8-GTK*H=3+Kx#{6DK}$`pKh_1(4ExBve9oA%;4dx`xX zb;T938}81(pKnxI=dd9^zIV;P6){}g@$wI5OboAz25hY!mrwjU3l?hUq1j5Eud7UlmulKE9qX}=Do4hR7& zn(zRi-<;#@;2r(ue5GtODVm%ZvpVzU^hx_YW%85fD{6mM`6B9fL5bOZv}ob(xrWiA zl`8V+`zvKlh&n&nsEhkU0KmN6Oyx)AOb&n$u)-jSJGH3&Hn0qX0IK=w9pE^r6Gq|Q za2Wrt)9YoHEi3P;nzx&HIlqcKq0{TFT&-j1CLrgF~4*r^O zrJu)$We0)G0OZ3s`FtWGA|fIpA|fIpA|j$4ApSpD;X0I6et&>8$@TgFf%NE;&M%Am z1B7vZfH3VjVB6N`OIv#q{%w7}NmZ`Tm!`EtMrVD#$q%5)_4(4YcF5?g&o{k8rmFS% z(vJOP{gcc#2Ah}c}0Y=A^(YW1i>gcI`b5m z7MGP^uu2mHI|2q4@4kVdKGs!(fx!#n2$&c?#}JhUwoMdZIDfnAD#IrR6O0f5TZjZe zj{1N93qy|HRR&`>WuU&h4ERC>rvqL-e!-x^0Eq&x{13no&}N9Uz*L7TN@}+XGcauZ z0AvXRJ@A7e5r-r2hQQtH?-(TPR8TAfiM=><2h0aWOLg!YhQuBYhWRBwz%%;Ff*EAl*Lm4j6U7r~^hFFd`fP0PP-v2@MvA8~^|S07*qo IM6N<$f+@X@dH?_b delta 277 zcmV+w0qXvR1F!;+B!BlwL_t(|ob8l9OG0rJh9AYD;o_u4g7^oV#3}p+4M7q#DAd%f zI0n|IXt9rwgPUB}Mh7qS1%gt73m3P#_tJLV#2R119L}@+IKz40mvat3Fbu=^XHrVj zMXQ~J;pe6fqc7&(Ow!6IO{GeWAn-S}6K53}o$3oor*o5RzkjM$`Y(eiNPFw|@00Bn ze8&+Jcf7kBSiZmV`v_H}0E4?T7K;D?-(LYR_hwQ6=>{G>mvZ?CfI=b9)5laYmjeLY zK0g33d>hH;a)55L^~3zSc)r^J0KMuJ0G9`+vN`O*S1e6n;*L}G#tFH@V;m>+; zCnq=|BrB%QG0U;P02gG2M00001bW%=J06^y0W&i*Hen~_@R49>S zV4w^zDxe7nE21edatuIKpduE4rXUqn2bux~MsZXH3__)73TD*N93fP|fK!10x`ILs z69mvrV8^hcq7}`GS*y|9u%;W8&vjx0eg(JC6mXy`=mIL>dxs>z4OH*|2;qPasDJ^4 n;NTrY0ID8_cct$hFc1v@&DR delta 417 zcmV;S0bc(70j~p)7=H)`0001UdV2H#00DMML_t(|ob8t}PQx$&MPE?|RH;iCI^dyM zkh++m7jUHxC?jWJ=L(#mf|-j{A`2@LUb=KBl9vp4V5r#4Kod2hEhTnRB=}oo$?y5w zi52+IUx<{_B}w8ojU6=sf4+<`#;{4ER)HKoN+FkMOkf@UpnqVL!erQPMqm^CCVoTR zKeZYWh@yyVE5C&UeC@TXt#E*?16AQlDYVvDYtdSNJAbe(r~-#T z4XnenEMqpC?LDt|13qwX0be+HY{5EwKA&TZsWexm213E1LIZ=rz{PRQa=G+|!>WPR zikwnvA?hw)FJVsFJbTaq&S?7CkKZXsQtenxIp&)T8GDe`5F~(Lg;(|Zg%0$JhrhL00000 LNkvXXu0mjfr*p&W diff --git a/Resources/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png b/Resources/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_off.png index 59940f44ca4373ace1a3068261c7b823721077c6..8704ab6bcfc9d1cc1a0e0a9959d1eef636a6de3b 100644 GIT binary patch delta 208 zcmaFG^nh`KL_G^L0|P^2NcwRgr5@lD;(Fk~0XL6`__VVBoyH%wI*JNQuMRX&SGQ19 z(Ka%-6%mzo@d{Oxkp5D`d>5#Nu_VYZn8D%MjWi%9-P6S}L}Oxd!U7%xtp%JM+KV&| zig+AC7buA}EK?8-WuL{w=2jTO%Hy;&gX_vg8O4wTo({>ZGHWxqtkM^1&bZsUEa5nl zmc|3iW|af1Aq5N^4;$E5G%&V2c);V#uu87YcxgJHfdDUqo1Ij^3JEVwpq&h!u6{1- HoD!M`@`vI+$Fb`L>fF@Y}pwS?^+cAM2Q0)Kt*=er~2%iF~w0!2~q z;vwAMU0$8NJa3kNv6Fc)5yC0c8ob7-vx=O;UYIdDkSKo{OL4Ru|Q zo*Ugj3_MyO77h_d(1jagu-49+HLHPCaG246hto^PxI(^;hXInDq8002ovPDHLkV1m=$n416q diff --git a/Resources/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_on.png b/Resources/Textures/Objects/Weapons/Melee/stunbaton.rsi/stunbaton_on.png index 2132190ad45aa02a2165f4372bacbfd0be16514e..2adbe76e9e16819f2e91a89abba140f95eebb6db 100644 GIT binary patch delta 1738 zcmV;*1~vJ^2-yvgBYyw^b5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!vFvd!vV){sAK>D25w12K~#8N?OaW09z_`5D0;G1^VgQ9g%)cOy@(XOeMZ5bL-Alz zDgm`39x5sn@tS&(R(eo`VorJywDuz6!HcANu;9U48>)>Og@0;N+q4QEJgoDqGs|SY z+1>ee-*=|F-#qZLyR%>NWS(c{-Pzsu%gEy5ViFR)UQg}~CvKA4h(PdS!2OeN_7L*W z8?K*!WJYd#h^)~Qz%Am0+%M#3H9!hKKYL`$;;ZlM3z6&AM8`h~!GHTbU!4}4&jo*J z|Je+XBmQAQet-1K=^wJTw~oD^W;-4rM;!PMoOUFo+pR+y;<5Z`St#r{9H4jkNm)UTM{)AH~|sF z=5Pppq#Y5ji(TjblN*51T|_?l_V3A$-`sul`_=g~@_*wTSQGqN+R^!OtoVgUZ2TDA zRsGlb*{%UaZ;-;QXjjS1CZPL%NP7yP(5_kqj+ zQ+(N62`U53&dX+RDC5^hya|$PKwa^va3hNkl+C`z21N0j@i#Icir<{SwgFLm8va@a zMDgkP2U-OkdhzA#Y2M#g*OSrl9VFrpvI^S0=YPHp{JzEK;CG$@5dY4x(FBo-pYlEO z(RrOeg!ml%P6jCPN5{4%SO2^sTm3)Lv9`qPiRjXu5eihphO zx6bgF8W6?j=PxuMitmTtYCsgeBh$+op2<QShA6rnn3<2ME20Dm_>*&DICpn4DbCe!w6LZ5`tfF<}=TgpUeP|i^#u{BQ|{{+5ks=zJKH{ z>^wKWLXr7p{A31zT!bpW>myfH!W@v?dhwGte%*Kt?)s@teV6z##m3kfb0r?MiAT&&n+es`v|*iezx;e<2zh;6g5PU*Q)N znxanUDgT~9`OU%5Xcg4Nc?z_JAEFnTUfX~uJ`I1Z6coj$;~!`SplAu(E`QbM4+0K; zX9HBl7_hRu+*dQhjjr-Lr65I)a=rNd0MFkK(8;@AC~E^Zf2jdce185y1ETnT_^k#+ z@%{1lt%6+0^@3FD`plu@hXH1cPi#+j-F0t@t$A+O){SlEF+~g$zqJv+)P-CHwFtw+ zUw9*?rkR>zD5qGx>@gJ#34ed(ovKWyclr( zeAxQ@8yTz@VJUi6sERj8A1)kcU$wLiY%@R?UsVO;RbI9ek%PqmQ+!pZ3UJovw-H=3 zz*~KO>w#+kWuO)kWqP6N^V=3|*MQLV`E3EqDo9;yL)GWE3FHRoMSn|pcm+i9i$Q9D zSuv(oJ5l&zkQqQ(t~X2|xd=z`>!Yy&QT%3TWIzJeplg?g7k8|5cT<8Mx_BRwUQC_QEEUGpC5$=MDhK=YCsgoPR-|3mK|CRkHY=>Ok8Oenk&Q=%%teb?s?3o7q~mB^v$^Sch`M-dS` gsJs)7;6#%A2Q;gBDykCIUH||907*qoM6N<$g8DaQjsO4v delta 1071 zcmV+~1kn504Z{eKBYy-6NklF>BjE7=~Y$7)nOh5JQcjOY7+h@oK2DIN%J0 z{)5t?WB)?vPsrH+(IHuyKafB@nc}H&i36^O45pyrx=7|MTbAV9-KYC>F9eG~{M@VW zPSU~mMt~QoYBnG;L?=3&*Xmf>V7kITFbF-24x6(XngGyh0e>>Ia}r9z?*kYo_}y9^ zSH|JdPv`y)$3h4>91i9E9gby)j&gEL^9L$lh8S4pXZn9;0v;VUXEQWU&&18o4-HJG zQ+)b7yoqK*Q#Ut-KUeu@?}wKWzi)#Npqu4idjVp&dh$=Mfp`#>l*Jz#WcOqK+n%^g z;dkQ$69w2X|9>L@POgEAp0Qib8XwBcBjAs7_+L!`?FC4KpTByLYp zw)xUhfNgLc-&+O#lW)QQzA4_JjyCKF7jQ!?`|^Y>Z-T+Ek&U-%D& z5VGBFW4T zn=b=DbAJU;nXd{z^9$SUwgiCiP~zd&AL}Qe>ijhwy?!Y@U#|-71CE}*fDnT0BvhIo zgQ*6ggKGQ=SI?AYi^&cly?XL%f{{*CSI zQu_1P`eSyrYuMl$;tpzcT;#d4KxzO|6Ese1esHL=0BV%kw}c2Vsc4 z_2h5i8@mkUht>Jv9W8Qy80;i>KKahZRZ0v`r|G0S5HB@?*q}3PvLxo&j%3mwH6?wKi>HB zZ+}*QK7hW-G3?{NQuzRr8^3-1`+R_9esU62A|GIK<5kpuas~Q)05RX^1Bm&R%LnMV z92Mp7V)}dlG2iC{i1|JrK+N~~0Ajw+2N3gpK7g3-^8v(spARTGe@)b_c^vsZAK(g~ zYkxj9-{%8}YxF%Nb4{%ZJ@&T^NWOg@0CoX7`oR_XHr+*Rs)KowN_d;oWqJ|Ezv$hYR{ms9zGN+^rE pKcB07K7d%xUc`f<@qt8y=R=jP2qBvaNdTSoy@g8oJGIYH>X){PBW$kRW^1? z03R+5{;&eM73HVJ)sc)Y2x16`gc!sSt_BZ;-pbXp9tkcls0p%G5pb9QdQ(u*Uzn?f bp5!6EN-!F3!qZui00000NkvXXu0mjf%S5GH delta 1571 zcmV+;2Hg3L1E~y<8Gi-<0047(dh`GQ010qNS#tmY4#WTe4#WYKD-Ig~00rAgL_t(| zoTXP=Z&Xzj{`NkXxl}3_ffU*f)dyenK`;dI@??P0tA-HYc+e=+s;G%3CjJ3@k$?j4 z`o^UQRZ?Dz7rdZ8s0kPq+S1YnXi6w;=iK)8TW7c!?Q~|!?te6sIp^%XzO~l3zO{~v zD%R@Nt1T9=Mn^|grDy&Jpq#OZ%u3iO0(fNK{t0yLTm%^Y^gLd&II{0Lw(VX4o5X;2 zu`Iqa=UM*+<<9khY@AT_FvgTF@)*y*kH4p&93M8GfaBExj+mxB{@HK>DpwB`2e3wC z!YqNYDPCCD#eeeX>YNdoGX?TscFl6Dt%7nh=_w|d;kOIRf9>UVDpb^~M{cyNZ_9ekhjE>Enj`AF!0r3`EtQ#19t1v?Uff01{wlE}0j;CeI?*!Z5 zUXIT{xrWvqOW?X$f{GX*0@i{%SJq5<04C-qhFiZB$$#{9dQ^|&A74htD-AHjg_wMx zr$%{jGIcO+fVLMG;mDp5Y8L2hZL_|G`PPY6@+=1?D}1 z3W~g!WsWU@pJDW#o%`*P*}f}1(MK9hlC|JywWo^C=pcq`Bc1=>(vfGvT7B} zOlqDLA>~#AX3_lkCJg^Hz=#Z>uyG4dVOcO8x0iNB0YpC%g3{!OQ#*iQAdc-O} z$A1W9=t+QZCW4>$AapFOZ^ihpr*YxS&ro=33(GEhH+n{tDNa)m&rBj5|FdjQn70|3 zD1=|Plma&m(PJ?#iPls(WerxWZNsHA$1!qt0BfFk{_eg+>`_B9S?Fwe5vmA4Wmx0! zY${6Xwuel#4QJD^Yv#GIu?rWzK7zroj(^_Wm=u|RMvWjXHDbX4q?{}bE3#ZXo(ZOr zXLaC~#ybL#scG5Jj^VSX?rhU_i(zJ_NMdqBn%06NprzH4K$KePIQdNJFe`)<;t4Q< zuwjg5a$@+$FG~BuA{@tKDiTs*X5j#A63|2DQ2CDihq*z z(>;X^9WahGXeuBZTBS*5UI4-6bL!Zq&Gv=&mZi09N$w_=pj3u-Zk3jFN+BjIrE%S} zXs0k@#G!;KI=TN6w(iK^ zI{;}w`HY|T1(}FMo`v~JWI8End|GnpC}z{iNU>BCP9j!&sI)2qkV8A(T7^@GMzDF; zDn>Of@xhi#6H*05_6tr$ED5GaW8mO0Hov@z&rIGsfMD#r(wWC>+pCS}-+woVww(NQQ zQkZ2rvTV8i2gcC#Mhi?h#k}Zhj;;P z#?V)1ye0s$g5lqk-|(=tR66+e=ZPDV}GOuaxLp@ zGCP%)rV4J{xnAq48Nlr|d-3P=6;8yynwQXf0#NDgxZM2(y&YF;v>clC?=#AQ`3GlC Vkp%GIK=l9s002ovPDHLkV1f}G diff --git a/Resources/Textures/Structures/Decoration/banner.rsi/banner_cargo.png b/Resources/Textures/Structures/Decoration/banner.rsi/banner_cargo.png index ff808bbc8bbca4efe6e3184df645be9251be9340..899c8d6e4cb0aa6d7e4b18368d411d3c34846a18 100644 GIT binary patch delta 400 zcmV;B0dM|?1fK(t8Gi!+002a!ipBr{08vm(R7I4Olsz^yO-f25RUA-;r2yt67B^nbq1cr&G0TGzVcCeZWi;Sc4y@M)01DM8Hu; zCQ*72l2ei#3}O)~bJj3%djxJq!g&CxI}E6$m7zAINTe&#?5 u#-OeQkj5AZR|5ViK&Y!cD(bJe%P*r#9jPSko{sy&W}GzqMVyB{|CjODk|Tytnx-`iP@d-_U!Ok!Hh;58{7N!^E>DcHbg0PG z0S4-ZNBfLj%p%cK zkPF7t7b7P?zJK}>ZJO-_8OZU$0H#1HOsFXuV0j;lc6wO`8OZUBaXP@$vfgYQTx>>d zpe~Htv=MAVO)Me+)}XJ@07X%d0w+l_ECA>*9<$HiG27j$oNf8>gN_-z6AmR3z}!VO)*kq*kIQ2W&p3;QQD0F0000D#*AQSSYXs;JS z5hCq}P0z%HjP0-sNU5JW`700000 LNkvXXu0mjfoiw9? delta 711 zcmV;&0yzEs0@MYN8Gi-<0047(dh`GQ010qNS#tmY4#WTe4#WYKD-Ig~00M?dL_t(| zob8uQXcIvghM!H7)<_#g@w9kcFJ1&orKk`O>M=qEd(uPOc(bR1XbWvAA}YlSimeqd zf-QQf2l3}%1VNPggV5809wY}Vilj;slyG$=gK4Ke@fLoL3;((O8)`s0;FC+yhAUzoZB#fE`*AC zT>x3V^EYsI$4i^iizfwYsq^6PvGFd7n^1orHBUu>Yrr*FF10QpIlh3JE7aPVE2y002ovPDHLkV1i6$NB#f+ diff --git a/Resources/Textures/Structures/Decoration/banner.rsi/banner_medical.png b/Resources/Textures/Structures/Decoration/banner.rsi/banner_medical.png index 5bbd100773c4f80fca7401fc06b73090a0460cd8..c2dc11d21bf969063699ae9e24e9e1991b61e312 100644 GIT binary patch delta 405 zcmV;G0c!rN1Ed3x8Gi!+002a!ipBr{06$PnR7I4Olsz^yO-f2E;oDc5}eXC0umnSZwX*CF!%^q`b6##n21 z0l<-ehOM)9u@+zfw>plX@9qPvtGl@tzS0Bm&ilpt7LL^U1K{i~$z9kz)!|+Ecm|e+ z8lMj|UwTE8h?0aFlZme$h$x<}AQZ+kfXGDn69x7Wdmsyd6gE*1A7k!-BxBeB&C>~> z6F>t%9Y6!1^?x%Ucil@5)cQ-=_$qZTCtxc&uY~s827pyPaix+8zzDBUYB~otEp!I_ zyNmrC1e%}2ev;wpBwH_vY;e+x)^DC(S~vS{D-r2vH^sqcBBw-JKPAeeFNFxg<)@ry zHh<571FQp(8Gi-<0047(dh`GQ010qNS#tmY4#WTe4#WYKD-Ig~00CY}L_t(| zob8uAi^4zR^S=w4yUU7w3>JPZ z#LdUftcf7!+vIDq@3T8I3)=>QJkLwPLSY!TOffltGPB-j4u3)jl5rG8aN2(tj-^Aj zivsXR$8n%DoGDY+b%8336(36lb2+RaNs_`9DNF$d`o5o{ImcLskh(y5I!HCpq}~A5 z0K3n@-Sc>Td@ev;dIMAf;3gvbSe8|` diff --git a/Resources/Textures/Structures/Decoration/banner.rsi/banner_science.png b/Resources/Textures/Structures/Decoration/banner.rsi/banner_science.png index 8ae607cdcd4d5e1df01dccb78885b23735ae9246..1da59b8a315e63fb905d1f87ac802147b584434e 100644 GIT binary patch delta 402 zcmV;D0d4-71fc_v8Gi!+002a!ipBr{06$PnR7I4Olsz^yO-f2lp(5 zD@K6&=ROQ~QUJ1@7j-6q@u|QLJ=uvSTrj} z`tjQ9KfB3DQ5<{h|2#9c;*k(mmSrtHY`tC=IiD6lO6>gi+J7bfdHfb#D})-w9CTt# z@c!qf`QC1~QdQN?*lM+s@p#;{AEqxYUFg8@Qu1T#T2 z^-ay80bc9JLZ(s=4Y}sOS<4H6mI{4%9pe~#99>1IA-8a=0JhPG$$c+?TwXo7cLU(= z8DBOEGyuCZZxlKKhMt7n3CIHALWeR6;P&ex8rIk;uz!BN37^kC_5#|C42O@CA>c&h>cmMzZ0d!JM zQvg8b*k%9#0Rc%wK~y-6os)rbgCGz@;gAv%)}u-P|66-^1b>o>v@_iq&y{^5cFL>q+|q>^2S7Z79k2+#6n2arNaTT0I|Y)AtTcTsauwp$zS%GX;F zS!wll&F)Ru9dl*JwOVC%JAMFp2V}ge@exCp~!WL;cPk<75 zsG#`_*vgbZTYK*e)S{Ukr7i$Xb(S^oXixe741cX*?BX2YmK$-lP0fn|I@A23GQQ-# zVq0MGc?PUi(h6(W5m17Aw*}y1ZbU~ko^l}?!Y!Wjj^t%5pE(%L0Yq8>@~4n0 gt#U)sZ}F+V0m*+FJ_`6b!~g&Q07*qoM6N<$f*>!WQUCw| delta 729 zcmV;~0w(>E1Lg&g8Gi-<0047(dh`GQ010qNS#tmY4#WTe4#WYKD-Ig~00NjvL_t(| zob8v-YZE~f$6u0NgDVI{yT*<5;LSh6lh9I38$s%!hoUq#DU{lJ@ZYcv#l#{Af*!O* zYXaiIlYfM_fYQ{s3cUr%uXW!pvz^`S&aa9mAA}5VcK7?)w|{Tuz0p9x^7*_23f6A7 zQ@m@&0C-}CZ9{c`64r13gsy1@zO6SVY%JJ;FX>rPC@t=SxErq?AAA2D_vZ{^e7#}F z*P56ZgO7_y>?a@)u1M&PAU@%Okk2ehfod>>-76jZ(M}R+&9SSdf>g7&>j^1`{|`A3?L&Mg?~V!L0XLKxk)H0%mQH<7XTAY z6@n+u1n@Qv#%3gHtWAgs@UQGYfDnL6B8nuS5CCq+xSnMoh&b8Gl>sn&-@cr39^L&e zRpE$ub>D*7`LE2bh#P^dkq*WpQ3!=TJ>or{4{(324^Lm4uzAN8j)LDeOv2LgUjRTe zvS)%ZD}M^~+YcR#9KV+p0w@>V8vnt9((F-ULeh!%{G$c;X513asDi=(q(4OQdc*CA z=Ij6>PJDaM^u)0QE4AfGhN385e|HHgw|)dZB8*mhe*sqKeLz+TmIj~;6N>K~JlH zleNxG&d(mz)pvgY^Xy-~m6IJn@F7egv@)y@1Au#rc?;WZ9qyv#vp}_UOXj3Cnn)yt zsoV7j*kVeFBSQ%}rMO)H$q{lZlvIE!RS_K+gaWh(f8TSu0_Tq*;u}zjoJ+|pxdMRT zj7MR~HGm1>{&Rc)@O8;K?^d5dlpkln8p^*l$S&}UQsA^*P)ef{r1SwfiCRPNovh6{ zPD43O8q+7`u!P_r;*txo$uYHdY_}W)khTxR_k19aG46pA1hES`fSUpY@l#%r@Gn;N Y1)&HU!*I$j%K!iX07*qoM6N<$f)*2>QUCw| delta 555 zcmV+`0@VGA1Gxl{8Gi-<0047(dh`GQ00DDSM?wIu&K&6g000DMK}|sb0I`n?{9y$E z00GHKL_t(oN4=LzP69y?h6fA}AHfwnj5l%TF}#8Y@EBgfW4QAsMt1CoQ6ABeZ|EXT z+mEUN{gTcU(3$_|uj(Om>bkBz{)PupGx!!cT)pf7m)FocmtLmlxZtCT7*<8otaWs)+3pT>_?eKIbdez$+(ek$5 z3S;JZ@rjXWJCX)A5!QG8;hymO*V3!`+8TLUEok9k1`vN-d^B~5`{CKnM9Dzc zU9(ukfxX(aRDTKh2KaV4{C%WwlmxhuFU;%M5+2Sk_g?2v831nR_cn&?v0yp{kei2? z!PYI{0m2#fDGg(6__&8WjAA4cfYMQn#qB3h`u3on0SJ36W_Vwg6_WvonFwRIU^W29 zG||cc9Lq%3KQXdb|PHkB>|*PCri3xMInQ3OHIaS|>>j(;Ed4xs>K=M)xk49Wfg zy|7y_xMWvWknZ2fID*wORpw*|?zbFlM1kr&wI#HL;Hm(Cd%`ui(6a;O09Zhm9jF9A z`40FF{C5XXt~$U5pvv+MQI{Nf3S5bG3Vtmk0GngT2H=L2$;6|1D>&{F2nT&-hA*X0 x#PLx8P9l4~)g+{5ZmLQjas?KF#y+)o-~s27g>AAvL-hav002ovPDHLkV1flUeD?qV delta 233 zcmVH;^h=_=p znVD{GZb(Q-P*6}nKtMAyGgVbpi;IgbEiLbAP}=|i00DGTPE!Ct=GbNc004MNL_t(I zjn$Gt4uBvG1i?bFfcXF4iX}=l0ym@6(`Gl!1*JZO9%kLmYGTUQ$p9vwgmdk!v1n1k z8EWtLlW?Bl=!x=^B;kx@0me5J0V5B5QxW1w5r3wjn}Ti%hwh@_EESJb#dGgYMRR~} j4VIMV7Hj#j9n=eK@vIN;nsJPK00000NkvXXu0mjf5`1IE diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/ca-o0.png b/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/ca-o0.png index 5eefb01db6a9d02c26a92a0504afb413f4dd4dc9..2cf968a8c32f2cfbd9b8e88a4e2e1ecb31796958 100644 GIT binary patch delta 124 zcmX@gJ%e$Aaw!92age(c!@6@aFBupZSkfJR9T^xl_H+M9WCij$3p^r=85sDEfH31! zZ9ZwBpsuHjV@QVc+vAR03<^BV8y@}t|3;+jyN-8;CNEGL2==_4e0yJf)LsT=RaUKB V=KpIx?sNl*d%F6$taD0e0szPtCOrTE delta 1460 zcmV;l1xxyv0mTcDBYy*bdQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+U-_f(5ex`{j-#J5y+MxOhwX3o+s!7~r0GnjQ)_HwgaE&XvD5nU=T3j%AXQFT%}Xui zz>#aN+%WOuI_i0)BcIpv$oCTN-{=!R78ojllUcbhzn+B-yPaKoai5+Q*?`hi1QO;YB*%I6 z%SPf9X`w%_9CnZm_mm(+9?uFu-aOtz9>+64Z$N&`(tGs%8{l)hE#w`R<;Dn;4;M&3 z2LFWkDX_d`#D8xWW_vA__aN^*eeZQWUCRZCmb;>k^gt)}z_0^yIky!I!>O3tIVy%9 z?HHhZ9^>d3gDNr5--$9!YP4yca?n7EnHL)*^8hWOb1|+wf*;8-XtPO`DGufauLRDr z@JlXq#;s?(L5|3iV9Eq%ApF}SeQ)t+M%sG>L=<59g?|A7n!-MaVM+d%n@Jam*{!$%!$@|1}-G0U{+v(C0CEp&>Nm#(sG`KqgJ)Y_Jv zx9+lS`>wmauy(Wh)%Xe4=w^*irozr%Sc5j1J%3zKv=a@^z!+x&;}#4=2Mf*)r<^&M z3(k%jT}~NEYH*V#1!JHvFJj&F!rd!#pW-d4e~LH$5pyZ1`yI?BQ1>-&AFwv#J2y_o zuD38SJp^wb)|a=$-r}Ry{;vEFI$E`(9q*0GW}vmt&R?5vGTWoq=yyBRMcdNd#$Is; zet-J1&Al#&!l4@|zg?1>Y>>ISrz~Q0fuQqLr_UW zLm+T+Z)Rz1WdHzpoPCi!NW)MRg-=sODJ>3m5OK&*#lfPeh@(`o2o_3Pp;ZTym;OPM zh9t$sQE)9d__J7baBPxntxpr zfT~$WDjpNFxmB^}6#<0c!#GAIX6lLbVg{b|bx)mCcTt|@-S=mOl)T9RpGZ8%bi*Rv zAfDN@bk6(4VOEk9;&b9LgDyz?$aUG}H_ki6e@t zQNECMS>e3JS*_Gq>z@3D!MwJT=6^b^A;htS1dga5(r*_wr^NjE7N1G-;q`(qgB*#(+)+x|Yb?dA#Ke+I6!w!hi{ zWoO54hX`2A*`ukQ~WRODGh8_cQvY9MFFY1lHWSwSUfW`T%5T zR?9cQ!67hGr0jK%clUPA?cbi({C)tY_;S3yUE(hQ000JJOGiWi{{a60|De66lK=n! z32;bRa{vGi!T<- O0000!lvI6;>1s;*b3=DinK$vl= zHlH+5P|MTBF(ktM?eT-03<^BV2Oj*hj&Ai^&|mk?gM$+&Z~yIN?tS&u;%zFd3Okwq Ua|%yy1d1|vy85}Sb4q9e06&Z*mH+?% delta 1397 zcmV-*1&aEW0f!5aBYy)=dQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+U=KHlH@21 zMgLjFECERf!E$&;^bUIYbHQX*W>xj0y60;mP1wQ$B%y<`?Z)?S)BS^kJ+TXFDdreG zj*vsQsxPSSe`mA^9v~W6xPQ%Th zEYHhZe+&|OzVsB1p**fi%*h@@ttZrWI_^-;8Ohr*c8$fodsGBNmb$``Fb^T|`NRW`H>D{FFj|qB8@~!6HXa8P%mwYZqFYj_0PmWOew1M>V z%2^Y+&$!E+~cWr8sP|8a3YYy3)abGDTt^68!{#DB$Wi6V?eZe7s}KxiIrx)XdW z2fu$Et-lcfMw2#WP%Az=*_6%DEy z)igQHT7RO%5H-eVacy$N#Db|sGfS4OBuOD@ipf$+IhD*=um@_6*>cJ`m%^^VQGu<3 z{so%KRcfeOW3`%UuBBo5w9vH0W-YbcO6M*e+eDAudg{5CL2h8D7-8s$!$ul;l!>)A zWrnFUPMc}wS?<&h)eq$h)aX#-O=|VCJ2hyX*?-Lht?fh?GZ5oIAa0Am+QAkxpC|-g zxx({FL(Uy*YQ-G4#O0o_M#pHS=TJ=Zp| z>nU70^@67l?IRV(sjQb;`CIui^fL4^^fL4^^fL4&XK00MYjfaZYK%E#?hn6N@u{sM zbARNyG$YjYC5x|-Xh8|TeC4jn*+3dO!+Fk~@M5(Y&Dpd%{R%R%^=7jS zTX$j2g(LnLdbnnc$xi&!m3`;mOZTIOugd?CA!KlN{3*MC0l68sNgs__8~^|ThG|1X zP)S2WAaHVTW@&6?004NLeUUv#!%!53Pk&QIDJ>3m5OK&*#lfPeh@(`o2o_3Pp;ZTy zm;OPMh9t$sQE)9d__J7baBPx znq3oss#!)V9uu>WTDX2A=hGPn}eEQJ&@9_h*HayvYEcNPj%X zbi*RvAfDN@bk6(4VOEk9;&b9LgDyz?$aUG}H_k zi6e@tQNECMS>e3JS*_Gq>z@3D!MwJT<~pq*#Ib|~k`N)IhB7L!5T#us#YBqEV;=rN z$DbsZOs+B*ITlcb3d!+<|H1Fsntz3oO54hX`2A*`ukQ~WRODGh8_cQvY9MFFY1lHWSwa#(+ z0Ay%Z%QwKmAuv*;>~)WK_jb;x7OI00v@9M??Vs0B-*P|De66 zlK=n!32;bRa{vGi!TR9M69 z&@mCfFaX3bU-@(XVCb4iyB8M#0KhF)TkUW6S;zAZ^Y#P(4RC_P00000NkvXXu0mjf Dquig` diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/ca-o2.png b/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/ca-o2.png index 57a1eaa3abf69de8e86ed2770586422210073bd3..ef543a5e09e2dbc409b7e40bace45555c2417234 100644 GIT binary patch delta 123 zcmaFPHJx#SatQ-tage(c!@6@aFBupZSkfJR9T^xl_H+M9WCij$3p^r=85sDEfH31! zZ9ZwBppK`DV@QPi+v6KK84P%s9Vh;M?)zHc1B>6pXMA!CfeQBAn7Zq$Y~L(eq XSFpZjyZL?#P?*8f)z4*}Q$iB}`=uz< delta 1365 zcmV-b1*-a&0p|*kBYy)edQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+U=H2lH@21 zh4-9dj({YD#BunI=o`%O=V3duGOMa*s=I43i!@<_g)HRv0d|;w|5)KK92{c|NzHS~ z8SR)#DqPXr*s7>^K9he_LyqBQtg-Hk;-*uigpD%pT&K6G$eycLotvrPa%oN z)lVPUr%2uQ{zqj6r&-Jp)J1z625gMO% zkbW-yA@Re&a(~Z=pF2vuh59J+TEo|x=iAkkp=iElR17zD_A_v7kX(+(GK%3;wCyMb z#bb>f$Vcq4p>!(5P=5tVG^o;|dDNXcQcOJAP&0Ry!{%aKxPuW%+JzRI6dB?m*Lemo zOTzbD=oq&g<8^Y3JP58#2nOIkFX7)hf2D-A`j&{I-+#42Uc9F$(%9to6B>*A(| zz_)VYj}P+)QxydDklFEqHMTQEQ+k_Qa&Q*-De7|xQLu3ZfJ7Kukqik4U;&BJUPa7C z1RWiKDl%tD+(3X-c_2wnSQ;FRo%h&y#^~^6<;>fRy$K*xvKZJDseqM|V*Xfi%%Q5H zNma9&dVkQOHA_xebIz9cCRa@?nOZh8w_?@BldESpcQ0NG7r`E=B^N7RN~x7Y#XuFY zD*P)fjhnR8wB=^aTWPgp`t;Pb=WgA5>2>gsfo)>sVZ%ombyAqvDQ22F^R(%+%(^hw zmaMdNrPS@llsqWYoy0yVm*@j+_)*_|4!!GG))g7$Wzn;D34A`rJtVDDg?S#(N? zH@VF$mc7d)cXwJ`%-0WQ;0;Tz3oEwq1`#$TzRA=Ni68>Q|ue3bZim zO85s+y0=6m^+8Jj00D++LqkwWLqi~Na&Km7Y-Iodc$|HaJxIe)6opSyMJX)~b`WvM zP{qNbsEDIfu?QAQTcK44lb8NMlZGV4#eY$7EjakISaoo5*44pP5CnffTwRt63 zRCiIH<=yvZg_OL>0G~)a$8^IY-XNaYv~yF6ykH@F@r8h{K$3Lb6 z0XiiRAR+Gn001dTL_t(o!|l+a5dbg@12JFspEuNtMYL XAKwM*NnS>*00000NkvXXu0mjfl>e4G diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/ca-o3.png b/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/ca-o3.png index 3522af81e5c10f2a19cf15e3bd4f3d47b42b13e1..4e1dc10c29ad7d0b38db1cb30eddbb1734ca537e 100644 GIT binary patch delta 120 zcmeC>p29dmxsZXeILO_JVcj{ImkbOHEa{HEjtmSN`?>!lvI6;>1s;*b3=DinK$vl= zHlH+5P}9@JF(ktM?a_^#3{jX)z4*}Q$iB}ND3u; delta 1403 zcmV->1%&#P0gVfgBYy)^dQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+U-_tlH4c^ z{m&_K1SBCOj)VEA+8gBfd0=->_sr~WCdr>vwJ9SkK$71BK^Wivnc*8Aj+8@E^IUR{ zcv4A)D;gfpr|gcaPvKHNKCHVUMipH{E&6?%P}aDVeb$Fn~8)x&-nx;kzK zWqDrS`eTsL^QotF87kv!T!kIu7Nw0>&+DHGIq|zcX(7J14>=tNSKF^#9jM2 zM%F7*mwwmGr?!fU1qeJ>Bmm^;_ii%AM}VF{zO}-8^zW^A;B&q7@(#;asFLR8Hna*vqTDyqZh=8Mm zpo+*@5+7iIRJkKaP9m9uvGekcIYxUdD`#G6Qmh|Kjx|(OG^uJ< zQx96SW`D^kYtGs7N^;f2lBs1gb1PO|Jh^&ybNAx4a1rD{ExB0nQcA5HDjZeVsu*9P zIrxwx9eU)$4nN9K8ivcj-tIJ$LKgORs~%K&BXJ=*Yu{k231STAMP{ z)S0JEpJmoNYZt2@$`@Fpi#6U%t#Ni|4ccIKGk-xVo#^5WjBz3`Zi|7H!4_xHDJ5Ra zEzV+DSxy;2>f$DJT8x3hG>CQ5ox3;Yeu}rC{!_g1|1lR9b$^1n0O~&S_6ci!opWt7 zcJ9K3X%y@}^bfCry{<#8{;qr(dKr2ddKr2dy2*(okGkOCQ@1%n>2t_jsTcQ52(7Cg zrGMepnfhw?TBf8IcN@aKn7G#b0L{s}8Y_rtAOHXX zhG|1XP)S2WAaHVTW@&6?004NLeUUv#!+%f|g-=sODJ>3m5OK&*#lfPeh@(`o2o_3P zp;ZTym;OPMh9t$sQE)9d__J7baBPxnq3oss#!)V9uu>WTDX2A=hGPn}eEQJ&@9_h*Hayno36 zpGZ8%bi*RvAfDN@bk6(4VOEk9;&b9LgDyz?$aUG}H_ki6e@tQNECMS>e3JS*_Gq>z@3D!MwJT<~pq*#Ib|~k`N)IhB7L!5T#us#YBqE zV;=rN$DbsZOs+B*ITlcb3d!+<|9`>n*_wr^NjE7N1G-;q`(qgB*#(+)+x|Yb?dA#K ze+I6!w!hi{WoO54hX`2A*`ukQ~WRODGh8_cQvY9MFFY1lHWS zwa#(+0Ay%Z%QwKmAuv*;>~)WK_jb;x7OI00v@9M|4C0{{a60 z|De66lK=n!32;bRa{vGi!T^@ zR9M69(4i3kFbo4RUqSZY*qx!Yk@%BO007_{ou<>*>^KLkcL6zopr0Do3PQ~&?~ delta 2059 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 literal 10749 zcmV zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3>vavV93tpDQ_a|G{?76A>jUp=xbgM4sjpXjeVxdCocR6;y0U&>pBKq-{~UL( zm6*0)OK!D%Ug~(AXug+RfBhdnmi7G5-_4f;1uLz@g@X4xxM1)1`m7-6{}8&4-|t@U z<7emZ+1wxRm0*xBxA*>O?PmjhG4lI2^SkwX|K9k%zupmkv*pW;hF`vY!KL4hhmY&~ z&le6KEAn4osLO{xZs+y?Ijf$tpS$ZZW6}JIsB3KZJ`Haim^|E%WxfjkCw?#YtMOHB zw-c8yJMG|Wy5>CAKgLD3TzALq`*XU<5~JU~@YDN=TXd|3;+vm1#)YUacYFygtWfdX z^xNRj82rat+`VqQ*PE_#<>h#*cbp7||NLeC_Tqo}GUrU0$jO-faWU)iiffi3&*?kw z!XfUw{FbhP-(N5H+fRW-sN%tN&0M*`ZpU+qk=(D?3fIqp_a%NlDdbF9uM=P*uANwn z3wXe07m~})7VnF5*soC}aBT~qr z;F}gY&WbS+KP6UTsHc!(N-3w3s!1*N9CFMl=UlQtu9r|^NhOz3YH6j{P-9Is*HUY3 zwKv}a7?@garPbD2@7{DS)Hzq@{hcGjk1*m$BabrbXroWUXU3Uko@LhAW?z1V1x&2G z%BriazMa`1#g04eyvwe;?S6>06HYwoEwWRh>pa`G51jNAEi+=uUedG25N z&7}IT{O143b4ITF|KT}<>;ClHH(pzU;CVgvWT9+o1KG#xyLKa-IFSply!YCxueLq6 zRp+dc|0-A#?Vk2@E`Ipn^y92l4wvQATYA4AI5V|!*3su@ zQLQ01<)Zz7>vDfpdW?1n~zV>}Tyj?n5XAH;Xq$6dGqMra`Io~dZPq7;FX*uu3 zd|8TW?Olwu_fqMe{Ypk<49Vnt4nsyiOCKN9h zKUV0gW+{B3bZXRpFdZ|`CO^Y*lOz4ke;uD$9eizF~}B$8#56S>a8)S$2==nBOX9nAF_` zyEqB+c>QubrH=IdICKQwT*`)FyTLOq>#Bp8W?jdJ12qILzB^G`<9e}?3H_quDiiRh zKni2#9Er?3HnB*UC8qBf=H9BxTO}Mg0c`eS#Icq4jnz5-a*Mdgcr6@QE`iWao`jn_ zBbG4EY^#j`p{VoVN|!?U!&Q@b!(L*uByT9c}tpZJEG3qSra=y&$w(va$Y-xH=;<=hIzJ;#u#k71U) zdxcGKS1Pp>#&#BPezOMa*miR>_qdl1aQ6O>9#E{SCXY(FIMw6>@9Ws@EMMV?7ix1$6TaB%glu*qeC$pB zWdf2I0Od+;*QtGC%&9Ooiv5joP9Sf|KVKn^7$#vS-;2_d;N1m&PgwM3^MDD|1?h`B zKsC2OI}=CEO?!>q3n0uI1~@>$1yJ0qj!+4l=O+1xp%8*OtWFpS?BWCh#th3F;EwYX zIYc4%m1-AnL>4&WpU6Ff(hdgmc_hxlV@S^0wF*!!Z}9{WW0~wO67SZ_ZtzyFGZjso zW_cFj01Lt(4rJ9uB4SBPE)hLH6eDhTF2m{+}A=r zV@@rkkS1X`u9Zmzius|`Vm>;oFaJveoWm0wR3jdeCdD&=OScaTuIi zsjfrac|+p`=OH@5Qjpf>gn89N?tvSLXeee@=nH58d%4W5Noge56xs+EY*fos%sTfl z%hUFFQmQPDtK`{I<2IamS#A;q5gq19FE+_FQ4|Nw_HdCd8rS1Pia)>fdf`n*hfst? zZIl+_8;cb#)~B3X{Uls>K8*w&aJn_2ewd%+ByYGQ^YfB#Ol}pqOTcbTlT$v_!29KU z#PIs_>U}M#D$F?p90bp8J`6X_?dC6R@fu%802#B&q@|{Lv<#I?YjDf5!!0D8Jm=6jKxX8MU zt)NEY9swzODceRuz9?9RZ4rY4!=Kh96$~Th+8$d#iU9zVyL2_YrbCM{wD@A{a|0A@ z3BMO5yd^8N+BW3?HWA)}y=!D5S?F2}wSiawOeZUb&1`TT@O0QXLp2~#tppoe@H1Ku zuY|B;+}aT2`XqA4qU|6v@5U=T66CLGCmmzrrL) z!9Fs^5GXz#H^^`hwq!f`)a)R1sCXVEO+EZQeLZ}*nSO?E1Oz4BIV=SWxl3;dHLA`; zvIOX)u+9hA_+=Omzj(0=mJGizZ-ODN_?cp;$q&f@g^--~C14B2yDJ5p%;Mfi&{QeIwgMfT7T8zlduu}7mf$8i z+FxP_0fWxUtckF!t3xY*DmBx5AKs(8OarI8g+g4%|JA68@*lCh<)a0x2n*4+v%BeJ zzRRcoo@ud}f6g?C*G^rl--Y-S!$j~s5kVaOeChSd*YN%b?pFwZyk!0i{qMs2Cnx+$ za`Rua{HG*0yz-cU3UolrhGk};%>*<@=?1%844G1J;4d-3hk=vGKo1#f;pc_ z&ckr3sJ`n{VC_-IS?#GnwHg(MW@DWN7CB(NGc`|)t5TcU)J{kFZmDrZ99)1ut;6gH zKIDh3FR3HN*rfr^!*2#Mfpzj3_=s^CYoWq@@R2G-85@#wUZl$9%i!TRu=|#YkMR(A zqys5C+Qa(2YbzUceBQDguG@h;K2QKLH^tg4+F4u~$`0uxA#1IvQI;oFC}LXb7Gx-r z_2vv&eG&I+er$E12KUnT4a~Z0M>CCbIpgh9xFw4LQk))+#qZtbybZSml0XamP@@Pu z3lOs9-0{d)cM3WbCnb2{Xror@2j!d+7hHqs+au9HAvJ~Ka!^{3LZ|LB6f|YWy^Tjk zK$@q77kAy@dmhSPQ4`Xt2Tke4r8+J)s5y1EVYF2oGNgjDD57v>zbjyq6XXePaXXKG zJ?**;J=f+}Nt9D$$w>x&RiNA*30w0d+(GWfofM~iPeLXVyjc5)Cu+Q@{<6@8*bHNSK08a`{3u9BBf*N!EG1T3o$pjAZ8{=% zfJ2NQDT1g9FfQ?qvRCZVc15HVvJOuzqQ}ciRFaYs-D%GNWn|S^r9@f*XDAl`ltju=CA~IX6d(a% z=YZN6qz%;@P!kUE>A5s;l31xz19zZ0k-CUZ(tU4EM&3%B653A*E9HUOrQeAO_^?1{ zKE0dKUlJb=5#a0dQIs~A9)h}Zs7RFXQb!AzayRowcTTP4MLxz2x0}Afb5S8`BqSM% z7=_&`Ja&Dx$Z?j{zDQWmLp5&%U)?p53LV>PSokjX7|;ScKb!wpRoI|l_Tx8jF$;kD z4ckCt2{fe9D-viirE zEM&?^f!4GQ3rywDRgekLH9&upl`S>#vKT@IbMQ?I6%7RHNMzL;DN7n7?33cfo~!V? z3f%LdKR?5XM%sF{*{g2Iw5h02FPrmpuOQVX3FtP@6QR?UTlT~{e-*wP*{XdKw8>TB ze2f!_E8amV2f#{hsOcgwgnOc-GIxqF9~Cm@P=pJKhK=yNT7t=*h2gTOR=m(Qf_mMF zBDF!tv_1OOy9t%8<>FAugOq-t$$TIm$Lmb+68s4ap$*rdvTV%ji#YuO0CPX2@W!E_ z6?m?|r1TtL4T!^PQsbSm+Z8hf1S$=bkZLhIN013F4>r>d5vL%{7vM+@d1@pThU|CLV%`ViAwh}yx{DUv zJvns1L(k17JnyOYGp0m6M0Lg-av%lm?Mluzj2zoOnmYZpH>xJVtzUQx;8hy{>Dn$M zAPZD_Z+TSThflS+QAhUXch}9}qtq(Q4w8X72|@uhsGFD(1&XPXvcaw)$NwaeAh^JZ`c83Vh!TLX?q%j z$EXr>d$K#hV$p`&-Y~ycOtnEM;e9vAsux{`#=&oAa#k%}HEqC_2_BI%eivS#sBmk? z&NUQV2r)ie=cYV}iDjiH;k&4H%V+L%r#d^4r41h{UF=P>JQNb}-87g{@Dmi`e2xpY zhU7g(9a-Nq>^;J|A&484E7m2={dA!Wc#W1kZE^>xnt=psS0Dlu*?BITWfoSO&Saw5R1%sEDwt0t*zur=VyxyV^Y10)rd7BnnR~SwI0v zDUG@n%Z;>;0%=1+inNzR&VrA=#V9%kCOaV{xaWP$I&B2TTam%TB-&nzdTe6vLa)Bn z5S5$X`&9N(+f&=bPO^rYF`$>?63BxJV1x`LlrW6d=44S1x}gk<&?x6@HO;{%Cdt69 z^`RLa3K7UA^#$kLI` z7k;SA66ZdFxnyTr_#Hsvf+cm$pV>7bVYxbBQ}aebqH=kw!5a`fJP)5zuqI8jpp?B5 z5V{E1j&dS+Q5XC(buQsr(;N)NTTt2ySa%@o0o9^LDGW+DVl{D6b4g9Lk)qq~A;b%L z5df&9DHW@N9zq6@k@vjKP=^VjnQQJhRlVIShfi=vZ0vll0c!~_qJFdodchF9Q#g6S zNK&^Nk&T4NWv8^SmQgyk3?HwMbhPK#+O!r=bsF=LPCL>BT(j9%ffOSmZJ!kZ3GHrq zQCCTS?dffGc~~-Z6a;}Pq&t*El!AB^4L&-}w;u-|pdaEV3F-nJ%nhtZc4?E3`iG0f zU@jdC>f`TX_G_Q9U$#tL?TF_9AcOTJB*k*uI1oYFM?>wY0oFGGT>+&sDQenZt;0}2 zr?gyQK?_@Ei(k7BY1) zs62Fui*Ya1dc5+!*zlk3&Afo%b#K}qp(eL@@ds%io;{G(`fD!$E!ti?k%D$<;7c2f zs+o^>hkw3kd3>V9-c`Ph`NfuBuBYxw8N#FY1>cu@U5x9$FE?I#?$`BRSNnA>^IYs- z-P{1j#Uv1edMl_R#Y7r-%xlPm1pV1kr<9eW2`+21TE=(StE(q6`gB&4UgR#ccGX^- znsXcOyTu?+RElT=OA`|f(p(O7q>WNp^`m`oD)f^2-S6CnihX-`+(3v%NkV6Ll2+J4 z*rg704mQFqAQsB?c1LA8_e4fvr-TlRFo)80+(G2qZK(Ab5Pl6JPwB9`wCtq{l6s6> z-4T3IlL#z?x<1FN=LY;x!hjP>UAAJe(7gfA)&`a4Zi~|A!oO{Zj5|IwKlism5sF@? zemmQQ|H6}bR}~4!t_GW?d?1O>EC{?~a{Xb))Z9`Om%R%b^7i|%CPUvZuu^N_603#h zQfUOL1`v#v2+sK;+i1_aVkS&-_opUU9pM+hT8O=KX0t*3*JAEc%8%FVHB-4ev~-b^ zGM`5~XLYndZX+dw03+sv3RtOAx!|s->+TVMOC{{2%Dqod0b|)znXyrj$df&l1t3B+ zupLs|b>uV^Z^AIt$mp$Q9K^~3#T10MGeI^{wHYd3w8ek$Ip-t7Aypw5(Qx$ziB{lj zQn?otxW|H)p+-}1kQH_k%2al338Um(fZAVts
TUh_xF|C(=XbqD%WgUBO*jBBgG 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 VU&TB4t!V%N002ovPDHLkV1h7j^?CpR literal 10791 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 delta 2748 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 delta 534 zcmV+x0_pwU2d)H=BYy$)Nkl_rY(eK3#Vpg%+;Dz5C@x+V)u z0`13#wK{jgToTIN9)z%n)})EnqzNG`LI{gM`w3`2K?sY~hV>bB#yPsq3T6ZVK>PTd z3Xy4}sM8BYn1AUNMREMIQN2#*pk9Y?a>BH;CEy00=b7W3PG?F`Rq-7E0stgl{)jI` zfDnQ`>{A&45Q~}mZV^}%OahW5;ZiPO{0eZpFtrz{A_yU_5fIX8BpMAWFGXMxxJ7{H zd0^()jx3XaEWZH&;y4CE07fvigb-@O0!ybyg{7m#fq&P9h!y%(Od}w7dypi_7w0sw z!Po*tQIN@IJaJsB*5>lT_AT%Wa|FJ}W3=w?Hb)@Hah`60D*?dYE%-krnHMk?HA@vF z>%#3#iOL6uNB$ECa-6G;Z4Q*nCHm=oFJK#ieE!t2zX+1a)ba#2dJ!2P39s-zG@piF YF`2?d0uREQRsaA107*qoM6N<$g5fswmH+?% diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/meta.json index 1da3a0a7336..1c8714a8dbe 100644 --- a/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/meta.json +++ b/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from goonstation at https://github.com/goonstation/goonstation/commit/cbe076402ed43b1cd861295bbcb95608c453de7a. Edited by chromiumboy", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise/. Edited by FoxxoTrystan", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/static.png b/Resources/Textures/Structures/Power/Generation/Singularity/collector.rsi/static.png index 6097982ee260134d4413f66b77a391f69a499f97..7d8c7a8c49c0d50a333def578ddb231367e3d74e 100644 GIT binary patch delta 1592 zcmV-82FLlB1;`ALBYyw^b5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!~g&e!~vBn4jTXf1>H$RK~z{r#a2s?6jc;Hb?e@G^lRF|5d|5dhy(%)MOP@GxG>5> z7R1EF#6*Llzd&3Vq+!5MBs=0pPtR9 zw*4%F1 z5TKiaUoi12Nq-m$Doi|!vV1PECovNkyj~hhCQXFhf}8~={J!2TVrZlF?zbmv&un@! zU)~n9zqxd3?7clZ^W`vs&vtxr)|t85uu7Iw8ZW!md+V+@+wp_8ANnod&(bUtCfs(U zU0GFe>+2_K?U}iS#o0~}^v+Y(V+e%Jl}_odvOBhRynpO9JAps%2c5=3$6LMb?iWwL zx_k;uWeTg}SkjiJVPr;{q*)RtJ;eadO{eN`SdA=6(m0Hh+_28SVG1FnFr}@e*1-wF zNfO17O8Yf3z<@w}>;h*Q%q`cgId*wf)d|``7BbSum=WCaUB82M2g2T_Fn;k*hH#NeHDYOeBIUVk?VaL&;M(=?4z&cD_yP%(%D zlf(}y6$-g_m?R>J8(`EpLqtcX3|yBiZkjsm`7di#fWbcDcuQs4O zVt?aInB6xmEhDXpE#Wo4ANO?hi;pFTAp{)ksUXF!2oos@qXdPA5=R2D$=)eP;!^M^_Fjh{>bX6H8ZAO2I(p7{>(qyu z;f|}@sGhYc@VgXrL(R*SCRHmXveEkGu`;#h^79}hRDt%&G);0`n{3;nQpF|T3-n-_ zIEp!lT80bGFW*ByZ@kFn=*Y*1(fL^~FzYARYN2Lhk>(p7dCiFYc1(#69*Lk*wSPu8 zW?Hn^3aLFCkcX$=itvmn@(^ft674l|ujN@M>tLb! z#2C+pFt~`IjuLp5;15taCERAck$)m>L1fUiD-GhTq@=L#lEN9wDa^4!A?~E>*E>jE zMsaGQZ5@4Xxn->zKcHS8qsvd7Aiv{L?4w&ylz)$1eu0|J zRxYswj({e>*syV1wt4eI^v=4^_4?VP_fxfArQ>_2^ty=m8+$2r(A%|MMa#BBB9PSn z%hLpY+x`Wm0bkkZttg|%|JbJq@EK!j>UX+w`JA>rl!B7$P`O;8^2YUa>QIxud~=%0 zlUVl$_W9g}oSaB$df#c9-hch0p7UNGaR=W3@H`Dc0G|Ryu#F;atp?@=rOiPQ2YBH0 zE_(2%U9{ns-L&D%?tJ&=Se}5rYIGE!7F_z^HJc8-jmrr0BA$P4&(QcU+?>G7?D7LJ z14W{%XiiD`8zJR6@2$CSwa)cdUw=0=iuwOHhKg|eqg(UUt-V&qSXZ|d0;xb7Y}dd?rC2P}P-m>K{a<`E2q8Z>6Q1$HvPqyak_U;^-; z0kxvwcSUKKqks8KF)R!W4qSjI9x5$_f$gwdUZew~$CT7j?MAY8v@n4h1UhuiI@_ko=s zJ8(K(O%<az&f&(GpUTFm0SxuI0WaW1hP-_5MPS34AM280lSdYh<=v(>t!Sven7Sz8a0H7d>D2QT9DyQWTTb<8$h5DPmxw*}? aD#E`ZG) diff --git a/Resources/Textures/Structures/Wallmounts/screen.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/screen.rsi/meta.json index a4c3148aa8e..25f0d7f1981 100644 --- a/Resources/Textures/Structures/Wallmounts/screen.rsi/meta.json +++ b/Resources/Textures/Structures/Wallmounts/screen.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "From vgstation: https://github.com/vgstation-coders/vgstation13/commit/a7290010020e541ed6b57817a07023ca6bef26fe#diff-20395160138bed693d15eee6f16d671531b5fa533ec52c50e8df6d52370dbecd", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise/", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Structures/Wallmounts/screen.rsi/screen.png b/Resources/Textures/Structures/Wallmounts/screen.rsi/screen.png index a3e7a3fe55a7e1903126e5a270dbf1a58c74b4e3..6b6329e83d0189509e614d991e696343ef5dcc20 100644 GIT binary patch delta 351 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 delta 2092 zcmV+{2-Ekp5$h0;R(}9VL_t(YOEp$&kK4u-JwtNH;ak#5_U8|p`XUCftuW8 z7VMZpU^YPo(en!kAbc_KWgc}3Y!TZJAOiTJfH>luYny}_4}ZwTUtYa*f!{s7$s<%n z3ANal?P9qA;4jxXDy*FpwD<`B0LVuM(*8!5znFqX&Dx8J_tVGuDfbnF}9i=v-?e!W~S*6S=6 z{h2NdxFc#|7Ka&ea-ljTr3m1JcdOhU$vMEgg5lw~M}K_y$pCzL8^3w|!+ipmYq;>) zDqZF2!?B9v#8cc$q}jq(p`J{-URNUSX9u40{RoS+@2ofT*vQ3=G*ZghQDWsIN;5Bv z!YDg+#XUaV_&(Wz#cf&a9S=PN7&9m&LVrB=V@C!Q+Z6i4uIu&DSPw^>8HT=v6R-o4qi*QXdF-pEa}bd*NX-m$=Guj5iP;9*&+#b~r-k*~ zqR8NJO=B<`GAiLy3{%(7Zt~=z?V4jpaPFV2N%kIrRyUK%(VyTa4ucVrd1qaf72qmSQCBU-VPFI^JA*3)yqs(41S4bTkA#OXFjMSsqk+oARw3G@vH^>PYgYq1@e)zCS z305@t8%t)=;nKMLsv?eE3sI2ZmoVCvr4vF9(+u{|X1gf5w_6C;i2bnq69y?9S;;qq*gW(ah^0F5v%zx83b66OGA@NaZ3r*AQiT&k7ZH1<7w@q7I z^v!_zUJqTJ1aqdm3|Uz1PfV&X zAPm?rc7p|@ON2TKH_o8~z6H3`sK(vs`!rm9!WmZq(n(~etR zs+csE(stO!_|b!Ux7}r4kWL@2-`D2U6Ot&QNkZ5>GRZCBxH;ES@gS=t#)Q*6T|k=# zYrSocuGwaZ$iWyQFnvGvJAd2?BPT{hYi5;W)3vne;Ye1cplS#&i>8NWA0%X_Z6xl9 zz{cUX#vMt>$2M1(=%t*@NJVl3<(zdaC{*;4CU)H2kYtv@S*oHmRjjUKgvKx4Zk7@E zm2%_TieSx*FxENEWOUF`*#Plr>me%77KZXcm4m4L<6aSEPE+A?S*eSS9(}jT8Sl=xatT z(p$USqgqoetzuc(aZ<$T1%1v~Qjy5w-+&qv&u9)$NC!hwtVFzG$opW?Tk(i#sR|=f z-F^9dbK~`gs?)-irhnJ7a~AHPl96ECJR4dq(aa33BVHTS_-AHWPX&BK=XgkK13NrQj%o2E^ zq}8cZ%KgW8-5d-&bkKo=nsbJc(zaquJ+#Mz=ew&kI+is#hJTMEOFsVk{r3-t^3+z8 z`NuZtD=ky=Yix8sYGyhg7KkKHcw4cF$=h682MieI(2UiH;{Ep3pT0v=U*a*j+3np; zwleME$JcN2gxWlMyoxY3!tG!rI(P2BRQgbs!({R-SMtnjSvB&$70(z~=wh5!XgwKI z;LcJ#kGp^U@_+v8l=-tfb-noWA7AFueD?%;L?5I~O9w71*2$1?BKJw5_A?y!Eq1(l zEtY@(`M)pkHnpK!`eyTFe>!||_q2HT?~d?COx1AD8-fsdh=0C$V3ykhRxCL^S*WR^ zyri+lkpl=9)FpZ45PuA{c~UaCels2hp&shUb9+eEr6tWG8Jgk(i<2#uTTQ_?0R9i} Wn)}~#Yr(bv0000iu)f&k=MiObiLg}$+c|XTtLSHfec%IeWbn-9{KhDdpPO%7JS|zXh*7N z5BD`~FXmF>>wkt4e|UL6Jk5{$w`YA;E=qagBI+>?`&mm;cv~)uj3z=AEnViN?|x5d zseD%FLCoHJD9eSnRuN3nd~u4L;kRV?4<67ZiteF#FSH@2HNzEjOs3c6Oi(o}2F zVj(K@sU!NCu`n{h0DY*8#VHhCM*~WiE0uz^Nn*Vr<9{lK8yAx*%F~tRjSe2&i40Ao z)kmbE9GQ|iO(NHwJuu4QVy0Y)QcqcOjirB7D3O>nxEE26#8Od3J}M5gTEnfY;7Hq= zB0CCAloWO+cH0__E+R{`&xk{bj+x5yFy+PO5H-KGEn?bjSd&`UuWZm`mca0kP)b}& zld}Dmynn7jM;z!pJT)sAe*S49={-2h1!;L;WcQAkeCkr9FT+JoA>0W}i=6rvN>mKE zET?v2L(UB=p;hIv3A~h0t1f%uiw{A8y$vIK=;GdcYj5)Ae>jUvBRKpe|-4)BO5WLGP-ka zo5(Dys>Qjrmi`oSEQFCiGNqg0+TKe?wSHK{X42@2&+?3dz`9ZTv9D{pj*3K-EvgWZZjVNyJT zc>qA(vn0kR$?uuB$nudxW1gpya>!;rAb$=pC@98O1q!=?@Z-m2f)DPxQx`)rd3(G@ zQpwFl7j|o^gx`R{y8*PanEo<4YCUV!k*LIufL5o(k zDCB!4hLdg&IY@e%X>N-0k=I}w#j;UyWnFFBh<4_A8VC;9G%BixK#@pI=NchreEHq^?7l|9HHO z?+Jk-aXM5K8|P28_MBP!Xar!V4Lb-l^BpvCq-mm=mUXz<*Ax`N5uPBiN2Oo;@v?xQEn+PSxJK=dX24x$Vj~}2!HA=G^s_b?38g_@^jUWubQ$ zXHD16m5bJMWG6Xjf7yZb!c@r$Jgt;c0%I#xn__kAZMNKb@^_)e`wWJmq>odj`P^s+ zkOPu4Lr%t>!)fQCBEPCVdw(gb%z4?_@K}1kE3ri2t-nR^6+eFW;d^bqecNk3Kn{o9 zL-C9bHOK07rYM?43Rpz+ejkwe=yT5vBL9>mpQs?7bJ^vF_v34+Yuv+)^y=JsS)5Dw z!>4y(kh#(cOQlzqY(ts($>o|7Bi*7(5CoBIhe=q>EOmMDu0;&8*MAk1)CdDNkVZ?i zF)%)|_2_fiy5v5+12D-GB1FiRA_SywiKjr?uA)O_z$D{<*i&W9rNGEP5}E*o%eK3rRag z-;FhNM$6szb0t(d3_0?zUvCAzl5$C7bN_YQ{ySd`719v6qF)P-$JMbUldGa%_u=#<1{rJU91K0-r^64n012!rfQEGzdI|xX}DHnRG zj;ztuwvj``8-JtxM$f}kOAdk(eKs?rv@prBk;Gz?uDE)d{Smwrbx|NIx1nDZUs41r z%$oM}){ynqsYP`AZ%T05MCgpPt&nyHk2|)wwMSjyu{$bQDb_RVtnT*g zR3@R9Tf2|h#9+64ev*!)`(0hwwr7D~K0mRw9i=1*;&$^+KV;RnBlLpYZkf<;ajToR zP=VLz9aXYI`VKn5nI+ZY}&D@fNFk=X*ME00000 MNkvXXt^-0~f*({x+5i9m delta 2231 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 P00000NkvXXu0mjft$R&e diff --git a/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_construct-2.png b/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_construct-2.png index 122fb41398207e6033b43ef850e9133e71b91922..63c1502b8a7955f594121f1659eb34bc10b71367 100644 GIT binary patch delta 2207 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 delta 2278 zcmV{pHq< zB3jA?5%sBRBrnKo%&27;XndkpxjC^4Uqmi3ht+s#rHF~f6o2XuqJRi59#I%z&8aj@ z$tiW%=eKrDqW9JZoq+N*BdiZXX z$(yPaq~LN$yhoFWsOP{FSVA3YDLux^IIRS;U1oQPZ3Gl53Ox){Ol-@Q*=)YOeFe&h zg}~6zZ-6fdo;*9~^}54h(qYvSF4VLM)chp$6X0Z$$A2lxm`jGg+3#qTSVQzGMR+i8 z0UvxK02epH!-Ku8h)nFk#&i9+--(ZAc^F2PhqG9mbREwZjf#iEoP||ukmWf}K;5|F z+M8Auc6VSa+qPT7V||B$II;Y|50W`gx9DWxILHp_Ze;1iu&6=+Lj(nhP|aqQ_c?G!q3JTFNRfxIqCD zhGNx{T4HaPmB4I7&LnZl%XRSz)nV12YVmv?SFOu z+kYST4oCtXz<^58)8Fs!e|_)KgS}t6>*p|1)Y>`Q_mzXhsW`zB2Hmb|px{*MAgDU(m(Omk zW8~g<2WJK#BzC;e=@j`fJVWJZW?F^CPG2ff7*ziXTDI~Z3))7um}ZJmy?gEbUbp-C zCp%w%^Gy)C8^Zu}_UQY)&#qq`_By}2`T;Ur5$b(W^P=SZcn*y@@YUuAL-G>C@PBNc z9ibMaR!ez&Hb=_n06O5%Op;+z>$`XU{D&{T`u5$)?2CW>`|p3cty~wL*OJ#73?4BS z*{Wkv;!?-;7-$%OSfF$kIe|spBA5}v>+!bu~dkjH1jBUr(ThODUwv8y$R1AS+>G ztJf^^EK^+Ez5npe?q_%J-&dvZ0-U6{UXl-BvEK#0P7+NyYdByk)1ugac><1s2j+(t z&trq`OG{w+Vq^|WFe}y#p5@^avVe%iX&P+czkd)nZ11TcK7CQ z=dGezl#BW1TL0d6Ka^acfQG|9Y+O02rOY#0oVdO@P3zJ6`pGQAZ002Eq7X>RK9;nc zS^;-~Es(|OyEi|=@bedM4u1|`|L4i`NBhs8{PY?lw{Kh?UjE>c8+YcjSu1mr!f4CTk7lYe$zp1m7v=uJi}hje`u2Mp{r=WyFd7Xv zMx(qao*n)?9P~Cv1DIkmTbsd;7Ou7|-Q zFQ+XVb>fjtoU=M1fXezNp3k16&X~0vlZBO34;;malX)RoX7FbhDP#da_ zTU5@bmSgtgV3sX(!+9rB@$|>t-J@wXFZ13C0xgZul&G%+zl2IuQdGEjXpCeOM!2o$ z!o+Q^T(}4rI9JJBQu=0m>&C}aELQPo%w%J8kn~kK-G4tg>_oWFNDEgH#Dw5`)p&WIjE=xt_jy z3c4duEMM~{xFIms@#*`+BSP1Lf<9VKg)9`Bw1OBt4mqG1xaBDC6%2ULkQ&d{eFh(h zhaz9pi@-7~veB1D-KZ|o6)Z;5?TrPHq)$ diff --git a/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_construct-3.png b/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_construct-3.png index 2e7be27dd052dec7ba25dfe40051b9af101b3176..ad5094443b9ad586d2b45c2f4f9bce0d5cc7d00e 100644 GIT binary patch delta 2210 zcmV;T2wnH165tV#R)2L#L_t(YOBI$!w_C>%geQj^frl4FQnVOk`Do#tk9YF_{|ny9 z8`69M;fBdQ?ePXX0MlLdRW<$NkH7WgkV4!#b_|(v8{{G`lFma6!0KZ3$>ld*Ng2ml zB!>mHs7cAimy;e~+o~9aj6_5BtPocNA)^$672mPUzkIw;1%C*P@8-^QxFegBPeRNr6{d#{@s9MCGML3IZR#Z_T0{+853E3O-+$Me!Rao z_mazRTTC%}wr)&af9#;oyR-Rxxke}z))ybS7m<=e*7ADGZ6dyx*X#Fqu<0#^vXN*# ztwG%Ub6y|26Mv-Fm23X<<5Pb(-L5b9_Go<4`XE)ZL+ZDq)f6Por&*^ik*YACrn$|2 z&3UeJGRIy`Ap`>RNg`HGx@_g_4Ly>duHipwepev!zyJ0=Dr4Tn)}`UWpsS47THV5O ztzgwmO>D1DJWN8wK`23Zh*gL*Mv!&rh_P^HS_vFAuYVUVq)G82)o8#3wy?6YK5&1a zJ*vEf#94&BFgoQ?mAZ)=Pr5&KW2w}byP~D#(0)`Im4)v32CD8^WH6v_mG_fb;-$G@ z&+C$t*nwq6L0qX@S7ga2E;4Ix^{v$;4KMie?tlxL}{ z+n0h=2Fgo~-o+eJ=RGCnW|kTyPfHNA$?nH0_J2X8Z8NkeDrMy^wKbU1KHmTSK@2=+ z82qu|s){J4T6ILk{C^`Spn?|Ih@R81kJ%&)&Lq=S*BnnV6bL#NrSEJ)7n_`vP|+w* zY^w0KF$i21swSAMjPgytIyGiB07hc%>XCb1*PiU- zn}11XS=#D_F`@2AUXrCpE*byj^Rb~C%peC#j#7_=T1i~yPBQUCiEpWeUEdqNpuvH)JFa1?FU5a>+Wwk ztYnbw$%+xmrF{z(!lCC4L#Z-7V;D(M-H>H_Sed$eaW~BV+xF+nkGJQ4LtgmYqm?2X{u{>EO)O_kZnK@jqby@jdC$>G zZ)#PnEHR&Z4J57Ej;IJ`l%Px3<9}?dqnbOy$dCZ|DpR6jc8Mc%bc|RFJshD^{MX+f zyV&jFe6ET4w!-q`_BK3GNYkzUC zIcC`e+FQa&J(#raY+aS7eB0w;ZHr+pQfU=$hFRcxOZ+-iYQKBCR}hnZ_kZcvhfjYw zp1PFsMo`2Rs7@>d;KaN|bhay5?Jgp2yIHW*!hinjJ(Exgo6VG_Nk5ba(Q*YULhhT9 zg;zU$X(7mD3m;rtW1d?fkX;n|aGH69q zXv5naMdh-qZhkXvq>8|L-+xWr$J67`9}kn@C!?f&M<2&e{3xT#SLs?mn<6NigI1xf zZKdq!ix1>}a``*|@YDNuwtIQmYT1eIceh0dg7vke>TO|Mp##Ve=(en91$EsPFL2E? zl%V9u6b(`~;w-=J&rlmQ@j_d5ZW3niGynPX1L?T%&@j!=DobMzlYcz;QgarxA!-!m zL}dqDD94M)&0bAm#-na^MIa*;#P<{#B$u+_>JVEgH2SJ>gn$0!y(I_8S1kyHuA!YPv9i7|Nj^VcfIL$;ywoA?$e-$R4Z$i)z@Tq+J8pKeUyehgG~F`i>$M+ zs%4Dg-DZ^5(e`LYXF?112g}o-d=k#W9SCwAf|iM(n!vVfy{WE>d3WW;KYjSxjXiNl z+U3)>ly|Z+jl%G}n`(Bby>zA48ZhadINwF2POR-f1R(AP`%nz2&FU*5T&!0ZPX;O8 zngw+*jEQum@_!bjw~y^NTJrXar_w;|Ftp2P{pQ51DN8K1io=h zE8gltZkuOH0JtEWcedRx)W?<+g+-4$Wlx&Mv?Hhg`hUkiO-n+=Q4VFImhg1l-DHOa z$*h~svn(N(LSWcv?t7$(Dcurc6J#hHO`%b?2D%QVOVO?6c&9zh*Q+^+bt{U0{cRKo}G8-{h7mFDG2d_=ZqabiurvLx|07*qoM6N<$f^dmZP5=M^ delta 2277 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 diff --git a/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_construct-5.png b/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_construct-5.png index 0969ca3ecd71c546d306133c643d8452f1b607c3..c6d8dacab13d04c03d2d9f6b1a7c9507b9005150 100644 GIT binary patch delta 2203 zcmV;M2xRxo64?=uR)20uL_t(YOBI&MjwHtsL}Ooat?KITA!kTPGn6Rspqn5F0?7aW z3{eMC(@Rz57Q2Tlc*$F4hKHFwbNchof9uI9g}8U@95UrL$W`1Vorf5J)z#>W%kR9B zGLE%K4l8O=lai}17d^nfS1}41iH7W1A+82OMkxdH%T~_b&@=hw9)4Bxw*?~q`;Q-^GUi?ET^gPYy2^;H z)vYYo3Rcb3#CGb$!z4r;gc5{@ScOPq1X+iU7zNs(*30yOQps<6fG@>_EBY27P{j*sCs0P!GK;W?2km0LzSmxKp=n$dXT7WY*5edq`e%RhE7%vnw%KdBZL7yt}w&v#u|qw?olH z@Iw=Tw0{C>jlbn!tFnprZ0aA-DZ#(|IMV!qcqbLb(hIr=PrGtzR%Flp#f~xFDH&%y zwkeFNJo0%y;YswRAg`2FQwCK00xZfH?d%F118*I-I}eEjK? z7Wql_4Tu;NA7vu zdVg|^?pIcOe8RTHeS?ZBcD~Ze838$oi2r);F@EatV zqD<};65b9PLeZL8ZGsSC^be>ltrHol%*u=;>p&{Y1eip%Um#>RKyCw@&{<4|2x`F+ zw?XHcZTI`e-zXAybu^2R4Zb*x+pI~6D1U;?kV`Dxbdc1O-jMcIHN@~1ApYsoJd%(8 zx-*|*?#i~jB`%+MlVtJgnVP=mx!EK-3QB6DetrK6k@|Z0 zC5Md+vK?75Lbp+Y$Iyv0zeOs^P5QdBo&*$!5w?om7pa~E~BW-#G2DgH!k4}VTz zMN&=dNaSRPMhzAeNR6c%uPwCbOb)U}hm6C<=v6eg*-qhEma(V2=V+xj zwJKKDn6JGClD2F|R0K0h(535fHh<|h2`t=K762*fac@TZ2wm66C+Iat3g(g?vksXp+rN6r@Ume)C#L>e{*j+ zW|`0*jc`&ACT$1XHsvYbk9gYJ#V{ACw2C9KE8LgF??a{bcW;jhOLmz_M}J8rA*H+% z6mbKp6AJ-2G4Bzb?MhaMi-`MSRxGvf@BaG8Bvis?Go@+LPvuE8u0Tb|eKWG~>Y%R; zf^2U3=Y=;q@G+A^6#8_Td4!~WazS!&(!C9rozPa8COgGXD`WIuzx=zSewf1Ai6)?9w7+U*OIEYg>i)rAVZ-0x?L62ZC|~>Ez?kfk|R?z zNZAr+`Tcl-+Cmd=v{mOWVfH@r?>;|~jtdXOwk);E(%8c!PrlTg1%GW3HHvbgvI8!Z z<1K5=UQJ=fqi%IYAR`sT_Y@f9`lhw?igavSI2lKZNSLVvtqXB1 zwu>>^wh`r4-YP^v%zrR-^?^sCxIGOP&sNC?(DUE~KiSqTA5hKDBuqQIV>zv@eptR# zUYML(8!clSuCeHY?$i#BBRn3}Whp{*;ivc4}#zP#LkyimC00sVjcw*M~Y zj+EK2|2wuAu*0&*c|d^HCn@X0r$G;?R@N-5@5vstjgb2&4S#z9nU1R$S!Z8WV~pb6 zZj{#1_Gm_DLIeAgjQ()m1Sct}Oh|pML1Zo;W0J^J%w~ z53(_h!ti{UY7VHqbfwlBFzJIh-$kTOY;8dVAZ~$UD27ytuo1$=c8Bp|kmAxTsDoil zq&t;MklsGGZ+|rM_KM`=RDhkTr)%;RH+raa6HMONxgRT(s2H{9ZW4@v(2`>-iGr@#E;pQe!zaetIUnW!Z^-48d}VMQ|Qrt>UI z$fXb%cAEPhX<|w@LhOPJg`+7nN=$ekN|&PB%K1Tin(udW5!+rA|KZDnSn+6K)kNKH z!P}+v%I%1Rl dSBn_`4-|G%(+ua+k(dAg002ovPDHLkV1kYxN;3ce delta 2320 zcmV+r3GepV5zP{iR)6A2L_t(YOD$Ggj~qu8KGpZ@+;{fkyVuTcvLFX1#6hxy70a=L z1Oy3C#1pSP^2P(N{16^MNJIz%QZ7Iqun~xuU=m|Hj@Nc}6Wg=1J=6E@>dO>g_qx=Y z>8|dobIy0Z^POU^pW6~di!vsn0r9HL5;7{;sTn3Jlc*MYM1PFqiAX1=u>v=BnY%<2 zvYQ7YiwHLck!|6dgHbV2&Zx^Sp4*a%jxP_p7DYiwM$Ze=UcX1AR#<>{{^Nl3lE~Do zVaK>}>-O5=qoSzEFh{G${OOl0*&}C}1i=XfpJh)H+0}^nlY6k!7&vum>(2Ie6anu~5iNbi0GuJXl*@nop)d<0j^t!82hQSRo>gU!p7~nKn1p z>Cs(@7lPN7x9>f-ek@C~il;dVgltpRC(j}3YlsA%P?u^7rns2|JZGxT)Go0Vi)=x* zfoXDyt?6Yr_n&=s6UKUkKDfTicB>z!4 z^8nb4oOzxgphd}}B$LPjVaOFVi3Qg7SPIORXmXhLP3HHSxEmCaJgi9sl>&@%a0Nw` z@mhAqduf);Gk~*pWCXHv(sN^28C~K&X^w>C>wl)(RDqD1Op&mu(3v)29f6Ilk!`u6 zHG@drzjnR-1`o_pNK-9o5jPX6LW=NaLBlHHkWj)rU0FTU(HWAQ8-__j^ex?xnV~H5 zD2&K1D_YQKky4h%;Jl{VA6&cst;^@%zxsD3P>M;^H4C?_ta%Br2?)1C4|r33 z?0@nL>#L`~=H0&&GU~}RVEKgEYm^4m;iP?d>&|30KUnd@?_L30Ok%(M!=Daj2E*Rv zix=mUT`I6uVs=-;RB(<8QB5k3pW681{w;ZZZQu@9v-u-eQWk<`Jnm^WjB!#ks&9UJ z|I#;K8oLOnc(}9saOcszon70ue)034dVhPn+Yg@{A9aVWl-6n?+lH$0lGiojkP&}( zFiPfAsfm$T0D`w^Y(~ee$b?G`v!E274^NMu+%ReMS`qf)+Z>$|_-?M4l z(8)1fK!(a`l&MXN8tjNXk*IhIT8Oz;w(6>^@tO(NU`Y4B*qeL4?HrUOX1FTTvVU#9 z`>z}AIutVMJub2&&9bQvixDHGS!s<3g?0MqKvq;Q%uo@a8n;G8Q%vwde&^apZ@zKq z!S3UvC_K*x$vi*wd{BF_>-lwEfA7^Zzx(U8^oqcEwJAeP&p=Y+v zOp}uG1e-y(pwu z=u|$=T2~JSd-F;r19KfeEu zOBY^#y1LyNwJI@Mrg)AGvU!NkCPY?cn=6CQC!Pdkc-WMOq@pY7QIb=Erm0Zk=?+c~ z%>Z#r(l1>&+wOj@oquV%)zeW&R7E7Il_!{ClvXhi5wD(M&+pKUXP&#@%^38dMX;04=6xKxH2O2mQY77M1PLPFRDVL*v1hl_V9}G z2p^$K;x{WR{7J-P6pocon&nET`PCc60&*m|WCPIl$?BsPz~V+^no$_Ak<7->FhMC~ z-X5R}alOZzj$p%6!?ZwkdF$BHP~o#E%bBbSp4Si*xiFlP*)%*-q`B($N$o1E&I@>? z0g!;Sks*RgS$`MZ&q7>>uRhd^jU!Vm-T|pXf+ErEW$!=Eb1&kKCE>Vncin0)FfSf9n&S#1y4_s>= z#wav;QljP4tLM+}%|bto`Yi;_sB&t>$Q57MBIjj}N`J=%7CW|s(@M*B5v8$?gi&pZ zGAb!}G&%R;SxVwIAC=r$Ssiu;BAwm7x7~Gco+%X^MUWdNXCze0v)bmFRX* zB?|~uRN}m4*HjKDv&kP&s;D*uZlM~-?Cj>lN2gpN%)Xy-_Vwqtx~e!kro1GsL)fvm zoDlN`2!F@A8Yq+=qPdsS6eq7<|KE2XY^^TE0%7IDrLpJFj;<~TyPsx&9}>$<4<`i1 zx+Z3skI diff --git a/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_over0.png b/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_over0.png index 89314482b3a3b392d3c07d3092e1ec06d182fe21..ce5adc65b5d1ef61d0c96091b9af8d7d0de684e4 100644 GIT binary patch delta 3191 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 delta 3188 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%0AT+W84P&p=<^=(f$0jPgNQ@erekDB{uZE2 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 delta 3188 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%0AT+W84P&p=<^=(f$0jPgNQ@erekDB{uZE2 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 delta 2859 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 JPDHLkV1k@(WV!$V diff --git a/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_over5.png b/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_over5.png index a9d7e7173785fed126e8dd104c6b89171fb666d5..d0384e6f2b4af36283ac4b470bbd10fee64c74ee 100644 GIT binary patch delta 3086 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 delta 2864 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 O0000z&Ip80}8C%oayHOZ@*b) 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 delta 2859 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 JPDHLkV1k@(WV!$V diff --git a/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_over7.png b/Resources/Textures/Structures/Walls/solid_rust.rsi/reinf_over7.png index 970ef1f1f5d37be02883f75dac02757445580d97..45a43655f63538bf0c87c3d80f63f39cc7b8f211 100644 GIT binary patch delta 3040 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 MNkvXXt^-0~f-km3f&c&j delta 2270 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 delta 2592 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 delta 2592 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 Ef_zHf+W-In delta 2507 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 delta 2518 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 diff --git a/Resources/Textures/Structures/Walls/solid_rust.rsi/solid6.png b/Resources/Textures/Structures/Walls/solid_rust.rsi/solid6.png index 8737894cbf0f7b8e5edaff7ed5d4fe981dbaf434..ba6ac0281f9fb64913d60d50c23036cabc209da0 100644 GIT binary patch delta 3114 zcmV+_4At}F6Vw=xNq@lr01m+cxRGn^000Z=NklO*8*uj>sUCbMnU4vo^#$UBu+D{0QWce#C-R&) z5hNvWaQy!F{}8{ce!%O?3+y?=WnLgvjwxyJ9Gpk=^qNin$;vUV8j46oM+m$mRrS`(jyZV z-O1H3&ZAQg9t-G7pimZ4%b?2W?#SmjpIcZjh=7k?#|X+cp+tjwf^jGfBD&q!^I*lCxjJN`;KQ?#vl+@t8Rxo%MJSvTC_t`3xlJNr1^cqV5-T z$x4w!vQHGM?u3*YVbVP+bf|TLpbP?mIOPd!@`{vVXm1=2fJ$ZTgN&pY z(vdBbBTzVbwu(2OzzaZ-1M*kQWdEt*E-Q^BF>Hq$;!N6S@;KNrnoq6U_|$yw(cRXzt#Fn*Vq3!x#MY z=l|NTXLrQz3JAVO?jwv}A%i5DRR~>%HGdPx^a;7BkY|*#((pJYZ)twA=eG+1>$1Ox z_r`7jMy{Y!3K{0Ia(5F;|1MH2a>ncx_3bMxt!-X7em-Di(yxY!SX4#?T>isnTshl>5)NBi4l zE`DFRk+s1cpQ-XPw#P#u%8Ximz<-IPcy#OrAQ}U!D>8eBSmsy;1vOieo<&M^HzWbU z1{7?EnaWM1SBwNFt2p1ObB>PP08|r5d^dPUbMo3FH1zA)8B5DakWYxNK^rJ><%;K^ z;MgK37oi?C)_AA{c#GWt^kqQ$I3ro^tOAz(bRyF;aqlpNCnyYv8Ko@~kAIZ4tF6&< zcGAyrbSCes92dI*sHY5Vs<1SQBBz2GSEwO_fAaKl4^~KqTBZcUz~?Lc`hY0{^qHE# z{pRY9jNJgV*KvWktqmAT&qhsRDf?+;YMbZ8F9h((b>mzc+RRu!a}zvc+|USpWm+&SJ#Os- ztM2f)j+mzbdYCZXzhLgE6qgLYj=yqBd;$)rXmq{{Cw!v}l5a~qrzL#TAkAx%U0!oUB!XMZ%=316SSMU^-2s~i`*0dSMR!{2aq7E2%S+W#LOKTj~6@h|R% z;WpqZ?wBc#^lOWsU;h{D*rR*=4}SR!EufXa>{FZqXT0Zb$8V9+5>NdF-{}+uou5)3 zNp`_}*=}%tjgYp8)iX80Cs2Yirww+r#L;0W9+blq&KKZxA%8EV8Q39cwsCilkP{OY z+p|8;RE-)@Z}$O?jokoj7eIYM|6?-5f%Bj9zV`-USfN+y0#?U^CP=4(ls9tzb80Lt zy2T>l@khsQ0J?F;jG9b;ho&8_9EZK81>%eM3tAuPnJ2u;h@5^PUldAtM)AyPX+D7t z_`YVZpDBUAKY!z!!k|2*s7=60Jh2s?@Iw*tefa~PRe{U*8FY0)B^|z^Dyo}La82+& zDa`Q&#bd?9p*P&f_6uF1S&+5^?zu+aUZH&p(M5h{(ajR=%_G{~|NZXReFjER?%pc2 z=FBmezHxz=r~=!JBI!&{An{d#ULnEF3oJuQ1iNn?Rm-n4+Tb z3FyfFL;cF0zanv(Wv`KCGt{bKn0GENZ_fc79lHUrtzzN#oa+N-eMXE1Sx#SIIy~8D zENEb-l!V6}pXvWo2Y04-m^$i#PXItF3e0PY7&>le&%~}MoJx!QZeTBl>&*v=`&1$ zQKq=f0scx5kU_>A(F_Plj-aUeBHs%OGCjc&n14}ZZ=`tN?hnTHeU;SqTv zFj>yg&eK;+GtE_CrOa%rmX)(YrPLTT`Q)LXzLtb##pPb}v;F&bH~?LjViIJE!J_FS zD}O;@wA-WchOI-r(0be-NuEq0(Yb9`M;VHYLWlD@wG? z1R_itUa=ACfQ8d#t`{UX>WPKGXbI_W?~8mq4hKM>MBFZ|7Y<_{A-f)@8^M_GFlmMR z2`D&DQ~5wcp4gr%*hqETd_^_6Hx36N%zqxwNl71}X_)4>`v@~NQZN$MQf5yAwo5oc zN`z%V!7-|~`wWvsn}31_C6O$6nC;JBI7+D@tRwNU$7My8$(@hX4wgbH+JHfEw94%- zN;>|YMA`0t$m(za3=`6n$wF<9+8LOFDzcccED8KNBbh0j3UUO^)b=+7!jl(P1AoWe zS5^PM%E#ky0L}@MZnmdMzJ}y{2O^rWq{q1M=bAjg_p%D7BH&5UavFuasgTDDa_Z_|N|sx98&LmE=Zt&q4}oJZfp-a6)QnvFRBk+aO3D=M*>)V9FNrJ;zV~`5*tk zh0sjtm)__b28nvRozAi7#fnx}Sbv5qCJLc+ok)sg#=$@rJx`kdI*l^!H&cfLkOYpw zlB!Om2;0-9?0qe$3?G}lE?5!FoBeJE_B-`}a~ZszvG^Ph8m+x?JOIm`dbs6IPmrgP zwN1_MOBl-Cp>ZU?hJuJ9hz${A2Qjkmha6ckKwLN~cE;fVxID)&D6})no_{`irrCj8 z;J4?9SBbm5A<-I!T4AME2+576$>P`TG28q8TWf~{P^c13mf~{aS2*GPLxrcO3}q#d z78$-u8Wi;te3|2$ropAEQB(oHW0gEL#rrBBkHZ0&)-PCIMzptqJv{@zd_nhDhgUj1 z{pCSxY%%`&3)X4Ee|L8t*CXOV$JiQ+q5tK5m5&DSH-fj&)3AkQl>h($07*qoM6N<$ Ef@F2y!~g&Q delta 2484 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 From 106cdd80eac12f92c84105f86d73fadda504e71b Mon Sep 17 00:00:00 2001 From: SimpleStation Changelogs Date: Tue, 11 Jun 2024 00:34:28 +0000 Subject: [PATCH 22/24] Automatic Changelog Update (#435) --- Resources/Changelog/Changelog.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index babf5f90782..1f876cf9a7f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -4148,3 +4148,21 @@ Entries: message: All species can now bring their own cultures and languages id: 6119 time: '2024-06-10T20:48:48.0000000+00:00' +- author: FoxxoTrystan + changes: + - type: Tweak + message: Rust Walls Sprites. + - type: Tweak + message: Armor, Bulletproof, Riot Suit Resprites. + - type: Tweak + message: New Banners Sprites. + - type: Tweak + message: Stunbaton, Fire Extinguisher New Sprites. + - type: Tweak + message: Rack/WallScreen resprites. + - type: Tweak + message: Stock Parts new sprites! + - type: Tweak + message: Radiation Collector has now a new sprite! + id: 6120 + time: '2024-06-11T00:34:02.0000000+00:00' From 0fecb72ff1f2a333f810aaac6a0787574c23ae10 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 20:36:56 -0400 Subject: [PATCH 23/24] Update mutants.yml --- Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml index 44c5cde0323..2a385341806 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml @@ -140,7 +140,7 @@ - type: Psionic removable: false - type: MetapsionicPower - - type: InvisibilityPower + - type: PsionicInvisibilityPower - type: AntiPsionicWeapon punish: false modifiers: From 68c8a3a78c8cb80fac0f69c61a627ddd7e9459f3 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 10 Jun 2024 20:41:14 -0400 Subject: [PATCH 24/24] Update psionicPowers.yml --- Resources/Prototypes/Nyanotrasen/psionicPowers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml index d90fe9a820e..ca1764e204c 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml @@ -6,7 +6,7 @@ TelegnosisPower: 1 PsionicRegenerationPower: 1 RegenerativeStasisPower: 0.3 - #PsionicInvisibilityPower: 0.15 #Temporarily disabled, engine update broke it + PsionicInvisibilityPower: 0.15 MindSwapPower: 0.15 NoosphericZapPower: 0.15 PyrokinesisPower: 0.15