feat: properly perform predicted porta pottys (fix toilet prediction) (#39394)

This commit is contained in:
Perry Fraser
2025-08-05 21:34:41 -04:00
committed by GitHub
parent 63b2979e73
commit 053c5f64a0
5 changed files with 146 additions and 147 deletions

View File

@@ -1,8 +0,0 @@
using Content.Shared.Toilet.Systems;
namespace Content.Server.Toilet;
public sealed class ToiletSystem : SharedToiletSystem
{
}

View File

@@ -2,39 +2,36 @@ using Robust.Shared.Audio;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Toilet.Components namespace Content.Shared.Toilet.Components;
/// <summary>
/// Seats that can toggled up and down with visuals to match.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ToiletComponent : Component
{ {
/// <summary>
/// Toilets that can be flushed, seats toggled up and down, items hidden in cistern.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ToiletComponent : Component
{
/// <summary> /// <summary>
/// Toggles seat state. /// Toggles seat state.
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public bool ToggleSeat; public bool ToggleSeat;
/// <summary> /// <summary>
/// Sound to play when toggling toilet seat. /// Sound to play when toggling toilet seat.
/// </summary> /// </summary>
[DataField] [DataField]
public SoundSpecifier SeatSound = new SoundPathSpecifier("/Audio/Effects/toilet_seat_down.ogg"); public SoundSpecifier SeatSound = new SoundPathSpecifier("/Audio/Effects/toilet_seat_down.ogg");
}
[Serializable, NetSerializable]
public enum ToiletVisuals : byte
{
SeatVisualState,
}
[Serializable, NetSerializable]
public enum SeatVisualState : byte
{
SeatUp,
SeatDown
}
} }
[Serializable, NetSerializable]
public enum ToiletVisuals : byte
{
SeatVisualState,
}
[Serializable, NetSerializable]
public enum SeatVisualState : byte
{
SeatUp,
SeatDown,
}

View File

@@ -1,109 +0,0 @@
using Content.Shared.Buckle.Components;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
using Content.Shared.Plunger.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Shared.Toilet.Components;
namespace Content.Shared.Toilet.Systems
{
/// <summary>
/// Handles sprite changes for both toilet seat up and down as well as for lid open and closed. Handles interactions with hidden stash
/// </summary>
public abstract class SharedToiletSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToiletComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ToiletComponent, GetVerbsEvent<AlternativeVerb>>(OnToggleSeatVerb);
SubscribeLocalEvent<ToiletComponent, ActivateInWorldEvent>(OnActivateInWorld);
}
private void OnMapInit(EntityUid uid, ToiletComponent component, MapInitEvent args)
{
if (_random.Prob(0.5f))
component.ToggleSeat = true;
if (_random.Prob(0.3f))
{
TryComp<PlungerUseComponent>(uid, out var plunger);
if (plunger == null)
return;
plunger.NeedsPlunger = true;
}
UpdateAppearance(uid);
Dirty(uid, component);
}
public bool CanToggle(EntityUid uid)
{
return TryComp<StrapComponent>(uid, out var strap) && strap.BuckledEntities.Count == 0;
}
private void OnToggleSeatVerb(EntityUid uid, ToiletComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess || !CanToggle(uid) || args.Hands == null)
return;
AlternativeVerb toggleVerb = new()
{
Act = () => ToggleToiletSeat(uid, args.User, component)
};
if (component.ToggleSeat)
{
toggleVerb.Text = Loc.GetString("toilet-seat-close");
toggleVerb.Icon =
new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/close.svg.192dpi.png"));
}
else
{
toggleVerb.Text = Loc.GetString("toilet-seat-open");
toggleVerb.Icon =
new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/open.svg.192dpi.png"));
}
args.Verbs.Add(toggleVerb);
}
private void OnActivateInWorld(EntityUid uid, ToiletComponent comp, ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex)
return;
args.Handled = true;
ToggleToiletSeat(uid, args.User, comp);
}
public void ToggleToiletSeat(EntityUid uid, EntityUid? user = null, ToiletComponent? component = null, MetaDataComponent? meta = null)
{
if (!Resolve(uid, ref component))
return;
component.ToggleSeat = !component.ToggleSeat;
_audio.PlayPredicted(component.SeatSound, uid, uid);
UpdateAppearance(uid, component);
Dirty(uid, component, meta);
}
private void UpdateAppearance(EntityUid uid, ToiletComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
_appearance.SetData(uid, ToiletVisuals.SeatVisualState, component.ToggleSeat ? SeatVisualState.SeatUp : SeatVisualState.SeatDown);
}
}
}

View File

@@ -0,0 +1,116 @@
using Content.Shared.Buckle.Components;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
using Content.Shared.Plunger.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Shared.Toilet.Components;
namespace Content.Shared.Toilet.Systems;
/// <summary>
/// Handles sprite changes for both toilet seat up and down as well as for lid
/// open and closed.
/// </summary>
public sealed class ToiletSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToiletComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ToiletComponent, GetVerbsEvent<AlternativeVerb>>(OnToggleSeatVerb);
SubscribeLocalEvent<ToiletComponent, ActivateInWorldEvent>(OnActivateInWorld);
}
private void OnMapInit(Entity<ToiletComponent> ent, ref MapInitEvent args)
{
if (_random.Prob(0.5f))
{
ent.Comp.ToggleSeat = true;
Dirty(ent);
}
if (_random.Prob(0.3f)
&& TryComp<PlungerUseComponent>(ent, out var plunger))
{
plunger.NeedsPlunger = true;
Dirty(ent, plunger);
}
UpdateAppearance(ent);
}
private void OnToggleSeatVerb(Entity<ToiletComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess || args.Hands == null || !CanToggle(ent))
return;
var user = args.User;
AlternativeVerb toggleVerb = new() { Act = () => ToggleToiletSeat(ent.AsNullable(), user) };
if (ent.Comp.ToggleSeat)
{
toggleVerb.Text = Loc.GetString("toilet-seat-close");
toggleVerb.Icon =
new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/close.svg.192dpi.png"));
}
else
{
toggleVerb.Text = Loc.GetString("toilet-seat-open");
toggleVerb.Icon =
new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/open.svg.192dpi.png"));
}
args.Verbs.Add(toggleVerb);
}
private void OnActivateInWorld(Entity<ToiletComponent> ent, ref ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex)
return;
args.Handled = true;
ToggleToiletSeat(ent.AsNullable(), args.User);
}
private void UpdateAppearance(Entity<ToiletComponent> ent)
{
_appearance.SetData(ent,
ToiletVisuals.SeatVisualState,
ent.Comp.ToggleSeat ? SeatVisualState.SeatUp : SeatVisualState.SeatDown);
}
/// <summary>
/// Toggles a toilet's seat. Yup. Doesn't check if anyone is on the seat.
/// </summary>
/// <param name="ent">The toilet being seat-toggled.</param>
/// <param name="user">The user doing the toggling; used for predicted audio.</param>
/// <seealso cref="CanToggle" />
public void ToggleToiletSeat(Entity<ToiletComponent?> ent, EntityUid? user = null)
{
if (!Resolve(ent, ref ent.Comp))
return;
ent.Comp.ToggleSeat = !ent.Comp.ToggleSeat;
_audio.PlayPredicted(ent.Comp.SeatSound, ent, user);
UpdateAppearance((ent, ent.Comp));
Dirty(ent);
}
/// <summary>
/// Whether or not a toilet seat can be toggled without phasing through
/// someone's back. (That is, no one is seated on it.)
/// </summary>
/// <seealso cref="ToggleToiletSeat" />
public bool CanToggle(EntityUid uid)
{
return TryComp<StrapComponent>(uid, out var strap) && strap.BuckledEntities.Count == 0;
}
}

View File

@@ -75,6 +75,9 @@
autoDrain: false autoDrain: false
- type: StaticPrice - type: StaticPrice
price: 100 price: 100
- type: ActivatableUI
key: enum.DisposalUnitUiKey.Key
verbOnly: true # Not strictly needed, but we want E to toggle lid
- type: UserInterface - type: UserInterface
interfaces: interfaces:
enum.DisposalUnitUiKey.Key: enum.DisposalUnitUiKey.Key: