Add ability to shake fizzy drinks so they spray in peoples' faces (#25574)
* Implemented Shakeable * Prevent shaking open Openables * Prevent shaking empty drinks. Moved part of DrinkSystem to Shared. * DrinkSystem can have a little more prediction, as a treat * Cleanup * Overhauled PressurizedDrink * Make soda cans/bottles and champagne shakeable. The drink shaker too, for fun. * We do a little refactoring. PressurizedDrink is now PressurizedSolution, and fizziness now only works on solutions containing a reagent marked as fizzy. * Documentation, cleanup, and tweaks. * Changed fizziness calculation to use a cubic-out easing curve. * Removed broken YAML that has avoid the linter's wrath for far too long * Changed reagent fizzy bool to fizziness float. Solution fizzability now scales with reagent proportion. * Rename file to match changed class name * DoAfter improvements. Cancel if the user moves away; block if no hands. * Match these filenames too * And this one * guh * Updated to use Shared puddle methods * Various fixes and improvements. * Made AttemptShakeEvent a struct * AttemptAddFizzinessEvent too
This commit is contained in:
155
Content.Shared/Nutrition/EntitySystems/ShakeableSystem.cs
Normal file
155
Content.Shared/Nutrition/EntitySystems/ShakeableSystem.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
public sealed partial class ShakeableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ShakeableComponent, GetVerbsEvent<Verb>>(AddShakeVerb);
|
||||
SubscribeLocalEvent<ShakeableComponent, ShakeDoAfterEvent>(OnShakeDoAfter);
|
||||
}
|
||||
|
||||
private void AddShakeVerb(EntityUid uid, ShakeableComponent component, GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (!CanShake((uid, component), args.User))
|
||||
return;
|
||||
|
||||
var shakeVerb = new Verb()
|
||||
{
|
||||
Text = Loc.GetString(component.ShakeVerbText),
|
||||
Act = () => TryStartShake((args.Target, component), args.User)
|
||||
};
|
||||
args.Verbs.Add(shakeVerb);
|
||||
}
|
||||
|
||||
private void OnShakeDoAfter(Entity<ShakeableComponent> entity, ref ShakeDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
TryShake((entity, entity.Comp), args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to start the doAfter to shake the entity.
|
||||
/// Fails and returns false if the entity cannot be shaken for any reason.
|
||||
/// If successful, displays popup messages, plays shake sound, and starts the doAfter.
|
||||
/// </summary>
|
||||
public bool TryStartShake(Entity<ShakeableComponent?> entity, EntityUid user)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return false;
|
||||
|
||||
if (!CanShake(entity, user))
|
||||
return false;
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager,
|
||||
user,
|
||||
entity.Comp.ShakeDuration,
|
||||
new ShakeDoAfterEvent(),
|
||||
eventTarget: entity,
|
||||
target: user,
|
||||
used: entity)
|
||||
{
|
||||
NeedHand = true,
|
||||
BreakOnDamage = true,
|
||||
DistanceThreshold = 1,
|
||||
MovementThreshold = 0.01f,
|
||||
BreakOnHandChange = entity.Comp.RequireInHand,
|
||||
};
|
||||
if (entity.Comp.RequireInHand)
|
||||
doAfterArgs.BreakOnHandChange = true;
|
||||
|
||||
if (!_doAfter.TryStartDoAfter(doAfterArgs))
|
||||
return false;
|
||||
|
||||
var userName = Identity.Entity(user, EntityManager);
|
||||
var shakeableName = Identity.Entity(entity, EntityManager);
|
||||
|
||||
var selfMessage = Loc.GetString(entity.Comp.ShakePopupMessageSelf, ("user", userName), ("shakeable", shakeableName));
|
||||
var othersMessage = Loc.GetString(entity.Comp.ShakePopupMessageOthers, ("user", userName), ("shakeable", shakeableName));
|
||||
_popup.PopupPredicted(selfMessage, othersMessage, user, user);
|
||||
|
||||
_audio.PlayPredicted(entity.Comp.ShakeSound, entity, user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to shake the entity, skipping the doAfter.
|
||||
/// Fails and returns false if the entity cannot be shaken for any reason.
|
||||
/// If successful, raises a ShakeEvent on the entity.
|
||||
/// </summary>
|
||||
public bool TryShake(Entity<ShakeableComponent?> entity, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return false;
|
||||
|
||||
if (!CanShake(entity, user))
|
||||
return false;
|
||||
|
||||
var ev = new ShakeEvent(user);
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is it possible for the given user to shake the entity?
|
||||
/// </summary>
|
||||
public bool CanShake(Entity<ShakeableComponent?> entity, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return false;
|
||||
|
||||
// If required to be in hand, fail if the user is not holding this entity
|
||||
if (user != null && entity.Comp.RequireInHand && !_hands.IsHolding(user.Value, entity, out _))
|
||||
return false;
|
||||
|
||||
var attemptEv = new AttemptShakeEvent();
|
||||
RaiseLocalEvent(entity, ref attemptEv);
|
||||
if (attemptEv.Cancelled)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a ShakeableComponent is shaken, after the doAfter completes.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ShakeEvent(EntityUid? Shaker);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when trying to shake a ShakeableComponent. If cancelled, the
|
||||
/// entity will not be shaken.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct AttemptShakeEvent()
|
||||
{
|
||||
public bool Cancelled;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ShakeDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user