From 2ecfb9552a2f16016845845cc8c4bc9f3e019fdc Mon Sep 17 00:00:00 2001 From: Jessica M Date: Fri, 10 Oct 2025 13:17:24 -0700 Subject: [PATCH] Add variables to CluwneComponent, allowing for admeme customizing. Also localized two strings. (#40466) * Add variables to cluwne component, update to the new style, add unremovable option to setoutfit. * not nullable, shorthand * Add comments, address reviews * why, was i drunk? * Apply suggestions from code review --------- Co-authored-by: Jessica M Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Clothing/Systems/OutfitSystem.cs | 5 +- Content.Server/Cluwne/CluwneSystem.cs | 68 ++++++++------- Content.Shared/Cluwne/CluwneComponent.cs | 84 +++++++++++++++++-- Resources/Locale/en-US/cluwne/cluwne.ftl | 2 + 4 files changed, 122 insertions(+), 37 deletions(-) diff --git a/Content.Server/Clothing/Systems/OutfitSystem.cs b/Content.Server/Clothing/Systems/OutfitSystem.cs index c3fea28bcf..c02a4f1a3b 100644 --- a/Content.Server/Clothing/Systems/OutfitSystem.cs +++ b/Content.Server/Clothing/Systems/OutfitSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Access.Components; using Content.Shared.Clothing; using Content.Shared.Hands.Components; using Content.Shared.Humanoid; +using Content.Shared.Interaction.Components; using Content.Shared.Inventory; using Content.Shared.PDA; using Content.Shared.Preferences; @@ -23,7 +24,7 @@ public sealed class OutfitSystem : EntitySystem [Dependency] private readonly InventorySystem _invSystem = default!; [Dependency] private readonly SharedStationSpawningSystem _spawningSystem = default!; - public bool SetOutfit(EntityUid target, string gear, Action? onEquipped = null) + public bool SetOutfit(EntityUid target, string gear, Action? onEquipped = null, bool unremovable = false) { if (!EntityManager.TryGetComponent(target, out InventoryComponent? inventoryComponent)) return false; @@ -60,6 +61,8 @@ public sealed class OutfitSystem : EntitySystem } _invSystem.TryEquip(target, equipmentEntity, slot.Name, silent: true, force: true, inventory: inventoryComponent); + if (unremovable) + EnsureComp(equipmentEntity); onEquipped?.Invoke(target, equipmentEntity); } diff --git a/Content.Server/Cluwne/CluwneSystem.cs b/Content.Server/Cluwne/CluwneSystem.cs index 21a15d812f..e51a01a1d4 100644 --- a/Content.Server/Cluwne/CluwneSystem.cs +++ b/Content.Server/Cluwne/CluwneSystem.cs @@ -7,7 +7,6 @@ using Content.Server.Clothing.Systems; using Content.Shared.Chat.Prototypes; using Robust.Shared.Random; using Content.Shared.Stunnable; -using Content.Shared.Damage.Prototypes; using Content.Shared.Damage; using Robust.Shared.Prototypes; using Content.Server.Emoting.Systems; @@ -21,7 +20,6 @@ namespace Content.Server.Cluwne; public sealed class CluwneSystem : EntitySystem { - private static readonly ProtoId GeneticDamageGroup = "Genetic"; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -48,15 +46,14 @@ public sealed class CluwneSystem : EntitySystem /// /// On death removes active comps and gives genetic damage to prevent cloning, reduce this to allow cloning. /// - private void OnMobState(EntityUid uid, CluwneComponent component, MobStateChangedEvent args) + private void OnMobState(Entity ent, ref MobStateChangedEvent args) { if (args.NewMobState == MobState.Dead) { - RemComp(uid); - RemComp(uid); - RemComp(uid); - var damageSpec = new DamageSpecifier(_prototypeManager.Index(GeneticDamageGroup), 300); - _damageableSystem.TryChangeDamage(uid, damageSpec); + RemComp(ent.Owner); + RemComp(ent.Owner); + RemComp(ent.Owner); + _damageableSystem.TryChangeDamage(ent.Owner, ent.Comp.RevertDamage); } } @@ -65,52 +62,65 @@ public sealed class CluwneSystem : EntitySystem /// /// OnStartup gives the cluwne outfit, ensures clumsy, and makes sure emote sounds are laugh. /// - private void OnComponentStartup(EntityUid uid, CluwneComponent component, ComponentStartup args) + private void OnComponentStartup(Entity ent, ref ComponentStartup args) { - if (component.EmoteSoundsId == null) + if (ent.Comp.EmoteSoundsId == null) return; - _prototypeManager.TryIndex(component.EmoteSoundsId, out EmoteSounds); - EnsureComp(uid); - _autoEmote.AddEmote(uid, "CluwneGiggle"); - EnsureComp(uid); + _prototypeManager.TryIndex(ent.Comp.EmoteSoundsId, out EmoteSounds); - _popupSystem.PopupEntity(Loc.GetString("cluwne-transform", ("target", uid)), uid, PopupType.LargeCaution); - _audio.PlayPvs(component.SpawnSound, uid); - _nameMod.RefreshNameModifiers(uid); + if (ent.Comp.RandomEmote && ent.Comp.AutoEmoteId != null) + { + EnsureComp(ent.Owner); + _autoEmote.AddEmote(ent.Owner, ent.Comp.AutoEmoteId); + } + + EnsureComp(ent.Owner); - _outfitSystem.SetOutfit(uid, "CluwneGear"); + var transformMessage = Loc.GetString(ent.Comp.TransformMessage, ("target", ent.Owner)); + + _popupSystem.PopupEntity(transformMessage, ent.Owner, PopupType.LargeCaution); + _audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner); + + _nameMod.RefreshNameModifiers(ent.Owner); + + + _outfitSystem.SetOutfit(ent.Owner, ent.Comp.OutfitId, unremovable: true); } /// /// Handles the timing on autoemote as well as falling over and honking. /// - private void OnEmote(EntityUid uid, CluwneComponent component, ref EmoteEvent args) + private void OnEmote(Entity ent, ref EmoteEvent args) { if (args.Handled) return; - args.Handled = _chat.TryPlayEmoteSound(uid, EmoteSounds, args.Emote); - if (_robustRandom.Prob(component.GiggleRandomChance)) + if (!ent.Comp.RandomEmote) + return; + + args.Handled = _chat.TryPlayEmoteSound(ent.Owner, EmoteSounds, args.Emote); + + if (_robustRandom.Prob(ent.Comp.GiggleRandomChance)) { - _audio.PlayPvs(component.SpawnSound, uid); - _chat.TrySendInGameICMessage(uid, "honks", InGameICChatType.Emote, ChatTransmitRange.Normal); + _audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner); + _chat.TrySendInGameICMessage(ent.Owner, Loc.GetString(ent.Comp.GiggleEmote), InGameICChatType.Emote, ChatTransmitRange.Normal); } - else if (_robustRandom.Prob(component.KnockChance)) + else if (_robustRandom.Prob(ent.Comp.KnockChance)) { - _audio.PlayPvs(component.KnockSound, uid); - _stunSystem.TryUpdateParalyzeDuration(uid, TimeSpan.FromSeconds(component.ParalyzeTime)); - _chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal); + _audio.PlayPvs(ent.Comp.KnockSound, ent.Owner); + _stunSystem.TryUpdateParalyzeDuration(ent.Owner, TimeSpan.FromSeconds(ent.Comp.ParalyzeTime)); + _chat.TrySendInGameICMessage(ent.Owner, Loc.GetString(ent.Comp.KnockEmote), InGameICChatType.Emote, ChatTransmitRange.Normal); } } /// /// Applies "Cluwnified" prefix /// - private void OnRefreshNameModifiers(Entity entity, ref RefreshNameModifiersEvent args) + private void OnRefreshNameModifiers(Entity ent, ref RefreshNameModifiersEvent args) { - args.AddModifier("cluwne-name-prefix"); + args.AddModifier(ent.Comp.NamePrefix); } } diff --git a/Content.Shared/Cluwne/CluwneComponent.cs b/Content.Shared/Cluwne/CluwneComponent.cs index c9f96d030b..692a8ec4f9 100644 --- a/Content.Shared/Cluwne/CluwneComponent.cs +++ b/Content.Shared/Cluwne/CluwneComponent.cs @@ -1,6 +1,9 @@ using Robust.Shared.Audio; using Content.Shared.Chat.Prototypes; +using Content.Shared.Damage; +using Content.Shared.Roles; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Cluwne; @@ -12,22 +15,74 @@ public sealed partial class CluwneComponent : Component /// /// timings for giggles and knocks. /// - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan DamageGiggleCooldown = TimeSpan.FromSeconds(2); - [ViewVariables(VVAccess.ReadWrite)] + /// + /// Amount of genetic damage dealt when they revert + /// + [DataField] + public DamageSpecifier RevertDamage = new() + { + DamageDict = new() + { + { "Genetic", 300.0 }, + }, + }; + + /// + /// Chance that the Cluwne will be knocked over and paralyzed. + /// + [DataField] public float KnockChance = 0.05f; - [ViewVariables(VVAccess.ReadWrite)] + /// + /// Chance that the Cluwne will randomly giggle + /// + [DataField] public float GiggleRandomChance = 0.1f; - [DataField("emoteId", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? EmoteSoundsId = "Cluwne"; + /// + /// Enable random emoting? + /// + [DataField] + public bool RandomEmote = true; + + /// + /// Emote sound collection that the Cluwne should use. + /// + [DataField("emoteId")] + public ProtoId? EmoteSoundsId = "Cluwne"; + + /// + /// Emote to use for the Cluwne Giggling + /// + [DataField] + public ProtoId? AutoEmoteId = "CluwneGiggle"; + + /// + /// Message to popup when the Cluwne is transformed + /// + [DataField] + public LocId TransformMessage = "cluwne-transform"; + + /// + /// Name prefix for the Cluwne. + /// Example "Urist McHuman" will be "Cluwned Urist McHuman" + /// + [DataField] + public LocId NamePrefix = "cluwne-name-prefix"; + + /// + /// Outfit ID that the cluwne will spawn with. + /// + [DataField] + public ProtoId OutfitId = "CluwneGear"; /// /// Amount of time cluwne is paralyzed for when falling over. /// - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float ParalyzeTime = 2f; /// @@ -36,6 +91,21 @@ public sealed partial class CluwneComponent : Component [DataField("spawnsound")] public SoundSpecifier SpawnSound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg"); - [DataField("knocksound")] + /// + /// Emote to use for the cluwne giggling + /// + [DataField] + public LocId GiggleEmote = "cluwne-giggle-emote"; + + /// + /// Sound to play when the Cluwne is knocked over and paralyzed + /// + [DataField] public SoundSpecifier KnockSound = new SoundPathSpecifier("/Audio/Items/airhorn.ogg"); + + /// + /// Emote thats used when the cluwne getting knocked over + /// + [DataField] + public LocId KnockEmote = "cluwne-knock-emote"; } diff --git a/Resources/Locale/en-US/cluwne/cluwne.ftl b/Resources/Locale/en-US/cluwne/cluwne.ftl index 0ffd3f32df..e469cbfbac 100644 --- a/Resources/Locale/en-US/cluwne/cluwne.ftl +++ b/Resources/Locale/en-US/cluwne/cluwne.ftl @@ -1,2 +1,4 @@ cluwne-transform = {CAPITALIZE(THE($target))} turned into a cluwne! cluwne-name-prefix = cluwnified {$baseName} +cluwne-knock-emote = spasms +cluwne-giggle-emote = honks