2020-10-16 20:35:09 +02:00
#nullable enable
using System ;
2020-11-09 20:22:19 -08:00
using Content.Shared.Alert ;
2020-10-16 20:35:09 +02:00
using Content.Shared.GameObjects.Components.Mobs ;
2021-03-08 12:10:48 +11:00
using Content.Shared.GameObjects.Components.Movement ;
2021-04-05 14:08:45 +02:00
using Content.Shared.GameObjects.EntitySystemMessages.Pulling ;
2021-03-08 12:10:48 +11:00
using Content.Shared.GameObjects.EntitySystems.ActionBlocker ;
2020-10-28 10:12:46 +01:00
using Content.Shared.Physics ;
2020-10-16 20:35:09 +02:00
using Content.Shared.Physics.Pull ;
using Robust.Shared.Containers ;
using Robust.Shared.GameObjects ;
2020-11-19 00:37:16 +11:00
using Robust.Shared.Log ;
2020-10-16 20:35:09 +02:00
using Robust.Shared.Map ;
2020-10-28 10:12:46 +01:00
using Robust.Shared.Physics ;
2021-03-08 04:09:59 +11:00
using Robust.Shared.Physics.Dynamics.Joints ;
2021-04-05 14:08:45 +02:00
using Robust.Shared.Players ;
2020-10-16 20:35:09 +02:00
using Robust.Shared.Serialization ;
namespace Content.Shared.GameObjects.Components.Pulling
{
2021-03-08 12:10:48 +11:00
public abstract class SharedPullableComponent : Component , ICollideSpecial , IRelayMoveInput
2020-10-16 20:35:09 +02:00
{
public override string Name = > "Pullable" ;
public override uint? NetID = > ContentNetIDs . PULLABLE ;
2021-03-08 04:09:59 +11:00
[ComponentDependency] private readonly PhysicsComponent ? _physics = default ! ;
2020-10-28 10:12:46 +01:00
2020-11-26 13:48:10 +00:00
/// <summary>
/// Only set in Puller->set! Only set in unison with _pullerPhysics!
/// </summary>
2020-10-16 20:35:09 +02:00
private IEntity ? _puller ;
2021-04-05 14:08:45 +02:00
public IPhysBody ? PullerPhysics { get ; private set ; }
2021-03-08 04:09:59 +11:00
private DistanceJoint ? _pullJoint ;
2020-10-16 20:35:09 +02:00
2021-04-05 14:08:45 +02:00
public float? MaxDistance = > _pullJoint ? . MaxLength ;
private MapCoordinates ? _movingTo ;
2020-11-26 13:48:10 +00:00
/// <summary>
/// The current entity pulling this component.
/// Setting this performs the entire setup process for pulling.
/// </summary>
2020-10-16 20:35:09 +02:00
public virtual IEntity ? Puller
{
get = > _puller ;
2020-11-26 13:48:10 +00:00
set
2020-10-16 20:35:09 +02:00
{
if ( _puller = = value )
{
return ;
}
2020-11-26 13:48:10 +00:00
// New value. Abandon being pulled by any existing object.
if ( _puller ! = null )
{
var oldPuller = _puller ;
2021-04-05 14:08:45 +02:00
var oldPullerPhysics = PullerPhysics ;
2020-11-26 13:48:10 +00:00
_puller = null ;
2021-02-16 02:52:25 +01:00
Dirty ( ) ;
2021-04-05 14:08:45 +02:00
PullerPhysics = null ;
2020-11-26 13:48:10 +00:00
2021-02-27 04:12:09 +01:00
if ( _physics ! = null & & oldPullerPhysics ! = null )
2020-11-26 13:48:10 +00:00
{
var message = new PullStoppedMessage ( oldPullerPhysics , _physics ) ;
oldPuller . SendMessage ( null , message ) ;
Owner . SendMessage ( null , message ) ;
oldPuller . EntityManager . EventBus . RaiseEvent ( EventSource . Local , message ) ;
_physics . WakeBody ( ) ;
}
// else-branch warning is handled below
}
2020-10-16 20:35:09 +02:00
2020-11-26 13:48:10 +00:00
// Now that is settled, prepare to be pulled by a new object.
2020-11-02 11:58:47 +01:00
if ( _physics = = null )
2020-10-16 20:35:09 +02:00
{
2021-03-08 04:09:59 +11:00
Logger . WarningS ( "c.go.c.pulling" , "Well now you've done it, haven't you? SharedPullableComponent on {0} didn't have an IPhysBody." , Owner ) ;
2020-10-16 20:35:09 +02:00
return ;
}
2021-04-05 14:08:45 +02:00
if ( value = = null )
{
MovingTo = null ;
}
else
2020-10-16 20:35:09 +02:00
{
2020-11-26 13:48:10 +00:00
// Pulling a new object : Perform sanity checks.
if ( ! _canStartPull ( value ) )
2020-10-16 20:35:09 +02:00
{
2020-11-26 13:48:10 +00:00
return ;
2020-10-16 20:35:09 +02:00
}
2021-03-08 04:09:59 +11:00
if ( ! value . TryGetComponent < PhysicsComponent > ( out var pullerPhysics ) )
2020-11-26 13:48:10 +00:00
{
return ;
}
if ( ! value . TryGetComponent < SharedPullerComponent > ( out var valuePuller ) )
{
return ;
}
// Ensure that the puller is not currently pulling anything.
// If this isn't done, then it happens too late, and the start/stop messages go out of order,
// and next thing you know it thinks it's not pulling anything even though it is!
var oldPulling = valuePuller . Pulling ;
if ( oldPulling ! = null )
{
if ( oldPulling . TryGetComponent ( out SharedPullableComponent ? pullable ) )
{
2021-03-08 04:09:59 +11:00
pullable . TryStopPull ( ) ;
2020-11-26 13:48:10 +00:00
}
else
{
Logger . WarningS ( "c.go.c.pulling" , "Well now you've done it, haven't you? Someone transferred pulling to this component (on {0}) while presently pulling something that has no Pullable component (on {1})!" , Owner , oldPulling ) ;
return ;
}
}
// Continue with pulling process.
2021-03-08 04:09:59 +11:00
var pullAttempt = new PullAttemptMessage ( pullerPhysics , _physics ) ;
2020-11-26 13:48:10 +00:00
value . SendMessage ( null , pullAttempt ) ;
if ( pullAttempt . Cancelled )
{
return ;
}
Owner . SendMessage ( null , pullAttempt ) ;
if ( pullAttempt . Cancelled )
{
return ;
}
// Pull start confirm
_puller = value ;
2021-02-16 02:52:25 +01:00
Dirty ( ) ;
2021-04-05 14:08:45 +02:00
PullerPhysics = pullerPhysics ;
2020-10-16 20:35:09 +02:00
2021-04-05 14:08:45 +02:00
var message = new PullStartedMessage ( PullerPhysics , _physics ) ;
2020-11-26 13:48:10 +00:00
_puller . SendMessage ( null , message ) ;
Owner . SendMessage ( null , message ) ;
_puller . EntityManager . EventBus . RaiseEvent ( EventSource . Local , message ) ;
2021-04-05 14:08:45 +02:00
var union = PullerPhysics . GetWorldAABB ( ) . Union ( _physics . GetWorldAABB ( ) ) ;
2021-03-08 04:09:59 +11:00
var length = Math . Max ( union . Size . X , union . Size . Y ) * 0.75f ;
2020-11-26 13:48:10 +00:00
_physics . WakeBody ( ) ;
2021-03-08 04:09:59 +11:00
_pullJoint = pullerPhysics . CreateDistanceJoint ( _physics ) ;
// _physics.BodyType = BodyType.Kinematic; // TODO: Need to consider their original bodytype
2021-04-01 14:32:13 +11:00
_pullJoint . CollideConnected = false ;
2021-03-08 04:09:59 +11:00
_pullJoint . Length = length * 0.75f ;
_pullJoint . MaxLength = length ;
2020-11-26 13:48:10 +00:00
}
// Code here will not run if pulling a new object was attempted and failed because of the returns from the refactor.
2020-10-16 20:35:09 +02:00
}
}
public bool BeingPulled = > Puller ! = null ;
2021-04-05 14:08:45 +02:00
public MapCoordinates ? MovingTo
{
get = > _movingTo ;
set
{
if ( _movingTo = = value )
{
return ;
}
_movingTo = value ;
if ( value = = null )
{
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , new PullableStopMovingMessage ( ) ) ;
}
else
{
Owner . EntityManager . EventBus . RaiseLocalEvent ( Owner . Uid , new PullableMoveMessage ( ) ) ;
}
}
}
2020-11-26 13:48:10 +00:00
/// <summary>
/// Sanity-check pull. This is called from Puller setter, so it will never deny a pull that's valid by setting Puller.
/// It might allow an impossible pull (i.e: puller has no PhysicsComponent somehow).
/// Ultimately this is only used separately to stop TryStartPull from cancelling a pull for no reason.
/// </summary>
private bool _canStartPull ( IEntity puller )
2020-10-16 20:35:09 +02:00
{
if ( ! puller . HasComponent < SharedPullerComponent > ( ) )
{
return false ;
}
2020-11-02 11:58:47 +01:00
if ( _physics = = null )
2020-10-16 20:35:09 +02:00
{
return false ;
}
2021-05-13 02:13:12 +02:00
if ( _physics . BodyType = = BodyType . Static )
2020-10-16 20:35:09 +02:00
{
return false ;
}
if ( puller = = Owner )
{
return false ;
}
if ( ! puller . IsInSameOrNoContainer ( Owner ) )
{
return false ;
}
return true ;
}
public bool TryStartPull ( IEntity puller )
{
2020-11-26 13:48:10 +00:00
if ( ! _canStartPull ( puller ) )
2020-10-16 20:35:09 +02:00
{
return false ;
}
TryStopPull ( ) ;
Puller = puller ;
if ( Puller ! = puller )
{
return false ;
}
return true ;
}
public bool TryStopPull ( )
{
if ( ! BeingPulled )
{
return false ;
}
2021-03-08 04:09:59 +11:00
if ( _physics ! = null & & _pullJoint ! = null )
{
_physics . RemoveJoint ( _pullJoint ) ;
}
_pullJoint = null ;
2020-10-16 20:35:09 +02:00
Puller = null ;
return true ;
}
public bool TogglePull ( IEntity puller )
{
2020-10-26 18:15:19 +01:00
if ( BeingPulled )
2020-10-16 20:35:09 +02:00
{
2020-10-26 18:15:19 +01:00
if ( Puller = = puller )
{
return TryStopPull ( ) ;
}
else
{
TryStopPull ( ) ;
return TryStartPull ( puller ) ;
}
2020-10-16 20:35:09 +02:00
}
2020-10-26 18:15:19 +01:00
return TryStartPull ( puller ) ;
2020-10-16 20:35:09 +02:00
}
2021-04-05 14:08:45 +02:00
public bool TryMoveTo ( MapCoordinates to )
2020-10-16 20:35:09 +02:00
{
if ( Puller = = null )
{
return false ;
}
2020-11-02 11:58:47 +01:00
if ( _physics = = null )
2020-10-16 20:35:09 +02:00
{
return false ;
}
2021-04-05 14:08:45 +02:00
MovingTo = to ;
2021-03-08 04:09:59 +11:00
return true ;
2020-10-16 20:35:09 +02:00
}
2021-02-18 09:09:07 +01:00
public override ComponentState GetComponentState ( ICommonSession player )
2020-10-16 20:35:09 +02:00
{
return new PullableComponentState ( Puller ? . Uid ) ;
}
public override void HandleComponentState ( ComponentState ? curState , ComponentState ? nextState )
{
base . HandleComponentState ( curState , nextState ) ;
2020-11-26 14:33:31 +01:00
if ( curState is not PullableComponentState state )
2020-10-16 20:35:09 +02:00
{
return ;
}
if ( state . Puller = = null )
{
Puller = null ;
return ;
}
2020-11-19 00:37:16 +11:00
if ( ! Owner . EntityManager . TryGetEntity ( state . Puller . Value , out var entity ) )
{
Logger . Error ( $"Invalid entity {state.Puller.Value} for pulling" ) ;
return ;
}
Puller = entity ;
2020-10-16 20:35:09 +02:00
}
public override void HandleMessage ( ComponentMessage message , IComponent ? component )
{
base . HandleMessage ( message , component ) ;
2020-11-26 14:33:31 +01:00
if ( message is not PullMessage pullMessage | |
2020-10-16 20:35:09 +02:00
pullMessage . Pulled . Owner ! = Owner )
{
return ;
}
2021-04-05 14:08:45 +02:00
var pulledStatus = Owner . GetComponentOrNull < SharedAlertsComponent > ( ) ;
2020-11-26 13:48:10 +00:00
2020-10-16 20:35:09 +02:00
switch ( message )
{
2021-04-05 14:08:45 +02:00
case PullStartedMessage :
pulledStatus ? . ShowAlert ( AlertType . Pulled ) ;
2020-10-16 20:35:09 +02:00
break ;
2021-04-05 14:08:45 +02:00
case PullStoppedMessage :
pulledStatus ? . ClearAlert ( AlertType . Pulled ) ;
2020-10-16 20:35:09 +02:00
break ;
}
}
public override void OnRemove ( )
{
TryStopPull ( ) ;
2021-04-05 14:08:45 +02:00
MovingTo = null ;
2020-10-16 20:35:09 +02:00
base . OnRemove ( ) ;
}
2020-10-28 10:12:46 +01:00
public bool PreventCollide ( IPhysBody collidedWith )
{
if ( _puller = = null | | _physics = = null )
{
return false ;
}
return ( _physics . CollisionLayer & collidedWith . CollisionMask ) = = ( int ) CollisionGroup . MobImpassable ;
}
2021-03-08 12:10:48 +11:00
// TODO: Need a component bus relay so all entities can use this and not just players
void IRelayMoveInput . MoveInputPressed ( ICommonSession session )
{
var entity = session . AttachedEntity ;
if ( entity = = null | | ! ActionBlockerSystem . CanMove ( entity ) ) return ;
TryStopPull ( ) ;
}
2020-10-16 20:35:09 +02:00
}
[Serializable, NetSerializable]
public class PullableComponentState : ComponentState
{
public readonly EntityUid ? Puller ;
public PullableComponentState ( EntityUid ? puller ) : base ( ContentNetIDs . PULLABLE )
{
Puller = puller ;
}
}
}