2023-07-08 14:08:32 +10:00
using System.Numerics ;
2022-07-18 19:49:52 +10:00
using Content.Shared.Gravity ;
2022-03-24 02:33:01 +13:00
using Content.Shared.Interaction ;
2022-06-23 12:13:22 +10:00
using Content.Shared.Movement.Components ;
2023-05-07 14:57:23 +12:00
using Content.Shared.Projectiles ;
2022-03-24 02:33:01 +13:00
using Content.Shared.Tag ;
2023-05-19 17:10:31 +10:00
using Robust.Shared.Map ;
2022-03-24 02:33:01 +13:00
using Robust.Shared.Physics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Components ;
2022-12-28 03:58:53 +11:00
using Robust.Shared.Physics.Systems ;
2022-03-24 02:33:01 +13:00
using Robust.Shared.Timing ;
namespace Content.Shared.Throwing ;
public sealed class ThrowingSystem : EntitySystem
{
2023-05-13 09:54:37 +10:00
public const float ThrowAngularImpulse = 5f ;
2023-05-29 17:11:54 +10:00
public const float PushbackDefault = 2f ;
2022-03-24 02:33:01 +13:00
/// <summary>
/// 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.
/// </summary>
public const float FlyTime = 0.15f ;
2022-07-18 19:49:52 +10:00
[Dependency] private readonly SharedGravitySystem _gravity = default ! ;
2022-03-24 02:33:01 +13:00
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
2022-12-28 03:58:53 +11:00
[Dependency] private readonly SharedPhysicsSystem _physics = default ! ;
2023-05-19 17:10:31 +10:00
[Dependency] private readonly SharedTransformSystem _transform = default ! ;
2022-03-24 02:33:01 +13:00
[Dependency] private readonly ThrownItemSystem _thrownSystem = default ! ;
[Dependency] private readonly TagSystem _tagSystem = default ! ;
2023-05-19 17:10:31 +10:00
public void TryThrow (
EntityUid uid ,
EntityCoordinates coordinates ,
float strength = 1.0f ,
EntityUid ? user = null ,
float pushbackRatio = PushbackDefault ,
bool playSound = true )
{
var thrownPos = Transform ( uid ) . MapPosition ;
var mapPos = coordinates . ToMap ( EntityManager , _transform ) ;
if ( mapPos . MapId ! = thrownPos . MapId )
return ;
TryThrow ( uid , mapPos . Position - thrownPos . Position , strength , user , pushbackRatio , playSound ) ;
}
2022-03-24 02:33:01 +13:00
/// <summary>
/// Tries to throw the entity if it has a physics component, otherwise does nothing.
/// </summary>
2022-07-18 19:49:52 +10:00
/// <param name="uid">The entity being thrown.</param>
2022-03-24 02:33:01 +13:00
/// <param name="direction">A vector pointing from the entity to its destination.</param>
/// <param name="strength">How much the direction vector should be multiplied for velocity.</param>
/// <param name="pushbackRatio">The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced</param>
2023-05-07 14:57:23 +12:00
public void TryThrow ( EntityUid uid ,
2022-03-24 02:33:01 +13:00
Vector2 direction ,
float strength = 1.0f ,
EntityUid ? user = null ,
2023-05-19 17:10:31 +10:00
float pushbackRatio = PushbackDefault ,
bool playSound = true )
2022-03-24 02:33:01 +13:00
{
2023-05-07 14:57:23 +12:00
var physicsQuery = GetEntityQuery < PhysicsComponent > ( ) ;
if ( ! physicsQuery . TryGetComponent ( uid , out var physics ) )
2022-03-24 02:33:01 +13:00
return ;
2023-05-07 14:57:23 +12:00
var projectileQuery = GetEntityQuery < ProjectileComponent > ( ) ;
var tagQuery = GetEntityQuery < TagComponent > ( ) ;
TryThrow (
uid ,
direction ,
physics ,
Transform ( uid ) ,
projectileQuery ,
strength ,
user ,
2023-05-19 17:10:31 +10:00
pushbackRatio ,
playSound ) ;
2023-05-07 14:57:23 +12:00
}
/// <summary>
/// Tries to throw the entity if it has a physics component, otherwise does nothing.
/// </summary>
/// <param name="uid">The entity being thrown.</param>
/// <param name="direction">A vector pointing from the entity to its destination.</param>
/// <param name="strength">How much the direction vector should be multiplied for velocity.</param>
/// <param name="pushbackRatio">The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced</param>
public void TryThrow ( EntityUid uid ,
Vector2 direction ,
PhysicsComponent physics ,
TransformComponent transform ,
EntityQuery < ProjectileComponent > projectileQuery ,
float strength = 1.0f ,
EntityUid ? user = null ,
2023-05-19 17:10:31 +10:00
float pushbackRatio = PushbackDefault ,
bool playSound = true )
2023-05-07 14:57:23 +12:00
{
2023-07-08 14:08:32 +10:00
if ( strength < = 0 | | direction = = Vector2Helpers . Infinity | | direction = = Vector2Helpers . NaN | | direction = = Vector2 . Zero )
2022-03-24 02:33:01 +13:00
return ;
2022-07-16 13:04:14 +10:00
if ( ( physics . BodyType & ( BodyType . Dynamic | BodyType . KinematicController ) ) = = 0x0 )
2022-03-24 02:33:01 +13:00
{
2023-08-04 18:56:39 +10:00
Log . Warning ( $"Tried to throw entity {ToPrettyString(uid)} but can't throw {physics.BodyType} bodies!" ) ;
2022-03-24 02:33:01 +13:00
return ;
}
2023-05-07 14:57:23 +12:00
if ( projectileQuery . HasComponent ( uid ) )
return ;
2022-03-24 02:33:01 +13:00
var comp = EnsureComp < ThrownItemComponent > ( uid ) ;
comp . Thrower = user ;
2023-08-04 18:56:39 +10:00
ThrowingAngleComponent ? throwingAngle = null ;
2023-05-07 14:57:23 +12:00
2022-03-24 02:33:01 +13:00
// Give it a l'il spin.
2023-08-04 18:56:39 +10:00
if ( physics . InvI > 0f & & ( ! TryComp ( uid , out throwingAngle ) | | throwingAngle . AngularVelocity ) )
{
2023-05-13 09:54:37 +10:00
_physics . ApplyAngularImpulse ( uid , ThrowAngularImpulse / physics . InvI , body : physics ) ;
2023-08-04 18:56:39 +10:00
}
2022-03-24 02:33:01 +13:00
else
2023-08-04 18:56:39 +10:00
{
Resolve ( uid , ref throwingAngle , false ) ;
var gridRot = _transform . GetWorldRotation ( transform . ParentUid ) ;
var angle = direction . ToWorldAngle ( ) - gridRot ;
var offset = throwingAngle ? . Angle ? ? Angle . Zero ;
_transform . SetLocalRotation ( uid , angle + offset ) ;
}
2022-03-24 02:33:01 +13:00
if ( user ! = null )
_interactionSystem . ThrownInteraction ( user . Value , uid ) ;
2023-07-08 14:08:32 +10:00
var impulseVector = direction . Normalized ( ) * strength * physics . Mass ;
2023-01-15 15:38:59 +11:00
_physics . ApplyLinearImpulse ( uid , impulseVector , body : physics ) ;
2022-03-24 02:33:01 +13:00
// Estimate time to arrival so we can apply OnGround status and slow it much faster.
2023-07-08 14:08:32 +10:00
var time = direction . Length ( ) / strength ;
2022-03-24 02:33:01 +13:00
if ( time < FlyTime )
{
2023-05-19 17:10:31 +10:00
_thrownSystem . LandComponent ( uid , comp , physics , playSound ) ;
2022-03-24 02:33:01 +13:00
}
else
{
2022-12-28 03:58:53 +11:00
_physics . SetBodyStatus ( physics , BodyStatus . InAir ) ;
2022-03-24 02:33:01 +13:00
Timer . Spawn ( TimeSpan . FromSeconds ( time - FlyTime ) , ( ) = >
{
2022-12-28 03:58:53 +11:00
if ( physics . Deleted )
return ;
2023-05-19 17:10:31 +10:00
_thrownSystem . LandComponent ( uid , comp , physics , playSound ) ;
2022-03-24 02:33:01 +13:00
} ) ;
}
// Give thrower an impulse in the other direction
2022-06-23 12:13:22 +10:00
if ( user ! = null & &
2023-05-13 09:54:37 +10:00
pushbackRatio ! = 0.0f & &
physics . Mass > 0f & &
2023-05-07 14:57:23 +12:00
TryComp ( user . Value , out PhysicsComponent ? userPhysics ) & &
2022-07-18 19:49:52 +10:00
_gravity . IsWeightless ( user . Value , userPhysics ) )
2022-03-24 02:33:01 +13:00
{
var msg = new ThrowPushbackAttemptEvent ( ) ;
2023-03-11 19:26:01 +11:00
RaiseLocalEvent ( uid , msg ) ;
2023-05-29 17:11:54 +10:00
const float MassLimit = 5f ;
2022-03-24 02:33:01 +13:00
if ( ! msg . Cancelled )
2023-05-29 17:11:54 +10:00
_physics . ApplyLinearImpulse ( user . Value , - impulseVector / physics . Mass * pushbackRatio * MathF . Min ( MassLimit , physics . Mass ) , body : userPhysics ) ;
2022-03-24 02:33:01 +13:00
}
}
}