2023-07-08 14:08:32 +10:00
using System.Numerics ;
2023-12-04 09:32:17 +03:00
using Content.Shared.Administration.Logs ;
2024-01-30 03:50:41 -07:00
using Content.Shared.Camera ;
2023-12-04 09:32:17 +03:00
using Content.Shared.Database ;
2022-07-18 19:49:52 +10:00
using Content.Shared.Gravity ;
2024-01-12 23:29:19 -08:00
using Content.Shared.Hands.Components ;
using Content.Shared.Hands.EntitySystems ;
2022-03-24 02:33:01 +13:00
using Content.Shared.Interaction ;
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 ;
2023-10-06 17:43:54 -07:00
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
2022-07-18 19:49:52 +10:00
[Dependency] private readonly SharedGravitySystem _gravity = 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 ! ;
2024-01-30 03:50:41 -07:00
[Dependency] private readonly SharedCameraRecoilSystem _recoil = default ! ;
2023-12-04 09:32:17 +03:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2022-03-24 02:33:01 +13:00
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 )
{
2024-02-28 00:51:20 +11:00
var thrownPos = Transform ( uid ) . MapPosition ;
2024-02-03 10:32:30 -08:00
var mapPos = coordinates . ToMap ( EntityManager , _transform ) ;
2023-05-19 17:10:31 +10:00
if ( mapPos . MapId ! = thrownPos . MapId )
return ;
2024-02-03 10:32:30 -08:00
TryThrow ( uid , mapPos . Position - thrownPos . Position , strength , user , pushbackRatio , playSound ) ;
2023-05-19 17:10:31 +10:00
}
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 > ( ) ;
2024-02-03 10:32:30 -08:00
var tagQuery = GetEntityQuery < TagComponent > ( ) ;
2023-05-07 14:57:23 +12:00
TryThrow (
uid ,
direction ,
physics ,
Transform ( uid ) ,
projectileQuery ,
strength ,
user ,
2024-02-03 10:32:30 -08: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-09-22 02:45:21 -07:00
// Allow throwing if this projectile only acts as a projectile when shot, otherwise disallow
if ( projectileQuery . TryGetComponent ( uid , out var proj ) & & ! proj . OnlyCollideWhenShot )
2023-05-07 14:57:23 +12:00
return ;
2024-02-03 10:32:30 -08:00
var comp = new ThrownItemComponent ( ) ;
comp . Thrower = user ;
2023-10-06 17:43:54 -07:00
// Estimate time to arrival so we can apply OnGround status and slow it much faster.
var time = direction . Length ( ) / strength ;
comp . ThrownTime = _gameTiming . CurTime ;
2024-01-12 23:29:19 -08:00
// did we launch this with something stronger than our hands?
if ( TryComp < HandsComponent > ( 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 ) ;
2023-10-06 17:43:54 -07:00
comp . PlayLandSound = playSound ;
2024-01-30 03:50:41 -07:00
AddComp ( uid , comp , true ) ;
2023-10-06 17:43:54 -07:00
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
2023-12-04 09:32:17 +03:00
var throwEvent = new ThrownEvent ( user , uid ) ;
RaiseLocalEvent ( uid , ref throwEvent , true ) ;
2022-03-24 02:33:01 +13:00
if ( user ! = null )
2023-12-04 09:32:17 +03:00
_adminLogger . Add ( LogType . Throw , LogImpact . Low , $"{ToPrettyString(user.Value):user} threw {ToPrettyString(uid):entity}" ) ;
2022-03-24 02:33:01 +13:00
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
2023-12-27 18:05:20 -05:00
if ( comp . LandTime = = null | | comp . LandTime < = TimeSpan . Zero )
2022-03-24 02:33:01 +13:00
{
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
}
2024-01-30 03:50:41 -07:00
if ( user = = null )
return ;
2024-02-03 10:32:30 -08:00
_recoil . KickCamera ( user . Value , - direction * 0.04f ) ;
2024-01-30 03:50:41 -07:00
2022-03-24 02:33:01 +13:00
// Give thrower an impulse in the other direction
2024-01-30 03:50:41 -07:00
if ( pushbackRatio ! = 0.0f & &
2023-05-13 09:54:37 +10:00
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
}
}
}