--- /dev/null
+using Content.Server.Actions;
+using Content.Server.Chat.Systems;
+using Content.Server.Humanoid;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Markings;
+using Content.Shared.Mobs;
+using Content.Shared.Toggleable;
+using Content.Shared.Wagging;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Wagging;
+
+/// <summary>
+/// Adds an action to toggle wagging animation for tails markings that supporting this
+/// </summary>
+public sealed class WaggingSystem : EntitySystem
+{
+ [Dependency] private readonly ActionsSystem _actions = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<WaggingComponent, MapInitEvent>(OnWaggingMapInit);
+ SubscribeLocalEvent<WaggingComponent, ComponentShutdown>(OnWaggingShutdown);
+ SubscribeLocalEvent<WaggingComponent, ToggleActionEvent>(OnWaggingToggle);
+ SubscribeLocalEvent<WaggingComponent, MobStateChangedEvent>(OnMobStateChanged);
+ }
+
+ private void OnWaggingMapInit(EntityUid uid, WaggingComponent component, MapInitEvent args)
+ {
+ _actions.AddAction(uid, ref component.ActionEntity, component.Action, uid);
+ }
+
+ private void OnWaggingShutdown(EntityUid uid, WaggingComponent component, ComponentShutdown args)
+ {
+ _actions.RemoveAction(uid, component.ActionEntity);
+ }
+
+ private void OnWaggingToggle(EntityUid uid, WaggingComponent component, ref ToggleActionEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ TryToggleWagging(uid, wagging: component);
+ }
+
+ private void OnMobStateChanged(EntityUid uid, WaggingComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState != MobState.Dead)
+ return;
+
+ if (component.Wagging)
+ TryToggleWagging(uid, wagging: component);
+ }
+
+ public bool TryToggleWagging(EntityUid uid, WaggingComponent? wagging = null, HumanoidAppearanceComponent? humanoid = null)
+ {
+ if (!Resolve(uid, ref wagging, ref humanoid))
+ return false;
+
+ if (!humanoid.MarkingSet.Markings.TryGetValue(MarkingCategories.Tail, out var markings))
+ return false;
+
+ if (markings.Count == 0)
+ return false;
+
+ wagging.Wagging = !wagging.Wagging;
+
+ for (var idx = 0; idx < markings.Count; idx++) // Animate all possible tails
+ {
+ var currentMarkingId = markings[idx].MarkingId;
+ string newMarkingId;
+
+ if (wagging.Wagging)
+ {
+ newMarkingId = $"{currentMarkingId}{wagging.Suffix}";
+ }
+ else
+ {
+ if (currentMarkingId.EndsWith(wagging.Suffix))
+ {
+ newMarkingId = currentMarkingId[..^wagging.Suffix.Length];
+ }
+ else
+ {
+ newMarkingId = currentMarkingId;
+ Log.Warning($"Unable to revert wagging for {currentMarkingId}");
+ }
+ }
+
+ if (!_prototype.HasIndex<MarkingPrototype>(newMarkingId))
+ {
+ Log.Warning($"{ToPrettyString(uid)} tried toggling wagging but {newMarkingId} marking doesn't exist");
+ continue;
+ }
+
+ _humanoidAppearance.SetMarkingId(uid, MarkingCategories.Tail, idx, newMarkingId,
+ humanoid: humanoid);
+ }
+
+ var emoteText = Loc.GetString(wagging.Wagging ? "wagging-emote-start" : "wagging-emote-stop", ("ent", uid));
+ _chat.TrySendInGameICMessage(uid, emoteText, InGameICChatType.Emote, ChatTransmitRange.Normal); // Ok while emotes dont have radial menu
+
+ return true;
+ }
+}
/// <param name="actionId">Action entity to add</param>
/// <param name="component">The <see cref="performer"/>'s action component of </param>
/// <param name="actionPrototypeId">The action entity prototype id to use if <see cref="actionId"/> is invalid.</param>
- /// <param name="container">The entity that contains/enables this action (e.g., flashlight)..</param>
+ /// <param name="container">The entity that contains/enables this action (e.g., flashlight).</param>
public bool AddAction(EntityUid performer,
[NotNullWhen(true)] ref EntityUid? actionId,
string? actionPrototypeId,
--- /dev/null
+using Content.Shared.Chat.Prototypes;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Wagging;
+
+/// <summary>
+/// An emoting wag for markings.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class WaggingComponent : Component
+{
+ [DataField]
+ public EntProtoId Action = "ActionToggleWagging";
+
+ [DataField]
+ public EntityUid? ActionEntity;
+
+ [DataField]
+ public ProtoId<EmotePrototype> EmoteId = "WagTail";
+
+ /// <summary>
+ /// Suffix to add to get the animated marking.
+ /// </summary>
+ public string Suffix = "Animated";
+
+ /// <summary>
+ /// Is the entity currently wagging.
+ /// </summary>
+ [DataField]
+ public bool Wagging = false;
+}
--- /dev/null
+action-name-toggle-wagging = Wagging Tail
+action-description-toggle-wagging = Start or stop wagging tail.
+
+wagging-emote-start = starts wagging {POSS-ADJ($ent)} tail.
+wagging-emote-stop = stops wagging {POSS-ADJ($ent)} tail.
\ No newline at end of file
event: !type:ToggleEyesActionEvent
useDelay: 1 # so u cant give yourself and observers eyestrain by rapidly spamming the action
+- type: entity
+ id: ActionToggleWagging
+ name: action-name-toggle-wagging
+ description: action-description-toggle-wagging
+ noSpawn: true
+ components:
+ - type: InstantAction
+ icon: { sprite: Mobs/Customization/reptilian_parts.rsi, state: tail_smooth_behind }
+ iconOn: { sprite: Mobs/Customization/reptilian_parts.rsi, state: tail_smooth_behind }
+ itemIconStyle: NoItem
+ useDelay: 1 # emote spam
+ event: !type:ToggleActionEvent
sprites:
- sprite: Mobs/Customization/reptilian_parts.rsi
state: horns_kobold_ears
-
+
- type: marking
id: LizardHornsFloppyKoboldEars
bodyPart: HeadSide
sprites:
- sprite: Mobs/Customization/reptilian_parts.rsi
state: body_backspikes
+
+# Animated
+- type: marking
+ id: LizardTailSmoothAnimated
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: []
+ sprites:
+ - sprite: Mobs/Customization/reptilian_parts.rsi
+ state: tail_smooth_wagging
+
+- type: marking
+ id: LizardTailSpikesAnimated
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: []
+ sprites:
+ - sprite: Mobs/Customization/reptilian_parts.rsi
+ state: tail_spikes_wagging
+
+- type: marking
+ id: LizardTailLTigerAnimated
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: []
+ sprites:
+ - sprite: Mobs/Customization/reptilian_parts.rsi
+ state: tail_ltiger_wagging
+
+- type: marking
+ id: LizardTailDTigerAnimated
+ bodyPart: Tail
+ markingCategory: Tail
+ speciesRestriction: []
+ sprites:
+ - sprite: Mobs/Customization/reptilian_parts.rsi
+ state: tail_dtiger_wagging
heatDamage:
types:
Heat : 1.5 #per second, scales with temperature & other constants
+ - type: Wagging
- type: entity
parent: BaseSpeciesDummy
--- /dev/null
+- type: emote
+ id: WagTail
+ chatMessages: [wags tail]
+ chatTriggers:
+ - wag
+ - wag.
+ - wags
+ - wags.
+ - wagging
+ - wagging.
+ - wag tail
+ - wag tail.
+ - wags tail
+ - wags tail.
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "https://github.com/Skyrat-SS13/Skyrat-tg/tree/40e3cdbb15b8bc0d5ef2fb46133adf805bda5297, while Argali, Ayrshire, Myrsore and Bighorn are drawn by Ubaser, and Kobold Ears are drawn by Pigeonpeas. Body_underbelly made by Nairod(github) for SS14. Large is drawn by Ubaser.",
+ "copyright": "https://github.com/Skyrat-SS13/Skyrat-tg/tree/40e3cdbb15b8bc0d5ef2fb46133adf805bda5297, while Argali, Ayrshire, Myrsore and Bighorn are drawn by Ubaser, and Kobold Ears are drawn by Pigeonpeas. Body_underbelly made by Nairod(github) for SS14. Large drawn by Ubaser. Wagging tail by SonicDC.",
"size": {
"x": 32,
"y": 32
"name": "tail_ltiger",
"directions": 4
},
+ {
+ "name": "tail_smooth_wagging",
+ "directions": 4,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "tail_dtiger_wagging",
+ "directions": 4,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "tail_ltiger_wagging",
+ "directions": 4,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "tail_spikes_wagging",
+ "directions": 4,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
{
"name": "snout_round",
"directions": 4