From 2cd165618823b8055b4ac5a14a8e76ad0654250d Mon Sep 17 00:00:00 2001 From: HitPanda <104197232+EnefFlow@users.noreply.github.com> Date: Mon, 25 Sep 2023 01:44:44 +0300 Subject: [PATCH] [Fix] Mood system fix (#439) * mood back * fix ready --- .../White/Overlays/SaturationScaleOverlay.cs | 37 ++ .../White/Overlays/SaturationScaleSystem.cs | 61 +++ Content.Server/Arcade/BlockGame/BlockGame.cs | 5 + .../SpaceVillainArcadeSystem.cs | 4 + .../Atmos/EntitySystems/BarotraumaSystem.cs | 7 + .../Atmos/EntitySystems/FlammableSystem.cs | 5 + Content.Server/Bible/BibleSystem.cs | 3 + .../Body/Systems/RespiratorSystem.cs | 6 +- .../GameTicking/Rules/TraitorRuleSystem.cs | 3 + .../Interaction/InteractionPopupSystem.cs | 15 + Content.Server/Medical/VomitSystem.cs | 4 + Content.Server/White/Mood/MoodComponent.cs | 107 +++++ Content.Server/White/Mood/MoodSystem.cs | 415 ++++++++++++++++++ Content.Shared/Alert/AlertCategory.cs | 1 + Content.Shared/Alert/AlertType.cs | 14 + Content.Shared/Cuffs/SharedCuffableSystem.cs | 7 + .../Nutrition/EntitySystems/HungerSystem.cs | 18 +- .../EntitySystems/SharedCreamPieSystem.cs | 3 + .../Nutrition/EntitySystems/ThirstSystem.cs | 18 +- .../SharedRevolutionarySystem.cs | 15 + Content.Shared/Slippery/SlipperySystem.cs | 3 + .../White/Mood/MoodEffectPrototype.cs | 31 ++ .../White/Mood/MoodSerializables.cs | 36 ++ .../Overlays/SaturationScaleComponent.cs | 8 + .../Locale/ru-RU/white/mood/mood-alerts.ftl | 22 + Resources/Locale/ru-RU/white/mood/mood.ftl | 1 + Resources/Prototypes/Alerts/alerts.yml | 1 + .../Prototypes/Entities/Mobs/Species/base.yml | 1 + Resources/Prototypes/Shaders/shaders.yml | 5 + Resources/Prototypes/White/Alerts/alerts.yml | 110 +++++ .../White/Mood/generic_negativeEffects.yml | 51 +++ .../White/Mood/generic_positveEffects.yml | 52 +++ .../White/Mood/moodEffects_needs.yml | 87 ++++ .../Shaders/White/saturationscale.swsl | 12 + .../White/Interface/Alerts/mood.rsi/meta.json | 60 +++ .../White/Interface/Alerts/mood.rsi/mood1.png | Bin 0 -> 606 bytes .../White/Interface/Alerts/mood.rsi/mood2.png | Bin 0 -> 517 bytes .../White/Interface/Alerts/mood.rsi/mood3.png | Bin 0 -> 526 bytes .../White/Interface/Alerts/mood.rsi/mood4.png | Bin 0 -> 417 bytes .../White/Interface/Alerts/mood.rsi/mood5.png | Bin 0 -> 412 bytes .../White/Interface/Alerts/mood.rsi/mood6.png | Bin 0 -> 430 bytes .../White/Interface/Alerts/mood.rsi/mood7.png | Bin 0 -> 452 bytes .../White/Interface/Alerts/mood.rsi/mood8.png | Bin 0 -> 528 bytes .../White/Interface/Alerts/mood.rsi/mood9.png | Bin 0 -> 481 bytes .../Alerts/mood.rsi/mood_happiness_bad.png | Bin 0 -> 304 bytes .../Alerts/mood.rsi/mood_happiness_good.png | Bin 0 -> 343 bytes .../Interface/Alerts/mood.rsi/mood_insane.png | Bin 0 -> 3392 bytes 47 files changed, 1221 insertions(+), 7 deletions(-) create mode 100644 Content.Client/White/Overlays/SaturationScaleOverlay.cs create mode 100644 Content.Client/White/Overlays/SaturationScaleSystem.cs create mode 100644 Content.Server/White/Mood/MoodComponent.cs create mode 100644 Content.Server/White/Mood/MoodSystem.cs create mode 100644 Content.Shared/White/Mood/MoodEffectPrototype.cs create mode 100644 Content.Shared/White/Mood/MoodSerializables.cs create mode 100644 Content.Shared/White/Overlays/SaturationScaleComponent.cs create mode 100644 Resources/Locale/ru-RU/white/mood/mood-alerts.ftl create mode 100644 Resources/Locale/ru-RU/white/mood/mood.ftl create mode 100644 Resources/Prototypes/White/Alerts/alerts.yml create mode 100644 Resources/Prototypes/White/Mood/generic_negativeEffects.yml create mode 100644 Resources/Prototypes/White/Mood/generic_positveEffects.yml create mode 100644 Resources/Prototypes/White/Mood/moodEffects_needs.yml create mode 100644 Resources/Textures/Shaders/White/saturationscale.swsl create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/meta.json create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood1.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood2.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood3.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood4.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood5.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood6.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood7.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood8.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood9.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood_happiness_bad.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood_happiness_good.png create mode 100644 Resources/Textures/White/Interface/Alerts/mood.rsi/mood_insane.png diff --git a/Content.Client/White/Overlays/SaturationScaleOverlay.cs b/Content.Client/White/Overlays/SaturationScaleOverlay.cs new file mode 100644 index 0000000000..272b34fc46 --- /dev/null +++ b/Content.Client/White/Overlays/SaturationScaleOverlay.cs @@ -0,0 +1,37 @@ +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.White.Overlays; + +public sealed class SaturationScaleOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = 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").InstanceUnique(); + } + + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("saturation", Saturation); + + var handle = args.WorldHandle; + + handle.UseShader(_shader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/White/Overlays/SaturationScaleSystem.cs b/Content.Client/White/Overlays/SaturationScaleSystem.cs new file mode 100644 index 0000000000..543ba53ff5 --- /dev/null +++ b/Content.Client/White/Overlays/SaturationScaleSystem.cs @@ -0,0 +1,61 @@ +using Content.Shared.GameTicking; +using Content.Shared.White.Overlays; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client.White.Overlays; + +public sealed class SaturationScaleSystem : EntitySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly ILightManager _lightManager = default!; + + private SaturationScaleOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeNetworkEvent(RoundRestartCleanup); + + _overlay = new(); + } + + private void RoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, SaturationScaleComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, SaturationScaleComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnShutdown(EntityUid uid, SaturationScaleComponent component, ComponentShutdown args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + { + _overlayMan.RemoveOverlay(_overlay); + } + } + + private void OnInit(EntityUid uid, SaturationScaleComponent component, ComponentInit args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + _overlayMan.AddOverlay(_overlay); + } +} diff --git a/Content.Server/Arcade/BlockGame/BlockGame.cs b/Content.Server/Arcade/BlockGame/BlockGame.cs index 3af1828d56..4c8f0e6eed 100644 --- a/Content.Server/Arcade/BlockGame/BlockGame.cs +++ b/Content.Server/Arcade/BlockGame/BlockGame.cs @@ -2,6 +2,7 @@ using Content.Shared.Arcade; using Robust.Server.GameObjects; using Robust.Shared.Random; using System.Linq; +using Content.Shared.White.Mood; namespace Content.Server.Arcade.BlockGame; @@ -82,6 +83,10 @@ public sealed partial class BlockGame { _highScorePlacement = _arcadeSystem.RegisterHighScore(meta.EntityName, Points); SendHighscoreUpdate(); + //WD start + var ev = new MoodEffectEvent("ArcadePlay"); + _entityManager.EventBus.RaiseLocalEvent(meta.Owner, ev); + //WD end } 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 eae9b94964..3114a5479f 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Power.Components; using Content.Server.UserInterface; +using Content.Shared.White.Mood; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -73,6 +74,9 @@ public sealed partial class SpaceVillainArcadeSystem : EntitySystem if (!TryComp(uid, out var power) || !power.Powered) return; + if (msg.Session.AttachedEntity != null) + RaiseLocalEvent(msg.Session.AttachedEntity.Value, new MoodEffectEvent("ArcadePlay")); //WD edit + switch (msg.PlayerAction) { case PlayerAction.Attack: diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index 1b3e1b07a6..a82ea14767 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; +using Content.Shared.White.Mood; using Robust.Shared.Containers; namespace Content.Server.Atmos.EntitySystems @@ -203,6 +204,8 @@ namespace Content.Server.Atmos.EntitySystems _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage"); } + RaiseLocalEvent(uid, new MoodEffectEvent("MobLowPressure")); // WD edit + if (pressure <= Atmospherics.HazardLowPressure) { _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 2); @@ -230,6 +233,8 @@ namespace Content.Server.Atmos.EntitySystems _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking high pressure damage"); } + RaiseLocalEvent(uid, new MoodEffectEvent("MobHighPressure")); // WD edit + if (pressure >= Atmospherics.HazardHighPressure) { _alertsSystem.ShowAlert(uid, AlertType.HighPressure, 2); @@ -247,6 +252,8 @@ namespace Content.Server.Atmos.EntitySystems _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} stopped taking pressure damage"); } _alertsSystem.ClearAlertCategory(uid, AlertCategory.Pressure); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("MobLowPressure")); // WD edit + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("MobHighPressure")); // WD edit break; } } diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 95273f6e7f..3c697217ea 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -20,6 +20,9 @@ using Content.Shared.Timing; using Content.Shared.Toggleable; using Content.Shared.Weapons.Melee.Events; using Robust.Server.Audio; +using Content.Shared.White.Mood; +using Robust.Server.GameObjects; +using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; @@ -371,10 +374,12 @@ namespace Content.Server.Atmos.EntitySystems if (!flammable.OnFire) { _alertsSystem.ClearAlert(uid, AlertType.Fire); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("OnFire")); // WD edit continue; } _alertsSystem.ShowAlert(uid, AlertType.Fire); + RaiseLocalEvent(uid, new MoodEffectEvent("OnFire")); // WD edit if (flammable.FireStacks > 0) { diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index ae990ff80d..6f7686f4e4 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.Timing; using Content.Shared.Verbs; +using Content.Shared.White.Mood; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; @@ -154,6 +155,8 @@ namespace Content.Server.Bible _audio.PlayPvs(component.HealSoundPath, args.User); _delay.TryResetDelay((uid, useDelay)); } + + RaiseLocalEvent(args.Target.Value, new MoodEffectEvent("GotBlessed")); // WD 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 8155a3598e..cbaa93ba86 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -21,7 +21,8 @@ using Content.Shared.Mobs; // WD using Content.Shared.Mobs.Components; // WD using Content.Shared.Mobs.Systems; using Content.Shared.Popups; // WD -using Content.Shared.White.CPR.Events; // WD +using Content.Shared.White.CPR.Events; +using Content.Shared.White.Mood; // WD using JetBrains.Annotations; using Robust.Server.Audio; // WD using Robust.Shared.Audio; // WD @@ -188,6 +189,7 @@ namespace Content.Server.Body.Systems if (respirator.SuffocationCycles >= respirator.SuffocationCycleThreshold) { _alertsSystem.ShowAlert(uid, AlertType.LowOxygen); + RaiseLocalEvent(uid, new MoodEffectEvent("Suffocating")); // WD edit } _damageableSys.TryChangeDamage(uid, respirator.Damage, false, false); @@ -343,6 +345,8 @@ namespace Content.Server.Body.Systems args.Repeat = true; else component.CPRPerformedBy = null; + + RaiseLocalEvent(args.User, new MoodEffectEvent("SavedLife")); } //WD end } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index a6094e3606..34da3ed1d3 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -27,6 +27,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; using Content.Server.Objectives; +using Content.Shared.White.Mood; namespace Content.Server.GameTicking.Rules; @@ -242,6 +243,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem _npcFaction.RemoveFaction(entity, "NanoTrasen", false); _npcFaction.AddFaction(entity, "Syndicate"); + RaiseLocalEvent(mind.OwnedEntity.Value, new MoodEffectEvent("TraitorFocused")); // WD edit + // Give traitors their objectives if (giveObjectives) { diff --git a/Content.Server/Interaction/InteractionPopupSystem.cs b/Content.Server/Interaction/InteractionPopupSystem.cs index 474284cf80..406cac0bc8 100644 --- a/Content.Server/Interaction/InteractionPopupSystem.cs +++ b/Content.Server/Interaction/InteractionPopupSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.White.Mood; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; @@ -58,7 +59,21 @@ public sealed class InteractionPopupSystem : EntitySystem if (_random.Prob(component.SuccessChance)) { if (component.InteractSuccessString != null) + { msg = Loc.GetString(component.InteractSuccessString, ("target", Identity.Entity(uid, EntityManager))); // Success message (localized). + //WD start + if (component.InteractSuccessString == "hugging-success-generic") + { + var ev = new MoodEffectEvent("BeingHugged"); + RaiseLocalEvent(uid, ev); + } + else if (component.InteractSuccessString.Contains("petting-success-")) + { + var ev = new MoodEffectEvent("PetAnimal"); + RaiseLocalEvent(args.User, ev); + } + //WD end + } if (component.InteractSuccessSound != null) sfx = component.InteractSuccessSound; diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs index 8c3b15aed3..d5bef6997e 100644 --- a/Content.Server/Medical/VomitSystem.cs +++ b/Content.Server/Medical/VomitSystem.cs @@ -11,6 +11,8 @@ using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.StatusEffect; using Robust.Server.Audio; +using Content.Shared.White.Mood; +using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -94,6 +96,8 @@ namespace Content.Server.Medical // 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/White/Mood/MoodComponent.cs b/Content.Server/White/Mood/MoodComponent.cs new file mode 100644 index 0000000000..84f7e67ac2 --- /dev/null +++ b/Content.Server/White/Mood/MoodComponent.cs @@ -0,0 +1,107 @@ +using Content.Shared.Alert; +using Content.Shared.FixedPoint; +using Content.Shared.White.Mood; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; + +namespace Content.Server.White.Mood; + +[RegisterComponent] +public sealed partial class MoodComponent : Component +{ + [DataField("currentMoodLevel"), ViewVariables(VVAccess.ReadOnly)] + public float CurrentMoodLevel; + + [DataField("currentMoodThreshold"), ViewVariables(VVAccess.ReadOnly)] + public MoodThreshold CurrentMoodThreshold; + + [DataField("lastThreshold"), ViewVariables(VVAccess.ReadOnly)] + public MoodThreshold LastThreshold; + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary CategorisedEffects = new(); + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary UncategorisedEffects = new(); + + [DataField("slowdownSpeedModifier"), ViewVariables(VVAccess.ReadWrite)] + public float SlowdownSpeedModifier = 0.75f; + + [DataField("increaseSpeedModifier"), ViewVariables(VVAccess.ReadWrite)] + public float IncreaseSpeedModifier = 1.15f; + + [DataField("increaseCritThreshold"), ViewVariables(VVAccess.ReadWrite)] + public float IncreaseCritThreshold = 1.2f; + + [DataField("decreaseCritThreshold"), ViewVariables(VVAccess.ReadWrite)] + public float DecreaseCritThreshold = 0.9f; + + [ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 CritThresholdBeforeModify; + + [DataField("moodThresholds", customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodThresholds = new() + { + { MoodThreshold.VeryVeryGood, 10.0f }, + { MoodThreshold.VeryGood, 8.0f }, + { MoodThreshold.Good, 7.0f }, + { MoodThreshold.Great, 6.0f }, + { MoodThreshold.Neutral, 5.0f }, + { MoodThreshold.NotGreat, 4.0f }, + { MoodThreshold.Bad, 3.0f }, + { MoodThreshold.VeryBad, 2.0f }, + { MoodThreshold.VeryVeryBad, 1.0f }, + { MoodThreshold.Dead, 0.0f } + }; + + [DataField("moodThresholdsAlerts", customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodThresholdsAlerts = new() + { + { MoodThreshold.Dead, AlertType.MoodDead }, + { MoodThreshold.VeryVeryBad, AlertType.VeryVeryBad }, + { MoodThreshold.VeryBad, AlertType.VeryBad }, + { MoodThreshold.Bad, AlertType.Bad }, + { MoodThreshold.NotGreat, AlertType.NotGreat }, + { MoodThreshold.Neutral, AlertType.Neutral }, + { MoodThreshold.Great, AlertType.Great }, + { MoodThreshold.Good, AlertType.Good }, + { MoodThreshold.VeryGood, AlertType.VeryGood }, + { MoodThreshold.VeryVeryGood, AlertType.VeryVeryGood }, + { MoodThreshold.Insane, AlertType.Insane } + }; + + [DataField("moodChangeValues", customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodChangeValues = new() + { + { MoodChangeLevel.None , 0.0f }, + { MoodChangeLevel.Small , 0.3f }, + { MoodChangeLevel.Medium , 0.7f }, + { MoodChangeLevel.Big , 1.0f }, + { MoodChangeLevel.Huge , 1.3f }, + { MoodChangeLevel.Large , 2f } + }; + + [DataField("healthMoodEffectsThresholds", customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary HealthMoodEffectsThresholds = new() + { + { "HealthHeavyDamage", 80f }, + { "HealthSevereDamage", 50f }, + { "HealthLightDamage", 10f }, + { "HealthNoDamage", 5f } + }; +} + +[Serializable] +public enum MoodThreshold : ushort +{ + Insane = 1, + VeryVeryBad = 2, + VeryBad = 3, + Bad = 4, + NotGreat = 5, + Neutral = 6, + Great = 7, + Good = 8, + VeryGood = 9, + VeryVeryGood = 10, + Dead = 0 +} diff --git a/Content.Server/White/Mood/MoodSystem.cs b/Content.Server/White/Mood/MoodSystem.cs new file mode 100644 index 0000000000..b9c43d1e80 --- /dev/null +++ b/Content.Server/White/Mood/MoodSystem.cs @@ -0,0 +1,415 @@ +using Content.Server.Chat.Managers; +using Content.Shared.Alert; +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 Content.Shared.White.Mood; +using Content.Shared.White.Overlays; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Timer = Robust.Shared.Timing.Timer; + +namespace Content.Server.White.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!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnMoodEffect); + SubscribeLocalEvent(OnDamageChange); + SubscribeLocalEvent(OnRefreshMoveSpeed); + SubscribeLocalEvent(OnRemoveEffect); + } + + private void OnRemoveEffect(EntityUid uid, MoodComponent component, MoodRemoveEffectEvent args) + { + if (component.UncategorisedEffects.TryGetValue(args.EffectId, out _)) + RemoveTimedOutEffect(uid, component, args.EffectId); + else + { + foreach (var (category, id) in component.CategorisedEffects) + { + if (id == args.EffectId) + { + RemoveTimedOutEffect(uid, component, args.EffectId, category); + return; + } + } + } + } + + private void OnRefreshMoveSpeed(EntityUid uid, MoodComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (component.CurrentMoodThreshold is > MoodThreshold.VeryBad and < MoodThreshold.VeryGood or MoodThreshold.Dead) + return; + + if (_jetpack.IsUserFlying(uid)) + return; + + var modifier = GetMovementThreshold(component.CurrentMoodThreshold) switch + { + -1 => component.SlowdownSpeedModifier, + 1 => component.IncreaseSpeedModifier, + _ => 1 + }; + + args.ModifySpeed(modifier, modifier); + } + + private void OnMoodEffect(EntityUid uid, MoodComponent component, MoodEffectEvent args) + { + if (!_prototypeManager.TryIndex(args.EffectId, out var prototype)) + return; + + ApplyEffect(uid, component, prototype); + } + + private void ApplyEffect(EntityUid uid, MoodComponent component, MoodEffectPrototype prototype) + { + var amount = component.CurrentMoodLevel; + + if (!component.MoodChangeValues.TryGetValue(prototype.MoodChange, out var value)) + return; + + //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) + { + if (!component.MoodChangeValues.TryGetValue(oldPrototype.MoodChange, out var oldValue)) + return; + + amount += (oldPrototype.PositiveEffect ? -oldValue : oldValue) + (prototype.PositiveEffect ? value : -value); + component.CategorisedEffects[prototype.Category] = prototype.ID; + } + } + else + { + component.CategorisedEffects.Add(prototype.Category, prototype.ID); + amount += prototype.PositiveEffect ? value : -value; + } + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromMinutes(prototype.Timeout), () => RemoveTimedOutEffect(uid, component, prototype.ID, prototype.Category)); + } + //Apply uncategorised effect + else + { + if (component.UncategorisedEffects.TryGetValue(prototype.ID, out _)) + return; + + var effectValue = prototype.PositiveEffect ? value : -value; + + component.UncategorisedEffects.Add(prototype.ID, effectValue); + amount += effectValue; + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromMinutes(prototype.Timeout), () => RemoveTimedOutEffect(uid, component, prototype.ID)); + } + + SetMood(uid, amount, component); + } + + private void RemoveTimedOutEffect(EntityUid uid, MoodComponent comp, string prototypeId, string? category = null) + { + var amount = comp.CurrentMoodLevel; + + if (category == null) + { + if (!comp.UncategorisedEffects.TryGetValue(prototypeId, out var value)) + return; + + amount += -value; + comp.UncategorisedEffects.Remove(prototypeId); + } + else + { + if (!comp.CategorisedEffects.TryGetValue(category, out var currentProtoId)) + return; + if (currentProtoId != prototypeId) + return; + if (!_prototypeManager.TryIndex(currentProtoId, out var currentProto)) + return; + if (!comp.MoodChangeValues.TryGetValue(currentProto.MoodChange, out var value)) + return; + + amount += currentProto.PositiveEffect ? -value : value; + comp.CategorisedEffects.Remove(category); + } + + SetMood(uid, amount, comp); + } + + private void OnMobStateChanged(EntityUid uid, MoodComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead && args.OldMobState != MobState.Dead) + { + SetMood(uid, component.MoodThresholds[MoodThreshold.Dead], component, true); + } + + else if (args.OldMobState == MobState.Dead && args.NewMobState != MobState.Dead) + { + ReapplyAllEffects(uid, component); + } + } + + private void ReapplyAllEffects(EntityUid uid, MoodComponent component) + { + var amount = component.MoodThresholds[MoodThreshold.Neutral]; + + foreach (var (_, protoId) in component.CategorisedEffects) + { + if (!_prototypeManager.TryIndex(protoId, out var prototype)) + return; + + if (!component.MoodChangeValues.TryGetValue(prototype.MoodChange, out var value)) + return; + + amount += prototype.PositiveEffect ? value : -value; + } + + foreach (var (_, value) in component.UncategorisedEffects) + { + amount += value; + } + + SetMood(uid, amount, component, refresh: true); + } + + private void OnInit(EntityUid uid, MoodComponent component, ComponentInit args) + { + _mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold); + if (critThreshold != null) + component.CritThresholdBeforeModify = critThreshold.Value; + + var amount = component.MoodThresholds[MoodThreshold.Neutral]; + SetMood(uid, amount, component, refresh: true); + } + + public void SetMood(EntityUid uid, float amount, MoodComponent? component = null, bool force = false, bool refresh = false) + { + if (!Resolve(uid, ref component)) + return; + + if (component.CurrentMoodThreshold == MoodThreshold.Dead && !refresh) + return; + + if (!force) + { + component.CurrentMoodLevel = Math.Clamp(amount, + component.MoodThresholds[MoodThreshold.Dead] + 0.1f, + component.MoodThresholds[MoodThreshold.VeryVeryGood]); + } + else + { + component.CurrentMoodLevel = amount; + } + + 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)) + return; + + if (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, AlertCategory.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)) + return; + + if (!_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.VeryVeryGood]; + + 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.VeryGood => 1, + <= MoodThreshold.VeryBad => -1, + _ => 0 + }; + } + + #region HealthStatusCheck + + private void OnDamageChange(EntityUid uid, MoodComponent component, DamageChangedEvent args) + { + var damage = args.Damageable.TotalDamage.Float(); + var protoId = "HealthNoDamage"; + var value = component.HealthMoodEffectsThresholds["HealthNoDamage"]; + + foreach (var threshold in component.HealthMoodEffectsThresholds) + { + if (threshold.Value <= damage && threshold.Value >= value) + { + protoId = threshold.Key; + value = threshold.Value; + } + } + + var ev = new MoodEffectEvent(protoId); + RaiseLocalEvent(uid, ev); + } + + #endregion +} + +[UsedImplicitly] +[DataDefinition] +public sealed partial class ShowMoodEffects : IAlertClick +{ + public void AlertClicked(EntityUid uid) + { + var entityManager = IoCManager.Resolve(); + var prototypeManager = IoCManager.Resolve(); + var chatManager = IoCManager.Resolve(); + + if (!entityManager.TryGetComponent(uid, out var comp)) + return; + + if (comp.CurrentMoodThreshold == MoodThreshold.Dead) + return; + + if (!entityManager.TryGetComponent(uid, out var actorComp)) + return; + + var msgStart = Loc.GetString("mood-show-effects-start"); + chatManager.ChatMessageToOne(ChatChannel.Emotes, msgStart, msgStart, EntityUid.Invalid, false, + actorComp.PlayerSession.ConnectedClient); + + foreach (var (_, protoId) in comp.CategorisedEffects) + { + if (!prototypeManager.TryIndex(protoId, out var proto)) + continue; + + if (proto.Hidden) + continue; + + SendDescToChat(proto, actorComp); + } + + foreach (var (protoId, _) in comp.UncategorisedEffects) + { + if (!prototypeManager.TryIndex(protoId, out var proto)) + continue; + + if (proto.Hidden) + continue; + + SendDescToChat(proto, actorComp); + } + } + + private void SendDescToChat(MoodEffectPrototype proto, ActorComponent comp) + { + var chatManager = IoCManager.Resolve(); + + var color = proto.PositiveEffect ? "#008000" : "#BA0000"; + var msg = $"[font size=10][color={color}]{proto.Description}[/color][/font]"; + + chatManager.ChatMessageToOne(ChatChannel.Emotes, msg, msg, EntityUid.Invalid, false, + comp.PlayerSession.ConnectedClient); + } +} diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs index 7450f585a4..a769e88355 100644 --- a/Content.Shared/Alert/AlertCategory.cs +++ b/Content.Shared/Alert/AlertCategory.cs @@ -10,6 +10,7 @@ public enum AlertCategory Breathing, Buckled, Health, + Mood, //WD-edit Internals, Stamina, Piloting, diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index f236e6fb09..7b40876df2 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -25,6 +25,20 @@ namespace Content.Shared.Alert Bleeding, BorgBattery, BorgBatteryNone, + //WD start + Insane, + VeryVeryBad, + VeryBad, + Bad, + NotGreat, + Neutral, + Great, + Good, + VeryGood, + VeryVeryGood, + MoodDead, + //WD end + CultBuffed, PilotingShuttle, Peckish, Starving, diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index c51a2fb185..cd16b0bc33 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -30,6 +30,7 @@ using Content.Shared.Verbs; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio.Systems; using Content.Shared.White.EndOfRoundStats.CuffedTime; +using Content.Shared.White.Mood; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Player; @@ -172,9 +173,15 @@ namespace Content.Shared.Cuffs _actionBlocker.UpdateCanMove(uid); if (component.CanStillInteract) + { _alerts.ClearAlert(uid, AlertType.Handcuffed); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("Handcuffed")); //WD edit + } else + { _alerts.ShowAlert(uid, AlertType.Handcuffed); + RaiseLocalEvent(uid, new MoodEffectEvent("Handcuffed")); // WD edit + } var ev = new CuffedStateChangeEvent(); RaiseLocalEvent(uid, ref ev); diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index 9c09603510..ad09db1824 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -4,6 +4,9 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Rejuvenate; +using Content.Shared.White.Mood; +using Robust.Shared.GameStates; +using Robust.Shared.Network; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -18,6 +21,7 @@ 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!; // WD edit public override void Initialize() { @@ -26,7 +30,7 @@ public sealed class HungerSystem : EntitySystem SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnRefreshMovespeed); + //SubscribeLocalEvent(OnRefreshMovespeed); WD-edit SubscribeLocalEvent(OnRejuvenate); } @@ -115,10 +119,18 @@ public sealed class HungerSystem : EntitySystem if (component.CurrentThreshold == component.LastThreshold && !force) return; - if (GetMovementThreshold(component.CurrentThreshold) != GetMovementThreshold(component.LastThreshold)) + //WD start + if (_net.IsServer) + { + var ev = new MoodEffectEvent("Hunger" + component.CurrentThreshold); + RaiseLocalEvent(uid, ev); + } + + /*if (GetMovementThreshold(component.CurrentThreshold) != GetMovementThreshold(component.LastThreshold)) { _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); - } + }*/ + //WD end 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 bd7251b943..c278b88b48 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Nutrition.Components; using Content.Shared.Stunnable; using Content.Shared.Throwing; +using Content.Shared.White.Mood; using JetBrains.Annotations; namespace Content.Shared.Nutrition.EntitySystems @@ -44,6 +45,8 @@ namespace Content.Shared.Nutrition.EntitySystems { _appearance.SetData(uid, CreamPiedVisuals.Creamed, value, appearance); } + + RaiseLocalEvent(uid, new MoodEffectEvent("Creampied")); // WD edit } 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 05a2338768..7b869a3bfb 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -6,6 +6,11 @@ using Content.Shared.Rejuvenate; using JetBrains.Annotations; using Robust.Shared.Random; using Robust.Shared.Timing; +using Content.Shared.Movement.Components; +using Content.Shared.Alert; +using Content.Shared.Movement.Systems; +using Content.Shared.Rejuvenate; +using Content.Shared.White.Mood; namespace Content.Shared.Nutrition.EntitySystems; @@ -26,7 +31,7 @@ public sealed class ThirstSystem : EntitySystem _sawmill = Logger.GetSawmill("thirst"); - SubscribeLocalEvent(OnRefreshMovespeed); + //SubscribeLocalEvent(OnRefreshMovespeed); WD-edit SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnRejuvenate); SubscribeLocalEvent(OnUnpaused); @@ -114,11 +119,13 @@ public sealed class ThirstSystem : EntitySystem private void UpdateEffects(EntityUid uid, ThirstComponent component) { - if (IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) && + //WD start + /*if (IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) && TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent)) { _movement.RefreshMovementSpeedModifiers(uid, movementSlowdownComponent); - } + }*/ + //WD end // Update UI if (ThirstComponent.ThirstThresholdAlertTypes.TryGetValue(component.CurrentThirstThreshold, out var alertId)) @@ -130,6 +137,11 @@ public sealed class ThirstSystem : EntitySystem _alerts.ClearAlertCategory(uid, AlertCategory.Thirst); } + //WD start + var ev = new MoodEffectEvent("Thirst" + component.CurrentThirstThreshold); + RaiseLocalEvent(uid, ev); + //WD end + switch (component.CurrentThirstThreshold) { case ThirstThreshold.OverHydrated: diff --git a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs index 1399b116e0..61c11be0d2 100644 --- a/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs +++ b/Content.Shared/Revolutionary/SharedRevolutionarySystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Mindshield.Components; using Content.Shared.Popups; using Content.Shared.Revolutionary.Components; using Content.Shared.Stunnable; +using Content.Shared.White.Mood; namespace Content.Shared.Revolutionary; @@ -15,8 +16,22 @@ public sealed class SharedRevolutionarySystem : EntitySystem { base.Initialize(); SubscribeLocalEvent(MindShieldImplanted); + SubscribeLocalEvent(OnInit); // WD EDIT + SubscribeLocalEvent(OnShutdown); // WD EDIT } + // WD START + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + RaiseLocalEvent(ent, new MoodRemoveEffectEvent("RevolutionFocused")); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + RaiseLocalEvent(ent, new MoodEffectEvent("RevolutionFocused")); + } + // WD END + /// /// When the mindshield is implanted in the rev it will popup saying they were deconverted. In Head Revs it will remove the mindshield component. /// diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 60d53eb16f..b841ef1c0b 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Inventory; using Content.Shared.StatusEffect; using Content.Shared.StepTrigger.Systems; using Content.Shared.Stunnable; +using Content.Shared.White.Mood; using JetBrains.Annotations; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -78,6 +79,8 @@ public sealed class SlipperySystem : EntitySystem _stun.TryParalyze(other, TimeSpan.FromSeconds(component.ParalyzeTime), true); + RaiseLocalEvent(other, new MoodEffectEvent("MobSlipped")); // WD edit + // Preventing from playing the slip sound when you are already knocked down. if (playSound) { diff --git a/Content.Shared/White/Mood/MoodEffectPrototype.cs b/Content.Shared/White/Mood/MoodEffectPrototype.cs new file mode 100644 index 0000000000..5cad886171 --- /dev/null +++ b/Content.Shared/White/Mood/MoodEffectPrototype.cs @@ -0,0 +1,31 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations; + +namespace Content.Shared.White.Mood; + +[Prototype("moodEffect")] +public sealed class MoodEffectPrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; } = default!; + + [DataField("desc", required: true)] + public string Description = string.Empty; + + [DataField("moodChange", customTypeSerializer: typeof(EnumSerializer), required: true)] + public Enum MoodChange = default!; + + [DataField("positiveEffect", required: true)] + public bool PositiveEffect; + + [DataField("timeout")] + public int Timeout; + + [DataField("hidden")] + public bool Hidden; + + //If mob already has effect of the same category, the new one will replace the old one. + [DataField("category")] + public string? Category; +} diff --git a/Content.Shared/White/Mood/MoodSerializables.cs b/Content.Shared/White/Mood/MoodSerializables.cs new file mode 100644 index 0000000000..bd1b707cd6 --- /dev/null +++ b/Content.Shared/White/Mood/MoodSerializables.cs @@ -0,0 +1,36 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.White.Mood; + +[Serializable, NetSerializable] +public enum MoodChangeLevel : byte +{ + None, + Small, + Medium, + Big, + Huge, + Large +} + +[Serializable, NetSerializable] +public sealed partial class MoodEffectEvent : EntityEventArgs +{ + public string EffectId; + + public MoodEffectEvent(string effectId) + { + EffectId = effectId; + } +} + +[Serializable, NetSerializable] +public sealed partial class MoodRemoveEffectEvent : EntityEventArgs +{ + public string EffectId; + + public MoodRemoveEffectEvent(string effectId) + { + EffectId = effectId; + } +} diff --git a/Content.Shared/White/Overlays/SaturationScaleComponent.cs b/Content.Shared/White/Overlays/SaturationScaleComponent.cs new file mode 100644 index 0000000000..489b79eab3 --- /dev/null +++ b/Content.Shared/White/Overlays/SaturationScaleComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.White.Overlays; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SaturationScaleComponent : Component +{ +} diff --git a/Resources/Locale/ru-RU/white/mood/mood-alerts.ftl b/Resources/Locale/ru-RU/white/mood/mood-alerts.ftl new file mode 100644 index 0000000000..043494dc67 --- /dev/null +++ b/Resources/Locale/ru-RU/white/mood/mood-alerts.ftl @@ -0,0 +1,22 @@ +alerts-mood-insane-name = Безумие +alerts-mood-insane-desc = В моей душе тлеют мрак и безнадежность, мир обречен на абсолютное зло. +alerts-mood-very-very-bad-name = Печально +alerts-mood-very-very-bad-desc = Я борюсь с болями и страхами, моя судьба - череда мучений и страданий. +alerts-mood-very-bad-name = Очень плохо +alerts-mood-very-bad-desc = Моя жизнь иссякла, как кровь из раны, и вокруг лишь мрак и отчаяние. +alerts-mood-bad-name = Плохо +alerts-mood-bad-desc = Силы покидают меня, и каждый день становится тяжелым испытанием. +alerts-mood-not-great-name = Нехорошо +alerts-mood-not-great-desc = Мир полон угроз и боли, и мои надежды медленно умирают. +alerts-mood-neutral-name = Нормально +alerts-mood-neutral-desc = Я продолжаю свой путь, несмотря на угрозы и лишения, ища хоть малейший свет во мраке. +alerts-mood-great-name = Неплохо +alerts-mood-great-desc = В этом мире полном страданий, я обретаю небольшое облегчение и надежду. +alerts-mood-good-name = Хорошо +alerts-mood-good-desc = Моя сила восстанавливается, и мир кажется меньшим злом и болью. +alerts-mood-very-good-name = Очень хорошо +alerts-mood-very-good-desc = Я ощущаю в себе силы и надежду на лучшие дни, несмотря на угрозы, что таятся вокруг. +alerts-mood-very-very-good-name = Великолепно +alerts-mood-very-very-good-desc = Моя душа полна света и силы, и я готов сразиться с тьмой в этом жестоком мире. +alerts-mood-dead-name = Мёртв +alerts-mood-dead-desc = Вечная пустота окутала меня, и мир больше не имеет власти над моей душой. diff --git a/Resources/Locale/ru-RU/white/mood/mood.ftl b/Resources/Locale/ru-RU/white/mood/mood.ftl new file mode 100644 index 0000000000..b9619035ea --- /dev/null +++ b/Resources/Locale/ru-RU/white/mood/mood.ftl @@ -0,0 +1 @@ +mood-show-effects-start = [font size=12]Настроение:[/font] diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 1e72376048..a97479ef81 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -6,6 +6,7 @@ order: - category: Health - alertType: Bleeding + - category: Mood # WD edit - category: Stamina - alertType: SuitPower - category: Internals diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 9652da27d4..4752e6a94c 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -234,6 +234,7 @@ - type: ExaminableClothes - type: CharacterInformation - type: Penetrated + - type: Mood - type: entity save: false diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index e286dcb7a4..f6f30fdd4e 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -99,3 +99,8 @@ id: Cataracts kind: source path: "/Textures/Shaders/cataracts.swsl" + +- type: shader + id: SaturationScale + kind: source + path: "/Textures/Shaders/White/saturationscale.swsl" diff --git a/Resources/Prototypes/White/Alerts/alerts.yml b/Resources/Prototypes/White/Alerts/alerts.yml new file mode 100644 index 0000000000..b04ec45e4f --- /dev/null +++ b/Resources/Prototypes/White/Alerts/alerts.yml @@ -0,0 +1,110 @@ +# Moods +- type: alert + id: Insane + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood_insane + name: alerts-mood-insane-name + description: alerts-mood-insane-desc + +- type: alert + id: VeryVeryBad + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood1 + name: alerts-mood-very-very-bad-name + description: alerts-mood-very-very-bad-desc + +- type: alert + id: VeryBad + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood2 + name: alerts-mood-very-bad-name + description: alerts-mood-very-bad-desc + +- type: alert + id: Bad + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood3 + name: alerts-mood-bad-name + description: alerts-mood-bad-desc + +- type: alert + id: NotGreat + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood4 + name: alerts-mood-not-great-name + description: alerts-mood-not-great-desc + +- type: alert + id: Neutral + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood5 + name: alerts-mood-neutral-name + description: alerts-mood-neutral-desc + +- type: alert + id: Great + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood6 + name: alerts-mood-great-name + description: alerts-mood-great-desc + +- type: alert + id: Good + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood7 + name: alerts-mood-good-name + description: alerts-mood-good-desc + +- type: alert + id: VeryGood + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood8 + name: alerts-mood-very-good-name + description: alerts-mood-very-good-desc + +- type: alert + id: VeryVeryGood + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood9 + name: alerts-mood-very-very-good-name + description: alerts-mood-very-very-good-desc + +- type: alert + id: MoodDead + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/White/Interface/Alerts/mood.rsi + state: mood_happiness_bad + name: alerts-mood-dead-name + description: alerts-mood-dead-desc diff --git a/Resources/Prototypes/White/Mood/generic_negativeEffects.yml b/Resources/Prototypes/White/Mood/generic_negativeEffects.yml new file mode 100644 index 0000000000..7deab7f4f6 --- /dev/null +++ b/Resources/Prototypes/White/Mood/generic_negativeEffects.yml @@ -0,0 +1,51 @@ +- type: moodEffect + id: Handcuffed + desc: "Кажется мои выходки кто-то заметил." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: false + +- type: moodEffect + id: Suffocating + desc: "НЕ.. МОГУ... ДЫШАТЬ..." + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: false + timeout: 1 + +- type: moodEffect + id: OnFire + desc: "ГОРЮ!!!" + moodChange: enum.MoodChangeLevel.Big + positiveEffect: false + +- type: moodEffect + id: Creampied + desc: "Меня окремили. На вкус как пирог." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: false + timeout: 3 + +- type: moodEffect + id: MobSlipped + desc: "Опять поскальзываюсь. Надо быть аккуратней." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: false + timeout: 3 + +- type: moodEffect + id: MobVomit + desc: "Меня только что вырвало. Мерзость." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: false + timeout: 8 + +- type: moodEffect + id: MobLowPressure + desc: "Меня сейчас разорвёт наружу!" + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: false + +- type: moodEffect + id: MobHighPressure + desc: "На меня оказывается огромное давление!" + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: false diff --git a/Resources/Prototypes/White/Mood/generic_positveEffects.yml b/Resources/Prototypes/White/Mood/generic_positveEffects.yml new file mode 100644 index 0000000000..71fd30dc0b --- /dev/null +++ b/Resources/Prototypes/White/Mood/generic_positveEffects.yml @@ -0,0 +1,52 @@ +- type: moodEffect + id: BeingHugged + desc: "Обнимашки - круто." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: true + timeout: 2 + +- type: moodEffect + id: ArcadePlay + desc: "Я весело поиграл в интересную аркаду." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: true + timeout: 8 + +- type: moodEffect + id: GotBlessed + desc: "Меня благословили." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: true + timeout: 8 + +- type: moodEffect + id: PetAnimal + desc: "Животные такие милые! Не могу перестать их гладить!" + moodChange: enum.MoodChangeLevel.Small + positiveEffect: true + timeout: 5 + +- type: moodEffect + id: SavedLife + desc: "Так приятно спасать чью-то жизнь." + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: true + timeout: 8 + +- type: moodEffect + id: TraitorFocused #Used for traitors to boost their goals completion. + desc: "У меня есть цель, и я добьюсь её, во что бы то ни стало!" + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: true + +- type: moodEffect + id: RevolutionFocused #Used for revolution + desc: "СЛАВА РЕВОЛЮЦИИ!!!" + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: true + +- type: moodEffect + id: CultFocused + desc: "Знаю правду, славим великого!" + moodChange: enum.MoodChangeLevel.Big + positiveEffect: true diff --git a/Resources/Prototypes/White/Mood/moodEffects_needs.yml b/Resources/Prototypes/White/Mood/moodEffects_needs.yml new file mode 100644 index 0000000000..03f52858c8 --- /dev/null +++ b/Resources/Prototypes/White/Mood/moodEffects_needs.yml @@ -0,0 +1,87 @@ +#Hunger +- type: moodEffect + id: HungerOverfed + desc: "Во мне столько жира..." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: false + category: "Hunger" + +- type: moodEffect + id: HungerOkay + desc: "Мой желудок полон!" + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: true + category: "Hunger" + +- type: moodEffect + id: HungerPeckish + desc: "Хочу есть." + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: true + category: "Hunger" + +- type: moodEffect + id: HungerStarving + desc: "Голодаю!" + moodChange: enum.MoodChangeLevel.Big + positiveEffect: false + category: "Hunger" + +#Thirst +- type: moodEffect + id: ThirstOverHydrated + desc: "СЛИШКОМ МНОГО ВОДЫ..." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: false + category: "Thirst" + +- type: moodEffect + id: ThirstOkay + desc: "Не хочу пить." + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: true + category: "Thirst" + +- type: moodEffect + id: ThirstThirsty + desc: "Хочу пить." + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: false + category: "Thirst" + +- type: moodEffect + id: ThirstParched + desc: "ВОДЫ!" + moodChange: enum.MoodChangeLevel.Big + positiveEffect: false + category: "Thirst" + +#Health +- type: moodEffect + id: HealthNoDamage + desc: "Чувствую себя лишённым боли." + moodChange: enum.MoodChangeLevel.None + positiveEffect: true + hidden: true + category: "Health" + +- type: moodEffect + id: HealthLightDamage + desc: "Мои ссадины жгутся." + moodChange: enum.MoodChangeLevel.Small + positiveEffect: false + category: "Health" + +- type: moodEffect + id: HealthSevereDamage + desc: "Сильная боль пронзает меня." + moodChange: enum.MoodChangeLevel.Medium + positiveEffect: false + category: "Health" + +- type: moodEffect + id: HealthHeavyDamage + desc: "Агония гложет мою душу!" + moodChange: enum.MoodChangeLevel.Large + positiveEffect: false + category: "Health" diff --git a/Resources/Textures/Shaders/White/saturationscale.swsl b/Resources/Textures/Shaders/White/saturationscale.swsl new file mode 100644 index 0000000000..9829e20762 --- /dev/null +++ b/Resources/Textures/Shaders/White/saturationscale.swsl @@ -0,0 +1,12 @@ +uniform highp float saturation; // Between 0 and 2; +uniform sampler2D SCREEN_TEXTURE; + +void fragment() { + highp vec4 color = texture(SCREEN_TEXTURE, UV); + + highp float brightness = (color.r + color.g + color.b) / 3.0; + + color.rgb = mix(vec3(brightness), color.rgb, saturation); + + COLOR = color; +} diff --git a/Resources/Textures/White/Interface/Alerts/mood.rsi/meta.json b/Resources/Textures/White/Interface/Alerts/mood.rsi/meta.json new file mode 100644 index 0000000000..c40ea9334e --- /dev/null +++ b/Resources/Textures/White/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/White/Interface/Alerts/mood.rsi/mood1.png b/Resources/Textures/White/Interface/Alerts/mood.rsi/mood1.png new file mode 100644 index 0000000000000000000000000000000000000000..ae1e1386db42e7f143b976c3fcce346d4d3a3517 GIT binary patch literal 606 zcmV-k0-^nhP)BrlP ziWZjAl}<8%dJo@Ur~2_634I{S0N!3)+b|ip%KE-PztmiHp7~?^4?>WEJl}W=z+@w{ zDqeS5If0L%f4pmoWe5alOHM#)@$60wLJ~?MB*+zz)=tyvqVz1FGhs07P4&Bl#zQQxe0G>E>+OaF@aW0WUdKE_CV?>ABY9uI>v9rAWc{%h#WD9RiW<#G%;OF z+5Iph=0ed*ZqH%?SdH<3c7+hqRogyz<>d4@yazDp;lYvJl3WP2Af`C!oZj2;{NgN| s92r4NrSKsxEr6p8VNd}f@aF=40HLZPfvz_;vj6}907*qoM6N<$f@&ZK+W-In literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/Alerts/mood.rsi/mood2.png b/Resources/Textures/White/Interface/Alerts/mood.rsi/mood2.png new file mode 100644 index 0000000000000000000000000000000000000000..41be928f0254eaa036c473b17ad6538fa4b4a418 GIT binary patch literal 517 zcmV+g0{Z=lP)_EcC|}Nh;7tbx>nat;sy4?POev<`j*Q1cJZrHJshtO0F*^z%qoAY#h61F#@3pne$a^DzHiI}_J86-90U85P6rL)P zAWkap4-Qf);QcdiK%>5nb#vIs-vw$E*7Hj-w+X+*6aAr&_f`OP2uE~6wd3^>w+2ul zmSHMy0rGEJ^@9NjV#f|&$lKSC-UTt}$E5GSz-Rzt*csp(Rm}F9FW+)~00000NkvXX Hu0mjf80z1L literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/Alerts/mood.rsi/mood3.png b/Resources/Textures/White/Interface/Alerts/mood.rsi/mood3.png new file mode 100644 index 0000000000000000000000000000000000000000..179991e1984a7a5a9620c9d286387e53308358dc GIT binary patch literal 526 zcmV+p0`dKcP)u0$23LtSrM`ll_ddP_oxXyW z)MCXWU?SnD$TcLaJ9{_B-rm0%a!O2?3T8L={pOotj&nG2oB;kWz~lW$yWd~mXX;M? zNI1Ic6%Vq!6hs7o;v_u29RRpevyJ_(9`5wZ+Xowh4M5_#ur8jDM}zdk=LWR7%XNYU zP!NYqyyo%tj)4Fs1lxfyI4dT|feHz{t72XkS^=o~%VEEKSfhb=pO%J@F%Q4E2Q+X( z3^2w1f-`h}p5_EPY5@lIA4T$6@MgfhgAie$K(E?eXaw;4k0SHs&VC*b0oGqhenxo$ z!F*(LZF@n~0urf=tl0VY&cbm+1c27A7fDGJBfQ>ME(sO@iqeA%0*lG>^#t8MGciRA$HzOJuzJ#U;B2>`heF3OeKiLa;+?dMd_0-*3+ zK#}~X=d}UzN_Du1d>8U%V}#KGHz%o{4M2gFp(w3ecPWuLxA$TIav_3VYWYpS@ID}M zN%bNC7Xo6iYt}9wojNgzxBEMS5Wfv`fefUS7skQ=2M!hN44|tx0URCR2Q)C{eyl9z Q761SM07*qoM6N<$g5Epg+yDRo literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/Alerts/mood.rsi/mood4.png b/Resources/Textures/White/Interface/Alerts/mood.rsi/mood4.png new file mode 100644 index 0000000000000000000000000000000000000000..4ea7d701171961ee9650101bf4f0d5f2850952b6 GIT binary patch literal 417 zcmV;S0bc%zP)3KoCVSvDMZ@JHrhqwYK5_9>ZgK04r-Py#cYq z#8wV~j_b$GvI~C*(U>@u%uf7oWDm`|=bE4Z>`{jV+2-)$nW?@1IziaqV{~Epp3`jk%i_ZTCx(fCN&{zBcz7B8)M(FbqYD#L^2?9bjN!L!5=%a0YI}Sx~nQ40Nct zKu4%keJO;b`7RYzrFsDpr~lcpUDnCOwgAi*V0${h?B9>;W%U;T2&ehMyRHRM0f-2M z_s8AGUTOJgAZP%HyTUN6mr4iANN=C3}uLG8;1JxBk zB<91k>mY!>2Wv#t0CuWblQTa9$lY~y;2%QL5mgeddMUzD=74FvK=p_A(5^9y0w4>O zr@up)>E=a|jQTF{MH)^$7d;K&%YxgI=mJgjGyu#lI$zt_qnb4u09i0Xp2~-~CVCZs z4Iv^jW>JI?hL7f%>en#@0OGD|ZSenrp@O3UjAaYJe~Vzn1e=<0}9V?hQK+ zUoXGnyMLhsQ2~fJgvX=q)3db^qk*6SAZ`o8;9n~tD1eZ-4WfadE6`8cY-kaC*q*4^ zAONyYk`@ghv1vYrsY1QZfh@a3X?8B|Uv|OfSo}))HPxa3u>LZ!Z*m(*#_WhbcnjcK ze`G3xQV~2e{eT_hW8**{sFnaCp%`YnmI8P;u|`x4V3wLS8S^oKtey0M?-0U{@WIJp zRLMCYLOv1SGr)x`2!JYNj{WUhcPT%UJp}+WGj(;k>0`5}0qiQ^41kdI2*fqn(*Q8D z>0GCx3B?)>fGXg>Ph69|3c!RA5qaHWdqNm7nmN_4V+a7m_G_*5_dr>}$^fdeKfuoc YZdR%A(l*6QTfpY>H z7gQDnshk422p%>M)*IV91BM8Rk+8+ivhSN8@0v&YXS)O3F2LLT<;VX1@xFMt3IK$k zW zUI0WNg)(urD4b9}8;P=@ehSjh@BRHx6LyUIOa)M^ziNR=s}I6FhchrJ0O~JU7;~h3 z&>H7v~CHceV{4APYtPSh=GMDx=7z_#jZc1m)cdjjG^v^m094|m}Keq03HhonG0bs+O(e>^+ zjjbX@AJdm8kSG8cXTz6wZ#%y&X|M%}iG~<}jaLn`cpV@&bZe!N5CQZbCRPo5t;VY$ zfavRqeSdroLWls2F?5mSB-QzP?`JC88w6nQyGhu4RyUbbF_NVs2mtRp&hzqVQ*5;& zY+4soK~xv4va4VbVh`I_nd0GjgehT8SEnu4+phJ*4O zzHaaWwB_F?>~Yg%jXw`jHQ<{Ak{J;Vfc!%{g(Vc;ft2>Qf)@a}Lk`yizEMTCITzaC z>})}yX(*ZlFw|zG?Ki9&6P}(y0Gw<%;Z{jK4Q+Wx_)R(&-4R4Zh*~JJUajS-Vjy_0 z^g{A!836K#rdoA=n3V@?f&~N3tHNnPX zypIBaxF9j(F=qLUfMK*8sy_B|8ht5c1BeDd(56KB;cmrMkbQN$e@spQf2ALkW$ir! Sjh&(Z0000$~*PvWGCxBVq+?zm501&6}(e=frd)>G;3nvx`H2}q9VFs^vMhF!^v$&G#Y?c&{ zK`am|0GRBsZk#s!u=n+8soGlvVD<;6$7%rV?c15E3Pk{9A3mYGdwgHNm@sj@6s9TY zpI?e5Tn~FJ)hYsDw*AK@@rQ3Sv_kE7O2^grhb zP4+4P1iU~WaNJVC@zjQT3Rr5mJyl*#g0}o>0Ge?<2(aI=h#&7Edu{pE0F0dLDQ?N$ z1b~d{xkw)fhCy*l_9g%XCjy5I-t%G4WjuI(^rMszSWyk0c3b@{k3lw#-Ti0uX8_&+ XH<#j3?9i*P00000NkvXXu0mjf{rAj+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/Alerts/mood.rsi/mood_happiness_bad.png b/Resources/Textures/White/Interface/Alerts/mood.rsi/mood_happiness_bad.png new file mode 100644 index 0000000000000000000000000000000000000000..4ed8f4d68f80b53ca8cb313f183fdd138a84dbcb GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4jBOuH;Rhv&5DEP?J z#WAE}&f96;e1{cyT%uQAZ#{gVF}m_$M$*TR0iPUCt#NXg*`6z7x4!!;!y0Y@ryq|j zp8v69Js@@ayAB6y!!ymLcVl`AqYpdmV7ziiDq4XtQ6YxGwwU{n!=3*k2D`%z19#N3 ztl4raJ4wlv@k`ImQoV+#%Z{1*qVH#1Jy^iIppbvj(yd(ryYHT`;Z@kF{gtss%vkbv zWoyKK{tS_lbP0l+XkKwKH~8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/Alerts/mood.rsi/mood_happiness_good.png b/Resources/Textures/White/Interface/Alerts/mood.rsi/mood_happiness_good.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9943a301554eb2ecee8c1ac8a5ed1ff986d340 GIT binary patch literal 343 zcmV-d0jU0oP)0D^ zjan@S>6h$Y1i(dGuBN5;z!4Gw&|}~i50@tWVgRld1jME7rv{)|Kr<=;La1GyWHpG+ p8nM(zKn%`#md_e416ZXS;0;#4pQJXDRW|?t002ovPDHLkV1n@-dIJCe literal 0 HcmV?d00001 diff --git a/Resources/Textures/White/Interface/Alerts/mood.rsi/mood_insane.png b/Resources/Textures/White/Interface/Alerts/mood.rsi/mood_insane.png new file mode 100644 index 0000000000000000000000000000000000000000..b2407bbdad8de3faed98027537ff2fe6657450cc GIT binary patch literal 3392 zcmY+HX*|^J8pr>$$QY#=Yh{@+*&R~C2s4Ah;2Fz9*%?u`u~!&jh%AM%v^P?7#Ky&Y6syL61*ONCteuUgF#}p+;xDKNdtVEF`n}`J_0g?_^DBYt^oY)Y z9vq6r_R&qM%Y=KglsbpGey0Oh9bsq$0QXp}{qm0-0EjAUkP?56vO zJ|vn*rf>7@<@Em&I+M9-Mmm;>$GyK%QtXkbU|v*D1b9k2vjrXm3yJXWdHo^%@~}i+ zy1|2?XVQSWYHn;!Pvo3es!aR#OG@zh=hwIgdaje6ZUx1I_X;8Cj)iQH-Re=5i|OqxeQA zO`2gCR{xQyT#?6`xX{m?9j_t})qQn)M^-%dC*iKpC{iHPF3<2j<((mWbFeRjsSRt| z?la^~L>k>STH@l2dRCzMmTLaK%TNA+-t2h5KIq-AXP30 zV_Y?IQanKnLEc;8?&5arM*D%;F=RYV7tB}*_bei3Px9#LC(3>##U`57|NK?0(m7dK z?OM?9duawYPcIRea;6vZs;y!~A{V|!9qWmNC$lBST?gAcHNX+;tFK3Oz$X7_JB@&@G?gv?C%vv53?iWU<2GtFylcH26e7Ky`=2?EC3z#-Z z+S6MbbG+0O>FnwdjwU1{EhZp2sOFzT^p8;?zRiYZ+cb&4$^9|OX+W}4DIvHk`nEg5 zt{ga7Y}Zu?)|vJ1F`#y1AVRplvE!uuV2>jz9083<0xEA{)EaJpGbNFkA3c*+Fy#F zT+cpe*|py<|A=Nn~BQeBpj8ZY22Y+Y1zfj6P_Nf zY2M->w=UJUl~N>&5wewq4e` zW_k3>lWrTgenT!oaufN&HS2Xdn>}7$f{`))pY=PZx#u@!lGKw__cFLMJ?ii42FKDpGEm=_b7Mdx6?Q4Fo9Pt2 zqON}?6W~~HPj!XXGzYzWWdRKVkrFl8$Xk<$?hcrek7@~v8($%ZB(>Wacd>)URZ^}2 z7E|{#sM}c%#CG^FSGqjoqST{EWY-Dss|_56su)`LO&a$&XzPONNGr;V)3USd<&mmI ziJyEYaQz|;C#;}Gn@2n zEq?D@kFt9$^taL6buar$fR>)8^L)gO3de+MhPd}2{|L~Zi4V1551aiRg`N6#bk0r{qQQffE&RzRNU+RHHA# z=$J~eoBFVZzqxILjrR&#^pTplZY`l*HD#%JjKh*4ya3PB1)s^Sp z&7RaKTWrpK{0;X-%cStEtAQO6y>ADPS5g!!I}D~1{sVgLm5A%i8H==V|F{;zb?#cC z+4|2~J1hBy^~6padaW9(?*>b3n7JY0=2-UZ&SJ_%8ot~Lv;im`(o6Zx*}!KUfbo|% zq;DTFn91tdovZUoD`L#pA}E8WG_n#N)fvIyc;I@9*7M;zKLx z{p8;_up~i}pa5g+Sqf5xO)iQeAPL8E4!WjV8;8DeFU8RPPtytQCv?R!8<%Er0uBhCF&pG_{&jZ&06H8@6uA|6+pELOX z#wS{xg;Cnz;$KgWK7RRnL#*m^X*}GA% z9Qut`hi??xL|aTq&rzb`NQd|Qrc!(2YIN@bX*nqj{Qzov(K9Z<6kYlPWyp;P&Orr= zD83N~O%-tM<*fxrigGP}x=A4_Y6Ewdh{S~<6{l!psgbpOm?!a(G<|B}g@jx>;!FmC zbACc3$2`N4O=TI0{|CHC*f{3D;L)?gk-yr`VXI9(ge&<7QPJHco8z5&@>6vk{iIzU zn=&YZWO8Lwf0ZlnwcSXot$Z*rBi-tH?Gg$eif1?BvKGxN<7=0;%tqp67I6lx4ggJ% z<7>o8mj9c7`fPo_t>nPgmgS-#$!+X4IWqjcy+*|b9($CUu#f6;cEimgIa=o2uWb7% zKU<{!)fjke$EI5bsx&(*)E3!yXoEp#@#o)u+XK;LcD5-8_$QVckkJPI_YzAfI>mtV3r{q!(@*ea zFpF9F;h+-~G=7tbZf`3kXf+uOjlGYWFGH{Hxbv?+TXz(76&?<|)%okA^eOPHBVq}3 zD!=g@_4sp%U(JalrTZj28=SyKdh2Fy=U+$thmry4(Eetj% z{wTOOHfpZ}2eLZt(({sMR(D#gR)k%zuB1bC#})kW1za#0%{vpDRBvfFWX|zwLq%i(S#(_mIbC7^5u>8uoy7dOk?O{Xe09lh zPYE(P_DS(9?F|Mcrn7k(v09hZu$jnMnc&mcB9DvQMIe{YEj@&ZUk#)q0-e~Xi!G5Z zP$P_7{dlp}JD%yq)l>6D9F6}Oh1u#a2_nSasFmhnlLlK7fB3%y{vF5yu(i#kvSQA< R*6x=Gu(q%xJ~X4G{2MkGU>N`a literal 0 HcmV?d00001