From a9054c5e9c8f595874c034abd14fc63b3831a411 Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:38:52 -0500 Subject: [PATCH] Mjollnir and Singularity Hammer for Wizard (#34446) --- .../Anomaly/SharedAnomalyCoreSystem.cs | 6 +- Content.Shared/Anomaly/SharedAnomalySystem.cs | 10 +- .../RepulseAttract/RepulseAttractComponent.cs | 38 +++ .../RepulseAttract/RepulseAttractSystem.cs | 78 ++++++ Content.Shared/Throwing/ThrowingSystem.cs | 22 +- Content.Shared/Timing/UseDelaySystem.cs | 7 +- .../Components/MeleeThrowOnHitComponent.cs | 91 ++----- .../Components/UseDelayOnMeleeHitComponent.cs | 12 + .../Weapons/Melee/MeleeThrowOnHitSystem.cs | 117 +++------ .../Weapons/Melee/UseDelayOnMeleeHitSystem.cs | 39 +++ .../Components/WieldableComponent.cs | 8 +- .../Wieldable/SharedWieldableSystem.cs | 11 +- .../Locale/en-US/store/spellbook-catalog.ftl | 6 + .../Prototypes/Catalog/spellbook_catalog.yml | 26 ++ .../Objects/Specific/Research/anomaly.yml | 1 - .../Objects/Weapons/Melee/hammers.yml | 109 ++++++++ .../Objects/Weapons/Melee/sledgehammer.yml | 25 -- .../Weapons/Melee/mjollnir.rsi/icon.png | Bin 0 -> 1297 bytes .../Melee/mjollnir.rsi/inhand-left.png | Bin 0 -> 686 bytes .../Melee/mjollnir.rsi/inhand-right.png | Bin 0 -> 681 bytes .../Weapons/Melee/mjollnir.rsi/meta.json | 42 +++ .../mjollnir.rsi/wielded-inhand-left.png | Bin 0 -> 695 bytes .../mjollnir.rsi/wielded-inhand-right.png | Bin 0 -> 684 bytes .../Melee/singularityhammer.rsi/icon.png | Bin 0 -> 2836 bytes .../singularityhammer.rsi/inhand-left.png | Bin 0 -> 4596 bytes .../singularityhammer.rsi/inhand-right.png | Bin 0 -> 4704 bytes .../Melee/singularityhammer.rsi/meta.json | 244 ++++++++++++++++++ .../wielded-inhand-left.png | Bin 0 -> 6297 bytes .../wielded-inhand-right.png | Bin 0 -> 6401 bytes 29 files changed, 692 insertions(+), 200 deletions(-) create mode 100644 Content.Shared/RepulseAttract/RepulseAttractComponent.cs create mode 100644 Content.Shared/RepulseAttract/RepulseAttractSystem.cs create mode 100644 Content.Shared/Weapons/Melee/Components/UseDelayOnMeleeHitComponent.cs create mode 100644 Content.Shared/Weapons/Melee/UseDelayOnMeleeHitSystem.cs create mode 100644 Resources/Prototypes/Entities/Objects/Weapons/Melee/hammers.yml delete mode 100644 Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml create mode 100644 Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/icon.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/meta.json create mode 100644 Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-right.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/icon.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/meta.json create mode 100644 Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-right.png diff --git a/Content.Shared/Anomaly/SharedAnomalyCoreSystem.cs b/Content.Shared/Anomaly/SharedAnomalyCoreSystem.cs index f4864a532b..70735dbb40 100644 --- a/Content.Shared/Anomaly/SharedAnomalyCoreSystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalyCoreSystem.cs @@ -36,9 +36,9 @@ public sealed class SharedAnomalyCoreSystem : EntitySystem var (uid, comp) = ent; // don't waste charges on non-anchorable non-anomalous static bodies. - if (!HasComp(args.Hit) - && !HasComp(args.Hit) - && TryComp(args.Hit, out var body) + if (!HasComp(args.Target) + && !HasComp(args.Target) + && TryComp(args.Target, out var body) && body.BodyType == BodyType.Static) return; diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index 05e12d406e..3f8da9e3c8 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Anomaly.Prototypes; using Content.Shared.Database; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Throwing; using Content.Shared.Weapons.Melee.Components; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; @@ -41,19 +42,22 @@ public abstract class SharedAnomalySystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnAnomalyThrowStart); - SubscribeLocalEvent(OnAnomalyThrowEnd); + SubscribeLocalEvent(OnLand); } private void OnAnomalyThrowStart(Entity ent, ref MeleeThrowOnHitStartEvent args) { - if (!TryComp(args.Used, out var corePowered) || !TryComp(ent, out var body)) + if (!TryComp(args.Weapon, out var corePowered) || !TryComp(ent, out var body)) return; + + // anomalies are static by default, so we have set them to dynamic to be throwable _physics.SetBodyType(ent, BodyType.Dynamic, body: body); ChangeAnomalyStability(ent, Random.NextFloat(corePowered.StabilityPerThrow.X, corePowered.StabilityPerThrow.Y), ent.Comp); } - private void OnAnomalyThrowEnd(Entity ent, ref MeleeThrowOnHitEndEvent args) + private void OnLand(Entity ent, ref LandEvent args) { + // revert back to static _physics.SetBodyType(ent, BodyType.Static); } diff --git a/Content.Shared/RepulseAttract/RepulseAttractComponent.cs b/Content.Shared/RepulseAttract/RepulseAttractComponent.cs new file mode 100644 index 0000000000..c167d81ec6 --- /dev/null +++ b/Content.Shared/RepulseAttract/RepulseAttractComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared.Physics; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.RepulseAttract; + +/// +/// Used to repulse or attract entities away from the entity this is on +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(RepulseAttractSystem))] +public sealed partial class RepulseAttractComponent : Component +{ + /// + /// How fast should the Repulsion/Attraction be? + /// A positive value will repulse objects, a negative value will attract + /// + [DataField, AutoNetworkedField] + public float Speed = 5.0f; + + /// + /// How close do the entities need to be? + /// + [DataField, AutoNetworkedField] + public float Range = 5.0f; + + /// + /// What kind of entities should this effect apply to? + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? Whitelist; + + /// + /// What collision layers should be excluded? + /// The default excludes ghost mobs, revenants, the AI camera etc. + /// + [DataField, AutoNetworkedField] + public CollisionGroup CollisionMask = CollisionGroup.GhostImpassable; +} diff --git a/Content.Shared/RepulseAttract/RepulseAttractSystem.cs b/Content.Shared/RepulseAttract/RepulseAttractSystem.cs new file mode 100644 index 0000000000..9363d7bbec --- /dev/null +++ b/Content.Shared/RepulseAttract/RepulseAttractSystem.cs @@ -0,0 +1,78 @@ +using Content.Shared.Physics; +using Content.Shared.Throwing; +using Content.Shared.Timing; +using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Whitelist; +using Content.Shared.Wieldable; +using Robust.Shared.Map; +using Robust.Shared.Physics.Components; +using System.Numerics; +using Content.Shared.Weapons.Melee; + +namespace Content.Shared.RepulseAttract; + +public sealed class RepulseAttractSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly ThrowingSystem _throw = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly SharedTransformSystem _xForm = default!; + [Dependency] private readonly UseDelaySystem _delay = default!; + + private EntityQuery _physicsQuery; + private HashSet _entSet = new(); + public override void Initialize() + { + base.Initialize(); + + _physicsQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnMeleeAttempt, before: [typeof(UseDelayOnMeleeHitSystem)], after: [typeof(SharedWieldableSystem)]); + } + private void OnMeleeAttempt(Entity ent, ref MeleeHitEvent args) + { + if (_delay.IsDelayed(ent.Owner)) + return; + + TryRepulseAttract(ent, args.User); + } + + public bool TryRepulseAttract(Entity ent, EntityUid user) + { + var position = _xForm.GetMapCoordinates(ent.Owner); + return TryRepulseAttract(position, user, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask); + } + + public bool TryRepulseAttract(MapCoordinates position, EntityUid? user, float speed, float range, EntityWhitelist? whitelist = null, CollisionGroup layer = CollisionGroup.SingularityLayer) + { + _entSet.Clear(); + var epicenter = position.Position; + _lookup.GetEntitiesInRange(position.MapId, epicenter, range, _entSet, flags: LookupFlags.Dynamic | LookupFlags.Sundries); + + foreach (var target in _entSet) + { + if (!_physicsQuery.TryGetComponent(target, out var physics) + || (physics.CollisionLayer & (int)layer) != 0x0) // exclude layers like ghosts + continue; + + if (_whitelist.IsWhitelistFail(whitelist, target)) + continue; + + var targetPos = _xForm.GetWorldPosition(target); + + // vector from epicenter to target entity + var direction = targetPos - epicenter; + + if (direction == Vector2.Zero) + continue; + + // attract: throw all items directly to to the epicenter + // repulse: throw them up to the maximum range + var throwDirection = speed < 0 ? -direction : direction.Normalized() * (range - direction.Length()); + + _throw.TryThrow(target, throwDirection, Math.Abs(speed), user, recoil: false, compensateFriction: true); + } + + return true; + } +} diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index 549473278e..c37f235698 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -2,6 +2,7 @@ using System.Numerics; using Content.Shared.Administration.Logs; using Content.Shared.Camera; using Content.Shared.CCVar; +using Content.Shared.Construction.Components; using Content.Shared.Database; using Content.Shared.Friction; using Content.Shared.Gravity; @@ -57,7 +58,8 @@ public sealed class ThrowingSystem : EntitySystem bool recoil = true, bool animated = true, bool playSound = true, - bool doSpin = true) + bool doSpin = true, + bool unanchor = false) { var thrownPos = _transform.GetMapCoordinates(uid); var mapPos = _transform.ToMapCoordinates(coordinates); @@ -65,7 +67,7 @@ public sealed class ThrowingSystem : EntitySystem if (mapPos.MapId != thrownPos.MapId) return; - TryThrow(uid, mapPos.Position - thrownPos.Position, baseThrowSpeed, user, pushbackRatio, friction, compensateFriction: compensateFriction, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); + TryThrow(uid, mapPos.Position - thrownPos.Position, baseThrowSpeed, user, pushbackRatio, friction, compensateFriction: compensateFriction, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin, unanchor: unanchor); } /// @@ -78,6 +80,7 @@ public sealed class ThrowingSystem : EntitySystem /// friction value used for the distance calculation. If set to null this defaults to the standard tile values /// True will adjust the throw so the item stops at the target coordinates. False means it will land at the target and keep sliding. /// Whether spin will be applied to the thrown entity. + /// If true and the thrown entity has , unanchor the thrown entity public void TryThrow(EntityUid uid, Vector2 direction, float baseThrowSpeed = 10.0f, @@ -88,7 +91,8 @@ public sealed class ThrowingSystem : EntitySystem bool recoil = true, bool animated = true, bool playSound = true, - bool doSpin = true) + bool doSpin = true, + bool unanchor = false) { var physicsQuery = GetEntityQuery(); if (!physicsQuery.TryGetComponent(uid, out var physics)) @@ -105,7 +109,7 @@ public sealed class ThrowingSystem : EntitySystem baseThrowSpeed, user, pushbackRatio, - friction, compensateFriction: compensateFriction, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); + friction, compensateFriction: compensateFriction, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin, unanchor: unanchor); } /// @@ -118,6 +122,7 @@ public sealed class ThrowingSystem : EntitySystem /// friction value used for the distance calculation. If set to null this defaults to the standard tile values /// True will adjust the throw so the item stops at the target coordinates. False means it will land at the target and keep sliding. /// Whether spin will be applied to the thrown entity. + /// If true and the thrown entity has , unanchor the thrown entity public void TryThrow(EntityUid uid, Vector2 direction, PhysicsComponent physics, @@ -131,16 +136,17 @@ public sealed class ThrowingSystem : EntitySystem bool recoil = true, bool animated = true, bool playSound = true, - bool doSpin = true) + bool doSpin = true, + bool unanchor = false) { if (baseThrowSpeed <= 0 || direction == Vector2Helpers.Infinity || direction == Vector2Helpers.NaN || direction == Vector2.Zero || friction < 0) return; + if (unanchor && HasComp(uid)) + _transform.Unanchor(uid); + if ((physics.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) - { - Log.Warning($"Tried to throw entity {ToPrettyString(uid)} but can't throw {physics.BodyType} bodies!"); return; - } // Allow throwing if this projectile only acts as a projectile when shot, otherwise disallow if (projectileQuery.TryGetComponent(uid, out var proj) && !proj.OnlyCollideWhenShot) diff --git a/Content.Shared/Timing/UseDelaySystem.cs b/Content.Shared/Timing/UseDelaySystem.cs index 9707a4ddbe..c6a1818443 100644 --- a/Content.Shared/Timing/UseDelaySystem.cs +++ b/Content.Shared/Timing/UseDelaySystem.cs @@ -88,8 +88,11 @@ public sealed class UseDelaySystem : EntitySystem /// /// Returns true if the entity has a currently active UseDelay with the specified ID. /// - public bool IsDelayed(Entity ent, string id = DefaultId) + public bool IsDelayed(Entity ent, string id = DefaultId) { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + if (!ent.Comp.Delays.TryGetValue(id, out var entry)) return false; @@ -144,7 +147,7 @@ public sealed class UseDelaySystem : EntitySystem /// Otherwise reset it and return true. public bool TryResetDelay(Entity ent, bool checkDelayed = false, string id = DefaultId) { - if (checkDelayed && IsDelayed(ent, id)) + if (checkDelayed && IsDelayed((ent.Owner, ent.Comp), id)) return false; if (!ent.Comp.Delays.TryGetValue(id, out var entry)) diff --git a/Content.Shared/Weapons/Melee/Components/MeleeThrowOnHitComponent.cs b/Content.Shared/Weapons/Melee/Components/MeleeThrowOnHitComponent.cs index 82ffc5e51f..84e75129b1 100644 --- a/Content.Shared/Weapons/Melee/Components/MeleeThrowOnHitComponent.cs +++ b/Content.Shared/Weapons/Melee/Components/MeleeThrowOnHitComponent.cs @@ -9,104 +9,49 @@ namespace Content.Shared.Weapons.Melee.Components; /// This is used for a melee weapon that throws whatever gets hit by it in a line /// until it hits a wall or a time limit is exhausted. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [Access(typeof(MeleeThrowOnHitSystem))] -[AutoGenerateComponentState] public sealed partial class MeleeThrowOnHitComponent : Component { /// /// The speed at which hit entities should be thrown. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public float Speed = 10f; /// - /// How long hit entities remain thrown, max. + /// The maximum distance the hit entity should be thrown. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public float Lifetime = 3f; - - /// - /// How long we wait to start accepting collision. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MinLifetime = 0.05f; + [DataField, AutoNetworkedField] + public float Distance = 20f; /// /// Whether or not anchorable entities should be unanchored when hit. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public bool UnanchorOnHit; /// - /// Whether or not the throwing behavior occurs by default. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public bool Enabled = true; -} - -/// -/// Component used to track entities that have been yeeted by -/// -[RegisterComponent, NetworkedComponent] -[AutoGenerateComponentState] -[Access(typeof(MeleeThrowOnHitSystem))] -public sealed partial class MeleeThrownComponent : Component -{ - /// - /// The velocity of the throw - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public Vector2 Velocity; - - /// - /// How long the throw will last. + /// How long should this stun the target, if applicable? /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public float Lifetime; + [DataField, AutoNetworkedField] + public TimeSpan? StunTime; /// - /// How long we wait to start accepting collision. + /// Should this also work on a throw-hit? /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MinLifetime; - - /// - /// At what point in time will the throw be complete? - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoNetworkedField] - public TimeSpan ThrownEndTime; - - /// - /// At what point in time will the be exhausted - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoNetworkedField] - public TimeSpan MinLifetimeTime; - - /// - /// the status to which the entity will return when the thrown ends - /// - [DataField] - public BodyStatus PreviousStatus; + [DataField, AutoNetworkedField] + public bool ActivateOnThrown; } /// -/// Event raised before an entity is thrown by to see if a throw is allowed. -/// If not handled, the enabled field on the component will be used instead. +/// Raised a weapon entity with to see if a throw is allowed. /// [ByRefEvent] -public record struct AttemptMeleeThrowOnHitEvent(EntityUid Hit, bool Cancelled = false, bool Handled = false); - -[ByRefEvent] -public record struct MeleeThrowOnHitStartEvent(EntityUid User, EntityUid Used); +public record struct AttemptMeleeThrowOnHitEvent(EntityUid Target, EntityUid? User, bool Cancelled = false, bool Handled = false); +/// +/// Raised a target entity before it is thrown by . +/// [ByRefEvent] -public record struct MeleeThrowOnHitEndEvent(); +public record struct MeleeThrowOnHitStartEvent(EntityUid Weapon, EntityUid? User); diff --git a/Content.Shared/Weapons/Melee/Components/UseDelayOnMeleeHitComponent.cs b/Content.Shared/Weapons/Melee/Components/UseDelayOnMeleeHitComponent.cs new file mode 100644 index 0000000000..644971bc01 --- /dev/null +++ b/Content.Shared/Weapons/Melee/Components/UseDelayOnMeleeHitComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Weapons.Melee.Components; + +/// +/// Activates UseDelay when a Melee Weapon is used to hit something. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(UseDelayOnMeleeHitSystem))] +public sealed partial class UseDelayOnMeleeHitComponent : Component +{ + +} diff --git a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs index 7886356233..bc95be926a 100644 --- a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs +++ b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs @@ -1,12 +1,13 @@ -using System.Numerics; using Content.Shared.Construction.Components; +using Content.Shared.Stunnable; +using Content.Shared.Throwing; +using Content.Shared.Timing; using Content.Shared.Weapons.Melee.Components; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; -using Robust.Shared.Timing; +using System.Numerics; namespace Content.Shared.Weapons.Melee; @@ -15,113 +16,67 @@ namespace Content.Shared.Weapons.Melee; /// public sealed class MeleeThrowOnHitSystem : EntitySystem { - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; - + [Dependency] private readonly UseDelaySystem _delay = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; /// public override void Initialize() { SubscribeLocalEvent(OnMeleeHit); - SubscribeLocalEvent(OnThrownStartup); - SubscribeLocalEvent(OnThrownShutdown); - SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnThrowHit); } - private void OnMeleeHit(Entity ent, ref MeleeHitEvent args) + private void OnMeleeHit(Entity weapon, ref MeleeHitEvent args) { - var (_, comp) = ent; + // TODO: MeleeHitEvent is weird. Why is this even raised if we don't hit something? if (!args.IsHit) return; - var mapPos = _transform.GetMapCoordinates(args.User).Position; - foreach (var hit in args.HitEntities) - { - var hitPos = _transform.GetMapCoordinates(hit).Position; - var angle = args.Direction ?? hitPos - mapPos; - if (angle == Vector2.Zero) - continue; - - if (!CanThrowOnHit(ent, hit)) - continue; - - if (comp.UnanchorOnHit && HasComp(hit)) - { - _transform.Unanchor(hit, Transform(hit)); - } - - RemComp(hit); - var ev = new MeleeThrowOnHitStartEvent(args.User, ent); - RaiseLocalEvent(hit, ref ev); - var thrownComp = new MeleeThrownComponent - { - Velocity = angle.Normalized() * comp.Speed, - Lifetime = comp.Lifetime, - MinLifetime = comp.MinLifetime - }; - AddComp(hit, thrownComp); - } - } - - private void OnThrownStartup(Entity ent, ref ComponentStartup args) - { - var (_, comp) = ent; - - if (!TryComp(ent, out var body) || - (body.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) + if (_delay.IsDelayed(weapon.Owner)) return; - comp.PreviousStatus = body.BodyStatus; - comp.ThrownEndTime = _timing.CurTime + TimeSpan.FromSeconds(comp.Lifetime); - comp.MinLifetimeTime = _timing.CurTime + TimeSpan.FromSeconds(comp.MinLifetime); - _physics.SetBodyStatus(ent, body, BodyStatus.InAir); - _physics.SetLinearVelocity(ent, Vector2.Zero, body: body); - _physics.ApplyLinearImpulse(ent, comp.Velocity * body.Mass, body: body); - Dirty(ent, ent.Comp); - } + if (args.HitEntities.Count == 0) + return; - private void OnThrownShutdown(Entity ent, ref ComponentShutdown args) - { - if (TryComp(ent, out var body)) - _physics.SetBodyStatus(ent, body, ent.Comp.PreviousStatus); - var ev = new MeleeThrowOnHitEndEvent(); - RaiseLocalEvent(ent, ref ev); + var userPos = _transform.GetWorldPosition(args.User); + foreach (var target in args.HitEntities) + { + var targetPos = _transform.GetMapCoordinates(target).Position; + var direction = args.Direction ?? targetPos - userPos; + ThrowOnHitHelper(weapon, args.User, target, direction); + } } - private void OnStartCollide(Entity ent, ref StartCollideEvent args) + private void OnThrowHit(Entity weapon, ref ThrowDoHitEvent args) { - var (_, comp) = ent; - if (!args.OtherFixture.Hard || !args.OtherBody.CanCollide || !args.OurFixture.Hard || !args.OurBody.CanCollide) + if (!weapon.Comp.ActivateOnThrown) return; - if (_timing.CurTime < comp.MinLifetimeTime) + if (!TryComp(args.Thrown, out var weaponPhysics)) return; - RemCompDeferred(ent, ent.Comp); + ThrowOnHitHelper(weapon, args.Component.Thrower, args.Target, weaponPhysics.LinearVelocity); } - public bool CanThrowOnHit(Entity ent, EntityUid target) + private void ThrowOnHitHelper(Entity ent, EntityUid? user, EntityUid target, Vector2 direction) { - var (uid, comp) = ent; + var attemptEvent = new AttemptMeleeThrowOnHitEvent(target, user); + RaiseLocalEvent(ent.Owner, ref attemptEvent); - var ev = new AttemptMeleeThrowOnHitEvent(target); - RaiseLocalEvent(uid, ref ev); + if (attemptEvent.Cancelled) + return; - if (ev.Handled) - return !ev.Cancelled; + var startEvent = new MeleeThrowOnHitStartEvent(ent.Owner, user); + RaiseLocalEvent(target, ref startEvent); - return comp.Enabled; - } + if (ent.Comp.StunTime != null) + _stun.TryParalyze(target, ent.Comp.StunTime.Value, false); - public override void Update(float frameTime) - { - base.Update(frameTime); + if (direction == Vector2.Zero) + return; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) - { - if (_timing.CurTime > comp.ThrownEndTime) - RemCompDeferred(uid, comp); - } + _throwing.TryThrow(target, direction.Normalized() * ent.Comp.Distance, ent.Comp.Speed, user, unanchor: ent.Comp.UnanchorOnHit); } } diff --git a/Content.Shared/Weapons/Melee/UseDelayOnMeleeHitSystem.cs b/Content.Shared/Weapons/Melee/UseDelayOnMeleeHitSystem.cs new file mode 100644 index 0000000000..0400bf5f91 --- /dev/null +++ b/Content.Shared/Weapons/Melee/UseDelayOnMeleeHitSystem.cs @@ -0,0 +1,39 @@ +using Content.Shared.Throwing; +using Content.Shared.Timing; +using Content.Shared.Weapons.Melee.Components; +using Content.Shared.Weapons.Melee.Events; + +namespace Content.Shared.Weapons.Melee; + +/// +public sealed class UseDelayOnMeleeHitSystem : EntitySystem +{ + [Dependency] private readonly UseDelaySystem _delay = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMeleeHit); + SubscribeLocalEvent(OnThrowHitEvent); + } + + private void OnThrowHitEvent(Entity ent, ref ThrowDoHitEvent args) + { + TryResetDelay(ent); + } + + private void OnMeleeHit(Entity ent, ref MeleeHitEvent args) + { + TryResetDelay(ent); + } + + private void TryResetDelay(Entity ent) + { + var uid = ent.Owner; + + if (!TryComp(uid, out var useDelay)) + return; + + _delay.TryResetDelay((uid, useDelay), checkDelayed: true); + } +} diff --git a/Content.Shared/Wieldable/Components/WieldableComponent.cs b/Content.Shared/Wieldable/Components/WieldableComponent.cs index c146940b7a..c7be70e7c9 100644 --- a/Content.Shared/Wieldable/Components/WieldableComponent.cs +++ b/Content.Shared/Wieldable/Components/WieldableComponent.cs @@ -28,11 +28,17 @@ public sealed partial class WieldableComponent : Component /// /// Whether using the item inhand while wielding causes the item to unwield. - /// Unwielding can conflict with other inhand actions. + /// Unwielding can conflict with other inhand actions. /// [DataField] public bool UnwieldOnUse = true; + /// + /// Should use delay trigger after the wield/unwield? + /// + [DataField] + public bool UseDelayOnWield = true; + [DataField("wieldedInhandPrefix")] public string? WieldedInhandPrefix = "wielded"; diff --git a/Content.Shared/Wieldable/SharedWieldableSystem.cs b/Content.Shared/Wieldable/SharedWieldableSystem.cs index 110c941dac..b3d2e1bdf1 100644 --- a/Content.Shared/Wieldable/SharedWieldableSystem.cs +++ b/Content.Shared/Wieldable/SharedWieldableSystem.cs @@ -191,6 +191,9 @@ public abstract class SharedWieldableSystem : EntitySystem args.Handled = TryWield(uid, component, args.User); else if (component.UnwieldOnUse) args.Handled = TryUnwield(uid, component, args.User); + + if (HasComp(uid) && !component.UseDelayOnWield) + args.ApplyDelay = false; } public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet = false) @@ -235,9 +238,11 @@ public abstract class SharedWieldableSystem : EntitySystem if (!CanWield(used, component, user)) return false; - if (TryComp(used, out UseDelayComponent? useDelay) - && !_delay.TryResetDelay((used, useDelay), true)) - return false; + if (TryComp(used, out UseDelayComponent? useDelay) && component.UseDelayOnWield) + { + if (!_delay.TryResetDelay((used, useDelay), true)) + return false; + } var attemptEv = new WieldAttemptEvent(user); RaiseLocalEvent(used, ref attemptEv); diff --git a/Resources/Locale/en-US/store/spellbook-catalog.ftl b/Resources/Locale/en-US/store/spellbook-catalog.ftl index 0c650c0d4d..73d8118fc5 100644 --- a/Resources/Locale/en-US/store/spellbook-catalog.ftl +++ b/Resources/Locale/en-US/store/spellbook-catalog.ftl @@ -52,6 +52,12 @@ spellbook-wand-polymorph-carp-description = For when you need a carp filet quick spellbook-wand-locker-name = Wand of the Locker spellbook-wand-locker-description = Shoot cursed lockers at your enemies and lock em away! +spellbook-hammer-mjollnir-name = Mjollnir +spellbook-hammer-mjollnir-description = Wield the power of THUNDER in your hands. Send foes flying with a mighty swing or by throwing it right at em! + +spellbook-hammer-singularity-name = Singularity Hammer +spellbook-hammer-singularity-description = Ever wonder what it'd be like to be the singularity? Swing this hammer to draw in your surroundings, even works if you miss! + spellbook-staff-animation-name = Staff of Animation spellbook-staff-animation-description = Bring inanimate objects to life! diff --git a/Resources/Prototypes/Catalog/spellbook_catalog.yml b/Resources/Prototypes/Catalog/spellbook_catalog.yml index 0486330940..768475a03b 100644 --- a/Resources/Prototypes/Catalog/spellbook_catalog.yml +++ b/Resources/Prototypes/Catalog/spellbook_catalog.yml @@ -197,6 +197,32 @@ - !type:ListingLimitedStockCondition stock: 1 +- type: listing + id: SpellbookHammerMjollnir + name: spellbook-hammer-mjollnir-name + description: spellbook-hammer-mjollnir-description + productEntity: Mjollnir + cost: + WizCoin: 2 + categories: + - SpellbookEquipment + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: SpellbookHammerSingularity + name: spellbook-hammer-singularity-name + description: spellbook-hammer-singularity-description + productEntity: SingularityHammer + cost: + WizCoin: 2 + categories: + - SpellbookEquipment + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - type: listing id: SpellbookStaffAnimation name: spellbook-staff-animation-name diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml index efbd7c2db4..0872f0fc3e 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml @@ -142,7 +142,6 @@ - type: CorePoweredThrower - type: MeleeThrowOnHit unanchorOnHit: true - enabled: false - type: ItemSlots slots: core_slot: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/hammers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/hammers.yml new file mode 100644 index 0000000000..d2a0ecdbfb --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/hammers.yml @@ -0,0 +1,109 @@ +- type: entity + name: sledgehammer + parent: BaseItem + id: Sledgehammer + description: The perfect tool for wanton carnage. + components: + - type: Sprite + sprite: Objects/Weapons/Melee/sledgehammer.rsi + state: icon + - type: MeleeWeapon + wideAnimationRotation: -135 + damage: + types: + Blunt: 10 + Structural: 10 + soundHit: + collection: MetalThud + - type: Wieldable + - type: IncreaseDamageOnWield + damage: + types: + Blunt: 10 + Structural: 10 + - type: Item + size: Large + +- type: entity + id: Mjollnir + parent: [ BaseItem, BaseMagicalContraband ] + name: Mjollnir + description: A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy. + components: + - type: Wieldable + useDelayOnWield: false + - type: MeleeRequiresWield + - type: LandAtCursor + - type: Sprite + sprite: Objects/Weapons/Melee/mjollnir.rsi + layers: + - state: icon + - type: UseDelay + delay: 10 + - type: UseDelayOnMeleeHit + - type: MeleeThrowOnHit + stunTime: 3 + activateOnThrown: true + - type: MeleeWeapon + wideAnimationRotation: -135 + damage: + types: + Blunt: 5 + Structural: 5 + soundHit: + path: /Audio/Effects/tesla_consume.ogg + params: + variation: 0.10 + - type: IncreaseDamageOnWield + damage: + types: + Blunt: 20 + Structural: 25 + - type: DamageOtherOnHit + damage: + types: + Blunt: 15 + Structural: 15 + - type: Item + size: Ginormous + +- type: entity + id: SingularityHammer + parent: [ BaseItem, BaseMagicalContraband ] + name: Singularity Hammer + description: The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows. + components: + - type: Wieldable + useDelayOnWield: false + - type: MeleeRequiresWield + - type: Sprite + sprite: Objects/Weapons/Melee/singularityhammer.rsi + layers: + - state: icon + - type: RepulseAttract + speed: -15 #Anything above this pushes things too far away from the Wizard + range: 5 + whitelist: + components: + - MobMover + - Item + - type: UseDelay + delay: 10 + - type: UseDelayOnMeleeHit + - type: MeleeWeapon + wideAnimationRotation: -135 + damage: + types: + Blunt: 5 + Structural: 5 + soundHit: + path: /Audio/Effects/radpulse5.ogg + params: + variation: 0.10 + - type: IncreaseDamageOnWield + damage: + types: + Blunt: 15 + Structural: 15 + - type: Item + size: Ginormous diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml deleted file mode 100644 index 0c75015d9a..0000000000 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml +++ /dev/null @@ -1,25 +0,0 @@ -- type: entity - name: sledgehammer - parent: BaseItem - id: Sledgehammer - description: The perfect tool for wanton carnage. - components: - - type: Sprite - sprite: Objects/Weapons/Melee/sledgehammer.rsi - state: icon - - type: MeleeWeapon - wideAnimationRotation: -135 - damage: - types: - Blunt: 10 - Structural: 10 - soundHit: - collection: MetalThud - - type: Wieldable - - type: IncreaseDamageOnWield - damage: - types: - Blunt: 10 - Structural: 10 - - type: Item - size: Large diff --git a/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..420a46f29a14632f8f2ada2bb1c3d90fdfa1f8e1 GIT binary patch literal 1297 zcmb7Ee>l^59RGY9+lb~zemwGQyB3~UGdsFY_xNsEm(7|VvFghCO?6fYXN7I0POdr; zHNu?CQ%AUd;5hM+q*Afk^c)vrDu&Fjx$o@e{<#19}g3Qnwk3$#K8^NdT;SxEfG_+qw+^7!hO=khBi&ZLW#y<0dgt&VjX|9)?jHUSdomU5MX`8--c zNtvgo!=<%Oz1XfwVg2aVeSwah5d(}z_hQzzx&W2VHMxL+zLqeW1yM=czai76-LH^S>SsH^Tx&1uyVXWwXQbi_ z`|cDF(16tZo-dj6lk5K;xUN8PO`iHlS zdPa_XmYFM&<~-Xg62szKe0m)?kQ-d8j}?yotigZ|)VC<|>SmqL5d zc~LSr;-F6zTy~OIhC~$(+&%Hq`iXl`-LV_1d@~HfdU;i;M;2~sKMkZG%7;N}{MHGJ zhLL6K$|)S3j?i9e-d5L;rvMd!Fdc;^O`_hwNL#FBIQ(5 zohNATn@63MO<=J8N}R>j8Um3V2GjR73B`5EIuQ9`8Tw!F)Ko;bLK^S%5s04}(fxW8&%M3bojD@mcmX6F zFMx#O1(0yO01{3i0psyF#bX$THF6wd0li*N1z)e%)$=HdoR(lDpxtg~!GjD#gf31PQGQ4pjq-WK;x zgdkxWMQV~ne!wMx>$^+K1TjZ&O5hS(YAUQHkVBNd7H};oVqFaBH3Kjm!uTw54sM~W z0uTU_C#24K=LRj!M(}`D0D>P5htz7dC~xqPH5!f7XKlbL070AF74-X&X;wl2FunFo zrR~c>?g-h;C=XJAO}qdSju$|}@d8LVUH}Ql3n1Zm0VEtRz}64#(^J6Nk6=Xrw#u>9 zg{rb?Ii0&7fn5MjT5oS52yAwKsW1M4jeZ1n0q7q{c3x{4{{>>JAAwZ>O6IStHO>12 zqHZTU{=LuOH*EAHunEY5-)z-MtCPRECiE9ztNg!9z*av3n*f~L)lUC_sv^hB+}A&_ z(T~6;K$o`g&#OLyPx1!;)B8F45m*Fp*7eHnM_>_Pqpnw0DV{}utrB?|zvI^eR3VDY z36&C{?mC7Gr%DOXnPaor%sm<^B>*66#M(jR2q@B9P$>aG^dwjWFMx#O1(0yw0UpGA U0m2tiv;Y7A07*qoM6N<$g1e+A)&Kwi literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4128442ba30552fc3763c8a9091d426d599305 GIT binary patch literal 681 zcmV;a0#^NrP)b;?kj|2z~;coMQfgS7J$PFLxJmdG}jd%jN9*sN3z*cs!=sFM->qx5oL&X^h|@VfL|Z%%Zh(>mL*nfZHC@9A_TE^XIMw z50nLP@KwFY;rprVN0bCqJ$RtH8B-Ea_27%3B%p4=gPj6+e-B*T;{)EQ?)(0^0uVg! z?_Ju|Y=ex2gnbWdxdJc{2Vqk)7!1f|cxT%0_jB(MJC^`Ua5eq2EEbD2n-9(arVhdX zcOpgnBBXo~l!O;R!tnw~I9>n=#|t3gcmX6FFMx#O1=yHWr(dA#UIXI-bovD_9UsUxBFx4-R9iPsVNa!WYDGUN@ z^$BeOHcRGdCWRq!dCP_bZ2@JJ%(=#AV{UB$WtGfr{U84T2NhzN@5v@F^1jepw&la)ac-2xl9 zo0b@KOwjL`8sTzolC;|9^0d#JJl@&*mYus>Ip^`4T?XokPYswoEYC0m@fkNvIn1Mg z%6-sXw%fLtJ9C!YMMlLX`VW)VhMne~>eX7cS58(*s+Xudw@S|VU%sw$>n=5w#TPqPU46A_LY}$Bg6tcs z3VtWNN!zS>@A}IUuI!tv3j!~3>M?e5|9fq0x38Xyx$(4;{_)R`Cm(wE;dje6%N_qK zSPbfG*9j){=6E!TJFdPu>*KZOj~=|M5m!-eujgF zF~h&V5Bq-Xxo`jb`L@&f#SdP7n|kcq^?+%t6Rs&r37q12P;N3)FYDh;`{&Ftc2n3R zT=&ctpKz?Ka^2BQ3_>o<*G}@Zu%+2eW7lxj&=)wZaRe;tG?Vee-{;d^Zv#!OX*iw# zi{nQnV`k3u=TSP`3XUcUQzSpcs{qa19$PotxPJS3lWq6)PBVEFv;60NQ=R5;ipis+ xj{Qw`TEg7Y+tU>^+a(=NC9+K@T4DZxKYhWQ$KnYr=Yc7a!PC{xWt~$(695DVHq!tA literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..5ef1c92af24967cc50d8e47b9c7a568524b1dd4e GIT binary patch literal 684 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|Qf> zr;B4q#hkaZHhM8T3bd(D_%7gCd}E1l4RiPEOG{2kM|5$pi#Y{F{ALSZ>RH;lm4k7& z^8{@kr5`L&ovvqO&TX7|=ia*XyxBAPnX0XC-kn)*Uf%fow}Ol)L(5{OhJp|Vfj%yV zLxGGA7FrA(?yL+CS1>FTo8`AW`?Hnjq(N>1<^uL1% z4BbbSBK)G6QX;qYJ>KE->e-iy+QRQUwsM!&uCw?iJ2%#G-t*Lt5eiM$cgSsBIiJO- z_@;Wzy?v4gR5VupeERd;gXQ1*wc^@y&wO9za3@hm#L(5?;G^PicmE&yyj04wWv#Hm z?6V48i;i*LRtXbv{rRvsS^m)P=btU#aL>~}XDipg`n4BB$dQm|PaYJcFORp|uaLXL z^5(H4vzb;#c%2YoxxM9gOSW>&L-XL*{tSl9r?+39>-73pOyQeoHBKM3$*4#&_%svZt#b_;yb|*=+rR&u>jH+$qiev~B(V1DXz#^BaD% zYJ3y-Z|IbK9M_m+oPK$h&9PdBwX6=aYM8e*#mr;4Eg`?Yp}*q}d%|Iu`e%vb{RH&bI*6ry>suJZ>B&VPT>V`3@?CVcmW(kivUO0KNXxle2UJV zSwsWBpP|Elj8NZ^z9zDSPw%=b`0iX21%<;23mFh z)%0-Bwwm}e34q|Yy}F!EKl3hie((adUHjUE-{3?{1z})iS8(#@QSp7*t`4XC4@<$X zgHa}zGw4iQ&Edn45l3*eTf_LL?5+XR@2d=)RnUDqT z-SdfhYUj@+fEfG30I&eXpI(n0225gPv@OhpkUKmN+x*Btp8s}qy)N0q6-3p}l)K6}v0<0$iTyMp4Q@@psEvz{4Q2@buvWRr#0AyO7ol zjX#!I0L}t@FN*Ov3W7K}=|pzExRPGp`UUBR=!HaqDncCpXS=_me$D*lEF>WW4}~f} zj*0jv7fM~T1V9!HP?zWh zvJive`anj~uzwZ z{FNGPiys#UY5u>O1i*EC|Cwlft43}LjmV!`04NXDJ7%;Sxt`A}e<}f(ymi`TbnxsU zaS}FrUnBD;3V^g@@?+)tK z<+KCtnK|ED4fR7(rK~HS&vzqoYKQ_;Bi?7l7g#egn@CoIC_wRm01m9NL ztNib@KPVcX2mS)cv7EmD`r~3F?bVOk>AA0Vh<{X<*(MOj|I(q)bHiW2M$#)Qy8=`- z02^tyI0=y3=zV$T7emlVsj48_Jeq0siX+ebLI7->ecD9PH<#Q~`maFvi*06F-pAHOMF!XFaHZPn*<@PRj;Tv~H|epblAFY%wXZ zm&nlS)xn|-8?^O8@M9iA210}+plpyU*L2|hBm0_o9!i5aek~cmJ@NIN$(RY#ndQXg*uY8J8x5IdG%a|hro;CYCLLO>Rx zQu~dpgdqa9c1;C64sz-F(f)q%#}>aj1fmlF+!2s@M*RWkh3uI{-~q1HXVU_kCQLxi zLI%zQ{sb;nPvhjQ7g+UTE>e%96SOoJ>HP31YLN_mGPL^x*u1R<9N%?LfGR)l=W|&) zpa$P0KW{y-5S)=idyk9GV9g&O%Cft+kn9)%Of|leKY{D>0dxkPS6|imSqRQZ9uchj z19S(xR{pg0`KI{O`XjtQz%jq~2RP>U{s71P-XGwY-}?g`^Lu}QV}9=sh~ej_Upe&$ zxUSE4=}+kGiAwZQC2qwZfPMN6pZwsgJ|FzNZP9z}e&_xK79X8Z_X=7?fgAtI@BwG_ z`S|3GGx>gk3xB}KXn8+>=bn>f8U-jkFx(Z)T6!N{GUrm-NQ3t4^F#iYTkod-W?WEu z-p;x^zp&~L;P%tNpvXMy{(xEG=egs5(r=FH^IL8u`uprZG!pW+g~x33yJ~;gA3#xk zJe(q{*MCoan(rJ{gUo-?+&hfjI!wdw+#fJB_m05y#1n^deg4dR`vY7AUxxPwcvyuO zz%jf4j-f>WR0*Rlru*sHrN>6voJ(*c95=xiEt?}AWo)7Vpc=RkK}K5s%mQ%mQ5%EC z^D5R>eZCxg1VQlk*yC5LKx%D&CIQ%-fU>+*%a=Ow2ZWdJ;(ICh7Ww5`B()MREq^8f zxHdlN9;vAyh@#jrZ^9!E_)F)ZJiodYel2rAl0hNqB(i`-NLoFc;1~6%dyk9ZFA2u{ z0h|Tc&URIwUvFh{R+Q>NLlHjQzRCXk1K>o&B%H>k1L&^L2fu8RO9CcLodsK58J+g` zm^0LZEp6)n1mWeaC#?5ES^T6dM4hH?Ap44cL>__XZ`oXxpG)XvWEKFuP|;8>hd0YLC|Kha#~=SLIp$~+EfA0JQ)|C(=>j@x$Nd)y2;_#7h{ z+Wi5tQGt8E$>6K}5Tc)Wm0a%qCW4RpA>6x`!S@s7jI0!Vz?eTkcY|x?uhd|j{IsVs zzx=R}{wn|9l=26lgoejj?+>sHk@DeX9afw7-u#9Wezafr>$s+zKLAU88x?Dn_;ptd zCJKPG=R(!VQlD=#_|E+SNH^YOa#f%2qEJ}ockU0snCl|#nID}X_x=D|!C%|q-ya~p zqeLB5(ysgguKBI{1JwF>9&=sQ=ey=-e}J?12k&U-9;2Qt_0P^n-IO5+QV3W^RM>~cWz%jf4j-f>W zem)%Tk76~6%`2PF@1M%ZEC2_8?*j)3H>rDjqP-Mb_4!jBnFNrZOt@@UhxPjWMnL8a zNb^8eFG6Kx764C%B}#rGt`{L5nFJUbGbgY^q@Mctyd(Sj1HyS&7NHtYt!AoVYN{gh z5X3f}2)nChLE0A`-S(eo;oj)|0hpZC`golMO^pIA3o-Uep%4}svFn!2 z;_q^OJW91A6U88Tl%Yj{RgW(bwfO3LTyRVtcGCFB@`lX*0IYuHk`*osSI2t<-}P4W zWJG3v0PPunoUd|J$BGYrJdF7R{AOG_OeudrBVx+<0~!@m!XMDcnDp-tXmm{K_Xp$w zllJ`qdBLQ6e?Z<)(I4RA`TcoBtUti^^ZWCRSbqSVA%Kng_`IXwx;{SdDER{zF6#61 mjIuw#VSRp{F=_k(dBy*JnrO|ia$$-90000&fGI||JdDmc4u~fv-3RP@8`EM*UgQ1xkR`C z006J?Rs9>xv6b0okFYcQTb|A;%z@MM>Kz{dfUo0sV@WyBCkg-@Ei~5GxfzhP`pP5v z7F@gw6|2FP_y))LXm2eo@;7*BehR7`~6?=00E3A-H$C%KLt}4h~-N z!jR`v?kAq$XX%$p4MY^3r0t(idv&bC-n_^;Ug=1n+JYLXL2Ch|ehs|i8XWIuI{yP<5p;SayF_6wOSH2S;DN}$L)l4y(iHx3x|T!rH;sdMQ)mr5 zTBa(XbI#6s^=dtJoBYE9i&`@{h;ltwQ2Yc}2FBSyQQxaI+P>IZetyD+^(eTJx0^khhK+!Xx6LkOI7;$zB&cl)X#w*DniW;<^Hk= zI4y0SgfJ0g^O<~7x?(g!8zeV`=rsHY8^H)mDXH}Xt4-5OLE1oyW&B-fUm6ywDW(n5 za=G@X)7$Ke607>U?Sk8V$(J>t7}mDtb{>ileWf~jxQi#>kH?F~+wPLj z;6u^UBfNOT;Ec?eZ>(8+3-LP-zLjy#WiAR&bjW82h33nC1*AKCt0r~(;*UY;mkJLa z(Cn`E4UJIV_Xak-)*{!uc-d8e8pB zR)ZK(NGc3R13uclwaxQu=`+VjDi~meSeb`qH_nY~MlQ->9|Gb*s+SLvx=R%D`c{U! zxMMBizLq+~y6wyd*6z@u^h`rD(;qX;FS4*`GLtqdj}vc%bEJars-e^e%NNn-L*8b3 z#Gjza?C7~BhC>_;D_ncQd}_o7;*!Q_X#;EIdgHueb!aMsQS>jvi;BKLSmdLz0M_PR zrLO?t#oEBl)1%o|=Qh|A71~1G3{uKKi9tg@G8O~N*HyQ2>nsG>jtS0oiwGd&O2F}g z3arY+aMM1O>{M{74$%RQB|M(YLowRnGU7std+d2L6=$!j)aiX&?ZNxbu8x?8d(gXm zRn!{q(mpzfD$qMq1rJCNdwRz-XJkOvM=3cQ)@3V*EWLvVK+@Uo6xIKH9ZQ>EJV(1D z0qQRTQ#kSn!1klV1Aq?IVo?Bg28SB?x%eIK8_2!;h&Axb?MH~);r?tB2GMf|+kNKs6T1azVyD#@peKmaUL+WtGaLq< zZrk*G{s;>Cc+i$t*A(J$$Ob8ZC#=8TiCZdiN0+_9Hrp>dQoQD=mivHwGeH`NXM>%8sm-g;(1?!%M-p=Hf_Ry0RB3iJ`kK9ubsftwq@i#!oFN7dMyo#qe)oGzn>Uaw?H;>J8*8*7Bx zR6=Up!MSmTJOc*b>pzT7goY`aVuHn!>dW!&&Q+?lKP{k;A+cx8vuH6=RRv`8%b}lP zoU)i?UQa13J?LrJw-4iA*gjaFOO*_;{UT_j2MYQ+mbKfdxBf6NecSp>XB0ds(=Q9@ z+LP^M@`Wlib3O$*b-3smVHy;>N_AZl%3QVGj9vv%^GjfEwLXJV&KSH%aRUP&VnbBF zzCS?qyR?m9oKGoP2J+Wg6%>QUjV-MpeVZ7RC>6?WMA*8}lV1S4MP~^?oymD0%lgJn z{%6zf-3J8)uyv2{qhbyHPxv?wZoUHZUNLQF07mefWp(Svd-!Yv4NjUNQW$`haiyg4 z1W@cdNNdix(K@|qJ+Jp`ldgE9+TEaN=?R*giG%De|lEG7r;Z<^Q8HR001l;R`pkOJ7tG62ADFsy95f zvQOc7GZqrCY!e9n0e`5c0DiL{mKjxsZi9@0=<`TLfWJi5q#dW*n*0|f+bg{b+8BVA z&F_Ow?)SQyW-*NiiW?KCQOeYg0YfTkZ2zYPYW&>XMJy~YQrd8jl_xS}=c%h8jZoy_ zT1*QGS)mztgds@nHaofaMJ&hIKcdz$FK*Q7!3+p1 zT^5*@=_1PLhu=P?g<;5Nnv*FdGk&{azMt6q=w-V>dUUgnrRoM!yP%?xOeI={EPPMRi#PGTt#Q z-dAQ(@18Ql^IzR!DAkKKF>S^v}Z43S$SqC^=dqJ zaqHSB)B7-PPS00S&^d*7pQ*-caf*g%Hf2aWjWztEQp+zyva#QXU?vuAGgJJeFSGbp zj^E>9ibe}1MuGh?3V@|`@I9@CT+xcd%C!Dt@xRG~=D9{yUzPu?ZX2fG{!YYMyTJ$w zBBJ(`@QFj+?kOhE*`tr3HU>3!-`BO7M6-N&XL~FYvo4jgHb3)It{9>_$9}~pthZ)b z1|;r8YS-F?Z!S?NuXgiHdw*eS!Tkr?QVf6X=T7-obkv zW%S0sKw7S)X*^})yVocooGoD3OLpn?&frP5KFN?p;#?Imq|dCvbqUmRSzlxv`NIq^ z>_TTr`aJ0uF#})cG@a$%U#rm#Aa=W;&J-l$Sbq5#mg%HTdHH~~hUSOvKY!4NfurMv zeIA}%i0+B95c#$#pCzr+Y=n?+?@Y`%R^aj@Y~Fa{PJ#NtOIM$;Ld)axRVKTSU9Jn2 z0@uv&kShTt#JnXUO)B5}c>EoJmA(D*LmiLeZqy2<>hh}K4=X&jjo>y*x^s_~;w*uK z3ny=JndPu`&iq)~f4$9zpINPdpnDv zxk-gFU%iOJ)f?+m^5I>|k~}fNRqbg85q>6_Y#NoyET%{7uDvZ-ZwJz?8T9Gwt3c_) z>Vmcc`r9P>6!Q|(32SSg-=8gde|BMO>Skspq2i?lxfW)x?|8)wp*E@|6;_=F%JF(_6Oq)2Pp*=6!4KP&~AHvrlNO$v=|pn81-Gd@EUgZ zV{Z|)1XJqH)ZwP3#>J4`+f6w`pd2MW+Xvug3q;dWcw_*0+K(;e)GKT>OGEz!>~tMd zlkZc`U(&>aBBlO{^?wf`{|){B`)c7CRtX>YTKb?jY5$^&2BcnM`@z3|1V+4(T$E>S zV>h*I6-eiV!nGtv%dGnYSNvzi_ZF$Mr>S0i?kUt67)=%3V{J4>D|~+Xp3%60vBzC$ zPg-=dx~Fko+2C)P6ybD@pdGD2ZdEn&De9|4iLg|8(FQlKgFe22>lTqW436%I3VCYr z$BMMZkS;iGa@C;6)#)6LJeWkCi6_B>Mjg3=UbOVk>O@KN8F)Nx$RCQ*JT|gK)ka&l zx}M5lX+((YWu_Z!p3c!JV^QuCaF~<|wCiUh)ySIjT&jRlrpVyq_ zf~_owK8yf!F$OZEz7t*!u4H`8bno=*8ulr%@#Ekj`u9McSQQXkUaj%mP}N4y1qDfHGIC6^U zy!7y-s=M{}?zdV68VO<>Ip_7gQpuT~XOi>4JqkCOW^s}CYaQ}YLwY7uf&QQ>Ar<*P z-*I%aA_q36)y8im-q<;crj1xw;pJId1No_==mchT4gZsHXx9@=DaKZVAv{KZP{|n= zKnSv(5~ynuCT3vw$+&xI~_Xqk{|gKe}Z)V>1}{0qS+XBO;D94;dSj!R({|T#AKZnR zBV&+x?A@`nucC!WYmg0+iKWWKogfdu3m?XDEXzc_HtE)kO~rkq;D}eKf6NCj9s$Bt zB@5hk3(3tt+NJWHADJ0W2jVA9z>rQ1R-FU9CsoGCyL0JP|Dm&#|0AZeb8nSG0GhrM zuc1%X>0+cPF3pIYp)Q`JzLKri7Rg0~3Kt zSH4q|3X90uLG*)h)HZhX5m$)w175UkK$dUa^lC#gz1VM)6RHYFD{>J|w2biy_}T}3 z+eG*J(V2Q!@E(p@rafDW{>jGiQyq(-zw8pU6EeN(-61k-2;QTxv(-mbyyp&oYc8+% z3-?YTF%rC0`Bq~yA!@E`LwWV>)p%f!gxZQaeJoXbF!dc-sm10v6WnSxzQ$R);bA8D zFK(m^Bew(Gxm(Q4dle^c9K3J+X7MkKUK$g-`X7uwL)xv7maB!J$(X~cBEXyyLAm7w zW!>c8uNw9!z;dSa`hUVVT7!P=3^SEt@lkY^-ensRCQ&1X-$4Krv-ZC?@P&gEr1&+J U=r1A6XB>dBfw_LUu3gxF0h5^w0ssI2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..51774aca75e4106aa46d0f5ae8f90098e07fa8eb GIT binary patch literal 4704 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 A=Kufz literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/meta.json new file mode 100644 index 0000000000..395910364c --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/meta.json @@ -0,0 +1,244 @@ +{ + "version": 1, + "license": "CC-BY-NC-SA-3.0", + "copyright": "Taken from and modified by LemonInTheDark on TGStation at commit https://github.com/tgstation/tgstation/commit/a64baadebe20b10c69c1747c42dc51c7377c9c97", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4, + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + }, + { + "name": "wielded-inhand-left", + "directions": 4, + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + }, + { + "name": "inhand-right", + "directions": 4, + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + }, + { + "name": "wielded-inhand-right", + "directions": 4, + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ], + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + }, + { + "name": "icon", + "delays": [ + [ + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..f798047ee2c16e4a1f3d5e62f51a69b530b2ee33 GIT binary patch literal 6297 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 literal 0 HcmV?d00001 -- 2.51.2