From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sat, 22 Apr 2023 07:03:50 +0000 (-0400) Subject: Lipid Extractor (#15597) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=4f9ae1116e9319c06f725b306ea0dae82c1529df;p=space-station-14.git Lipid Extractor (#15597) --- diff --git a/Content.Server/Nutrition/Components/FatExtractorComponent.cs b/Content.Server/Nutrition/Components/FatExtractorComponent.cs new file mode 100644 index 0000000000..8a0826c29c --- /dev/null +++ b/Content.Server/Nutrition/Components/FatExtractorComponent.cs @@ -0,0 +1,94 @@ +using Content.Server.Nutrition.EntitySystems; +using Content.Shared.Construction.Prototypes; +using Content.Shared.Nutrition.Components; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Nutrition.Components; + +/// +/// This is used for a machine that extracts hunger from entities and creates meat. Yum! +/// +[RegisterComponent, Access(typeof(FatExtractorSystem))] +public sealed class FatExtractorComponent : Component +{ + /// + /// Whether or not the extractor is currently extracting fat from someone + /// + [DataField("processing")] + public bool Processing = true; + + /// + /// How much nutrition is extracted per second. + /// + [DataField("nutritionPerSecond"), ViewVariables(VVAccess.ReadWrite)] + public int NutritionPerSecond = 10; + + /// + /// The base rate of extraction + /// + [DataField("baseNutritionPerSecond"), ViewVariables(VVAccess.ReadWrite)] + public int BaseNutritionPerSecond = 10; + + #region Machine Upgrade + /// + /// Which machine part affects the nutrition rate + /// + [DataField("machinePartNutritionRate", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartNutritionRate = "Laser"; + + /// + /// The increase in rate per each rating above 1. + /// + [DataField("partRatingRateMultiplier")] + public float PartRatingRateMultiplier = 10; + #endregion + + /// + /// An accumulator which tracks extracted nutrition to determine + /// when to spawn a meat. + /// + [DataField("nutrientAccumulator"), ViewVariables(VVAccess.ReadWrite)] + public int NutrientAccumulator; + + /// + /// How high has to be to spawn meat + /// + [DataField("nutrientPerMeat"), ViewVariables(VVAccess.ReadWrite)] + public int NutrientPerMeat = 60; + + /// + /// Meat spawned by the extractor. + /// + [DataField("meatPrototype", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string MeatPrototype = "FoodMeat"; + + /// + /// When the next update will occur + /// + [DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan NextUpdate; + + /// + /// How long each update takes + /// + [DataField("updateTime"), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan UpdateTime = TimeSpan.FromSeconds(1); + + /// + /// The sound played when extracting + /// + [DataField("processSound")] + public SoundSpecifier? ProcessSound; + + public IPlayingAudioStream? Stream; + + /// + /// A minium hunger threshold for extracting nutrition. + /// Ignored when emagged. + /// + [DataField("minHungerThreshold")] + public HungerThreshold MinHungerThreshold = HungerThreshold.Okay; +} diff --git a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs new file mode 100644 index 0000000000..6ab432d490 --- /dev/null +++ b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs @@ -0,0 +1,164 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Construction; +using Content.Server.Nutrition.Components; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.Storage.Components; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Storage.Components; +using Robust.Shared.Timing; + +namespace Content.Server.Nutrition.EntitySystems; + +/// +/// This handles logic and interactions relating to +/// +public sealed class FatExtractorSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnGotEmagged); + SubscribeLocalEvent(OnClosed); + SubscribeLocalEvent(OnOpen); + SubscribeLocalEvent(OnPowerChanged); + } + + private void OnRefreshParts(EntityUid uid, FatExtractorComponent component, RefreshPartsEvent args) + { + var rating = args.PartRatings[component.MachinePartNutritionRate] - 1; + component.NutritionPerSecond = component.BaseNutritionPerSecond + (int) (component.PartRatingRateMultiplier * rating); + } + + private void OnUpgradeExamine(EntityUid uid, FatExtractorComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("fat-extractor-component-rate", (float) component.NutritionPerSecond / component.BaseNutritionPerSecond); + } + + private void OnUnpaused(EntityUid uid, FatExtractorComponent component, ref EntityUnpausedEvent args) + { + component.NextUpdate += args.PausedTime; + } + + private void OnGotEmagged(EntityUid uid, FatExtractorComponent component, ref GotEmaggedEvent args) + { + args.Handled = true; + args.Repeatable = false; + } + + private void OnClosed(EntityUid uid, FatExtractorComponent component, ref StorageAfterCloseEvent args) + { + StartProcessing(uid, component); + } + + private void OnOpen(EntityUid uid, FatExtractorComponent component, ref StorageAfterOpenEvent args) + { + StopProcessing(uid, component); + } + + private void OnPowerChanged(EntityUid uid, FatExtractorComponent component, ref PowerChangedEvent args) + { + if (!args.Powered) + StopProcessing(uid, component); + } + + public void StartProcessing(EntityUid uid, FatExtractorComponent? component = null, EntityStorageComponent? storage = null) + { + if (!Resolve(uid, ref component, ref storage)) + return; + + if (component.Processing) + return; + + if (!this.IsPowered(uid, EntityManager)) + return; + + if (!TryGetValidOccupant(uid, out _, component, storage)) + return; + + component.Processing = true; + _appearance.SetData(uid, FatExtractorVisuals.Processing, true); + component.Stream = _audio.PlayPvs(component.ProcessSound, uid); + component.NextUpdate = _timing.CurTime + component.UpdateTime; + } + + public void StopProcessing(EntityUid uid, FatExtractorComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (!component.Processing) + return; + + component.Processing = false; + _appearance.SetData(uid, FatExtractorVisuals.Processing, false); + component.Stream?.Stop(); + } + + public bool TryGetValidOccupant(EntityUid uid, [NotNullWhen(true)] out EntityUid? occupant, FatExtractorComponent? component = null, EntityStorageComponent? storage = null) + { + occupant = null; + if (!Resolve(uid, ref component, ref storage)) + return false; + + occupant = storage.Contents.ContainedEntities.FirstOrDefault(); + + if (!TryComp(occupant, out var hunger)) + return false; + + if (hunger.CurrentHunger < component.NutritionPerSecond) + return false; + + if (hunger.CurrentThreshold < component.MinHungerThreshold && !HasComp(uid)) + return false; + + return true; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var fat, out var storage)) + { + if (TryGetValidOccupant(uid, out var occupant, fat, storage)) + { + if (!fat.Processing) + StartProcessing(uid, fat, storage); + } + else + { + StopProcessing(uid, fat); + continue; + } + + if (!fat.Processing) + continue; + + if (_timing.CurTime < fat.NextUpdate) + continue; + fat.NextUpdate += fat.UpdateTime; + + _hunger.ModifyHunger(occupant.Value, -fat.NutritionPerSecond); + fat.NutrientAccumulator += fat.NutritionPerSecond; + if (fat.NutrientAccumulator >= fat.NutrientPerMeat) + { + fat.NutrientAccumulator -= fat.NutrientPerMeat; + Spawn(fat.MeatPrototype, Transform(uid).Coordinates); + } + } + } +} diff --git a/Content.Shared/Lock/LockComponent.cs b/Content.Shared/Lock/LockComponent.cs index d8c0963830..fae5788612 100644 --- a/Content.Shared/Lock/LockComponent.cs +++ b/Content.Shared/Lock/LockComponent.cs @@ -34,6 +34,12 @@ public sealed class LockComponent : Component /// [DataField("lockingSound"), ViewVariables(VVAccess.ReadWrite)] public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg"); + + /// + /// Whether or not an emag disables it. + /// + [DataField("breakOnEmag")] + public bool BreakOnEmag = true; } [Serializable, NetSerializable] diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 2af6366982..79a0e26fbd 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -203,7 +203,7 @@ public sealed class LockSystem : EntitySystem private bool HasUserAccess(EntityUid uid, EntityUid user, AccessReaderComponent? reader = null, bool quiet = true) { // Not having an AccessComponent means you get free access. woo! - if (!Resolve(uid, ref reader)) + if (!Resolve(uid, ref reader, false)) return true; if (_accessReader.IsAllowed(user, reader)) @@ -234,7 +234,7 @@ public sealed class LockSystem : EntitySystem private void OnEmagged(EntityUid uid, LockComponent component, ref GotEmaggedEvent args) { - if (!component.Locked) + if (!component.Locked || !component.BreakOnEmag) return; _audio.PlayPredicted(component.UnlockSound, uid, null, AudioParams.Default.WithVolume(-5)); _appearanceSystem.SetData(uid, StorageVisuals.Locked, false); diff --git a/Content.Shared/Nutrition/Components/SharedFatExtractor.cs b/Content.Shared/Nutrition/Components/SharedFatExtractor.cs new file mode 100644 index 0000000000..b52e1a9022 --- /dev/null +++ b/Content.Shared/Nutrition/Components/SharedFatExtractor.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Nutrition.Components; + +[Serializable, NetSerializable] +public enum FatExtractorVisuals : byte +{ + Processing +} + +public enum FatExtractorVisualLayers : byte +{ + Light, + Stack, + Smoke +} diff --git a/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl b/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl new file mode 100644 index 0000000000..b2df0e079e --- /dev/null +++ b/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl @@ -0,0 +1,8 @@ +fat-extractor-component-rate = extraction rate + +fat-extractor-fact-1 = Fats are triglycerides made up of a combination of different building blocks; glycerol and fatty acids. +fat-extractor-fact-2 = Adults should get a recommended 20-35% of their energy intake from fat. +fat-extractor-fact-3 = Being overweight or obese puts you at an increased risk of chronic diseases, such as cardiovascular diseases, metabolic syndrome, type 2 diabetes, and some types of cancers. +fat-extractor-fact-4 = Not all fats are bad. A certain amount of fat is an essential part of a healthy balanced diet. +fat-extractor-fact-5 = Saturated fat should form no more than 11% of your daily calories. +fat-extractor-fact-6 = Unsaturated fat, that is monounsaturated fats, polyunsaturated fats, and omega-3 fatty acids, is found in plants and fish. \ No newline at end of file diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index bfbae68d97..8641bc892e 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -61,6 +61,7 @@ - MicrowaveMachineCircuitboard - BoozeDispenserMachineCircuitboard - SodaDispenserMachineCircuitboard + - FatExtractorMachineCircuitboard # Biological Technology Tree diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml new file mode 100644 index 0000000000..4e8b3eb88e --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml @@ -0,0 +1,9 @@ +- type: advertisementsPack + id: FatExtractorFacts + advertisements: + - fat-extractor-fact-1 + - fat-extractor-fact-2 + - fat-extractor-fact-3 + - fat-extractor-fact-4 + - fat-extractor-fact-5 + - fat-extractor-fact-6 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 4ace8ea4f0..7df228de48 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -673,6 +673,23 @@ Glass: 2 Cable: 2 +- type: entity + id: FatExtractorMachineCircuitboard + parent: BaseMachineCircuitboard + name: lipid extractor machine board + components: + - type: Sprite + state: service + - type: MachineBoard + prototype: FatExtractor + requirements: + Laser: 1 + componentRequirements: + Utensil: + Amount: 1 + DefaultPrototype: ForkPlastic + ExamineName: Utensil + - type: entity id: EmitterCircuitboard parent: BaseMachineCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/fatextractor.yml b/Resources/Prototypes/Entities/Structures/Machines/fatextractor.yml new file mode 100644 index 0000000000..3914f5e5e2 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/fatextractor.yml @@ -0,0 +1,113 @@ +- type: entity + id: FatExtractor + parent: BaseMachinePowered + name: lipid extractor + description: Safely and efficiently extracts excess fat from a subject. + components: + - type: FatExtractor + processSound: + path: /Audio/Machines/microwave_loop.ogg + params: + loop: true + maxdistance: 5 + - type: Sprite + netsync: false + sprite: Structures/Machines/fat_sucker.rsi + snapCardinals: true + layers: + - state: fat + - state: fat_door_off + map: ["enum.StorageVisualLayers.Door"] + - state: fat_red + shader: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + - state: fat_green + shader: unshaded + visible: false + map: ["enum.FatExtractorVisualLayers.Light"] + - state: fat_panel + visible: false + map: ["enum.WiresVisualLayers.MaintenancePanel"] + - state: fat_stack #cash cash cash + visible: false + map: ["enum.FatExtractorVisualLayers.Stack"] + - state: fat_smoke + visible: false + map: ["enum.FatExtractorVisualLayers.Smoke"] + - type: Lock + breakOnEmag: false + - type: GenericVisualizer + visuals: + enum.StorageVisuals.Open: + enum.StorageVisualLayers.Door: + True: { visible: false } + False: { visible: true } + enum.FatExtractorVisuals.Processing: + enum.StorageVisualLayers.Door: + True: { state: fat_door_on } + False: { state: fat_door_off } + enum.FatExtractorVisualLayers.Smoke: + True: { visible: true } + False: { visible: false } + enum.FatExtractorVisualLayers.Stack: + True: { visible: true } + False: { visible: false } + enum.FatExtractorVisualLayers.Light: + True: { visible: true } + False: { visible: false } + enum.PowerDeviceVisuals.Powered: + enum.FatExtractorVisualLayers.Light: + False: { visible: false } + enum.PowerDeviceVisualLayers.Powered: + True: { visible: true } + False: { visible: false } + enum.StorageVisuals.HasContents: + enum.PowerDeviceVisualLayers.Powered: + True: { state: fat_yellow } + False: { state: fat_red } + enum.WiresVisuals.MaintenancePanelState: + enum.WiresVisualLayers.MaintenancePanel: + True: { visible: true } + False: { visible: false } + - type: Construction + graph: Machine + node: machine + containers: + - machine_board + - machine_parts + - entity_storage + - type: EmptyOnMachineDeconstruct + containers: + - entity_storage + - type: Damageable + damageContainer: Inorganic + damageModifierSet: StrongMetallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:ChangeConstructionNodeBehavior + node: machineFrame + - !type:DoActsBehavior + acts: ["Destruction"] + - type: Machine + board: FatExtractorMachineCircuitboard + - type: Wires + BoardName: FatExtractor + LayoutId: FatExtractor + - type: Appearance + - type: Speech + - type: Advertise + pack: FatExtractorFacts + - type: StaticPrice + price: 1000 + - type: ResistLocker + - type: EntityStorage + capacity: 1 + - type: ContainerContainer + containers: + machine_board: !type:Container + machine_parts: !type:Container + entity_storage: !type:Container diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 62a89a3dda..18bae43997 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -300,6 +300,7 @@ - ReagentGrinderMachineCircuitboard - HotplateMachineCircuitboard - MicrowaveMachineCircuitboard + - FatExtractorMachineCircuitboard - UniformPrinterMachineCircuitboard - ShuttleConsoleCircuitboard - RadarConsoleCircuitboard diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 468c0e5a56..5cb7cf7c5d 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -424,6 +424,14 @@ Steel: 100 Glass: 900 +- type: latheRecipe + id: FatExtractorMachineCircuitboard + result: FatExtractorMachineCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 + - type: latheRecipe id: SurveillanceCameraRouterCircuitboard result: SurveillanceCameraRouterCircuitboard diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat.png new file mode 100644 index 0000000000..cbd63ae3ee Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_door_off.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_door_off.png new file mode 100644 index 0000000000..c8cfe3b0a8 Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_door_off.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_door_on.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_door_on.png new file mode 100644 index 0000000000..a47692a251 Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_door_on.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_green.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_green.png new file mode 100644 index 0000000000..d971f3d6c0 Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_green.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_panel.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_panel.png new file mode 100644 index 0000000000..7062b2646f Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_panel.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_red.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_red.png new file mode 100644 index 0000000000..c3f17feffc Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_red.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_smoke.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_smoke.png new file mode 100644 index 0000000000..0a5575dbbb Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_smoke.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_stack.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_stack.png new file mode 100644 index 0000000000..64c5f5aba2 Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_stack.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_yellow.png b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_yellow.png new file mode 100644 index 0000000000..b1546fc397 Binary files /dev/null and b/Resources/Textures/Structures/Machines/fat_sucker.rsi/fat_yellow.png differ diff --git a/Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json b/Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json new file mode 100644 index 0000000000..5100252ae9 --- /dev/null +++ b/Resources/Textures/Structures/Machines/fat_sucker.rsi/meta.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "taken from /tg/station at commit https://github.com/tgstation/tgstation/commit/48370e5a35a19eab427d3e403b653e65fa391ca2", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "fat" + }, + { + "name": "fat_door_off" + }, + { + "name": "fat_door_on" + }, + { + "name": "fat_green" + }, + { + "name": "fat_yellow" + }, + { + "name": "fat_red" + }, + { + "name": "fat_stack" + }, + { + "name": "fat_smoke", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.4 + ] + ] + }, + { + "name": "fat_panel" + } + ] +} \ No newline at end of file