--- /dev/null
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Overlays;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Overlays;
+
+public sealed class ShowHungerIconsSystem : EquipmentHudSystem<ShowHungerIconsComponent>
+{
+ [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<HungerComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
+ }
+
+ private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent hungerComponent, ref GetStatusIconsEvent args)
+ {
+ if (!IsActive || args.InContainer)
+ return;
+
+ var healthIcons = DecideHungerIcon(uid, hungerComponent);
+
+ args.StatusIcons.AddRange(healthIcons);
+ }
+
+ private IReadOnlyList<StatusIconPrototype> DecideHungerIcon(EntityUid uid, HungerComponent hungerComponent)
+ {
+ var result = new List<StatusIconPrototype>();
+
+ switch (hungerComponent.CurrentThreshold)
+ {
+ case HungerThreshold.Overfed:
+ if (_prototypeMan.TryIndex<StatusIconPrototype>("HungerIconOverfed", out var overfed))
+ {
+ result.Add(overfed);
+ }
+ break;
+ case HungerThreshold.Peckish:
+ if (_prototypeMan.TryIndex<StatusIconPrototype>("HungerIconPeckish", out var peckish))
+ {
+ result.Add(peckish);
+ }
+ break;
+ case HungerThreshold.Starving:
+ if (_prototypeMan.TryIndex<StatusIconPrototype>("HungerIconStarving", out var starving))
+ {
+ result.Add(starving);
+ }
+ break;
+ }
+
+ return result;
+ }
+}
--- /dev/null
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Overlays;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Overlays;
+
+public sealed class ShowThirstIconsSystem : EquipmentHudSystem<ShowThirstIconsComponent>
+{
+ [Dependency] private readonly IPrototypeManager _prototypeMan = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ThirstComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
+ }
+
+ private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent thirstComponent, ref GetStatusIconsEvent args)
+ {
+ if (!IsActive || args.InContainer)
+ return;
+
+ var healthIcons = DecideThirstIcon(uid, thirstComponent);
+
+ args.StatusIcons.AddRange(healthIcons);
+ }
+
+ private IReadOnlyList<StatusIconPrototype> DecideThirstIcon(EntityUid uid, ThirstComponent thirstComponent)
+ {
+ var result = new List<StatusIconPrototype>();
+
+ switch (thirstComponent.CurrentThirstThreshold)
+ {
+ case ThirstThreshold.OverHydrated:
+ if (_prototypeMan.TryIndex<StatusIconPrototype>("ThirstIconOverhydrated", out var overhydrated))
+ {
+ result.Add(overhydrated);
+ }
+ break;
+ case ThirstThreshold.Thirsty:
+ if (_prototypeMan.TryIndex<StatusIconPrototype>("ThirstIconThirsty", out var thirsty))
+ {
+ result.Add(thirsty);
+ }
+ break;
+ case ThirstThreshold.Parched:
+ if (_prototypeMan.TryIndex<StatusIconPrototype>("ThirstIconParched", out var parched))
+ {
+ result.Add(parched);
+ }
+ break;
+ }
+
+ return result;
+ }
+}
-using Content.Server.Nutrition.Components;
-using Content.Shared.Chemistry.Reagent;
using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Nutrition.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
-using Content.Server.Fluids.Components;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
-using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Popups;
using Content.Server.Stunnable;
-using Content.Shared.Audio;
using Content.Shared.Chemistry.Components;
-using Content.Shared.Fluids.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
+++ /dev/null
-using Content.Shared.Alert;
-
-namespace Content.Server.Nutrition.Components
-{
- [Flags]
- public enum ThirstThreshold : byte
- {
- // Hydrohomies
- Dead = 0,
- Parched = 1 << 0,
- Thirsty = 1 << 1,
- Okay = 1 << 2,
- OverHydrated = 1 << 3,
- }
-
- [RegisterComponent]
- public sealed partial class ThirstComponent : Component
- {
- // Base stuff
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("baseDecayRate")]
- public float BaseDecayRate = 0.1f;
-
- [ViewVariables(VVAccess.ReadWrite)]
- public float ActualDecayRate;
-
- // Thirst
- [ViewVariables(VVAccess.ReadOnly)]
- public ThirstThreshold CurrentThirstThreshold;
-
- public ThirstThreshold LastThirstThreshold;
-
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("startingThirst")]
- public float CurrentThirst = -1f;
-
- [DataField("thresholds")]
- public Dictionary<ThirstThreshold, float> ThirstThresholds { get; private set; } = new()
- {
- {ThirstThreshold.OverHydrated, 600.0f},
- {ThirstThreshold.Okay, 450.0f},
- {ThirstThreshold.Thirsty, 300.0f},
- {ThirstThreshold.Parched, 150.0f},
- {ThirstThreshold.Dead, 0.0f},
- };
-
- public static readonly Dictionary<ThirstThreshold, AlertType> ThirstThresholdAlertTypes = new()
- {
- {ThirstThreshold.Thirsty, AlertType.Thirsty},
- {ThirstThreshold.Parched, AlertType.Parched},
- {ThirstThreshold.Dead, AlertType.Parched},
- };
- }
-}
// ComponentActivatedClientSystems
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSecurityIconsComponent>>(RelayInventoryEvent);
+ SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RelayInventoryEvent);
+ SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetStrippingVerbs);
}
namespace Content.Shared.Nutrition.Components;
[RegisterComponent, NetworkedComponent, Access(typeof(HungerSystem))]
+[AutoGenerateComponentState]
public sealed partial class HungerComponent : Component
{
/// <summary>
/// The current hunger amount of the entity
/// </summary>
[DataField("currentHunger"), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
public float CurrentHunger;
/// <summary>
/// Affected by <seealso cref="CurrentThreshold"/>
/// </summary>
[DataField("actualDecayRate"), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
public float ActualDecayRate;
/// <summary>
/// Stored in order to prevent recalculating
/// </summary>
[DataField("lastThreshold"), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
public HungerThreshold LastThreshold;
/// <summary>
/// The current hunger threshold the entity is at
/// </summary>
[DataField("currentThreshold"), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
public HungerThreshold CurrentThreshold;
/// <summary>
/// A dictionary relating HungerThreshold to the amount of <see cref="CurrentHunger"/> needed for each one
/// </summary>
[DataField("thresholds", customTypeSerializer: typeof(DictionarySerializer<HungerThreshold, float>))]
+ [AutoNetworkedField(cloneData: true)]
public Dictionary<HungerThreshold, float> Thresholds = new()
{
{ HungerThreshold.Overfed, 200.0f },
/// A dictionary relating hunger thresholds to corresponding alerts.
/// </summary>
[DataField("hungerThresholdAlerts", customTypeSerializer: typeof(DictionarySerializer<HungerThreshold, AlertType>))]
+ [AutoNetworkedField(cloneData: true)]
public Dictionary<HungerThreshold, AlertType> HungerThresholdAlerts = new()
{
{ HungerThreshold.Peckish, AlertType.Peckish },
/// A dictionary relating HungerThreshold to how much they modify <see cref="BaseDecayRate"/>.
/// </summary>
[DataField("hungerThresholdDecayModifiers", customTypeSerializer: typeof(DictionarySerializer<HungerThreshold, float>))]
+ [AutoNetworkedField(cloneData: true)]
public Dictionary<HungerThreshold, float> HungerThresholdDecayModifiers = new()
{
{ HungerThreshold.Overfed, 1.2f },
/// The amount of slowdown applied when an entity is starving
/// </summary>
[DataField("starvingSlowdownModifier"), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
public float StarvingSlowdownModifier = 0.75f;
/// <summary>
/// The time when the hunger will update next.
/// </summary>
[DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
public TimeSpan NextUpdateTime;
/// <summary>
/// The time between each update.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
public TimeSpan UpdateRate = TimeSpan.FromSeconds(1);
}
-[Serializable, NetSerializable]
-public sealed class HungerComponentState : ComponentState
-{
- public float CurrentHunger;
-
- public float BaseDecayRate;
-
- public float ActualDecayRate;
-
- public HungerThreshold LastHungerThreshold;
-
- public HungerThreshold CurrentThreshold;
-
- public float StarvingSlowdownModifier;
-
- public TimeSpan NextUpdateTime;
-
- public HungerComponentState(float currentHunger,
- float baseDecayRate,
- float actualDecayRate,
- HungerThreshold lastHungerThreshold,
- HungerThreshold currentThreshold,
- float starvingSlowdownModifier,
- TimeSpan nextUpdateTime)
- {
- CurrentHunger = currentHunger;
- BaseDecayRate = baseDecayRate;
- ActualDecayRate = actualDecayRate;
- LastHungerThreshold = lastHungerThreshold;
- CurrentThreshold = currentThreshold;
- StarvingSlowdownModifier = starvingSlowdownModifier;
- NextUpdateTime = nextUpdateTime;
- }
-}
-
[Serializable, NetSerializable]
public enum HungerThreshold : byte
{
--- /dev/null
+using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Alert;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Nutrition.Components;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(ThirstSystem))]
+[AutoGenerateComponentState]
+public sealed partial class ThirstComponent : Component
+{
+ // Base stuff
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("baseDecayRate")]
+ [AutoNetworkedField]
+ public float BaseDecayRate = 0.1f;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public float ActualDecayRate;
+
+ // Thirst
+ [ViewVariables(VVAccess.ReadOnly)]
+ [AutoNetworkedField]
+ public ThirstThreshold CurrentThirstThreshold;
+
+ [ViewVariables(VVAccess.ReadOnly)]
+ [AutoNetworkedField]
+ public ThirstThreshold LastThirstThreshold;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("startingThirst")]
+ [AutoNetworkedField]
+ public float CurrentThirst = -1f;
+
+ /// <summary>
+ /// The time when the hunger will update next.
+ /// </summary>
+ [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public TimeSpan NextUpdateTime;
+
+ /// <summary>
+ /// The time between each update.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public TimeSpan UpdateRate = TimeSpan.FromSeconds(1);
+
+ [DataField("thresholds")]
+ [AutoNetworkedField(cloneData: true)]
+ public Dictionary<ThirstThreshold, float> ThirstThresholds = new()
+ {
+ {ThirstThreshold.OverHydrated, 600.0f},
+ {ThirstThreshold.Okay, 450.0f},
+ {ThirstThreshold.Thirsty, 300.0f},
+ {ThirstThreshold.Parched, 150.0f},
+ {ThirstThreshold.Dead, 0.0f},
+ };
+
+ public static readonly Dictionary<ThirstThreshold, AlertType> ThirstThresholdAlertTypes = new()
+ {
+ {ThirstThreshold.Thirsty, AlertType.Thirsty},
+ {ThirstThreshold.Parched, AlertType.Parched},
+ {ThirstThreshold.Dead, AlertType.Parched},
+ };
+}
+
+[Flags]
+public enum ThirstThreshold : byte
+{
+ // Hydrohomies
+ Dead = 0,
+ Parched = 1 << 0,
+ Thirsty = 1 << 1,
+ Okay = 1 << 2,
+ OverHydrated = 1 << 3,
+}
-using Content.Shared.Alert;
+using Content.Shared.Alert;
using Content.Shared.Damage;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Rejuvenate;
-using Robust.Shared.GameStates;
using Robust.Shared.Random;
using Robust.Shared.Timing;
{
base.Initialize();
- SubscribeLocalEvent<HungerComponent, ComponentGetState>(OnGetState);
- SubscribeLocalEvent<HungerComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<HungerComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<HungerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<HungerComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<HungerComponent, RejuvenateEvent>(OnRejuvenate);
}
- private void OnGetState(EntityUid uid, HungerComponent component, ref ComponentGetState args)
- {
- args.State = new HungerComponentState(component.CurrentHunger,
- component.BaseDecayRate,
- component.ActualDecayRate,
- component.LastThreshold,
- component.CurrentThreshold,
- component.StarvingSlowdownModifier,
- component.NextUpdateTime);
- }
-
- private void OnHandleState(EntityUid uid, HungerComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not HungerComponentState state)
- return;
- component.CurrentHunger = state.CurrentHunger;
- component.BaseDecayRate = state.BaseDecayRate;
- component.ActualDecayRate = state.ActualDecayRate;
- component.LastThreshold = state.LastHungerThreshold;
- component.CurrentThreshold = state.CurrentThreshold;
- component.StarvingSlowdownModifier = state.StarvingSlowdownModifier;
- component.NextUpdateTime = state.NextUpdateTime;
- }
-
private void OnUnpaused(EntityUid uid, HungerComponent component, ref EntityUnpausedEvent args)
{
component.NextUpdateTime += args.PausedTime;
-using Content.Server.Nutrition.Components;
-using JetBrains.Annotations;
-using Robust.Shared.Random;
-using Content.Shared.Movement.Components;
using Content.Shared.Alert;
+using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
+using Content.Shared.Nutrition.Components;
using Content.Shared.Rejuvenate;
+using JetBrains.Annotations;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
namespace Content.Server.Nutrition.EntitySystems;
[UsedImplicitly]
public sealed class ThirstSystem : EntitySystem
{
+ [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
[Dependency] private readonly SharedJetpackSystem _jetpack = default!;
private ISawmill _sawmill = default!;
- private float _accumulatedFrameTime;
public override void Initialize()
{
SubscribeLocalEvent<ThirstComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
SubscribeLocalEvent<ThirstComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<ThirstComponent, RejuvenateEvent>(OnRejuvenate);
+ SubscribeLocalEvent<ThirstComponent, EntityUnpausedEvent>(OnUnpaused);
}
private void OnComponentStartup(EntityUid uid, ThirstComponent component, ComponentStartup args)
public override void Update(float frameTime)
{
- _accumulatedFrameTime += frameTime;
+ base.Update(frameTime);
- if (_accumulatedFrameTime > 1)
+ var query = EntityQueryEnumerator<ThirstComponent>();
+ while (query.MoveNext(out var uid, out var thirst))
{
- var query = EntityManager.EntityQueryEnumerator<ThirstComponent>();
- while (query.MoveNext(out var uid, out var comp))
- {
- UpdateThirst(comp, - comp.ActualDecayRate);
- var calculatedThirstThreshold = GetThirstThreshold(comp, comp.CurrentThirst);
- if (calculatedThirstThreshold != comp.CurrentThirstThreshold)
- {
- comp.CurrentThirstThreshold = calculatedThirstThreshold;
- UpdateEffects(uid, comp);
- }
- }
- _accumulatedFrameTime -= 1;
+ if (_timing.CurTime < thirst.NextUpdateTime)
+ continue;
+
+ thirst.NextUpdateTime += thirst.UpdateRate;
+
+ UpdateThirst(thirst, -thirst.ActualDecayRate);
+ var calculatedThirstThreshold = GetThirstThreshold(thirst, thirst.CurrentThirst);
+
+ if (calculatedThirstThreshold == thirst.CurrentThirstThreshold)
+ continue;
+
+ thirst.CurrentThirstThreshold = calculatedThirstThreshold;
+ UpdateEffects(uid, thirst);
+ Dirty(uid, thirst);
}
}
+
+ private void OnUnpaused(EntityUid uid, ThirstComponent component, ref EntityUnpausedEvent args)
+ {
+ component.NextUpdateTime += args.PausedTime;
+ }
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Overlays;
+
+/// <summary>
+/// This component allows you to see the hungriness of mobs.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShowHungerIconsComponent : Component { }
using Robust.Shared.GameStates;
-namespace Content.Shared.Overlays
-{
- /// <summary>
- /// This component allows you to see job icons above mobs.
- /// </summary>
- [RegisterComponent, NetworkedComponent]
- public sealed partial class ShowSecurityIconsComponent : Component { }
-}
+namespace Content.Shared.Overlays;
+
+/// <summary>
+/// This component allows you to see job icons above mobs.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShowSecurityIconsComponent : Component { }
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Overlays;
+
+/// <summary>
+/// This component allows you to see the thirstiness of mobs.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShowThirstIconsComponent : Component { }
sprite: Clothing/Eyes/Hud/beergoggles.rsi
- type: Clothing
sprite: Clothing/Eyes/Hud/beergoggles.rsi
+ - type: ShowThirstIcons
- type: entity
parent: ClothingEyesBase
sprite: Clothing/Eyes/Hud/friedonion.rsi
- type: Clothing
sprite: Clothing/Eyes/Hud/friedonion.rsi
+ - type: ShowHungerIcons
+ - type: Food
+ - type: SolutionContainerManager
+ solutions:
+ food:
+ maxVol: 3
+ reagents:
+ - ReagentId: Nutriment
+ Quantity: 3
+ - type: FlavorProfile
+ flavors:
+ - onion
+ - greasey
- type: entity
parent: ClothingEyesBase
sprite: Clothing/Eyes/Hud/onionbeer.rsi
- type: Clothing
sprite: Clothing/Eyes/Hud/onionbeer.rsi
+ - type: ShowHungerIcons
+ - type: ShowThirstIcons
- type: entity
parent: ClothingEyesBase
sprite: Clothing/Eyes/Hud/medonion.rsi
- type: Clothing
sprite: Clothing/Eyes/Hud/medonion.rsi
+ - type: ShowHungerIcons
- type: entity
parent: ClothingEyesBase
sprite: Clothing/Eyes/Hud/medonionbeer.rsi
- type: Clothing
sprite: Clothing/Eyes/Hud/medonionbeer.rsi
+ - type: ShowHungerIcons
+ - type: ShowThirstIcons
- type: entity
parent: ClothingEyesBase
- type: Clothing
sprite: Clothing/Eyes/Hud/omni.rsi
- type: ShowSecurityIcons
+ - type: ShowHungerIcons
+ - type: ShowThirstIcons
--- /dev/null
+#Hunger
+- type: statusIcon
+ id: HungerIconOverfed
+ priority: 5
+ icon:
+ sprite: Interface/Misc/food_icons.rsi
+ state: overfed
+ locationPreference: Right
+
+- type: statusIcon
+ id: HungerIconPeckish
+ priority: 5
+ icon:
+ sprite: Interface/Misc/food_icons.rsi
+ state: peckish
+ locationPreference: Right
+
+- type: statusIcon
+ id: HungerIconStarving
+ priority: 5
+ icon:
+ sprite: Interface/Misc/food_icons.rsi
+ state: starving
+ locationPreference: Right
+
+#Thirst
+- type: statusIcon
+ id: ThirstIconOverhydrated
+ priority: 5
+ icon:
+ sprite: Interface/Misc/food_icons.rsi
+ state: overhydrated
+ locationPreference: Left
+
+- type: statusIcon
+ id: ThirstIconThirsty
+ priority: 5
+ icon:
+ sprite: Interface/Misc/food_icons.rsi
+ state: thirsty
+ locationPreference: Left
+
+- type: statusIcon
+ id: ThirstIconParched
+ priority: 5
+ icon:
+ sprite: Interface/Misc/food_icons.rsi
+ state: parched
+ locationPreference: Left