From 8a03d761f440229375b29a2ef77d230c14f10d9f Mon Sep 17 00:00:00 2001 From: Kirus59 <145689588+Kirus59@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:37:12 +0300 Subject: [PATCH] change oven --- .../Components/CookingConstantlyComponent.cs | 13 + .../Systems/CookingConstantlySystem.cs | 41 +++ .../Components/CookingConstantlyComponent.cs | 53 ++++ .../Systems/CookingConstantlySystem.cs | 289 ++++++++++++++++++ .../SharedCookingConstantlyComponent.cs | 66 ++++ .../Systems/SharedCookingConstantlySystem.cs | 82 +++++ .../SS220/SupaKitchen/Machines/oven.yml | 56 ++-- 7 files changed, 566 insertions(+), 34 deletions(-) create mode 100644 Content.Client/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs create mode 100644 Content.Client/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs create mode 100644 Content.Server/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs create mode 100644 Content.Server/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs create mode 100644 Content.Shared/SS220/SupaKitchen/Components/SharedCookingConstantlyComponent.cs create mode 100644 Content.Shared/SS220/SupaKitchen/Systems/SharedCookingConstantlySystem.cs diff --git a/Content.Client/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs b/Content.Client/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs new file mode 100644 index 000000000000..96ab6f87376f --- /dev/null +++ b/Content.Client/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs @@ -0,0 +1,13 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Shared.SS220.SupaKitchen.Components; + +namespace Content.Client.SS220.SupaKitchen.Components; + +[RegisterComponent] +public sealed partial class CookingConstantlyComponent : SharedCookingConstantlyComponent +{ + [DataField] + public string ActiveState = "oven_on"; + [DataField] + public string NonActiveState = "oven_off"; +} diff --git a/Content.Client/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs b/Content.Client/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs new file mode 100644 index 000000000000..efa5be2d55b3 --- /dev/null +++ b/Content.Client/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs @@ -0,0 +1,41 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Client.SS220.SupaKitchen.Components; +using Content.Shared.SS220.SupaKitchen.Components; +using Content.Shared.SS220.SupaKitchen.Systems; +using Content.Shared.Storage.Components; +using Robust.Client.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Client.SS220.SupaKitchen.Systems; + +public sealed partial class CookingConstantlySystem : SharedCookingConstantlySystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + + SubscribeLocalEvent(OnStorageOpenAttempt); + SubscribeLocalEvent(OnStorageCloseAttempt); + SubscribeLocalEvent(OnStorageOpen); + + SubscribeLocalEvent(OnAppearanceChange); + } + + private void OnAppearanceChange(Entity entity, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + return; + + if (!_appearance.TryGetData(entity, CookingConstantlyVisuals.Active, out var isActive)) + return; + + var state = isActive ? entity.Comp.ActiveState : entity.Comp.NonActiveState; + args.Sprite.LayerSetState(CookingConstantlyVisuals.Active, state); + args.Sprite.LayerSetVisible(CookingConstantlyVisuals.ActiveUnshaded, isActive); + } +} diff --git a/Content.Server/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs b/Content.Server/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs new file mode 100644 index 000000000000..9852fbb5bc7c --- /dev/null +++ b/Content.Server/SS220/SupaKitchen/Components/CookingConstantlyComponent.cs @@ -0,0 +1,53 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Shared.DeviceLinking; +using Content.Shared.FixedPoint; +using Content.Shared.SS220.SupaKitchen; +using Content.Shared.SS220.SupaKitchen.Components; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; + +namespace Content.Server.SS220.SupaKitchen.Components; + +[RegisterComponent] +public sealed partial class CookingConstantlyComponent : SharedCookingConstantlyComponent +{ + [ViewVariables] + public EntityUid? LastUser; + + [ViewVariables] + public List EntityPack = []; + + [ViewVariables] + public Dictionary ReagentsPack = []; + + [ViewVariables] + public float PackCookingTime; + + [ViewVariables] + public CookingRecipePrototype? CurrentCookingRecipe; + + [DataField] + public string ContainerName = "cooking_constantly_entity_container"; + + [ViewVariables] + public Container Container = default; + + #region audio + [DataField] + public SoundSpecifier ActivateSound = new SoundPathSpecifier("/Audio/SS220/SupaKitchen/oven/oven_loop_start.ogg"); + [DataField] + public SoundSpecifier FoodDoneSound = new SoundPathSpecifier("/Audio/SS220/SupaKitchen/ring.ogg"); + + [DataField] + public SoundSpecifier LoopingSound = new SoundPathSpecifier("/Audio/SS220/SupaKitchen/oven/oven_loop_mid.ogg"); + #endregion + + #region Sink ports + [DataField] + public ProtoId OnPort = "On"; + + [DataField] + public ProtoId TogglePort = "Toggle"; + #endregion +} diff --git a/Content.Server/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs b/Content.Server/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs new file mode 100644 index 000000000000..2727da1b0a27 --- /dev/null +++ b/Content.Server/SS220/SupaKitchen/Systems/CookingConstantlySystem.cs @@ -0,0 +1,289 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Server.DeviceLinking.Events; +using Content.Server.Power.EntitySystems; +using Content.Server.SS220.SupaKitchen.Components; +using Content.Server.Storage.EntitySystems; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Destructible; +using Content.Shared.FixedPoint; +using Content.Shared.Power; +using Content.Shared.SS220.SupaKitchen; +using Content.Shared.SS220.SupaKitchen.Components; +using Content.Shared.SS220.SupaKitchen.Systems; +using Content.Shared.Storage.Components; +using Content.Shared.Verbs; +using Robust.Server.Audio; +using Robust.Server.Containers; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using System.Linq; + +namespace Content.Server.SS220.SupaKitchen.Systems; + +public sealed partial class CookingConstantlySystem : SharedCookingConstantlySystem +{ + [Dependency] private readonly ContainerSystem _container = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly CookingInstrumentSystem _cookingInstrument = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly EntityStorageSystem _entityStorage = default!; + [Dependency] private readonly ApcSystem _apcSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); + + SubscribeLocalEvent(OnStorageOpenAttempt); + SubscribeLocalEvent(OnStorageCloseAttempt); + SubscribeLocalEvent(OnStorageOpen); + + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnBreak); + SubscribeLocalEvent>(OnAlternativeVerb); + SubscribeLocalEvent(OnSignalReceived); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var component)) + { + if (component.CurrentState != CookingConstantlyState.Active || + component.CurrentCookingRecipe is null) + continue; + + if (!HasContents(component)) + continue; + + component.PackCookingTime += frameTime; + if (component.CurrentCookingRecipe.CookTime > component.PackCookingTime) + continue; + + FinalizeCooking(uid, component); + } + } + + private void OnInit(EntityUid uid, CookingConstantlyComponent component, ComponentInit args) + { + if (component.UseEntityStorage) + component.Container = _container.EnsureContainer(uid, EntityStorageSystem.ContainerName); + else + component.Container = _container.EnsureContainer(uid, "cooking_machine_entity_container"); + } + + private void OnMapInit(Entity entity, ref MapInitEvent args) + { + if (!_apcSystem.IsPowered(entity, EntityManager)) + SetState(entity, entity, CookingConstantlyState.UnPowered); + } + + private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) + { + var (uid, comp) = entity; + if (comp.CurrentState is CookingConstantlyState.Broken) + return; + + if (!args.Powered) + { + Deactivate(uid, comp, false); + SetState(uid, comp, CookingConstantlyState.UnPowered); + } + else if (comp.CurrentState is CookingConstantlyState.UnPowered) + { + if (comp.LastState is CookingConstantlyState.Active) + Activate(uid, comp); + else + SetState(uid, comp, comp.LastState); + } + } + + private void OnBreak(Entity entity, ref BreakageEventArgs args) + { + Deactivate(entity, entity); + + if (entity.Comp.UseEntityStorage) + _entityStorage.CloseStorage(entity); + + _container.EmptyContainer(entity.Comp.Container); + + SetState(entity, entity, CookingConstantlyState.Broken); + } + + private void OnAlternativeVerb(Entity entity, ref GetVerbsEvent args) + { + var user = args.User; + AlternativeVerb? newVerb = null; + switch (entity.Comp.CurrentState) + { + case CookingConstantlyState.Idle: + newVerb = new AlternativeVerb() + { + Act = () => Activate(entity, entity, user), + Text = "Activate" + }; + break; + case CookingConstantlyState.Active: + newVerb = new AlternativeVerb() + { + Act = () => Deactivate(entity, entity), + Text = "Deactivate" + }; + break; + } + + if (newVerb != null) + args.Verbs.Add(newVerb); + } + + private void OnSignalReceived(Entity entity, ref SignalReceivedEvent args) + { + if (args.Port == entity.Comp.OnPort.Id && + entity.Comp.CurrentState is CookingConstantlyState.Idle) + Activate(entity, entity); + + if (args.Port == entity.Comp.TogglePort.Id) + { + switch (entity.Comp.CurrentState) + { + case CookingConstantlyState.Active: + Deactivate(entity, entity); + break; + case CookingConstantlyState.Idle: + Activate(entity, entity); + break; + } + } + } + + public void Activate(EntityUid uid, CookingConstantlyComponent component, EntityUid? user = null) + { + CycleCooking(uid, component); + + component.LastUser = user; + SetState(uid, component, CookingConstantlyState.Active); + + _audio.PlayPvs(component.ActivateSound, uid); + + var audioStream = _audio.PlayPvs(component.LoopingSound, uid, AudioParams.Default.WithLoop(true).WithMaxDistance(5)); + component.PlayingStream = GetNetEntity(audioStream?.Entity); + Dirty(uid, component); + } + + private void CycleCooking(EntityUid uid, CookingConstantlyComponent component) + { + if (!HasContents(component)) + return; + + if (IsPackChanged(uid, component)) + { + component.PackCookingTime = 0; + component.CurrentCookingRecipe = null; + } + + if (component.CurrentCookingRecipe is null) + component.CurrentCookingRecipe = GetCookingRecipe(uid, component); + } + + private void FinalizeCooking(EntityUid uid, CookingConstantlyComponent component) + { + if (component.CurrentCookingRecipe is null) + return; + + var container = component.Container; + _cookingInstrument.SubtractContents(container, component.CurrentCookingRecipe); + SpawnInContainerOrDrop(component.CurrentCookingRecipe.Result, uid, container.ID); + CycleCooking(uid, component); + } + + public bool IsPackChanged(EntityUid uid, CookingConstantlyComponent component, bool setNew = true) + { + var container = component.Container; + var containedEntities = container.ContainedEntities.OrderBy(x => x).ToList(); + + var isPackChanged = false; + if (!component.EntityPack.SequenceEqual(containedEntities)) + isPackChanged = true; + + var reagentDict = new Dictionary(); + foreach (var item in containedEntities) + { + if (!TryComp(item, out var solMan)) + continue; + + foreach (var (_, soln) in _solutionContainer.EnumerateSolutions((item, solMan))) + { + var solution = soln.Comp.Solution; + { + foreach (var (reagent, quantity) in solution.Contents) + { + if (!reagentDict.TryAdd(reagent.Prototype, quantity)) + reagentDict[reagent.Prototype] += quantity; + } + } + } + } + + reagentDict = reagentDict.OrderBy(x => x.Key).ToDictionary(); + if (!component.ReagentsPack.SequenceEqual(reagentDict)) + isPackChanged = true; + + if (setNew && isPackChanged) + { + component.EntityPack = containedEntities; + component.ReagentsPack = reagentDict; + } + + return isPackChanged; + } + + public bool HasContents(CookingConstantlyComponent component) + { + return component.Container.ContainedEntities.Any(); + } + + private CookingRecipePrototype? GetCookingRecipe(EntityUid uid, CookingConstantlyComponent component) + { + if (!TryComp(uid, out var cookingInstrument)) + return null; + + var solidsDict = new Dictionary(); + var reagentDict = new Dictionary(); + + foreach (var item in component.Container.ContainedEntities.ToList()) + { + var metaData = MetaData(item); //this still begs for cooking refactor + if (metaData.EntityPrototype == null) + continue; + + if (!solidsDict.TryAdd(metaData.EntityPrototype.ID, 1)) + solidsDict[metaData.EntityPrototype.ID]++; + + if (!TryComp(item, out var solMan)) + continue; + + foreach (var (_, soln) in _solutionContainer.EnumerateSolutions((item, solMan))) + { + var solution = soln.Comp.Solution; + { + foreach (var (reagent, quantity) in solution.Contents) + { + if (!reagentDict.TryAdd(reagent.Prototype, quantity)) + reagentDict[reagent.Prototype] += quantity; + } + } + } + } + + return _cookingInstrument.GetSatisfiedPortionedRecipe(cookingInstrument, solidsDict, reagentDict, 0).Item1; + } +} diff --git a/Content.Shared/SS220/SupaKitchen/Components/SharedCookingConstantlyComponent.cs b/Content.Shared/SS220/SupaKitchen/Components/SharedCookingConstantlyComponent.cs new file mode 100644 index 000000000000..1ece54adf195 --- /dev/null +++ b/Content.Shared/SS220/SupaKitchen/Components/SharedCookingConstantlyComponent.cs @@ -0,0 +1,66 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.SS220.SupaKitchen.Components; + +[NetworkedComponent] +public abstract partial class SharedCookingConstantlyComponent : Component +{ + #region State + [ViewVariables] + public CookingConstantlyState LastState = CookingConstantlyState.Idle; + + [ViewVariables] + public CookingConstantlyState CurrentState + { + get => _currentState; + set + { + LastState = _currentState; + _currentState = value; + } + + } + + [ViewVariables] + public CookingConstantlyState _currentState = CookingConstantlyState.Idle; + #endregion + + [DataField] + public bool UseEntityStorage = true; + + public NetEntity? PlayingStream { get; set; } +} + +[Serializable, NetSerializable] +public sealed class CookingConstantlyComponentState : ComponentState +{ + public CookingConstantlyState LastState; + public CookingConstantlyState CurrentState; + public NetEntity? PlayingStream; + + public CookingConstantlyComponentState(CookingConstantlyState lastState, CookingConstantlyState currentState, NetEntity? playingStream) + { + LastState = lastState; + CurrentState = currentState; + PlayingStream = playingStream; + } +} + +[Serializable, NetSerializable] +public enum CookingConstantlyState +{ + UnPowered, + Idle, + Active, + Broken +} + +[Serializable, NetSerializable] +public enum CookingConstantlyVisuals +{ + VisualState, + Active, + ActiveUnshaded +} diff --git a/Content.Shared/SS220/SupaKitchen/Systems/SharedCookingConstantlySystem.cs b/Content.Shared/SS220/SupaKitchen/Systems/SharedCookingConstantlySystem.cs new file mode 100644 index 000000000000..897642b62873 --- /dev/null +++ b/Content.Shared/SS220/SupaKitchen/Systems/SharedCookingConstantlySystem.cs @@ -0,0 +1,82 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Shared.SS220.SupaKitchen.Components; +using Content.Shared.Storage.Components; +using Robust.Shared.Audio.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.SS220.SupaKitchen.Systems; + +public abstract partial class SharedCookingConstantlySystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + protected void OnGetState(EntityUid uid, SharedCookingConstantlyComponent component, ref ComponentGetState args) + { + args.State = new CookingConstantlyComponentState(component.LastState, + component.CurrentState, + component.PlayingStream); + } + + protected void OnHandleState(EntityUid uid, SharedCookingConstantlyComponent component, ref ComponentHandleState args) + { + if (args.Current is not CookingConstantlyComponentState state) + return; + + component.LastState = state.LastState; + component.CurrentState = state.CurrentState; + component.PlayingStream = state.PlayingStream; + } + + #region Storage + protected void OnStorageOpenAttempt(EntityUid uid, SharedCookingConstantlyComponent component, ref StorageOpenAttemptEvent args) + { + if (args.Cancelled || !component.UseEntityStorage) + return; + + if (component.CurrentState is CookingConstantlyState.Broken) + args.Cancelled = true; + } + + protected void OnStorageCloseAttempt(EntityUid uid, SharedCookingConstantlyComponent component, ref StorageCloseAttemptEvent args) + { + if (args.Cancelled || !component.UseEntityStorage) + return; + + if (component.CurrentState is CookingConstantlyState.Broken) + args.Cancelled = true; + } + + protected void OnStorageOpen(EntityUid uid, SharedCookingConstantlyComponent component, ref StorageAfterOpenEvent args) + { + Deactivate(uid, component); + } + #endregion + + public void Deactivate(EntityUid uid, SharedCookingConstantlyComponent component, bool changeState = true) + { + if (component.CurrentState != CookingConstantlyState.Active) + return; + + if (changeState) + SetState(uid, component, CookingConstantlyState.Idle); + + _audio.Stop(GetEntity(component.PlayingStream)); + } + + public void SetState(EntityUid uid, SharedCookingConstantlyComponent component, CookingConstantlyState state) + { + component.CurrentState = state; + Dirty(uid, component); + + UpdateAppearance(uid, component); + } + + private void UpdateAppearance(EntityUid uid, SharedCookingConstantlyComponent component) + { + var isActive = component.CurrentState is CookingConstantlyState.Active; + + _appearance.SetData(uid, CookingConstantlyVisuals.VisualState, component.CurrentState); + _appearance.SetData(uid, CookingConstantlyVisuals.Active, isActive); + } +} diff --git a/Resources/Prototypes/SS220/SupaKitchen/Machines/oven.yml b/Resources/Prototypes/SS220/SupaKitchen/Machines/oven.yml index 94539ee1aecf..9a19cbedb99b 100644 --- a/Resources/Prototypes/SS220/SupaKitchen/Machines/oven.yml +++ b/Resources/Prototypes/SS220/SupaKitchen/Machines/oven.yml @@ -8,45 +8,39 @@ noRot: true # Make it behave like a closet - type: EntityStorage - - type: EntityStorageVisuals - #stateBaseClosed: generic - stateDoorOpen: oven_door_open - stateDoorClosed: oven_door_closed +# - type: EntityStorageVisuals +# #stateBaseClosed: generic +# stateDoorOpen: oven_door_open +# stateDoorClosed: oven_door_closed - type: PlaceableSurface placeCentered: true isPlaceable: false - type: CookingInstrument + ignoreTime: true instrumentType: oven - - type: CookingMachine - beginCookingSound: "/Audio/SS220/SupaKitchen/oven/oven_loop_start.ogg" - loopingSound: "/Audio/SS220/SupaKitchen/oven/oven_loop_mid.ogg" - foodDoneSound: "/Audio/SS220/SupaKitchen/ring.ogg" - useEntityStorage: true +# - type: CookingMachine +# beginCookingSound: "/Audio/SS220/SupaKitchen/oven/oven_loop_start.ogg" +# loopingSound: "/Audio/SS220/SupaKitchen/oven/oven_loop_mid.ogg" +# foodDoneSound: "/Audio/SS220/SupaKitchen/ring.ogg" +# useEntityStorage: true + - type: CookingConstantly - type: DeviceLinkSink ports: - On - - type: ActivatableUI - key: enum.CookingMachineUiKey.Key - altentativeVerbOnly: true - - type: UserInterface - interfaces: - enum.CookingMachineUiKey.Key: - type: CookingMachineBoundUserInterface - type: Sprite sprite: SS220/Structures/Machines/oven.rsi offset: 0,-0.5 snapCardinals: true layers: - state: oven_base - map: ["enum.CookingMachineVisualizerLayers.Base"] + map: ["base"] - state: oven_door_closed map: ["oven_door"] - - state: oven_off - map: ["oven_toggle"] - - state: oven_on_unshaded + - map: ["enum.CookingConstantlyVisuals.Active"] + state: oven_off + - map: ["enum.CookingConstantlyVisuals.ActiveUnshaded"] + state: oven_on_unshaded visible: false - map: ["oven_toggle_unshaded"] - shader: unshaded - type: Appearance - type: GenericVisualizer visuals: @@ -54,23 +48,17 @@ oven_door: True: { state: oven_door_open } False: { state: oven_door_closed } - enum.PowerDeviceVisuals.VisualState: - enum.CookingMachineVisualizerLayers.Base: + enum.CookingConstantlyVisuals.VisualState: + base: + UnPowered: { state: "oven_base" } Idle: { state: "oven_base" } + Active: { state: "oven_base" } Broken: { state: "oven_damaged" } - Cooking: { state: "oven_base" } oven_door: + UnPowered: { visible: true } Idle: { visible: true } + Active: { visible: true } Broken: { visible: false } - Cooking: { visible: true } - oven_toggle: - Idle: { state: "oven_off" } - Broken: { state: "oven_off" } - Cooking: { state: "oven_on" } - oven_toggle_unshaded: - Idle: { visible: false } - Broken: { visible: false } - Cooking: { visible: true } - type: Physics - type: Fixtures fixtures: