diff --git a/Content.Server/Chemistry/Components/SolutionInjectWhileEmbeddedComponent.cs b/Content.Server/Chemistry/Components/SolutionInjectWhileEmbeddedComponent.cs new file mode 100644 index 0000000000..0f10e2a449 --- /dev/null +++ b/Content.Server/Chemistry/Components/SolutionInjectWhileEmbeddedComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + + +namespace Content.Server.Chemistry.Components; + +/// +/// Used for embeddable entities that should try to inject a +/// contained solution into a target over time while they are embbeded into. +/// +[RegisterComponent, AutoGenerateComponentPause] +public sealed partial class SolutionInjectWhileEmbeddedComponent : BaseSolutionInjectOnEventComponent { + /// + ///The time at which the injection will happen. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan NextUpdate; + + /// + ///The delay between each injection in seconds. + /// + [DataField] + public TimeSpan UpdateInterval = TimeSpan.FromSeconds(3); +} + diff --git a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs index d56fded024..f15edcf067 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Events; using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Projectiles; @@ -29,6 +30,7 @@ public sealed class SolutionInjectOnCollideSystem : EntitySystem SubscribeLocalEvent(HandleProjectileHit); SubscribeLocalEvent(HandleEmbed); SubscribeLocalEvent(HandleMeleeHit); + SubscribeLocalEvent(OnInjectOverTime); } private void HandleProjectileHit(Entity entity, ref ProjectileHitEvent args) @@ -49,6 +51,11 @@ public sealed class SolutionInjectOnCollideSystem : EntitySystem TryInjectTargets((entity.Owner, entity.Comp), args.HitEntities, args.User); } + private void OnInjectOverTime(Entity entity, ref InjectOverTimeEvent args) + { + DoInjection((entity.Owner, entity.Comp), args.EmbeddedIntoUid); + } + private void DoInjection(Entity injectorEntity, EntityUid target, EntityUid? source = null) { TryInjectTargets(injectorEntity, [target], source); diff --git a/Content.Server/Chemistry/EntitySystems/SolutionInjectWhileEmbeddedSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionInjectWhileEmbeddedSystem.cs new file mode 100644 index 0000000000..2baeba9da1 --- /dev/null +++ b/Content.Server/Chemistry/EntitySystems/SolutionInjectWhileEmbeddedSystem.cs @@ -0,0 +1,60 @@ +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Server.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Events; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Content.Shared.Projectiles; +using Content.Shared.Tag; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Collections; +using Robust.Shared.Timing; + +namespace Content.Server.Chemistry.EntitySystems; + +/// +/// System for handling injecting into an entity while a projectile is embedded. +/// +public sealed class SolutionInjectWhileEmbeddedSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly BloodstreamSystem _bloodstream = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly TagSystem _tag = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var injectComponent, out var projectileComponent)) + { + if (_gameTiming.CurTime < injectComponent.NextUpdate) + continue; + + injectComponent.NextUpdate += injectComponent.UpdateInterval; + + if(projectileComponent.EmbeddedIntoUid == null) + continue; + + var ev = new InjectOverTimeEvent(projectileComponent.EmbeddedIntoUid.Value); + RaiseLocalEvent(uid, ref ev); + + } + } +} diff --git a/Content.Shared/Chemistry/InjectOverTimeEvent.cs b/Content.Shared/Chemistry/InjectOverTimeEvent.cs new file mode 100644 index 0000000000..ca5ab4213f --- /dev/null +++ b/Content.Shared/Chemistry/InjectOverTimeEvent.cs @@ -0,0 +1,13 @@ +namespace Content.Shared.Chemistry.Events; + +/// +/// Raised directed on an entity when it embeds in another entity. +/// +[ByRefEvent] +public readonly record struct InjectOverTimeEvent(EntityUid embeddedIntoUid) +{ + /// + /// Entity that is embedded in. + /// + public readonly EntityUid EmbeddedIntoUid = embeddedIntoUid; +} diff --git a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs index 008b7c2ced..e4125945d2 100644 --- a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs +++ b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs @@ -13,37 +13,43 @@ public sealed partial class EmbeddableProjectileComponent : Component /// /// Minimum speed of the projectile to embed. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public float MinimumSpeed = 5f; /// /// Delete the entity on embedded removal? /// Does nothing if there's no RemovalTime. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public bool DeleteOnRemove; /// /// How long it takes to remove the embedded object. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public float? RemovalTime = 3f; /// /// Whether this entity will embed when thrown, or only when shot as a projectile. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public bool EmbedOnThrow = true; /// /// How far into the entity should we offset (0 is wherever we collided). /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public Vector2 Offset = Vector2.Zero; /// /// Sound to play after embedding into a hit target. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public SoundSpecifier? Sound; + + /// + /// Uid of the entity the projectile is embed into. + /// + [DataField, AutoNetworkedField] + public EntityUid? EmbeddedIntoUid; } diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 1b7d6d6f99..85e75d6d29 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -71,6 +71,8 @@ public abstract partial class SharedProjectileSystem : EntitySystem TryComp(uid, out var physics); _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform); _transform.AttachToGridOrMap(uid, xform); + component.EmbeddedIntoUid = null; + Dirty(uid, component); // Reset whether the projectile has damaged anything if it successfully was removed if (TryComp(uid, out var projectile)) @@ -127,8 +129,10 @@ public abstract partial class SharedProjectileSystem : EntitySystem } _audio.PlayPredicted(component.Sound, uid, null); + component.EmbeddedIntoUid = target; var ev = new EmbedEvent(user, target); RaiseLocalEvent(uid, ref ev); + Dirty(uid, component); } private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args) diff --git a/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml b/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml index 7c46974818..89db045c96 100644 --- a/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml +++ b/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml @@ -26,4 +26,9 @@ - files: ["ship_duster.ogg", "ship_friendship.ogg", "ship_svalinn.ogg", "ship_perforator.ogg"] license: "CC0-1.0" copyright: "Created by MIXnikita for Space Station 14" - source: "https://github.com/MIXnikita" \ No newline at end of file + source: "https://github.com/MIXnikita" + +- files: ["syringe_gun.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from vgstation" + source: "https://github.com/vgstation-coders/vgstation13/commit/23303188abe6fe31b114a218a1950d7325a23730" \ No newline at end of file diff --git a/Resources/Audio/Weapons/Guns/Gunshots/syringe_gun.ogg b/Resources/Audio/Weapons/Guns/Gunshots/syringe_gun.ogg new file mode 100644 index 0000000000..2132808ecd Binary files /dev/null and b/Resources/Audio/Weapons/Guns/Gunshots/syringe_gun.ogg differ diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 0f74b4afaf..3f2ac403ac 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -386,6 +386,66 @@ - Syringe - Trash +- type: entity + name: mini syringe + parent: Syringe + description: A regular syringe, reshaped to fit inside of a gun. + id: MiniSyringe + components: + - type: Sprite + sprite: Objects/Specific/Chemistry/syringe.rsi + layers: + - state: minisyringe1 + map: ["enum.SolutionContainerLayers.Fill"] + visible: false + - state: syringeproj + - type: SolutionContainerVisuals + maxFillLevels: 3 + fillBaseName: minisyringe + inHandsMaxFillLevels: 3 + inHandsFillBaseName: -fill- + - type: EmbeddableProjectile + offset: "-0.1,0" + minimumSpeed: 3 + removalTime: 0.25 + embedOnThrow: false + - type: SolutionInjectWhileEmbedded + transferAmount: 1 + solution: injector + updateInterval: 2 + - type: SolutionInjectOnEmbed + transferAmount: 2 + solution: injector + - type: Fixtures + fixtures: + fix1: + shape: !type:PhysShapeCircle + radius: 0.2 + density: 5 + mask: + - ItemMask + restitution: 0.3 + friction: 0.2 + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.1,-0.3,0.1,0.3" + hard: false + mask: + - Impassable + - BulletImpassable + - type: Projectile + deleteOnCollide: false + onlyCollideWhenShot: true + damage: + types: + Piercing: 5 + - type: Tag + tags: + - Syringe + - Trash + - SyringeGunAmmo + - type: entity parent: BaseSyringe id: PrefilledSyringe diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml index 1d18c2b050..63a7acd257 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml @@ -103,6 +103,43 @@ containers: storagebase: !type:Container ents: [] + +- type: entity + name: syringe gun + parent: BaseStorageItem + id: LauncherSyringe + description: Load full of poisoned syringes for optimal fun. + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Cannons/syringe_gun.rsi + layers: + - state: syringe_gun + - type: Storage + maxItemSize: Normal + grid: + - 0,0,2,0 + whitelist: + tags: + - SyringeGunAmmo + - type: Gun + fireRate: 1 + selectedMode: SemiAuto + availableModes: + - SemiAuto + - FullAuto + soundGunshot: + path: /Audio/Weapons/Guns/Gunshots/syringe_gun.ogg + soundEmpty: + path: /Audio/Weapons/Guns/Empty/empty.ogg + clumsyProof: true + - type: ContainerAmmoProvider + container: storagebase + - type: Item + size: Normal + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [] # shoots bullets instead of throwing them, no other changes - type: entity diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index d3ffcdcac1..8962da5790 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -1300,6 +1300,9 @@ - type: Tag id: Syringe +- type: Tag + id: SyringeGunAmmo + - type: Tag id: Spellbook diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json index 1495eccd7a..0c29f3e3fb 100644 --- a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json @@ -57,6 +57,15 @@ { "name": "syringe2" }, + { + "name": "minisyringe1" + }, + { + "name": "minisyringe2" + }, + { + "name": "minisyringe3" + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe1.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe1.png new file mode 100644 index 0000000000..66c49a744c Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe1.png differ diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe2.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe2.png new file mode 100644 index 0000000000..6f5d566354 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe2.png differ diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe3.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe3.png new file mode 100644 index 0000000000..71d3bad044 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe3.png differ diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png index 7819a48c66..5faf746ae3 100644 Binary files a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-left.png new file mode 100644 index 0000000000..b59a4d5268 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-right.png new file mode 100644 index 0000000000..6a8268d27e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/meta.json new file mode 100644 index 0000000000..dae584eb81 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vgstation13 at https://github.com/vgstation-coders/vgstation13 at f91dfe2e0dba1b7a8b9a0fa18251340920979a62, held sprite by ScarKy0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "syringe_gun" + }, + { + "name": "inhand-left", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/syringe_gun.png b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/syringe_gun.png new file mode 100644 index 0000000000..972714d1c7 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/syringe_gun.png differ