mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Refactor hunger and thirst systems (#34166)
* ready * oop forgot the HUD stuff * woooo examinable generalized tooooo * oop I forgot to rename the files * bungled the merge, I guess * this is called hubris * Verin's comments * PR comments how did I use `ProtoId<> Hunger` in more than a few spots but then just say "thirst can stay a literal :^)"? also changed a few spots to my beloved ``` functionInvocation( one param per line ) ``` ``` * Satiation effect condition now with ADDED FUNCTIONALITY?? :o * tfw you trust the markov chain recommendation because long generic type names are tough q-q * TFW YOU TRUST THE MARKOV CHAIN BUT IT DOESN'T UNDERSTAND THE DIFFERING SEMANTICS OF MAX AND MIN AHHHHH * tfw you think to yourself "i need to fix the effects", but then forget to and the code doesn't even compile. * centralize satiation constants * other small comments * The enum is dead. But at what cost? * oop * like 12 more ooperses * green checks are mine by right; I have performed the rites; I have done my part; you will not deny me this succor * oops I changed the semantics from `<=` to just `<` and didn't update the tests :) * serialization has bested me :agony: also PR comments * never trust the auto merge :agony: * literally just this * force engine back to master because idk why it's different * fix randomness and update master * submodule fuckery * fall before my might * okay I ran the linter, ARE YOU HAPPY?? * cursed ovis, trouble me no more * protoman stuff * inb4 linter bonks me * break non-critical stuff from the main satiation proto *  inb4 this breaks something somewhere lmao * my two greatest PRs COLLIDE I wonder why robust says it's changed :hmmclueless * sanity is at an all-time low * serialization, I guess * I don't believe you * field deltas on class \w*satiation\w*components * PR comments * 我做泥團子 😔 * doc + kill one property that's trivially accessible after the threshold caching changes * fixes --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: AJCM-git <ajcm_mail@disroot.org>
This commit is contained in:
parent
6f6a5694c8
commit
8ad82dc665
96 changed files with 2701 additions and 1529 deletions
|
|
@ -1,27 +0,0 @@
|
|||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class ShowHungerIconsSystem : EquipmentHudSystem<ShowHungerIconsComponent>
|
||||
{
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HungerComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent component, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive)
|
||||
return;
|
||||
|
||||
if (_hunger.TryGetStatusIconPrototype(component, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
57
Content.Client/Overlays/ShowSatiationIconsSystem.cs
Normal file
57
Content.Client/Overlays/ShowSatiationIconsSystem.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// This system handles the inclusion of satiation status icons for entities with the <see cref="ShowSatiationIconsComponent"/>
|
||||
/// </summary>
|
||||
public sealed partial class ShowSatiationIconsSystem : EquipmentHudSystem<ShowSatiationIconsComponent>
|
||||
{
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
|
||||
private HashSet<ProtoId<SatiationTypePrototype>> _types = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowSatiationIconsComponent> args)
|
||||
{
|
||||
base.UpdateInternal(args);
|
||||
|
||||
// Any time we update `ShowIcons` component, we need to reconstruct the set of satiation types to show.
|
||||
_types = [];
|
||||
foreach (var type in args.Components.SelectMany(comp => comp.ShownTypes))
|
||||
{
|
||||
_types.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DeactivateInternal()
|
||||
{
|
||||
base.DeactivateInternal();
|
||||
_types.Clear();
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnGetStatusIconsEvent(Entity<SatiationComponent> entity, ref GetStatusIconsEvent args)
|
||||
{
|
||||
if (!IsActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var shownTypeId in _types)
|
||||
{
|
||||
if (_satiation.GetStatusIconOrNull(entity, shownTypeId) is { } iconId)
|
||||
{
|
||||
args.StatusIcons.Add(iconId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class ShowThirstIconsSystem : EquipmentHudSystem<ShowThirstIconsComponent>
|
||||
{
|
||||
[Dependency] private ThirstSystem _thirst = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ThirstComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent component, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive)
|
||||
return;
|
||||
|
||||
if (_thirst.TryGetStatusIconPrototype(component, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
|
@ -26,30 +25,34 @@ public sealed class HungerThirstTest : InteractionTest
|
|||
// Ensure that the player can breathe and not suffocate
|
||||
await AddAtmosphere();
|
||||
|
||||
var hungerComponent = Comp<HungerComponent>(Player);
|
||||
var thirstComponent = Comp<ThirstComponent>(Player);
|
||||
var hungerSystem = SEntMan.System<HungerSystem>();
|
||||
var thirstSystem = SEntMan.System<ThirstSystem>();
|
||||
var satiationComponent = Comp<SatiationComponent>(Player);
|
||||
var entity = new Entity<SatiationComponent>(SPlayer, satiationComponent);
|
||||
var satiationSystem = SEntMan.System<SatiationSystem>();
|
||||
var ingestionSystem = SEntMan.System<IngestionSystem>();
|
||||
|
||||
const string okayThresholdKey = "Okay";
|
||||
|
||||
// Set initial value
|
||||
hungerSystem.SetHunger(SPlayer, hungerComponent.Thresholds[HungerThreshold.Okay], hungerComponent);
|
||||
thirstSystem.SetThirst(SPlayer, thirstComponent, thirstComponent.ThirstThresholds[ThirstThreshold.Okay]);
|
||||
Assert.That(satiationSystem.GetKeysForType(entity, SatiationSystem.Hunger), Contains.Item(okayThresholdKey));
|
||||
satiationSystem.SetValue(entity, SatiationSystem.Hunger, okayThresholdKey);
|
||||
Assert.That(satiationSystem.GetKeysForType(entity, SatiationSystem.Thirst), Contains.Item(okayThresholdKey));
|
||||
satiationSystem.SetValue(entity, SatiationSystem.Thirst, okayThresholdKey);
|
||||
|
||||
// Ensure hunger and thirst value decrease over time (the Urist gets hungrier/thirstier)
|
||||
var previousHungerValue = hungerSystem.GetHunger(hungerComponent);
|
||||
var previousThirstValue = thirstComponent.CurrentThirst; // TODO: combined sation system with a sane API
|
||||
var previousHungerValue = satiationSystem.GetValueOrNull(entity, SatiationSystem.Hunger);
|
||||
Assert.That(previousHungerValue, Is.Not.Null);
|
||||
var previousThirstValue = satiationSystem.GetValueOrNull(entity, SatiationSystem.Thirst);
|
||||
Assert.That(previousThirstValue, Is.Not.Null);
|
||||
|
||||
// Simulate long enough for both update loops to run
|
||||
var runTime = Math.Max((float)hungerComponent.ThresholdUpdateRate.TotalSeconds, (float)thirstComponent.UpdateRate.TotalSeconds) + 1f;
|
||||
await RunSeconds(runTime);
|
||||
await RunSeconds(2);
|
||||
|
||||
var currentHungerValue = hungerSystem.GetHunger(hungerComponent);
|
||||
Assert.That(currentHungerValue, Is.LessThan(previousHungerValue), "Hunger value did not decrease over time");
|
||||
var currentHungerValue = satiationSystem.GetValueOrNull(entity, SatiationSystem.Hunger);
|
||||
Assert.That(currentHungerValue, Is.Not.Null.And.LessThan(previousHungerValue), "Hunger value did not decrease over time");
|
||||
previousHungerValue = currentHungerValue;
|
||||
|
||||
var currentThirstValue = thirstComponent.CurrentThirst;
|
||||
Assert.That(currentThirstValue, Is.LessThan(previousThirstValue), "Thirst value did not decrease over time");
|
||||
var currentThirstValue = satiationSystem.GetValueOrNull(entity, SatiationSystem.Thirst);
|
||||
Assert.That(currentThirstValue, Is.Not.Null.And.LessThan(previousThirstValue), "Thirst value did not decrease over time");
|
||||
previousThirstValue = currentThirstValue;
|
||||
|
||||
// Now we spawn food in the Urist's hand
|
||||
|
|
@ -66,7 +69,7 @@ public sealed class HungerThirstTest : InteractionTest
|
|||
Assert.That(foodEaten, Is.Null, "Food item did not disappear after eating it");
|
||||
|
||||
// Ensure that the hunger value has increased (The Urist is less hungry)
|
||||
Assert.That(hungerSystem.GetHunger(hungerComponent), Is.GreaterThan(previousHungerValue), "Hunger value did not increase after eating food");
|
||||
Assert.That(satiationSystem.GetValueOrNull(entity, SatiationSystem.Hunger), Is.GreaterThan(previousHungerValue!), "Hunger value did not increase after eating food");
|
||||
|
||||
// Now we spawn a drink in the Urist's hand
|
||||
var drink = await PlaceInHands(_drink);
|
||||
|
|
@ -88,7 +91,7 @@ public sealed class HungerThirstTest : InteractionTest
|
|||
Assert.That(solution.Value.Comp.Solution.Volume, Is.LessThan(initialSolutionVolume), "Solution volume did not decrease after drinking");
|
||||
|
||||
// Ensure that the thirst value has increased (The Urist is less thirsty)
|
||||
Assert.That(thirstComponent.CurrentThirst, Is.GreaterThan(previousThirstValue), "Thirst value did not increase after drinking");
|
||||
Assert.That(satiationSystem.GetValueOrNull(entity, SatiationSystem.Thirst), Is.GreaterThan(previousThirstValue!), "Thirst value did not increase after drinking");
|
||||
|
||||
// Make sure that the glass did not get deleted after drinking from it
|
||||
var glass = HandSys.GetActiveItem((SPlayer, Hands));
|
||||
|
|
|
|||
303
Content.IntegrationTests/Tests/Nutrition/SatiationTest.cs
Normal file
303
Content.IntegrationTests/Tests/Nutrition/SatiationTest.cs
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.IntegrationTests.Fixtures;
|
||||
using Content.IntegrationTests.Fixtures.Attributes;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using NUnit.Framework.Constraints;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Nutrition;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(SatiationSystem))]
|
||||
[TestOf(typeof(SatiationPrototype))]
|
||||
public sealed class SatiationTest : GameTest
|
||||
{
|
||||
private const string TestSatiationId = "TestSatiation";
|
||||
private const string DeadKey = "Dead";
|
||||
private const string MiddleKey = "Okay";
|
||||
private const string MaxxedKey = "Maxxed";
|
||||
private const string NotRealKey = "ashfdjkashfljkahdjskfjadshfgkjlhadsekljfhjalds";
|
||||
private static readonly ProtoId<SatiationTypePrototype> TestSatiationType = "Hunger";
|
||||
private const string TestProto = "TestSatiationDummy";
|
||||
private const int StartingMin = 30;
|
||||
private const int StartingMax = 35;
|
||||
private const int MiddleValue = 50;
|
||||
private const int MaxValue = 100;
|
||||
|
||||
[TestPrototypes]
|
||||
private static readonly string SatiationPrototypes = $@"
|
||||
- type: satiation
|
||||
id: {TestSatiationId}
|
||||
baseDecayRate: 1
|
||||
maximumValue: {MaxValue}
|
||||
thresholds: # Intentionally out of ordinal order.
|
||||
{DeadKey}: 0
|
||||
{MaxxedKey}: 100
|
||||
{MiddleKey}: {MiddleValue}
|
||||
startingValueMinimum: {StartingMin}
|
||||
startingValueMaximum: {StartingMax}
|
||||
decayModifiers:
|
||||
25: 0.5
|
||||
alertCategory: Hunger
|
||||
|
||||
- type: entity
|
||||
id: {TestProto}
|
||||
name: dummy
|
||||
components:
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: {TestSatiationId}
|
||||
";
|
||||
|
||||
[SidedDependency(Side.Server)] private readonly SatiationSystem _satiation = default!;
|
||||
|
||||
[Test, RunOnSide(Side.Server)]
|
||||
[Description(
|
||||
"Verifies the basic operations of 'SatiationSystem.SetValue', 'SatiationSystem.ModifyValue', and 'SatiationSystem.GetValueOrNull'")]
|
||||
public void SatiationBasicTest()
|
||||
{
|
||||
var entity = SEntity<SatiationComponent>(SSpawn(TestProto));
|
||||
|
||||
// Verify the starting value is in the starting range.
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(_satiation.GetValueOrNull(entity, TestSatiationType),
|
||||
Is.LessThanOrEqualTo(StartingMax).And.GreaterThanOrEqualTo(StartingMin));
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: StartingMin, below: StartingMax),
|
||||
Is.True);
|
||||
}
|
||||
|
||||
// The rest of this modifies the value and verifies the numeric value is what's expected
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, MiddleKey);
|
||||
Assert.That(_satiation.GetValueOrNull(entity, TestSatiationType), Is.EqualTo(MiddleValue));
|
||||
|
||||
_satiation.ModifyValue(entity, TestSatiationType, -20);
|
||||
Assert.That(_satiation.GetValueOrNull(entity, TestSatiationType), Is.EqualTo(MiddleValue - 20));
|
||||
|
||||
_satiation.ModifyValue(entity, TestSatiationType, -int.MaxValue);
|
||||
Assert.That(_satiation.GetValueOrNull(entity, TestSatiationType), Is.Zero);
|
||||
|
||||
_satiation.ModifyValue(entity, TestSatiationType, int.MaxValue);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(_satiation.GetValueOrNull(entity, TestSatiationType), Is.EqualTo(MaxValue));
|
||||
Assert.That(_satiation.GetValueOrNull(entity, TestSatiationType + NotRealKey), Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, RunOnSide(Side.Server)]
|
||||
[Description("Verifies 'SatiationSystem.TryGetValueByThreshold' when threshold keys are integers")]
|
||||
public void SatiationGetValueByThresholdTest()
|
||||
{
|
||||
var entity = SEntity<SatiationComponent>(SSpawn(TestProto));
|
||||
var dict = new Dictionary<SatiationValue, int>
|
||||
{
|
||||
// Arbitrary order to test that the implementation doesn't care about order.
|
||||
[20] = 20,
|
||||
[0] = 0,
|
||||
[40] = 40,
|
||||
[80] = 80,
|
||||
[100] = 100,
|
||||
[60] = 60,
|
||||
};
|
||||
|
||||
// All of these work by setting a value, calling `TryGetValueByThreshold`, and verifying the exact results are
|
||||
// what's expected.
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, value: 100);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
var res = _satiation.TryGetValueByThreshold(entity,
|
||||
TestSatiationType,
|
||||
dict,
|
||||
out var result,
|
||||
out var nextLower);
|
||||
Assert.That(res, Is.True);
|
||||
Assert.That(result, Is.EqualTo(100));
|
||||
Assert.That(nextLower, Is.EqualTo(80));
|
||||
}
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, value: 55);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
var res = _satiation.TryGetValueByThreshold(entity,
|
||||
TestSatiationType,
|
||||
dict,
|
||||
out var result,
|
||||
out var nextLower);
|
||||
Assert.That(res, Is.True);
|
||||
Assert.That(result, Is.EqualTo(60));
|
||||
Assert.That(nextLower, Is.EqualTo(40));
|
||||
}
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, value: 0);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
var res = _satiation.TryGetValueByThreshold(entity,
|
||||
TestSatiationType,
|
||||
dict,
|
||||
out var result,
|
||||
out var nextLower);
|
||||
Assert.That(res, Is.True);
|
||||
Assert.That(result, Is.Zero);
|
||||
Assert.That(nextLower, Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, RunOnSide(Side.Server)]
|
||||
[Description("Verifies 'SatiationSystem.TryGetValueByThreshold' when threshold keys are strings")]
|
||||
public void SatiationGetValueByThresholdKeysTest()
|
||||
{
|
||||
var entity = SEntity<SatiationComponent>(SSpawn(TestProto));
|
||||
var dict = new Dictionary<SatiationValue, int>
|
||||
{
|
||||
// Arbitrary order to test that the implementation doesn't care about order.
|
||||
[DeadKey] = 20,
|
||||
[MaxxedKey] = 0,
|
||||
[MiddleKey] = 40,
|
||||
};
|
||||
|
||||
// All of these work by setting a value, calling `TryGetValueByThreshold`, and verifying the exact results are
|
||||
// what's expected.
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, MaxxedKey);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
var res = _satiation.TryGetValueByThreshold(entity,
|
||||
TestSatiationType,
|
||||
dict,
|
||||
out var result,
|
||||
out var nextLower);
|
||||
Assert.That(res, Is.True);
|
||||
Assert.That(result, Is.Zero);
|
||||
Assert.That(nextLower, Is.EqualTo(MiddleValue));
|
||||
}
|
||||
|
||||
_satiation.ModifyValue(entity, TestSatiationType, -10);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
var res = _satiation.TryGetValueByThreshold(entity,
|
||||
TestSatiationType,
|
||||
dict,
|
||||
out var result,
|
||||
out var nextLower);
|
||||
Assert.That(res, Is.True);
|
||||
Assert.That(result, Is.Zero);
|
||||
Assert.That(nextLower, Is.EqualTo(MiddleValue));
|
||||
}
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, MiddleKey);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
var res = _satiation.TryGetValueByThreshold(entity,
|
||||
TestSatiationType,
|
||||
dict,
|
||||
out var result,
|
||||
out var nextLower);
|
||||
Assert.That(res, Is.True);
|
||||
Assert.That(result, Is.EqualTo(40));
|
||||
Assert.That(nextLower, Is.Zero);
|
||||
}
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, DeadKey);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
var res = _satiation.TryGetValueByThreshold(entity,
|
||||
TestSatiationType,
|
||||
dict,
|
||||
out var result,
|
||||
out var nextLower);
|
||||
Assert.That(res, Is.True);
|
||||
Assert.That(result, Is.EqualTo(20));
|
||||
Assert.That(nextLower, Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, RunOnSide(Side.Server)]
|
||||
[Description("Verifies 'SatiationSystem.IsValueInRange'")]
|
||||
[SuppressMessage("Assertion",
|
||||
"NUnit2057:Remove unnecessary lambda expression",
|
||||
Justification = "Necessity of lambda depends on build configuration")]
|
||||
public void SatiationRangeTests()
|
||||
{
|
||||
var entity = SEntity<SatiationComponent>(SSpawn(TestProto));
|
||||
|
||||
// All of these work by setting a value, then asserting that `IsValueInRange` for various ranges returns what's
|
||||
// expected.
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, value: 100);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: DeadKey), Is.True);
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: MaxxedKey), Is.False);
|
||||
Assert.That(_satiation.IsValueInRange(entity,
|
||||
TestSatiationType,
|
||||
below: MaxxedKey,
|
||||
hypotheticalValueDelta: -1),
|
||||
Is.True);
|
||||
}
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, value: MiddleValue + 5);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: MiddleKey), Is.True);
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: MiddleKey, below: MaxxedKey),
|
||||
Is.True);
|
||||
Assert.That(
|
||||
_satiation.IsValueInRange(entity, TestSatiationType, above: MaxxedKey, hypotheticalValueDelta: -10),
|
||||
Is.False);
|
||||
}
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
// Disable "ForbidLiteral" errors. Making these all into constants to be used once is unnecessary.
|
||||
#pragma warning disable RA0033
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: 0), Is.True);
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: -1000), Is.True);
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: 100), Is.False);
|
||||
Assert.That(() => _satiation.IsValueInRange(entity, TestSatiationType, below: 60, above: 70),
|
||||
Is.False.OrFailsDebugAssertInDebug());
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, below: 50, hypotheticalValueDelta: -10),
|
||||
Is.True);
|
||||
#pragma warning restore RA0033
|
||||
}
|
||||
|
||||
_satiation.SetValue(entity, TestSatiationType, value: 0);
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(_satiation.IsValueInRange(entity, TestSatiationType, above: NotRealKey), Is.False);
|
||||
Assert.That(() => _satiation.IsValueInRange(entity, TestSatiationType),
|
||||
Is.True.OrFailsDebugAssertInDebug());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static file class ConstraintExt
|
||||
{
|
||||
#if DEBUG
|
||||
private const bool IsDebug = true;
|
||||
#else
|
||||
private const bool IsDebug = false;
|
||||
#endif
|
||||
|
||||
extension(Constraint constraint)
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the receiver <see cref="Constraint"/> when <c>#define DEBUG</c> is false, otherwise returns a
|
||||
/// <see cref="ThrowsConstraint"/> with exception type <see cref="DebugAssertException"/>. This is useful when
|
||||
/// something throws in Debug, but fails gracefully otherwise.
|
||||
/// </summary>
|
||||
// TODO Put this somewhere else reusable https://github.com/space-wizards/space-station-14/pull/34166#discussion_r3692882923
|
||||
public Constraint OrFailsDebugAssertInDebug() =>
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
// ReSharper disable once HeuristicUnreachableCode
|
||||
IsDebug ? Throws.InstanceOf<DebugAssertException>() : constraint;
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,11 @@ using Content.Shared.IdentityManagement;
|
|||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
|
|
@ -23,7 +25,7 @@ public sealed partial class EggLayerSystem : EntitySystem
|
|||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private ActionsSystem _actions = default!;
|
||||
[Dependency] private AudioSystem _audio = default!;
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private PopupSystem _popup = default!;
|
||||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
|
|
@ -83,15 +85,15 @@ public sealed partial class EggLayerSystem : EntitySystem
|
|||
return false;
|
||||
|
||||
// Allow infinitely laying eggs if they can't get hungry.
|
||||
if (TryComp<HungerComponent>(uid, out var hunger))
|
||||
if (TryComp<SatiationComponent>(uid, out var satiation))
|
||||
{
|
||||
if (_hunger.GetHunger(hunger) < egglayer.HungerUsage)
|
||||
if (_satiation.GetValueOrNull((uid, satiation), SatiationSystem.Hunger) < egglayer.HungerUsage)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("action-popup-lay-egg-too-hungry"), uid, uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
_hunger.ModifyHunger(uid, -egglayer.HungerUsage, hunger);
|
||||
_satiation.ModifyValue((uid, satiation), SatiationSystem.Hunger, -egglayer.HungerUsage);
|
||||
}
|
||||
|
||||
foreach (var ent in EntitySpawnCollection.GetSpawns(egglayer.EggSpawn, _random))
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.NPC.HTN.Preconditions;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the active hand entity has the specified components.
|
||||
/// </summary>
|
||||
public sealed partial class HungryPrecondition : HTNPrecondition
|
||||
{
|
||||
[Dependency] private IEntityManager _entManager = default!;
|
||||
|
||||
[DataField(required: true)]
|
||||
public HungerThreshold MinHungerState = HungerThreshold.Starving;
|
||||
|
||||
public override bool IsMet(NPCBlackboard blackboard)
|
||||
{
|
||||
if (!blackboard.TryGetValue<EntityUid>(NPCBlackboard.Owner, out var owner, _entManager))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _entManager.TryGetComponent<HungerComponent>(owner, out var hunger) ? hunger.CurrentThreshold <= MinHungerState : false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.NPC.HTN.Preconditions;
|
||||
|
||||
/// <summary>
|
||||
/// IsMet if has a satiation component whose satiation for <see cref="BaseSatiationPrecondition.SatiationType"/> meets
|
||||
/// the criteria specified by <see cref="Above"/> and <see cref="Below"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="SatiationSystem.IsValueInRange"/>
|
||||
public sealed partial class BaseSatiationPrecondition : HTNPrecondition
|
||||
{
|
||||
[Dependency] private IEntityManager _entManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The bottom of the range the agent's satiation must be in for this condition to be met. If null, the range has
|
||||
/// no bottom.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SatiationValue? Above;
|
||||
|
||||
/// <summary>
|
||||
/// The top of the range the agent's satiation must be in for this condition to be met. If null, the range has no
|
||||
/// bottom.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SatiationValue? Below;
|
||||
|
||||
/// <summary>
|
||||
/// The type of the satiation considered by this condition. If the agent does not have this satiation type, this
|
||||
/// condition can never be met.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SatiationTypePrototype> SatiationType;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsMet(NPCBlackboard blackboard)
|
||||
{
|
||||
if (!blackboard.TryGetValue<EntityUid>(NPCBlackboard.Owner, out var owner, _entManager) ||
|
||||
!_entManager.TryGetComponent<SatiationComponent>(owner, out var satiation))
|
||||
return false;
|
||||
|
||||
return _entManager.System<SatiationSystem>()
|
||||
.IsValueInRange(
|
||||
(owner, satiation),
|
||||
SatiationType,
|
||||
Above,
|
||||
Below
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.NPC.HTN.Preconditions;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the active hand entity has the specified components.
|
||||
/// </summary>
|
||||
public sealed partial class ThirstyPrecondition : HTNPrecondition
|
||||
{
|
||||
[Dependency] private IEntityManager _entManager = default!;
|
||||
|
||||
[DataField(required: true)]
|
||||
public ThirstThreshold MinThirstState = ThirstThreshold.Parched;
|
||||
|
||||
public override bool IsMet(NPCBlackboard blackboard)
|
||||
{
|
||||
if (!blackboard.TryGetValue<EntityUid>(NPCBlackboard.Owner, out var owner, _entManager))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _entManager.TryGetComponent<ThirstComponent>(owner, out var thirst) ? thirst.CurrentThirstThreshold <= MinThirstState : false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
using Content.Shared.Nutrition.Prototypes;
|
||||
|
||||
namespace Content.Server.NPC.Queries.Considerations;
|
||||
|
||||
public sealed partial class DrinkValueCon : UtilityConsideration
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This consideration will return 0 if the entity's thirst is above this value.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public SatiationValue ThirstThreshold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
using Content.Shared.Nutrition.Prototypes;
|
||||
|
||||
namespace Content.Server.NPC.Queries.Considerations;
|
||||
|
||||
public sealed partial class FoodValueCon : UtilityConsideration
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This consideration will return 0 if the entity's hunger is above this value.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public SatiationValue HungerThreshold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.NPC.Queries;
|
||||
|
|
@ -5,17 +6,24 @@ using Content.Server.NPC.Queries.Considerations;
|
|||
using Content.Server.NPC.Queries.Curves;
|
||||
using Content.Server.NPC.Queries.Queries;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Fluids.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Stealth;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Temperature.Components;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Content.Shared.Turrets;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
|
|
@ -26,14 +34,6 @@ using Microsoft.Extensions.ObjectPool;
|
|||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Temperature.Components;
|
||||
using Content.Shared.Stealth;
|
||||
using Content.Shared.Stealth.Components;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
|
|
@ -58,6 +58,7 @@ public sealed partial class NPCUtilitySystem : EntitySystem
|
|||
[Dependency] private MobThresholdSystem _thresholdSystem = default!;
|
||||
[Dependency] private TurretTargetSettingsSystem _turretTargetSettings = default!;
|
||||
[Dependency] private DamageableSystem _damageable = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private SharedStealthSystem _stealth = default!;
|
||||
[Dependency] private EntityQuery<PuddleComponent> _puddleQuery = default!;
|
||||
|
||||
|
|
@ -164,7 +165,7 @@ public sealed partial class NPCUtilitySystem : EntitySystem
|
|||
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
||||
switch (consideration)
|
||||
{
|
||||
case FoodValueCon:
|
||||
case FoodValueCon foodValueConsideration:
|
||||
{
|
||||
// do we have a mouth available? Is the food item opened?
|
||||
if (!_ingestion.CanConsume(owner, targetUid))
|
||||
|
|
@ -173,7 +174,9 @@ public sealed partial class NPCUtilitySystem : EntitySystem
|
|||
var avoidBadFood = !HasComp<IgnoreBadFoodComponent>(owner);
|
||||
|
||||
// only eat when hungry or if it will eat anything
|
||||
if (TryComp<HungerComponent>(owner, out var hunger) && hunger.CurrentThreshold > HungerThreshold.Okay && avoidBadFood)
|
||||
if (TryComp<SatiationComponent>(owner, out var satiation) &&
|
||||
_satiation.IsValueInRange((owner, satiation), SatiationSystem.Hunger, below: foodValueConsideration.HungerThreshold) &&
|
||||
avoidBadFood)
|
||||
return 0f;
|
||||
|
||||
// no mouse don't eat the uranium-235
|
||||
|
|
@ -186,14 +189,15 @@ public sealed partial class NPCUtilitySystem : EntitySystem
|
|||
|
||||
return 1f;
|
||||
}
|
||||
case DrinkValueCon:
|
||||
case DrinkValueCon drinkValueConsideration:
|
||||
{
|
||||
// can't drink closed drinks and can't drink with a mask on...
|
||||
if (!_ingestion.CanConsume(owner, targetUid))
|
||||
return 0f;
|
||||
|
||||
// only drink when thirsty
|
||||
if (TryComp<ThirstComponent>(owner, out var thirst) && thirst.CurrentThirstThreshold > ThirstThreshold.Okay)
|
||||
if (TryComp<SatiationComponent>(owner, out var satiation) &&
|
||||
_satiation.IsValueInRange((owner, satiation), SatiationSystem.Thirst, below: drinkValueConsideration.ThirstThreshold))
|
||||
return 0f;
|
||||
|
||||
// no janicow don't drink the blood puddle
|
||||
|
|
@ -511,7 +515,7 @@ public sealed partial class NPCUtilitySystem : EntitySystem
|
|||
{
|
||||
switch (filter)
|
||||
{
|
||||
case Content.Server.NPC.Queries.Queries.ComponentFilter compFilter:
|
||||
case ComponentFilter compFilter:
|
||||
{
|
||||
_entityList.Clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
|
@ -66,9 +66,9 @@ public sealed partial class FatExtractorComponent : Component
|
|||
public EntityUid? Stream;
|
||||
|
||||
/// <summary>
|
||||
/// A minium hunger threshold for extracting nutrition.
|
||||
/// A minium hunger threshold for extracting nutrition, as specified by a <see cref="SatiationPrototype.Thresholds"/>.
|
||||
/// Ignored when emagged.
|
||||
/// </summary>
|
||||
[DataField("minHungerThreshold")]
|
||||
public HungerThreshold MinHungerThreshold = HungerThreshold.Okay;
|
||||
[DataField(required: true)]
|
||||
public SatiationValue MinHungerThreshold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ using Content.Shared.NameModifier.EntitySystems;
|
|||
using Content.Shared.Nutrition.AnimalHusbandry;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ namespace Content.Server.Nutrition.EntitySystems;
|
|||
public sealed partial class AnimalHusbandrySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
|
|
@ -121,12 +123,20 @@ public sealed partial class AnimalHusbandrySystem : EntitySystem
|
|||
if (TryComp<InteractionPopupComponent>(uid, out var interactionPopup))
|
||||
_audio.PlayPvs(interactionPopup.InteractSuccessSound, uid);
|
||||
|
||||
_hunger.ModifyHunger(uid, -component.HungerPerBirth);
|
||||
_hunger.ModifyHunger(partner, -component.HungerPerBirth);
|
||||
if (TryComp<SatiationComponent>(uid, out var satiation))
|
||||
{
|
||||
_satiation.ModifyValue((uid, satiation), SatiationSystem.Hunger, -component.HungerPerBirth);
|
||||
}
|
||||
|
||||
if (TryComp<SatiationComponent>(partner, out var partnerSatiation))
|
||||
{
|
||||
_satiation.ModifyValue((partner, partnerSatiation), SatiationSystem.Hunger, -component.HungerPerBirth);
|
||||
}
|
||||
|
||||
component.GestationEndTime = _timing.CurTime + component.GestationDuration;
|
||||
component.Gestating = true;
|
||||
_adminLog.Add(LogType.Action, $"{ToPrettyString(uid)} (carrier) and {ToPrettyString(partner)} (partner) successfully bred.");
|
||||
_adminLog.Add(LogType.Action,
|
||||
$"{ToPrettyString(uid)} (carrier) and {ToPrettyString(partner)} (partner) successfully bred.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -148,11 +158,27 @@ public sealed partial class AnimalHusbandrySystem : EntitySystem
|
|||
if (_mobState.IsIncapacitated(uid))
|
||||
return false;
|
||||
|
||||
if (TryComp<HungerComponent>(uid, out var hunger) && _hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay)
|
||||
return false;
|
||||
// If no satiations, no limit to reproduction
|
||||
if (!TryComp<SatiationComponent>(uid, out var satiation))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryComp<ThirstComponent>(uid, out var thirst) && thirst.CurrentThirstThreshold < ThirstThreshold.Okay)
|
||||
return false;
|
||||
// Check minimum hunger requirement
|
||||
if (component!.MinHungerThreshold is { } minHunger)
|
||||
{
|
||||
if (!satiation.Has(SatiationSystem.Hunger) ||
|
||||
_satiation.IsValueInRange((uid, satiation), SatiationSystem.Hunger, below: minHunger))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check minimum thirst requirement
|
||||
if (component.MinThirstThreshold is { } minThirst)
|
||||
{
|
||||
if (!satiation.Has(SatiationSystem.Thirst) ||
|
||||
_satiation.IsValueInRange((uid, satiation), SatiationSystem.Thirst, below: minThirst))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -199,6 +225,7 @@ public sealed partial class AnimalHusbandrySystem : EntitySystem
|
|||
// Make sure the name prefix is applied
|
||||
_nameMod.RefreshNameModifiers(offspring);
|
||||
}
|
||||
|
||||
_adminLog.Add(LogType.Action, $"{ToPrettyString(uid)} gave birth to {ToPrettyString(offspring)}.");
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +252,8 @@ public sealed partial class AnimalHusbandrySystem : EntitySystem
|
|||
|
||||
if (_timing.CurTime < reproductive.NextBreedAttempt)
|
||||
continue;
|
||||
reproductive.NextBreedAttempt += _random.Next(reproductive.MinBreedAttemptInterval, reproductive.MaxBreedAttemptInterval);
|
||||
reproductive.NextBreedAttempt +=
|
||||
_random.Next(reproductive.MinBreedAttemptInterval, reproductive.MaxBreedAttemptInterval);
|
||||
|
||||
// no.
|
||||
if (HasComp<ActorComponent>(uid) || TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Nutrition.EntitySystems;
|
||||
|
|
@ -22,7 +23,7 @@ public sealed partial class FatExtractorSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private EmagSystem _emag = default!;
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
|
||||
|
|
@ -95,23 +96,35 @@ public sealed partial class FatExtractorSystem : EntitySystem
|
|||
component.Stream = _audio.Stop(component.Stream);
|
||||
}
|
||||
|
||||
public bool TryGetValidOccupant(EntityUid uid, [NotNullWhen(true)] out EntityUid? occupant, FatExtractorComponent? component = null, EntityStorageComponent? storage = null)
|
||||
public bool TryGetValidOccupant(EntityUid uid,
|
||||
[NotNullWhen(true)] out Entity<SatiationComponent>? occupant,
|
||||
FatExtractorComponent? component = null,
|
||||
EntityStorageComponent? storage = null)
|
||||
{
|
||||
occupant = null;
|
||||
if (!Resolve(uid, ref component, ref storage))
|
||||
return false;
|
||||
|
||||
occupant = storage.Contents.ContainedEntities.FirstOrDefault();
|
||||
var firstEntity = storage.Contents.ContainedEntities.FirstOrDefault();
|
||||
if (firstEntity is not { Valid: true })
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryComp<HungerComponent>(occupant, out var hunger))
|
||||
if (!TryComp<SatiationComponent>(firstEntity, out var satiation))
|
||||
return false;
|
||||
|
||||
if (_hunger.GetHunger(hunger) < component.NutritionPerSecond)
|
||||
Entity<SatiationComponent> entity = (firstEntity, satiation);
|
||||
|
||||
if (_satiation.GetValueOrNull(entity, SatiationSystem.Hunger) < component.NutritionPerSecond &&
|
||||
!_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
return false;
|
||||
|
||||
if (hunger.CurrentThreshold < component.MinHungerThreshold && !_emag.CheckFlag(uid, EmagType.Interaction))
|
||||
if (_satiation.IsValueInRange(entity, SatiationSystem.Hunger, below: component.MinHungerThreshold) &&
|
||||
!HasComp<EmaggedComponent>(uid))
|
||||
return false;
|
||||
|
||||
occupant = entity;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +153,7 @@ public sealed partial class FatExtractorSystem : EntitySystem
|
|||
continue;
|
||||
fat.NextUpdate += fat.UpdateTime;
|
||||
|
||||
_hunger.ModifyHunger(occupant.Value, -fat.NutritionPerSecond);
|
||||
_satiation.ModifyValue(occupant.Value, SatiationSystem.Hunger, -fat.NutritionPerSecond);
|
||||
fat.NutrientAccumulator += fat.NutritionPerSecond;
|
||||
if (fat.NutrientAccumulator >= fat.NutrientPerMeat)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Nutrition;
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class Hungry : LocalizedEntityCommands
|
||||
{
|
||||
public override string Command => "hungry";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-player"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is not {Valid: true} playerEntity)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(playerEntity, out HungerComponent? hunger))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-component", ("comp", nameof(HungerComponent))));
|
||||
return;
|
||||
}
|
||||
|
||||
var hungryThreshold = hunger.Thresholds[HungerThreshold.Starving];
|
||||
EntityManager.System<HungerSystem>().SetHunger(playerEntity, hungryThreshold, hunger);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Server.Database.Migrations.Postgres;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.Console;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Nutrition;
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class SetNutrit : LocalizedEntityCommands
|
||||
{
|
||||
public override string Command => "setnutrit";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-player"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is not { Valid: true } playerEntity)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific",
|
||||
("properAmount", 2),
|
||||
("currentAmount", args.Length)
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
var systemString = args[0];
|
||||
switch (systemString)
|
||||
{
|
||||
case "hunger":
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(playerEntity, out HungerComponent? hunger))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-component", ("comp", nameof(HungerComponent))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(args[1], out HungerThreshold hungerThreshold))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-setnutrit-error-invalid-threshold",
|
||||
("thresholdType", nameof(HungerThreshold)),
|
||||
("thresholdString", args[1])
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
var hungerValue = hunger.Thresholds[hungerThreshold];
|
||||
EntityManager.System<HungerSystem>().SetHunger(playerEntity, hungerValue, hunger);
|
||||
return;
|
||||
}
|
||||
case "thirst":
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(playerEntity, out ThirstComponent? thirst))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-component", ("comp", nameof(ThirstComponent))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(args[1], out ThirstThreshold thirstThreshold))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-setnutrit-error-invalid-threshold",
|
||||
("thresholdType", nameof(ThirstThreshold)),
|
||||
("thresholdString", args[1])
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
var thirstValue = thirst.ThirstThresholds[thirstThreshold];
|
||||
EntityManager.System<ThirstSystem>().SetThirst(playerEntity, thirst, thirstValue);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
shell.WriteError($"invalid nutrition system ${systemString}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
string[] kinds = { "hunger", "thirst" };
|
||||
return CompletionResult.FromHintOptions(kinds, "nutrition system");
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
return args[0] switch
|
||||
{
|
||||
"hunger" => CompletionResult.FromHintOptions(Enum.GetNames<HungerThreshold>(), nameof(HungerThreshold)),
|
||||
"thirst" => CompletionResult.FromHintOptions(Enum.GetNames<ThirstThreshold>(), nameof(ThirstThreshold)),
|
||||
_ => CompletionResult.Empty,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Content.Server/Nutrition/SetSatiation.cs
Normal file
135
Content.Server/Nutrition/SetSatiation.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Nutrition;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the running player's entity's satiation of a given type to a given value or threshold.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed partial class SetSatiation : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private IPrototypeManager _proto = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Command => "setsatiation";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-only-players-can-run-this-command"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is not { Valid: true } playerEntity)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific",
|
||||
("properAmount", 2),
|
||||
("currentAmount", args.Length)
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent<SatiationComponent>(playerEntity, out var comp))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component",
|
||||
("uid", playerEntity.Id),
|
||||
("componentName", nameof(SatiationComponent))));
|
||||
return;
|
||||
}
|
||||
|
||||
ProtoId<SatiationTypePrototype> type = args[0];
|
||||
if (comp.GetOrNull(type) is not { } satiation)
|
||||
{
|
||||
var typeName = _proto.TryIndex(type, out var t) ? t.Name : $"\"{type}\"";
|
||||
shell.WriteLine(Loc.GetString(
|
||||
"shell-target-entity-does-not-have-message",
|
||||
("missing", Loc.GetString("cmd-nutrition-setsatiation-need", ("satiation", typeName)))
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_proto.Resolve(satiation.Prototype, out var proto))
|
||||
{
|
||||
shell.WriteError(Loc.GetString(
|
||||
"cmd-nutrition-setsatiation-prototype-error",
|
||||
("satiation-proto-id", satiation.Prototype)
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
if (float.TryParse(args[1], out var value))
|
||||
{
|
||||
_satiation.SetValue((playerEntity, comp), type, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!proto.Thresholds.TryGetValue(args[1], out var valueFromKey))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString(
|
||||
"cmd-nutrition-setsatiation-no-matching-key-error",
|
||||
("key", args[1]),
|
||||
("satiation-proto-id", proto.ID)
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
_satiation.SetValue((playerEntity, comp), type, value: valueFromKey);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (shell.Player?.AttachedEntity is not { } playerEntity ||
|
||||
!EntityManager.TryGetComponent<SatiationComponent>(playerEntity, out var comp))
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var entity = new Entity<SatiationComponent>(playerEntity, comp);
|
||||
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
return CompletionResult.FromHintOptions(
|
||||
entity.Comp.Satiations.Keys.Select(it => it.Id),
|
||||
nameof(SatiationTypePrototype)
|
||||
);
|
||||
case 2:
|
||||
if (!_proto.TryIndex<SatiationPrototype>(args[0], out var satiationProto) ||
|
||||
_satiation.GetMaximumValue(entity, satiationProto.ID) is not { } maxValue)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var keys = _satiation.GetKeysForType(entity, satiationProto.ID)
|
||||
.Select(key =>
|
||||
new CompletionOption(key, Loc.GetString("cmd-nutrition-setsatiation-hint-key")));
|
||||
|
||||
return CompletionResult.FromHintOptions(
|
||||
keys.Concat(
|
||||
[
|
||||
new CompletionOption(maxValue.ToString(CultureInfo.InvariantCulture),
|
||||
Loc.GetString("cmd-nutrition-setsatiation-hint-max-value"))
|
||||
]
|
||||
),
|
||||
hint: null
|
||||
);
|
||||
default:
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Nutrition;
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class Thirsty : LocalizedEntityCommands
|
||||
{
|
||||
public override string Command => "thirsty";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-player"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is not {Valid: true} playerEntity)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(playerEntity, out ThirstComponent? thirst))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-nutrition-error-component", ("comp", nameof(ThirstComponent))));
|
||||
return;
|
||||
}
|
||||
|
||||
var thirstyThreshold = thirst.ThirstThresholds[ThirstThreshold.Parched];
|
||||
EntityManager.System<ThirstSystem>().SetThirst(playerEntity, thirst, thirstyThreshold);
|
||||
}
|
||||
}
|
||||
103
Content.Server/Nutrition/Unsatiate.cs
Normal file
103
Content.Server/Nutrition/Unsatiate.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Nutrition;
|
||||
|
||||
/// <summary>
|
||||
/// This command sets the specified <see cref="Satiation">Satiation(s)</see> to 10% of its maximum.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed partial class Unsatiate : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Command => "unsatiate";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-only-players-can-run-this-command"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is not { Valid: true } playerEntity)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(playerEntity, out SatiationComponent? satiation))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component",
|
||||
("uid", playerEntity.Id),
|
||||
("componentName", nameof(SatiationComponent))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
ExecuteWithoutArgs((playerEntity, satiation));
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecuteWithArgs(shell, (playerEntity, satiation), args);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteWithoutArgs(Entity<SatiationComponent> entity)
|
||||
{
|
||||
foreach (var satiation in entity.Comp.Satiations.Keys)
|
||||
{
|
||||
if (_satiation.GetMaximumValue(entity, satiation) is not { } maxValue)
|
||||
continue;
|
||||
|
||||
_satiation.SetValue(entity, satiation, maxValue / 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteWithArgs(IConsoleShell shell, Entity<SatiationComponent> entity, string[] args)
|
||||
{
|
||||
// Check all of the types given before modifying any of them.
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (_protoMan.TryIndex<SatiationPrototype>(arg, out var satiationType) && entity.Comp.Has(satiationType.ID))
|
||||
continue;
|
||||
|
||||
shell.WriteLine(Loc.GetString("shell-target-entity-does-not-have-message",
|
||||
("missing", Loc.GetString("cmd-nutrition-satiation-need", ("satiation", arg)))));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (_satiation.GetMaximumValue(entity, arg) is not { } maxValue)
|
||||
continue;
|
||||
|
||||
_satiation.SetValue(entity, arg, maxValue / 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
// Always suggest any satiation types which aren't already in the arg list.
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (shell.Player?.AttachedEntity is not { } playerEntity ||
|
||||
!EntityManager.TryGetComponent<SatiationComponent>(playerEntity, out var comp))
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var absentSatiationTypeIds =
|
||||
comp.Satiations.Keys.Select(it => it.Id).Where(type => !args.Contains(type));
|
||||
return CompletionResult.FromHintOptions(absentSatiationTypeIds, nameof(SatiationTypePrototype));
|
||||
}
|
||||
}
|
||||
|
|
@ -6,14 +6,16 @@ using Content.Server.NPC.HTN;
|
|||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.RatKing;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Chat;
|
||||
|
||||
namespace Content.Server.RatKing
|
||||
{
|
||||
|
|
@ -23,7 +25,7 @@ namespace Content.Server.RatKing
|
|||
[Dependency] private AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private ChatSystem _chat = default!;
|
||||
[Dependency] private HTNSystem _htn = default!;
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private NPCSystem _npc = default!;
|
||||
[Dependency] private PopupSystem _popup = default!;
|
||||
|
||||
|
|
@ -44,17 +46,17 @@ namespace Content.Server.RatKing
|
|||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp<HungerComponent>(uid, out var hunger))
|
||||
if (!TryComp<SatiationComponent>(uid, out var satiation))
|
||||
return;
|
||||
|
||||
//make sure the hunger doesn't go into the negatives
|
||||
if (_hunger.GetHunger(hunger) < component.HungerPerArmyUse)
|
||||
if (_satiation.GetValueOrNull((uid, satiation), SatiationSystem.Hunger) < component.HungerPerArmyUse)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
|
||||
return;
|
||||
}
|
||||
args.Handled = true;
|
||||
_hunger.ModifyHunger(uid, -component.HungerPerArmyUse, hunger);
|
||||
_satiation.ModifyValue((uid, satiation), SatiationSystem.Hunger, -component.HungerPerArmyUse);
|
||||
var servant = Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates);
|
||||
var comp = EnsureComp<RatKingServantComponent>(servant);
|
||||
comp.King = uid;
|
||||
|
|
@ -74,17 +76,17 @@ namespace Content.Server.RatKing
|
|||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!TryComp<HungerComponent>(uid, out var hunger))
|
||||
if (!TryComp<SatiationComponent>(uid, out var satiation))
|
||||
return;
|
||||
|
||||
//make sure the hunger doesn't go into the negatives
|
||||
if (_hunger.GetHunger(hunger) < component.HungerPerDomainUse)
|
||||
if (_satiation.GetValueOrNull((uid, satiation), SatiationSystem.Hunger) < component.HungerPerDomainUse)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
|
||||
return;
|
||||
}
|
||||
args.Handled = true;
|
||||
_hunger.ModifyHunger(uid, -component.HungerPerDomainUse, hunger);
|
||||
_satiation.ModifyValue((uid, satiation), SatiationSystem.Hunger, -component.HungerPerDomainUse);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("rat-king-domain-popup"), uid);
|
||||
var tileMix = _atmos.GetTileMixture(uid, excite: true);
|
||||
|
|
|
|||
|
|
@ -137,8 +137,7 @@ public sealed partial class ZombieSystem
|
|||
//get diseases, breath, be thirst, be hungry, die in space, get double sentience, have offspring or be paraplegic.
|
||||
RemComp<RespiratorComponent>(target);
|
||||
RemComp<BarotraumaComponent>(target);
|
||||
RemComp<HungerComponent>(target);
|
||||
RemComp<ThirstComponent>(target);
|
||||
RemComp<SatiationComponent>(target);
|
||||
RemComp<ReproductiveComponent>(target);
|
||||
RemComp<ReproductivePartnerComponent>(target);
|
||||
RemComp<LegsParalyzedComponent>(target);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
|
|
@ -43,6 +44,12 @@ public sealed partial class UdderComponent : Component
|
|||
[DataField, AutoNetworkedField]
|
||||
public float HungerUsage = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// If the entity's hunger satiation is below this value, it cannot be milked.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public SatiationValue MinHungerThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// How long to wait before producing.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ using Content.Shared.IdentityManagement;
|
|||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Udder;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Animals;
|
||||
|
|
@ -18,7 +20,7 @@ namespace Content.Shared.Animals;
|
|||
/// </summary>
|
||||
public sealed partial class UdderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private SharedPopupSystem _popupSystem = default!;
|
||||
|
|
@ -71,13 +73,15 @@ public sealed partial class UdderSystem : EntitySystem
|
|||
continue;
|
||||
|
||||
// Actually there is food digestion so no problem with instant reagent generation "OnFeed"
|
||||
if (TryComp(uid, out HungerComponent? hunger))
|
||||
if (TryComp<SatiationComponent>(uid, out var satiation))
|
||||
{
|
||||
// Is there enough nutrition to produce reagent?
|
||||
if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay)
|
||||
if (_satiation.IsValueInRange((uid, satiation), SatiationSystem.Hunger, above: udder.MinHungerThreshold, hypotheticalValueDelta: -udder.HungerUsage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_hunger.ModifyHunger(uid, -udder.HungerUsage, hunger);
|
||||
_satiation.ModifyValue((uid, satiation), SatiationSystem.Hunger, -udder.HungerUsage);
|
||||
}
|
||||
|
||||
//TODO: toxins from bloodstream !?
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
|
|
@ -43,6 +44,12 @@ public sealed partial class WoolyComponent : Component
|
|||
[DataField, AutoNetworkedField]
|
||||
public float HungerUsage = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// If the entity's hunger satiation is below this value, it cannot be milked.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public SatiationValue MinHungerThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// How long to wait before growing wool.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ using Content.Shared.Mobs.Systems;
|
|||
using Content.Shared.Nutrition;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Animals;
|
||||
|
|
@ -14,7 +16,7 @@ namespace Content.Shared.Animals;
|
|||
/// </summary>
|
||||
public sealed partial class WoolySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
|
@ -64,13 +66,15 @@ public sealed partial class WoolySystem : EntitySystem
|
|||
continue;
|
||||
|
||||
// Actually there is food digestion so no problem with instant reagent generation "OnFeed"
|
||||
if (TryComp(uid, out HungerComponent? hunger))
|
||||
if (TryComp<SatiationComponent>(uid, out var satiation))
|
||||
{
|
||||
// Is there enough nutrition to produce reagent?
|
||||
if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay)
|
||||
if (_satiation.IsValueInRange((uid, satiation), SatiationSystem.Hunger, above: wooly.MinHungerThreshold, hypotheticalValueDelta: -wooly.HungerUsage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_hunger.ModifyHunger(uid, -wooly.HungerUsage, hunger);
|
||||
_satiation.ModifyValue((uid, satiation), SatiationSystem.Hunger, -wooly.HungerUsage);
|
||||
}
|
||||
|
||||
_solutionContainer.TryAddReagent(wooly.Solution.Value, wooly.ReagentId, wooly.Quantity, out _);
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.EntityConditions.Conditions.Body;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this entity's hunger is within a specified minimum and maximum.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||
public sealed partial class TotalHungerEntityConditionSystem : EntityConditionSystem<HungerComponent, HungerCondition>
|
||||
{
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
|
||||
protected override void Condition(Entity<HungerComponent> entity, ref EntityConditionEvent<HungerCondition> args)
|
||||
{
|
||||
var total = _hunger.GetHunger(entity.Comp);
|
||||
args.Result = total >= args.Condition.Min && total <= args.Condition.Max;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityCondition"/>
|
||||
public sealed partial class HungerCondition : EntityConditionBase<HungerCondition>
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum hunger required to fulfill this condition.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Min;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum hunger required to fulfill this condition.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Max = float.PositiveInfinity;
|
||||
|
||||
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||
Loc.GetString("entity-condition-guidebook-total-hunger", ("max", float.IsPositiveInfinity(Max) ? int.MaxValue : Max), ("min", Min));
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.EntityConditions.Conditions.Body;
|
||||
|
||||
/// <summary>
|
||||
/// A condition which passes if the specified <see cref="SatiationTypePrototype"/> is between the specified
|
||||
/// <see cref="SatiationCondition.Min"/> and <see cref="SatiationCondition.Max"/>. If the entity does not have the
|
||||
/// specified satiation, the condition evaluates to false.
|
||||
/// </summary>
|
||||
public sealed partial class SatiationEntityConditionSystem : EntityConditionSystem<SatiationComponent, SatiationCondition>
|
||||
{
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Condition(Entity<SatiationComponent> entity,
|
||||
ref EntityConditionEvent<SatiationCondition> args)
|
||||
{
|
||||
if (_satiation.GetValueOrNull(entity, args.Condition.SatiationType) is not { } satiation)
|
||||
return;
|
||||
|
||||
args.Result =
|
||||
(args.Condition.MinInclusive && satiation >= args.Condition.Min || satiation > args.Condition.Min) &&
|
||||
(args.Condition.MaxInclusive && satiation <= args.Condition.Max || satiation < args.Condition.Max);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityCondition"/>
|
||||
public sealed partial class SatiationCondition : EntityConditionBase<SatiationCondition>
|
||||
{
|
||||
/// <summary>
|
||||
/// The value above which this condition will fail. If <see cref="MaxInclusive"/> is false, the condition will fail
|
||||
/// if at that value as well.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Max = float.PositiveInfinity;
|
||||
|
||||
/// <summary>
|
||||
/// The value below which this condition will fail. If <see cref="MinInclusive"/> is false, the condition will fail
|
||||
/// if at that value as well.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Min = 0;
|
||||
|
||||
/// <summary>
|
||||
/// If <c>true</c>, values exactly equal to <see cref="Max"/> will NOT fail.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool MaxInclusive = false;
|
||||
|
||||
/// <summary>
|
||||
/// If <c>true</c>, values exactly equal to <see cref="Min"/> will NOT fail.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool MinInclusive = false;
|
||||
|
||||
/// <summary>
|
||||
/// The type of satiation whose value will be considered.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<SatiationTypePrototype> SatiationType;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
|
||||
{
|
||||
return Loc.GetString("entity-condition-guidebook-total-satiation",
|
||||
("max", float.IsPositiveInfinity(Max) ? int.MaxValue : Max),
|
||||
("min", Min),
|
||||
("type", prototype.Index(SatiationType).Name));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +1,49 @@
|
|||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.EntityEffects.Effects.Body;
|
||||
|
||||
// TODO: These systems are in the same file since satiation should be one system instead of two. Combine these when that happens.
|
||||
// TODO: Arguably oxygen saturation should also be added here...
|
||||
/// <summary>
|
||||
/// Modifies the thirst level of a given entity, multiplied by scale.
|
||||
/// Attempts to find a <see cref="SatiationComponent"/> on the target, and update its value of the specified
|
||||
/// <see cref="SatiationTypePrototype"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||
public sealed partial class SatiateThirstEntityEffectsSystem : EntityEffectSystem<ThirstComponent, SatiateThirst>
|
||||
public sealed partial class SatiateEntityEffectsSystem : EntityEffectSystem<SatiationComponent, Satiate>
|
||||
{
|
||||
[Dependency] private ThirstSystem _thirst = default!;
|
||||
protected override void Effect(Entity<ThirstComponent> entity, ref EntityEffectEvent<SatiateThirst> args)
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
|
||||
protected override void Effect(Entity<SatiationComponent> entity, ref EntityEffectEvent<Satiate> args)
|
||||
{
|
||||
_thirst.ModifyThirst(entity, entity.Comp, args.Effect.Factor * args.Scale);
|
||||
_satiation.ModifyValue(entity, args.Effect.SatiationType, args.Effect.Factor * args.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the hunger level of a given entity, multiplied by scale.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||
public sealed partial class SatiateHungerEntityEffectsSystem : EntityEffectSystem<HungerComponent, SatiateHunger>
|
||||
{
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
protected override void Effect(Entity<HungerComponent> entity, ref EntityEffectEvent<SatiateHunger> args)
|
||||
{
|
||||
_hunger.ModifyHunger(entity, args.Effect.Factor * args.Scale, entity.Comp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A type of <see cref="EntityEffectBase{T}"/> made for satiation effects.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The effect inheriting this BaseEffect</typeparam>
|
||||
/// <inheritdoc cref="EntityEffect"/>
|
||||
public abstract partial class Satiate<T> : EntityEffectBase<T> where T : EntityEffectBase<T>
|
||||
public sealed partial class Satiate : EntityEffectBase<Satiate>
|
||||
{
|
||||
public const float AverageSatiation = 3f; // Magic number. Not sure how it was calculated since I didn't make it.
|
||||
|
||||
/// <summary>
|
||||
/// Change in satiation.
|
||||
/// How much the specified <see cref="SatiationType"/> is modified by this effect.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Factor = 1.5f;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Satiate{T}"/>
|
||||
public sealed partial class SatiateThirst : Satiate<SatiateThirst>
|
||||
{
|
||||
public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("entity-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative", Factor / AverageSatiation));
|
||||
}
|
||||
/// <summary>
|
||||
/// The type of satiation to modify.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<SatiationTypePrototype> SatiationType;
|
||||
|
||||
/// <inheritdoc cref="Satiate{T}"/>
|
||||
public sealed partial class SatiateHunger : Satiate<SatiateHunger>
|
||||
{
|
||||
public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("entity-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", Factor / AverageSatiation));
|
||||
{
|
||||
return Loc.GetString(
|
||||
"entity-effect-guidebook-satiate",
|
||||
("chance", Probability),
|
||||
("relative", Factor / AverageSatiation),
|
||||
("type", prototype.Index(SatiationType).Name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,8 +104,7 @@ public partial class InventorySystem
|
|||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowJobIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthBarsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHealthIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowHungerIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowThirstIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSatiationIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RefRelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RefRelayInventoryEvent);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Content.Shared.Mobs.Systems;
|
|||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
|
@ -22,15 +23,14 @@ namespace Content.Shared.Medical;
|
|||
public sealed partial class VomitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private INetManager _netManager = default!;
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private MovementModStatusSystem _movementMod = default!;
|
||||
[Dependency] private ThirstSystem _thirst = default!;
|
||||
[Dependency] private SharedAudioSystem _audio = default!;
|
||||
[Dependency] private SharedBloodstreamSystem _bloodstream = default!;
|
||||
[Dependency] private SharedForensicsSystem _forensics = default!;
|
||||
[Dependency] private SharedPopupSystem _popup = default!;
|
||||
[Dependency] private SharedPuddleSystem _puddle = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
|
@ -86,11 +86,12 @@ public sealed partial class VomitSystem : EntitySystem
|
|||
return;
|
||||
|
||||
// Vomiting makes you hungrier and thirstier
|
||||
if (TryComp<HungerComponent>(uid, out var hunger))
|
||||
_hunger.ModifyHunger(uid, hungerAdded, hunger);
|
||||
|
||||
if (TryComp<ThirstComponent>(uid, out var thirst))
|
||||
_thirst.ModifyThirst(uid, thirst, thirstAdded);
|
||||
if (TryComp<SatiationComponent>(uid, out var satiation))
|
||||
{
|
||||
Entity<SatiationComponent> entity = (uid, satiation);
|
||||
_satiation.ModifyValue(entity, SatiationSystem.Hunger, hungerAdded);
|
||||
_satiation.ModifyValue(entity, SatiationSystem.Thirst, thirstAdded);
|
||||
}
|
||||
|
||||
// It fully empties the stomach, this amount from the chem stream is relatively small
|
||||
var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Shared.Storage;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
|
|
@ -86,6 +87,18 @@ public sealed partial class ReproductiveComponent : Component
|
|||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float HungerPerBirth = 75f;
|
||||
|
||||
/// <summary>
|
||||
/// If the entity's hunger satiation is below this value, it cannot reproduce.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SatiationValue? MinHungerThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// If the entity's thirst satiation is below this value, it cannot reproduce.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SatiationValue? MinThirstThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Popup shown when an entity gives birth.
|
||||
/// Configurable for things like laying eggs.
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Adds text to the entity's description box based on its current hunger threshold.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(ExaminableHungerSystem))]
|
||||
public sealed partial class ExaminableHungerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of hunger thresholds to LocIds of the messages to display.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<HungerThreshold, LocId> Descriptions = new()
|
||||
{
|
||||
{ HungerThreshold.Overfed, "examinable-hunger-component-examine-overfed"},
|
||||
{ HungerThreshold.Okay, "examinable-hunger-component-examine-okay"},
|
||||
{ HungerThreshold.Peckish, "examinable-hunger-component-examine-peckish"},
|
||||
{ HungerThreshold.Starving, "examinable-hunger-component-examine-starving"},
|
||||
{ HungerThreshold.Dead, "examinable-hunger-component-examine-starving"}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// LocId of a fallback message to display if the entity has no <see cref="HungerComponent"/>
|
||||
/// or does not have a value in <see cref="Descriptions"/> for the current threshold.
|
||||
/// </summary>
|
||||
public LocId NoHungerDescription = "examinable-hunger-component-examine-none";
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component which specifies what <see cref="ExaminableSatiationComponent"/> shows in examine descriptions.
|
||||
/// </summary>
|
||||
/// <seealso cref="ExaminableSatiationSystem"/>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
|
||||
[Access(typeof(ExaminableSatiationSystem))]
|
||||
public sealed partial class ExaminableSatiationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Examination localization string IDs per threshold per satiation type.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public Dictionary<ProtoId<SatiationTypePrototype>, SatiationThresholds<LocId?>> Satiations;
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(HungerSystem))]
|
||||
[AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
|
||||
public sealed partial class HungerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The hunger value as authoritatively set by the server as of <see cref="LastAuthoritativeHungerChangeTime"/>.
|
||||
/// This value should be updated relatively infrequently. To get the current hunger, which changes with each update,
|
||||
/// use <see cref="HungerSystem.GetHunger"/>.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
[AutoNetworkedField]
|
||||
public float LastAuthoritativeHungerValue;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which <see cref="LastAuthoritativeHungerValue"/> was last updated.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public TimeSpan LastAuthoritativeHungerChangeTime;
|
||||
|
||||
/// <summary>
|
||||
/// The base amount at which <see cref="LastAuthoritativeHungerValue"/> decays.
|
||||
/// </summary>
|
||||
/// <remarks>Any time this is modified, <see cref="HungerSystem.SetAuthoritativeHungerValue"/> should be called.</remarks>
|
||||
[DataField("baseDecayRate"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float BaseDecayRate = 0.01666666666f;
|
||||
|
||||
/// <summary>
|
||||
/// The actual amount at which <see cref="LastAuthoritativeHungerValue"/> decays.
|
||||
/// Affected by <seealso cref="CurrentThreshold"/>
|
||||
/// </summary>
|
||||
/// <remarks>Any time this is modified, <see cref="HungerSystem.SetAuthoritativeHungerValue"/> should be called.</remarks>
|
||||
[DataField("actualDecayRate"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public float ActualDecayRate;
|
||||
|
||||
/// <summary>
|
||||
/// The last threshold this entity was at.
|
||||
/// 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>
|
||||
/// <remarks>Any time this is modified, <see cref="HungerSystem.SetAuthoritativeHungerValue"/> should be called.</remarks>
|
||||
[DataField("currentThreshold"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public HungerThreshold CurrentThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary relating HungerThreshold to the amount of <see cref="HungerSystem.GetHunger">current hunger</see> needed for each one
|
||||
/// </summary>
|
||||
[DataField("thresholds", customTypeSerializer: typeof(DictionarySerializer<HungerThreshold, float>))]
|
||||
[AutoNetworkedField]
|
||||
public Dictionary<HungerThreshold, float> Thresholds = new()
|
||||
{
|
||||
{ HungerThreshold.Overfed, 200.0f },
|
||||
{ HungerThreshold.Okay, 150.0f },
|
||||
{ HungerThreshold.Peckish, 100.0f },
|
||||
{ HungerThreshold.Starving, 50.0f },
|
||||
{ HungerThreshold.Dead, 0.0f }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary relating hunger thresholds to corresponding alerts.
|
||||
/// </summary>
|
||||
[DataField("hungerThresholdAlerts")]
|
||||
[AutoNetworkedField]
|
||||
public Dictionary<HungerThreshold, ProtoId<AlertPrototype>> HungerThresholdAlerts = new()
|
||||
{
|
||||
{ HungerThreshold.Peckish, "Peckish" },
|
||||
{ HungerThreshold.Starving, "Starving" },
|
||||
{ HungerThreshold.Dead, "Starving" }
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertCategoryPrototype> HungerAlertCategory = "Hunger";
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary relating HungerThreshold to how much they modify <see cref="BaseDecayRate"/>.
|
||||
/// </summary>
|
||||
[DataField("hungerThresholdDecayModifiers", customTypeSerializer: typeof(DictionarySerializer<HungerThreshold, float>))]
|
||||
[AutoNetworkedField]
|
||||
public Dictionary<HungerThreshold, float> HungerThresholdDecayModifiers = new()
|
||||
{
|
||||
{ HungerThreshold.Overfed, 1.2f },
|
||||
{ HungerThreshold.Okay, 1f },
|
||||
{ HungerThreshold.Peckish, 0.8f },
|
||||
{ HungerThreshold.Starving, 0.6f },
|
||||
{ HungerThreshold.Dead, 0.6f }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The amount of slowdown applied when an entity is starving
|
||||
/// </summary>
|
||||
[DataField("starvingSlowdownModifier"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public float StarvingSlowdownModifier = 0.75f;
|
||||
|
||||
/// <summary>
|
||||
/// Damage dealt when your current threshold is at HungerThreshold.Dead
|
||||
/// </summary>
|
||||
[DataField("starvationDamage")]
|
||||
public DamageSpecifier? StarvationDamage;
|
||||
|
||||
/// <summary>
|
||||
/// The time when the hunger threshold will update next.
|
||||
/// </summary>
|
||||
[DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextThresholdUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// The time between each hunger threshold update.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public TimeSpan ThresholdUpdateRate = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum HungerThreshold : byte
|
||||
{
|
||||
Overfed = 1 << 3,
|
||||
Okay = 1 << 2,
|
||||
Peckish = 1 << 1,
|
||||
Starving = 1 << 0,
|
||||
Dead = 0,
|
||||
}
|
||||
37
Content.Shared/Nutrition/Components/SatiationComponent.cs
Normal file
37
Content.Shared/Nutrition/Components/SatiationComponent.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component which is basically just a collection of <see cref="Satiation"/>s keyed by their
|
||||
/// <see cref="SatiationTypePrototype"/>s.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
|
||||
// Nothing should modify the dictionary once it's deserialized. Perhaps satiations can be dynamically
|
||||
// added and removed in the future, but not today.
|
||||
[Access(typeof(SatiationSystem))]
|
||||
public sealed partial class SatiationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The actual <see cref="Satiation"/>s this entity has, keyed by their <see cref="SatiationTypePrototype">type</see>.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
[AutoNetworkedField]
|
||||
public Dictionary<ProtoId<SatiationTypePrototype>, Satiation> Satiations = [];
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this has a <see cref="Satiation"/> of the specified <paramref name="type"/>.
|
||||
/// </summary>
|
||||
[Access(Other = AccessPermissions.Execute)]
|
||||
public bool Has(ProtoId<SatiationTypePrototype> type) => GetOrNull(type) != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Satiation"/> of the given <paramref name="type"/> on this component, or
|
||||
/// <c>null</c> if no such satiation exists.
|
||||
/// </summary>
|
||||
[Access(Other = AccessPermissions.Execute)]
|
||||
public Satiation? GetOrNull(ProtoId<SatiationTypePrototype> type) => Satiations.GetValueOrDefault(type);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Shared.Damage;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component causes its entity to continuously take damage based on the entity's current satiation.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
|
||||
[Access(typeof(SatiationDamageSystem))]
|
||||
public sealed partial class SatiationDamageComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Damage values by satiation threshold, for a satiation type.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public Dictionary<ProtoId<SatiationTypePrototype>, SatiationThresholds<DamageSpecifier?>> Satiations;
|
||||
|
||||
/// <summary>
|
||||
/// How often the damage is applied.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan Frequency = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// Runtime data, indicating when the next damage application will occur.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan NextDamageTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component causes its entity to have movement speed modifiers applied based on the entity's current satiations.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true),
|
||||
Access(typeof(SatiationSpeedModifierSystem))]
|
||||
public sealed partial class SatiationSpeedModifierComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Speed modifiers, expressed as <see cref="float"/>s, contained within <see cref="SatiationThresholds{T}"/>,
|
||||
/// keyed by the <see cref="SatiationTypePrototype">satiation types</see> they are thresholds for.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public Dictionary<ProtoId<SatiationTypePrototype>, SatiationThresholds<float>> Satiations;
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
using Content.Shared.Alert;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(ThirstSystem))]
|
||||
[AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
|
||||
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;
|
||||
|
||||
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public ThirstThreshold CurrentThirstThreshold;
|
||||
|
||||
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
|
||||
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]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// The time between each update.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan UpdateRate = TimeSpan.FromSeconds(1);
|
||||
|
||||
[DataField("thresholds")]
|
||||
[AutoNetworkedField]
|
||||
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},
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertCategoryPrototype> ThirstyCategory = "Thirst";
|
||||
|
||||
public static readonly Dictionary<ThirstThreshold, ProtoId<AlertPrototype>> ThirstThresholdAlertTypes = new()
|
||||
{
|
||||
{ThirstThreshold.Thirsty, "Thirsty"},
|
||||
{ThirstThreshold.Parched, "Parched"},
|
||||
{ThirstThreshold.Dead, "Parched"},
|
||||
};
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ThirstThreshold : byte
|
||||
{
|
||||
// Hydrohomies
|
||||
Dead = 0,
|
||||
Parched = 1 << 0,
|
||||
Thirsty = 1 << 1,
|
||||
Okay = 1 << 2,
|
||||
OverHydrated = 1 << 3,
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// This abstract system provides a convenient interface for implementing effects which react to changes in
|
||||
/// <see cref="Satiation"/> thresholds.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <see cref="SatiationDamageSystem"/> uses this to maintain a damage descriptor which changes depending on the entity's
|
||||
/// satiation values. When the satiation values change, the damage descriptor is automatically updated by this system.
|
||||
/// </example>
|
||||
/// <typeparam name="TComp">The type of component this system interacts with</typeparam>
|
||||
/// <typeparam name="T">
|
||||
/// The type of value this system maintains. It must be contained within a <see cref="SatiationThresholds{T}"/>
|
||||
/// <see cref="GetThresholds">accessible via <typeparamref name="TComp">TComp</typeparamref></see>.
|
||||
/// </typeparam>
|
||||
/// <remarks>Note that this <b>is not</b> related to <see cref="EntityEffects"/></remarks>
|
||||
public abstract partial class BaseSatiationEffectSystem<TComp, T> : EntitySystem where TComp : Component
|
||||
{
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private EntityQuery<SatiationComponent> _satiationQuery;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
|
||||
/// <summary>
|
||||
/// How to access <see cref="SatiationThresholds{T}"/> via a <typeparamref name="TComp"/>.
|
||||
/// </summary>
|
||||
protected abstract Dictionary<ProtoId<SatiationTypePrototype>, SatiationThresholds<T>> GetThresholds(TComp comp);
|
||||
|
||||
/// <summary>
|
||||
/// The default <typeparamref name="T"/> value to set our maintained value to in the case that our current satiation
|
||||
/// is outside of <see cref="GetThresholds"/>.
|
||||
/// </summary>
|
||||
protected abstract T DefaultValue();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TComp, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TComp, SatiationUpdateEvent>(OnSatiationUpdate);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
// Updates maintained values when reaching the projected threshold time.
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<TComp, SatiationComponent>();
|
||||
while (query.MoveNext(out var ent, out var comp, out var satiation))
|
||||
{
|
||||
foreach (var (type, thresholds) in GetThresholds(comp))
|
||||
{
|
||||
if (_timing.CurTime < thresholds.ProjectedThresholdChangeTime)
|
||||
continue;
|
||||
|
||||
UpdateSatiation((ent, comp), satiation, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MustCallBase]
|
||||
protected virtual void OnMapInit(Entity<TComp> entity, ref MapInitEvent args)
|
||||
{
|
||||
// Make sure we have a satiation component. Realistically, this just exists to cause test failures if an entity
|
||||
// with `TComp` doesn't have a `SatiationComponent`.
|
||||
var comp = EnsureComp<SatiationComponent>(entity);
|
||||
foreach (var type in GetThresholds(entity.Comp).Keys)
|
||||
{
|
||||
UpdateSatiation(entity, comp, type);
|
||||
}
|
||||
}
|
||||
|
||||
// If a satiation value is changed directly, we react and update our maintained value.
|
||||
[MustCallBase]
|
||||
protected void OnSatiationUpdate(Entity<TComp> entity, ref SatiationUpdateEvent args)
|
||||
{
|
||||
if (!_satiationQuery.TryComp(entity, out var comp))
|
||||
return;
|
||||
|
||||
UpdateSatiation(entity, comp, args.Type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the maintained <typeparamref name="T"/> based on the entity's satiation.
|
||||
/// </summary>
|
||||
private void UpdateSatiation(Entity<TComp> entity, SatiationComponent comp, ProtoId<SatiationTypePrototype> type)
|
||||
{
|
||||
// Get the current satiation value...
|
||||
if (!GetThresholds(entity.Comp).TryGetValue(type, out var thresholds))
|
||||
return;
|
||||
|
||||
// ... and then use it to get the appropriate threshold T value.
|
||||
if (_satiation.TryGetValueByThreshold(
|
||||
(entity, comp),
|
||||
type,
|
||||
thresholds.Thresholds,
|
||||
out var result,
|
||||
out var nextLowerThreshold))
|
||||
{
|
||||
thresholds.Current = result ?? DefaultValue();
|
||||
|
||||
// Predict when our satiation will decay to the next threshold down.
|
||||
thresholds.ProjectedThresholdChangeTime = nextLowerThreshold is { } lower
|
||||
? _satiation.GetTimeToDecay((entity, comp), type, lower)
|
||||
: null;
|
||||
}
|
||||
else
|
||||
{
|
||||
thresholds.Current = DefaultValue();
|
||||
thresholds.ProjectedThresholdChangeTime = null;
|
||||
}
|
||||
|
||||
Dirty(entity);
|
||||
|
||||
AfterSatiationUpdate(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called after <see cref="UpdateSatiation"/> completes its work maintaining <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
protected virtual void AfterSatiationUpdate(Entity<TComp> entity) { }
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
/// <inheritdoc cref="ExaminableHungerComponent"/>
|
||||
public sealed partial class ExaminableHungerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private HungerSystem _hunger = default!;
|
||||
|
||||
[Dependency] private EntityQuery<HungerComponent> _hungerQuery = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ExaminableHungerComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the text provided on examine.
|
||||
/// Changes depending on the amount of hunger the target has.
|
||||
/// </summary>
|
||||
private void OnExamine(Entity<ExaminableHungerComponent> entity, ref ExaminedEvent args)
|
||||
{
|
||||
var identity = Identity.Entity(entity, EntityManager);
|
||||
|
||||
if (!_hungerQuery.TryComp(entity, out var hungerComp)
|
||||
|| !entity.Comp.Descriptions.TryGetValue(_hunger.GetHungerThreshold(hungerComp), out var locId))
|
||||
{
|
||||
// Use a fallback message if the entity has no HungerComponent
|
||||
// or is missing a description for the current threshold
|
||||
locId = entity.Comp.NoHungerDescription;
|
||||
}
|
||||
|
||||
var msg = Loc.GetString(locId, ("entity", identity));
|
||||
args.PushMarkup(msg);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Makes descriptions specified in <see cref="ExaminableSatiationComponent"/> show up in response to
|
||||
/// <see cref="ExaminedEvent"/>s.
|
||||
/// </summary>
|
||||
/// <seealso cref="ExaminableSatiationComponent"/>
|
||||
public sealed partial class ExaminableSatiationSystem : BaseSatiationEffectSystem<ExaminableSatiationComponent, LocId?>
|
||||
{
|
||||
protected override Dictionary<ProtoId<SatiationTypePrototype>, SatiationThresholds<LocId?>> GetThresholds(
|
||||
ExaminableSatiationComponent comp
|
||||
) => comp.Satiations;
|
||||
|
||||
protected override LocId? DefaultValue() => null;
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnExamine(Entity<ExaminableSatiationComponent> entity, ref ExaminedEvent args)
|
||||
{
|
||||
var identity = Identity.Entity(entity, EntityManager);
|
||||
foreach (var (_, thresholds) in entity.Comp.Satiations)
|
||||
{
|
||||
if (thresholds.Current is not { } loc)
|
||||
continue;
|
||||
|
||||
args.PushMarkup(Loc.GetString(loc, ("entity", identity)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage.Systems;
|
||||
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.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
public sealed partial class HungerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private AlertsSystem _alerts = default!;
|
||||
[Dependency] private DamageableSystem _damageable = default!;
|
||||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
[Dependency] private SharedJetpackSystem _jetpack = default!;
|
||||
|
||||
private static readonly ProtoId<SatiationIconPrototype> HungerIconOverfedId = "HungerIconOverfed";
|
||||
private static readonly ProtoId<SatiationIconPrototype> HungerIconPeckishId = "HungerIconPeckish";
|
||||
private static readonly ProtoId<SatiationIconPrototype> HungerIconStarvingId = "HungerIconStarving";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HungerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<HungerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<HungerComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
SubscribeLocalEvent<HungerComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, HungerComponent component, MapInitEvent args)
|
||||
{
|
||||
var amount = _random.Next(
|
||||
(int) component.Thresholds[HungerThreshold.Peckish] + 10,
|
||||
(int) component.Thresholds[HungerThreshold.Okay]);
|
||||
SetHunger(uid, amount, component);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, HungerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, component.HungerAlertCategory);
|
||||
}
|
||||
|
||||
private void OnRefreshMovespeed(EntityUid uid, HungerComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (component.CurrentThreshold > HungerThreshold.Starving)
|
||||
return;
|
||||
|
||||
if (_jetpack.IsUserFlying(uid))
|
||||
return;
|
||||
|
||||
args.ModifySpeed(component.StarvingSlowdownModifier, component.StarvingSlowdownModifier);
|
||||
}
|
||||
|
||||
private void OnRejuvenate(EntityUid uid, HungerComponent component, RejuvenateEvent args)
|
||||
{
|
||||
SetHunger(uid, component.Thresholds[HungerThreshold.Okay], component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current hunger value of the given <see cref="HungerComponent"/>.
|
||||
/// </summary>
|
||||
public float GetHunger(HungerComponent component)
|
||||
{
|
||||
var dt = _timing.CurTime - component.LastAuthoritativeHungerChangeTime;
|
||||
var value = component.LastAuthoritativeHungerValue - (float)dt.TotalSeconds * component.ActualDecayRate;
|
||||
return ClampHungerWithinThresholds(component, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds to the current hunger of an entity by the specified value
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="amount"></param>
|
||||
/// <param name="component"></param>
|
||||
public void ModifyHunger(EntityUid uid, float amount, HungerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
SetHunger(uid, GetHunger(component) + amount, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current hunger of an entity to the specified value
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="amount"></param>
|
||||
/// <param name="component"></param>
|
||||
public void SetHunger(EntityUid uid, float amount, HungerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
SetAuthoritativeHungerValue((uid, component), amount);
|
||||
UpdateCurrentThreshold(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="HungerComponent.LastAuthoritativeHungerValue"/> and
|
||||
/// <see cref="HungerComponent.LastAuthoritativeHungerChangeTime"/>, and dirties this entity. This "resets" the
|
||||
/// starting point for <see cref="GetHunger"/>'s calculation.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity whose hunger will be set.</param>
|
||||
/// <param name="value">The value to set the entity's hunger to.</param>
|
||||
private void SetAuthoritativeHungerValue(Entity<HungerComponent> entity, float value)
|
||||
{
|
||||
entity.Comp.LastAuthoritativeHungerChangeTime = _timing.CurTime;
|
||||
entity.Comp.LastAuthoritativeHungerValue = ClampHungerWithinThresholds(entity.Comp, value);
|
||||
DirtyField(entity.Owner, entity.Comp, nameof(HungerComponent.LastAuthoritativeHungerChangeTime));
|
||||
DirtyField(entity.Owner, entity.Comp, nameof(HungerComponent.LastAuthoritativeHungerValue));
|
||||
}
|
||||
|
||||
private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var calculatedHungerThreshold = GetHungerThreshold(component);
|
||||
if (calculatedHungerThreshold == component.CurrentThreshold)
|
||||
return;
|
||||
|
||||
component.CurrentThreshold = calculatedHungerThreshold;
|
||||
DirtyField(uid, component, nameof(HungerComponent.CurrentThreshold));
|
||||
DoHungerThresholdEffects(uid, component);
|
||||
}
|
||||
|
||||
private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component = null, bool force = false)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.CurrentThreshold == component.LastThreshold && !force)
|
||||
return;
|
||||
|
||||
if (GetMovementThreshold(component.CurrentThreshold) != GetMovementThreshold(component.LastThreshold))
|
||||
{
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
if (component.HungerThresholdAlerts.TryGetValue(component.CurrentThreshold, out var alertId))
|
||||
{
|
||||
_alerts.ShowAlert(uid, alertId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, component.HungerAlertCategory);
|
||||
}
|
||||
|
||||
if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier))
|
||||
{
|
||||
component.ActualDecayRate = component.BaseDecayRate * modifier;
|
||||
DirtyField(uid, component, nameof(HungerComponent.ActualDecayRate));
|
||||
SetAuthoritativeHungerValue((uid, component), GetHunger(component));
|
||||
}
|
||||
|
||||
component.LastThreshold = component.CurrentThreshold;
|
||||
DirtyField(uid, component, nameof(HungerComponent.LastThreshold));
|
||||
}
|
||||
|
||||
private void DoContinuousHungerEffects(EntityUid uid, HungerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.CurrentThreshold <= HungerThreshold.Starving &&
|
||||
component.StarvationDamage is { } damage &&
|
||||
!_mobState.IsDead(uid))
|
||||
{
|
||||
_damageable.TryChangeDamage(uid, damage, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hunger threshold for an entity based on the amount of food specified.
|
||||
/// If a specific amount isn't specified, just uses the current hunger of the entity
|
||||
/// </summary>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="food"></param>
|
||||
/// <returns></returns>
|
||||
public HungerThreshold GetHungerThreshold(HungerComponent component, float? food = null)
|
||||
{
|
||||
food ??= GetHunger(component);
|
||||
var result = HungerThreshold.Dead;
|
||||
var value = component.Thresholds[HungerThreshold.Overfed];
|
||||
foreach (var threshold in component.Thresholds)
|
||||
{
|
||||
if (threshold.Value <= value && threshold.Value >= food)
|
||||
{
|
||||
result = threshold.Key;
|
||||
value = threshold.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A check that returns if the entity is below a hunger threshold.
|
||||
/// </summary>
|
||||
public bool IsHungerBelowState(EntityUid uid, HungerThreshold threshold, float? food = null, HungerComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return false; // It's never going to go hungry, so it's probably fine to assume that it's not... you know, hungry.
|
||||
|
||||
return GetHungerThreshold(comp, food) < threshold;
|
||||
}
|
||||
|
||||
private bool GetMovementThreshold(HungerThreshold threshold)
|
||||
{
|
||||
switch (threshold)
|
||||
{
|
||||
case HungerThreshold.Overfed:
|
||||
case HungerThreshold.Okay:
|
||||
return true;
|
||||
case HungerThreshold.Peckish:
|
||||
case HungerThreshold.Starving:
|
||||
case HungerThreshold.Dead:
|
||||
return false;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(threshold), threshold, null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetStatusIconPrototype(HungerComponent component, [NotNullWhen(true)] out SatiationIconPrototype? prototype)
|
||||
{
|
||||
switch (component.CurrentThreshold)
|
||||
{
|
||||
case HungerThreshold.Overfed:
|
||||
ProtoMan.Resolve(HungerIconOverfedId, out prototype);
|
||||
break;
|
||||
case HungerThreshold.Peckish:
|
||||
ProtoMan.Resolve(HungerIconPeckishId, out prototype);
|
||||
break;
|
||||
case HungerThreshold.Starving:
|
||||
ProtoMan.Resolve(HungerIconStarvingId, out prototype);
|
||||
break;
|
||||
default:
|
||||
prototype = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return prototype != null;
|
||||
}
|
||||
|
||||
private static float ClampHungerWithinThresholds(HungerComponent component, float hungerValue)
|
||||
{
|
||||
return Math.Clamp(hungerValue,
|
||||
component.Thresholds[HungerThreshold.Dead],
|
||||
component.Thresholds[HungerThreshold.Overfed]);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<HungerComponent>();
|
||||
while (query.MoveNext(out var uid, out var hunger))
|
||||
{
|
||||
if (_timing.CurTime < hunger.NextThresholdUpdateTime)
|
||||
continue;
|
||||
hunger.NextThresholdUpdateTime = _timing.CurTime + hunger.ThresholdUpdateRate;
|
||||
|
||||
UpdateCurrentThreshold(uid, hunger);
|
||||
DoContinuousHungerEffects(uid, hunger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -235,9 +235,9 @@ public sealed partial class IngestionSystem
|
|||
foreach (var effect in entry.Effects)
|
||||
{
|
||||
// ignores any effect conditions, just cares about how much it can hydrate
|
||||
if (effect is SatiateHunger hunger)
|
||||
if (effect is Satiate satiate && satiate.SatiationType == SatiationSystem.Hunger)
|
||||
{
|
||||
total += hunger.Factor * quantity.Quantity.Float();
|
||||
total += satiate.Factor * quantity.Quantity.Float();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -286,9 +286,9 @@ public sealed partial class IngestionSystem
|
|||
foreach (var effect in entry.Effects)
|
||||
{
|
||||
// ignores any effect conditions, just cares about how much it can hydrate
|
||||
if (effect is SatiateThirst thirst)
|
||||
if (effect is Satiate satiate && satiate.SatiationType == SatiationSystem.Thirst)
|
||||
{
|
||||
total += thirst.Factor * quantity.Quantity.Float();
|
||||
total += satiate.Factor * quantity.Quantity.Float();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
147
Content.Shared/Nutrition/EntitySystems/Satiation.cs
Normal file
147
Content.Shared/Nutrition/EntitySystems/Satiation.cs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// A need whose value decays over time. Examples include Thirst and Hunger.
|
||||
/// </summary>
|
||||
[DataDefinition, Serializable, NetSerializable, Access(typeof(SatiationSystem))]
|
||||
public sealed partial class Satiation
|
||||
{
|
||||
/// <summary>
|
||||
/// This satiation's <see cref="SatiationTypePrototype"/>.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public ProtoId<SatiationTypePrototype> SatiationType;
|
||||
|
||||
/// <summary>
|
||||
/// This satiation's <see cref="SatiationPrototype"/>, which describes how it changes over time.
|
||||
/// </summary>
|
||||
[DataField(required: true), ViewVariables(VVAccess.ReadOnly)]
|
||||
public ProtoId<SatiationPrototype> Prototype;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The value of this satiation as of <see cref="LastAuthoritativeChangeTime"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To get the current value at any arbitrary time, use <see cref="SatiationSystem.GetValueOrNull"/>
|
||||
/// </remarks>.
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public float LastAuthoritativeValue = float.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// The last time <see cref="LastAuthoritativeValue"/> was modified.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadOnly)]
|
||||
public TimeSpan LastAuthoritativeChangeTime;
|
||||
|
||||
/// <summary>
|
||||
/// The rate at which this satiation value is expected to decay. It is a combination of
|
||||
/// <see cref="SatiationPrototype.BaseDecayRate"/> and modifiers.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public float ActualDecayRate;
|
||||
|
||||
/// <summary>
|
||||
/// When <see cref="ActualDecayRate"/> is expected to change, if nothing but normal decay affects this satiation.
|
||||
/// This is used to predict satiation updates on clients.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? NextDecayRateModUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="NextDecayRateModUpdateTime"/>, but for satiation alerts.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? NextAlertUpdateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A combination of configuration (<see cref="Thresholds"/>) and state (<see cref="Current"/>) which describes how a
|
||||
/// <typeparamref name="T"/> value changes related to <see cref="Satiation"/>, as well as its current value.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <see cref="SatiationDamageSystem"/> uses this to describe and track damage descriptors based on satiations, applying
|
||||
/// the damage regularly over time. <see cref="Thresholds"/> describes the damage to apply at various levels of
|
||||
/// satiation, and <see cref="Current"/> tracks the current damage to apply every update, meaning it doesn't need to be
|
||||
/// looked up from <see cref="Thresholds"/> on every tick.
|
||||
/// </example>
|
||||
/// <remarks>This should probably only ever be used in conjunction with <see cref="BaseSatiationEffectSystem{TComp,T}"/></remarks>
|
||||
[DataDefinition, Serializable]
|
||||
public sealed partial class SatiationThresholds<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <typeparamref name="T"/> values keyed by the satiation values at or below which the T value becomes "active".
|
||||
/// </summary>
|
||||
/// <seealso cref="SatiationSystem.TryGetValueByThreshold"/>
|
||||
[IncludeDataField]
|
||||
public Dictionary<SatiationValue, T> Thresholds = [];
|
||||
|
||||
/// <summary>
|
||||
/// When this satiation is expected to decay from its current threshold to the next lower threshold. This
|
||||
/// is null when there is no lower threshold to decay to.
|
||||
/// </summary>
|
||||
public TimeSpan? ProjectedThresholdChangeTime;
|
||||
|
||||
/// <summary>
|
||||
/// The current <typeparamref name="T"/> value, at least when maintained by something like
|
||||
/// <see cref="BaseSatiationEffectSystem{TComp,T}"/>
|
||||
/// </summary>
|
||||
public T Current;
|
||||
}
|
||||
|
||||
[TypeSerializer]
|
||||
public sealed partial class SatiationThresholdsSerializer<T> : ITypeSerializer<SatiationThresholds<T>, MappingDataNode>,
|
||||
ITypeCopier<SatiationThresholds<T>>
|
||||
{
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null
|
||||
) => serializationManager.ValidateNode<Dictionary<SatiationValue, T>>(node, context);
|
||||
|
||||
public SatiationThresholds<T> Read(
|
||||
ISerializationManager serializationManager,
|
||||
MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<SatiationThresholds<T>>? instanceProvider = null
|
||||
) => new()
|
||||
{
|
||||
Thresholds = serializationManager.Read<Dictionary<SatiationValue, T>>(
|
||||
node,
|
||||
context,
|
||||
hookCtx.SkipHooks,
|
||||
instanceProvider is { } ip ? () => ip().Thresholds : null,
|
||||
true
|
||||
),
|
||||
};
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
SatiationThresholds<T> value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null
|
||||
) => serializationManager.WriteValue(value.Thresholds, alwaysWrite, context, true);
|
||||
|
||||
public void CopyTo(
|
||||
ISerializationManager serializationManager,
|
||||
SatiationThresholds<T> source,
|
||||
ref SatiationThresholds<T> target,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null
|
||||
) => serializationManager.CopyTo(source.Thresholds, ref target.Thresholds, context, true, true);
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// This system implements the behavior of <see cref="SatiationDamageComponent"/>
|
||||
/// </summary>
|
||||
public sealed partial class SatiationDamageSystem :
|
||||
BaseSatiationEffectSystem<SatiationDamageComponent, DamageSpecifier?>
|
||||
{
|
||||
[Dependency] private DamageableSystem _damageable = default!;
|
||||
[Dependency] private MobStateSystem _mobState = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var satiationDamageQuery = EntityQueryEnumerator<SatiationDamageComponent>();
|
||||
while (satiationDamageQuery.MoveNext(out var ent, out var comp))
|
||||
{
|
||||
if (_timing.CurTime < comp.NextDamageTime ||
|
||||
_mobState.IsDead(ent))
|
||||
continue;
|
||||
|
||||
comp.NextDamageTime = _timing.CurTime + comp.Frequency;
|
||||
DirtyField(ent, comp, nameof(SatiationDamageComponent.NextDamageTime));
|
||||
|
||||
foreach (var (_, thresholds) in comp.Satiations)
|
||||
{
|
||||
if (thresholds.Current is not { } damage)
|
||||
continue;
|
||||
|
||||
_damageable.TryChangeDamage(ent, damage, interruptsDoAfters: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override DamageSpecifier? DefaultValue() => null;
|
||||
|
||||
protected override Dictionary<ProtoId<SatiationTypePrototype>, SatiationThresholds<DamageSpecifier?>> GetThresholds(
|
||||
SatiationDamageComponent comp) => comp.Satiations;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
public sealed partial class SatiationSpeedModifierSystem :
|
||||
BaseSatiationEffectSystem<SatiationSpeedModifierComponent, float>
|
||||
{
|
||||
[Dependency] private MovementSpeedModifierSystem _movementSpeedModifier = default!;
|
||||
|
||||
protected override Dictionary<ProtoId<SatiationTypePrototype>, SatiationThresholds<float>> GetThresholds(
|
||||
SatiationSpeedModifierComponent comp) => comp.Satiations;
|
||||
|
||||
protected override float DefaultValue() => 1f;
|
||||
|
||||
protected override void AfterSatiationUpdate(Entity<SatiationSpeedModifierComponent> entity)
|
||||
{
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(entity.Owner);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private static void OnRefreshMovementSpeed(
|
||||
Entity<SatiationSpeedModifierComponent> entity,
|
||||
ref RefreshMovementSpeedModifiersEvent args
|
||||
)
|
||||
{
|
||||
foreach (var (_, thresholds) in entity.Comp.Satiations)
|
||||
{
|
||||
args.ModifySpeed(thresholds.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
260
Content.Shared/Nutrition/EntitySystems/SatiationSystem.Proxy.cs
Normal file
260
Content.Shared/Nutrition/EntitySystems/SatiationSystem.Proxy.cs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
// This part provides functions for use in other systems.
|
||||
public sealed partial class SatiationSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets <paramref name="entity"/>'s current value of the satiation of <paramref name="type"/>. If this entity does
|
||||
/// not have that satiation, returns null.
|
||||
/// </summary>
|
||||
public float? GetValueOrNull(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type
|
||||
)
|
||||
{
|
||||
if (GetAndResolveSatiationOfType(entity, type) is not var (satiation, proto))
|
||||
return null;
|
||||
|
||||
return CalculateCurrentValue(satiation, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="entity"/>'s current satiation of <paramref name="type"/> to a value corresponding to
|
||||
/// <paramref name="satiationValue"/>. If this entity does not have that satiation, or the key does not correspond to
|
||||
/// any threshold, has no effect.
|
||||
/// </summary>
|
||||
public void SetValue(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type,
|
||||
[ForbidLiteral] SatiationValue satiationValue
|
||||
)
|
||||
{
|
||||
if (GetAndResolveSatiationOfType(entity, type) is not var (satiation, proto) ||
|
||||
proto.GetValueOrNull(satiationValue) is not { } value)
|
||||
return;
|
||||
|
||||
SetAuthoritativeValue(entity, satiation, proto, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="entity"/>'s current satiation of <paramref name="type"/> to <paramref name="value"/>. If
|
||||
/// this entity does not have that satiation, has no effect.
|
||||
/// </summary>
|
||||
// [OverloadResolutionPriority(1)] // If you pass in an int, avoid instantiating a record to hold it. // Requires a newer language version :agony:
|
||||
public void SetValue(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type,
|
||||
float value
|
||||
)
|
||||
{
|
||||
if (GetAndResolveSatiationOfType(entity, type) is not var (satiation, proto))
|
||||
return;
|
||||
|
||||
SetAuthoritativeValue(entity, satiation, proto, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <paramref name="entity"/>'s current satiation of <paramref name="type"/> to its current value plus
|
||||
/// <paramref name="amount"/>. If this entity does not have that satiation, has no effect.
|
||||
/// </summary>
|
||||
public void ModifyValue(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type,
|
||||
float amount
|
||||
)
|
||||
{
|
||||
if (GetValueOrNull(entity, type) is { } currentValue)
|
||||
{
|
||||
SetValue(entity, type, currentValue + amount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not the current value (plus optional <paramref name="hypotheticalValueDelta"/>) is between
|
||||
/// the values described by <paramref name="above"/> and <paramref name="below"/>. If <paramref name="entity"/>
|
||||
/// does not have a <see cref="Satiation"/> of the specified <paramref name="type"/>, returns false. If either
|
||||
/// above- or below-key is null, any value is considered in-range compared to that key.
|
||||
/// If either key is specified but not present in <paramref name="type"/>'s <see cref="SatiationPrototype.Thresholds"/>,
|
||||
/// all values are considered out-of-range.
|
||||
/// </summary>
|
||||
public bool IsValueInRange(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type,
|
||||
[ForbidLiteral] SatiationValue? above = null,
|
||||
[ForbidLiteral] SatiationValue? below = null,
|
||||
float hypotheticalValueDelta = 0
|
||||
)
|
||||
{
|
||||
if (above is null && below is null)
|
||||
{
|
||||
DebugTools.Assert("Range is unbounded. Is this a programming error?");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GetAndResolveSatiationOfType(entity, type) is not var (satiation, proto))
|
||||
return false;
|
||||
|
||||
// Resolve the bounds to integers we can actually compare against.
|
||||
int? valueAbove = null;
|
||||
if (above is { } a && (valueAbove = proto.GetValueOrNull(a)) is null)
|
||||
return false; // `above` is not null, but we failed to resolve its value.
|
||||
|
||||
int? valueAtOrBelow = null;
|
||||
if (below is { } b && (valueAtOrBelow = proto.GetValueOrNull(b)) is null)
|
||||
return false; // `atOrBelow` is not null, but we failed to resolve its value.
|
||||
|
||||
if (valueAbove > valueAtOrBelow)
|
||||
{
|
||||
DebugTools.Assert("Range is empty. Is this a programming error?");
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = CalculateCurrentValue(satiation, proto);
|
||||
if (hypotheticalValueDelta is not 0)
|
||||
currentValue = proto.ClampSatiationWithinThresholds(currentValue + hypotheticalValueDelta);
|
||||
|
||||
var isAboveBottom = valueAbove is null || currentValue > valueAbove;
|
||||
var isAtOrBelowTop = valueAtOrBelow is null || currentValue < valueAtOrBelow;
|
||||
;
|
||||
|
||||
return isAboveBottom && isAtOrBelowTop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function returns a value from <see cref="valuesByThreshold"/> based on the current value of
|
||||
/// <paramref name="entity"/>'s satiation of the given <paramref name="type"/>. The value in
|
||||
/// <paramref name="valuesByThreshold"/> with the lowest key greater than the current satiation value is returned.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity whose satiation is considered</param>
|
||||
/// <param name="type">The type of satiation to consider</param>
|
||||
/// <param name="valuesByThreshold">The values, keyed by <see cref="SatiationValue"/> thresholds</param>
|
||||
/// <param name="result">The value selected from <paramref name="valuesByThreshold"/></param>
|
||||
/// <param name="nextLowerThreshold">
|
||||
/// The keying-threshold-value of the next threshold below the selected value. Most consumers will not have a use
|
||||
/// for this value, but it is used by <see cref="BaseSatiationEffectSystem{TComp,T}"/>'s prediction of value decay.
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of values in <paramref name="valuesByThreshold"/></typeparam>
|
||||
/// <returns>
|
||||
/// True if a value was selected. False if <paramref name="valuesByThreshold"/> is empty, if the current value of
|
||||
/// the specified satiation is higher than all thresholds, or the given entity does not have a satiation of the
|
||||
/// given type.
|
||||
/// </returns>
|
||||
public bool TryGetValueByThreshold<T>(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type,
|
||||
Dictionary<SatiationValue, T> valuesByThreshold,
|
||||
out T? result,
|
||||
out int? nextLowerThreshold
|
||||
)
|
||||
{
|
||||
result = default;
|
||||
nextLowerThreshold = null;
|
||||
if (GetValueOrNull(entity, type) is not { } currentValue ||
|
||||
GetAndResolveSatiationOfType(entity, type) is not var (_, proto))
|
||||
return false;
|
||||
|
||||
using var valuesByDescendingThreshold = valuesByThreshold
|
||||
// Resolve keys to threshold integers, discarding any keys which cannot be resolved.
|
||||
.Select(it => proto.GetValueOrNull(it.Key) is { } value ? ((int, T)?)(value, it.Value) : null)
|
||||
.OfType<(int, T)>()
|
||||
// Order by descending threshold.
|
||||
.OrderByDescending(it => it.Item1)
|
||||
.GetEnumerator();
|
||||
if (!valuesByDescendingThreshold.MoveNext())
|
||||
{
|
||||
// `values` is empty, so there are no values to return.
|
||||
result = default;
|
||||
nextLowerThreshold = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentValue > valuesByDescendingThreshold.Current.Item1)
|
||||
{
|
||||
// `currentSatiation` is higher than all thresholds, so we don't have a value, but we can return a next
|
||||
// lower threshold.
|
||||
result = default;
|
||||
nextLowerThreshold = valuesByDescendingThreshold.Current.Item1;
|
||||
return false;
|
||||
}
|
||||
|
||||
var nextHigher = valuesByDescendingThreshold.Current;
|
||||
while (valuesByDescendingThreshold.MoveNext())
|
||||
{
|
||||
var nextLower = valuesByDescendingThreshold.Current;
|
||||
if (currentValue > nextLower.Item1)
|
||||
{
|
||||
// The current value is below `nextHigher` and above `nextLower`, so `nextHigher` is the correct threshold.
|
||||
result = nextHigher.Item2;
|
||||
nextLowerThreshold = nextLower.Item1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Loop, setting `nextLower` to the next iteration's `nextHigher`
|
||||
nextHigher = nextLower;
|
||||
}
|
||||
|
||||
// We've run out of thresholds below.
|
||||
result = nextHigher.Item2;
|
||||
nextLowerThreshold = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the <see cref="StatusIconPrototype"/> appropriate for the given entity's <see cref="Satiation"/> of the
|
||||
/// specified <paramref name="type"/>. If the entity does not have the specified satiation type, or if the satiation
|
||||
/// does not have an icon for its current state, returns null.
|
||||
/// </summary>
|
||||
/// <remarks>This should almost definitely never be used by anything other than the client's Overlay system</remarks>
|
||||
public StatusIconPrototype? GetStatusIconOrNull(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type
|
||||
)
|
||||
{
|
||||
if (entity.Comp.GetOrNull(type) is not { } satiation ||
|
||||
!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
||||
return null;
|
||||
|
||||
TryGetValueByThreshold(entity, type, proto.Icons, out var iconProtoId, out _);
|
||||
return ProtoMan.Resolve(iconProtoId, out var icon) ? icon : null;
|
||||
}
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Returns the all of the <see cref="SatiationPrototype.Thresholds">key strings</see> of the given
|
||||
/// <paramref name="type"/> for <paramref name="entity"/>, or empty if no such type exists.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is expected that <paramref name="type"/> is validated with before calling this. If it fails to resolve, an
|
||||
/// error will be logged.
|
||||
/// </remarks>
|
||||
public IEnumerable<string> GetKeysForType(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type
|
||||
)
|
||||
{
|
||||
return GetAndResolveSatiationOfType(entity, type)?.Proto.Thresholds.Keys ?? Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="SatiationPrototype.MaximumValue"/> of the given <paramref name="type"/> for
|
||||
/// <paramref name="entity"/>, or null if no such type exists.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is expected that <paramref name="type"/> is validated with before calling this. If it fails to resolve, an
|
||||
/// error will be logged.
|
||||
/// </remarks>
|
||||
public int? GetMaximumValue(
|
||||
Entity<SatiationComponent> entity,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type
|
||||
) => GetAndResolveSatiationOfType(entity, type)?.Proto.MaximumValue;
|
||||
|
||||
#endregion
|
||||
}
|
||||
253
Content.Shared/Nutrition/EntitySystems/SatiationSystem.cs
Normal file
253
Content.Shared/Nutrition/EntitySystems/SatiationSystem.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
using Content.Shared.Alert;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Rejuvenate;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// This system manages the <see cref="SatiationComponent"/>. Broadly, what that means is that it handles the decay of
|
||||
/// satiations in <see cref="Update"/>, and external changes to satiations through accessors like
|
||||
/// <see cref="ModifyValue"/>.
|
||||
/// </summary>
|
||||
public sealed partial class SatiationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private AlertsSystem _alerts = default!;
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
|
||||
[Dependency] private EntityQuery<SatiationComponent> _satiationQuery;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the <c>Hunger</c> satiation type. Provided because it is so commonly used in Content.
|
||||
/// </summary>
|
||||
public static readonly ProtoId<SatiationTypePrototype> Hunger = "Hunger";
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the <c>Thirst</c> satiation type. Provided because it is so commonly used in Content.
|
||||
/// </summary>
|
||||
public static readonly ProtoId<SatiationTypePrototype> Thirst = "Thirst";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<SatiationComponent>();
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
Entity<SatiationComponent> entity = (uid, component);
|
||||
foreach (var (satiation, proto) in GetSatiationsAndTypes(entity))
|
||||
{
|
||||
if (_timing.CurTime >= satiation.NextAlertUpdateTime)
|
||||
{
|
||||
UpdateAlerts(entity, satiation, proto);
|
||||
}
|
||||
|
||||
if (_timing.CurTime >= satiation.NextDecayRateModUpdateTime)
|
||||
{
|
||||
SetAuthoritativeValue(entity, satiation, proto, CalculateCurrentValue(satiation, proto));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets starting satiation values.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnMapInit(Entity<SatiationComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
foreach (var (type, satiation) in entity.Comp.Satiations)
|
||||
{
|
||||
if (!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
||||
continue;
|
||||
|
||||
satiation.SatiationType = type;
|
||||
|
||||
// TODO: Replace with RandomPredicted once the engine PR is merged
|
||||
var rand = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(entity));
|
||||
var value = rand.NextFloat(proto.StartingValueMinimum, proto.StartingValueMaximum);
|
||||
|
||||
SetAuthoritativeValue(entity, satiation, proto, value);
|
||||
}
|
||||
|
||||
DirtyField(entity.AsNullable(), nameof(SatiationComponent.Satiations));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears alerts.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnShutdown(Entity<SatiationComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
foreach (var (_, proto) in GetSatiationsAndTypes(entity))
|
||||
{
|
||||
_alerts.ClearAlertCategory(entity.Owner, proto.AlertCategory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all satiations to their maximums.
|
||||
/// </summary>
|
||||
[SubscribeLocalEvent]
|
||||
private void OnRejuvenate(Entity<SatiationComponent> entity, ref RejuvenateEvent args)
|
||||
{
|
||||
foreach (var type in entity.Comp.Satiations.Keys)
|
||||
{
|
||||
SetValue(entity, type, satiationValue: int.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This helper resolves <paramref name="type"/> and returns the corresponding <see cref="Satiation"/> from
|
||||
/// <paramref name="satiations"/> along with its <see cref="SatiationPrototype"/>.
|
||||
/// Returns null if the prototype fails to resolve, or if the component does not have the specified satiation.
|
||||
/// </summary>
|
||||
private (Satiation Satiation, SatiationPrototype Proto)? GetAndResolveSatiationOfType(
|
||||
SatiationComponent satiations,
|
||||
[ForbidLiteral] ProtoId<SatiationTypePrototype> type
|
||||
)
|
||||
{
|
||||
if (satiations.GetOrNull(type) is not { } satiation ||
|
||||
!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
||||
return null;
|
||||
|
||||
return (satiation, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="GetAndResolveSatiationOfType"/>, this helper returns all <see cref="Satiation"/>s on
|
||||
/// <paramref name="satiations"/> along with their corresponding <see cref="SatiationPrototype"/>s.
|
||||
/// </summary>
|
||||
private IEnumerable<(Satiation, SatiationPrototype)> GetSatiationsAndTypes(SatiationComponent satiations)
|
||||
{
|
||||
foreach (var satiation in satiations.Satiations.Values)
|
||||
{
|
||||
if (!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
||||
continue;
|
||||
|
||||
yield return (satiation, proto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the current value of the given <see cref="Satiation"/> by linearly extrapolating the change of the
|
||||
/// value based on <see cref="Satiation.LastAuthoritativeValue"/>, <see cref="Satiation.LastAuthoritativeChangeTime"/>
|
||||
/// and <see cref="Satiation.ActualDecayRate"/>
|
||||
/// </summary>
|
||||
private float CalculateCurrentValue(Satiation satiation, SatiationPrototype proto)
|
||||
{
|
||||
var dt = _timing.CurTime - satiation.LastAuthoritativeChangeTime;
|
||||
var value = satiation.LastAuthoritativeValue - (float)dt.TotalSeconds * satiation.ActualDecayRate;
|
||||
return proto.ClampSatiationWithinThresholds(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The beating heart of this system, this function sets the given <paramref name="entity"/>'s
|
||||
/// <paramref name="satiation"/> to <paramref name="value"/>. This involves
|
||||
/// updating obvious fields on the <see cref="SatiationComponent"/>, but since changes to the value also affect the
|
||||
/// current threshold, we need to consider all of the effects that has as well.
|
||||
/// </summary>
|
||||
private void SetAuthoritativeValue(
|
||||
Entity<SatiationComponent> entity,
|
||||
Satiation satiation,
|
||||
SatiationPrototype proto,
|
||||
float value
|
||||
)
|
||||
{
|
||||
// Update the authoritative value itself.
|
||||
satiation.LastAuthoritativeChangeTime = _timing.CurTime;
|
||||
satiation.LastAuthoritativeValue = proto.ClampSatiationWithinThresholds(value);
|
||||
|
||||
if (!TryGetValueByThreshold(
|
||||
entity,
|
||||
satiation.SatiationType,
|
||||
proto.DecayModifiers,
|
||||
out var currentDecayMod,
|
||||
out var nextLowerThreshold
|
||||
))
|
||||
{
|
||||
currentDecayMod = 1f;
|
||||
}
|
||||
|
||||
satiation.ActualDecayRate = proto.BaseDecayRate * currentDecayMod;
|
||||
|
||||
if (nextLowerThreshold is { } t)
|
||||
{
|
||||
satiation.NextDecayRateModUpdateTime =
|
||||
_timing.CurTime + TimeSpan.FromSeconds((value - t) / satiation.ActualDecayRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
satiation.NextDecayRateModUpdateTime = null;
|
||||
}
|
||||
|
||||
var updateEvent = new SatiationUpdateEvent(satiation.SatiationType);
|
||||
RaiseLocalEvent(entity, ref updateEvent);
|
||||
|
||||
DirtyField(entity.AsNullable(), nameof(SatiationComponent.Satiations));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// This is basically a reimplementation of <see cref="BaseSatiationEffectSystem{TComp,T}.OnSatiationUpdate"/>.
|
||||
/// </remarks>
|
||||
[SubscribeLocalEvent]
|
||||
private void UpdateAlertsOnSatiationUpdated(Entity<SatiationComponent> entity, ref SatiationUpdateEvent args)
|
||||
{
|
||||
if (entity.Comp.GetOrNull(args.Type) is not { } satiation ||
|
||||
!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
||||
return;
|
||||
|
||||
UpdateAlerts(entity, satiation, proto);
|
||||
}
|
||||
|
||||
private void UpdateAlerts(
|
||||
Entity<SatiationComponent> entity,
|
||||
Satiation satiation,
|
||||
SatiationPrototype proto
|
||||
)
|
||||
{
|
||||
if (TryGetValueByThreshold(
|
||||
entity,
|
||||
satiation.SatiationType,
|
||||
proto.Alerts,
|
||||
out var result,
|
||||
out var nextLowerThreshold))
|
||||
{
|
||||
if (result is { } alert)
|
||||
{
|
||||
_alerts.ShowAlert(entity.Owner, alert);
|
||||
satiation.NextAlertUpdateTime = nextLowerThreshold is { } lower
|
||||
? GetTimeToDecay(entity, satiation.SatiationType, lower)
|
||||
: null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(entity.Owner, proto.AlertCategory);
|
||||
satiation.NextAlertUpdateTime = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(entity.Owner, proto.AlertCategory);
|
||||
satiation.NextAlertUpdateTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan? GetTimeToDecay(Entity<SatiationComponent> entity,
|
||||
ProtoId<SatiationTypePrototype> type,
|
||||
int threshold
|
||||
)
|
||||
{
|
||||
if (GetValueOrNull(entity, type) is not { } value ||
|
||||
entity.Comp.GetOrNull(type) is not { } satiation)
|
||||
return null;
|
||||
|
||||
return _timing.CurTime + TimeSpan.FromSeconds((value - threshold) / satiation.ActualDecayRate);
|
||||
}
|
||||
}
|
||||
|
||||
// Best effort on change to authoritative value or decay rate
|
||||
[ByRefEvent]
|
||||
public readonly record struct SatiationUpdateEvent(ProtoId<SatiationTypePrototype> Type);
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
using Content.Shared.Alert;
|
||||
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.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ThirstSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IGameTiming _timing = default!;
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
[Dependency] private AlertsSystem _alerts = default!;
|
||||
[Dependency] private MovementSpeedModifierSystem _movement = default!;
|
||||
[Dependency] private SharedJetpackSystem _jetpack = default!;
|
||||
|
||||
private static readonly ProtoId<SatiationIconPrototype> ThirstIconOverhydratedId = "ThirstIconOverhydrated";
|
||||
private static readonly ProtoId<SatiationIconPrototype> ThirstIconThirstyId = "ThirstIconThirsty";
|
||||
private static readonly ProtoId<SatiationIconPrototype> ThirstIconParchedId = "ThirstIconParched";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ThirstComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
SubscribeLocalEvent<ThirstComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ThirstComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, ThirstComponent component, MapInitEvent args)
|
||||
{
|
||||
// Do not change behavior unless starting value is explicitly defined
|
||||
if (component.CurrentThirst < 0)
|
||||
{
|
||||
component.CurrentThirst = _random.Next(
|
||||
(int) component.ThirstThresholds[ThirstThreshold.Thirsty] + 10,
|
||||
(int) component.ThirstThresholds[ThirstThreshold.Okay] - 1);
|
||||
|
||||
DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst));
|
||||
}
|
||||
component.NextUpdateTime = _timing.CurTime;
|
||||
component.CurrentThirstThreshold = GetThirstThreshold(component, component.CurrentThirst);
|
||||
component.LastThirstThreshold = ThirstThreshold.Okay; // TODO: Potentially change this -> Used Okay because no effects.
|
||||
// TODO: Check all thresholds make sense and throw if they don't.
|
||||
UpdateEffects(uid, component);
|
||||
|
||||
DirtyFields(uid, component, null, nameof(ThirstComponent.NextUpdateTime), nameof(ThirstComponent.CurrentThirstThreshold), nameof(ThirstComponent.LastThirstThreshold));
|
||||
|
||||
TryComp(uid, out MovementSpeedModifierComponent? moveMod);
|
||||
_movement.RefreshMovementSpeedModifiers((uid, moveMod));
|
||||
}
|
||||
|
||||
private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
// TODO: This should really be taken care of somewhere else
|
||||
if (_jetpack.IsUserFlying(uid))
|
||||
return;
|
||||
|
||||
var mod = component.CurrentThirstThreshold <= ThirstThreshold.Parched ? 0.75f : 1.0f;
|
||||
args.ModifySpeed(mod, mod);
|
||||
}
|
||||
|
||||
private void OnRejuvenate(EntityUid uid, ThirstComponent component, RejuvenateEvent args)
|
||||
{
|
||||
SetThirst(uid, component, component.ThirstThresholds[ThirstThreshold.Okay]);
|
||||
}
|
||||
|
||||
private ThirstThreshold GetThirstThreshold(ThirstComponent component, float amount)
|
||||
{
|
||||
ThirstThreshold result = ThirstThreshold.Dead;
|
||||
var value = component.ThirstThresholds[ThirstThreshold.OverHydrated];
|
||||
foreach (var threshold in component.ThirstThresholds)
|
||||
{
|
||||
if (threshold.Value <= value && threshold.Value >= amount)
|
||||
{
|
||||
result = threshold.Key;
|
||||
value = threshold.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void ModifyThirst(EntityUid uid, ThirstComponent component, float amount)
|
||||
{
|
||||
SetThirst(uid, component, component.CurrentThirst + amount);
|
||||
}
|
||||
|
||||
public void SetThirst(EntityUid uid, ThirstComponent component, float amount)
|
||||
{
|
||||
component.CurrentThirst = Math.Clamp(amount,
|
||||
component.ThirstThresholds[ThirstThreshold.Dead],
|
||||
component.ThirstThresholds[ThirstThreshold.OverHydrated]
|
||||
);
|
||||
|
||||
DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst));
|
||||
}
|
||||
|
||||
private bool IsMovementThreshold(ThirstThreshold threshold)
|
||||
{
|
||||
switch (threshold)
|
||||
{
|
||||
case ThirstThreshold.Dead:
|
||||
case ThirstThreshold.Parched:
|
||||
return true;
|
||||
case ThirstThreshold.Thirsty:
|
||||
case ThirstThreshold.Okay:
|
||||
case ThirstThreshold.OverHydrated:
|
||||
return false;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(threshold), threshold, null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetStatusIconPrototype(ThirstComponent component, [NotNullWhen(true)] out SatiationIconPrototype? prototype)
|
||||
{
|
||||
switch (component.CurrentThirstThreshold)
|
||||
{
|
||||
case ThirstThreshold.OverHydrated:
|
||||
ProtoMan.Resolve(ThirstIconOverhydratedId, out prototype);
|
||||
break;
|
||||
|
||||
case ThirstThreshold.Thirsty:
|
||||
ProtoMan.Resolve(ThirstIconThirstyId, out prototype);
|
||||
break;
|
||||
|
||||
case ThirstThreshold.Parched:
|
||||
ProtoMan.Resolve(ThirstIconParchedId, out prototype);
|
||||
break;
|
||||
|
||||
default:
|
||||
prototype = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return prototype != null;
|
||||
}
|
||||
|
||||
private void UpdateEffects(EntityUid uid, ThirstComponent component)
|
||||
{
|
||||
if (IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) &&
|
||||
TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent))
|
||||
{
|
||||
_movement.RefreshMovementSpeedModifiers((uid, movementSlowdownComponent));
|
||||
}
|
||||
|
||||
// Update UI
|
||||
if (ThirstComponent.ThirstThresholdAlertTypes.TryGetValue(component.CurrentThirstThreshold, out var alertId))
|
||||
{
|
||||
_alerts.ShowAlert(uid, alertId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, component.ThirstyCategory);
|
||||
}
|
||||
|
||||
DirtyField(uid, component, nameof(ThirstComponent.LastThirstThreshold));
|
||||
DirtyField(uid, component, nameof(ThirstComponent.ActualDecayRate));
|
||||
|
||||
switch (component.CurrentThirstThreshold)
|
||||
{
|
||||
case ThirstThreshold.OverHydrated:
|
||||
component.LastThirstThreshold = component.CurrentThirstThreshold;
|
||||
component.ActualDecayRate = component.BaseDecayRate * 1.2f;
|
||||
return;
|
||||
|
||||
case ThirstThreshold.Okay:
|
||||
component.LastThirstThreshold = component.CurrentThirstThreshold;
|
||||
component.ActualDecayRate = component.BaseDecayRate;
|
||||
return;
|
||||
|
||||
case ThirstThreshold.Thirsty:
|
||||
// Same as okay except with UI icon saying drink soon.
|
||||
component.LastThirstThreshold = component.CurrentThirstThreshold;
|
||||
component.ActualDecayRate = component.BaseDecayRate * 0.8f;
|
||||
return;
|
||||
case ThirstThreshold.Parched:
|
||||
_movement.RefreshMovementSpeedModifiers(uid);
|
||||
component.LastThirstThreshold = component.CurrentThirstThreshold;
|
||||
component.ActualDecayRate = component.BaseDecayRate * 0.6f;
|
||||
return;
|
||||
|
||||
case ThirstThreshold.Dead:
|
||||
return;
|
||||
|
||||
default:
|
||||
Log.Error($"No thirst threshold found for {component.CurrentThirstThreshold}");
|
||||
throw new ArgumentOutOfRangeException($"No thirst threshold found for {component.CurrentThirstThreshold}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<ThirstComponent>();
|
||||
while (query.MoveNext(out var uid, out var thirst))
|
||||
{
|
||||
if (_timing.CurTime < thirst.NextUpdateTime)
|
||||
continue;
|
||||
|
||||
thirst.NextUpdateTime += thirst.UpdateRate;
|
||||
|
||||
ModifyThirst(uid, thirst, -thirst.ActualDecayRate);
|
||||
var calculatedThirstThreshold = GetThirstThreshold(thirst, thirst.CurrentThirst);
|
||||
|
||||
if (calculatedThirstThreshold == thirst.CurrentThirstThreshold)
|
||||
continue;
|
||||
|
||||
thirst.CurrentThirstThreshold = calculatedThirstThreshold;
|
||||
UpdateEffects(uid, thirst);
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Content.Shared/Nutrition/Prototypes/SatiationPrototype.cs
Normal file
148
Content.Shared/Nutrition/Prototypes/SatiationPrototype.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
||||
|
||||
namespace Content.Shared.Nutrition.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// A specific variety of satiation. For example, an animal which is always hungry would use one prototype while a
|
||||
/// Diona which rarely gets hungry would use a different one.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class SatiationPrototype : IPrototype, IInheritingPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<SatiationPrototype>))]
|
||||
public string[]? Parents { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[AbstractDataField]
|
||||
public bool Abstract { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The base rate at which this satiation decreases per second.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public float BaseDecayRate;
|
||||
|
||||
/// <summary>
|
||||
/// The highest value this satiation can have. Increases beyond this value are clamped to this value.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public int MaximumValue;
|
||||
|
||||
/// <summary>
|
||||
/// The definition of key strings referentiable by <see cref="SatiationValue"/>s. Any reference to a key in this map
|
||||
/// by a <see cref="SatiationValue"/> will be resolved to the numeric value associated to that key here before use.
|
||||
/// <br/>
|
||||
/// Note hat different satiations can use the same keys without issue. Indeed, the intention is that a "base"
|
||||
/// satiation can define values and modifiers on itself using these keys, and then inheriting satiation prototypes
|
||||
/// can simply change the numeric values associated with those keys without needing to redefine the values and
|
||||
/// modifiers.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(DictionarySerializer<string, int>))]
|
||||
public Dictionary<string, int> Thresholds = [];
|
||||
|
||||
/// <summary>
|
||||
/// The lowest possible value this satiation can be initialized to.
|
||||
/// </summary>
|
||||
[DataField("startingValueMinimum", required: true)]
|
||||
private SatiationValue _startingValueMinimum = default!;
|
||||
|
||||
/// <inheritdoc cref="_startingValueMinimum" />
|
||||
public int StartingValueMinimum => GetValueOrNull(_startingValueMinimum) ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// The highest possible value this satiation can be initialized to.
|
||||
/// </summary>
|
||||
[DataField("startingValueMaximum", required: true)]
|
||||
private SatiationValue _startingValueMaximum = default!;
|
||||
|
||||
/// <inheritdoc cref="_startingValueMaximum" />
|
||||
public int StartingValueMaximum => GetValueOrNull(_startingValueMaximum) ?? MaximumValue;
|
||||
|
||||
/// <summary>
|
||||
/// Modifiers to <see cref="BaseDecayRate"/> based on the current threshold.
|
||||
/// </summary>
|
||||
/// <seealso cref="Satiation.ActualDecayRate"/>
|
||||
[DataField(customTypeSerializer: typeof(DictionarySerializer<SatiationValue, float>))]
|
||||
public Dictionary<SatiationValue, float> DecayModifiers = [];
|
||||
|
||||
#region Alerts
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AlertCategory"/> which all <see cref="Alerts"/> should belong to.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<AlertCategoryPrototype> AlertCategory;
|
||||
|
||||
/// <summary>
|
||||
/// Alerts to show when in the corresponding threshold.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(DictionarySerializer<SatiationValue, ProtoId<AlertPrototype>?>))]
|
||||
public Dictionary<SatiationValue, ProtoId<AlertPrototype>?> Alerts = [];
|
||||
|
||||
/// <summary>
|
||||
/// Icons to show to accompany <see cref="Alerts"/> when in the corresponding threshold.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(DictionarySerializer<SatiationValue, ProtoId<SatiationIconPrototype>?>))]
|
||||
public Dictionary<SatiationValue, ProtoId<SatiationIconPrototype>?> Icons = [];
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clamps <paramref name="value"/> between this prototype's highest and lowest values.
|
||||
/// </summary>
|
||||
public float ClampSatiationWithinThresholds(float value) => Math.Clamp(value, 0, MaximumValue);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get an integer value from the given <paramref name="satiationValue"/>. If
|
||||
/// <paramref name="satiationValue"/> is an immediate value, use its contained integer value. If it is a key,
|
||||
/// attempts to look up the integer value of that key in <see cref="Thresholds"/>; in the case that a key not
|
||||
/// present in this proto type is requested, returns null.
|
||||
/// </summary>
|
||||
public int? GetValueOrNull(SatiationValue satiationValue)
|
||||
{
|
||||
if (satiationValue.Key is { } key)
|
||||
return Thresholds.TryGetValue(key, out var v) ? v : null;
|
||||
|
||||
return satiationValue.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This type is a union of <c>int</c> and <c>string</c> for use with satiations. When it contains an immediate integer
|
||||
/// value, that value is used. When it contains a string key, that key is looked up in a
|
||||
/// <see cref="SatiationPrototype"/> to resolve its integer value before use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Values of this type should not be created directly. Instead, rely on the implicit conversion operators. Similarly,
|
||||
/// the fields in this type should not have their values modified.
|
||||
/// </remarks>
|
||||
/// <seealso cref="SatiationPrototype.GetValueOrNull"/>
|
||||
// TODO Replace with an actual union type once that's available in C#15: https://devblogs.microsoft.com/dotnet/csharp-15-union-types/
|
||||
[DataRecord, Serializable, NetSerializable]
|
||||
public partial record struct SatiationValue()
|
||||
{
|
||||
[DataField, Access]
|
||||
public int Value = -1;
|
||||
|
||||
[DataField, Access]
|
||||
public string? Key = null;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator SatiationValue(int value) => new() { Value = value };
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator SatiationValue(string key) => new() { Key = key };
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Nutrition.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Types of satiation, eg. Hunger, Thirst.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class SatiationTypePrototype : IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The localization of the name of this type of satiation.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public LocId NameLoc;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public string Name => Loc.GetString(NameLoc);
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Content.Shared.Nutrition.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// This serializer for <see cref="SatiationValue"/> allows YAMLers to use either an int or a string directly where a
|
||||
/// <see cref="SatiationValue"/> is expected.
|
||||
/// </summary>
|
||||
// TODO Replace with an actual union type once that's available in C#15: https://devblogs.microsoft.com/dotnet/csharp-15-union-types/
|
||||
[UsedImplicitly, TypeSerializer]
|
||||
public sealed class SatiationValueSerializer : ITypeSerializer<SatiationValue, ValueDataNode>
|
||||
{
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null
|
||||
) => int.TryParse(node.Value, out _)
|
||||
? serializationManager.ValidateNode<int>(node, context)
|
||||
: serializationManager.ValidateNode<string>(node, context);
|
||||
|
||||
public SatiationValue Read(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<SatiationValue>? instanceProvider = null
|
||||
) => int.TryParse(node.Value, out _)
|
||||
? serializationManager.Read<int>(node, context)
|
||||
: serializationManager.Read<string>(node, context, notNullableOverride: true);
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
SatiationValue value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null
|
||||
) => value.Key is not null
|
||||
? serializationManager.WriteValue(value.Key, notNullableOverride: true)
|
||||
: serializationManager.WriteValue(value.Value);
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
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 { }
|
||||
18
Content.Shared/Overlays/ShowSatiationIconsComponent.cs
Normal file
18
Content.Shared/Overlays/ShowSatiationIconsComponent.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// This component allows the owner to see the satiation of mobs.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ShowSatiationIconsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SatiationTypePrototype"/>s to be shown to the owner of this component.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<SatiationTypePrototype>> ShownTypes = [];
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
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 { }
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Sericulture;
|
||||
|
||||
|
|
@ -57,10 +57,8 @@ public sealed partial class SericultureComponent : Component
|
|||
public float HungerCost = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// The lowest hunger threshold that this mob can be in before it's allowed to spin silk.
|
||||
/// If the entity's hunger satiation is below this value, it cannot spin web.
|
||||
/// </summary>
|
||||
[DataField("minHungerThreshold")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public HungerThreshold MinHungerThreshold = HungerThreshold.Okay;
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public SatiationValue MinHungerThreshold;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ using Robust.Shared.Serialization;
|
|||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Network;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Sericulture;
|
||||
|
||||
|
|
@ -21,7 +23,7 @@ public abstract partial class SharedSericultureSystem : EntitySystem
|
|||
// Systems
|
||||
[Dependency] private SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private HungerSystem _hungerSystem = default!;
|
||||
[Dependency] private SatiationSystem _satiation = default!;
|
||||
[Dependency] private SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private SharedStackSystem _stackSystem = default!;
|
||||
|
||||
|
|
@ -70,18 +72,16 @@ public abstract partial class SharedSericultureSystem : EntitySystem
|
|||
|
||||
private void OnSericultureStart(EntityUid uid, SericultureComponent comp, SericultureActionEvent args)
|
||||
{
|
||||
if (!TryComp<HungerComponent>(uid, out var hungerComp)
|
||||
|| _hungerSystem.IsHungerBelowState(uid,
|
||||
comp.MinHungerThreshold,
|
||||
_hungerSystem.GetHunger(hungerComp) - comp.HungerCost,
|
||||
hungerComp))
|
||||
if (!TryComp<SatiationComponent>(uid, out var satiationComponent) ||
|
||||
!_satiation.IsValueInRange((uid, satiationComponent), SatiationSystem.Hunger, above: comp.MinHungerThreshold, hypotheticalValueDelta: -comp.HungerCost))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString(comp.PopupText), uid, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
var doAfter = new DoAfterArgs(EntityManager, uid, comp.ProductionLength, new SericultureDoAfterEvent(), uid)
|
||||
{ // I'm not sure if more things should be put here, but imo ideally it should probably be set in the component/YAML. Not sure if this is currently possible.
|
||||
{
|
||||
// I'm not sure if more things should be put here, but imo ideally it should probably be set in the component/YAML. Not sure if this is currently possible.
|
||||
BreakOnMove = true,
|
||||
BlockDuplicate = true,
|
||||
BreakOnDamage = true,
|
||||
|
|
@ -91,24 +91,20 @@ public abstract partial class SharedSericultureSystem : EntitySystem
|
|||
_doAfterSystem.TryStartDoAfter(doAfter);
|
||||
}
|
||||
|
||||
|
||||
private void OnSericultureDoAfter(EntityUid uid, SericultureComponent comp, SericultureDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Handled || comp.Deleted)
|
||||
return;
|
||||
|
||||
if (!TryComp<HungerComponent>(uid,
|
||||
out var hungerComp) // A check, just incase the doafter is somehow performed when the entity is not in the right hunger state.
|
||||
|| _hungerSystem.IsHungerBelowState(uid,
|
||||
comp.MinHungerThreshold,
|
||||
_hungerSystem.GetHunger(hungerComp) - comp.HungerCost,
|
||||
hungerComp))
|
||||
// A check, just incase the doafter is somehow performed when the entity is not in the right hunger state.
|
||||
if (!TryComp<SatiationComponent>(uid, out var satiationComponent) ||
|
||||
!_satiation.IsValueInRange((uid, satiationComponent), SatiationSystem.Hunger, above: comp.MinHungerThreshold, hypotheticalValueDelta: -comp.HungerCost))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString(comp.PopupText), uid, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
_hungerSystem.ModifyHunger(uid, -comp.HungerCost, hungerComp);
|
||||
_satiation.ModifyValue((uid, satiationComponent), SatiationSystem.Hunger, -comp.HungerCost);
|
||||
|
||||
if (!_netManager.IsClient) // Have to do this because spawning stuff in shared is CBT.
|
||||
{
|
||||
|
|
@ -124,11 +120,10 @@ public abstract partial class SharedSericultureSystem : EntitySystem
|
|||
/// <summary>
|
||||
/// Should be relayed upon using the action.
|
||||
/// </summary>
|
||||
public sealed partial class SericultureActionEvent : InstantActionEvent { }
|
||||
public sealed partial class SericultureActionEvent : InstantActionEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Is relayed at the end of the sericulturing doafter.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class SericultureDoAfterEvent : SimpleDoAfterEvent { }
|
||||
|
||||
public sealed partial class SericultureDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ namespace Content.Shared.Whitelist;
|
|||
public sealed partial class EntityWhitelistSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private TagSystem _tag = default!;
|
||||
[Dependency] private EntityQuery<ItemComponent> _itemQuery;
|
||||
|
||||
[Dependency] private EntityQuery<ItemComponent> _itemQuery = default!;
|
||||
|
||||
private CompName _itemComponentName = default;
|
||||
private CompName _tagComponentName = default;
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ entity-condition-guidebook-group-damage =
|
|||
}
|
||||
}
|
||||
|
||||
entity-condition-guidebook-total-hunger =
|
||||
entity-condition-guidebook-total-satiation =
|
||||
{ $max ->
|
||||
[2147483648] the target has at least {NATURALFIXED($min, 2)} total hunger
|
||||
[2147483648] the target has at least {NATURALFIXED($min, 2)} total {$type}
|
||||
*[other] { $min ->
|
||||
[0] the target has at most {NATURALFIXED($max, 2)} total hunger
|
||||
*[other] the target has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} total hunger
|
||||
[0] the target has at most {NATURALFIXED($max, 2)} total {$type}
|
||||
*[other] the target has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} total {$type}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,22 +67,13 @@ entity-effect-guidebook-smoke-area =
|
|||
*[other] create
|
||||
} large quantities of smoke
|
||||
|
||||
entity-effect-guidebook-satiate-thirst =
|
||||
entity-effect-guidebook-satiate =
|
||||
{ $chance ->
|
||||
[1] Satiates
|
||||
*[other] satiate
|
||||
} { $relative ->
|
||||
[1] thirst averagely
|
||||
*[other] thirst at {NATURALFIXED($relative, 3)}x the average rate
|
||||
}
|
||||
|
||||
entity-effect-guidebook-satiate-hunger =
|
||||
{ $chance ->
|
||||
[1] Satiates
|
||||
*[other] satiate
|
||||
} { $relative ->
|
||||
[1] hunger averagely
|
||||
*[other] hunger at {NATURALFIXED($relative, 3)}x the average rate
|
||||
[1] {$type} averagely
|
||||
*[other] {$type} at {NATURALFIXED($relative, 3)}x the average rate
|
||||
}
|
||||
|
||||
entity-effect-guidebook-health-change =
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
examinable-hunger-component-examine-overfed = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} stuffed!
|
||||
examinable-hunger-component-examine-okay = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} content.
|
||||
examinable-hunger-component-examine-peckish = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} hungry.
|
||||
examinable-hunger-component-examine-starving = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} starved!
|
||||
examinable-hunger-component-examine-none = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "seem", "seems")} not to get hungry.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
examinable-satiation-component-examine-hunger-full = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} stuffed!
|
||||
examinable-satiation-component-examine-hunger-okay = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} content.
|
||||
examinable-satiation-component-examine-hunger-concerned = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} hungry.
|
||||
examinable-satiation-component-examine-hunger-desperate = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} starved!
|
||||
examinable-satiation-component-examine-hunger-none = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "seem", "seems")} not to get hungry.
|
||||
|
||||
examinable-satiation-component-examine-thirst-full = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} well hydrated!
|
||||
examinable-satiation-component-examine-thirst-okay = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} quenched.
|
||||
examinable-satiation-component-examine-thirst-concerned = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} thirsty.
|
||||
examinable-satiation-component-examine-thirst-desperate = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} parched!
|
||||
examinable-satiation-component-examine-thirst-none = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "seem", "seems")} not to get thirsty.
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
cmd-nutrition-error-player = You cannot use this command unless you are a player.
|
||||
cmd-nutrition-error-entity = You cannot use this command without an entity.
|
||||
cmd-nutrition-error-component = Your entity does not have a {$comp} component.
|
||||
cmd-nutrition-satiation-need = {$satiation} satiation need
|
||||
|
||||
cmd-hungry-desc = makes you hungry
|
||||
cmd-hungry-help = sets your hungry level to starving
|
||||
cmd-setnutrit-desc = modify hunger and thirst
|
||||
cmd-setnutrit-help = set your hunger or thirst to one of the built-in thresholds
|
||||
cmd-setnutrit-error-invalid-threshold = invalid {$thresholdType} `{$thresholdString}`
|
||||
cmd-setsatiation-desc = Sets your satiation need.
|
||||
cmd-setsatiation-help = Sets your specified satiation need to the specified threshold.
|
||||
|
||||
cmd-thirsty-desc = makes you thirsty
|
||||
cmd-thirsty-help = sets your thirst level to partched
|
||||
cmd-nutrition-setsatiation-prototype-error = Invalid satiation \"{$satiation-proto-id}\"
|
||||
cmd-nutrition-setsatiation-no-matching-key-error = Unrecognized key \"{$key}\" for satiation \"{$satiation-proto-id}\"
|
||||
cmd-nutrition-setsatiation-hint-key = Satiation Value Key
|
||||
cmd-nutrition-setsatiation-hint-max-value = Maximum Value
|
||||
|
||||
cmd-unsatiate-desc = Makes your satiation needs desperate.
|
||||
cmd-unsatiate-help = Sets the specified satiation needs to the "desperate" threshold. Affects specified satiation types, or all types if none are specified.
|
||||
|
|
|
|||
2
Resources/Locale/en-US/nutrition/satiation-types.ftl
Normal file
2
Resources/Locale/en-US/nutrition/satiation-types.ftl
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
satiation-type-hunger = hunger
|
||||
satiation-type-thirst = thirst
|
||||
12
Resources/Prototypes/Body/Satiation/Hunger/examination.yml
Normal file
12
Resources/Prototypes/Body/Satiation/Hunger/examination.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
- type: entity
|
||||
abstract: true
|
||||
id: BaseExaminableHunger
|
||||
components:
|
||||
- type: ExaminableSatiation
|
||||
satiations:
|
||||
Hunger:
|
||||
Dead: "examinable-satiation-component-examine-hunger-desperate"
|
||||
Starving: "examinable-satiation-component-examine-hunger-desperate"
|
||||
Peckish: "examinable-satiation-component-examine-hunger-concerned"
|
||||
Okay: "examinable-satiation-component-examine-hunger-okay"
|
||||
Overfed: "examinable-satiation-component-examine-hunger-full"
|
||||
104
Resources/Prototypes/Body/Satiation/Hunger/hunger.yml
Normal file
104
Resources/Prototypes/Body/Satiation/Hunger/hunger.yml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
- type: satiation
|
||||
id: NormalSatiationHunger
|
||||
baseDecayRate: 0.01666666666
|
||||
maximumValue: 200
|
||||
thresholds:
|
||||
Overfed: 200
|
||||
Okay: 150
|
||||
Peckish: 100
|
||||
Starving: 50
|
||||
Dead: 0
|
||||
startingValueMinimum: Peckish
|
||||
startingValueMaximum: Okay
|
||||
decayModifiers:
|
||||
Overfed: 1.2
|
||||
Okay: 1.0
|
||||
Peckish: 0.8
|
||||
Starving: 0.6
|
||||
alertCategory: Hunger
|
||||
alerts:
|
||||
Peckish: Peckish
|
||||
Starving: Starving
|
||||
icons:
|
||||
Overfed: HungerIconOverfed
|
||||
Okay: null
|
||||
Peckish: HungerIconPeckish
|
||||
Starving: HungerIconStarving
|
||||
|
||||
- type: satiation
|
||||
id: SimpleMobBaseHunger
|
||||
parent: NormalSatiationHunger
|
||||
thresholds: # only animals and rats are derived from this prototype so let's override it here and in rats' proto
|
||||
Overfed: 100
|
||||
Okay: 50
|
||||
Peckish: 25
|
||||
Starving: 10
|
||||
baseDecayRate: 0.00925925925926 # it is okay for animals to eat and drink less than humans, but more frequently
|
||||
maximumValue: 100
|
||||
|
||||
- type: satiation
|
||||
id: SnailHunger
|
||||
parent: SimpleMobBaseHunger
|
||||
thresholds:
|
||||
Overfed: 35
|
||||
Okay: 25
|
||||
Peckish: 15
|
||||
Starving: 10
|
||||
baseDecayRate: 0.1
|
||||
maximumValue: 35
|
||||
|
||||
- type: satiation
|
||||
id: CockroachHunger
|
||||
parent: SimpleMobBaseHunger
|
||||
baseDecayRate: 0.25
|
||||
|
||||
- type: satiation
|
||||
id: MouseHunger
|
||||
parent: SimpleMobBaseHunger
|
||||
thresholds:
|
||||
Overfed: 35
|
||||
Okay: 25
|
||||
Peckish: 15
|
||||
Starving: 10
|
||||
baseDecayRate: 0.5 # I'm very hungry! Give me. The cheese.
|
||||
maximumValue: 35
|
||||
|
||||
- type: satiation
|
||||
id: MouseAdmemeHunger
|
||||
parent: MouseHunger
|
||||
baseDecayRate: 10 # always hungry
|
||||
|
||||
- type: satiation
|
||||
id: HamsterHunger
|
||||
parent: SimpleMobBaseHunger
|
||||
baseDecayRate: 0.3
|
||||
|
||||
- type: satiation
|
||||
id: RegalRatHunger
|
||||
parent: SimpleMobBaseHunger
|
||||
thresholds:
|
||||
Overfed: 200
|
||||
Okay: 150
|
||||
Peckish: 100
|
||||
Starving: 50
|
||||
baseDecayRate: 0.01666666666
|
||||
maximumValue: 200
|
||||
|
||||
- type: satiation
|
||||
id: ScurretHunger
|
||||
parent: SimpleMobBaseHunger
|
||||
baseDecayRate: 0.05 # They get a lil' hungy
|
||||
|
||||
- type: satiation
|
||||
id: DionaHunger
|
||||
parent: NormalSatiationHunger
|
||||
baseDecayRate: 0.0083
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: BaseHungerSlowdown
|
||||
component:
|
||||
- type: SatiationSpeedModifier
|
||||
satiations:
|
||||
Hunger:
|
||||
Peckish: 0.75
|
||||
69
Resources/Prototypes/Body/Satiation/Thirst/thirst.yml
Normal file
69
Resources/Prototypes/Body/Satiation/Thirst/thirst.yml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
- type: satiation
|
||||
id: NormalSatiationThirst
|
||||
baseDecayRate: 0.1
|
||||
maximumValue: 600
|
||||
thresholds:
|
||||
Overhydrated: 600
|
||||
Okay: 450
|
||||
Thirsty: 300
|
||||
Parched: 150
|
||||
Dead: 0
|
||||
startingValueMinimum: Thirsty
|
||||
startingValueMaximum: Okay
|
||||
decayModifiers:
|
||||
Overhydrated: 1.2
|
||||
Okay: 1.0
|
||||
Thirsty: 0.8
|
||||
Parched: 0.6
|
||||
alertCategory: Thirst
|
||||
alerts:
|
||||
Thirsty: Thirsty
|
||||
Parched: Parched
|
||||
icons:
|
||||
Overhydrated: ThirstIconOverhydrated
|
||||
Okay: null
|
||||
Thirsty: ThirstIconThirsty
|
||||
Parched: ThirstIconParched
|
||||
|
||||
- type: satiation
|
||||
id: MouseThirst
|
||||
parent: NormalSatiationThirst
|
||||
baseDecayRate: 0.04
|
||||
maximumValue: 35
|
||||
thresholds:
|
||||
Overhydrated: 35
|
||||
Okay: 25
|
||||
Thirsty: 15
|
||||
Parched: 10
|
||||
Dead: 0
|
||||
|
||||
- type: satiation
|
||||
id: MouseAdmemeThirst
|
||||
parent: MouseThirst
|
||||
baseDecayRate: 10
|
||||
|
||||
- type: satiation
|
||||
id: SimpleMobBaseThirst
|
||||
parent: NormalSatiationThirst
|
||||
baseDecayRate: 0.04
|
||||
maximumValue: 200
|
||||
thresholds:
|
||||
Overhydrated: 200
|
||||
Okay: 150
|
||||
Thirsty: 100
|
||||
Parched: 50
|
||||
Dead: 0
|
||||
|
||||
- type: satiation
|
||||
id: DionaThirst
|
||||
parent: NormalSatiationThirst
|
||||
baseDecayRate: 0.0083
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: BaseThirstSlowdown
|
||||
component:
|
||||
- type: SatiationSpeedModifier
|
||||
satiations:
|
||||
Thirst:
|
||||
Parched: 0.75
|
||||
7
Resources/Prototypes/Body/Satiation/types.yml
Normal file
7
Resources/Prototypes/Body/Satiation/types.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
- type: satiationType
|
||||
id: Hunger
|
||||
name: satiation-type-hunger
|
||||
|
||||
- type: satiationType
|
||||
id: Thirst
|
||||
name: satiation-type-thirst
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
productionLength: 2
|
||||
entityProduced: MaterialWebSilk1
|
||||
hungerCost: 4 # Should total to 25 total silk on full hunger
|
||||
minHungerThreshold: Peckish
|
||||
- type: Tag
|
||||
tags:
|
||||
- CanPilot
|
||||
|
|
|
|||
|
|
@ -79,10 +79,12 @@
|
|||
damageRecovery:
|
||||
types:
|
||||
Asphyxiation: -1.0
|
||||
- type: Hunger
|
||||
baseDecayRate: 0.0083
|
||||
- type: Thirst
|
||||
baseDecayRate: 0.0083
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: DionaHunger
|
||||
Thirst:
|
||||
prototype: DionaThirst
|
||||
- type: Damageable
|
||||
damageModifierSet: Diona
|
||||
- type: DamageVisuals
|
||||
|
|
|
|||
|
|
@ -161,11 +161,17 @@
|
|||
- MobFlammable
|
||||
- BaseSpeciesMob
|
||||
- BaseMobDestructible
|
||||
- BaseHungerSlowdown
|
||||
- BaseThirstSlowdown
|
||||
id: BaseSpeciesMobOrganic
|
||||
save: false
|
||||
components:
|
||||
- type: Hunger
|
||||
- type: Thirst
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: NormalSatiationHunger
|
||||
Thirst:
|
||||
prototype: NormalSatiationThirst
|
||||
- type: Barotrauma
|
||||
damage:
|
||||
types:
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@
|
|||
sprite: Clothing/Eyes/Hud/beergoggles.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Hud/beergoggles.rsi
|
||||
- type: ShowThirstIcons
|
||||
- type: ShowSatiationIcons
|
||||
shownTypes:
|
||||
- Thirst
|
||||
- type: SolutionScanner
|
||||
- type: Tag
|
||||
tags:
|
||||
|
|
@ -116,7 +118,9 @@
|
|||
sprite: Clothing/Eyes/Hud/friedonion.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Hud/friedonion.rsi
|
||||
- type: ShowHungerIcons
|
||||
- type: ShowSatiationIcons
|
||||
shownTypes:
|
||||
- Hunger
|
||||
- type: Edible
|
||||
- type: Solution
|
||||
solution:
|
||||
|
|
@ -138,8 +142,10 @@
|
|||
sprite: Clothing/Eyes/Hud/onionbeer.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Hud/onionbeer.rsi
|
||||
- type: ShowHungerIcons
|
||||
- type: ShowThirstIcons
|
||||
- type: ShowSatiationIcons
|
||||
shownTypes:
|
||||
- Hunger
|
||||
- Thirst
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesHudBase, ShowMedicalIcons]
|
||||
|
|
@ -151,7 +157,9 @@
|
|||
sprite: Clothing/Eyes/Hud/medonion.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Hud/medonion.rsi
|
||||
- type: ShowHungerIcons
|
||||
- type: ShowSatiationIcons
|
||||
shownTypes:
|
||||
- Hunger
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesHudBase, ShowMedicalIcons]
|
||||
|
|
@ -163,8 +171,10 @@
|
|||
sprite: Clothing/Eyes/Hud/medonionbeer.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Hud/medonionbeer.rsi
|
||||
- type: ShowHungerIcons
|
||||
- type: ShowThirstIcons
|
||||
- type: ShowSatiationIcons
|
||||
shownTypes:
|
||||
- Hunger
|
||||
- Thirst
|
||||
|
||||
- type: entity
|
||||
parent: [ClothingEyesHudBase, ShowSecurityIcons, ShowMedicalIcons, BaseSecurityCommandContraband]
|
||||
|
|
@ -210,8 +220,10 @@
|
|||
damageContainers:
|
||||
- Biological
|
||||
- Inorganic
|
||||
- type: ShowHungerIcons
|
||||
- type: ShowThirstIcons
|
||||
- type: ShowSatiationIcons
|
||||
shownTypes:
|
||||
- Hunger
|
||||
- Thirst
|
||||
- type: ShowSyndicateIcons
|
||||
|
||||
- type: entity
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@
|
|||
|
||||
- type: entity
|
||||
name: chicken
|
||||
parent: [SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible]
|
||||
parent: [SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible, BaseExaminableHunger]
|
||||
id: MobChicken
|
||||
description: Comes before an egg, and IS a dinosaur!
|
||||
components:
|
||||
|
|
@ -239,6 +239,7 @@
|
|||
breedChance: 0.05
|
||||
birthPopup: reproductive-laid-egg-popup
|
||||
makeOffspringInfant: false
|
||||
minHungerThreshold: Peckish
|
||||
partnerWhitelist:
|
||||
tags:
|
||||
- Chicken
|
||||
|
|
@ -274,7 +275,6 @@
|
|||
- type: EggLayer
|
||||
eggSpawn:
|
||||
- id: FoodEgg
|
||||
- type: ExaminableHunger
|
||||
- type: ReplacementAccent
|
||||
accent: chicken
|
||||
- type: SentienceTarget
|
||||
|
|
@ -391,8 +391,10 @@
|
|||
- horrible
|
||||
- terrible
|
||||
- chewy
|
||||
- type: Hunger
|
||||
baseDecayRate: 0.25
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: CockroachHunger
|
||||
- type: Extractable
|
||||
grindableSolutionName: food
|
||||
- type: Solution
|
||||
|
|
@ -660,7 +662,7 @@
|
|||
|
||||
- type: entity
|
||||
name: mallard duck #Quack
|
||||
parent: [SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible]
|
||||
parent: [SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible, BaseExaminableHunger]
|
||||
id: MobDuckMallard
|
||||
description: An adorable mallard duck, it's fluffy and soft!
|
||||
components:
|
||||
|
|
@ -690,6 +692,7 @@
|
|||
breedChance: 0.05
|
||||
birthPopup: reproductive-laid-egg-popup
|
||||
makeOffspringInfant: false
|
||||
minHungerThreshold: Peckish
|
||||
partnerWhitelist:
|
||||
tags:
|
||||
- Duck
|
||||
|
|
@ -725,7 +728,6 @@
|
|||
- type: EggLayer
|
||||
eggSpawn:
|
||||
- id: FoodEgg
|
||||
- type: ExaminableHunger
|
||||
- type: ReplacementAccent
|
||||
accent: duck
|
||||
- type: SentienceTarget
|
||||
|
|
@ -863,7 +865,7 @@
|
|||
|
||||
- type: entity
|
||||
name: cow
|
||||
parent: [ BaseMobRuminant, SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible ]
|
||||
parent: [ BaseMobRuminant, SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible, BaseExaminableHunger ]
|
||||
id: MobCow
|
||||
description: Moo.
|
||||
components:
|
||||
|
|
@ -878,6 +880,7 @@
|
|||
- DoorBumpOpener
|
||||
- Cow
|
||||
- type: Reproductive
|
||||
minHungerThreshold: Peckish
|
||||
partnerWhitelist:
|
||||
tags:
|
||||
- Cow
|
||||
|
|
@ -908,10 +911,10 @@
|
|||
solutions:
|
||||
- SolutionUdder
|
||||
- type: Udder
|
||||
minHungerThreshold: Peckish
|
||||
reagentId: Milk
|
||||
quantityPerUpdate: 25
|
||||
growthDelay: 30
|
||||
- type: ExaminableHunger
|
||||
- type: ToolRefinable
|
||||
refineResult:
|
||||
- id: FoodMeat
|
||||
|
|
@ -1022,7 +1025,7 @@
|
|||
|
||||
- type: entity
|
||||
name: goat
|
||||
parent: [ BaseMobRuminant, SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible ]
|
||||
parent: [ BaseMobRuminant, SimpleMobBase, BaseSlicingRefinable, BaseMobDestructible, BaseExaminableHunger ]
|
||||
id: MobGoat
|
||||
description: Her spine consists of long sharp segments, no wonder she is so grumpy.
|
||||
components:
|
||||
|
|
@ -1050,6 +1053,7 @@
|
|||
- DoorBumpOpener
|
||||
- Goat
|
||||
- type: Reproductive
|
||||
minHungerThreshold: Peckish
|
||||
partnerWhitelist:
|
||||
tags:
|
||||
- Goat
|
||||
|
|
@ -1070,11 +1074,12 @@
|
|||
- SolutionUdderGoat
|
||||
- SolutionWool
|
||||
- type: Udder
|
||||
minHungerThreshold: Peckish
|
||||
reagentId: MilkGoat
|
||||
quantityPerUpdate: 25
|
||||
growthDelay: 20
|
||||
- type: ExaminableHunger
|
||||
- type: Wooly
|
||||
minHungerThreshold: Peckish
|
||||
- type: Edible
|
||||
destroyOnEmpty: false
|
||||
solution: wool
|
||||
|
|
@ -1134,7 +1139,7 @@
|
|||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: [BaseMobRuminant, BaseSlicingRefinable]
|
||||
parent: [BaseMobRuminant, BaseSlicingRefinable, BaseExaminableHunger]
|
||||
id: MobSheepBase
|
||||
name: sheepBase
|
||||
description: A sheep.
|
||||
|
|
@ -1203,8 +1208,9 @@
|
|||
reagentId: MilkSheep
|
||||
quantityPerUpdate: 25
|
||||
growthDelay: 20
|
||||
- type: ExaminableHunger
|
||||
minHungerThreshold: Peckish
|
||||
- type: Wooly
|
||||
minHungerThreshold: Peckish
|
||||
- type: Shearable
|
||||
targetSolutionName: wool
|
||||
shearedProductId: MaterialCotton1 # Cotton is spawned because there is no wool item.
|
||||
|
|
@ -2006,23 +2012,12 @@
|
|||
- type: FlavorProfile
|
||||
flavors:
|
||||
- meaty
|
||||
- type: Thirst
|
||||
startingThirst: 25 # spawn with Okay thirst state
|
||||
thresholds:
|
||||
OverHydrated: 35
|
||||
Okay: 25
|
||||
Thirsty: 15
|
||||
Parched: 10
|
||||
Dead: 0
|
||||
baseDecayRate: 0.04
|
||||
- type: Hunger
|
||||
thresholds:
|
||||
Overfed: 35
|
||||
Okay: 25
|
||||
Peckish: 15
|
||||
Starving: 10
|
||||
Dead: 0
|
||||
baseDecayRate: 0.5 # I'm very hungry! Give me. The cheese.
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: MouseHunger
|
||||
Thirst:
|
||||
prototype: MouseThirst
|
||||
- type: Extractable
|
||||
grindableSolutionName: food
|
||||
- type: ToolRefinable
|
||||
|
|
@ -3707,8 +3702,10 @@
|
|||
- type: FlavorProfile
|
||||
flavors:
|
||||
- meaty
|
||||
- type: Hunger
|
||||
baseDecayRate: 0.3
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: HamsterHunger
|
||||
- type: Extractable
|
||||
grindableSolutionName: food
|
||||
- type: Solution
|
||||
|
|
@ -3816,6 +3813,7 @@
|
|||
- Pig
|
||||
- VimPilot
|
||||
- type: Reproductive
|
||||
minHungerThreshold: Peckish
|
||||
partnerWhitelist:
|
||||
tags:
|
||||
- Pig
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- type: entity
|
||||
name: rat king
|
||||
id: MobRatKing
|
||||
parent: [ BaseMobRat, SimpleMobBase, MobCombat, BaseSlicingRefinable, MobNightVision, BaseMobDestructible ]
|
||||
parent: [ BaseMobRat, SimpleMobBase, MobCombat, BaseSlicingRefinable, MobNightVision, BaseMobDestructible, BaseThirstSlowdown ]
|
||||
description: He's da rat. He make da roolz.
|
||||
components:
|
||||
- type: CombatMode
|
||||
|
|
@ -61,22 +61,12 @@
|
|||
types:
|
||||
Slash: 12
|
||||
Piercing: 8
|
||||
- type: Hunger # probably should be prototyped
|
||||
thresholds:
|
||||
Overfed: 200
|
||||
Okay: 150
|
||||
Peckish: 100
|
||||
Starving: 50
|
||||
Dead: 0
|
||||
baseDecayRate: 0.01666666666
|
||||
- type: Thirst
|
||||
thresholds:
|
||||
OverHydrated: 600
|
||||
Okay: 450
|
||||
Thirsty: 300
|
||||
Parched: 150
|
||||
Dead: 0
|
||||
baseDecayRate: 0.1
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: RegalRatHunger
|
||||
Thirst:
|
||||
prototype: NormalSatiationThirst
|
||||
- type: DamageStateVisuals
|
||||
states:
|
||||
Alive:
|
||||
|
|
@ -173,7 +163,7 @@
|
|||
- type: entity
|
||||
name: rat servant
|
||||
id: MobRatServant
|
||||
parent: [ BaseMobRat, SimpleMobBase, MobCombat, BaseSlicingRefinable ]
|
||||
parent: [ BaseMobRat, SimpleMobBase, MobCombat, BaseSlicingRefinable, BaseThirstSlowdown ]
|
||||
description: He's da mini rat. He don't make da roolz.
|
||||
categories: [ HideSpawnMenu ] #Must be configured to a King or the AI breaks.
|
||||
components:
|
||||
|
|
@ -255,22 +245,12 @@
|
|||
types:
|
||||
Slash: 5
|
||||
Piercing: 3
|
||||
- type: Hunger # probably should be prototyped
|
||||
thresholds:
|
||||
Overfed: 200
|
||||
Okay: 150
|
||||
Peckish: 100
|
||||
Starving: 50
|
||||
Dead: 0
|
||||
baseDecayRate: 0.01666666666
|
||||
- type: Thirst
|
||||
thresholds:
|
||||
OverHydrated: 600
|
||||
Okay: 450
|
||||
Thirsty: 300
|
||||
Parched: 150
|
||||
Dead: 0
|
||||
baseDecayRate: 0.1
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: RegalRatHunger
|
||||
Thirst:
|
||||
prototype: NormalSatiationThirst
|
||||
- type: DamageStateVisuals
|
||||
states:
|
||||
Alive:
|
||||
|
|
|
|||
|
|
@ -133,8 +133,10 @@
|
|||
|
||||
- type: InventorySlots
|
||||
- type: Edible # Why? I'm not sure but I also am afraid of the answer.
|
||||
- type: Hunger
|
||||
baseDecayRate: 0.05 # They get a lil' hungy
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: ScurretHunger
|
||||
- type: BodyEmotes # Grants them clapping and so on.
|
||||
soundsId: Scurret
|
||||
- type: Deathgasp
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@
|
|||
- MobBloodstream
|
||||
- MobFlammable
|
||||
- BaseMobAnimal
|
||||
- BaseHungerSlowdown
|
||||
- BaseThirstSlowdown
|
||||
id: SimpleSpaceMobBase # Mob without barotrauma, freezing and asphyxiation (for space carps!?)
|
||||
suffix: AI
|
||||
components:
|
||||
|
|
@ -67,26 +69,18 @@
|
|||
- MobRespirator
|
||||
- MobAtmosStandard
|
||||
- SimpleSpaceMobBase
|
||||
- BaseHungerSlowdown
|
||||
- BaseThirstSlowdown
|
||||
id: SimpleMobBase # for air breathers
|
||||
suffix: AI
|
||||
abstract: true
|
||||
components:
|
||||
- type: Hunger
|
||||
thresholds: # only animals and rats are derived from this prototype so let's override it here and in rats' proto
|
||||
Overfed: 100
|
||||
Okay: 50
|
||||
Peckish: 25
|
||||
Starving: 10
|
||||
Dead: 0
|
||||
baseDecayRate: 0.00925925925926 # it is okay for animals to eat and drink less than humans, but more frequently
|
||||
- type: Thirst
|
||||
thresholds:
|
||||
OverHydrated: 200
|
||||
Okay: 150
|
||||
Thirsty: 100
|
||||
Parched: 50
|
||||
Dead: 0
|
||||
baseDecayRate: 0.04
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: SimpleMobBaseHunger
|
||||
Thirst:
|
||||
prototype: SimpleMobBaseThirst
|
||||
- type: StatusEffects
|
||||
allowed:
|
||||
- Electrocution
|
||||
|
|
|
|||
|
|
@ -571,23 +571,12 @@
|
|||
Quantity: 3
|
||||
- ReagentId: Water
|
||||
Quantity: 4 #It makes saline if you add salt!
|
||||
- type: Thirst
|
||||
startingThirst: 25 # spawn with Okay thirst state
|
||||
thresholds:
|
||||
OverHydrated: 35
|
||||
Okay: 25
|
||||
Thirsty: 15
|
||||
Parched: 10
|
||||
Dead: 0
|
||||
baseDecayRate: 0.04
|
||||
- type: Hunger
|
||||
thresholds:
|
||||
Overfed: 35
|
||||
Okay: 25
|
||||
Peckish: 15
|
||||
Starving: 10
|
||||
Dead: 0
|
||||
baseDecayRate: 0.1
|
||||
- type: Satiation
|
||||
satiations:
|
||||
Hunger:
|
||||
prototype: SnailHunger
|
||||
Thirst:
|
||||
prototype: MouseThirst
|
||||
- type: Extractable
|
||||
grindableSolutionName: food
|
||||
- type: Tag
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
description: Safely and efficiently extracts excess fat from a subject.
|
||||
components:
|
||||
- type: FatExtractor
|
||||
minHungerThreshold: Peckish
|
||||
processSound:
|
||||
path: /Audio/Machines/microwave_loop.ogg
|
||||
params:
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
- tasks:
|
||||
- !type:HTNPrimitiveTask
|
||||
preconditions:
|
||||
- !type:HungryPrecondition
|
||||
minHungerState: Peckish # See HungerThreshold enum
|
||||
- !type:SatiationPrecondition
|
||||
satiationType: Hunger
|
||||
below: Peckish
|
||||
operator: !type:UtilityOperator
|
||||
proto: NearbyFood
|
||||
|
||||
|
|
@ -35,8 +36,9 @@
|
|||
- tasks:
|
||||
- !type:HTNPrimitiveTask
|
||||
preconditions:
|
||||
- !type:ThirstyPrecondition
|
||||
minThirstState: Parched # See ThirstThreshold enum
|
||||
- !type:SatiationPrecondition
|
||||
satiationType: Thirst
|
||||
below: Parched
|
||||
operator: !type:UtilityOperator
|
||||
proto: NearbyDrink
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
curve: !type:PresetCurve
|
||||
preset: TargetDistance
|
||||
- !type:FoodValueCon
|
||||
hungerThreshold: Peckish
|
||||
curve: !type:QuadraticCurve
|
||||
slope: 1.0
|
||||
exponent: 0.4
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
curve: !type:PresetCurve
|
||||
preset: TargetDistance
|
||||
- !type:DrinkValueCon
|
||||
thirstThreshold: Thirsty
|
||||
curve: !type:QuadraticCurve
|
||||
slope: 1.0
|
||||
exponent: 0.4
|
||||
|
|
|
|||
|
|
@ -273,7 +273,8 @@
|
|||
metabolites:
|
||||
Ethanol: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:HealthChange
|
||||
damage:
|
||||
|
|
@ -429,7 +430,8 @@
|
|||
metabolites:
|
||||
Ethanol: 0.6
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 3
|
||||
fizziness: 0.8
|
||||
|
||||
|
|
@ -872,9 +874,11 @@
|
|||
metabolites:
|
||||
Ethanol: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: -2
|
||||
- !type:EvenHealthChange
|
||||
damage:
|
||||
|
|
@ -1367,7 +1371,8 @@
|
|||
metabolites:
|
||||
Ethanol: 0.5
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:HealthChange
|
||||
damage:
|
||||
|
|
@ -1520,7 +1525,8 @@
|
|||
effects:
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectRatvarianLanguage
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -1542,7 +1548,8 @@
|
|||
effects:
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectMuted
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -1825,7 +1832,8 @@
|
|||
Ethanol: 0.2
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -1853,7 +1861,8 @@
|
|||
Ethanol: 0.3
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -1881,7 +1890,8 @@
|
|||
Ethanol: 0.2
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -1909,7 +1919,8 @@
|
|||
Ethanol: 0.3
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -1937,7 +1948,8 @@
|
|||
Ethanol: 0.14
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -1965,7 +1977,8 @@
|
|||
Ethanol: 0.3
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -2052,7 +2065,8 @@
|
|||
effects:
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectMuted
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -2103,7 +2117,8 @@
|
|||
damage:
|
||||
types:
|
||||
Poison: .5
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -2258,7 +2273,8 @@
|
|||
metabolites:
|
||||
Ethanol: 0.9
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 3
|
||||
- !type:Drunk
|
||||
boozePower: 10
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 3
|
||||
reactiveEffects:
|
||||
Extinguish:
|
||||
|
|
@ -28,7 +29,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
plantMetabolism:
|
||||
- !type:PlantAdjustNutrition
|
||||
|
|
@ -53,7 +55,8 @@
|
|||
metabolites:
|
||||
Ethanol: 0.12
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
reactiveEffects:
|
||||
Flammable:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
metabolites:
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -50,7 +51,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -64,7 +66,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 4 # Coconut water is 94% water
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -78,7 +81,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -98,7 +102,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -156,7 +161,8 @@
|
|||
metabolites:
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -210,7 +216,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
- !type:HealthChange
|
||||
damage:
|
||||
types:
|
||||
|
|
@ -234,7 +241,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -254,7 +262,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 4
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -281,7 +290,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 4
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -304,7 +314,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -2
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -316,7 +327,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
# TODO heal mimes
|
||||
|
||||
|
|
@ -338,7 +350,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 6
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -377,7 +390,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -437,7 +451,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 4
|
||||
Respiration:
|
||||
effects:
|
||||
|
|
@ -467,7 +482,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 3 # Slightly worse for quenching thirst than just straight water
|
||||
plantMetabolism:
|
||||
- !type:PlantAdjustWater
|
||||
|
|
@ -490,7 +506,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 1
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -504,7 +521,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 4
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -526,7 +544,8 @@
|
|||
metabolites:
|
||||
Theobromine: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 4
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -540,7 +559,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
fizziness: 0.3
|
||||
|
||||
|
|
@ -596,7 +616,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 0.6
|
||||
|
||||
- type: reagent
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -40,7 +41,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 4
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -80,7 +82,8 @@
|
|||
metabolites:
|
||||
Theobromine: 0.2
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectDrowsiness
|
||||
|
|
@ -205,7 +208,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 4
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -266,7 +270,8 @@
|
|||
metabolites:
|
||||
Theobromine: 0.2
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 2
|
||||
- !type:HealthChange
|
||||
conditions:
|
||||
|
|
|
|||
|
|
@ -129,9 +129,11 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 0.5
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -0.5 # high salt content
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -162,7 +164,8 @@
|
|||
Digestion:
|
||||
effects:
|
||||
# eating salt on its own kinda sucks, kids
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -0.5
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -181,7 +184,8 @@
|
|||
Digestion:
|
||||
# 12 diona blood for 1 unit of syrup, this stuff better be worthwhile.
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 6.0 #Stronger than cookedramen
|
||||
footstepSound:
|
||||
collection: FootstepSlime
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
metabolisms:
|
||||
Metabolites:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
plantMetabolism:
|
||||
- !type:PlantAdjustNutrition
|
||||
amount: 1.5
|
||||
|
|
@ -37,7 +38,8 @@
|
|||
# Helps you stop bleeding to an extent.
|
||||
- !type:ModifyBleed
|
||||
amount: -0.25
|
||||
- !type:SatiateHunger #Numbers are balanced with this in mind + it helps limit how much healing you can get from food
|
||||
- !type:Satiate #Numbers are balanced with this in mind + it helps limit how much healing you can get from food
|
||||
satiationType: Hunger
|
||||
# Lets plants benefit too
|
||||
plantMetabolism:
|
||||
- !type:PlantAdjustNutrition
|
||||
|
|
@ -63,7 +65,8 @@
|
|||
Brute: -0.4
|
||||
- !type:ModifyBloodLevel
|
||||
amount: 1 # weaker than iron but pretty good all things considered
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
pricePerUnit: 3
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -78,7 +81,8 @@
|
|||
metabolisms:
|
||||
Metabolites:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
conditions:
|
||||
- !type:ReagentCondition #Only satiates when eaten with nutriment
|
||||
reagent: Nutriment
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
Sugar: 0.4
|
||||
Nutriment: 0.2
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -0.2 #its flour
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -29,7 +30,8 @@
|
|||
metabolites:
|
||||
Nutriment: 0.2
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -0.2 #its cornflour
|
||||
|
||||
- type: reagent
|
||||
|
|
@ -223,7 +225,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
conditions:
|
||||
- !type:ReagentCondition #Only satiates when eaten with nutriment
|
||||
reagent: Nutriment
|
||||
|
|
@ -266,7 +269,8 @@
|
|||
metabolites:
|
||||
Nutriment: 0.2
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -0.2
|
||||
|
||||
- type: reagent
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
metabolites:
|
||||
UncookedAnimalProteins: 0.2
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1.0
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
|
|
@ -74,7 +75,8 @@
|
|||
Digestion:
|
||||
# Delicious!
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 1
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
|
|
@ -106,9 +108,11 @@
|
|||
Digestion:
|
||||
# Sweet!
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 1
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
footstepSound:
|
||||
collection: FootstepSlime
|
||||
|
|
@ -160,7 +164,8 @@
|
|||
Digestion:
|
||||
# Disgusting!
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -0.5
|
||||
Bloodstream:
|
||||
effects:
|
||||
|
|
@ -183,7 +188,8 @@
|
|||
metabolisms:
|
||||
Bloodstream:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1.5
|
||||
# Dragon doesn't require airloss healing, so omnizine is still best for humans.
|
||||
- !type:ModifyBloodLevel
|
||||
|
|
@ -237,7 +243,8 @@
|
|||
metabolites:
|
||||
Nutriment: 0.1
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 0.5
|
||||
footstepSound:
|
||||
collection: FootstepBlood
|
||||
|
|
@ -255,7 +262,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 1.5
|
||||
- !type:HealthChange
|
||||
damage:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
type: [Plant]
|
||||
|
|
@ -36,7 +37,8 @@
|
|||
metabolisms:
|
||||
Bloodstream:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
type: [Plant]
|
||||
|
|
|
|||
|
|
@ -181,7 +181,8 @@
|
|||
metabolisms:
|
||||
Bloodstream:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -5 # This is basically industrial dessicant
|
||||
- !type:PopupMessage
|
||||
visualType: MediumCaution
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
type: [ Moth ]
|
||||
|
|
|
|||
|
|
@ -484,7 +484,8 @@
|
|||
Heat: -0.33
|
||||
Shock: -0.33
|
||||
Cold: -0.33
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -10
|
||||
conditions:
|
||||
- !type:ReagentCondition
|
||||
|
|
@ -723,7 +724,8 @@
|
|||
metabolisms:
|
||||
Bloodstream:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 6
|
||||
- !type:ModifyBloodLevel
|
||||
amount: 6
|
||||
|
|
@ -861,7 +863,8 @@
|
|||
Bloodstream:
|
||||
effects:
|
||||
# what the hell, this isn't satiating at all!!
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: -1
|
||||
|
||||
# Should heal quite literally everything, use in very small amounts
|
||||
|
|
@ -1155,7 +1158,8 @@
|
|||
damage:
|
||||
types:
|
||||
Blunt: -2.25 # 9 per u
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: -1
|
||||
- !type:HealthChange
|
||||
conditions:
|
||||
|
|
@ -1178,7 +1182,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 3
|
||||
- !type:HealthChange
|
||||
conditions:
|
||||
|
|
|
|||
|
|
@ -202,9 +202,11 @@
|
|||
Asphyxiation: -2
|
||||
# Side effects
|
||||
- !type:ResetNarcolepsy
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 1
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
|
||||
- type: reagent
|
||||
|
|
|
|||
|
|
@ -167,7 +167,8 @@
|
|||
metabolisms:
|
||||
Bloodstream:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
|
|
@ -209,7 +210,8 @@
|
|||
metabolisms:
|
||||
Bloodstream:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: 1
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
|
|
|
|||
|
|
@ -178,7 +178,8 @@
|
|||
metabolisms:
|
||||
Bloodstream:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:Satiate
|
||||
satiationType: Thirst
|
||||
factor: -1.5
|
||||
- !type:HealthChange
|
||||
ignoreResistances: true
|
||||
|
|
@ -696,12 +697,14 @@
|
|||
effects:
|
||||
- !type:HealthChange
|
||||
conditions:
|
||||
- !type:HungerCondition
|
||||
- !type:SatiationCondition
|
||||
satiationType: Hunger
|
||||
max: 50
|
||||
damage:
|
||||
types:
|
||||
Poison: 2
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: -6
|
||||
|
||||
# inspired by the spider neurotoxin GsMtx-4
|
||||
|
|
@ -757,7 +760,8 @@
|
|||
metabolisms:
|
||||
Digestion:
|
||||
effects:
|
||||
- !type:SatiateHunger
|
||||
- !type:Satiate
|
||||
satiationType: Hunger
|
||||
factor: 1
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue