* Fix eating and drinking verbs showing up after a short delay and making your verb UI bounce
* Usings fix
* Usings fix
* Usings fix
* Usings fix
* CVar fix
* Predicted ppups
* Openable predicted popup
* Fix audio prediction
using Content.Client.Eui;
-using Content.Server.Ghost.Roles.Raffles;
using Content.Shared.Eui;
using Content.Shared.Ghost.Roles;
+using Content.Shared.Ghost.Roles.Raffles;
using JetBrains.Annotations;
using Robust.Client.Console;
using Robust.Client.Player;
-using System.Linq;
-using System.Numerics;
-using Content.Server.Ghost.Roles.Raffles;
+using System.Numerics;
using Content.Shared.Ghost.Roles.Raffles;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
-using Content.Server.Actions;
using Content.Server.Bed.Components;
-using Content.Server.Body.Systems;
using Content.Server.Power.EntitySystems;
using Content.Shared.Bed;
using Content.Shared.Bed.Components;
using Content.Shared.Bed.Sleep;
using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
using Content.Shared.Buckle.Components;
using Content.Shared.Damage;
using Content.Shared.Emag.Systems;
using Content.Server.Body.Components;
-using Content.Shared.EntityEffects.Effects;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Alert;
+using Content.Shared.Body.Events;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Drunk;
+using Content.Shared.EntityEffects.Effects;
using Content.Shared.FixedPoint;
using Content.Shared.Forensics;
using Content.Shared.Forensics.Components;
+using System.Numerics;
using Content.Server.Body.Components;
using Content.Server.Ghost;
using Content.Server.Humanoid;
using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
using Content.Shared.Body.Part;
using Content.Shared.Body.Systems;
+using Content.Shared.Damage.Components;
using Content.Shared.Humanoid;
using Content.Shared.Mind;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Timing;
-using System.Numerics;
-using Content.Shared.Damage.Components;
namespace Content.Server.Body.Systems;
using Content.Server.Body.Components;
-using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Administration.Logs;
+using Content.Shared.Body.Events;
using Content.Shared.Body.Organ;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
_solutionContainerSystem.UpdateChemicals(soln.Value);
}
}
-
- // TODO REFACTOR THIS
- // This will cause rates to slowly drift over time due to floating point errors.
- // Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
- [ByRefEvent]
- public readonly record struct ApplyMetabolicMultiplierEvent(
- EntityUid Uid,
- float Multiplier,
- bool Apply)
- {
- /// <summary>
- /// The entity whose metabolism is being modified.
- /// </summary>
- public readonly EntityUid Uid = Uid;
-
- /// <summary>
- /// What the metabolism's update rate will be multiplied by.
- /// </summary>
- public readonly float Multiplier = Multiplier;
-
- /// <summary>
- /// If true, apply the multiplier. If false, revert it.
- /// </summary>
- public readonly bool Apply = Apply;
- }
}
using Content.Server.Body.Components;
using Content.Server.Chat.Systems;
using Content.Server.EntityEffects;
-using Content.Shared.EntityEffects.EffectConditions;
-using Content.Shared.EntityEffects.Effects;
-using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Alert;
using Content.Shared.Atmos;
using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.EffectConditions;
+using Content.Shared.EntityEffects.Effects;
using Content.Shared.Mobs.Systems;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using System.Numerics;
using Content.Server.Administration.Logs;
using Content.Server.Decals;
-using Content.Server.Nutrition.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Crayon;
using Content.Shared.Database;
using Content.Shared.Decals;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
+using Content.Shared.Nutrition.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.GameStates;
-using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Crayon;
using Content.Server.Body.Systems;
using Content.Server.Kitchen.Components;
-using Content.Server.Nutrition.EntitySystems;
-using Content.Shared.Body.Components;
using Content.Shared.Administration.Logs;
+using Content.Shared.Body.Components;
using Content.Shared.Database;
-using Content.Shared.Interaction;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Popups;
-using Content.Shared.Storage;
-using Content.Shared.Verbs;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
-using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
using Content.Shared.Kitchen;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Popups;
+using Content.Shared.Storage;
+using Content.Shared.Verbs;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Shared.Random;
using Content.Server.Forensics;
using Content.Server.Popups;
using Content.Server.Stunnable;
-using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition.Components;
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.EntitySystems;
namespace Content.Server.Nutrition.Components;
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.EntitySystems;
namespace Content.Server.Nutrition.Components;
-using Content.Server.Body.Components;
using Content.Server.Body.Systems;
-using Content.Shared.EntityEffects.Effects;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.Inventory;
using Content.Server.Popups;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.DoAfter;
-using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
-using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
// run after openable so its always open -> drink
SubscribeLocalEvent<DrinkComponent, UseInHandEvent>(OnUse, before: [typeof(ServerInventorySystem)], after: [typeof(OpenableSystem)]);
SubscribeLocalEvent<DrinkComponent, AfterInteractEvent>(AfterInteract);
- SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
SubscribeLocalEvent<DrinkComponent, ConsumeDoAfterEvent>(OnDoAfter);
}
_appearance.SetData(uid, FoodVisuals.Visual, drainAvailable.Float(), appearance);
}
- /// <summary>
- /// Tries to feed the drink item to the target entity
- /// </summary>
- private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
- {
- if (!HasComp<BodyComponent>(target))
- return false;
-
- if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(target, out var stomachs))
- return false;
-
- if (_openable.IsClosed(item, user))
- return true;
-
- if (!_solutionContainer.TryGetSolution(item, drink.Solution, out _, out var drinkSolution) || drinkSolution.Volume <= 0)
- {
- if (drink.IgnoreEmpty)
- return false;
-
- _popup.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", item)), item, user);
- return true;
- }
-
- if (_food.IsMouthBlocked(target, user))
- return true;
-
- if (!_interaction.InRangeUnobstructed(user, item, popup: true))
- return true;
-
- var forceDrink = user != target;
-
- if (forceDrink)
- {
- var userName = Identity.Entity(user, EntityManager);
-
- _popup.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), user, target);
-
- // logging
- _adminLogger.Add(LogType.ForceFeed, LogImpact.High, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
- }
- else
- {
- // log voluntary drinking
- _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
- }
-
- var flavors = _flavorProfile.GetLocalizedFlavorsMessage(user, drinkSolution);
-
- var doAfterEventArgs = new DoAfterArgs(EntityManager,
- user,
- forceDrink ? drink.ForceFeedDelay : drink.Delay,
- new ConsumeDoAfterEvent(drink.Solution, flavors),
- eventTarget: item,
- target: target,
- used: item)
- {
- BreakOnHandChange = false,
- BreakOnMove = forceDrink,
- BreakOnDamage = true,
- MovementThreshold = 0.01f,
- DistanceThreshold = 1.0f,
- // do-after will stop if item is dropped when trying to feed someone else
- // or if the item started out in the user's own hands
- NeedHand = forceDrink || _hands.IsHolding(user, item),
- };
-
- _doAfter.TryStartDoAfter(doAfterEventArgs);
- return true;
- }
-
/// <summary>
/// Raised directed at a victim when someone has force fed them a drink.
/// </summary>
if (args.Used is null || !_solutionContainer.TryGetSolution(args.Used.Value, args.Solution, out var soln, out var solution))
return;
- if (_openable.IsClosed(args.Used.Value, args.Target.Value))
+ if (_openable.IsClosed(args.Used.Value, args.Target.Value, predicted: true))
return;
// TODO this should really be checked every tick.
if (!forceDrink && solution.Volume > 0)
args.Repeat = true;
}
-
- private void AddDrinkVerb(Entity<DrinkComponent> entity, ref GetVerbsEvent<AlternativeVerb> ev)
- {
- if (entity.Owner == ev.User ||
- !ev.CanInteract ||
- !ev.CanAccess ||
- !TryComp<BodyComponent>(ev.User, out var body) ||
- !_body.TryGetBodyOrganEntityComps<StomachComponent>((ev.User, body), out var stomachs))
- return;
-
- // Make sure the solution exists
- if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solution))
- return;
-
- // no drinking from living drinks, have to kill them first.
- if (_mobState.IsAlive(entity))
- return;
-
- var user = ev.User;
- AlternativeVerb verb = new()
- {
- Act = () =>
- {
- TryDrink(user, user, entity.Comp, entity);
- },
- Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
- Text = Loc.GetString("drink-system-verb-drink"),
- Priority = 2
- };
-
- ev.Verbs.Add(verb);
- }
}
-using Content.Server.Atmos;
-using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Nutrition.Components;
using Content.Server.Popups;
+using Content.Shared.Atmos;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
-using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Nutrition;
-using System.Threading;
-using Content.Shared.Atmos;
+using Content.Shared.Nutrition.EntitySystems;
/// <summary>
/// System for vapes
+++ /dev/null
-using Content.Shared.Containers.ItemSlots;
-using Content.Server.Nutrition.Components;
-using Content.Server.Popups;
-using Content.Shared.Interaction;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Content.Shared.Tools.EntitySystems;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Random;
-
-namespace Content.Server.Nutrition.EntitySystems
-{
- /// <summary>
- /// Handles usage of the utensils on the food items
- /// </summary>
- internal sealed class UtensilSystem : SharedUtensilSystem
- {
- [Dependency] private readonly IRobustRandom _robustRandom = default!;
- [Dependency] private readonly FoodSystem _foodSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
- }
-
- /// <summary>
- /// Clicked with utensil
- /// </summary>
- private void OnAfterInteract(Entity<UtensilComponent> entity, ref AfterInteractEvent ev)
- {
- if (ev.Handled || ev.Target == null || !ev.CanReach)
- return;
-
- var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
- ev.Handled = result.Handled;
- }
-
- public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
- {
- if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
- return (false, false);
-
- //Prevents food usage with a wrong utensil
- if ((food.Utensil & utensil.Comp.Types) == 0)
- {
- _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
- return (false, true);
- }
-
- if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
- return (false, true);
-
- return _foodSystem.TryFeed(user, user, target, food);
- }
-
- /// <summary>
- /// Attempt to break the utensil after interaction.
- /// </summary>
- /// <param name="uid">Utensil.</param>
- /// <param name="userUid">User of the utensil.</param>
- public void TryBreak(EntityUid uid, EntityUid userUid, UtensilComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- if (_robustRandom.Prob(component.BreakChance))
- {
- _audio.PlayPvs(component.BreakSound, userUid, AudioParams.Default.WithVolume(-2f));
- EntityManager.DeleteEntity(uid);
- }
- }
- }
-}
-using Content.Server.Body.Systems;
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Server.Body.Components
+namespace Content.Shared.Body.Components
{
- [RegisterComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
+ [RegisterComponent, NetworkedComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
public sealed partial class StomachComponent : Component
{
/// <summary>
/// What solution should this stomach push reagents into, on the body?
/// </summary>
[DataField]
- public string BodySolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
+ public string BodySolutionName = "chemicals";
/// <summary>
/// Time between reagents being ingested and them being
--- /dev/null
+namespace Content.Shared.Body.Events;
+
+// TODO REFACTOR THIS
+// This will cause rates to slowly drift over time due to floating point errors.
+// Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
+[ByRefEvent]
+public readonly record struct ApplyMetabolicMultiplierEvent(
+ EntityUid Uid,
+ float Multiplier,
+ bool Apply)
+{
+ /// <summary>
+ /// The entity whose metabolism is being modified.
+ /// </summary>
+ public readonly EntityUid Uid = Uid;
+
+ /// <summary>
+ /// What the metabolism's update rate will be multiplied by.
+ /// </summary>
+ public readonly float Multiplier = Multiplier;
+
+ /// <summary>
+ /// If true, apply the multiplier. If false, revert it.
+ /// </summary>
+ public readonly bool Apply = Apply;
+}
-using Content.Server.Body.Components;
-using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Events;
using Content.Shared.Body.Organ;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
-namespace Content.Server.Body.Systems
+namespace Content.Shared.Body.Systems
{
public sealed class StomachSystem : EntitySystem
{
using Content.Shared.ActionBlocker;
-using Content.Shared.Burial;
using Content.Shared.Burial.Components;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Audio.Systems;
-namespace Content.Server.Burial.Systems;
+namespace Content.Shared.Burial;
public sealed class BurialSystem : EntitySystem
{
/// some food object won't spam a user with flavors.
/// </summary>
public static readonly CVarDef<int>
- FlavorLimit = CVarDef.Create("flavor.limit", 10, CVar.SERVERONLY);
+ FlavorLimit = CVarDef.Create("flavor.limit", 10, CVar.SERVER | CVar.REPLICATED);
public static readonly CVarDef<string> DestinationFile =
CVarDef.Create("autogen.destination_file", "", CVar.SERVER | CVar.SERVERONLY);
using Content.Shared.Body.Components;
-using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
-namespace Content.Server.EntityEffects.EffectConditions;
+namespace Content.Shared.EntityEffects.EffectConditions;
/// <summary>
/// Condition for if the entity is or isn't wearing internals.
-namespace Content.Server.Ghost.Roles.Raffles;
+namespace Content.Shared.Ghost.Roles.Raffles;
/// <summary>
/// Defines settings for a ghost role raffle.
-using Content.Server.Ghost.Roles.Raffles;
-using Robust.Shared.Prototypes;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Ghost.Roles.Raffles;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
-namespace Content.Server.Light.Components;
+namespace Content.Shared.Light.Components;
/// <summary>
/// Device that allows user to quikly change bulbs in <see cref="PoweredLightComponent"/>
-namespace Content.Server.Medical.Components;
+namespace Content.Shared.Medical.Cryogenics;
/// <summary>
/// Tracking component for an enabled cryo pod (which periodically tries to inject chemicals in the occupant, if one exists)
-using Content.Server.Medical.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
using Content.Shared.Database;
-namespace Content.Server.Nutrition.Components;
+using Robust.Shared.GameStates;
-[RegisterComponent]
+namespace Content.Shared.Nutrition.Components;
+
+[RegisterComponent, NetworkedComponent]
public sealed partial class FlavorProfileComponent : Component
{
/// <summary>
/// Localized string containing the base flavor of this entity.
/// </summary>
- [DataField("flavors")]
+ [DataField]
public HashSet<string> Flavors { get; private set; } = new();
/// <summary>
/// Reagent IDs to ignore when processing this flavor profile. Defaults to nutriment.
/// </summary>
- [DataField("ignoreReagents")]
+ [DataField]
public HashSet<string> IgnoreReagents { get; private set; } = new()
{
"Nutriment",
"Vitamin",
- "Protein"
+ "Protein",
};
}
-using Content.Server.Body.Components;
-using Content.Shared.Nutrition.Components;
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Body.Components;
using Content.Shared.FixedPoint;
+using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
-namespace Content.Server.Nutrition.Components;
+namespace Content.Shared.Nutrition.Components;
[RegisterComponent, Access(typeof(FoodSystem), typeof(FoodSequenceSystem))]
public sealed partial class FoodComponent : Component
-using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.EntitySystems;
-namespace Content.Server.Nutrition.Components;
+namespace Content.Shared.Nutrition.Components;
/// <summary>
/// Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking.
/// In the event that more head-wear & mask functionality is added (like identity systems, or raising/lowering of
/// masks), then this component might become redundant.
/// </remarks>
-[RegisterComponent, Access(typeof(FoodSystem), typeof(DrinkSystem), typeof(IngestionBlockerSystem))]
+[RegisterComponent, Access(typeof(FoodSystem), typeof(SharedDrinkSystem), typeof(IngestionBlockerSystem))]
public sealed partial class IngestionBlockerComponent : Component
{
/// <summary>
namespace Content.Shared.Nutrition.Components
{
- [RegisterComponent, NetworkedComponent, Access(typeof(SharedUtensilSystem))]
+ [RegisterComponent, NetworkedComponent, Access(typeof(UtensilSystem))]
public sealed partial class UtensilComponent : Component
{
[DataField("types")]
-using Content.Server.Nutrition.Components;
+using System.Linq;
using Content.Shared.CCVar;
using Content.Shared.Chemistry.Components;
-using Content.Shared.Nutrition;
+using Content.Shared.Nutrition.Components;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
-using System.Linq;
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
/// <summary>
/// Deals with flavor profiles when you eat something.
using System.Numerics;
using System.Text;
-using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Interaction;
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 Content.Shared.Popups;
using Content.Shared.Storage.Components;
using Content.Shared.Tag;
-using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
{
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize()
{
if (start.Comp.FoodLayers.Count >= start.Comp.MaxLayers && !elementIndexed.Final || start.Comp.Finished)
{
if (user is not null)
- _popup.PopupEntity(Loc.GetString("food-sequence-no-space"), start, user.Value);
+ _popup.PopupClient(Loc.GetString("food-sequence-no-space"), start, user.Value);
return false;
}
-using Content.Server.Body.Components;
-using Content.Server.Body.Systems;
-using Content.Shared.Chemistry.EntitySystems;
-using Content.Server.Inventory;
-using Content.Server.Nutrition.Components;
-using Content.Shared.Nutrition.Components;
-using Content.Server.Popups;
-using Content.Server.Stack;
+using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
using Content.Shared.Body.Organ;
+using Content.Shared.Body.Systems;
using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
+using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Mobs.Systems;
-using Content.Shared.Nutrition;
-using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Popups;
using Content.Shared.Stacks;
using Content.Shared.Storage;
using Content.Shared.Verbs;
+using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Utility;
-using System.Linq;
-using Content.Shared.Containers.ItemSlots;
-using Robust.Server.GameObjects;
-using Content.Shared.Whitelist;
-using Content.Shared.Destructible;
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
/// <summary>
/// Handles feeding attempts both on yourself and on the target.
/// </summary>
public sealed class FoodSystem : EntitySystem
{
- [Dependency] private readonly BodySystem _body = default!;
+ [Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly ReactiveSystem _reaction = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
- [Dependency] private readonly StackSystem _stack = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedStackSystem _stack = default!;
[Dependency] private readonly StomachSystem _stomach = default!;
[Dependency] private readonly UtensilSystem _utensil = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
// TODO add InteractNoHandEvent for entities like mice.
// run after openable for wrapped/peelable foods
- SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand, after: new[] { typeof(OpenableSystem), typeof(ServerInventorySystem) });
+ SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand, after: new[] { typeof(OpenableSystem), typeof(InventorySystem) });
SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
SubscribeLocalEvent<FoodComponent, ConsumeDoAfterEvent>(OnDoAfter);
if (HasComp<UnremoveableComponent>(food))
return (false, false);
- if (_openable.IsClosed(food, user))
+ if (_openable.IsClosed(food, user, predicted: true))
return (false, true);
if (!_solutionContainer.TryGetSolution(food, foodComp.Solution, out _, out var foodSolution))
// Check for used storage on the food item
if (TryComp<StorageComponent>(food, out var storageState) && storageState.Container.ContainedEntities.Any())
{
- _popup.PopupEntity(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
+ _popup.PopupClient(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
return (false, true);
}
{
if (itemSlots.Slots.Any(slot => slot.Value.HasItem))
{
- _popup.PopupEntity(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
+ _popup.PopupClient(Loc.GetString("food-has-used-storage", ("food", food)), user, user);
return (false, true);
}
}
if (GetUsesRemaining(food, foodComp) <= 0)
{
- _popup.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
+ _popup.PopupClient(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user);
DeleteAndSpawnTrash(foodComp, food, user);
return (false, true);
}
if (!_transform.GetMapCoordinates(user).InRange(_transform.GetMapCoordinates(target), MaxFeedDistance))
{
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
- _popup.PopupEntity(message, user, user);
+ _popup.PopupClient(message, user, user);
return (false, true);
}
if (stomachToUse == null)
{
_solutionContainer.TryAddSolution(soln.Value, split);
- _popup.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other", ("target", args.Target.Value)) : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Target.Value, args.User);
+ _popup.PopupClient(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other", ("target", args.Target.Value)) : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Target.Value, args.User);
return;
}
var userName = Identity.Entity(args.User, EntityManager);
_popup.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName), ("flavors", flavors)), entity.Owner, entity.Owner);
- _popup.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.User, args.User);
+ _popup.PopupClient(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.User, args.User);
// log successful force feed
_adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity.Owner):user} forced {ToPrettyString(args.User):target} to eat {ToPrettyString(entity.Owner):food}");
}
else
{
- _popup.PopupEntity(Loc.GetString(entity.Comp.EatMessage, ("food", entity.Owner), ("flavors", flavors)), args.User, args.User);
+ _popup.PopupClient(Loc.GetString(entity.Comp.EatMessage, ("food", entity.Owner), ("flavors", flavors)), args.User, args.User);
// log successful voluntary eating
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(entity.Owner):food}");
}
- _audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-1f).WithVariation(0.20f));
+ _audio.PlayPredicted(entity.Comp.UseSound, args.Target.Value, args.User, AudioParams.Default.WithVolume(-1f).WithVariation(0.20f));
// Try to break all used utensils
foreach (var utensil in utensils)
// If "required" field is set, try to block eating without proper utensils used
if (component.UtensilRequired && (usedTypes & component.Utensil) != component.Utensil)
{
- _popup.PopupEntity(Loc.GetString("food-you-need-to-hold-utensil", ("utensil", component.Utensil ^ usedTypes)), user, user);
+ _popup.PopupClient(Loc.GetString("food-you-need-to-hold-utensil", ("utensil", component.Utensil ^ usedTypes)), user, user);
return false;
}
RaiseLocalEvent(uid, attempt, false);
if (attempt.Cancelled && attempt.Blocker != null && popupUid != null)
{
- _popup.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", attempt.Blocker.Value)),
+ _popup.PopupClient(Loc.GetString("food-system-remove-mask", ("entity", attempt.Blocker.Value)),
uid, popupUid.Value);
}
-using Content.Server.Nutrition.Components;
-using Content.Shared.Clothing;
+using Content.Shared.Clothing;
+using Content.Shared.Nutrition.Components;
-namespace Content.Server.Nutrition.EntitySystems;
+namespace Content.Shared.Nutrition.EntitySystems;
public sealed class IngestionBlockerSystem : EntitySystem
{
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Examine;
-using Content.Shared.Lock;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
+using Content.Shared.Lock;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Verbs;
/// Drinks that don't have OpenableComponent are automatically open, so it returns false.
/// If user is not null a popup will be shown to them.
/// </summary>
- public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null)
+ public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null, bool predicted = false)
{
if (!Resolve(uid, ref comp, false))
return false;
return false;
if (user != null)
- _popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
+ {
+ if (predicted)
+ _popup.PopupClient(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
+ else
+ _popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
+ }
return true;
}
+using Content.Shared.Administration.Logs;
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Robust.Shared.Utility;
namespace Content.Shared.Nutrition.EntitySystems;
public abstract partial class SharedDrinkSystem : EntitySystem
{
- [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly SharedBodySystem _body = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly FlavorProfileSystem _flavorProfile = default!;
+ [Dependency] private readonly FoodSystem _food = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly SharedInteractionSystem _interaction = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
public override void Initialize()
{
SubscribeLocalEvent<DrinkComponent, AttemptShakeEvent>(OnAttemptShake);
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
+ SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
}
protected void OnAttemptShake(Entity<DrinkComponent> entity, ref AttemptShakeEvent args)
protected void OnExamined(Entity<DrinkComponent> entity, ref ExaminedEvent args)
{
TryComp<OpenableComponent>(entity, out var openable);
- if (_openable.IsClosed(entity.Owner, null, openable) || !args.IsInDetailsRange || !entity.Comp.Examinable)
+ if (_openable.IsClosed(entity.Owner, null, openable, true) || !args.IsInDetailsRange || !entity.Comp.Examinable)
return;
var empty = IsEmpty(entity, entity.Comp);
}
}
+ private void AddDrinkVerb(Entity<DrinkComponent> entity, ref GetVerbsEvent<AlternativeVerb> ev)
+ {
+ if (entity.Owner == ev.User ||
+ !ev.CanInteract ||
+ !ev.CanAccess ||
+ !TryComp<BodyComponent>(ev.User, out var body) ||
+ !_body.TryGetBodyOrganEntityComps<StomachComponent>((ev.User, body), out var stomachs))
+ return;
+
+ // Make sure the solution exists
+ if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var solution))
+ return;
+
+ // no drinking from living drinks, have to kill them first.
+ if (_mobState.IsAlive(entity))
+ return;
+
+ var user = ev.User;
+ AlternativeVerb verb = new()
+ {
+ Act = () =>
+ {
+ TryDrink(user, user, entity.Comp, entity);
+ },
+ Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
+ Text = Loc.GetString("drink-system-verb-drink"),
+ Priority = 2
+ };
+
+ ev.Verbs.Add(verb);
+ }
+
protected FixedPoint2 DrinkVolume(EntityUid uid, DrinkComponent? component = null)
{
if (!Resolve(uid, ref component))
return remainingString;
}
+
+ /// <summary>
+ /// Tries to feed the drink item to the target entity
+ /// </summary>
+ protected bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
+ {
+ if (!HasComp<BodyComponent>(target))
+ return false;
+
+ if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(target, out var stomachs))
+ return false;
+
+ if (_openable.IsClosed(item, user, predicted: true))
+ return true;
+
+ if (!_solutionContainer.TryGetSolution(item, drink.Solution, out _, out var drinkSolution) || drinkSolution.Volume <= 0)
+ {
+ if (drink.IgnoreEmpty)
+ return false;
+
+ _popup.PopupClient(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", item)), item, user);
+ return true;
+ }
+
+ if (_food.IsMouthBlocked(target, user))
+ return true;
+
+ if (!_interaction.InRangeUnobstructed(user, item, popup: true))
+ return true;
+
+ var forceDrink = user != target;
+
+ if (forceDrink)
+ {
+ var userName = Identity.Entity(user, EntityManager);
+
+ _popup.PopupEntity(Loc.GetString("drink-component-force-feed", ("user", userName)), user, target);
+
+ // logging
+ _adminLogger.Add(LogType.ForceFeed, LogImpact.High, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
+ }
+ else
+ {
+ // log voluntary drinking
+ _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SharedSolutionContainerSystem.ToPrettyString(drinkSolution)}");
+ }
+
+ var flavors = _flavorProfile.GetLocalizedFlavorsMessage(user, drinkSolution);
+
+ var doAfterEventArgs = new DoAfterArgs(EntityManager,
+ user,
+ forceDrink ? drink.ForceFeedDelay : drink.Delay,
+ new ConsumeDoAfterEvent(drink.Solution, flavors),
+ eventTarget: item,
+ target: target,
+ used: item)
+ {
+ BreakOnHandChange = false,
+ BreakOnMove = forceDrink,
+ BreakOnDamage = true,
+ MovementThreshold = 0.01f,
+ DistanceThreshold = 1.0f,
+ // do-after will stop if item is dropped when trying to feed someone else
+ // or if the item started out in the user's own hands
+ NeedHand = forceDrink || _hands.IsHolding(user, item),
+ };
+
+ _doAfter.TryStartDoAfter(doAfterEventArgs);
+ return true;
+ }
}
+++ /dev/null
-namespace Content.Shared.Nutrition.EntitySystems;
-
-public abstract class SharedUtensilSystem : EntitySystem
-{
-}
--- /dev/null
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Interaction;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Popups;
+using Content.Shared.Tools.EntitySystems;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Random;
+
+namespace Content.Shared.Nutrition.EntitySystems;
+
+public sealed class UtensilSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly FoodSystem _foodSystem = default!;
+ [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly IRobustRandom _robustRandom = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
+ }
+
+ /// <summary>
+ /// Clicked with utensil
+ /// </summary>
+ private void OnAfterInteract(Entity<UtensilComponent> entity, ref AfterInteractEvent ev)
+ {
+ if (ev.Handled || ev.Target == null || !ev.CanReach)
+ return;
+
+ var result = TryUseUtensil(ev.User, ev.Target.Value, entity);
+ ev.Handled = result.Handled;
+ }
+
+ public (bool Success, bool Handled) TryUseUtensil(EntityUid user, EntityUid target, Entity<UtensilComponent> utensil)
+ {
+ if (!EntityManager.TryGetComponent(target, out FoodComponent? food))
+ return (false, false);
+
+ //Prevents food usage with a wrong utensil
+ if ((food.Utensil & utensil.Comp.Types) == 0)
+ {
+ _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", utensil.Owner)), user, user);
+ return (false, true);
+ }
+
+ if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true))
+ return (false, true);
+
+ return _foodSystem.TryFeed(user, user, target, food);
+ }
+
+ /// <summary>
+ /// Attempt to break the utensil after interaction.
+ /// </summary>
+ /// <param name="uid">Utensil.</param>
+ /// <param name="userUid">User of the utensil.</param>
+ public void TryBreak(EntityUid uid, EntityUid userUid, UtensilComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (_robustRandom.Prob(component.BreakChance))
+ {
+ _audio.PlayPredicted(component.BreakSound, userUid, userUid, AudioParams.Default.WithVolume(-2f));
+ EntityManager.DeleteEntity(uid);
+ }
+ }
+}
using Content.Shared.Inventory;
-namespace Content.Server.Storage.Components;
+namespace Content.Shared.Storage.Components;
/// <summary>
/// Applies an ongoing pickup area around the attached entity.
-using Content.Server.Storage.Components;
using Content.Shared.Inventory;
+using Content.Shared.Storage.Components;
using Content.Shared.Whitelist;
-using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;