artifact crusher (#22301)

This commit is contained in:
Nemanja
2023-12-11 18:15:47 -05:00
committed by GitHub
parent 0433d3b06a
commit 6e91346ff3
23 changed files with 501 additions and 1 deletions

View File

@@ -37,15 +37,27 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStora
{ {
if (open) if (open)
{ {
args.Sprite.LayerSetVisible(StorageVisualLayers.Door, true); if (comp.OpenDrawDepth != null)
args.Sprite.DrawDepth = comp.OpenDrawDepth.Value;
if (comp.StateDoorOpen != null) if (comp.StateDoorOpen != null)
{
args.Sprite.LayerSetState(StorageVisualLayers.Door, comp.StateDoorOpen); args.Sprite.LayerSetState(StorageVisualLayers.Door, comp.StateDoorOpen);
args.Sprite.LayerSetVisible(StorageVisualLayers.Door, true);
}
else
{
args.Sprite.LayerSetVisible(StorageVisualLayers.Door, false);
}
if (comp.StateBaseOpen != null) if (comp.StateBaseOpen != null)
args.Sprite.LayerSetState(StorageVisualLayers.Base, comp.StateBaseOpen); args.Sprite.LayerSetState(StorageVisualLayers.Base, comp.StateBaseOpen);
} }
else else
{ {
if (comp.ClosedDrawDepth != null)
args.Sprite.DrawDepth = comp.ClosedDrawDepth.Value;
if (comp.StateDoorClosed != null) if (comp.StateDoorClosed != null)
{ {
args.Sprite.LayerSetState(StorageVisualLayers.Door, comp.StateDoorClosed); args.Sprite.LayerSetState(StorageVisualLayers.Door, comp.StateDoorClosed);

View File

@@ -45,4 +45,16 @@ public sealed partial class EntityStorageVisualsComponent : Component
[DataField("stateUnlocked")] [DataField("stateUnlocked")]
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public string? StateUnlocked = "unlocked"; public string? StateUnlocked = "unlocked";
/// <summary>
/// The drawdepth the object has when it's open
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public int? OpenDrawDepth;
/// <summary>
/// The drawdepth the object has when it's closed
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public int? ClosedDrawDepth;
} }

View File

@@ -0,0 +1,9 @@
using Content.Shared.Xenoarchaeology.Equipment;
namespace Content.Client.Xenoarchaeology.Equipment;
/// <inheritdoc/>
public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
{
}

View File

@@ -0,0 +1,138 @@
using Content.Server.Body.Systems;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
using Content.Server.Storage.Components;
using Content.Server.Xenoarchaeology.XenoArtifacts;
using Content.Shared.Body.Components;
using Content.Shared.Damage;
using Content.Shared.Verbs;
using Content.Shared.Xenoarchaeology.Equipment;
using Robust.Shared.Collections;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Xenoarchaeology.Equipment.Systems;
/// <inheritdoc/>
public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ArtifactSystem _artifact = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly StackSystem _stack = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ArtifactCrusherComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
SubscribeLocalEvent<ArtifactCrusherComponent, PowerChangedEvent>(OnPowerChanged);
}
private void OnGetVerbs(Entity<ArtifactCrusherComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Crushing)
return;
if (!TryComp<EntityStorageComponent>(ent, out var entityStorageComp) || entityStorageComp.Contents.ContainedEntities.Count == 0)
return;
if (entityStorageComp.Contents.Contains(args.User) || !this.IsPowered(ent, EntityManager))
return;
var verb = new AlternativeVerb
{
Text = Loc.GetString("artifact-crusher-verb-start-crushing"),
Priority = 2,
Act = () => StartCrushing((ent, ent.Comp, entityStorageComp))
};
args.Verbs.Add(verb);
}
private void OnPowerChanged(Entity<ArtifactCrusherComponent> ent, ref PowerChangedEvent args)
{
if (!args.Powered)
StopCrushing(ent);
}
public void StartCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
{
var (_, crusher, _) = ent;
if (crusher.Crushing)
return;
crusher.Crushing = true;
crusher.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
crusher.CrushEndTime = _timing.CurTime + crusher.CrushDuration;
crusher.CrushingSoundEntity = AudioSystem.PlayPvs(crusher.CrushingSound, ent);
Appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, true);
Dirty(ent, ent.Comp1);
}
public void FinishCrushing(Entity<ArtifactCrusherComponent, EntityStorageComponent> ent)
{
var (_, crusher, storage) = ent;
StopCrushing((ent, ent.Comp1), false);
AudioSystem.PlayPvs(crusher.CrushingCompleteSound, ent);
crusher.CrushingSoundEntity = null;
Dirty(ent, ent.Comp1);
var contents = new ValueList<EntityUid>(storage.Contents.ContainedEntities);
var coords = Transform(ent).Coordinates;
foreach (var contained in contents)
{
if (crusher.CrushingWhitelist.IsValid(contained, EntityManager))
{
var amount = _random.Next(crusher.MinFragments, crusher.MaxFragments);
var stacks = _stack.SpawnMultiple(crusher.FragmentStackProtoId, amount, coords);
foreach (var stack in stacks)
{
ContainerSystem.Insert((stack, null, null, null), crusher.OutputContainer);
}
_artifact.ForceActivateArtifact(contained);
}
if (!TryComp<BodyComponent>(contained, out var body))
Del(contained);
var gibs = _body.GibBody(contained, body: body, gibOrgans: true, deleteBrain: true);
foreach (var gib in gibs)
{
ContainerSystem.Insert((gib, null, null, null), crusher.OutputContainer);
}
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ArtifactCrusherComponent, EntityStorageComponent>();
while (query.MoveNext(out var uid, out var crusher, out var storage))
{
if (!crusher.Crushing)
continue;
if (crusher.NextSecond < _timing.CurTime)
{
var contents = new ValueList<EntityUid>(storage.Contents.ContainedEntities);
foreach (var contained in contents)
{
_damageable.TryChangeDamage(contained, crusher.CrushingDamage);
}
crusher.NextSecond += TimeSpan.FromSeconds(1);
Dirty(uid, crusher);
}
if (crusher.CrushEndTime < _timing.CurTime)
{
FinishCrushing((uid, crusher, storage));
}
}
}
}

View File

@@ -0,0 +1,110 @@
using Content.Shared.Damage;
using Content.Shared.Stacks;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Xenoarchaeology.Equipment;
/// <summary>
/// This is an entity storage that, when activated, crushes the artifact inside of it and gives artifact fragments.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedArtifactCrusherSystem))]
public sealed partial class ArtifactCrusherComponent : Component
{
/// <summary>
/// Whether or not the crusher is currently in the process of crushing something.
/// </summary>
[DataField, AutoNetworkedField]
public bool Crushing;
/// <summary>
/// When the current crushing will end.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan CrushEndTime;
/// <summary>
/// The next second. Used to apply damage over time.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan NextSecond;
/// <summary>
/// The total duration of the crushing.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan CrushDuration = TimeSpan.FromSeconds(10);
/// <summary>
/// A whitelist specifying what items, when crushed, will give fragments.
/// </summary>
[DataField]
public EntityWhitelist CrushingWhitelist = new();
/// <summary>
/// The minimum amount of fragments spawned.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public int MinFragments = 2;
/// <summary>
/// The maximum amount of fragments spawned, non-inclusive.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public int MaxFragments = 5;
/// <summary>
/// The material for the fragments.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public ProtoId<StackPrototype> FragmentStackProtoId = "ArtifactFragment";
/// <summary>
/// A container used to hold fragments and gibs from crushing.
/// </summary>
[ViewVariables]
public Container OutputContainer;
/// <summary>
/// The ID for <see cref="OutputContainer"/>
/// </summary>
[DataField]
public string OutputContainerName = "output_container";
/// <summary>
/// Damage dealt each second to entities inside while crushing.
/// </summary>
[DataField]
public DamageSpecifier CrushingDamage = new();
/// <summary>
/// Sound played at the end of a successful crush.
/// </summary>
[DataField, AutoNetworkedField]
public SoundSpecifier? CrushingCompleteSound = new SoundPathSpecifier("/Audio/Effects/metal_crunch.ogg");
/// <summary>
/// Sound played throughout the entire crushing. Cut off if ended early.
/// </summary>
[DataField, AutoNetworkedField]
public SoundSpecifier? CrushingSound = new SoundPathSpecifier("/Audio/Effects/hydraulic_press.ogg");
/// <summary>
/// Stores entity of <see cref="CrushingSound"/> to allow ending it early.
/// </summary>
[DataField]
public (EntityUid, AudioComponent)? CrushingSoundEntity;
}
[Serializable, NetSerializable]
public enum ArtifactCrusherVisuals : byte
{
Crushing
}

View File

@@ -0,0 +1,54 @@
using Content.Shared.Storage.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
namespace Content.Shared.Xenoarchaeology.Equipment;
/// <summary>
/// This handles logic relating to <see cref="ArtifactCrusherComponent"/>
/// </summary>
public abstract class SharedArtifactCrusherSystem : EntitySystem
{
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] protected readonly SharedAudioSystem AudioSystem = default!;
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ArtifactCrusherComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ArtifactCrusherComponent, StorageAfterOpenEvent>(OnStorageAfterOpen);
}
private void OnInit(Entity<ArtifactCrusherComponent> ent, ref ComponentInit args)
{
ent.Comp.OutputContainer = ContainerSystem.EnsureContainer<Container>(ent, ent.Comp.OutputContainerName);
}
private void OnStorageAfterOpen(Entity<ArtifactCrusherComponent> ent, ref StorageAfterOpenEvent args)
{
StopCrushing(ent);
ContainerSystem.EmptyContainer(ent.Comp.OutputContainer);
}
public void StopCrushing(Entity<ArtifactCrusherComponent> ent, bool early = true)
{
var (_, crusher) = ent;
if (!crusher.Crushing)
return;
crusher.Crushing = false;
Appearance.SetData(ent, ArtifactCrusherVisuals.Crushing, false);
if (early)
{
AudioSystem.Stop(crusher.CrushingSoundEntity?.Item1, crusher.CrushingSoundEntity?.Item2);
crusher.CrushingSoundEntity = null;
}
Dirty(ent, ent.Comp);
}
}

View File

@@ -107,6 +107,16 @@
license: "CC-BY-4.0" license: "CC-BY-4.0"
source: "https://freesound.org/people/thomas_evdokimoff/sounds/202193/" source: "https://freesound.org/people/thomas_evdokimoff/sounds/202193/"
- files: ["hydraulic_press.ogg"]
copyright: 'Created by chainsaw_dinner_party on Freesound.org. Extended and converted to MONO and .ogg by EmoGarbage404 (github)'
license: "CC0-1.0"
source: "https://freesound.org/people/chainsaw_dinner_party/sounds/403075/"
- files: ["metal_crunch.ogg"]
copyright: 'Created by PNMCarrieRailfan on Freesound.org. Edited and exported to .ogg by EmoGarbage404 (github)'
license: "CC-BY-NC-4.0"
source: "https://freesound.org/people/PNMCarrieRailfan/sounds/682439/"
- files: ["voteding.ogg"] - files: ["voteding.ogg"]
copyright: '"Bike, Bell Ding, Single, 01-01.wav" byInspectorJ (www.jshaw.co.uk) of Freesound.org; The volume has been reduced.' copyright: '"Bike, Bell Ding, Single, 01-01.wav" byInspectorJ (www.jshaw.co.uk) of Freesound.org; The volume has been reduced.'
license: "CC-BY-4.0" license: "CC-BY-4.0"

Binary file not shown.

Binary file not shown.

View File

@@ -6,6 +6,7 @@ wires-board-name-thermomachine = Thermomachine
wires-board-name-pa = Mk2 Particle Accelerator wires-board-name-pa = Mk2 Particle Accelerator
wires-board-name-highsec = HighSec Control wires-board-name-highsec = HighSec Control
wires-board-name-vessel = Vessel wires-board-name-vessel = Vessel
wires-board-name-crusher = Crusher
wires-board-name-smes = SMES wires-board-name-smes = SMES
wires-board-name-substation = Substation wires-board-name-substation = Substation
wires-board-name-apc = APC wires-board-name-apc = APC

View File

@@ -0,0 +1 @@
artifact-crusher-verb-start-crushing = Start crushing

View File

@@ -217,6 +217,22 @@
Steel: 5 Steel: 5
Cable: 1 Cable: 1
- type: entity
id: ArtifactCrusherMachineCircuitboard
parent: BaseMachineCircuitboard
name: artifact crusher machine board
description: A machine printed circuit board for an artifact crusher.
components:
- type: Sprite
state: science
- type: MachineBoard
prototype: MachineArtifactCrusher
requirements:
Manipulator: 2
materialRequirements:
Glass: 1
Steel: 5
- type: entity - type: entity
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
id: AnomalyVesselCircuitboard id: AnomalyVesselCircuitboard

View File

@@ -133,3 +133,81 @@
enum.PowerDeviceVisualLayers.Powered: enum.PowerDeviceVisualLayers.Powered:
True: { visible: true } True: { visible: true }
False: { visible: false } False: { visible: false }
- type: entity
id: MachineArtifactCrusher
parent: [ ConstructibleMachine, BaseMachinePowered ]
name: artifact crusher
description: Best not to let your fingers get stuck...
components:
- type: ArtifactCrusher
crushingWhitelist:
components:
- Artifact
crushingDamage:
types:
Blunt: 10
- type: Machine
board: ArtifactCrusherMachineCircuitboard
- type: Wires
boardName: wires-board-name-crusher
layoutId: Crusher
- type: WiresPanel
- type: Sprite
sprite: Structures/Machines/artifact_crusher.rsi
offset: 0,0.5
noRot: true
layers:
- state: base
map: ["enum.StorageVisualLayers.Base"]
- state: door-closed
map: ["enum.StorageVisualLayers.Door"]
- state: piston
map: ["pistonlayer"]
- state: glass
- state: lights
map: ["enum.PowerDeviceVisualLayers.Powered"]
shader: unshaded
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeCircle
radius: 0.45
density: 50
mask:
- HighImpassable
layer:
- HighImpassable
- type: EntityStorage
capacity: 1
whitelist:
components:
- Artifact
- type: Appearance
- type: GenericVisualizer
visuals:
enum.ArtifactCrusherVisuals.Crushing:
pistonlayer:
True: {state: piston-push}
False: {state: piston}
enum.PowerDeviceVisuals.Powered:
enum.PowerDeviceVisualLayers.Powered:
True: { visible: true }
False: { visible: false }
- type: EntityStorageVisuals
stateDoorClosed: door-closed
openDrawDepth: 0
closedDrawDepth: 4
- type: Construction
containers:
- machine_board
- machine_parts
- entity_storage
- output_container
- type: ContainerContainer
containers:
machine_board: !type:Container
machine_parts: !type:Container
entity_storage: !type:Container
output_container: !type:Container

View File

@@ -374,6 +374,7 @@
- APECircuitboard - APECircuitboard
- ArtifactAnalyzerMachineCircuitboard - ArtifactAnalyzerMachineCircuitboard
- TraversalDistorterMachineCircuitboard - TraversalDistorterMachineCircuitboard
- ArtifactCrusherMachineCircuitboard
- BoozeDispenserMachineCircuitboard - BoozeDispenserMachineCircuitboard
- SodaDispenserMachineCircuitboard - SodaDispenserMachineCircuitboard
- TelecomServerCircuitboard - TelecomServerCircuitboard

View File

@@ -240,6 +240,15 @@
Glass: 900 Glass: 900
Gold: 100 Gold: 100
- type: latheRecipe
id: ArtifactCrusherMachineCircuitboard
result: ArtifactCrusherMachineCircuitboard
completetime: 4
materials:
Steel: 100
Glass: 900
Gold: 100
- type: latheRecipe - type: latheRecipe
id: AnomalyVesselCircuitboard id: AnomalyVesselCircuitboard
result: AnomalyVesselCircuitboard result: AnomalyVesselCircuitboard

View File

@@ -97,6 +97,7 @@
cost: 5000 cost: 5000
recipeUnlocks: recipeUnlocks:
- TraversalDistorterMachineCircuitboard - TraversalDistorterMachineCircuitboard
- ArtifactCrusherMachineCircuitboard
- type: technology - type: technology
id: AdvancedAnomalyResearch id: AdvancedAnomalyResearch

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

View File

@@ -0,0 +1,48 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Made by brainfood1183 (github) for ss14",
"size": {
"x": 32,
"y": 64
},
"states": [
{
"name": "glass"
},
{
"name": "door-closed"
},
{
"name": "piston"
},
{
"name": "base"
},
{
"name": "lights"
},
{
"name": "piston-push",
"delays": [
[
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
0.66,
1
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B