Add industrial smelters (admin only) (#43651)

* Initial commit with shredder base functionality

* Fix items being deleted and make items continue moving after a grid tile gets deleted

* Make a bunch of previously unpullable/unrecyclable things work

* temp commit

* Fix the recycling hitbox to actually be good code

* Fix material spawning via tiles

* Finish up the prototype visuals for the shredder

* SHRED EVERYTHING

* Add reduced efficiency for recycling with shreder

* Fix miss in conflict merge

* Make more things pullable

* Update shredder textures to look different to recyclers

* Remove unused

* "fix" repairability and sprites

* Update sprites, set up TODO-s

* Fix switches, description

* Fix up some comments

* Add "Do Not Map" to the smelter

* Add to meta.json

* Review fixes

* Review changes

* Review changes

* Remove readonly in new system

* Aaaand partial class

* Fix the final test issues

* Update and resolve merge conflicts

* Update the impulse strength to match new mass values

* Fix issues after merge conflict

* Update size, fix physics resolve errors, prevent computers/circuitboards from being deleted

* Review comments

* Fix tiles giving steel

* Add a todo about tilestacking
This commit is contained in:
SlamBamActionman 2026-09-22 09:04:21 +00:00 • committed by GitHub
parent 86e633be6e
commit 0cd76c7bac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 557 additions and 38 deletions

View file

@ -37,6 +37,9 @@ public sealed class ReclaimerLoopTest : InteractionTest
var reclaimComp = Comp<MaterialReclaimerComponent>(Target);
// Put a floor tile down
await InteractUsing(FloorTileId);
// Power the reclaimer
await SpawnEntity(ApcId, SEntMan.GetCoordinates(TargetCoords));
await RunTicks(1);
@ -49,9 +52,6 @@ public sealed class ReclaimerLoopTest : InteractionTest
// Check that reclaimer enabled
Assume.That(reclaimComp.Enabled, "The reclaimer did not get or stay enabled");
// Put a floor tile down
await InteractUsing(FloorTileId);
// Reclaimer can't reclaim materials? Job's done.
if (!reclaimComp.ReclaimMaterials)
Assert.Ignore("Cannot reclaim materials");

View file

@ -210,19 +210,7 @@ public sealed partial class MaterialReclaimerSystem : SharedMaterialReclaimerSys
_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);
}
}
_materialStorage.EjectAllMaterial(reclaimer, xform.Coordinates, storage, true);
}
private void SpawnChemicalsFromComposition(EntityUid reclaimer,

View file

@ -224,11 +224,13 @@ public sealed partial class MaterialStorageSystem : SharedMaterialStorageSystem
/// <param name="entity">The entity with storage to eject from.</param>
/// <param name="coordinates">The position where to spawn the created sheets. If not given, they're spawned next to the entity.</param>
/// <param name="component">The storage component on <paramref name="entity"/>. Resolved automatically if not given.</param>
/// <param name="mergeContacts">If true, spawned stacks will attempt to merge with existing stacks in the area.</param>
/// <returns>The stack entities that were spawned.</returns>
public List<EntityUid> EjectAllMaterial(
EntityUid entity,
EntityCoordinates? coordinates = null,
MaterialStorageComponent? component = null)
MaterialStorageComponent? component = null,
bool mergeContacts = false)
{
if (!Resolve(entity, ref component))
return new List<EntityUid>();
@ -242,6 +244,14 @@ public sealed partial class MaterialStorageSystem : SharedMaterialStorageSystem
allSpawned.AddRange(spawned);
}
if (mergeContacts)
{
foreach (var stack in allSpawned)
{
_stackSystem.TryMergeToContacts(stack);
}
}
return allSpawned;
}
}

View file

@ -0,0 +1,26 @@
using Content.Shared.Materials;
using Content.Shared.Maps;
namespace Content.Server.Materials;
/// <inheritdoc/>
public sealed partial class ServerTileReclaimerSystem : TileReclaimerSystem
{
[Dependency] private MaterialStorageSystem _materialStorage = default!;
protected override void SpawnMaterialsFromComposition(Entity<MaterialStorageComponent?, TransformComponent?> ent,
ContentTileDefinition tileDefinition,
float efficiency)
{
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2))
return;
foreach (var (material, amount) in tileDefinition.MaterialComposition)
{
var outputAmount = (int) (amount * efficiency);
_materialStorage.TryChangeMaterialAmount(ent, material, outputAmount, ent.Comp1);
}
_materialStorage.EjectAllMaterial(ent, ent.Comp2.Coordinates, ent.Comp1, true);
}
}

View file

@ -1,4 +1,5 @@
using Content.Shared.Atmos;
using Content.Shared.Materials;
using Content.Shared.Movement.Systems;
using Content.Shared.Shuttles.Systems;
using Content.Shared.Tools;
@ -132,6 +133,12 @@ public sealed partial class ContentTileDefinition : IPrototype, IInheritingProto
/// </summary>
[DataField] public bool EditorHidden { get; private set; } = false;
/// <summary>
/// If this tile is reclaimed, what materials would come out of it?
/// </summary>
[DataField]
public Dictionary<ProtoId<MaterialPrototype>, int> MaterialComposition = new();
public void AssignTileId(ushort id)
{
TileId = id;

View file

@ -0,0 +1,77 @@
using Content.Shared.Maps;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Materials;
/// <summary>
/// Makes an entity into a grid reclaimer, able to delete grids in front of it and produce materials.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
[Access(typeof(TileReclaimerSystem))]
public sealed partial class TileReclaimerComponent : Component
{
/// <summary>
/// An "enable" toggle for things like interfacing with machine linking.
/// </summary>
[DataField, AutoNetworkedField]
public bool Enabled = true;
/// <summary>
/// A master control for whether or not the reclaimer is broken and can function.
/// </summary>
[DataField, AutoNetworkedField]
public bool Broken;
/// <summary>
/// A whitelist for what grid entities can be affected by this reclaimer.
/// </summary>
[DataField, AutoNetworkedField]
public EntityWhitelist? Whitelist;
/// <summary>
/// A whitelist for what grid entities can be affected by this reclaimer.
/// </summary>
[DataField, AutoNetworkedField]
public EntityWhitelist? Blacklist;
/// <summary>
/// The delay after a grid has been consumed that it can recycle again.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan RecycleDelay = TimeSpan.FromSeconds(0.3f);
/// <summary>
/// The bounding box in local coordinates for where it will check for grids to reclaim.
/// </summary>
[DataField, AutoNetworkedField]
public Box2 RecyclingBox = new(-0.5f, 0.5f, 0.5f, 1.5f);
/// <summary>
/// Multiplier for the material being extracted from the tile, as defined in the tile's <see cref="ContentTileDefinition.MaterialComposition"/>
/// </summary>
[DataField, AutoNetworkedField]
public float Efficiency = 1f;
/// <summary>
/// How strong impulse the reclaimer should apply to a grid after it has destroyed one of it tiles.
/// This helps guide the grid further into the reclaimer.
/// </summary>
[DataField, AutoNetworkedField]
public float SlurpStrength = 1600f;
/// <summary>
/// Next time a recycling attempt can be made.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadOnly)]
[AutoNetworkedField, AutoPausedField]
public TimeSpan NextRecycle;
/// <summary>
/// The sound played when something is being processed.
/// </summary>
[DataField, AutoNetworkedField]
public SoundSpecifier? Sound;
}

View file

@ -0,0 +1,141 @@
using Content.Shared.Conveyor;
using Content.Shared.Maps;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Content.Shared.Materials;
/// <summary>
/// Handles interactions and logic related to <see cref="TileReclaimerComponent"/>.
/// </summary>
public abstract partial class TileReclaimerSystem : EntitySystem
{
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private SharedTransformSystem _transform = default!;
[Dependency] private IGameTiming _timing = default!;
[Dependency] private SharedMapSystem _mapSystem = default!;
[Dependency] private SharedPhysicsSystem _physics = default!;
[Dependency] private EntityLookupSystem _lookup = default!;
[Dependency] private TurfSystem _turf = default!;
[Dependency] private EntityWhitelistSystem _whitelist = default!;
[Dependency] private EntityQuery<PhysicsComponent> _physicsComponentQuery = default!;
[SubscribeLocalEvent]
private void OnMapInit(Entity<TileReclaimerComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextRecycle = _timing.CurTime + ent.Comp.RecycleDelay;
DirtyField(ent.Owner, ent.Comp, nameof(TileReclaimerComponent.NextRecycle));
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<TileReclaimerComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var tileReclaimer, out var xform))
{
if (tileReclaimer.NextRecycle > _timing.CurTime)
return;
Update((uid, tileReclaimer, xform));
}
}
private void Update(Entity<TileReclaimerComponent, TransformComponent> ent)
{
ent.Comp1.NextRecycle += ent.Comp1.RecycleDelay;
DirtyField(ent.Owner, ent.Comp1, nameof(TileReclaimerComponent.NextRecycle));
//TODO: The recycler is hardcoded to rely on the conveyor component for its powered state, and this system is based on the same functionality. That should be fixed into a more general solution for both systems, but for the sake of consistency I'm not doing that now.
if (!TryComp<ConveyorComponent>(ent, out var conveyor) || conveyor.State == ConveyorState.Off)
return;
var reclaimerGrid = _transform.GetGrid((ent, ent));
if (reclaimerGrid == null)
return;
var mapPos = _transform.GetMapCoordinates(ent);
var grids = new List<Entity<MapGridComponent>>();
var entMatrix = _transform.GetWorldMatrix(ent.Owner);
var box = entMatrix.TransformBox(ent.Comp1.RecyclingBox);
_mapSystem.FindGridsIntersecting(mapPos.MapId, box, ref grids);
var shredded = false;
foreach (var grid in grids)
{
if (TerminatingOrDeleted(grid) || EntityManager.IsQueuedForDeletion(grid))
continue;
if (grid == reclaimerGrid)
continue;
if (!_whitelist.CheckBoth(grid, ent.Comp1.Blacklist, ent.Comp1.Whitelist))
continue;
var intersectingEntities = new HashSet<EntityUid>();
foreach (var tile in _mapSystem.GetTilesIntersecting(grid.Owner, grid.Comp, box))
{
var tileDef = _turf.GetContentTileDefinition(tile);
if (tileDef.Indestructible)
continue;
intersectingEntities.Clear();
_lookup.GetLocalEntitiesIntersecting(tile.GridUid, tile.GridIndices, intersectingEntities);
foreach (var entityOnTile in intersectingEntities)
{
if (!_physicsComponentQuery.TryComp(entityOnTile, out var physicsComp))
{
// Marker/Spawn entities and similar don't have physics, so we just plain remove them.
QueueDel(entityOnTile);
continue;
}
_physics.SetCanCollide(entityOnTile, true, body: physicsComp);
_physics.ApplyLinearImpulse(entityOnTile, _physics.GetLinearVelocity(grid.Owner, Transform(entityOnTile).LocalPosition), body: physicsComp);
}
_mapSystem.SetTile(tile.GridUid, grid, tile.GridIndices, Tile.Empty);
// TODO: Ideally this should be using tilestacking to get materials out of every tile in the stack, as opposed to the top one. Not doing it right now as it may be reworked however.
SpawnMaterialsFromComposition((ent, null, ent.Comp2), tileDef, ent.Comp1.Efficiency);
shredded = true;
// If it was the last tile, grid's probably gone
if (TerminatingOrDeleted(grid))
continue;
// We suck in the grid slurrrrp
// TODO: This can probably be refined, as it applies a stacking speed which could theoretically get out of hand if a grid is very long.
_physics.ApplyLinearImpulse(grid, ent.Comp1.SlurpStrength * _transform.GetWorldRotation(ent.Comp2).ToWorldVec(), tile.GridIndices);
}
}
if (!shredded)
return;
_audio.PlayPvs(ent.Comp1.Sound, ent);
}
/// <summary>
/// Spawns materials from the <see cref="ContentTileDefinition.MaterialComposition"/> of the given tile.
/// </summary>
/// <param name="ent">Entity performing the spawning action.</param>
/// <param name="tileDefinition">Tile definition of the tile being reclaimed.</param>
/// <param name="efficiency">Multiplier of the material amount.</param>
protected virtual void SpawnMaterialsFromComposition(Entity<MaterialStorageComponent?, TransformComponent?> ent,
ContentTileDefinition tileDefinition,
float efficiency)
{
// Handled on the server because that's where MaterialStorageSystem is.
}
}

View file

@ -276,3 +276,8 @@
copyright: "by petercornell (Freesound), sampled and sped up to 400% by whatston3 (GitHub)"
license: "CC0-1.0"
source: https://freesound.org/people/petercornell/sounds/837240/
- files: ["grind.ogg"]
copyright: 'modified by SlamBamActionman (Github) from "01-1 Angle Grinder.wav" by domiscz of Freesound.org'
license: "CC-BY-SA-3.0"
source: https://freesound.org/people/domiscz/sounds/461728/

Binary file not shown.

View file

@ -81,6 +81,9 @@
interfaces:
enum.DeimplantUiKey.Key:
type: DeimplantBoundUserInterface
- type: Tag
tags:
- SmeltingImmune
- type: entity
id: Implanter
@ -153,7 +156,8 @@
True: {state: broken}
False: {state: implanter1}
- type: Tag
tags: []
tags:
- SmeltingImmune
#Fun implanters

View file

@ -26,6 +26,8 @@
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 5
mask:
- LowImpassable
layer:
- DoorPassable
- type: Damageable

View file

@ -18,6 +18,7 @@
collection: GlassSmash
- type: Transform
anchored: true
- type: Pullable
- type: Clickable
- type: InteractionOutline
- type: Sprite

View file

@ -62,7 +62,6 @@
arrows:
- offset: 0, -1.35
- offset: 0, 0
- type: CollideMaterialReclaimer
- type: MaterialReclaimer
enabled: false
@ -132,3 +131,136 @@
- type: Speech
speechVerb: Robotic
speechSounds: SyndieBorg
- type: entity
parent: [ BaseMachinePowered, StructureHealthMachineStrong ]
id: IndustrialSmelter
name: industrial smelter
suffix: Do Not Map
description: A massive stationary machine capable of chewing through flooring and walls. Must be powered to function.
components:
- type: Sprite
drawdepth: Doors
sprite: Structures/Machines/smelter.rsi
layers:
- state: smelter0
map: ["enum.RecyclerVisualLayers.Main"]
offset: 0, 0.5
- type: Appearance
- type: RecyclerVisuals
baseKey: smelter
- type: Conveyor
angle: 90 # Conveyor goes sideways
- type: DeviceLinkSink
ports:
- Reverse
- Forward
- Off
- type: MaterialStorage
insertOnInteract: false
- type: CollideMaterialReclaimer
- type: MaterialReclaimer
enabled: false
scaleProcessSpeed: false
minimumProcessDuration: 0
efficiency: 0.1
blacklist: # TODO: This will need to be iterated on to properly balance all items
components:
- Material
- Pda
- IdCard
- GrapplingProjectile
- Circuitboard
- Computer
tags:
- HighRiskItem
- SmeltingImmune
sound:
path: /Audio/Effects/saw.ogg
params:
volume: -3
cutOffSound: false
- type: TileReclaimer
sound:
path: /Audio/Effects/grind.ogg # TODO: Needs more CRONCH, more SMELT
params:
volume: -3
recyclingBox: "-0.35, 0.5, 0.35, 1"
blacklist:
components:
- StationMember
- CargoShuttle
- ArrivalsShuttle
- EmergencyShuttle
- type: Fixtures
fixtures:
brrt:
shape:
!type:PhysShapeAabb
bounds: "-0.5, -0.15, 0.5, 1.5"
hard: false
layer:
- FullTileLayer
- DoorPassable # Here to hit floor objects like carpets etc.
mask:
- FullTileMask # Necessary for grilles.
- TabletopMachineLayer # This is needed for lights.
collision:
shape:
!type:PhysShapeAabb
bounds: "-0.49,-0.49,0.49,0.49"
hard: true
mask:
- Impassable
layer:
- Opaque
- BulletImpassable
conveyor:
shape: !type:PolygonShape
vertices:
- -0.55,-0.55
- 0.55,-0.55
- 0.55,0.55
- -0.55,0.55
layer:
- Impassable
- MidImpassable
- LowImpassable
hard: False
- type: AmbientSound
enabled: false
volume: -8
range: 5
sound:
path: /Audio/Ambience/Objects/circular_saw.ogg
- type: Transform
anchored: true
noRot: false
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 200
behaviors:
- !type:DoActsBehavior
acts: ["Breakage"]
- !type:PlaySoundBehavior
sound:
collection: MetalBreak
- type: Repairable
doAfterDelay: 5
fuelCost: 25
- type: Tag
tags:
- EmagImmune
- type: Speech
speechVerb: Robotic
speechSounds: SyndieBorg
- type: entity
parent: IndustrialSmelter
id: IndustrialSmelterAdmeme
suffix: No Grid Whitelist, Admeme
components:
- type: TileReclaimer
blacklist: null

View file

@ -8,6 +8,7 @@
components:
- type: Transform
anchored: true
- type: Pullable
- type: Physics
bodyType: Static
- type: Sprite
@ -19,6 +20,8 @@
shape:
!type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,0.5"
mask:
- DoorPassable
- type: Airtight
noAirWhenFullyAirBlocked: false
- type: Clickable
@ -35,6 +38,7 @@
components:
- type: Transform
anchored: true
- type: Pullable
- type: Physics
bodyType: Static
- type: Sprite
@ -46,6 +50,8 @@
shape:
!type:PhysShapeAabb
bounds: "-0.48,-0.48,0.48,-0.40"
mask:
- DoorPassable
- type: Airtight
noAirWhenFullyAirBlocked: false
airBlockedDirection:

View file

@ -129,6 +129,15 @@
guides:
- VoltageNetworks
- Power
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.45,0.25,0.05"
density: 5
mask:
- FullTileMask
# APC under construction
- type: entity

View file

@ -132,13 +132,11 @@
# Compact Wall Substation Base
- type: entity
parent: [ CoreSubstation, StructureHealthMachine ]
parent: [ CoreSubstation, BaseWallmount, StructureHealthMachine ]
id: BaseSubstationWall
categories: [ HideSpawnMenu ]
name: wallmount substation
description: A substation designed for compact shuttles and spaces.
placement:
mode: SnapgridCenter
components:
- type: AmbientSound
range: 2
@ -149,20 +147,12 @@
netsync: false
castShadows: false
offset: 0, -0.2
- type: Clickable
- type: ContainerFill
containers:
board: [ WallmountSubstationElectronics ]
- type: ContainerContainer
containers:
board: !type:Container
- type: InteractionOutline
- type: Physics
bodyType: Static
canCollide: false
- type: Fixtures
- type: Transform
anchored: true
- type: Sprite # TODO: add sprite for maintenance panel open
drawdepth: WallMountedItems
sprite: Structures/Power/substation.rsi
@ -196,7 +186,15 @@
maxIntensity: 50
intensitySlope: 2
totalIntensity: 100
- type: WallMount
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.35,-0.45,0.35,0.20"
density: 5
mask:
- FullTileMask
# Substations in use

View file

@ -171,6 +171,15 @@
- entity_storage
- type: Paintable
group: WallCloset
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.25,-0.35,0.25,0.35"
density: 5
mask:
- FullTileMask
#Wall locker
- type: entity

View file

@ -97,8 +97,6 @@
- type: DeviceLinkSource
ports:
- Pressed
- type: Transform
anchored: false
- type: Destructible
thresholds:
- trigger: *DamageMachineFlimsy
@ -493,7 +491,18 @@
sprite: Structures/Wallmounts/switch-frame.rsi
state: grey
- type: Rotatable
- type: Transform
anchored: true
- type: Pullable
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 5
mask:
- FullTileMask
- type: entity
id: ButtonFrameGrey

View file

@ -12,7 +12,6 @@
- type: Rotatable
- type: Physics
canCollide: false
- type: Fixtures
- type: Clickable
- type: WallMount
- type: InteractionOutline
@ -23,6 +22,16 @@
- Structure
- type: Sprite
drawdepth: WallMountedItems
- type: Pullable
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 5
mask:
- FullTileMask
- type: entity
abstract: true

View file

@ -160,7 +160,17 @@
graph: Grille
node: grilleBroken
deconstructionTarget: start
- type: Fixtures # overwrite BaseStructure parent.
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,-0.28125"
density: 5
mask:
- DoorPassable
layer:
- DoorPassable
- type: Physics
bodyType: Static
canCollide: false

View file

@ -321,6 +321,9 @@
density: 4000
- type: RadiationBlocker
resistance: 5
- type: Tag
tags:
- SmeltingImmune
- type: entity
parent: [ WallPlastitaniumIndestructible, StructureHealthWallStrong ]
@ -353,6 +356,10 @@
state: state0
- type: RadiationBlocker
resistance: 5
- type: Tag
tags:
- Diagonal
- SmeltingImmune
- type: entity
parent: [ WallPlastitaniumDiagonalIndestructible, StructureHealthWallStrong ]

View file

@ -86,6 +86,11 @@
components:
- type: ExaminableDamage
messages: null
- type: Tag
tags:
- ForceFixRotations
- Window
- SmeltingImmune
- type: entity
parent: [ PlastitaniumWindowSquareBase, StructureHealthBarrierStrong ]
@ -171,6 +176,11 @@
components:
- type: ExaminableDamage
messages: null
- type: Tag
tags:
- ForceFixRotations
- Window
- SmeltingImmune
- type: entity
id: PlastitaniumWindowDiagonal

View file

@ -1,6 +1,6 @@
# TODO move this to tiles once tile smoothing is supported
- type: entity
parent: StructureHealthFurniture
parent: [ BaseStructure, StructureHealthFurniture ]
id: Catwalk
name: catwalk
description: A catwalk for easier EVA maneuvering and cable placement.
@ -17,8 +17,6 @@
- type: Icon
sprite: Structures/catwalk.rsi
state: catwalk_preview
- type: Transform
anchored: true
- type: IconSmooth
key: catwalk
base: catwalk_
@ -51,3 +49,16 @@
cost: 2
delay: 2
fx: EffectRCDDeconstruct2
- type: Physics
canCollide: false
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 5
mask:
- LowImpassable
layer:
- DoorPassable

View file

@ -22,6 +22,8 @@
- FloorDirt
- PlatingRCD
- FloorHullReinforced
materialComposition:
Steel: 100 # 0.5 for lattice, 0.25 for plating, 0.25 for tile
- type: tile
id: FloorSteelCheckerLight

View file

@ -6,6 +6,8 @@
isSubfloor: true
footstepSounds:
collection: FootstepPlating
materialComposition:
Steel: 75 # 0.75 sheets; 0.5 for the lattice, 0.25 for the tile
baseTurf: Lattice
- type: tile
@ -77,3 +79,5 @@
heatCapacity: 10000
mass: 200
variants: 3
materialComposition:
Steel: 50 # 0.5 sheets = 1 rod

View file

@ -1289,6 +1289,9 @@
- type: Tag
id: Shiv # CargoBounty: BountyShiv
- type: Tag
id: SmeltingImmune # for entities that should not be deleted industrial smelter's MaterialReclaimer
- type: Tag
id: Sidearm # Storage whitelist: ClothingBeltSecurity, ClothingShoesMilitaryBase

View file

@ -0,0 +1,39 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Smelter sprites by SlamBamActionman (Github)",
"size": {
"x": 32,
"y": 64
},
"states": [
{
"name": "smelter0"
},
{
"name": "smelter1",
"delays": [
[
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "smelter1-0",
"delays": [
[
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "smelter2"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB