private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
{
if (!_appearanceSystem.TryGetData<VisualState>(uid, Visuals.VisualState, out var state, appearance))
- {
return;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored);
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
- sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseFlush, state is VisualState.Flushing or VisualState.Charging);
+ sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFlush, state is VisualState.OverlayFlushing or VisualState.OverlayCharging);
var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
? sprite.LayerGetState(chargingLayer)
: new RSI.StateId(DefaultChargeState);
// This is a transient state so not too worried about replaying in range.
- if (state == VisualState.Flushing)
+ if (state == VisualState.OverlayFlushing)
{
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
{
- var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseFlush, out var flushLayer)
+ var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
? sprite.LayerGetState(flushLayer)
: new RSI.StateId(DefaultFlushState);
{
new AnimationTrackSpriteFlick
{
- LayerKey = DisposalUnitVisualLayers.BaseFlush,
+ LayerKey = DisposalUnitVisualLayers.OverlayFlush,
KeyFrames =
{
// Play the flush animation
_animationSystem.Play(uid, anim, AnimationKey);
}
}
- else if (state == VisualState.Charging)
- {
- sprite.LayerSetState(DisposalUnitVisualLayers.BaseFlush, chargingState);
- }
+ else if (state == VisualState.OverlayCharging)
+ sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, new RSI.StateId("disposal-charging"));
else
- {
_animationSystem.Stop(uid, AnimationKey);
- }
if (!_appearanceSystem.TryGetData<HandleState>(uid, Visuals.Handle, out var handleState, appearance))
- {
handleState = HandleState.Normal;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
if (!_appearanceSystem.TryGetData<LightStates>(uid, Visuals.Light, out var lightState, appearance))
- {
lightState = LightStates.Off;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
(lightState & LightStates.Charging) != 0);
Unanchored,
Base,
BaseCharging,
- BaseFlush,
+ OverlayFlush,
OverlayCharging,
OverlayReady,
OverlayFull,
+++ /dev/null
-using Content.Shared.Toilet;
-using Robust.Client.GameObjects;
-
-namespace Content.Client.Toilet;
-
-public sealed class ToiletVisualsSystem : VisualizerSystem<ToiletComponent>
-{
- protected override void OnAppearanceChange(EntityUid uid, ToiletComponent component, ref AppearanceChangeEvent args)
- {
- if (args.Sprite == null) return;
-
- AppearanceSystem.TryGetData<bool>(uid, ToiletVisuals.LidOpen, out var lidOpen, args.Component);
- AppearanceSystem.TryGetData<bool>(uid, ToiletVisuals.SeatUp, out var seatUp, args.Component);
-
- var state = (lidOpen, seatUp) switch
- {
- (false, false) => "closed_toilet_seat_down",
- (false, true) => "closed_toilet_seat_up",
- (true, false) => "open_toilet_seat_down",
- (true, true) => "open_toilet_seat_up"
- };
-
- args.Sprite.LayerSetState(0, state);
- }
-}
+++ /dev/null
-using Content.Shared.Construction;
-using Content.Shared.Examine;
-using Content.Shared.Toilet;
-using JetBrains.Annotations;
-
-namespace Content.Server.Construction.Conditions
-{
- [UsedImplicitly]
- [DataDefinition]
- public sealed partial class ToiletLidClosed : IGraphCondition
- {
- public bool Condition(EntityUid uid, IEntityManager entityManager)
- {
- if (!entityManager.TryGetComponent(uid, out ToiletComponent? toilet))
- return false;
-
- return !toilet.LidOpen;
- }
-
- public bool DoExamine(ExaminedEvent args)
- {
- var entity = args.Examined;
-
- if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out ToiletComponent? toilet)) return false;
- if (!toilet.LidOpen) return false;
-
- args.PushMarkup(Loc.GetString("construction-examine-condition-toilet-lid-closed") + "\n");
- return true;
- }
-
- public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
- {
- yield return new ConstructionGuideEntry()
- {
- Localization = "construction-step-condition-toilet-lid-closed"
- };
- }
- }
-}
{
// This is not an interaction, activation, or alternative verb type because unfortunately most users are
// unwilling to accept that this is where they belong and don't want to accidentally climb inside.
- if (!component.MobsCanEnter ||
- !args.CanAccess ||
+ if (!args.CanAccess ||
!args.CanInteract ||
component.Container.ContainedEntities.Contains(args.User) ||
!_actionBlockerSystem.CanMove(args.User))
switch (state)
{
case DisposalsPressureState.Flushed:
- _appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Flushing, appearance);
+ _appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.OverlayFlushing, appearance);
break;
case DisposalsPressureState.Pressurizing:
- _appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Charging, appearance);
+ _appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.OverlayCharging, appearance);
break;
case DisposalsPressureState.Ready:
_appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Anchored, appearance);
using Content.Server.Popups;
-using Content.Server.Storage.Components;
+using Content.Shared.Storage.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
+++ /dev/null
-using Content.Server.Storage.EntitySystems;
-using Content.Shared.Containers.ItemSlots;
-using Content.Shared.Item;
-using Content.Shared.Toilet;
-using Robust.Shared.Containers;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Storage.Components
-{
- /// <summary>
- /// Logic for a secret slot stash, like plant pot or toilet cistern.
- /// Unlike <see cref="ItemSlotsComponent"/> it doesn't have interaction logic or verbs.
- /// Other classes like <see cref="ToiletComponent"/> should implement it.
- /// </summary>
- [RegisterComponent]
- [Access(typeof(SecretStashSystem))]
- public sealed partial class SecretStashComponent : Component
- {
- /// <summary>
- /// Max item size that can be fitted into secret stash.
- /// </summary>
- [DataField("maxItemSize")]
- public ProtoId<ItemSizePrototype> MaxItemSize = "Small";
-
- /// <summary>
- /// IC secret stash name. For example "the toilet cistern".
- /// If empty string, will replace it with entity name in init.
- /// </summary>
- [DataField("secretPartName", readOnly: true)]
- public string SecretPartName { get; set; } = "";
-
- /// <summary>
- /// Container used to keep secret stash item.
- /// </summary>
- [ViewVariables]
- public ContainerSlot ItemContainer = default!;
-
- }
-}
-using Content.Server.Body.Systems;
-using Content.Shared.Buckle;
-using Content.Server.Popups;
-using Content.Server.Storage.Components;
-using Content.Server.Storage.EntitySystems;
-using Content.Shared.Audio;
-using Content.Shared.Body.Components;
-using Content.Shared.Body.Part;
-using Content.Shared.Buckle.Components;
-using Content.Shared.Examine;
-using Content.Shared.IdentityManagement;
-using Content.Shared.Interaction;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Popups;
-using Content.Shared.Toilet;
-using Content.Shared.Tools;
-using Content.Shared.Tools.Components;
-using Content.Shared.Verbs;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Player;
-using Robust.Shared.Random;
-using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
+using Content.Shared.Toilet.Systems;
-namespace Content.Server.Toilet
-{
- public sealed class ToiletSystem : EntitySystem
- {
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly BodySystem _body = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SecretStashSystem _secretStash = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly SharedToolSystem _tool = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<ToiletComponent, ComponentInit>(OnInit);
- SubscribeLocalEvent<ToiletComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<ToiletComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand);
- SubscribeLocalEvent<ToiletComponent, ExaminedEvent>(OnExamine);
- SubscribeLocalEvent<ToiletComponent, SuicideEvent>(OnSuicide);
- SubscribeLocalEvent<ToiletComponent, ToiletPryDoAfterEvent>(OnToiletPried);
- SubscribeLocalEvent<ToiletComponent, GetVerbsEvent<AlternativeVerb>>(OnToggleSeatVerb);
- }
-
- private void OnSuicide(EntityUid uid, ToiletComponent component, SuicideEvent args)
- {
- if (args.Handled)
- return;
-
- // Check that victim has a head
- // FIXME: since suiciding turns you into a ghost immediately, both messages are seen, not sure how this can be fixed
- if (TryComp<BodyComponent>(args.Victim, out var body) &&
- _body.BodyHasPartType(args.Victim, BodyPartType.Head, body))
- {
- var othersMessage = Loc.GetString("toilet-component-suicide-head-message-others",
- ("victim", Identity.Entity(args.Victim, EntityManager)), ("owner", uid));
- _popup.PopupEntity(othersMessage, uid, Filter.PvsExcept(args.Victim), true, PopupType.MediumCaution);
-
- var selfMessage = Loc.GetString("toilet-component-suicide-head-message",
- ("owner", uid));
- _popup.PopupEntity(selfMessage, uid, args.Victim, PopupType.LargeCaution);
-
- args.SetHandled(SuicideKind.Asphyxiation);
- }
- else
- {
- var othersMessage = Loc.GetString("toilet-component-suicide-message-others",
- ("victim", Identity.Entity(args.Victim, EntityManager)), ("owner", uid));
- _popup.PopupEntity(othersMessage, uid, Filter.PvsExcept(uid), true, PopupType.MediumCaution);
-
- var selfMessage = Loc.GetString("toilet-component-suicide-message",
- ("owner", uid));
- _popup.PopupEntity(selfMessage, uid, args.Victim, PopupType.LargeCaution);
-
- args.SetHandled(SuicideKind.Blunt);
- }
- }
-
- private void OnInit(EntityUid uid, ToiletComponent component, ComponentInit args)
- {
- EnsureComp<SecretStashComponent>(uid);
- }
-
- private void OnMapInit(EntityUid uid, ToiletComponent component, MapInitEvent args)
- {
- // roll is toilet seat will be up or down
- component.IsSeatUp = _random.Prob(0.5f);
- UpdateSprite(uid, component);
- }
-
- private void OnInteractUsing(EntityUid uid, ToiletComponent component, InteractUsingEvent args)
- {
- if (args.Handled)
- return;
-
- // are player trying place or lift of cistern lid?
- if (_tool.UseTool(args.Used, args.User, uid, component.PryLidTime, component.PryingQuality, new ToiletPryDoAfterEvent()))
- {
- args.Handled = true;
- }
- // maybe player trying to hide something inside cistern?
- else if (component.LidOpen)
- {
- args.Handled = true;
- _secretStash.TryHideItem(uid, args.User, args.Used);
- }
- }
-
- private void OnInteractHand(EntityUid uid, ToiletComponent component, InteractHandEvent args)
- {
- if (args.Handled)
- return;
+namespace Content.Server.Toilet;
- // trying get something from stash?
- if (component.LidOpen)
- {
- var gotItem = _secretStash.TryGetItem(uid, args.User);
- if (gotItem)
- {
- args.Handled = true;
- return;
- }
- }
-
- args.Handled = true;
- }
-
- private void OnToggleSeatVerb(EntityUid uid, ToiletComponent component, GetVerbsEvent<AlternativeVerb> args)
- {
- if (!args.CanInteract || !args.CanAccess || !CanToggle(uid))
- return;
-
- var alterToiletSeatText = component.IsSeatUp ? Loc.GetString("toilet-seat-close") : Loc.GetString("toilet-seat-open");
-
- var verb = new AlternativeVerb()
- {
- Act = () => {
- if (CanToggle(uid))
- ToggleToiletSeat(uid, component);
- },
- Text = alterToiletSeatText
- };
-
- args.Verbs.Add(verb);
- }
-
- private void OnExamine(EntityUid uid, ToiletComponent component, ExaminedEvent args)
- {
- if (args.IsInDetailsRange && component.LidOpen)
- {
- if (_secretStash.HasItemInside(uid))
- {
- var msg = Loc.GetString("toilet-component-on-examine-found-hidden-item");
- args.PushMarkup(msg);
- }
- }
- }
-
- private void OnToiletPried(EntityUid uid, ToiletComponent toilet, ToiletPryDoAfterEvent args)
- {
- if (args.Cancelled)
- return;
-
- toilet.LidOpen = !toilet.LidOpen;
- UpdateSprite(uid, toilet);
- }
-
- public bool CanToggle(EntityUid uid)
- {
- return TryComp<StrapComponent>(uid, out var strap) && strap.BuckledEntities.Count == 0;
- }
-
- public void ToggleToiletSeat(EntityUid uid, ToiletComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- component.IsSeatUp = !component.IsSeatUp;
- _audio.PlayPvs(component.ToggleSound, uid, AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation));
- UpdateSprite(uid, component);
- }
-
- private void UpdateSprite(EntityUid uid, ToiletComponent component)
- {
- if (!TryComp<AppearanceComponent>(uid, out var appearance))
- return;
+public sealed class ToiletSystem : SharedToiletSystem
+{
- _appearance.SetData(uid, ToiletVisuals.LidOpen, component.LidOpen, appearance);
- _appearance.SetData(uid, ToiletVisuals.SeatUp, component.IsSeatUp, appearance);
- }
- }
}
/// Entities that this strap accepts and can buckle
/// If null it accepts any entity
/// </summary>
- [DataField]
- [ViewVariables]
- public EntityWhitelist? AllowedEntities;
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntityWhitelist? Whitelist;
+
+ /// <summary>
+ /// Entities that this strap does not accept and cannot buckle.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntityWhitelist? Blacklist;
/// <summary>
/// The change in position to the strapped mob
}
// Does it pass the Whitelist
- if (strapComp.AllowedEntities != null &&
- !strapComp.AllowedEntities.IsValid(userUid, EntityManager))
+ if (strapComp.Whitelist != null &&
+ !strapComp.Whitelist.IsValid(buckleUid, EntityManager) || strapComp.Blacklist?.IsValid(buckleUid, EntityManager) == true)
{
if (_netManager.IsServer)
_popup.PopupEntity(Loc.GetString("buckle-component-cannot-fit-message"), userUid, buckleUid, PopupType.Medium);
using Robust.Shared.Audio;
+using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
[ViewVariables(VVAccess.ReadWrite), DataField("soundFlush")]
public SoundSpecifier? FlushSound = new SoundPathSpecifier("/Audio/Machines/disposalflush.ogg");
+ /// <summary>
+ /// Blacklists (prevents) entities listed from being placed inside.
+ /// </summary>
+ [DataField]
+ public EntityWhitelist? Blacklist;
+
+ /// <summary>
+ /// Whitelists (allows) entities listed from being placed inside.
+ /// </summary>
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
/// <summary>
/// Sound played when an object is inserted into the disposal unit.
/// </summary>
/// <summary>
/// State for this disposals unit.
/// </summary>
- [DataField("state")]
+ [DataField]
public DisposalsPressureState State;
// TODO: Just make this use vaulting.
/// <summary>
/// We'll track whatever just left disposals so we know what collision we need to ignore until they stop intersecting our BB.
/// </summary>
- [ViewVariables, DataField("recentlyEjected")]
+ [ViewVariables, DataField]
public List<EntityUid> RecentlyEjected = new();
/// <summary>
/// Next time the disposal unit will be pressurized.
/// </summary>
- [DataField("nextPressurized", customTypeSerializer:typeof(TimeOffsetSerializer))]
+ [DataField(customTypeSerializer:typeof(TimeOffsetSerializer))]
public TimeSpan NextPressurized = TimeSpan.Zero;
/// <summary>
/// <summary>
/// How long it takes from the start of a flush animation to return the sprite to normal.
/// </summary>
- [DataField("flushDelay")]
+ [DataField]
public TimeSpan FlushDelay = TimeSpan.FromSeconds(3);
- [DataField("mobsCanEnter")]
- public bool MobsCanEnter = true;
-
/// <summary>
/// Removes the pressure requirement for flushing.
/// </summary>
- [DataField("disablePressure"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
public bool DisablePressure;
/// <summary>
- /// Last time that an entity tried to exit this disposal unit.
+ /// Last time that an entity tried to exit this disposal unit.
/// </summary>
[ViewVariables]
public TimeSpan LastExitAttempt;
- [DataField("autoEngageEnabled")]
+ [DataField]
public bool AutomaticEngage = true;
[ViewVariables(VVAccess.ReadWrite)]
- [DataField("autoEngageTime")]
+ [DataField]
public TimeSpan AutomaticEngageTime = TimeSpan.FromSeconds(30);
/// <summary>
- /// Delay from trying to enter disposals ourselves.
+ /// Delay from trying to enter disposals ourselves.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
- [DataField("entryDelay")]
+ [DataField]
public float EntryDelay = 0.5f;
/// <summary>
- /// Delay from trying to shove someone else into disposals.
+ /// Delay from trying to shove someone else into disposals.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float DraggedEntryDelay = 2.0f;
/// <summary>
- /// Container of entities inside this disposal unit.
+ /// Container of entities inside this disposal unit.
/// </summary>
[ViewVariables] public Container Container = default!;
// TODO: Network power shit instead fam.
- [ViewVariables, DataField("powered")]
+ [ViewVariables, DataField]
public bool Powered;
/// <summary>
/// Was the disposals unit engaged for a manual flush.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField("engaged")]
+ [ViewVariables(VVAccess.ReadWrite), DataField]
public bool Engaged;
/// <summary>
/// Next time this unit will flush. Is the lesser of <see cref="FlushDelay"/> and <see cref="AutomaticEngageTime"/>
/// </summary>
- [ViewVariables, DataField("nextFlush", customTypeSerializer:typeof(TimeOffsetSerializer))]
+ [ViewVariables, DataField(customTypeSerializer:typeof(TimeOffsetSerializer))]
public TimeSpan? NextFlush;
[Serializable, NetSerializable]
{
UnAnchored,
Anchored,
- Flushing,
- Charging
+ OverlayFlushing,
+ OverlayCharging
}
[Serializable, NetSerializable]
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using Content.Shared.Body.Components;
using Content.Shared.Disposal.Components;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Emag.Systems;
using Content.Shared.Item;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
using Content.Shared.Throwing;
using Robust.Shared.Audio;
using Robust.Shared.Physics.Components;
{
[Dependency] protected readonly IGameTiming GameTiming = default!;
[Dependency] protected readonly MetaDataSystem Metadata = default!;
- [Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] protected readonly SharedJointSystem Joints = default!;
protected static TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5);
if (!Transform(uid).Anchored)
return false;
- // TODO: Probably just need a disposable tag.
var storable = HasComp<ItemComponent>(entity);
if (!storable && !HasComp<BodyComponent>(entity))
return false;
- //Check if the entity is a mob and if mobs can be inserted
- if (TryComp<MobStateComponent>(entity, out var damageState) && !component.MobsCanEnter)
+ if (component.Blacklist?.IsValid(entity, EntityManager) == true)
return false;
- if (TryComp<PhysicsComponent>(entity, out var physics) && (physics.CanCollide || storable))
+ if (component.Whitelist != null && component.Whitelist?.IsValid(entity, EntityManager) != true)
+ return false;
+
+ if (TryComp<PhysicsComponent>(entity, out var physics) && (physics.CanCollide) || storable)
return true;
+ else
+ return false;
- return damageState != null && (!component.MobsCanEnter || _mobState.IsDead(entity, damageState));
}
public abstract void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, SharedDisposalUnitComponent? disposal = null);
-using Content.Server.Plants.Systems;
-using Content.Server.Storage.Components;
+using Content.Shared.Storage.Components;
using Robust.Shared.Audio;
-namespace Content.Server.Plants.Components
+namespace Content.Shared.Plants
{
/// <summary>
/// Interaction wrapper for <see cref="SecretStashComponent"/>.
-using Content.Server.Plants.Components;
-using Content.Server.Popups;
-using Content.Server.Storage.Components;
-using Content.Server.Storage.EntitySystems;
-using Content.Shared.Audio;
+using Content.Shared.Popups;
+using Content.Shared.Storage.Components;
+using Content.Shared.Storage.EntitySystems;
using Content.Shared.Interaction;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
-using Robust.Shared.Player;
-namespace Content.Server.Plants.Systems
+namespace Content.Shared.Plants
{
public sealed class PottedPlantHideSystem : EntitySystem
{
- [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SecretStashSystem _stashSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Plunger.Components
+{
+ /// <summary>
+ /// Allows entity to unblock target entity with PlungerUseComponent.
+ /// </summary>
+ [RegisterComponent, NetworkedComponent,AutoGenerateComponentState]
+ public sealed partial class PlungerComponent : Component
+ {
+ /// <summary>
+ /// Duration of plunger doafter event.
+ /// </summary>
+ [DataField]
+ [AutoNetworkedField]
+ public float PlungeDuration = 2f;
+ }
+}
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Content.Shared.Random;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Plunger.Components
+{
+ /// <summary>
+ /// Entity can interact with plungers.
+ /// </summary>
+ [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+ public sealed partial class PlungerUseComponent : Component
+ {
+ /// <summary>
+ /// If true entity has been plungered.
+ /// </summary>
+ [DataField]
+ [AutoNetworkedField]
+ public bool Plunged;
+
+ /// <summary>
+ /// If true entity can interact with plunger.
+ /// </summary>
+ [DataField]
+ [AutoNetworkedField]
+ public bool NeedsPlunger = false;
+
+ /// <summary>
+ /// A weighted random entity prototype containing the different loot that rummaging can provide.
+ /// </summary>
+ [DataField]
+ [AutoNetworkedField]
+ public ProtoId<WeightedRandomEntityPrototype> PlungerLoot = "PlungerLoot";
+
+
+ /// <summary>
+ /// Sound played on rummage completion.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/glug.ogg");
+ }
+}
--- /dev/null
+
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Plunger;
+
+[Serializable, NetSerializable]
+public sealed partial class PlungerDoAfterEvent : SimpleDoAfterEvent
+{
+}
--- /dev/null
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Content.Shared.Popups;
+using Content.Shared.Plunger.Components;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Timing;
+using Content.Shared.Random.Helpers;
+using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Content.Shared.Random;
+
+namespace Content.Shared.Plunger.Systems;
+
+/// <summary>
+/// Plungers can be used to unblock entities with PlungerUseComponent.
+/// </summary>
+public sealed class PlungerSystem : EntitySystem
+{
+ [Dependency] protected readonly IPrototypeManager _proto = default!;
+ [Dependency] protected readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<PlungerComponent, AfterInteractEvent>(OnInteract);
+ SubscribeLocalEvent<PlungerComponent, PlungerDoAfterEvent>(OnDoAfter);
+ }
+
+ private void OnInteract(EntityUid uid, PlungerComponent component, AfterInteractEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (!args.CanReach || args.Target is not { Valid: true } target)
+ return;
+
+ if (!TryComp<PlungerUseComponent>(args.Target, out var plunger))
+ return;
+
+ if (plunger.NeedsPlunger)
+ return;
+
+ _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.PlungeDuration, new PlungerDoAfterEvent(), uid, target, uid)
+ {
+ BreakOnMove = true,
+ BreakOnDamage = true,
+ MovementThreshold = 1.0f,
+ });
+ args.Handled = true;
+ }
+
+ private void OnDoAfter(EntityUid uid, PlungerComponent component, DoAfterEvent args)
+ {
+ if (args.Cancelled || args.Handled || args.Args.Target == null)
+ return;
+
+ if (args.Target is not { Valid: true } target)
+ return;
+
+ if (!TryComp(target, out PlungerUseComponent? plunge))
+ return;
+
+ _popup.PopupClient(Loc.GetString("plunger-unblock", ("target", target)), args.User, args.User, PopupType.Medium);
+ plunge.Plunged = true;
+
+ var spawn = _proto.Index<WeightedRandomEntityPrototype>(plunge.PlungerLoot).Pick(_random);
+
+ _audio.PlayPredicted(plunge.Sound, uid, uid);
+ Spawn(spawn, Transform(target).Coordinates);
+ RemComp<PlungerUseComponent>(target);
+ Dirty(target, plunge);
+
+ args.Handled = true;
+ }
+}
+
--- /dev/null
+using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Item;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
+using Content.Shared.Tools;
+using Robust.Shared.GameStates;
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Storage.Components
+{
+ /// <summary>
+ /// Logic for a secret slot stash, like plant pot or toilet cistern.
+ /// Unlike <see cref="ItemSlotsComponent"/> it doesn't have interaction logic or verbs.
+ /// Other classes like <see cref="ToiletComponent"/> should implement it.
+ /// </summary>
+ [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+ [Access(typeof(SecretStashSystem))]
+ public sealed partial class SecretStashComponent : Component
+ {
+ /// <summary>
+ /// Max item size that can be fitted into secret stash.
+ /// </summary>
+ [DataField("maxItemSize")]
+ public ProtoId<ItemSizePrototype> MaxItemSize = "Small";
+
+ /// <summary>
+ /// If stash has way to open then this will switch between open and closed.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool ToggleOpen;
+
+ /// <summary>
+ /// Prying the door.
+ /// </summary>
+ [DataField]
+ public float PryDoorTime = 1f;
+
+ [DataField]
+ public ProtoId<ToolQualityPrototype> PryingQuality = "Prying";
+
+ /// <summary>
+ /// Is stash openable?.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool OpenableStash = false;
+
+ /// <summary>
+ /// IC secret stash name. For example "the toilet cistern".
+ /// If empty string, will replace it with entity name in init.
+ /// </summary>
+ [DataField]
+ public string SecretPartName { get; set; } = "";
+
+ [DataField, AutoNetworkedField]
+ public string ExamineStash = "comp-secret-stash-on-examine-found-hidden-item";
+
+ /// <summary>
+ /// Container used to keep secret stash item.
+ /// </summary>
+ [ViewVariables]
+ public ContainerSlot ItemContainer = default!;
+
+ }
+
+ /// <summary>
+ /// Simple pry event for prying open a stash door.
+ /// </summary>
+ [Serializable, NetSerializable]
+ public sealed partial class StashPryDoAfterEvent : SimpleDoAfterEvent
+ {
+ }
+
+ /// <summary>
+ /// Visualizers for handling stash open closed state if stash has door.
+ /// </summary>
+ [Serializable, NetSerializable]
+ public enum StashVisuals : byte
+ {
+ DoorVisualState,
+ }
+
+ [Serializable, NetSerializable]
+ public enum DoorVisualState : byte
+ {
+ DoorOpen,
+ DoorClosed
+ }
+}
-using Content.Server.Popups;
-using Content.Server.Storage.Components;
+using Content.Shared.Popups;
+using Content.Shared.Storage.Components;
using Content.Shared.Destructible;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Item;
using Robust.Shared.Containers;
+using Content.Shared.Interaction;
+using Content.Shared.Tools.Systems;
+using Content.Shared.Examine;
-namespace Content.Server.Storage.EntitySystems
+namespace Content.Shared.Storage.EntitySystems
{
+ /// <summary>
+ /// Secret Stash allows an item to be hidden within.
+ /// </summary>
public sealed class SecretStashSystem : EntitySystem
{
- [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
+ [Dependency] private readonly SharedToolSystem _tool = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
+ SubscribeLocalEvent<SecretStashComponent, StashPryDoAfterEvent>(OnSecretStashPried);
+ SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing);
+ SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
+ SubscribeLocalEvent<SecretStashComponent, ExaminedEvent>(OnExamine);
}
private void OnInit(EntityUid uid, SecretStashComponent component, ComponentInit args)
return component.ItemContainer.ContainedEntity != null;
}
+ private void OnInteractUsing(EntityUid uid, SecretStashComponent component, InteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (!component.OpenableStash)
+ return;
+
+ // is player trying place or lift off cistern lid?
+ if (_tool.UseTool(args.Used, args.User, uid, component.PryDoorTime, component.PryingQuality, new StashPryDoAfterEvent()))
+ args.Handled = true;
+ // maybe player is trying to hide something inside cistern?
+ else if (component.ToggleOpen)
+ {
+ TryHideItem(uid, args.User, args.Used);
+ args.Handled = true;
+ }
+ }
+
+ private void OnInteractHand(EntityUid uid, SecretStashComponent component, InteractHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (!component.OpenableStash)
+ return;
+
+ // trying to get something from stash?
+ if (component.ToggleOpen)
+ {
+ var gotItem = TryGetItem(uid, args.User);
+ if (gotItem)
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+ args.Handled = true;
+ }
+
+ private void OnSecretStashPried(EntityUid uid, SecretStashComponent component, StashPryDoAfterEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ ToggleOpen(uid, component);
+ }
+
+ public void ToggleOpen(EntityUid uid, SecretStashComponent? component = null, MetaDataComponent? meta = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ component.ToggleOpen = !component.ToggleOpen;
+
+ UpdateAppearance(uid, component);
+ Dirty(uid, component, meta);
+ }
+
+ private void UpdateAppearance(EntityUid uid, SecretStashComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ _appearance.SetData(uid, StashVisuals.DoorVisualState, component.ToggleOpen ? DoorVisualState.DoorOpen : DoorVisualState.DoorClosed);
+ }
+
/// <summary>
/// Tries to hide item inside secret stash from hands of user.
/// </summary>
if (container.ContainedEntity != null)
{
var msg = Loc.GetString("comp-secret-stash-action-hide-container-not-empty");
- _popupSystem.PopupEntity(msg, uid, userUid);
+ _popupSystem.PopupClient(msg, uid, userUid);
return false;
}
{
var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
("item", itemToHideUid), ("stash", GetSecretPartName(uid, component)));
- _popupSystem.PopupEntity(msg, uid, userUid);
+ _popupSystem.PopupClient(msg, uid, userUid);
return false;
}
// all done, show success message
var successMsg = Loc.GetString("comp-secret-stash-action-hide-success",
("item", itemToHideUid), ("this", GetSecretPartName(uid, component)));
- _popupSystem.PopupEntity(successMsg, uid, userUid);
+ _popupSystem.PopupClient(successMsg, uid, userUid);
return true;
}
// show success message
var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something",
("stash", GetSecretPartName(uid, component)));
- _popupSystem.PopupEntity(successMsg, uid, userUid);
+ _popupSystem.PopupClient(successMsg, uid, userUid);
return true;
}
+ private void OnExamine(EntityUid uid, SecretStashComponent component, ExaminedEvent args)
+ {
+ if (args.IsInDetailsRange && component.ToggleOpen)
+ {
+ if (HasItemInside(uid))
+ {
+ var msg = Loc.GetString(component.ExamineStash);
+ args.PushMarkup(msg);
+ }
+ }
+ }
+
private string GetSecretPartName(EntityUid uid, SecretStashComponent stash)
{
if (stash.SecretPartName != "")
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Toilet.Components
+{
+ /// <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>
+ /// Toggles seat state.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool ToggleSeat;
+
+
+ /// <summary>
+ /// Sound to play when toggling toilet seat.
+ /// </summary>
+ [DataField]
+ 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
+ }
+}
+
--- /dev/null
+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)
+ 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);
+ }
+ }
+}
+++ /dev/null
-using Content.Shared.DoAfter;
-using Content.Shared.Tools;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.Toilet
-{
- [RegisterComponent]
- public sealed partial class ToiletComponent : Component
- {
- [DataField("pryLidTime")]
- public float PryLidTime = 1f;
-
- [DataField("pryingQuality", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
- public string PryingQuality = "Prying";
-
- [DataField("toggleSound")]
- public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Effects/toilet_seat_down.ogg");
-
- [DataField("lidOpen")]
- public bool LidOpen = false;
-
- [DataField("isSeatUp")]
- public bool IsSeatUp = false;
- }
-
- [Serializable, NetSerializable]
- public sealed partial class ToiletPryDoAfterEvent : SimpleDoAfterEvent
- {
- }
-}
+++ /dev/null
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Toilet;
-
-[Serializable, NetSerializable]
-public enum ToiletVisuals
-{
- LidOpen,
- SeatUp
-}
license: "CC0-1.0"
copyright: "Created by brittmosel"
source: "https://freesound.org/people/brittmosel/sounds/529300/"
+
+- files: ["splash.ogg"]
+ license: "CC0-1.0"
+ copyright: "Created by deadrobotmusic"
+ source: "https://freesound.org/people/deadrobotmusic/sounds/609953/"
+
+- files: ["flush.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Created by the_toilet_guy"
+ source: "https://freesound.org/people/the_toilet_guy/sounds/98770/"
comp-secret-stash-action-hide-container-not-empty = There's already something in here?!
comp-secret-stash-action-hide-item-too-big = { THE($item) } is too big to fit in {$stash}!
comp-secret-stash-action-get-item-found-something = There was something inside {$stash}!
+comp-secret-stash-on-examine-found-hidden-item = There is something hidden inside.
secret-stash-part-plant = the plant
secret-stash-part-toilet = the toilet cistern
toilet-component-suicide-message = You bash yourself with {THE($owner)}!
toilet-seat-close = Close Seat
toilet-seat-open = Open Seat
+
+plunger-unblock = You unblock the {THE($target)}!
damage:
types:
Blunt: 3
+ - type: Plunger
+
+- type: weightedRandomEntity
+ id: PlungerLoot
+ weights:
+ RandomSpawner100: 56
+ SpacemenFigureSpawner: 28
+ SpawnMobCockroach: 5
+ MaintenanceToolSpawner: 5
- type: entity
parent: BaseItem
name: toilet
id: ToiletEmpty
suffix: Empty
- parent: SeatBase
+ parent: [ DisposalUnitBase, SeatBase ]
description: The HT-451, a torque rotation-based, waste disposal unit for small matter. This one seems remarkably clean.
components:
- - type: MeleeSound
- soundGroups:
- Brute:
- path:
- "/Audio/Weapons/slash.ogg"
- - type: Anchorable
- type: Sprite
sprite: Structures/Furniture/toilet.rsi
- state: closed_toilet_seat_up
+ layers:
+ - state: condisposal
+ map: [ "enum.DisposalUnitVisualLayers.Unanchored" ]
+ - state: disposal
+ map: [ "enum.DisposalUnitVisualLayers.Base" ]
+ - state: disposal-flush
+ map: [ "enum.DisposalUnitVisualLayers.OverlayFlush" ]
+ - state: dispover-charge
+ map: [ "enum.DisposalUnitVisualLayers.OverlayCharging" ]
+ - state: dispover-ready
+ map: [ "enum.DisposalUnitVisualLayers.OverlayReady" ]
+ - state: dispover-full
+ map: [ "enum.DisposalUnitVisualLayers.OverlayFull" ]
+ - state: dispover-handle
+ map: [ "enum.DisposalUnitVisualLayers.OverlayEngaged" ]
+ - map: [ "DoorVisualState.DoorOpen" ]
+ - map: [ "SeatVisualState.SeatUp" ]
+ - type: Rotatable
+ - type: Transform
+ noRot: false
+ - type: Strap
+ whitelist:
+ components:
+ - HumanoidAppearance
+ - type: DisposalUnit
+ autoEngageEnabled: false
+ noUI: true
+ blacklist:
+ components:
+ - HumanoidAppearance
+ - Plunger
+ - SolutionTransfer
+ whitelist:
+ components:
+ - Item
+ soundFlush: /Audio/Effects/Fluids/flush.ogg
+ soundInsert: /Audio/Effects/Fluids/splash.ogg
- type: Toilet
- - type: SecretStash
- secretPartName: secret-stash-part-toilet
- type: ContainerContainer
containers:
stash: !type:ContainerSlot {}
- - type: SolutionContainerManager
- solutions:
- drainBuffer:
- maxVol: 500
- toilet:
- maxVol: 250
- - type: Transform
- anchored: true
+ disposals: !type:Container
- type: Physics
bodyType: Static
- type: Construction
graph: Toilet
node: toilet
+ - type: PlungerUse
- type: Appearance
+ - type: SecretStash
+ secretPartName: secret-stash-part-toilet
+ examineStash: toilet-component-on-examine-found-hidden-item
+ openableStash: true
- type: Drain
autoDrain: false
- - type: DumpableSolution
- solution: drainBuffer
- - type: SolutionContainerVisuals
- maxFillLevels: 1
- fillBaseName: fill-
- solutionName: drainBuffer
- type: StaticPrice
price: 25
+ - type: UserInterface
+ interfaces:
+ - key: enum.DisposalUnitUiKey.Key
+ type: DisposalUnitBoundUserInterface
+ - type: RatKingRummageable
+ - type: SolutionContainerManager
+ solutions:
+ drainBuffer:
+ maxVol: 100
+ tank:
+ maxVol: 500
+ - type: SolutionRegeneration
+ solution: tank
+ generated:
+ reagents:
+ - ReagentId: Water
+ Quantity: 1
+ - type: DrainableSolution
+ solution: tank
+ - type: ReagentTank
+ - type: DumpableSolution
+ solution: drainBuffer
+ - type: GenericVisualizer
+ visuals:
+ enum.ToiletVisuals.SeatVisualState:
+ SeatVisualState.SeatUp:
+ SeatUp: { state: disposal-up }
+ SeatDown: { state: disposal-down }
+ enum.StashVisuals.DoorVisualState:
+ DoorVisualState.DoorOpen:
+ DoorOpen: { state: disposal-open }
+ DoorClosed: { state: disposal-closed }
- type: entity
id: ToiletDirtyWater
components:
- type: Sprite
sprite: Structures/Piping/disposal.rsi
- snapCardinals: true
layers:
- state: condisposal
map: [ "enum.DisposalUnitVisualLayers.Unanchored" ]
- state: disposal-charging
map: [ "enum.DisposalUnitVisualLayers.BaseCharging" ]
- state: disposal-flush
- map: [ "enum.DisposalUnitVisualLayers.BaseFlush" ]
+ map: [ "enum.DisposalUnitVisualLayers.OverlayFlush" ]
- state: dispover-charge
map: [ "enum.DisposalUnitVisualLayers.OverlayCharging" ]
- state: dispover-ready
map: [ "enum.DisposalUnitVisualLayers.OverlayEngaged" ]
- type: Physics
bodyType: Static
- - type: Fixtures
- fixtures:
- fix1:
- shape:
- !type:PhysShapeAabb
- bounds: "-0.25,-0.4,0.25,0.4"
- density: 75
- mask:
- - MachineMask
- layer:
- - MachineLayer
- type: Destructible
thresholds:
- trigger:
parent: DisposalUnitBase
name: disposal unit
components:
+ - type: Sprite
+ sprite: Structures/Piping/disposal.rsi
+ snapCardinals: true
- type: Construction
graph: DisposalMachine
node: disposal_unit
components:
- type: Sprite
sprite: Structures/Piping/disposal.rsi
+ snapCardinals: true
layers:
- state: conmailing
map: [ "enum.DisposalUnitVisualLayers.Unanchored" ]
- state: mailing-charging
map: [ "enum.DisposalUnitVisualLayers.BaseCharging" ]
- state: mailing-flush
- map: [ "enum.DisposalUnitVisualLayers.BaseFlush" ]
+ map: [ "enum.DisposalUnitVisualLayers.OverlayFlush" ]
- state: dispover-charge
map: [ "enum.DisposalUnitVisualLayers.OverlayCharging" ]
- state: dispover-ready
conditions:
- !type:EntityAnchored
anchored: false
- - !type:ToiletLidClosed {}
steps:
- tool: Welding
doAfter: 2
description: A human excrement flushing apparatus.
icon:
sprite: Structures/Furniture/toilet.rsi
- state: closed_toilet_seat_up
+ state: disposal
objectType: Structure
placementMode: SnapgridCenter
canBuildInImpassable: false
"x": 32,
"y": 32
},
- "license": "CC-BY-SA-3.0",
- "copyright": "Taken from cev-eris at commit https://github.com/discordia-space/CEV-Eris/commit/2cb66bae0e253e13d37f8939e0983bb94fee243e",
+ "license": "CC-BY-NC-SA-3.0",
+ "copyright": "Made by brainfood1183 (github) for ss14",
"states": [
{
- "name": "closed_toilet_seat_down",
- "directions": 4
+ "name": "condisposal",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
},
{
- "name": "closed_toilet_seat_up",
- "directions": 4
+ "name": "disposal-open",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
},
{
- "name": "open_toilet_seat_down",
- "directions": 4
+ "name": "disposal-closed",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
},
{
- "name": "open_toilet_seat_up",
- "directions": 4
+ "name": "disposal",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "disposal-up",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "disposal-down",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "disposal-charging",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "disposal-flush",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ [
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ [
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ [
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0,
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "dispover-charge",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "dispover-full",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "dispover-handle",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "dispover-ready",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
}
]
-}
\ No newline at end of file
+}