var (uid, comp) = ent;
// don't waste charges on non-anchorable non-anomalous static bodies.
- if (!HasComp<AnomalyComponent>(args.Hit)
- && !HasComp<AnchorableComponent>(args.Hit)
- && TryComp<PhysicsComponent>(args.Hit, out var body)
+ if (!HasComp<AnomalyComponent>(args.Target)
+ && !HasComp<AnchorableComponent>(args.Target)
+ && TryComp<PhysicsComponent>(args.Target, out var body)
&& body.BodyType == BodyType.Static)
return;
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;
base.Initialize();
SubscribeLocalEvent<AnomalyComponent, MeleeThrowOnHitStartEvent>(OnAnomalyThrowStart);
- SubscribeLocalEvent<AnomalyComponent, MeleeThrowOnHitEndEvent>(OnAnomalyThrowEnd);
+ SubscribeLocalEvent<AnomalyComponent, LandEvent>(OnLand);
}
private void OnAnomalyThrowStart(Entity<AnomalyComponent> ent, ref MeleeThrowOnHitStartEvent args)
{
- if (!TryComp<CorePoweredThrowerComponent>(args.Used, out var corePowered) || !TryComp<PhysicsComponent>(ent, out var body))
+ if (!TryComp<CorePoweredThrowerComponent>(args.Weapon, out var corePowered) || !TryComp<PhysicsComponent>(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<AnomalyComponent> ent, ref MeleeThrowOnHitEndEvent args)
+ private void OnLand(Entity<AnomalyComponent> ent, ref LandEvent args)
{
+ // revert back to static
_physics.SetBodyType(ent, BodyType.Static);
}
--- /dev/null
+using Content.Shared.Physics;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.RepulseAttract;
+
+/// <summary>
+/// Used to repulse or attract entities away from the entity this is on
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(RepulseAttractSystem))]
+public sealed partial class RepulseAttractComponent : Component
+{
+ /// <summary>
+ /// How fast should the Repulsion/Attraction be?
+ /// A positive value will repulse objects, a negative value will attract
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Speed = 5.0f;
+
+ /// <summary>
+ /// How close do the entities need to be?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Range = 5.0f;
+
+ /// <summary>
+ /// What kind of entities should this effect apply to?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntityWhitelist? Whitelist;
+
+ /// <summary>
+ /// What collision layers should be excluded?
+ /// The default excludes ghost mobs, revenants, the AI camera etc.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public CollisionGroup CollisionMask = CollisionGroup.GhostImpassable;
+}
--- /dev/null
+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<PhysicsComponent> _physicsQuery;
+ private HashSet<EntityUid> _entSet = new();
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _physicsQuery = GetEntityQuery<PhysicsComponent>();
+
+ SubscribeLocalEvent<RepulseAttractComponent, MeleeHitEvent>(OnMeleeAttempt, before: [typeof(UseDelayOnMeleeHitSystem)], after: [typeof(SharedWieldableSystem)]);
+ }
+ private void OnMeleeAttempt(Entity<RepulseAttractComponent> ent, ref MeleeHitEvent args)
+ {
+ if (_delay.IsDelayed(ent.Owner))
+ return;
+
+ TryRepulseAttract(ent, args.User);
+ }
+
+ public bool TryRepulseAttract(Entity<RepulseAttractComponent> 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;
+ }
+}
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;
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);
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);
}
/// <summary>
/// <param name="friction">friction value used for the distance calculation. If set to null this defaults to the standard tile values</param>
/// <param name="compensateFriction">True will adjust the throw so the item stops at the target coordinates. False means it will land at the target and keep sliding.</param>
/// <param name="doSpin">Whether spin will be applied to the thrown entity.</param>
+ /// <param name="unanchor">If true and the thrown entity has <see cref="AnchorableComponent"/>, unanchor the thrown entity</param>
public void TryThrow(EntityUid uid,
Vector2 direction,
float baseThrowSpeed = 10.0f,
bool recoil = true,
bool animated = true,
bool playSound = true,
- bool doSpin = true)
+ bool doSpin = true,
+ bool unanchor = false)
{
var physicsQuery = GetEntityQuery<PhysicsComponent>();
if (!physicsQuery.TryGetComponent(uid, out var physics))
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);
}
/// <summary>
/// <param name="friction">friction value used for the distance calculation. If set to null this defaults to the standard tile values</param>
/// <param name="compensateFriction">True will adjust the throw so the item stops at the target coordinates. False means it will land at the target and keep sliding.</param>
/// <param name="doSpin">Whether spin will be applied to the thrown entity.</param>
+ /// <param name="unanchor">If true and the thrown entity has <see cref="AnchorableComponent"/>, unanchor the thrown entity</param>
public void TryThrow(EntityUid uid,
Vector2 direction,
PhysicsComponent physics,
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<AnchorableComponent>(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)
/// <summary>
/// Returns true if the entity has a currently active UseDelay with the specified ID.
/// </summary>
- public bool IsDelayed(Entity<UseDelayComponent> ent, string id = DefaultId)
+ public bool IsDelayed(Entity<UseDelayComponent?> ent, string id = DefaultId)
{
+ if (!Resolve(ent, ref ent.Comp, false))
+ return false;
+
if (!ent.Comp.Delays.TryGetValue(id, out var entry))
return false;
/// Otherwise reset it and return true.</param>
public bool TryResetDelay(Entity<UseDelayComponent> 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))
/// 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.
/// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(MeleeThrowOnHitSystem))]
-[AutoGenerateComponentState]
public sealed partial class MeleeThrowOnHitComponent : Component
{
/// <summary>
/// The speed at which hit entities should be thrown.
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public float Speed = 10f;
/// <summary>
- /// How long hit entities remain thrown, max.
+ /// The maximum distance the hit entity should be thrown.
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
- public float Lifetime = 3f;
-
- /// <summary>
- /// How long we wait to start accepting collision.
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float MinLifetime = 0.05f;
+ [DataField, AutoNetworkedField]
+ public float Distance = 20f;
/// <summary>
/// Whether or not anchorable entities should be unanchored when hit.
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public bool UnanchorOnHit;
/// <summary>
- /// Whether or not the throwing behavior occurs by default.
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
- public bool Enabled = true;
-}
-
-/// <summary>
-/// Component used to track entities that have been yeeted by <see cref="MeleeThrowOnHitComponent"/>
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentState]
-[Access(typeof(MeleeThrowOnHitSystem))]
-public sealed partial class MeleeThrownComponent : Component
-{
- /// <summary>
- /// The velocity of the throw
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
- public Vector2 Velocity;
-
- /// <summary>
- /// How long the throw will last.
+ /// How long should this stun the target, if applicable?
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
- public float Lifetime;
+ [DataField, AutoNetworkedField]
+ public TimeSpan? StunTime;
/// <summary>
- /// How long we wait to start accepting collision.
+ /// Should this also work on a throw-hit?
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float MinLifetime;
-
- /// <summary>
- /// At what point in time will the throw be complete?
- /// </summary>
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoNetworkedField]
- public TimeSpan ThrownEndTime;
-
- /// <summary>
- /// At what point in time will the <see cref="MinLifetime"/> be exhausted
- /// </summary>
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoNetworkedField]
- public TimeSpan MinLifetimeTime;
-
- /// <summary>
- /// the status to which the entity will return when the thrown ends
- /// </summary>
- [DataField]
- public BodyStatus PreviousStatus;
+ [DataField, AutoNetworkedField]
+ public bool ActivateOnThrown;
}
/// <summary>
-/// Event raised before an entity is thrown by <see cref="MeleeThrowOnHitComponent"/> 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 <see cref="MeleeThrowOnHitComponent"/> to see if a throw is allowed.
/// </summary>
[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);
+/// <summary>
+/// Raised a target entity before it is thrown by <see cref="MeleeThrowOnHitComponent"/>.
+/// </summary>
[ByRefEvent]
-public record struct MeleeThrowOnHitEndEvent();
+public record struct MeleeThrowOnHitStartEvent(EntityUid Weapon, EntityUid? User);
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Weapons.Melee.Components;
+
+/// <summary>
+/// Activates UseDelay when a Melee Weapon is used to hit something.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(UseDelayOnMeleeHitSystem))]
+public sealed partial class UseDelayOnMeleeHitComponent : Component
+{
+
+}
-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;
/// </summary>
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!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<MeleeThrowOnHitComponent, MeleeHitEvent>(OnMeleeHit);
- SubscribeLocalEvent<MeleeThrownComponent, ComponentStartup>(OnThrownStartup);
- SubscribeLocalEvent<MeleeThrownComponent, ComponentShutdown>(OnThrownShutdown);
- SubscribeLocalEvent<MeleeThrownComponent, StartCollideEvent>(OnStartCollide);
+ SubscribeLocalEvent<MeleeThrowOnHitComponent, ThrowDoHitEvent>(OnThrowHit);
}
- private void OnMeleeHit(Entity<MeleeThrowOnHitComponent> ent, ref MeleeHitEvent args)
+ private void OnMeleeHit(Entity<MeleeThrowOnHitComponent> 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<AnchorableComponent>(hit))
- {
- _transform.Unanchor(hit, Transform(hit));
- }
-
- RemComp<MeleeThrownComponent>(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<MeleeThrownComponent> ent, ref ComponentStartup args)
- {
- var (_, comp) = ent;
-
- if (!TryComp<PhysicsComponent>(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<MeleeThrownComponent> ent, ref ComponentShutdown args)
- {
- if (TryComp<PhysicsComponent>(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<MeleeThrownComponent> ent, ref StartCollideEvent args)
+ private void OnThrowHit(Entity<MeleeThrowOnHitComponent> 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<PhysicsComponent>(args.Thrown, out var weaponPhysics))
return;
- RemCompDeferred(ent, ent.Comp);
+ ThrowOnHitHelper(weapon, args.Component.Thrower, args.Target, weaponPhysics.LinearVelocity);
}
- public bool CanThrowOnHit(Entity<MeleeThrowOnHitComponent> ent, EntityUid target)
+ private void ThrowOnHitHelper(Entity<MeleeThrowOnHitComponent> 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<MeleeThrownComponent>();
- 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);
}
}
--- /dev/null
+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;
+
+/// <inheritdoc cref="UseDelayOnMeleeHitComponent"/>
+public sealed class UseDelayOnMeleeHitSystem : EntitySystem
+{
+ [Dependency] private readonly UseDelaySystem _delay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<UseDelayOnMeleeHitComponent, MeleeHitEvent>(OnMeleeHit);
+ SubscribeLocalEvent<UseDelayOnMeleeHitComponent, ThrowDoHitEvent>(OnThrowHitEvent);
+ }
+
+ private void OnThrowHitEvent(Entity<UseDelayOnMeleeHitComponent> ent, ref ThrowDoHitEvent args)
+ {
+ TryResetDelay(ent);
+ }
+
+ private void OnMeleeHit(Entity<UseDelayOnMeleeHitComponent> ent, ref MeleeHitEvent args)
+ {
+ TryResetDelay(ent);
+ }
+
+ private void TryResetDelay(Entity<UseDelayOnMeleeHitComponent> ent)
+ {
+ var uid = ent.Owner;
+
+ if (!TryComp<UseDelayComponent>(uid, out var useDelay))
+ return;
+
+ _delay.TryResetDelay((uid, useDelay), checkDelayed: true);
+ }
+}
/// <summary>
/// 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.
/// </summary>
[DataField]
public bool UnwieldOnUse = true;
+ /// <summary>
+ /// Should use delay trigger after the wield/unwield?
+ /// </summary>
+ [DataField]
+ public bool UseDelayOnWield = true;
+
[DataField("wieldedInhandPrefix")]
public string? WieldedInhandPrefix = "wielded";
args.Handled = TryWield(uid, component, args.User);
else if (component.UnwieldOnUse)
args.Handled = TryUnwield(uid, component, args.User);
+
+ if (HasComp<UseDelayComponent>(uid) && !component.UseDelayOnWield)
+ args.ApplyDelay = false;
}
public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet = false)
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);
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!
- !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
- type: CorePoweredThrower
- type: MeleeThrowOnHit
unanchorOnHit: true
- enabled: false
- type: ItemSlots
slots:
core_slot:
--- /dev/null
+- 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
+++ /dev/null
-- 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
--- /dev/null
+{
+ "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
+ ]
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "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
+ ]
+ ]
+ }
+ ]
+}