mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-18 22:18:52 +03:00
# Description Implements the softcrit functionality. Similiar to critical state but spessmen will be able to communicate and crawl around, but not pick up items. Also supports configuring what is and isn't allowed in different MobStates (per mob prototype): you can enable picking up items while in softcrit so people can pick up their lasgun and continue shooting after taking a 40x46mm to their ass cheeks from the guest nukies while being dragged to safety.  <details> <summary><h1>Technical details</h1></summary> New prototype type: "mobStateParams" (`MobStateParametersPrototype`) Used to specify what can and can't be done when in a certain mobstate. Of note that they are not actually bound to any `MobState` by themselves. To assign a params prototype to a mobstate, use `InitMobStateParams` in `MobStateComponent`. It has to be a prototype because if I just did something akin to `Dictionary<MobState, Dictionary<string, bool>>`, you'd have to check the parent and copy every flag besides the one you wish to modify. That is, if I understand how the prototype system works correctly, which I frankly doubt. <!-- Working on softcrit made me hate prototypes. --> MobStateComponent now has: - `Dictionary<string, string> InitMobStateParams`, for storing "mobstate - parameter prototype" pairs. `<string, string>` because it has to be editable via mob prototypes. Named "mobStateParams" for mob prototypes. - `public Dictionary<MobState, MobStateParametersPrototype> MobStateParams` for actually storing the params for each state - `public Dictionary<MobState, MobStateParametersOverride> MobStateParamsOverrides` for storing overrides. `MobStateParametersOverride` is a struct which mirrors all `MobStateParametersPrototype`'s fields, except they're all nullable. This is meant for code which wants to temporarily override some setting, like a spell which allows dead people to talk. This is not the best solution, but it should do at first. A better option would be tracking each change separately, instead of hoping different systems overriding the same flag will play nicely with eachother. - a shitton of getter methods TraitModifyMobState now has: - `public Dictionary<string, string> Params` to specify a new prototype to use. - Important note: All values of `MobStateParametersPrototype` are nullable, which is a hack to support `TraitModifyMobState`. This trait takes one `MobStateParametersPrototype` per mobstate and applies all of its non-null values. This way, a params prototype can be created which will only have `pointing: true` and the trait can apply it (e.g. to critstate, so we can spam pointing while dying like it's a game of turbo dota) - The above is why that wall of getters exists: They check the relevant override struct, then the relevant prototype. If both are null, they default to false (0f for floats.) The only exception is OxyDamageOverlay, because it's used both for oxy damage overlay (if null) and as a vision-limiting black void in crit.. MobStateSystem now has: - a bunch of new "IsSomething"/"CanDoSomething" methods to check the various flags, alongside rewritten old ones. -  lookin ahh predicate factory </details> --- # TODO done: - [x] Make proper use of `MobStateSystem.IsIncapacitated()`. done: some checks were changed, some left as they did what was (more or less) intended. <details>Previous `IsIncapacitated()` implementation simply checked if person was in crit or dead. Now there is a `IsIncapacitated` flag in the parameters, but it's heavily underutilized. I may need some help on this one, since I don't know where would be a good place to check for it and I absolutely will not just scour the entire build in search for them. </details> - [x] Separate force-dropping items from being downed done: dropItemsOnEntering bool field. If true, will drop items upon entering linked mobstate. - [x] Don't drop items if `ForceDown` is true but `PickingUp` is also true. done: dropItemsOnEntering bool field. If true, will drop items upon entering linked mobstate. - [x] Actually check what are "conscious attempts" are used for done: whether or not mob is conscious. Renamed the bool field accordingly. - [x] Look into adding a way to make people choke "slowly" in softcrit as opposed to choking at "regular speed" in crit. Make that into a param option? Make that into a float so the speed can be finetuned? done: `BreathingMultiplier` float field added. <details> 1f is regular breathing, 0.25 is "quarter-breathing". Air taken is multiplied by `BreathingMultiplier` and suffocation damage taken (that is dealt by RespiratorSystem, not all oxy damage) is multiplied by `1-BreathingMultiplier`. </details> - [x] make sure the serializer actually does its job done: it doesn't. Removed. - [x] Make an option to prohibit using radio headsets while in softcrit done: Requires Incapacitated parameter to be false to be able to use headset radio. - [x] Make sure it at least compiles not done: - [ ] probably move some other stuff to Params if it makes sense. Same thing as with `IsIncapacitated` though: I kinda don't want to, at least for now. --- <details><summary><h1>No media</h1></summary> <p> :p </p> </details> --- # Changelog 🆑 - add: Soft critical state. Crawl to safety, or to your doom - whatever is closer. --------- Signed-off-by: RedFoxIV <38788538+RedFoxIV@users.noreply.github.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit 9a357c1774f1a783844a07b5414f504ca574d84c)
469 lines
18 KiB
C#
469 lines
18 KiB
C#
using Content.Server.Chat.Managers;
|
|
using Content.Server.Popups;
|
|
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.Mood;
|
|
using Content.Shared.Overlays;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Traits.Assorted.Components;
|
|
using JetBrains.Annotations;
|
|
using Robust.Shared.Prototypes;
|
|
using Timer = Robust.Shared.Timing.Timer;
|
|
using Robust.Server.Player;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Configuration;
|
|
using Content.Shared.CCVar;
|
|
|
|
namespace Content.Server.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 PopupSystem _popup = default!;
|
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
|
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<MoodComponent, ComponentStartup>(OnInit);
|
|
SubscribeLocalEvent<MoodComponent, ComponentShutdown>(OnShutdown);
|
|
SubscribeLocalEvent<MoodComponent, MobStateChangedEvent>(OnMobStateChanged);
|
|
SubscribeLocalEvent<MoodComponent, MoodEffectEvent>(OnMoodEffect);
|
|
SubscribeLocalEvent<MoodComponent, DamageChangedEvent>(OnDamageChange);
|
|
SubscribeLocalEvent<MoodComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMoveSpeed);
|
|
SubscribeLocalEvent<MoodComponent, MoodRemoveEffectEvent>(OnRemoveEffect);
|
|
}
|
|
|
|
private void OnShutdown(EntityUid uid, MoodComponent component, ComponentShutdown args) =>
|
|
_alerts.ClearAlertCategory(uid, component.MoodCategory);
|
|
|
|
private void OnRemoveEffect(EntityUid uid, MoodComponent component, MoodRemoveEffectEvent args)
|
|
{
|
|
if (!_config.GetCVar(CCVars.MoodEnabled))
|
|
return;
|
|
|
|
if (component.UncategorisedEffects.TryGetValue(args.EffectId, out _))
|
|
RemoveTimedOutEffect(uid, args.EffectId);
|
|
else
|
|
foreach (var (category, id) in component.CategorisedEffects)
|
|
if (id == args.EffectId)
|
|
{
|
|
RemoveTimedOutEffect(uid, args.EffectId, category);
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void OnRefreshMoveSpeed(EntityUid uid, MoodComponent component, RefreshMovementSpeedModifiersEvent args)
|
|
{
|
|
if (!_config.GetCVar(CCVars.MoodEnabled)
|
|
|| 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)
|
|
|| !_config.GetCVar(CCVars.MoodEnabled)
|
|
|| !_prototypeManager.TryIndex<MoodEffectPrototype>(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<MoodEffectPrototype>(oldPrototypeId, out var oldPrototype))
|
|
return;
|
|
|
|
// Don't send the moodlet popup if we already have the moodlet.
|
|
if (!component.CategorisedEffects.ContainsValue(prototype.ID))
|
|
SendEffectText(uid, prototype);
|
|
|
|
if (prototype.ID != oldPrototype.ID)
|
|
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));
|
|
}
|
|
// Apply uncategorised effect
|
|
else
|
|
{
|
|
if (component.UncategorisedEffects.TryGetValue(prototype.ID, out _))
|
|
return;
|
|
|
|
var moodChange = prototype.MoodChange * eventModifier + eventOffset;
|
|
if (moodChange == 0)
|
|
return;
|
|
|
|
// Don't send the moodlet popup if we already have the moodlet.
|
|
if (!component.UncategorisedEffects.ContainsKey(prototype.ID))
|
|
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)
|
|
{
|
|
if (prototype.Hidden)
|
|
return;
|
|
|
|
_popup.PopupEntity(prototype.Description, uid, uid, (prototype.MoodChange > 0) ? PopupType.Medium : PopupType.MediumCaution);
|
|
}
|
|
|
|
private void RemoveTimedOutEffect(EntityUid uid, string prototypeId, string? category = null)
|
|
{
|
|
if (!TryComp<MoodComponent>(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<MoodEffectPrototype>(currentProtoId))
|
|
return;
|
|
comp.CategorisedEffects.Remove(category);
|
|
}
|
|
|
|
ReplaceMood(uid, prototypeId);
|
|
RefreshMood(uid, comp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Some moods specifically create a moodlet upon expiration. This is normally used for "Addiction" type moodlets,
|
|
/// such as a positive moodlet from an addictive substance that becomes a negative moodlet when a timer ends.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Moodlets that use this should probably also share a category with each other, but this isn't necessarily required.
|
|
/// Only if you intend that "Re-using the drug" should also remove the negative moodlet.
|
|
/// </remarks>
|
|
private void ReplaceMood(EntityUid uid, string prototypeId)
|
|
{
|
|
if (!_prototypeManager.TryIndex<MoodEffectPrototype>(prototypeId, out var proto)
|
|
|| proto.MoodletOnEnd is null)
|
|
return;
|
|
|
|
var ev = new MoodEffectEvent(proto.MoodletOnEnd);
|
|
EntityManager.EventBus.RaiseLocalEvent(uid, ev);
|
|
}
|
|
|
|
private void OnMobStateChanged(EntityUid uid, MoodComponent component, MobStateChangedEvent args)
|
|
{
|
|
if (!_config.GetCVar(CCVars.MoodEnabled))
|
|
return;
|
|
|
|
if (args.IsDead() && !args.WasDead())
|
|
{
|
|
var ev = new MoodEffectEvent("Dead");
|
|
RaiseLocalEvent(uid, ev);
|
|
}
|
|
else if (args.WasDead() && !args.IsDead())
|
|
{
|
|
var ev = new MoodRemoveEffectEvent("Dead");
|
|
RaiseLocalEvent(uid, ev);
|
|
}
|
|
RefreshMood(uid, component);
|
|
}
|
|
|
|
// <summary>
|
|
// Recalculate the mood level of an entity by summing up all moodlets.
|
|
// </summary>
|
|
private void RefreshMood(EntityUid uid, MoodComponent component)
|
|
{
|
|
var amount = 0f;
|
|
|
|
foreach (var (_, protoId) in component.CategorisedEffects)
|
|
{
|
|
if (!_prototypeManager.TryIndex<MoodEffectPrototype>(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 (!_config.GetCVar(CCVars.MoodEnabled))
|
|
return;
|
|
|
|
if (_config.GetCVar(CCVars.MoodModifiesThresholds)
|
|
&& TryComp<MobThresholdsComponent>(uid, out var mobThresholdsComponent))
|
|
{
|
|
if(_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, mobThresholdsComponent))
|
|
component.CritThresholdBeforeModify = critThreshold.Value;
|
|
if(_mobThreshold.TryGetThresholdForState(uid, MobState.SoftCritical, out var softCritThreshold, mobThresholdsComponent))
|
|
component.SoftCritThresholdBeforeModify = softCritThreshold.Value;
|
|
}
|
|
EnsureComp<NetMoodComponent>(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;
|
|
|
|
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<NetMoodComponent>(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, component.MoodCategory);
|
|
|
|
component.LastThreshold = component.CurrentMoodThreshold;
|
|
}
|
|
|
|
private void RefreshShaders(EntityUid uid, int modifier)
|
|
{
|
|
if (modifier == -1)
|
|
EnsureComp<SaturationScaleOverlayComponent>(uid);
|
|
else
|
|
RemComp<SaturationScaleOverlayComponent>(uid);
|
|
}
|
|
|
|
private void SetCritThreshold(EntityUid uid, MoodComponent component, int modifier)
|
|
{
|
|
if (!_config.GetCVar(CCVars.MoodModifiesThresholds)
|
|
|| !TryComp<MobThresholdsComponent>(uid, out var mobThresholds))
|
|
return;
|
|
|
|
if (_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var key))
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (_mobThreshold.TryGetThresholdForState(uid, MobState.SoftCritical, out key))
|
|
{
|
|
var newKey = modifier switch
|
|
{
|
|
1 => FixedPoint2.New(key.Value.Float() * component.IncreaseSoftCritThreshold),
|
|
-1 => FixedPoint2.New(key.Value.Float() * component.DecreaseSoftCritThreshold),
|
|
_ => component.SoftCritThresholdBeforeModify,
|
|
};
|
|
|
|
component.SoftCritThresholdBeforeModify = key.Value;
|
|
_mobThreshold.SetMobStateThreshold(uid, newKey, MobState.SoftCritical, 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) =>
|
|
threshold switch
|
|
{
|
|
>= MoodThreshold.Good => 1,
|
|
<= MoodThreshold.Meh => -1,
|
|
_ => 0,
|
|
};
|
|
|
|
private void OnDamageChange(EntityUid uid, MoodComponent component, DamageChangedEvent args)
|
|
{
|
|
if (!_mobThreshold.TryGetPercentageForState(uid, MobState.SoftCritical, 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)
|
|
{
|
|
protoId = threshold.Key;
|
|
value = threshold.Value;
|
|
}
|
|
|
|
var ev = new MoodEffectEvent(protoId);
|
|
RaiseLocalEvent(uid, ev);
|
|
}
|
|
}
|
|
|
|
[UsedImplicitly, DataDefinition]
|
|
public sealed partial class ShowMoodEffects : IAlertClick
|
|
{
|
|
public void AlertClicked(EntityUid uid)
|
|
{
|
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
|
var chatManager = IoCManager.Resolve<IChatManager>();
|
|
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
|
|
|
if (!entityManager.TryGetComponent<MoodComponent>(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<MoodEffectPrototype>(protoId, out var proto)
|
|
|| proto.Hidden)
|
|
continue;
|
|
|
|
SendDescToChat(proto, session);
|
|
}
|
|
|
|
foreach (var (protoId, _) in comp.UncategorisedEffects)
|
|
{
|
|
if (!prototypeManager.TryIndex<MoodEffectPrototype>(protoId, out var proto)
|
|
|| proto.Hidden)
|
|
continue;
|
|
|
|
SendDescToChat(proto, session);
|
|
}
|
|
}
|
|
|
|
private void SendDescToChat(MoodEffectPrototype proto, ICommonSession session)
|
|
{
|
|
var chatManager = IoCManager.Resolve<IChatManager>();
|
|
|
|
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);
|
|
}
|
|
}
|