diff --git a/Content.Shared/Body/Components/BodyComponent.cs b/Content.Shared/Body/Components/BodyComponent.cs
index 113dbb3592..022d3a7799 100644
--- a/Content.Shared/Body/Components/BodyComponent.cs
+++ b/Content.Shared/Body/Components/BodyComponent.cs
@@ -21,6 +21,13 @@ public sealed class BodyComponent : Component, IDraggable
[DataField("gibSound")]
public SoundSpecifier GibSound = new SoundCollectionSpecifier("gib");
+ ///
+ /// The amount of legs required to move at full speed.
+ /// If 0, then legs do not impact speed.
+ ///
+ [DataField("requiredLegs")]
+ public int RequiredLegs;
+
bool IDraggable.CanStartDrag(StartDragDropEvent args)
{
return true;
diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs
index 237e63fe24..92a0c88a2a 100644
--- a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs
+++ b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs
@@ -164,6 +164,32 @@ public partial class SharedBodySystem
}
}
+ ///
+ /// Returns all body part slots in the graph, including ones connected by
+ /// body parts which are null.
+ ///
+ ///
+ ///
+ ///
+ public IEnumerable GetAllBodyPartSlots(EntityUid partId, BodyPartComponent? part = null)
+ {
+ if (!Resolve(partId, ref part, false))
+ yield break;
+
+ foreach (var slot in part.Children.Values)
+ {
+ if (!TryComp(slot.Child, out var childPart))
+ continue;
+
+ yield return slot;
+
+ foreach (var child in GetAllBodyPartSlots(slot.Child.Value, childPart))
+ {
+ yield return child;
+ }
+ }
+ }
+
public virtual HashSet GibBody(EntityUid? partId, bool gibOrgans = false,
BodyComponent? body = null, bool deleteItems = false)
{
diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
index f6951f0024..3c2ada65b2 100644
--- a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
+++ b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
@@ -6,6 +6,7 @@ using Content.Shared.Body.Organ;
using Content.Shared.Body.Part;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
+using Content.Shared.Movement.Components;
using Content.Shared.Random.Helpers;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@@ -211,6 +212,9 @@ public partial class SharedBodySystem
if (part.Body is { } newBody)
{
+ if (part.PartType == BodyPartType.Leg)
+ UpdateMovementSpeed(newBody);
+
var partAddedEvent = new BodyPartAddedEvent(slot.Id, part);
RaiseLocalEvent(newBody, ref partAddedEvent);
@@ -254,10 +258,11 @@ public partial class SharedBodySystem
var args = new BodyPartRemovedEvent(slot.Id, part);
RaiseLocalEvent(oldBody, ref args);
- if (part.PartType == BodyPartType.Leg &&
- !GetBodyChildrenOfType(oldBody, BodyPartType.Leg).Any())
+ if (part.PartType == BodyPartType.Leg)
{
- Standing.Down(oldBody);
+ UpdateMovementSpeed(oldBody);
+ if(!GetBodyChildrenOfType(oldBody, BodyPartType.Leg).Any())
+ Standing.Down(oldBody);
}
if (part.IsVital && !GetBodyChildrenOfType(oldBody, part.PartType).Any())
@@ -282,6 +287,44 @@ public partial class SharedBodySystem
return true;
}
+ public void UpdateMovementSpeed(EntityUid body, BodyComponent? component = null, MovementSpeedModifierComponent? movement = null)
+ {
+ if (!Resolve(body, ref component, ref movement, false))
+ return;
+
+ if (component.RequiredLegs <= 0)
+ return;
+
+ if (component.Root?.Child is not { } root)
+ return;
+
+ var allSlots = GetAllBodyPartSlots(root).ToHashSet();
+ var allLegs = new HashSet();
+ foreach (var slot in allSlots)
+ {
+ if (slot.Type == BodyPartType.Leg && slot.Child is { } child)
+ allLegs.Add(child);
+ }
+
+ var walkSpeed = 0f;
+ var sprintSpeed = 0f;
+ var acceleration = 0f;
+ foreach (var leg in allLegs)
+ {
+ if (!TryComp(leg, out var legModifier))
+ continue;
+
+ walkSpeed += legModifier.BaseWalkSpeed;
+ sprintSpeed += legModifier.BaseSprintSpeed;
+ acceleration += legModifier.Acceleration;
+ }
+
+ walkSpeed /= component.RequiredLegs;
+ sprintSpeed /= component.RequiredLegs;
+ acceleration /= component.RequiredLegs;
+ Movement.ChangeBaseSpeed(body, walkSpeed, sprintSpeed, acceleration, movement);
+ }
+
public bool DropPartAt(EntityUid? partId, EntityCoordinates dropAt, BodyPartComponent? part = null)
{
if (partId == null || !DropPart(partId, part))
diff --git a/Content.Shared/Body/Systems/SharedBodySystem.cs b/Content.Shared/Body/Systems/SharedBodySystem.cs
index b91aea1628..4d195e0a50 100644
--- a/Content.Shared/Body/Systems/SharedBodySystem.cs
+++ b/Content.Shared/Body/Systems/SharedBodySystem.cs
@@ -1,4 +1,5 @@
using Content.Shared.Damage;
+using Content.Shared.Movement.Systems;
using Content.Shared.Standing;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
@@ -14,6 +15,7 @@ public abstract partial class SharedBodySystem : EntitySystem
[Dependency] protected readonly SharedContainerSystem Containers = default!;
[Dependency] protected readonly DamageableSystem Damageable = default!;
[Dependency] protected readonly StandingStateSystem Standing = default!;
+ [Dependency] protected readonly MovementSpeedModifierSystem Movement = default!;
public override void Initialize()
{
diff --git a/Resources/Prototypes/Body/Parts/animal.yml b/Resources/Prototypes/Body/Parts/animal.yml
index 98e7bf5e6d..fb4ac59b2a 100644
--- a/Resources/Prototypes/Body/Parts/animal.yml
+++ b/Resources/Prototypes/Body/Parts/animal.yml
@@ -37,6 +37,7 @@
components:
- type: BodyPart
partType: Leg
+ - type: MovementSpeedModifier
- type: entity
id: FeetAnimal
diff --git a/Resources/Prototypes/Body/Parts/diona.yml b/Resources/Prototypes/Body/Parts/diona.yml
index 2a1d33fc4e..b2ce8c47ce 100644
--- a/Resources/Prototypes/Body/Parts/diona.yml
+++ b/Resources/Prototypes/Body/Parts/diona.yml
@@ -118,6 +118,9 @@
- type: BodyPart
partType: Leg
symmetry: Left
+ - type: MovementSpeedModifier
+ baseWalkSpeed : 1.5
+ baseSprintSpeed : 3.5
- type: entity
id: RightLegDiona
@@ -131,6 +134,9 @@
- type: BodyPart
partType: Leg
symmetry: Right
+ - type: MovementSpeedModifier
+ baseWalkSpeed : 1.5
+ baseSprintSpeed : 3.5
- type: entity
id: LeftFootDiona
diff --git a/Resources/Prototypes/Body/Parts/human.yml b/Resources/Prototypes/Body/Parts/human.yml
index be8a9e4edc..c999a6a0ff 100644
--- a/Resources/Prototypes/Body/Parts/human.yml
+++ b/Resources/Prototypes/Body/Parts/human.yml
@@ -136,6 +136,7 @@
- type: BodyPart
partType: Leg
symmetry: Left
+ - type: MovementSpeedModifier
- type: entity
id: RightLegHuman
@@ -152,6 +153,7 @@
- type: BodyPart
partType: Leg
symmetry: Right
+ - type: MovementSpeedModifier
- type: entity
id: LeftFootHuman
diff --git a/Resources/Prototypes/Body/Parts/reptilian.yml b/Resources/Prototypes/Body/Parts/reptilian.yml
index a98faf6c21..34b21d21ee 100644
--- a/Resources/Prototypes/Body/Parts/reptilian.yml
+++ b/Resources/Prototypes/Body/Parts/reptilian.yml
@@ -134,6 +134,9 @@
- type: BodyPart
partType: Leg
symmetry: Left
+ - type: MovementSpeedModifier
+ baseWalkSpeed : 2.7
+ baseSprintSpeed : 4.5
- type: entity
id: RightLegReptilian
@@ -150,6 +153,9 @@
- type: BodyPart
partType: Leg
symmetry: Right
+ - type: MovementSpeedModifier
+ baseWalkSpeed : 2.7
+ baseSprintSpeed : 4.5
- type: entity
id: LeftFootReptilian
diff --git a/Resources/Prototypes/Body/Parts/skeleton.yml b/Resources/Prototypes/Body/Parts/skeleton.yml
index af2ea2c6eb..dc9d1ac1cd 100644
--- a/Resources/Prototypes/Body/Parts/skeleton.yml
+++ b/Resources/Prototypes/Body/Parts/skeleton.yml
@@ -149,6 +149,7 @@
- type: BodyPart
partType: Leg
symmetry: Left
+ - type: MovementSpeedModifier
- type: entity
id: RightLegSkeleton
@@ -165,6 +166,7 @@
- type: BodyPart
partType: Leg
symmetry: Right
+ - type: MovementSpeedModifier
- type: entity
id: LeftFootSkeleton
diff --git a/Resources/Prototypes/Body/Parts/slime.yml b/Resources/Prototypes/Body/Parts/slime.yml
index 0ec7e611c4..1cf5da75fd 100644
--- a/Resources/Prototypes/Body/Parts/slime.yml
+++ b/Resources/Prototypes/Body/Parts/slime.yml
@@ -135,6 +135,7 @@
- type: BodyPart
partType: Leg
symmetry: Left
+ - type: MovementSpeedModifier
- type: entity
id: RightLegSlime
@@ -151,6 +152,7 @@
- type: BodyPart
partType: Leg
symmetry: Right
+ - type: MovementSpeedModifier
- type: entity
id: LeftFootSlime
diff --git a/Resources/Prototypes/Body/Parts/vox.yml b/Resources/Prototypes/Body/Parts/vox.yml
index e820511455..8606ebac14 100644
--- a/Resources/Prototypes/Body/Parts/vox.yml
+++ b/Resources/Prototypes/Body/Parts/vox.yml
@@ -136,6 +136,7 @@
- type: BodyPart
partType: Leg
symmetry: Left
+ - type: MovementSpeedModifier
- type: entity
id: RightLegVox
@@ -152,6 +153,7 @@
- type: BodyPart
partType: Leg
symmetry: Right
+ - type: MovementSpeedModifier
- type: entity
id: LeftFootVox
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index 6a361cf23a..bf02f311c0 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -711,6 +711,7 @@
speechSounds: Monkey
- type: Body
prototype: Primate
+ requiredLegs: 1 # TODO: More than 1 leg
- type: DamageStateVisuals
states:
Alive:
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
index 1324c2f577..cd29daacd1 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
@@ -50,6 +50,7 @@
Piercing: 8
- type: Body
prototype: Rat
+ requiredLegs: 1 # TODO: More than 1 leg
- type: Hunger # probably should be prototyped
thresholds:
Overfed: 200
@@ -225,6 +226,7 @@
Piercing: 3
- type: Body
prototype: Rat
+ requiredLegs: 1 # TODO: More than 1 leg
- type: Hunger # probably should be prototyped
thresholds:
Overfed: 200
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index 608cf0e7a2..9665486e79 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -177,6 +177,7 @@
species: Human
- type: Body
prototype: Human
+ requiredLegs: 2
- type: Damageable
damageContainer: Biological
- type: RadiationReceiver
@@ -366,6 +367,7 @@
species: Human
- type: Body
prototype: Human
+ requiredLegs: 2
- type: Damageable
damageContainer: Biological
- type: MobState
diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml
index 5bbd02ae05..5c58efab1b 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml
@@ -15,6 +15,7 @@
state: full
- type: Body
prototype: Diona
+ requiredLegs: 2
- type: Damageable
damageContainer: Biological
damageModifierSet: Diona
diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml
index 733de0f758..884cf7ce23 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml
@@ -24,6 +24,7 @@
scale: 1, 0.8
- type: Body
prototype: Human
+ requiredLegs: 2
- type: entity
save: false
diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml
index ceae93a83f..3ba3fee94f 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml
@@ -14,6 +14,7 @@
state: full
- type: Body
prototype: Reptilian
+ requiredLegs: 2
- type: LizardAccent
- type: Speech
speechSounds: Lizard
diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml
index 71cabfab5f..1faf9a31a5 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml
@@ -12,6 +12,7 @@
state: full
- type: Body
prototype: Skeleton
+ requiredLegs: 2
gibSound: /Audio/Effects/bone_rattle.ogg
- type: Damageable
damageContainer: Biological
diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml
index be38a22c4c..0f183594fe 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml
@@ -11,6 +11,7 @@
state: full
- type: Body
prototype: Slime
+ requiredLegs: 2
- type: Humanoid
species: SlimePerson
- type: Speech
diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml
index d5963eb15d..499dc2587a 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml
@@ -81,6 +81,7 @@
- map: [ "pocket2" ]
- type: Body
prototype: Vox
+ requiredLegs: 2
# Vox nitrogen stuff is handled in their metabolism
- type: Respirator
damage:
@@ -110,4 +111,5 @@
species: Vox
- type: Body
prototype: Vox
+ requiredLegs: 2