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. + /// How long should this stun the target, if applicable? /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public bool Enabled = true; + [DataField, AutoNetworkedField] + public TimeSpan? StunTime; + + /// + /// Should this also work on a throw-hit? + /// + [DataField, AutoNetworkedField] + public bool ActivateOnThrown; } /// -/// Component used to track entities that have been yeeted by +/// Raised a weapon entity with to see if a throw is allowed. /// -[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. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public float Lifetime; - - /// - /// How long we wait to start accepting collision. - /// - [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; -} +[ByRefEvent] +public record struct AttemptMeleeThrowOnHitEvent(EntityUid Target, EntityUid? User, bool Cancelled = false, bool Handled = false); /// -/// 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 target entity before it is thrown by . /// [ByRefEvent] -public record struct AttemptMeleeThrowOnHitEvent(EntityUid Hit, bool Cancelled = false, bool Handled = false); - -[ByRefEvent] -public record struct MeleeThrowOnHitStartEvent(EntityUid User, EntityUid Used); - -[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) + if (_delay.IsDelayed(weapon.Owner)) + return; + + if (args.HitEntities.Count == 0) + return; + + var userPos = _transform.GetWorldPosition(args.User); + foreach (var target 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); + var targetPos = _transform.GetMapCoordinates(target).Position; + var direction = args.Direction ?? targetPos - userPos; + ThrowOnHitHelper(weapon, args.User, target, direction); } } - private void OnThrownStartup(Entity ent, ref ComponentStartup args) + private void OnThrowHit(Entity weapon, ref ThrowDoHitEvent args) { - var (_, comp) = ent; - - if (!TryComp(ent, out var body) || - (body.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) + if (!weapon.Comp.ActivateOnThrown) 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); - } - - 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); - } - - private void OnStartCollide(Entity ent, ref StartCollideEvent args) - { - var (_, comp) = ent; - if (!args.OtherFixture.Hard || !args.OtherBody.CanCollide || !args.OurFixture.Hard || !args.OurBody.CanCollide) + if (!TryComp(args.Thrown, out var weaponPhysics)) return; - if (_timing.CurTime < comp.MinLifetimeTime) + ThrowOnHitHelper(weapon, args.Component.Thrower, args.Target, weaponPhysics.LinearVelocity); + } + + private void ThrowOnHitHelper(Entity ent, EntityUid? user, EntityUid target, Vector2 direction) + { + var attemptEvent = new AttemptMeleeThrowOnHitEvent(target, user); + RaiseLocalEvent(ent.Owner, ref attemptEvent); + + if (attemptEvent.Cancelled) return; - RemCompDeferred(ent, ent.Comp); - } + var startEvent = new MeleeThrowOnHitStartEvent(ent.Owner, user); + RaiseLocalEvent(target, ref startEvent); - public bool CanThrowOnHit(Entity ent, EntityUid target) - { - var (uid, comp) = ent; + if (ent.Comp.StunTime != null) + _stun.TryParalyze(target, ent.Comp.StunTime.Value, false); - var ev = new AttemptMeleeThrowOnHitEvent(target); - RaiseLocalEvent(uid, ref ev); + if (direction == Vector2.Zero) + return; - if (ev.Handled) - return !ev.Cancelled; - - return comp.Enabled; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - 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 0000000000..420a46f29a Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-left.png new file mode 100644 index 0000000000..2b5216cc45 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-left.png differ 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 0000000000..3c4128442b Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/meta.json new file mode 100644 index 0000000000..d304313523 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/meta.json @@ -0,0 +1,42 @@ +{ + "version": 1, + "license": "CC-BY-NC-SA-3.0", + "copyright": "Taken from and modified by ProtivogaSpriter on TGStation at commit https://github.com/tgstation/tgstation/commit/2614518661bfac1dcc96f07cfcc70b4abacd27bf", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "icon", + "delays": [ + [ + 2, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..499a827bc9 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-left.png differ 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 0000000000..5ef1c92af2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/mjollnir.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/icon.png new file mode 100644 index 0000000000..1c3a04bce1 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-left.png new file mode 100644 index 0000000000..f385980712 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-left.png differ 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 0000000000..51774aca75 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/inhand-right.png differ 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 0000000000..f798047ee2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..cec47b089d Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/singularityhammer.rsi/wielded-inhand-right.png differ