diff --git a/Content.Client/_Goobstation/Emoting/AnimatedEmotesSystem.cs b/Content.Client/_Goobstation/Emoting/AnimatedEmotesSystem.cs new file mode 100644 index 00000000000..33b89fbed10 --- /dev/null +++ b/Content.Client/_Goobstation/Emoting/AnimatedEmotesSystem.cs @@ -0,0 +1,137 @@ +using Robust.Client.Animations; +using Robust.Shared.Animations; +using Robust.Shared.GameStates; +using Robust.Client.GameObjects; +using Content.Shared.Emoting; +using System.Numerics; +using Robust.Shared.Prototypes; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Rotation; + +namespace Content.Client.Emoting; + +public sealed partial class AnimatedEmotesSystem : SharedAnimatedEmotesSystem +{ + [Dependency] private readonly AnimationPlayerSystem _anim = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly IPrototypeManager _prot = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHandleState); + + SubscribeLocalEvent(OnFlip); + SubscribeLocalEvent(OnSpin); + SubscribeLocalEvent(OnJump); + } + + public void PlayEmote(EntityUid uid, Animation anim, string animationKey = "emoteAnimKeyId") + { + if (_anim.HasRunningAnimation(uid, animationKey)) + return; + + _anim.Play(uid, anim, animationKey); + } + + private void OnHandleState(EntityUid uid, AnimatedEmotesComponent component, ref ComponentHandleState args) + { + if (args.Current is not AnimatedEmotesComponentState state + || !_prot.TryIndex(state.Emote, out var emote)) + return; + + if (emote.Event != null) + RaiseLocalEvent(uid, emote.Event); + } + + private void OnFlip(Entity ent, ref AnimationFlipEmoteEvent args) + { + var angle = Angle.Zero; + + if (TryComp(ent, out var rotation)) + { + _appearance.TryGetData(ent, RotationVisuals.RotationState, out var state); + + angle = state switch + { + RotationState.Vertical => rotation.VerticalRotation, + RotationState.Horizontal => rotation.HorizontalRotation, + _ => Angle.Zero + }; + } + + var a = new Animation + { + Length = TimeSpan.FromMilliseconds(500), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Rotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(angle, 0f), + new AnimationTrackProperty.KeyFrame(angle + Angle.FromDegrees(180), 0.25f), + new AnimationTrackProperty.KeyFrame(angle + Angle.FromDegrees(360), 0.25f), + } + } + } + }; + PlayEmote(ent, a); + } + private void OnSpin(Entity ent, ref AnimationSpinEmoteEvent args) + { + var a = new Animation + { + Length = TimeSpan.FromMilliseconds(600), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(TransformComponent), + Property = nameof(TransformComponent.LocalRotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(90), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(180), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(270), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.Zero, 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(90), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(180), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(270), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.Zero, 0.075f), + } + } + } + }; + PlayEmote(ent, a, "emoteAnimSpin"); + } + private void OnJump(Entity ent, ref AnimationJumpEmoteEvent args) + { + var a = new Animation + { + Length = TimeSpan.FromMilliseconds(250), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + InterpolationMode = AnimationInterpolationMode.Cubic, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), + new AnimationTrackProperty.KeyFrame(new Vector2(0, .35f), 0.125f), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0.125f), + } + } + } + }; + PlayEmote(ent, a); + } +} diff --git a/Content.Server/GameTicking/Presets/GamePresetPrototype.cs b/Content.Server/GameTicking/Presets/GamePresetPrototype.cs index 4731364ace2..f82f0c25c70 100644 --- a/Content.Server/GameTicking/Presets/GamePresetPrototype.cs +++ b/Content.Server/GameTicking/Presets/GamePresetPrototype.cs @@ -33,6 +33,15 @@ public sealed partial class GamePresetPrototype : IPrototype [DataField("maxPlayers")] public int? MaxPlayers; + // Begin Imp + /// + /// Ensures that this gamemode does not get selected for a number of rounds + /// by something like Secret. This is not considered when the preset is forced. + /// + [DataField] + public int Cooldown = 0; + // End Imp + [DataField("rules", customTypeSerializer: typeof(PrototypeIdListSerializer))] public IReadOnlyList Rules { get; private set; } = Array.Empty(); diff --git a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs index e82cecde368..d38b798bc58 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Chat.Managers; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; +using Content.Shared._DV.CCVars; // DeltaV using Content.Shared.GameTicking.Components; using Content.Shared.Random; using Content.Shared.CCVar; @@ -22,6 +23,11 @@ public sealed class SecretRuleSystem : GameRuleSystem [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IComponentFactory _compFact = default!; + [Dependency] private readonly GameTicker _ticker = default!; // begin Imp + + // Dictionary that contains the minimum round number for certain preset + // prototypes to be allowed to roll again + private static Dictionary, int> _nextRoundAllowed = new(); // end Imp private string _ruleCompName = default!; @@ -46,6 +52,15 @@ protected override void Added(EntityUid uid, SecretRuleComponent component, Game Log.Info($"Selected {preset.ID} as the secret preset."); _adminLogger.Add(LogType.EventStarted, $"Selected {preset.ID} as the secret preset."); + if (_configurationManager.GetCVar(DCCVars.EnableBacktoBack) == true) // DeltaV + { + if (preset.Cooldown > 0) // Begin Imp + { + _nextRoundAllowed[preset.ID] = _ticker.RoundId + preset.Cooldown + 1; + Log.Info($"{preset.ID} is now on cooldown until {_nextRoundAllowed[preset.ID]}"); + } // End Imp + } // DeltaV + foreach (var rule in preset.Rules) { EntityUid ruleEnt; @@ -167,6 +182,15 @@ private bool CanPick([NotNullWhen(true)] GamePresetPrototype? selected, int play return false; } + if (_configurationManager.GetCVar(DCCVars.EnableBacktoBack) == true) // DeltaV + { + if (_nextRoundAllowed.ContainsKey(selected.ID) && _nextRoundAllowed[selected.ID] > _ticker.RoundId) // Begin Imp + { + Log.Info($"Skipping preset {selected.ID} (Not available until round {_nextRoundAllowed[selected.ID]}"); + return false; + } // End Imp + } // DeltaV + return true; } } diff --git a/Content.Server/_Goobstation/Emoting/AnimatedEmotesSystem.cs b/Content.Server/_Goobstation/Emoting/AnimatedEmotesSystem.cs new file mode 100644 index 00000000000..cc4863d31f7 --- /dev/null +++ b/Content.Server/_Goobstation/Emoting/AnimatedEmotesSystem.cs @@ -0,0 +1,28 @@ +using Robust.Shared.GameStates; +using Content.Server.Chat.Systems; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Emoting; +using Robust.Shared.Prototypes; + +namespace Content.Server.Emoting; + +public sealed partial class AnimatedEmotesSystem : SharedAnimatedEmotesSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEmote); + } + + private void OnEmote(EntityUid uid, AnimatedEmotesComponent component, ref EmoteEvent args) + { + PlayEmoteAnimation(uid, component, args.Emote.ID); + } + + public void PlayEmoteAnimation(EntityUid uid, AnimatedEmotesComponent component, ProtoId prot) + { + component.Emote = prot; + Dirty(uid, component); + } +} diff --git a/Content.Shared/Chat/Prototypes/EmotePrototype.cs b/Content.Shared/Chat/Prototypes/EmotePrototype.cs index 7ee958ee6a7..34d54bc3600 100644 --- a/Content.Shared/Chat/Prototypes/EmotePrototype.cs +++ b/Content.Shared/Chat/Prototypes/EmotePrototype.cs @@ -68,6 +68,10 @@ public sealed partial class EmotePrototype : IPrototype /// [DataField] public HashSet ChatTriggers = new(); + + // goob edit - animations + [DataField] + public object? Event = null; } /// diff --git a/Content.Shared/_DV/CCVars/DCCVars.cs b/Content.Shared/_DV/CCVars/DCCVars.cs index d28faf854d2..059f16732da 100644 --- a/Content.Shared/_DV/CCVars/DCCVars.cs +++ b/Content.Shared/_DV/CCVars/DCCVars.cs @@ -155,4 +155,10 @@ public sealed class DCCVars /// public static readonly CVarDef DiscordReplyColor = CVarDef.Create("admin.discord_reply_color", string.Empty, CVar.SERVERONLY); + + /// + /// Whether or not to disable the preset selecting test rule from running. Should be disabled in production. DeltaV specific, attached to Impstation Secret concurrent feature. + /// + public static readonly CVarDef EnableBacktoBack = + CVarDef.Create("game.disable_preset_test", false, CVar.SERVERONLY); } diff --git a/Content.Shared/_Goobstation/Emoting/AnimatedEmotesComponent.cs b/Content.Shared/_Goobstation/Emoting/AnimatedEmotesComponent.cs new file mode 100644 index 00000000000..fc8121bbe5a --- /dev/null +++ b/Content.Shared/_Goobstation/Emoting/AnimatedEmotesComponent.cs @@ -0,0 +1,28 @@ +using Content.Shared.Chat.Prototypes; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Emoting; + +// use as a template +//[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationNameEmoteEvent : EntityEventArgs { } + +[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationFlipEmoteEvent : EntityEventArgs { } +[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationSpinEmoteEvent : EntityEventArgs { } +[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationJumpEmoteEvent : EntityEventArgs { } + +[RegisterComponent, NetworkedComponent] public sealed partial class AnimatedEmotesComponent : Component +{ + [DataField] public ProtoId? Emote; +} + +[Serializable, NetSerializable] public sealed partial class AnimatedEmotesComponentState : ComponentState +{ + public ProtoId? Emote; + + public AnimatedEmotesComponentState(ProtoId? emote) + { + Emote = emote; + } +} diff --git a/Content.Shared/_Goobstation/Emoting/SharedAnimatedEmotesSystem.cs b/Content.Shared/_Goobstation/Emoting/SharedAnimatedEmotesSystem.cs new file mode 100644 index 00000000000..b19e8c26a1e --- /dev/null +++ b/Content.Shared/_Goobstation/Emoting/SharedAnimatedEmotesSystem.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Emoting; + +public abstract class SharedAnimatedEmotesSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetState); + } + + private void OnGetState(Entity ent, ref ComponentGetState args) + { + args.State = new AnimatedEmotesComponentState(ent.Comp.Emote); + } +} diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index ce996342105..8f7b312f0c0 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -1,18 +1,4 @@ Entries: -- author: Colin-Tel - changes: - - message: Playtime requirements across "newbie jobs" have been adjusted. - type: Tweak - id: 438 - time: '2024-07-21T16:39:02.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/1500 -- author: Colin-Tel - changes: - - message: Service Workers can now choose green clothes in their loadouts! - type: Add - id: 439 - time: '2024-07-21T16:44:24.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/1499 - author: jimmy12or changes: - message: Adds Crystalthistle! A plant that produces Quartzite! @@ -3858,3 +3844,18 @@ id: 937 time: '2025-01-18T11:56:47.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/2762 +- author: whateverusername0, AirFryerBuyOneGetOneFree, Darkmajia, hivehum, crocodilecarousel + changes: + - message: 'Added two new animated emotes from Impstation: jump and spin.' + type: Add + id: 938 + time: '2025-01-18T23:50:21.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2746 +- author: TGRCDev, Lyndomen + changes: + - message: Gamemodes now have cooldowns to prevent back-to-back modes. Modes that + have been voted on are exempt from this. + type: Add + id: 939 + time: '2025-01-19T00:33:45.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2744 diff --git a/Resources/Locale/en-US/_Goobstation/emotes.ftl b/Resources/Locale/en-US/_Goobstation/emotes.ftl new file mode 100644 index 00000000000..f212a413abf --- /dev/null +++ b/Resources/Locale/en-US/_Goobstation/emotes.ftl @@ -0,0 +1,5 @@ + +chat-emote-name-spin = Spin +chat-emote-name-jump = Jump +chat-emote-msg-spin = spins! +chat-emote-msg-jump = jumps! diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index e553ed97f30..d2da2240f30 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -111,6 +111,7 @@ - type: Tag tags: - CannotSuicide + - type: AnimatedEmotes # goob edit - animated emotes # From the uplink injector - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 90f3e8380f3..84428577471 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -43,6 +43,7 @@ - type: MovementSpeedModifier - type: RequireProjectileTarget active: False + - type: AnimatedEmotes # goob edit - animated emotes - type: entity save: false diff --git a/Resources/Prototypes/_Goobstation/Actions/emotes.yml b/Resources/Prototypes/_Goobstation/Actions/emotes.yml new file mode 100644 index 00000000000..a7485248db2 --- /dev/null +++ b/Resources/Prototypes/_Goobstation/Actions/emotes.yml @@ -0,0 +1,27 @@ +- type: emote + id: Spin + name: chat-emote-name-spin + chatMessages: ["chat-emote-msg-spin"] + icon: _Impstation/Interface/Emotes/spin.png #imp + chatTriggers: + - spin + - spins + - spins. + - spins! + event: !type:AnimationSpinEmoteEvent + +- type: emote + id: Jump + name: chat-emote-name-jump + chatMessages: ["chat-emote-msg-jump"] + icon: _Impstation/Interface/Emotes/jump.png #imp + chatTriggers: + - jump + - jumps + - jumps. + - jumps! + - bounce + - bounces + - bounces. + - bounces! + event: !type:AnimationJumpEmoteEvent diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 5eaafddfaf9..39ffa1d4b26 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -5,6 +5,7 @@ name: survival-title showInVote: true # secret # DeltaV - Me when the survival. Used for periapsis. description: survival-description + cooldown: 2 # Imp - Can't occur thrice rules: - MeteorSwarmScheduler - RampingStationEventScheduler @@ -155,6 +156,7 @@ - tator name: traitor-title description: traitor-description + cooldown: 2 # Imp - Can't occur thrice showInVote: false rules: - Traitor @@ -185,6 +187,7 @@ name: nukeops-title description: nukeops-description showInVote: false + cooldown: 2 # Imp - Can't occur thrice rules: - Nukeops - SubGamemodesRule @@ -222,6 +225,7 @@ - zomber name: zombie-title description: zombie-description + cooldown: 2 # Imp - Can't occur thrice showInVote: false rules: - Zombie diff --git a/Resources/Textures/_Impstation/Interface/Emotes/attributions.yml b/Resources/Textures/_Impstation/Interface/Emotes/attributions.yml new file mode 100644 index 00000000000..dc86ad5ba96 --- /dev/null +++ b/Resources/Textures/_Impstation/Interface/Emotes/attributions.yml @@ -0,0 +1,6 @@ +- files: + - jump.png + - spin.png + license: "CC-BY-SA-4.0" + copyright: "Created by Carousel for Impstation" + source: "https://github.com/impstation/imp-station-14" diff --git a/Resources/Textures/_Impstation/Interface/Emotes/jump.png b/Resources/Textures/_Impstation/Interface/Emotes/jump.png new file mode 100644 index 00000000000..d0de87ef617 Binary files /dev/null and b/Resources/Textures/_Impstation/Interface/Emotes/jump.png differ diff --git a/Resources/Textures/_Impstation/Interface/Emotes/spin.png b/Resources/Textures/_Impstation/Interface/Emotes/spin.png new file mode 100644 index 00000000000..c5de318e63b Binary files /dev/null and b/Resources/Textures/_Impstation/Interface/Emotes/spin.png differ