From 8c8ccc9625b45a1c5cd50e03a3d14bb73ed5eae9 Mon Sep 17 00:00:00 2001
From: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Date: Fri, 24 Jul 2026 00:34:05 -0700
Subject: [PATCH 01/35] Fix not being able to stack atmos devices (#44886)
---
.../Recipes/Construction/atmospherics.yml | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/Resources/Prototypes/Recipes/Construction/atmospherics.yml b/Resources/Prototypes/Recipes/Construction/atmospherics.yml
index 4d8554d74c..d0a6b0b584 100644
--- a/Resources/Prototypes/Recipes/Construction/atmospherics.yml
+++ b/Resources/Prototypes/Recipes/Construction/atmospherics.yml
@@ -22,6 +22,13 @@
conditions:
- !type:NoUnstackableInTile
+# we allow stacking for shit like radiators on top of atmosdevices
+# for now (trademark patent pending)
+- type: construction
+ abstract: true
+ parent: BaseAtmos
+ id: BaseGasDeviceStackable
+
- type: construction
abstract: true
parent: BaseGasDevice
@@ -35,6 +42,13 @@
graph: GasBinary
placementMode: AlignAtmosPipeLayers # most binary devices support layers
+- type: construction
+ abstract: true
+ parent: BaseGasDeviceStackable
+ id: BaseGasBinaryStackable
+ graph: GasBinary
+ placementMode: AlignAtmosPipeLayers
+
- type: construction
abstract: true
parent: BaseGasDevice
@@ -370,13 +384,13 @@
placementMode: SnapgridCenter
- type: construction
- parent: BaseGasBinary
+ parent: BaseGasBinaryStackable
id: HeatExchanger
targetNode: radiator
placementMode: SnapgridCenter
- type: construction
- parent: BaseGasBinary
+ parent: BaseGasBinaryStackable
id: HeatExchangerBend
name: construction-recipe-heat-exchanger-bend
targetNode: bendradiator
From 6f26e69ef9a008e811814ea8a5b684ab202fab04 Mon Sep 17 00:00:00 2001
From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Date: Sat, 25 Jul 2026 21:30:43 +0200
Subject: [PATCH 02/35] Fix windoors not closing if an entity is on the same
tile yet not touching the windoor (#44081)
* fix the thing to be good :)
* Review changes
* The curse of doing review comments in order
---
.../Doors/Components/DoorComponent.cs | 14 ++++++
.../Doors/Systems/SharedDoorSystem.cs | 48 ++++++++++++++-----
.../Doors/Windoors/base_structurewindoors.yml | 2 +
3 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/Content.Shared/Doors/Components/DoorComponent.cs b/Content.Shared/Doors/Components/DoorComponent.cs
index 714e45b604..d7926b8be4 100644
--- a/Content.Shared/Doors/Components/DoorComponent.cs
+++ b/Content.Shared/Doors/Components/DoorComponent.cs
@@ -301,6 +301,20 @@ public sealed partial class DoorComponent : Component
[DataField]
public bool ClickOpen = true;
+ ///
+ /// If true, the door closing check will try to match only entities intersecting the door's first fixture.
+ /// If false, the door will check the full tile the door is on.
+ ///
+ [DataField]
+ public bool CheckFixtureCollision;
+
+ ///
+ /// If true, the door will be able to close over entities with the MachineLayer fixture layer.
+ /// Useful for windoors or other thin doors.
+ ///
+ [DataField]
+ public bool AllowMachineLayer;
+
[DataField(customTypeSerializer: typeof(ConstantSerializer))]
public int OpenDrawDepth = (int) DrawDepth.DrawDepth.Doors;
diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs
index ed82ebae97..cd6382c403 100644
--- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs
+++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs
@@ -23,7 +23,9 @@ using Robust.Shared.Timing;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
namespace Content.Shared.Doors.Systems;
@@ -54,8 +56,6 @@ public abstract partial class SharedDoorSystem : EntitySystem
///
private readonly HashSet> _activeDoors = new();
- private readonly HashSet> _doorIntersecting = new();
-
public override void Initialize()
{
base.Initialize();
@@ -450,7 +450,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
if (!HasAccess(uid, user, door))
return false;
- return !ev.PerformCollisionCheck || !GetColliding(uid).Any();
+ return !ev.PerformCollisionCheck || !GetColliding(uid, null, null, door.CheckFixtureCollision, door.PerformCollisionCheck).Any();
}
public void StartClosing(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
@@ -522,7 +522,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
}
///
- /// Crushes everyone colliding with us by more than %.
+ /// Crushes everyone colliding with the door.
///
public void Crush(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null)
{
@@ -532,9 +532,9 @@ public abstract partial class SharedDoorSystem : EntitySystem
if (!door.CanCrush)
return;
- // Find entities and apply curshing effects
+ // Find entities and apply crushing effects
var stunTime = door.DoorStunTime + door.OpenTimeOne;
- foreach (var entity in GetColliding(uid, physics))
+ foreach (var entity in GetColliding(uid, physics, null, door.CheckFixtureCollision, door.AllowMachineLayer))
{
door.CurrentlyCrushing.Add(entity);
if (door.CrushDamage != null)
@@ -552,11 +552,17 @@ public abstract partial class SharedDoorSystem : EntitySystem
}
///
- /// Get all entities that collide with this door by more than percent.\
+ /// Checks if there are any entities colliding with the door, passing them back out to use e.g. to prevent closing.
///
- public IEnumerable GetColliding(EntityUid uid, PhysicsComponent? physics = null)
+ /// The door entity to check.
+ /// The door's .
+ /// If true, the door will do a more exact check based on its first fixture.
+ /// The door's .
+ /// The door will be able to close over .
+ /// The list of entities inside the door.
+ public IEnumerable GetColliding(EntityUid uid, PhysicsComponent? physics = null, FixturesComponent? fixtures = null, bool checkFixtureCollision = false, bool allowMachineLayer = false)
{
- if (!Resolve(uid, ref physics))
+ if (!Resolve(uid, ref physics) || !Resolve(uid, ref fixtures))
yield break;
var xform = Transform(uid);
@@ -566,13 +572,27 @@ public abstract partial class SharedDoorSystem : EntitySystem
yield break;
var tileRef = _mapSystem.GetTileRef(xform.GridUid.Value, mapGridComp, xform.Coordinates);
- _doorIntersecting.Clear();
- _entityLookup.GetLocalEntitiesIntersecting(xform.GridUid.Value, tileRef.GridIndices, _doorIntersecting, gridComp: mapGridComp, flags: (LookupFlags.All & ~LookupFlags.Sensors));
+ var doorIntersecting = new HashSet>();
+
+ if (checkFixtureCollision && fixtures.Fixtures.TryFirstOrNull(out var fixture))
+ {
+ var fixtureToWorld = PhysicsSystem.GetPhysicsTransform(uid, xform);
+
+ _entityLookup.GetEntitiesIntersecting(xform.MapID,
+ fixture.Value.Value.Shape,
+ fixtureToWorld,
+ doorIntersecting,
+ flags: LookupFlags.All & ~(LookupFlags.Sensors | LookupFlags.Contained));
+ }
+ else
+ {
+ _entityLookup.GetLocalEntitiesIntersecting(xform.GridUid.Value, tileRef.GridIndices, doorIntersecting, gridComp: mapGridComp, flags: LookupFlags.All & ~(LookupFlags.Sensors | LookupFlags.Contained));
+ }
// TODO SLOTH fix electro's code.
// ReSharper disable once InconsistentNaming
- foreach (var otherPhysics in _doorIntersecting)
+ foreach (var otherPhysics in doorIntersecting)
{
if (otherPhysics.Comp == physics)
continue;
@@ -593,6 +613,10 @@ public abstract partial class SharedDoorSystem : EntitySystem
if (otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.ConveyorMask)
continue;
+ // We want windoors to be able to close over machines
+ if (allowMachineLayer && otherPhysics.Comp.CollisionLayer == (int) CollisionGroup.MachineLayer)
+ continue;
+
if ((physics.CollisionMask & otherPhysics.Comp.CollisionLayer) == 0 && (otherPhysics.Comp.CollisionMask & physics.CollisionLayer) == 0)
continue;
diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml
index eed3cd6f15..86d0d2bc8b 100644
--- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml
+++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml
@@ -107,6 +107,8 @@
path: /Audio/Machines/windoor_open.ogg
denySound:
path: /Audio/Machines/airlock_deny.ogg
+ checkFixtureCollision: true
+ allowMachineLayer: true
- type: Airlock
keepOpenIfClicked: true
openPanelVisible: true
From d158047dfda051d8e8f06b281edc0e991f897ba6 Mon Sep 17 00:00:00 2001
From: Mehnix <56132549+Mehnix@users.noreply.github.com>
Date: Sat, 25 Jul 2026 22:08:19 +0100
Subject: [PATCH 03/35] Artifact trigger continuation popup (#44909)
* Continuation popup
* Update from Comments
Whitespace
Remove server conditions
* refactor: xml-doc dots, whitespaces, minor cleanups
---------
Co-authored-by: Fildrance
---
.../Components/XenoArtifactComponent.cs | 24 +++++++++++++++++++
.../SharedXenoArtifactSystem.Unlock.cs | 10 ++++----
.../Artifact/SharedXenoArtifactSystem.XAT.cs | 8 +++++--
.../xenoarchaeology/artifact-component.ftl | 1 +
4 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs
index ce037c7c2e..144dccc546 100644
--- a/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs
+++ b/Content.Shared/Xenoarchaeology/Artifact/Components/XenoArtifactComponent.cs
@@ -166,6 +166,30 @@ public sealed partial class XenoArtifactComponent : Component
///
[DataField]
public EntProtoId SelfActivateAction = "ActionArtifactActivate";
+
+ ///
+ /// Message shown on artifact unlock beginning.
+ ///
+ [DataField]
+ public LocId? UnlockBeginMsg = "artifact-unlock-state-begin";
+
+ ///
+ /// Message shown on trigger being applied successfully whilst the artifact is unlocking.
+ ///
+ [DataField]
+ public LocId? UnlockContinueMsg = "artifact-unlock-state-continue";
+
+ ///
+ /// Message shown on artifact unlocking ending successfully.
+ ///
+ [DataField]
+ public LocId? UnlockSuccessMsg = "artifact-unlock-state-end-success";
+
+ ///
+ /// Message shown on artifact unlocking ending with failure.
+ ///
+ [DataField]
+ public LocId? UnlockFailureMsg = "artifact-unlock-state-end-failure";
}
///
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs
index e521e93a4e..b2be023a83 100644
--- a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs
+++ b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.Unlock.cs
@@ -65,7 +65,7 @@ public abstract partial class SharedXenoArtifactSystem
///
public void FinishUnlockingState(Entity ent)
{
- string unlockAttemptResultMsg;
+ string? unlockAttemptResultMsg;
XenoArtifactComponent artifactComponent = ent;
XenoArtifactUnlockingComponent unlockingComponent = ent;
@@ -74,7 +74,7 @@ public abstract partial class SharedXenoArtifactSystem
{
SetNodeUnlocked((ent, artifactComponent), node.Value);
ActivateNode((ent, ent), (node.Value, node.Value), null, null, Transform(ent).Coordinates, false);
- unlockAttemptResultMsg = "artifact-unlock-state-end-success";
+ unlockAttemptResultMsg = artifactComponent.UnlockSuccessMsg;
// as an experiment - unlocking node doesn't activate it, activation is left for player to decide.
// var activated = ActivateNode((ent, artifactComponent), node.Value, null, null, Transform(ent).Coordinates, false);
@@ -83,13 +83,15 @@ public abstract partial class SharedXenoArtifactSystem
}
else
{
- unlockAttemptResultMsg = "artifact-unlock-state-end-failure";
+ unlockAttemptResultMsg = artifactComponent.UnlockFailureMsg;
soundEffect = unlockingComponent.UnlockActivationFailedSound;
}
if (_net.IsServer)
{
- _popup.PopupEntity(Loc.GetString(unlockAttemptResultMsg), ent);
+ if (unlockAttemptResultMsg != null)
+ _popup.PopupEntity(Loc.GetString(unlockAttemptResultMsg), ent);
+
_audio.PlayPvs(soundEffect, ent.Owner);
}
diff --git a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs
index 4d9e2e6df4..e8573d2a2a 100644
--- a/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs
+++ b/Content.Shared/Xenoarchaeology/Artifact/SharedXenoArtifactSystem.XAT.cs
@@ -70,8 +70,9 @@ public abstract partial class SharedXenoArtifactSystem
unlockingComp.EndTime = _timing.CurTime + ent.Comp.UnlockStateDuration;
Log.Debug($"{ToPrettyString(ent)} entered unlocking state");
- if (_net.IsServer)
- _popup.PopupEntity(Loc.GetString("artifact-unlock-state-begin"), ent);
+ if (ent.Comp.UnlockBeginMsg != null)
+ _popup.PopupEntity(Loc.GetString(ent.Comp.UnlockBeginMsg), ent);
+
Dirty(ent);
}
else if (node != null)
@@ -87,6 +88,9 @@ public abstract partial class SharedXenoArtifactSystem
)
// we add time on each new trigger, if it is not going to fail us
unlockingComp.EndTime += ent.Comp.UnlockStateIncrementPerNode;
+
+ if (ent.Comp.UnlockContinueMsg != null)
+ _popup.PopupEntity(Loc.GetString(ent.Comp.UnlockContinueMsg), ent);
}
if (node != null && unlockingComp.TriggeredNodeIndexes.Add(GetIndex(ent, node.Value)))
diff --git a/Resources/Locale/en-US/xenoarchaeology/artifact-component.ftl b/Resources/Locale/en-US/xenoarchaeology/artifact-component.ftl
index 90029b282c..c131be8859 100644
--- a/Resources/Locale/en-US/xenoarchaeology/artifact-component.ftl
+++ b/Resources/Locale/en-US/xenoarchaeology/artifact-component.ftl
@@ -4,6 +4,7 @@ artifact-verb-activate = Activate artifact
### Unlocking
artifact-unlock-state-begin = It begins to shift in strange ways...
+artifact-unlock-state-continue = The shifting continues, intensifying...
artifact-unlock-state-end-success = It slows down, visibly changed.
artifact-unlock-state-end-failure = It slows down before uneventfully stopping.
From 7a8fab78e3b8178f811c3dd3b244c141578493c6 Mon Sep 17 00:00:00 2001
From: Pok <113675512+Pok27@users.noreply.github.com>
Date: Sun, 26 Jul 2026 01:29:39 +0300
Subject: [PATCH 04/35] Move Accents to Shared v2 (#44772)
* move-accents-to-shared
* Update AccentSystem.cs
* Update ListeningSystem.cs
* aaaaaa
* Update AccentSystem.cs
* oh god
* aaooao
* //
* review
* fix
* Update VocalSystem.cs
---
Content.Client/Entry/EntryPoint.cs | 1 -
.../DamagedSiliconAccentSystem.cs | 5 +
.../EntitySystems/RatvarianLanguageSystem.cs | 8 --
.../Speech/EntitySystems/SlurredSystem.cs | 12 --
.../Speech/EntitySystems/StutteringSystem.cs | 12 --
.../Administration/Commands/OwoifyCommand.cs | 3 +-
.../Systems/AdminVerbSystem.Smites.cs | 2 -
.../Chat/Systems/ChatSystem.Utility.cs | 2 +-
Content.Server/Chat/Systems/ChatSystem.cs | 2 +-
.../Cloning/CloningSystem.Subscriptions.cs | 2 +-
Content.Server/Cluwne/CluwneSystem.cs | 4 +-
.../Electrocution/ElectrocutionSystem.cs | 2 +-
.../Effects/MakeSentientEntityEffectSystem.cs | 2 +-
.../GameTicking/GameTicker.Spawning.cs | 2 -
.../Components/BackwardsAccentComponent.cs | 6 -
.../Speech/Components/BarkAccentComponent.cs | 6 -
.../Components/BleatingAccentComponent.cs | 9 --
.../Components/BlockListeningComponent.cs | 11 --
.../Speech/Components/FrontalLispComponent.cs | 6 -
.../Components/GermanAccentComponent.cs | 6 -
.../Components/LizardAccentComponent.cs | 9 --
.../Components/MobsterAccentComponent.cs | 17 ---
.../Components/MonkeyAccentComponent.cs | 6 -
.../Speech/Components/MothAccentComponent.cs | 9 --
.../Speech/Components/OwOAccentComponent.cs | 6 -
.../Components/ReplacementAccentComponent.cs | 16 ---
.../Components/RussianAccentComponent.cs | 6 -
.../Components/ScrambledAccentComponent.cs | 6 -
.../Components/SkeletonAccentComponent.cs | 16 ---
.../Speech/Components/SlowAccentComponent.cs | 9 --
.../Components/SouthernAccentComponent.cs | 8 --
.../Components/SpanishAccentComponent.cs | 6 -
.../Components/UnblockableSpeechComponent.cs | 7 -
.../Speech/EntitySystems/BarkAccentSystem.cs | 33 -----
.../EntitySystems/BlockListeningSystem.cs | 19 ---
.../DamagedSiliconAccentSystem.cs | 108 +--------------
.../Speech/EntitySystems/FrontalLispSystem.cs | 26 ----
.../Speech/EntitySystems/MeleeSpeechSystem.cs | 82 -----------
.../Speech/EntitySystems/OwOAccentSystem.cs | 31 -----
.../EntitySystems/RatvarianLanguageSystem.cs | 14 --
.../EntitySystems/SpeakOnActionSystem.cs | 39 ------
.../Speech/EntitySystems/StutteringSystem.cs | 75 -----------
.../EntitySystems/UnblockableSpeechSystem.cs | 18 ---
.../Speech/EntitySystems/VocalSystem.cs | 127 ------------------
.../EntitySystems/VoiceOverrideSystem.cs | 22 ---
.../{Components => }/ListenWireAction.cs | 4 +-
Content.Server/Speech/Muting/MutingSystem.cs | 2 +-
Content.Server/Speech/SpeechNoiseSystem.cs | 74 ----------
.../{Components => }/SpeechWireAction.cs | 0
.../SurveillanceCameraSpeakerSystem.cs | 4 +-
Content.Server/Telephone/TelephoneSystem.cs | 1 +
Content.Server/VoiceMask/VoiceMaskSystem.cs | 2 +-
.../Zombies/ZombieSystem.Transform.cs | 24 ++--
Content.Server/Zombies/ZombieSystem.cs | 2 +-
Content.Shared/Chat/EmotesEvents.cs | 8 +-
Content.Shared/Chat/SharedChatSystem.Emote.cs | 8 +-
Content.Shared/Chat/SharedChatSystem.cs | 1 -
Content.Shared/Speech/AccentEvent.cs | 8 +-
.../Components/ActiveListenerComponent.cs | 4 +-
.../Components/AllCapsAccentComponent.cs | 6 +-
.../Components/BackwardsAccentComponent.cs | 11 ++
.../Speech/Components/BarkAccentComponent.cs | 11 ++
.../Components/BleatingAccentComponent.cs | 12 ++
.../Components/BlockListeningComponent.cs | 9 ++
.../DamagedSiliconAccentComponent.cs | 40 +++---
.../Components/FrenchAccentComponent.cs | 8 +-
.../Speech/Components/FrontalLispComponent.cs | 11 ++
.../Components/GermanAccentComponent.cs | 11 ++
.../Components/LizardAccentComponent.cs | 11 ++
.../Speech/Components/MeleeSpeechComponent.cs | 33 ++---
.../Components/MobsterAccentComponent.cs | 18 +++
.../Components/MonkeyAccentComponent.cs | 11 ++
.../Speech/Components/MothAccentComponent.cs | 11 ++
.../Components/MumbleAccentComponent.cs | 11 +-
.../Speech/Components/OwOAccentComponent.cs | 11 ++
.../Components/ParrotAccentComponent.cs | 14 +-
.../Components/PirateAccentComponent.cs | 14 +-
.../Components/RatvarianLanguageComponent.cs | 4 +-
.../Components/ReplacementAccentComponent.cs | 17 +++
.../Components/RussianAccentComponent.cs | 11 ++
.../Components/ScrambledAccentComponent.cs | 11 ++
.../Components/SkeletonAccentComponent.cs | 18 +++
.../Speech/Components/SlowAccentComponent.cs | 11 ++
.../Components/SlurredAccentComponent.cs | 20 ++-
.../Components/SouthernAccentComponent.cs | 11 ++
.../Components/SpanishAccentComponent.cs | 11 ++
.../Components/SpeakOnActionComponent.cs | 3 +-
.../Components/StutteringAccentComponent.cs | 5 +
.../Components/UnblockableSpeechComponent.cs | 11 ++
.../Speech/Components/VocalComponent.cs | 8 +-
.../Components/VoiceOverrideComponent.cs | 10 +-
.../Speech/EntitySystems}/AccentSystem.cs | 11 +-
.../EntitySystems/BackwardsAccentSystem.cs | 5 +-
.../Speech/EntitySystems/BarkAccentSystem.cs | 43 ++++++
.../EntitySystems/BleatingAccentSystem.cs | 5 +-
.../EntitySystems/BlockListeningSystem.cs | 12 ++
.../EntitySystems/EmoteBlockerSystem.cs | 14 +-
.../Speech/EntitySystems}/EmotesMenuSystem.cs | 5 +-
.../EntitySystems/FrenchAccentSystem.cs | 24 ++--
.../Speech/EntitySystems/FrontalLispSystem.cs | 25 ++++
.../EntitySystems/GermanAccentSystem.cs | 19 ++-
.../Speech/EntitySystems/ListeningSystem.cs | 18 +--
.../EntitySystems/LizardAccentSystem.cs | 5 +-
.../Speech/EntitySystems/MeleeSpeechSystem.cs | 85 ++++++++++++
.../EntitySystems/MobsterAccentSystem.cs | 28 ++--
.../EntitySystems/MonkeyAccentSystem.cs | 18 ++-
.../Speech/EntitySystems/MothAccentSystem.cs | 9 +-
.../EntitySystems/MumbleAccentSystem.cs | 12 +-
.../Speech/EntitySystems/OwOAccentSystem.cs | 42 ++++++
.../EntitySystems/ParrotAccentSystem.cs | 38 +++---
.../EntitySystems/PirateAccentSystem.cs | 25 ++--
...geSystem.cs => RatvarianLanguageSystem.cs} | 28 ++--
.../EntitySystems/ReplacementAccentSystem.cs | 58 +++++---
.../EntitySystems/RussianAccentSystem.cs | 8 +-
.../EntitySystems/ScrambledAccentSystem.cs | 16 ++-
.../SharedDamagedSiliconAccentSystem.cs | 122 +++++++++++++++++
.../EntitySystems/SharedMeleeSpeechSystem.cs | 7 -
.../EntitySystems/SharedSlurredSystem.cs | 12 --
.../SharedSpeakOnActionSystem.cs | 13 --
.../EntitySystems/SharedStutteringSystem.cs | 25 ----
.../EntitySystems/SkeletonAccentSystem.cs | 26 ++--
.../Speech/EntitySystems/SlowAccentSystem.cs | 5 +-
.../Speech/EntitySystems/SlurredSystem.cs | 79 +++++------
.../EntitySystems/SouthernAccentSystem.cs | 7 +-
.../EntitySystems/SpanishAccentSystem.cs | 6 +-
.../EntitySystems/SpeakOnActionSystem.cs | 26 ++++
.../Speech/EntitySystems/SpeechNoiseSystem.cs | 70 ++++++++++
.../Speech/EntitySystems/StutteringSystem.cs | 73 ++++++++++
.../EntitySystems/UnblockableSpeechSystem.cs | 13 ++
.../Speech/EntitySystems/VocalSystem.cs | 124 +++++++++++++++++
.../EntitySystems/VoiceOverrideSystem.cs | 17 +++
...istenWireAction.cs => ListenWireAction.cs} | 0
.../Prototypes/ReplacementAccentPrototype.cs | 8 +-
...peechWireAction.cs => SpeechWireAction.cs} | 0
134 files changed, 1289 insertions(+), 1261 deletions(-)
create mode 100644 Content.Client/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
delete mode 100644 Content.Client/Speech/EntitySystems/RatvarianLanguageSystem.cs
delete mode 100644 Content.Client/Speech/EntitySystems/SlurredSystem.cs
delete mode 100644 Content.Client/Speech/EntitySystems/StutteringSystem.cs
delete mode 100644 Content.Server/Speech/Components/BackwardsAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/BarkAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/BleatingAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/BlockListeningComponent.cs
delete mode 100644 Content.Server/Speech/Components/FrontalLispComponent.cs
delete mode 100644 Content.Server/Speech/Components/GermanAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/LizardAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/MobsterAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/MonkeyAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/MothAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/OwOAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/ReplacementAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/RussianAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/ScrambledAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/SkeletonAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/SlowAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/SouthernAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/SpanishAccentComponent.cs
delete mode 100644 Content.Server/Speech/Components/UnblockableSpeechComponent.cs
delete mode 100644 Content.Server/Speech/EntitySystems/BarkAccentSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/BlockListeningSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/FrontalLispSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/MeleeSpeechSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/OwOAccentSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/RatvarianLanguageSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/SpeakOnActionSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/StutteringSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/UnblockableSpeechSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/VocalSystem.cs
delete mode 100644 Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs
rename Content.Server/Speech/{Components => }/ListenWireAction.cs (98%)
delete mode 100644 Content.Server/Speech/SpeechNoiseSystem.cs
rename Content.Server/Speech/{Components => }/SpeechWireAction.cs (100%)
create mode 100644 Content.Shared/Speech/Components/BackwardsAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/BarkAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/BleatingAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/BlockListeningComponent.cs
rename {Content.Server => Content.Shared}/Speech/Components/FrenchAccentComponent.cs (60%)
create mode 100644 Content.Shared/Speech/Components/FrontalLispComponent.cs
create mode 100644 Content.Shared/Speech/Components/GermanAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/LizardAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/MobsterAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/MonkeyAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/MothAccentComponent.cs
rename {Content.Server => Content.Shared}/Speech/Components/MumbleAccentComponent.cs (63%)
create mode 100644 Content.Shared/Speech/Components/OwOAccentComponent.cs
rename {Content.Server => Content.Shared}/Speech/Components/ParrotAccentComponent.cs (84%)
rename {Content.Server => Content.Shared}/Speech/Components/PirateAccentComponent.cs (61%)
create mode 100644 Content.Shared/Speech/Components/ReplacementAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/RussianAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/ScrambledAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/SkeletonAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/SlowAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/SouthernAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/SpanishAccentComponent.cs
create mode 100644 Content.Shared/Speech/Components/UnblockableSpeechComponent.cs
rename {Content.Server => Content.Shared}/Speech/Components/VoiceOverrideComponent.cs (82%)
rename {Content.Server/Speech => Content.Shared/Speech/EntitySystems}/AccentSystem.cs (67%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/BackwardsAccentSystem.cs (70%)
create mode 100644 Content.Shared/Speech/EntitySystems/BarkAccentSystem.cs
rename {Content.Server => Content.Shared}/Speech/EntitySystems/BleatingAccentSystem.cs (79%)
create mode 100644 Content.Shared/Speech/EntitySystems/BlockListeningSystem.cs
rename {Content.Server/Speech => Content.Shared/Speech/EntitySystems}/EmotesMenuSystem.cs (83%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/FrenchAccentSystem.cs (71%)
create mode 100644 Content.Shared/Speech/EntitySystems/FrontalLispSystem.cs
rename {Content.Server => Content.Shared}/Speech/EntitySystems/GermanAccentSystem.cs (82%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/ListeningSystem.cs (80%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/LizardAccentSystem.cs (88%)
create mode 100644 Content.Shared/Speech/EntitySystems/MeleeSpeechSystem.cs
rename {Content.Server => Content.Shared}/Speech/EntitySystems/MobsterAccentSystem.cs (83%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/MonkeyAccentSystem.cs (72%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/MothAccentSystem.cs (59%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/MumbleAccentSystem.cs (79%)
create mode 100644 Content.Shared/Speech/EntitySystems/OwOAccentSystem.cs
rename {Content.Server => Content.Shared}/Speech/EntitySystems/ParrotAccentSystem.cs (62%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/PirateAccentSystem.cs (60%)
rename Content.Shared/Speech/EntitySystems/{SharedRatvarianLanguageSystem.cs => RatvarianLanguageSystem.cs} (87%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/ReplacementAccentSystem.cs (77%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/RussianAccentSystem.cs (84%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/ScrambledAccentSystem.cs (67%)
create mode 100644 Content.Shared/Speech/EntitySystems/SharedDamagedSiliconAccentSystem.cs
delete mode 100644 Content.Shared/Speech/EntitySystems/SharedMeleeSpeechSystem.cs
delete mode 100644 Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs
delete mode 100644 Content.Shared/Speech/EntitySystems/SharedSpeakOnActionSystem.cs
delete mode 100644 Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs
rename {Content.Server => Content.Shared}/Speech/EntitySystems/SkeletonAccentSystem.cs (52%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/SlowAccentSystem.cs (87%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/SlurredSystem.cs (53%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/SouthernAccentSystem.cs (89%)
rename {Content.Server => Content.Shared}/Speech/EntitySystems/SpanishAccentSystem.cs (94%)
create mode 100644 Content.Shared/Speech/EntitySystems/SpeakOnActionSystem.cs
create mode 100644 Content.Shared/Speech/EntitySystems/SpeechNoiseSystem.cs
create mode 100644 Content.Shared/Speech/EntitySystems/StutteringSystem.cs
create mode 100644 Content.Shared/Speech/EntitySystems/UnblockableSpeechSystem.cs
create mode 100644 Content.Shared/Speech/EntitySystems/VocalSystem.cs
create mode 100644 Content.Shared/Speech/EntitySystems/VoiceOverrideSystem.cs
rename Content.Shared/Speech/{EntitySystems/SharedListenWireAction.cs => ListenWireAction.cs} (100%)
rename {Content.Server => Content.Shared}/Speech/Prototypes/ReplacementAccentPrototype.cs (61%)
rename Content.Shared/Speech/{EntitySystems/SharedSpeechWireAction.cs => SpeechWireAction.cs} (100%)
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index 74f693c890..29ff0e85aa 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -107,7 +107,6 @@ namespace Content.Client.Entry
_prototypeManager.RegisterIgnore("utilityQuery");
_prototypeManager.RegisterIgnore("utilityCurvePreset");
- _prototypeManager.RegisterIgnore("accent");
_prototypeManager.RegisterIgnore("gasReaction");
_prototypeManager.RegisterIgnore("seed"); // Seeds prototypes are server-only.
_prototypeManager.RegisterIgnore("objective");
diff --git a/Content.Client/Speech/EntitySystems/DamagedSiliconAccentSystem.cs b/Content.Client/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
new file mode 100644
index 0000000000..36368b62bf
--- /dev/null
+++ b/Content.Client/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Speech.EntitySystems;
+
+namespace Content.Client.Speech.EntitySystems;
+
+public sealed class DamagedSiliconAccentSystem : SharedDamagedSiliconAccentSystem;
diff --git a/Content.Client/Speech/EntitySystems/RatvarianLanguageSystem.cs b/Content.Client/Speech/EntitySystems/RatvarianLanguageSystem.cs
deleted file mode 100644
index f24194a597..0000000000
--- a/Content.Client/Speech/EntitySystems/RatvarianLanguageSystem.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Content.Shared.Speech.EntitySystems;
-
-namespace Content.Client.Speech.EntitySystems;
-
-public sealed class RatvarianLanguageSystem : SharedRatvarianLanguageSystem
-{
-
-}
diff --git a/Content.Client/Speech/EntitySystems/SlurredSystem.cs b/Content.Client/Speech/EntitySystems/SlurredSystem.cs
deleted file mode 100644
index a4bb677edf..0000000000
--- a/Content.Client/Speech/EntitySystems/SlurredSystem.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Content.Shared.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-
-namespace Content.Client.Speech.EntitySystems;
-
-public sealed class SlurredSystem : SharedSlurredSystem
-{
- public override string Accentuate(string message, Entity? ent = null)
- {
- return message;
- }
-}
diff --git a/Content.Client/Speech/EntitySystems/StutteringSystem.cs b/Content.Client/Speech/EntitySystems/StutteringSystem.cs
deleted file mode 100644
index 9c98df4588..0000000000
--- a/Content.Client/Speech/EntitySystems/StutteringSystem.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Content.Shared.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-
-namespace Content.Client.Speech.EntitySystems;
-
-public sealed class StutteringSystem : SharedStutteringSystem
-{
- public override string Accentuate(string message, Entity? ent = null)
- {
- return message;
- }
-}
diff --git a/Content.Server/Administration/Commands/OwoifyCommand.cs b/Content.Server/Administration/Commands/OwoifyCommand.cs
index dcb9b4bcce..2acc7fadac 100644
--- a/Content.Server/Administration/Commands/OwoifyCommand.cs
+++ b/Content.Server/Administration/Commands/OwoifyCommand.cs
@@ -1,7 +1,6 @@
-using Content.Server.Speech.EntitySystems;
using Content.Shared.Administration;
+using Content.Shared.Speech.EntitySystems;
using Robust.Shared.Console;
-using Robust.Shared.Random;
namespace Content.Server.Administration.Commands;
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
index 3dee9db3a9..a114c5f112 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
@@ -13,14 +13,12 @@ using Content.Server.Pointing.Components;
using Content.Server.Polymorph.Systems;
using Content.Server.Popups;
using Content.Server.Roles;
-using Content.Server.Speech.Components;
using Content.Shared.Speech.Components;
using Content.Server.Storage.EntitySystems;
using Content.Server.Tabletop;
using Content.Shared.Actions;
using Content.Shared.Administration;
using Content.Shared.Administration.Components;
-using Content.Shared.Administration.Systems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Body;
diff --git a/Content.Server/Chat/Systems/ChatSystem.Utility.cs b/Content.Server/Chat/Systems/ChatSystem.Utility.cs
index fc24e75cc7..b1eba9f856 100644
--- a/Content.Server/Chat/Systems/ChatSystem.Utility.cs
+++ b/Content.Server/Chat/Systems/ChatSystem.Utility.cs
@@ -1,9 +1,9 @@
using System.Linq;
using System.Text;
-using Content.Server.Speech.Prototypes;
using Content.Shared.Chat;
using Content.Shared.Ghost;
using Content.Shared.Players;
+using Content.Shared.Speech.Prototypes;
using Robust.Shared.Console;
using Robust.Shared.Network;
using Robust.Shared.Player;
diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs
index 5432b552e5..fbbe41310b 100644
--- a/Content.Server/Chat/Systems/ChatSystem.cs
+++ b/Content.Server/Chat/Systems/ChatSystem.cs
@@ -3,7 +3,6 @@ using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
-using Content.Server.Speech.EntitySystems;
using Content.Server.Station.Systems;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration;
@@ -13,6 +12,7 @@ using Content.Shared.Examine;
using Content.Shared.Ghost;
using Content.Shared.Mobs.Systems;
using Content.Shared.Players.RateLimiting;
+using Content.Shared.Speech.EntitySystems;
using Robust.Server.Player;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
diff --git a/Content.Server/Cloning/CloningSystem.Subscriptions.cs b/Content.Server/Cloning/CloningSystem.Subscriptions.cs
index cb1c8593f8..559618306e 100644
--- a/Content.Server/Cloning/CloningSystem.Subscriptions.cs
+++ b/Content.Server/Cloning/CloningSystem.Subscriptions.cs
@@ -1,5 +1,4 @@
using Content.Server.Forensics;
-using Content.Server.Speech.EntitySystems;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Cloning.Events;
@@ -15,6 +14,7 @@ using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Paper;
using Content.Shared.Speech.Components;
+using Content.Shared.Speech.EntitySystems;
using Content.Shared.Stacks;
using Content.Shared.Storage;
using Content.Shared.Store;
diff --git a/Content.Server/Cluwne/CluwneSystem.cs b/Content.Server/Cluwne/CluwneSystem.cs
index d36c58e301..bb96744aea 100644
--- a/Content.Server/Cluwne/CluwneSystem.cs
+++ b/Content.Server/Cluwne/CluwneSystem.cs
@@ -1,9 +1,8 @@
using Content.Server.Chat;
using Content.Server.Chat.Systems;
-using Content.Server.Emoting.Systems;
using Content.Server.Clothing.Systems;
+using Content.Server.Emoting.Systems;
using Content.Server.Popups;
-using Content.Server.Speech.EntitySystems;
using Content.Shared.Chat;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Clumsy;
@@ -13,6 +12,7 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Mobs;
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Popups;
+using Content.Shared.Speech.EntitySystems;
using Content.Shared.Stunnable;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs
index de2fbb6d69..b605ae58f8 100644
--- a/Content.Server/Electrocution/ElectrocutionSystem.cs
+++ b/Content.Server/Electrocution/ElectrocutionSystem.cs
@@ -50,7 +50,7 @@ public sealed partial class ElectrocutionSystem : SharedElectrocutionSystem
[Dependency] private SharedJitteringSystem _jittering = default!;
[Dependency] private SharedPopupSystem _popup = default!;
[Dependency] private SharedStunSystem _stun = default!;
- [Dependency] private SharedStutteringSystem _stuttering = default!;
+ [Dependency] private StutteringSystem _stuttering = default!;
[Dependency] private TagSystem _tag = default!;
[Dependency] private MetaDataSystem _metaData = default!;
[Dependency] private TurfSystem _turf = default!;
diff --git a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
index c623b25857..b1e2703995 100644
--- a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
@@ -1,8 +1,8 @@
using Content.Server.Ghost.Roles.Components;
-using Content.Server.Speech.Components;
using Content.Shared.EntityEffects;
using Content.Shared.EntityEffects.Effects;
using Content.Shared.Mind.Components;
+using Content.Shared.Speech.Components;
namespace Content.Server.EntityEffects.Effects;
diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs
index d18b37fc23..d5b6649104 100644
--- a/Content.Server/GameTicking/GameTicker.Spawning.cs
+++ b/Content.Server/GameTicking/GameTicker.Spawning.cs
@@ -5,12 +5,10 @@ using Content.Server.Administration.Managers;
using Content.Server.Administration.Systems;
using Content.Server.GameTicking.Events;
using Content.Server.Spawners.Components;
-using Content.Server.Speech.Components;
using Content.Server.Station.Components;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.GameTicking;
-using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mind;
using Content.Shared.Players;
diff --git a/Content.Server/Speech/Components/BackwardsAccentComponent.cs b/Content.Server/Speech/Components/BackwardsAccentComponent.cs
deleted file mode 100644
index 6ee8aa7690..0000000000
--- a/Content.Server/Speech/Components/BackwardsAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class BackwardsAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/BarkAccentComponent.cs b/Content.Server/Speech/Components/BarkAccentComponent.cs
deleted file mode 100644
index f584a2aa23..0000000000
--- a/Content.Server/Speech/Components/BarkAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class BarkAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/BleatingAccentComponent.cs b/Content.Server/Speech/Components/BleatingAccentComponent.cs
deleted file mode 100644
index 4f889b1e87..0000000000
--- a/Content.Server/Speech/Components/BleatingAccentComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Makes this entity speak like a sheep or a goat in all chat messages it sends.
-///
-[RegisterComponent]
-public sealed partial class BleatingAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/BlockListeningComponent.cs b/Content.Server/Speech/Components/BlockListeningComponent.cs
deleted file mode 100644
index 3c747252ba..0000000000
--- a/Content.Server/Speech/Components/BlockListeningComponent.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Causes all ListenAttemptEvents to fail on the entity.
-///
-[RegisterComponent]
-public sealed partial class BlockListeningComponent : Component
-{
-
-}
diff --git a/Content.Server/Speech/Components/FrontalLispComponent.cs b/Content.Server/Speech/Components/FrontalLispComponent.cs
deleted file mode 100644
index 9f0ec761e3..0000000000
--- a/Content.Server/Speech/Components/FrontalLispComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class FrontalLispComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/GermanAccentComponent.cs b/Content.Server/Speech/Components/GermanAccentComponent.cs
deleted file mode 100644
index 10b7ed0234..0000000000
--- a/Content.Server/Speech/Components/GermanAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class GermanAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/LizardAccentComponent.cs b/Content.Server/Speech/Components/LizardAccentComponent.cs
deleted file mode 100644
index 6008c7248e..0000000000
--- a/Content.Server/Speech/Components/LizardAccentComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Hiss!
-///
-[RegisterComponent]
-public sealed partial class LizardAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/MobsterAccentComponent.cs b/Content.Server/Speech/Components/MobsterAccentComponent.cs
deleted file mode 100644
index db92c5fd7a..0000000000
--- a/Content.Server/Speech/Components/MobsterAccentComponent.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Nyehh, my gabagool, see?
-/// Etc etc.
-///
-[RegisterComponent]
-public sealed partial class MobsterAccentComponent : BaseAccentComponent
-{
- ///
- /// Do you make all the rules?
- ///
- [DataField("isBoss")]
- public bool IsBoss = true;
-}
diff --git a/Content.Server/Speech/Components/MonkeyAccentComponent.cs b/Content.Server/Speech/Components/MonkeyAccentComponent.cs
deleted file mode 100644
index b2ae0ed246..0000000000
--- a/Content.Server/Speech/Components/MonkeyAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class MonkeyAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/MothAccentComponent.cs b/Content.Server/Speech/Components/MothAccentComponent.cs
deleted file mode 100644
index e3a61cc4cf..0000000000
--- a/Content.Server/Speech/Components/MothAccentComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Buzzz!
-///
-[RegisterComponent]
-public sealed partial class MothAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/OwOAccentComponent.cs b/Content.Server/Speech/Components/OwOAccentComponent.cs
deleted file mode 100644
index a7c1d3c7b4..0000000000
--- a/Content.Server/Speech/Components/OwOAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class OwOAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/ReplacementAccentComponent.cs b/Content.Server/Speech/Components/ReplacementAccentComponent.cs
deleted file mode 100644
index 4a6508b241..0000000000
--- a/Content.Server/Speech/Components/ReplacementAccentComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Server.Speech.Prototypes;
-using Content.Shared.Speech.Components;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Replaces full sentences or words within sentences with new strings.
-///
-[RegisterComponent]
-public sealed partial class ReplacementAccentComponent : BaseAccentComponent
-{
- [DataField(customTypeSerializer: typeof(PrototypeIdSerializer), required: true)]
- public string Accent = default!;
-
-}
diff --git a/Content.Server/Speech/Components/RussianAccentComponent.cs b/Content.Server/Speech/Components/RussianAccentComponent.cs
deleted file mode 100644
index 2b06b3b668..0000000000
--- a/Content.Server/Speech/Components/RussianAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class RussianAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/ScrambledAccentComponent.cs b/Content.Server/Speech/Components/ScrambledAccentComponent.cs
deleted file mode 100644
index f2f75a18fd..0000000000
--- a/Content.Server/Speech/Components/ScrambledAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class ScrambledAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/SkeletonAccentComponent.cs b/Content.Server/Speech/Components/SkeletonAccentComponent.cs
deleted file mode 100644
index 50b52b57f7..0000000000
--- a/Content.Server/Speech/Components/SkeletonAccentComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Rattle me bones!
-///
-[RegisterComponent]
-public sealed partial class SkeletonAccentComponent : BaseAccentComponent
-{
- ///
- /// Chance that the message will be appended with "ACK ACK!"
- ///
- [DataField("ackChance")]
- public float ackChance = 0.3f; // Funnier if it doesn't happen every single time
-}
diff --git a/Content.Server/Speech/Components/SlowAccentComponent.cs b/Content.Server/Speech/Components/SlowAccentComponent.cs
deleted file mode 100644
index 225b727b9e..0000000000
--- a/Content.Server/Speech/Components/SlowAccentComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// Makes... the... entity... talk... like... this...
-///
-[RegisterComponent]
-public sealed partial class SlowAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/SouthernAccentComponent.cs b/Content.Server/Speech/Components/SouthernAccentComponent.cs
deleted file mode 100644
index ac44e051cb..0000000000
--- a/Content.Server/Speech/Components/SouthernAccentComponent.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Content.Server.Speech.EntitySystems;
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-[Access(typeof(SouthernAccentSystem))]
-public sealed partial class SouthernAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/SpanishAccentComponent.cs b/Content.Server/Speech/Components/SpanishAccentComponent.cs
deleted file mode 100644
index c7c769b074..0000000000
--- a/Content.Server/Speech/Components/SpanishAccentComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using Content.Shared.Speech.Components;
-
-namespace Content.Server.Speech.Components;
-
-[RegisterComponent]
-public sealed partial class SpanishAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/UnblockableSpeechComponent.cs b/Content.Server/Speech/Components/UnblockableSpeechComponent.cs
deleted file mode 100644
index 48e2b9863a..0000000000
--- a/Content.Server/Speech/Components/UnblockableSpeechComponent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Speech.Components
-{
- [RegisterComponent]
- public sealed partial class UnblockableSpeechComponent : Component
- {
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/BarkAccentSystem.cs b/Content.Server/Speech/EntitySystems/BarkAccentSystem.cs
deleted file mode 100644
index dcf8f8f08c..0000000000
--- a/Content.Server/Speech/EntitySystems/BarkAccentSystem.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-using Robust.Shared.Random;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed partial class BarkAccentSystem : RelayAccentSystem
-{
- [Dependency] private IRobustRandom _random = default!;
-
- private static readonly IReadOnlyList Barks = new List{
- " Woof!", " WOOF", " wof-wof"
- }.AsReadOnly();
-
- private static readonly IReadOnlyDictionary SpecialWords = new Dictionary()
- {
- { "ah", "arf" },
- { "Ah", "Arf" },
- { "oh", "oof" },
- { "Oh", "Oof" },
- };
-
- public override string Accentuate(string message, Entity? ent = null)
- {
- foreach (var (word, repl) in SpecialWords)
- {
- message = message.Replace(word, repl);
- }
-
- return message.Replace("!", _random.Pick(Barks))
- .Replace("l", "r").Replace("L", "R");
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs b/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs
deleted file mode 100644
index 6b09877512..0000000000
--- a/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Content.Server.Speech.Components;
-using Content.Shared.Speech;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed class BlockListeningSystem : EntitySystem
-{
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnListenAttempt);
- }
-
- private void OnListenAttempt(EntityUid uid, BlockListeningComponent component, ListenAttemptEvent args)
- {
- args.Cancel();
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs b/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
index 067dc34687..3ab654746a 100644
--- a/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
+++ b/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
@@ -1,119 +1,19 @@
using System.Text;
using Content.Server.Destructible;
-using Content.Shared.Speech.Components;
-using Content.Shared.Damage.Components;
-using Content.Shared.Damage.Systems;
using Content.Shared.FixedPoint;
-using Content.Shared.Power.EntitySystems;
-using Content.Shared.PowerCell;
+using Content.Shared.Speech.Components;
using Content.Shared.Speech.EntitySystems;
+
using Robust.Shared.Random;
namespace Content.Server.Speech.EntitySystems;
-public sealed partial class DamagedSiliconAccentSystem : RelayAccentSystem
+public sealed partial class DamagedSiliconAccentSystem : SharedDamagedSiliconAccentSystem
{
[Dependency] private IRobustRandom _random = default!;
- [Dependency] private SharedBatterySystem _battery = default!;
- [Dependency] private PowerCellSystem _powerCell = default!;
[Dependency] private DestructibleSystem _destructibleSystem = default!;
- [Dependency] private DamageableSystem _damageable = default!;
- protected override Type[]? AccentAfter => [typeof(ReplacementAccentSystem)];
- protected override Type[]? RelayAccentAfter => [typeof(ReplacementAccentSystem)];
-
- public override string Accentuate(string message, Entity? ent = null)
- {
- if (ent == null)
- return message;
-
- var uid = ent.Value.Owner;
-
- if (ent.Value.Comp.EnableChargeCorruption)
- {
- var currentChargeLevel = 0.0f;
- if (ent.Value.Comp.OverrideChargeLevel.HasValue)
- {
- currentChargeLevel = ent.Value.Comp.OverrideChargeLevel.Value;
- }
- else if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
- {
- currentChargeLevel = _battery.GetChargeLevel(battery.Value.AsNullable());
- }
- currentChargeLevel = Math.Clamp(currentChargeLevel, 0.0f, 1.0f);
- // Corrupt due to low power (drops characters on longer messages)
- message = CorruptPower(message, currentChargeLevel, ent.Value.Comp);
- }
-
- if (ent.Value.Comp.EnableDamageCorruption)
- {
- var damage = FixedPoint2.Zero;
- if (ent.Value.Comp.OverrideTotalDamage.HasValue)
- {
- damage = ent.Value.Comp.OverrideTotalDamage.Value;
- }
- else if (TryComp(uid, out var damageable))
- {
- damage = _damageable.GetTotalDamage((uid, damageable));
- }
- // Corrupt due to damage (drop, repeat, replace with symbols)
- message = CorruptDamage(message, damage, ent.Value);
- }
-
- return message;
- }
-
- public string CorruptPower(string message, float chargeLevel, DamagedSiliconAccentComponent comp)
- {
- // The first idxMin characters are SAFE
- var idxMin = comp.StartPowerCorruptionAtCharIdx;
- // Probability will max at idxMax
- var idxMax = comp.MaxPowerCorruptionAtCharIdx;
-
- // Fast bails, would not have an effect
- if (chargeLevel > comp.ChargeThresholdForPowerCorruption || message.Length < idxMin)
- {
- return message;
- }
-
- var outMsg = new StringBuilder();
-
- var maxDropProb = comp.MaxDropProbFromPower * (1.0f - chargeLevel / comp.ChargeThresholdForPowerCorruption);
-
- var idx = -1;
- foreach (var letter in message)
- {
- idx++;
- if (idx < idxMin) // Fast character, no effect
- {
- outMsg.Append(letter);
- continue;
- }
-
- // use an x^2 interpolation to increase the drop probability until we hit idxMax
- var probToDrop = idx >= idxMax
- ? maxDropProb
- : (float)Math.Pow(((double)idx - idxMin) / (idxMax - idxMin), 2.0) * maxDropProb;
- // Ensure we're in the range for Prob()
- probToDrop = Math.Clamp(probToDrop, 0.0f, 1.0f);
-
- if (_random.Prob(probToDrop)) // Lose a character
- {
- // Additional chance to change to dot for flavor instead of full drop
- if (_random.Prob(comp.ProbToCorruptDotFromPower))
- {
- outMsg.Append('.');
- }
- }
- else // Character is safe
- {
- outMsg.Append(letter);
- }
- }
- return outMsg.ToString();
- }
-
- private string CorruptDamage(string message, FixedPoint2 totalDamage, Entity ent)
+ protected override string CorruptDamage(string message, FixedPoint2 totalDamage, Entity ent)
{
var outMsg = new StringBuilder();
diff --git a/Content.Server/Speech/EntitySystems/FrontalLispSystem.cs b/Content.Server/Speech/EntitySystems/FrontalLispSystem.cs
deleted file mode 100644
index 8cf62d23e3..0000000000
--- a/Content.Server/Speech/EntitySystems/FrontalLispSystem.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed class FrontalLispSystem : RelayAccentSystem
-{
- // @formatter:off
- private static readonly Regex RegexUpperTh = new(@"[T]+[Ss]+|[S]+[Cc]+(?=[IiEeYy]+)|[C]+(?=[IiEeYy]+)|[P][Ss]+|([S]+[Tt]+|[T]+)(?=[Ii]+[Oo]+[Uu]*[Nn]*)|[C]+[Hh]+(?=[Ii]*[Ee]*)|[Z]+|[S]+|[X]+(?=[Ee]+)");
- private static readonly Regex RegexLowerTh = new(@"[t]+[s]+|[s]+[c]+(?=[iey]+)|[c]+(?=[iey]+)|[p][s]+|([s]+[t]+|[t]+)(?=[i]+[o]+[u]*[n]*)|[c]+[h]+(?=[i]*[e]*)|[z]+|[s]+|[x]+(?=[e]+)");
- private static readonly Regex RegexUpperEcks = new(@"[E]+[Xx]+[Cc]*|[X]+");
- private static readonly Regex RegexLowerEcks = new(@"[e]+[x]+[c]*|[x]+");
- // @formatter:on
-
- public override string Accentuate(string message, Entity? ent = null)
- {
- // handles ts, sc(i|e|y), c(i|e|y), ps, st(io(u|n)), ch(i|e), z, s
- message = RegexUpperTh.Replace(message, "TH");
- message = RegexLowerTh.Replace(message, "th");
- // handles ex(c), x
- message = RegexUpperEcks.Replace(message, "EKTH");
- message = RegexLowerEcks.Replace(message, "ekth");
- return message;
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/MeleeSpeechSystem.cs b/Content.Server/Speech/EntitySystems/MeleeSpeechSystem.cs
deleted file mode 100644
index e9e224dddb..0000000000
--- a/Content.Server/Speech/EntitySystems/MeleeSpeechSystem.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using Content.Server.Administration.Logs;
-using Content.Shared.Actions;
-using Content.Shared.Database;
-using Content.Shared.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-using Robust.Server.GameObjects;
-using Robust.Shared.Player;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed partial class MeleeSpeechSystem : SharedMeleeSpeechSystem
-{
- [Dependency] private IAdminLogManager _adminLogger = default!;
- [Dependency] private SharedActionsSystem _actionSystem = default!;
- [Dependency] private UserInterfaceSystem _uiSystem = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnBattlecryChanged);
- SubscribeLocalEvent(OnConfigureAction);
- SubscribeLocalEvent(OnGetActions);
- SubscribeLocalEvent(OnComponentMapInit);
- }
- private void OnComponentMapInit(EntityUid uid, MeleeSpeechComponent component, MapInitEvent args)
- {
- _actionSystem.AddAction(uid, ref component.ConfigureActionEntity, component.ConfigureAction, uid);
- }
- private void OnGetActions(EntityUid uid, MeleeSpeechComponent component, GetItemActionsEvent args)
- {
- args.AddAction(ref component.ConfigureActionEntity, component.ConfigureAction);
- }
- private void OnBattlecryChanged(EntityUid uid, MeleeSpeechComponent comp, MeleeSpeechBattlecryChangedMessage args)
- {
- if (!TryComp(uid, out var meleeSpeechUser))
- return;
- var battlecry = args.Battlecry;
- if (battlecry.Length > comp.MaxBattlecryLength)
- battlecry = battlecry[..comp.MaxBattlecryLength];
- TryChangeBattlecry(uid, battlecry, meleeSpeechUser);
- }
- ///
- /// Attempts to open the Battlecry UI.
- ///
- private void OnConfigureAction(EntityUid uid, MeleeSpeechComponent comp, MeleeSpeechConfigureActionEvent args)
- {
- TryOpenUi(args.Performer, uid, comp);
- }
- public void TryOpenUi(EntityUid user, EntityUid source, MeleeSpeechComponent? component = null)
- {
- if (!Resolve(source, ref component))
- return;
- if (!TryComp(user, out var actor))
- return;
- _uiSystem.TryToggleUi(source, MeleeSpeechUiKey.Key, actor.PlayerSession);
- }
- ///
- /// Attempts to change the battlecry of an entity.
- /// Returns true/false.
- ///
- ///
- /// Logs changes to an entity's battlecry
- ///
- public bool TryChangeBattlecry(EntityUid uid, string? battlecry, MeleeSpeechComponent? meleeSpeechComp = null)
- {
- if (!Resolve(uid, ref meleeSpeechComp))
- return false;
- if (!string.IsNullOrWhiteSpace(battlecry))
- {
- battlecry = battlecry.Trim();
- }
- else
- {
- battlecry = null;
- }
- if (meleeSpeechComp.Battlecry == battlecry)
- return true;
- meleeSpeechComp.Battlecry = battlecry;
- Dirty(uid, meleeSpeechComp);
- _adminLogger.Add(LogType.ItemConfigure, LogImpact.Medium, $" {ToPrettyString(uid):entity}'s battlecry has been changed to {battlecry}");
- return true;
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/OwOAccentSystem.cs b/Content.Server/Speech/EntitySystems/OwOAccentSystem.cs
deleted file mode 100644
index 9f37fe6372..0000000000
--- a/Content.Server/Speech/EntitySystems/OwOAccentSystem.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-using Robust.Shared.Random;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed partial class OwOAccentSystem : RelayAccentSystem
-{
- [Dependency] private IRobustRandom _random = default!;
-
- private static readonly IReadOnlyList Faces = new List{
- " (•`ω´•)", " ;;w;;", " owo", " UwU", " >w<", " ^w^"
- }.AsReadOnly();
-
- private static readonly IReadOnlyDictionary SpecialWords = new Dictionary()
- {
- { "you", "wu" },
- };
-
- public override string Accentuate(string message, Entity? ent = null)
- {
- foreach (var (word, repl) in SpecialWords)
- {
- message = message.Replace(word, repl);
- }
-
- return message.Replace("!", _random.Pick(Faces))
- .Replace("r", "w").Replace("R", "W")
- .Replace("l", "w").Replace("L", "W");
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/RatvarianLanguageSystem.cs b/Content.Server/Speech/EntitySystems/RatvarianLanguageSystem.cs
deleted file mode 100644
index 0978154b23..0000000000
--- a/Content.Server/Speech/EntitySystems/RatvarianLanguageSystem.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Shared.Speech.EntitySystems;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed partial class RatvarianLanguageSystem : SharedRatvarianLanguageSystem
-{
- public override void DoRatvarian(EntityUid uid, TimeSpan time, bool refresh)
- {
- if (refresh)
- Status.TryUpdateStatusEffectDuration(uid, Ratvarian, time);
- else
- Status.TryAddStatusEffectDuration(uid, Ratvarian, time);
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/SpeakOnActionSystem.cs b/Content.Server/Speech/EntitySystems/SpeakOnActionSystem.cs
deleted file mode 100644
index de4a814345..0000000000
--- a/Content.Server/Speech/EntitySystems/SpeakOnActionSystem.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Content.Server.Chat.Systems;
-using Content.Shared.Actions.Events;
-using Content.Shared.Chat;
-using Content.Shared.Speech;
-using Content.Shared.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-using Content.Shared.Speech.Muting;
-
-namespace Content.Server.Speech.EntitySystems;
-
-///
-/// As soon as the chat refactor moves to Shared
-/// the logic here can move to the shared
-///
-public sealed partial class SpeakOnActionSystem : SharedSpeakOnActionSystem
-{
- [Dependency] private ChatSystem _chat = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnActionPerformed);
- }
-
- private void OnActionPerformed(Entity ent, ref ActionPerformedEvent args)
- {
- var user = args.Performer;
-
- // If we can't speak, we can't speak
- if (!HasComp(user) || HasComp(user))
- return;
-
- if (string.IsNullOrWhiteSpace(ent.Comp.Sentence))
- return;
-
- _chat.TrySendInGameICMessage(user, Loc.GetString(ent.Comp.Sentence), InGameICChatType.Speak, false);
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/StutteringSystem.cs b/Content.Server/Speech/EntitySystems/StutteringSystem.cs
deleted file mode 100644
index 11fc1438d0..0000000000
--- a/Content.Server/Speech/EntitySystems/StutteringSystem.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System.Text;
-using System.Text.RegularExpressions;
-using Content.Shared.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-using Robust.Shared.Random;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed partial class StutteringSystem : SharedStutteringSystem
-{
- [Dependency] private IRobustRandom _random = default!;
-
- // Regex of characters to stutter.
- private static readonly Regex Stutter = new(@"[b-df-hj-np-tv-wxyz]",
- RegexOptions.Compiled | RegexOptions.IgnoreCase);
-
- public override void DoStutter(EntityUid uid, TimeSpan time, bool refresh)
- {
- if (refresh)
- Status.TryUpdateStatusEffectDuration(uid, Stuttering, time);
- else
- Status.TryAddStatusEffectDuration(uid, Stuttering, time);
- }
-
- public override void DoRemoveStutterTime(EntityUid uid, TimeSpan timeRemoved)
- {
- Status.TryAddTime(uid, Stuttering, -timeRemoved);
- }
-
- public override void DoRemoveStutter(EntityUid uid)
- {
- Status.TryRemoveStatusEffect(uid, Stuttering);
- }
-
- public override string Accentuate(string message, Entity? ent = null)
- {
- if (ent == null)
- return message;
-
- var length = message.Length;
-
- var finalMessage = new StringBuilder();
-
- string newLetter;
-
- for (var i = 0; i < length; i++)
- {
- newLetter = message[i].ToString();
- if (Stutter.IsMatch(newLetter) && _random.Prob(ent.Value.Comp.MatchRandomProb))
- {
- if (_random.Prob(ent.Value.Comp.FourRandomProb))
- {
- newLetter = $"{newLetter}-{newLetter}-{newLetter}-{newLetter}";
- }
- else if (_random.Prob(ent.Value.Comp.ThreeRandomProb))
- {
- newLetter = $"{newLetter}-{newLetter}-{newLetter}";
- }
- else if (_random.Prob(ent.Value.Comp.CutRandomProb))
- {
- newLetter = "";
- }
- else
- {
- newLetter = $"{newLetter}-{newLetter}";
- }
- }
-
- finalMessage.Append(newLetter);
- }
-
- return finalMessage.ToString();
- }
-}
-
diff --git a/Content.Server/Speech/EntitySystems/UnblockableSpeechSystem.cs b/Content.Server/Speech/EntitySystems/UnblockableSpeechSystem.cs
deleted file mode 100644
index 6d5f165d6c..0000000000
--- a/Content.Server/Speech/EntitySystems/UnblockableSpeechSystem.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Content.Server.Speech.Components;
-using Content.Shared.Chat;
-
-namespace Content.Server.Speech.EntitySystems
-{
- public sealed class UnblockableSpeechSystem : EntitySystem
- {
- public override void Initialize()
- {
- SubscribeLocalEvent(OnCheck);
- }
-
- private void OnCheck(EntityUid uid, UnblockableSpeechComponent component, CheckIgnoreSpeechBlockerEvent args)
- {
- args.IgnoreBlocker = true;
- }
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/VocalSystem.cs b/Content.Server/Speech/EntitySystems/VocalSystem.cs
deleted file mode 100644
index 7bd561b4f8..0000000000
--- a/Content.Server/Speech/EntitySystems/VocalSystem.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using Content.Server.Actions;
-using Content.Server.Chat.Systems;
-using Content.Shared.Chat;
-using Content.Shared.Chat.Prototypes;
-using Content.Shared.Humanoid;
-using Content.Shared.Speech;
-using Content.Shared.Speech.Components;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Random;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed partial class VocalSystem : EntitySystem
-{
- [Dependency] private IRobustRandom _random = default!;
- [Dependency] private SharedAudioSystem _audio = default!;
- [Dependency] private ChatSystem _chat = default!;
- [Dependency] private ActionsSystem _actions = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnMapInit);
- SubscribeLocalEvent(OnShutdown);
- SubscribeLocalEvent(OnVoiceChanged);
- SubscribeLocalEvent(OnEmote);
- SubscribeLocalEvent(OnEmoteAction);
- }
-
- ///
- /// Copy this component's datafields from one entity to another.
- /// This can't use CopyComp because of the ScreamActionEntity DataField, which should not be copied.
- ///
- public void CopyComponent(Entity source, EntityUid target)
- {
- if (!Resolve(source, ref source.Comp))
- return;
-
- var targetComp = EnsureComp(target);
- targetComp.ScreamId = source.Comp.ScreamId;
- targetComp.Wilhelm = source.Comp.Wilhelm;
- targetComp.WilhelmProbability = source.Comp.WilhelmProbability;
- LoadSounds(target, targetComp);
-
- Dirty(target, targetComp);
- }
-
- private void OnMapInit(EntityUid uid, VocalComponent component, MapInitEvent args)
- {
- // try to add scream action when vocal comp added
- _actions.AddAction(uid, ref component.EmoteActionEntity, component.EmoteAction);
- }
-
- private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args)
- {
- // remove scream action when component removed
- if (component.EmoteActionEntity != null)
- {
- _actions.RemoveAction(uid, component.EmoteActionEntity);
- }
- }
-
- private void OnVoiceChanged(EntityUid uid, VocalComponent component, VoiceChangedEvent args)
- {
- LoadSounds(uid, component, args.NewVoice);
- }
-
- private void OnEmote(EntityUid uid, VocalComponent component, ref EmoteEvent args)
- {
- if (args.Handled || !args.Emote.Category.HasFlag(EmoteCategory.Vocal))
- return;
-
- // snowflake case for wilhelm scream easter egg
- if (args.Emote == component.ScreamId)
- {
- args.Handled = TryPlayScreamSound(uid, component);
- return;
- }
-
- if (component.EmoteSounds is not { } sounds)
- return;
-
- // just play regular sound based on emote proto
- args.Handled = _chat.TryPlayEmoteSound(uid, ProtoMan.Index(sounds), args.Emote);
- }
-
- private void OnEmoteAction(EntityUid uid, VocalComponent component, EmoteActionEvent args)
- {
- if (args.Handled)
- return;
-
- _chat.TryEmoteWithChat(uid, args.Emote);
- args.Handled = true;
- }
-
- private bool TryPlayScreamSound(EntityUid uid, VocalComponent component)
- {
- if (_random.Prob(component.WilhelmProbability))
- {
- _audio.PlayPvs(component.Wilhelm, uid, component.Wilhelm.Params);
- return true;
- }
-
- if (component.EmoteSounds is not { } sounds)
- return false;
-
- return _chat.TryPlayEmoteSound(uid, ProtoMan.Index(sounds), component.ScreamId);
- }
-
- ///
- /// This only works on Humanoids. Mobs should have emoteSounds on set directly instead.
- ///
- private void LoadSounds(EntityUid uid, VocalComponent component, ProtoId? protoId = null)
- {
- if (!TryComp(uid, out var humanoid))
- return;
-
- protoId ??= humanoid.Voice;
-
- if (!ProtoMan.HasIndex(protoId))
- return;
-
- component.EmoteSounds = protoId;
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs b/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs
deleted file mode 100644
index daaad099d6..0000000000
--- a/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Content.Shared.Chat;
-using Content.Server.Speech.Components;
-
-namespace Content.Server.Speech.EntitySystems;
-
-public sealed partial class VoiceOverrideSystem : EntitySystem
-{
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnTransformSpeakerName);
- }
-
- private void OnTransformSpeakerName(Entity entity, ref TransformSpeakerNameEvent args)
- {
- if (!entity.Comp.Enabled)
- return;
-
- args.VoiceName = entity.Comp.NameOverride ?? args.VoiceName;
- args.SpeechVerb = entity.Comp.SpeechVerbOverride ?? args.SpeechVerb;
- }
-}
diff --git a/Content.Server/Speech/Components/ListenWireAction.cs b/Content.Server/Speech/ListenWireAction.cs
similarity index 98%
rename from Content.Server/Speech/Components/ListenWireAction.cs
rename to Content.Server/Speech/ListenWireAction.cs
index f9f1d9e92e..f8f6045523 100644
--- a/Content.Server/Speech/Components/ListenWireAction.cs
+++ b/Content.Server/Speech/ListenWireAction.cs
@@ -1,10 +1,10 @@
using Content.Server.Chat.Systems;
-using Content.Shared.Radio.Components;
using Content.Server.Radio.EntitySystems;
-using Content.Server.Speech.Components;
using Content.Server.Wires;
using Content.Shared.Radio;
+using Content.Shared.Radio.Components;
using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
using Content.Shared.Wires;
using Robust.Shared.Prototypes;
diff --git a/Content.Server/Speech/Muting/MutingSystem.cs b/Content.Server/Speech/Muting/MutingSystem.cs
index 5ccd30bd76..2dd1e00906 100644
--- a/Content.Server/Speech/Muting/MutingSystem.cs
+++ b/Content.Server/Speech/Muting/MutingSystem.cs
@@ -1,5 +1,5 @@
using Content.Server.Popups;
-using Content.Server.Speech.EntitySystems;
+using Content.Shared.Speech.EntitySystems;
using Content.Shared.Abilities.Mime;
using Content.Shared.Chat;
using Content.Shared.Chat.Prototypes;
diff --git a/Content.Server/Speech/SpeechNoiseSystem.cs b/Content.Server/Speech/SpeechNoiseSystem.cs
deleted file mode 100644
index 79d4cadc19..0000000000
--- a/Content.Server/Speech/SpeechNoiseSystem.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using Content.Shared.Chat;
-using Content.Shared.Speech;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Speech
-{
- public sealed partial class SpeechSoundSystem : EntitySystem
- {
- [Dependency] private IGameTiming _gameTiming = default!;
- [Dependency] private IRobustRandom _random = default!;
- [Dependency] private SharedAudioSystem _audio = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnEntitySpoke);
- }
-
- public SoundSpecifier? GetSpeechSound(Entity ent, string message)
- {
- if (ent.Comp.SpeechSounds == null)
- return null;
-
- // Play speech sound
- SoundSpecifier? contextSound;
- var prototype = ProtoMan.Index(ent.Comp.SpeechSounds);
-
- // Different sounds for ask/exclaim based on last character
- contextSound = message[^1] switch
- {
- '?' => prototype.AskSound,
- '!' => prototype.ExclaimSound,
- _ => prototype.SaySound
- };
-
- // Use exclaim sound if most characters are uppercase.
- int uppercaseCount = 0;
- for (int i = 0; i < message.Length; i++)
- {
- if (char.IsUpper(message[i]))
- uppercaseCount++;
- }
- if (uppercaseCount > (message.Length / 2))
- {
- contextSound = prototype.ExclaimSound;
- }
-
- var scale = (float) _random.NextGaussian(1, prototype.Variation);
- contextSound.Params = ent.Comp.AudioParams.WithPitchScale(scale);
- return contextSound;
- }
-
- private void OnEntitySpoke(EntityUid uid, SpeechComponent component, EntitySpokeEvent args)
- {
- if (component.SpeechSounds == null)
- return;
-
- var currentTime = _gameTiming.CurTime;
- var cooldown = TimeSpan.FromSeconds(component.SoundCooldownTime);
-
- // Ensure more than the cooldown time has passed since last speaking
- if (currentTime - component.LastTimeSoundPlayed < cooldown)
- return;
-
- var sound = GetSpeechSound((uid, component), args.Message);
- component.LastTimeSoundPlayed = currentTime;
- _audio.PlayPvs(sound, uid);
- }
- }
-}
diff --git a/Content.Server/Speech/Components/SpeechWireAction.cs b/Content.Server/Speech/SpeechWireAction.cs
similarity index 100%
rename from Content.Server/Speech/Components/SpeechWireAction.cs
rename to Content.Server/Speech/SpeechWireAction.cs
diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs
index 4ad44ec9ac..97e454a45d 100644
--- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs
+++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs
@@ -1,8 +1,8 @@
using Content.Server.Chat.Systems;
-using Content.Server.Speech;
using Content.Server.SurveillanceCamera.Systems;
-using Content.Shared.Speech;
using Content.Shared.Chat;
+using Content.Shared.Speech;
+using Content.Shared.Speech.EntitySystems;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
diff --git a/Content.Server/Telephone/TelephoneSystem.cs b/Content.Server/Telephone/TelephoneSystem.cs
index 1c64d6c40d..97ca38212e 100644
--- a/Content.Server/Telephone/TelephoneSystem.cs
+++ b/Content.Server/Telephone/TelephoneSystem.cs
@@ -433,6 +433,7 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem
{
var activeListener = AddComp(entity);
activeListener.Range = entity.Comp.ListeningRange;
+ Dirty(entity, activeListener);
}
if (!microphoneOn && HasComp(entity))
diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.cs b/Content.Server/VoiceMask/VoiceMaskSystem.cs
index 25da4b516d..48c2a88aa3 100644
--- a/Content.Server/VoiceMask/VoiceMaskSystem.cs
+++ b/Content.Server/VoiceMask/VoiceMaskSystem.cs
@@ -1,4 +1,3 @@
-using Content.Server.Speech;
using Content.Shared.Actions;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
@@ -12,6 +11,7 @@ using Content.Shared.Inventory;
using Content.Shared.Lock;
using Content.Shared.Popups;
using Content.Shared.Speech;
+using Content.Shared.Speech.EntitySystems;
using Content.Shared.VoiceMask;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs
index 03cd0267c8..b668927f86 100644
--- a/Content.Server/Zombies/ZombieSystem.Transform.cs
+++ b/Content.Server/Zombies/ZombieSystem.Transform.cs
@@ -5,46 +5,43 @@ using Content.Server.Body.Components;
using Content.Server.Chat;
using Content.Server.Chat.Managers;
using Content.Server.Ghost;
-using Content.Server.Ghost.Roles.Components;
-using Content.Server.Humanoid;
using Content.Server.Inventory;
using Content.Server.Mind;
using Content.Server.NPC;
using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems;
using Content.Server.StationEvents.Components;
-using Content.Server.Speech.Components;
using Content.Shared.Body;
using Content.Shared.Body.Components;
using Content.Shared.CombatMode;
using Content.Shared.CombatMode.Pacification;
+using Content.Shared.Ghost.Roles.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
+using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.NameModifier.EntitySystems;
+using Content.Shared.NPC.Prototypes;
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.AnimalHusbandry;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
+using Content.Shared.Prying.Components;
+using Content.Shared.Roles;
+using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Tag;
+using Content.Shared.Temperature.Components;
+using Content.Shared.Traits.Assorted;
using Content.Shared.Weapons.Melee;
using Content.Shared.Zombies;
-using Content.Shared.Prying.Components;
-using Content.Shared.Traits.Assorted;
using Robust.Shared.Audio.Systems;
-using Content.Shared.Ghost.Roles.Components;
-using Content.Shared.Humanoid.Markings;
-using Content.Shared.IdentityManagement;
-using Content.Shared.Tag;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
-using Content.Shared.NPC.Prototypes;
-using Content.Shared.Roles;
-using Content.Shared.Temperature.Components;
using Robust.Shared.Utility;
namespace Content.Server.Zombies;
@@ -70,6 +67,7 @@ public sealed partial class ZombieSystem
[Dependency] private MindSystem _mind = default!;
[Dependency] private MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private NameModifierSystem _nameMod = default!;
+ [Dependency] private ReplacementAccentSystem _replacementAccent = default!;
[Dependency] private NPCSystem _npc = default!;
[Dependency] private TagSystem _tag = default!;
[Dependency] private ISharedPlayerManager _player = default!;
@@ -152,7 +150,7 @@ public sealed partial class ZombieSystem
if (TryComp(target, out var accent))
accentType = accent.Accent;
- EnsureComp(target).Accent = accentType;
+ _replacementAccent.ApplyAccent(target, accentType);
//This is needed for stupid entities that fuck up combat mode component
//in an attempt to make an entity not attack. This is the easiest way to do it.
diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs
index 6c890536d0..1d1be3c0ba 100644
--- a/Content.Server/Zombies/ZombieSystem.cs
+++ b/Content.Server/Zombies/ZombieSystem.cs
@@ -4,7 +4,7 @@ using Content.Server.Body.Systems;
using Content.Server.Chat;
using Content.Server.Chat.Systems;
using Content.Server.Emoting.Systems;
-using Content.Server.Speech.EntitySystems;
+using Content.Shared.Speech.EntitySystems;
using Content.Shared.Anomaly.Components;
using Content.Shared.Armor;
using Content.Shared.Bed.Sleep;
diff --git a/Content.Shared/Chat/EmotesEvents.cs b/Content.Shared/Chat/EmotesEvents.cs
index 196bbb43bd..74eedca31a 100644
--- a/Content.Shared/Chat/EmotesEvents.cs
+++ b/Content.Shared/Chat/EmotesEvents.cs
@@ -1,5 +1,6 @@
using Content.Shared.Chat.Prototypes;
using Content.Shared.Inventory;
+using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@@ -28,8 +29,13 @@ public sealed class BeforeEmoteEvent(EntityUid source, EmotePrototype emote)
/// Use it to play sound, change sprite or something else.
///
[ByRefEvent]
-public record struct EmoteEvent(EmotePrototype Emote)
+public record struct EmoteEvent(NetEntity Source, EmotePrototype Emote)
{
+ ///
+ /// The entity that performed the emote.
+ ///
+ public NetEntity Source = Source;
+
///
/// The used emote.
///
diff --git a/Content.Shared/Chat/SharedChatSystem.Emote.cs b/Content.Shared/Chat/SharedChatSystem.Emote.cs
index 8b4b8a1d91..e9b74dd49f 100644
--- a/Content.Shared/Chat/SharedChatSystem.Emote.cs
+++ b/Content.Shared/Chat/SharedChatSystem.Emote.cs
@@ -156,7 +156,7 @@ public abstract partial class SharedChatSystem
// optional override params > general params for all sounds in set > individual sound params
var param = audioParams ?? proto.GeneralParams ?? sound.Params;
- _audio.PlayPvs(sound, uid, param);
+ _audio.PlayPredicted(sound, uid, uid, param);
return true;
}
///
@@ -222,10 +222,6 @@ public abstract partial class SharedChatSystem
if (beforeEv.Cancelled)
{
- // Chat is not predicted anyways, so no need to predict this popup either.
- if (_net.IsClient)
- return false;
-
if (beforeEv.Blocker != null)
{
_popup.PopupEntity(
@@ -251,7 +247,7 @@ public abstract partial class SharedChatSystem
return false;
}
- var ev = new EmoteEvent(proto);
+ var ev = new EmoteEvent(GetNetEntity(uid), proto);
RaiseLocalEvent(uid, ref ev);
return true;
diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs
index a4df6ff26d..37799f6bc0 100644
--- a/Content.Shared/Chat/SharedChatSystem.cs
+++ b/Content.Shared/Chat/SharedChatSystem.cs
@@ -49,7 +49,6 @@ public abstract partial class SharedChatSystem : EntitySystem
[Dependency] private ActionBlockerSystem _actionBlocker = default!;
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private IRobustRandom _random = default!;
- [Dependency] private INetManager _net = default!;
///
/// Cache of the keycodes for faster lookup.
diff --git a/Content.Shared/Speech/AccentEvent.cs b/Content.Shared/Speech/AccentEvent.cs
index 30d384cbb2..4404404246 100644
--- a/Content.Shared/Speech/AccentEvent.cs
+++ b/Content.Shared/Speech/AccentEvent.cs
@@ -1,11 +1,11 @@
-using Content.Shared.Inventory;
+using Content.Shared.Inventory;
namespace Content.Shared.Speech;
///
-/// Raised on an entity to apply speech accents to its message.
-/// Handlers should modify in place.
-/// Relayed through inventory (e.g. voice masks) and status effects.
+/// Raised on an entity to apply speech accents to its message.
+/// Handlers should modify in place.
+/// Relayed through inventory (e.g. voice masks) and status effects.
///
[ByRefEvent]
public record struct AccentGetEvent(EntityUid Entity, string Message) : IInventoryRelayEvent
diff --git a/Content.Shared/Speech/Components/ActiveListenerComponent.cs b/Content.Shared/Speech/Components/ActiveListenerComponent.cs
index fde108a817..921c6ce499 100644
--- a/Content.Shared/Speech/Components/ActiveListenerComponent.cs
+++ b/Content.Shared/Speech/Components/ActiveListenerComponent.cs
@@ -6,12 +6,12 @@ namespace Content.Shared.Speech.Components;
///
/// This component is used to relay speech events to other systems.
///
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ActiveListenerComponent : Component
{
///
/// The range in which to listen to speech.
///
- [DataField]
+ [DataField, AutoNetworkedField]
public float Range = SharedChatSystem.VoiceRange;
}
diff --git a/Content.Shared/Speech/Components/AllCapsAccentComponent.cs b/Content.Shared/Speech/Components/AllCapsAccentComponent.cs
index 7b0c37b538..88ad1e8286 100644
--- a/Content.Shared/Speech/Components/AllCapsAccentComponent.cs
+++ b/Content.Shared/Speech/Components/AllCapsAccentComponent.cs
@@ -1,7 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
namespace Content.Shared.Speech.Components;
///
/// Marks a speech status effect that transforms spoken text to uppercase.
///
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(AllCapsAccentSystem))]
public sealed partial class AllCapsAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/BackwardsAccentComponent.cs b/Content.Shared/Speech/Components/BackwardsAccentComponent.cs
new file mode 100644
index 0000000000..8d577ccd39
--- /dev/null
+++ b/Content.Shared/Speech/Components/BackwardsAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// sdrawkcaB
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(BackwardsAccentSystem))]
+public sealed partial class BackwardsAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/BarkAccentComponent.cs b/Content.Shared/Speech/Components/BarkAccentComponent.cs
new file mode 100644
index 0000000000..8917f73464
--- /dev/null
+++ b/Content.Shared/Speech/Components/BarkAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Arf! Arso makes "L" into "R"!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(BarkAccentSystem))]
+public sealed partial class BarkAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/BleatingAccentComponent.cs b/Content.Shared/Speech/Components/BleatingAccentComponent.cs
new file mode 100644
index 0000000000..7891dacfb7
--- /dev/null
+++ b/Content.Shared/Speech/Components/BleatingAccentComponent.cs
@@ -0,0 +1,12 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Makes this entity speak like a sheep or a goat in all chat messages it sends.
+/// Repeats the vowel in certain consonant-vowel pairs so you taaaalk liiiike thiiiis.
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(BleatingAccentSystem))]
+public sealed partial class BleatingAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/BlockListeningComponent.cs b/Content.Shared/Speech/Components/BlockListeningComponent.cs
new file mode 100644
index 0000000000..472d03e16e
--- /dev/null
+++ b/Content.Shared/Speech/Components/BlockListeningComponent.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Causes all ListenAttemptEvents to fail on the entity.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class BlockListeningComponent : Component;
diff --git a/Content.Shared/Speech/Components/DamagedSiliconAccentComponent.cs b/Content.Shared/Speech/Components/DamagedSiliconAccentComponent.cs
index 919347d080..d1965b1bd9 100644
--- a/Content.Shared/Speech/Components/DamagedSiliconAccentComponent.cs
+++ b/Content.Shared/Speech/Components/DamagedSiliconAccentComponent.cs
@@ -1,79 +1,83 @@
-using Content.Shared.FixedPoint;
+using Content.Shared.FixedPoint;
+using Content.Shared.Speech.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Shared.Speech.Components;
+///
+/// Applies damage corruption effects to the speech.
+///
[RegisterComponent, NetworkedComponent]
public sealed partial class DamagedSiliconAccentComponent : BaseAccentComponent
{
///
- /// Enable damage corruption effects
+ /// Enable damage corruption effects
///
[DataField]
public bool EnableDamageCorruption = true;
///
- /// Override total damage for damage corruption effects
+ /// Override total damage for damage corruption effects
///
[DataField]
public FixedPoint2? OverrideTotalDamage;
///
- /// The probability that a character will be corrupted when total damage at or above .
+ /// The probability that a character will be corrupted when total damage at or above .
///
[DataField]
public float MaxDamageCorruption = 0.5f;
///
- /// Probability of character corruption will increase linearly to once until
- /// total damage is at or above this value. If null, it will use the value returned by
- /// DestructibleSystem.DestroyedAt, which is the damage threshold for destruction or breakage.
+ /// Probability of character corruption will increase linearly to once until
+ /// total damage is at or above this value. If null, it will use the value returned by
+ /// DestructibleSystem.DestroyedAt, which is the damage threshold for destruction or breakage.
///
[DataField]
public FixedPoint2? DamageAtMaxCorruption;
///
- /// Enable charge level corruption effects
+ /// Enable charge level corruption effects
///
[DataField]
public bool EnableChargeCorruption = true;
///
- /// Override charge level for charge level corruption effects
+ /// Override charge level for charge level corruption effects
///
[DataField]
public float? OverrideChargeLevel;
///
- /// If the power cell charge is below this value (as a fraction of maximum charge),
- /// power corruption will begin to be applied.
+ /// If the power cell charge is below this value (as a fraction of maximum charge),
+ /// power corruption will begin to be applied.
///
[DataField]
public float ChargeThresholdForPowerCorruption = 0.15f;
///
- /// Regardless of charge level, this is how many characters at the start of a message will be 100% safe
- /// from being dropped.
+ /// Regardless of charge level, this is how many characters at the start of a message will be 100% safe
+ /// from being dropped.
///
[DataField]
public int StartPowerCorruptionAtCharIdx = 8;
///
- /// The probability that a character will be dropped due to charge level will be maximum for characters past
- /// this index. This has the effect of longer messages dropping more characters later in the message.
+ /// The probability that a character will be dropped due to charge level will be maximum for characters past
+ /// this index. This has the effect of longer messages dropping more characters later in the message.
///
[DataField]
public int MaxPowerCorruptionAtCharIdx = 40;
///
- /// The maximum probability that a character will be dropped due to charge level.
+ /// The maximum probability that a character will be dropped due to charge level.
///
[DataField]
public float MaxDropProbFromPower = 0.5f;
///
- /// If a character is "dropped", this is the probability that the character will be turned into a period instead
- /// of completely deleting the character.
+ /// If a character is "dropped", this is the probability that the character will be turned into a period instead
+ /// of completely deleting the character.
///
[DataField]
public float ProbToCorruptDotFromPower = 0.6f;
diff --git a/Content.Server/Speech/Components/FrenchAccentComponent.cs b/Content.Shared/Speech/Components/FrenchAccentComponent.cs
similarity index 60%
rename from Content.Server/Speech/Components/FrenchAccentComponent.cs
rename to Content.Shared/Speech/Components/FrenchAccentComponent.cs
index 29743f2787..2bbbde42b7 100644
--- a/Content.Server/Speech/Components/FrenchAccentComponent.cs
+++ b/Content.Shared/Speech/Components/FrenchAccentComponent.cs
@@ -1,11 +1,11 @@
-using Content.Server.Speech.EntitySystems;
-using Content.Shared.Speech.Components;
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
-namespace Content.Server.Speech.Components;
+namespace Content.Shared.Speech.Components;
///
/// French accent replaces spoken letters. "th" becomes "z" and "H" at the start of a word becomes "'".
///
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent]
[Access(typeof(FrenchAccentSystem))]
public sealed partial class FrenchAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/FrontalLispComponent.cs b/Content.Shared/Speech/Components/FrontalLispComponent.cs
new file mode 100644
index 0000000000..a5082c0f5d
--- /dev/null
+++ b/Content.Shared/Speech/Components/FrontalLispComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Lithping!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(FrontalLispSystem))]
+public sealed partial class FrontalLispComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/GermanAccentComponent.cs b/Content.Shared/Speech/Components/GermanAccentComponent.cs
new file mode 100644
index 0000000000..e7c4bb6768
--- /dev/null
+++ b/Content.Shared/Speech/Components/GermanAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Das accent, ja?
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(GermanAccentSystem))]
+public sealed partial class GermanAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/LizardAccentComponent.cs b/Content.Shared/Speech/Components/LizardAccentComponent.cs
new file mode 100644
index 0000000000..2d06c1875b
--- /dev/null
+++ b/Content.Shared/Speech/Components/LizardAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Hiss!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(LizardAccentSystem))]
+public sealed partial class LizardAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/MeleeSpeechComponent.cs b/Content.Shared/Speech/Components/MeleeSpeechComponent.cs
index e9bea22e2d..f93d928832 100644
--- a/Content.Shared/Speech/Components/MeleeSpeechComponent.cs
+++ b/Content.Shared/Speech/Components/MeleeSpeechComponent.cs
@@ -2,19 +2,15 @@ using Content.Shared.Actions;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Speech.Components;
-[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentState]
-
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MeleeSpeechComponent : Component
{
///
/// The battlecry to be said when an entity attacks with this component
///
- [ViewVariables(VVAccess.ReadWrite)]
[DataField("Battlecry")]
[AutoNetworkedField]
public string? Battlecry;
@@ -22,21 +18,22 @@ public sealed partial class MeleeSpeechComponent : Component
///
/// The maximum amount of characters allowed in a battlecry
///
- [ViewVariables(VVAccess.ReadWrite)]
[DataField("MaxBattlecryLength")]
[AutoNetworkedField]
public int MaxBattlecryLength = 12;
- [DataField] public EntProtoId ConfigureAction = "ActionConfigureMeleeSpeech";
+ [DataField]
+ public EntProtoId ConfigureAction = "ActionConfigureMeleeSpeech";
///
/// The action to open the battlecry UI
///
- [DataField("configureActionEntity")] public EntityUid? ConfigureActionEntity;
+ [DataField]
+ public EntityUid? ConfigureActionEntity;
}
///
-/// Key representing which is currently open.
+/// Key representing which is currently open.
/// Useful when there are multiple UI for an object. Here it's future-proofing only.
///
[Serializable, NetSerializable]
@@ -49,23 +46,15 @@ public enum MeleeSpeechUiKey : byte
/// Represents an state that can be sent to the client
///
[Serializable, NetSerializable]
-public sealed class MeleeSpeechBoundUserInterfaceState : BoundUserInterfaceState
+public sealed class MeleeSpeechBoundUserInterfaceState(string currentBattlecry) : BoundUserInterfaceState
{
- public string CurrentBattlecry { get; }
- public MeleeSpeechBoundUserInterfaceState(string currentBattlecry)
- {
- CurrentBattlecry = currentBattlecry;
- }
+ public string CurrentBattlecry { get; } = currentBattlecry;
}
[Serializable, NetSerializable]
-public sealed class MeleeSpeechBattlecryChangedMessage : BoundUserInterfaceMessage
+public sealed class MeleeSpeechBattlecryChangedMessage(string battlecry) : BoundUserInterfaceMessage
{
- public string Battlecry { get; }
- public MeleeSpeechBattlecryChangedMessage(string battlecry)
- {
- Battlecry = battlecry;
- }
+ public string Battlecry { get; } = battlecry;
}
-public sealed partial class MeleeSpeechConfigureActionEvent : InstantActionEvent { }
+public sealed partial class MeleeSpeechConfigureActionEvent : InstantActionEvent;
diff --git a/Content.Shared/Speech/Components/MobsterAccentComponent.cs b/Content.Shared/Speech/Components/MobsterAccentComponent.cs
new file mode 100644
index 0000000000..ca1b62b61c
--- /dev/null
+++ b/Content.Shared/Speech/Components/MobsterAccentComponent.cs
@@ -0,0 +1,18 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Nyehh, my gabagool, see?
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(MobsterAccentSystem))]
+public sealed partial class MobsterAccentComponent : BaseAccentComponent
+{
+ ///
+ /// Do you make all the rules?
+ ///
+ [DataField]
+ public bool IsBoss = true;
+}
diff --git a/Content.Shared/Speech/Components/MonkeyAccentComponent.cs b/Content.Shared/Speech/Components/MonkeyAccentComponent.cs
new file mode 100644
index 0000000000..e8e73c4112
--- /dev/null
+++ b/Content.Shared/Speech/Components/MonkeyAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// OOH AAH!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(MonkeyAccentSystem))]
+public sealed partial class MonkeyAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/MothAccentComponent.cs b/Content.Shared/Speech/Components/MothAccentComponent.cs
new file mode 100644
index 0000000000..fd3d3317b3
--- /dev/null
+++ b/Content.Shared/Speech/Components/MothAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Buzzz!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(MothAccentSystem))]
+public sealed partial class MothAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/MumbleAccentComponent.cs b/Content.Shared/Speech/Components/MumbleAccentComponent.cs
similarity index 63%
rename from Content.Server/Speech/Components/MumbleAccentComponent.cs
rename to Content.Shared/Speech/Components/MumbleAccentComponent.cs
index 0d809f64ac..a3f69f407b 100644
--- a/Content.Server/Speech/Components/MumbleAccentComponent.cs
+++ b/Content.Shared/Speech/Components/MumbleAccentComponent.cs
@@ -1,9 +1,14 @@
-using Content.Shared.Speech.Components;
using Robust.Shared.Audio;
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
-namespace Content.Server.Speech.Components;
+namespace Content.Shared.Speech.Components;
-[RegisterComponent]
+///
+/// Hmmfff!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(MumbleAccentSystem))]
public sealed partial class MumbleAccentComponent : BaseAccentComponent
{
///
diff --git a/Content.Shared/Speech/Components/OwOAccentComponent.cs b/Content.Shared/Speech/Components/OwOAccentComponent.cs
new file mode 100644
index 0000000000..f888113211
--- /dev/null
+++ b/Content.Shared/Speech/Components/OwOAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// I am not typing out an example for this one.
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(OwOAccentSystem))]
+public sealed partial class OwOAccentComponent : BaseAccentComponent;
diff --git a/Content.Server/Speech/Components/ParrotAccentComponent.cs b/Content.Shared/Speech/Components/ParrotAccentComponent.cs
similarity index 84%
rename from Content.Server/Speech/Components/ParrotAccentComponent.cs
rename to Content.Shared/Speech/Components/ParrotAccentComponent.cs
index 2e10a505c6..980996c672 100644
--- a/Content.Server/Speech/Components/ParrotAccentComponent.cs
+++ b/Content.Shared/Speech/Components/ParrotAccentComponent.cs
@@ -1,11 +1,13 @@
-using Content.Shared.Speech.Components;
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
-namespace Content.Server.Speech.Components;
+namespace Content.Shared.Speech.Components;
///
-/// Makes this entity speak like a parrot in all chat messages it sends.
+/// Makes this entity speak like a parrot in all chat messages it sends. SQUAWK!
///
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(ParrotAccentSystem))]
public sealed partial class ParrotAccentComponent : BaseAccentComponent
{
///
@@ -35,7 +37,8 @@ public sealed partial class ParrotAccentComponent : BaseAccentComponent
///
/// Strings to use as squawking noises.
///
- public readonly string[] Squawks = [
+ public readonly string[] Squawks =
+ [
"accent-parrot-squawk-1",
"accent-parrot-squawk-2",
"accent-parrot-squawk-3",
@@ -45,5 +48,4 @@ public sealed partial class ParrotAccentComponent : BaseAccentComponent
"accent-parrot-squawk-7",
"accent-parrot-squawk-8"
];
-
}
diff --git a/Content.Server/Speech/Components/PirateAccentComponent.cs b/Content.Shared/Speech/Components/PirateAccentComponent.cs
similarity index 61%
rename from Content.Server/Speech/Components/PirateAccentComponent.cs
rename to Content.Shared/Speech/Components/PirateAccentComponent.cs
index 34afabb8ee..eb34e681d1 100644
--- a/Content.Server/Speech/Components/PirateAccentComponent.cs
+++ b/Content.Shared/Speech/Components/PirateAccentComponent.cs
@@ -1,14 +1,16 @@
-using Content.Server.Speech.EntitySystems;
-using Content.Shared.Speech.Components;
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
-namespace Content.Server.Speech.Components;
+namespace Content.Shared.Speech.Components;
-[RegisterComponent]
+///
+/// Lets you speak Ratvarian!
+///
+[RegisterComponent, NetworkedComponent]
[Access(typeof(PirateAccentSystem))]
public sealed partial class PirateAccentComponent : BaseAccentComponent
{
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("yarrChance")]
+ [DataField]
public float YarrChance = 0.5f;
[ViewVariables]
diff --git a/Content.Shared/Speech/Components/RatvarianLanguageComponent.cs b/Content.Shared/Speech/Components/RatvarianLanguageComponent.cs
index ddb4ce5e90..c195c648ef 100644
--- a/Content.Shared/Speech/Components/RatvarianLanguageComponent.cs
+++ b/Content.Shared/Speech/Components/RatvarianLanguageComponent.cs
@@ -1,4 +1,5 @@
-using Robust.Shared.GameStates;
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
namespace Content.Shared.Speech.Components;
@@ -6,4 +7,5 @@ namespace Content.Shared.Speech.Components;
/// Marks a speech status effect that transforms spoken text into Ratvarian.
///
[RegisterComponent, NetworkedComponent]
+[Access(typeof(RatvarianLanguageSystem))]
public sealed partial class RatvarianLanguageComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/ReplacementAccentComponent.cs b/Content.Shared/Speech/Components/ReplacementAccentComponent.cs
new file mode 100644
index 0000000000..edb3d9bb3d
--- /dev/null
+++ b/Content.Shared/Speech/Components/ReplacementAccentComponent.cs
@@ -0,0 +1,17 @@
+using Content.Shared.Speech.Prototypes;
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Replaces full sentences or words within sentences with new strings.
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(ReplacementAccentSystem))]
+public sealed partial class ReplacementAccentComponent : BaseAccentComponent
+{
+ [DataField(required: true)]
+ public ProtoId Accent;
+}
diff --git a/Content.Shared/Speech/Components/RussianAccentComponent.cs b/Content.Shared/Speech/Components/RussianAccentComponent.cs
new file mode 100644
index 0000000000..e210ccd434
--- /dev/null
+++ b/Content.Shared/Speech/Components/RussianAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// ЯussiДи! Becomes incomprehensible to read for anyone who actually knows cyrillic.
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(RussianAccentSystem))]
+public sealed partial class RussianAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/ScrambledAccentComponent.cs b/Content.Shared/Speech/Components/ScrambledAccentComponent.cs
new file mode 100644
index 0000000000..dd82de4ccb
--- /dev/null
+++ b/Content.Shared/Speech/Components/ScrambledAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Structure is sentence weird accent this with!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(ScrambledAccentSystem))]
+public sealed partial class ScrambledAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/SkeletonAccentComponent.cs b/Content.Shared/Speech/Components/SkeletonAccentComponent.cs
new file mode 100644
index 0000000000..d8e301feb8
--- /dev/null
+++ b/Content.Shared/Speech/Components/SkeletonAccentComponent.cs
@@ -0,0 +1,18 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Rattle me bones!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SkeletonAccentSystem))]
+public sealed partial class SkeletonAccentComponent : BaseAccentComponent
+{
+ ///
+ /// Chance that the message will be appended with "ACK ACK!"
+ ///
+ [DataField]
+ public float AckChance = 0.3f; // Funnier if it doesn't happen every single time
+}
diff --git a/Content.Shared/Speech/Components/SlowAccentComponent.cs b/Content.Shared/Speech/Components/SlowAccentComponent.cs
new file mode 100644
index 0000000000..51e4186bc9
--- /dev/null
+++ b/Content.Shared/Speech/Components/SlowAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Makes... the... entity... talk... like... this...
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SlowAccentSystem))]
+public sealed partial class SlowAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/SlurredAccentComponent.cs b/Content.Shared/Speech/Components/SlurredAccentComponent.cs
index 004a62a777..daf52870c8 100644
--- a/Content.Shared/Speech/Components/SlurredAccentComponent.cs
+++ b/Content.Shared/Speech/Components/SlurredAccentComponent.cs
@@ -1,6 +1,24 @@
+using Content.Shared.Speech.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Shared.Speech.Components;
+///
+/// Go houhhm... yo'r drrunkk...
+///
[RegisterComponent, NetworkedComponent]
-public sealed partial class SlurredAccentComponent : BaseAccentComponent;
+[Access(typeof(SlurredSystem))]
+public sealed partial class SlurredAccentComponent : BaseAccentComponent
+{
+ ///
+ /// Divisor applied to total seconds used to get the odds of slurred speech occuring.
+ ///
+ [DataField]
+ public float SlurredModifier = 1100f;
+
+ ///
+ /// Minimum amount of time on the slurred accent for it to start taking effect.
+ ///
+ [DataField]
+ public float SlurredThreshold = 80f;
+}
diff --git a/Content.Shared/Speech/Components/SouthernAccentComponent.cs b/Content.Shared/Speech/Components/SouthernAccentComponent.cs
new file mode 100644
index 0000000000..89543aaa9e
--- /dev/null
+++ b/Content.Shared/Speech/Components/SouthernAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Dagnabit.
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SouthernAccentSystem))]
+public sealed partial class SouthernAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/SpanishAccentComponent.cs b/Content.Shared/Speech/Components/SpanishAccentComponent.cs
new file mode 100644
index 0000000000..e8227cf942
--- /dev/null
+++ b/Content.Shared/Speech/Components/SpanishAccentComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Mios dios!
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SpanishAccentSystem))]
+public sealed partial class SpanishAccentComponent : BaseAccentComponent;
diff --git a/Content.Shared/Speech/Components/SpeakOnActionComponent.cs b/Content.Shared/Speech/Components/SpeakOnActionComponent.cs
index 3de09932e2..8a33c858b5 100644
--- a/Content.Shared/Speech/Components/SpeakOnActionComponent.cs
+++ b/Content.Shared/Speech/Components/SpeakOnActionComponent.cs
@@ -6,7 +6,8 @@ namespace Content.Shared.Speech.Components;
///
/// Action components which should write a message to ICChat on use
///
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedSpeakOnActionSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SpeakOnActionSystem))]
public sealed partial class SpeakOnActionComponent : Component
{
///
diff --git a/Content.Shared/Speech/Components/StutteringAccentComponent.cs b/Content.Shared/Speech/Components/StutteringAccentComponent.cs
index ff64885238..1c1dc1b95d 100644
--- a/Content.Shared/Speech/Components/StutteringAccentComponent.cs
+++ b/Content.Shared/Speech/Components/StutteringAccentComponent.cs
@@ -1,8 +1,13 @@
+using Content.Shared.Speech.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Shared.Speech.Components;
+///
+/// S-s-s-stuttering!
+///
[RegisterComponent, NetworkedComponent]
+[Access(typeof(StutteringSystem))]
public sealed partial class StutteringAccentComponent : BaseAccentComponent
{
///
diff --git a/Content.Shared/Speech/Components/UnblockableSpeechComponent.cs b/Content.Shared/Speech/Components/UnblockableSpeechComponent.cs
new file mode 100644
index 0000000000..842cb18e83
--- /dev/null
+++ b/Content.Shared/Speech/Components/UnblockableSpeechComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// Makes the entity's speech unblockable by speech-blocking effects or entities.
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(UnblockableSpeechSystem))]
+public sealed partial class UnblockableSpeechComponent : Component;
diff --git a/Content.Shared/Speech/Components/VocalComponent.cs b/Content.Shared/Speech/Components/VocalComponent.cs
index f41c5e7e3d..1579887fac 100644
--- a/Content.Shared/Speech/Components/VocalComponent.cs
+++ b/Content.Shared/Speech/Components/VocalComponent.cs
@@ -1,5 +1,5 @@
using Content.Shared.Chat.Prototypes;
-using Content.Shared.Humanoid;
+using Content.Shared.Speech.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@@ -9,8 +9,8 @@ namespace Content.Shared.Speech.Components;
///
/// Component required for entities to be able to do vocal emotions.
///
-[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentState]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(VocalSystem))]
public sealed partial class VocalComponent : Component
{
//TODO: Wilhelm scream logic needs to be more generic
@@ -53,5 +53,5 @@ public sealed partial class VocalComponent : Component
///
[DataField]
[AutoNetworkedField]
- public ProtoId? EmoteSounds = null;
+ public ProtoId? EmoteSounds;
}
diff --git a/Content.Server/Speech/Components/VoiceOverrideComponent.cs b/Content.Shared/Speech/Components/VoiceOverrideComponent.cs
similarity index 82%
rename from Content.Server/Speech/Components/VoiceOverrideComponent.cs
rename to Content.Shared/Speech/Components/VoiceOverrideComponent.cs
index 349babc948..7369c12200 100644
--- a/Content.Server/Speech/Components/VoiceOverrideComponent.cs
+++ b/Content.Shared/Speech/Components/VoiceOverrideComponent.cs
@@ -1,7 +1,7 @@
-using Content.Shared.Speech;
+using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-namespace Content.Server.Speech.Components;
+namespace Content.Shared.Speech.Components;
///
/// Will change the voice of the entity that has the component (e.g radio and speech).
@@ -10,7 +10,7 @@ namespace Content.Server.Speech.Components;
/// Before using this component, please take a look at the the TransformSpeakerNameEvent (and the inventory relay version).
/// Depending on what you're doing, it could be a better choice!
///
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent]
public sealed partial class VoiceOverrideComponent : Component
{
///
@@ -18,14 +18,14 @@ public sealed partial class VoiceOverrideComponent : Component
/// Uses the localized version of the string and if null wont do anything.
///
[DataField]
- public string? NameOverride = null;
+ public string? NameOverride;
///
/// The verb that will be used insteand of an entities default one.
/// If null, the defaut will be used.
///
[DataField]
- public ProtoId? SpeechVerbOverride = null;
+ public ProtoId? SpeechVerbOverride;
///
/// If true, the override values (if they are not null) will be applied.
diff --git a/Content.Server/Speech/AccentSystem.cs b/Content.Shared/Speech/EntitySystems/AccentSystem.cs
similarity index 67%
rename from Content.Server/Speech/AccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/AccentSystem.cs
index 67353e8e9b..f05765b9f9 100644
--- a/Content.Server/Speech/AccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/AccentSystem.cs
@@ -1,18 +1,13 @@
using System.Text.RegularExpressions;
using Content.Shared.Chat;
-using Content.Shared.Speech;
-namespace Content.Server.Speech;
+namespace Content.Shared.Speech.EntitySystems;
-public sealed class AccentSystem : EntitySystem
+public sealed partial class AccentSystem : EntitySystem
{
public static readonly Regex SentenceRegex = new(@"(?<=[\.!\?‽])(?![\.!\?‽])", RegexOptions.Compiled);
- public override void Initialize()
- {
- SubscribeLocalEvent(AccentHandler);
- }
-
+ [SubscribeLocalEvent]
private void AccentHandler(TransformSpeechEvent args)
{
if (args.Cancelled)
diff --git a/Content.Server/Speech/EntitySystems/BackwardsAccentSystem.cs b/Content.Shared/Speech/EntitySystems/BackwardsAccentSystem.cs
similarity index 70%
rename from Content.Server/Speech/EntitySystems/BackwardsAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/BackwardsAccentSystem.cs
index 838e0d53d5..2bb9941898 100644
--- a/Content.Server/Speech/EntitySystems/BackwardsAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/BackwardsAccentSystem.cs
@@ -1,7 +1,6 @@
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class BackwardsAccentSystem : RelayAccentSystem
{
diff --git a/Content.Shared/Speech/EntitySystems/BarkAccentSystem.cs b/Content.Shared/Speech/EntitySystems/BarkAccentSystem.cs
new file mode 100644
index 0000000000..501a3a0d3a
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/BarkAccentSystem.cs
@@ -0,0 +1,43 @@
+using System.Collections.Frozen;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class BarkAccentSystem : RelayAccentSystem
+{
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private IRobustRandom _random = default!;
+
+ private static readonly IReadOnlyList Barks =
+ [
+ " Woof!", " WOOF", " wof-wof",
+ ];
+
+ private static readonly FrozenDictionary SpecialWords =
+ new Dictionary
+ {
+ { "ah", "arf" },
+ { "Ah", "Arf" },
+ { "oh", "oof" },
+ { "Oh", "Oof" },
+ }.ToFrozenDictionary();
+
+ public override string Accentuate(string message, Entity? ent = null)
+ {
+ var random = ent.HasValue
+ ? SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value))
+ : _random;
+
+ foreach (var (word, repl) in SpecialWords)
+ {
+ message = message.Replace(word, repl);
+ }
+
+ return message.Replace("!", random.Pick(Barks))
+ .Replace("l", "r")
+ .Replace("L", "R");
+ }
+}
diff --git a/Content.Server/Speech/EntitySystems/BleatingAccentSystem.cs b/Content.Shared/Speech/EntitySystems/BleatingAccentSystem.cs
similarity index 79%
rename from Content.Server/Speech/EntitySystems/BleatingAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/BleatingAccentSystem.cs
index 0871dd1c72..4c5605a1c0 100644
--- a/Content.Server/Speech/EntitySystems/BleatingAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/BleatingAccentSystem.cs
@@ -1,8 +1,7 @@
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class BleatingAccentSystem : RelayAccentSystem
{
diff --git a/Content.Shared/Speech/EntitySystems/BlockListeningSystem.cs b/Content.Shared/Speech/EntitySystems/BlockListeningSystem.cs
new file mode 100644
index 0000000000..2ae03feafd
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/BlockListeningSystem.cs
@@ -0,0 +1,12 @@
+using Content.Shared.Speech.Components;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class BlockListeningSystem : EntitySystem
+{
+ [SubscribeLocalEvent]
+ private void OnListenAttempt(Entity ent, ref ListenAttemptEvent args)
+ {
+ args.Cancel();
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/EmoteBlockerSystem.cs b/Content.Shared/Speech/EntitySystems/EmoteBlockerSystem.cs
index e2c9728fbc..eaa7b8e957 100644
--- a/Content.Shared/Speech/EntitySystems/EmoteBlockerSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/EmoteBlockerSystem.cs
@@ -14,26 +14,26 @@ public sealed class EmoteBlockerSystem : EntitySystem
SubscribeLocalEvent>(OnRelayedEmoteEvent);
}
- private static void OnRelayedEmoteEvent(Entity entity, ref InventoryRelayedEvent args)
+ private static void OnRelayedEmoteEvent(Entity ent, ref InventoryRelayedEvent args)
{
- OnEmoteEvent(entity, ref args.Args);
+ OnEmoteEvent(ent, ref args.Args);
}
- private static void OnEmoteEvent(Entity entity, ref BeforeEmoteEvent args)
+ private static void OnEmoteEvent(Entity ent, ref BeforeEmoteEvent args)
{
- if (entity.Comp.BlocksEmotes.Contains(args.Emote))
+ if (ent.Comp.BlocksEmotes.Contains(args.Emote))
{
args.Cancel();
- args.Blocker = entity;
+ args.Blocker = ent;
return;
}
- foreach (var blockedCat in entity.Comp.BlocksCategories)
+ foreach (var blockedCat in ent.Comp.BlocksCategories)
{
if (blockedCat == args.Emote.Category)
{
args.Cancel();
- args.Blocker = entity;
+ args.Blocker = ent;
return;
}
}
diff --git a/Content.Server/Speech/EmotesMenuSystem.cs b/Content.Shared/Speech/EntitySystems/EmotesMenuSystem.cs
similarity index 83%
rename from Content.Server/Speech/EmotesMenuSystem.cs
rename to Content.Shared/Speech/EntitySystems/EmotesMenuSystem.cs
index 8a4841d82a..978c75b2d9 100644
--- a/Content.Server/Speech/EmotesMenuSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/EmotesMenuSystem.cs
@@ -1,11 +1,10 @@
using Content.Shared.Chat;
-using Content.Server.Chat.Systems;
-namespace Content.Server.Speech;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class EmotesMenuSystem : EntitySystem
{
- [Dependency] private ChatSystem _chat = default!;
+ [Dependency] private SharedChatSystem _chat = default!;
public override void Initialize()
{
diff --git a/Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs b/Content.Shared/Speech/EntitySystems/FrenchAccentSystem.cs
similarity index 71%
rename from Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/FrenchAccentSystem.cs
index 50d5961d37..be173dff30 100644
--- a/Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/FrenchAccentSystem.cs
@@ -1,8 +1,7 @@
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
///
/// System that gives the speaker a faux-French accent.
@@ -11,7 +10,7 @@ public sealed partial class FrenchAccentSystem : RelayAccentSystem
+{
+ // @formatter:off
+ private static readonly Regex RegexUpperTh = new("[T]+[Ss]+|[S]+[Cc]+(?=[IiEeYy]+)|[C]+(?=[IiEeYy]+)|[P][Ss]+|([S]+[Tt]+|[T]+)(?=[Ii]+[Oo]+[Uu]*[Nn]*)|[C]+[Hh]+(?=[Ii]*[Ee]*)|[Z]+|[S]+|[X]+(?=[Ee]+)");
+ private static readonly Regex RegexLowerTh = new("[t]+[s]+|[s]+[c]+(?=[iey]+)|[c]+(?=[iey]+)|[p][s]+|([s]+[t]+|[t]+)(?=[i]+[o]+[u]*[n]*)|[c]+[h]+(?=[i]*[e]*)|[z]+|[s]+|[x]+(?=[e]+)");
+ private static readonly Regex RegexUpperEcks = new("[E]+[Xx]+[Cc]*|[X]+");
+ private static readonly Regex RegexLowerEcks = new("[e]+[x]+[c]*|[x]+");
+ // @formatter:on
+
+ public override string Accentuate(string message, Entity? ent = null)
+ {
+ // handles ts, sc(i|e|y), c(i|e|y), ps, st(io(u|n)), ch(i|e), z, s
+ message = RegexUpperTh.Replace(message, "TH");
+ message = RegexLowerTh.Replace(message, "th");
+ // handles ex(c), x
+ message = RegexUpperEcks.Replace(message, "EKTH");
+ message = RegexLowerEcks.Replace(message, "ekth");
+ return message;
+ }
+}
diff --git a/Content.Server/Speech/EntitySystems/GermanAccentSystem.cs b/Content.Shared/Speech/EntitySystems/GermanAccentSystem.cs
similarity index 82%
rename from Content.Server/Speech/EntitySystems/GermanAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/GermanAccentSystem.cs
index 47868dd9ff..83adebe110 100644
--- a/Content.Server/Speech/EntitySystems/GermanAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/GermanAccentSystem.cs
@@ -1,14 +1,15 @@
using System.Text;
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class GermanAccentSystem : RelayAccentSystem
{
+ [Dependency] private IGameTiming _timing = default!;
[Dependency] private IRobustRandom _random = default!;
[Dependency] private ReplacementAccentSystem _replacement = default!;
@@ -19,11 +20,15 @@ public sealed partial class GermanAccentSystem : RelayAccentSystem ze is handled by replacements already)
var msgBuilder = new StringBuilder(msg);
@@ -51,7 +56,7 @@ public sealed partial class GermanAccentSystem : RelayAccentSystem
-/// This system redirects local chat messages to listening entities (e.g., radio microphones).
+/// This system redirects local chat messages to listening entities (e.g., radio microphones).
///
public sealed partial class ListeningSystem : EntitySystem
{
[Dependency] private SharedTransformSystem _xforms = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnSpeak);
- }
-
+ [SubscribeLocalEvent]
private void OnSpeak(EntitySpokeEvent ev)
{
PingListeners(ev.Source, ev.Message, ev.ObfuscatedMessage);
}
+ ///
+ /// Sends a speech message to entities listening within range.
+ ///
public void PingListeners(EntityUid source, string message, string? obfuscatedMessage)
{
// TODO whispering / audio volume? Microphone sensitivity?
@@ -54,7 +50,7 @@ public sealed partial class ListeningSystem : EntitySystem
continue;
}
- if (obfuscatedEv != null && distance > ChatSystem.WhisperClearRange)
+ if (obfuscatedEv != null && distance > SharedChatSystem.WhisperClearRange)
RaiseLocalEvent(listenerUid, obfuscatedEv);
else
RaiseLocalEvent(listenerUid, ev);
diff --git a/Content.Server/Speech/EntitySystems/LizardAccentSystem.cs b/Content.Shared/Speech/EntitySystems/LizardAccentSystem.cs
similarity index 88%
rename from Content.Server/Speech/EntitySystems/LizardAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/LizardAccentSystem.cs
index b460d66e94..3467a7005a 100644
--- a/Content.Server/Speech/EntitySystems/LizardAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/LizardAccentSystem.cs
@@ -1,8 +1,7 @@
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed class LizardAccentSystem : RelayAccentSystem
{
diff --git a/Content.Shared/Speech/EntitySystems/MeleeSpeechSystem.cs b/Content.Shared/Speech/EntitySystems/MeleeSpeechSystem.cs
new file mode 100644
index 0000000000..7e25d0c5cb
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/MeleeSpeechSystem.cs
@@ -0,0 +1,85 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Actions;
+using Content.Shared.Database;
+using Content.Shared.Speech.Components;
+using Robust.Shared.Player;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class MeleeSpeechSystem : EntitySystem
+{
+ [Dependency] private ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private SharedActionsSystem _action = default!;
+ [Dependency] private SharedUserInterfaceSystem _ui = default!;
+
+ [SubscribeLocalEvent]
+ private void OnComponentMapInit(Entity ent, ref MapInitEvent args)
+ {
+ _action.AddAction(ent, ref ent.Comp.ConfigureActionEntity, ent.Comp.ConfigureAction, ent);
+ }
+
+ [SubscribeLocalEvent]
+ private void OnGetActions(Entity ent, ref GetItemActionsEvent args)
+ {
+ args.AddAction(ref ent.Comp.ConfigureActionEntity, ent.Comp.ConfigureAction);
+ }
+
+ [SubscribeLocalEvent]
+ private void OnBattlecryChanged(Entity ent, ref MeleeSpeechBattlecryChangedMessage args)
+ {
+ var battlecry = args.Battlecry;
+ if (battlecry.Length > ent.Comp.MaxBattlecryLength)
+ battlecry = battlecry[..ent.Comp.MaxBattlecryLength];
+
+ TryChangeBattlecry(ent.AsNullable(), battlecry);
+ }
+
+ ///
+ /// Attempts to open the Battlecry UI.
+ ///
+ [SubscribeLocalEvent]
+ private void OnConfigureAction(Entity ent, ref MeleeSpeechConfigureActionEvent args)
+ {
+ TryOpenUi(args.Performer, ent.AsNullable());
+ }
+
+ ///
+ /// Attempts to open the Battlecry UI for a user.
+ ///
+ public void TryOpenUi(EntityUid user, Entity source)
+ {
+ if (!Resolve(source, ref source.Comp))
+ return;
+
+ if (!TryComp(user, out var actor))
+ return;
+
+ _ui.TryToggleUi(source.Owner, MeleeSpeechUiKey.Key, actor.PlayerSession);
+ }
+
+ ///
+ /// Attempts to change the battlecry of an entity.
+ /// Returns true/false.
+ ///
+ ///
+ /// Logs changes to an entity's battlecry
+ ///
+ public bool TryChangeBattlecry(Entity ent, string? battlecry)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ battlecry = !string.IsNullOrWhiteSpace(battlecry) ? battlecry.Trim() : null;
+
+ if (ent.Comp.Battlecry == battlecry)
+ return true;
+
+ ent.Comp.Battlecry = battlecry;
+ Dirty(ent);
+ _adminLogger.Add(LogType.ItemConfigure,
+ LogImpact.Medium,
+ $" {ToPrettyString(ent):entity}'s battlecry has been changed to {battlecry}");
+
+ return true;
+ }
+}
diff --git a/Content.Server/Speech/EntitySystems/MobsterAccentSystem.cs b/Content.Shared/Speech/EntitySystems/MobsterAccentSystem.cs
similarity index 83%
rename from Content.Server/Speech/EntitySystems/MobsterAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/MobsterAccentSystem.cs
index 48d0ff88df..6b94012fe9 100644
--- a/Content.Server/Speech/EntitySystems/MobsterAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/MobsterAccentSystem.cs
@@ -1,10 +1,11 @@
using System.Linq;
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class MobsterAccentSystem : RelayAccentSystem
{
@@ -16,6 +17,8 @@ public sealed partial class MobsterAccentSystem : RelayAccentSystem thinkin'
// king -> king
@@ -40,12 +47,12 @@ public sealed partial class MobsterAccentSystem : RelayAccentSystem
{
+ [Dependency] private IGameTiming _timing = default!;
[Dependency] private IRobustRandom _random = default!;
public override string Accentuate(string message, Entity? ent = null)
{
+ var random = ent.HasValue
+ ? SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value))
+ : _random;
+
var words = message.Split();
var accentedMessage = new StringBuilder(message.Length + 2);
@@ -18,7 +24,7 @@ public sealed partial class MonkeyAccentSystem : RelayAccentSystem= 0.5)
+ if (random.NextDouble() >= 0.5)
{
if (word.Length > 1)
{
@@ -27,7 +33,7 @@ public sealed partial class MonkeyAccentSystem : RelayAccentSystem= 0.3)
+ if (random.NextDouble() >= 0.3)
accentedMessage.Append('K');
}
else
@@ -37,7 +43,7 @@ public sealed partial class MonkeyAccentSystem : RelayAccentSystem= 0.8)
+ if (random.NextDouble() >= 0.8)
accentedMessage.Append('H');
else
accentedMessage.Append('A');
diff --git a/Content.Server/Speech/EntitySystems/MothAccentSystem.cs b/Content.Shared/Speech/EntitySystems/MothAccentSystem.cs
similarity index 59%
rename from Content.Server/Speech/EntitySystems/MothAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/MothAccentSystem.cs
index 60f7812914..03b0dde54c 100644
--- a/Content.Server/Speech/EntitySystems/MothAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/MothAccentSystem.cs
@@ -1,13 +1,12 @@
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed class MothAccentSystem : RelayAccentSystem
{
- private static readonly Regex RegexLowerBuzz = new Regex("z{1,3}");
- private static readonly Regex RegexUpperBuzz = new Regex("Z{1,3}");
+ private static readonly Regex RegexLowerBuzz = new("z{1,3}");
+ private static readonly Regex RegexUpperBuzz = new("Z{1,3}");
public override string Accentuate(string message, Entity? ent = null)
{
diff --git a/Content.Server/Speech/EntitySystems/MumbleAccentSystem.cs b/Content.Shared/Speech/EntitySystems/MumbleAccentSystem.cs
similarity index 79%
rename from Content.Server/Speech/EntitySystems/MumbleAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/MumbleAccentSystem.cs
index 7cf1ce3320..f5fd2dceb8 100644
--- a/Content.Server/Speech/EntitySystems/MumbleAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/MumbleAccentSystem.cs
@@ -1,16 +1,12 @@
-using Content.Server.Chat.Systems;
-using Content.Server.Speech.Components;
+using Content.Shared.Speech.Components;
using Content.Shared.Chat;
using Content.Shared.Chat.Prototypes;
-using Content.Shared.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
-using Robust.Shared.Prototypes;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class MumbleAccentSystem : RelayAccentSystem
{
- [Dependency] private ChatSystem _chat = default!;
+ [Dependency] private SharedChatSystem _chat = default!;
[Dependency] private ReplacementAccentSystem _replacement = default!;
public override void Initialize()
@@ -39,6 +35,6 @@ public sealed partial class MumbleAccentSystem : RelayAccentSystem? ent = null)
{
- return _replacement.ApplyReplacements(message, "mumble");
+ return _replacement.ApplyReplacements(message, "mumble", ent?.Owner);
}
}
diff --git a/Content.Shared/Speech/EntitySystems/OwOAccentSystem.cs b/Content.Shared/Speech/EntitySystems/OwOAccentSystem.cs
new file mode 100644
index 0000000000..73dcd80cb0
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/OwOAccentSystem.cs
@@ -0,0 +1,42 @@
+using System.Collections.Frozen;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class OwOAccentSystem : RelayAccentSystem
+{
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private IRobustRandom _random = default!;
+
+ private static readonly IReadOnlyList Faces =
+ [
+ " (•`ω´•)", " ;;w;;", " owo", " UwU", " >w<", " ^w^",
+ ];
+
+ private static readonly FrozenDictionary SpecialWords =
+ new Dictionary
+ {
+ { "you", "wu" },
+ }.ToFrozenDictionary();
+
+ public override string Accentuate(string message, Entity? ent = null)
+ {
+ var random = ent.HasValue
+ ? SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value))
+ : _random;
+
+ foreach (var (word, repl) in SpecialWords)
+ {
+ message = message.Replace(word, repl);
+ }
+
+ return message.Replace("!", random.Pick(Faces))
+ .Replace("r", "w")
+ .Replace("R", "W")
+ .Replace("l", "w")
+ .Replace("L", "W");
+ }
+}
diff --git a/Content.Server/Speech/EntitySystems/ParrotAccentSystem.cs b/Content.Shared/Speech/EntitySystems/ParrotAccentSystem.cs
similarity index 62%
rename from Content.Server/Speech/EntitySystems/ParrotAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/ParrotAccentSystem.cs
index fffe1875cf..4d5eb3edbe 100644
--- a/Content.Server/Speech/EntitySystems/ParrotAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/ParrotAccentSystem.cs
@@ -1,25 +1,27 @@
using System.Linq;
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class ParrotAccentSystem : RelayAccentSystem
{
- private static readonly Regex WordCleanupRegex = new Regex("[^A-Za-z0-9 -]");
+ private static readonly Regex WordCleanupRegex = new("[^A-Za-z0-9 -]");
- [Dependency] private IRobustRandom _random = default!;
+ [Dependency] private IGameTiming _timing = default!;
public override string Accentuate(string message, Entity? ent = null)
{
- // TODO: Make this accent possible to apply without an entity with the component.
if (ent == null)
return message;
+ var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value));
+
// Sometimes repeat the longest word at the end of the message, after a squawk! SQUAWK! Sometimes!
- if (_random.Prob(ent.Value.Comp.LongestWordRepeatChance))
+ if (random.Prob(ent.Value.Comp.LongestWordRepeatChance))
{
// Don't count non-alphanumeric characters as parts of words
var cleaned = WordCleanupRegex.Replace(message, string.Empty);
@@ -27,6 +29,7 @@ public sealed partial class ParrotAccentSystem : RelayAccentSystem word.Length);
+
if (longest?.Length >= ent.Value.Comp.LongestWordMinLength)
{
message = EnsurePunctuation(message);
@@ -34,21 +37,21 @@ public sealed partial class ParrotAccentSystem : RelayAccentSystem
private string EnsurePunctuation(string message)
{
- if (!message.EndsWith('!') && !message.EndsWith('?') && !message.EndsWith('.'))
- return message + '!';
- return message;
+ return message.EndsWith('!') || message.EndsWith('?') || message.EndsWith('.')
+ ? message
+ : message + '!';
}
///
/// Returns a random, localized squawk sound.
///
- private string GetRandomSquawk(Entity entity)
+ private string GetRandomSquawk(Entity ent)
{
- return Loc.GetString(_random.Pick(entity.Comp.Squawks));
+ var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent));
+ return Loc.GetString(random.Pick(ent.Comp.Squawks));
}
}
diff --git a/Content.Server/Speech/EntitySystems/PirateAccentSystem.cs b/Content.Shared/Speech/EntitySystems/PirateAccentSystem.cs
similarity index 60%
rename from Content.Server/Speech/EntitySystems/PirateAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/PirateAccentSystem.cs
index aa7ed96529..88de28e174 100644
--- a/Content.Server/Speech/EntitySystems/PirateAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/PirateAccentSystem.cs
@@ -1,32 +1,39 @@
using System.Linq;
-using Content.Server.Speech.Components;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
using System.Text.RegularExpressions;
-using Content.Shared.Speech.EntitySystems;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class PirateAccentSystem : RelayAccentSystem
{
private static readonly Regex FirstWordAllCapsRegex = new(@"^(\S+)");
+ [Dependency] private IGameTiming _timing = default!;
[Dependency] private IRobustRandom _random = default!;
[Dependency] private ReplacementAccentSystem _replacement = default!;
- // converts left word when typed into the right word. For example typing you becomes ye.
+ // Converts left word when typed into the right word. For example typing you becomes ye.
public override string Accentuate(string message, Entity? ent = null)
{
- var msg = _replacement.ApplyReplacements(message, "pirate");
+ var random = ent.HasValue
+ ? SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value))
+ : _random;
- if (!_random.Prob(ent.HasValue ? ent.Value.Comp.YarrChance : 0.5f))
+ var msg = _replacement.ApplyReplacements(message, "pirate", ent?.Owner);
+
+ if (!random.Prob(ent.HasValue ? ent.Value.Comp.YarrChance : 0.5f))
return msg;
- //Checks if the first word of the sentence is all caps
- //So the prefix can be allcapped and to not resanitize the captial
+
+ // Checks if the first word of the sentence is all caps
+ // So the prefix can be allcapped and to not resanitize the captial
var firstWordAllCaps = !FirstWordAllCapsRegex.Match(msg).Value.Any(char.IsLower);
if (ent.HasValue)
{
- var pick = _random.Pick(ent.Value.Comp.PirateWords);
+ var pick = random.Pick(ent.Value.Comp.PirateWords);
var pirateWord = Loc.GetString(pick);
// Reverse sanitize capital
if (!firstWordAllCaps)
diff --git a/Content.Shared/Speech/EntitySystems/SharedRatvarianLanguageSystem.cs b/Content.Shared/Speech/EntitySystems/RatvarianLanguageSystem.cs
similarity index 87%
rename from Content.Shared/Speech/EntitySystems/SharedRatvarianLanguageSystem.cs
rename to Content.Shared/Speech/EntitySystems/RatvarianLanguageSystem.cs
index 969af67990..73447ff5f1 100644
--- a/Content.Shared/Speech/EntitySystems/SharedRatvarianLanguageSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/RatvarianLanguageSystem.cs
@@ -7,14 +7,14 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Speech.EntitySystems;
-public abstract partial class SharedRatvarianLanguageSystem : RelayAccentSystem
+public sealed partial class RatvarianLanguageSystem : RelayAccentSystem
{
public static readonly EntProtoId Ratvarian = "StatusEffectRatvarianLanguage";
- [Dependency] protected StatusEffectsSystem Status = default!;
+ [Dependency] private StatusEffectsSystem _statusEffect = default!;
- protected override Type[] AccentBefore => [typeof(SharedSlurredSystem), typeof(SharedStutteringSystem)];
- protected override Type[] RelayAccentBefore => [typeof(SharedSlurredSystem), typeof(SharedStutteringSystem)];
+ protected override Type[] AccentBefore => [typeof(SlurredSystem), typeof(StutteringSystem)];
+ protected override Type[] RelayAccentBefore => [typeof(SlurredSystem), typeof(StutteringSystem)];
// This is the word of Ratvar and those who speak it shall abide by His rules:
/*
@@ -40,14 +40,6 @@ public abstract partial class SharedRatvarianLanguageSystem : RelayAccentSystem<
private static readonly Regex TomyPattern = new(@"(to|my)\s", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ProperNouns = new("(ratvar)|(nezbere)|(sevtuq)|(nzcrentr)|(inath-neq)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
- ///
- /// Applies or refreshes the Ratvarian speech status effect on an entity.
- ///
- [PublicAPI]
- public virtual void DoRatvarian(EntityUid uid, TimeSpan time, bool refresh)
- {
- }
-
public override string Accentuate(string message, Entity? ent = null)
{
var ruleTranslation = message;
@@ -105,4 +97,16 @@ public abstract partial class SharedRatvarianLanguageSystem : RelayAccentSystem<
return finalMessage.ToString().Trim();
}
+
+ ///
+ /// Applies or refreshes the Ratvarian speech status effect on an entity.
+ ///
+ [PublicAPI]
+ public void DoRatvarian(EntityUid uid, TimeSpan time, bool refresh)
+ {
+ if (refresh)
+ _statusEffect.TryUpdateStatusEffectDuration(uid, Ratvarian, time);
+ else
+ _statusEffect.TryAddStatusEffectDuration(uid, Ratvarian, time);
+ }
}
diff --git a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs b/Content.Shared/Speech/EntitySystems/ReplacementAccentSystem.cs
similarity index 77%
rename from Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/ReplacementAccentSystem.cs
index e40af39fa1..854e37b958 100644
--- a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/ReplacementAccentSystem.cs
@@ -1,14 +1,14 @@
using System.Linq;
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Server.Speech.Prototypes;
-using Content.Shared.Speech;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
+using Content.Shared.Speech.Prototypes;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
// TODO: Code in-game languages and make this a language
///
@@ -16,6 +16,7 @@ namespace Content.Server.Speech.EntitySystems;
///
public sealed partial class ReplacementAccentSystem : RelayAccentSystem
{
+ [Dependency] private IGameTiming _timing = default!;
[Dependency] private IRobustRandom _random = default!;
private readonly Dictionary, (Regex regex, string replacement)[]>
@@ -40,26 +41,40 @@ public sealed partial class ReplacementAccentSystem : RelayAccentSystem
- /// Attempts to apply a given replacement accent prototype to a message.
+ /// Applies a replacement accent to an entity.
///
[PublicAPI]
- public string ApplyReplacements(string message, string accent)
+ public void ApplyAccent(EntityUid uid, string accent)
+ {
+ var component = EnsureComp(uid);
+ component.Accent = accent;
+ Dirty(uid, component);
+ }
+
+ ///
+ /// Attempts to apply a given replacement accent prototype to a message.
+ ///
+ [PublicAPI]
+ public string ApplyReplacements(string message, string accent, EntityUid? uid = null)
{
if (!ProtoMan.TryIndex(accent, out var prototype))
return message;
- if (!_random.Prob(prototype.ReplacementChance))
+ var random = uid != null
+ ? SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(uid.Value))
+ : _random;
+ if (!random.Prob(prototype.ReplacementChance))
return message;
// Prioritize fully replacing if that exists--
// ideally both aren't used at the same time (but we don't have a way to enforce that in serialization yet)
if (prototype.FullReplacements != null)
{
- return prototype.FullReplacements.Length != 0 ? Loc.GetString(_random.Pick(prototype.FullReplacements)) : "";
+ return prototype.FullReplacements.Length != 0 ? Loc.GetString(random.Pick(prototype.FullReplacements)) : "";
}
// Prohibition of repeated word replacements.
@@ -73,10 +88,10 @@ public sealed partial class ReplacementAccentSystem : RelayAccentSystem 0; i--)
+ for (var i = regex.Count(maskMessage); i > 0; i--)
{
// fetch the match again as the character indices may have changed
- Match match = regex.Match(maskMessage);
+ var match = regex.Match(maskMessage);
var replacement = replace;
// Intelligently replace capitalization
@@ -109,11 +124,11 @@ public sealed partial class ReplacementAccentSystem : RelayAccentSystem
+ return
+ [
+ .. replacements.Select(kv =>
{
var (first, replace) = kv;
var firstLoc = Loc.GetString(first);
@@ -132,8 +149,8 @@ public sealed partial class ReplacementAccentSystem : RelayAccentSystem
{
@@ -11,7 +9,7 @@ public sealed partial class RussianAccentSystem : RelayAccentSystem? ent = null)
{
- var accentedMessage = new StringBuilder(_replacement.ApplyReplacements(message, "russian"));
+ var accentedMessage = new StringBuilder(_replacement.ApplyReplacements(message, "russian", ent?.Owner));
for (var i = 0; i < accentedMessage.Length; i++)
{
diff --git a/Content.Server/Speech/EntitySystems/ScrambledAccentSystem.cs b/Content.Shared/Speech/EntitySystems/ScrambledAccentSystem.cs
similarity index 67%
rename from Content.Server/Speech/EntitySystems/ScrambledAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/ScrambledAccentSystem.cs
index 459f15bdbf..654024a29b 100644
--- a/Content.Server/Speech/EntitySystems/ScrambledAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/ScrambledAccentSystem.cs
@@ -1,30 +1,36 @@
using System.Linq;
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class ScrambledAccentSystem : RelayAccentSystem
{
private static readonly Regex RegexLoneI = new(@"(?<=\ )i(?=[\ \.\?]|$)");
+ [Dependency] private IGameTiming _timing = default!;
[Dependency] private IRobustRandom _random = default!;
public override string Accentuate(string message, Entity? ent = null)
{
+ var random = ent.HasValue
+ ? SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value))
+ : _random;
+
var words = message.ToLower().Split();
if (words.Length < 2)
{
- var pick = _random.Next(1, 8);
+ var pick = random.Next(1, 8);
// If they try to weasel out of it by saying one word at a time we give them this.
return Loc.GetString($"accent-scrambled-words-{pick}");
}
// Scramble the words
- var scrambled = words.OrderBy(x => _random.Next()).ToArray();
+ var scrambled = words.OrderBy(_ => random.Next()).ToArray();
var msg = string.Join(" ", scrambled);
diff --git a/Content.Shared/Speech/EntitySystems/SharedDamagedSiliconAccentSystem.cs b/Content.Shared/Speech/EntitySystems/SharedDamagedSiliconAccentSystem.cs
new file mode 100644
index 0000000000..8369ad1425
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/SharedDamagedSiliconAccentSystem.cs
@@ -0,0 +1,122 @@
+using System.Text;
+using Content.Shared.Damage.Components;
+using Content.Shared.Damage.Systems;
+using Content.Shared.FixedPoint;
+using Content.Shared.Power.EntitySystems;
+using Content.Shared.PowerCell;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public abstract partial class SharedDamagedSiliconAccentSystem : RelayAccentSystem
+{
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private SharedBatterySystem _battery = default!;
+ [Dependency] private PowerCellSystem _powerCell = default!;
+ [Dependency] private DamageableSystem _damageable = default!;
+
+ protected override Type[] AccentAfter => [typeof(ReplacementAccentSystem)];
+ protected override Type[] RelayAccentAfter => [typeof(ReplacementAccentSystem)];
+
+ public override string Accentuate(string message, Entity? ent = null)
+ {
+ if (ent == null)
+ return message;
+
+ var uid = ent.Value.Owner;
+ if (ent.Value.Comp.EnableChargeCorruption)
+ {
+ var currentChargeLevel = 0.0f;
+ if (ent.Value.Comp.OverrideChargeLevel.HasValue)
+ {
+ currentChargeLevel = ent.Value.Comp.OverrideChargeLevel.Value;
+ }
+ else if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
+ {
+ currentChargeLevel = _battery.GetChargeLevel(battery.Value.AsNullable());
+ }
+ currentChargeLevel = Math.Clamp(currentChargeLevel, 0.0f, 1.0f);
+ // Corrupt due to low power (drops characters on longer messages)
+ message = CorruptPower(message, currentChargeLevel, ent.Value);
+ }
+
+ if (ent.Value.Comp.EnableDamageCorruption)
+ {
+ var damage = FixedPoint2.Zero;
+ if (ent.Value.Comp.OverrideTotalDamage.HasValue)
+ {
+ damage = ent.Value.Comp.OverrideTotalDamage.Value;
+ }
+ else if (TryComp(uid, out var damageable))
+ {
+ damage = _damageable.GetTotalDamage((uid, damageable));
+ }
+ // Corrupt due to damage (drop, repeat, replace with symbols)
+ message = CorruptDamage(message, damage, ent.Value);
+ }
+
+ return message;
+ }
+
+ protected virtual string CorruptDamage(string message, FixedPoint2 totalDamage, Entity ent)
+ {
+ return message;
+ }
+
+ ///
+ /// Corrupts a message based on the entity's charge level.
+ ///
+ public string CorruptPower(string message, float chargeLevel, Entity ent)
+ {
+ // The first idxMin characters are SAFE
+ var idxMin = ent.Comp.StartPowerCorruptionAtCharIdx;
+ // Probability will max at idxMax
+ var idxMax = ent.Comp.MaxPowerCorruptionAtCharIdx;
+
+ // Fast bails, would not have an effect
+ if (chargeLevel > ent.Comp.ChargeThresholdForPowerCorruption || message.Length < idxMin)
+ {
+ return message;
+ }
+
+ var outMsg = new StringBuilder();
+
+ var maxDropProb = ent.Comp.MaxDropProbFromPower * (1.0f - chargeLevel / ent.Comp.ChargeThresholdForPowerCorruption);
+
+ var idx = -1;
+ foreach (var letter in message)
+ {
+ idx++;
+ if (idx < idxMin) // Fast character, no effect
+ {
+ outMsg.Append(letter);
+ continue;
+ }
+
+ // use an x^2 interpolation to increase the drop probability until we hit idxMax
+ var probToDrop = idx >= idxMax
+ ? maxDropProb
+ : (float)Math.Pow(((double)idx - idxMin) / (idxMax - idxMin), 2.0) * maxDropProb;
+ // Ensure we're in the range for Prob()
+ probToDrop = Math.Clamp(probToDrop, 0.0f, 1.0f);
+
+ var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent));
+ if (random.Prob(probToDrop)) // Lose a character
+ {
+ // Additional chance to change to dot for flavor instead of full drop
+ if (random.Prob(ent.Comp.ProbToCorruptDotFromPower))
+ {
+ outMsg.Append('.');
+ }
+ }
+ else // Character is safe
+ {
+ outMsg.Append(letter);
+ }
+ }
+ return outMsg.ToString();
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/SharedMeleeSpeechSystem.cs b/Content.Shared/Speech/EntitySystems/SharedMeleeSpeechSystem.cs
deleted file mode 100644
index 21841ef0ac..0000000000
--- a/Content.Shared/Speech/EntitySystems/SharedMeleeSpeechSystem.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Speech.EntitySystems;
-
-public abstract class SharedMeleeSpeechSystem : EntitySystem
-{
-}
diff --git a/Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs b/Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs
deleted file mode 100644
index b53a7743ee..0000000000
--- a/Content.Shared/Speech/EntitySystems/SharedSlurredSystem.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Content.Shared.Speech.Components;
-using Content.Shared.StatusEffect;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Speech.EntitySystems;
-
-public abstract class SharedSlurredSystem : RelayAccentSystem
-{
- public static readonly EntProtoId Stutter = "StatusEffectSlurred";
-
- public virtual void DoSlur(EntityUid uid, TimeSpan time, StatusEffectsComponent? status = null) { }
-}
diff --git a/Content.Shared/Speech/EntitySystems/SharedSpeakOnActionSystem.cs b/Content.Shared/Speech/EntitySystems/SharedSpeakOnActionSystem.cs
deleted file mode 100644
index 075d336cc4..0000000000
--- a/Content.Shared/Speech/EntitySystems/SharedSpeakOnActionSystem.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Shared.Chasm;
-using Content.Shared.Speech.Components;
-using Content.Shared.Speech.Muting;
-using System;
-
-namespace Content.Shared.Speech.EntitySystems;
-
-///
-/// Once the chat refactor has happened, move the code from
-///
-/// to here and set this class to sealed.
-///
-public abstract class SharedSpeakOnActionSystem : EntitySystem;
diff --git a/Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs b/Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs
deleted file mode 100644
index 06871aad09..0000000000
--- a/Content.Shared/Speech/EntitySystems/SharedStutteringSystem.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Content.Shared.Speech.Components;
-using Content.Shared.StatusEffectNew;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Speech.EntitySystems;
-
-public abstract partial class SharedStutteringSystem : RelayAccentSystem
-{
- public static readonly EntProtoId Stuttering = "StatusEffectStutter";
-
- [Dependency] protected StatusEffectsSystem Status = default!;
-
- // For code in shared... I imagine we ain't getting accent prediction anytime soon so let's not bother.
- public virtual void DoStutter(EntityUid uid, TimeSpan time, bool refresh)
- {
- }
-
- public virtual void DoRemoveStutterTime(EntityUid uid, TimeSpan timeRemoved)
- {
- }
-
- public virtual void DoRemoveStutter(EntityUid uid)
- {
- }
-}
diff --git a/Content.Server/Speech/EntitySystems/SkeletonAccentSystem.cs b/Content.Shared/Speech/EntitySystems/SkeletonAccentSystem.cs
similarity index 52%
rename from Content.Server/Speech/EntitySystems/SkeletonAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/SkeletonAccentSystem.cs
index 655ad4aec5..852f18ee79 100644
--- a/Content.Server/Speech/EntitySystems/SkeletonAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/SkeletonAccentSystem.cs
@@ -1,20 +1,25 @@
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class SkeletonAccentSystem : RelayAccentSystem
{
+ [Dependency] private IGameTiming _timing = default!;
[Dependency] private IRobustRandom _random = default!;
[Dependency] private ReplacementAccentSystem _replacement = default!;
- [GeneratedRegex(@"(?? ent = null)
{
+ var random = ent.HasValue
+ ? SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value))
+ : _random;
+
// Order:
// Do character manipulations first
// Then direct word/phrase replacements
@@ -24,16 +29,15 @@ public sealed partial class SkeletonAccentSystem : RelayAccentSystem bone ; lonely -> bonely; clone -> clone (remains unchanged).
- msg = BoneRegex().Replace(msg, "bone");
+ msg = BoneRegex.Replace(msg, "bone");
// apply word replacements
- msg = _replacement.ApplyReplacements(msg, "skeleton");
+ msg = _replacement.ApplyReplacements(msg, "skeleton", ent?.Owner);
// Suffix:
- if (_random.Prob(ent.HasValue ? ent.Value.Comp.ackChance : 0.3f))
- {
- msg += (" " + Loc.GetString("skeleton-suffix")); // e.g. "We only want to socialize. ACK ACK!"
- }
+ if (random.Prob(ent.HasValue ? ent.Value.Comp.AckChance : 0.3f))
+ msg += " " + Loc.GetString("skeleton-suffix"); // e.g. "We only want to socialize. ACK ACK!"
+
return msg;
}
}
diff --git a/Content.Server/Speech/EntitySystems/SlowAccentSystem.cs b/Content.Shared/Speech/EntitySystems/SlowAccentSystem.cs
similarity index 87%
rename from Content.Server/Speech/EntitySystems/SlowAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/SlowAccentSystem.cs
index a92839c213..7c8d86ba02 100644
--- a/Content.Server/Speech/EntitySystems/SlowAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/SlowAccentSystem.cs
@@ -1,8 +1,7 @@
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class SlowAccentSystem : RelayAccentSystem
{
diff --git a/Content.Server/Speech/EntitySystems/SlurredSystem.cs b/Content.Shared/Speech/EntitySystems/SlurredSystem.cs
similarity index 53%
rename from Content.Server/Speech/EntitySystems/SlurredSystem.cs
rename to Content.Shared/Speech/EntitySystems/SlurredSystem.cs
index a15cc22c14..a3b7769d06 100644
--- a/Content.Server/Speech/EntitySystems/SlurredSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/SlurredSystem.cs
@@ -1,63 +1,34 @@
using System.Text;
-using Content.Shared.Speech;
+using Content.Shared.Random.Helpers;
using Content.Shared.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffectNew.Components;
using Robust.Shared.Random;
using Robust.Shared.Timing;
+using Robust.Shared.Prototypes;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
-public sealed partial class SlurredSystem : SharedSlurredSystem
+public sealed partial class SlurredSystem : RelayAccentSystem
{
+ public static readonly EntProtoId Stutter = "StatusEffectSlurred";
+
[Dependency] private StatusEffectsSystem _status = default!;
- [Dependency] private IRobustRandom _random = default!;
[Dependency] private IGameTiming _timing = default!;
- ///
- /// Divisor applied to total seconds used to get the odds of slurred speech occuring.
- ///
- private const float SlurredModifier = 1100f;
-
- ///
- /// Minimum amount of time on the slurred accent for it to start taking effect.
- ///
- private const float SlurredThreshold = 80f;
-
- ///
- /// Slur chance scales with the time remaining on any status effect with the SlurredAccentComponent.
- /// Typically, this is equivalent to "drunkenness" on the DrunkStatusEffect
- ///
- private float GetProbabilityScale(EntityUid uid)
+ public override string Accentuate(string message, Entity? ent = null)
{
- if (!TryComp(uid, out var component) || component.AppliedTo == null)
- return 0;
+ if (ent == null)
+ return message;
- if (!_status.TryGetMaxTime(component.AppliedTo.Value, out var time))
- return 0;
-
- // This is a magic number. Why this value? No clue it was made 3 years before I refactored this.
- var magic = time.Item2 == null ? SlurredModifier : (float) (time.Item2 - _timing.CurTime).Value.TotalSeconds - SlurredThreshold;
-
- return Math.Clamp(magic / SlurredModifier, 0f, 1f);
- }
-
- // TODO: Make this accent possible to use without a status effect
- protected override void OnAccent(Entity ent, ref AccentGetEvent args)
- {
- var scale = GetProbabilityScale(ent);
- args.Message = Accentuate(args.Message, scale);
- }
-
- private string Accentuate(string message, float scale)
- {
+ var scale = GetProbabilityScale(ent.Value);
+ var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value));
var sb = new StringBuilder();
// This is pretty much ported from TG.
foreach (var character in message)
{
- if (_random.Prob(scale / 3f))
+ if (random.Prob(scale / 3f))
{
var lower = char.ToLowerInvariant(character);
var newString = lower switch
@@ -73,7 +44,7 @@ public sealed partial class SlurredSystem : SharedSlurredSystem
sb.Append(newString);
}
- if (_random.Prob(scale / 20f))
+ if (random.Prob(scale / 20f))
{
if (character == ' ')
{
@@ -86,13 +57,13 @@ public sealed partial class SlurredSystem : SharedSlurredSystem
}
}
- if (!_random.Prob(scale * 3/20))
+ if (!random.Prob(scale * 3 / 20))
{
sb.Append(character);
continue;
}
- var next = _random.Next(1, 3) switch
+ var next = random.Next(1, 3) switch
{
1 => "'",
2 => $"{character}{character}",
@@ -104,4 +75,24 @@ public sealed partial class SlurredSystem : SharedSlurredSystem
return sb.ToString();
}
+
+ ///
+ /// Slur chance scales with the time remaining on any status effect with the SlurredAccentComponent.
+ /// Typically, this is equivalent to "drunkenness" on the DrunkStatusEffect
+ ///
+ private float GetProbabilityScale(Entity ent)
+ {
+ if (!TryComp(ent, out var component) || component.AppliedTo == null)
+ return 1f;
+
+ if (!_status.TryGetMaxTime(component.AppliedTo.Value, out var time))
+ return 1f;
+
+ // This is a magic number. Why this value? No clue it was made 3 years before I refactored this.
+ var magic = time.Item2 == null
+ ? ent.Comp.SlurredModifier
+ : (float)(time.Item2 - _timing.CurTime).Value.TotalSeconds - ent.Comp.SlurredThreshold;
+
+ return Math.Clamp(magic / ent.Comp.SlurredModifier, 0f, 1f);
+ }
}
diff --git a/Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs b/Content.Shared/Speech/EntitySystems/SouthernAccentSystem.cs
similarity index 89%
rename from Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/SouthernAccentSystem.cs
index ef56a0264a..b77e465628 100644
--- a/Content.Server/Speech/EntitySystems/SouthernAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/SouthernAccentSystem.cs
@@ -1,8 +1,7 @@
using System.Text.RegularExpressions;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed partial class SouthernAccentSystem : RelayAccentSystem
{
@@ -17,7 +16,7 @@ public sealed partial class SouthernAccentSystem : RelayAccentSystem? ent = null)
{
- message = _replacement.ApplyReplacements(message, "southern");
+ message = _replacement.ApplyReplacements(message, "southern", ent?.Owner);
//They shoulda started runnin' an' hidin' from me!
message = RegexLowerIng.Replace(message, "in'");
diff --git a/Content.Server/Speech/EntitySystems/SpanishAccentSystem.cs b/Content.Shared/Speech/EntitySystems/SpanishAccentSystem.cs
similarity index 94%
rename from Content.Server/Speech/EntitySystems/SpanishAccentSystem.cs
rename to Content.Shared/Speech/EntitySystems/SpanishAccentSystem.cs
index e68d23a190..3d77840656 100644
--- a/Content.Server/Speech/EntitySystems/SpanishAccentSystem.cs
+++ b/Content.Shared/Speech/EntitySystems/SpanishAccentSystem.cs
@@ -1,9 +1,8 @@
using System.Text.RegularExpressions;
using System.Text;
-using Content.Server.Speech.Components;
-using Content.Shared.Speech.EntitySystems;
+using Content.Shared.Speech.Components;
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
public sealed class SpanishAccentSystem : RelayAccentSystem
{
@@ -50,6 +49,7 @@ public sealed class SpanishAccentSystem : RelayAccentSystem ' '
});
}
+
if (toInsert.Length == 0)
msg.Append(s);
else
diff --git a/Content.Shared/Speech/EntitySystems/SpeakOnActionSystem.cs b/Content.Shared/Speech/EntitySystems/SpeakOnActionSystem.cs
new file mode 100644
index 0000000000..6880ef9edf
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/SpeakOnActionSystem.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Speech.Components;
+using Content.Shared.Actions.Events;
+using Content.Shared.Chat;
+using Content.Shared.Speech.Muting;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class SpeakOnActionSystem : EntitySystem
+{
+ [Dependency] private SharedChatSystem _chat = default!;
+
+ [SubscribeLocalEvent]
+ private void OnActionPerformed(Entity ent, ref ActionPerformedEvent args)
+ {
+ var user = args.Performer;
+
+ // If we can't speak, we can't speak.
+ if (!HasComp(user) || HasComp(user))
+ return;
+
+ if (string.IsNullOrWhiteSpace(ent.Comp.Sentence))
+ return;
+
+ _chat.TrySendInGameICMessage(user, Loc.GetString(ent.Comp.Sentence), InGameICChatType.Speak, false);
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/SpeechNoiseSystem.cs b/Content.Shared/Speech/EntitySystems/SpeechNoiseSystem.cs
new file mode 100644
index 0000000000..ea5f99e66f
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/SpeechNoiseSystem.cs
@@ -0,0 +1,70 @@
+using Content.Shared.Chat;
+using Content.Shared.Random.Helpers;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class SpeechSoundSystem : EntitySystem
+{
+ [Dependency] private IGameTiming _gameTiming = default!;
+ [Dependency] private SharedAudioSystem _audio = default!;
+
+ [SubscribeLocalEvent]
+ private void OnEntitySpoke(Entity ent, ref EntitySpokeEvent args)
+ {
+ if (ent.Comp.SpeechSounds == null)
+ return;
+
+ var currentTime = _gameTiming.CurTime;
+ var cooldown = TimeSpan.FromSeconds(ent.Comp.SoundCooldownTime);
+
+ // Ensure more than the cooldown time has passed since last speaking
+ if (currentTime - ent.Comp.LastTimeSoundPlayed < cooldown)
+ return;
+
+ var sound = GetSpeechSound(ent, args.Message);
+ ent.Comp.LastTimeSoundPlayed = currentTime;
+ _audio.PlayPredicted(sound, ent, ent);
+ }
+
+ ///
+ /// Gets the speech sound for a message.
+ ///
+ public SoundSpecifier? GetSpeechSound(Entity ent, string message)
+ {
+ if (ent.Comp.SpeechSounds == null)
+ return null;
+
+ // Play speech sound
+ var prototype = ProtoMan.Index(ent.Comp.SpeechSounds);
+
+ // Different sounds for ask/exclaim based on last character
+ var contextSound = message[^1] switch
+ {
+ '?' => prototype.AskSound,
+ '!' => prototype.ExclaimSound,
+ _ => prototype.SaySound
+ };
+
+ // Use exclaim sound if most characters are uppercase.
+ var uppercaseCount = 0;
+ foreach (var t in message)
+ {
+ if (char.IsUpper(t))
+ uppercaseCount++;
+ }
+
+ if (uppercaseCount > message.Length / 2)
+ {
+ contextSound = prototype.ExclaimSound;
+ }
+
+ var random = SharedRandomExtensions.PredictedRandom(_gameTiming, GetNetEntity(ent));
+ var scale = (float)random.NextGaussian(1, prototype.Variation);
+ contextSound.Params = ent.Comp.AudioParams.WithPitchScale(scale);
+ return contextSound;
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/StutteringSystem.cs b/Content.Shared/Speech/EntitySystems/StutteringSystem.cs
new file mode 100644
index 0000000000..1a0624372b
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/StutteringSystem.cs
@@ -0,0 +1,73 @@
+using System.Text;
+using System.Text.RegularExpressions;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
+using Content.Shared.StatusEffectNew;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class StutteringSystem : RelayAccentSystem
+{
+ public static readonly EntProtoId StutterEffect = "StatusEffectSlurred";
+
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private StatusEffectsSystem _statusEffects = default!;
+
+ // Regex of characters to stutter.
+ private static readonly Regex Stutter = new("[b-df-hj-np-tv-wxyz]",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ public override string Accentuate(string message, Entity? ent = null)
+ {
+ if (ent == null)
+ return message;
+
+ var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Value));
+ var length = message.Length;
+ var finalMessage = new StringBuilder();
+
+ for (var i = 0; i < length; i++)
+ {
+ var newLetter = message[i].ToString();
+ if (Stutter.IsMatch(newLetter) && random.Prob(ent.Value.Comp.MatchRandomProb))
+ {
+ if (random.Prob(ent.Value.Comp.FourRandomProb))
+ {
+ newLetter = $"{newLetter}-{newLetter}-{newLetter}-{newLetter}";
+ }
+ else if (random.Prob(ent.Value.Comp.ThreeRandomProb))
+ {
+ newLetter = $"{newLetter}-{newLetter}-{newLetter}";
+ }
+ else if (random.Prob(ent.Value.Comp.CutRandomProb))
+ {
+ newLetter = "";
+ }
+ else
+ {
+ newLetter = $"{newLetter}-{newLetter}";
+ }
+ }
+
+ finalMessage.Append(newLetter);
+ }
+
+ return finalMessage.ToString();
+ }
+
+ ///
+ /// Applies or refreshes the stuttering speech status effect on an entity.
+ ///
+ [PublicAPI]
+ public void DoStutter(EntityUid uid, TimeSpan time, bool refresh)
+ {
+ if (refresh)
+ _statusEffects.TryUpdateStatusEffectDuration(uid, StutterEffect, time);
+ else
+ _statusEffects.TryAddStatusEffectDuration(uid, StutterEffect, time);
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/UnblockableSpeechSystem.cs b/Content.Shared/Speech/EntitySystems/UnblockableSpeechSystem.cs
new file mode 100644
index 0000000000..90580e593a
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/UnblockableSpeechSystem.cs
@@ -0,0 +1,13 @@
+using Content.Shared.Speech.Components;
+using Content.Shared.Chat;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class UnblockableSpeechSystem : EntitySystem
+{
+ [SubscribeLocalEvent]
+ private void OnCheck(Entity ent, ref CheckIgnoreSpeechBlockerEvent args)
+ {
+ args.IgnoreBlocker = true;
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/VocalSystem.cs b/Content.Shared/Speech/EntitySystems/VocalSystem.cs
new file mode 100644
index 0000000000..f241debe1e
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/VocalSystem.cs
@@ -0,0 +1,124 @@
+using Content.Shared.Actions;
+using Content.Shared.Chat;
+using Content.Shared.Chat.Prototypes;
+using Content.Shared.Humanoid;
+using Content.Shared.Random.Helpers;
+using Content.Shared.Speech.Components;
+using JetBrains.Annotations;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class VocalSystem : EntitySystem
+{
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private SharedAudioSystem _audio = default!;
+ [Dependency] private SharedChatSystem _chat = default!;
+ [Dependency] private SharedActionsSystem _actions = default!;
+
+ [SubscribeLocalEvent]
+ private void OnMapInit(Entity ent, ref MapInitEvent args)
+ {
+ // try to add scream action when vocal comp added
+ _actions.AddAction(ent.Owner, ref ent.Comp.EmoteActionEntity, ent.Comp.EmoteAction);
+ Dirty(ent);
+ }
+
+ [SubscribeLocalEvent]
+ private void OnShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ // remove scream action when component removed
+ _actions.RemoveAction(ent.Owner, ent.Comp.EmoteActionEntity);
+ Dirty(ent);
+ }
+
+ [SubscribeLocalEvent]
+ private void OnVoiceChanged(Entity ent, ref VoiceChangedEvent args)
+ {
+ LoadSounds(ent, args.NewVoice);
+ }
+
+ [SubscribeLocalEvent]
+ private void OnEmote(Entity ent, ref EmoteEvent args)
+ {
+ if (args.Handled || !args.Emote.Category.HasFlag(EmoteCategory.Vocal))
+ return;
+
+ // snowflake case for wilhelm scream easter egg
+ if (args.Emote == ent.Comp.ScreamId)
+ {
+ args.Handled = TryPlayScreamSound(ent, GetEntity(args.Source));
+ return;
+ }
+
+ if (ent.Comp.EmoteSounds is not { } sounds)
+ return;
+
+ // just play regular sound based on emote proto
+ args.Handled = _chat.TryPlayEmoteSound(ent.Owner, ProtoMan.Index(sounds), args.Emote);
+ }
+
+ [SubscribeLocalEvent]
+ private void OnEmoteAction(Entity ent, ref EmoteActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ _chat.TryEmoteWithChat(ent.Owner, args.Emote);
+ args.Handled = true;
+ }
+
+ ///
+ /// Copy this component's datafields from one entity to another.
+ /// This can't use CopyComp because of the ScreamActionEntity DataField, which should not be copied.
+ ///
+ [PublicAPI]
+ public void CopyComponent(Entity source, EntityUid target)
+ {
+ if (!Resolve(source, ref source.Comp))
+ return;
+
+ var targetComp = EnsureComp(target);
+ targetComp.ScreamId = source.Comp.ScreamId;
+ targetComp.Wilhelm = source.Comp.Wilhelm;
+ targetComp.WilhelmProbability = source.Comp.WilhelmProbability;
+ LoadSounds((target, targetComp));
+
+ Dirty(target, targetComp);
+ }
+
+ private bool TryPlayScreamSound(Entity ent, EntityUid user)
+ {
+ var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(ent.Owner), GetNetEntity(user));
+ if (random.Prob(ent.Comp.WilhelmProbability))
+ {
+ _audio.PlayPredicted(ent.Comp.Wilhelm, ent.Owner, user, ent.Comp.Wilhelm.Params);
+ return true;
+ }
+
+ if (ent.Comp.EmoteSounds is not { } sounds)
+ return false;
+
+ return _chat.TryPlayEmoteSound(ent.Owner, ProtoMan.Index(sounds), ent.Comp.ScreamId);
+ }
+
+ ///
+ /// This only works on Humanoids. Mobs should have emoteSounds on set directly instead.
+ ///
+ private void LoadSounds(Entity ent, ProtoId? protoId = null)
+ {
+ if (!TryComp(ent.Owner, out var humanoid))
+ return;
+
+ protoId ??= humanoid.Voice;
+
+ if (!ProtoMan.HasIndex(protoId))
+ return;
+
+ ent.Comp.EmoteSounds = protoId;
+ Dirty(ent);
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/VoiceOverrideSystem.cs b/Content.Shared/Speech/EntitySystems/VoiceOverrideSystem.cs
new file mode 100644
index 0000000000..a1d6f291cf
--- /dev/null
+++ b/Content.Shared/Speech/EntitySystems/VoiceOverrideSystem.cs
@@ -0,0 +1,17 @@
+using Content.Shared.Chat;
+using Content.Shared.Speech.Components;
+
+namespace Content.Shared.Speech.EntitySystems;
+
+public sealed partial class VoiceOverrideSystem : EntitySystem
+{
+ [SubscribeLocalEvent]
+ private void OnTransformSpeakerName(Entity ent, ref TransformSpeakerNameEvent args)
+ {
+ if (!ent.Comp.Enabled)
+ return;
+
+ args.VoiceName = ent.Comp.NameOverride ?? args.VoiceName;
+ args.SpeechVerb = ent.Comp.SpeechVerbOverride ?? args.SpeechVerb;
+ }
+}
diff --git a/Content.Shared/Speech/EntitySystems/SharedListenWireAction.cs b/Content.Shared/Speech/ListenWireAction.cs
similarity index 100%
rename from Content.Shared/Speech/EntitySystems/SharedListenWireAction.cs
rename to Content.Shared/Speech/ListenWireAction.cs
diff --git a/Content.Server/Speech/Prototypes/ReplacementAccentPrototype.cs b/Content.Shared/Speech/Prototypes/ReplacementAccentPrototype.cs
similarity index 61%
rename from Content.Server/Speech/Prototypes/ReplacementAccentPrototype.cs
rename to Content.Shared/Speech/Prototypes/ReplacementAccentPrototype.cs
index cde4baeb2f..dc541e0a1b 100644
--- a/Content.Server/Speech/Prototypes/ReplacementAccentPrototype.cs
+++ b/Content.Shared/Speech/Prototypes/ReplacementAccentPrototype.cs
@@ -1,6 +1,6 @@
using Robust.Shared.Prototypes;
-namespace Content.Server.Speech.Prototypes;
+namespace Content.Shared.Speech.Prototypes;
[Prototype("accent")]
public sealed partial class ReplacementAccentPrototype : IPrototype
@@ -11,14 +11,14 @@ public sealed partial class ReplacementAccentPrototype : IPrototype
public string ID { get; private set; } = default!;
///
- /// If this array is non-null, the full text of anything said will be randomly replaced with one of these words.
+ /// If this array is non-null, the full text of anything said will be randomly replaced with one of these words.
///
[DataField]
public string[]? FullReplacements;
///
- /// If this dictionary is non-null and is null, any keys surrounded by spaces
- /// (words) will be replaced by the value, attempting to intelligently keep capitalization.
+ /// If this dictionary is non-null and is null, any keys surrounded by spaces
+ /// (words) will be replaced by the value, attempting to intelligently keep capitalization.
///
[DataField]
public Dictionary? WordReplacements;
diff --git a/Content.Shared/Speech/EntitySystems/SharedSpeechWireAction.cs b/Content.Shared/Speech/SpeechWireAction.cs
similarity index 100%
rename from Content.Shared/Speech/EntitySystems/SharedSpeechWireAction.cs
rename to Content.Shared/Speech/SpeechWireAction.cs
From b92ce0002643095d940385ef5d95c87fc1aff960 Mon Sep 17 00:00:00 2001
From: Whatstone <166147148+whatston3@users.noreply.github.com>
Date: Sat, 25 Jul 2026 18:45:49 -0400
Subject: [PATCH 05/35] Bugfix: Check UIDs in TryMergeStacks (#44914)
Check entity UIDs in TryMergeStacks
---
Content.Shared/Stacks/SharedStackSystem.API.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Content.Shared/Stacks/SharedStackSystem.API.cs b/Content.Shared/Stacks/SharedStackSystem.API.cs
index 3459f4f0dc..96a13e3ac2 100644
--- a/Content.Shared/Stacks/SharedStackSystem.API.cs
+++ b/Content.Shared/Stacks/SharedStackSystem.API.cs
@@ -120,7 +120,7 @@ public abstract partial class SharedStackSystem
{
transferred = 0;
- if (donor == recipient)
+ if (donor.Owner == recipient.Owner)
return false;
// Recipient is being torn down, don't give it anything.
From 19dbe485f1cd112b49ee93528071ca1419f5f08c Mon Sep 17 00:00:00 2001
From: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Date: Sun, 26 Jul 2026 00:34:06 -0700
Subject: [PATCH 06/35] Disposals dynamic power consumption (#44113)
* init
* trolled by my own test
---
.../Disposal/Unit/SharedDisposalUnitSystem.cs | 11 +++++++++--
.../Entities/Structures/Piping/Disposal/units.yml | 5 +++++
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs
index 1e8fe47fe3..cc43ea889e 100644
--- a/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs
+++ b/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs
@@ -55,6 +55,7 @@ public abstract partial class SharedDisposalUnitSystem : EntitySystem
[Dependency] private SharedMapSystem _map = default!;
[Dependency] private SharedTransformSystem _xform = default!;
[Dependency] private INetManager _net = default!;
+ [Dependency] private SharedPowerStateSystem _powerState = default!;
public override void Initialize()
{
@@ -259,9 +260,15 @@ public abstract partial class SharedDisposalUnitSystem : EntitySystem
ent.Comp.State = state;
- if (state == DisposalsPressureState.Ready)
+ switch (state)
{
- ent.Comp.NextPressurized = TimeSpan.Zero;
+ case DisposalsPressureState.Ready:
+ ent.Comp.NextPressurized = TimeSpan.Zero;
+ _powerState.SetWorkingState(ent.Owner, false);
+ break;
+ case DisposalsPressureState.Pressurizing:
+ _powerState.SetWorkingState(ent.Owner, true);
+ break;
}
RecalculateFlushTime(ent, true);
diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
index f9525d9989..02fd03d95e 100644
--- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
+++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
@@ -114,6 +114,11 @@
- type: StaticPrice
price: 70
- type: PowerSwitch
+ - type: ApcPowerReceiver
+ powerLoad: 5
+ - type: PowerState
+ idlePowerDraw: 5
+ workingPowerDraw: 1000
- type: entity
id: DisposalUnit
From 2e9c4c01032d9341383458d30bbe13426f322cba Mon Sep 17 00:00:00 2001
From: Princess Cheeseballs
<66055347+Princess-Cheeseballs@users.noreply.github.com>
Date: Sun, 26 Jul 2026 00:56:44 -0700
Subject: [PATCH 07/35] IHeatContainers for TemperatureComponent (#42785)
* Hope I'm not forgetting anything...
* They call me the sender the way I'm sending it.
* remove commented out YAML
* last of the slop
* fix test fails
* first batch of test fails
* no more test fails
* make cryo pods work again.
* Make cryo work better-er
* I didn't like that a firesuit helmet was so effective on its own so I decided to be a dick about it.
* review.com
* oop
* actually real quick do this
* fix YAML issues
* IHeatContainer my beloved <3
* borger
* not networked :(
* jackets
* burgeewqjfrwjkhaskfjfsahjkfsahjkfsahjkfsajhkfsa
* utter yaml nonsense
* phishing
---------
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
---
.../Medical/Cryogenics/CryoPodWindow.xaml.cs | 2 +-
.../Interaction/EdgeClobbering.cs | 3 +-
.../Atmos/Components/AtmosExposedComponent.cs | 29 +++-
.../Atmos/EntitySystems/AtmosphereSystem.cs | 16 +--
.../Atmos/EntitySystems/FlammableSystem.cs | 3 +-
Content.Server/Atmos/Rotting/RottingSystem.cs | 2 +-
.../Body/Systems/ThermalRegulatorSystem.cs | 16 +--
.../ConstructionSystem.Interactions.cs | 13 +-
.../Kitchen/EntitySystems/MicrowaveSystem.cs | 3 +-
.../Medical/HealthAnalyzerSystem.cs | 2 +-
.../NPC/Systems/NPCUtilitySystem.cs | 2 +-
.../ContainerTemperatureComponent.cs | 14 +-
.../InternalTemperatureComponent.cs | 43 +++---
.../TemperatureProtectionComponent.cs | 10 +-
.../Temperature/Systems/EntityHeaterSystem.cs | 4 +-
.../Systems/TemperatureSystem.Damage.cs | 16 +--
.../Temperature/Systems/TemperatureSystem.cs | 92 ++++--------
Content.Shared/Atmos/AtmosExposedEvents.cs | 9 +-
.../TemperatureEntityConditionSystem.cs | 2 +-
.../AdjustTemperatureEntityEffectsSystem.cs | 2 +-
.../Inventory/InventorySystem.Relay.cs | 2 +-
.../Cryogenics/InsideCryoPodComponent.cs | 6 +
.../Cryogenics/SharedInsideCryoPodSystem.cs | 20 +--
.../Components/EntityHeaterComponent.cs | 2 +-
.../Components/TemperatureComponent.cs | 36 +++--
.../Systems/SharedEntityHeaterSystem.cs | 4 +-
.../Systems/SharedTemperatureSystem.cs | 134 ++++++++++++++++--
.../Temperature/TemperatureEvents.cs | 38 ++---
Resources/Prototypes/Atmospherics/gases.yml | 6 +-
Resources/Prototypes/Body/Species/moth.yml | 3 -
.../Prototypes/Body/Species/reptilian.yml | 3 -
.../Prototypes/Body/Species/vulpkanin.yml | 5 -
Resources/Prototypes/Body/species_base.yml | 4 +-
.../Clothing/Head/base_clothinghead.yml | 19 ++-
.../Entities/Clothing/Head/eva-helmets.yml | 3 -
.../Clothing/Head/hardsuit-helmets.yml | 13 +-
.../Entities/Clothing/Head/helmets.yml | 10 +-
.../Entities/Clothing/Head/hoods.yml | 5 +-
.../Clothing/Neck/base_clothingneck.yml | 5 +-
.../Entities/Clothing/OuterClothing/armor.yml | 5 +-
.../OuterClothing/base_clothingouter.yml | 100 ++++++-------
.../Entities/Clothing/OuterClothing/coats.yml | 7 +-
.../Clothing/OuterClothing/hardsuits.yml | 19 +--
.../Entities/Clothing/OuterClothing/misc.yml | 12 +-
.../Clothing/OuterClothing/softsuits.yml | 19 +--
.../Clothing/OuterClothing/specific.yml | 5 +-
.../Entities/Clothing/OuterClothing/suits.yml | 39 +----
.../Clothing/OuterClothing/wintercoats.yml | 7 +-
.../Clothing/Shoes/base_clothingshoes.yml | 6 +-
.../Entities/Clothing/base_clothing.yml | 28 ++++
.../Prototypes/Entities/Mobs/NPCs/animals.yml | 9 --
.../Prototypes/Entities/Mobs/NPCs/scurret.yml | 3 -
.../Prototypes/Entities/Mobs/NPCs/xeno.yml | 4 -
Resources/Prototypes/Entities/Mobs/base.yml | 5 -
.../Entities/Objects/Consumable/Food/egg.yml | 12 +-
.../Entities/Objects/Consumable/Food/meat.yml | 19 +--
.../Entities/Objects/Misc/ice_crust.yml | 3 +
.../Entities/Objects/Misc/kudzu.yml | 45 +++---
.../Entities/Objects/Misc/spaceshroom.yml | 7 +-
.../Structures/Machines/Medical/cryo_pod.yml | 2 +-
.../Entities/Structures/spider_web.yml | 7 -
Resources/Prototypes/Reagents/medicine.yml | 34 +++--
62 files changed, 493 insertions(+), 505 deletions(-)
diff --git a/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs
index 76b1c876d3..3faa03512d 100644
--- a/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs
+++ b/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs
@@ -181,7 +181,7 @@ public sealed partial class CryoPodWindow : FancyWindow
}
// Status checklist
- const float fallbackTemperatureRequirement = 213;
+ const float fallbackTemperatureRequirement = Atmospherics.T0C;
var hasTemperatureCheck = (hasGas && hasCorrectTemperature
&& (lowestTempRequirement != null || msg.GasMix.Temperature < fallbackTemperatureRequirement));
var hasChemicals = (hasBeaker && !isBeakerEmpty);
diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/EdgeClobbering.cs b/Content.IntegrationTests/Tests/Construction/Interaction/EdgeClobbering.cs
index 9f578148cf..0f8f48663c 100644
--- a/Content.IntegrationTests/Tests/Construction/Interaction/EdgeClobbering.cs
+++ b/Content.IntegrationTests/Tests/Construction/Interaction/EdgeClobbering.cs
@@ -41,7 +41,8 @@ public sealed class EdgeClobbering : InteractionTest
var sTarget = SEntMan.GetEntity(Target!.Value);
await InteractUsing(Screw, false);
- SEntMan.EventBus.RaiseLocalEvent(sTarget, new OnTemperatureChangeEvent(0f, 0f, 0f));
+ var ev = new TemperatureChangedEvent(0f, 0f);
+ SEntMan.EventBus.RaiseLocalEvent(sTarget, ref ev);
await AwaitDoAfters();
Assert.That(SEntMan.GetComponent(sTarget).Node, Is.EqualTo("C"));
diff --git a/Content.Server/Atmos/Components/AtmosExposedComponent.cs b/Content.Server/Atmos/Components/AtmosExposedComponent.cs
index 721aae8f4f..0f40e998ea 100644
--- a/Content.Server/Atmos/Components/AtmosExposedComponent.cs
+++ b/Content.Server/Atmos/Components/AtmosExposedComponent.cs
@@ -1,11 +1,26 @@
-namespace Content.Server.Atmos.Components
+using Content.Shared.Temperature.Components;
+
+namespace Content.Server.Atmos.Components;
+
+// not if i get there first - Flipp
+///
+/// Represents that entity can be exposed to Atmos
+///
+[RegisterComponent, AutoGenerateComponentPause]
+public sealed partial class AtmosExposedComponent : Component
{
- // not if i get there first - Flipp
///
- /// Represents that entity can be exposed to Atmos
+ /// Last real time we were exposed to the atmosphere!
///
- [RegisterComponent]
- public sealed partial class AtmosExposedComponent : Component
- {
- }
+ [DataField, AutoPausedField]
+ public TimeSpan LastExposure = TimeSpan.Zero;
+
+ ///
+ /// The amount of surface area this entity has exposed to the atmosphere in m^2.
+ /// The human body has about 2m^2 of surface area so we use that as a default.
+ /// This is multiplied by the
+ /// Source: https://pmc.ncbi.nlm.nih.gov/articles/PMC8953946/
+ ///
+ [DataField]
+ public float ExposedArea = 2f;
}
diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs
index 400b778cac..dd09d95ed2 100644
--- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs
@@ -48,7 +48,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
[Dependency] private EntityQuery _firelockQuery = default!;
private const float ExposedUpdateDelay = 1f;
- private float _exposedTimer = 0f;
private HashSet _entSet = new();
@@ -103,24 +102,23 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
UpdateProcessing(frameTime);
UpdateHighPressure(frameTime);
- _exposedTimer += frameTime;
-
- if (_exposedTimer < ExposedUpdateDelay)
- return;
+ var delay = TimeSpan.FromSeconds(ExposedUpdateDelay);
var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out _, out var transform))
+ while (query.MoveNext(out var uid, out var exposed, out var transform))
{
+ if (exposed.LastExposure + delay > _gameTiming.CurTime)
+ continue;
+
var air = GetContainingMixture((uid, transform));
if (air == null)
continue;
- var updateEvent = new AtmosExposedUpdateEvent(transform.Coordinates, air, transform);
+ var updateEvent = new AtmosExposedUpdateEvent(transform.Coordinates, air, transform, ExposedUpdateDelay, exposed.ExposedArea);
RaiseLocalEvent(uid, ref updateEvent);
+ exposed.LastExposure = _gameTiming.CurTime;
}
-
- _exposedTimer -= ExposedUpdateDelay;
}
private void CacheDecals()
diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
index 137da5f10d..490e7ae845 100644
--- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
@@ -453,8 +453,7 @@ namespace Content.Server.Atmos.EntitySystems
var source = EnsureComp(uid);
_ignitionSourceSystem.SetIgnited((uid, source));
- if (TryComp(uid, out TemperatureComponent? temp))
- _temperatureSystem.ChangeHeat(uid, 12500 * flammable.FireStacks, false, temp);
+ _temperatureSystem.ChangeHeat(uid, 12500 * flammable.FireStacks, false);
var ev = new GetFireProtectionEvent();
// let the thing on fire handle it
diff --git a/Content.Server/Atmos/Rotting/RottingSystem.cs b/Content.Server/Atmos/Rotting/RottingSystem.cs
index 46dd913bcf..cbad6f9a84 100644
--- a/Content.Server/Atmos/Rotting/RottingSystem.cs
+++ b/Content.Server/Atmos/Rotting/RottingSystem.cs
@@ -44,7 +44,7 @@ public sealed partial class RottingSystem : SharedRottingSystem
{
if (args.Handled)
return;
- args.Handled = component.CurrentTemperature < Atmospherics.T0C + 0.85f;
+ args.Handled = component.Temperature < Atmospherics.T0C + 0.85f;
}
///
diff --git a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs
index 581c308f56..e0b8899a17 100644
--- a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs
+++ b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs
@@ -55,10 +55,10 @@ public sealed partial class ThermalRegulatorSystem : EntitySystem
var totalMetabolismTempChange = ent.Comp1.MetabolismHeat - ent.Comp1.RadiatedHeat;
// implicit heat regulation
- var tempDiff = Math.Abs(ent.Comp2.CurrentTemperature - ent.Comp1.NormalBodyTemperature);
- var heatCapacity = _tempSys.GetHeatCapacity(ent, ent);
+ var tempDiff = Math.Abs(ent.Comp2.Temperature - ent.Comp1.NormalBodyTemperature);
+ var heatCapacity = ent.Comp2.HeatCapacity;
var targetHeat = tempDiff * heatCapacity;
- if (ent.Comp2.CurrentTemperature > ent.Comp1.NormalBodyTemperature)
+ if (ent.Comp2.Temperature > ent.Comp1.NormalBodyTemperature)
{
totalMetabolismTempChange -= Math.Min(targetHeat, ent.Comp1.ImplicitHeatRegulation);
}
@@ -67,10 +67,10 @@ public sealed partial class ThermalRegulatorSystem : EntitySystem
totalMetabolismTempChange += Math.Min(targetHeat, ent.Comp1.ImplicitHeatRegulation);
}
- _tempSys.ChangeHeat(ent, totalMetabolismTempChange, ignoreHeatResistance: true, ent);
+ _tempSys.ChangeHeat((ent, ent.Comp2), totalMetabolismTempChange, ignoreHeatResistance: true);
// recalc difference and target heat
- tempDiff = Math.Abs(ent.Comp2.CurrentTemperature - ent.Comp1.NormalBodyTemperature);
+ tempDiff = Math.Abs(ent.Comp2.Temperature - ent.Comp1.NormalBodyTemperature);
targetHeat = tempDiff * heatCapacity;
// if body temperature is not within comfortable, thermal regulation
@@ -78,19 +78,19 @@ public sealed partial class ThermalRegulatorSystem : EntitySystem
if (tempDiff < ent.Comp1.ThermalRegulationTemperatureThreshold)
return;
- if (ent.Comp2.CurrentTemperature > ent.Comp1.NormalBodyTemperature)
+ if (ent.Comp2.Temperature > ent.Comp1.NormalBodyTemperature)
{
if (!_actionBlockerSys.CanSweat(ent))
return;
- _tempSys.ChangeHeat(ent, -Math.Min(targetHeat, ent.Comp1.SweatHeatRegulation), ignoreHeatResistance: true, ent);
+ _tempSys.ChangeHeat((ent, ent.Comp2), -Math.Min(targetHeat, ent.Comp1.SweatHeatRegulation), ignoreHeatResistance: true);
}
else
{
if (!_actionBlockerSys.CanShiver(ent))
return;
- _tempSys.ChangeHeat(ent, Math.Min(targetHeat, ent.Comp1.ShiveringHeatRegulation), ignoreHeatResistance: true, ent);
+ _tempSys.ChangeHeat((ent, ent.Comp2), Math.Min(targetHeat, ent.Comp1.ShiveringHeatRegulation), ignoreHeatResistance: true);
}
}
}
diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs
index ca4865639c..f467e2c220 100644
--- a/Content.Server/Construction/ConstructionSystem.Interactions.cs
+++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs
@@ -42,7 +42,7 @@ namespace Content.Server.Construction
SubscribeLocalEvent(EnqueueEvent,
new []{typeof(AnchorableSystem), typeof(PryingSystem), typeof(WeldableSystem)},
new []{typeof(EncryptionKeySystem)});
- SubscribeLocalEvent(EnqueueEvent);
+ SubscribeLocalEvent(EnqueueRefEvent);
SubscribeLocalEvent(EnqueueEvent);
}
@@ -382,7 +382,7 @@ namespace Content.Server.Construction
case TemperatureConstructionGraphStep temperatureChangeStep:
{
- if (ev is not OnTemperatureChangeEvent)
+ if (ev is not TemperatureChangedEvent)
break;
// Some things, like microwaves, might need to block the temperature construction step from kicking in, or override it entirely.
@@ -400,7 +400,7 @@ namespace Content.Server.Construction
}
else if (TryComp(uid, out var tempComp))
{
- temp = tempComp.CurrentTemperature;
+ temp = tempComp.Temperature;
}
else
{
@@ -544,6 +544,13 @@ namespace Content.Server.Construction
#region Event Handlers
+ // Why does this system have you subscribe to an event,
+ // and then pass it through 5 layers of bullshit into a switch statement which tries to guess what event got passed?
+ private void EnqueueRefEvent(Entity entity, ref T args) where T : struct
+ {
+ EnqueueEvent(entity, entity.Comp, args);
+ }
+
///
/// Queues a directed event to be handled by construction on the next update tick.
/// Used as a handler for any events that construction can listen to.
diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs
index 774331080f..e2a16b2ebf 100644
--- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs
+++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs
@@ -179,8 +179,7 @@ namespace Content.Server.Kitchen.EntitySystems
var heatToAdd = time * component.BaseHeatMultiplier;
foreach (var entity in component.Storage.ContainedEntities)
{
- if (TryComp(entity, out var tempComp))
- _temperature.ChangeHeat(entity, heatToAdd * component.ObjectHeatMultiplier, false, tempComp);
+ _temperature.ChangeHeat(entity, heatToAdd * component.ObjectHeatMultiplier, false);
foreach (var (_, soln) in _solutionContainer.EnumerateSolutions(entity))
{
diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs
index cce1d4ad13..869cfc69d5 100644
--- a/Content.Server/Medical/HealthAnalyzerSystem.cs
+++ b/Content.Server/Medical/HealthAnalyzerSystem.cs
@@ -231,7 +231,7 @@ public sealed partial class HealthAnalyzerSystem : EntitySystem
var bodyTemperature = float.NaN;
if (TryComp(entity, out var temp))
- bodyTemperature = temp.CurrentTemperature;
+ bodyTemperature = temp.Temperature;
var bloodAmount = float.NaN;
var bleeding = false;
diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs
index c0e7db0120..859aac60a0 100644
--- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs
+++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs
@@ -384,7 +384,7 @@ public sealed partial class NPCUtilitySystem : EntitySystem
if (!TryComp(targetUid, out var temperature))
return 0f;
- return temperature.CurrentTemperature <= con.MinTemp ? 1f : 0f;
+ return temperature.Temperature <= con.MinTemp ? 1f : 0f;
}
default:
throw new NotImplementedException();
diff --git a/Content.Server/Temperature/Components/ContainerTemperatureComponent.cs b/Content.Server/Temperature/Components/ContainerTemperatureComponent.cs
index 7c7b1e81e8..7cbd398b79 100644
--- a/Content.Server/Temperature/Components/ContainerTemperatureComponent.cs
+++ b/Content.Server/Temperature/Components/ContainerTemperatureComponent.cs
@@ -1,11 +1,23 @@
-namespace Content.Server.Temperature.Components;
+using Content.Shared.Temperature.Components;
+namespace Content.Server.Temperature.Components;
+
+///
+/// Overrides the thresholds for an entity inserted into this container.
+/// Used for cryogenics typically.
+///
[RegisterComponent]
public sealed partial class ContainerTemperatureComponent : Component
{
+ ///
+ /// Overrides the for an entity inserted into this container.
+ ///
[DataField]
public float? HeatDamageThreshold;
+ ///
+ /// Overrides the for an entity inserted into this container.
+ ///
[DataField]
public float? ColdDamageThreshold;
}
diff --git a/Content.Server/Temperature/Components/InternalTemperatureComponent.cs b/Content.Server/Temperature/Components/InternalTemperatureComponent.cs
index 1e456d4410..ff134727f0 100644
--- a/Content.Server/Temperature/Components/InternalTemperatureComponent.cs
+++ b/Content.Server/Temperature/Components/InternalTemperatureComponent.cs
@@ -1,4 +1,7 @@
using Content.Server.Temperature.Systems;
+using Content.Shared.Atmos;
+using Content.Shared.Temperature.Components;
+using Content.Shared.Temperature.HeatContainer;
namespace Content.Server.Temperature.Components;
@@ -12,37 +15,33 @@ namespace Content.Server.Temperature.Components;
/// Too cold? Suffering hypothermia, start shivering to warm up and increase hunger.
///
[RegisterComponent, Access(typeof(TemperatureSystem))]
-public sealed partial class InternalTemperatureComponent : Component
+public sealed partial class InternalTemperatureComponent : Component, IHeatContainer
{
+ // TODO: These values probably shouldn't be duplicated from temperature component, but they're only used for the chef atm so low priority.
///
/// Internal temperature which is modified by surface temperature.
- /// This gets set to on mapinit.
+ /// This gets set to on mapinit.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float Temperature;
+ [DataField]
+ public float Temperature { get; set; } = Atmospherics.T20C;
///
- /// Thermal conductivity of the material in W/m/K.
- /// Higher conductivity means its insides will heat up faster.
+ /// Heat capacity of our internal temperature.
+ /// This gets set to on mapinit.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float Conductivity = 0.5f;
+ [DataField]
+ public float HeatCapacity { get; set; }
///
- /// Average thickness between the surface and the inside.
- /// For meats and such this is constant.
- /// Thicker materials take longer for heat to dissipate.
+ /// Thermal Conductance in W/K to this entity's .
+ /// Roughly estimated by multiplying meat's thermal conductivity of about 0.4 W/(m*K) by the total surface area of the meat,
+ /// and dividing by the thickness of the meat.
+ /// Then we multiply by four because we should only care about half the thickness typically, and also we're sharing a heat capacity.
+ /// Yes this is stupid. I'll care when chef has content or this is used by BodySystem.
+ /// No I'm not doing a custom value for each piece of meat.
///
- [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
- public float Thickness;
+ [DataField]
+ public float Conductance = 40f;
+
- ///
- /// Surface area in m^2 for the purpose of conducting surface temperature to the inside.
- /// Larger surface area means it takes longer to heat up/cool down
- ///
- ///
- /// For meats etc this should just be the area of the cooked surface not the whole thing as it's only getting heat from one side usually.
- ///
- [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
- public float Area;
}
diff --git a/Content.Server/Temperature/Components/TemperatureProtectionComponent.cs b/Content.Server/Temperature/Components/TemperatureProtectionComponent.cs
index 01e46cfc8d..8c40e35fa0 100644
--- a/Content.Server/Temperature/Components/TemperatureProtectionComponent.cs
+++ b/Content.Server/Temperature/Components/TemperatureProtectionComponent.cs
@@ -7,16 +7,10 @@ namespace Content.Server.Temperature.Components;
public sealed partial class TemperatureProtectionComponent : Component
{
///
- /// Multiplier for the transferred heat when heating up
+ /// Multiplier for the transferred heat when heating up or cooling down.
///
[DataField]
- public float HeatingCoefficient = 1.0f;
-
- ///
- /// Multiplier for the transferred heat when cooling down
- ///
- [DataField]
- public float CoolingCoefficient = 1.0f;
+ public float Coefficient = 1.0f;
}
///
diff --git a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs
index a9c6ede33f..1c46afa011 100644
--- a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs
+++ b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs
@@ -35,13 +35,13 @@ public sealed partial class EntityHeaterSystem : SharedEntityHeaterSystem
if (!power.Powered)
continue;
- // don't divide by total entities since it's a big grill
+ // divide by total entities to encourage the use of multiple grills
// excess would just be wasted in the air but that's not worth simulating
// if you want a heater thermomachine just use that...
var energy = power.PowerReceived * deltaTime;
foreach (var ent in placer.PlacedEntities)
{
- _temperature.ChangeHeat(ent, energy);
+ _temperature.ChangeHeat(ent, energy / placer.PlacedEntities.Count);
}
}
}
diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.Damage.cs b/Content.Server/Temperature/Systems/TemperatureSystem.Damage.cs
index c7e31bad81..c16ef38325 100644
--- a/Content.Server/Temperature/Systems/TemperatureSystem.Damage.cs
+++ b/Content.Server/Temperature/Systems/TemperatureSystem.Damage.cs
@@ -52,9 +52,9 @@ public sealed partial class TemperatureSystem
private void InitializeDamage()
{
- SubscribeLocalEvent(ServerAlert);
+ SubscribeLocalEvent(ServerAlert);
- SubscribeLocalEvent(EnqueueDamage);
+ SubscribeLocalEvent(EnqueueDamage);
SubscribeLocalEvent(OnUnpaused);
// Allows overriding thresholds based on the parent's thresholds.
@@ -98,7 +98,7 @@ public sealed partial class TemperatureSystem
var heatDamageThreshold = entity.Comp.ParentHeatDamageThreshold ?? entity.Comp.HeatDamageThreshold;
var coldDamageThreshold = entity.Comp.ParentColdDamageThreshold ?? entity.Comp.ColdDamageThreshold;
- if (temperature.CurrentTemperature >= heatDamageThreshold)
+ if (temperature.Temperature >= heatDamageThreshold)
{
if (!entity.Comp.TakingDamage)
{
@@ -106,11 +106,11 @@ public sealed partial class TemperatureSystem
entity.Comp.TakingDamage = true;
}
- var diff = Math.Abs(temperature.CurrentTemperature - heatDamageThreshold);
+ var diff = Math.Abs(temperature.Temperature - heatDamageThreshold);
var tempDamage = c / (1 + a * Math.Pow(Math.E, -heatK * diff)) - y;
_damageable.TryChangeDamage(entity.Owner, entity.Comp.HeatDamage * tempDamage * deltaTime.TotalSeconds, ignoreResistances: true, interruptsDoAfters: false);
}
- else if (temperature.CurrentTemperature <= coldDamageThreshold)
+ else if (temperature.Temperature <= coldDamageThreshold)
{
if (!entity.Comp.TakingDamage)
{
@@ -118,7 +118,7 @@ public sealed partial class TemperatureSystem
entity.Comp.TakingDamage = true;
}
- var diff = Math.Abs(temperature.CurrentTemperature - coldDamageThreshold);
+ var diff = Math.Abs(temperature.Temperature - coldDamageThreshold);
var tempDamage =
Math.Sqrt(diff * (Math.Pow(entity.Comp.DamageCap.Double(), 2) / coldDamageThreshold));
_damageable.TryChangeDamage(entity.Owner, entity.Comp.ColdDamage * tempDamage * deltaTime.TotalSeconds, ignoreResistances: true, interruptsDoAfters: false);
@@ -130,7 +130,7 @@ public sealed partial class TemperatureSystem
}
}
- private void ServerAlert(Entity entity, ref OnTemperatureChangeEvent args)
+ private void ServerAlert(Entity entity, ref TemperatureChangedEvent args)
{
ProtoId type;
float threshold;
@@ -175,7 +175,7 @@ public sealed partial class TemperatureSystem
_alerts.ClearAlertCategory(entity.AsNullable(), TemperatureAlertCategory);
}
- private void EnqueueDamage(Entity ent, ref OnTemperatureChangeEvent args)
+ private void EnqueueDamage(Entity ent, ref TemperatureChangedEvent args)
{
if (ShouldUpdateDamage.Add(ent) && !ent.Comp.TakingDamage)
ent.Comp.LastUpdate = _gameTiming.CurTime;
diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs
index 14539728f3..0698e090ab 100644
--- a/Content.Server/Temperature/Systems/TemperatureSystem.cs
+++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs
@@ -6,6 +6,7 @@ using Content.Shared.Rejuvenate;
using Content.Shared.Temperature;
using Content.Shared.Projectiles;
using Content.Shared.Temperature.Components;
+using Content.Shared.Temperature.HeatContainer;
using Content.Shared.Temperature.Systems;
namespace Content.Server.Temperature.Systems;
@@ -20,7 +21,7 @@ public sealed partial class TemperatureSystem : SharedTemperatureSystem
SubscribeLocalEvent(OnAtmosExposedUpdate);
SubscribeLocalEvent(OnRejuvenate);
- Subs.SubscribeWithRelay(OnTemperatureChangeAttempt, held: false);
+ Subs.SubscribeWithRelay(OnBeforeHeatExchange, held: false);
SubscribeLocalEvent(OnInit);
@@ -29,6 +30,15 @@ public sealed partial class TemperatureSystem : SharedTemperatureSystem
InitializeDamage();
}
+ protected override void OnMapInit(Entity entity, ref MapInitEvent args)
+ {
+ base.OnMapInit(entity, ref args);
+
+ // Make sure entities don't spawn cold!
+ if (_thermalRegulatorQuery.TryComp(entity, out var comp))
+ entity.Comp.Temperature = comp.NormalBodyTemperature;
+ }
+
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -37,72 +47,28 @@ public sealed partial class TemperatureSystem : SharedTemperatureSystem
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out var comp, out var temp))
{
- // don't do anything if they equalised
- var diff = Math.Abs(temp.CurrentTemperature - comp.Temperature);
+ // don't do anything if they equalized
+ var diff = Math.Abs(temp.Temperature - comp.Temperature);
if (diff < 0.1f)
continue;
- // heat flow in W/m^2 as per fourier's law in 1D.
- var q = comp.Conductivity * diff / comp.Thickness;
-
- // convert to J then K
- var joules = q * comp.Area * frameTime;
- var degrees = joules / GetHeatCapacity(uid, temp);
- if (temp.CurrentTemperature < comp.Temperature)
- degrees *= -1;
-
- // exchange heat between inside and surface
- comp.Temperature += degrees;
- ForceChangeTemperature(uid, temp.CurrentTemperature - degrees, temp);
+ ConductHeat((uid, temp), ref comp, frameTime, comp.Conductance, true);
}
UpdateDamage();
}
- public void ForceChangeTemperature(EntityUid uid, float temp, TemperatureComponent? temperature = null)
- {
- if (!TemperatureQuery.Resolve(uid, ref temperature))
- return;
-
- var lastTemp = temperature.CurrentTemperature;
- var delta = temperature.CurrentTemperature - temp;
- temperature.CurrentTemperature = temp;
- RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), broadcast: true);
- }
-
- public override void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null)
- {
- if (!TemperatureQuery.Resolve(uid, ref temperature, false))
- return;
-
- if (!ignoreHeatResistance)
- {
- var ev = new ModifyChangedTemperatureEvent(heatAmount);
- RaiseLocalEvent(uid, ev);
- heatAmount = ev.TemperatureDelta;
- }
-
- float lastTemp = temperature.CurrentTemperature;
- temperature.CurrentTemperature += heatAmount / GetHeatCapacity(uid, temperature);
- float delta = temperature.CurrentTemperature - lastTemp;
-
- RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), broadcast: true);
- }
-
- private void OnAtmosExposedUpdate(EntityUid uid, TemperatureComponent temperature, ref AtmosExposedUpdateEvent args)
+ private void OnAtmosExposedUpdate(Entity entity, ref AtmosExposedUpdateEvent args)
{
var transform = args.Transform;
if (transform.MapUid == null)
return;
- var temperatureDelta = args.GasMixture.Temperature - temperature.CurrentTemperature;
- var airHeatCapacity = _atmosphere.GetHeatCapacity(args.GasMixture, false);
- var heatCapacity = GetHeatCapacity(uid, temperature);
- // TODO ATMOS: This heat transfer formula is really really wrong, it needs to be pulled out. Pending on HeatContainers.
- var heat = temperatureDelta * (airHeatCapacity * heatCapacity /
- (airHeatCapacity + heatCapacity));
- ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature);
+ // TODO ATMOS: Atmos heat containers!!!
+ var atmosContainer = new HeatContainer(_atmosphere.GetHeatCapacity(args.GasMixture, false), args.GasMixture.Temperature);
+ ConductHeat(entity.AsNullable(), ref atmosContainer, args.DeltaTime, args.ConductivityMod);
+ args.GasMixture.Temperature = atmosContainer.Temperature;
}
private void OnInit(Entity entity, ref MapInitEvent args)
@@ -110,24 +76,20 @@ public sealed partial class TemperatureSystem : SharedTemperatureSystem
if (!TemperatureQuery.TryComp(entity, out var temp))
return;
- entity.Comp.Temperature = temp.CurrentTemperature;
+ // TODO: This shouldn't copy temperature component, but this component is so niche it's not worth the effort of fixing.
+ entity.Comp.Temperature = temp.Temperature;
+ entity.Comp.HeatCapacity = temp.HeatCapacity;
}
- private void OnRejuvenate(EntityUid uid, TemperatureComponent comp, RejuvenateEvent args)
+ private void OnRejuvenate(Entity entity, ref RejuvenateEvent args)
{
- ForceChangeTemperature(uid, Atmospherics.T20C, comp);
+ SetTemperature(entity.AsNullable(), _thermalRegulatorQuery.CompOrNull(entity)?.NormalBodyTemperature ?? Atmospherics.T20C);
}
- private void OnTemperatureChangeAttempt(EntityUid uid, TemperatureProtectionComponent component, ModifyChangedTemperatureEvent args)
+ private void OnBeforeHeatExchange(Entity entity, ref BeforeHeatExchangeEvent args)
{
- var coefficient = args.TemperatureDelta < 0
- ? component.CoolingCoefficient
- : component.HeatingCoefficient;
-
- var ev = new GetTemperatureProtectionEvent(coefficient);
- RaiseLocalEvent(uid, ref ev);
-
- args.TemperatureDelta *= ev.Coefficient;
+ // TODO: Proper coverage modifiers!!! This should be its own system which relays to inventory and then based on coverage spits out a modifier!
+ args.HeatTransferModifier *= entity.Comp.Coefficient;
}
private void ChangeTemperatureOnCollide(Entity ent, ref ProjectileHitEvent args)
diff --git a/Content.Shared/Atmos/AtmosExposedEvents.cs b/Content.Shared/Atmos/AtmosExposedEvents.cs
index 41b65fe24f..b64ebb49cd 100644
--- a/Content.Shared/Atmos/AtmosExposedEvents.cs
+++ b/Content.Shared/Atmos/AtmosExposedEvents.cs
@@ -9,15 +9,22 @@ namespace Content.Shared.Atmos;
/// The coordinates of the entity that is being exposed.
/// The gas mixture that the entity is exposed to.
/// The xform of the entity that is being exposed.
+/// Amount of time since the last AtmosExposedUpdateEvent, in seconds.
+/// A modifier for thermal conductivity between the exposed entity and the atmosphere.
+/// Typically equal to
[ByRefEvent]
public readonly struct AtmosExposedUpdateEvent(
EntityCoordinates coordinates,
GasMixture mixture,
- TransformComponent transform)
+ TransformComponent transform,
+ float deltaTime,
+ float conductivityMod)
{
public readonly EntityCoordinates Coordinates = coordinates;
public readonly GasMixture GasMixture = mixture;
public readonly TransformComponent Transform = transform;
+ public readonly float DeltaTime = deltaTime;
+ public readonly float ConductivityMod = conductivityMod;
}
///
diff --git a/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs
index f87bbecf8c..6a818a1d33 100644
--- a/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs
+++ b/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs
@@ -12,7 +12,7 @@ public sealed partial class TemperatureEntityConditionSystem : EntityConditionSy
{
protected override void Condition(Entity entity, ref EntityConditionEvent args)
{
- if (entity.Comp.CurrentTemperature >= args.Condition.Min && entity.Comp.CurrentTemperature <= args.Condition.Max)
+ if (entity.Comp.Temperature >= args.Condition.Min && entity.Comp.Temperature <= args.Condition.Max)
args.Result = true;
}
}
diff --git a/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs
index 94b850fd98..418730e33a 100644
--- a/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs
+++ b/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs
@@ -16,7 +16,7 @@ public sealed partial class AdjustTemperatureEntityEffectSystem : EntityEffectSy
{
var amount = args.Effect.Amount * args.Scale;
- _temperature.ChangeHeat(entity, amount, true, entity.Comp);
+ _temperature.ChangeHeat(entity.AsNullable(), amount, true);
}
}
diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs
index 439123320e..7283fb6153 100644
--- a/Content.Shared/Inventory/InventorySystem.Relay.cs
+++ b/Content.Shared/Inventory/InventorySystem.Relay.cs
@@ -49,7 +49,6 @@ public partial class InventorySystem
SubscribeLocalEvent(RelayInventoryEvent);
SubscribeLocalEvent(RelayInventoryEvent);
SubscribeLocalEvent(RelayInventoryEvent);
- SubscribeLocalEvent(RelayInventoryEvent);
SubscribeLocalEvent(RelayInventoryEvent);
SubscribeLocalEvent(RelayInventoryEvent);
SubscribeLocalEvent(RelayInventoryEvent);
@@ -86,6 +85,7 @@ public partial class InventorySystem
SubscribeLocalEvent(RefRelayInventoryEvent);
SubscribeLocalEvent(RefRelayInventoryEvent);
SubscribeLocalEvent(RefRelayInventoryEvent);
+ SubscribeLocalEvent(RefRelayInventoryEvent);
SubscribeLocalEvent(RefRelayInventoryEvent);
SubscribeLocalEvent(RefRelayInventoryEvent);
SubscribeLocalEvent(RefRelayInventoryEvent);
diff --git a/Content.Shared/Medical/Cryogenics/InsideCryoPodComponent.cs b/Content.Shared/Medical/Cryogenics/InsideCryoPodComponent.cs
index 03cc3edf13..033a79e3ff 100644
--- a/Content.Shared/Medical/Cryogenics/InsideCryoPodComponent.cs
+++ b/Content.Shared/Medical/Cryogenics/InsideCryoPodComponent.cs
@@ -10,4 +10,10 @@ public sealed partial class InsideCryoPodComponent: Component
[ViewVariables]
[DataField("previousOffset")]
public Vector2 PreviousOffset { get; set; } = new(0, 0);
+
+ ///
+ /// A modifier for the conductance between the cryo pod and the entity inside.
+ ///
+ [DataField]
+ public float ConductanceMod = 20f; // Arbitrary number. Likely cryopods will have to be heat containers with special juice in the future, or use Frezon...
}
diff --git a/Content.Shared/Medical/Cryogenics/SharedInsideCryoPodSystem.cs b/Content.Shared/Medical/Cryogenics/SharedInsideCryoPodSystem.cs
index da3604e6e5..76fd6034e8 100644
--- a/Content.Shared/Medical/Cryogenics/SharedInsideCryoPodSystem.cs
+++ b/Content.Shared/Medical/Cryogenics/SharedInsideCryoPodSystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Standing;
+using Content.Shared.Temperature;
using Robust.Shared.Containers;
namespace Content.Shared.Medical.Cryogenics;
@@ -7,23 +8,24 @@ public abstract partial class SharedCryoPodSystem
{
public virtual void InitializeInsideCryoPod()
{
- SubscribeLocalEvent(HandleDown);
SubscribeLocalEvent(OnEntGotRemovedFromContainer);
+ SubscribeLocalEvent(HandleDown);
+ SubscribeLocalEvent(OnBeforeHeatExchange);
+ }
+
+ private void OnEntGotRemovedFromContainer(Entity entity, ref EntGotRemovedFromContainerMessage args)
+ {
+ RemCompDeferred(entity);
}
// Must stand in the cryo pod
- private void HandleDown(EntityUid uid, InsideCryoPodComponent component, DownAttemptEvent args)
+ private void HandleDown(Entity entity, ref DownAttemptEvent args)
{
args.Cancel();
}
- private void OnEntGotRemovedFromContainer(EntityUid uid, InsideCryoPodComponent component, EntGotRemovedFromContainerMessage args)
+ private void OnBeforeHeatExchange(Entity entity, ref BeforeHeatExchangeEvent args)
{
- if (Terminating(uid))
- {
- return;
- }
-
- RemComp(uid);
+ args.HeatTransferModifier *= entity.Comp.ConductanceMod;
}
}
diff --git a/Content.Shared/Temperature/Components/EntityHeaterComponent.cs b/Content.Shared/Temperature/Components/EntityHeaterComponent.cs
index 6cf97a0534..76bb4f4ba7 100644
--- a/Content.Shared/Temperature/Components/EntityHeaterComponent.cs
+++ b/Content.Shared/Temperature/Components/EntityHeaterComponent.cs
@@ -16,7 +16,7 @@ public sealed partial class EntityHeaterComponent : Component
/// Low and medium are 33% and 66% respectively.
///
[DataField]
- public float Power = 2400f;
+ public float Power = 12000f;
///
/// Current setting of the heater. If it is off or unpowered it won't heat anything.
diff --git a/Content.Shared/Temperature/Components/TemperatureComponent.cs b/Content.Shared/Temperature/Components/TemperatureComponent.cs
index fd79c4b4b2..00b0cf2934 100644
--- a/Content.Shared/Temperature/Components/TemperatureComponent.cs
+++ b/Content.Shared/Temperature/Components/TemperatureComponent.cs
@@ -1,4 +1,6 @@
using Content.Shared.Atmos;
+using Content.Shared.Temperature.HeatContainer;
+using Content.Shared.Temperature.Systems;
namespace Content.Shared.Temperature.Components;
@@ -7,23 +9,31 @@ namespace Content.Shared.Temperature.Components;
/// informing others of the current temperature.
///
[RegisterComponent]
-public sealed partial class TemperatureComponent : Component
+[Access(typeof(SharedTemperatureSystem))]
+public sealed partial class TemperatureComponent : Component, IHeatContainer
{
///
- /// Surface temperature which is modified by the environment.
+ /// The specific heat capacity of this entity in J/(kg*K). Humans are about 3kJ/(kg*K)
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float CurrentTemperature = Atmospherics.T20C;
+ [DataField]
+ public float SpecificHeat = 3000f;
+
+ [DataField]
+ public float HeatCapacity { get; set; }
+
+ [DataField]
+ public float Temperature { get; set; } = Atmospherics.T20C;
///
- /// Heat capacity per kg of mass.
+ /// Thermal Conductivity in W/(K*m^2).
+ /// Human skin is about 0.3 W/(m*K)
+ /// Divide that by the thickness of skin of about 2mm giving us a final value of 150
+ /// Source: https://pmc.ncbi.nlm.nih.gov/articles/PMC8953946/
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float SpecificHeat = 50f;
-
- ///
- /// How well does the air surrounding you merge into your body temperature?
- ///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float AtmosTemperatureTransferEfficiency = 0.1f;
+ ///
+ /// This value should be multiplied by a surface area value based on the amount of area in contact.
+ /// For Atmospherics, this is typically 2m^2, the surface area of the average body.
+ ///
+ [DataField]
+ public float ThermalConductivity = 150f;
}
diff --git a/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs b/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs
index 9a8dc57d4b..848052ebf6 100644
--- a/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs
+++ b/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs
@@ -88,8 +88,8 @@ public abstract partial class SharedEntityHeaterSystem : EntitySystem
// Just think of the load as a little LED, or bad wiring, or something.
return setting switch
{
- EntityHeaterSetting.Low => max / 3f,
- EntityHeaterSetting.Medium => max * 2f / 3f,
+ EntityHeaterSetting.Low => max / 4f,
+ EntityHeaterSetting.Medium => max / 2f,
EntityHeaterSetting.High => max,
_ => 0.01f,
};
diff --git a/Content.Shared/Temperature/Systems/SharedTemperatureSystem.cs b/Content.Shared/Temperature/Systems/SharedTemperatureSystem.cs
index 32c370d220..375579c6bc 100644
--- a/Content.Shared/Temperature/Systems/SharedTemperatureSystem.cs
+++ b/Content.Shared/Temperature/Systems/SharedTemperatureSystem.cs
@@ -1,9 +1,10 @@
using System.Linq;
-using Content.Shared.Atmos;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Temperature.Components;
+using Content.Shared.Temperature.HeatContainer;
using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Events;
using Robust.Shared.Timing;
namespace Content.Shared.Temperature.Systems;
@@ -27,11 +28,33 @@ public abstract partial class SharedTemperatureSystem : EntitySystem
{
base.Initialize();
- SubscribeLocalEvent(OnTemperatureChanged);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnMassDataChanged);
+ SubscribeLocalEvent(OnTemperatureChanged);
SubscribeLocalEvent(OnRefreshMovementSpeedModifiers);
}
- private void OnTemperatureChanged(Entity ent, ref OnTemperatureChangeEvent args)
+ ///
+ /// Calculates the HeatCapacity for this entity based on its SpecificHeat and mass.
+ /// This is a carryover from the old TemperatureSystem.
+ /// If this entity does not have a , then it assumes it's 1kg.
+ ///
+ protected virtual void OnMapInit(Entity entity, ref MapInitEvent args)
+ {
+ // We calculate the heat capacity for our entity by multiplying its mass by its specific heat.
+ // If the entity has no mass, we assume it's 1 kilogram.
+ var mass = CompOrNull(entity)?.FixturesMass ?? 1f;
+
+ // TODO: This assumes this is temperature for the whole body, but ideally we want it split into surface and internal temperature!
+ entity.Comp.HeatCapacity = mass * entity.Comp.SpecificHeat;
+ }
+
+ private void OnMassDataChanged(Entity entity, ref MassDataChangedEvent args)
+ {
+ entity.Comp.HeatCapacity = args.NewMass * entity.Comp.SpecificHeat;
+ }
+
+ private void OnTemperatureChanged(Entity ent, ref TemperatureChangedEvent args)
{
foreach (var (threshold, modifier) in ent.Comp.Thresholds)
{
@@ -82,18 +105,107 @@ public abstract partial class SharedTemperatureSystem : EntitySystem
}
}
- public virtual void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null)
+ ///
+ /// Conducts heat between an entity with TemperatureComponent and another
+ ///
+ /// Entity we're conducting heat with
+ /// Heat container which is conducting heat with our entity
+ /// The amount of time that the heat is allowed to conduct, in seconds. This value should be small.
+ /// An optional heat transfer modifier for this exchange
+ /// Whether we should avoid raising an event which checks for conduction modifiers on our entity.
+ /// Returns the amount of heat exchanged, in Joules. A positive value means the entity lost heat energy.
+ public float ConductHeat(Entity entity, ref T heatContainer, float deltaT, float heatTransferMod = 1f, bool ignoreHeatResistance = false) where T : IHeatContainer
{
+ if (!TemperatureQuery.Resolve(entity, ref entity.Comp, false)
+ || MathHelper.CloseTo(entity.Comp.Temperature, heatContainer.Temperature))
+ return 0f;
- }
-
- public float GetHeatCapacity(EntityUid uid, TemperatureComponent? comp = null, PhysicsComponent? physics = null)
- {
- if (!TemperatureQuery.Resolve(uid, ref comp) || !Resolve(uid, ref physics, false) || physics.FixturesMass <= 0)
+ var conductance = entity.Comp.ThermalConductivity * heatTransferMod;
+ if (!ignoreHeatResistance)
{
- return Atmospherics.MinimumHeatCapacity;
+ var ev = new BeforeHeatExchangeEvent();
+ RaiseLocalEvent(entity, ref ev);
+ conductance *= ev.HeatTransferModifier;
}
- return comp.SpecificHeat * physics.FixturesMass;
+ var lastTemp = entity.Comp.Temperature;
+ var heatEx = HeatContainerHelpers.ConductHeat(ref entity.Comp, ref heatContainer, deltaT, conductance);
+
+ var changeEv = new TemperatureChangedEvent(entity.Comp.Temperature, lastTemp);
+ RaiseLocalEvent(entity, ref changeEv, broadcast: true);
+ return heatEx;
+ }
+
+ ///
+ /// Conducts heat for an entity with a TemperatureComponent to a source at a fixed temperature.
+ ///
+ /// Entity we're conducting heat with
+ /// Temperature this entity is being exposed to
+ /// The amount of time that the heat is allowed to conduct, in seconds. This value should be small.
+ /// An optional heat transfer modifier for this exchange
+ /// Whether we should avoid raising an event which checks for conduction modifiers on our entity.
+ /// Returns the amount of heat exchanged, in Joules. A positive value means the entity lost heat energy.
+ public float ConductHeat(Entity entity, float temperature, float deltaT, float heatTransferMod = 1f, bool ignoreHeatResistance = false)
+ {
+ if (!TemperatureQuery.Resolve(entity, ref entity.Comp, false)
+ || MathHelper.CloseTo(entity.Comp.Temperature, temperature))
+ return 0f;
+
+ var conductance = entity.Comp.ThermalConductivity * heatTransferMod;
+ if (!ignoreHeatResistance)
+ {
+ var ev = new BeforeHeatExchangeEvent();
+ RaiseLocalEvent(entity, ref ev);
+ conductance *= ev.HeatTransferModifier;
+ }
+
+ var lastTemp = entity.Comp.Temperature;
+ var heatEx = HeatContainerHelpers.ConductHeat(ref entity.Comp, temperature, deltaT, conductance);
+
+ var changeEv = new TemperatureChangedEvent(entity.Comp.Temperature, lastTemp);
+ RaiseLocalEvent(entity, ref changeEv, broadcast: true);
+ return heatEx;
+ }
+
+ ///
+ /// Adds or removes the specified amount of heat from an entity.
+ ///
+ /// Entity whose temperature we're modifying.
+ /// The change in heat.
+ /// Whether we should avoid raising an event to modify the conductance of our entity.
+ /// Returns the amount of heat that was actually added, after applying heat resistances.
+ public float ChangeHeat(Entity entity, float heatAmount, bool ignoreHeatResistance = false)
+ {
+ if (!TemperatureQuery.Resolve(entity, ref entity.Comp, false) || heatAmount == 0f)
+ return 0f;
+
+ if (!ignoreHeatResistance)
+ {
+ var ev = new BeforeHeatExchangeEvent();
+ RaiseLocalEvent(entity, ref ev );
+ heatAmount *= ev.HeatTransferModifier;
+ }
+
+ var lastTemp = entity.Comp.Temperature;
+ HeatContainerHelpers.AddHeat(ref entity.Comp, heatAmount);
+
+ var changeEv = new TemperatureChangedEvent(entity.Comp.Temperature, lastTemp);
+ RaiseLocalEvent(entity, ref changeEv, broadcast: true);
+
+ return heatAmount;
+ }
+
+ ///
+ /// Protected because you should not be calling this. Sets heat to a new value.
+ ///
+ protected void SetTemperature(Entity entity, float temp)
+ {
+ if (!TemperatureQuery.Resolve(entity, ref entity.Comp))
+ return;
+
+ var lastTemp = entity.Comp.Temperature;
+ entity.Comp.Temperature = temp;
+ var changeEv = new TemperatureChangedEvent(entity.Comp.Temperature, lastTemp);
+ RaiseLocalEvent(entity, ref changeEv, broadcast: true);
}
}
diff --git a/Content.Shared/Temperature/TemperatureEvents.cs b/Content.Shared/Temperature/TemperatureEvents.cs
index 7a26d07e30..5f596b1b7f 100644
--- a/Content.Shared/Temperature/TemperatureEvents.cs
+++ b/Content.Shared/Temperature/TemperatureEvents.cs
@@ -2,29 +2,29 @@ using Content.Shared.Inventory;
namespace Content.Shared.Temperature;
-public sealed class ModifyChangedTemperatureEvent : EntityEventArgs, IInventoryRelayEvent
+///
+/// This event is raised before heat is exchanged so that the conductance of the exchange can be changed.
+///
+[ByRefEvent]
+public record struct BeforeHeatExchangeEvent() : IInventoryRelayEvent
{
public SlotFlags TargetSlots { get; } = ~SlotFlags.POCKET;
- public float TemperatureDelta;
-
- public ModifyChangedTemperatureEvent(float temperature)
- {
- TemperatureDelta = temperature;
- }
+ ///
+ /// A multiplicative modifier for heat transfers on the entity this event is being raised to.
+ ///
+ public float HeatTransferModifier = 1f;
}
-public sealed class OnTemperatureChangeEvent : EntityEventArgs
+///
+/// This event is raised after heat is exchanged to inform other systems that temperature has changed.
+///
+/// Current temperature of this entity.
+/// Previous temperature of this entity.
+[ByRefEvent]
+public record struct TemperatureChangedEvent(float CurrentTemperature, float LastTemperature)
{
- public readonly float CurrentTemperature;
- public readonly float LastTemperature;
- public readonly float TemperatureDelta;
-
- public OnTemperatureChangeEvent(float current, float last, float delta)
- {
- CurrentTemperature = current;
- LastTemperature = last;
- TemperatureDelta = delta;
- }
+ public readonly float CurrentTemperature = CurrentTemperature;
+ public readonly float LastTemperature = LastTemperature;
+ public readonly float TemperatureDelta = CurrentTemperature - LastTemperature;
}
-
diff --git a/Resources/Prototypes/Atmospherics/gases.yml b/Resources/Prototypes/Atmospherics/gases.yml
index b217074ca8..94244e55ce 100644
--- a/Resources/Prototypes/Atmospherics/gases.yml
+++ b/Resources/Prototypes/Atmospherics/gases.yml
@@ -2,7 +2,7 @@
id: Oxygen
name: gas-oxygen
abbreviation: gas-oxygen-abbreviation
- molarHeatCapacity: 20
+ molarHeatCapacity: 29
heatCapacityRatio: 1.4
molarMass: 32
color: '#2887E8'
@@ -14,7 +14,7 @@
id: Nitrogen
name: gas-nitrogen
abbreviation: gas-nitrogen-abbreviation
- molarHeatCapacity: 30
+ molarHeatCapacity: 29
heatCapacityRatio: 1.4
molarMass: 28
color: '#DA1010'
@@ -25,7 +25,7 @@
id: CarbonDioxide
name: gas-carbon-dioxide
abbreviation: gas-carbon-dioxide-abbreviation
- molarHeatCapacity: 30
+ molarHeatCapacity: 37
heatCapacityRatio: 1.3
molarMass: 44
color: '#4e4e4e'
diff --git a/Resources/Prototypes/Body/Species/moth.yml b/Resources/Prototypes/Body/Species/moth.yml
index 326fa64bdb..18dcec3ab9 100644
--- a/Resources/Prototypes/Body/Species/moth.yml
+++ b/Resources/Prototypes/Body/Species/moth.yml
@@ -178,9 +178,6 @@
damage:
types:
Heat: 2.5 # moths burn more easily
- - type: Temperature
- currentTemperature: 310.15
- specificHeat: 46
- type: TemperatureDamage # Moths hate the heat and thrive in the cold.
heatDamageThreshold: 320
coldDamageThreshold: 230
diff --git a/Resources/Prototypes/Body/Species/reptilian.yml b/Resources/Prototypes/Body/Species/reptilian.yml
index 222ea617bb..74eab20814 100644
--- a/Resources/Prototypes/Body/Species/reptilian.yml
+++ b/Resources/Prototypes/Body/Species/reptilian.yml
@@ -149,9 +149,6 @@
damage:
types:
Slash: 5
- - type: Temperature
- currentTemperature: 310.15
- specificHeat: 42
- type: TemperatureDamage
heatDamageThreshold: 400
coldDamageThreshold: 285
diff --git a/Resources/Prototypes/Body/Species/vulpkanin.yml b/Resources/Prototypes/Body/Species/vulpkanin.yml
index a48649d7b4..38c551434c 100644
--- a/Resources/Prototypes/Body/Species/vulpkanin.yml
+++ b/Resources/Prototypes/Body/Species/vulpkanin.yml
@@ -173,8 +173,6 @@
types:
Piercing: 2
Slash: 3
- - type: Temperature
- specificHeat: 44
- type: TemperatureDamage
heatDamageThreshold: 320
coldDamageThreshold: 230
@@ -186,9 +184,6 @@
Heat: 2.5 # Per second, scales with temperature & other constants
- type: Wagging
action: ActionToggleWaggingVulpkanin
- - type: TemperatureProtection
- heatingCoefficient: 1.2
- coolingCoefficient: 0.3
- type: JumpAbility
action: ActionVulpkaninGravityJump
canCollide: true
diff --git a/Resources/Prototypes/Body/species_base.yml b/Resources/Prototypes/Body/species_base.yml
index ae386d2be2..6428ddb87e 100644
--- a/Resources/Prototypes/Body/species_base.yml
+++ b/Resources/Prototypes/Body/species_base.yml
@@ -185,11 +185,9 @@
- type: Fingerprint
- type: Blindable
- type: Temperature
- currentTemperature: 310.15
- specificHeat: 42
- type: TemperatureDamage
heatDamageThreshold: 325
- coldDamageThreshold: 260
+ coldDamageThreshold: 273.15
coldDamage:
types:
Cold: 0.1 #per second, scales with temperature & other constants
diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml
index bfe0e53600..ec3f13dde9 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml
@@ -103,7 +103,7 @@
- type: entity
abstract: true
- parent: ClothingHeadBase
+ parent: [ ClothingHeadBase, TemperatureProtection ]
id: ClothingHeadEVAHelmetBase
name: base space helmet
components:
@@ -116,9 +116,6 @@
- type: PressureProtection
highPressureMultiplier: 0.6
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.2
- coolingCoefficient: 0.2
- type: IngestionBlocker
- type: Clothing
#Copies ClothingHeadHardsuitBase behavior
@@ -141,7 +138,7 @@
- type: entity
abstract: true
- # No parent since we aren't actually an item.
+ parent: TemperatureProtection
id: ClothingHeadHardsuitBase
name: base hardsuit helmet
categories: [ HideSpawnMenu ]
@@ -163,9 +160,6 @@
- type: PressureProtection
highPressureMultiplier: 0.3
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.1
- coolingCoefficient: 0.1
- type: Armor
modifiers:
coefficients:
@@ -188,6 +182,11 @@
- HeadSide
- FacialHair
+- type: entity
+ abstract: true
+ parent: [ TemperatureProtection, ClothingHeadHardsuitBase ]
+ id: ClothingHeadHardsuitInsulatedBase
+
- type: entity
abstract: true
# No parent since it isn't an item
@@ -247,6 +246,7 @@
- type: entity
abstract: true
+ parent: TemperatureProtectionMinimal
id: ClothingHeadHatHoodWinterBase
name: base winter coat hood
description: A hood, made to keep your head warm.
@@ -260,9 +260,6 @@
unequipSound: /Audio/Effects/rustle2.ogg
quickEquip: false
slots: [ HEAD ]
- - type: TemperatureProtection
- heatingCoefficient: 1.05
- coolingCoefficient: 0.7
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.90
- type: Armor # so zombification resistance shows up
diff --git a/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml
index 0d2814d537..4a70210939 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/eva-helmets.yml
@@ -95,9 +95,6 @@
- state: equipped-head
head-vox:
- state: equipped-head-vox
- - type: TemperatureProtection
- heatingCoefficient: 0.1
- coolingCoefficient: 0.1
- type: Armor
modifiers:
coefficients:
diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
index cc8761ca62..848227e3bb 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
@@ -7,7 +7,7 @@
#CREW HARDSUITS
#Atmospherics Hardsuit
- type: entity
- parent: [ ClothingHeadHardsuitBase, ClothingHeadSuitWithLightBase ]
+ parent: [ ClothingHeadHardsuitInsulatedBase, ClothingHeadSuitWithLightBase ]
id: ClothingHeadHelmetHardsuitAtmos
name: atmos hardsuit helmet
description: A special hardsuit helmet designed for working in low-pressure, high thermal environments.
@@ -62,9 +62,6 @@
- type: PressureProtection
highPressureMultiplier: 0.08
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.005
- coolingCoefficient: 0.005
- type: FireProtection
reduction: 0.2
- type: Tag
@@ -584,7 +581,7 @@
#Syndicate Elite Hardsuit
- type: entity
- parent: [ ClothingHeadHardsuitBase, ClothingHeadSuitWithLightBase ]
+ parent: [ ClothingHeadHardsuitInsulatedBase, ClothingHeadSuitWithLightBase ]
id: ClothingHeadHelmetHardsuitSyndieElite
name: syndicate elite helmet
description: An elite version of the blood-red hardsuit's helmet, with improved radiation resistance and fireproofing. Property of Gorlex Marauders.
@@ -598,9 +595,6 @@
- type: PressureProtection
highPressureMultiplier: 0.08
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.005
- coolingCoefficient: 0.005
- type: FireProtection
reduction: 0.2
- type: Armor
@@ -919,9 +913,6 @@
- type: PressureProtection
highPressureMultiplier: 0.08
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.005
- coolingCoefficient: 0.005
- type: Armor
modifiers:
coefficients:
diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
index ba6e5d7b53..5f68a1104a 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
@@ -276,7 +276,7 @@
#Fire Helmet
- type: entity
- parent: ClothingHeadLightBase
+ parent: [ClothingHeadLightBase, TemperatureProtection]
id: ClothingHeadHelmetFire
name: fire helmet
description: An atmos tech's best friend. Provides some heat resistance and looks cool.
@@ -287,9 +287,6 @@
sprite: Clothing/Head/Helmets/firehelmet.rsi
quickEquip: true
- type: IngestionBlocker
- - type: TemperatureProtection
- heatingCoefficient: 0.5
- coolingCoefficient: 0.8
- type: FireProtection
reduction: 0.15 # not fully sealed so less protection
- type: ItemSlots
@@ -309,7 +306,7 @@
#Atmos Fire Helmet
- type: entity
- parent: [ClothingHeadLightBase, BaseEngineeringContraband]
+ parent: [ClothingHeadLightBase, TemperatureProtection, BaseEngineeringContraband]
id: ClothingHeadHelmetAtmosFire
name: atmos fire helmet
description: An atmos fire helmet, able to keep the user cool in any situation.
@@ -320,9 +317,6 @@
sprite: Clothing/Head/Helmets/atmos_firehelmet.rsi
quickEquip: true
- type: IngestionBlocker
- - type: TemperatureProtection
- heatingCoefficient: 0.3
- coolingCoefficient: 0.8
- type: FireProtection
reduction: 0.2
- type: PressureProtection
diff --git a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml
index a9f119146b..44759965a9 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml
@@ -226,16 +226,13 @@
- Hair
- type: entity
- parent: ClothingHeadHatHoodCarp
+ parent: [ ClothingHeadHatHoodCarp, TemperatureProtection ]
id: ClothingHeadHelmetHardsuitCarp
categories: [ HideSpawnMenu ]
components:
- type: PressureProtection
highPressureMultiplier: 0.6
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.2
- coolingCoefficient: 0.2
- type: BreathMask
# this is on the hood so you only fool the fish if you wear the whole set
# wear carp suit and security helmet, they'll know you are fake
diff --git a/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml b/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml
index 4e0bdbe2a2..32ce5bc2cd 100644
--- a/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml
+++ b/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml
@@ -33,7 +33,7 @@
- type: entity
abstract: true
- parent: ClothingNeckBase
+ parent: [ TemperatureProtectionMinimal, ClothingNeckBase ]
id: ClothingScarfBase
components:
- type: Tag
@@ -42,6 +42,3 @@
- ClothMade
- Recyclable
- WhitelistChameleon
- - type: TemperatureProtection
- heatingCoefficient: 1.025
- coolingCoefficient: 0.5
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
index 510f9895b0..4c986c4881 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
@@ -164,7 +164,7 @@
#Elite web vest
- type: entity
- parent: [ClothingOuterArmorBase, ClothingOuterStorageBase, BaseSyndicateContraband]
+ parent: [ClothingOuterArmorBase, TemperatureProtection, ClothingOuterStorageBase, BaseSyndicateContraband]
id: ClothingOuterVestWebElite
name: elite web vest
description: A synthetic armor vest. This one has added webbing and heat resistant fibers.
@@ -173,9 +173,6 @@
sprite: Clothing/OuterClothing/Vests/elitevest.rsi
- type: Clothing
sprite: Clothing/OuterClothing/Vests/elitevest.rsi
- - type: TemperatureProtection
- heatingCoefficient: 0.1
- coolingCoefficient: 0.1
- type: Armor
modifiers:
coefficients:
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml
index 1ab627f078..de3f7208de 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml
@@ -24,6 +24,19 @@
sprintModifier: 0.9
- type: HeldSpeedModifier
+- type: entity
+ abstract: true
+ parent: [ ClothingOuterBaseLarge, TemperatureProtectionGood ]
+ id: ClothingOuterBaseLargeInsulated
+
+- type: entity
+ abstract: true
+ parent: [ ClothingOuterBaseLarge, TemperatureProtectionGreat ]
+ id: ClothingOuterBaseLargeFireproof
+ components:
+ - type: FireProtection
+ reduction: 0.65
+
- type: entity
abstract: true
parent: ClothingOuterBase
@@ -61,6 +74,11 @@
- ReagentId: Fiber
Quantity: 60
+- type: entity
+ abstract: true
+ parent: [ ClothingOuterStorageEdible, TemperatureProtectionMinimal]
+ id: ClothingOuterJacketBase
+
- type: entity
abstract: true
parent: [ClothingOuterStorageBase, BaseFoldable]
@@ -104,8 +122,8 @@
- type: entity
abstract: true
- parent: ClothingOuterStorageBase
- id: ClothingOuterStorageToggleableBase
+ parent: ClothingOuterJacketBase
+ id: ClothingOuterJacketToggleableBase
components:
- type: ToggleableClothing
clothingPrototype: ClothingHeadHatHoodWinterDefault
@@ -118,23 +136,41 @@
- type: entity
abstract: true
- parent: [ClothingOuterBase, GeigerCounterClothing, AllowSuitStorageClothing]
- id: ClothingOuterHardsuitBase
- name: base hardsuit
+ parent: [ClothingOuterBaseLargeInsulated, AllowSuitStorageClothing]
+ id: ClothingOuterEVASuitBase
+ name: base EVA Suit
components:
# SlotBlocker blocked until UI change
# - type: SlotBlock
# slots: [innerclothing, feet]
- type: PressureProtection
- highPressureMultiplier: 0.3
+ highPressureMultiplier: 0.6
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.01
- coolingCoefficient: 0.01
+ - type: ClothingSpeedModifier
+ walkModifier: 0.8
+ sprintModifier: 0.8
+ - type: ProtectedFromStepTriggers
+ slots: WITHOUT_POCKET
+ - type: DamageOnInteractProtection
+ damageProtection:
+ flatReductions:
+ Heat: 10 # the average lightbulb only does around four damage!
+ slots: OUTERCLOTHING
+ - type: HideLayerClothing
+ slots:
+ - Tail
+
+- type: entity
+ abstract: true
+ parent: [ ClothingOuterEVASuitBase, GeigerCounterClothing ]
+ id: ClothingOuterHardsuitBase
+ name: base hardsuit
+ components:
+ - type: PressureProtection
+ highPressureMultiplier: 0.3
- type: ClothingSpeedModifier
walkModifier: 0.6
sprintModifier: 0.6
- - type: HeldSpeedModifier
- type: Item
size: Ginormous
shape:
@@ -159,48 +195,6 @@
tags:
- Hardsuit
- WhitelistChameleon
- - type: ProtectedFromStepTriggers
- slots: WITHOUT_POCKET
- - type: DamageOnInteractProtection
- damageProtection:
- flatReductions:
- Heat: 10 # the average lightbulb only does around four damage!
- slots: OUTERCLOTHING
- - type: HideLayerClothing
- slots:
- - Tail
-
-- type: entity
- abstract: true
- parent: [ClothingOuterBase, AllowSuitStorageClothing]
- id: ClothingOuterEVASuitBase
- name: base EVA Suit
- components:
-# SlotBlocker blocked until UI change
-# - type: SlotBlock
-# slots: [innerclothing, feet]
- - type: PressureProtection
- highPressureMultiplier: 0.6
- lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.01 # Not complete protection from fire
- coolingCoefficient: 0.01
- - type: ClothingSpeedModifier
- walkModifier: 0.8
- sprintModifier: 0.8
- - type: HeldSpeedModifier
- - type: Item
- size: Huge
- - type: ProtectedFromStepTriggers
- slots: WITHOUT_POCKET
- - type: DamageOnInteractProtection
- damageProtection:
- flatReductions:
- Heat: 10 # the average lightbulb only does around four damage!
- slots: OUTERCLOTHING
- - type: HideLayerClothing
- slots:
- - Tail
- type: entity
parent: ClothingOuterEVASuitBase
@@ -216,10 +210,10 @@
toggleable-clothing: !type:ContainerSlot {}
- type: entity
+ abstract: true
parent: ClothingOuterBase
id: ClothingOuterBaseToggleable
name: hoodie with hood
- abstract: True
components:
- type: ToggleableClothing
clothingPrototype: ClothingHeadHatHoodWinterDefault
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml
index 235650d789..bc3d02385c 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml
@@ -100,7 +100,7 @@
sprite: Clothing/OuterClothing/Coats/hos_trenchcoat.rsi
- type: entity
- parent: ClothingOuterStorageToggleableBase
+ parent: ClothingOuterJacketToggleableBase
id: ClothingOuterCoatJensen
name: jensen coat
description: A jensen coat.
@@ -123,7 +123,7 @@
id: SyndieHandyFlag
- type: entity
- parent: ClothingOuterStorageEdible
+ parent: ClothingOuterJacketBase
id: ClothingOuterCoatTrench
name: trench coat
description: A comfy trench coat.
@@ -132,9 +132,6 @@
sprite: Clothing/OuterClothing/Coats/trenchcoat.rsi
- type: Clothing
sprite: Clothing/OuterClothing/Coats/trenchcoat.rsi
- - type: TemperatureProtection
- heatingCoefficient: 0.1
- coolingCoefficient: 0.1
- type: Armor
modifiers:
coefficients:
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
index f15dfdf732..056b47da30 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
@@ -15,9 +15,6 @@
- type: PressureProtection
highPressureMultiplier: 0.02
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.001
- coolingCoefficient: 0.001
- type: FireProtection
reduction: 0.8
- type: ExplosionResistance
@@ -213,9 +210,6 @@
Radiation: 0.1
- type: ExplosionResistance
damageCoefficient: 0.2
- - type: TemperatureProtection
- heatingCoefficient: 0.001
- coolingCoefficient: 0.001
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetHardsuitMaxim
@@ -350,7 +344,7 @@
#Chief Engineer's Hardsuit
- type: entity
- parent: [ClothingOuterHardsuitBase, BaseCommandContraband]
+ parent: [ TemperatureProtectionGreat, ClothingOuterHardsuitBase, BaseCommandContraband]
id: ClothingOuterHardsuitEngineeringWhite
name: chief engineer's hardsuit
description: A special hardsuit that protects against hazardous, low pressure environments, made for the chief engineer of the station.
@@ -594,7 +588,7 @@
#Syndicate Elite Hardsuit
- type: entity
- parent: [ClothingOuterHardsuitBase, BaseSyndicateContraband]
+ parent: [ TemperatureProtectionGreat, ClothingOuterHardsuitBase, BaseSyndicateContraband ]
id: ClothingOuterHardsuitSyndieElite
name: syndicate elite hardsuit
description: An elite version of the blood-red hardsuit, with improved radiation resistance and fireproofing. Property of Gorlex Marauders.
@@ -606,9 +600,6 @@
- type: PressureProtection
highPressureMultiplier: 0.02
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.001
- coolingCoefficient: 0.001
- type: ExplosionResistance
damageCoefficient: 0.3
- type: FireProtection
@@ -986,8 +977,7 @@
highPressureMultiplier: 0.02
lowPressureMultiplier: 1000
- type: TemperatureProtection
- heatingCoefficient: 0.001
- coolingCoefficient: 0.001
+ coefficient: 0.001
- type: ExplosionResistance
damageCoefficient: 0.2
- type: FireProtection
@@ -1024,9 +1014,6 @@
- type: PressureProtection
highPressureMultiplier: 0.02
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.001
- coolingCoefficient: 0.001
- type: ExplosionResistance
damageCoefficient: 0.7
- type: Armor
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml
index c1f8e250cf..2612bc8507 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml
@@ -304,7 +304,7 @@
sprite: Clothing/OuterClothing/Misc/hospitalgown.rsi
- type: entity
- parent: ClothingOuterBase
+ parent: ClothingOuterJacketBase
id: ClothingOuterFlannelRed
name: red flannel jacket
description: An old fashioned red flannel jacket for space autumn.
@@ -336,11 +336,9 @@
color: "#670a09"
- state: equipped-OUTERCLOTHING-lines
color: "#000000"
- - type: TemperatureProtection
- coolingCoefficient: 0.3
- type: entity
- parent: ClothingOuterBase
+ parent: ClothingOuterJacketBase
id: ClothingOuterFlannelBlue
name: blue flannel jacket
description: An old fashioned blue flannel jacket for space autumn.
@@ -372,11 +370,9 @@
color: "#3232a6"
- state: equipped-OUTERCLOTHING-lines
color: "#000000"
- - type: TemperatureProtection
- coolingCoefficient: 0.3
- type: entity
- parent: ClothingOuterBase
+ parent: ClothingOuterJacketBase
id: ClothingOuterFlannelGreen
name: green flannel jacket
description: An old fashioned green flannel jacket for space autumn.
@@ -408,8 +404,6 @@
color: "#164d0f"
- state: equipped-OUTERCLOTHING-lines
color: "#000000"
- - type: TemperatureProtection
- coolingCoefficient: 0.3
- type: entity
parent: ClothingOuterBase
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml
index 7b81ad7ed2..1e9aae7b64 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml
@@ -36,7 +36,7 @@
#Emergency EVA
- type: entity
- parent: ClothingOuterEVASuitBaseToggleable
+ parent: [ TemperatureProtectionMinimal, ClothingOuterEVASuitBaseToggleable ]
id: ClothingOuterSuitEmergency
name: emergency pressure suit
description: This bulky, uninsulated pressure suit was clearly not meant for extended use in outer space, despite NanoTrasen's claims that it passes corporate regulations.
@@ -48,10 +48,6 @@
- type: ClothingSpeedModifier
walkModifier: 0.7
sprintModifier: 0.7
- - type: HeldSpeedModifier
- - type: TemperatureProtection
- heatingCoefficient: 0.75
- coolingCoefficient: 0.5
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetEVALarge
- type: Tag
@@ -63,7 +59,7 @@
#Prisoner EVA
- type: entity
- parent: ClothingOuterEVASuitBase
+ parent: [ TemperatureProtectionMinimal, ClothingOuterEVASuitBase]
id: ClothingOuterHardsuitEVAPrisoner
name: prisoner pressure suit
description: An emergency pressure suit meant for prisoners. It's not fully rated for extended use in outer space, but some wardens consider the lack of thermal insulation a feature.
@@ -75,10 +71,6 @@
- type: ClothingSpeedModifier
walkModifier: 0.7
sprintModifier: 0.7
- - type: HeldSpeedModifier
- - type: TemperatureProtection
- heatingCoefficient: 0.75
- coolingCoefficient: 0.5
- type: Tag
tags:
- SuitEVA
@@ -115,17 +107,10 @@
- type: Appearance
- type: Clothing
sprite: Clothing/OuterClothing/Hardsuits/paramed.rsi
- - type: PressureProtection
- highPressureMultiplier: 0.5
- lowPressureMultiplier: 1000
- type: ClothingSpeedModifier
walkModifier: 0.9
sprintModifier: 0.9
requireActivated: false
- - type: HeldSpeedModifier
- - type: TemperatureProtection
- heatingCoefficient: 0.1
- coolingCoefficient: 0.1
- type: Armor
modifiers:
coefficients:
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml
index 56508af486..169a322dd4 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml
@@ -1,5 +1,5 @@
- type: entity
- parent: [ClothingOuterBase, AllowSuitStorageClothingGasTanks, BaseChameleon]
+ parent: [ClothingOuterBase, TemperatureProtectionMinimal, AllowSuitStorageClothingGasTanks, BaseChameleon]
id: ClothingOuterChameleon
name: vest
description: A thick vest with a rubbery, water-resistant shell.
@@ -14,9 +14,6 @@
- type: ChameleonClothing
slot: [outerClothing]
default: ClothingOuterVest
- - type: TemperatureProtection # Same as a basic winter coat.
- heatingCoefficient: 1.1
- coolingCoefficient: 0.1
- type: Armor
modifiers:
coefficients:
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
index 1b1bfdab15..15b615e230 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
@@ -44,7 +44,7 @@
sprite: Clothing/OuterClothing/Suits/janitor_bombsuit.rsi
- type: entity
- parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing]
+ parent: [ ClothingOuterBaseLargeFireproof, AllowSuitStorageClothing ]
id: ClothingOuterSuitFire
name: fire suit
description: A suit that helps protect against hazardous temperatures.
@@ -55,11 +55,6 @@
sprite: Clothing/OuterClothing/Suits/fire.rsi
- type: PressureProtection
highPressureMultiplier: 0.04
- - type: TemperatureProtection
- heatingCoefficient: 0.005
- coolingCoefficient: 0.05
- - type: FireProtection
- reduction: 0.65 # doesnt have a full seal so not as good
- type: Armor
modifiers:
coefficients:
@@ -78,7 +73,7 @@
- Tail
- type: entity
- parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing]
+ parent: [ TemperatureProtectionGreat, ClothingOuterEVASuitBase, AllowSuitStorageClothing ]
id: ClothingOuterSuitAtmosFire
name: atmos fire suit
description: An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame.
@@ -87,12 +82,6 @@
sprite: Clothing/OuterClothing/Suits/atmos_firesuit.rsi
- type: Clothing
sprite: Clothing/OuterClothing/Suits/atmos_firesuit.rsi
- - type: PressureProtection
- highPressureMultiplier: 0.02
- lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.001
- coolingCoefficient: 0.05
- type: FireProtection
reduction: 0.8 # atmos firesuit offers best protection, hardsuits are a little vulnerable
- type: Armor
@@ -101,19 +90,10 @@
Slash: 0.9
Heat: 0.8
Cold: 0.8
- - type: ClothingSpeedModifier
- walkModifier: 0.8
- sprintModifier: 0.8
- - type: HeldSpeedModifier
- type: GroupExamine
- - type: ProtectedFromStepTriggers
- slots: WITHOUT_POCKET
- type: Tag
tags:
- WhitelistChameleon
- - type: HideLayerClothing
- slots:
- - Tail
- type: entity
parent: [ClothingOuterBaseLarge, GeigerCounterClothing, AllowSuitStorageClothing]
@@ -146,7 +126,7 @@
- Tail
- type: entity
- parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing, BaseToggleClothing, BaseHighlyIllegalContraband]
+ parent: [ ClothingOuterBaseLargeInsulated, AllowSuitStorageClothing, BaseToggleClothing, BaseHighlyIllegalContraband ]
id: ClothingOuterSuitSpaceNinja
name: space ninja suit
description: This black technologically advanced, cybernetically-enhanced suit provides many abilities like invisibility or teleportation.
@@ -159,9 +139,6 @@
- type: PressureProtection
highPressureMultiplier: 0.6
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.01
- coolingCoefficient: 0.01
- type: Armor
modifiers:
coefficients:
@@ -297,16 +274,13 @@
relayAccent: true
- type: entity
- parent: ClothingOuterBase
+ parent: [ ClothingOuterBaseToggleable, AllowSuitStorageClothing ]
id: ClothingOuterSuitCarp
name: carp suit
description: A special suit that makes you look just like a space carp, if your eyesight is bad.
components:
- - type: AllowSuitStorage
- type: Sprite
sprite: Clothing/OuterClothing/Suits/carpsuit.rsi
- - type: Item
- size: Normal
- type: Clothing
sprite: Clothing/OuterClothing/Suits/carpsuit.rsi
- type: ToggleableClothing
@@ -321,7 +295,7 @@
- Tail
- type: entity
- parent: ClothingOuterSuitCarp
+ parent: [ ClothingOuterSuitCarp, TemperatureProtectionGood ]
id: ClothingOuterHardsuitCarp
suffix: Hardsuit, DO NOT MAP
categories: [ DoNotMap ]
@@ -329,9 +303,6 @@
- type: PressureProtection
highPressureMultiplier: 0.6
lowPressureMultiplier: 1000
- - type: TemperatureProtection
- heatingCoefficient: 0.01
- coolingCoefficient: 0.01
- type: ToggleableClothing
clothingPrototype: ClothingHeadHelmetHardsuitCarp
- type: StaticPrice
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml
index 1d09b24a22..88699d7e26 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml
@@ -1,5 +1,5 @@
- type: entity
- parent: ClothingOuterStorageEdible
+ parent: [ TemperatureProtection, ClothingOuterJacketBase ]
id: ClothingOuterWinterCoat
name: winter coat
description: A heavy jacket made from 'synthetic' animal furs.
@@ -8,11 +8,6 @@
sprite: Clothing/OuterClothing/WinterCoats/coat.rsi
- type: Clothing
sprite: Clothing/OuterClothing/WinterCoats/coat.rsi
- - type: TemperatureProtection
- heatingCoefficient: 1.1
- coolingCoefficient: 0.1
- - type: Item
- size: Normal
- type: Armor
modifiers:
coefficients:
diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml b/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml
index e799e3ed30..5733cc7cfb 100644
--- a/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml
+++ b/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml
@@ -67,10 +67,6 @@
- type: entity
abstract: true
- parent: ClothingShoesBaseButcherable
+ parent: [ ClothingShoesBaseButcherable, TemperatureProtectionMinimal ]
id: ClothingShoesBaseWinterBoots
description: Fluffy boots to help survive even the coldest of winters.
- components:
- - type: TemperatureProtection
- heatingCoefficient: 1.025
- coolingCoefficient: 0.5
diff --git a/Resources/Prototypes/Entities/Clothing/base_clothing.yml b/Resources/Prototypes/Entities/Clothing/base_clothing.yml
index f02feff158..2cfbeac59b 100644
--- a/Resources/Prototypes/Entities/Clothing/base_clothing.yml
+++ b/Resources/Prototypes/Entities/Clothing/base_clothing.yml
@@ -69,3 +69,31 @@
- type: ItemToggle
onUse: false # can't really wear it like that
- type: ToggleClothing
+
+- type: entity
+ abstract: true
+ id: TemperatureProtectionMinimal # Really light jackets and hoods.
+ components:
+ - type: TemperatureProtection
+ coefficient: 0.8
+
+- type: entity
+ abstract: true
+ id: TemperatureProtection # Winter coats and environmentally sealed helmets
+ components:
+ - type: TemperatureProtection
+ coefficient: 0.6
+
+- type: entity
+ abstract: true
+ id: TemperatureProtectionGood # EVA suits and basic hardsuits
+ components:
+ - type: TemperatureProtection
+ coefficient: 0.05
+
+- type: entity
+ abstract: true
+ id: TemperatureProtectionGreat # Fire-retardant suits
+ components:
+ - type: TemperatureProtection
+ coefficient: 0.01
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index 45f6612f1a..90fa37782e 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -1801,9 +1801,6 @@
types:
Slash: 5
Piercing: 4
- - type: Temperature
- currentTemperature: 310.15
- specificHeat: 42
- type: TemperatureDamage
heatDamageThreshold: 360
coldDamageThreshold: 285
@@ -2533,9 +2530,6 @@
- type: NpcFactionMember
factions:
- Passive
- - type: Temperature
- currentTemperature: 310.15
- specificHeat: 46
- type: TemperatureDamage
heatDamageThreshold: 335
coldDamageThreshold: 230
@@ -3228,9 +3222,6 @@
damageModifierSet: Infernal
- type: Injurable
damageContainer: BiologicalMetaphysical
- - type: Temperature
- currentTemperature: 310.15
- specificHeat: 42
- type: TemperatureDamage
heatDamageThreshold: 4000 #They come from hell, so..
coldDamageThreshold: 260
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/scurret.yml b/Resources/Prototypes/Entities/Mobs/NPCs/scurret.yml
index f0795a1fd6..4ceddc730a 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/scurret.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/scurret.yml
@@ -35,9 +35,6 @@
0: Alive
60: Critical
125: Dead
- - type: Temperature
- currentTemperature: 310.15
- specificHeat: 42
- type: TemperatureDamage
heatDamageThreshold: 360
coldDamageThreshold: 285
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
index b2427856f7..547886f76a 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
@@ -105,8 +105,6 @@
- type: GhostTakeoverAvailable
- type: TypingIndicator
proto: alien
- - type: Temperature
- currentTemperature: 310.15
- type: TemperatureDamage
heatDamageThreshold: 360
coldDamageThreshold: -150
@@ -529,8 +527,6 @@
amount: 5
- type: TypingIndicator
proto: alien
- - type: Temperature
- currentTemperature: 310.15
- type: TemperatureDamage
heatDamageThreshold: 360
coldDamageThreshold: -150
diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml
index f3447bce3a..ee63ea6d9b 100644
--- a/Resources/Prototypes/Entities/Mobs/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/base.yml
@@ -179,8 +179,6 @@
components:
- type: AtmosExposed
- type: Temperature
- currentTemperature: 310.15
- specificHeat: 42
- type: TemperatureDamage
heatDamageThreshold: 325
coldDamageThreshold: 0
@@ -214,9 +212,6 @@
shiveringHeatRegulation: 500
normalBodyTemperature: 310.15
thermalRegulationTemperatureThreshold: 2
- - type: Temperature
- currentTemperature: 310.15
- specificHeat: 42
- type: TemperatureDamage
heatDamageThreshold: 325
coldDamageThreshold: 260
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml
index ca6c7ca508..20ead4214c 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml
@@ -60,13 +60,11 @@
# all below are for egg cooking/exploding
- type: AtmosExposed
- type: Temperature
+ thermalConductivity: 1
+ temperature: 293.15
+ heatCapacity: 15000
- type: TemperatureDamage
- type: InternalTemperature
- # ~1mm shell and ~1cm of albumen
- thickness: 0.011
- area: 0.04
- # conductivity of egg shell based on a paper from Romanoff and Romanoff (1949)
- conductivity: 0.456
# Splat
- type: entity
@@ -123,6 +121,6 @@
- ReagentId: EggCooked
Quantity: 6
- type: Temperature
- # preserve temperature from the boiling step
- currentTemperature: 344
+ temperature: 344.0 # preserve temperature from the boiling step, TODO: Construction system that doesn't suck.
+ heatCapacity: 15000
- type: TemperatureDamage
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml
index 74e5c6ce53..9315826b78 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml
@@ -39,12 +39,11 @@
# let air cook and freeze meat for cooking and preservation
- type: AtmosExposed
- type: Temperature
- currentTemperature: 290
+ thermalConductivity: 1
+ heatCapacity: 750
- type: TemperatureDamage
# required for cooking to work
- type: InternalTemperature
- thickness: 0.02
- area: 0.02 # arbitrary number that sounds right for a slab of meat
- type: Material
- type: PhysicalComposition
materialComposition:
@@ -83,8 +82,6 @@
refineResult:
- id: FoodMeatCutlet
amount: 5
- - type: InternalTemperature
- conductivity: 0.43
- type: Construction
graph: MeatSteak
node: start
@@ -178,10 +175,6 @@
Quantity: 6
- ReagentId: Fat
Quantity: 9
- - type: InternalTemperature
- conductivity: 0.44
- thickness: 0.004 # bacon is thin so faster to cook than a steak
- area: 0.0075 # ~5x15cm
- type: Construction
graph: Bacon
node: start
@@ -256,8 +249,6 @@
refineResult:
- id: FoodMeatChickenCutlet
amount: 5
- - type: InternalTemperature
- conductivity: 0.41
- type: Construction
graph: ChickenSteak
node: start
@@ -352,8 +343,6 @@
components:
- type: Sprite
state: goliath
- - type: InternalTemperature
- thickness: 0.1 # very big, do cook it in lava
- type: Construction
graph: GoliathSteak
node: start
@@ -781,8 +770,6 @@
- Meat
- type: Sprite
state: raw_patty
- - type: InternalTemperature
- conductivity: 0.43
- type: Construction
graph: CookedPatty
node: start
@@ -851,8 +838,6 @@
refineResult:
- id: FoodMeatCutlet
amount: 16
- - type: InternalTemperature
- conductivity: 0.43
- type: Construction
graph: AnomalyMeatSteak
node: start
diff --git a/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml b/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml
index fd34bdf42b..82daefcb99 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml
@@ -46,11 +46,14 @@
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Temperature
+ temperature: 250
+ heatCapacity: 13440.002
- type: TemperatureDamage
heatDamage:
types:
Heat: 5
coldDamage: {}
+ heatDamageThreshold: 273.15
coldDamageThreshold: 0
- type: FrictionContacts
- type: TileFrictionModifier
diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
index b49a5b5238..eb98dcd15b 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
@@ -17,6 +17,19 @@
id: Kudzu
- type: SpeedModifierContacts
affectAirborne: true
+ - type: Fixtures
+ fixtures:
+ fix1:
+ hard: false
+ density: 7
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.5,-0.5,0.5,0.5"
+ layer:
+ - MidImpassable
+ - type: Temperature
+ temperature: 293.15
+ heatCapacity: 21000
- type: entity
parent: [ BaseKudzu, SolutionFood ]
@@ -35,16 +48,6 @@
drawdepth: Overdoors
- type: KudzuVisuals
- type: Clickable
- - type: Fixtures
- fixtures:
- fix1:
- hard: false
- density: 7
- shape:
- !type:PhysShapeAabb
- bounds: "-0.5,-0.5,0.5,0.5"
- layer:
- - MidImpassable
- type: Damageable
damageModifierSet: Wood
- type: Injurable
@@ -56,7 +59,6 @@
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- - type: Temperature
- type: TemperatureDamage
heatDamage:
types:
@@ -197,16 +199,6 @@
drawdepth: Overdoors
- type: KudzuVisuals
- type: Clickable
- - type: Fixtures
- fixtures:
- fix1:
- hard: false
- density: 7
- shape:
- !type:PhysShapeAabb
- bounds: "-0.5,-0.5,0.5,0.5"
- layer:
- - MidImpassable
- type: Damageable
- type: Injurable
- type: Destructible
@@ -235,6 +227,17 @@
Heat: -1.0
Cold: -1.0
Blunt: -0.5 # Needs to be balanced (approx 3x) with vacuum damage to stall but not kill Kudzu
+ - type: TemperatureDamage
+ heatDamage:
+ types:
+ Heat: 10
+ coldDamage:
+ types:
+ Cold: 5 #per second, scales with temperature & other constants
+ - type: Barotrauma
+ damage:
+ types:
+ Blunt: 0.10 #per second, scales with pressure and other constants.
- type: Flammable
fireSpread: true
damage:
diff --git a/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml
index f6a63b0e5b..fdbeaa8dbb 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml
@@ -87,12 +87,11 @@
defaultTarget: cooked spaceshroom
- type: AtmosExposed
- type: Temperature
- currentTemperature: 290
+ thermalConductivity: 1
+ temperature: 293.15
+ heatCapacity: 750
- type: TemperatureDamage
- type: InternalTemperature
- thickness: 0.02
- area: 0.02
- conductivity: 0.43
- type: Tag
tags:
- Mushroom
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
index ad6c09834d..9091f36401 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml
@@ -108,7 +108,7 @@
- type: Climbable # so that ejected bodies don't get stuck
vaultable: false
- type: ContainerTemperature
- coldDamageThreshold: 10
+ coldDamageThreshold: 213
- type: GuideHelp
guides:
- Cryogenics
diff --git a/Resources/Prototypes/Entities/Structures/spider_web.yml b/Resources/Prototypes/Entities/Structures/spider_web.yml
index 6a223e9179..50dba8e2ee 100644
--- a/Resources/Prototypes/Entities/Structures/spider_web.yml
+++ b/Resources/Prototypes/Entities/Structures/spider_web.yml
@@ -24,13 +24,6 @@
- type: Damageable
damageModifierSet: Wood
- type: Injurable
- - type: Temperature
- - type: TemperatureDamage
- heatDamage:
- types:
- Heat: 5
- coldDamage: {}
- coldDamageThreshold: 0
- type: Flammable
fireSpread: true
damage:
diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml
index a38be9e4cb..b0542174b3 100644
--- a/Resources/Prototypes/Reagents/medicine.yml
+++ b/Resources/Prototypes/Reagents/medicine.yml
@@ -188,17 +188,19 @@
metabolisms:
Bloodstream:
effects:
- - !type:EvenHealthChange
- conditions:
- - !type:TemperatureCondition
- # this is a little arbitrary but they gotta be pretty cold
- max: 213.0
- damage:
- # todo scale with temp like SS13
- Airloss: -3
- Brute: -2
- Burn: -2
- Toxin: -2
+ - !type:AdjustTemperature
+ amount: 80000 # Heats you up as it metabolizes necessitating constant cooling.
+ - !type:EvenHealthChange
+ conditions:
+ - !type:TemperatureCondition
+ # taken from tg, must be below freezing.
+ max: 273.15
+ damage:
+ # todo scale with temp like SS13
+ Airloss: -3
+ Brute: -2
+ Burn: -2
+ Toxin: -2
- type: reagent
id: Doxarubixadone
@@ -214,7 +216,7 @@
- !type:HealthChange
conditions:
- !type:TemperatureCondition
- max: 213.0
+ max: 273.15
damage:
types:
Cellular: -2
@@ -520,12 +522,12 @@
conditions:
- !type:TemperatureCondition
max: 293.15
- amount: 100000 # thermal energy, not temperature!
+ amount: 1000000 # thermal energy, not temperature!
- !type:AdjustTemperature
conditions:
- !type:TemperatureCondition
min: 293.15
- amount: -10000
+ amount: -1000000
- !type:PopupMessage
type: Local
visualType: Medium
@@ -1327,10 +1329,12 @@
metabolisms:
Bloodstream:
effects:
+ - !type:AdjustTemperature
+ amount: 80000 # Heats you up as it metabolizes necessitating constant cooling.
- !type:EvenHealthChange
conditions:
- !type:TemperatureCondition
- max: 213.0
+ max: 273.15
damage:
Brute: -3
Burn: -3
From c73fc00e8a6a758a08722151c90cd6425d21d6da Mon Sep 17 00:00:00 2001
From: Schuyler Duryee
Date: Sun, 26 Jul 2026 01:13:45 -0700
Subject: [PATCH 08/35] Fix default metamorphic glasses (e.g. milk) solution
not showing (#44917)
* add check to set fill sprite to visible for default metamorphic glasses
* edit comment for clarity
---
.../Chemistry/Visualizers/SolutionContainerVisualsSystem.cs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
index 7d2874bd11..7b39ad514b 100644
--- a/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
+++ b/Content.Client/Chemistry/Visualizers/SolutionContainerVisualsSystem.cs
@@ -80,6 +80,10 @@ public sealed partial class SolutionContainerVisualsSystem : VisualizerSystem
Date: Sun, 26 Jul 2026 11:43:23 +0300
Subject: [PATCH 09/35] Prediction + small Cleanup: WaggingSystem (#44867)
* Predict
* Update Content.Shared/Wagging/WaggingSystem.cs
Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com>
* refactor: fix xml-doc
* refactor: remove excessive dirty
---------
Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com>
Co-authored-by: Fildrance
---
Content.Shared/Wagging/WaggingComponent.cs | 32 ++++++---
.../Wagging/WaggingSystem.cs | 69 ++++++++++++-------
2 files changed, 68 insertions(+), 33 deletions(-)
rename {Content.Server => Content.Shared}/Wagging/WaggingSystem.cs (66%)
diff --git a/Content.Shared/Wagging/WaggingComponent.cs b/Content.Shared/Wagging/WaggingComponent.cs
index 4173cb5173..c96884a265 100644
--- a/Content.Shared/Wagging/WaggingComponent.cs
+++ b/Content.Shared/Wagging/WaggingComponent.cs
@@ -1,38 +1,50 @@
using Content.Shared.Body;
-using Content.Shared.Chat.Prototypes;
using Content.Shared.Humanoid;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Wagging;
///
/// An emoting wag for markings.
///
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
+[Access(typeof(WaggingSystem))]
public sealed partial class WaggingComponent : Component
{
- [DataField]
+ ///
+ /// The prototype id of the wagging action.
+ ///
+ [DataField, AutoNetworkedField]
public EntProtoId Action = "ActionToggleWagging";
- [DataField]
+ ///
+ /// Reference to the action entity.
+ ///
+ [DataField, AutoNetworkedField]
public EntityUid? ActionEntity;
- [DataField]
+ ///
+ /// The visual layer of the tail marking.
+ ///
+ [DataField, AutoNetworkedField]
public HumanoidVisualLayers Layer = HumanoidVisualLayers.Tail;
- [DataField]
+ ///
+ /// The organ category to which the tail is attached.
+ ///
+ [DataField, AutoNetworkedField]
public ProtoId Organ = "Torso";
///
- /// Suffix to add to get the animated marking.
+ /// The suffix to add to get the animated marking.
///
+ [DataField, AutoNetworkedField]
public string Suffix = "Animated";
///
- /// Is the entity currently wagging.
+ /// Whether the entity is currently wagging.
///
- [DataField]
+ [DataField, AutoNetworkedField]
public bool Wagging = false;
}
diff --git a/Content.Server/Wagging/WaggingSystem.cs b/Content.Shared/Wagging/WaggingSystem.cs
similarity index 66%
rename from Content.Server/Wagging/WaggingSystem.cs
rename to Content.Shared/Wagging/WaggingSystem.cs
index 8aa401e571..7b227bedf7 100644
--- a/Content.Server/Wagging/WaggingSystem.cs
+++ b/Content.Shared/Wagging/WaggingSystem.cs
@@ -1,34 +1,26 @@
-using Content.Server.Actions;
+using Content.Shared.Actions;
using Content.Shared.Body;
using Content.Shared.Cloning.Events;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Mobs;
using Content.Shared.Toggleable;
-using Content.Shared.Wagging;
-using Robust.Shared.Prototypes;
+using JetBrains.Annotations;
using Robust.Shared.Utility;
-namespace Content.Server.Wagging;
+namespace Content.Shared.Wagging;
///
-/// Adds an action to toggle wagging animation for tails markings that supporting this
+/// Adds an action to toggle the wagging animation for tails markings that support it.
///
public sealed partial class WaggingSystem : EntitySystem
{
- [Dependency] private ActionsSystem _actions = default!;
+ [Dependency] private SharedActionsSystem _actions = default!;
[Dependency] private SharedVisualBodySystem _visualBody = default!;
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnWaggingMapInit);
- SubscribeLocalEvent(OnWaggingShutdown);
- SubscribeLocalEvent(OnWaggingToggle);
- SubscribeLocalEvent(OnMobStateChanged);
- SubscribeLocalEvent(OnCloning);
- }
-
+ ///
+ /// Copies the component and its values to the clone.
+ ///
+ [SubscribeLocalEvent]
private void OnCloning(Entity ent, ref CloningEvent args)
{
if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name))
@@ -43,35 +35,64 @@ public sealed partial class WaggingSystem : EntitySystem
AddComp(args.CloneUid, cloneComp, true);
}
+ ///
+ /// Adds the wagging action during initialization.
+ ///
+ [SubscribeLocalEvent]
private void OnWaggingMapInit(Entity ent, ref MapInitEvent args)
{
_actions.AddAction(ent, ref ent.Comp.ActionEntity, ent.Comp.Action, ent);
+ DirtyField(ent.AsNullable(), nameof(WaggingComponent.ActionEntity));
}
+ ///
+ /// Removes the wagging action during shutdown.
+ ///
+ [SubscribeLocalEvent]
private void OnWaggingShutdown(Entity ent, ref ComponentShutdown args)
{
_actions.RemoveAction(ent.Owner, ent.Comp.ActionEntity);
}
+ ///
+ /// Attempts to toggle the wagging action.
+ ///
+ [SubscribeLocalEvent]
private void OnWaggingToggle(Entity ent, ref ToggleActionEvent args)
{
if (args.Handled)
return;
- TryToggleWagging(ent.AsNullable());
+ if (!TryToggleWagging(ent.AsNullable()))
+ return;
+
+ args.Handled = true;
}
+ ///
+ /// Tries to disable wagging when the mob state changes.
+ ///
+ [SubscribeLocalEvent]
private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args)
{
- if (ent.Comp.Wagging)
- TryToggleWagging(ent.AsNullable());
+ TryToggleWagging(ent.AsNullable(), false);
}
- private bool TryToggleWagging(Entity ent)
+ ///
+ /// If provided - attempts to set wagging to it, otherwise tries to toggle wagging.
+ ///
+ /// The entity to toggle.
+ /// The desired wagging state.
+ /// Whether wagging was toggled.
+ [PublicAPI]
+ public bool TryToggleWagging(Entity ent, bool? desired = null)
{
if (!Resolve(ent, ref ent.Comp))
return false;
+ if (desired == ent.Comp.Wagging)
+ return false;
+
if (!_visualBody.TryGatherMarkingsData(ent.Owner,
[ent.Comp.Layer],
out _,
@@ -85,14 +106,15 @@ public sealed partial class WaggingSystem : EntitySystem
return false;
ent.Comp.Wagging = !ent.Comp.Wagging;
+ DirtyField(ent, nameof(WaggingComponent.Wagging));
markingsSet = markingsSet.ShallowClone();
- foreach (var (layers, markings) in markingsSet)
+ foreach (var (layers, _) in markingsSet)
{
markingsSet[layers] = markingsSet[layers].ShallowClone();
var layerMarkings = markingsSet[layers];
- for (int i = 0; i < layerMarkings.Count; i++)
+ for (var i = 0; i < layerMarkings.Count; i++)
{
var currentMarkingId = layerMarkings[i].MarkingId;
string newMarkingId;
@@ -128,6 +150,7 @@ public sealed partial class WaggingSystem : EntitySystem
{
[ent.Comp.Organ] = markingsSet
});
+
return true;
}
}
From 63c90e4d4e331c53dee76de20c205b33269888dd Mon Sep 17 00:00:00 2001
From: pathetic meowmeow
Date: Sun, 26 Jul 2026 05:20:50 -0400
Subject: [PATCH 10/35] Organ relations (#44812)
* Organ relations
* address reviews
---
.../Tests/Body/DetachableOrganSystemTest.cs | 111 +++++++++++
.../Tests/Body/InitialBodySpawnTest.cs | 67 +++++++
.../Tests/Body/InitialBodyValidationTest.cs | 179 ++++++++++++++++++
.../Tests/Body/OrganRelationSystemTest.cs | 93 +++++++++
.../Tests/Sprite/ItemSpriteTest.cs | 3 +-
Content.Server/Body/BodyCommand.cs | 42 ++++
Content.Server/Body/OrganCommand.cs | 112 +++++++++++
Content.Shared/Body/ChildOrganComponent.cs | 17 ++
.../Body/DetachableOrganComponent.cs | 18 ++
Content.Shared/Body/DetachableOrganSystem.cs | 52 +++++
Content.Shared/Body/InitialBodyComponent.cs | 6 +
Content.Shared/Body/InitialBodySystem.cs | 25 ++-
.../Body/InternalChildOrganComponent.cs | 9 +
Content.Shared/Body/OrganRelationSystem.cs | 120 ++++++++++++
Content.Shared/Body/ParentOrganComponent.cs | 17 ++
Resources/Locale/en-US/body/commands.ftl | 23 +++
Resources/Prototypes/Body/base_organs.yml | 109 +++++++++--
Resources/Prototypes/Body/detached.yml | 15 ++
.../Prototypes/Body/species_appearance.yml | 8 +
19 files changed, 1004 insertions(+), 22 deletions(-)
create mode 100644 Content.IntegrationTests/Tests/Body/DetachableOrganSystemTest.cs
create mode 100644 Content.IntegrationTests/Tests/Body/InitialBodySpawnTest.cs
create mode 100644 Content.IntegrationTests/Tests/Body/InitialBodyValidationTest.cs
create mode 100644 Content.IntegrationTests/Tests/Body/OrganRelationSystemTest.cs
create mode 100644 Content.Server/Body/BodyCommand.cs
create mode 100644 Content.Server/Body/OrganCommand.cs
create mode 100644 Content.Shared/Body/ChildOrganComponent.cs
create mode 100644 Content.Shared/Body/DetachableOrganComponent.cs
create mode 100644 Content.Shared/Body/DetachableOrganSystem.cs
create mode 100644 Content.Shared/Body/InternalChildOrganComponent.cs
create mode 100644 Content.Shared/Body/OrganRelationSystem.cs
create mode 100644 Content.Shared/Body/ParentOrganComponent.cs
create mode 100644 Resources/Locale/en-US/body/commands.ftl
create mode 100644 Resources/Prototypes/Body/detached.yml
diff --git a/Content.IntegrationTests/Tests/Body/DetachableOrganSystemTest.cs b/Content.IntegrationTests/Tests/Body/DetachableOrganSystemTest.cs
new file mode 100644
index 0000000000..67d210ccf8
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Body/DetachableOrganSystemTest.cs
@@ -0,0 +1,111 @@
+using System.Linq;
+using Content.IntegrationTests.Fixtures;
+using Content.IntegrationTests.Fixtures.Attributes;
+using Content.Shared.Body;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.IntegrationTests.Tests.Body;
+
+[TestFixture]
+[TestOf(typeof(DetachableOrganSystem))]
+public sealed class DetachableOrganSystemTest : GameTest
+{
+ private const string DetachTestOldBody = "DetachTestOldBody";
+ private const string DetachTestNewBody = "DetachTestNewBody";
+ private const string DetachTestGrandParentOrgan = "DetachTestGrandParentOrgan";
+ private const string DetachTestRootOrgan = "DetachTestRootOrgan";
+ private const string DetachTestChildOrgan = "DetachTestChildOrgan";
+ private const string DetachTestSiblingOrgan = "DetachTestSiblingOrgan";
+
+ [TestPrototypes]
+ private const string Prototypes = $@"
+- type: entity
+ id: {DetachTestOldBody}
+ components:
+ - type: Body
+
+- type: entity
+ id: {DetachTestNewBody}
+ components:
+ - type: Body
+
+- type: entity
+ id: {DetachTestGrandParentOrgan}
+ components:
+ - type: Organ
+ - type: ParentOrgan
+ - type: ChildOrgan
+
+- type: entity
+ id: {DetachTestRootOrgan}
+ components:
+ - type: Organ
+ - type: ParentOrgan
+ - type: ChildOrgan
+ - type: DetachableOrgan
+ detachedBody: DetachTestNewBody
+
+- type: entity
+ id: {DetachTestChildOrgan}
+ components:
+ - type: Organ
+ - type: ChildOrgan
+
+- type: entity
+ id: {DetachTestSiblingOrgan}
+ components:
+ - type: Organ
+ - type: ChildOrgan
+";
+
+ [SidedDependency(Side.Server)] private SharedContainerSystem _container = default!;
+ [SidedDependency(Side.Server)] private OrganRelationSystem _organRelation = default!;
+ [SidedDependency(Side.Server)] private DetachableOrganSystem _detachableOrgan = default!;
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void Detach()
+ {
+ var oldBody = SSpawn(DetachTestOldBody);
+ var oldBodyContainer = _container.GetContainer(oldBody, BodyComponent.ContainerID);
+
+ var grandParent = SSpawn(DetachTestGrandParentOrgan);
+ var root = SSpawn(DetachTestRootOrgan);
+ var child = SSpawn(DetachTestChildOrgan);
+ var sibling = SSpawn(DetachTestSiblingOrgan);
+
+ _container.Insert(grandParent, oldBodyContainer, force: true);
+ _container.Insert(root, oldBodyContainer, force: true);
+ _container.Insert(child, oldBodyContainer, force: true);
+ _container.Insert(sibling, oldBodyContainer, force: true);
+
+ _organRelation.Relate(grandParent, root);
+ _organRelation.Relate(grandParent, sibling);
+ _organRelation.Relate(root, child);
+
+ var newBody = _detachableOrgan.Detach(root);
+
+ Assert.That(newBody, Is.Not.Null);
+
+ var newBodyContained = _container.GetContainer(newBody!.Value, BodyComponent.ContainerID).ContainedEntities.ToList();
+ Assert.That(newBodyContained, Is.EquivalentTo(new[] { root, child }));
+
+ var oldBodyContained = oldBodyContainer.ContainedEntities.ToList();
+ Assert.That(oldBodyContained, Is.EquivalentTo(new[] { grandParent, sibling }));
+
+ var grandParentComp = SComp(grandParent);
+ Assert.That(grandParentComp.Children, Does.Not.Contain(root));
+ Assert.That(grandParentComp.Children, Does.Contain(sibling));
+
+ Assert.That(SComp(root).Parent, Is.Null);
+ Assert.That(SComp(root).Children, Does.Contain(child));
+ Assert.That(SComp(child).Parent, Is.EqualTo(root));
+
+ Assert.That(SComp(root).Body, Is.EqualTo(newBody));
+ Assert.That(SComp(child).Body, Is.EqualTo(newBody));
+ Assert.That(SComp(grandParent).Body, Is.EqualTo(oldBody));
+ Assert.That(SComp(sibling).Body, Is.EqualTo(oldBody));
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Body/InitialBodySpawnTest.cs b/Content.IntegrationTests/Tests/Body/InitialBodySpawnTest.cs
new file mode 100644
index 0000000000..8b1b855125
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Body/InitialBodySpawnTest.cs
@@ -0,0 +1,67 @@
+using System.Linq;
+using Content.IntegrationTests.Fixtures;
+using Content.IntegrationTests.Fixtures.Attributes;
+using Content.Shared.Body;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests.Body;
+
+[TestFixture]
+[TestOf(typeof(InitialBodySystem))]
+public sealed class InitialBodySpawnTest : GameTest
+{
+ [TestPrototypes]
+ private const string Prototypes = @"
+- type: organCategory
+ id: InitialBodySpawnTestParent
+
+- type: organCategory
+ id: InitialBodySpawnTestChild
+
+- type: entity
+ id: InitialBodySpawnTestBody
+ components:
+ - type: Body
+ - type: InitialBody
+ organs:
+ InitialBodySpawnTestParent: InitialBodySpawnTestParentOrgan
+ InitialBodySpawnTestChild: InitialBodySpawnTestChildOrgan
+ relationships:
+ InitialBodySpawnTestParent: [InitialBodySpawnTestChild]
+
+- type: entity
+ id: InitialBodySpawnTestParentOrgan
+ components:
+ - type: Organ
+ - type: ParentOrgan
+
+- type: entity
+ id: InitialBodySpawnTestChildOrgan
+ components:
+ - type: Organ
+ - type: ChildOrgan
+";
+
+ [SidedDependency(Side.Server)] private SharedContainerSystem _container = default!;
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void SpawningWiresUpOrganRelations()
+ {
+ var body = SSpawn("InitialBodySpawnTestBody");
+
+ var bodyContainer = _container.GetContainer(body, BodyComponent.ContainerID);
+ var contained = bodyContainer.ContainedEntities.ToList();
+ Assert.That(contained, Has.Count.EqualTo(2));
+
+ var parent = contained.Single(SEntMan.HasComponent);
+ var child = contained.Single(SEntMan.HasComponent);
+
+ var parentComp = SComp(parent);
+ var childComp = SComp(child);
+
+ Assert.That(parentComp.Children, Does.Contain(child));
+ Assert.That(childComp.Parent, Is.EqualTo(parent));
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Body/InitialBodyValidationTest.cs b/Content.IntegrationTests/Tests/Body/InitialBodyValidationTest.cs
new file mode 100644
index 0000000000..851964e422
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Body/InitialBodyValidationTest.cs
@@ -0,0 +1,179 @@
+using System.Collections.Generic;
+using System.Linq;
+using Content.IntegrationTests.Fixtures;
+using Content.IntegrationTests.Fixtures.Attributes;
+using Content.Shared.Body;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.IntegrationTests.Tests.Body;
+
+[TestFixture]
+[TestOf(typeof(InitialBodySystem))]
+public sealed class InitialBodyValidationTest : GameTest
+{
+ [SidedDependency(Side.Server)] private IComponentFactory _componentFactory = default!;
+ [SidedDependency(Side.Server)] private IPrototypeManager _prototype = default!;
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void RelatedOrgansCanBeRelated()
+ {
+ using var scope = Assert.EnterMultipleScope();
+
+ foreach (var proto in _prototype.EnumeratePrototypes())
+ {
+ if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
+ continue;
+
+ if (!proto.TryComp(out var initial, _componentFactory))
+ continue;
+
+ if (initial.Relationships is not { } relationships)
+ continue;
+
+ var organs = initial.Organs;
+ foreach (var (parent, children) in relationships)
+ {
+ if (!organs.TryGetValue(parent, out var parentProtoId))
+ continue;
+
+ var parentProto = _prototype.Index(parentProtoId);
+ if (!parentProto.HasComp(_componentFactory))
+ {
+ Assert.Fail($"{proto.ID}'s parent organ {parent} {parentProtoId} is missing {nameof(ParentOrganComponent)}");
+ }
+
+ foreach (var child in children)
+ {
+ if (!organs.TryGetValue(child, out var childProtoId))
+ continue;
+
+ var childProto = _prototype.Index(childProtoId);
+ if (!childProto.HasComp(_componentFactory))
+ {
+ Assert.Fail($"{proto.ID}'s child organ {child} {childProtoId} (child of {parent} {parentProtoId}) is missing {nameof(ChildOrganComponent)}");
+ }
+ }
+ }
+ }
+ }
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void NoMultipleParents()
+ {
+ using var scope = Assert.EnterMultipleScope();
+
+ foreach (var proto in _prototype.EnumeratePrototypes())
+ {
+ if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
+ continue;
+
+ if (!proto.TryComp(out var initial, _componentFactory))
+ continue;
+
+ if (initial.Relationships is not { } relationships)
+ continue;
+
+ var parentOf = new Dictionary, ProtoId>();
+ foreach (var (parent, children) in relationships)
+ {
+ foreach (var child in children)
+ {
+ if (parentOf.TryGetValue(child, out var existingParent))
+ {
+ Assert.Fail($"{proto.ID}'s organ category {child} is claimed as a child by both {existingParent} and {parent}");
+ }
+
+ parentOf[child] = parent;
+ }
+ }
+ }
+ }
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void InternalChildOrgansAreNotDetachable()
+ {
+ using var scope = Assert.EnterMultipleScope();
+
+ foreach (var proto in _prototype.EnumeratePrototypes())
+ {
+ if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
+ continue;
+
+ if (!proto.HasComp(_componentFactory))
+ continue;
+
+ if (proto.HasComp(_componentFactory))
+ {
+ Assert.Fail($"{proto.ID} has both {nameof(InternalChildOrganComponent)} and {nameof(DetachableOrganComponent)}. Pick a lane, make your organs internal or detachable, but not both.");
+ }
+ }
+ }
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void NoCyclicalRelationships()
+ {
+ using var scope = Assert.EnterMultipleScope();
+
+ foreach (var proto in _prototype.EnumeratePrototypes())
+ {
+ if (proto.Abstract || Pair.IsTestEntityPrototype(proto.ID))
+ continue;
+
+ if (!proto.TryComp(out var initial, _componentFactory))
+ continue;
+
+ if (initial.Relationships is not { } relationships)
+ continue;
+
+ var visited = new HashSet>();
+ var stack = new List>();
+
+ foreach (var parent in relationships.Keys)
+ {
+ if (TryFindCycle(parent, relationships, visited, stack, out var cycle))
+ {
+ Assert.Fail($"{proto.ID} has a cycle in its InitialBodyComponent relationships: {string.Join(" -> ", cycle)}");
+ }
+ }
+ }
+ }
+
+ private static bool TryFindCycle(
+ ProtoId node,
+ Dictionary, HashSet>> relationships,
+ HashSet> visited,
+ List> stack,
+ out List> cycle)
+ {
+ if (stack.Contains(node))
+ {
+ var start = stack.IndexOf(node);
+ cycle = stack.Skip(start).Append(node).ToList();
+ return true;
+ }
+
+ cycle = new List>();
+
+ if (!visited.Add(node))
+ return false;
+
+ if (!relationships.TryGetValue(node, out var children))
+ return false;
+
+ stack.Add(node);
+
+ foreach (var child in children)
+ {
+ if (TryFindCycle(child, relationships, visited, stack, out cycle))
+ return true;
+ }
+
+ stack.RemoveAt(stack.Count - 1);
+ return false;
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Body/OrganRelationSystemTest.cs b/Content.IntegrationTests/Tests/Body/OrganRelationSystemTest.cs
new file mode 100644
index 0000000000..ee012f9815
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Body/OrganRelationSystemTest.cs
@@ -0,0 +1,93 @@
+using System.Linq;
+using Content.IntegrationTests.Fixtures;
+using Content.IntegrationTests.Fixtures.Attributes;
+using Content.Shared.Body;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.IntegrationTests.Tests.Body;
+
+[TestFixture]
+[TestOf(typeof(OrganRelationSystem))]
+public sealed class OrganRelationSystemTest : GameTest
+{
+ [TestPrototypes]
+ private const string Prototypes = @"
+- type: entity
+ id: OrganRelationTestOrgan
+ components:
+ - type: Organ
+ - type: ParentOrgan
+ - type: ChildOrgan
+";
+
+ [SidedDependency(Side.Server)] private OrganRelationSystem _organRelation = default!;
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void RelateAndOrphan()
+ {
+ var parent = SSpawn("OrganRelationTestOrgan");
+ var child = SSpawn("OrganRelationTestOrgan");
+
+ _organRelation.Relate(parent, child);
+
+ var parentComp = SComp(parent);
+ var childComp = SComp(child);
+
+ Assert.That(parentComp.Children, Does.Contain(child));
+ Assert.That(childComp.Parent, Is.EqualTo(parent));
+
+ _organRelation.Orphan(child);
+
+ Assert.That(parentComp.Children, Does.Not.Contain(child));
+ Assert.That(childComp.Parent, Is.Null);
+ }
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void Traversal()
+ {
+ var grandParent = SSpawn("OrganRelationTestOrgan");
+ var parent = SSpawn("OrganRelationTestOrgan");
+ var child = SSpawn("OrganRelationTestOrgan");
+
+ _organRelation.Relate(grandParent, parent);
+ _organRelation.Relate(parent, child);
+
+ var allChildren = _organRelation.AllChildren(grandParent).Select(e => e.Owner).ToList();
+ Assert.That(allChildren, Is.EquivalentTo(new[] { parent, child }));
+
+ var allParents = _organRelation.AllParents(child).Select(e => e.Owner).ToList();
+ Assert.That(allParents, Is.EquivalentTo(new[] { parent, grandParent }));
+ }
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void ParentDeletion()
+ {
+ var parent = SSpawn("OrganRelationTestOrgan");
+ var child = SSpawn("OrganRelationTestOrgan");
+
+ _organRelation.Relate(parent, child);
+
+ SDeleteNow(parent);
+
+ Assert.That(SEntMan.Deleted(child), Is.False);
+ Assert.That(SComp(child).Parent, Is.Null);
+ }
+
+ [Test]
+ [RunOnSide(Side.Server)]
+ public void ChildDeletion()
+ {
+ var parent = SSpawn("OrganRelationTestOrgan");
+ var child = SSpawn("OrganRelationTestOrgan");
+
+ _organRelation.Relate(parent, child);
+
+ SDeleteNow(child);
+
+ Assert.That(SComp(parent).Children, Does.Not.Contain(child));
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs
index ca667f2e7a..4c2f0be60e 100644
--- a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs
+++ b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs
@@ -29,7 +29,8 @@ public sealed class PrototypeSaveTest : GameTest
{
// The only prototypes that should get ignored are those that REQUIRE setup to get a sprite. At that point it is
// the responsibility of the spawner to ensure that a valid sprite is set.
- "VirtualItem"
+ "VirtualItem",
+ "DetachedBody"
};
[Test]
diff --git a/Content.Server/Body/BodyCommand.cs b/Content.Server/Body/BodyCommand.cs
new file mode 100644
index 0000000000..6e9fe5b838
--- /dev/null
+++ b/Content.Server/Body/BodyCommand.cs
@@ -0,0 +1,42 @@
+using System.Linq;
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Body;
+using Robust.Shared.Containers;
+using Robust.Shared.Toolshed;
+
+namespace Content.Server.Body;
+
+[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
+public sealed class BodyCommand : ToolshedCommand
+{
+ private SharedContainerSystem? _container;
+
+ [CommandImplementation("insert")]
+ public void Insert([PipedArgument] EntityUid body, EntityUid organ)
+ {
+ _container ??= GetSys();
+
+ if (!_container.TryGetContainer(body, BodyComponent.ContainerID, out var container))
+ return;
+
+ _container.Insert(organ, container, force: true);
+ }
+
+ [CommandImplementation("organs")]
+ public IEnumerable Organs([PipedArgument] EntityUid body)
+ {
+ _container ??= GetSys();
+
+ if (!_container.TryGetContainer(body, BodyComponent.ContainerID, out var container))
+ return Enumerable.Empty();
+
+ return container.ContainedEntities.ToList();
+ }
+
+ [CommandImplementation("organs")]
+ public IEnumerable Organs([PipedArgument] IEnumerable body)
+ {
+ return body.SelectMany(Organs);
+ }
+}
diff --git a/Content.Server/Body/OrganCommand.cs b/Content.Server/Body/OrganCommand.cs
new file mode 100644
index 0000000000..e791783cf6
--- /dev/null
+++ b/Content.Server/Body/OrganCommand.cs
@@ -0,0 +1,112 @@
+using System.Linq;
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Body;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed;
+
+namespace Content.Server.Body;
+
+[ToolshedCommand, AdminCommand(AdminFlags.Debug)]
+public sealed class OrganCommand : ToolshedCommand
+{
+ private DetachableOrganSystem? _detachableOrgan;
+ private SharedContainerSystem? _container;
+ private OrganRelationSystem? _organRelation;
+
+ [CommandImplementation("parent")]
+ public EntityUid? Parent([PipedArgument] EntityUid child)
+ {
+ if (!TryComp(child, out var childOrgan))
+ return null;
+
+ return childOrgan.Parent;
+ }
+
+ [CommandImplementation("parent")]
+ public IEnumerable Parent([PipedArgument] IEnumerable children)
+ {
+ return children.Select(Parent);
+ }
+
+ [CommandImplementation("children")]
+ public IEnumerable Children([PipedArgument] EntityUid parent)
+ {
+ if (!TryComp(parent, out var parentOrgan))
+ yield break;
+
+ foreach (var child in parentOrgan.Children)
+ {
+ yield return child;
+ }
+ }
+
+ [CommandImplementation("children")]
+ public IEnumerable Children([PipedArgument] IEnumerable parents)
+ {
+ return parents.SelectMany(Children);
+ }
+
+ [CommandImplementation("detach")]
+ public EntityUid? Detach([PipedArgument] EntityUid organ)
+ {
+ _detachableOrgan ??= GetSys();
+ return _detachableOrgan.Detach(organ);
+ }
+
+ [CommandImplementation("detach")]
+ public IEnumerable Detach([PipedArgument] IEnumerable organs)
+ {
+ return organs.Select(Detach);
+ }
+
+ [CommandImplementation("attach")]
+ public void Attach([PipedArgument] EntityUid parent, EntityUid child)
+ {
+ _container ??= GetSys();
+ _organRelation ??= GetSys();
+
+ if (!TryComp(parent, out var parentOrgan) ||
+ !TryComp(child, out var childOrgan) ||
+ !TryComp(parent, out var parentOrganComponent) ||
+ parentOrganComponent.Body is not { } body)
+ return;
+
+ if (!_container.TryGetContainer(body, BodyComponent.ContainerID, out var container))
+ return;
+
+ _container.Insert(child, container, force: true);
+ _organRelation.Relate((parent, parentOrgan), (child, childOrgan));
+ }
+
+ [CommandImplementation("attach")]
+ public void Attach([PipedArgument] IEnumerable