From 57f2a768a07ac12dd30458557070852965d64d4d Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Mon, 10 Apr 2023 00:38:20 -0400 Subject: [PATCH] Material Reclaimer (#14969) * Material Reclaimer * Fix this test * autostack output, tweak volume, add upgrade examine * whitelist AND blacklist support why not * trying so hard to get this fucking test to work * EmoGarbage delves into MaterialArbitrageTest, never to return * VV and restore cloth to glory * make the system more robust * even more stuff has composition; add blacklist for important items * fix test fails * convert recycling * forgor :sadge: * lol * simply a modiCUM of doc commentary --- .../Materials/MaterialReclaimerSystem.cs | 9 + .../Recycling/RecyclerVisualizer.cs | 70 ----- .../Tests/MaterialArbitrageTest.cs | 126 +++++++-- Content.Server/Cargo/Systems/PricingSystem.cs | 11 +- .../Materials/MaterialReclaimerSystem.cs | 261 ++++++++++++++++++ .../Materials/MaterialStorageSystem.cs | 36 ++- .../Physics/Controllers/ConveyorController.cs | 13 +- .../Components/RecyclableComponent.cs | 26 -- .../Recycling/Components/RecyclerComponent.cs | 34 --- Content.Server/Recycling/RecyclerSystem.cs | 199 ------------- Content.Shared/Lathe/LatheComponent.cs | 2 +- .../ActiveMaterialReclaimerComponent.cs | 32 +++ .../CollideMaterialReclaimerComponent.cs | 16 ++ .../Materials/MaterialReclaimerComponent.cs | 177 ++++++++++++ .../Materials/MaterialStorageComponent.cs | 6 + .../Materials/PhysicalCompositionComponent.cs | 30 ++ .../SharedMaterialReclaimerSystem.cs | 249 +++++++++++++++++ .../Materials/SharedMaterialStorageSystem.cs | 2 +- .../Recycling/SharedRecyclerComponent.cs | 10 - .../Audio/Ambience/Objects/attributions.yml | 7 +- Resources/Audio/Ambience/Objects/crushing.ogg | Bin 0 -> 36925 bytes .../Locale/en-US/materials/materials.ftl | 3 + Resources/Prototypes/Body/Parts/silicon.yml | 5 +- .../Catalog/Research/technologies.yml | 1 + .../Clothing/Belt/base_clothingbelt.yml | 5 + .../Entities/Clothing/Head/misc.yml | 3 + .../Entities/Clothing/Head/welding.yml | 6 + .../Entities/Clothing/Masks/masks.yml | 5 +- .../Clothing/OuterClothing/hardsuits.yml | 4 + .../Entities/Clothing/Shoes/magboots.yml | 4 + .../Prototypes/Entities/Mobs/NPCs/animals.yml | 2 - .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 2 - .../Entities/Mobs/NPCs/simplemob.yml | 2 - .../Entities/Mobs/Player/silicon.yml | 2 - .../Prototypes/Entities/Mobs/Species/base.yml | 2 - .../Objects/Consumable/Drinks/drinks.yml | 4 +- .../Objects/Consumable/Drinks/drinks_cans.yml | 4 +- .../Objects/Consumable/Drinks/drinks_cups.yml | 6 + .../Consumable/Drinks/drinks_flasks.yml | 3 + .../Consumable/Drinks/drinks_special.yml | 3 + .../Consumable/Drinks/trash_drinks.yml | 4 +- .../Consumable/Food/Containers/bowl.yml | 4 +- .../Consumable/Food/Containers/plate.yml | 9 +- .../Consumable/Food/Containers/tin.yml | 6 + .../Entities/Objects/Consumable/Food/egg.yml | 1 - .../Objects/Consumable/Food/food_base.yml | 1 - .../Objects/Consumable/Food/frozen.yml | 1 - .../Entities/Objects/Consumable/Food/meat.yml | 1 + .../Objects/Consumable/Food/produce.yml | 1 - .../Objects/Consumable/Food/snacks.yml | 4 +- .../Smokeables/Cigarettes/cartons.yml | 4 +- .../Smokeables/Cigarettes/cigarette.yml | 1 - .../Smokeables/Cigarettes/joints.yml | 2 - .../Smokeables/Cigarettes/packs.yml | 4 +- .../Smokeables/Cigarettes/rolling_paper.yml | 2 - .../Consumable/Smokeables/base_smokeables.yml | 7 +- .../Entities/Objects/Decoration/present.yml | 1 - .../Machine/base_machineboard.yml | 6 + .../Circuitboards/Machine/production.yml | 34 ++- .../Devices/Circuitboards/computer.yml | 13 + .../Devices/Electronics/base_electronics.yml | 5 + .../Devices/Electronics/power_electronics.yml | 5 + .../Objects/Devices/hand_teleporter.yml | 3 + .../Entities/Objects/Fun/crayons.yml | 1 - .../Prototypes/Entities/Objects/Fun/toys.yml | 78 +++--- .../Objects/Materials/Sheets/glass.yml | 2 - .../Entities/Objects/Materials/shards.yml | 1 - .../Prototypes/Entities/Objects/Misc/box.yml | 5 + .../Entities/Objects/Misc/broken_bottle.yml | 4 +- .../Entities/Objects/Misc/dat_fukken_disk.yml | 3 + .../Entities/Objects/Misc/handcuffs.yml | 6 +- .../Objects/Misc/identification_cards.yml | 8 +- .../Entities/Objects/Misc/paper.yml | 5 +- .../Entities/Objects/Misc/utensils.yml | 1 - .../Entities/Objects/Power/lights.yml | 4 +- .../Entities/Objects/Shields/shields.yml | 8 +- .../Objects/Specific/Medical/hypospray.yml | 7 +- .../Entities/Objects/Specific/atmos.yml | 4 + .../Objects/Specific/chemistry-bottles.yml | 4 +- .../Entities/Objects/Tools/bucket.yml | 3 + .../Entities/Objects/Tools/flare.yml | 1 - .../Entities/Objects/Tools/gas_tanks.yml | 8 + .../Entities/Objects/Tools/glowstick.yml | 1 - .../Entities/Objects/Tools/jetpacks.yml | 5 +- .../Entities/Objects/Tools/matches.yml | 2 - .../Entities/Objects/Tools/tools.yml | 34 +++ .../Entities/Objects/Tools/welders.yml | 3 + .../Ammunition/Cartridges/base_cartridge.yml | 1 - .../Guns/Ammunition/Projectiles/toy.yml | 2 +- .../Weapons/Guns/Battery/battery_guns.yml | 3 + .../Entities/Structures/Machines/lathe.yml | 1 + .../Machines/material_reclaimer.yml | 101 +++++++ .../Entities/Structures/Machines/recycler.yml | 58 +++- .../Storage/Canisters/gas_canisters.yml | 4 +- .../Entities/Structures/Windows/window.yml | 6 +- .../Reagents/Materials/materials.yml | 2 +- .../Prototypes/Recipes/Lathes/electronics.yml | 8 + .../Prototypes/Recipes/Lathes/janitorial.yml | 2 +- Resources/Prototypes/tags.yml | 3 + .../material_reclaimer.rsi/fill-1.png | Bin 0 -> 112 bytes .../material_reclaimer.rsi/fill-2.png | Bin 0 -> 117 bytes .../material_reclaimer.rsi/fill-3.png | Bin 0 -> 121 bytes .../material_reclaimer.rsi/fill-4.png | Bin 0 -> 119 bytes .../material_reclaimer.rsi/fill-5.png | Bin 0 -> 122 bytes .../material_reclaimer.rsi/fill-6.png | Bin 0 -> 120 bytes .../material_reclaimer.rsi/gear-active.png | Bin 0 -> 379 bytes .../material_reclaimer.rsi/gear-idle.png | Bin 0 -> 202 bytes .../Machines/material_reclaimer.rsi/icon.png | Bin 0 -> 709 bytes .../Machines/material_reclaimer.rsi/meta.json | 57 ++++ .../Machines/material_reclaimer.rsi/panel.png | Bin 0 -> 204 bytes .../Machines/material_reclaimer.rsi/unlit.png | Bin 0 -> 223 bytes 111 files changed, 1448 insertions(+), 499 deletions(-) create mode 100644 Content.Client/Materials/MaterialReclaimerSystem.cs delete mode 100644 Content.Client/Recycling/RecyclerVisualizer.cs create mode 100644 Content.Server/Materials/MaterialReclaimerSystem.cs delete mode 100644 Content.Server/Recycling/Components/RecyclableComponent.cs delete mode 100644 Content.Server/Recycling/Components/RecyclerComponent.cs delete mode 100644 Content.Server/Recycling/RecyclerSystem.cs create mode 100644 Content.Shared/Materials/ActiveMaterialReclaimerComponent.cs create mode 100644 Content.Shared/Materials/CollideMaterialReclaimerComponent.cs create mode 100644 Content.Shared/Materials/MaterialReclaimerComponent.cs create mode 100644 Content.Shared/Materials/PhysicalCompositionComponent.cs create mode 100644 Content.Shared/Materials/SharedMaterialReclaimerSystem.cs delete mode 100644 Content.Shared/Recycling/SharedRecyclerComponent.cs create mode 100644 Resources/Audio/Ambience/Objects/crushing.ogg create mode 100644 Resources/Prototypes/Entities/Structures/Machines/material_reclaimer.yml create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-1.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-2.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-3.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-4.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-5.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-6.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-active.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-idle.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/icon.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/meta.json create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/panel.png create mode 100644 Resources/Textures/Structures/Machines/material_reclaimer.rsi/unlit.png diff --git a/Content.Client/Materials/MaterialReclaimerSystem.cs b/Content.Client/Materials/MaterialReclaimerSystem.cs new file mode 100644 index 0000000000..eec55eac3c --- /dev/null +++ b/Content.Client/Materials/MaterialReclaimerSystem.cs @@ -0,0 +1,9 @@ +using Content.Shared.Materials; + +namespace Content.Client.Materials; + +/// +public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem +{ + +} diff --git a/Content.Client/Recycling/RecyclerVisualizer.cs b/Content.Client/Recycling/RecyclerVisualizer.cs deleted file mode 100644 index 856ebffc5e..0000000000 --- a/Content.Client/Recycling/RecyclerVisualizer.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Content.Shared.Conveyor; -using Content.Shared.Recycling; -using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Serialization.Manager.Attributes; - -namespace Content.Client.Recycling -{ - [UsedImplicitly] - public sealed class RecyclerVisualizer : AppearanceVisualizer - { - [DataField("state_on")] - private string _stateOn = "grinder-o1"; - - [DataField("state_off")] - private string _stateOff = "grinder-o0"; - - [Obsolete("Subscribe to your component being initialised instead.")] - public override void InitializeEntity(EntityUid entity) - { - base.InitializeEntity(entity); - - var entMan = IoCManager.Resolve(); - if (!entMan.TryGetComponent(entity, out SpriteComponent? sprite) || - !entMan.TryGetComponent(entity, out AppearanceComponent? appearance)) - { - return; - } - - UpdateAppearance(appearance, sprite); - } - - [Obsolete("Subscribe to AppearanceChangeEvent instead.")] - public override void OnChangeData(AppearanceComponent component) - { - base.OnChangeData(component); - - var entities = IoCManager.Resolve(); - if (!entities.TryGetComponent(component.Owner, out SpriteComponent? sprite)) - { - return; - } - - UpdateAppearance(component, sprite); - } - - private void UpdateAppearance(AppearanceComponent component, SpriteComponent sprite) - { - var state = _stateOff; - if (component.TryGetData(ConveyorVisuals.State, out ConveyorState conveyorState) && conveyorState != ConveyorState.Off) - { - state = _stateOn; - } - - if (component.TryGetData(RecyclerVisuals.Bloody, out bool bloody) && bloody) - { - state += "bld"; - } - - sprite.LayerSetState(RecyclerVisualLayers.Main, state); - } - } - - public enum RecyclerVisualLayers : byte - { - Main - } -} diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index de8b78a55f..406b5aad81 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -14,10 +14,12 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; using System; -using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; +using Content.Shared.Chemistry.Reagent; using Content.Shared.Construction.Components; +using Content.Shared.FixedPoint; +using Content.Shared.Materials; namespace Content.IntegrationTests.Tests; @@ -31,10 +33,7 @@ public sealed class MaterialArbitrageTest [Test] public async Task NoMaterialArbitrage() { - // TODO check lathe resource prices? - // I CBF doing that atm because I know that will probably fail for most lathe recipies. - - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings() {NoClient = true}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings {NoClient = true}); var server = pairTracker.Pair.Server; var testMap = await PoolManager.CreateTestMap(pairTracker); @@ -51,8 +50,9 @@ public sealed class MaterialArbitrageTest var compFact = server.ResolveDependency(); var constructionName = compFact.GetComponentName(typeof(ConstructionComponent)); + var compositionName = compFact.GetComponentName(typeof(PhysicalCompositionComponent)); + var materialName = compFact.GetComponentName(typeof(MaterialComponent)); var destructibleName = compFact.GetComponentName(typeof(DestructibleComponent)); - var stackName = compFact.GetComponentName(typeof(StackComponent)); // construct inverted lathe recipe dictionary Dictionary latheRecipes = new(); @@ -84,6 +84,9 @@ public sealed class MaterialArbitrageTest { var materials = new Dictionary(); var graph = protoManager.Index(comp.Graph); + if (graph.Start == null) + continue; + if (!graph.TryPath(graph.Start, comp.Node, out var path) || path.Length == 0) continue; @@ -93,10 +96,25 @@ public sealed class MaterialArbitrageTest var edge = cur.GetEdge(node.Name); cur = node; + if (edge == null) + continue; + foreach (var step in edge.Steps) { - if (step is MaterialConstructionGraphStep materialStep) - materials[materialStep.MaterialPrototypeId] = materialStep.Amount + materials.GetValueOrDefault(materialStep.MaterialPrototypeId); + if (step is not MaterialConstructionGraphStep materialStep) + continue; + + var stackProto = protoManager.Index(materialStep.MaterialPrototypeId); + var spawnProto = protoManager.Index(stackProto.Spawn); + + if (!spawnProto.Components.TryGetValue(materialName, out var matreg)) + continue; + + var mat = (MaterialComponent) matreg.Component; + foreach (var (matId, amount) in mat.Materials) + { + materials[matId] = materialStep.Amount * amount + materials.GetValueOrDefault(matId); + } } } constructionMaterials.Add(id, materials); @@ -136,11 +154,16 @@ public sealed class MaterialArbitrageTest spawnedEnts[key] = spawnedEnts.GetValueOrDefault(key) + value.Max; var spawnProto = protoManager.Index(key); - if (!spawnProto.Components.TryGetValue(stackName, out var reg)) + + // get the amount of each material included in the entity + if (!spawnProto.Components.TryGetValue(materialName, out var matreg)) continue; + var mat = (MaterialComponent) matreg.Component; - var stack = (StackComponent) reg.Component; - spawnedMats[stack.StackTypeId] = value.Max + spawnedMats.GetValueOrDefault(stack.StackTypeId); + foreach (var (matId, amount) in mat.Materials) + { + spawnedMats[matId] = value.Max * amount + spawnedMats.GetValueOrDefault(matId); + } } } } @@ -211,18 +234,21 @@ public sealed class MaterialArbitrageTest continue; var spawnProto = protoManager.Index(spawnCompletion.Prototype); - if (!spawnProto.Components.TryGetValue(stackName, out var reg)) - continue; - var stack = (StackComponent) reg.Component; + if (!spawnProto.Components.TryGetValue(materialName, out var matreg)) + continue; - materials[stack.StackTypeId] = spawnCompletion.Amount + materials.GetValueOrDefault(stack.StackTypeId); + var mat = (MaterialComponent) matreg.Component; + foreach (var (matId, amount) in mat.Materials) + { + materials[matId] = spawnCompletion.Amount * amount + materials.GetValueOrDefault(matId); + } } } deconstructionMaterials.Add(id, materials); } - // This is functionally the same loop as before, but now testinng deconstruction rather than destruction. + // This is functionally the same loop as before, but now testing deconstruction rather than destruction. // This is pretty braindead. In principle construction graphs can have loops and whatnot. Assert.Multiple(async () => @@ -258,6 +284,58 @@ public sealed class MaterialArbitrageTest } }); + // create phyiscal composition dictionary + // this doesn't account for the chemicals in the composition + Dictionary physicalCompositions = new(); + foreach (var proto in protoManager.EnumeratePrototypes()) + { + if (proto.NoSpawn || proto.Abstract) + continue; + + if (!proto.Components.TryGetValue(compositionName, out var composition)) + continue; + + var comp = (PhysicalCompositionComponent) composition.Component; + physicalCompositions.Add(proto.ID, comp); + } + + // This is functionally the same loop as before, but now testing composition rather than destruction or deconstruction. + // This doesn't take into account chemicals generated when deconstructing. Maybe it should. + Assert.Multiple(async () => + { + foreach (var (id, compositionComponent) in physicalCompositions) + { + // Check cargo sell price + var materialPrice = await GetDeconstructedPrice(compositionComponent.MaterialComposition); + var chemicalPrice = await GetChemicalCompositionPrice(compositionComponent.ChemicalComposition); + var sumPrice = materialPrice + chemicalPrice; + var price = await GetPrice(id); + if (sumPrice > 0 && price > 0) + Assert.LessOrEqual(sumPrice, price, $"{id} increases in price after decomposed into raw materials"); + + // Check lathe production + if (latheRecipes.TryGetValue(id, out var recipe)) + { + foreach (var (matId, amount) in recipe.RequiredMaterials) + { + var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); + if (compositionComponent.MaterialComposition.TryGetValue(matId, out var numSpawned)) + Assert.LessOrEqual(numSpawned, actualAmount, $"The physical composition of {id} has more {matId} than required to produce via an (upgraded) lathe."); + } + } + + // Check construction. + if (constructionMaterials.TryGetValue(id, out var constructionMats)) + { + foreach (var (matId, amount) in constructionMats) + { + if (compositionComponent.MaterialComposition.TryGetValue(matId, out var numSpawned)) + Assert.LessOrEqual(numSpawned, amount, $"The physical composition of {id} has more {matId} than required to construct it."); + } + } + } + }); + await server.WaitPost(() => mapManager.DeleteMap(testMap.MapId)); await pairTracker.CleanReturnAsync(); @@ -293,8 +371,20 @@ public sealed class MaterialArbitrageTest double price = 0; foreach (var (id, num) in mats) { - var matProto = protoManager.Index(id).Spawn; - price += num * await GetPrice(matProto); + var matProto = protoManager.Index(id); + price += num * matProto.Price; + } + return price; + } + + + async Task GetChemicalCompositionPrice(Dictionary mats) + { + double price = 0; + foreach (var (id, num) in mats) + { + var reagentProto = protoManager.Index(id); + price += num.Double() * reagentProto.PricePerUnit; } return price; } diff --git a/Content.Server/Cargo/Systems/PricingSystem.cs b/Content.Server/Cargo/Systems/PricingSystem.cs index 6a1125ef36..3d4bd64a4c 100644 --- a/Content.Server/Cargo/Systems/PricingSystem.cs +++ b/Content.Server/Cargo/Systems/PricingSystem.cs @@ -213,7 +213,7 @@ public sealed class PricingSystem : EntitySystem { double price = 0; - if (TryComp(uid, out var material) && !HasComp(uid)) + if (TryComp(uid, out var material)) { var matPrice = GetMaterialPrice(material); if (TryComp(uid, out var stack)) @@ -229,8 +229,7 @@ public sealed class PricingSystem : EntitySystem { double price = 0; - if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(MaterialComponent)), out var materials) && - !prototype.Components.ContainsKey(_factory.GetComponentName(typeof(StackPriceComponent)))) + if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(MaterialComponent)), out var materials)) { var materialsComp = (MaterialComponent) materials.Component; var matPrice = GetMaterialPrice(materialsComp); @@ -276,7 +275,8 @@ public sealed class PricingSystem : EntitySystem var price = 0.0; if (TryComp(uid, out var stackPrice) && - TryComp(uid, out var stack)) + TryComp(uid, out var stack) && + !HasComp(uid)) // don't double count material prices { price += stack.Count * stackPrice.Price; } @@ -289,7 +289,8 @@ public sealed class PricingSystem : EntitySystem var price = 0.0; if (prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StackPriceComponent)), out var stackpriceProto) && - prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StackComponent)), out var stackProto)) + prototype.Components.TryGetValue(_factory.GetComponentName(typeof(StackComponent)), out var stackProto) && + !prototype.Components.ContainsKey(_factory.GetComponentName(typeof(MaterialComponent)))) { var stackPrice = (StackPriceComponent) stackpriceProto.Component; var stack = (StackComponent) stackProto.Component; diff --git a/Content.Server/Materials/MaterialReclaimerSystem.cs b/Content.Server/Materials/MaterialReclaimerSystem.cs new file mode 100644 index 0000000000..80c4a40bb6 --- /dev/null +++ b/Content.Server/Materials/MaterialReclaimerSystem.cs @@ -0,0 +1,261 @@ +using System.Linq; +using Content.Server.Chemistry.Components.SolutionManager; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.Construction; +using Content.Server.Fluids.EntitySystems; +using Content.Server.GameTicking; +using Content.Server.Nutrition.Components; +using Content.Server.Players; +using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Server.Stack; +using Content.Server.Wires; +using Content.Shared.Body.Systems; +using Content.Shared.Chemistry.Components; +using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Materials; +using Robust.Server.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Server.Materials; + +/// +public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + [Dependency] private readonly GameTicker _ticker = default!; + [Dependency] private readonly MaterialStorageSystem _materialStorage = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly SharedBodySystem _body = default!; //bobby + [Dependency] private readonly SpillableSystem _spillable = default!; + [Dependency] private readonly StackSystem _stack = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnInteractUsing, + before: new []{typeof(WiresSystem), typeof(SolutionTransferSystem)}); + SubscribeLocalEvent(OnSuicide); + SubscribeLocalEvent(OnActivePowerChanged); + } + private void OnStartup(EntityUid uid, MaterialReclaimerComponent component, ComponentStartup args) + { + component.OutputSolution = _solutionContainer.EnsureSolution(uid, component.SolutionContainerId); + } + + private void OnUpgradeExamine(EntityUid uid, MaterialReclaimerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade(Loc.GetString("material-reclaimer-upgrade-process-rate"), component.MaterialProcessRate / component.BaseMaterialProcessRate); + } + + private void OnRefreshParts(EntityUid uid, MaterialReclaimerComponent component, RefreshPartsEvent args) + { + var rating = args.PartRatings[component.MachinePartProcessRate] - 1; + component.MaterialProcessRate = component.BaseMaterialProcessRate * MathF.Pow(component.PartRatingProcessRateMultiplier, rating); + Dirty(component); + } + + private void OnPowerChanged(EntityUid uid, MaterialReclaimerComponent component, ref PowerChangedEvent args) + { + AmbientSound.SetAmbience(uid, args.Powered); + component.Powered = args.Powered; + Dirty(component); + } + + private void OnInteractUsing(EntityUid uid, MaterialReclaimerComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + // if we're trying to get a solution out of the reclaimer, don't destroy it + if (component.OutputSolution.Contents.Any()) + { + if (TryComp(args.Used, out var managerComponent) && + managerComponent.Solutions.Any(s => s.Value.AvailableVolume > 0)) + { + if (TryComp(args.Used, out var drink) && + !drink.Opened) + return; + + if (TryComp(args.Used, out var transfer) && + transfer.CanReceive) + return; + } + } + + args.Handled = TryStartProcessItem(uid, args.Used, component, args.User); + } + + private void OnSuicide(EntityUid uid, MaterialReclaimerComponent component, SuicideEvent args) + { + if (args.Handled) + return; + + args.SetHandled(SuicideKind.Bloodloss); + var victim = args.Victim; + if (TryComp(victim, out ActorComponent? actor) && + actor.PlayerSession.ContentData()?.Mind is { } mind) + { + _ticker.OnGhostAttempt(mind, false); + if (mind.OwnedEntity is { Valid: true } entity) + { + _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message"), entity); + } + } + + _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message-others", ("victim", Identity.Entity(victim, EntityManager))), + victim, + Filter.PvsExcept(victim, entityManager: EntityManager), true); + + _body.GibBody(victim, true); + _appearance.SetData(uid, RecyclerVisuals.Bloody, true); + } + + private void OnActivePowerChanged(EntityUid uid, ActiveMaterialReclaimerComponent component, ref PowerChangedEvent args) + { + if (!args.Powered) + TryFinishProcessItem(uid, null, component); + } + + /// + public override bool TryFinishProcessItem(EntityUid uid, MaterialReclaimerComponent? component = null, ActiveMaterialReclaimerComponent? active = null) + { + if (!Resolve(uid, ref component, ref active, false)) + return false; + + if (!base.TryFinishProcessItem(uid, component, active)) + return false; + + if (active.ReclaimingContainer.ContainedEntities.FirstOrNull() is not { } item) + return false; + + active.ReclaimingContainer.Remove(item); + Dirty(component); + + // scales the output if the process was interrupted. + var completion = 1f - Math.Clamp((float) Math.Round((active.EndTime - Timing.CurTime) / active.Duration), + 0f, 1f); + Reclaim(uid, item, completion, component); + + return true; + } + + /// + public override void Reclaim(EntityUid uid, + EntityUid item, + float completion = 1f, + MaterialReclaimerComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var xform = Transform(uid); + + SpawnMaterialsFromComposition(uid, item, completion * component.Efficiency, xform: xform); + SpawnChemicalsFromComposition(uid, item, completion, component, xform); + + if (CanGib(uid, item, component)) + { + _body.GibBody(item, true); + _appearance.SetData(uid, RecyclerVisuals.Bloody, true); + } + + QueueDel(item); + } + + private void SpawnMaterialsFromComposition(EntityUid reclaimer, + EntityUid item, + float efficiency, + MaterialStorageComponent? storage = null, + TransformComponent? xform = null, + PhysicalCompositionComponent? composition = null) + { + if (!Resolve(reclaimer, ref storage, ref xform, false)) + return; + + if (!Resolve(item, ref composition, false)) + return; + + foreach (var (material, amount) in composition.MaterialComposition) + { + var outputAmount = (int) (amount * efficiency); + _materialStorage.TryChangeMaterialAmount(reclaimer, material, outputAmount, storage); + } + + foreach (var (storedMaterial, storedAmount) in storage.Storage) + { + var stacks = _materialStorage.SpawnMultipleFromMaterial(storedAmount, storedMaterial, + xform.Coordinates, + out var materialOverflow); + var amountConsumed = storedAmount - materialOverflow; + _materialStorage.TryChangeMaterialAmount(reclaimer, storedMaterial, -amountConsumed, storage); + foreach (var stack in stacks) + { + _stack.TryMergeToContacts(stack); + } + } + } + + private void SpawnChemicalsFromComposition(EntityUid reclaimer, + EntityUid item, + float efficiency, + MaterialReclaimerComponent? reclaimerComponent = null, + TransformComponent? xform = null, + PhysicalCompositionComponent? composition = null) + { + if (!Resolve(reclaimer, ref reclaimerComponent, ref xform)) + return; + + var overflow = new Solution(); + var totalChemicals = new Dictionary(); + + if (Resolve(item, ref composition, false)) + { + foreach (var (key, value) in composition.ChemicalComposition) + { + totalChemicals[key] = totalChemicals.GetValueOrDefault(key) + value; + } + } + + // if the item we inserted has reagents, add it in. + if (TryComp(item, out var solutionContainer)) + { + foreach (var solution in solutionContainer.Solutions.Values) + { + foreach (var quantity in solution.Contents) + { + totalChemicals[quantity.ReagentId] = + totalChemicals.GetValueOrDefault(quantity.ReagentId) + quantity.Quantity; + } + } + } + + foreach (var (reagent, amount) in totalChemicals) + { + var outputAmount = amount * efficiency * reclaimerComponent.Efficiency; + _solutionContainer.TryAddReagent(reclaimer, reclaimerComponent.OutputSolution, reagent, outputAmount, + out var accepted); + var overflowAmount = outputAmount - accepted; + if (overflowAmount > 0) + { + overflow.AddReagent(reagent, overflowAmount); + } + } + + if (overflow.Volume > 0) + { + _spillable.SpillAt(reclaimer, overflow, reclaimerComponent.PuddleId, transformComponent: xform); + } + } +} diff --git a/Content.Server/Materials/MaterialStorageSystem.cs b/Content.Server/Materials/MaterialStorageSystem.cs index 4e4aaeec58..e79a47d067 100644 --- a/Content.Server/Materials/MaterialStorageSystem.cs +++ b/Content.Server/Materials/MaterialStorageSystem.cs @@ -48,9 +48,9 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem return false; if (!base.TryInsertMaterialEntity(user, toInsert, receiver, component)) return false; - _audio.PlayPvs(component.InsertingSound, component.Owner); - _popup.PopupEntity(Loc.GetString("machine-insert-item", ("user", user), ("machine", component.Owner), - ("item", toInsert)), component.Owner); + _audio.PlayPvs(component.InsertingSound, receiver); + _popup.PopupEntity(Loc.GetString("machine-insert-item", ("user", user), ("machine", receiver), + ("item", toInsert)), receiver); QueueDel(toInsert); // Logging @@ -67,16 +67,27 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem /// 1 biomass = 1 biomass in its stack, /// but 100 plasma = 1 sheet of plasma, etc. /// - [PublicAPI] public List SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates) { + return SpawnMultipleFromMaterial(amount, material, coordinates, out _); + } + + /// + /// Spawn an amount of a material in stack entities. + /// Note the 'amount' is material dependent. + /// 1 biomass = 1 biomass in its stack, + /// but 100 plasma = 1 sheet of plasma, etc. + /// + public List SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates, out int overflowMaterial) + { + overflowMaterial = 0; if (!_prototypeManager.TryIndex(material, out var stackType)) { Logger.Error("Failed to index material prototype " + material); return new List(); } - return SpawnMultipleFromMaterial(amount, stackType, coordinates); + return SpawnMultipleFromMaterial(amount, stackType, coordinates, out overflowMaterial); } /// @@ -85,8 +96,22 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem /// 1 biomass = 1 biomass in its stack, /// but 100 plasma = 1 sheet of plasma, etc. /// + [PublicAPI] public List SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates) { + return SpawnMultipleFromMaterial(amount, materialProto, coordinates, out _); + } + + /// + /// Spawn an amount of a material in stack entities. + /// Note the 'amount' is material dependent. + /// 1 biomass = 1 biomass in its stack, + /// but 100 plasma = 1 sheet of plasma, etc. + /// + public List SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates, out int overflowMaterial) + { + overflowMaterial = 0; + if (amount <= 0) return new List(); @@ -96,6 +121,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem var materialPerStack = material.Materials[materialProto.ID]; var amountToSpawn = amount / materialPerStack; + overflowMaterial = amount - amountToSpawn * materialPerStack; return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates); } } diff --git a/Content.Server/Physics/Controllers/ConveyorController.cs b/Content.Server/Physics/Controllers/ConveyorController.cs index 1d18eb70f4..31779ed1a7 100644 --- a/Content.Server/Physics/Controllers/ConveyorController.cs +++ b/Content.Server/Physics/Controllers/ConveyorController.cs @@ -1,8 +1,7 @@ using Content.Server.MachineLinking.Events; using Content.Server.MachineLinking.System; +using Content.Server.Materials; using Content.Server.Power.Components; -using Content.Server.Recycling; -using Content.Server.Recycling.Components; using Content.Shared.Conveyor; using Content.Shared.Maps; using Content.Shared.Physics; @@ -18,7 +17,7 @@ namespace Content.Server.Physics.Controllers; public sealed class ConveyorController : SharedConveyorController { [Dependency] private readonly FixtureSystem _fixtures = default!; - [Dependency] private readonly RecyclerSystem _recycler = default!; + [Dependency] private readonly MaterialReclaimerSystem _materialReclaimer = default!; [Dependency] private readonly SignalLinkerSystem _signalSystem = default!; [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -105,13 +104,7 @@ public sealed class ConveyorController : SharedConveyorController if (TryComp(uid, out var physics)) _broadphase.RegenerateContacts(physics); - if (TryComp(uid, out var recycler)) - { - if (component.State != ConveyorState.Off) - _recycler.EnableRecycler(recycler); - else - _recycler.DisableRecycler(recycler); - } + _materialReclaimer.SetReclaimerEnabled(uid, component.State != ConveyorState.Off); UpdateAppearance(uid, component); Dirty(component); diff --git a/Content.Server/Recycling/Components/RecyclableComponent.cs b/Content.Server/Recycling/Components/RecyclableComponent.cs deleted file mode 100644 index baa3794423..0000000000 --- a/Content.Server/Recycling/Components/RecyclableComponent.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Recycling.Components -{ - [RegisterComponent, Access(typeof(RecyclerSystem))] - public sealed class RecyclableComponent : Component - { - /// - /// The prototype that will be spawned on recycle. - /// - [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer))] public string? Prototype; - - /// - /// The amount of things that will be spawned on recycle. - /// - [DataField("amount")] public int Amount = 1; - - /// - /// Whether this is "safe" to recycle or not. - /// If this is false, the recycler's safety must be disabled to recycle it. - /// - [DataField("safe")] - public bool Safe { get; set; } = true; - } -} diff --git a/Content.Server/Recycling/Components/RecyclerComponent.cs b/Content.Server/Recycling/Components/RecyclerComponent.cs deleted file mode 100644 index 017c9881c1..0000000000 --- a/Content.Server/Recycling/Components/RecyclerComponent.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Shared.Recycling; -using Robust.Shared.Audio; - -namespace Content.Server.Recycling.Components -{ - // TODO: Add sound and safe beep - [RegisterComponent] - [Access(typeof(RecyclerSystem))] - public sealed class RecyclerComponent : Component - { - [Dependency] private readonly IEntityManager _entMan = default!; - - [DataField("enabled")] - public bool Enabled; - - /// - /// The percentage of material that will be recovered - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("efficiency")] - internal float Efficiency = 0.25f; - - /// - /// Default sound to play when recycling - /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("sound")] - public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Effects/saw.ogg"); - - // Ratelimit sounds to avoid spam - public TimeSpan LastSound; - - public int ItemsProcessed; - } -} diff --git a/Content.Server/Recycling/RecyclerSystem.cs b/Content.Server/Recycling/RecyclerSystem.cs deleted file mode 100644 index de7c558b4c..0000000000 --- a/Content.Server/Recycling/RecyclerSystem.cs +++ /dev/null @@ -1,199 +0,0 @@ -using Content.Server.Audio; -using Content.Server.Body.Systems; -using Content.Server.GameTicking; -using Content.Server.Players; -using Content.Server.Popups; -using Content.Server.Power.Components; -using Content.Server.Power.EntitySystems; -using Content.Server.Recycling.Components; -using Content.Shared.Audio; -using Content.Shared.Body.Components; -using Content.Shared.Emag.Components; -using Content.Shared.Emag.Systems; -using Content.Shared.Examine; -using Content.Shared.IdentityManagement; -using Content.Shared.Interaction.Events; -using Content.Shared.Recycling; -using Content.Shared.Tag; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Physics.Events; -using Robust.Shared.Player; -using Robust.Shared.Timing; - -namespace Content.Server.Recycling -{ - public sealed class RecyclerSystem : EntitySystem - { - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly AmbientSoundSystem _ambience = default!; - [Dependency] private readonly BodySystem _bodySystem = default!; - [Dependency] private readonly GameTicker _ticker = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly TagSystem _tags = default!; - [Dependency] private readonly AudioSystem _soundSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; - - private const string RecyclerColliderName = "brrt"; - - private const float RecyclerSoundCooldown = 0.8f; - - public override void Initialize() - { - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnCollide); - SubscribeLocalEvent(OnEmagged); - SubscribeLocalEvent(OnSuicide); - SubscribeLocalEvent(OnPowerChanged); - } - - private void OnExamined(EntityUid uid, RecyclerComponent component, ExaminedEvent args) - { - args.PushMarkup(Loc.GetString("recycler-count-items", ("items", component.ItemsProcessed))); - } - - private void OnSuicide(EntityUid uid, RecyclerComponent component, SuicideEvent args) - { - if (args.Handled) return; - args.SetHandled(SuicideKind.Bloodloss); - var victim = args.Victim; - if (TryComp(victim, out ActorComponent? actor) && - actor.PlayerSession.ContentData()?.Mind is { } mind) - { - _ticker.OnGhostAttempt(mind, false); - if (mind.OwnedEntity is { Valid: true } entity) - { - _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message"), entity); - } - } - - _popup.PopupEntity(Loc.GetString("recycler-component-suicide-message-others", ("victim", Identity.Entity(victim, EntityManager))), - victim, - Filter.PvsExcept(victim, entityManager: EntityManager), true); - - if (TryComp(victim, out var body)) - { - _bodySystem.GibBody(victim, true, body); - } - - Bloodstain(component); - } - - public void EnableRecycler(RecyclerComponent component) - { - if (component.Enabled) return; - - component.Enabled = true; - - if (TryComp(component.Owner, out ApcPowerReceiverComponent? apcPower)) - { - _ambience.SetAmbience(component.Owner, apcPower.Powered); - } - else - { - _ambience.SetAmbience(component.Owner, true); - } - - } - - public void DisableRecycler(RecyclerComponent component) - { - if (!component.Enabled) return; - - component.Enabled = false; - _ambience.SetAmbience(component.Owner, false); - } - - private void OnPowerChanged(EntityUid uid, RecyclerComponent component, ref PowerChangedEvent args) - { - if (component.Enabled) - { - _ambience.SetAmbience(uid, args.Powered); - } - } - - private void OnCollide(EntityUid uid, RecyclerComponent component, ref StartCollideEvent args) - { - if (component.Enabled && args.OurFixture.ID != RecyclerColliderName) - return; - - if (TryComp(uid, out ApcPowerReceiverComponent? apcPower)) - { - if (!apcPower.Powered) - return; - } - - Recycle(component, args.OtherFixture.Body.Owner); - } - - private void Recycle(RecyclerComponent component, EntityUid entity) - { - RecyclableComponent? recyclable = null; - - // Can only recycle things that are tagged trash or recyclable... And also check the safety of the thing to recycle. - if (!_tags.HasAnyTag(entity, "Trash", "Recyclable") && - (!TryComp(entity, out recyclable) || !recyclable.Safe && !HasComp(component.Owner))) - { - return; - } - - // TODO: Prevent collision with recycled items - - // Mobs are a special case! - if (CanGib(component, entity)) - { - _bodySystem.GibBody(entity, true, Comp(entity)); - Bloodstain(component); - return; - } - - if (recyclable == null) - QueueDel(entity); - else - Recycle(recyclable, component.Efficiency); - - if (component.Sound != null && (_timing.CurTime - component.LastSound).TotalSeconds > RecyclerSoundCooldown) - { - _soundSystem.PlayPvs(component.Sound, component.Owner, AudioHelpers.WithVariation(0.01f).WithVolume(-3)); - component.LastSound = _timing.CurTime; - } - - component.ItemsProcessed++; - } - - private bool CanGib(RecyclerComponent component, EntityUid entity) - { - return HasComp(entity) && HasComp(component.Owner) && - this.IsPowered(component.Owner, EntityManager); - } - - public void Bloodstain(RecyclerComponent component) - { - if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearance)) - { - _appearanceSystem.SetData(component.Owner, RecyclerVisuals.Bloody, true, appearance); - } - } - - private void Recycle(RecyclableComponent component, float efficiency = 1f) - { - if (!string.IsNullOrEmpty(component.Prototype)) - { - var xform = Transform(component.Owner); - - for (var i = 0; i < Math.Max(component.Amount * efficiency, 1); i++) - { - Spawn(component.Prototype, xform.Coordinates); - } - } - - QueueDel(component.Owner); - } - - private void OnEmagged(EntityUid uid, RecyclerComponent component, ref GotEmaggedEvent args) - { - // no fancy conditions - args.Handled = true; - } - } -} diff --git a/Content.Shared/Lathe/LatheComponent.cs b/Content.Shared/Lathe/LatheComponent.cs index 752eec5aba..4cfd94c80f 100644 --- a/Content.Shared/Lathe/LatheComponent.cs +++ b/Content.Shared/Lathe/LatheComponent.cs @@ -85,7 +85,7 @@ namespace Content.Shared.Lathe [DataField("partRatingMaterialUseMultiplier")] public float PartRatingMaterialUseMultiplier = DefaultPartRatingMaterialUseMultiplier; - public const float DefaultPartRatingMaterialUseMultiplier = 0.75f; + public const float DefaultPartRatingMaterialUseMultiplier = 0.85f; #endregion } diff --git a/Content.Shared/Materials/ActiveMaterialReclaimerComponent.cs b/Content.Shared/Materials/ActiveMaterialReclaimerComponent.cs new file mode 100644 index 0000000000..b62be502e3 --- /dev/null +++ b/Content.Shared/Materials/ActiveMaterialReclaimerComponent.cs @@ -0,0 +1,32 @@ +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Materials; + +/// +/// Tracker component for the process of reclaiming entities +/// +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedMaterialReclaimerSystem))] +public sealed class ActiveMaterialReclaimerComponent : Component +{ + /// + /// Container used to store the item currently being reclaimed + /// + [ViewVariables(VVAccess.ReadWrite)] + public Container ReclaimingContainer = default!; + + /// + /// When the reclaiming process ends. + /// + [DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan EndTime; + + /// + /// The length of the reclaiming process. + /// Used for calculations. + /// + [DataField("duration"), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan Duration; +} diff --git a/Content.Shared/Materials/CollideMaterialReclaimerComponent.cs b/Content.Shared/Materials/CollideMaterialReclaimerComponent.cs new file mode 100644 index 0000000000..4d9b072ad2 --- /dev/null +++ b/Content.Shared/Materials/CollideMaterialReclaimerComponent.cs @@ -0,0 +1,16 @@ +namespace Content.Shared.Materials; + +/// +/// Valid items that collide with an entity with this component +/// will begin to be reclaimed. +/// +/// +[RegisterComponent] +public sealed class CollideMaterialReclaimerComponent : Component +{ + /// + /// The fixture that starts reclaiming on collision. + /// + [DataField("fixtureId")] + public string FixtureId = "brrt"; +} diff --git a/Content.Shared/Materials/MaterialReclaimerComponent.cs b/Content.Shared/Materials/MaterialReclaimerComponent.cs new file mode 100644 index 0000000000..aaaa656f65 --- /dev/null +++ b/Content.Shared/Materials/MaterialReclaimerComponent.cs @@ -0,0 +1,177 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Construction.Prototypes; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Materials; + +/// +/// This is a machine that handles converting entities +/// into the raw materials and chemicals that make them up. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedMaterialReclaimerSystem))] +public sealed class MaterialReclaimerComponent : Component +{ + /// + /// Whether or not the machine has power. We put it here + /// so we can network and predict it. + /// + [DataField("powered"), ViewVariables(VVAccess.ReadWrite)] + public bool Powered; + + /// + /// An "enable" toggle for things like interfacing with machine linking + /// + [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)] + public bool Enabled = true; + + /// + /// How efficiently the materials are reclaimed. + /// In practice, a multiplier per material when calculating the output of the reclaimer. + /// + [DataField("efficiency"), ViewVariables(VVAccess.ReadWrite)] + public float Efficiency = 1f; + + /// + /// Whether or not the process + /// speed scales with the amount of materials being processed + /// or if it's just + /// + [DataField("scaleProcessSpeed")] + public bool ScaleProcessSpeed = true; + + /// + /// How quickly it takes to consume X amount of materials per second. + /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. + /// + [DataField("baseMaterialProcessRate"), ViewVariables(VVAccess.ReadWrite)] + public float BaseMaterialProcessRate = 100f; + + /// + /// How quickly it takes to consume X amount of materials per second. + /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. + /// + [DataField("materialProcessRate"), ViewVariables(VVAccess.ReadWrite)] + public float MaterialProcessRate = 100f; + + /// + /// Machine part whose rating modifies + /// + [DataField("machinePartProcessRate", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string MachinePartProcessRate = "Manipulator"; + + /// + /// How much the machine part quality affects the + /// + [DataField("partRatingProcessRateMultiplier"), ViewVariables(VVAccess.ReadWrite)] + public float PartRatingProcessRateMultiplier = 1.5f; + + /// + /// The minimum amount fo time it can take to process an entity. + /// this value supercedes the calculated one using + /// + [DataField("minimumProcessDuration"), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan MinimumProcessDuration = TimeSpan.FromSeconds(0.5f); + + /// + /// The id of our output solution + /// + [DataField("solutionContainerId"), ViewVariables(VVAccess.ReadWrite)] + public string SolutionContainerId = "output"; + + /// + /// The prototype for the puddle + /// + [DataField("puddleId", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string PuddleId = "PuddleSmear"; + + /// + /// The solution itself. + /// + [ViewVariables(VVAccess.ReadWrite)] + public Solution OutputSolution = default!; + + /// + /// a whitelist for what entities can be inserted into this reclaimer + /// + [DataField("whitelist")] + public EntityWhitelist? Whitelist; + + /// + /// a blacklist for what entities cannot be inserted into this reclaimer + /// + [DataField("blacklist")] + public EntityWhitelist? Blacklist; + + /// + /// The sound played when something is being processed. + /// + [DataField("sound")] + public SoundSpecifier? Sound; + + /// + /// whether or not we cut off the sound early when the reclaiming ends. + /// + [DataField("cutOffSound")] + public bool CutOffSound = true; + + /// + /// When the next sound will be allowed to be played. Used to prevent spam. + /// + [DataField("nextSound", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextSound; + + /// + /// Minimum time inbetween each + /// + [DataField("soundCooldown")] + public TimeSpan SoundCooldown = TimeSpan.FromSeconds(0.8f); + + public IPlayingAudioStream? Stream; + + /// + /// A counter of how many items have been processed + /// + /// + /// I saw this on the recycler and i'm porting it because it's cute af + /// + [DataField("itemsProcessed")] + public int ItemsProcessed; +} + +[Serializable, NetSerializable] +public sealed class MaterialReclaimerComponentState : ComponentState +{ + public bool Powered; + + public bool Enabled; + + public float MaterialProcessRate; + + public int ItemsProcessed; + + public MaterialReclaimerComponentState(bool powered, bool enabled, float materialProcessRate, int itemsProcessed) + { + Powered = powered; + Enabled = enabled; + MaterialProcessRate = materialProcessRate; + ItemsProcessed = itemsProcessed; + } +} + +[NetSerializable, Serializable] +public enum RecyclerVisuals +{ + Bloody +} + +public enum RecyclerVisualLayers : byte +{ + Main, + Bloody +} diff --git a/Content.Shared/Materials/MaterialStorageComponent.cs b/Content.Shared/Materials/MaterialStorageComponent.cs index 491c4d3cb7..71b4056042 100644 --- a/Content.Shared/Materials/MaterialStorageComponent.cs +++ b/Content.Shared/Materials/MaterialStorageComponent.cs @@ -15,6 +15,12 @@ public sealed class MaterialStorageComponent : Component [DataField("storage", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] public Dictionary Storage { get; set; } = new(); + /// + /// Whether or not interacting with the materialstorage inserts the material in hand. + /// + [DataField("insertOnInteract")] + public bool InsertOnInteract = true; + /// /// How much material the storage can store in total. /// diff --git a/Content.Shared/Materials/PhysicalCompositionComponent.cs b/Content.Shared/Materials/PhysicalCompositionComponent.cs new file mode 100644 index 0000000000..2d23a8ccf6 --- /dev/null +++ b/Content.Shared/Materials/PhysicalCompositionComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; + +namespace Content.Shared.Materials; + +/// +/// This is used for assigning an innate material/chemical composition to an entity. +/// These aren't materials per se, but rather the materials which "make up" an entity. +/// This also isn't something that should exist simultaneously with . +/// +/// +/// The reason for duel material/chemical is for the eventual +/// combination of the two systems. +/// +[RegisterComponent] +public sealed class PhysicalCompositionComponent : Component +{ + /// + /// The materials that "make up" this entity + /// + [DataField("materialComposition", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] + public Dictionary MaterialComposition = new(); + + /// + /// The chemicals that "make up" this entity + /// + [DataField("chemicalComposition", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] + public Dictionary ChemicalComposition = new(); +} diff --git a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs new file mode 100644 index 0000000000..18a744e80d --- /dev/null +++ b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs @@ -0,0 +1,249 @@ +using System.Linq; +using Content.Shared.Administration.Logs; +using Content.Shared.Audio; +using Content.Shared.Body.Components; +using Content.Shared.Database; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; +using Content.Shared.Examine; +using Content.Shared.Mobs.Components; +using Content.Shared.Stacks; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; + +namespace Content.Shared.Materials; + +/// +/// Handles interactions and logic related to , +/// , and . +/// +public abstract class SharedMaterialReclaimerSystem : EntitySystem +{ + [Dependency] private readonly ISharedAdminLogManager _adminLog = default!; + [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] protected readonly SharedAmbientSoundSystem AmbientSound = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + + public const string ActiveReclaimerContainerId = "active-material-reclaimer-container"; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnCollide); + SubscribeLocalEvent(OnActiveStartup); + SubscribeLocalEvent(OnActiveUnpaused); + } + + private void OnGetState(EntityUid uid, MaterialReclaimerComponent component, ref ComponentGetState args) + { + args.State = new MaterialReclaimerComponentState(component.Powered, + component.Enabled, + component.MaterialProcessRate, + component.ItemsProcessed); + } + + private void OnHandleState(EntityUid uid, MaterialReclaimerComponent component, ref ComponentHandleState args) + { + if (args.Current is not MaterialReclaimerComponentState state) + return; + component.Powered = state.Powered; + component.Enabled = state.Enabled; + component.MaterialProcessRate = state.MaterialProcessRate; + component.ItemsProcessed = state.ItemsProcessed; + } + + private void OnUnpaused(EntityUid uid, MaterialReclaimerComponent component, ref EntityUnpausedEvent args) + { + component.NextSound += args.PausedTime; + } + + private void OnExamined(EntityUid uid, MaterialReclaimerComponent component, ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("recycler-count-items", ("items", component.ItemsProcessed))); + } + + private void OnEmagged(EntityUid uid, MaterialReclaimerComponent component, ref GotEmaggedEvent args) + { + args.Handled = true; + } + + private void OnCollide(EntityUid uid, CollideMaterialReclaimerComponent component, ref StartCollideEvent args) + { + if (args.OurFixture.ID != component.FixtureId) + return; + if (!TryComp(uid, out var reclaimer)) + return; + TryStartProcessItem(uid, args.OtherFixture.Body.Owner, reclaimer); + } + + private void OnActiveStartup(EntityUid uid, ActiveMaterialReclaimerComponent component, ComponentStartup args) + { + component.ReclaimingContainer = _container.EnsureContainer(uid, ActiveReclaimerContainerId); + } + + private void OnActiveUnpaused(EntityUid uid, ActiveMaterialReclaimerComponent component, ref EntityUnpausedEvent args) + { + component.EndTime += args.PausedTime; + } + + /// + /// Tries to start processing an item via a . + /// + public bool TryStartProcessItem(EntityUid uid, EntityUid item, MaterialReclaimerComponent? component = null, EntityUid? user = null) + { + if (!Resolve(uid, ref component)) + return false; + + if (!CanStart(uid, component)) + return false; + + if (component.Whitelist != null && !component.Whitelist.IsValid(item) || + HasComp(item) && !CanGib(uid, item, component)) // whitelist? We be gibbing, boy! + return false; + + if (component.Blacklist != null && component.Blacklist.IsValid(item)) + return false; + + if (user != null) + { + _adminLog.Add(LogType.Action, LogImpact.High, + $"{ToPrettyString(user.Value):player} destroyed {ToPrettyString(item)} in the material reclaimer, {ToPrettyString(uid)}"); + } + + if (Timing.CurTime > component.NextSound) + component.Stream = _audio.PlayPvs(component.Sound, uid); + component.NextSound = Timing.CurTime + component.SoundCooldown; + + var duration = GetReclaimingDuration(uid, item, component); + // if it's instant, don't bother with all the active comp stuff. + if (duration == TimeSpan.Zero) + { + Reclaim(uid, item, 1, component); + return true; + } + + var active = EnsureComp(uid); + active.Duration = duration; + active.EndTime = Timing.CurTime + duration; + active.ReclaimingContainer.Insert(item); + return true; + } + + /// + /// Finishes processing an item, freeing up the the reclaimer. + /// + /// + /// This doesn't reclaim the entity itself, but rather ends the formal + /// process started with . + /// The actual reclaiming happens in + /// + public virtual bool TryFinishProcessItem(EntityUid uid, MaterialReclaimerComponent? component = null, ActiveMaterialReclaimerComponent? active = null) + { + if (!Resolve(uid, ref component, ref active, false)) + return false; + + RemCompDeferred(uid, active); + return true; + } + + /// + /// Spawns the materials and chemicals associated + /// with an entity. Also deletes the item. + /// + public virtual void Reclaim(EntityUid uid, + EntityUid item, + float completion = 1f, + MaterialReclaimerComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.ItemsProcessed++; + if (component.CutOffSound) + component.Stream?.Stop(); + + Dirty(component); + } + + /// + /// Sets the Enabled field on the reclaimer. + /// + public void SetReclaimerEnabled(EntityUid uid, bool enabled, MaterialReclaimerComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + return; + component.Enabled = enabled; + AmbientSound.SetAmbience(uid, enabled && component.Powered); + Dirty(component); + } + + /// + /// Whether or not the specified reclaimer can currently + /// begin reclaiming another entity. + /// + public bool CanStart(EntityUid uid, MaterialReclaimerComponent component) + { + if (HasComp(uid)) + return false; + + return component.Powered && component.Enabled; + } + + /// + /// Whether or not the reclaimer satisfies the conditions + /// allowing it to gib/reclaim a living creature. + /// + public bool CanGib(EntityUid uid, EntityUid victim, MaterialReclaimerComponent component) + { + return component.Powered && + component.Enabled && + HasComp(victim) && + HasComp(uid); + } + + /// + /// Gets the duration of processing a specified entity. + /// Processing is calculated from the sum of the materials within the entity. + /// It does not regard the chemicals within it. + /// + public TimeSpan GetReclaimingDuration(EntityUid reclaimer, + EntityUid item, + MaterialReclaimerComponent? reclaimerComponent = null, + PhysicalCompositionComponent? compositionComponent = null) + { + if (!Resolve(reclaimer, ref reclaimerComponent)) + return TimeSpan.Zero; + + if (!reclaimerComponent.ScaleProcessSpeed || + !Resolve(item, ref compositionComponent, false)) + return reclaimerComponent.MinimumProcessDuration; + + var materialSum = compositionComponent.MaterialComposition.Values.Sum(); + materialSum *= CompOrNull(item)?.Count ?? 1; + var duration = TimeSpan.FromSeconds(materialSum / reclaimerComponent.MaterialProcessRate); + if (duration < reclaimerComponent.MinimumProcessDuration) + duration = reclaimerComponent.MinimumProcessDuration; + return duration; + } + + /// + public override void Update(float frameTime) + { + base.Update(frameTime); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var active, out var reclaimer)) + { + if (Timing.CurTime < active.EndTime) + continue; + TryFinishProcessItem(uid, reclaimer, active); + } + } +} diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs index 2ade6b5705..7daea6977b 100644 --- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs +++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs @@ -252,7 +252,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem private void OnInteractUsing(EntityUid uid, MaterialStorageComponent component, InteractUsingEvent args) { - if (args.Handled) + if (args.Handled || !component.InsertOnInteract) return; args.Handled = TryInsertMaterialEntity(args.User, args.Used, uid, component); } diff --git a/Content.Shared/Recycling/SharedRecyclerComponent.cs b/Content.Shared/Recycling/SharedRecyclerComponent.cs deleted file mode 100644 index 33cbc5165c..0000000000 --- a/Content.Shared/Recycling/SharedRecyclerComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Recycling -{ - [NetSerializable, Serializable] - public enum RecyclerVisuals - { - Bloody - } -} diff --git a/Resources/Audio/Ambience/Objects/attributions.yml b/Resources/Audio/Ambience/Objects/attributions.yml index 8322c91a48..c084f551c7 100644 --- a/Resources/Audio/Ambience/Objects/attributions.yml +++ b/Resources/Audio/Ambience/Objects/attributions.yml @@ -1,4 +1,9 @@ - files: ["anomaly_generator.ogg"] license: "CC0-1.0" copyright: "Created by steaq, converted Mono and .ogg by EmoGarbage" - source: "https://freesound.org/people/steaq/sounds/509249/" \ No newline at end of file + source: "https://freesound.org/people/steaq/sounds/509249/" + +- files: ["crushing.ogg"] + license: "CC-BY-4.0" + copyright: "Created by juskiddink, converted Mono and .ogg by EmoGarbage" + source: "https://freesound.org/people/juskiddink/sounds/66956/" \ No newline at end of file diff --git a/Resources/Audio/Ambience/Objects/crushing.ogg b/Resources/Audio/Ambience/Objects/crushing.ogg new file mode 100644 index 0000000000000000000000000000000000000000..401ae15f6f04a289bcef1a98fd2ef920fe04eb1b GIT binary patch literal 36925 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 literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/materials/materials.ftl b/Resources/Locale/en-US/materials/materials.ftl index f3cb457d73..336b7f1206 100644 --- a/Resources/Locale/en-US/materials/materials.ftl +++ b/Resources/Locale/en-US/materials/materials.ftl @@ -18,3 +18,6 @@ materials-plasma = plasma materials-plastic = plastic materials-wood = wood materials-uranium = uranium + +# Material Reclaimer +material-reclaimer-upgrade-process-rate = process rate \ No newline at end of file diff --git a/Resources/Prototypes/Body/Parts/silicon.yml b/Resources/Prototypes/Body/Parts/silicon.yml index 90e682282f..266c1dd5a6 100644 --- a/Resources/Prototypes/Body/Parts/silicon.yml +++ b/Resources/Prototypes/Body/Parts/silicon.yml @@ -16,6 +16,9 @@ - type: Tag tags: - Trash + - type: PhysicalComposition + materialComposition: + Steel: 25 - type: entity id: LeftArmBorg @@ -36,7 +39,6 @@ tags: - Trash - BorgArm - - type: Recyclable - type: entity id: RightArmBorg @@ -57,4 +59,3 @@ tags: - Trash - BorgArm - - type: Recyclable diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index 5c605abffc..f10f43bd7a 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -364,6 +364,7 @@ - SheetPlastic - SheetRGlass - SheetGlass1 + - MaterialReclaimerMachineCircuitboard # Electromagnetic Theory Technology Tree diff --git a/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml b/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml index d23807a1fa..0c400828d8 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml @@ -10,6 +10,11 @@ - type: Clothing slots: [belt] quickEquip: false + - type: PhysicalComposition + materialComposition: + Cloth: 50 + - type: StaticPrice + price: 25 - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Clothing/Head/misc.yml b/Resources/Prototypes/Entities/Clothing/Head/misc.yml index 1f1a8b4146..b010f1a978 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/misc.yml @@ -125,6 +125,9 @@ sprite: Clothing/Head/Misc/cone.rsi - type: Clothing sprite: Clothing/Head/Misc/cone.rsi + - type: PhysicalComposition #you can't just pass up some free plastic! + materialComposition: + Plastic: 100 - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Entities/Clothing/Head/welding.yml b/Resources/Prototypes/Entities/Clothing/Head/welding.yml index f410104784..94254e625f 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/welding.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/welding.yml @@ -8,6 +8,12 @@ - type: FlashImmunity - type: IdentityBlocker - type: EyeProtection + - type: PhysicalComposition + materialComposition: + Steel: 500 + Glass: 100 + - type: StaticPrice + price: 50 - type: entity parent: WeldingMaskBase diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml index 3f5d9f8755..1298ac5322 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml @@ -137,7 +137,10 @@ - type: Tag tags: - PetWearable - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Steel: 50 + Plastic: 100 - type: entity parent: ClothingMaskBase diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index c3946c6606..08fd95b278 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -259,6 +259,10 @@ sprintModifier: 0.75 - type: Item size: 100 + - type: Tag + tags: + - WhitelistChameleon + - HighRiskItem - type: ExplosionResistance damageCoefficient: 0.1 - type: ToggleableClothing diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml index f8398f93d0..6fc0525604 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml @@ -58,6 +58,10 @@ sprintModifier: 1 enabled: false - type: NoSlip + - type: Tag + tags: + - WhitelistChameleon + - HighRiskItem - type: StaticPrice price: 750 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index bc42856392..5bd5deb20b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -824,7 +824,6 @@ tags: - Trash - CannotSuicide - - type: Recyclable - type: Respirator damage: types: @@ -1997,7 +1996,6 @@ tags: - Trash - CannotSuicide - - type: Recyclable - type: Respirator damage: types: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index e708eb9d9b..5dcc5ec98a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -38,8 +38,6 @@ noRot: true drawdepth: Mobs netsync: false - - type: Recyclable - safe: false - type: Faction factions: - SimpleNeutral diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index e5e7efcf2c..71ed356e28 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -139,8 +139,6 @@ - type: Polymorphable - type: Pullable - type: Buckle - - type: Recyclable - safe: false - type: StandingState - type: Alerts - type: NameIdentifier diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 8c7181b00c..7a12adb345 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -56,8 +56,6 @@ - type: Pullable - type: Examiner - type: Puller - - type: Recyclable - safe: false - type: StandingState - type: Alerts - type: Tag diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index d49205a2a3..fea68b0c2a 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -280,8 +280,6 @@ spawned: - id: FoodMeat amount: 5 - # - type: Recyclable Turns out turning off recycler safeties without considering the instagib is a bad idea - # safe: false - type: Speech speechSounds: Alto - type: Vocal diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml index 6e2d74a855..1e355ec798 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml @@ -72,6 +72,9 @@ damage: types: Blunt: 5 + - type: PhysicalComposition + materialComposition: + Glass: 25 # Transformable container - normal glass - type: entity @@ -2164,7 +2167,6 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml index 4a40578fd4..b03d917559 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml @@ -51,10 +51,12 @@ types: Blunt: 0 - type: ItemCooldown - - type: Recyclable - type: SpaceGarbage - type: TrashOnEmpty solution: drink + - type: PhysicalComposition + materialComposition: + Steel: 50 #reduce, reuse, recycle - type: entity parent: DrinkCanBaseFull diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml index 1d15533407..c91593bfc2 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml @@ -66,6 +66,9 @@ - type: SolutionContainerVisuals maxFillLevels: 3 fillBaseName: icon- + - type: PhysicalComposition + materialComposition: + Glass: 25 - type: entity parent: DrinkBaseMug @@ -130,6 +133,9 @@ components: - type: Sprite sprite: Objects/Consumable/Drinks/mug_metal.rsi + - type: PhysicalComposition + materialComposition: + Steel: 25 - type: entity parent: DrinkBaseMug diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_flasks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_flasks.yml index d2a5a52f3c..ae2daac052 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_flasks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_flasks.yml @@ -7,6 +7,9 @@ - type: Drink - type: Sprite sprite: Objects/Consumable/Drinks/shinyflask.rsi + - type: PhysicalComposition + materialComposition: + Steel: 50 - type: entity parent: DrinkBase diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml index 2fbc947041..684681074e 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml @@ -29,6 +29,9 @@ interfaces: - key: enum.TransferAmountUiKey.Key type: TransferAmountBoundUserInterface + - type: PhysicalComposition + materialComposition: + Steel: 50 - type: entity parent: DrinkGlassBase diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml index 03b04d7e55..a1a1eef3f3 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml @@ -68,7 +68,9 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Glass: 100 - type: SpaceGarbage # Containers diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml index e6392877f3..04693e0596 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml @@ -69,7 +69,9 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Glass: 50 - type: SpaceGarbage - type: StaticPrice price: 0 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml index 39f2bfa1a8..f5b0ff6880 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml @@ -45,7 +45,9 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Glass: 100 - type: SpaceGarbage - type: entity @@ -61,7 +63,6 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage # Small Plate @@ -147,5 +148,7 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: SpaceGarbage diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml index 84bbb670be..80129c01e0 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml @@ -28,6 +28,9 @@ Blunt: 3 - type: Damageable damageContainer: Inorganic + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: entity abstract: true @@ -46,6 +49,9 @@ - type: Tag tags: - Trash + - type: PhysicalComposition + materialComposition: + Steel: 100 # Tins # Need something that you can open these tins with. I suggest a prying or cutting tool. diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml index 41eb9ca924..9ecfae3324 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml @@ -110,7 +110,6 @@ tags: - Egg - Trash - - type: Recyclable - type: SpaceGarbage # Egg diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/food_base.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/food_base.yml index 31c120b6aa..a9f0609914 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/food_base.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/food_base.yml @@ -10,7 +10,6 @@ flavors: - food - type: Food - - type: Recyclable - type: SpaceGarbage - type: Sprite netsync: false diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml index c9e973ff14..12b93c35a8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml @@ -275,5 +275,4 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml index 925d5f86ab..8c8c35388d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml @@ -225,6 +225,7 @@ - type: Tag tags: - Raw + - HighRiskItem - type: Sprite state: corgi - type: SolutionContainerManager diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 1c8d38aa78..d20aacdc1f 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -213,7 +213,6 @@ Quantity: 4 - type: Extractable grindableSolutionName: food - - type: Recyclable - type: SpaceGarbage - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml index bfa59aaf64..ee0c29fbe9 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml @@ -387,7 +387,9 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: SpaceGarbage - type: StaticPrice price: 0 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cartons.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cartons.yml index d7cc6a01fb..52fa3dd50b 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cartons.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cartons.yml @@ -24,7 +24,9 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Plastic: 50 - type: SpaceGarbage - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml index 65e4772bff..777d396869 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml @@ -12,7 +12,6 @@ tags: - Cigarette - Trash - - type: Recyclable - type: SpaceGarbage - type: Clothing sprite: Objects/Consumable/Smokeables/Cigarettes/cigarette.rsi diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml index 0e4a78bca0..42aa84d43d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml @@ -12,7 +12,6 @@ tags: - Cigarette - Trash - - type: Recyclable - type: SpaceGarbage - type: Clothing sprite: Objects/Consumable/Smokeables/Cannabis/joint.rsi @@ -45,7 +44,6 @@ tags: - Cigarette - Trash - - type: Recyclable - type: SpaceGarbage - type: Clothing sprite: Objects/Consumable/Smokeables/Cannabis/blunt.rsi diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/packs.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/packs.yml index c508333057..0368c599ac 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/packs.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/packs.yml @@ -8,7 +8,9 @@ tags: - CigPack - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Steel: 50 - type: SpaceGarbage - type: Storage capacity: 6 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml index e62db9e40f..c4b53f45b5 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml @@ -59,7 +59,6 @@ tags: - RollingPaper - Trash - - type: Recyclable - type: SpaceGarbage - type: entity @@ -90,7 +89,6 @@ tags: - CigFilter - Trash - - type: Recyclable - type: entity id: CigaretteFilter1 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml index ef58235903..c534b73094 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml @@ -13,7 +13,6 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage - type: StaticPrice price: 5 @@ -50,14 +49,14 @@ bowl_slot: !type:ContainerSlot - type: ItemSlots - type: SmokingPipe - bowl_slot: + bowl_slot: name: Bowl whitelist: tags: - Smokable - insertSound: + insertSound: path: /Audio/Weapons/Guns/Empty/empty.ogg - ejectSound: + ejectSound: path: /Audio/Weapons/Guns/Empty/empty.ogg - type: SolutionContainerManager solutions: diff --git a/Resources/Prototypes/Entities/Objects/Decoration/present.yml b/Resources/Prototypes/Entities/Objects/Decoration/present.yml index 3a7d059e9f..c5a308591c 100644 --- a/Resources/Prototypes/Entities/Objects/Decoration/present.yml +++ b/Resources/Prototypes/Entities/Objects/Decoration/present.yml @@ -376,5 +376,4 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml index 0ff99e2902..f11e2021c6 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml @@ -14,3 +14,9 @@ - DroneUsable - type: StaticPrice price: 100 + - type: PhysicalComposition + materialComposition: + Glass: 400 + chemicalComposition: + Silicon: 20 + diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index adc52ac563..4ace8ea4f0 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -466,6 +466,11 @@ materialRequirements: CableMV: 5 CableHV: 5 + - type: PhysicalComposition + materialComposition: + Glass: 200 + chemicalComposition: + Silicon: 20 - type: entity parent: BaseMachineCircuitboard @@ -515,6 +520,11 @@ Capacitor: 1 materialRequirements: CableHV: 5 + - type: PhysicalComposition + materialComposition: + Glass: 200 + chemicalComposition: + Silicon: 20 - type: entity id: ThrusterMachineCircuitboard @@ -555,6 +565,11 @@ Capacitor: 2 materialRequirements: CableHV: 10 + - type: PhysicalComposition + materialComposition: + Glass: 200 + chemicalComposition: + Silicon: 20 - type: entity id: ReagentGrinderMachineCircuitboard @@ -611,6 +626,21 @@ DefaultPrototype: CryostasisBeaker ExamineName: Cryostasis Beaker +- type: entity + id: MaterialReclaimerMachineCircuitboard + parent: BaseMachineCircuitboard + name: material reclaimer machine board + components: + - type: Sprite + state: supply + - type: MachineBoard + prototype: MaterialReclaimer + requirements: + Manipulator: 2 + materialRequirements: + Steel: 5 + Plastic: 5 + - type: entity id: OreProcessorMachineCircuitboard parent: BaseMachineCircuitboard @@ -726,7 +756,7 @@ materialRequirements: Steel: 10 Plasma: 5 - + - type: entity id: BoozeDispenserMachineCircuitboard parent: BaseMachineCircuitboard @@ -766,7 +796,7 @@ - type: entity id: TelecomServerCircuitboard parent: BaseMachineCircuitboard - name: telecommunication server machine board + name: telecommunication server machine board description: A machine printed circuit board for an telecommunication server. components: - type: MachineBoard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 25e3ef4181..bfe7cfc64e 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -13,6 +13,11 @@ - DroneUsable - type: StaticPrice price: 100 + - type: PhysicalComposition + materialComposition: + Glass: 400 + chemicalComposition: + Silicon: 20 - type: entity parent: BaseComputerCircuitboard @@ -68,6 +73,10 @@ prototype: ComputerCargoOrders - type: StaticPrice price: 750 + - type: Tag + tags: + - DroneUsable + - HighRiskItem - type: entity parent: BaseComputerCircuitboard @@ -178,6 +187,10 @@ prototype: ComputerId - type: StaticPrice price: 750 + - type: Tag + tags: + - DroneUsable + - HighRiskItem - type: entity parent: BaseComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml index e4f789f1f3..ec24e4be15 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml @@ -14,3 +14,8 @@ - DroneUsable - type: StaticPrice price: 100 + - type: PhysicalComposition + materialComposition: + Glass: 200 + chemicalComposition: + Silicon: 20 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml index 7adc3a1095..5b48ede4c1 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml @@ -10,6 +10,11 @@ sprite: Objects/Misc/module.rsi state: charger_APC netsync: false + - type: PhysicalComposition + materialComposition: + Glass: 50 + chemicalComposition: + Silicon: 20 # Wallmount Substation - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml index 55839ada54..0acd4fbf19 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml @@ -9,3 +9,6 @@ layers: - state: icon - type: HandTeleporter + - type: Tag + tags: + - HighRiskItem diff --git a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml index 0dc6cb3045..d0856c64f4 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml @@ -10,7 +10,6 @@ - Write - Crayon - Trash - - type: Recyclable - type: SpaceGarbage - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 848993121c..ca7504c063 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -26,6 +26,11 @@ damage: types: Blunt: 0 + - type: PhysicalComposition + materialComposition: + Cloth: 100 + - type: StaticPrice + price: 5 - type: entity parent: BasePlushie @@ -243,7 +248,6 @@ - type: Sprite state: narplush - - type: entity parent: BasePlushie id: PlushieCarp @@ -356,55 +360,66 @@ - type: entity parent: BaseItem + id: BaseFigurine + name: figurine + description: A small miniature. + abstract: true + components: + - type: Sprite + sprite: Objects/Fun/toys.rsi + netsync: false + - type: PhysicalComposition + materialComposition: + Plastic: 100 + - type: StaticPrice + price: 10 + +- type: entity + parent: BaseFigurine id: ToyAi name: AI toy description: A scaled-down toy AI core. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: AI - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyNuke name: nuke toy description: A plastic model of a Nuclear Fission Explosive. No uranium included... probably. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: nuketoy - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyAssistant # TODO rename but needs map changes name: passenger toy description: Grey tide world wide! components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: doll - type: Item sprite: Objects/Fun/toys.rsi heldPrefix: doll - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyGriffin name: griffin toy description: An action figure modeled after 'The Griffin', criminal mastermind. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: griffinprize - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyHonk name: H.O.N.K. toy description: Mini-Mecha action figure! 'Mecha No. 6/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: honkprize - type: entity @@ -423,125 +438,111 @@ path: /Audio/Items/Toys/ian.ogg - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyMarauder name: marauder toy description: Mini-Mecha action figure! 'Mecha No. 7/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: marauderprize - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyMauler name: mauler toy description: Mini-Mecha action figure! 'Mecha No. 9/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: maulerprize - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyGygax name: gygax toy description: Mini-Mecha action figure! 'Mecha No. 4/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: gygaxtoy - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyOdysseus name: odysseus toy description: Mini-Mecha action figure! 'Mecha No. 10/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: odysseusprize - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyOwlman name: owl toy description: An action figure modeled after 'The Owl', defender of justice. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: owlprize - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyDeathRipley name: deathripley toy description: Mini-Mecha action figure! 'Mecha No. 3/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: deathripleytoy - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyPhazon name: phazon toy description: Mini-Mecha action figure! 'Mecha No. 11/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: phazonprize - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyFireRipley name: fire ripley description: Mini-Mecha action figure! 'Mecha No. 2/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: fireripleytoy - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyReticence name: reticence toy description: Mini-Mecha action figure! 'Mecha No. 12/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: reticenceprize - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyRipley name: ripley toy description: Mini-Mecha action figure! 'Mecha No. 1/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: ripleytoy - - type: entity - parent: BaseItem + parent: BaseFigurine id: ToySeraph name: seraph toy description: Mini-Mecha action figure! 'Mecha No. 8/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: seraphprize - - type: entity - parent: BaseItem + parent: BaseFigurine id: ToyDurand name: durand toy description: Mini-Mecha action figure! 'Mecha No. 5/12' is written on the back. components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: durandprize @@ -549,13 +550,12 @@ ### Help i'm sorting these and my previous self let this message here to taunt me aaaaa - type: entity - parent: BaseItem + parent: BaseFigurine id: ToySkeleton name: skeleton toy description: Spooked ya! components: - type: Sprite - sprite: Objects/Fun/toys.rsi state: skeletonprize ## Toyweapons @@ -843,8 +843,6 @@ available: - enum.DamageStateVisualLayers.Base: base: Sixteen - - type: StaticPrice - price: 3 - type: entity parent: BaseItem diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml index 0a0c876e99..e49d6a3207 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml @@ -51,8 +51,6 @@ - type: Material materials: Glass: 100 - - type: StackPrice - price: 5 - type: Stack stackType: Glass - type: Sprite diff --git a/Resources/Prototypes/Entities/Objects/Materials/shards.yml b/Resources/Prototypes/Entities/Objects/Materials/shards.yml index 2ae5298d57..a0711122da 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/shards.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/shards.yml @@ -51,7 +51,6 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage - type: Damageable damageContainer: Inorganic diff --git a/Resources/Prototypes/Entities/Objects/Misc/box.yml b/Resources/Prototypes/Entities/Objects/Misc/box.yml index 5f3e1e6cd9..d893b56cce 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/box.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/box.yml @@ -20,3 +20,8 @@ node: boxcardboard containers: - entity_storage + - type: PhysicalComposition + materialComposition: + Cardboard: 100 + - type: StaticPrice + price: 10 diff --git a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml index 8d7462cc96..e7f2088b64 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml @@ -23,6 +23,8 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Glass: 50 - type: SpaceGarbage diff --git a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml index b0fe40562e..30a0065056 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml @@ -18,6 +18,9 @@ - type: WarpPoint follow: true location: nuke disk + - type: Tag + tags: + - HighRiskItem - type: entity name: nuclear authentication disk diff --git a/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml b/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml index 6499ceeab1..b21f47cb60 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml @@ -90,10 +90,12 @@ components: - type: Item size: 2 - - type: Recyclable - type: Tag tags: - Trash + - type: PhysicalComposition + materialComposition: + Plastic: 25 - type: entity name: broken zipties @@ -114,4 +116,4 @@ - type: Sprite sprite: Objects/Misc/cablecuffs.rsi state: cuff-broken - color: forestgreen \ No newline at end of file + color: forestgreen diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index 50da83763b..b1ba64a8f8 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -107,6 +107,10 @@ heldPrefix: gold - type: PresetIdCard job: Captain + - type: Tag + tags: + - WhitelistChameleon + - HighRiskItem - type: entity parent: IDCardStandard @@ -548,7 +552,7 @@ - state: idzookeeper - type: PresetIdCard job: Zookeeper - + - type: entity parent: IDCardStandard id: DetectiveIDCard @@ -572,7 +576,7 @@ - state: idcentcom - type: Item heldPrefix: gold - + - type: entity parent: IDCardStandard diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index 4dbe14d202..d35fb1eafb 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -31,7 +31,6 @@ - Trash - type: Appearance - type: PaperVisuals - - type: Recyclable - type: entity name: office paper @@ -182,7 +181,9 @@ sprite: Objects/Misc/bureaucracy.rsi heldPrefix: pen size: 2 - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Steel: 25 - type: entity name: Cybersun pen diff --git a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml index ee6d680485..e2891c48bf 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml @@ -10,7 +10,6 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Power/lights.yml b/Resources/Prototypes/Entities/Objects/Power/lights.yml index a0aa6b4330..ccc3cdb146 100644 --- a/Resources/Prototypes/Entities/Objects/Power/lights.yml +++ b/Resources/Prototypes/Entities/Objects/Power/lights.yml @@ -60,7 +60,9 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Glass: 25 - type: SpaceGarbage - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Shields/shields.yml b/Resources/Prototypes/Entities/Objects/Shields/shields.yml index 29ddee84c6..d1668feae5 100644 --- a/Resources/Prototypes/Entities/Objects/Shields/shields.yml +++ b/Resources/Prototypes/Entities/Objects/Shields/shields.yml @@ -43,11 +43,11 @@ - !type:SpawnEntitiesBehavior spawn: SheetSteel: - min: 5 - max: 5 + min: 2 + max: 2 SheetGlass: - min: 5 - max: 5 + min: 2 + max: 2 - type: StaticPrice price: 50 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml index 27361d6b1b..b08a665274 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml @@ -22,6 +22,9 @@ delay: 0.5 - type: StaticPrice price: 750 + - type: Tag + tags: + - HighRiskItem - type: entity name: gorlex hypospray @@ -78,7 +81,9 @@ - type: Tag tags: - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Plastic: 50 - type: SpaceGarbage - type: StaticPrice price: 75 # These are limited supply items. diff --git a/Resources/Prototypes/Entities/Objects/Specific/atmos.yml b/Resources/Prototypes/Entities/Objects/Specific/atmos.yml index 21ce9f46d4..1bb1ae59ca 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/atmos.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/atmos.yml @@ -32,3 +32,7 @@ - DroneUsable - type: StaticPrice price: 80 + - type: PhysicalComposition + materialComposition: + Steel: 400 + Glass: 100 diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml index 3c255483d4..73c6a3a08f 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml @@ -9,7 +9,9 @@ tags: - Bottle - Trash - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Glass: 25 - type: SpaceGarbage - type: Sprite sprite: Objects/Specific/Chemistry/bottle.rsi diff --git a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml index a61b6efb4e..532e838817 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml @@ -58,3 +58,6 @@ - type: Tag tags: - Bucket + - type: PhysicalComposition + materialComposition: + Plastic: 50 diff --git a/Resources/Prototypes/Entities/Objects/Tools/flare.yml b/Resources/Prototypes/Entities/Objects/Tools/flare.yml index 71261cc8a9..e67c8f74a4 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flare.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flare.yml @@ -8,7 +8,6 @@ tags: - Flare - Trash - - type: Recyclable - type: SpaceGarbage - type: ExpendableLight spentName: expendable-light-spent-flare-name diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 21b5ebde94..4c8dcda6c6 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -35,6 +35,11 @@ damage: types: Blunt: 10 + - type: PhysicalComposition + materialComposition: + Steel: 400 + - type: StaticPrice + price: 20 - type: entity parent: GasTankBase @@ -109,6 +114,9 @@ damage: types: Blunt: 5 + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: entity parent: EmergencyOxygenTank diff --git a/Resources/Prototypes/Entities/Objects/Tools/glowstick.yml b/Resources/Prototypes/Entities/Objects/Tools/glowstick.yml index 9b58d9105e..41919ddce6 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/glowstick.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/glowstick.yml @@ -4,7 +4,6 @@ id: GlowstickBase description: Useful for raves and emergencies. components: - - type: Recyclable - type: SpaceGarbage - type: ExpendableLight spentName: expendable-light-spent-green-glowstick-name diff --git a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml index 388cef6019..cd8da06a32 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml @@ -147,6 +147,9 @@ - type: Item sprite: Objects/Tanks/Jetpacks/captain.rsi size: 30 + - type: Tag + tags: + - HighRiskItem # Filled captain - type: entity @@ -248,7 +251,7 @@ sprite: Objects/Tanks/Jetpacks/void.rsi slots: - Back - - suitStorage + - suitStorage # Filled void - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Tools/matches.yml b/Resources/Prototypes/Entities/Objects/Tools/matches.yml index 9350cf4da6..3700a1bfb1 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/matches.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/matches.yml @@ -18,7 +18,6 @@ tags: - Matchstick - Trash - - type: Recyclable - type: SpaceGarbage - type: Sprite netsync: false @@ -94,5 +93,4 @@ - type: Tag tags: - Trash - - type: Recyclable - type: SpaceGarbage diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index ac419a7016..9708b7f538 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -35,6 +35,9 @@ - type: Item sprite: Objects/Tools/wirecutters.rsi - type: LatticeCutting + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: StaticPrice price: 40 @@ -74,6 +77,9 @@ available: - enum.DamageStateVisualLayers.Base: screwdriver: Rainbow + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: StaticPrice price: 40 @@ -106,6 +112,9 @@ - Anchoring useSound: path: /Audio/Items/ratchet.ogg + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: StaticPrice price: 40 @@ -139,6 +148,9 @@ useSound: path: /Audio/Items/crowbar.ogg - type: TilePrying + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: StaticPrice price: 40 @@ -200,6 +212,10 @@ - type: Tag tags: - DroneUsable + - type: PhysicalComposition + materialComposition: + Steel: 100 + Plastic: 100 - type: StaticPrice price: 60 @@ -242,6 +258,10 @@ path: /Audio/Items/drill_use.ogg changeSound: path: /Audio/Items/change_drill.ogg + - type: PhysicalComposition + materialComposition: + Steel: 300 + Plastic: 100 - type: StaticPrice price: 100 @@ -264,6 +284,10 @@ quickEquip: false slots: - Belt + - type: PhysicalComposition + materialComposition: + Steel: 600 + Plastic: 100 - type: StaticPrice price: 100 @@ -288,6 +312,10 @@ - type: Item sprite: Objects/Tools/rcd.rsi heldPrefix: ammo + - type: PhysicalComposition + materialComposition: + Steel: 100 + Plastic: 100 - type: StaticPrice price: 60 @@ -310,6 +338,12 @@ Blunt: 10 - type: Item sprite: Objects/Tools/shovel.rsi + - type: PhysicalComposition + materialComposition: + Steel: 100 + Wood: 50 + - type: StaticPrice + price: 25 - type: entity name: omnitool diff --git a/Resources/Prototypes/Entities/Objects/Tools/welders.yml b/Resources/Prototypes/Entities/Objects/Tools/welders.yml index fbf43fefa3..0834b192b0 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/welders.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/welders.yml @@ -60,6 +60,9 @@ color: orange - type: Appearance - type: RequiresEyeProtection + - type: PhysicalComposition + materialComposition: + Steel: 200 - type: StaticPrice price: 40 - type: IgnitionSource diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/base_cartridge.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/base_cartridge.yml index c021eebed6..230be6a76b 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/base_cartridge.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/base_cartridge.yml @@ -11,5 +11,4 @@ - Cartridge - type: Item size: 1 - - type: Recyclable - type: SpaceGarbage diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml index 39542eccc2..34a39c1583 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml @@ -7,9 +7,9 @@ - type: Tag tags: - BulletFoam + - Trash - type: Ammo - type: Sprite sprite: Objects/Fun/toys.rsi layers: - state: foamdart - - type: Recyclable \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index 2b2f7ed0b0..ba1e9cfa46 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -390,6 +390,9 @@ steps: 5 zeroVisible: true - type: Appearance + - type: Tag + tags: + - HighRiskItem - type: StaticPrice price: 750 diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index f7599c12c9..6bce0ecbf8 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -306,6 +306,7 @@ - DawInstrumentMachineCircuitboard - CloningConsoleComputerCircuitboard - StasisBedMachineCircuitboard + - MaterialReclaimerMachineCircuitboard - OreProcessorMachineCircuitboard - RipleyCentralElectronics - RipleyPeripheralsElectronics diff --git a/Resources/Prototypes/Entities/Structures/Machines/material_reclaimer.yml b/Resources/Prototypes/Entities/Structures/Machines/material_reclaimer.yml new file mode 100644 index 0000000000..26d4adf09d --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/material_reclaimer.yml @@ -0,0 +1,101 @@ +- type: entity + parent: [ BaseMachinePowered, ConstructibleMachine ] + id: MaterialReclaimer + name: material reclaimer + description: Cannot reclaim immaterial things, like motivation. + components: + - type: Sprite + sprite: Structures/Machines/material_reclaimer.rsi + snapCardinals: true + netsync: false + layers: + - state: icon + map: ["enum.LatheVisualLayers.IsRunning"] + - state: gear-active + map: ["enum.DamageStateVisualLayers.Base"] + - state: unlit + shader: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + - state: fill-6 + map: ["enum.SolutionContainerLayers.Fill"] + visible: false + - state: panel + map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: Appearance + - type: SolutionContainerVisuals + maxFillLevels: 6 + fillBaseName: fill- + - type: WiresVisuals + - type: GenericVisualizer + visuals: + enum.PowerDeviceVisuals.Powered: + enum.DamageStateVisualLayers.Base: + True: { state: gear-active} + False: { state: gear-idle } + enum.PowerDeviceVisualLayers.Powered: + True: { visible: true } + False: { visible: false } + - type: LitOnPowered + - type: PointLight + radius: 1.5 + energy: 1.6 + enabled: false + color: "#da824d" + mask: /Textures/Effects/LightMasks/cone.png + autoRot: true + offset: "0, 0.4" + castShadows: false + - type: PowerSwitch + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:ChangeConstructionNodeBehavior + node: machineFrame + - !type:DoActsBehavior + acts: ["Destruction"] + - type: Machine + board: MaterialReclaimerMachineCircuitboard + - type: Wires + BoardName: "reclaimer" + LayoutId: Reclaimer + - type: MaterialReclaimer + whitelist: + components: + - PhysicalComposition + - SpaceGarbage + tags: + - Trash + - Recyclable + blacklist: + components: + - Material + - PDA + - IdCard + tags: + - HighRiskItem + sound: + path: /Audio/Ambience/Objects/crushing.ogg + params: + volume: 5 + maxdistance: 5 + loop: true + - type: MaterialStorage + insertOnInteract: false + - type: ContainerContainer + containers: + active-material-reclaimer-container: !type:Container + machine_board: !type:Container + machine_parts: !type:Container + - type: SolutionContainerManager + solutions: + output: + maxVol: 100 + - type: DrainableSolution + solution: output + - type: ExaminableSolution + solution: output + - type: StaticPrice + price: 500 diff --git a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml index 6faa94dd3f..49b6e563dd 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml @@ -11,12 +11,11 @@ sound: # TODO: https://freesound.org/people/derjuli/sounds/448133/ CC-NC- path: /Audio/Ambience/Objects/circular_saw.ogg - - type: Physics - type: Fixtures fixtures: - shape: !type:PhysShapeAabb - bounds: "-0.2,-0.2,0.2,0.2" + bounds: "-0.15,-0.15,0.15,0.15" id: brrt hard: false layer: @@ -58,11 +57,58 @@ layers: - state: grinder-o0 map: ["enum.RecyclerVisualLayers.Main"] + - state: grinder-o0bld + map: ["enum.RecyclerVisualLayers.Bloody"] + visible: false - type: Appearance + - type: GenericVisualizer visuals: - - type: RecyclerVisualizer - state_on: grinder-o1 - state_off: grinder-o0 - - type: Recycler + enum.RecyclerVisuals.Bloody: + enum.RecyclerVisualLayers.Main: + True: { visible: false } + False: { visible: true } + enum.RecyclerVisualLayers.Bloody: + True: { visible: true } + False: { visible: false } + enum.ConveyorVisuals.State: + enum.RecyclerVisualLayers.Main: + Forward: { state: grinder-o1 } + Reverse: { state: grinder-o1 } + Off: { state: grinder-o0 } + enum.RecyclerVisualLayers.Bloody: + Forward: { state: grinder-o1bld } + Reverse: { state: grinder-o1bld } + Off: { state: grinder-o0bld } + - type: CollideMaterialReclaimer + - type: MaterialReclaimer + enabled: false + efficiency: 0.25 + scaleProcessSpeed: false #instant! + minimumProcessDuration: 0 + whitelist: + components: + - PhysicalComposition + - SpaceGarbage + tags: + - Trash + - Recyclable + blacklist: + components: + - Material + - PDA + - IdCard + - HumanoidAppearance + tags: + - HighRiskItem + sound: + path: /Audio/Effects/saw.ogg + params: + volume: -3 + cutOffSound: false + - type: SolutionContainerManager + solutions: + output: + maxVol: 0 #exists only for the overflow stuff on material reclaimer + - type: MaterialStorage - type: Conveyor - type: Rotatable \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml index 345eea9897..2e89c2f622 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml @@ -608,7 +608,9 @@ - MachineMask layer: - MachineLayer - - type: Recyclable + - type: PhysicalComposition + materialComposition: + Steel: 500 - type: StaticPrice price: 60 diff --git a/Resources/Prototypes/Entities/Structures/Windows/window.yml b/Resources/Prototypes/Entities/Structures/Windows/window.yml index 2234f3de72..e68dbb8f62 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/window.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/window.yml @@ -169,7 +169,7 @@ graph: WindowDirectional node: windowDirectional - type: StaticPrice - price: 5 + price: 10 - type: entity id: WindowTintedDirectional @@ -193,8 +193,6 @@ state: tinted_window - type: Occluder boundingBox: "-0.5,-0.5,0.5,-0.3" - - type: StaticPrice - price: 5 - type: entity id: WindowFrostedDirectional @@ -213,5 +211,3 @@ - type: Icon sprite: Structures/Windows/directional.rsi state: frosted_window - - type: StaticPrice - price: 5 diff --git a/Resources/Prototypes/Reagents/Materials/materials.yml b/Resources/Prototypes/Reagents/Materials/materials.yml index faaa2523ec..15af26eb47 100644 --- a/Resources/Prototypes/Reagents/Materials/materials.yml +++ b/Resources/Prototypes/Reagents/Materials/materials.yml @@ -12,7 +12,7 @@ name: materials-cardboard icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cardboard } color: "#70736c" - price: 0.05 + price: 0.005 - type: material id: Cloth diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index c5aea71800..468c0e5a56 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -292,6 +292,14 @@ Glass: 900 Gold: 100 +- type: latheRecipe + id: MaterialReclaimerMachineCircuitboard + result: MaterialReclaimerMachineCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 + - type: latheRecipe id: OreProcessorMachineCircuitboard result: OreProcessorMachineCircuitboard diff --git a/Resources/Prototypes/Recipes/Lathes/janitorial.yml b/Resources/Prototypes/Recipes/Lathes/janitorial.yml index 4c537c30e5..1515ccdaad 100644 --- a/Resources/Prototypes/Recipes/Lathes/janitorial.yml +++ b/Resources/Prototypes/Recipes/Lathes/janitorial.yml @@ -18,7 +18,7 @@ result: Bucket completetime: 2 materials: - Steel: 100 + Plastic: 100 - type: latheRecipe id: WetFloorSign diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index bfb69b2448..f7870ccdc6 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -302,6 +302,9 @@ - type: Tag id: HidesHair # for headwear. +- type: Tag + id: HighRiskItem + - type: Tag id: Hoe diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-1.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..32efd405d81a8dafb12bdc332a469218c516d712 GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}TAnVBArY-_ z&u$cCP~c(SSn+?MukYEVPErr**%%t`r)uBXc3mT~n(@OEhP!4gOlK#2nhMm#;OXk; Jvd$@?2>?}*BG~`{ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-2.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-2.png new file mode 100644 index 0000000000000000000000000000000000000000..71883d8117d1f5c96a249be1b7c7494e2b2d6bf8 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}`kpS1ArY-_ z&pC22DDW^Jy!QXUm9Z Q12r>vy85}Sb4q9e019CukpKVy literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-5.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-5.png new file mode 100644 index 0000000000000000000000000000000000000000..1ed097f5e2b3cf599b408c9e19676d613e2bd2cd GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}CY~;iArY-_ z&u!#oP~c%c*z&iW&s2!jcS_T4#y9;840Y3E4~K>x4}A7DyOed0kbxY-fgQYRx-494 TB}3K$^)q<7`njxgN@xNAW)&FPsrc+cV0qua{5We7Y0R489O+lc`M)iGUz^W z?8*OZj?WyQ@3E|szBE$s#zR%jS*=#G=e;%``aN&qn+q3pLPD?YdUCxicjKJx zg`U5D{E?b-|2ON7_MWtb+fFXi?K*nr`LVK->vQ>bM=iX&BYKA79@Y)w^ZU|0gF>%) zZC2W_D*Dew-XA%WebxK#{(b~;8HkiCUv2v@zFIQ+eT_rB{J*gK|3vrx7JKvHTKDuT zH)XI?yEf?9e>uvk_iEkO^C^BrWIJ)U84+xaMw@JED r_e19KUF-gUBZ;p-)`0Asz|G((q2V;c>d_^jdl)=j{an^LB{Ts5>rP10 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/icon.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7ba2ef785e9f02313b7c566c7d8ce1d0a2666a09 GIT binary patch literal 709 zcmV;$0y_PPP)Px%e@R3^R9J<@md|U`U>L{06>rjRwwn$GNjpOiLXmYD9u$l>A&0^2z+VT8h~Pat`d6_~}oy$ntX*eg6| zfbe&(5@9qNvIUU9>gP|132$OQ%;xZ-^9(ERR{;RRLK(Bik72OBv1?;)s53yG zTJGzR`#Lpox5o%}K{$VA&LXOlgkovL#$bB8cLK%bgjYI{ISOhx*N2d=aR(k#T zy7?^g{p9OmI#`JXyaKbD^ZqkPj{QEZ9^K?b?1R?lkCsHXEc@+R~@Ae7mzaJz6#BC0LY7W7me03b>{dhCZE3n~K>*+ZxZCYtRQL|%>cT@eBfMgY_`JU4&J zTDo=>je0%yLe*Lgjd~p%UxJ~^_P!IibjG#?Uc)0ZQvl#uAP6p rZKt>^ER})W^wfWOFZ{8&Wyk#kql-2-j=cjs00000NkvXXu0mjfp!q#5 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/meta.json b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/meta.json new file mode 100644 index 0000000000..3e27312a6c --- /dev/null +++ b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/meta.json @@ -0,0 +1,57 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Created by EmoGarbage404 (github) for Space Station 14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + }, + { + "name": "fill-6" + }, + { + "name": "gear-active", + "delays": [ + [ + 0.25, + 0.25, + 0.25 + ] + ] + }, + { + "name": "gear-idle" + }, + { + "name": "icon" + }, + { + "name": "panel" + }, + { + "name": "unlit", + "delays": [ + [ + 0.1, + 1.0 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/panel.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..b3de5c1be18ec59d3208675020b060f9bc6d879e GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}ZJsWUArY;~ z2@_9dGXLxT-TmLbdg{adsr#P(Px#*Of5#Q0i-$h3Z`U_e zk=~OOvAb*H!+XuuJp6g_Ne7;Ctg>cc`&PdpVMeuMkE5p}=Xxu*s0g_?av%P494};3 vWmRpf=6`2+4finmvMJ0J_wYZccEP}*iobEi2fpo1mtDFhtXXoiw#WBwj-PJ7>A#HhqFJG