]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Material Reclaimer (#14969)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Mon, 10 Apr 2023 04:38:20 +0000 (00:38 -0400)
committerGitHub <noreply@github.com>
Mon, 10 Apr 2023 04:38:20 +0000 (23:38 -0500)
* 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

111 files changed:
Content.Client/Materials/MaterialReclaimerSystem.cs [new file with mode: 0644]
Content.Client/Recycling/RecyclerVisualizer.cs [deleted file]
Content.IntegrationTests/Tests/MaterialArbitrageTest.cs
Content.Server/Cargo/Systems/PricingSystem.cs
Content.Server/Materials/MaterialReclaimerSystem.cs [new file with mode: 0644]
Content.Server/Materials/MaterialStorageSystem.cs
Content.Server/Physics/Controllers/ConveyorController.cs
Content.Server/Recycling/Components/RecyclableComponent.cs [deleted file]
Content.Server/Recycling/Components/RecyclerComponent.cs [deleted file]
Content.Server/Recycling/RecyclerSystem.cs [deleted file]
Content.Shared/Lathe/LatheComponent.cs
Content.Shared/Materials/ActiveMaterialReclaimerComponent.cs [new file with mode: 0644]
Content.Shared/Materials/CollideMaterialReclaimerComponent.cs [new file with mode: 0644]
Content.Shared/Materials/MaterialReclaimerComponent.cs [new file with mode: 0644]
Content.Shared/Materials/MaterialStorageComponent.cs
Content.Shared/Materials/PhysicalCompositionComponent.cs [new file with mode: 0644]
Content.Shared/Materials/SharedMaterialReclaimerSystem.cs [new file with mode: 0644]
Content.Shared/Materials/SharedMaterialStorageSystem.cs
Content.Shared/Recycling/SharedRecyclerComponent.cs [deleted file]
Resources/Audio/Ambience/Objects/attributions.yml
Resources/Audio/Ambience/Objects/crushing.ogg [new file with mode: 0644]
Resources/Locale/en-US/materials/materials.ftl
Resources/Prototypes/Body/Parts/silicon.yml
Resources/Prototypes/Catalog/Research/technologies.yml
Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml
Resources/Prototypes/Entities/Clothing/Head/misc.yml
Resources/Prototypes/Entities/Clothing/Head/welding.yml
Resources/Prototypes/Entities/Clothing/Masks/masks.yml
Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml
Resources/Prototypes/Entities/Mobs/Player/silicon.yml
Resources/Prototypes/Entities/Mobs/Species/base.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_flasks.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/tin.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/food_base.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/frozen.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml
Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cartons.yml
Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/cigarette.yml
Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml
Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/packs.yml
Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/rolling_paper.yml
Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml
Resources/Prototypes/Entities/Objects/Decoration/present.yml
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/power_electronics.yml
Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml
Resources/Prototypes/Entities/Objects/Fun/crayons.yml
Resources/Prototypes/Entities/Objects/Fun/toys.yml
Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml
Resources/Prototypes/Entities/Objects/Materials/shards.yml
Resources/Prototypes/Entities/Objects/Misc/box.yml
Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml
Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml
Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml
Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
Resources/Prototypes/Entities/Objects/Misc/paper.yml
Resources/Prototypes/Entities/Objects/Misc/utensils.yml
Resources/Prototypes/Entities/Objects/Power/lights.yml
Resources/Prototypes/Entities/Objects/Shields/shields.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml
Resources/Prototypes/Entities/Objects/Specific/atmos.yml
Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml
Resources/Prototypes/Entities/Objects/Tools/bucket.yml
Resources/Prototypes/Entities/Objects/Tools/flare.yml
Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml
Resources/Prototypes/Entities/Objects/Tools/glowstick.yml
Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml
Resources/Prototypes/Entities/Objects/Tools/matches.yml
Resources/Prototypes/Entities/Objects/Tools/tools.yml
Resources/Prototypes/Entities/Objects/Tools/welders.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/base_cartridge.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Resources/Prototypes/Entities/Structures/Machines/material_reclaimer.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Structures/Machines/recycler.yml
Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml
Resources/Prototypes/Entities/Structures/Windows/window.yml
Resources/Prototypes/Reagents/Materials/materials.yml
Resources/Prototypes/Recipes/Lathes/electronics.yml
Resources/Prototypes/Recipes/Lathes/janitorial.yml
Resources/Prototypes/tags.yml
Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-1.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-2.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-3.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-4.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-5.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-6.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-active.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-idle.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/icon.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/panel.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/material_reclaimer.rsi/unlit.png [new file with mode: 0644]

diff --git a/Content.Client/Materials/MaterialReclaimerSystem.cs b/Content.Client/Materials/MaterialReclaimerSystem.cs
new file mode 100644 (file)
index 0000000..eec55ea
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.Materials;
+
+namespace Content.Client.Materials;
+
+/// <inheritdoc/>
+public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
+{
+
+}
diff --git a/Content.Client/Recycling/RecyclerVisualizer.cs b/Content.Client/Recycling/RecyclerVisualizer.cs
deleted file mode 100644 (file)
index 856ebff..0000000
+++ /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<IEntityManager>();
-            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<IEntityManager>();
-            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
-    }
-}
index de8b78a55ffd39c25e9da1356386af8c05743a37..406b5aad812046c7459011c72406a19503972ebd 100644 (file)
@@ -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<IComponentFactory>();
 
         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<string, LatheRecipePrototype> latheRecipes = new();
@@ -84,6 +84,9 @@ public sealed class MaterialArbitrageTest
         {
             var materials = new Dictionary<string, int>();
             var graph = protoManager.Index<ConstructionGraphPrototype>(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<StackPrototype>(materialStep.MaterialPrototypeId);
+                    var spawnProto = protoManager.Index<EntityPrototype>(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<EntityPrototype>(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<EntityPrototype>(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<string, PhysicalCompositionComponent> physicalCompositions = new();
+        foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
+        {
+            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<StackPrototype>(id).Spawn;
-                price += num * await GetPrice(matProto);
+                var matProto = protoManager.Index<MaterialPrototype>(id);
+                price += num * matProto.Price;
+            }
+            return price;
+        }
+
+
+        async Task<double> GetChemicalCompositionPrice(Dictionary<string, FixedPoint2> mats)
+        {
+            double price = 0;
+            foreach (var (id, num) in mats)
+            {
+                var reagentProto = protoManager.Index<ReagentPrototype>(id);
+                price += num.Double() * reagentProto.PricePerUnit;
             }
             return price;
         }
index 6a1125ef36c2ba4cd74bca522bd4468879bdbe05..3d4bd64a4cafd81a403c996786a097d040632a30 100644 (file)
@@ -213,7 +213,7 @@ public sealed class PricingSystem : EntitySystem
     {
         double price = 0;
 
-        if (TryComp<MaterialComponent>(uid, out var material) && !HasComp<StackPriceComponent>(uid))
+        if (TryComp<MaterialComponent>(uid, out var material))
         {
             var matPrice = GetMaterialPrice(material);
             if (TryComp<StackComponent>(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<StackPriceComponent>(uid, out var stackPrice) &&
-            TryComp<StackComponent>(uid, out var stack))
+            TryComp<StackComponent>(uid, out var stack) &&
+            !HasComp<MaterialComponent>(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 (file)
index 0000000..80c4a40
--- /dev/null
@@ -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;
+
+/// <inheritdoc/>
+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!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<MaterialReclaimerComponent, ComponentStartup>(OnStartup);
+        SubscribeLocalEvent<MaterialReclaimerComponent, RefreshPartsEvent>(OnRefreshParts);
+        SubscribeLocalEvent<MaterialReclaimerComponent, UpgradeExamineEvent>(OnUpgradeExamine);
+        SubscribeLocalEvent<MaterialReclaimerComponent, PowerChangedEvent>(OnPowerChanged);
+        SubscribeLocalEvent<MaterialReclaimerComponent, InteractUsingEvent>(OnInteractUsing,
+            before: new []{typeof(WiresSystem), typeof(SolutionTransferSystem)});
+        SubscribeLocalEvent<MaterialReclaimerComponent, SuicideEvent>(OnSuicide);
+        SubscribeLocalEvent<ActiveMaterialReclaimerComponent, PowerChangedEvent>(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<SolutionContainerManagerComponent>(args.Used, out var managerComponent) &&
+                managerComponent.Solutions.Any(s => s.Value.AvailableVolume > 0))
+            {
+                if (TryComp<DrinkComponent>(args.Used, out var drink) &&
+                    !drink.Opened)
+                    return;
+
+                if (TryComp<SolutionTransferComponent>(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);
+    }
+
+    /// <inheritdoc/>
+    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;
+    }
+
+    /// <inheritdoc/>
+    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<string, FixedPoint2>();
+
+        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<SolutionContainerManagerComponent>(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);
+        }
+    }
+}
index 4e4aaeec5876fe6334f94be146553fc1c3f98e47..e79a47d0673c916d323a495d484f0db0518fff44 100644 (file)
@@ -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.
     /// </summary>
-    [PublicAPI]
     public List<EntityUid> SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates)
     {
+        return SpawnMultipleFromMaterial(amount, material, coordinates, out _);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    public List<EntityUid> SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates, out int overflowMaterial)
+    {
+        overflowMaterial = 0;
         if (!_prototypeManager.TryIndex<MaterialPrototype>(material, out var stackType))
         {
             Logger.Error("Failed to index material prototype " + material);
             return new List<EntityUid>();
         }
 
-        return SpawnMultipleFromMaterial(amount, stackType, coordinates);
+        return SpawnMultipleFromMaterial(amount, stackType, coordinates, out overflowMaterial);
     }
 
     /// <summary>
@@ -85,8 +96,22 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
     ///     1 biomass = 1 biomass in its stack,
     ///     but 100 plasma = 1 sheet of plasma, etc.
     /// </summary>
+    [PublicAPI]
     public List<EntityUid> SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates)
     {
+        return SpawnMultipleFromMaterial(amount, materialProto, coordinates, out _);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    public List<EntityUid> SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates, out int overflowMaterial)
+    {
+        overflowMaterial = 0;
+
         if (amount <= 0)
             return new List<EntityUid>();
 
@@ -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);
     }
 }
index 1d18eb70f4ecee69c33a35f1079ad9f18c0a8dad..31779ed1a7efaf9ee07aee30bc4559bf965e71b4 100644 (file)
@@ -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<PhysicsComponent>(uid, out var physics))
             _broadphase.RegenerateContacts(physics);
 
-        if (TryComp<RecyclerComponent>(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 (file)
index baa3794..0000000
+++ /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
-    {
-        /// <summary>
-        ///     The prototype that will be spawned on recycle.
-        /// </summary>
-        [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] public string? Prototype;
-
-        /// <summary>
-        ///     The amount of things that will be spawned on recycle.
-        /// </summary>
-        [DataField("amount")] public int Amount = 1;
-
-        /// <summary>
-        ///     Whether this is "safe" to recycle or not.
-        ///     If this is false, the recycler's safety must be disabled to recycle it.
-        /// </summary>
-        [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 (file)
index 017c988..0000000
+++ /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;
-
-        /// <summary>
-        ///     The percentage of material that will be recovered
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("efficiency")]
-        internal float Efficiency = 0.25f;
-
-        /// <summary>
-        /// Default sound to play when recycling
-        /// </summary>
-        [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 (file)
index de7c558..0000000
+++ /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<RecyclerComponent, ExaminedEvent>(OnExamined);
-            SubscribeLocalEvent<RecyclerComponent, StartCollideEvent>(OnCollide);
-            SubscribeLocalEvent<RecyclerComponent, GotEmaggedEvent>(OnEmagged);
-            SubscribeLocalEvent<RecyclerComponent, SuicideEvent>(OnSuicide);
-            SubscribeLocalEvent<RecyclerComponent, PowerChangedEvent>(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<BodyComponent?>(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<EmaggedComponent>(component.Owner)))
-            {
-                return;
-            }
-
-            // TODO: Prevent collision with recycled items
-
-            // Mobs are a special case!
-            if (CanGib(component, entity))
-            {
-                _bodySystem.GibBody(entity, true, Comp<BodyComponent>(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<BodyComponent>(entity) && HasComp<EmaggedComponent>(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;
-        }
-    }
-}
index 752eec5abaaa154149fc74a8e948344d20f7ee11..4cfd94c80f979cf28f1484c1912104fe985df6b3 100644 (file)
@@ -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 (file)
index 0000000..b62be50
--- /dev/null
@@ -0,0 +1,32 @@
+using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Materials;
+
+/// <summary>
+/// Tracker component for the process of reclaiming entities
+/// <seealso cref="MaterialReclaimerComponent"/>
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedMaterialReclaimerSystem))]
+public sealed class ActiveMaterialReclaimerComponent : Component
+{
+    /// <summary>
+    /// Container used to store the item currently being reclaimed
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    public Container ReclaimingContainer = default!;
+
+    /// <summary>
+    /// When the reclaiming process ends.
+    /// </summary>
+    [DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan EndTime;
+
+    /// <summary>
+    /// The length of the reclaiming process.
+    /// Used for calculations.
+    /// </summary>
+    [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 (file)
index 0000000..4d9b072
--- /dev/null
@@ -0,0 +1,16 @@
+namespace Content.Shared.Materials;
+
+/// <summary>
+/// Valid items that collide with an entity with this component
+/// will begin to be reclaimed.
+/// <seealso cref="MaterialReclaimerComponent"/>
+/// </summary>
+[RegisterComponent]
+public sealed class CollideMaterialReclaimerComponent : Component
+{
+    /// <summary>
+    /// The fixture that starts reclaiming on collision.
+    /// </summary>
+    [DataField("fixtureId")]
+    public string FixtureId = "brrt";
+}
diff --git a/Content.Shared/Materials/MaterialReclaimerComponent.cs b/Content.Shared/Materials/MaterialReclaimerComponent.cs
new file mode 100644 (file)
index 0000000..aaaa656
--- /dev/null
@@ -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;
+
+/// <summary>
+/// This is a machine that handles converting entities
+/// into the raw materials and chemicals that make them up.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedMaterialReclaimerSystem))]
+public sealed class MaterialReclaimerComponent : Component
+{
+    /// <summary>
+    /// Whether or not the machine has power. We put it here
+    /// so we can network and predict it.
+    /// </summary>
+    [DataField("powered"), ViewVariables(VVAccess.ReadWrite)]
+    public bool Powered;
+
+    /// <summary>
+    /// An "enable" toggle for things like interfacing with machine linking
+    /// </summary>
+    [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
+    public bool Enabled = true;
+
+    /// <summary>
+    /// How efficiently the materials are reclaimed.
+    /// In practice, a multiplier per material when calculating the output of the reclaimer.
+    /// </summary>
+    [DataField("efficiency"), ViewVariables(VVAccess.ReadWrite)]
+    public float Efficiency = 1f;
+
+    /// <summary>
+    /// Whether or not the process
+    /// speed scales with the amount of materials being processed
+    /// or if it's just <see cref="MinimumProcessDuration"/>
+    /// </summary>
+    [DataField("scaleProcessSpeed")]
+    public bool ScaleProcessSpeed = true;
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [DataField("baseMaterialProcessRate"), ViewVariables(VVAccess.ReadWrite)]
+    public float BaseMaterialProcessRate = 100f;
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [DataField("materialProcessRate"), ViewVariables(VVAccess.ReadWrite)]
+    public float MaterialProcessRate = 100f;
+
+    /// <summary>
+    /// Machine part whose rating modifies <see cref="MaterialProcessRate"/>
+    /// </summary>
+    [DataField("machinePartProcessRate", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>)), ViewVariables(VVAccess.ReadWrite)]
+    public string MachinePartProcessRate = "Manipulator";
+
+    /// <summary>
+    /// How much the machine part quality affects the <see cref="MaterialProcessRate"/>
+    /// </summary>
+    [DataField("partRatingProcessRateMultiplier"), ViewVariables(VVAccess.ReadWrite)]
+    public float PartRatingProcessRateMultiplier = 1.5f;
+
+    /// <summary>
+    /// The minimum amount fo time it can take to process an entity.
+    /// this value supercedes the calculated one using <see cref="MaterialProcessRate"/>
+    /// </summary>
+    [DataField("minimumProcessDuration"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan MinimumProcessDuration = TimeSpan.FromSeconds(0.5f);
+
+    /// <summary>
+    /// The id of our output solution
+    /// </summary>
+    [DataField("solutionContainerId"), ViewVariables(VVAccess.ReadWrite)]
+    public string SolutionContainerId = "output";
+
+    /// <summary>
+    /// The prototype for the puddle
+    /// </summary>
+    [DataField("puddleId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
+    public string PuddleId = "PuddleSmear";
+
+    /// <summary>
+    /// The solution itself.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    public Solution OutputSolution = default!;
+
+    /// <summary>
+    /// a whitelist for what entities can be inserted into this reclaimer
+    /// </summary>
+    [DataField("whitelist")]
+    public EntityWhitelist? Whitelist;
+
+    /// <summary>
+    /// a blacklist for what entities cannot be inserted into this reclaimer
+    /// </summary>
+    [DataField("blacklist")]
+    public EntityWhitelist? Blacklist;
+
+    /// <summary>
+    /// The sound played when something is being processed.
+    /// </summary>
+    [DataField("sound")]
+    public SoundSpecifier? Sound;
+
+    /// <summary>
+    /// whether or not we cut off the sound early when the reclaiming ends.
+    /// </summary>
+    [DataField("cutOffSound")]
+    public bool CutOffSound = true;
+
+    /// <summary>
+    /// When the next sound will be allowed to be played. Used to prevent spam.
+    /// </summary>
+    [DataField("nextSound", customTypeSerializer: typeof(TimeOffsetSerializer))]
+    public TimeSpan NextSound;
+
+    /// <summary>
+    /// Minimum time inbetween each <see cref="Sound"/>
+    /// </summary>
+    [DataField("soundCooldown")]
+    public TimeSpan SoundCooldown = TimeSpan.FromSeconds(0.8f);
+
+    public IPlayingAudioStream? Stream;
+
+    /// <summary>
+    /// A counter of how many items have been processed
+    /// </summary>
+    /// <remarks>
+    /// I saw this on the recycler and i'm porting it because it's cute af
+    /// </remarks>
+    [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
+}
index 491c4d3cb73f282ecd17ed00f5f2e445501063d4..71b4056042a8afaaabb963073c1db68a31bda2d8 100644 (file)
@@ -15,6 +15,12 @@ public sealed class MaterialStorageComponent : Component
     [DataField("storage", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MaterialPrototype>))]
     public Dictionary<string, int> Storage { get; set; } = new();
 
+    /// <summary>
+    /// Whether or not interacting with the materialstorage inserts the material in hand.
+    /// </summary>
+    [DataField("insertOnInteract")]
+    public bool InsertOnInteract = true;
+
     /// <summary>
     ///     How much material the storage can store in total.
     /// </summary>
diff --git a/Content.Shared/Materials/PhysicalCompositionComponent.cs b/Content.Shared/Materials/PhysicalCompositionComponent.cs
new file mode 100644 (file)
index 0000000..2d23a8c
--- /dev/null
@@ -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;
+
+/// <summary>
+/// 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 <see cref="MaterialComponent"/>.
+/// </summary>
+/// <remarks>
+/// The reason for duel material/chemical is for the eventual
+/// combination of the two systems.
+/// </remarks>
+[RegisterComponent]
+public sealed class PhysicalCompositionComponent : Component
+{
+    /// <summary>
+    /// The materials that "make up" this entity
+    /// </summary>
+    [DataField("materialComposition", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MaterialPrototype>))]
+    public Dictionary<string, int> MaterialComposition = new();
+
+    /// <summary>
+    /// The chemicals that "make up" this entity
+    /// </summary>
+    [DataField("chemicalComposition", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, ReagentPrototype>))]
+    public Dictionary<string, FixedPoint2> ChemicalComposition = new();
+}
diff --git a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs
new file mode 100644 (file)
index 0000000..18a744e
--- /dev/null
@@ -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;
+
+/// <summary>
+/// Handles interactions and logic related to <see cref="MaterialReclaimerComponent"/>,
+/// <see cref="CollideMaterialReclaimerComponent"/>, and <see cref="ActiveMaterialReclaimerComponent"/>.
+/// </summary>
+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";
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<MaterialReclaimerComponent, ComponentGetState>(OnGetState);
+        SubscribeLocalEvent<MaterialReclaimerComponent, ComponentHandleState>(OnHandleState);
+        SubscribeLocalEvent<MaterialReclaimerComponent, EntityUnpausedEvent>(OnUnpaused);
+        SubscribeLocalEvent<MaterialReclaimerComponent, ExaminedEvent>(OnExamined);
+        SubscribeLocalEvent<MaterialReclaimerComponent, GotEmaggedEvent>(OnEmagged);
+        SubscribeLocalEvent<CollideMaterialReclaimerComponent, StartCollideEvent>(OnCollide);
+        SubscribeLocalEvent<ActiveMaterialReclaimerComponent, ComponentStartup>(OnActiveStartup);
+        SubscribeLocalEvent<ActiveMaterialReclaimerComponent, EntityUnpausedEvent>(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<MaterialReclaimerComponent>(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<Container>(uid, ActiveReclaimerContainerId);
+    }
+
+    private void OnActiveUnpaused(EntityUid uid, ActiveMaterialReclaimerComponent component, ref EntityUnpausedEvent args)
+    {
+        component.EndTime += args.PausedTime;
+    }
+
+    /// <summary>
+    /// Tries to start processing an item via a <see cref="MaterialReclaimerComponent"/>.
+    /// </summary>
+    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<MobStateComponent>(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<ActiveMaterialReclaimerComponent>(uid);
+        active.Duration = duration;
+        active.EndTime = Timing.CurTime + duration;
+        active.ReclaimingContainer.Insert(item);
+        return true;
+    }
+
+    /// <summary>
+    /// Finishes processing an item, freeing up the the reclaimer.
+    /// </summary>
+    /// <remarks>
+    /// This doesn't reclaim the entity itself, but rather ends the formal
+    /// process started with <see cref="ActiveMaterialReclaimerComponent"/>.
+    /// The actual reclaiming happens in <see cref="Reclaim"/>
+    /// </remarks>
+    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;
+    }
+
+    /// <summary>
+    /// Spawns the materials and chemicals associated
+    /// with an entity. Also deletes the item.
+    /// </summary>
+    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);
+    }
+
+    /// <summary>
+    /// Sets the Enabled field on the reclaimer.
+    /// </summary>
+    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);
+    }
+
+    /// <summary>
+    /// Whether or not the specified reclaimer can currently
+    /// begin reclaiming another entity.
+    /// </summary>
+    public bool CanStart(EntityUid uid, MaterialReclaimerComponent component)
+    {
+        if (HasComp<ActiveMaterialReclaimerComponent>(uid))
+            return false;
+
+        return component.Powered && component.Enabled;
+    }
+
+    /// <summary>
+    /// Whether or not the reclaimer satisfies the conditions
+    /// allowing it to gib/reclaim a living creature.
+    /// </summary>
+    public bool CanGib(EntityUid uid, EntityUid victim, MaterialReclaimerComponent component)
+    {
+        return component.Powered &&
+               component.Enabled &&
+               HasComp<BodyComponent>(victim) &&
+               HasComp<EmaggedComponent>(uid);
+    }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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<StackComponent>(item)?.Count ?? 1;
+        var duration = TimeSpan.FromSeconds(materialSum / reclaimerComponent.MaterialProcessRate);
+        if (duration < reclaimerComponent.MinimumProcessDuration)
+            duration = reclaimerComponent.MinimumProcessDuration;
+        return duration;
+    }
+
+    /// <inheritdoc/>
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+        var query = EntityQueryEnumerator<ActiveMaterialReclaimerComponent, MaterialReclaimerComponent>();
+        while (query.MoveNext(out var uid, out var active, out var reclaimer))
+        {
+            if (Timing.CurTime < active.EndTime)
+                continue;
+            TryFinishProcessItem(uid, reclaimer, active);
+        }
+    }
+}
index 2ade6b5705294c8c66bf51a61dfc1d4aee068830..7daea6977b0683cccf0914157d139e0db43090e7 100644 (file)
@@ -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 (file)
index 33cbc51..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Recycling
-{
-    [NetSerializable, Serializable]
-    public enum RecyclerVisuals
-    {
-        Bloody
-    }
-}
index 8322c91a48c2f340951e16b61e54d7aa78c6a2e0..c084f551c7efce63a7c9662c3d06c95fd076684b 100644 (file)
@@ -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 (file)
index 0000000..401ae15
Binary files /dev/null and b/Resources/Audio/Ambience/Objects/crushing.ogg differ
index f3cb457d7300139fa00985d45af2b7c69abdd3b9..336b7f12062dfb67156261a425f73d83a34e3f71 100644 (file)
@@ -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
index 90e682282f7bcac85fd44a48d7d88ae1c94feb81..266c1dd5a67e1dfa774002d1f4805d2ba4fb4893 100644 (file)
@@ -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
index 5c605abffc420d3473a614c3950685240b55fb6e..f10f43bd7a46098d893b42f499119e9b7586d34b 100644 (file)
   - SheetPlastic
   - SheetRGlass
   - SheetGlass1
+  - MaterialReclaimerMachineCircuitboard
 
 # Electromagnetic Theory Technology Tree
 
index d23807a1fa821f0395cf512bffe10f68a6640687..0c400828d812e42d04e00fb173ce4f1c3daf9481 100644 (file)
   - type: Clothing
     slots: [belt]
     quickEquip: false
+  - type: PhysicalComposition
+    materialComposition:
+      Cloth: 50
+  - type: StaticPrice
+    price: 25
 
 - type: entity
   abstract: true
index 1f1a8b4146d5d6639f1f2b2cf9359f3ab07ecaa4..b010f1a978491513fa216d69714eaf9b06ec3742 100644 (file)
     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
index f410104784b1c2418233f5655c9955ec54e657d3..94254e625ff1501fbd34e95a383780eaff9430ae 100644 (file)
@@ -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
index 3f5d9f87556d04291ebd280cd1bc1af8624566f8..1298ac532221a2901edd8fafabd0bcca8ab203f4 100644 (file)
   - type: Tag
     tags:
     - PetWearable
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 50
+      Plastic: 100
 
 - type: entity
   parent: ClothingMaskBase
index c3946c6606739a71ad3e5e9e522723fbe27c2aec..08fd95b2780c785f0906b001cb9ca4ecb1bc5d69 100644 (file)
     sprintModifier: 0.75
   - type: Item
     size: 100
+  - type: Tag
+    tags:
+    - WhitelistChameleon
+    - HighRiskItem
   - type: ExplosionResistance
     damageCoefficient: 0.1
   - type: ToggleableClothing
index f8398f93d06a9660645d46595c5d92dc51d78485..6fc0525604bce08865931a6e6c8d129a0648df72 100644 (file)
     sprintModifier: 1
     enabled: false
   - type: NoSlip
+  - type: Tag
+    tags:
+    - WhitelistChameleon
+    - HighRiskItem
   - type: StaticPrice
     price: 750
 
index bc428563926026e5b4f9994f8d5530605e1dcf2f..5bd5deb20b2d175c4c2749086a8fbd91ae25ee45 100644 (file)
     tags:
     - Trash
     - CannotSuicide
-  - type: Recyclable
   - type: Respirator
     damage:
       types:
     tags:
     - Trash
     - CannotSuicide
-  - type: Recyclable
   - type: Respirator
     damage:
       types:
index e708eb9d9bce1ca999a4027858b1b7bab5522c1b..5dcc5ec98ab57bff8bdeb007db7d6aa0dfb4dec9 100644 (file)
@@ -38,8 +38,6 @@
     noRot: true
     drawdepth: Mobs
     netsync: false
-  - type: Recyclable
-    safe: false
   - type: Faction
     factions:
     - SimpleNeutral
index e5e7efcf2c6c9b84b1374dcd2d30db48e1e1ac56..71ed356e2857d3e896ca6178f5836dd5d21fca7e 100644 (file)
   - type: Polymorphable
   - type: Pullable
   - type: Buckle
-  - type: Recyclable
-    safe: false
   - type: StandingState
   - type: Alerts
   - type: NameIdentifier
index 8c7181b00c2011a8d0835056f7433bc0018dd746..7a12adb345fdf20866c716e0d6cd8d98f7f85be9 100644 (file)
@@ -56,8 +56,6 @@
   - type: Pullable
   - type: Examiner
   - type: Puller
-  - type: Recyclable
-    safe: false
   - type: StandingState
   - type: Alerts
   - type: Tag
index d49205a2a3735a2ee802469a912d3c76eb12ce1b..fea68b0c2a2a622ed8123407e0a7fdffa71b62a3 100644 (file)
     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
index 6e2d74a8550ac2211c3e4c86433a6b055f55d1b7..1e355ec798760b4b89ede162dcc5edd83c45e56a 100644 (file)
@@ -72,6 +72,9 @@
     damage:
       types:
         Blunt: 5
+  - type: PhysicalComposition
+    materialComposition:
+      Glass: 25
 
 # Transformable container - normal glass
 - type: entity
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
 
 - type: entity
index 4a40578fd4cb6b234611f01e80e693308ddb22d6..b03d9175596994c7570ed09472701b8149d4d2f0 100644 (file)
       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
index 1d15533407bb2d15b25605309eadc08224772810..c91593bfc2fe8748c716d11fbedd8329ed5f7f33 100644 (file)
@@ -66,6 +66,9 @@
   - type: SolutionContainerVisuals
     maxFillLevels: 3
     fillBaseName: icon-
+  - type: PhysicalComposition
+    materialComposition:
+      Glass: 25
 
 - type: entity
   parent: DrinkBaseMug
   components:
   - type: Sprite
     sprite: Objects/Consumable/Drinks/mug_metal.rsi
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 25
 
 - type: entity
   parent: DrinkBaseMug
index d2a5a52f3c4fcc3a2982eec3554377c27de6c7f5..ae2daac052a51a036e3883d0b58e6347851f0e7f 100644 (file)
@@ -7,6 +7,9 @@
   - type: Drink
   - type: Sprite
     sprite: Objects/Consumable/Drinks/shinyflask.rsi
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 50
 
 - type: entity
   parent: DrinkBase
index 2fbc947041739ef35642003889f9ae1022feefb7..684681074e26d913c473307d7074e7194a94be5e 100644 (file)
@@ -29,6 +29,9 @@
     interfaces:
     - key: enum.TransferAmountUiKey.Key
       type: TransferAmountBoundUserInterface
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 50
 
 - type: entity
   parent: DrinkGlassBase
index 03b04d7e555307a80762c3d1f616274170d050d0..a1a1eef3f3cb22fa42d9ba14f4a5ce6c7458ee8d 100644 (file)
@@ -68,7 +68,9 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Glass: 100
   - type: SpaceGarbage
 
 # Containers
index e6392877f30a2a4c0e611109aa00c149f893150f..04693e059614116a7de0cfe12d78b1481a4e56e9 100644 (file)
@@ -69,7 +69,9 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Glass: 50
   - type: SpaceGarbage
   - type: StaticPrice
     price: 0
index 39f2bfa1a8f25aa9290eb3cc0aaab019f98977be..f5b0ff6880ece3023d03b294cb7af659ab7e8fdf 100644 (file)
@@ -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
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
   - type: SpaceGarbage
index 84bbb670be0443746e6794ebc2c4a274961e76fa..80129c01e030a3d3d67fa1213bcb871afbb99606 100644 (file)
@@ -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.
index 41eb9ca9248c3174b3c87e408c1dc7b8084a563f..9ecfae33246c3f722e9e24ba31e07f6f7683b9c6 100644 (file)
     tags:
     - Egg
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
 
 # Egg
index 31c120b6aa062f34111f0d2d7f6eeba399c453fe..a9f06099144a983b9db8438a4e14df53d101d3f0 100644 (file)
@@ -10,7 +10,6 @@
     flavors:
       - food
   - type: Food
-  - type: Recyclable
   - type: SpaceGarbage
   - type: Sprite
     netsync: false
index c9e973ff140aa42dff695bb46ea83600cfbf0a75..12b93c35a8d8b6c6d092d291d8f631a8b616543f 100644 (file)
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
index 925d5f86ab46063e70c6e671c9a384d6909f0fae..8c8c35388d922d416fc05eba49bc577ede95055b 100644 (file)
   - type: Tag
     tags:
     - Raw
+    - HighRiskItem
   - type: Sprite
     state: corgi
   - type: SolutionContainerManager
index 1c8d38aa786cc2ee001057375afe8ba32da5b0fb..d20aacdc1f27241a24ea79de404f849be265b6b1 100644 (file)
           Quantity: 4
   - type: Extractable
     grindableSolutionName: food
-  - type: Recyclable
   - type: SpaceGarbage
 
 - type: entity
index bfa59aaf64f87d03ff40065486817108a9b51e5d..ee0c29fbe927a24d5069991049ae81fd87d921a4 100644 (file)
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
   - type: SpaceGarbage
   - type: StaticPrice
     price: 0
index d7cc6a01fb409fd3fbdbcdeac4098d962f9aa65f..52fa3dd50b872b98c4791c0f109cf9be2d050785 100644 (file)
@@ -24,7 +24,9 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Plastic: 50
   - type: SpaceGarbage
 
 - type: entity
index 65e4772bff15bee91328d6fcb5af211f6a5e0ab6..777d39686977cdd603ff009e623d100da792a494 100644 (file)
@@ -12,7 +12,6 @@
     tags:
       - Cigarette
       - Trash
-  - type: Recyclable
   - type: SpaceGarbage
   - type: Clothing
     sprite: Objects/Consumable/Smokeables/Cigarettes/cigarette.rsi
index 0e4a78bca009488d271e9f762762720a0fc3dc75..42aa84d43db3e945a492684a2ad2b04b3f1a5026 100644 (file)
@@ -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
index c508333057aa0a8af94ac245e6883aeb1cf3fa43..0368c599ac39e420e9b65d7f4755ce0f9660a876 100644 (file)
@@ -8,7 +8,9 @@
     tags:
     - CigPack
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 50
   - type: SpaceGarbage
   - type: Storage
     capacity: 6
index e62db9e40f23fb7be87d7a8576ed9f7c160b1d31..c4b53f45b5bb5f8f814582c6f897a2a387ba7090 100644 (file)
@@ -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
index ef58235903e892b1dea29adbc6e65c20e94843d4..c534b730940e116925a9c645b2f07b9deb27b92b 100644 (file)
@@ -13,7 +13,6 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
   - type: StaticPrice
     price: 5
       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:
index 3a7d059e9f2c6a660eeb2f7223b3cb694f24d034..c5a308591c4c0553eae803a3101f29c39a0ecbb4 100644 (file)
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
index 0ff99e29022644aff94f0a78d325808766996e23..f11e2021c68caa6cc2c8d57b98fca59d69b572d2 100644 (file)
@@ -14,3 +14,9 @@
         - DroneUsable
     - type: StaticPrice
       price: 100
+    - type: PhysicalComposition
+      materialComposition:
+        Glass: 400
+      chemicalComposition:
+        Silicon: 20
+
index adc52ac563c7b5b0c37af705c6fb7883f83ab82d..4ace8ea4f0592bfd12228f9c596015ab5485fe71 100644 (file)
       materialRequirements:
         CableMV: 5
         CableHV: 5
+    - type: PhysicalComposition
+      materialComposition:
+        Glass: 200
+      chemicalComposition:
+        Silicon: 20
 
 - type: entity
   parent: BaseMachineCircuitboard
         Capacitor: 1
       materialRequirements:
         CableHV: 5
+    - type: PhysicalComposition
+      materialComposition:
+        Glass: 200
+      chemicalComposition:
+        Silicon: 20
 
 - type: entity
   id: ThrusterMachineCircuitboard
         Capacitor: 2
       materialRequirements:
         CableHV: 10
+    - type: PhysicalComposition
+      materialComposition:
+        Glass: 200
+      chemicalComposition:
+        Silicon: 20
 
 - type: entity
   id: ReagentGrinderMachineCircuitboard
           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
     materialRequirements:
       Steel: 10
       Plasma: 5
-      
+
 - type: entity
   id: BoozeDispenserMachineCircuitboard
   parent: BaseMachineCircuitboard
 - 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
index 25e3ef418177e231a6d67c0ff4d344634a8bc636..bfe7cfc64efbf6087504a9c500771f233a38e101 100644 (file)
         - DroneUsable
     - type: StaticPrice
       price: 100
+    - type: PhysicalComposition
+      materialComposition:
+        Glass: 400
+      chemicalComposition:
+        Silicon: 20
 
 - type: entity
   parent: BaseComputerCircuitboard
       prototype: ComputerCargoOrders
     - type: StaticPrice
       price: 750
+    - type: Tag
+      tags:
+      - DroneUsable
+      - HighRiskItem
 
 - type: entity
   parent: BaseComputerCircuitboard
       prototype: ComputerId
     - type: StaticPrice
       price: 750
+    - type: Tag
+      tags:
+      - DroneUsable
+      - HighRiskItem
 
 - type: entity
   parent: BaseComputerCircuitboard
index e4f789f1f3b262f907bc760c92717e50c9205329..ec24e4be1577d73ca907b013888749e945def7c3 100644 (file)
@@ -14,3 +14,8 @@
         - DroneUsable
     - type: StaticPrice
       price: 100
+    - type: PhysicalComposition
+      materialComposition:
+        Glass: 200
+      chemicalComposition:
+        Silicon: 20
index 7adc3a1095a85de0e6cf3ec2a61ad2013e6e730d..5b48ede4c1dd6db5620538dc3773044e2a44be7d 100644 (file)
       sprite: Objects/Misc/module.rsi
       state: charger_APC
       netsync: false
+    - type: PhysicalComposition
+      materialComposition:
+        Glass: 50
+      chemicalComposition:
+        Silicon: 20
 
 # Wallmount Substation
 - type: entity
index 55839ada5431b5b97c62aa7800f754707b79c0ef..0acd4fbf19f9a0d3f998d38b913374fce68ad04c 100644 (file)
@@ -9,3 +9,6 @@
     layers:
     - state: icon
   - type: HandTeleporter
+  - type: Tag
+    tags:
+    - HighRiskItem
index 0dc6cb3045ecbcf54bbc150a6bf0f6f59f337dc7..d0856c64f4bb4f3b787eec6d6f95160bd2ad8e94 100644 (file)
@@ -10,7 +10,6 @@
     - Write
     - Crayon
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
   - type: UserInterface
     interfaces:
index 848993121cde7164834bb35aa179430578dbf580..ca7504c063dc60a6fb10f61b84308f2200cbe49f 100644 (file)
     damage:
       types:
         Blunt: 0
+  - type: PhysicalComposition
+    materialComposition:
+      Cloth: 100
+  - type: StaticPrice
+    price: 5
 
 - type: entity
   parent: BasePlushie
   - type: Sprite
     state: narplush
 
-
 - type: entity
   parent: BasePlushie
   id: PlushieCarp
 
 - 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
       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
 
 
 ### 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
     available:
       - enum.DamageStateVisualLayers.Base:
           base: Sixteen
-  - type: StaticPrice
-    price: 3
 
 - type: entity
   parent: BaseItem
index 0a0c876e99be8e7edafc444faf2eab9188003082..e49d6a3207deac079fe2ab268bc37e21feb2a669 100644 (file)
@@ -51,8 +51,6 @@
   - type: Material
     materials:
       Glass: 100
-  - type: StackPrice
-    price: 5
   - type: Stack
     stackType: Glass
   - type: Sprite
index 2ae5298d5709f91e5eb09a4c9ebec9f7273d0900..a0711122da646aa7b80c8eb7bd2c895cd5e9ac71 100644 (file)
@@ -51,7 +51,6 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
   - type: Damageable
     damageContainer: Inorganic
index 5f3e1e6cd962899ac27796e3d9bddbda70aa3501..d893b56cce66f84b582d78a0e3967fdb76ce94a1 100644 (file)
@@ -20,3 +20,8 @@
     node: boxcardboard
     containers:
       - entity_storage
+  - type: PhysicalComposition
+    materialComposition:
+      Cardboard: 100
+  - type: StaticPrice
+    price: 10
index 8d7462cc96a5366033f8b9f35568d7981a8f8656..e7f2088b646afe83fc297a1a9bbaea52404c450b 100644 (file)
@@ -23,6 +23,8 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Glass: 50
   - type: SpaceGarbage
 
index b0fe40562ecc37099fd022907c359b7a3f7ae065..30a0065056084641f633670c42cb88a4e2716758 100644 (file)
@@ -18,6 +18,9 @@
   - type: WarpPoint
     follow: true
     location: nuke disk
+  - type: Tag
+    tags:
+    - HighRiskItem
 
 - type: entity
   name: nuclear authentication disk
index 6499ceeab1deaeabd0a2336acd3efacef119d3ea..b21f47cb60685595416029260cdf7f5fa46fdbce 100644 (file)
   components:
   - type: Item
     size: 2
-  - type: Recyclable
   - type: Tag
     tags:
     - Trash
+  - type: PhysicalComposition
+    materialComposition:
+      Plastic: 25
 
 - type: entity
   name: broken zipties
   - type: Sprite
     sprite: Objects/Misc/cablecuffs.rsi
     state: cuff-broken
-    color: forestgreen
\ No newline at end of file
+    color: forestgreen
index 50da83763bb99f25be8a770b55bb7477ce0f2f5e..b1ba64a8f8290d4a582f5e7d48374f8f78982ef3 100644 (file)
     heldPrefix: gold
   - type: PresetIdCard
     job: Captain
+  - type: Tag
+    tags:
+      - WhitelistChameleon
+      - HighRiskItem
 
 - type: entity
   parent: IDCardStandard
     - state: idzookeeper
   - type: PresetIdCard
     job: Zookeeper
-    
+
 - type: entity
   parent: IDCardStandard
   id: DetectiveIDCard
     - state: idcentcom
   - type: Item
     heldPrefix: gold
-    
+
 
 - type: entity
   parent: IDCardStandard
index 4dbe14d20298bd4bef55142b9e9b162692b92e13..d35fb1eafbdb70e6ce7f5b278962db13f313421c 100644 (file)
@@ -31,7 +31,6 @@
     - Trash
   - type: Appearance
   - type: PaperVisuals
-  - type: Recyclable
 
 - type: entity
   name: office paper
     sprite: Objects/Misc/bureaucracy.rsi
     heldPrefix: pen
     size: 2
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 25
 
 - type: entity
   name: Cybersun pen
index ee6d6804856b7b660188096ba4ce008b1d395c43..e2891c48bff20f58ec7ffaa14c0e65d06459546f 100644 (file)
@@ -10,7 +10,6 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
 
 - type: entity
index a0aa6b4330d0794e4bdbe6e6b12b2fe4d30e5a0e..ccc3cdb14639501d49400a1b4df8fc8d4296d896 100644 (file)
@@ -60,7 +60,9 @@
   - type: Tag
     tags:
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Glass: 25
   - type: SpaceGarbage
 
 - type: entity
index 29ddee84c62769acf5b7fce6027f3953fbddb3ad..d1668feae579aef8e63df54ca772ce54efaf49f0 100644 (file)
             - !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
 
index 27361d6b1b0f970577485471b0a6cf56cc6bca6c..b08a66527456276ea0b4adfbd43f7f6aa24c48fe 100644 (file)
@@ -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.
index 21ce9f46d44da07e4990d8b1bf7b6f4fbd69854d..1bb1ae59ca3a1e4666d9b46ab97449671f873926 100644 (file)
@@ -32,3 +32,7 @@
       - DroneUsable
   - type: StaticPrice
     price: 80
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 400
+      Glass: 100
index 3c255483d44cf5f2384fbb4ccffb9c2f0a15fc29..73c6a3a08f2ef65a886bb889d56e5eba9c95b96c 100644 (file)
@@ -9,7 +9,9 @@
     tags:
     - Bottle
     - Trash
-  - type: Recyclable
+  - type: PhysicalComposition
+    materialComposition:
+      Glass: 25
   - type: SpaceGarbage
   - type: Sprite
     sprite: Objects/Specific/Chemistry/bottle.rsi
index a61b6efb4e28ba6a10d5f498f2e805b809a92384..532e8388179ed6a39e5527da574bdbbd95bac640 100644 (file)
@@ -58,3 +58,6 @@
   - type: Tag
     tags:
       - Bucket
+  - type: PhysicalComposition
+    materialComposition:
+      Plastic: 50
index 71261cc8a9e6454dd855fd9b8a0f3d0a6e47cc6c..e67c8f74a4b7798ab74e3feedda696173627de31 100644 (file)
@@ -8,7 +8,6 @@
     tags:
     - Flare
     - Trash
-  - type: Recyclable
   - type: SpaceGarbage
   - type: ExpendableLight
     spentName: expendable-light-spent-flare-name
index 21b5ebde9467658933eecea2086df2304792e699..4c8dcda6c6db6d876a73d36657e1f8f1429bdeac 100644 (file)
     damage:
       types:
         Blunt: 10
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 400
+  - type: StaticPrice
+    price: 20
 
 - type: entity
   parent: GasTankBase
     damage:
       types:
         Blunt: 5
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
 
 - type: entity
   parent: EmergencyOxygenTank
index 9b58d9105ea4bdc89034829e77721f01936f5b5e..41919ddce6c96928b0de5f8c7f81b62ad89c4f86 100644 (file)
@@ -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
index 388cef6019826d133513786f46c4b4e6f5a1a16b..cd8da06a32adc35da1efc4f6808e191d406e7664 100644 (file)
   - type: Item
     sprite: Objects/Tanks/Jetpacks/captain.rsi
     size: 30
+  - type: Tag
+    tags:
+    - HighRiskItem
 
 # Filled captain
 - type: entity
     sprite: Objects/Tanks/Jetpacks/void.rsi
     slots:
       - Back
-      - suitStorage      
+      - suitStorage
 
 # Filled void
 - type: entity
index 9350cf4da667232d7d175913916e4453ed6a494f..3700a1bfb163015a05284706561cd49fc097c57d 100644 (file)
@@ -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
index ac419a7016f3d7bfb11cfac28e7ade213ae9c831..9708b7f538a0da20174d289ee289c75dedcbef8f 100644 (file)
@@ -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
 
       - Anchoring
     useSound:
       path: /Audio/Items/ratchet.ogg
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
   - type: StaticPrice
     price: 40
 
     useSound:
       path: /Audio/Items/crowbar.ogg
   - type: TilePrying
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
   - type: StaticPrice
     price: 40
 
   - type: Tag
     tags:
       - DroneUsable
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
+      Plastic: 100
   - type: StaticPrice
     price: 60
 
           path: /Audio/Items/drill_use.ogg
         changeSound:
           path: /Audio/Items/change_drill.ogg
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 300
+      Plastic: 100
   - type: StaticPrice
     price: 100
 
     quickEquip: false
     slots:
     - Belt
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 600
+      Plastic: 100
   - type: StaticPrice
     price: 100
 
   - type: Item
     sprite: Objects/Tools/rcd.rsi
     heldPrefix: ammo
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
+      Plastic: 100
   - type: StaticPrice
     price: 60
 
         Blunt: 10
   - type: Item
     sprite: Objects/Tools/shovel.rsi
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 100
+      Wood: 50
+  - type: StaticPrice
+    price: 25
 
 - type: entity
   name: omnitool
index fbf43fefa3770457c05022d2f397516dab930778..0834b192b07d0a334168fd7f08fdd75935a8387d 100644 (file)
@@ -60,6 +60,9 @@
     color: orange
   - type: Appearance
   - type: RequiresEyeProtection
+  - type: PhysicalComposition
+    materialComposition:
+      Steel: 200
   - type: StaticPrice
     price: 40
   - type: IgnitionSource
index c021eebed6eeb8b1b72d3f21fa24781438f227a7..230be6a76b318f54c985e21d8155f15137daf842 100644 (file)
@@ -11,5 +11,4 @@
     - Cartridge
   - type: Item
     size: 1
-  - type: Recyclable
   - type: SpaceGarbage
index 39542eccc206945f23a4ef2b0afb0788719520c0..34a39c1583eb707677c0bdae9a8ffe02b57942a8 100644 (file)
@@ -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
index 2b2f7ed0b07e75645917e18a01d3fcfd2c5071cf..ba1e9cfa4637b8bf8d4cd18b793a32e8b51b97a7 100644 (file)
     steps: 5
     zeroVisible: true
   - type: Appearance
+  - type: Tag
+    tags:
+    - HighRiskItem
   - type: StaticPrice
     price: 750
 
index f7599c12c9bba667b342e9f86da6e80134695897..6bce0ecbf865b1cd16d051a89740fdefa1b9237c 100644 (file)
       - 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 (file)
index 0000000..26d4adf
--- /dev/null
@@ -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
index 6faa94dd3f9c3c3a848ef6b2952131549e296f61..49b6e563dd95f546e057fb3cf15e84c3962ac530 100644 (file)
     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:
     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
index 345eea98974c89f3131a0027cc2be85716cb7ef2..2e89c2f622702667d22e30f209fc83990ce09b82 100644 (file)
         - MachineMask
         layer:
         - MachineLayer
-    - type: Recyclable
+    - type: PhysicalComposition
+      materialComposition:
+        Steel: 500
     - type: StaticPrice
       price: 60
 
index 2234f3de7288f94047a2841e5b472d352234f258..e68dbb8f62ce5044a856c2bd540a91e4802d497e 100644 (file)
     graph: WindowDirectional
     node: windowDirectional
   - type: StaticPrice
-    price: 5
+    price: 10
 
 - type: entity
   id: WindowTintedDirectional
     state: tinted_window
   - type: Occluder
     boundingBox: "-0.5,-0.5,0.5,-0.3"
-  - type: StaticPrice
-    price: 5
 
 - type: entity
   id: WindowFrostedDirectional
   - type: Icon
     sprite: Structures/Windows/directional.rsi
     state: frosted_window
-  - type: StaticPrice
-    price: 5
index faaa2523ece7572c452cadcabba7f680c532b761..15af26eb471557b45ded415772a01b77e21c1a4d 100644 (file)
@@ -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
index c5aea718000d715272ea3c0daa1a67a76ae35b56..468c0e5a56a63756fee6853fd8eecc0af8c47985 100644 (file)
     Glass: 900
     Gold: 100
 
+- type: latheRecipe
+  id: MaterialReclaimerMachineCircuitboard
+  result: MaterialReclaimerMachineCircuitboard
+  completetime: 4
+  materials:
+    Steel: 100
+    Glass: 900
+
 - type: latheRecipe
   id: OreProcessorMachineCircuitboard
   result: OreProcessorMachineCircuitboard
index 4c537c30e5a43d98fe5dd50a0b2608b42fb65646..1515ccdaada5cb70622609f04897cf35cd9cbeb4 100644 (file)
@@ -18,7 +18,7 @@
   result: Bucket
   completetime: 2
   materials:
-    Steel: 100
+    Plastic: 100
 
 - type: latheRecipe
   id: WetFloorSign
index bfb69b2448e4ebcefec9832cbc5b49f376165248..f7870ccdc6ec0aaa6ad2ab26c15188b834313be7 100644 (file)
 - 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 (file)
index 0000000..32efd40
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-1.png differ
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 (file)
index 0000000..71883d8
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-2.png differ
diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-3.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-3.png
new file mode 100644 (file)
index 0000000..747e6a3
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-3.png differ
diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-4.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-4.png
new file mode 100644 (file)
index 0000000..e734cce
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-4.png differ
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 (file)
index 0000000..1ed097f
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-5.png differ
diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-6.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-6.png
new file mode 100644 (file)
index 0000000..ab269ef
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/fill-6.png differ
diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-active.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-active.png
new file mode 100644 (file)
index 0000000..a228b14
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-active.png differ
diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-idle.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-idle.png
new file mode 100644 (file)
index 0000000..96a795a
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/gear-idle.png differ
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 (file)
index 0000000..7ba2ef7
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/icon.png differ
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 (file)
index 0000000..3e27312
--- /dev/null
@@ -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 (file)
index 0000000..b3de5c1
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/panel.png differ
diff --git a/Resources/Textures/Structures/Machines/material_reclaimer.rsi/unlit.png b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/unlit.png
new file mode 100644 (file)
index 0000000..9c9511a
Binary files /dev/null and b/Resources/Textures/Structures/Machines/material_reclaimer.rsi/unlit.png differ