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 ;
2020-11-09 20:22:19 -08:00
using Content.Shared.GameObjects.EntitySystems ;
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-02-18 09:09:07 +01:00
using Robust.Shared.Players ;
2020-10-16 20:35:09 +02:00
using Robust.Shared.Serialization ;
namespace Content.Shared.GameObjects.Components.Pulling
{
2020-10-28 10:12:46 +01:00
public abstract class SharedPullableComponent : Component , ICollideSpecial
2020-10-16 20:35:09 +02:00
{
public override string Name = > "Pullable" ;
public override uint? NetID = > ContentNetIDs . PULLABLE ;
2020-11-02 11:58:47 +01:00
[ComponentDependency] private readonly IPhysicsComponent ? _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 ;
2020-11-26 13:48:10 +00:00
private IPhysicsComponent ? _pullerPhysics ;
public IPhysicsComponent ? PullerPhysics = > _pullerPhysics ;
2020-10-16 20:35:09 +02:00
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 ;
var oldPullerPhysics = _pullerPhysics ;
_puller = null ;
2021-02-16 02:52:25 +01:00
Dirty ( ) ;
2020-11-26 13:48:10 +00:00
_pullerPhysics = null ;
if ( _physics ! = null )
{
var message = new PullStoppedMessage ( oldPullerPhysics , _physics ) ;
oldPuller . SendMessage ( null , message ) ;
Owner . SendMessage ( null , message ) ;
oldPuller . EntityManager . EventBus . RaiseEvent ( EventSource . Local , message ) ;
_physics . WakeBody ( ) ;
_physics . TryRemoveController < PullController > ( ) ;
}
// 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
{
2020-11-26 13:48:10 +00:00
Logger . WarningS ( "c.go.c.pulling" , "Well now you've done it, haven't you? SharedPullableComponent on {0} didn't have an IPhysicsComponent." , Owner ) ;
2020-10-16 20:35:09 +02:00
return ;
}
2020-11-26 13:48:10 +00:00
if ( value ! = null )
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
}
2020-11-26 13:48:10 +00:00
if ( ! value . TryGetComponent < IPhysicsComponent > ( out var valuePhysics ) )
{
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 ) )
{
pullable . Puller = null ;
}
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.
var pullAttempt = new PullAttemptMessage ( valuePhysics , _physics ) ;
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 ( ) ;
2020-11-26 13:48:10 +00:00
_pullerPhysics = valuePhysics ;
2020-10-16 20:35:09 +02:00
2020-11-26 13:48:10 +00:00
_physics . EnsureController < PullController > ( ) . Manager = this ;
var message = new PullStartedMessage ( _pullerPhysics , _physics ) ;
_puller . SendMessage ( null , message ) ;
Owner . SendMessage ( null , message ) ;
_puller . EntityManager . EventBus . RaiseEvent ( EventSource . Local , message ) ;
_physics . WakeBody ( ) ;
}
// 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 ;
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 ;
}
2020-11-02 11:58:47 +01:00
if ( _physics . Anchored )
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 ;
}
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
}
public bool TryMoveTo ( EntityCoordinates to )
{
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 ;
}
2020-11-02 11:58:47 +01:00
if ( ! _physics . TryGetController ( out PullController controller ) )
2020-10-16 20:35:09 +02:00
{
return false ;
}
return controller . TryMoveTo ( Puller . Transform . Coordinates , to ) ;
}
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 ;
}
2020-11-26 13:48:10 +00:00
SharedAlertsComponent ? pulledStatus = Owner . GetComponentOrNull < SharedAlertsComponent > ( ) ;
2020-10-16 20:35:09 +02:00
switch ( message )
{
case PullStartedMessage msg :
2020-11-26 13:48:10 +00:00
if ( pulledStatus ! = null )
{
pulledStatus . ShowAlert ( AlertType . Pulled ) ;
}
2020-10-16 20:35:09 +02:00
break ;
case PullStoppedMessage msg :
2020-11-26 13:48:10 +00:00
if ( pulledStatus ! = null )
{
pulledStatus . ClearAlert ( AlertType . Pulled ) ;
}
2020-10-16 20:35:09 +02:00
break ;
}
}
2020-11-09 20:22:19 -08:00
private void OnClickAlert ( ClickAlertEventArgs args )
{
EntitySystem
. Get < SharedPullingSystem > ( )
. GetPulled ( args . Player ) ?
. GetComponentOrNull < SharedPullableComponent > ( ) ?
. TryStopPull ( ) ;
}
2020-10-16 20:35:09 +02:00
public override void OnRemove ( )
{
TryStopPull ( ) ;
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 ;
}
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 ;
}
}
}