From 99851de78577aa8c12591080fe5550ac6f98ae0b Mon Sep 17 00:00:00 2001 From: haiwwkes <49613070+rhailrake@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:42:02 +0500 Subject: [PATCH] ayo (#545) --- .../Overlays/SaturationScaleOverlay.cs | 52 +++ .../Overlays/Systems/SaturationScaleSystem.cs | 62 +++ Content.Server/Arcade/BlockGame/BlockGame.cs | 4 + .../SpaceVillainArcadeSystem.cs | 3 + .../Atmos/EntitySystems/BarotraumaSystem.cs | 3 + .../Atmos/EntitySystems/FlammableSystem.cs | 3 + Content.Server/Bible/BibleSystem.cs | 3 + .../Body/Systems/RespiratorSystem.cs | 2 + Content.Server/Medical/VomitSystem.cs | 3 + Content.Server/_Sunrise/Mood/MoodComponent.cs | 112 +++++ Content.Server/_Sunrise/Mood/MoodSystem.cs | 416 ++++++++++++++++++ Content.Shared/CCVar/CCVars.cs | 13 + Content.Shared/Cuffs/SharedCuffableSystem.cs | 7 + .../Interaction/InteractionPopupSystem.cs | 15 + .../Nutrition/EntitySystems/HungerSystem.cs | 23 +- .../EntitySystems/SharedCreamPieSystem.cs | 6 + .../Nutrition/EntitySystems/ThirstSystem.cs | 15 +- Content.Shared/Slippery/SlipperySystem.cs | 7 +- .../_Sunrise/Mood/MoodCategoryPrototype.cs | 10 + .../_Sunrise/Mood/MoodEffectPrototype.cs | 24 + Content.Shared/_Sunrise/Mood/MoodEvents.cs | 25 ++ .../_Sunrise/Mood/SaturationScaleComponent.cs | 6 + .../_Sunrise/Mood/SharedMoodComponent.cs | 15 + .../ru-RU/_strings/_sunrise/mood/mood.ftl | 62 +++ Resources/Prototypes/Alerts/alerts.yml | 1 + Resources/Prototypes/Alerts/mood.yml | 112 +++++ .../Prototypes/Entities/Mobs/Species/base.yml | 1 + Resources/Prototypes/Mood/categories.yml | 8 + Resources/Prototypes/Mood/genericNeeds.yml | 63 +++ .../Mood/genericNegativeEffects.yml | 41 ++ .../Mood/genericPositiveEffects.yml | 24 + Resources/Prototypes/Shaders/scale.yml | 4 + .../Interface/Alerts/mood.rsi/meta.json | 60 +++ .../Interface/Alerts/mood.rsi/mood1.png | Bin 0 -> 638 bytes .../Interface/Alerts/mood.rsi/mood2.png | Bin 0 -> 513 bytes .../Interface/Alerts/mood.rsi/mood3.png | Bin 0 -> 499 bytes .../Interface/Alerts/mood.rsi/mood4.png | Bin 0 -> 461 bytes .../Interface/Alerts/mood.rsi/mood5.png | Bin 0 -> 441 bytes .../Interface/Alerts/mood.rsi/mood6.png | Bin 0 -> 479 bytes .../Interface/Alerts/mood.rsi/mood7.png | Bin 0 -> 514 bytes .../Interface/Alerts/mood.rsi/mood8.png | Bin 0 -> 485 bytes .../Interface/Alerts/mood.rsi/mood9.png | Bin 0 -> 485 bytes .../Alerts/mood.rsi/mood_happiness_bad.png | Bin 0 -> 544 bytes .../Alerts/mood.rsi/mood_happiness_good.png | Bin 0 -> 438 bytes .../Interface/Alerts/mood.rsi/mood_insane.png | Bin 0 -> 3528 bytes .../Textures/Shaders/saturationscale.swsl | 12 + 46 files changed, 1207 insertions(+), 10 deletions(-) create mode 100644 Content.Client/_Sunrise/Overlays/SaturationScaleOverlay.cs create mode 100644 Content.Client/_Sunrise/Overlays/Systems/SaturationScaleSystem.cs create mode 100644 Content.Server/_Sunrise/Mood/MoodComponent.cs create mode 100644 Content.Server/_Sunrise/Mood/MoodSystem.cs create mode 100644 Content.Shared/_Sunrise/Mood/MoodCategoryPrototype.cs create mode 100644 Content.Shared/_Sunrise/Mood/MoodEffectPrototype.cs create mode 100644 Content.Shared/_Sunrise/Mood/MoodEvents.cs create mode 100644 Content.Shared/_Sunrise/Mood/SaturationScaleComponent.cs create mode 100644 Content.Shared/_Sunrise/Mood/SharedMoodComponent.cs create mode 100644 Resources/Locale/ru-RU/_strings/_sunrise/mood/mood.ftl create mode 100644 Resources/Prototypes/Alerts/mood.yml create mode 100644 Resources/Prototypes/Mood/categories.yml create mode 100644 Resources/Prototypes/Mood/genericNeeds.yml create mode 100644 Resources/Prototypes/Mood/genericNegativeEffects.yml create mode 100644 Resources/Prototypes/Mood/genericPositiveEffects.yml create mode 100644 Resources/Prototypes/Shaders/scale.yml create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/meta.json create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood1.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood2.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood3.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood4.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood5.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood6.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood7.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood8.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood9.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png create mode 100644 Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png create mode 100644 Resources/Textures/Shaders/saturationscale.swsl diff --git a/Content.Client/_Sunrise/Overlays/SaturationScaleOverlay.cs b/Content.Client/_Sunrise/Overlays/SaturationScaleOverlay.cs new file mode 100644 index 00000000000..165a3927a16 --- /dev/null +++ b/Content.Client/_Sunrise/Overlays/SaturationScaleOverlay.cs @@ -0,0 +1,52 @@ +using System.Numerics; +using Content.Shared._Sunrise.Mood; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client._Sunrise.Overlays; + +public sealed class SaturationScaleOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] IEntityManager _entityManager = default!; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _shader; + private const float Saturation = 0.5f; + + + public SaturationScaleOverlay() + { + IoCManager.InjectDependencies(this); + + _shader = _prototypeManager.Index("SaturationScale").Instance().Duplicate(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_playerManager.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("saturation", Saturation); + + var handle = args.WorldHandle; + handle.SetTransform(Matrix3x2.Identity); + handle.UseShader(_shader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/_Sunrise/Overlays/Systems/SaturationScaleSystem.cs b/Content.Client/_Sunrise/Overlays/Systems/SaturationScaleSystem.cs new file mode 100644 index 00000000000..c51995cc1e0 --- /dev/null +++ b/Content.Client/_Sunrise/Overlays/Systems/SaturationScaleSystem.cs @@ -0,0 +1,62 @@ +using Content.Shared._Sunrise.Mood; +using Content.Shared.GameTicking; +using Robust.Client.Graphics; +using Robust.Shared.Player; + +namespace Content.Client._Sunrise.Overlays.Systems; + +public sealed class SaturationScaleSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly ISharedPlayerManager _playerMan = default!; + + private SaturationScaleOverlay _overlay = default!; + + + public override void Initialize() + { + base.Initialize(); + + _overlay = new(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeNetworkEvent(RoundRestartCleanup); + } + + + private void RoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, SaturationScaleOverlayComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, SaturationScaleOverlayComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnShutdown(EntityUid uid, SaturationScaleOverlayComponent component, ComponentShutdown args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnInit(EntityUid uid, SaturationScaleOverlayComponent component, ComponentInit args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.AddOverlay(_overlay); + } +} diff --git a/Content.Server/Arcade/BlockGame/BlockGame.cs b/Content.Server/Arcade/BlockGame/BlockGame.cs index 82063b6443f..c8ff7cdeb9c 100644 --- a/Content.Server/Arcade/BlockGame/BlockGame.cs +++ b/Content.Server/Arcade/BlockGame/BlockGame.cs @@ -2,6 +2,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Random; using System.Linq; +using Content.Shared._Sunrise.Mood; namespace Content.Server.Arcade.BlockGame; @@ -82,6 +83,9 @@ private void InvokeGameover() { _highScorePlacement = _arcadeSystem.RegisterHighScore(meta.EntityName, Points); SendHighscoreUpdate(); + + var ev = new MoodEffectEvent("ArcadePlay"); // Sunrise Edit + _entityManager.EventBus.RaiseLocalEvent(meta.Owner, ev); // Sunrise Edit } SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement)); } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs index 88c71ea18ef..2d567585713 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.UserInterface; using Content.Server.Advertise; using Content.Server.Advertise.Components; +using Content.Shared._Sunrise.Mood; using Content.Shared.Power; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; @@ -77,6 +78,8 @@ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent compone if (!TryComp(uid, out var power) || !power.Powered) return; + RaiseLocalEvent(msg.Actor, new MoodEffectEvent("ArcadePlay")); + switch (msg.PlayerAction) { case PlayerAction.Attack: diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index ec508790ba8..08079611e4d 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Administration.Logs; using Content.Server.Atmos.Components; +using Content.Shared._Sunrise.Mood; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Damage; @@ -245,6 +246,7 @@ public override void Update(float frameTime) _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage"); } + RaiseLocalEvent(uid, new MoodEffectEvent("MobLowPressure")); // Sunrise Edit _alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 2); } else if (pressure >= Atmospherics.HazardHighPressure) @@ -260,6 +262,7 @@ public override void Update(float frameTime) _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking high pressure damage"); } + RaiseLocalEvent(uid, new MoodEffectEvent("MobHighPressure")); // Sunrise Edit _alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 2); } else diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index bc96807af2d..6fb48c82e5f 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Server.Damage.Components; +using Content.Shared._Sunrise.Mood; using Content.Shared.ActionBlocker; using Content.Shared.Alert; using Content.Shared.Atmos; @@ -425,10 +426,12 @@ public override void Update(float frameTime) if (!flammable.OnFire) { + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("OnFire")); // Sunrise Edit _alertsSystem.ClearAlert(uid, flammable.FireAlert); continue; } + RaiseLocalEvent(uid, new MoodEffectEvent("OnFire")); // Sunrise Edit _alertsSystem.ShowAlert(uid, flammable.FireAlert); if (flammable.FireStacks > 0) diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index 76efe3290bf..d6a109321ab 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Bible.Components; using Content.Server.Ghost.Roles.Events; using Content.Server.Popups; +using Content.Shared._Sunrise.Mood; using Content.Shared.ActionBlocker; using Content.Shared.Actions; using Content.Shared.Bible; @@ -153,6 +154,8 @@ private void OnAfterInteract(EntityUid uid, BibleComponent component, AfterInter _audio.PlayPvs(component.HealSoundPath, args.User); _delay.TryResetDelay((uid, useDelay)); } + + RaiseLocalEvent(args.Target.Value, new MoodEffectEvent("GotBlessed")); // Sunrise Edit } private void AddSummonVerb(EntityUid uid, SummonableComponent component, GetVerbsEvent args) diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 78549245154..a77cf42bd8a 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Chat.Systems; using Content.Server.EntityEffects.EffectConditions; using Content.Server.EntityEffects.Effects; +using Content.Shared._Sunrise.Mood; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Alert; using Content.Shared.Atmos; @@ -315,6 +316,7 @@ private void TakeSuffocationDamage(Entity ent) { _alertsSystem.ShowAlert(ent, entity.Comp1.Alert); } + RaiseLocalEvent(ent, new MoodEffectEvent("Suffocating")); } _damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false); diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs index 5cff161e0eb..743139b9358 100644 --- a/Content.Server/Medical/VomitSystem.cs +++ b/Content.Server/Medical/VomitSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Forensics; using Content.Server.Popups; using Content.Server.Stunnable; +using Content.Shared._Sunrise.Mood; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; @@ -96,6 +97,8 @@ public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = - // Force sound to play as spill doesn't work if solution is empty. _audio.PlayPvs("/Audio/Effects/Fluids/splat.ogg", uid, AudioParams.Default.WithVariation(0.2f).WithVolume(-4f)); _popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid); + + RaiseLocalEvent(uid, new MoodEffectEvent("MobVomit")); } } } diff --git a/Content.Server/_Sunrise/Mood/MoodComponent.cs b/Content.Server/_Sunrise/Mood/MoodComponent.cs new file mode 100644 index 00000000000..9d2011d0524 --- /dev/null +++ b/Content.Server/_Sunrise/Mood/MoodComponent.cs @@ -0,0 +1,112 @@ +using Content.Shared.Alert; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; + +namespace Content.Server._Sunrise.Mood; + +[RegisterComponent] +public sealed partial class MoodComponent : Component +{ + [DataField] + public float CurrentMoodLevel; + + [DataField] + public MoodThreshold CurrentMoodThreshold; + + [DataField] + public MoodThreshold LastThreshold; + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary CategorisedEffects = new(); + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary UncategorisedEffects = new(); + + /// + /// The formula for the movement speed modifier is SpeedBonusGrowth ^ (MoodLevel - MoodThreshold.Neutral). + /// Change this ONLY BY 0.001 AT A TIME. + /// + [DataField] + public float SpeedBonusGrowth = 1.003f; + + /// + /// The lowest point that low morale can multiply our movement speed by. Lowering speed follows a linear curve, rather than geometric. + /// + [DataField] + public float MinimumSpeedModifier = 0.75f; + + /// + /// The maximum amount that high morale can multiply our movement speed by. This follows a significantly slower geometric sequence. + /// + [DataField] + public float MaximumSpeedModifier = 1.15f; + + [DataField] + public float IncreaseCritThreshold = 1.2f; + + [DataField] + public float DecreaseCritThreshold = 0.9f; + + [ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 CritThresholdBeforeModify; + + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodThresholds = new() + { + { MoodThreshold.Perfect, 100f }, + { MoodThreshold.Exceptional, 80f }, + { MoodThreshold.Great, 70f }, + { MoodThreshold.Good, 60f }, + { MoodThreshold.Neutral, 50f }, + { MoodThreshold.Meh, 40f }, + { MoodThreshold.Bad, 30f }, + { MoodThreshold.Terrible, 20f }, + { MoodThreshold.Horrible, 10f }, + { MoodThreshold.Dead, 0f } + }; + + [DataField(customTypeSerializer: typeof(DictionarySerializer>))] + public Dictionary> MoodThresholdsAlerts = new() + { + { MoodThreshold.Dead, "MoodDead" }, + { MoodThreshold.Horrible, "Horrible" }, + { MoodThreshold.Terrible, "Terrible" }, + { MoodThreshold.Bad, "Bad" }, + { MoodThreshold.Meh, "Meh" }, + { MoodThreshold.Neutral, "Neutral" }, + { MoodThreshold.Good, "Good" }, + { MoodThreshold.Great, "Great" }, + { MoodThreshold.Exceptional, "Exceptional" }, + { MoodThreshold.Perfect, "Perfect" }, + { MoodThreshold.Insane, "Insane" } + }; + + /// + /// These thresholds represent a percentage of Crit-Threshold, 0.8 corresponding with 80%. + /// + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary HealthMoodEffectsThresholds = new() + { + { "HealthHeavyDamage", 0.8f }, + { "HealthSevereDamage", 0.5f }, + { "HealthLightDamage", 0.1f }, + { "HealthNoDamage", 0.05f } + }; +} + +[Serializable] +public enum MoodThreshold : ushort +{ + Insane = 1, + Horrible = 2, + Terrible = 3, + Bad = 4, + Meh = 5, + Neutral = 6, + Good = 7, + Great = 8, + Exceptional = 9, + Perfect = 10, + Dead = 0 +} diff --git a/Content.Server/_Sunrise/Mood/MoodSystem.cs b/Content.Server/_Sunrise/Mood/MoodSystem.cs new file mode 100644 index 00000000000..af7e03476a7 --- /dev/null +++ b/Content.Server/_Sunrise/Mood/MoodSystem.cs @@ -0,0 +1,416 @@ +using Content.Server.Chat.Managers; +using Content.Shared._Sunrise.Mood; +using Content.Shared.Alert; +using Content.Shared.CCVar; +using Content.Shared.Chat; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Server._Sunrise.Mood; + +public sealed class MoodSystem : EntitySystem +{ + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnMoodEffect); + SubscribeLocalEvent(OnDamageChange); + SubscribeLocalEvent(OnRefreshMoveSpeed); + SubscribeLocalEvent(OnRemoveEffect); + SubscribeLocalEvent(OnAlertShowMoodEffectsAlert); + } + + private void OnAlertShowMoodEffectsAlert(Entity ent, ref ShowMoodEffectsAlertEvent args) + { + if (args.Handled) + return; + + AlertClicked(ent); + + args.Handled = true; + } + + private void OnRemoveEffect(EntityUid uid, MoodComponent component, MoodRemoveEffectEvent args) + { + if (component.UncategorisedEffects.TryGetValue(args.EffectId, out _)) + { + RemoveTimedOutEffect(uid, args.EffectId); + } + else + { + foreach (var (category, id) in component.CategorisedEffects) + { + if (id != args.EffectId) + continue; + + RemoveTimedOutEffect(uid, args.EffectId, category); + return; + } + } + } + + private void OnRefreshMoveSpeed(EntityUid uid, MoodComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (component.CurrentMoodThreshold is > MoodThreshold.Meh and < MoodThreshold.Good or MoodThreshold.Dead + || _jetpack.IsUserFlying(uid)) + return; + + // This ridiculous math serves a purpose making high mood less impactful on movement speed than low mood + var modifier = + Math.Clamp( + (component.CurrentMoodLevel >= component.MoodThresholds[MoodThreshold.Neutral]) + ? _config.GetCVar(CCVars.MoodIncreasesSpeed) + ? MathF.Pow(1.003f, component.CurrentMoodLevel - component.MoodThresholds[MoodThreshold.Neutral]) + : 1 + : _config.GetCVar(CCVars.MoodDecreasesSpeed) + ? 2 - component.MoodThresholds[MoodThreshold.Neutral] / component.CurrentMoodLevel + : 1, + component.MinimumSpeedModifier, + component.MaximumSpeedModifier); + + args.ModifySpeed(1, modifier); + } + + private void OnMoodEffect(EntityUid uid, MoodComponent component, MoodEffectEvent args) + { + if (!_config.GetCVar(CCVars.MoodEnabled) + || !_prototypeManager.TryIndex(args.EffectId, out var prototype)) + return; + + var ev = new OnMoodEffect(uid, args.EffectId, args.EffectModifier, args.EffectOffset); + RaiseLocalEvent(uid, ref ev); + + ApplyEffect(uid, component, prototype, ev.EffectModifier, ev.EffectOffset); + } + + private void ApplyEffect(EntityUid uid, MoodComponent component, MoodEffectPrototype prototype, float eventModifier = 1, float eventOffset = 0) + { + // Apply categorised effect + if (prototype.Category != null) + { + if (component.CategorisedEffects.TryGetValue(prototype.Category, out var oldPrototypeId)) + { + if (!_prototypeManager.TryIndex(oldPrototypeId, out var oldPrototype)) + return; + + if (prototype.ID != oldPrototype.ID) + { + SendEffectText(uid, prototype); + component.CategorisedEffects[prototype.Category] = prototype.ID; + } + } + else + { + component.CategorisedEffects.Add(prototype.Category, prototype.ID); + } + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromSeconds(prototype.Timeout), () => RemoveTimedOutEffect(uid, prototype.ID, prototype.Category)); + } + else + { + if (component.UncategorisedEffects.TryGetValue(prototype.ID, out _)) + return; + + var moodChange = prototype.MoodChange * eventModifier + eventOffset; + if (moodChange == 0) + return; + + SendEffectText(uid, prototype); + component.UncategorisedEffects.Add(prototype.ID, moodChange); + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromSeconds(prototype.Timeout), () => RemoveTimedOutEffect(uid, prototype.ID)); + } + + RefreshMood(uid, component); + } + + private void SendEffectText(EntityUid uid, MoodEffectPrototype prototype) + { + + } + + private void RemoveTimedOutEffect(EntityUid uid, string prototypeId, string? category = null) + { + if (!TryComp(uid, out var comp)) + return; + + if (category == null) + { + if (!comp.UncategorisedEffects.ContainsKey(prototypeId)) + return; + comp.UncategorisedEffects.Remove(prototypeId); + } + else + { + if (!comp.CategorisedEffects.TryGetValue(category, out var currentProtoId) + || currentProtoId != prototypeId + || !_prototypeManager.HasIndex(currentProtoId)) + return; + comp.CategorisedEffects.Remove(category); + } + + RefreshMood(uid, comp); + } + + private void OnMobStateChanged(EntityUid uid, MoodComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead && args.OldMobState != MobState.Dead) + { + var ev = new MoodEffectEvent("Dead"); + RaiseLocalEvent(uid, ev); + } + else if (args.OldMobState == MobState.Dead && args.NewMobState != MobState.Dead) + { + var ev = new MoodRemoveEffectEvent("Dead"); + RaiseLocalEvent(uid, ev); + } + RefreshMood(uid, component); + } + + private void RefreshMood(EntityUid uid, MoodComponent component) + { + var amount = 0f; + + foreach (var (_, protoId) in component.CategorisedEffects) + { + if (!_prototypeManager.TryIndex(protoId, out var prototype)) + continue; + + amount += prototype.MoodChange; + } + + foreach (var (_, value) in component.UncategorisedEffects) + { + amount += value; + } + + SetMood(uid, amount, component, refresh: true); + } + + private void OnInit(EntityUid uid, MoodComponent component, ComponentStartup args) + { + if (TryComp(uid, out var mobThresholdsComponent) + && _mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, mobThresholdsComponent)) + component.CritThresholdBeforeModify = critThreshold.Value; + + EnsureComp(uid); + RefreshMood(uid, component); + } + + private void SetMood(EntityUid uid, float amount, MoodComponent? component = null, bool force = false, bool refresh = false) + { + if (!_config.GetCVar(CCVars.MoodEnabled) + || !Resolve(uid, ref component) + || component.CurrentMoodThreshold == MoodThreshold.Dead && !refresh) + return; + + var neutral = component.MoodThresholds[MoodThreshold.Neutral]; + var ev = new OnSetMoodEvent(uid, amount, false); + RaiseLocalEvent(uid, ref ev); + + if (ev.Cancelled) + return; + else + { + uid = ev.Receiver; + amount = ev.MoodChangedAmount; + } + + var newMoodLevel = amount + neutral; + if (!force) + { + newMoodLevel = Math.Clamp(amount + neutral, + component.MoodThresholds[MoodThreshold.Dead], + component.MoodThresholds[MoodThreshold.Perfect]); + } + + component.CurrentMoodLevel = newMoodLevel; + + if (TryComp(uid, out var mood)) + { + mood.CurrentMoodLevel = component.CurrentMoodLevel; + mood.NeutralMoodThreshold = component.MoodThresholds.GetValueOrDefault(MoodThreshold.Neutral); + } + + UpdateCurrentThreshold(uid, component); + } + + private void UpdateCurrentThreshold(EntityUid uid, MoodComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var calculatedThreshold = GetMoodThreshold(component); + if (calculatedThreshold == component.CurrentMoodThreshold) + return; + + component.CurrentMoodThreshold = calculatedThreshold; + + DoMoodThresholdsEffects(uid, component); + } + + private void DoMoodThresholdsEffects(EntityUid uid, MoodComponent? component = null, bool force = false) + { + if (!Resolve(uid, ref component) + || component.CurrentMoodThreshold == component.LastThreshold && !force) + return; + + var modifier = GetMovementThreshold(component.CurrentMoodThreshold); + + // Modify mob stats + if (modifier != GetMovementThreshold(component.LastThreshold)) + { + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + SetCritThreshold(uid, component, modifier); + RefreshShaders(uid, modifier); + } + + // Modify interface + if (component.MoodThresholdsAlerts.TryGetValue(component.CurrentMoodThreshold, out var alertId)) + _alerts.ShowAlert(uid, alertId); + else + _alerts.ClearAlertCategory(uid, "Mood"); + + component.LastThreshold = component.CurrentMoodThreshold; + } + + private void RefreshShaders(EntityUid uid, int modifier) + { + if (modifier == -1) + EnsureComp(uid); + else + RemComp(uid); + } + + private void SetCritThreshold(EntityUid uid, MoodComponent component, int modifier) + { + if (!TryComp(uid, out var mobThresholds) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var key)) + return; + + var newKey = modifier switch + { + 1 => FixedPoint2.New(key.Value.Float() * component.IncreaseCritThreshold), + -1 => FixedPoint2.New(key.Value.Float() * component.DecreaseCritThreshold), + _ => component.CritThresholdBeforeModify + }; + + component.CritThresholdBeforeModify = key.Value; + _mobThreshold.SetMobStateThreshold(uid, newKey, MobState.Critical, mobThresholds); + } + + private MoodThreshold GetMoodThreshold(MoodComponent component, float? moodLevel = null) + { + moodLevel ??= component.CurrentMoodLevel; + var result = MoodThreshold.Dead; + var value = component.MoodThresholds[MoodThreshold.Perfect]; + + foreach (var threshold in component.MoodThresholds) + { + if (threshold.Value <= value && threshold.Value >= moodLevel) + { + result = threshold.Key; + value = threshold.Value; + } + } + + return result; + } + + private int GetMovementThreshold(MoodThreshold threshold) + { + return threshold switch + { + >= MoodThreshold.Good => 1, + <= MoodThreshold.Meh => -1, + _ => 0 + }; + } + + private void OnDamageChange(EntityUid uid, MoodComponent component, DamageChangedEvent args) + { + if (!_mobThreshold.TryGetPercentageForState(uid, MobState.Critical, args.Damageable.TotalDamage, out var damage)) + return; + + var protoId = "HealthNoDamage"; + var value = component.HealthMoodEffectsThresholds["HealthNoDamage"]; + + foreach (var threshold in component.HealthMoodEffectsThresholds) + { + if (!(threshold.Value <= damage) || !(threshold.Value >= value)) + continue; + + protoId = threshold.Key; + value = threshold.Value; + } + + var ev = new MoodEffectEvent(protoId); + RaiseLocalEvent(uid, ev); + } + + public void AlertClicked(EntityUid uid) + { + if (!TryComp(uid, out var comp) + || comp.CurrentMoodThreshold == MoodThreshold.Dead + || !_playerManager.TryGetSessionByEntity(uid, out var session)) + return; + + var msgStart = Loc.GetString("mood-show-effects-start"); + _chatManager.ChatMessageToOne(ChatChannel.Emotes, + msgStart, + msgStart, + EntityUid.Invalid, + false, + session.Channel); + + foreach (var (_, protoId) in comp.CategorisedEffects) + { + if (!_prototypeManager.TryIndex(protoId, out var proto) + || proto.Hidden) + continue; + + SendDescToChat(proto, session); + } + + foreach (var (protoId, _) in comp.UncategorisedEffects) + { + if (!_prototypeManager.TryIndex(protoId, out var proto) + || proto.Hidden) + continue; + + SendDescToChat(proto, session); + } + } + + private void SendDescToChat(MoodEffectPrototype proto, ICommonSession session) + { + var color = (proto.MoodChange > 0) ? "#008000" : "#BA0000"; + var msg = $"[font size=10][color={color}]{proto.Description}[/color][/font]"; + + _chatManager.ChatMessageToOne(ChatChannel.Emotes, msg, msg, EntityUid.Invalid, false, + session.Channel); + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 441ff649a98..6918e4cfcba 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -2348,5 +2348,18 @@ public static readonly CVarDef CVarDef.Create("rest.hold_look_up", true, CVar.CLIENT | CVar.ARCHIVE); #endregion + + #region Mood System + + public static readonly CVarDef MoodEnabled = + CVarDef.Create("mood.enabled", true, CVar.SERVER); + + public static readonly CVarDef MoodIncreasesSpeed = + CVarDef.Create("mood.increases_speed", true, CVar.SERVER); + + public static readonly CVarDef MoodDecreasesSpeed = + CVarDef.Create("mood.decreases_speed", true, CVar.SERVER); + + #endregion } } diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 6f8bd42e36f..8f5e77e96d8 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared._Sunrise.Mood; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Components; using Content.Shared.Administration.Logs; @@ -180,9 +181,15 @@ public void UpdateCuffState(EntityUid uid, CuffableComponent component) _actionBlocker.UpdateCanMove(uid); if (component.CanStillInteract) + { _alerts.ClearAlert(uid, component.CuffedAlert); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("Handcuffed")); + } else + { _alerts.ShowAlert(uid, component.CuffedAlert); + RaiseLocalEvent(uid, new MoodEffectEvent("Handcuffed")); + } var ev = new CuffedStateChangeEvent(); RaiseLocalEvent(uid, ref ev); diff --git a/Content.Shared/Interaction/InteractionPopupSystem.cs b/Content.Shared/Interaction/InteractionPopupSystem.cs index 20c079dfd8c..62eb2149b97 100644 --- a/Content.Shared/Interaction/InteractionPopupSystem.cs +++ b/Content.Shared/Interaction/InteractionPopupSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared._Sunrise.Mood; using Content.Shared.Bed.Sleep; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Components; @@ -93,8 +94,22 @@ private void SharedInteract( if (_random.Prob(component.SuccessChance)) { + // Sunrise Edit if (component.InteractSuccessString != null) + { msg = Loc.GetString(component.InteractSuccessString, ("target", Identity.Entity(uid, EntityManager))); // Success message (localized). + if (component.InteractSuccessString == "hugging-success-generic") + { + var moodEffectEvent = new MoodEffectEvent("BeingHugged"); + RaiseLocalEvent(target, moodEffectEvent); + } + else if (component.InteractSuccessString.Contains("petting-success-")) + { + var moodEffectEvent = new MoodEffectEvent("PetAnimal"); + RaiseLocalEvent(user, moodEffectEvent); + } + } + // Sunrise Edit if (component.InteractSuccessSound != null) sfx = component.InteractSuccessSound; diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index 6196669c190..f5bbfb57802 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -1,11 +1,15 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared._Sunrise.Mood; using Content.Shared.Alert; +using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Rejuvenate; using Content.Shared.StatusIcon; +using Robust.Shared.Configuration; +using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -23,6 +27,8 @@ public sealed class HungerSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IConfigurationManager _config = default!; [ValidatePrototypeId] private const string HungerIconOverfedId = "HungerIconOverfed"; @@ -66,10 +72,9 @@ private void OnShutdown(EntityUid uid, HungerComponent component, ComponentShutd private void OnRefreshMovespeed(EntityUid uid, HungerComponent component, RefreshMovementSpeedModifiersEvent args) { - if (component.CurrentThreshold > HungerThreshold.Starving) - return; - - if (_jetpack.IsUserFlying(uid)) + if (_config.GetCVar(CCVars.MoodEnabled) // Sunrise Edit + || component.CurrentThreshold > HungerThreshold.Starving + || _jetpack.IsUserFlying(uid)) return; args.ModifySpeed(component.StarvingSlowdownModifier, component.StarvingSlowdownModifier); @@ -133,7 +138,15 @@ private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component if (GetMovementThreshold(component.CurrentThreshold) != GetMovementThreshold(component.LastThreshold)) { - _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + // Sunrise Edit + if (!_config.GetCVar(CCVars.MoodEnabled)) + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + else if (_net.IsServer) + { + var ev = new MoodEffectEvent("Hunger" + component.CurrentThreshold); + RaiseLocalEvent(uid, ev); + } + // Sunrise Edit } if (component.HungerThresholdAlerts.TryGetValue(component.CurrentThreshold, out var alertId)) diff --git a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs index bd7251b9438..5543f6a3ade 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared._Sunrise.Mood; using Content.Shared.Nutrition.Components; using Content.Shared.Stunnable; using Content.Shared.Throwing; @@ -44,6 +45,11 @@ public void SetCreamPied(EntityUid uid, CreamPiedComponent creamPied, bool value { _appearance.SetData(uid, CreamPiedVisuals.Creamed, value, appearance); } + + if (value) + RaiseLocalEvent(uid, new MoodEffectEvent("Creampied")); + else + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("Creampied")); } private void OnCreamPieLand(EntityUid uid, CreamPieComponent component, ref LandEvent args) diff --git a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs index aa704354ba9..eb655ed6385 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -1,10 +1,13 @@ +using Content.Shared._Sunrise.Mood; using Content.Shared.Alert; +using Content.Shared.CCVar; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Rejuvenate; using Content.Shared.StatusIcon; using JetBrains.Annotations; +using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -21,6 +24,7 @@ public sealed class ThirstSystem : EntitySystem [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly IConfigurationManager _config = default!; [ValidatePrototypeId] private const string ThirstIconOverhydratedId = "ThirstIconOverhydrated"; @@ -70,7 +74,8 @@ private void OnMapInit(EntityUid uid, ThirstComponent component, MapInitEvent ar private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args) { // TODO: This should really be taken care of somewhere else - if (_jetpack.IsUserFlying(uid)) + if (_config.GetCVar(CCVars.MoodEnabled) + || _jetpack.IsUserFlying(uid)) return; var mod = component.CurrentThirstThreshold <= ThirstThreshold.Parched ? 0.75f : 1.0f; @@ -152,8 +157,9 @@ public bool TryGetStatusIconPrototype(ThirstComponent component, out SatiationIc private void UpdateEffects(EntityUid uid, ThirstComponent component) { - if (IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) && - TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent)) + if (!_config.GetCVar(CCVars.MoodEnabled) + && IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) + && TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent)) { _movement.RefreshMovementSpeedModifiers(uid, movementSlowdownComponent); } @@ -168,6 +174,9 @@ private void UpdateEffects(EntityUid uid, ThirstComponent component) _alerts.ClearAlertCategory(uid, component.ThirstyCategory); } + var ev = new MoodEffectEvent("Thirst" + component.CurrentThirstThreshold); + RaiseLocalEvent(uid, ev); + switch (component.CurrentThirstThreshold) { case ThirstThreshold.OverHydrated: diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 50137565b1c..4a0cf0ab6e6 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -1,3 +1,4 @@ +using Content.Shared._Sunrise.Mood; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Inventory; @@ -19,7 +20,7 @@ namespace Content.Shared.Slippery; -[UsedImplicitly] +[UsedImplicitly] public sealed class SlipperySystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; @@ -83,7 +84,7 @@ private void OnEntityExit(EntityUid uid, SlipperyComponent component, ref EndCol { if (HasComp(args.OtherEntity)) _speedModifier.AddModifiedEntity(args.OtherEntity); - } + } private bool CanSlip(EntityUid uid, EntityUid toSlip) { @@ -128,6 +129,8 @@ public void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other, _stun.TryParalyze(other, TimeSpan.FromSeconds(component.ParalyzeTime), true); + RaiseLocalEvent(other, new MoodEffectEvent("MobSlipped")); + // Sunrise-Start var evSlipped = new SlippedEvent(other); RaiseLocalEvent(other, ref evSlipped); diff --git a/Content.Shared/_Sunrise/Mood/MoodCategoryPrototype.cs b/Content.Shared/_Sunrise/Mood/MoodCategoryPrototype.cs new file mode 100644 index 00000000000..f6d67dc7703 --- /dev/null +++ b/Content.Shared/_Sunrise/Mood/MoodCategoryPrototype.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._Sunrise.Mood; + +[Prototype] +public sealed class MoodCategoryPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; +} diff --git a/Content.Shared/_Sunrise/Mood/MoodEffectPrototype.cs b/Content.Shared/_Sunrise/Mood/MoodEffectPrototype.cs new file mode 100644 index 00000000000..510323ae854 --- /dev/null +++ b/Content.Shared/_Sunrise/Mood/MoodEffectPrototype.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._Sunrise.Mood; + +[Prototype] +public sealed class MoodEffectPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + public string Description => Loc.GetString($"mood-effect-{ID}"); + + [DataField, ValidatePrototypeId] + public string? Category; + + [DataField(required: true)] + public float MoodChange; + + [DataField] + public int Timeout; + + [DataField] + public bool Hidden; +} diff --git a/Content.Shared/_Sunrise/Mood/MoodEvents.cs b/Content.Shared/_Sunrise/Mood/MoodEvents.cs new file mode 100644 index 00000000000..d44237a7127 --- /dev/null +++ b/Content.Shared/_Sunrise/Mood/MoodEvents.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.Mood; + +[Serializable, NetSerializable] +public sealed class MoodEffectEvent(string effectId, float effectModifier = 1f, float effectOffset = 0f) : EntityEventArgs +{ + public string EffectId = effectId; + + public float EffectModifier = effectModifier; + + public float EffectOffset = effectOffset; +} + +[Serializable, NetSerializable] +public sealed class MoodRemoveEffectEvent(string effectId) : EntityEventArgs +{ + public string EffectId = effectId; +} + +[ByRefEvent] +public record struct OnSetMoodEvent(EntityUid Receiver, float MoodChangedAmount, bool Cancelled); + +[ByRefEvent] +public record struct OnMoodEffect(EntityUid Receiver, string EffectId, float EffectModifier = 1, float EffectOffset = 0); diff --git a/Content.Shared/_Sunrise/Mood/SaturationScaleComponent.cs b/Content.Shared/_Sunrise/Mood/SaturationScaleComponent.cs new file mode 100644 index 00000000000..4bf38eaeddf --- /dev/null +++ b/Content.Shared/_Sunrise/Mood/SaturationScaleComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Sunrise.Mood; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SaturationScaleOverlayComponent : Component; diff --git a/Content.Shared/_Sunrise/Mood/SharedMoodComponent.cs b/Content.Shared/_Sunrise/Mood/SharedMoodComponent.cs new file mode 100644 index 00000000000..ab1dedd184f --- /dev/null +++ b/Content.Shared/_Sunrise/Mood/SharedMoodComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Alert; + +namespace Content.Shared._Sunrise.Mood; + +[RegisterComponent, AutoGenerateComponentState] +public sealed partial class NetMoodComponent : Component +{ + [DataField, AutoNetworkedField] + public float CurrentMoodLevel; + + [DataField, AutoNetworkedField] + public float NeutralMoodThreshold; +} + +public sealed partial class ShowMoodEffectsAlertEvent : BaseAlertEvent; diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/mood/mood.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/mood/mood.ftl new file mode 100644 index 00000000000..1037f619652 --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/_sunrise/mood/mood.ftl @@ -0,0 +1,62 @@ +alerts-mood-insane-name = Воодушевлён +alerts-mood-insane-desc = Я полон энергии и энтузиазма, готов горы свернуть! + +alerts-mood-horrible-name = Подавлен +alerts-mood-horrible-desc = Сейчас тяжело, но это временно. Нужно собраться с силами. + +alerts-mood-terrible-name = Расстроен +alerts-mood-terrible-desc = День выдался не из лёгких. Стоит немного отдохнуть. + +alerts-mood-bad-name = Не в духе +alerts-mood-bad-desc = Что-то сегодня всё не так. Может, стоит сменить обстановку? + +alerts-mood-meh-name = Так себе +alerts-mood-meh-desc = Ничего особенного. Обычный будний день. + +alerts-mood-neutral-name = Спокоен +alerts-mood-neutral-desc = Всё идёт своим чередом, без особых взлётов и падений. + +alerts-mood-good-name = Доволен +alerts-mood-good-desc = Дела идут неплохо, и это радует! + +alerts-mood-great-name = Воодушевлён +alerts-mood-great-desc = Сегодня определённо удачный день! Хочется сделать что-то хорошее. + +alerts-mood-exceptional-name = Вдохновлён +alerts-mood-exceptional-desc = Чувствую прилив сил и энергии. Горы готов свернуть! + +alerts-mood-perfect-name = На высоте +alerts-mood-perfect-desc = Потрясающее настроение! Кажется, сегодня всё по плечу! + +alerts-mood-dead-name = Мертв +alerts-mood-dead-desc = ... + +mood-show-effects-start = [font size=12]Текущее настроение:[/font] + +mood-effect-HungerOverfed = Я так наелся, что вот-вот лопну! +mood-effect-HungerOkay = Чувствую себя сытым. +mood-effect-HungerPeckish = Не отказался бы от перекуса. +mood-effect-HungerStarving = Мне срочно нужно поесть! +mood-effect-ThirstOverHydrated = Голова кружится от переизбытка жидкости. +mood-effect-ThirstOkay = Чувствую себя освеженным. +mood-effect-ThirstThirsty = Губы немного пересохли. +mood-effect-ThirstParched = Мне срочно нужно попить! +mood-effect-HealthNoDamage = Ничего не болит. +mood-effect-HealthLightDamage = Всего лишь царапина, но всё равно неприятно +mood-effect-HealthSevereDamage = Боль почти невыносима! +mood-effect-HealthHeavyDamage = Всё тело пронизывает боль! +mood-effect-Handcuffed = Меня держат в заточении. +mood-effect-Suffocating = Не могу... дышать... +mood-effect-OnFire = ГОРЮ!!! +mood-effect-Creampied = Меня окрестили. На вкус как пирог. +mood-effect-MobSlipped = Я поскользнулся! В следующий раз буду осторожнее. +mood-effect-MobVomit = Обед оказался не таким вкусным на обратном пути. +mood-effect-MobLowPressure = Кажется, сейчас взорвусь! +mood-effect-MobHighPressure = Чувствую, будто меня сжимает со всех сторон! +mood-effect-TraitSaturnine = Всё как-то не так. Ненавижу эту работу. +mood-effect-Dead = Вы мертвы. +mood-effect-BeingHugged = Обниматься - это приятно. +mood-effect-ArcadePlay = Здорово поиграл в интересный игровой автомат. +mood-effect-GotBlessed = Меня благословили. +mood-effect-PetAnimal = Животные такие милые, не могу перестать их гладить! +mood-effect-SavedLife = Как же приятно спасать чью-то жизнь diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 481b6c3f268..05b95974958 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -6,6 +6,7 @@ order: - alertType: ChangelingBiomass - category: Health + - category: Mood #Sunrise Edit - category: Stamina - alertType: ChangelingChemicals - alertType: SuitPower diff --git a/Resources/Prototypes/Alerts/mood.yml b/Resources/Prototypes/Alerts/mood.yml new file mode 100644 index 00000000000..e4d0b10b840 --- /dev/null +++ b/Resources/Prototypes/Alerts/mood.yml @@ -0,0 +1,112 @@ +- type: alertCategory + id: Mood + +- type: alert + id: Insane + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood_insane + name: alerts-mood-insane-name + description: alerts-mood-insane-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Horrible + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood1 + name: alerts-mood-horrible-name + description: alerts-mood-horrible-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Terrible + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood2 + name: alerts-mood-terrible-name + description: alerts-mood-terrible-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Bad + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood3 + name: alerts-mood-bad-name + description: alerts-mood-bad-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Meh + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood4 + name: alerts-mood-meh-name + description: alerts-mood-meh-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Neutral + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood5 + name: alerts-mood-neutral-name + description: alerts-mood-neutral-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Good + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood6 + name: alerts-mood-good-name + description: alerts-mood-good-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Great + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood7 + name: alerts-mood-great-name + description: alerts-mood-great-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Exceptional + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood8 + name: alerts-mood-exceptional-name + description: alerts-mood-exceptional-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: Perfect + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood9 + name: alerts-mood-perfect-name + description: alerts-mood-perfect-desc + clickEvent: !type:ShowMoodEffectsAlertEvent + +- type: alert + id: MoodDead + category: Mood + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood_happiness_bad + name: alerts-mood-dead-name + description: alerts-mood-dead-desc + clickEvent: !type:ShowMoodEffectsAlertEvent diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 40c24ac4f64..417808b9bf2 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -241,6 +241,7 @@ - type: Carriable - type: CanEscapeInventory - type: LayingDown + - type: Mood # Sunrise-End - type: Barotrauma damage: diff --git a/Resources/Prototypes/Mood/categories.yml b/Resources/Prototypes/Mood/categories.yml new file mode 100644 index 00000000000..23f1a8b0aaa --- /dev/null +++ b/Resources/Prototypes/Mood/categories.yml @@ -0,0 +1,8 @@ +- type: moodCategory + id: Health + +- type: moodCategory + id: Hunger + +- type: moodCategory + id: Thirst diff --git a/Resources/Prototypes/Mood/genericNeeds.yml b/Resources/Prototypes/Mood/genericNeeds.yml new file mode 100644 index 00000000000..d0b24b7d7fe --- /dev/null +++ b/Resources/Prototypes/Mood/genericNeeds.yml @@ -0,0 +1,63 @@ +# Hunger +- type: moodEffect + id: HungerOverfed + moodChange: -10 + category: "Hunger" + +- type: moodEffect + id: HungerOkay + moodChange: 7 + category: "Hunger" + +- type: moodEffect + id: HungerPeckish + moodChange: -3 + category: "Hunger" + +- type: moodEffect + id: HungerStarving + moodChange: -7 + category: "Hunger" + +# Thirst +- type: moodEffect + id: ThirstOverHydrated + moodChange: -3 + category: "Thirst" + +- type: moodEffect + id: ThirstOkay + moodChange: 7 + category: "Thirst" + +- type: moodEffect + id: ThirstThirsty + moodChange: -3 + category: "Thirst" + +- type: moodEffect + id: ThirstParched + moodChange: -7 + category: "Thirst" + +# Health +- type: moodEffect + id: HealthNoDamage + moodChange: 0 + hidden: true + category: "Health" + +- type: moodEffect + id: HealthLightDamage + moodChange: -3 + category: "Health" + +- type: moodEffect + id: HealthSevereDamage + moodChange: -7 + category: "Health" + +- type: moodEffect + id: HealthHeavyDamage + moodChange: -20 + category: "Health" diff --git a/Resources/Prototypes/Mood/genericNegativeEffects.yml b/Resources/Prototypes/Mood/genericNegativeEffects.yml new file mode 100644 index 00000000000..4b18538c966 --- /dev/null +++ b/Resources/Prototypes/Mood/genericNegativeEffects.yml @@ -0,0 +1,41 @@ +- type: moodEffect + id: Handcuffed + moodChange: -3 + +- type: moodEffect + id: Suffocating + moodChange: -7 + timeout: 6 + +- type: moodEffect + id: OnFire + moodChange: -10 + timeout: 600 + +- type: moodEffect + id: Creampied + moodChange: -3 + +- type: moodEffect + id: MobSlipped + moodChange: -3 + timeout: 180 + +- type: moodEffect + id: MobVomit + moodChange: -3 + timeout: 480 + +- type: moodEffect + id: MobLowPressure + moodChange: -7 + timeout: 10 + +- type: moodEffect + id: MobHighPressure + moodChange: -7 + timeout: 10 + +- type: moodEffect + id: Dead + moodChange: -1000 diff --git a/Resources/Prototypes/Mood/genericPositiveEffects.yml b/Resources/Prototypes/Mood/genericPositiveEffects.yml new file mode 100644 index 00000000000..e9da13e91a3 --- /dev/null +++ b/Resources/Prototypes/Mood/genericPositiveEffects.yml @@ -0,0 +1,24 @@ +- type: moodEffect + id: BeingHugged + moodChange: 3 + timeout: 120 + +- type: moodEffect + id: ArcadePlay + moodChange: 3 + timeout: 480 + +- type: moodEffect + id: GotBlessed + moodChange: 3 + timeout: 480 + +- type: moodEffect + id: PetAnimal + moodChange: 3 + timeout: 300 + +- type: moodEffect + id: SavedLife + moodChange: 7 + timeout: 480 diff --git a/Resources/Prototypes/Shaders/scale.yml b/Resources/Prototypes/Shaders/scale.yml new file mode 100644 index 00000000000..13ba5e9cb4a --- /dev/null +++ b/Resources/Prototypes/Shaders/scale.yml @@ -0,0 +1,4 @@ +- type: shader + id: SaturationScale + kind: source + path: "/Textures/Shaders/saturationscale.swsl" diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/meta.json b/Resources/Textures/Interface/Alerts/mood.rsi/meta.json new file mode 100644 index 00000000000..c40ea9334ed --- /dev/null +++ b/Resources/Textures/Interface/Alerts/mood.rsi/meta.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from NSV13 at b6b1e2bf2cc60455851317d8e82cca8716d9dac1", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "mood1" + }, + { + "name": "mood2" + }, + { + "name": "mood3" + }, + { + "name": "mood4" + }, + { + "name": "mood5" + }, + { + "name": "mood6" + }, + { + "name": "mood7" + }, + { + "name": "mood8" + }, + { + "name": "mood9" + }, + { + "name": "mood_insane", + "delays": [ + [ + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07 + ] + ] + }, + { + "name": "mood_happiness_good" + }, + { + "name": "mood_happiness_bad" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png new file mode 100644 index 0000000000000000000000000000000000000000..5b64b60989459f0cf8a86bd5faa5aa154e94056b GIT binary patch literal 638 zcmV-^0)hRBP)Px%I7vi7R9J=WmQPFLKp4iKmcp*I3TbV`V(THLsewrVDirU{N~M@nE^OdkXEdM zv|{nh2~w0NSjx*#)moVTi17p)-8PJFJ5+$r3UC2Pc^P6li=-fqY{cWnLF8Qlh)F<9 zg4OknaUm8j2y>uPsaih9E*<{bV}@XALZwo*K`z1VIJ}L^w_x*12{#E=W-GNU}!#fc_JkukrHupVE}*!Bou(Z z{~l~(Dsnfl0i0li>vLuR-yY}~%?K^wxTc~IuCbR`UDwbtn!Y<>U*-pEDUV1B^15%`DpF07*qoM6N<$f^VoFX8-^I literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png new file mode 100644 index 0000000000000000000000000000000000000000..03fe0f702f1d27b13f30bb770171e648f78a43c9 GIT binary patch literal 513 zcmV+c0{;DpP)Px$yGcYrR9J=WmOoAcK@`S6T*)S83$j2q7%_oOtgtZA!p1Yu+1OEX0}tR0yn&4! zXP}`WvCzr{Oo$2D2pdc`Bq(GomRV+4hMgHTTI^Sm$9wbr=FfWoq)PS2%IFj|YZbC7 ze*5&Iyxy*k@y$t5ltNTNq!XL93fW@J>H6VK8e&J*yivBL%mJWLwuRTE1@N0n1faWj z3&t`+;7#IrVJzIVTQHPGfPf9|UOi0|4O6om>cYZd>U10pJ7mswG+?CaEsyPUoC)#}8sTG)8l1&9?xq zjt&R_w1?xEF?-bPXPQH6=o%s=Ucp5vA1zj|S{qvsSnZsJVQWQF9z?9cU^?S~R3D%c zKyS1VLafKw+$3$ig1hI}43^`qJDtOY!F_+WZ*Huk53oHP(>3$}7zh9$r+W@Fl2>iX zwq0+ukTRxHegpJ)Tm1kR;#!_3^h{dd*Zi00000NkvXXu0mjf D#f9M6 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png new file mode 100644 index 0000000000000000000000000000000000000000..2974e7eb6476c205e5a2ce89bd47fc4abbcf8246 GIT binary patch literal 499 zcmVPx$tw}^dR9J=WRxwY)KotHE2LcNziA{vLkqCpjuykQE&J65s{sA`zHvWK{|DdyR za=C>CornoDl|a%CBoq=b=g_>SC8c-QXmIGa_2u3BzWd(0y8;<992vPZiw5mFkBdLP zeJa;GO+)-}dwwW|s|t}$9JK2^fjEMv*Dq-iC$i+-W?70H0J_cc!8WB5@RtzI0PS9@ z7Nv`7)grn8fI1ho;^+Px$heG+KbtzbI6dgKL=+LR7|3KXPBizM*;OOKK zFoT2Av7nHpA!M=8LY7n!L5PFi@RH``eQ2pe&Ukb0J?DPpCP0Z2Yeq#UXxMMZdG?Fj zC*^#bI>cwE`#hIeRfx3Xu-}dgh*P+_ewG%oAxl2!SW@HwFz8s^F=+`{C4_CF>J781 zlz28>;O0IMj$-W{5DIwv_|#4=6RX?-+X-MhfwWx&r8{6d0S;RA+-GFBodCVAC7n~M z1D+oxBx5(k*bM=EFB*aDB!psqCE)y|6$8L%GEYcEARmgM96Sg|aJaXVBfj!4goL4YR6zCkWDS0k87BS@V4JP}qUXJGX+LFP!H`OI02n?-q(0P3DwC zPXHqV0ODq&CT#U=x{xB47LZ3^nAN4DQ?#IeCcyI}G#j;q@HKn~0BqN{Qa&pY!0rQ$ zfh8t#mG&zM$co4=;N!!KGC836Q_8z;s^ld~{0n>mnz*s5Gc{p!00000NkvXXu0mjf DtVqVE literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png new file mode 100644 index 0000000000000000000000000000000000000000..b9b7e229776f027c6ffb13ac3de0a875baa102aa GIT binary patch literal 441 zcmV;q0Y?6bP)Px$b4f%&R9J=WRrXB>nijaTc^bpCpM$*y-4 zLVR*|Zi%SwIYPVx$E$T|CvM~U{?1#(p)dJpw)7$gfYEGeT~m;Nzl3v4)O`{OR$*Ca ze7rn3SF!gAI0by^&*0)dxvbAn78+V90KjxHY29r%I{<))W7fJ@0+L7o(ZqPy732#q z4^Y=Y)hTr=3vE2_<^&5!v%IW|J=G*GPz>cz4I7**5b?Y&HeF0wUAN<60BG&MnHwyi zizb0q3KEs+xbFnP`yH@#ZrZgi)&%EENIMkq+}r8f&8{B(+s#!khxe~xn&sv3>4kID zve0;cymb;2*dCCDMiPl1Uqqile@%c^s&;#muxm^XSbw3?EHC@KK)d%HKU+Ybh!FB1 j0VKk7&Ltl(;9uYyE`FsOi&OVi00000NkvXXu0mjfDb&6k literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png new file mode 100644 index 0000000000000000000000000000000000000000..158bf2aace1bfc490f0679948d0a68e3568dacf9 GIT binary patch literal 479 zcmV<50U-W~P)Px$nMp)JR9J=WRxxhEKoA^4BpQSeri$sHq(idlEDBm45ak1WfJfv5d;sMEX(=K) zY>Cb#bX+Qo5J8GuAg4&~WLxLl-boQDJe7UE-I?2+JsBub;>M_$1WjgMB3plbUTNpM zHz9s}dMMcO{ar^0dE;c}CB?*9ye!_yBJQZ<-pC@61AsTO3fp8P;4h&p6FJuhR8=jIn_6beI4@xH$_F75E+=!*dit*@FI{A(FX{l{s}9 z-Ch#_GW}1-8VkrHO|aXZfFIN87%vU-J;0cJr1(@&_R65V33(6ug9h2@eh`V$FX!uD zIg~9B38urOW7ox%><5t$lzwI`Mz}dEiOB-;U@V4h*WfyqaD19v7g_@L?1GM67w@u` zQ0Je6Y{KPyT?sem48h@e&5Y%qU9`#%82l5Cr^7^MNNzkGaw7p;glt`xyhMqAfiLpY Vvx-MQw_E@K002ovPDHLkV1mD7&r1LR literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png new file mode 100644 index 0000000000000000000000000000000000000000..e69847314e4990f854227b1a256640735f07024b GIT binary patch literal 514 zcmV+d0{#7oP)Px$yh%hsR9J=WR=-OEVHkdlqM=_P!6AeVC0Zzf1P-}4c)Np3i_ZN4{So~E%}p&0 z?$>HViy#IS9)gjGc%0bf5DwRHeII9j*Y_jBhI_{Md*AnYzW2v>2ZRW*U_?}c($$R1 zvOiq4r1LGS5TBkOFwd_6S5}CyW4fAggNWyZTs#VkSP~^KR5T%S04P+nz&0rf_)BnS zfZRT&MTL{bv?vM{0D#MD#lkDV6)>Glm6MCaN>`v>YolIk3)^{+dI$Og8~IX#c}DF0 zfh{#JLKUz~2Y@$R{+sp(HuWKPLQZ0SB_LT&xd6~G#*~By;zMFcjv#Pbkkz+nvV184 zd-BHgJJh|0D!R7Gj8zu!9e#pda|9fDt?}DsAiM?se*4jDj)Z+#_^|@dH%}43>!D>j zbJKmS?=j=Z05**ASB_wCD?k!7j4{1gz1Bv@98#Z-Ih@%H(sv#?An`d)mQ${NwgbTH z`Tgw+rtcpBfR*)CaO{j|_wbka@@gbQZ`;k`1s- zN2u>g0{kL?E;1*(GppCS>q?UYia(`6ubV7+h!Fn*pOhlSOpo?sdH?_b07*qoM6N<$ Eg4X-pr~m)} literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png new file mode 100644 index 0000000000000000000000000000000000000000..da80c1ba0469c44a0d27c11bbac4f6a9efe090b5 GIT binary patch literal 485 zcmVPx$pGibPR9J=WRy}LNP!v6pq7Bkkj0GVeZgqCGOQHS(S0}r=^p|w$TBof21+MK> zaJE}{@IYvlv_oSQOo#A75?*}wr4&2ljQ8Dp&$%DD3KS@CU>JFV+T&gzv%lRxYv()4 zLtMLkm%k%?*AYT?Y>#_E7I7Nx-8orAiArt`b|i8DFbBKLG365Qmr%AzR9|T=wG#Wx zg7;Wbj^f}QPzu<(Uhd?SSnUq1CQYm+O|o4Dtvle&Yp9PF(l=rE<~5idmz1;I*#h~761~T zYdki$fHdL+-!Ik3rh6v@$X`KL1b2 z?2!WBA;2(TaaE*3OS&;glpEoaqA-@%<=*D zZ+;x)CFIV`*i3oi!1&BcRJ~>m(%j?F{tsslyFs~cPx$pGibPR9J=WRxwY)P!RqQL(()QI>7+HfX>vFu#wQf3!IT}M0}Mt3(MpY}gI-g#uf11daOgMQdw1XW?%v&dK!OB2Mk3I8bh!4-&qAjkh5cbAmyX=M#Z{X>)#}={2C0AQ@7C8W@t-7#Gu>|}jBGW{t&y;df zft|?$FBeylrPz4}A_crZzQzvD@s-ZNvOC1GJ7n9VAT3|u3-J+h!wb#_cv z2G+AF06?kg8z*%p3vUnEiJW}45|D4|E&$kru}7ks6{u!K=tJVjhhm5jSpubDc)>*J z2jGcH7qmcM5w>c>x7$0f4C#WlLCytRfWO{9MR4~9A+!YZzLm;^w%_0D@TET zbOG(b81udrItf`X7G0!+lVfB`IbpwNJ0_gT0?+4{-T|Uwvr6C(6qa898i$-MfIz7Y z>ISlL`=227g~~T|S7{i~YDVDt>UzwoyB|`c*ncUtnt_jp6@af6j6)8@{FI8On=E;P b1pfk`u%)x^+rikH00000NkvXXu0mjfrn%nX literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png new file mode 100644 index 0000000000000000000000000000000000000000..4cc694792817e1ac2e8f5ed9f9542809318a53d3 GIT binary patch literal 544 zcmV+*0^j|KP)Px$+DSw~R9J=WmMxFMFdW98dyzGR*@a-{z|0|0D85N0{!SzJO=dP#iC98Hh6Btp zTe%ED2q!MxwOqTcJKXUkB;)_m=eM@&20ZoO_0p$_QhL+H!!WcS&#FNfd!DDw7e!$W z(z%dQdK1R0)k<5lUaxDAt`MDygt=^dI2@E3-}e=et`M(X&4VCN%zfWielk!H1n@l1 zxL2$IbdvA3Db_`eQzYO0d_J$^S(a(*r_*VD%^1UWyKP;HcU$5t%M_!oIg?tK;CwzS z|Ca_xBGPcP*1V9+b zMroQFbAJK|!|;W3jwDF{{Jd&qIgSIzael=`ZIUG5oHw3?Z%uS*nto0ulaH#ZaJgJ? zyJD z-d+H*bj#4E#u`BEhdj?44T_>bp69LWF3b9x&~;smMkCB-GbOj#Y^=?#0b~fBY|VQC i2nb!g&u95lPkjLoKPx$a7jc#R9J=WmN9aJFc3w*GdYA?N*YQ^8VXw9fD&pBqMCMZ040SF0lmj`<^;4# zkt`#}l35gIl7ctGjOK&%Khg>T?Ckt82cJ%LU2B_OmgVO0-hGUVqHsN;eS?V1rMPD3 zN9KLsiv#q1FAnkc(xktE>+Ns1+uh|m3!93NnWhQH<8f7!u(|~EJU?hQBb#RHyE(vj zhzLX^*4Nmy1Vn@^%dGcUCnDr|{@MWJI3kK7WLbu;>j2=vgmn%wj-x2R(=005`cX=x{6h#-!awy6U^5XX1A+1ERO37bpX z62t+9VX&I;GMJkA>>5vtu&Sy9zD_tUM3W#0to^tEo2KD0oQRK2rIb$76j2m8_j{h_ z7={7o^Lf>n+591THoaS>-YWo4t?fg@nsonrp_I~X+dBUP+P2ke`~IaN%%j-X-0R=X gWh5=Uot=Nh2PsBfp%*T>-2eap07*qoM6N<$f*Nwjv;Y7A literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png new file mode 100644 index 0000000000000000000000000000000000000000..2b563f4af09695eda3d56974b94dc375af00bc40 GIT binary patch literal 3528 zcmYM1c{r5O7suZjWJ{JTSu(clB1@DoMuhD97D7alB}-;7ma=6hDNFY3OT=U>Wr`?M zwqa=Oijc95$uhsu^Lu{3`@Hww_nzmxf4t|M@8>x;(ahuq3lkp`001lo`qwR}yX(nh zI7MCOpNFbZH&Bqp4H!^AB(OqtiZQsZZ55WkUU18o6UWs@N#z%CSJ?aO&T^b^`-sk& zU;A|v-9;7G7kY_zGOftz7fO1V*{TMZU)N>F>wD`uml(1s0BgLnH~sI}jz#wj&n<$8pa{O-Z4P7~Axdv6mtG=5e+G8#UN@!MNCsF$>N)>%+Z)yVJ4$ z9tRFmNt@ECg@TJ`z_#r5gccoXxthhRSAgti)+OO1sqO1LXWrS~gm0gp-lHz>yayO= zylu#FuGe398-)@vRyEcYX<=$k)Vg#JNICvuZ5kLlFk$Fv$(|Bv>nReiWXQvrf zJ)<@v1~B^H5qFap0EF_4b(Q~~N8Ln42%v5f6#>^eP8lvnt~m-T$E;CSY!7Un@-2ZE z3H8VL|MWy8!b#mDcW7#8vXUQY^gFWoe(~{U z3Qb+K)Tr9CDty)b+phD2iJn@(>_`0R`aL@ay4i5}Ab5_^r<*`3kIF?h&EWfNuW1`PK2%TmH*_jrt^kO|1&3rGVu#Vf@(k%2(EA5xD16O~m zAXNbYh~?K)&2)=b6>#ywslo8+`>KPZd4r6?vegaUa>UJEdX#z_6~)tCY#8&&MZZe!=~Zy?7j9W89EvzZ23{ zGEWo3^DT;;%u~Kri+K8!VC_kJ@TS63LQn6{ybDE>*xRAdt$&Wg$8_Z7aR0AcW(vP5 z<q|%jD57hL#^t zf%ZgaN-Sb-OhD^$|9RSab+`D!y6i&ABVpWU&@8!#XSG5~^yGtntCyOLtoKhz5Ys3-c_vg$Z8vAR^DBkv z!P-N&V^*xLmujin9x$HPKobou$BqN?CCKuCES1V~5k3h{3h zYd$sW?sfnpQ*Um$y3vitp5W=5s3%jk)Q}AZ5$;v&Gy3LRzgKQeZG(Ac&~(!Ck2y8g zrOx+rX@KfO>|ef*M^@D$%gfcL$6k8{aD7hmdP6SS9n)!NcmuY45m4`N5pAl#B;!ww zlwiLuuKEK%qm2IpteS%DLtQ3$W|AiK!Q#E8a1BSLUnz(>rv|Tkry6YG1_m`Ef{t=- z8yr&cI8HD3!SQLkWdIo@=X_xo9ku+fsz7pSb5TH*WcZUBc*9i`d^MqJ+wx4PR?a}@ zuy@C-tpzrZb?D_mBp~|AszqGIvzo|aQnEXq;60bvQJE@s5(ZfB`Az>?;!-CJfuTPK zBH7-2gc{7g$#I|Okc!7Bc<1ehPA?Lqt_eHQAY!xp>mBNS(Fu3y@Uon=P;NDO^+)fM zB%7gqItV#x@dO5CHKL?-?1_!@$s(M{!l|>84p~f4Zp45*<^7;yCKV;@e1bMM>b0{O zk0+(C5u_HEhP1dFax%kk6xvW=*&>PdOGDg0hJ;l>xI?(V`Kc+MfBYn#oo}PjW2cBb zivTaLg35&*GKZP^7YaBGwUE*95A?rr1`c$2ObuM~Z%%%yg-MaixyFLkoSM{O>3*v) zT@3wcLN;#dhl8AEaa%{L0|NFSR7Se%|MI53eNAK4ar7eH!^_YA-cMF(c%4|bnIeau zVcj1=-3XHMaLL&#PiK|4J2(P3j>mNFsiZ-QIvjkgh4lAZR9jsVEg8t|*3m+9Ba}u{ z>~$~({Qmy0$U4~YS)ft>rmwq%@ z>j%U0oh0ay?=(z)DBp?up!!z-vpfQOzv%D->7-2|A5@oRi+54bV8G%<;fS$i#)Rn& z`F|=u&M`TryaxkM5Fo@+6ZWBW$@d~Fp*`^UCiE06D(cILp}QY>`O=Nf1HVvoR_&q<6uI74SsfiAG=*8bvI)(=M!TjbDoYtf^~O4Fv|5RM{*8@mcn-YuO4%x>|J0Hn;vbQZE1M6dro zUJ}%Eq~r|D8eXDyAEWW?fgv4Be5s&9*XjDV43lT4NrfHEa2HWu?@YVz3u&?OjU6RQ+r)qc^pH>{nvT zU$xW+|E>xj`bx?)wrMcb=31Ve^1D#mqeL+_$6qHg5?%MUtbrUmiw!GAc`!*j;VVg& zuqV`3$GfAaNf+HWe%iOoD_kDVyZ zr*j%1SA;`1m=@Nc%Q5C%Xp;kP4-^0opgiRvxaYDIMeTdsfDKKPdygWRLwcox$6iEQ zA$2P83W#RWRWeFbfNS^`{Z@9)x9TM2TB*#Lvd9iE&RE>1rr^*^ClwPgN&{42yozBj z-l;Mg&_(`mGg5Jz#|FB7Nx;G$E3f0^ZyTU2LpzJYUx7&K5$7li5&)Ye0;xpT1RHjC z-7(R(4^Jg9O3mLRKYz@ncrCpc#gicWjmQBpW9F6XZ0rle(9p7_R!Lz&?8fSdgSVRJ!lnOi�=H#=Skph+?xQlreT(S?$o0bg5hPUyk z$U%&RBm>DTiOcapb<$SnGYhI=q6B@I_ciQSh8Nt85NH%h5SP_--K;?WuV1i9!j`mk iaB