diff --git a/Content.Server/MagicMirror/MagicMirrorSystem.cs b/Content.Server/MagicMirror/MagicMirrorSystem.cs index 8d8a6bfa3b..f4f889e549 100644 --- a/Content.Server/MagicMirror/MagicMirrorSystem.cs +++ b/Content.Server/MagicMirror/MagicMirrorSystem.cs @@ -4,8 +4,12 @@ using Content.Server.Humanoid; using Content.Shared.DoAfter; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; +using Content.Shared.Inventory; using Content.Shared.MagicMirror; +using Content.Shared.Popups; +using Content.Shared.Tag; using Robust.Shared.Audio.Systems; namespace Content.Server.MagicMirror; @@ -19,6 +23,9 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly MarkingManager _markings = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; public override void Initialize() { @@ -46,9 +53,26 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem if (component.Target is not { } target) return; + // Check if the target getting their hair altered has any clothes that hides their hair + if (CheckHeadSlotOrClothes(message.Actor, component.Target.Value)) + { + _popup.PopupEntity( + component.Target == message.Actor + ? Loc.GetString("magic-mirror-blocked-by-hat-self") + : Loc.GetString("magic-mirror-blocked-by-hat-self-target"), + message.Actor, + message.Actor, + PopupType.Medium); + return; + } + _doAfterSystem.Cancel(component.DoAfter); component.DoAfter = null; + var doafterTime = component.SelectSlotTime; + if (component.Target == message.Actor) + doafterTime /= 3; + var doAfter = new MagicMirrorSelectDoAfterEvent() { Category = message.Category, @@ -56,7 +80,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem Marking = message.Marking, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.SelectSlotTime, doAfter, uid, target: target, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, doafterTime, doAfter, uid, target: target, used: uid) { DistanceThreshold = SharedInteractionSystem.InteractionRange, BreakOnDamage = true, @@ -66,6 +90,15 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem }, out var doAfterId); + if (component.Target == message.Actor) + { + _popup.PopupEntity(Loc.GetString("magic-mirror-change-slot-self"), component.Target.Value, component.Target.Value, PopupType.Medium); + } + else + { + _popup.PopupEntity(Loc.GetString("magic-mirror-change-slot-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium); + } + component.DoAfter = doAfterId; _audio.PlayPvs(component.ChangeHairSound, uid); } @@ -102,9 +135,26 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem if (component.Target is not { } target) return; + // Check if the target getting their hair altered has any clothes that hides their hair + if (CheckHeadSlotOrClothes(message.Actor, component.Target.Value)) + { + _popup.PopupEntity( + component.Target == message.Actor + ? Loc.GetString("magic-mirror-blocked-by-hat-self") + : Loc.GetString("magic-mirror-blocked-by-hat-self-target"), + message.Actor, + message.Actor, + PopupType.Medium); + return; + } + _doAfterSystem.Cancel(component.DoAfter); component.DoAfter = null; + var doafterTime = component.ChangeSlotTime; + if (component.Target == message.Actor) + doafterTime /= 3; + var doAfter = new MagicMirrorChangeColorDoAfterEvent() { Category = message.Category, @@ -112,7 +162,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem Colors = message.Colors, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.ChangeSlotTime, doAfter, uid, target: target, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, doafterTime, doAfter, uid, target: target, used: uid) { BreakOnDamage = true, BreakOnMove = true, @@ -121,6 +171,15 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem }, out var doAfterId); + if (component.Target == message.Actor) + { + _popup.PopupEntity(Loc.GetString("magic-mirror-change-color-self"), component.Target.Value, component.Target.Value, PopupType.Medium); + } + else + { + _popup.PopupEntity(Loc.GetString("magic-mirror-change-color-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium); + } + component.DoAfter = doAfterId; } private void OnChangeColorDoAfter(EntityUid uid, MagicMirrorComponent component, MagicMirrorChangeColorDoAfterEvent args) @@ -156,16 +215,33 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem if (component.Target is not { } target) return; + // Check if the target getting their hair altered has any clothes that hides their hair + if (CheckHeadSlotOrClothes(message.Actor, component.Target.Value)) + { + _popup.PopupEntity( + component.Target == message.Actor + ? Loc.GetString("magic-mirror-blocked-by-hat-self") + : Loc.GetString("magic-mirror-blocked-by-hat-self-target"), + message.Actor, + message.Actor, + PopupType.Medium); + return; + } + _doAfterSystem.Cancel(component.DoAfter); component.DoAfter = null; + var doafterTime = component.RemoveSlotTime; + if (component.Target == message.Actor) + doafterTime /= 3; + var doAfter = new MagicMirrorRemoveSlotDoAfterEvent() { Category = message.Category, Slot = message.Slot, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.RemoveSlotTime, doAfter, uid, target: target, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, doafterTime, doAfter, uid, target: target, used: uid) { DistanceThreshold = SharedInteractionSystem.InteractionRange, BreakOnDamage = true, @@ -174,6 +250,15 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem }, out var doAfterId); + if (component.Target == message.Actor) + { + _popup.PopupEntity(Loc.GetString("magic-mirror-remove-slot-self"), component.Target.Value, component.Target.Value, PopupType.Medium); + } + else + { + _popup.PopupEntity(Loc.GetString("magic-mirror-remove-slot-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium); + } + component.DoAfter = doAfterId; _audio.PlayPvs(component.ChangeHairSound, uid); } @@ -210,15 +295,32 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem if (component.Target == null) return; + // Check if the target getting their hair altered has any clothes that hides their hair + if (CheckHeadSlotOrClothes(message.Actor, component.Target.Value)) + { + _popup.PopupEntity( + component.Target == message.Actor + ? Loc.GetString("magic-mirror-blocked-by-hat-self") + : Loc.GetString("magic-mirror-blocked-by-hat-self-target"), + message.Actor, + message.Actor, + PopupType.Medium); + return; + } + _doAfterSystem.Cancel(component.DoAfter); component.DoAfter = null; + var doafterTime = component.AddSlotTime; + if (component.Target == message.Actor) + doafterTime /= 3; + var doAfter = new MagicMirrorAddSlotDoAfterEvent() { Category = message.Category, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.AddSlotTime, doAfter, uid, target: component.Target.Value, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, doafterTime, doAfter, uid, target: component.Target.Value, used: uid) { BreakOnDamage = true, BreakOnMove = true, @@ -227,6 +329,15 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem }, out var doAfterId); + if (component.Target == message.Actor) + { + _popup.PopupEntity(Loc.GetString("magic-mirror-add-slot-self"), component.Target.Value, component.Target.Value, PopupType.Medium); + } + else + { + _popup.PopupEntity(Loc.GetString("magic-mirror-add-slot-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium); + } + component.DoAfter = doAfterId; _audio.PlayPvs(component.ChangeHairSound, uid); } @@ -265,4 +376,32 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem ent.Comp.Target = null; Dirty(ent); } + + /// + /// Helper function that checks if the wearer has anything on their head + /// Or if they have any clothes that hides their hair + /// + private bool CheckHeadSlotOrClothes(EntityUid user, EntityUid target) + { + if (TryComp(target, out var inventoryComp)) + { + // any hat whatsoever will block haircutting + if (_inventory.TryGetSlotEntity(target, "head", out var hat, inventoryComp)) + { + return true; + } + + // maybe there's some kind of armor that has the HidesHair tag as well, so check every slot for it + var slots = _inventory.GetSlotEnumerator((target, inventoryComp), SlotFlags.WITHOUT_POCKET); + while (slots.MoveNext(out var slot)) + { + if (slot.ContainedEntity != null && _tagSystem.HasTag(slot.ContainedEntity.Value, "HidesHair")) + { + return true; + } + } + } + + return false; + } } diff --git a/Content.Shared/MagicMirror/MagicMirrorComponent.cs b/Content.Shared/MagicMirror/MagicMirrorComponent.cs index 95b1736979..97738be228 100644 --- a/Content.Shared/MagicMirror/MagicMirrorComponent.cs +++ b/Content.Shared/MagicMirror/MagicMirrorComponent.cs @@ -20,28 +20,28 @@ public sealed partial class MagicMirrorComponent : Component public EntityUid? Target; /// - /// doafter time required to add a new slot + /// Do after time to add a new slot, adding hair to a person /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan AddSlotTime = TimeSpan.FromSeconds(5); + public TimeSpan AddSlotTime = TimeSpan.FromSeconds(7); /// - /// doafter time required to remove a existing slot + /// Do after time to remove a slot, removing hair from a person /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan RemoveSlotTime = TimeSpan.FromSeconds(2); + public TimeSpan RemoveSlotTime = TimeSpan.FromSeconds(7); /// - /// doafter time required to change slot + /// Do after time to change a person's hairstyle /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan SelectSlotTime = TimeSpan.FromSeconds(3); + public TimeSpan SelectSlotTime = TimeSpan.FromSeconds(7); /// - /// doafter time required to recolor slot + /// Do after time to change a person's hair color /// [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan ChangeSlotTime = TimeSpan.FromSeconds(1); + public TimeSpan ChangeSlotTime = TimeSpan.FromSeconds(7); /// /// Sound emitted when slots are changed diff --git a/Resources/Locale/en-US/character-appearance/components/magic-mirror-component.ftl b/Resources/Locale/en-US/character-appearance/components/magic-mirror-component.ftl index e9018171a4..0906cccee5 100644 --- a/Resources/Locale/en-US/character-appearance/components/magic-mirror-component.ftl +++ b/Resources/Locale/en-US/character-appearance/components/magic-mirror-component.ftl @@ -1,3 +1,15 @@ magic-mirror-component-activate-user-has-no-hair = You can't have any hair! -magic-mirror-window-title = Magic Mirror \ No newline at end of file +magic-mirror-window-title = Magic Mirror +magic-mirror-add-slot-self = You're giving yourself some hair. +magic-mirror-remove-slot-self = You're removing some of your hair. +magic-mirror-change-slot-self = You're changing your hairstyle. +magic-mirror-change-color-self = You're changing your hair color. + +magic-mirror-add-slot-target = Hair is being added to you by {$user}. +magic-mirror-remove-slot-target = Your hair is being cut off by {$user}. +magic-mirror-change-slot-target = Your hairstyle is being changed by {$user}. +magic-mirror-change-color-target = Your hair color is being changed by {$user}. + +magic-mirror-blocked-by-hat-self = You need to take off your hat before changing your hair. +magic-mirror-blocked-by-hat-self-target = You try to change their hair but their clothes gets in the way.