From 02ea6ce57cd06f1d8f4603f5f85b332ced5e0e44 Mon Sep 17 00:00:00 2001 From: Alex Evgrashin Date: Wed, 20 Jan 2021 10:02:34 +0300 Subject: [PATCH] Toilet (#3012) * Ported sprites from eris * Added yml * lid open/close logic * interactivity * Working on new secret stash component * Object will drop on destruction * Can get item and examine message * Reagent container and some cleaning * Moved potted plant to stash * New base prefab * Now you can deconstruct toilet * Small fixes * Fixed unknown components errors * Fixed grammar errors Co-authored-by: Paul Ritter * Now use prob * More grammar * Update Content.Server/Construction/Conditions/ToiletLidClosed.cs Aaaaaaaa Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * No delays * Amazing sound design * Moved sound to mono * Toilet viz Co-authored-by: Paul Ritter Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../Watercloset/ToiletVisualizer.cs | 26 +++ Content.Client/IgnoredComponents.cs | 2 + .../Conditions/ToiletLidClosed.cs | 35 ++++ .../Items/Storage/SecretStashComponent.cs | 119 ++++++++++++ .../Components/PottedPlantHideComponent.cs | 53 +----- .../Components/Watercloset/ToiletComponent.cs | 174 ++++++++++++++++++ .../Watercloset/SharedWaterclosetVisuals.cs | 13 ++ Resources/Audio/Effects/toilet_seat_down.ogg | Bin 0 -> 10856 bytes .../Constructible/Furniture/potted_plants.yml | 4 +- .../Constructible/Watercloset/toilet.yml | 39 ++++ .../Recipes/Construction/Graphs/toilet.yml | 29 +++ .../toilet.rsi/closed_toilet_seat_down.png | Bin 0 -> 1469 bytes .../toilet.rsi/closed_toilet_seat_up.png | Bin 0 -> 1597 bytes .../Watercloset/toilet.rsi/meta.json | 27 +++ .../toilet.rsi/open_toilet_seat_down.png | Bin 0 -> 1390 bytes .../toilet.rsi/open_toilet_seat_up.png | Bin 0 -> 1540 bytes 16 files changed, 473 insertions(+), 48 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Watercloset/ToiletVisualizer.cs create mode 100644 Content.Server/Construction/Conditions/ToiletLidClosed.cs create mode 100644 Content.Server/GameObjects/Components/Items/Storage/SecretStashComponent.cs create mode 100644 Content.Server/GameObjects/Components/Watercloset/ToiletComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Watercloset/SharedWaterclosetVisuals.cs create mode 100644 Resources/Audio/Effects/toilet_seat_down.ogg create mode 100644 Resources/Prototypes/Entities/Constructible/Watercloset/toilet.yml create mode 100644 Resources/Prototypes/Recipes/Construction/Graphs/toilet.yml create mode 100644 Resources/Textures/Constructible/Watercloset/toilet.rsi/closed_toilet_seat_down.png create mode 100644 Resources/Textures/Constructible/Watercloset/toilet.rsi/closed_toilet_seat_up.png create mode 100644 Resources/Textures/Constructible/Watercloset/toilet.rsi/meta.json create mode 100644 Resources/Textures/Constructible/Watercloset/toilet.rsi/open_toilet_seat_down.png create mode 100644 Resources/Textures/Constructible/Watercloset/toilet.rsi/open_toilet_seat_up.png diff --git a/Content.Client/GameObjects/Components/Watercloset/ToiletVisualizer.cs b/Content.Client/GameObjects/Components/Watercloset/ToiletVisualizer.cs new file mode 100644 index 0000000000..f776b68e75 --- /dev/null +++ b/Content.Client/GameObjects/Components/Watercloset/ToiletVisualizer.cs @@ -0,0 +1,26 @@ +#nullable enable +using Content.Shared.GameObjects.Components.Watercloset; +using Robust.Client.GameObjects; +using Robust.Client.Interfaces.GameObjects.Components; + +namespace Content.Client.GameObjects.Components.Watercloset +{ + public class ToiletVisualizer : AppearanceVisualizer + { + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) return; + + if (!component.TryGetData(ToiletVisuals.LidOpen, out bool lidOpen)) lidOpen = false; + if (!component.TryGetData(ToiletVisuals.SeatUp, out bool seatUp)) seatUp = false; + + var state = string.Format("{0}_toilet_{1}", + lidOpen ? "open" : "closed", + seatUp ? "seat_up" : "seat_down"); + + sprite.LayerSetState(0, state); + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 77d93add0d..6823e56a43 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -237,6 +237,8 @@ namespace Content.Client "DamageOnLand", "GasFilter", "Recyclable", + "SecretStash", + "Toilet", "ClusterFlash" }; } diff --git a/Content.Server/Construction/Conditions/ToiletLidClosed.cs b/Content.Server/Construction/Conditions/ToiletLidClosed.cs new file mode 100644 index 0000000000..a1ad695bf6 --- /dev/null +++ b/Content.Server/Construction/Conditions/ToiletLidClosed.cs @@ -0,0 +1,35 @@ +#nullable enable +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Watercloset; +using Content.Shared.Construction; +using JetBrains.Annotations; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Server.Construction.Conditions +{ + [UsedImplicitly] + public class ToiletLidClosed : IEdgeCondition + { + public async Task Condition(IEntity entity) + { + if (!entity.TryGetComponent(out ToiletComponent? toilet)) return false; + return !toilet.LidOpen; + } + + public bool DoExamine(IEntity entity, FormattedMessage message, bool inExamineRange) + { + if (!entity.TryGetComponent(out ToiletComponent? toilet)) return false; + if (!toilet.LidOpen) return false; + + message.AddMarkup(Loc.GetString("Use a [color=yellow]crowbar[/color] to close the lid.\n")); + return true; + } + + public void ExposeData(ObjectSerializer serializer) + { + } + } +} diff --git a/Content.Server/GameObjects/Components/Items/Storage/SecretStashComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/SecretStashComponent.cs new file mode 100644 index 0000000000..b2255dee11 --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/Storage/SecretStashComponent.cs @@ -0,0 +1,119 @@ +#nullable enable +using Content.Server.GameObjects.Components.GUI; +using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces; +using Robust.Server.GameObjects.Components.Container; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Items.Storage +{ + /// + /// Logic for secret single slot stash, like plant pot or toilet cistern + /// + [RegisterComponent] + public class SecretStashComponent : Component, IDestroyAct + { + public override string Name => "SecretStash"; + + [ViewVariables] private int _maxItemSize; + [ViewVariables] private string _secretPartName = ""; + + [ViewVariables] private ContainerSlot _itemContainer = default!; + + public override void Initialize() + { + base.Initialize(); + _itemContainer = ContainerManagerComponent.Ensure("stash", Owner, out _); + } + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _maxItemSize, "maxItemSize", (int) ReferenceSizes.Pocket); + serializer.DataField(ref _secretPartName, "secretPartName", Loc.GetString("{0:theName}")); + } + + /// + /// Tries to hide item inside secret stash from hands of user + /// + /// + /// + /// True if item was hidden inside stash + public bool TryHideItem(IEntity user, IEntity itemToHide) + { + if (_itemContainer.ContainedEntity != null) + { + Owner.PopupMessage(user, Loc.GetString("There's already something in here?!")); + return false; + } + + if (!itemToHide.TryGetComponent(out ItemComponent? item)) + return false; + + if (item.Size > _maxItemSize) + { + Owner.PopupMessage(user, + Loc.GetString("{0:TheName} is too big to fit in {1}!", itemToHide, _secretPartName)); + return false; + } + + if (!user.TryGetComponent(out IHandsComponent? hands)) + return false; + + if (!hands.Drop(itemToHide, _itemContainer)) + return false; + + Owner.PopupMessage(user, Loc.GetString("You hide {0:theName} in {1}.", itemToHide, _secretPartName)); + return true; + } + + /// + /// Try get item and place it in users hand + /// If user can't take it by hands, will drop item from container + /// + /// + /// True if user recieved item + public bool TryGetItem(IEntity user) + { + if (_itemContainer.ContainedEntity == null) + return false; + + Owner.PopupMessage(user, Loc.GetString("There was something inside {0}!", _secretPartName)); + + if (user.TryGetComponent(out HandsComponent? hands)) + { + if (!_itemContainer.ContainedEntity.TryGetComponent(out ItemComponent? item)) + return false; + hands.PutInHandOrDrop(item); + } + else if (_itemContainer.Remove(_itemContainer.ContainedEntity)) + { + _itemContainer.ContainedEntity.Transform.Coordinates = Owner.Transform.Coordinates; + } + + return true; + } + + /// + /// Is there something inside secret stash item container? + /// + /// + public bool HasItemInside() + { + return _itemContainer.ContainedEntity != null; + } + + public void OnDestroy(DestructionEventArgs eventArgs) + { + // drop item inside + if (_itemContainer.ContainedEntity != null) + { + _itemContainer.ContainedEntity.Transform.Coordinates = Owner.Transform.Coordinates; + } + } + } +} diff --git a/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs b/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs index cc924cffd4..288fa0242d 100644 --- a/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs +++ b/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs @@ -17,74 +17,33 @@ namespace Content.Server.GameObjects.Components [RegisterComponent] public class PottedPlantHideComponent : Component, IInteractUsing, IInteractHand { - private const int MaxItemSize = (int) ReferenceSizes.Pocket; - public override string Name => "PottedPlantHide"; - [ViewVariables] private ContainerSlot _itemContainer; + [ViewVariables] private SecretStashComponent _secretStash = default!; public override void Initialize() { base.Initialize(); - - _itemContainer = - ContainerManagerComponent.Ensure("potted_plant_hide", Owner, out _); + _secretStash = Owner.EnsureComponent(); } async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { - if (_itemContainer.ContainedEntity != null) - { - Rustle(); - - Owner.PopupMessage(eventArgs.User, Loc.GetString("There's already something in here?!")); - return false; - } - - var size = eventArgs.Using.GetComponent().Size; - - // TODO: use proper text macro system for this. - - if (size > MaxItemSize) - { - Owner.PopupMessage(eventArgs.User, - Loc.GetString("{0:TheName} is too big to fit in the plant!", eventArgs.Using)); - return false; - } - - var handsComponent = eventArgs.User.GetComponent(); - - if (!handsComponent.Drop(eventArgs.Using, _itemContainer)) - { - return false; - } - - Owner.PopupMessage(eventArgs.User, Loc.GetString("You hide {0:theName} in the plant.", eventArgs.Using)); Rustle(); - return true; + return _secretStash.TryHideItem(eventArgs.User, eventArgs.Using); } bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) { Rustle(); - if (_itemContainer.ContainedEntity == null) + var gotItem = _secretStash.TryGetItem(eventArgs.User); + if (!gotItem) { Owner.PopupMessage(eventArgs.User, Loc.GetString("You root around in the roots.")); - return true; } - Owner.PopupMessage(eventArgs.User, Loc.GetString("There was something in there!")); - if (eventArgs.User.TryGetComponent(out HandsComponent hands)) - { - hands.PutInHandOrDrop(_itemContainer.ContainedEntity.GetComponent()); - } - else if (_itemContainer.Remove(_itemContainer.ContainedEntity)) - { - _itemContainer.ContainedEntity.Transform.Coordinates = Owner.Transform.Coordinates; - } - - return true; + return gotItem; } private void Rustle() diff --git a/Content.Server/GameObjects/Components/Watercloset/ToiletComponent.cs b/Content.Server/GameObjects/Components/Watercloset/ToiletComponent.cs new file mode 100644 index 0000000000..e2f0ff1376 --- /dev/null +++ b/Content.Server/GameObjects/Components/Watercloset/ToiletComponent.cs @@ -0,0 +1,174 @@ +#nullable enable +using Content.Server.GameObjects.Components.Interactable; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.GameObjects.Components.Strap; +using Content.Server.Interfaces.Chat; +using Content.Server.Interfaces.GameObjects; +using Content.Server.Utility; +using Content.Shared.Audio; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.Components.Body.Part; +using Content.Shared.GameObjects.Components.Interactable; +using Content.Shared.GameObjects.Components.Watercloset; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Random; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; +using System.Threading.Tasks; + +namespace Content.Server.GameObjects.Components.Watercloset +{ + [RegisterComponent] + public class ToiletComponent : Component, IInteractUsing, + IInteractHand, IMapInit, IExamine, ISuicideAct + { + public sealed override string Name => "Toilet"; + + private const float PryLidTime = 1f; + + private bool _isPrying = false; + + [ViewVariables] public bool LidOpen { get; private set; } + [ViewVariables] public bool IsSeatUp { get; private set; } + + [ViewVariables] private SecretStashComponent _secretStash = default!; + + public override void Initialize() + { + base.Initialize(); + _secretStash = Owner.EnsureComponent(); + } + + public void MapInit() + { + // roll is toilet seat will be up or down + var random = IoCManager.Resolve(); + IsSeatUp = random.Prob(0.5f); + UpdateSprite(); + } + + public async Task InteractUsing(InteractUsingEventArgs eventArgs) + { + // are player trying place or lift of cistern lid? + if (eventArgs.Using.TryGetComponent(out ToolComponent? tool) + && tool!.HasQuality(ToolQuality.Prying)) + { + // check if someone is already prying this toilet + if (_isPrying) + return false; + _isPrying = true; + + if (!await tool.UseTool(eventArgs.User, Owner, PryLidTime, ToolQuality.Prying)) + { + _isPrying = false; + return false; + } + + _isPrying = false; + + // all cool - toggle lid + LidOpen = !LidOpen; + UpdateSprite(); + + return true; + } + // maybe player trying to hide something inside cistern? + else if (LidOpen) + { + return _secretStash.TryHideItem(eventArgs.User, eventArgs.Using); + } + + return false; + } + + public bool InteractHand(InteractHandEventArgs eventArgs) + { + // trying get something from stash? + if (LidOpen) + { + var gotItem = _secretStash.TryGetItem(eventArgs.User); + + if (gotItem) + return true; + } + + // just want to up/down seat? + // check that nobody seats on seat right now + if (Owner.TryGetComponent(out StrapComponent? strap)) + { + if (strap.BuckledEntities.Count != 0) + return false; + } + + ToggleToiletSeat(); + return true; + } + + public void Examine(FormattedMessage message, bool inDetailsRange) + { + if (inDetailsRange && LidOpen) + { + if (_secretStash.HasItemInside()) + { + message.AddMarkup(Loc.GetString("There is [color=darkgreen]something[/color] inside cistern!")); + } + } + } + + public void ToggleToiletSeat() + { + IsSeatUp = !IsSeatUp; + EntitySystem.Get() + .PlayFromEntity("/Audio/Effects/toilet_seat_down.ogg", Owner, AudioHelpers.WithVariation(0.05f)); + + UpdateSprite(); + } + + private void UpdateSprite() + { + if (Owner.TryGetComponent(out AppearanceComponent? appearance)) + { + appearance.SetData(ToiletVisuals.LidOpen, LidOpen); + appearance.SetData(ToiletVisuals.SeatUp, IsSeatUp); + } + } + + public SuicideKind Suicide(IEntity victim, IChatManager chat) + { + // check that victim even have head + if (victim.TryGetComponent(out var body) && + body.GetPartsOfType(BodyPartType.Head).Count != 0) + { + var othersMessage = Loc.GetString("{0:theName} sticks their head into {1:theName} and flushes it!", victim, Owner); + victim.PopupMessageOtherClients(othersMessage); + + var selfMessage = Loc.GetString("You stick your head into {0:theName} and flush it!", Owner); + victim.PopupMessage(selfMessage); + + return SuicideKind.Asphyxiation; + } + else + { + var othersMessage = Loc.GetString("{0:theName} bashes themselves with {1:theName}!", victim, Owner); + victim.PopupMessageOtherClients(othersMessage); + + var selfMessage = Loc.GetString("You bash yourself with {0:theName}!", Owner); + victim.PopupMessage(selfMessage); + + return SuicideKind.Blunt; + } + } + + } +} diff --git a/Content.Shared/GameObjects/Components/Watercloset/SharedWaterclosetVisuals.cs b/Content.Shared/GameObjects/Components/Watercloset/SharedWaterclosetVisuals.cs new file mode 100644 index 0000000000..0ee66dd2c9 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Watercloset/SharedWaterclosetVisuals.cs @@ -0,0 +1,13 @@ +#nullable enable +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.GameObjects.Components.Watercloset +{ + [Serializable, NetSerializable] + public enum ToiletVisuals + { + LidOpen, + SeatUp + } +} diff --git a/Resources/Audio/Effects/toilet_seat_down.ogg b/Resources/Audio/Effects/toilet_seat_down.ogg new file mode 100644 index 0000000000000000000000000000000000000000..dc568adf232c1c36ccaef9d1d66a2fb895f387da GIT binary patch literal 10856 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 CEJ`*2 literal 0 HcmV?d00001 diff --git a/Resources/Prototypes/Entities/Constructible/Furniture/potted_plants.yml b/Resources/Prototypes/Entities/Constructible/Furniture/potted_plants.yml index a5bcf67edd..8272e0e572 100644 --- a/Resources/Prototypes/Entities/Constructible/Furniture/potted_plants.yml +++ b/Resources/Prototypes/Entities/Constructible/Furniture/potted_plants.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity id: PottedPlantBase abstract: true components: @@ -23,6 +23,8 @@ - type: Sprite sprite: Constructible/Misc/potted_plants.rsi - type: PottedPlantHide + - type: SecretStash + secretPartName: the plant - type: Anchorable - type: Pullable diff --git a/Resources/Prototypes/Entities/Constructible/Watercloset/toilet.yml b/Resources/Prototypes/Entities/Constructible/Watercloset/toilet.yml new file mode 100644 index 0000000000..cb73fef1f9 --- /dev/null +++ b/Resources/Prototypes/Entities/Constructible/Watercloset/toilet.yml @@ -0,0 +1,39 @@ +- type: entity + name: toilet + id: ToiletEmpty + suffix: Empty + parent: SeatBase + description: The HT-451, a torque rotation-based, waste disposal unit for small matter. This one seems remarkably clean. + components: + - type: Sprite + sprite: Constructible/Watercloset/toilet.rsi + state: closed_toilet_seat_up + netsync: false + - type: Toilet + - type: SecretStash + secretPartName: the toilet cistern + - type: SolutionContainer + maxVol: 250 + - type: Physics + shapes: + - !type:PhysShapeAabb + layer: [ Passable ] + - type: Construction + graph: toilet + node: toilet + - type: Appearance + visuals: + - type: ToiletVisualizer + +- type: entity + id: ToiletDirtyWater + parent: ToiletEmpty + suffix: Dirty Water + components: + - type: SolutionContainer + contents: + reagents: + - ReagentId: chem.Water + Quantity: 180 + - ReagentId: chem.Toxin + Quantity: 20 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/toilet.yml b/Resources/Prototypes/Recipes/Construction/Graphs/toilet.yml new file mode 100644 index 0000000000..faf5cc9f6b --- /dev/null +++ b/Resources/Prototypes/Recipes/Construction/Graphs/toilet.yml @@ -0,0 +1,29 @@ +- type: constructionGraph + id: toilet + start: start + graph: + - node: start + edges: + - to: toilet + completed: + - !type:SnapToGrid { } + steps: + - material: Metal + amount: 5 + doAfter: 1 + - node: toilet + edges: + - to: start + completed: + - !type:SpawnPrototype + prototype: SteelSheet1 + amount: 5 + - !type:EmptyAllContainers {} + - !type:DeleteEntity {} + conditions: + - !type:EntityAnchored + anchored: false + - !type:ToiletLidClosed {} + steps: + - tool: Welding + doAfter: 2 \ No newline at end of file diff --git a/Resources/Textures/Constructible/Watercloset/toilet.rsi/closed_toilet_seat_down.png b/Resources/Textures/Constructible/Watercloset/toilet.rsi/closed_toilet_seat_down.png new file mode 100644 index 0000000000000000000000000000000000000000..af9b04af2b1852d090135f38323bab318333e4c4 GIT binary patch literal 1469 zcmV;u1w#6XP)~cJ6!^^47&L7fnx^$B z&KLs#6z2f|{4p6piCV43Z*OlM50P4}#_wBRRen&d`0>*_*X!+e8|`)*r>Cc$bsrMS zU0`w|X*F||x*xDRuu#K9*#H1w7zTi@<715V`2hgRMtUgtGC-+Rvc8K3Af@TNQmF(b zU+5?s;OxBFb8Z-hWe~$KpzFG0-JmF!%bw14ZEelh=%ZXYohfhtSY9ePHgiqWu)JCa zfWYSh<#L%l}g2z5tLd$E}!L-6Ju5r7rO#M{-HQO4aOMaqgnpp-`zlsK+gyg zB?C-OB%$lNtjRfM`m;H@gcGnfTYAaBl2ao|SE-oDFBoYZx zh)}Sbh1!*=qHF*nkw^^mps&HNa(?RjHo&)rZv(hlchF|&M!8((c23ugQmLdgWCA`6 z;Ku_8?X8`LUBBgaJoqkfaB>BpS1HDrbuNAv>(TGLP#Ol$&YOVoW%}#no5!hnx{M-063A z_?ikYFBLrdx3240Uabc@6;8!dd}VW`Zxt#o#;BYzhF8D7!cpre*oagrp5nQDmNyy= zZknd;(P%VyE}!M8cq;ISP%56{rfKq8t;S8$?1@I1rpeQp0yj<5_p^NmMZ0l9`{9l; zhJ%x<(1N1W3hjK5VHg9oJYS=X*7^2c{i4$U0t6RMog_^0djOYiP9HW%Kr<|eX7u8%rakJFzdh2FfEh=2C zR360-Q`nuun-u&)wIlg72y2ZsmEIE6GO=k*xYyGwL1Gg3-K45F-vnXKb&^*34KkX_P zA$A#2dB7$?LNtPWBfEYcWcS&CMic4QR9OnUtd-aip*(QJc-&+8W z=f6O*oC~uHuK5hEO@g9Cb5EZE=Jcng9R(|L4!2 zKQU+`U&zs*_g_98#&pMgFvu5j^!oZb@Cg&peS`AX$`^8U|Kn%awjJ1y5Q3!_&mN2> z#8o;m<{1pbkoI$37XYyT@iUfQJfjam2tVj_wr$&&d7vwpFb%nH&=SCZ5lSY&5cKtR z`rHMjQpwlJzIpRT+vKzaFm9AeC2Cog_kW47sjd*TOn^DLyY?9brBaC!Li*-n^SQ1I z%d!GBLNt+{X*3!%U&zr^GBxzfa4MOij^ohn?QQBfPERJ4Y$&Z1bILI0e(pfX~T=M$*nx?a+)++E|(&9I#Z9M+z@wmyDV1fxI zm|%hlCYa#=iFDS~j8weUQx}+>xjo`C z3OF?`L{MYSJPhVEDm%#vZ@a;wPaZqGK;vk zYVQX>jb*|pu7I|h^;jI}rkaAXDw+8NbkFC;3Cps8S}m}ih^5cs`9hA)&dl|tn4+%t z8wF%Mv9{TnIiLU<2f7jy85V!Q5M>>}T_MuOAD_ue?8@t_{;L3=d5z+nFKX~K2c2dG zE`w1OJ&qD#JopNLH;cuE=fkCH1Yk59h7st%<1E$8I*VX7R9;`j*6X*}+j)grE#WPT zqIpDpM?k!h!pAL^WqGvD&ddcK;juKjQdYQHt)j*)E22b&Gy2FbO5_VUy14MX?{rwC zV{zfR^0FOoHS4jRsSCRD+wamr83-YP=W?E{eN~!wV2J0D;yWPN?%CJhdG`le&BJaW z)VZ4OTk>SJcwvJ^eLBONYYvn(#ZN2S2jF4dx+)t1mXc?kVDzqKjsOH9Bj8&TzV3EfeYxZB;}b0Bn1+@ z5X6CG1xZmBM1+AAY%s{2BmcL^Jm6CyA_{?rSddg+Ume>W!F(Y{w_d;XrHzM-`9QFm z8}{(^IE_Z*UfdMrZ?dx5C7w?91SLp(Cb=&;1CbW?H~UB|`y>0UuK;dN v+gN_O96EWoOn&sGDV?inWEOw9b5+{EEF|u^sd{$w00000NkvXXu0mjffJX~C literal 0 HcmV?d00001 diff --git a/Resources/Textures/Constructible/Watercloset/toilet.rsi/meta.json b/Resources/Textures/Constructible/Watercloset/toilet.rsi/meta.json new file mode 100644 index 0000000000..0761994277 --- /dev/null +++ b/Resources/Textures/Constructible/Watercloset/toilet.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "size": { + "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", + "states": [ + { + "name": "closed_toilet_seat_down", + "directions": 4 + }, + { + "name": "closed_toilet_seat_up", + "directions": 4 + }, + { + "name": "open_toilet_seat_down", + "directions": 4 + }, + { + "name": "open_toilet_seat_up", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Constructible/Watercloset/toilet.rsi/open_toilet_seat_down.png b/Resources/Textures/Constructible/Watercloset/toilet.rsi/open_toilet_seat_down.png new file mode 100644 index 0000000000000000000000000000000000000000..a0844c8793be34c452bbbe35ca434b5e14fca19f GIT binary patch literal 1390 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 literal 0 HcmV?d00001