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 00000000000..5b64b609894 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png differ 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 00000000000..03fe0f702f1 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png differ 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 00000000000..2974e7eb647 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png new file mode 100644 index 00000000000..a9ab22932a8 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png differ 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 00000000000..b9b7e229776 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png differ 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 00000000000..158bf2aace1 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png differ 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 00000000000..e69847314e4 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png differ 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 00000000000..da80c1ba046 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png new file mode 100644 index 00000000000..067fda63dcd Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png differ 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 00000000000..4cc69479281 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png new file mode 100644 index 00000000000..dbca2ba1dc4 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png differ 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 00000000000..2b563f4af09 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png differ diff --git a/Resources/Textures/Shaders/saturationscale.swsl b/Resources/Textures/Shaders/saturationscale.swsl new file mode 100644 index 00000000000..9829e207629 --- /dev/null +++ b/Resources/Textures/Shaders/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; +}