Fix eating and drinking verbs showing up after a short delay and making your verb UI bounce (#38164)
* 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
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
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;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
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;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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;
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Chemistry.Reagent;
|
||||
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;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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;
|
||||
@@ -11,8 +14,6 @@ using Content.Shared.Movement.Events;
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
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;
|
||||
@@ -231,29 +232,4 @@ namespace Content.Server.Body.Systems
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,19 @@ using Content.Server.Atmos.EntitySystems;
|
||||
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;
|
||||
|
||||
@@ -2,18 +2,17 @@ using System.Linq;
|
||||
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;
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
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;
|
||||
|
||||
@@ -4,8 +4,10 @@ using Content.Server.Fluids.EntitySystems;
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
namespace Content.Server.Nutrition.Components;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
namespace Content.Server.Nutrition.Components;
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
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;
|
||||
@@ -14,7 +13,7 @@ using Content.Shared.Chemistry.EntitySystems;
|
||||
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;
|
||||
@@ -24,7 +23,6 @@ using Content.Shared.Mobs.Systems;
|
||||
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;
|
||||
@@ -65,7 +63,6 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -157,76 +154,6 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
||||
_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>
|
||||
@@ -241,7 +168,7 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
||||
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.
|
||||
@@ -330,36 +257,4 @@ public sealed class DrinkSystem : SharedDrinkSystem
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
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
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
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>
|
||||
@@ -32,7 +33,7 @@ namespace Content.Server.Body.Components
|
||||
/// 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
|
||||
26
Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs
Normal file
26
Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
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
|
||||
{
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Burial;
|
||||
using Content.Shared.Burial.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -10,7 +9,7 @@ using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Burial.Systems;
|
||||
namespace Content.Shared.Burial;
|
||||
|
||||
public sealed class BurialSystem : EntitySystem
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed partial class CCVars
|
||||
/// 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);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Server.Ghost.Roles.Raffles;
|
||||
namespace Content.Shared.Ghost.Roles.Raffles;
|
||||
|
||||
/// <summary>
|
||||
/// Defines settings for a ghost role raffle.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Ghost.Roles.Raffles;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Ghost.Roles.Raffles;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Audio;
|
||||
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"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Database;
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
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",
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
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.
|
||||
@@ -9,7 +9,7 @@ namespace Content.Server.Nutrition.Components;
|
||||
/// 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>
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components
|
||||
{
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedUtensilSystem))]
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(UtensilSystem))]
|
||||
public sealed partial class UtensilComponent : Component
|
||||
{
|
||||
[DataField("types")]
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
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.
|
||||
@@ -1,21 +1,17 @@
|
||||
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
|
||||
{
|
||||
@@ -26,7 +22,7 @@ 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()
|
||||
{
|
||||
@@ -126,7 +122,7 @@ public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
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;
|
||||
@@ -21,42 +18,38 @@ using Content.Shared.Interaction.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!;
|
||||
@@ -69,7 +62,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
|
||||
// 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);
|
||||
@@ -116,7 +109,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
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))
|
||||
@@ -135,7 +128,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -144,7 +137,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +146,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -171,7 +164,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -268,7 +261,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -283,20 +276,20 @@ public sealed class FoodSystem : EntitySystem
|
||||
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)
|
||||
@@ -484,7 +477,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -533,7 +526,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
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;
|
||||
@@ -166,7 +166,7 @@ public sealed partial class OpenableSystem : EntitySystem
|
||||
/// 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;
|
||||
@@ -175,7 +175,12 @@ public sealed partial class OpenableSystem : EntitySystem
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
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()
|
||||
{
|
||||
@@ -17,6 +38,7 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<DrinkComponent, AttemptShakeEvent>(OnAttemptShake);
|
||||
SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
|
||||
}
|
||||
|
||||
protected void OnAttemptShake(Entity<DrinkComponent> entity, ref AttemptShakeEvent args)
|
||||
@@ -28,7 +50,7 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
||||
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);
|
||||
@@ -57,6 +79,38 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -87,4 +141,74 @@ public abstract partial class SharedDrinkSystem : EntitySystem
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
public abstract class SharedUtensilSystem : EntitySystem
|
||||
{
|
||||
}
|
||||
73
Content.Shared/Nutrition/EntitySystems/UtensilSystem.cs
Normal file
73
Content.Shared/Nutrition/EntitySystems/UtensilSystem.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Server.Storage.Components;
|
||||
namespace Content.Shared.Storage.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Applies an ongoing pickup area around the attached entity.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user