Fix Dwarf Displacement Cloning (#45256)

* half working

* leave this here

* works!

* damageable

* fix

* fix

* no index

* comment

* move reserve down
This commit is contained in:
ScarKy0 2026-08-19 18:36:43 +00:00 • committed by GitHub
parent 3e05e1adbd
commit b7182f5643
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 193 additions and 60 deletions

View file

@ -12,51 +12,63 @@ public sealed partial class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomal
[Dependency] private DisplacementMapSystem _displacement = default!;
[Dependency] private EntityQuery<InnerBodyAnomalyVisualsComponent> _visualsQuery = default!;
[Dependency] private EntityQuery<InnerBodyAnomalyComponent> _anomalyQuery = default!;
public override void Initialize()
{
SubscribeLocalEvent<InnerBodyAnomalyComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
SubscribeLocalEvent<InnerBodyAnomalyVisualsComponent, AfterAutoHandleStateEvent>(OnVisualsAfterHandleState);
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
SubscribeLocalEvent<InnerBodyAnomalyVisualsComponent, ComponentShutdown>(OnVisualsShutdown);
}
private void OnAfterHandleState(Entity<InnerBodyAnomalyComponent> ent, ref AfterAutoHandleStateEvent args)
private void UpdateVisuals(Entity<InnerBodyAnomalyComponent, InnerBodyAnomalyVisualsComponent?> ent)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (ent.Comp.FallbackSprite is null)
if (ent.Comp1.FallbackSprite is null)
return;
var index = _sprite.LayerMapReserve((ent.Owner, sprite), ent.Comp.LayerMap);
var index = _sprite.LayerMapReserve((ent.Owner, sprite), ent.Comp1.LayerMap);
if (TryComp<HumanoidProfileComponent>(ent, out var humanoid) &&
ent.Comp.SpeciesSprites.TryGetValue(humanoid.Species, out var speciesSprite))
ent.Comp1.SpeciesSprites.TryGetValue(humanoid.Species, out var speciesSprite))
{
_sprite.LayerSetSprite((ent.Owner, sprite), index, speciesSprite);
}
else
{
_sprite.LayerSetSprite((ent.Owner, sprite), index, ent.Comp.FallbackSprite);
_sprite.LayerSetSprite((ent.Owner, sprite), index, ent.Comp1.FallbackSprite);
}
_sprite.LayerSetVisible((ent.Owner, sprite), index, true);
sprite.LayerSetShader(index, "unshaded");
if (_visualsQuery.TryGetComponent(ent, out var visuals) && visuals.Displacement != null)
if (ent.Comp2 != null && ent.Comp2.Displacement != null && ProtoMan.Resolve(ent.Comp2.Displacement, out var displacement))
{
if (ProtoMan.Resolve(visuals.Displacement, out var displacement))
{
_displacement.TryAddDisplacement(displacement.Displacement,
(ent.Owner, sprite),
index,
ent.Comp.LayerMap,
out _);
}
else
{
_displacement.EnsureDisplacementIsNotOnSprite((ent.Owner, sprite), ent.Comp.LayerMap);
}
_displacement.TryAddDisplacement(displacement.Displacement,
(ent.Owner, sprite),
index,
ent.Comp1.LayerMap,
out _);
}
else
{
_displacement.EnsureDisplacementIsNotOnSprite((ent.Owner, sprite), ent.Comp1.LayerMap);
}
}
private void OnAfterHandleState(Entity<InnerBodyAnomalyComponent> ent, ref AfterAutoHandleStateEvent args)
{
_visualsQuery.TryGetComponent(ent.Owner, out var visuals);
UpdateVisuals((ent, ent.Comp, visuals));
}
private void OnVisualsAfterHandleState(Entity<InnerBodyAnomalyVisualsComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (_anomalyQuery.TryGetComponent(ent.Owner, out var anomaly))
UpdateVisuals((ent.Owner, anomaly, ent.Comp));
}
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
@ -69,4 +81,17 @@ public sealed partial class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomal
_displacement.EnsureDisplacementIsNotOnSprite((ent.Owner, sprite), ent.Comp.LayerMap);
}
private void OnVisualsShutdown(Entity<InnerBodyAnomalyVisualsComponent> ent, ref ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (!TryComp<InnerBodyAnomalyComponent>(ent, out var anomaly))
return;
var index = _sprite.LayerMapGet((ent.Owner, sprite), anomaly.LayerMap);
_displacement.EnsureDisplacementIsNotOnSprite((ent.Owner, sprite), anomaly.LayerMap);
}
}

View file

@ -2,11 +2,13 @@ using System.Linq;
using Content.Client.DisplacementMap;
using Content.Shared.Body;
using Content.Shared.CCVar;
using Content.Shared.DisplacementMap;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Body;
@ -64,6 +66,7 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
if (Comp<OrganComponent>(ent).Body is not { } body)
return;
RemoveVisual(ent, body);
ApplyVisual(ent, body);
}
@ -83,6 +86,11 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
ent.Comp.Layer,
out _);
}
if (displacement == null)
{
_displacement.EnsureDisplacementIsNotOnSprite((target, Comp<SpriteComponent>(target)), ent.Comp.Layer);
}
}
private void RemoveVisual(Entity<VisualOrganComponent> ent, EntityUid target)
@ -124,9 +132,9 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
ApplyVisual(ent, body);
}
protected override void SetOrganMarkings(Entity<VisualOrganMarkingsComponent> ent, Dictionary<HumanoidVisualLayers, List<Marking>> markings)
protected override void SetOrganMarkings(Entity<VisualOrganMarkingsComponent> ent, Dictionary<HumanoidVisualLayers, List<Marking>> markings, Dictionary<HumanoidVisualLayers, DisplacementData> displacement)
{
base.SetOrganMarkings(ent, markings);
base.SetOrganMarkings(ent, markings, displacement);
if (Comp<OrganComponent>(ent).Body is not { } body)
return;
@ -135,9 +143,9 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
ApplyMarkings(ent, body);
}
protected override void SetOrganAppearance(Entity<VisualOrganComponent> ent, PrototypeLayerData data)
protected override void SetOrganAppearance(Entity<VisualOrganComponent> ent, PrototypeLayerData data, ProtoId<DisplacementDataPrototype>? displacement)
{
base.SetOrganAppearance(ent, data);
base.SetOrganAppearance(ent, data, displacement);
if (Comp<OrganComponent>(ent).Body is not { } body)
return;

View file

@ -14,6 +14,7 @@ public sealed partial class DisplacementMapSystem : EntitySystem
//needs to be replaced later: see comment on line 48
private static readonly ProtoId<ShaderPrototype> UnshadedID = "unshaded";
public static readonly ProtoId<ShaderPrototype> DisplacedUnshaded = "DisplacedDrawUnshaded";
private static string? BuildDisplacementLayerKey(object key)
{
@ -126,6 +127,23 @@ public sealed partial class DisplacementMapSystem : EntitySystem
if (displacementLayerKey is null)
return false;
return _sprite.RemoveLayer(sprite.AsNullable(), displacementLayerKey, false);
if (_sprite.RemoveLayer(sprite.AsNullable(), displacementLayerKey, false))
{
//TODO : this is a kinda janky workaround for the fact that the current rendering pipeline does not have
//proper support for multiple shaders on a given layer (or an ubershader to handle stacking all of the effects well)
//Same reason as where the displacement is added initially
var shader = sprite.Comp[key] is SpriteComponent.Layer layer && layer.ShaderPrototype == DisplacedUnshaded
? SpriteSystem.UnshadedId.Id
: null;
if (shader != null)
sprite.Comp.LayerSetShader(key, shader);
else
sprite.Comp.LayerSetShader(key, null, null);
return true;
}
return false;
}
}

View file

@ -29,8 +29,8 @@ public sealed partial class CreamPieSystem : SharedCreamPieSystem
private void OnComponentShutdown(Entity<CreamPiedComponent> ent, ref ComponentShutdown args)
{
_sprite.RemoveLayer(ent.Owner, CreamPiedVisualLayer.Key);
_displacement.EnsureDisplacementIsNotOnSprite((ent, Comp<SpriteComponent>(ent)), CreamPiedVisualLayer.Key);
_sprite.RemoveLayer(ent.Owner, CreamPiedVisualLayer.Key);
}
private void OnAppearanceChange(Entity<CreamPiedComponent> ent, ref AppearanceChangeEvent args)
@ -54,7 +54,6 @@ public sealed partial class CreamPieSystem : SharedCreamPieSystem
var appearance = ent.Comp3;
// If there is no sprite to use, remove the layer. Otherwise ensure that it exists and set the visuals accordingly.
int index;
if (creamPied.Sprite == null)
{
_sprite.RemoveLayer((ent.Owner, sprite), CreamPiedVisualLayer.Key);
@ -62,7 +61,7 @@ public sealed partial class CreamPieSystem : SharedCreamPieSystem
return;
}
index = _sprite.LayerMapReserve((ent.Owner, sprite), CreamPiedVisualLayer.Key);
int index = _sprite.LayerMapReserve((ent.Owner, sprite), CreamPiedVisualLayer.Key);
_appearance.TryGetData<bool>(ent.Owner, CreamPiedVisuals.Creamed, out var isCreamPied, appearance);
_sprite.LayerSetSprite((ent.Owner, sprite), index, creamPied.Sprite);
@ -72,5 +71,9 @@ public sealed partial class CreamPieSystem : SharedCreamPieSystem
{
_displacement.TryAddDisplacement(displacementProto.Displacement, (ent.Owner, sprite), index, CreamPiedVisualLayer.Key, out _);
}
else
{
_displacement.EnsureDisplacementIsNotOnSprite((ent.Owner, sprite), CreamPiedVisualLayer.Key);
}
}
}

View file

@ -494,5 +494,26 @@ namespace Content.Server.Atmos.EntitySystems
}
}
}
public void CopyComponent(Entity<FlammableComponent?> entity, EntityUid clone)
{
if (!Resolve(entity, ref entity.Comp, false))
return;
// Don't clone being on fire here.
var cloneComp = EnsureComp<FlammableComponent>(clone);
cloneComp.Displacement = entity.Comp.Displacement;
cloneComp.AlwaysCombustible = entity.Comp.AlwaysCombustible;
cloneComp.CanExtinguish = entity.Comp.CanExtinguish;
cloneComp.Damage = entity.Comp.Damage.Clone();
cloneComp.FirestackFade = entity.Comp.FirestackFade;
cloneComp.FirestacksOnIgnite = entity.Comp.FirestacksOnIgnite;
cloneComp.MaximumFireStacks = entity.Comp.MaximumFireStacks;
cloneComp.MinimumFireStacks = entity.Comp.MinimumFireStacks;
cloneComp.ResistTime = entity.Comp.ResistTime;
Dirty(clone, cloneComp);
UpdateAppearance(clone, cloneComp);
}
}
}

View file

@ -1,9 +1,12 @@
using Content.Shared.Body.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Systems;
using Content.Server.Speech.EntitySystems;
using Content.Shared.Cloning.Events;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.FixedPoint;
using Content.Shared.Forensics.Components;
using Content.Shared.Forensics.Systems;
@ -14,6 +17,8 @@ using Content.Shared.Movement.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Paper;
using Content.Shared.Speech.Components;
using Content.Shared.Speech.EntitySystems;
@ -42,7 +47,10 @@ public sealed partial class CloningSystem
[Dependency] private SharedChameleonClothingSystem _chameleonClothing = default!;
[Dependency] private PullingSystem _pulling = default!;
[Dependency] private BloodstreamSystem _bloodstream = default!;
[Dependency] private SharedCreamPieSystem _creampie = default!;
[Dependency] private ForensicsSystem _forensics = default!;
[Dependency] private FlammableSystem _flammable = default!;
[Dependency] private DamageableSystem _damageable = default!;
public override void Initialize()
{
@ -68,6 +76,9 @@ public sealed partial class CloningSystem
SubscribeLocalEvent<MovementSpeedModifierComponent, CloningEvent>(OnCloneMovementSpeedModifier);
SubscribeLocalEvent<PullerComponent, CloningEvent>(OnClonePuller);
SubscribeLocalEvent<BloodstreamComponent, CloningEvent>(OnCloneBloodstream);
SubscribeLocalEvent<CreamPiedComponent, CloningEvent>(OnCloneCreamPied);
SubscribeLocalEvent<FlammableComponent, CloningEvent>(OnCloneFlammable);
SubscribeLocalEvent<DamageableComponent, CloningEvent>(OnCloneDamageable);
}
private void OnCloneItemStack(Entity<StackComponent> ent, ref CloningItemEvent args)
@ -163,4 +174,28 @@ public sealed partial class CloningSystem
_bloodstream.CopyComponent(ent.AsNullable(), args.CloneUid);
}
private void OnCloneCreamPied(Entity<CreamPiedComponent> ent, ref CloningEvent args)
{
if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name))
return;
_creampie.CopyComponent(ent.AsNullable(), args.CloneUid);
}
private void OnCloneFlammable(Entity<FlammableComponent> ent, ref CloningEvent args)
{
if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name))
return;
_flammable.CopyComponent(ent.AsNullable(), args.CloneUid);
}
private void OnCloneDamageable(Entity<DamageableComponent> ent, ref CloningEvent args)
{
if (!args.Settings.EventComponents.Contains(Factory.GetRegistration(ent.Comp.GetType()).Name))
return;
_damageable.CopyComponent(ent.AsNullable(), args.CloneUid);
}
}

View file

@ -7,7 +7,7 @@ namespace Content.Shared.Anomaly.Components;
/// <summary>
/// Complementary visuals component to <see cref="InnerBodyAnomalyComponent"/>.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class InnerBodyAnomalyVisualsComponent : Component
{
/// <summary>

View file

@ -8,7 +8,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Atmos.Components
{
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] // TODO: Access restriction so that other systems have to use the API to modify fire stacks
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] // TODO: Access restriction so that other systems have to use the API to modify fire stacks
public sealed partial class FlammableComponent : Component
{
/// <summary>
@ -36,43 +36,34 @@ namespace Content.Shared.Atmos.Components
/// The time resisting being on fire will take.
/// The mob will be paralyzed for this duration.
/// </summary>
[DataField]
[DataField, AutoNetworkedField]
public TimeSpan ResistTime = TimeSpan.FromSeconds(2);
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool OnFire;
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float FireStacks;
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
[DataField, AutoNetworkedField]
public float MaximumFireStacks = 10f;
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
[DataField, AutoNetworkedField]
public float MinimumFireStacks = -10f;
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public string FlammableFixtureID = "flammable";
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
[DataField, AutoNetworkedField]
public float MinIgnitionTemperature = 373.15f;
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool FireSpread { get; private set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool CanResistFire { get; private set; } = false;
[DataField(required: true)]
[ViewVariables(VVAccess.ReadWrite)]
[DataField(required: true), AutoNetworkedField]
public DamageSpecifier Damage = new(); // Empty by default, we don't want any funny NREs.
/// <summary>
@ -84,28 +75,25 @@ namespace Content.Shared.Atmos.Components
/// <summary>
/// Should the component be set on fire by interactions with isHot entities
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
[DataField, AutoNetworkedField]
public bool AlwaysCombustible = false;
/// <summary>
/// Can the component anyhow lose its FireStacks?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
[DataField, AutoNetworkedField]
public bool CanExtinguish = true;
/// <summary>
/// How many firestacks should be applied to component when being set on fire?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
[DataField, AutoNetworkedField]
public float FirestacksOnIgnite = 2.0f;
/// <summary>
/// Determines how quickly the object will fade out. With positive values, the object will flare up instead of going out.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField, AutoNetworkedField]
public float FirestackFade = -0.1f;
[DataField]
@ -114,7 +102,7 @@ namespace Content.Shared.Atmos.Components
/// <summary>
/// If set, applies the displacement map associated with the <see cref="DisplacementDataPrototype"/>.
/// </summary>
[DataField]
[DataField, AutoNetworkedField]
public ProtoId<DisplacementDataPrototype>? Displacement;
}
}

View file

@ -1,4 +1,5 @@
using System.Linq;
using Content.Shared.DisplacementMap;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid;
using Robust.Shared.Containers;
@ -74,15 +75,17 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
Dirty(ent);
}
protected virtual void SetOrganAppearance(Entity<VisualOrganComponent> ent, PrototypeLayerData data)
protected virtual void SetOrganAppearance(Entity<VisualOrganComponent> ent, PrototypeLayerData data, ProtoId<DisplacementDataPrototype>? displacement)
{
ent.Comp.Data = data;
ent.Comp.Displacement = displacement;
Dirty(ent);
}
protected virtual void SetOrganMarkings(Entity<VisualOrganMarkingsComponent> ent, Dictionary<HumanoidVisualLayers, List<Marking>> markings)
protected virtual void SetOrganMarkings(Entity<VisualOrganMarkingsComponent> ent, Dictionary<HumanoidVisualLayers, List<Marking>> markings, Dictionary<HumanoidVisualLayers, DisplacementData> displacement)
{
ent.Comp.Markings = markings;
ent.Comp.MarkingsDisplacement = displacement.ShallowClone();
Dirty(ent);
}
@ -94,7 +97,7 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
if (!other.Layer.Equals(ent.Comp.Layer))
return;
SetOrganAppearance(ent, other.Data);
SetOrganAppearance(ent, other.Data, other.Displacement);
}
private void OnMarkingsOrganCopyAppearance(Entity<VisualOrganMarkingsComponent> ent, ref BodyRelayedEvent<OrganCopyAppearanceEvent> args)
@ -105,7 +108,7 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
if (!other.MarkingData.Layers.SetEquals(ent.Comp.MarkingData.Layers))
return;
SetOrganMarkings(ent, other.Markings);
SetOrganMarkings(ent, other.Markings, other.MarkingsDisplacement);
}
private void OnVisualOrganApplyProfile(Entity<VisualOrganComponent> ent, ref BodyRelayedEvent<ApplyOrganProfileDataEvent> args)
@ -130,7 +133,7 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
if (ent.Comp.SexStateOverrides is { } overrides && overrides.TryGetValue(data.Sex, out var state))
{
ent.Comp.Data.State = state;
SetOrganAppearance(ent, ent.Comp.Data);
SetOrganAppearance(ent, ent.Comp.Data, ent.Comp.Displacement);
}
}
@ -168,7 +171,7 @@ public abstract partial class SharedVisualBodySystem : EntitySystem
kvp => kvp.Key,
kvp => ResolveMarkings(kvp.Value, profile.SkinColor, profile.EyeColor, groupProto.Appearances));
SetOrganMarkings(ent, resolved);
SetOrganMarkings(ent, resolved, ent.Comp.MarkingsDisplacement);
}
}

View file

@ -76,9 +76,11 @@ public sealed partial class DamageableComponent : Component
[Serializable, NetSerializable]
public sealed class DamageableComponentState(
DamageSpecifier damage,
ProtoId<DamageModifierSetPrototype>? modifierSetId)
ProtoId<DamageModifierSetPrototype>? modifierSetId,
ProtoId<DisplacementDataPrototype>? displacement)
: ComponentState
{
public readonly DamageSpecifier Damage = damage;
public readonly ProtoId<DamageModifierSetPrototype>? ModifierSetId = modifierSetId;
public readonly ProtoId<DisplacementDataPrototype>? Displacement = displacement;
}

View file

@ -189,7 +189,8 @@ public sealed partial class DamageableSystem
{
args.State = new DamageableComponentState(
_netMan.IsServer ? ent.Comp.Damage : ent.Comp.Damage.Clone(),
ent.Comp.DamageModifierSetId
ent.Comp.DamageModifierSetId,
ent.Comp.Displacement
);
}
@ -199,6 +200,7 @@ public sealed partial class DamageableSystem
return;
ent.Comp.DamageModifierSetId = state.ModifierSetId;
ent.Comp.Displacement = state.Displacement;
// Has the damage actually changed?
var newDamage = state.Damage.Clone();

View file

@ -108,4 +108,17 @@ public sealed partial class DamageableSystem : EntitySystem
}
return damageTypes;
}
public void CopyComponent(Entity<DamageableComponent?> entity, EntityUid clone)
{
if (!Resolve(entity, ref entity.Comp, false))
return;
// Don't clone current damage values.
var cloneComp = EnsureComp<DamageableComponent>(clone);
cloneComp.Displacement = entity.Comp.Displacement;
cloneComp.DamageModifierSetId = entity.Comp.DamageModifierSetId;
cloneComp.RadiationDamageTypeIDs = new (entity.Comp.RadiationDamageTypeIDs);
Dirty(clone, cloneComp);
}
}

View file

@ -160,4 +160,16 @@ public abstract partial class SharedCreamPieSystem : EntitySystem
{
ActivatePayload(ent);
}
public void CopyComponent(Entity<CreamPiedComponent?> entity, EntityUid clone)
{
if (!Resolve(entity, ref entity.Comp, false))
return;
// We don't clone whether you are actively creampied on purpose.
var cloneComp = EnsureComp<CreamPiedComponent>(clone);
cloneComp.Displacement = entity.Comp.Displacement;
cloneComp.Sprite = entity.Comp.Sprite;
Dirty(clone, cloneComp);
}
}

View file

@ -33,12 +33,12 @@
id: Body
components:
# general
- CreamPied
- DetailExaminable
- Dna
- Fingerprint
- Grammar # Pronouns
- HumanoidProfile # Age, Sex, Gender
- InnerBodyAnomalyVisuals # How you look with anomalies
- MeleeWeapon # Attack animation, damage and sound
- NpcFactionMember
- RandomPrice
@ -73,6 +73,9 @@
- SpanishAccent
- StutteringAccent
eventComponents:
- Damageable # Sprites and damage modifier
- Flammable # Displacements
- CreamPied # Displacements
- Hands # displacements
- Puller
- Vocal # voice sounds