diff --git a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs index b25b4fbb7d..28a07ae94a 100644 --- a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs +++ b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs @@ -59,7 +59,6 @@ public sealed class ThrownItemVisualizerSystem : EntitySystem if (length <= TimeSpan.Zero) return null; - length += TimeSpan.FromSeconds(ThrowingSystem.FlyTime); var scale = ent.Comp2.Scale; var lenFloat = (float) length.TotalSeconds; diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index feae130ce8..e2bb991318 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -207,13 +207,13 @@ namespace Content.Server.Hands.Systems var length = direction.Length(); var distance = Math.Clamp(length, minDistance, hands.ThrowRange); - direction *= distance/length; + direction *= distance / length; - var throwStrength = hands.ThrowForceMultiplier; + var throwSpeed = hands.BaseThrowspeed; // Let other systems change the thrown entity (useful for virtual items) // or the throw strength. - var ev = new BeforeThrowEvent(throwEnt, direction, throwStrength, player); + var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player); RaiseLocalEvent(player, ref ev); if (ev.Cancelled) @@ -223,7 +223,7 @@ namespace Content.Server.Hands.Systems if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands)) return false; - _throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowStrength, ev.PlayerUid); + _throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp(ev.ItemUid)); return true; } diff --git a/Content.Server/Lube/LubedSystem.cs b/Content.Server/Lube/LubedSystem.cs index c2d15c8a28..3c536dcceb 100644 --- a/Content.Server/Lube/LubedSystem.cs +++ b/Content.Server/Lube/LubedSystem.cs @@ -43,7 +43,7 @@ public sealed class LubedSystem : EntitySystem var user = args.Container.Owner; _transform.SetCoordinates(uid, Transform(user).Coordinates); _transform.AttachToGridOrMap(uid); - _throwing.TryThrow(uid, _random.NextVector2(), strength: component.SlipStrength); + _throwing.TryThrow(uid, _random.NextVector2(), baseThrowSpeed: component.SlipStrength); _popup.PopupEntity(Loc.GetString("lube-slip", ("target", Identity.Entity(uid, EntityManager))), user, user, PopupType.MediumCaution); } diff --git a/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs b/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs index 3944e94c20..0af4954519 100644 --- a/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs +++ b/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs @@ -37,7 +37,7 @@ public sealed class ContainmentFieldSystem : EntitySystem var fieldDir = Transform(uid).WorldPosition; var playerDir = Transform(otherBody).WorldPosition; - _throwing.TryThrow(otherBody, playerDir-fieldDir, strength: component.ThrowForce); + _throwing.TryThrow(otherBody, playerDir-fieldDir, baseThrowSpeed: component.ThrowForce); } } diff --git a/Content.Shared/Hands/Components/HandsComponent.cs b/Content.Shared/Hands/Components/HandsComponent.cs index 919d55f294..84a389a39c 100644 --- a/Content.Shared/Hands/Components/HandsComponent.cs +++ b/Content.Shared/Hands/Components/HandsComponent.cs @@ -38,11 +38,11 @@ public sealed partial class HandsComponent : Component public bool DisableExplosionRecursion = false; /// - /// The amount of throw impulse per distance the player is from the throw target. + /// Modifies the speed at which items are thrown. /// - [DataField("throwForceMultiplier")] + [DataField] [ViewVariables(VVAccess.ReadWrite)] - public float ThrowForceMultiplier { get; set; } = 10f; //should be tuned so that a thrown item lands about under the player's cursor + public float BaseThrowspeed { get; set; } = 10f; /// /// Distance after which longer throw targets stop increasing throw impulse. diff --git a/Content.Shared/Throwing/BeforeThrowEvent.cs b/Content.Shared/Throwing/BeforeThrowEvent.cs index 36e7dd758b..d2bf2f38b8 100644 --- a/Content.Shared/Throwing/BeforeThrowEvent.cs +++ b/Content.Shared/Throwing/BeforeThrowEvent.cs @@ -5,17 +5,17 @@ namespace Content.Shared.Throwing; [ByRefEvent] public struct BeforeThrowEvent { - public BeforeThrowEvent(EntityUid itemUid, Vector2 direction, float throwStrength, EntityUid playerUid) + public BeforeThrowEvent(EntityUid itemUid, Vector2 direction, float throwSpeed, EntityUid playerUid) { ItemUid = itemUid; Direction = direction; - ThrowStrength = throwStrength; + ThrowSpeed = throwSpeed; PlayerUid = playerUid; } public EntityUid ItemUid { get; set; } public Vector2 Direction { get; } - public float ThrowStrength { get; set;} + public float ThrowSpeed { get; set;} public EntityUid PlayerUid { get; } public bool Cancelled = false; diff --git a/Content.Shared/Throwing/LandAtCursorComponent.cs b/Content.Shared/Throwing/LandAtCursorComponent.cs new file mode 100644 index 0000000000..f8e99e2fc8 --- /dev/null +++ b/Content.Shared/Throwing/LandAtCursorComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Throwing +{ + /// + /// Makes an item land at the cursor when thrown and slide a little further. + /// Without it the item lands slightly in front and stops moving at the cursor. + /// Use this for throwing weapons that should pierce the opponent, for example spears. + /// + [RegisterComponent, NetworkedComponent] + public sealed partial class LandAtCursorComponent : Component { } +} diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index 7d94ada924..56bbf4c2bf 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -1,13 +1,12 @@ using System.Numerics; using Content.Shared.Administration.Logs; using Content.Shared.Camera; +using Content.Shared.CCVar; using Content.Shared.Database; +using Content.Shared.Friction; using Content.Shared.Gravity; -using Content.Shared.Hands.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; using Content.Shared.Projectiles; -using Content.Shared.Tag; +using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -31,7 +30,10 @@ public sealed class ThrowingSystem : EntitySystem /// The minimum amount of time an entity needs to be thrown before the timer can be run. /// Anything below this threshold never enters the air. /// - public const float FlyTime = 0.15f; + public const float MinFlyTime = 0.15f; + public const float FlyTimePercentage = 0.8f; + + private float _frictionModifier; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!; @@ -40,13 +42,23 @@ public sealed class ThrowingSystem : EntitySystem [Dependency] private readonly ThrownItemSystem _thrownSystem = default!; [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; + + public override void Initialize() + { + base.Initialize(); + + Subs.CVar(_configManager, CCVars.TileFrictionModifier, value => _frictionModifier = value, true); + } public void TryThrow( EntityUid uid, EntityCoordinates coordinates, - float strength = 1.0f, + float baseThrowSpeed = 10.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, + float? friction = null, + bool compensateFriction = false, bool recoil = true, bool animated = true, bool playSound = true, @@ -58,7 +70,7 @@ public sealed class ThrowingSystem : EntitySystem if (mapPos.MapId != thrownPos.MapId) return; - TryThrow(uid, mapPos.Position - thrownPos.Position, strength, user, pushbackRatio, 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); } /// @@ -66,14 +78,18 @@ public sealed class ThrowingSystem : EntitySystem /// /// The entity being thrown. /// A vector pointing from the entity to its destination. - /// How much the direction vector should be multiplied for velocity. + /// Throw velocity. Gets modified if compensateFriction is true. /// The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced + /// 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. public void TryThrow(EntityUid uid, Vector2 direction, - float strength = 1.0f, + float baseThrowSpeed = 10.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, + float? friction = null, + bool compensateFriction = false, bool recoil = true, bool animated = true, bool playSound = true, @@ -91,9 +107,10 @@ public sealed class ThrowingSystem : EntitySystem physics, Transform(uid), projectileQuery, - strength, + baseThrowSpeed, user, - pushbackRatio, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); + pushbackRatio, + friction, compensateFriction: compensateFriction, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); } /// @@ -101,23 +118,27 @@ public sealed class ThrowingSystem : EntitySystem /// /// The entity being thrown. /// A vector pointing from the entity to its destination. - /// How much the direction vector should be multiplied for velocity. + /// Throw velocity. Gets modified if compensateFriction is true. /// The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced + /// 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. public void TryThrow(EntityUid uid, Vector2 direction, PhysicsComponent physics, TransformComponent transform, EntityQuery projectileQuery, - float strength = 1.0f, + float baseThrowSpeed = 10.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, + float? friction = null, + bool compensateFriction = false, bool recoil = true, bool animated = true, bool playSound = true, bool doSpin = true) { - if (strength <= 0 || direction == Vector2Helpers.Infinity || direction == Vector2Helpers.NaN || direction == Vector2.Zero) + if (baseThrowSpeed <= 0 || direction == Vector2Helpers.Infinity || direction == Vector2Helpers.NaN || direction == Vector2.Zero || friction < 0) return; if ((physics.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) @@ -136,16 +157,22 @@ public sealed class ThrowingSystem : EntitySystem Animate = animated, }; - // Estimate time to arrival so we can apply OnGround status and slow it much faster. - var time = direction.Length() / strength; + // if not given, get the default friction value for distance calculation + var tileFriction = friction ?? _frictionModifier * TileFrictionController.DefaultFriction; + + if (tileFriction == 0f) + compensateFriction = false; // cannot calculate this if there is no friction + + // Set the time the item is supposed to be in the air so we can apply OnGround status. + // This is a free parameter, but we should set it to something reasonable. + var flyTime = direction.Length() / baseThrowSpeed; + if (compensateFriction) + flyTime *= FlyTimePercentage; + + if (flyTime < MinFlyTime) + flyTime = 0f; comp.ThrownTime = _gameTiming.CurTime; - // TODO: This is a bandaid, don't do this. - // if you want to force landtime have the caller handle it or add a new method. - // did we launch this with something stronger than our hands? - if (TryComp(comp.Thrower, out var hands) && strength > hands.ThrowForceMultiplier) - comp.LandTime = comp.ThrownTime + TimeSpan.FromSeconds(time); - else - comp.LandTime = time < FlyTime ? default : comp.ThrownTime + TimeSpan.FromSeconds(time - FlyTime); + comp.LandTime = comp.ThrownTime + TimeSpan.FromSeconds(flyTime); comp.PlayLandSound = playSound; AddComp(uid, comp, true); @@ -173,7 +200,12 @@ public sealed class ThrowingSystem : EntitySystem if (user != null) _adminLogger.Add(LogType.Throw, LogImpact.Low, $"{ToPrettyString(user.Value):user} threw {ToPrettyString(uid):entity}"); - var impulseVector = direction.Normalized() * strength * physics.Mass; + // if compensateFriction==true compensate for the distance the item will slide over the floor after landing by reducing the throw speed accordingly. + // else let the item land on the cursor and from where it slides a little further. + // This is an exact formula we get from exponentially decaying velocity after landing. + // If someone changes how tile friction works at some point, this will have to be adjusted. + var throwSpeed = compensateFriction ? direction.Length() / (flyTime + 1 / tileFriction) : baseThrowSpeed; + var impulseVector = direction.Normalized() * throwSpeed * physics.Mass; _physics.ApplyLinearImpulse(uid, impulseVector, body: physics); if (comp.LandTime == null || comp.LandTime <= TimeSpan.Zero) diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index 770273fa03..54dc5c3490 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -153,7 +153,7 @@ namespace Content.Shared.Throwing LandComponent(uid, thrown, physics, thrown.PlayLandSound); } - var stopThrowTime = (thrown.LandTime ?? thrown.ThrownTime) + TimeSpan.FromSeconds(ThrowingSystem.FlyTime); + var stopThrowTime = thrown.LandTime ?? thrown.ThrownTime; if (stopThrowTime <= _gameTiming.CurTime) { StopThrow(uid, thrown); diff --git a/Resources/Prototypes/Entities/Objects/Fun/darts.yml b/Resources/Prototypes/Entities/Objects/Fun/darts.yml index c0b5eb3399..dd25d503ce 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/darts.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/darts.yml @@ -10,6 +10,7 @@ offset: 0.0,0.0 - type: ThrowingAngle angle: 315 + - type: LandAtCursor - type: Fixtures fixtures: fix1: diff --git a/Resources/Prototypes/Entities/Objects/Misc/pen.yml b/Resources/Prototypes/Entities/Objects/Misc/pen.yml index 635df230a4..187672ec4d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/pen.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/pen.yml @@ -29,6 +29,7 @@ removalTime: 0.0 - type: ThrowingAngle angle: 315 + - type: LandAtCursor - type: DamageOtherOnHit damage: types: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index f4cc09430a..2a6f314904 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -238,6 +238,7 @@ embedOnThrow: True - type: ThrowingAngle angle: 0 + - type: LandAtCursor - type: Ammo muzzleFlash: null - type: Projectile diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml index 9b6c288e37..14595bd34a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml @@ -31,3 +31,4 @@ removalTime: .2 - type: ThrowingAngle angle: 180 + - type: LandAtCursor diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml index afe4644517..f862a0f10a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml @@ -92,6 +92,7 @@ Slash: 12 - type: EmbeddableProjectile sound: /Audio/Weapons/star_hit.ogg + - type: LandAtCursor - type: DamageOtherOnHit damage: types: @@ -150,6 +151,7 @@ damage: types: Slash: 10 + - type: LandAtCursor - type: Sprite sprite: Clothing/Head/Hats/greyflatcap.rsi - type: Clothing @@ -274,6 +276,7 @@ Slash: 5 - type: EmbeddableProjectile sound: /Audio/Weapons/star_hit.ogg + - type: LandAtCursor - type: DamageOtherOnHit ignoreResistances: true damage: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml index 0def916ddc..ea2e73ac15 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml @@ -8,6 +8,7 @@ offset: 0.15,0.15 - type: ThrowingAngle angle: 225 + - type: LandAtCursor - type: Tag tags: - Spear diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml index ca3edf68c0..fea9a8d5ea 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml @@ -47,4 +47,4 @@ staminaDamage: 55 # Sudden weight increase sapping stamina canThrowTrigger: true canMoveBreakout: true - + - type: LandAtCursor diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml index 35174ba34d..cc55aab237 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml @@ -96,6 +96,7 @@ damage: types: Blunt: 10 + - type: LandAtCursor - type: Damageable damageContainer: Inorganic - type: EmitSoundOnTrigger @@ -224,6 +225,7 @@ damage: types: Blunt: 10 + - type: LandAtCursor - type: EmitSoundOnTrigger sound: path: "/Audio/Effects/flash_bang.ogg" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml index c68feff0b5..0dfc9a0312 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml @@ -31,6 +31,7 @@ friction: 0.2 - type: EmbeddableProjectile sound: /Audio/Weapons/star_hit.ogg + - type: LandAtCursor - type: DamageOtherOnHit damage: types: