diff --git a/Content.Client/Markers/MarkerSystem.cs b/Content.Client/Markers/MarkerSystem.cs index bfaf333baa..f12329bd78 100644 --- a/Content.Client/Markers/MarkerSystem.cs +++ b/Content.Client/Markers/MarkerSystem.cs @@ -1,48 +1,49 @@ using Robust.Client.GameObjects; using Robust.Shared.GameObjects; -namespace Content.Client.Markers +namespace Content.Client.Markers; + +public sealed class MarkerSystem : EntitySystem { - public sealed class MarkerSystem : EntitySystem + private bool _markersVisible; + + public bool MarkersVisible { - private bool _markersVisible; - - public bool MarkersVisible + get => _markersVisible; + set { - get => _markersVisible; - set - { - _markersVisible = value; - UpdateMarkers(); - } + _markersVisible = value; + UpdateMarkers(); } + } - public override void Initialize() + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + } + + private void OnStartup(EntityUid uid, MarkerComponent marker, ComponentStartup args) + { + UpdateVisibility(uid); + } + + private void UpdateVisibility(EntityUid uid) + { + if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) { - base.Initialize(); - - SubscribeLocalEvent(OnStartup); + sprite.Visible = MarkersVisible; } + } - private void OnStartup(EntityUid uid, MarkerComponent marker, ComponentStartup args) - { - UpdateVisibility(marker); - } + private void UpdateMarkers() + { + var query = AllEntityQuery(); - private void UpdateVisibility(MarkerComponent marker) + while (query.MoveNext(out var uid, out var comp)) { - if (EntityManager.TryGetComponent(marker.Owner, out SpriteComponent? sprite)) - { - sprite.Visible = MarkersVisible; - } - } - - private void UpdateMarkers() - { - foreach (var markerComponent in EntityManager.EntityQuery(true)) - { - UpdateVisibility(markerComponent); - } + UpdateVisibility(uid); } } } diff --git a/Content.Client/Projectiles/ProjectileSystem.cs b/Content.Client/Projectiles/ProjectileSystem.cs index f5121bbff0..20be44792f 100644 --- a/Content.Client/Projectiles/ProjectileSystem.cs +++ b/Content.Client/Projectiles/ProjectileSystem.cs @@ -14,7 +14,6 @@ public sealed class ProjectileSystem : SharedProjectileSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnHandleState); SubscribeNetworkEvent(OnProjectileImpact); } @@ -54,11 +53,4 @@ public sealed class ProjectileSystem : SharedProjectileSystem _player.Play(ent, anim, "impact-effect"); } } - - private void OnHandleState(EntityUid uid, ProjectileComponent component, ref ComponentHandleState args) - { - if (args.Current is not ProjectileComponentState state) return; - component.Shooter = state.Shooter; - component.IgnoreShooter = state.IgnoreShooter; - } } diff --git a/Content.Client/Toggleable/ToggleableLightVisualsComponent.cs b/Content.Client/Toggleable/ToggleableLightVisualsComponent.cs index 6c7c3c256d..628726c3c1 100644 --- a/Content.Client/Toggleable/ToggleableLightVisualsComponent.cs +++ b/Content.Client/Toggleable/ToggleableLightVisualsComponent.cs @@ -13,7 +13,7 @@ namespace Content.Client.Toggleable; public sealed class ToggleableLightVisualsComponent : Component { /// - /// Sprite layer that will have it's visibility toggled when this item is toggled. + /// Sprite layer that will have its visibility toggled when this item is toggled. /// [DataField("spriteLayer")] public string SpriteLayer = "light"; diff --git a/Content.Client/Weapons/Marker/DamageMarkerSystem.cs b/Content.Client/Weapons/Marker/DamageMarkerSystem.cs new file mode 100644 index 0000000000..43ef216af7 --- /dev/null +++ b/Content.Client/Weapons/Marker/DamageMarkerSystem.cs @@ -0,0 +1,39 @@ +using Content.Shared.Weapons.Marker; +using Robust.Client.GameObjects; +using Robust.Shared.Timing; + +namespace Content.Client.Weapons.Marker; + +public sealed class DamageMarkerSystem : SharedDamageMarkerSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMarkerStartup); + SubscribeLocalEvent(OnMarkerShutdown); + } + + private void OnMarkerStartup(EntityUid uid, DamageMarkerComponent component, ComponentStartup args) + { + if (!_timing.ApplyingState || component.Effect == null || !TryComp(uid, out var sprite)) + return; + + var layer = sprite.LayerMapReserveBlank(DamageMarkerKey.Key); + sprite.LayerSetState(layer, component.Effect.RsiState, component.Effect.RsiPath); + } + + private void OnMarkerShutdown(EntityUid uid, DamageMarkerComponent component, ComponentShutdown args) + { + if (!_timing.ApplyingState || !TryComp(uid, out var sprite) || !sprite.LayerMapTryGet(DamageMarkerKey.Key, out var weh)) + return; + + sprite.RemoveLayer(weh); + } + + private enum DamageMarkerKey : byte + { + Key + } +} diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index 23271c5478..5fd39c1090 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.StatusEffect; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Weapons.Ranged.Components; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; @@ -85,6 +86,16 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem // Heavy attack. if (altDown == BoundKeyState.Down) { + // TODO: Need to make alt-fire melee its own component I guess? + // Melee and guns share a lot in the middle but share virtually nothing at the start and end so + // it's kinda tricky. + // I think as long as we make secondaries their own component it's probably fine + // as long as guncomp has an alt-use key then it shouldn't be too much of a PITA to deal with. + if (HasComp(weaponUid)) + { + return; + } + // We did the click to end the attack but haven't pulled the key up. if (weapon.Attacking) { diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index 7d82c98469..191c2b1089 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -1,6 +1,7 @@ using Content.Client.Items; using Content.Client.Weapons.Ranged.Components; using Content.Shared.Camera; +using Content.Shared.Input; using Content.Shared.Spawners.Components; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; @@ -137,7 +138,9 @@ public sealed partial class GunSystem : SharedGunSystem return; } - if (_inputSystem.CmdStates.GetState(EngineKeyFunctions.Use) != BoundKeyState.Down) + var useKey = gun.UseKey ? EngineKeyFunctions.Use : EngineKeyFunctions.UseSecondary; + + if (_inputSystem.CmdStates.GetState(useKey) != BoundKeyState.Down) { if (gun.ShotCounter != 0) EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = gunUid }); @@ -296,6 +299,9 @@ public sealed partial class GunSystem : SharedGunSystem _animPlayer.Play(ent, anim, "muzzle-flash"); var light = EnsureComp(uid); + if (light.Enabled) + return; + light.NetSyncEnabled = false; light.Enabled = true; light.Color = Color.FromHex("#cc8e2b"); diff --git a/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs b/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs index 06c2082045..13c58772fe 100644 --- a/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/ProjectileAnomalySystem.cs @@ -86,6 +86,6 @@ public sealed class ProjectileAnomalySystem : EntitySystem comp.Damage *= severity; - _gunSystem.ShootProjectile(ent, direction, Vector2.Zero, uid, component.MaxProjectileSpeed * severity); + _gunSystem.ShootProjectile(ent, direction, Vector2.Zero, uid, uid, component.MaxProjectileSpeed * severity); } } diff --git a/Content.Server/Gatherable/Components/GatheringProjectileComponent.cs b/Content.Server/Gatherable/Components/GatheringProjectileComponent.cs index 88388c7d06..0803ee1dde 100644 --- a/Content.Server/Gatherable/Components/GatheringProjectileComponent.cs +++ b/Content.Server/Gatherable/Components/GatheringProjectileComponent.cs @@ -6,5 +6,9 @@ namespace Content.Server.Gatherable.Components; [RegisterComponent] public sealed class GatheringProjectileComponent : Component { - + /// + /// How many more times we can gather. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("amount")] + public int Amount = 1; } diff --git a/Content.Server/Gatherable/GatherableSystem.Projectile.cs b/Content.Server/Gatherable/GatherableSystem.Projectile.cs index 62417bc7b8..b8f30a466c 100644 --- a/Content.Server/Gatherable/GatherableSystem.Projectile.cs +++ b/Content.Server/Gatherable/GatherableSystem.Projectile.cs @@ -17,12 +17,16 @@ public sealed partial class GatherableSystem { if (!args.OtherFixture.Hard || args.OurFixture.ID != SharedProjectileSystem.ProjectileFixture || + component.Amount <= 0 || !TryComp(args.OtherEntity, out var gatherable)) { return; } Gather(args.OtherEntity, uid, gatherable); - QueueDel(uid); + component.Amount--; + + if (component.Amount <= 0) + QueueDel(uid); } } diff --git a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs index 0902602953..303e2a2fd7 100644 --- a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs +++ b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs @@ -1,8 +1,5 @@ -using Content.Server.Actions; using Content.Server.Popups; using Content.Server.PowerCell; -using Content.Shared.Actions; -using Content.Shared.Actions.ActionTypes; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Light; @@ -11,10 +8,7 @@ using Content.Shared.Toggleable; using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Containers; using Robust.Shared.GameStates; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -37,7 +31,6 @@ namespace Content.Server.Light.EntitySystems { base.Initialize(); - SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnGetState); SubscribeLocalEvent(OnExamine); @@ -45,41 +38,7 @@ namespace Content.Server.Light.EntitySystems SubscribeLocalEvent(OnActivate); - SubscribeLocalEvent(OnGetActions); SubscribeLocalEvent(OnToggleAction); - - SubscribeLocalEvent(OnEntInserted); - SubscribeLocalEvent(OnEntRemoved); - } - - private void OnEntInserted( - EntityUid uid, - HandheldLightComponent component, - EntInsertedIntoContainerMessage args) - { - // Not guaranteed to be the correct container for our slot, I don't care. - UpdateLevel(uid, component); - } - - private void OnEntRemoved( - EntityUid uid, - HandheldLightComponent component, - EntRemovedFromContainerMessage args) - { - // Ditto above - UpdateLevel(uid, component); - } - - private void OnGetActions(EntityUid uid, HandheldLightComponent component, GetItemActionsEvent args) - { - if (component.ToggleAction == null - && _proto.TryIndex(component.ToggleActionId, out InstantActionPrototype? act)) - { - component.ToggleAction = new(act); - } - - if (component.ToggleAction != null) - args.Actions.Add(component.ToggleAction); } private void OnToggleAction(EntityUid uid, HandheldLightComponent component, ToggleActionEvent args) @@ -114,11 +73,6 @@ namespace Content.Server.Light.EntitySystems return (byte?) ContentHelpers.RoundToNearestLevels(battery.CurrentCharge / battery.MaxCharge * 255, 255, HandheldLightComponent.StatusLevels); } - private void OnRemove(EntityUid uid, HandheldLightComponent component, ComponentRemove args) - { - _activeLights.Remove(component); - } - private void OnActivate(EntityUid uid, HandheldLightComponent component, ActivateInWorldEvent args) { if (args.Handled) @@ -144,36 +98,6 @@ namespace Content.Server.Light.EntitySystems : Loc.GetString("handheld-light-component-on-examine-is-off-message")); } - public override void Shutdown() - { - base.Shutdown(); - _activeLights.Clear(); - } - - public override void Update(float frameTime) - { - var toRemove = new RemQueue(); - - foreach (var handheld in _activeLights) - { - var uid = handheld.Owner; - - if (handheld.Deleted) - { - toRemove.Add(handheld); - continue; - } - - if (Paused(uid)) continue; - TryUpdate(uid, handheld, frameTime); - } - - foreach (var light in toRemove) - { - _activeLights.Remove(light); - } - } - private void AddToggleLightVerb(EntityUid uid, HandheldLightComponent component, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract) diff --git a/Content.Server/Pinpointer/ProximityBeeperSystem.cs b/Content.Server/Pinpointer/ProximityBeeperSystem.cs index c0cf6cd605..0751ab50bf 100644 --- a/Content.Server/Pinpointer/ProximityBeeperSystem.cs +++ b/Content.Server/Pinpointer/ProximityBeeperSystem.cs @@ -1,6 +1,7 @@ using Content.Server.PowerCell; using Content.Shared.Interaction.Events; using Content.Shared.Pinpointer; +using Content.Shared.PowerCell; using Robust.Server.GameObjects; using Robust.Shared.Timing; diff --git a/Content.Server/PowerCell/PowerCellSystem.Draw.cs b/Content.Server/PowerCell/PowerCellSystem.Draw.cs new file mode 100644 index 0000000000..9d73138346 --- /dev/null +++ b/Content.Server/PowerCell/PowerCellSystem.Draw.cs @@ -0,0 +1,91 @@ +using Content.Server.Power.Components; +using Content.Shared.PowerCell; +using Content.Shared.PowerCell.Components; + +namespace Content.Server.PowerCell; + +public sealed partial class PowerCellSystem +{ + /* + * Handles PowerCellDraw + */ + + private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1); + + public override void Update(float frameTime) + { + base.Update(frameTime); + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp, out var slot)) + { + if (!comp.Drawing) + continue; + + if (_timing.CurTime < comp.NextUpdateTime) + continue; + + comp.NextUpdateTime += Delay; + + if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot)) + continue; + + if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery)) + continue; + + comp.Drawing = false; + var ev = new PowerCellSlotEmptyEvent(); + RaiseLocalEvent(uid, ref ev); + } + } + + private void OnUnpaused(EntityUid uid, PowerCellDrawComponent component, ref EntityUnpausedEvent args) + { + component.NextUpdateTime += args.PausedTime; + } + + private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args) + { + // Update the bools for client prediction. + bool canDraw; + bool canUse; + + if (component.UseRate > 0f) + { + canUse = args.Charge > component.UseRate; + } + else + { + canUse = true; + } + + if (component.DrawRate > 0f) + { + canDraw = args.Charge > 0f; + } + else + { + canDraw = true; + } + + if (canUse != component.CanUse || canDraw != component.CanDraw) + { + component.CanDraw = canDraw; + component.CanUse = canUse; + Dirty(component); + } + } + + private void OnDrawCellChanged(EntityUid uid, PowerCellDrawComponent component, PowerCellChangedEvent args) + { + var canDraw = !args.Ejected && HasCharge(uid, float.MinValue); + var canUse = !args.Ejected && HasActivatableCharge(uid, component); + + if (canUse != component.CanUse || canDraw != component.CanDraw) + { + component.CanDraw = canDraw; + component.CanUse = canUse; + Dirty(component); + } + } +} diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs index 6c6cd92d4c..7719773d9d 100644 --- a/Content.Server/PowerCell/PowerCellSystem.cs +++ b/Content.Server/PowerCell/PowerCellSystem.cs @@ -19,7 +19,10 @@ using Robust.Shared.Timing; namespace Content.Server.PowerCell; -public sealed class PowerCellSystem : SharedPowerCellSystem +/// +/// Handles Power cells +/// +public sealed partial class PowerCellSystem : SharedPowerCellSystem { [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IGameTiming _timing = default!; @@ -39,43 +42,19 @@ public sealed class PowerCellSystem : SharedPowerCellSystem SubscribeLocalEvent(OnChargeChanged); SubscribeLocalEvent(OnSolutionChange); SubscribeLocalEvent(OnRejuvenate); - SubscribeLocalEvent(OnCellExamined); - SubscribeLocalEvent(OnCellSlotExamined); SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnDrawChargeChanged); + SubscribeLocalEvent(OnDrawCellChanged); // funny + SubscribeLocalEvent(OnCellSlotExamined); SubscribeLocalEvent(OnSlotMicrowaved); + SubscribeLocalEvent(OnMicrowaved); } - public override void Update(float frameTime) - { - base.Update(frameTime); - var query = EntityQueryEnumerator(); - - while (query.MoveNext(out var uid, out var comp, out var slot)) - { - if (!comp.Enabled) - continue; - - if (_timing.CurTime < comp.NextUpdateTime) - continue; - comp.NextUpdateTime += TimeSpan.FromSeconds(1); - - if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot)) - continue; - - if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery)) - continue; - - comp.Enabled = false; - var ev = new PowerCellSlotEmptyEvent(); - RaiseLocalEvent(uid, ref ev); - } - } - private void OnRejuvenate(EntityUid uid, PowerCellComponent component, RejuvenateEvent args) { component.IsRigged = false; @@ -83,13 +62,13 @@ public sealed class PowerCellSystem : SharedPowerCellSystem private void OnSlotMicrowaved(EntityUid uid, PowerCellSlotComponent component, BeingMicrowavedEvent args) { - if (_itemSlotsSystem.TryGetSlot(uid, component.CellSlotId, out ItemSlot? slot)) - { - if (slot.Item == null) - return; + if (!_itemSlotsSystem.TryGetSlot(uid, component.CellSlotId, out var slot)) + return; - RaiseLocalEvent(slot.Item.Value, args); - } + if (slot.Item == null) + return; + + RaiseLocalEvent(slot.Item.Value, args); } private void OnMicrowaved(EntityUid uid, BatteryComponent component, BeingMicrowavedEvent args) @@ -111,17 +90,14 @@ public sealed class PowerCellSystem : SharedPowerCellSystem return; } - if (!TryComp(uid, out AppearanceComponent? appearance)) - return; - var frac = args.Charge / args.MaxCharge; var level = (byte) ContentHelpers.RoundToNearestLevels(frac, 1, PowerCellComponent.PowerCellVisualsLevels); - _sharedAppearanceSystem.SetData(uid, PowerCellVisuals.ChargeLevel, level, appearance); + _sharedAppearanceSystem.SetData(uid, PowerCellVisuals.ChargeLevel, level); // If this power cell is inside a cell-slot, inform that entity that the power has changed (for updating visuals n such). if (_containerSystem.TryGetContainingContainer(uid, out var container) && TryComp(container.Owner, out PowerCellSlotComponent? slot) - && _itemSlotsSystem.TryGetSlot(container.Owner, slot.CellSlotId, out ItemSlot? itemSlot)) + && _itemSlotsSystem.TryGetSlot(container.Owner, slot.CellSlotId, out var itemSlot)) { if (itemSlot.Item == uid) RaiseLocalEvent(container.Owner, new PowerCellChangedEvent(false)); @@ -136,11 +112,6 @@ public sealed class PowerCellSystem : SharedPowerCellSystem RaiseLocalEvent(uid, ref ev); } - private void OnUnpaused(EntityUid uid, PowerCellDrawComponent component, ref EntityUnpausedEvent args) - { - component.NextUpdateTime += args.PausedTime; - } - private void Explode(EntityUid uid, BatteryComponent? battery = null, EntityUid? cause = null) { if (!Resolve(uid, ref battery)) @@ -190,10 +161,10 @@ public sealed class PowerCellSystem : SharedPowerCellSystem public void SetPowerCellDrawEnabled(EntityUid uid, bool enabled, PowerCellDrawComponent? component = null) { - if (!Resolve(uid, ref component, false)) + if (!Resolve(uid, ref component, false) || enabled == component.Drawing) return; - component.Enabled = enabled; + component.Drawing = enabled; component.NextUpdateTime = _timing.CurTime; } diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index a9b02cdb8e..af517a0231 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -26,12 +26,6 @@ public sealed class ProjectileSystem : SharedProjectileSystem { base.Initialize(); SubscribeLocalEvent(OnStartCollide); - SubscribeLocalEvent(OnGetState); - } - - private void OnGetState(EntityUid uid, ProjectileComponent component, ref ComponentGetState args) - { - args.State = new ProjectileComponentState(component.Shooter, component.IgnoreShooter); } private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref StartCollideEvent args) diff --git a/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs b/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs index 14429dd455..df7f91a1c1 100644 --- a/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs +++ b/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs @@ -1,4 +1,5 @@ using Content.Server.PowerCell; +using Content.Shared.PowerCell; using Content.Shared.UserInterface; namespace Content.Server.UserInterface; diff --git a/Content.Server/Weapons/DamageMarkerSystem.cs b/Content.Server/Weapons/DamageMarkerSystem.cs new file mode 100644 index 0000000000..be65bca168 --- /dev/null +++ b/Content.Server/Weapons/DamageMarkerSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Weapons.Marker; + +namespace Content.Server.Weapons; + +public sealed class DamageMarkerSystem : SharedDamageMarkerSystem +{ + +} diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 995c1ffbc6..c872a8d975 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -118,7 +118,7 @@ public sealed partial class GunSystem : SharedGunSystem // pneumatic cannon doesn't shoot bullets it just throws them, ignore ammo handling if (throwItems && ent != null) { - ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, user); + ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user); continue; } @@ -136,14 +136,14 @@ public sealed partial class GunSystem : SharedGunSystem for (var i = 0; i < cartridge.Count; i++) { var uid = Spawn(cartridge.Prototype, fromEnt); - ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, user); + ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user); shotProjectiles.Add(uid); } } else { var uid = Spawn(cartridge.Prototype, fromEnt); - ShootOrThrow(uid, mapDirection, gunVelocity, gun, user); + ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user); shotProjectiles.Add(uid); } @@ -175,7 +175,7 @@ public sealed partial class GunSystem : SharedGunSystem shotProjectiles.Add(ent!.Value); MuzzleFlash(gunUid, newAmmo, user); Audio.PlayPredicted(gun.SoundGunshot, gunUid, user); - ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, user); + ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user); break; case HitscanPrototype hitscan: @@ -265,7 +265,7 @@ public sealed partial class GunSystem : SharedGunSystem }); } - private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid? user) + private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid gunUid, EntityUid? user) { // Do a throw if (!HasComp(uid)) @@ -276,10 +276,10 @@ public sealed partial class GunSystem : SharedGunSystem return; } - ShootProjectile(uid, mapDirection, gunVelocity, user, gun.ProjectileSpeed); + ShootProjectile(uid, mapDirection, gunVelocity, gunUid, user, gun.ProjectileSpeed); } - public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocity, EntityUid? user = null, float speed = 20f) + public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocity, EntityUid gunUid, EntityUid? user = null, float speed = 20f) { var physics = EnsureComp(uid); Physics.SetBodyStatus(physics, BodyStatus.InAir); @@ -293,6 +293,7 @@ public sealed partial class GunSystem : SharedGunSystem { var projectile = EnsureComp(uid); Projectiles.SetShooter(projectile, user.Value); + projectile.Weapon = gunUid; } TransformSystem.SetWorldRotation(uid, direction.ToWorldAngle()); diff --git a/Content.Shared/Interaction/Events/MeleeAttackAttemptEvent.cs b/Content.Shared/Interaction/Events/MeleeAttackAttemptEvent.cs deleted file mode 100644 index 01083e8987..0000000000 --- a/Content.Shared/Interaction/Events/MeleeAttackAttemptEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Content.Shared.Interaction.Events; - -/// -/// Raised on directed a weapon when being used in a melee attack. -/// -[ByRefEvent] -public struct MeleeAttackAttemptEvent -{ - public bool Cancelled = false; - public readonly EntityUid User; - - public MeleeAttackAttemptEvent(EntityUid user) - { - User = user; - } -} diff --git a/Content.Server/PowerCell/PowerCellDrawComponent.cs b/Content.Shared/PowerCell/PowerCellDrawComponent.cs similarity index 56% rename from Content.Server/PowerCell/PowerCellDrawComponent.cs rename to Content.Shared/PowerCell/PowerCellDrawComponent.cs index 213b414098..6963326b70 100644 --- a/Content.Server/PowerCell/PowerCellDrawComponent.cs +++ b/Content.Shared/PowerCell/PowerCellDrawComponent.cs @@ -1,15 +1,35 @@ +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.PowerCell; +namespace Content.Shared.PowerCell; /// /// Indicates that the entity's ActivatableUI requires power or else it closes. /// -[RegisterComponent, Access(typeof(PowerCellSystem))] -public sealed class PowerCellDrawComponent : Component +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PowerCellDrawComponent : Component { + #region Prediction + + /// + /// Whether there is any charge available to draw. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("canDraw"), AutoNetworkedField] + public bool CanDraw; + + /// + /// Whether there is sufficient charge to use. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("canUse"), AutoNetworkedField] + public bool CanUse; + + #endregion + + /// + /// Is this power cell currently drawing power every tick. + /// [ViewVariables(VVAccess.ReadWrite), DataField("enabled")] - public bool Enabled; + public bool Drawing; /// /// How much the entity draws while the UI is open. diff --git a/Content.Shared/PowerCell/SharedPowerCellSystem.cs b/Content.Shared/PowerCell/SharedPowerCellSystem.cs index 5c18152a23..57af440360 100644 --- a/Content.Shared/PowerCell/SharedPowerCellSystem.cs +++ b/Content.Shared/PowerCell/SharedPowerCellSystem.cs @@ -21,7 +21,7 @@ public abstract class SharedPowerCellSystem : EntitySystem private void OnRejuventate(EntityUid uid, PowerCellSlotComponent component, RejuvenateEvent args) { - if (!_itemSlots.TryGetSlot(uid, component.CellSlotId, out ItemSlot? itemSlot) || !itemSlot.Item.HasValue) + if (!_itemSlots.TryGetSlot(uid, component.CellSlotId, out var itemSlot) || !itemSlot.Item.HasValue) return; // charge entity batteries and remove booby traps. diff --git a/Content.Shared/Projectiles/ProjectileComponent.cs b/Content.Shared/Projectiles/ProjectileComponent.cs index bab2298a31..1e77e3954c 100644 --- a/Content.Shared/Projectiles/ProjectileComponent.cs +++ b/Content.Shared/Projectiles/ProjectileComponent.cs @@ -4,34 +4,42 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Projectiles +namespace Content.Shared.Projectiles; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ProjectileComponent : Component { - [RegisterComponent, NetworkedComponent] - public sealed class ProjectileComponent : Component - { - [ViewVariables(VVAccess.ReadWrite), DataField("impactEffect", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? ImpactEffect; + [ViewVariables(VVAccess.ReadWrite), DataField("impactEffect", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string? ImpactEffect; - public EntityUid Shooter { get; set; } + /// + /// User that shot this projectile. + /// + [DataField("shooter"), AutoNetworkedField] public EntityUid Shooter; - public bool IgnoreShooter = true; + /// + /// Weapon used to shoot. + /// + [DataField("weapon"), AutoNetworkedField] + public EntityUid Weapon; - [DataField("damage", required: true)] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier Damage = default!; + [DataField("ignoreShooter"), AutoNetworkedField] + public bool IgnoreShooter = true; - [DataField("deleteOnCollide")] - public bool DeleteOnCollide { get; } = true; + [DataField("damage", required: true)] [ViewVariables(VVAccess.ReadWrite)] + public DamageSpecifier Damage = new(); - [DataField("ignoreResistances")] - public bool IgnoreResistances { get; } = false; + [DataField("deleteOnCollide")] + public bool DeleteOnCollide = true; - // Get that juicy FPS hit sound - [DataField("soundHit")] public SoundSpecifier? SoundHit; + [DataField("ignoreResistances")] + public bool IgnoreResistances = false; - [DataField("soundForce")] - public bool ForceSound = false; + // Get that juicy FPS hit sound + [DataField("soundHit")] public SoundSpecifier? SoundHit; - public bool DamagedEntity; - } + [DataField("soundForce")] + public bool ForceSound = false; + + public bool DamagedEntity; } diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 53a271f34c..08b974cfda 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -30,31 +30,18 @@ namespace Content.Shared.Projectiles component.Shooter = uid; Dirty(component); } + } - [NetSerializable, Serializable] - public sealed class ProjectileComponentState : ComponentState + [Serializable, NetSerializable] + public sealed class ImpactEffectEvent : EntityEventArgs + { + public string Prototype; + public EntityCoordinates Coordinates; + + public ImpactEffectEvent(string prototype, EntityCoordinates coordinates) { - public ProjectileComponentState(EntityUid shooter, bool ignoreShooter) - { - Shooter = shooter; - IgnoreShooter = ignoreShooter; - } - - public EntityUid Shooter { get; } - public bool IgnoreShooter { get; } - } - - [Serializable, NetSerializable] - public sealed class ImpactEffectEvent : EntityEventArgs - { - public string Prototype; - public EntityCoordinates Coordinates; - - public ImpactEffectEvent(string prototype, EntityCoordinates coordinates) - { - Prototype = prototype; - Coordinates = coordinates; - } + Prototype = prototype; + Coordinates = coordinates; } } } diff --git a/Content.Shared/Weapons/Marker/DamageMarkerComponent.cs b/Content.Shared/Weapons/Marker/DamageMarkerComponent.cs new file mode 100644 index 0000000000..ef3b712f60 --- /dev/null +++ b/Content.Shared/Weapons/Marker/DamageMarkerComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared.Damage; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Utility; + +namespace Content.Shared.Weapons.Marker; + +/// +/// Marks an entity to take additional damage +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedDamageMarkerSystem))] +public sealed partial class DamageMarkerComponent : Component +{ + /// + /// Sprite to apply to the entity while damagemarker is applied. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("effect")] + public SpriteSpecifier.Rsi? Effect = new(new ResPath("/Textures/Objects/Weapons/Effects"), "shield2"); + + /// + /// Sound to play when the damage marker is procced. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("sound")] + public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/kinetic_accel.ogg"); + + [ViewVariables(VVAccess.ReadWrite), DataField("damage")] + public DamageSpecifier Damage = new(); + + /// + /// Entity that marked this entity for a damage surplus. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("marker"), AutoNetworkedField] + public EntityUid Marker; + + [ViewVariables(VVAccess.ReadWrite), DataField("endTime", customTypeSerializer:typeof(TimeOffsetSerializer)), AutoNetworkedField] + public TimeSpan EndTime; +} diff --git a/Content.Shared/Weapons/Marker/DamageMarkerOnCollideComponent.cs b/Content.Shared/Weapons/Marker/DamageMarkerOnCollideComponent.cs new file mode 100644 index 0000000000..e708dd7a2b --- /dev/null +++ b/Content.Shared/Weapons/Marker/DamageMarkerOnCollideComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Damage; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Weapons.Marker; + +/// +/// Applies when colliding with an entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedDamageMarkerSystem))] +public sealed partial class DamageMarkerOnCollideComponent : Component +{ + [DataField("whitelist"), AutoNetworkedField] + public EntityWhitelist? Whitelist = new(); + + [ViewVariables(VVAccess.ReadWrite), DataField("duration"), AutoNetworkedField] + public TimeSpan Duration = TimeSpan.FromSeconds(5); + + /// + /// Additional damage to be applied. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("damage")] + public DamageSpecifier Damage = new(); + + /// + /// How many more times we can apply it. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("amount"), AutoNetworkedField] + public int Amount = 1; +} diff --git a/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs b/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs new file mode 100644 index 0000000000..0f6fc34aca --- /dev/null +++ b/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs @@ -0,0 +1,81 @@ +using Content.Shared.Damage; +using Content.Shared.Projectiles; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Physics.Events; +using Robust.Shared.Timing; + +namespace Content.Shared.Weapons.Marker; + +public abstract class SharedDamageMarkerSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMarkerCollide); + SubscribeLocalEvent(OnMarkerUnpaused); + SubscribeLocalEvent(OnMarkerAttacked); + } + + private void OnMarkerAttacked(EntityUid uid, DamageMarkerComponent component, AttackedEvent args) + { + if (component.Marker != args.Used) + return; + + args.BonusDamage += component.Damage; + RemCompDeferred(uid); + _audio.PlayPredicted(component.Sound, uid, args.User); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.EndTime > _timing.CurTime) + continue; + + RemCompDeferred(uid); + } + } + + private void OnMarkerUnpaused(EntityUid uid, DamageMarkerComponent component, ref EntityUnpausedEvent args) + { + component.EndTime += args.PausedTime; + } + + private void OnMarkerCollide(EntityUid uid, DamageMarkerOnCollideComponent component, ref StartCollideEvent args) + { + if (!args.OtherFixture.Hard || + args.OurFixture.ID != SharedProjectileSystem.ProjectileFixture || + component.Amount <= 0 || + component.Whitelist?.IsValid(args.OtherEntity, EntityManager) == false || + !TryComp(uid, out var projectile) || + !projectile.Weapon.IsValid()) + { + return; + } + + // Markers are exclusive, deal with it. + var marker = EnsureComp(args.OtherEntity); + marker.Damage = new DamageSpecifier(component.Damage); + marker.Marker = projectile.Weapon; + marker.EndTime = _timing.CurTime + component.Duration; + component.Amount--; + Dirty(marker); + + if (component.Amount <= 0) + { + QueueDel(uid); + } + else + { + Dirty(component); + } + } +} diff --git a/Content.Shared/Weapons/Melee/Components/MeleeRequiresWieldComponent.cs b/Content.Shared/Weapons/Melee/Components/MeleeRequiresWieldComponent.cs new file mode 100644 index 0000000000..6cf12d58f2 --- /dev/null +++ b/Content.Shared/Weapons/Melee/Components/MeleeRequiresWieldComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Weapons.Melee.Components; + +/// +/// Indicates that this meleeweapon requires wielding to be useable. +/// +[RegisterComponent, NetworkedComponent] +public sealed class MeleeRequiresWieldComponent : Component +{ + +} diff --git a/Content.Shared/Weapons/Melee/Events/AttackEvent.cs b/Content.Shared/Weapons/Melee/Events/AttackEvent.cs index fdee5d507b..cb731258d9 100644 --- a/Content.Shared/Weapons/Melee/Events/AttackEvent.cs +++ b/Content.Shared/Weapons/Melee/Events/AttackEvent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Damage; using Robust.Shared.Map; using Robust.Shared.Serialization; @@ -37,6 +38,8 @@ namespace Content.Shared.Weapons.Melee.Events /// public EntityCoordinates ClickLocation { get; } + public DamageSpecifier BonusDamage = new(); + public AttackedEvent(EntityUid used, EntityUid user, EntityCoordinates clickLocation) { Used = used; diff --git a/Content.Shared/Weapons/Melee/Events/AttemptMeleeEvent.cs b/Content.Shared/Weapons/Melee/Events/AttemptMeleeEvent.cs new file mode 100644 index 0000000000..2800e3b34d --- /dev/null +++ b/Content.Shared/Weapons/Melee/Events/AttemptMeleeEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Weapons.Melee.Events; + +/// +/// Raised directed on a weapon when attempt a melee attack. +/// +[ByRefEvent] +public record struct AttemptMeleeEvent(bool Cancelled, string? Message); diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index faa6119840..124023dc13 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -15,6 +15,8 @@ using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Weapons.Melee.Components; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Audio; using Robust.Shared.Collections; using Robust.Shared.GameStates; @@ -70,6 +72,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem SubscribeLocalEvent(OnHandleState); SubscribeLocalEvent(OnMeleeDropped); SubscribeLocalEvent(OnMeleeSelected); + SubscribeLocalEvent(OnMeleeShot); SubscribeAllEvent(OnHeavyAttack); SubscribeAllEvent(OnLightAttack); @@ -89,6 +92,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem #endif } + private void OnMeleeShot(EntityUid uid, MeleeWeaponComponent component, ref GunShotEvent args) + { + if (!TryComp(uid, out var gun)) + return; + + if (gun.NextFire > component.NextAttack) + { + component.NextAttack = gun.NextFire; + Dirty(component); + } + } + private void OnMeleeUnpaused(EntityUid uid, MeleeWeaponComponent component, ref EntityUnpausedEvent args) { component.NextAttack += args.PausedTime; @@ -356,38 +371,64 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem } // Windup time checked elsewhere. + var fireRate = TimeSpan.FromSeconds(1f / weapon.AttackRate); + var swings = 0; + // TODO: If we get autoattacks then probably need a shotcounter like guns so we can do timing properly. if (weapon.NextAttack < curTime) weapon.NextAttack = curTime; - weapon.NextAttack += TimeSpan.FromSeconds(1f / weapon.AttackRate); - - // Attack confirmed - string animation; - - switch (attack) + while (weapon.NextAttack <= curTime) { - case LightAttackEvent light: - DoLightAttack(user, light, weaponUid, weapon, session); - animation = weapon.ClickAnimation; - break; - case DisarmAttackEvent disarm: - if (!DoDisarm(user, disarm, weaponUid, weapon, session)) - return; - - animation = weapon.ClickAnimation; - break; - case HeavyAttackEvent heavy: - DoHeavyAttack(user, heavy, weaponUid, weapon, session); - animation = weapon.WideAnimation; - break; - default: - throw new NotImplementedException(); + weapon.NextAttack += fireRate; + swings++; } - DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager, TransformSystem), weapon.Range, animation); - weapon.Attacking = true; Dirty(weapon); + + // Do this AFTER attack so it doesn't spam every tick + var ev = new AttemptMeleeEvent(); + RaiseLocalEvent(weaponUid, ref ev); + + if (ev.Cancelled) + { + if (ev.Message != null) + { + PopupSystem.PopupClient(ev.Message, weaponUid, user); + } + + return; + } + + // Attack confirmed + for (var i = 0; i < swings; i++) + { + string animation; + + switch (attack) + { + case LightAttackEvent light: + DoLightAttack(user, light, weaponUid, weapon, session); + animation = weapon.ClickAnimation; + break; + case DisarmAttackEvent disarm: + if (!DoDisarm(user, disarm, weaponUid, weapon, session)) + return; + + animation = weapon.ClickAnimation; + break; + case HeavyAttackEvent heavy: + DoHeavyAttack(user, heavy, weaponUid, weapon, session); + animation = weapon.WideAnimation; + break; + default: + throw new NotImplementedException(); + } + + DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager, TransformSystem), weapon.Range, animation); + } + + weapon.Attacking = true; } /// @@ -469,9 +510,10 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem Interaction.DoContactInteraction(user, ev.Target); // For stuff that cares about it being attacked. - RaiseLocalEvent(ev.Target.Value, new AttackedEvent(meleeUid, user, targetXform.Coordinates)); + var attackedEvent = new AttackedEvent(meleeUid, user, targetXform.Coordinates); + RaiseLocalEvent(ev.Target.Value, attackedEvent); - var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage, hitEvent.ModifiersList); + var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList); var damageResult = Damageable.TryChangeDamage(ev.Target, modifiedDamage, origin:user); if (damageResult != null && damageResult.Total > FixedPoint2.Zero) @@ -596,16 +638,15 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem // If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a // somewhat messy scuffle. See also, light attacks. Interaction.DoContactInteraction(user, target); - - RaiseLocalEvent(target, new AttackedEvent(meleeUid, user, Transform(target).Coordinates)); } - var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage, hitEvent.ModifiersList); var appliedDamage = new DamageSpecifier(); foreach (var entity in targets) { - RaiseLocalEvent(entity, new AttackedEvent(meleeUid, user, ev.Coordinates)); + var attackedEvent = new AttackedEvent(meleeUid, user, ev.Coordinates); + RaiseLocalEvent(entity, attackedEvent); + var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList); var damageResult = Damageable.TryChangeDamage(entity, modifiedDamage, origin:user); @@ -631,7 +672,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem if (appliedDamage.Total > FixedPoint2.Zero) { var target = entities.First(); - PlayHitSound(target, user, GetHighestDamageSound(modifiedDamage, _protoManager), hitEvent.HitSoundOverride, component.HitSound); + PlayHitSound(target, user, GetHighestDamageSound(appliedDamage, _protoManager), hitEvent.HitSoundOverride, component.HitSound); } else { diff --git a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs index 0ccc839902..df897bcc0f 100644 --- a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs @@ -79,6 +79,12 @@ public partial class GunComponent : Component #endregion + /// + /// Whether this gun is shot via the use key or the alt-use key. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("useKey"), AutoNetworkedField] + public bool UseKey = true; + /// /// Where the gun is being requested to shoot. /// diff --git a/Content.Shared/Weapons/Ranged/Components/GunRequiresWieldComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunRequiresWieldComponent.cs new file mode 100644 index 0000000000..b0aca3af14 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Components/GunRequiresWieldComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Weapons.Ranged.Components; + +/// +/// Indicates that this gun requires wielding to be useable. +/// +[RegisterComponent, NetworkedComponent] +public sealed class GunRequiresWieldComponent : Component +{ + +} diff --git a/Content.Shared/Weapons/Ranged/Components/UseDelayOnShootComponent.cs b/Content.Shared/Weapons/Ranged/Components/UseDelayOnShootComponent.cs new file mode 100644 index 0000000000..c2b0950502 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Components/UseDelayOnShootComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Timing; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Weapons.Ranged.Components; + +/// +/// Applies UseDelay whenever the entity shoots. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(UseDelayOnShootSystem))] +public sealed class UseDelayOnShootComponent : Component +{ + +} diff --git a/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs b/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs index 9b72136372..ded4ce34a2 100644 --- a/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs @@ -47,8 +47,10 @@ public sealed class RechargeBasicEntityAmmoSystem : EntitySystem if (_gun.UpdateBasicEntityAmmoCount(uid, ammo.Count.Value + 1, ammo)) { - if (_netManager.IsClient && _timing.IsFirstTimePredicted) - _audio.Play(recharge.RechargeSound, Filter.Local(), uid, true); + // We don't predict this because occasionally on client it may not play. + // PlayPredicted will still be predicted on the client. + if (_netManager.IsServer) + _audio.PlayPvs(recharge.RechargeSound, uid); } if (ammo.Count == ammo.Capacity) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 4768848972..89068db8ae 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -13,11 +13,12 @@ using Content.Shared.Projectiles; using Content.Shared.Tag; using Content.Shared.Throwing; using Content.Shared.Verbs; +using Content.Shared.Weapons.Melee; +using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Physics.Components; @@ -70,7 +71,7 @@ public abstract partial class SharedGunSystem : EntitySystem Sawmill.Level = LogLevel.Info; SubscribeAllEvent(OnShootRequest); SubscribeAllEvent(OnStopShootRequest); - SubscribeLocalEvent(OnGunMeleeAttempt); + SubscribeLocalEvent(OnGunMelee); // Ammo providers InitializeBallistic(); @@ -103,19 +104,23 @@ public abstract partial class SharedGunSystem : EntitySystem #endif } + private void OnGunMelee(EntityUid uid, GunComponent component, MeleeHitEvent args) + { + if (!TryComp(uid, out var melee)) + return; + + if (melee.NextAttack > component.NextFire) + { + component.NextFire = melee.NextAttack; + Dirty(component); + } + } + private void OnGunUnpaused(EntityUid uid, GunComponent component, ref EntityUnpausedEvent args) { component.NextFire += args.PausedTime; } - private void OnGunMeleeAttempt(EntityUid uid, GunComponent component, ref MeleeAttackAttemptEvent args) - { - if (TagSystem.HasTag(args.User, "GunsDisabled")) - return; - - args.Cancelled = true; - } - private void OnShootRequest(RequestShootEvent msg, EntitySessionEventArgs args) { var user = args.SenderSession.AttachedEntity; @@ -214,15 +219,12 @@ public abstract partial class SharedGunSystem : EntitySystem if (toCoordinates == null) return; - if (TagSystem.HasTag(user, "GunsDisabled")) - { - if (Timing.IsFirstTimePredicted) - Popup(Loc.GetString("gun-disabled"), user, user); - return; - } - var curTime = Timing.CurTime; + // Maybe Raise an event for this? CanAttack doesn't seem appropriate. + if (TryComp(gunUid, out var melee) && melee.NextAttack > curTime) + return; + // Need to do this to play the clicking sound for empty automatic weapons // but not play anything for burst fire. if (gun.NextFire > curTime) @@ -232,7 +234,8 @@ public abstract partial class SharedGunSystem : EntitySystem // First shot // Previously we checked shotcounter but in some cases all the bullets got dumped at once - if (gun.NextFire < curTime - fireRate) + // curTime - fireRate is insufficient because if you time it just right you can get a 3rd shot out slightly quicker. + if (gun.NextFire < curTime - fireRate || gun.ShotCounter == 0 && gun.NextFire < curTime) gun.NextFire = curTime; var shots = 0; @@ -263,6 +266,20 @@ public abstract partial class SharedGunSystem : EntitySystem throw new ArgumentOutOfRangeException($"No implemented shooting behavior for {gun.SelectedMode}!"); } + var attemptEv = new AttemptShootEvent(user, null); + RaiseLocalEvent(gunUid, ref attemptEv); + + if (attemptEv.Cancelled) + { + if (attemptEv.Message != null) + { + PopupSystem.PopupClient(attemptEv.Message, gunUid, user); + } + + gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds)); + return; + } + var fromCoordinates = Transform(user).Coordinates; // Remove ammo var ev = new TakeAmmoEvent(shots, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, user); @@ -279,10 +296,7 @@ public abstract partial class SharedGunSystem : EntitySystem // where the gun may be SemiAuto or Burst. gun.ShotCounter += shots; - var attemptEv = new AttemptShootEvent(user); - RaiseLocalEvent(gunUid, ref attemptEv); - - if (ev.Ammo.Count <= 0 || attemptEv.Cancelled) + if (ev.Ammo.Count <= 0) { // Play empty gun sounds if relevant // If they're firing an existing clip then don't play anything. @@ -415,7 +429,7 @@ public abstract partial class SharedGunSystem : EntitySystem /// Set this to true if the shot should be cancelled. /// Set this to true if the ammo shouldn't actually be fired, just thrown. [ByRefEvent] -public record struct AttemptShootEvent(EntityUid User, bool Cancelled = false, bool ThrowItems = false); +public record struct AttemptShootEvent(EntityUid User, string? Message, bool Cancelled = false, bool ThrowItems = false); /// /// Raised directed on the gun after firing. diff --git a/Content.Shared/Weapons/Ranged/Systems/UseDelayOnShootSystem.cs b/Content.Shared/Weapons/Ranged/Systems/UseDelayOnShootSystem.cs new file mode 100644 index 0000000000..10593f56df --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Systems/UseDelayOnShootSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared.Timing; +using Content.Shared.Weapons.Ranged.Components; + +namespace Content.Shared.Weapons.Ranged.Systems; + +public sealed class UseDelayOnShootSystem : EntitySystem +{ + [Dependency] private readonly UseDelaySystem _delay = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUseShoot); + } + + private void OnUseShoot(EntityUid uid, UseDelayOnShootComponent component, ref GunShotEvent args) + { + _delay.BeginDelay(uid); + } +} diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs index 929856367b..ea1ac7e42a 100644 --- a/Content.Shared/Wieldable/WieldableSystem.cs +++ b/Content.Shared/Wieldable/WieldableSystem.cs @@ -8,6 +8,10 @@ using Content.Shared.Item; using Content.Shared.Popups; using Content.Shared.Verbs; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Weapons.Melee.Components; +using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Wieldable.Components; using Robust.Shared.Player; @@ -34,9 +38,32 @@ public sealed class WieldableSystem : EntitySystem SubscribeLocalEvent>(AddToggleWieldVerb); SubscribeLocalEvent(OnDisarmAttemptEvent); + SubscribeLocalEvent(OnMeleeAttempt); + SubscribeLocalEvent(OnShootAttempt); + SubscribeLocalEvent(OnMeleeHit); } + private void OnMeleeAttempt(EntityUid uid, MeleeRequiresWieldComponent component, ref AttemptMeleeEvent args) + { + if (TryComp(uid, out var wieldable) && + !wieldable.Wielded) + { + args.Cancelled = true; + args.Message = Loc.GetString("wieldable-component-requires", ("item", uid)); + } + } + + private void OnShootAttempt(EntityUid uid, GunRequiresWieldComponent component, ref AttemptShootEvent args) + { + if (TryComp(uid, out var wieldable) && + !wieldable.Wielded) + { + args.Cancelled = true; + args.Message = Loc.GetString("wieldable-component-requires", ("item", uid)); + } + } + private void OnDisarmAttemptEvent(EntityUid uid, WieldableComponent component, DisarmAttemptEvent args) { if (component.Wielded) diff --git a/Resources/Audio/Weapons/attributions.yml b/Resources/Audio/Weapons/attributions.yml new file mode 100644 index 0000000000..c7be93af11 --- /dev/null +++ b/Resources/Audio/Weapons/attributions.yml @@ -0,0 +1,4 @@ +- files: ["plasm_cutter.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from Citadel station." + source: "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/5b43cb2545a19957ec6ce3352dceac5e347e77df/sound/weapons/plasma_cutter.ogg" diff --git a/Resources/Audio/Weapons/plasma_cutter.ogg b/Resources/Audio/Weapons/plasma_cutter.ogg new file mode 100644 index 0000000000..3698a70c4f Binary files /dev/null and b/Resources/Audio/Weapons/plasma_cutter.ogg differ diff --git a/Resources/Locale/en-US/wieldable/wieldable-component.ftl b/Resources/Locale/en-US/wieldable/wieldable-component.ftl index 30f81f5ca9..91eee8c2ea 100644 --- a/Resources/Locale/en-US/wieldable/wieldable-component.ftl +++ b/Resources/Locale/en-US/wieldable/wieldable-component.ftl @@ -9,9 +9,11 @@ wieldable-component-successful-wield-other = { THE($user) } wields { THE($item) wieldable-component-failed-wield-other = { THE($user) } unwields { THE($item) }. wieldable-component-no-hands = You don't have enough hands! -wieldable-component-not-enough-free-hands = {$number -> +wieldable-component-not-enough-free-hands = {$number -> [one] You need a free hand to wield { THE($item) }. *[other] You need { $number } free hands to wield { THE($item) }. } wieldable-component-not-in-hands = { CAPITALIZE(THE($item)) } isn't in your hands! +wieldable-component-requires = { CAPITALIZE(THE($item))} must be wielded! + diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml index 132131329d..502bd78b7a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml @@ -33,12 +33,6 @@ isLooped: true property: Radius enabled: false - toggleAction: - name: action-name-toggle-light - description: action-description-toggle-light - icon: { sprite: Objects/Tools/flashlight.rsi, state: flashlight } - iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png - event: !type:ToggleActionEvent - type: ToggleableLightVisuals spriteLayer: light inhandVisuals: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml index 829af331b6..472233d3b3 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml @@ -130,6 +130,7 @@ parent: BaseBulletHighVelocity name: Icicle description: Brrrrr. + noSpawn: true components: - type: Sprite sprite: Structures/Specific/Anomalies/ice_anom.rsi diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index 796383f0fc..b03a2c9692 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -309,6 +309,37 @@ - type: TimedDespawn lifetime: 0.4 +- type: entity + id: BulletCharge + name: charge bolt + parent: BaseBulletHighVelocity + noSpawn: true + description: Marks a target for additional damage. + components: + - type: Sprite + noRot: false + sprite: Objects/Weapons/Guns/Projectiles/magic.rsi + layers: + - state: chronobolt + shader: unshaded + - type: GatheringProjectile + - type: DamageMarkerOnCollide + whitelist: + components: + - MobState + damage: + types: + Blunt: 20 + Slash: 5 + - type: Projectile + impactEffect: BulletImpactEffectKinetic + damage: + types: + Blunt: 0 + # Short lifespan + - type: TimedDespawn + lifetime: 0.4 + - type: entity parent: BaseBullet id: AnomalousParticleDelta diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml new file mode 100644 index 0000000000..0f034a8df3 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml @@ -0,0 +1,96 @@ +- type: entity + name: crusher + parent: BaseItem + id: BaseWeaponCrusher # Crusher? But I... + abstract: true + description: An early design of the proto-kinetic accelerator. + components: + - type: Sharp + - type: UnpoweredFlashlight + toggleAction: + name: action-name-toggle-light + description: action-description-toggle-light + icon: { sprite: Objects/Tools/flashlight.rsi, state: flashlight } + iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png + event: !type:ToggleActionEvent + - type: PointLight + enabled: false + radius: 4 + +- type: entity + name: crusher + parent: BaseWeaponCrusher + id: WeaponCrusher + components: + - type: Sprite + sprite: Objects/Weapons/Melee/crusher.rsi + state: icon + - type: AmmoCounter + - type: UseDelayOnShoot + - type: UseDelay + delay: 1.9 + - type: Gun + soundGunshot: /Audio/Weapons/plasma_cutter.ogg + fireRate: 0.5 + useKey: false + - type: RechargeBasicEntityAmmo + rechargeCooldown: 1.5 + rechargeSound: + path: /Audio/Weapons/Guns/MagIn/kinetic_reload.ogg + - type: BasicEntityAmmoProvider + proto: BulletCharge + capacity: 1 + count: 1 + - type: MeleeWeapon + attackRate: 0.75 + damage: + types: + Blunt: 10 + Slash: 5 + - type: Wieldable + - type: MeleeRequiresWield + - type: GunRequiresWield + - type: Item + size: 150 + - type: DisarmMalus + - type: Tool + qualities: + - Prying + +- type: entity + name: crusher dagger + parent: BaseWeaponCrusher + id: WeaponCrusherDagger + description: A scaled down version of a proto-kinetic crusher, usually used in a last ditch scenario. + components: + - type: Sprite + sprite: Objects/Weapons/Melee/crusher_dagger.rsi + state: icon + - type: MeleeWeapon + attackRate: 1.5 + damage: + types: + Slash: 6.5 + - type: Item + size: 30 + +# Like a crusher... but better +- type: entity + name: crusher glaive + parent: WeaponCrusher + id: WeaponCrusherGlaive + description: An early design of the proto-kinetic accelerator, in glaive form. + components: + - type: UseDelayOnShoot + - type: UseDelay + delay: 1.9 + - type: Gun + fireRate: 1 + - type: RechargeBasicEntityAmmo + rechargeCooldown: 0.5 + - type: Sprite + sprite: Objects/Weapons/Melee/crusher_glaive.rsi + - type: MeleeWeapon + attackRate: 1.25 + - type: Item + size: 150 diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml index 1ec9992ee3..d4729fa13f 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml @@ -3,6 +3,7 @@ description: Accelerated particles. id: ParticlesProjectile parent: BaseBullet + noSpawn: true components: - type: Sprite layers: diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 3796775d66..d9720e1912 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -314,9 +314,6 @@ - type: Tag id: GuideEmbeded -- type: Tag - id: GunsDisabled # Allow certain entities to not use guns without complicating the system with an event - - type: Tag id: Handcuffs diff --git a/Resources/Textures/Objects/Weapons/Effects/meta.json b/Resources/Textures/Objects/Weapons/Effects/meta.json new file mode 100644 index 0000000000..408eccd3f8 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Effects/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/blob/192e2ce0821c8ed347f3b4164e7d76fe344f4bbf/icons/effects/effects.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "shield2", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Effects/shield2.png b/Resources/Textures/Objects/Weapons/Effects/shield2.png new file mode 100644 index 0000000000..488dd67f1f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Effects/shield2.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon-lit.png b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon-lit.png new file mode 100644 index 0000000000..dbd25ec0f9 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon-lit.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon-uncharged.png b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon-uncharged.png new file mode 100644 index 0000000000..f78929ced6 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon-uncharged.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon.png new file mode 100644 index 0000000000..d9b6ca8157 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/inhand-left.png new file mode 100644 index 0000000000..28eab44a32 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/inhand-right.png new file mode 100644 index 0000000000..dcf606531b Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/meta.json new file mode 100644 index 0000000000..2d91fb460b --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/meta.json @@ -0,0 +1,42 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/817e7c1f225876b45891e3f06908e6d032f0a8bc/icons/obj/mining.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-lit" + }, + { + "name": "icon-uncharged", + "delays": [ + [ + 0.3, + 0.3 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..6b55e43f72 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..b3d449f083 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/icon-lit.png b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/icon-lit.png new file mode 100644 index 0000000000..292963f919 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/icon-lit.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/icon.png new file mode 100644 index 0000000000..2c1bdcb845 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/inhand-left.png new file mode 100644 index 0000000000..5d64ccba6e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/inhand-right.png new file mode 100644 index 0000000000..cce061bab2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/meta.json new file mode 100644 index 0000000000..f076030635 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/crusher_dagger.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/817e7c1f225876b45891e3f06908e6d032f0a8bc/icons/obj/mining.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-lit" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon-lit.png b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon-lit.png new file mode 100644 index 0000000000..e33398abc2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon-lit.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon-uncharged.png b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon-uncharged.png new file mode 100644 index 0000000000..3929ec047e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon-uncharged.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon.png new file mode 100644 index 0000000000..980af14aa8 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/inhand-left.png new file mode 100644 index 0000000000..6d9aab7420 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/inhand-right.png new file mode 100644 index 0000000000..e2d24c9ee8 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/meta.json new file mode 100644 index 0000000000..2d91fb460b --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/meta.json @@ -0,0 +1,42 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/817e7c1f225876b45891e3f06908e6d032f0a8bc/icons/obj/mining.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-lit" + }, + { + "name": "icon-uncharged", + "delays": [ + [ + 0.3, + 0.3 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..ce6c530f30 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..84abf80148 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/crusher_glaive.rsi/wielded-inhand-right.png differ