diff --git a/Content.Client/Storage/Visualizers/EntityStorageVisualizerSystem.cs b/Content.Client/Storage/Visualizers/EntityStorageVisualizerSystem.cs index 39a8f3d932..772f516a69 100644 --- a/Content.Client/Storage/Visualizers/EntityStorageVisualizerSystem.cs +++ b/Content.Client/Storage/Visualizers/EntityStorageVisualizerSystem.cs @@ -37,15 +37,27 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem + /// The drawdepth the object has when it's open + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public int? OpenDrawDepth; + + /// + /// The drawdepth the object has when it's closed + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public int? ClosedDrawDepth; } diff --git a/Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs b/Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs new file mode 100644 index 0000000000..a57ef55574 --- /dev/null +++ b/Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs @@ -0,0 +1,9 @@ +using Content.Shared.Xenoarchaeology.Equipment; + +namespace Content.Client.Xenoarchaeology.Equipment; + +/// +public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem +{ + +} diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs new file mode 100644 index 0000000000..de417d087a --- /dev/null +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs @@ -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; + +/// +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!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetVerbs); + SubscribeLocalEvent(OnPowerChanged); + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || args.Hands == null || ent.Comp.Crushing) + return; + + if (!TryComp(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 ent, ref PowerChangedEvent args) + { + if (!args.Powered) + StopCrushing(ent); + } + + public void StartCrushing(Entity 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 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(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(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(); + 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(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)); + } + } + } +} diff --git a/Content.Shared/Xenoarchaeology/Equipment/ArtifactCrusherComponent.cs b/Content.Shared/Xenoarchaeology/Equipment/ArtifactCrusherComponent.cs new file mode 100644 index 0000000000..6bfbe5d05e --- /dev/null +++ b/Content.Shared/Xenoarchaeology/Equipment/ArtifactCrusherComponent.cs @@ -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; + +/// +/// This is an entity storage that, when activated, crushes the artifact inside of it and gives artifact fragments. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedArtifactCrusherSystem))] +public sealed partial class ArtifactCrusherComponent : Component +{ + /// + /// Whether or not the crusher is currently in the process of crushing something. + /// + [DataField, AutoNetworkedField] + public bool Crushing; + + /// + /// When the current crushing will end. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public TimeSpan CrushEndTime; + + /// + /// The next second. Used to apply damage over time. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public TimeSpan NextSecond; + + /// + /// The total duration of the crushing. + /// + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public TimeSpan CrushDuration = TimeSpan.FromSeconds(10); + + /// + /// A whitelist specifying what items, when crushed, will give fragments. + /// + [DataField] + public EntityWhitelist CrushingWhitelist = new(); + + /// + /// The minimum amount of fragments spawned. + /// + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public int MinFragments = 2; + + /// + /// The maximum amount of fragments spawned, non-inclusive. + /// + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public int MaxFragments = 5; + + /// + /// The material for the fragments. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public ProtoId FragmentStackProtoId = "ArtifactFragment"; + + /// + /// A container used to hold fragments and gibs from crushing. + /// + [ViewVariables] + public Container OutputContainer; + + /// + /// The ID for + /// + [DataField] + public string OutputContainerName = "output_container"; + + /// + /// Damage dealt each second to entities inside while crushing. + /// + [DataField] + public DamageSpecifier CrushingDamage = new(); + + /// + /// Sound played at the end of a successful crush. + /// + [DataField, AutoNetworkedField] + public SoundSpecifier? CrushingCompleteSound = new SoundPathSpecifier("/Audio/Effects/metal_crunch.ogg"); + + /// + /// Sound played throughout the entire crushing. Cut off if ended early. + /// + [DataField, AutoNetworkedField] + public SoundSpecifier? CrushingSound = new SoundPathSpecifier("/Audio/Effects/hydraulic_press.ogg"); + + /// + /// Stores entity of to allow ending it early. + /// + [DataField] + public (EntityUid, AudioComponent)? CrushingSoundEntity; +} + +[Serializable, NetSerializable] +public enum ArtifactCrusherVisuals : byte +{ + Crushing +} diff --git a/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactCrusherSystem.cs b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactCrusherSystem.cs new file mode 100644 index 0000000000..44e1b91a90 --- /dev/null +++ b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactCrusherSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared.Storage.Components; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; + +namespace Content.Shared.Xenoarchaeology.Equipment; + +/// +/// This handles logic relating to +/// +public abstract class SharedArtifactCrusherSystem : EntitySystem +{ + [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; + [Dependency] protected readonly SharedAudioSystem AudioSystem = default!; + [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnStorageAfterOpen); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + ent.Comp.OutputContainer = ContainerSystem.EnsureContainer(ent, ent.Comp.OutputContainerName); + } + + private void OnStorageAfterOpen(Entity ent, ref StorageAfterOpenEvent args) + { + StopCrushing(ent); + ContainerSystem.EmptyContainer(ent.Comp.OutputContainer); + } + + public void StopCrushing(Entity 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); + } +} diff --git a/Resources/Audio/Effects/attributions.yml b/Resources/Audio/Effects/attributions.yml index 252bc9ee14..2e14f74815 100644 --- a/Resources/Audio/Effects/attributions.yml +++ b/Resources/Audio/Effects/attributions.yml @@ -107,6 +107,16 @@ license: "CC-BY-4.0" 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"] 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" diff --git a/Resources/Audio/Effects/hydraulic_press.ogg b/Resources/Audio/Effects/hydraulic_press.ogg new file mode 100644 index 0000000000..0158384600 Binary files /dev/null and b/Resources/Audio/Effects/hydraulic_press.ogg differ diff --git a/Resources/Audio/Effects/metal_crunch.ogg b/Resources/Audio/Effects/metal_crunch.ogg new file mode 100644 index 0000000000..a0973b765a Binary files /dev/null and b/Resources/Audio/Effects/metal_crunch.ogg differ diff --git a/Resources/Locale/en-US/wires/wire-names.ftl b/Resources/Locale/en-US/wires/wire-names.ftl index 04bbca104e..aae87f9c53 100644 --- a/Resources/Locale/en-US/wires/wire-names.ftl +++ b/Resources/Locale/en-US/wires/wire-names.ftl @@ -6,6 +6,7 @@ wires-board-name-thermomachine = Thermomachine wires-board-name-pa = Mk2 Particle Accelerator wires-board-name-highsec = HighSec Control wires-board-name-vessel = Vessel +wires-board-name-crusher = Crusher wires-board-name-smes = SMES wires-board-name-substation = Substation wires-board-name-apc = APC diff --git a/Resources/Locale/en-US/xenoarchaeology/artifact-crusher.ftl b/Resources/Locale/en-US/xenoarchaeology/artifact-crusher.ftl new file mode 100644 index 0000000000..d16918bb95 --- /dev/null +++ b/Resources/Locale/en-US/xenoarchaeology/artifact-crusher.ftl @@ -0,0 +1 @@ +artifact-crusher-verb-start-crushing = Start crushing diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index b47e286bda..af4f8cba78 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -217,6 +217,22 @@ Steel: 5 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 parent: BaseMachineCircuitboard id: AnomalyVesselCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml index d1cc01ea40..4dd8ccdbbb 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -133,3 +133,81 @@ enum.PowerDeviceVisualLayers.Powered: True: { visible: true } 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 diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index ec83d4005f..5972a6c976 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -374,6 +374,7 @@ - APECircuitboard - ArtifactAnalyzerMachineCircuitboard - TraversalDistorterMachineCircuitboard + - ArtifactCrusherMachineCircuitboard - BoozeDispenserMachineCircuitboard - SodaDispenserMachineCircuitboard - TelecomServerCircuitboard diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index df031864cf..8477e6c9be 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -240,6 +240,15 @@ Glass: 900 Gold: 100 +- type: latheRecipe + id: ArtifactCrusherMachineCircuitboard + result: ArtifactCrusherMachineCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 + Gold: 100 + - type: latheRecipe id: AnomalyVesselCircuitboard result: AnomalyVesselCircuitboard diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml index 9eed5e5994..a58baf1b78 100644 --- a/Resources/Prototypes/Research/experimental.yml +++ b/Resources/Prototypes/Research/experimental.yml @@ -97,6 +97,7 @@ cost: 5000 recipeUnlocks: - TraversalDistorterMachineCircuitboard + - ArtifactCrusherMachineCircuitboard - type: technology id: AdvancedAnomalyResearch diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/base.png b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/base.png new file mode 100644 index 0000000000..cf281d87ff Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/base.png differ diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/door-closed.png b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/door-closed.png new file mode 100644 index 0000000000..3a863816f1 Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/door-closed.png differ diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/glass.png b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/glass.png new file mode 100644 index 0000000000..04bb703d97 Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/glass.png differ diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/lights.png b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/lights.png new file mode 100644 index 0000000000..181e9cb466 Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/lights.png differ diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/meta.json b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/meta.json new file mode 100644 index 0000000000..dc0d23c539 --- /dev/null +++ b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/meta.json @@ -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 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/piston-push.png b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/piston-push.png new file mode 100644 index 0000000000..e9fa5f8aae Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/piston-push.png differ diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/piston.png b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/piston.png new file mode 100644 index 0000000000..9b1fe54623 Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/piston.png differ