From 7409c32e0ca438b1e3dcff4271e60d2fa7931bef Mon Sep 17 00:00:00 2001 From: CrimeMoot Date: Sun, 10 Nov 2024 17:37:44 +0500 Subject: [PATCH] Revert "Revert "Upstreams"" --- Content.Client/Backmen/Body/BodySystem.cs | 73 ++++- Content.Client/Backmen/Surgery/SurgeryBui.cs | 43 +-- .../Backmen/Surgery/SurgerySystem.cs | 9 - .../Humanoid/HumanoidAppearanceSystem.cs | 8 +- .../Backmen/Surgery/SurgerySystem.cs | 13 +- Content.Server/Body/Systems/BodySystem.cs | 28 +- .../Thresholds/Behaviors/GibBehavior.cs | 5 +- .../Body/BodyPartAppearanceComponent.cs | 27 +- .../Body/SharedBodySystem.PartAppearance.cs | 175 ++++++---- .../Surgery/SharedSurgerySystem.Steps.cs | 15 + Content.Shared/Backmen/Surgery/SurgeryUI.cs | 5 + Content.Shared/Body/Part/BodyPartComponent.cs | 16 +- Content.Shared/Body/Part/BodyPartEvents.cs | 11 + .../Body/Systems/SharedBodySystem.Body.cs | 28 +- .../Body/Systems/SharedBodySystem.Parts.cs | 29 +- .../Body/Systems/SharedBodySystem.cs | 2 + .../Events/ProfileLoadFinishedEvent.cs | 9 + .../SharedHumanoidAppearanceSystem.cs | 4 +- Resources/Changelog/ChangelogBkm.yml | 10 + Resources/Locale/en-US/surgery/surgery-ui.ftl | 2 +- Resources/Prototypes/Body/Parts/skeleton.yml | 29 ++ .../Prototypes/Entities/Mobs/NPCs/animals.yml | 3 +- .../Entities/Mobs/Player/silicon_base.yml | 306 ++++++++++++++++++ .../Entities/Mobs/Species/skeleton.yml | 3 +- .../_Backmen/Body/Parts/shadowkin.yml | 53 +++ .../Entities/Objects/Misc/handcuffs.yml | 1 - .../_Backmen/Entities/Surgery/surgeries.yml | 4 +- 27 files changed, 762 insertions(+), 149 deletions(-) create mode 100644 Content.Shared/Humanoid/Events/ProfileLoadFinishedEvent.cs create mode 100644 Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml diff --git a/Content.Client/Backmen/Body/BodySystem.cs b/Content.Client/Backmen/Body/BodySystem.cs index 52af9d93493..bee569c1011 100644 --- a/Content.Client/Backmen/Body/BodySystem.cs +++ b/Content.Client/Backmen/Body/BodySystem.cs @@ -1,20 +1,83 @@ using Content.Shared.Body.Systems; using Content.Shared.Body.Part; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; using Robust.Client.GameObjects; +using Robust.Shared.Utility; +using Content.Shared.Body.Components; namespace Content.Client.Body.Systems; public sealed class BodySystem : SharedBodySystem { - protected override void UpdateAppearance(EntityUid uid, BodyPartAppearanceComponent component) + [Dependency] private readonly MarkingManager _markingManager = default!; + + private void ApplyMarkingToPart(MarkingPrototype markingPrototype, + IReadOnlyList? colors, + bool visible, + SpriteComponent sprite) + { + for (var j = 0; j < markingPrototype.Sprites.Count; j++) + { + var markingSprite = markingPrototype.Sprites[j]; + + if (markingSprite is not SpriteSpecifier.Rsi rsi) + { + continue; + } + + var layerId = $"{markingPrototype.ID}-{rsi.RsiState}"; + + if (!sprite.LayerMapTryGet(layerId, out _)) + { + var layer = sprite.AddLayer(markingSprite, j + 1); + sprite.LayerMapSet(layerId, layer); + sprite.LayerSetSprite(layerId, rsi); + } + + sprite.LayerSetVisible(layerId, visible); + + if (!visible) + { + continue; + } + + // Okay so if the marking prototype is modified but we load old marking data this may no longer be valid + // and we need to check the index is correct. + // So if that happens just default to white? + if (colors != null && j < colors.Count) + { + sprite.LayerSetColor(layerId, colors[j]); + } + else + { + sprite.LayerSetColor(layerId, Color.White); + } + } + } + + protected override void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component) { - if (TryComp(uid, out SpriteComponent? sprite)) + if (!TryComp(target, out SpriteComponent? sprite)) + return; + + if (component.Color != null) + sprite.Color = component.Color.Value; + + foreach (var (visualLayer, markingList) in component.Markings) { - if (component.Color != null) + foreach (var marking in markingList) { - //TODO a few things need to be adjusted before this is ready to be used - also need to find a way to update the player sprite - //sprite.Color = component.Color.Value; + if (!_markingManager.TryGetMarking(marking, out var markingPrototype)) + continue; + + ApplyMarkingToPart(markingPrototype, marking.MarkingColors, marking.Visible, sprite); } } } + + protected override void RemovePartMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance) + { + return; + } } diff --git a/Content.Client/Backmen/Surgery/SurgeryBui.cs b/Content.Client/Backmen/Surgery/SurgeryBui.cs index 37e741bf56c..31389d31f13 100644 --- a/Content.Client/Backmen/Surgery/SurgeryBui.cs +++ b/Content.Client/Backmen/Surgery/SurgeryBui.cs @@ -11,6 +11,8 @@ using Robust.Shared.Utility; using Robust.Shared.Timing; using Robust.Client.Timing; +using static Robust.Client.UserInterface.Control; +using OpenToolkit.GraphicsLibraryFramework; namespace Content.Client.Backmen.Surgery; @@ -24,7 +26,6 @@ public sealed class SurgeryBui : BoundUserInterface [Dependency] private readonly IGameTiming _timing = default!; private readonly SurgerySystem _system; - private readonly HandsSystem _hands; [ViewVariables] private SurgeryWindow? _window; @@ -37,25 +38,15 @@ public sealed class SurgeryBui : BoundUserInterface public SurgeryBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) { _system = _entities.System(); - _hands = _entities.System(); - - _system.OnStep += RefreshUI; - _hands.OnPlayerItemAdded += OnPlayerItemAdded; } - private void OnPlayerItemAdded(string handName, EntityUid item) + protected override void ReceiveMessage(BoundUserInterfaceMessage message) { - if (_throttling.handName.Equals(handName) - && _throttling.item.Equals(item) - && DateTime.UtcNow - _lastRefresh < TimeSpan.FromSeconds(0.2) - || !_timing.IsFirstTimePredicted - || _window == null - || !_window.IsOpen) + if (_window == null) return; - _throttling = (handName, item); - _lastRefresh = DateTime.UtcNow; - RefreshUI(); + if (message is SurgeryBuiRefreshMessage) + RefreshUI(); } protected override void UpdateState(BoundUserInterfaceState state) @@ -69,8 +60,6 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); if (disposing) _window?.Dispose(); - - _system.OnStep -= RefreshUI; } private void Update(SurgeryBuiState state) @@ -316,7 +305,6 @@ private void RefreshUI() { return; } - Logger.Debug($"Running RefreshUI on {Owner}"); var next = _system.GetNextStep(Owner, _part.Value, _surgery.Value.Ent); var i = 0; foreach (var child in _window.Steps.Children) @@ -357,26 +345,7 @@ private void RefreshUI() if (_player.LocalEntity is { } player && status == StepStatus.Next && !_system.CanPerformStep(player, Owner, _part.Value, stepButton.Step, false, out var popup, out var reason, out _)) - { stepButton.ToolTip = popup; - stepButton.Button.Disabled = true; - - switch (reason) - { - case StepInvalidReason.MissingSkills: - stepName.AddMarkup($" [color=red]{Loc.GetString("surgery-ui-window-steps-error-skills")}[/color]"); - break; - case StepInvalidReason.NeedsOperatingTable: - stepName.AddMarkup($" [color=red]{Loc.GetString("surgery-ui-window-steps-error-table")}[/color]"); - break; - case StepInvalidReason.Armor: - stepName.AddMarkup($" [color=red]{Loc.GetString("surgery-ui-window-steps-error-armor")}[/color]"); - break; - case StepInvalidReason.MissingTool: - stepName.AddMarkup($" [color=red]{Loc.GetString("surgery-ui-window-steps-error-tools")}[/color]"); - break; - } - } } var texture = _entities.GetComponentOrNull(stepButton.Step)?.Icon?.Default; diff --git a/Content.Client/Backmen/Surgery/SurgerySystem.cs b/Content.Client/Backmen/Surgery/SurgerySystem.cs index a64b2e308a2..a64135b7d11 100644 --- a/Content.Client/Backmen/Surgery/SurgerySystem.cs +++ b/Content.Client/Backmen/Surgery/SurgerySystem.cs @@ -4,17 +4,8 @@ namespace Content.Client.Backmen.Surgery; public sealed class SurgerySystem : SharedSurgerySystem { - public event Action? OnStep; - public override void Initialize() { base.Initialize(); - - SubscribeNetworkEvent(OnRefresh); - } - - private void OnRefresh(SurgeryUiRefreshEvent ev) - { - OnStep?.Invoke(); } } diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index 6eb5dd9ec98..7bdc9483235 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -55,7 +55,8 @@ private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent foreach (var (key, info) in component.CustomBaseLayers) { oldLayers.Remove(key); - SetLayerData(component, sprite, key, info.Id, sexMorph: false, color: info.Color); + // Shitmed modification: For whatever reason these weren't actually ignoring the skin color as advertised. + SetLayerData(component, sprite, key, info.Id, sexMorph: false, color: info.Color, overrideSkin: true); } // hide old layers @@ -73,7 +74,8 @@ private void SetLayerData( HumanoidVisualLayers key, string? protoId, bool sexMorph = false, - Color? color = null) + Color? color = null, + bool overrideSkin = false) { var layerIndex = sprite.LayerMapReserveBlank(key); var layer = sprite[layerIndex]; @@ -91,7 +93,7 @@ private void SetLayerData( var proto = _prototypeManager.Index(protoId); component.BaseLayers[key] = proto; - if (proto.MatchSkin) + if (proto.MatchSkin && !overrideSkin) layer.Color = component.SkinColor.WithAlpha(proto.LayerAlpha); if (proto.BaseSprite != null) diff --git a/Content.Server/Backmen/Surgery/SurgerySystem.cs b/Content.Server/Backmen/Surgery/SurgerySystem.cs index 0ef63818cb0..04d8904a115 100644 --- a/Content.Server/Backmen/Surgery/SurgerySystem.cs +++ b/Content.Server/Backmen/Surgery/SurgerySystem.cs @@ -49,9 +49,7 @@ public override void Initialize() SubscribeLocalEvent(OnStepAffixPartComplete); SubscribeLocalEvent(OnStepScreamComplete); SubscribeLocalEvent(OnStepSpawnComplete); - SubscribeLocalEvent(OnPrototypesReloaded); - LoadPrototypes(); } @@ -80,15 +78,9 @@ protected override void RefreshUI(EntityUid body) /* Reason we do this is because when applying a BUI State, it rolls back the state on the entity temporarily, which just so happens to occur right as we're checking for step completion, so we end up with the UI - not updating at all until you change tools or reopen the window. + not updating at all until you change tools or reopen the window. I love shitcode. */ - - var actors = _ui.GetActors(body, SurgeryUIKey.Key).ToArray(); - if (actors.Length == 0) - return; - - var filter = Filter.Entities(actors); - RaiseNetworkEvent(new SurgeryUiRefreshEvent(GetNetEntity(body)), filter); + _ui.ServerSendUiMessage(body, SurgeryUIKey.Key, new SurgeryBuiRefreshMessage()); } private void SetDamage(EntityUid body, @@ -114,6 +106,7 @@ private void OnToolAfterInteract(Entity ent, ref AfterInte if (args.Handled || !args.CanReach || args.Target == null + || !HasComp(args.Target) || !TryComp(args.User, out var surgery) || !surgery.CanOperate || !IsLyingDown(args.Target.Value, args.User)) diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index 6cfd93ce33e..de7d3e86712 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Body.Part; using Content.Shared.Body.Systems; using Content.Shared.Damage; +using Content.Shared.Gibbing.Events; using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Mobs.Systems; @@ -108,7 +109,9 @@ public override HashSet GibBody( Vector2? splatDirection = null, float splatModifier = 1, Angle splatCone = default, - SoundSpecifier? gibSoundOverride = null) + SoundSpecifier? gibSoundOverride = null, + GibType gib = GibType.Gib, + GibContentsOption contents = GibContentsOption.Drop) { if (!Resolve(bodyId, ref body, logMissing: false) || TerminatingOrDeleted(bodyId) @@ -122,7 +125,8 @@ public override HashSet GibBody( return new HashSet(); var gibs = base.GibBody(bodyId, gibOrgans, body, launchGibs: launchGibs, - splatDirection: splatDirection, splatModifier: splatModifier, splatCone: splatCone); + splatDirection: splatDirection, splatModifier: splatModifier, splatCone: splatCone, + gib: gib, contents: contents); var ev = new BeingGibbedEvent(gibs); RaiseLocalEvent(bodyId, ref ev); @@ -164,9 +168,27 @@ public override HashSet GibPart( } // start-backmen: surgery - protected override void UpdateAppearance(EntityUid uid, BodyPartAppearanceComponent component) + /*protected override void UpdateAppearance(EntityUid uid, BodyPartAppearanceComponent component) { return; + }*/ + + protected override void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component) + { + return; + } + + protected override void RemovePartMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance) + { + foreach (var (visualLayer, markingList) in partAppearance.Markings) + { + foreach (var marking in markingList) + { + _humanoidSystem.RemoveMarking(target, marking.MarkingId, sync: false, humanoid: bodyAppearance); + } + } + + Dirty(target, bodyAppearance); } // end-backmen: surgery } diff --git a/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs index c83fed19069..da054e24ac3 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs @@ -1,4 +1,5 @@ using Content.Shared.Body.Components; +using Content.Shared.Gibbing.Events; using JetBrains.Annotations; namespace Content.Server.Destructible.Thresholds.Behaviors @@ -7,13 +8,15 @@ namespace Content.Server.Destructible.Thresholds.Behaviors [DataDefinition] public sealed partial class GibBehavior : IThresholdBehavior { + [DataField] public GibType GibType = GibType.Gib; + [DataField] public GibContentsOption GibContents = GibContentsOption.Drop; [DataField("recursive")] private bool _recursive = true; public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) { if (system.EntityManager.TryGetComponent(owner, out BodyComponent? body)) { - system.BodySystem.GibBody(owner, _recursive, body); + system.BodySystem.GibBody(owner, _recursive, body, gib: GibType, contents: GibContents); } } } diff --git a/Content.Shared/Backmen/Surgery/Body/BodyPartAppearanceComponent.cs b/Content.Shared/Backmen/Surgery/Body/BodyPartAppearanceComponent.cs index 5c9aa5ba60a..53cf40b9b5f 100644 --- a/Content.Shared/Backmen/Surgery/Body/BodyPartAppearanceComponent.cs +++ b/Content.Shared/Backmen/Surgery/Body/BodyPartAppearanceComponent.cs @@ -1,26 +1,45 @@ +using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Humanoid.Markings; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.GameStates; namespace Content.Shared.Body.Part; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class BodyPartAppearanceComponent : Component { + /// + /// HumanoidVisualLayer type for this body part. + /// + [DataField, AutoNetworkedField] + public HumanoidVisualLayers Type { get; set; } + + /// + /// Relevant markings for this body part that will be applied on attachment. + /// + [DataField, AutoNetworkedField] + public Dictionary> Markings = new(); /// /// ID of this custom base layer. Must be a . /// - [DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer)), AutoNetworkedField] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), AutoNetworkedField] public string? ID { get; set; } /// /// Color of this custom base layer. Null implies skin colour if the corresponding is set to match skin. /// - [DataField("color"), AutoNetworkedField] + [DataField, AutoNetworkedField] public Color? Color { get; set; } - [DataField("originalBody"), AutoNetworkedField] + /// + /// Color of this custom base eye layer. Null implies eye colour if the corresponding is set to match skin. + /// + [DataField, AutoNetworkedField] + public Color? EyeColor { get; set; } + + [DataField, AutoNetworkedField] public EntityUid? OriginalBody { get; set; } //TODO add other custom variables such as species and markings - in case someone decides to attach a lizard arm to a human for example diff --git a/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.PartAppearance.cs b/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.PartAppearance.cs index 930da554379..dd5c40fe129 100644 --- a/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.PartAppearance.cs +++ b/Content.Shared/Backmen/Surgery/Body/SharedBodySystem.PartAppearance.cs @@ -1,83 +1,150 @@ +using System.Linq; using Content.Shared.Body.Components; using Content.Shared.Body.Part; -using Content.Shared.Body.Events; -using Robust.Shared.GameStates; using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Humanoid.Markings; namespace Content.Shared.Body.Systems; -// Code shamelessly stolen from MS14. public partial class SharedBodySystem { + [Dependency] private readonly SharedHumanoidAppearanceSystem _humanoid = default!; private void InitializePartAppearances() { base.Initialize(); - SubscribeLocalEvent(OnPartAppearanceInit); - SubscribeLocalEvent(OnPartAddedToBody); + SubscribeLocalEvent(OnPartAppearanceStartup); + SubscribeLocalEvent(HandleState); + SubscribeLocalEvent(OnPartAttachedToBody); + SubscribeLocalEvent(OnPartDroppedFromBody); } - private static readonly Dictionary<(BodyPartType, BodyPartSymmetry), HumanoidVisualLayers> BodyPartVisualLayers - = new Dictionary<(BodyPartType, BodyPartSymmetry), HumanoidVisualLayers> - { - { (BodyPartType.Head,BodyPartSymmetry.None), HumanoidVisualLayers.Head }, - { (BodyPartType.Tail,BodyPartSymmetry.None), HumanoidVisualLayers.Tail }, - { (BodyPartType.Torso,BodyPartSymmetry.None), HumanoidVisualLayers.Chest }, - { (BodyPartType.Arm,BodyPartSymmetry.Right), HumanoidVisualLayers.RArm }, - { (BodyPartType.Arm,BodyPartSymmetry.Left), HumanoidVisualLayers.LArm }, - { (BodyPartType.Hand,BodyPartSymmetry.Right), HumanoidVisualLayers.RHand }, - { (BodyPartType.Hand,BodyPartSymmetry.Left), HumanoidVisualLayers.LHand }, - { (BodyPartType.Leg,BodyPartSymmetry.Right), HumanoidVisualLayers.RLeg }, - { (BodyPartType.Leg,BodyPartSymmetry.Left), HumanoidVisualLayers.LLeg }, - { (BodyPartType.Foot,BodyPartSymmetry.Right), HumanoidVisualLayers.RLeg }, - { (BodyPartType.Foot,BodyPartSymmetry.Left), HumanoidVisualLayers.LLeg } - }; - - private void OnPartAppearanceInit(EntityUid uid, BodyPartAppearanceComponent component, ComponentInit args) + private void OnPartAppearanceStartup(EntityUid uid, BodyPartAppearanceComponent component, ComponentStartup args) { - if (TryComp(uid, out BodyPartComponent? part) && part.OriginalBody != null && - TryComp(part.OriginalBody.Value, out HumanoidAppearanceComponent? bodyAppearance)) - { - var customLayers = bodyAppearance.CustomBaseLayers; - var spriteLayers = bodyAppearance.BaseLayers; - var visualLayer = BodyPartVisualLayers[(part.PartType, part.Symmetry)]; + // God this function reeks, it needs some cleanup BADLY. Help is appreciated as always. - component.OriginalBody = part.OriginalBody.Value; + if (!TryComp(uid, out BodyPartComponent? part) + || part.OriginalBody == null + || TerminatingOrDeleted(part.OriginalBody.Value) + || !TryComp(part.OriginalBody.Value, out HumanoidAppearanceComponent? bodyAppearance) + || HumanoidVisualLayersExtension.ToHumanoidLayers(part) is not { } relevantLayer) + return; - if (customLayers.ContainsKey(visualLayer)) - { - component.ID = customLayers[visualLayer].Id; - component.Color = customLayers[visualLayer].Color; - } - else if (spriteLayers.ContainsKey(visualLayer)) + var customLayers = bodyAppearance.CustomBaseLayers; + var spriteLayers = bodyAppearance.BaseLayers; + component.Type = relevantLayer; + component.OriginalBody = part.OriginalBody.Value; + part.Sex = bodyAppearance.Sex; + + // Thanks Rane for the human reskin :) + if (bodyAppearance.Species.ToString() == "Felinid") + part.Species = "Human"; + else + part.Species = bodyAppearance.Species; + + if (customLayers.ContainsKey(component.Type)) + { + component.ID = customLayers[component.Type].Id; + component.Color = customLayers[component.Type].Color; + } + else if (spriteLayers.ContainsKey(component.Type)) + { + component.ID = spriteLayers[component.Type].ID; + component.Color = bodyAppearance.SkinColor; + } + else + { + component.Color = bodyAppearance.SkinColor; + var symmetryPrefix = part.Symmetry switch { - component.ID = spriteLayers[visualLayer].ID; - component.Color = bodyAppearance.SkinColor; - } - else + BodyPartSymmetry.Left => "L", + BodyPartSymmetry.Right => "R", + _ => "" + }; + + var genderSuffix = ""; + + if (part.PartType == BodyPartType.Torso || part.PartType == BodyPartType.Head) + genderSuffix = part.Sex.ToString(); + + component.ID = $"Mob{part.Species}{symmetryPrefix}{part.PartType}{genderSuffix}"; + } + + // I HATE HARDCODED CHECKS I HATE HARDCODED CHECKS I HATE HARDCODED CHECKS + if (part.PartType == BodyPartType.Head) + component.EyeColor = bodyAppearance.EyeColor; + + var markingsByLayer = new Dictionary>(); + + foreach (var layer in HumanoidVisualLayersExtension.Sublayers(relevantLayer)) + { + var category = MarkingCategoriesConversion.FromHumanoidVisualLayers(layer); + if (bodyAppearance.MarkingSet.Markings.TryGetValue(category, out var markingList)) + markingsByLayer[layer] = markingList.Select(m => new Marking(m.MarkingId, m.MarkingColors.ToList())).ToList(); + } + + component.Markings = markingsByLayer; + } + private void HandleState(EntityUid uid, BodyPartAppearanceComponent component, ref AfterAutoHandleStateEvent args) + { + ApplyPartMarkings(uid, component); + } + private void OnPartAttachedToBody(EntityUid uid, BodyComponent component, ref BodyPartAttachedEvent args) + { + if (!TryComp(args.Part, out BodyPartAppearanceComponent? partAppearance) + || !TryComp(uid, out HumanoidAppearanceComponent? bodyAppearance)) + return; + + _humanoid.SetBaseLayerId(uid, partAppearance.Type, partAppearance.ID, sync: true, bodyAppearance); + UpdateAppearance(uid, partAppearance); + } + + private void OnPartDroppedFromBody(EntityUid uid, BodyComponent component, ref BodyPartDroppedEvent args) + { + if (TerminatingOrDeleted(uid) + || !TryComp(args.Part, out BodyPartAppearanceComponent? appearance)) + return; + + RemoveAppearance(uid, appearance, args.Part); + } + + protected void UpdateAppearance(EntityUid target, + BodyPartAppearanceComponent component) + { + if (!TryComp(target, out HumanoidAppearanceComponent? bodyAppearance)) + return; + + if (component.EyeColor != null) + bodyAppearance.EyeColor = component.EyeColor.Value; + + if (component.Color != null) + _humanoid.SetBaseLayerColor(target, component.Type, component.Color, true, bodyAppearance); + + _humanoid.SetLayerVisibility(target, component.Type, true, true, bodyAppearance); + foreach (var (visualLayer, markingList) in component.Markings) + { + _humanoid.SetLayerVisibility(target, visualLayer, true, true, bodyAppearance); + foreach (var marking in markingList) { - var symmetry = ((BodyPartSymmetry) part.Symmetry).ToString(); - if (symmetry == "None") - symmetry = ""; - component.ID = "removed" + symmetry + ((BodyPartType) part.PartType).ToString(); - component.Color = bodyAppearance.SkinColor; + _humanoid.AddMarking(target, marking.MarkingId, marking.MarkingColors, false, true, bodyAppearance); } } - Dirty(uid, component); - UpdateAppearance(uid, component); + Dirty(target, bodyAppearance); } - public void OnPartAddedToBody(EntityUid uid, BodyPartAppearanceComponent component, ref BodyPartAddedEvent args) + protected void RemoveAppearance(EntityUid entity, BodyPartAppearanceComponent component, EntityUid partEntity) { - if (TryComp(uid, out HumanoidAppearanceComponent? bodyAppearance)) + if (!TryComp(entity, out HumanoidAppearanceComponent? bodyAppearance)) + return; + + foreach (var (visualLayer, markingList) in component.Markings) { - var part = args.Part; - var customLayers = bodyAppearance.CustomBaseLayers; - var visualLayer = BodyPartVisualLayers[(part.Comp.PartType, part.Comp.Symmetry)]; - customLayers[visualLayer] = new CustomBaseLayerInfo(component.ID, customLayers[visualLayer].Color); + _humanoid.SetLayerVisibility(entity, visualLayer, false, true, bodyAppearance); } + RemovePartMarkings(entity, component, bodyAppearance); } - protected abstract void UpdateAppearance(EntityUid uid, BodyPartAppearanceComponent component); + protected abstract void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component); + + protected abstract void RemovePartMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance); } diff --git a/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs b/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs index 2d180934e01..3ee397bca47 100644 --- a/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs +++ b/Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs @@ -160,6 +160,19 @@ private void OnToolCanPerform(Entity ent, ref SurgeryCanPe } } + if (_inventory.TryGetContainerSlotEnumerator(args.Body, out var containerSlotEnumerator, args.TargetSlots)) + { + while (containerSlotEnumerator.MoveNext(out var containerSlot)) + { + if (!containerSlot.ContainedEntity.HasValue) + continue; + + args.Invalid = StepInvalidReason.Armor; + args.Popup = Loc.GetString("surgery-ui-window-steps-error-armor"); + return; + } + } + RaiseLocalEvent(args.Body, ref args); if (args.Invalid != StepInvalidReason.None) @@ -292,6 +305,8 @@ private void OnAddPartStep(Entity ent, ref SurgeryS _body.TryCreatePartSlot(args.Part, slotName, partComp.PartType, out var _); _body.AttachPart(args.Part, slotName, tool); _body.ChangeSlotState((tool, partComp), false); + var ev = new BodyPartAttachedEvent((tool, partComp)); + RaiseLocalEvent(args.Body, ref ev); } } } diff --git a/Content.Shared/Backmen/Surgery/SurgeryUI.cs b/Content.Shared/Backmen/Surgery/SurgeryUI.cs index 1e8320522a1..5d55cdd430c 100644 --- a/Content.Shared/Backmen/Surgery/SurgeryUI.cs +++ b/Content.Shared/Backmen/Surgery/SurgeryUI.cs @@ -15,6 +15,11 @@ public sealed class SurgeryBuiState(Dictionary> choi public readonly Dictionary> Choices = choices; } +[Serializable, NetSerializable] +public sealed class SurgeryBuiRefreshMessage : BoundUserInterfaceMessage +{ +} + [Serializable, NetSerializable] public sealed class SurgeryStepChosenBuiMsg(NetEntity part, EntProtoId surgery, EntProtoId step, bool isBody) : BoundUserInterfaceMessage { diff --git a/Content.Shared/Body/Part/BodyPartComponent.cs b/Content.Shared/Body/Part/BodyPartComponent.cs index b4ca2d8999b..b3b5f3472a4 100644 --- a/Content.Shared/Body/Part/BodyPartComponent.cs +++ b/Content.Shared/Body/Part/BodyPartComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Backmen.Surgery.Tools; +using Content.Shared.Backmen.Surgery.Tools; using Content.Shared.Backmen.Targeting; using Content.Shared.Containers.ItemSlots; using Content.Shared.Body.Components; @@ -8,6 +8,7 @@ using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Serialization; +using Content.Shared.Humanoid; namespace Content.Shared.Body.Part; @@ -148,6 +149,19 @@ public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent [DataField, AutoNetworkedField] public ItemSlot ItemInsertionSlot = new(); + + /// + /// Current species. Dictates things like body part sprites. + /// + [DataField, AutoNetworkedField] + public string Species { get; set; } = ""; + + /// + /// Do not make a stupid joke do not make a stupid joke do not make a stupid joke. + /// + [DataField, AutoNetworkedField] + public Sex Sex { get; set; } = Sex.Male; + /// /// These are only for VV/Debug do not use these for gameplay/systems /// diff --git a/Content.Shared/Body/Part/BodyPartEvents.cs b/Content.Shared/Body/Part/BodyPartEvents.cs index f4444121e1d..9872b092002 100644 --- a/Content.Shared/Body/Part/BodyPartEvents.cs +++ b/Content.Shared/Body/Part/BodyPartEvents.cs @@ -1,11 +1,22 @@ +using Content.Shared.Humanoid; + namespace Content.Shared.Body.Part; [ByRefEvent] public readonly record struct BodyPartAddedEvent(string Slot, Entity Part); +// Kind of a clone of the above for surgical reattachment specifically. +[ByRefEvent] +public readonly record struct BodyPartAttachedEvent(Entity Part); + [ByRefEvent] public readonly record struct BodyPartRemovedEvent(string Slot, Entity Part); +// Kind of a clone of the above for any instances where we call DropPart(), reasoning being that RemovedEvent fires off +// a lot more often than what I'd like due to PVS. +[ByRefEvent] +public readonly record struct BodyPartDroppedEvent(Entity Part); + [ByRefEvent] public readonly record struct BodyPartEnableChangedEvent(bool Enabled); diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs index 677a8d470f1..45335c9b53f 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs @@ -13,6 +13,8 @@ using Content.Shared.Gibbing.Events; using Content.Shared.Gibbing.Systems; using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Events; +using Content.Shared.Humanoid.Prototypes; using Content.Shared.Inventory; using Content.Shared.Rejuvenate; using Content.Shared.Standing; @@ -53,6 +55,7 @@ private void InitializeBody() SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnStandAttempt); SubscribeLocalEvent(OnRejuvenate); + SubscribeLocalEvent(OnProfileLoadFinished); } private void OnBodyInserted(Entity ent, ref EntInsertedIntoContainerMessage args) @@ -126,10 +129,11 @@ private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype) var rootPartUid = SpawnInContainerOrDrop(protoRoot.Part, bodyEntity, BodyRootContainerId); var rootPart = Comp(rootPartUid); rootPart.Body = bodyEntity; + rootPart.OriginalBody = bodyEntity; Dirty(rootPartUid, rootPart); // Setup the rest of the body entities. SetupOrgans((rootPartUid, rootPart), protoRoot.Organs); - MapInitParts(rootPartUid, prototype); + MapInitParts(rootPartUid, rootPart, prototype); } private void OnBodyCanDrag(Entity ent, ref CanDragEvent args) @@ -183,7 +187,7 @@ private void OnRejuvenate(Entity ent, ref RejuvenateEvent args) /// /// Sets up all of the relevant body parts for a particular body entity and root part. /// - private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype) + private void MapInitParts(EntityUid rootPartId, BodyPartComponent rootPart, BodyPrototype prototype) { // Start at the root part and traverse the body graph, setting up parts as we go. // Basic BFS pathfind. @@ -222,6 +226,8 @@ private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype) var childPartComponent = Comp(childPart); var partSlot = CreatePartSlot(parentEntity, connection, childPartComponent.PartType, parentPartComponent); childPartComponent.ParentSlot = partSlot; + childPartComponent.OriginalBody = rootPart.Body; + Dirty(childPart, childPartComponent); var cont = Containers.GetContainer(parentEntity, GetPartSlotContainerId(connection)); if (partSlot is null || !Containers.Insert(childPart, cont)) @@ -361,7 +367,9 @@ public virtual HashSet GibBody( Vector2? splatDirection = null, float splatModifier = 1, Angle splatCone = default, - SoundSpecifier? gibSoundOverride = null) + SoundSpecifier? gibSoundOverride = null, + GibType gib = GibType.Gib, + GibContentsOption contents = GibContentsOption.Drop) { var gibs = new HashSet(); @@ -378,7 +386,7 @@ public virtual HashSet GibBody( foreach (var part in parts) { - _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, GibType.Gib, GibContentsOption.Drop, ref gibs, + _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, gib, contents, ref gibs, playAudio: false, launchGibs: true, launchDirection: splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier, launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone); @@ -442,4 +450,16 @@ public virtual HashSet GibPart( _audioSystem.PlayPredicted(gibSoundOverride, Transform(partId).Coordinates, null); return gibs; } + + private void OnProfileLoadFinished(EntityUid uid, BodyComponent component, ProfileLoadFinishedEvent args) + { + if (!TryComp(uid, out var appearance) + || TerminatingOrDeleted(uid)) + return; + + foreach (var part in GetBodyChildren(uid, component)) + { + EnsureComp(part.Id); + } + } } diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs index abfbf9ec235..1070a716fbd 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs @@ -172,14 +172,25 @@ protected virtual void RemovePart( protected virtual void DropPart(Entity partEnt) { ChangeSlotState(partEnt, true); - + // I don't know if this can cause issues, since any part that's being detached HAS to have a Body. + // though I really just want the compiler to shut the fuck up. + var body = partEnt.Comp.Body.GetValueOrDefault(); // We then detach the part, which will kickstart EntRemovedFromContainer events. if (TryComp(partEnt, out TransformComponent? transform) && _gameTiming.IsFirstTimePredicted) { - var ev = new BodyPartEnableChangedEvent(false); - RaiseLocalEvent(partEnt, ref ev); + var enableEvent = new BodyPartEnableChangedEvent(false); + RaiseLocalEvent(partEnt, ref enableEvent); + + if (TryComp(body, out HumanoidAppearanceComponent? bodyAppearance) + && !HasComp(partEnt) + && !TerminatingOrDeleted(body) + && !TerminatingOrDeleted(partEnt)) + EnsureComp(partEnt); + SharedTransform.AttachToGridOrMap(partEnt, transform); _randomHelper.RandomOffset(partEnt, 0.5f); + var droppedEvent = new BodyPartDroppedEvent(partEnt); + RaiseLocalEvent(body, ref droppedEvent); } } @@ -628,14 +639,10 @@ public bool AttachPart( // start-backmen: surgery if (TryComp(part.Body, out HumanoidAppearanceComponent? bodyAppearance) - && !HasComp(partId)) - { - var appearance = AddComp(partId); - appearance.OriginalBody = part.Body; - appearance.Color = bodyAppearance.SkinColor; - UpdateAppearance(partId, appearance); - } - // end-backmen: surgery + && !HasComp(partId) + && !TerminatingOrDeleted(parentPartId) + && !TerminatingOrDeleted(partId)) // Saw some exceptions involving these due to the spawn menu. + EnsureComp(partId); return Containers.Insert(partId, container); } diff --git a/Content.Shared/Body/Systems/SharedBodySystem.cs b/Content.Shared/Body/Systems/SharedBodySystem.cs index a00fb0ba4df..5d95a42c308 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.cs @@ -43,6 +43,8 @@ public override void Initialize() InitializeBody(); InitializeParts(); InitializeBkm(); // backmen + // To try and mitigate the server load due to integrity checks, we set up a Job Queue. + InitializePartAppearances(); } /// diff --git a/Content.Shared/Humanoid/Events/ProfileLoadFinishedEvent.cs b/Content.Shared/Humanoid/Events/ProfileLoadFinishedEvent.cs new file mode 100644 index 00000000000..fc894854dcf --- /dev/null +++ b/Content.Shared/Humanoid/Events/ProfileLoadFinishedEvent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Humanoid.Events; + +/// +/// Raised on an entity when their profile has finished being loaded +/// +public sealed class ProfileLoadFinishedEvent : EntityEventArgs +{ +} + diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs index 08a4fdce39c..50fd6699020 100644 --- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using System.Numerics; using Content.Corvax.Interfaces.Shared; using Content.Shared.CCVar; using Content.Shared.Decals; @@ -7,6 +8,7 @@ using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Corvax.TTS; +using Content.Shared.Humanoid.Events; using Content.Shared.IdentityManagement; using Content.Shared.Preferences; using Robust.Shared; @@ -447,7 +449,7 @@ public virtual void LoadProfile(EntityUid uid, } humanoid.Age = profile.Age; - + Dirty(uid, humanoid); } diff --git a/Resources/Changelog/ChangelogBkm.yml b/Resources/Changelog/ChangelogBkm.yml index 89bbb89dd9d..89aa7216270 100644 --- a/Resources/Changelog/ChangelogBkm.yml +++ b/Resources/Changelog/ChangelogBkm.yml @@ -223,3 +223,13 @@ id: 15 time: '2024-11-08T21:34:57.0000000+00:00' url: https://github.com/Rxup/space-station-14/pull/907 +- author: CrimeMoot + changes: + - message: "\u0418\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430 \u043E\u0448\ + \u0438\u0431\u043A\u0430 \u0441 \u043D\u0435\u043E\u0442\u043E\u0431\u0440\u0430\ + \u0436\u0435\u043D\u0438\u0435\u043C Popup \u043D\u0430 \u043C\u044F\u0433\u043A\ + \u0438\u0445 \u043D\u0430\u0440\u0443\u0447\u043D\u0438\u043A\u0430\u0445." + type: Fix + id: 16 + time: '2024-11-09T01:37:58.0000000+00:00' + url: https://github.com/Rxup/space-station-14/pull/908 diff --git a/Resources/Locale/en-US/surgery/surgery-ui.ftl b/Resources/Locale/en-US/surgery/surgery-ui.ftl index fd1c45fcb47..cf58da3977b 100644 --- a/Resources/Locale/en-US/surgery/surgery-ui.ftl +++ b/Resources/Locale/en-US/surgery/surgery-ui.ftl @@ -6,6 +6,6 @@ surgery-ui-window-steps = < Steps surgery-ui-window-steps-error-skills = You have no surgical skills. surgery-ui-window-steps-error-table = You need an operating table for this. surgery-ui-window-steps-error-armor = You need to remove their armor! -surgery-ui-window-steps-error-tools = You're missing tools for this surgery. +surgery-ui-window-steps-error-tools = Missing tools. surgery-error-laying = They need to be laying down! surgery-error-self-surgery = You can't perform surgery on yourself! diff --git a/Resources/Prototypes/Body/Parts/skeleton.yml b/Resources/Prototypes/Body/Parts/skeleton.yml index 1b378c62332..35d1d0f3738 100644 --- a/Resources/Prototypes/Body/Parts/skeleton.yml +++ b/Resources/Prototypes/Body/Parts/skeleton.yml @@ -18,6 +18,35 @@ - type: Tag tags: - Trash + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 50 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Slash + damage: 100 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Heat + damage: 200 + behaviors: + - !type:SpawnEntitiesBehavior + spawnInContainer: true + spawn: + Ash: + min: 1 + max: 1 + - !type:BurnBodyBehavior { } + - !type:PlaySoundBehavior + sound: + collection: MeatLaserImpact - type: entity id: TorsoSkeleton diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 4aee2f9d787..b50d134a7e0 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -400,7 +400,8 @@ damageType: Blunt damage: 10 behaviors: - - !type:GibBehavior { } + - !type:GibBehavior + gibContents: Skip - type: NonSpreaderZombie - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-organic diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml new file mode 100644 index 00000000000..5072841eaff --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml @@ -0,0 +1,306 @@ +- type: entity + save: false + id: PlayerSiliconHumanoidBase + parent: [BaseMob, MobDamageable, MobCombat, MobAtmosExposed, MobFlammable] + abstract: true + components: + - type: ContentEye + - type: CameraRecoil + - type: Reactive + groups: + Flammable: [Touch] + Extinguish: [Touch] + Acidic: [Touch] + reactions: + - reagents: [Water, SpaceCleaner] + methods: [Touch] + effects: + - !type:WashCreamPieReaction + - type: DamageOnHighSpeedImpact + damage: + types: + Blunt: 10 + soundHit: + path: /Audio/Effects/hit_kick.ogg + - type: Damageable + damageContainer: Silicon + damageModifierSet: IPC + - type: InteractionOutline + - type: MovementSpeedModifier + baseWalkSpeed: 4 + baseSprintSpeed: 3 + - type: ZombieImmune + - type: DoAfter + - type: RotationVisuals + horizontalRotation: 90 + - type: Examiner + # - type: Recyclable + # safe: false + # - type: EyeProtection # You'll want this if your robot can't wear glasses, like an IPC. + # protectionTime: 12 + #- type: Silicon + # entityType: enum.SiliconType.Player + # batteryPowered: false # Needs to also have a battery! + # chargeThresholdMid: 0.60 + # chargeThresholdLow: 0.30 + # chargeThresholdCritical: 0 + # speedModifierThresholds: + # 4: 1 + # 3: 1 + # 2: 0.80 + # 1: 0.45 + # 0: 0.00 + + - type: Temperature + heatDamageThreshold: 325 + coldDamageThreshold: 260 + currentTemperature: 310.15 + specificHeat: 42 + coldDamage: + types: + Cold: 0.1 #per second, scales with temperature & other constants + heatDamage: + types: + Heat: 3 #per second, scales with temperature & other constants + atmosTemperatureTransferEfficiency: 0.05 + - type: Deathgasp + prototype: SiliconDeathgasp + needsCritical: false + - type: MobState + allowedStates: + - Alive + - Dead + - type: MobThresholds + thresholds: + 0: Alive + 165: Dead + - type: Destructible + thresholds: + - trigger: !type:DamageTrigger + damage: 500 + behaviors: + - !type:GibBehavior { } + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: full + - type: Sprite + noRot: true + drawdepth: Mobs + layers: + - map: ["enum.HumanoidVisualLayers.Chest"] + - map: ["enum.HumanoidVisualLayers.Head"] + - map: ["enum.HumanoidVisualLayers.Snout"] + - map: ["enum.HumanoidVisualLayers.Eyes"] + - map: ["enum.HumanoidVisualLayers.RArm"] + - map: ["enum.HumanoidVisualLayers.LArm"] + - map: ["enum.HumanoidVisualLayers.RLeg"] + - map: ["enum.HumanoidVisualLayers.LLeg"] + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: ["enum.HumanoidVisualLayers.StencilMask"] + sprite: Mobs/Customization/masking_helpers.rsi + state: female_full + visible: false + - map: ["enum.HumanoidVisualLayers.LFoot"] + - map: ["enum.HumanoidVisualLayers.RFoot"] + - map: ["socks"] + - map: ["underpants"] + - map: ["undershirt"] + - map: ["jumpsuit"] + - map: ["enum.HumanoidVisualLayers.LHand"] + - map: ["enum.HumanoidVisualLayers.RHand"] + - map: ["enum.HumanoidVisualLayers.Handcuffs"] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: ["id"] + - map: ["gloves"] + - map: ["shoes"] + - map: ["ears"] + - map: ["outerClothing"] + - map: ["eyes"] + - map: ["belt"] + - map: ["neck"] + - map: ["back"] + - map: ["enum.HumanoidVisualLayers.FacialHair"] + - map: ["enum.HumanoidVisualLayers.Hair"] + - map: ["enum.HumanoidVisualLayers.HeadSide"] + - map: ["enum.HumanoidVisualLayers.HeadTop"] + - map: ["mask"] + - map: ["head"] + - map: ["pocket1"] + - map: ["pocket2"] + - map: ["enum.HumanoidVisualLayers.Tail"] + - map: ["clownedon"] # Dynamically generated + sprite: "Effects/creampie.rsi" + state: "creampie_human" + visible: false + + #- type: Bloodstream This is left commented out because it's not necessary for a robot, but you may want it. + # damageBleedModifiers: BloodlossIPC + # bloodReagent: Oil + # bleedReductionAmount: 0 + # bloodMaxVolume: 500 + # chemicalMaxVolume: 0 + # bleedPuddleThreshold: 3 + # bleedRefreshAmount: 0 + # bloodLossThreshold: 0 + # maxBleedAmount: 14 + # bloodlossDamage: + # types: + # Burn: 1.5 + # bloodlossHealDamage: + # types: + # Burn: 0 + - type: Flammable + fireSpread: true + canResistFire: true + damage: + types: + Heat: 0.75 #per second, scales with number of fire 'stacks' + # - type: Barotrauma # Not particularly modifiable. In the future, some response to pressure changes would be nice. + # damage: + # types: + # Blunt: 0.28 #per second, scales with pressure and other constants. + - type: Identity + # soundHit: + # path: /Audio/Effects/metalbreak.ogg + - type: RangedDamageSound + soundGroups: + Brute: + collection: MetalBulletImpact + soundTypes: + Heat: + collection: MetalLaserImpact + - type: Tag + tags: + - CanPilot + - FootstepSound + - DoorBumpOpener + - type: Hands + - type: Inventory + templateId: human + - type: InventorySlots + - type: Appearance + - type: GenericVisualizer + visuals: + enum.CreamPiedVisuals.Creamed: + clownedon: # Not 'creampied' bc I can already see Skyrat complaining about conflicts. + True: { visible: true } + False: { visible: false } + - type: Cuffable + #- type: Mood + - type: AnimationPlayer + - type: Buckle + - type: CreamPied + - type: Stripping + - type: Strippable + - type: UserInterface + interfaces: + enum.VoiceMaskUIKey.Key: + type: VoiceMaskBoundUserInterface + enum.HumanoidMarkingModifierKey.Key: + type: HumanoidMarkingModifierBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + - type: Emoting + - type: Grammar + attributes: + proper: true + - type: Climbing + - type: StandingState + - type: MindContainer + showExamineInfo: true + - type: SSDIndicator + - type: CanEscapeInventory + - type: HumanoidAppearance + species: IPC + - type: Body + prototype: IPC + requiredLegs: 2 + - type: Ensnareable + sprite: Objects/Misc/ensnare.rsi + - type: Speech + speechSounds: Pai + - type: Vocal + wilhelm: "/Audio/Voice/IPC/wilhelm.ogg" + sounds: + Male: UnisexIPC + Female: UnisexIPC + Unsexed: UnisexIPC + - type: MeleeWeapon + hidden: true + soundHit: + collection: Punch + angle: 30 + animation: WeaponArcFist + attackRate: 1 + damage: + types: + Blunt: 6 # It's tough. + - type: MobPrice + price: 1500 # Kidnapping a living person and selling them for cred is a good move. + deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. + - type: Pullable + - type: Puller + + - type: BodyEmotes + soundsId: GeneralBodyEmotes + - type: DamageVisuals + thresholds: [ 10, 20, 30, 50, 70, 100 ] + targetLayers: + - "enum.HumanoidVisualLayers.Chest" + - "enum.HumanoidVisualLayers.Head" + - "enum.HumanoidVisualLayers.LArm" + - "enum.HumanoidVisualLayers.LLeg" + - "enum.HumanoidVisualLayers.RArm" + - "enum.HumanoidVisualLayers.RLeg" + damageOverlayGroups: + Brute: + sprite: Mobs/Effects/brute_damage.rsi + color: "#DD8822" + # Organs + - type: IdExaminable + - type: HealthExaminable + examinableTypes: + - Blunt + - Slash + - Piercing + - Heat + - Shock + - type: StatusEffects + allowed: + - Stun + - KnockedDown + - SlowedDown + - Stutter + - SeeingRainbows + - Electrocution + # - Drunk + - SlurredSpeech + - PressureImmunity + - Muted + # - ForcedSleep + - TemporaryBlindness + - Pacified + # - PsionicsDisabled + # - PsionicallyInsulated + - type: Blindable + - type: FireVisuals + alternateState: Standing + - type: LightningTarget + priority: 1 + lightningExplode: false + + +- type: damageModifierSet + id: IPC + coefficients: + Poison: 0 + Cold: 0.2 + Heat: 2 + Shock: 2.5 + diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml index 15ef3f1cb98..c8d987874c7 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml @@ -41,7 +41,8 @@ !type:DamageTrigger damage: 150 behaviors: - - !type:GibBehavior { } + - !type:GibBehavior + gibContents: Skip - type: SlowOnDamage #modified speeds because they're so weak speedModifierThresholds: 60: 0.9 diff --git a/Resources/Prototypes/_Backmen/Body/Parts/shadowkin.yml b/Resources/Prototypes/_Backmen/Body/Parts/shadowkin.yml index a56c6d2db8f..159a542c396 100644 --- a/Resources/Prototypes/_Backmen/Body/Parts/shadowkin.yml +++ b/Resources/Prototypes/_Backmen/Body/Parts/shadowkin.yml @@ -11,11 +11,48 @@ sprite: Backmen/Mobs/Species/shadowkin.rsi - type: Damageable damageContainer: Biological + - type: Gibbable + - type: SurgeryTool - type: BodyPart - type: ContainerContainer containers: bodypart: !type:Container ents: [] + - type: StaticPrice #DynamicPrice + price: 100 + - type: Tag + tags: + - Trash + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 50 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Slash + damage: 100 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Heat + damage: 200 + behaviors: + - !type:SpawnEntitiesBehavior + spawnInContainer: true + spawn: + Ash: + min: 1 + max: 1 + - !type:BurnBodyBehavior { } + - !type:PlaySoundBehavior + sound: + collection: MeatLaserImpact + - type: entity id: TorsoShadowkin @@ -28,6 +65,11 @@ state: "torso_m" - type: BodyPart partType: Torso + toolName: "a torso" + containerName: "torso_slot" + - type: ContainerContainer + containers: + torso_slot: !type:ContainerSlot {} - type: entity id: HeadShadowkin @@ -40,6 +82,8 @@ state: "head_m" - type: BodyPart partType: Head + toolName: "a head" + vital: true - type: Input context: "ghost" - type: MovementSpeedModifier @@ -60,6 +104,7 @@ - type: BodyPart partType: Arm symmetry: Left + toolName: "a left arm" - type: entity id: RightArmShadowkin @@ -73,6 +118,7 @@ - type: BodyPart partType: Arm symmetry: Right + toolName: "a right arm" - type: entity id: LeftHandShadowkin @@ -86,6 +132,7 @@ - type: BodyPart partType: Hand symmetry: Left + toolName: "a left hand" - type: entity id: RightHandShadowkin @@ -99,6 +146,7 @@ - type: BodyPart partType: Hand symmetry: Right + toolName: "a right hand" - type: entity id: LeftLegShadowkin @@ -112,6 +160,7 @@ - type: BodyPart partType: Leg symmetry: Left + toolName: "a left leg" - type: MovementBodyPart - type: entity @@ -126,6 +175,7 @@ - type: BodyPart partType: Leg symmetry: Right + toolName: "a right leg" - type: MovementBodyPart - type: entity @@ -140,6 +190,7 @@ - type: BodyPart partType: Foot symmetry: Left + toolName: "a left foot" - type: entity id: RightFootShadowkin @@ -153,3 +204,5 @@ - type: BodyPart partType: Foot symmetry: Right + toolName: "a right foot" + diff --git a/Resources/Prototypes/_Backmen/Entities/Objects/Misc/handcuffs.yml b/Resources/Prototypes/_Backmen/Entities/Objects/Misc/handcuffs.yml index a49079806cf..22b054c75fb 100644 --- a/Resources/Prototypes/_Backmen/Entities/Objects/Misc/handcuffs.yml +++ b/Resources/Prototypes/_Backmen/Entities/Objects/Misc/handcuffs.yml @@ -16,4 +16,3 @@ - type: Handcuff cuffedRSI: Backmen/Objects/Misc/purplecuff.rsi bodyIconState: body-overlay - breakoutTime: 5 \ No newline at end of file diff --git a/Resources/Prototypes/_Backmen/Entities/Surgery/surgeries.yml b/Resources/Prototypes/_Backmen/Entities/Surgery/surgeries.yml index 1729cdd306c..5b0e7c83abf 100644 --- a/Resources/Prototypes/_Backmen/Entities/Surgery/surgeries.yml +++ b/Resources/Prototypes/_Backmen/Entities/Surgery/surgeries.yml @@ -69,7 +69,7 @@ categories: [ HideSpawnMenu ] components: - type: Surgery - requirement: SurgeryOpenIncision + #requirement: SurgeryOpenIncision steps: - SurgeryStepInsertFeature - SurgeryStepSealWounds @@ -149,7 +149,7 @@ - type: entity parent: SurgeryBase id: SurgeryAttachLeftHand - name: Attach Left Han + name: Attach Left Hand categories: [ HideSpawnMenu ] components: - type: Surgery