From df823d224500e407a610b4b90d3768a257493978 Mon Sep 17 00:00:00 2001 From: SoulSloth <67545203+SoulSloth@users.noreply.github.com> Date: Mon, 24 Aug 2020 06:32:18 -0400 Subject: [PATCH] Add Flashlight Visualizer/states (#1861) * Add art assets for cloning * Added a 'Scan DNA' button to the medical scanner * Made the UI update unconditional for the medical scanner until checks for power changes are in place * Update Medical scanner to reflect powered status and fix #1774 * added a 'scan dna' button the the medical scanner that will add the contained bodies Uid to a list in CloningSystem, fixed an issue with the menu not populating if the scanner starts in an unpowered state * Add disabling logic to 'Scan DNA' button on medical scanner * Removed un-used libraries * Added playing parameter to radiatingLightComponent, changed it's animation key to radiatingLight * refactored RadiatingLight into a visualizer * Added different light animations for differnt power states of a flashlight * split out the radiating light visualizer into two seperate visualizers * further refactored and tweaked handheldlight animations * further lantern light tweaks * removed un-used attributes in flashlight and lantern prototypes * fix null check in handheldlightcomponent --- .../Components/FlashLightVisualizer.cs | 119 ++++++++++++++++++ .../Components/LanternVisualizer.cs | 56 +++++++++ .../Components/RadiatingLightComponent.cs | 45 ------- Content.Server/Atmos/GasSprayerComponent.cs | 1 - .../Interactable/HandheldLightComponent.cs | 34 +++-- .../Components/Power/BatteryComponent.cs | 9 +- .../SharedHandheldLightComponent.cs | 16 +++ .../Entities/Objects/Tools/flashlight.yml | 39 +++--- .../Entities/Objects/Tools/lantern.yml | 4 +- 9 files changed, 243 insertions(+), 80 deletions(-) create mode 100644 Content.Client/GameObjects/Components/FlashLightVisualizer.cs create mode 100644 Content.Client/GameObjects/Components/LanternVisualizer.cs delete mode 100644 Content.Client/GameObjects/Components/RadiatingLightComponent.cs diff --git a/Content.Client/GameObjects/Components/FlashLightVisualizer.cs b/Content.Client/GameObjects/Components/FlashLightVisualizer.cs new file mode 100644 index 0000000000..a8851f2e1e --- /dev/null +++ b/Content.Client/GameObjects/Components/FlashLightVisualizer.cs @@ -0,0 +1,119 @@ +using System; +using Content.Shared.GameObjects.Components; +using JetBrains.Annotations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Shared.Animations; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components +{ + [UsedImplicitly] + public class FlashLightVisualizer : AppearanceVisualizer + { + private readonly Animation _radiatingLightAnimation = new Animation + { + Length = TimeSpan.FromSeconds(1), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(PointLightComponent), + InterpolationMode = AnimationInterpolationMode.Linear, + Property = nameof(PointLightComponent.Radius), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(3.0f, 0), + new AnimationTrackProperty.KeyFrame(2.0f, 0.5f), + new AnimationTrackProperty.KeyFrame(3.0f, 1) + } + } + } + }; + + private readonly Animation _blinkingLightAnimation = new Animation + { + Length = TimeSpan.FromSeconds(1), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(PointLightComponent), + //To create the blinking effect we go from nearly zero radius, to the light radius, and back + //We do this instead of messing with the `PointLightComponent.enabled` because we don't want the animation to affect component behavior + InterpolationMode = AnimationInterpolationMode.Nearest, + Property = nameof(PointLightComponent.Radius), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(0.1f, 0), + new AnimationTrackProperty.KeyFrame(2f, 0.5f), + new AnimationTrackProperty.KeyFrame(0.1f, 1) + } + } + } + }; + + private Action _radiatingCallback; + private Action _blinkingCallback; + + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + if (component.Deleted) + { + return; + } + + if (component.TryGetData(HandheldLightVisuals.Power, + out HandheldLightPowerStates state)) + { + PlayAnimation(component, state); + } + } + + private void PlayAnimation(AppearanceComponent component, HandheldLightPowerStates state) + { + component.Owner.EnsureComponent(out AnimationPlayerComponent animationPlayer); + + switch (state) + { + case HandheldLightPowerStates.LowPower: + if (!animationPlayer.HasRunningAnimation("radiatingLight")) + { + animationPlayer.Play(_radiatingLightAnimation, "radiatingLight"); + _radiatingCallback = (s) => animationPlayer.Play(_radiatingLightAnimation, s); + animationPlayer.AnimationCompleted += _radiatingCallback; + } + + break; + case HandheldLightPowerStates.Dying: + animationPlayer.Stop("radiatingLight"); + animationPlayer.AnimationCompleted -= _radiatingCallback; + if (!animationPlayer.HasRunningAnimation("blinkingLight")) + { + animationPlayer.Play(_blinkingLightAnimation, "blinkingLight"); + _blinkingCallback = (s) => animationPlayer.Play(_blinkingLightAnimation, s); + animationPlayer.AnimationCompleted += _blinkingCallback; + } + + break; + case HandheldLightPowerStates.FullPower: + if (animationPlayer.HasRunningAnimation("blinkingLight")) + { + animationPlayer.Stop("blinkingLight"); + animationPlayer.AnimationCompleted -= _blinkingCallback; + } + + if (animationPlayer.HasRunningAnimation("radiatingLight")) + { + animationPlayer.Stop("radiatingLight"); + animationPlayer.AnimationCompleted -= _radiatingCallback; + } + + break; + } + } + } +} diff --git a/Content.Client/GameObjects/Components/LanternVisualizer.cs b/Content.Client/GameObjects/Components/LanternVisualizer.cs new file mode 100644 index 0000000000..4508150c3a --- /dev/null +++ b/Content.Client/GameObjects/Components/LanternVisualizer.cs @@ -0,0 +1,56 @@ +using System; +using Content.Shared.GameObjects.Components; +using JetBrains.Annotations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Shared.Animations; +using Robust.Shared.GameObjects; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Client.GameObjects.Components +{ + [UsedImplicitly] + public class LanternVisualizer : AppearanceVisualizer + { + private readonly Animation _radiatingLightAnimation = new Animation + { + Length = TimeSpan.FromSeconds(5), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(PointLightComponent), + InterpolationMode = AnimationInterpolationMode.Linear, + Property = nameof(PointLightComponent.Radius), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(3.0f, 0), + new AnimationTrackProperty.KeyFrame(2.0f, 1.5f), + new AnimationTrackProperty.KeyFrame(3.0f, 3f) + } + } + } + }; + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + if (component.Deleted) + { + return; + } + + PlayAnimation(component); + } + + private void PlayAnimation(AppearanceComponent component) + { + component.Owner.EnsureComponent(out AnimationPlayerComponent animationPlayer); + if (animationPlayer.HasRunningAnimation("radiatingLight")) return; + animationPlayer.Play(_radiatingLightAnimation, "radiatingLight"); + animationPlayer.AnimationCompleted += s => animationPlayer.Play(_radiatingLightAnimation, s); + } + } +} diff --git a/Content.Client/GameObjects/Components/RadiatingLightComponent.cs b/Content.Client/GameObjects/Components/RadiatingLightComponent.cs deleted file mode 100644 index 9cfa272136..0000000000 --- a/Content.Client/GameObjects/Components/RadiatingLightComponent.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Robust.Client.Animations; -using Robust.Client.GameObjects; -using Robust.Client.GameObjects.Components.Animations; -using Robust.Shared.Animations; -using Robust.Shared.GameObjects; - -namespace Content.Client.GameObjects.Components -{ - [RegisterComponent] - public class RadiatingLightComponent : Component - { - public override string Name => "RadiatingLight"; - - protected override void Startup() - { - base.Startup(); - - var animation = new Animation - { - Length = TimeSpan.FromSeconds(4), - AnimationTracks = - { - new AnimationTrackComponentProperty - { - ComponentType = typeof(PointLightComponent), - InterpolationMode = AnimationInterpolationMode.Linear, - Property = nameof(PointLightComponent.Radius), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(3.0f, 0), - new AnimationTrackProperty.KeyFrame(2.0f, 1), - new AnimationTrackProperty.KeyFrame(3.0f, 2) - } - } - } - }; - - var playerComponent = Owner.EnsureComponent(); - playerComponent.Play(animation, "emergency"); - - playerComponent.AnimationCompleted += s => playerComponent.Play(animation, s); - } - } -} diff --git a/Content.Server/Atmos/GasSprayerComponent.cs b/Content.Server/Atmos/GasSprayerComponent.cs index d414ce1714..b8460f91dd 100644 --- a/Content.Server/Atmos/GasSprayerComponent.cs +++ b/Content.Server/Atmos/GasSprayerComponent.cs @@ -2,7 +2,6 @@ using Content.Server.Interfaces; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components; -using Content.Shared.GameObjects.Components.Pointing; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index b6de8165ab..b2daa8e37c 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -28,7 +28,8 @@ namespace Content.Server.GameObjects.Components.Interactable /// Component that represents a handheld lightsource which can be toggled on and off. /// [RegisterComponent] - internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, IMapInit + internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, + IMapInit { [Dependency] private readonly ISharedNotifyManager _notifyManager = default!; @@ -41,9 +42,12 @@ namespace Content.Server.GameObjects.Components.Interactable get { if (_cellContainer.ContainedEntity == null) return null; + if (_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell)) + { + return cell; + } - _cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell); - return cell; + return null; } } @@ -134,7 +138,6 @@ namespace Content.Server.GameObjects.Components.Interactable Activated = false; EntitySystem.Get().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner); - } private void TurnOn(IEntity user) @@ -147,7 +150,6 @@ namespace Content.Server.GameObjects.Components.Interactable var cell = Cell; if (cell == null) { - EntitySystem.Get().PlayFromEntity("/Audio/Machines/button.ogg", Owner); _notifyManager.PopupMessage(Owner, user, Loc.GetString("Cell missing...")); @@ -168,7 +170,6 @@ namespace Content.Server.GameObjects.Components.Interactable SetState(true); EntitySystem.Get().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner); - } private void SetState(bool on) @@ -191,10 +192,24 @@ namespace Content.Server.GameObjects.Components.Interactable public void OnUpdate(float frameTime) { - if (!Activated) return; + if (!Activated || Cell == null) return; - var cell = Cell; - if (cell == null || !cell.TryUseCharge(Wattage * frameTime)) TurnOff(); + var appearanceComponent = Owner.GetComponent(); + + if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.70) + { + appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower); + } + else if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.90) + { + appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower); + } + else + { + appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.Dying); + } + + if (Cell == null || !Cell.TryUseCharge(Wattage * frameTime)) TurnOff(); Dirty(); } @@ -226,7 +241,6 @@ namespace Content.Server.GameObjects.Components.Interactable } EntitySystem.Get().PlayFromEntity("/Audio/Items/pistol_magout.ogg", Owner); - } public override ComponentState GetComponentState() diff --git a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs index de481e77ec..e6224bf4bf 100644 --- a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs +++ b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs @@ -11,16 +11,15 @@ namespace Content.Server.GameObjects.Components.Power { public override string Name => "Battery"; - [ViewVariables(VVAccess.ReadWrite)] - public int MaxCharge { get => _maxCharge; set => SetMaxCharge(value); } + [ViewVariables(VVAccess.ReadWrite)] public int MaxCharge { get => _maxCharge; set => SetMaxCharge(value); } private int _maxCharge; [ViewVariables(VVAccess.ReadWrite)] public float CurrentCharge { get => _currentCharge; set => SetCurrentCharge(value); } + private float _currentCharge; - [ViewVariables] - public BatteryState BatteryState { get; private set; } + [ViewVariables] public BatteryState BatteryState { get; private set; } public override void ExposeData(ObjectSerializer serializer) { @@ -93,7 +92,7 @@ namespace Content.Server.GameObjects.Components.Power private void SetMaxCharge(int newMax) { _maxCharge = Math.Max(newMax, 0); - _currentCharge = Math.Min( _currentCharge, MaxCharge); + _currentCharge = Math.Min(_currentCharge, MaxCharge); UpdateStorageState(); OnChargeChanged(); } diff --git a/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs index 760bd134cf..f6dd37fcef 100644 --- a/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs +++ b/Content.Shared/GameObjects/Components/SharedHandheldLightComponent.cs @@ -25,4 +25,20 @@ namespace Content.Shared.GameObjects.Components public bool HasCell { get; } } } + + [Serializable, NetSerializable] + public enum HandheldLightVisuals + { + Power + } + + [Serializable, NetSerializable] + public enum HandheldLightPowerStates + { + FullPower, + LowPower, + Dying, + } + + } diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml index a3c589549b..cd4c5f7768 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlight.yml @@ -4,21 +4,24 @@ id: FlashlightLantern description: They light the way to freedom components: - - type: HandheldLight - - type: Sprite - sprite: Objects/Tools/flashlight.rsi - layers: - - state: lantern_off - - state: HandheldLightOnOverlay - shader: unshaded - visible: false - - type: Icon - sprite: Objects/Tools/flashlight.rsi - state: lantern_off - - type: Item - sprite: Objects/Tools/flashlight.rsi - HeldPrefix: off - - type: PointLight - enabled: false - radius: 3 - - type: LoopingSound + - type: HandheldLight + - type: Sprite + sprite: Objects/Tools/flashlight.rsi + layers: + - state: lantern_off + - state: HandheldLightOnOverlay + shader: unshaded + visible: false + - type: Icon + sprite: Objects/Tools/flashlight.rsi + state: lantern_off + - type: Item + sprite: Objects/Tools/flashlight.rsi + HeldPrefix: off + - type: PointLight + enabled: false + radius: 3 + - type: LoopingSound + - type: Appearance + visuals: + - type: FlashLightVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml index e1346cac53..d974dce9aa 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml @@ -23,5 +23,7 @@ radius: 3 energy: 2.5 color: "#FFC458" - - type: RadiatingLight - type: LoopingSound + - type: Appearance + visuals: + - type: LanternVisualizer