Files
tbd-station-14/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs
moneyl 3ab8036363 De-hardcode chemical metabolism of StomachComponent (#437)
* Move chemical reaction effects into Chemistry/ReactionEffects/ subfolder

* Replace hardcoded StomachComponent metabolism with IMetabolizable

The benefits of this approach are that reagent metabolism effects are not hardcoded into StomachComponent, and metabolism effects can be more easily chained together in yaml prototypes, and reagents can have different metabolism rates. One problem with this approach is that getting metabolism rates slower than 1u / second is impossible. Implementing #377 should resolve that problem.

* Fix DefaultFood and DefaultDrink so they remove reagent regardless of Hunger/ThirstComponent presence

Previously if neither of those were present the reagents wouldn't be removed from the stomach. This fixes that.

* Additional comment on function

* Make metabolizer interface implementations explicit

Also removed some unused using statements

* Make StomachComponent._reagentDeltas readonly

* Fix misleading variable names and docs for metabolizables

Changes one of the arguments for `IMetabolizable.Metabolize()` to be called `tickTime` instead of `frameTime` to more accurately reflect it's purpose. It's not really the frametime, but the time since the last metabolism tick. Also updated and expanded the docs to reflect this and to be more clear.
2019-11-21 23:24:19 +01:00

179 lines
5.9 KiB
C#

using System;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition;
using Content.Shared.Interfaces;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Nutrition
{
[RegisterComponent]
public class FoodComponent : Component, IAfterAttack, IUse
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
// Currently the design is similar to drinkcomponent but it's susceptible to change so left as is for now.
public override string Name => "Food";
private AppearanceComponent _appearanceComponent;
[ViewVariables]
private string _useSound;
[ViewVariables]
private string _finishPrototype;
[ViewVariables]
private SolutionComponent _contents;
[ViewVariables]
private int _transferAmount;
private Solution _initialContents; // This is just for loading from yaml
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
// Default is 1 use restoring 30
serializer.DataField(ref _initialContents, "contents", null);
serializer.DataField(ref _useSound, "use_sound", "/Audio/items/eatfood.ogg");
// Default is transfer 30 units
serializer.DataField(ref _transferAmount, "transfer_amount", 5);
// E.g. empty chip packet when done
serializer.DataField(ref _finishPrototype, "spawn_on_finish", null);
}
public override void Initialize()
{
base.Initialize();
if (_contents == null)
{
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
{
_contents = solutionComponent;
}
else
{
_contents = Owner.AddComponent<SolutionComponent>();
_contents.Initialize();
}
}
_contents.MaxVolume = _initialContents.TotalVolume;
}
protected override void Startup()
{
base.Startup();
if (_initialContents != null)
{
_contents.TryAddSolution(_initialContents, true, true);
}
_initialContents = null;
if (_contents.CurrentVolume == 0)
{
_contents.TryAddReagent("chem.Nutriment", 5, out _);
}
Owner.TryGetComponent(out AppearanceComponent appearance);
_appearanceComponent = appearance;
// UsesLeft() at the start should match the max, at least currently.
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.MaxUses, UsesLeft());
_updateAppearance();
}
private void _updateAppearance()
{
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.Visual, UsesLeft());
}
public int UsesLeft()
{
// In case transfer amount exceeds volume left
if (_contents.CurrentVolume == 0)
{
return 0;
}
return Math.Max(1, _contents.CurrentVolume / _transferAmount);
}
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
{
UseFood(eventArgs.User);
return true;
}
void IAfterAttack.AfterAttack(AfterAttackEventArgs eventArgs)
{
UseFood(eventArgs.Attacked);
}
void UseFood(IEntity user)
{
if (user == null)
{
return;
}
if (UsesLeft() == 0)
{
user.PopupMessage(user, _localizationManager.GetString("Empty"));
}
else
{
// TODO: Add putting food back in boxes here?
if (user.TryGetComponent(out StomachComponent stomachComponent))
{
var transferAmount = Math.Min(_transferAmount, _contents.CurrentVolume);
var split = _contents.SplitSolution(transferAmount);
if (stomachComponent.TryTransferSolution(split))
{
if (_useSound != null)
{
Owner.GetComponent<SoundComponent>()?.Play(_useSound);
user.PopupMessage(user, _localizationManager.GetString("Nom"));
}
}
else
{
// Add it back in
_contents.TryAddSolution(split);
user.PopupMessage(user, _localizationManager.GetString("Can't eat"));
}
}
}
if (UsesLeft() > 0)
{
return;
}
Owner.Delete();
if (_finishPrototype != null)
{
var finisher = Owner.EntityManager.SpawnEntity(_finishPrototype);
if (user.TryGetComponent(out HandsComponent handsComponent) && finisher.TryGetComponent(out ItemComponent itemComponent))
{
if (handsComponent.CanPutInHand(itemComponent))
{
handsComponent.PutInHand(itemComponent);
return;
}
}
finisher.Transform.GridPosition = user.Transform.GridPosition;
return;
}
}
}
}