2020-10-08 17:41:23 +02:00
#nullable enable
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using Content.Server.GameObjects.Components.Interactable ;
using Content.Server.GameObjects.Components.Stack ;
using Content.Server.GameObjects.EntitySystems.DoAfter ;
using Content.Shared.Construction ;
using Content.Shared.GameObjects.Components.Interactable ;
2020-08-01 17:37:12 +02:00
using Content.Shared.GameObjects.EntitySystems ;
2020-10-08 17:41:23 +02:00
using Content.Shared.Interfaces.GameObjects.Components ;
2021-02-11 01:13:03 -08:00
using Robust.Server.GameObjects ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.GameObjects ;
2020-10-08 17:41:23 +02:00
using Robust.Shared.IoC ;
using Robust.Shared.Localization ;
using Robust.Shared.Log ;
using Robust.Shared.Prototypes ;
2020-07-02 14:50:57 -07:00
using Robust.Shared.Serialization ;
2020-08-01 17:37:12 +02:00
using Robust.Shared.Utility ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.ViewVariables ;
2018-08-02 08:29:55 +02:00
namespace Content.Server.GameObjects.Components.Construction
{
2019-07-31 15:02:36 +02:00
[RegisterComponent]
2020-12-08 10:53:37 +00:00
public partial class ConstructionComponent : Component , IExamine , IInteractUsing
2018-08-02 08:29:55 +02:00
{
2020-10-08 17:41:23 +02:00
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
2018-08-02 08:29:55 +02:00
public override string Name = > "Construction" ;
2020-10-08 17:41:23 +02:00
private bool _handling = false ;
private TaskCompletionSource < object > ? _handlingTask = null ;
private string _graphIdentifier = string . Empty ;
private string _startingNodeIdentifier = string . Empty ;
2020-12-08 10:53:37 +00:00
private string _startingTargetNodeIdentifier = string . Empty ;
2020-10-08 17:41:23 +02:00
[ViewVariables]
2020-11-27 11:00:49 +01:00
private HashSet < string > _containers = new ( ) ;
2020-10-08 17:41:23 +02:00
[ViewVariables]
private List < List < ConstructionGraphStep > > ? _edgeNestedStepProgress = null ;
private ConstructionGraphNode ? _target = null ;
[ViewVariables]
2020-12-03 22:49:00 +01:00
public ConstructionGraphPrototype ? GraphPrototype { get ; private set ; }
2020-10-08 17:41:23 +02:00
[ViewVariables]
2020-12-03 22:49:00 +01:00
public ConstructionGraphNode ? Node { get ; private set ; } = null ;
2020-10-08 17:41:23 +02:00
[ViewVariables]
public ConstructionGraphEdge ? Edge { get ; private set ; } = null ;
public IReadOnlyCollection < string > Containers = > _containers ;
[ViewVariables]
public ConstructionGraphNode ? Target
{
get = > _target ;
set
{
ClearTarget ( ) ;
_target = value ;
UpdateTarget ( ) ;
}
}
2018-09-09 15:34:43 +02:00
[ViewVariables]
2020-10-08 17:41:23 +02:00
public ConstructionGraphEdge ? TargetNextEdge { get ; private set ; } = null ;
2018-08-02 08:29:55 +02:00
2020-07-02 14:50:57 -07:00
[ViewVariables]
2020-10-08 17:41:23 +02:00
public Queue < ConstructionGraphNode > ? TargetPathfinding { get ; private set ; } = null ;
[ViewVariables]
public int EdgeStep { get ; private set ; } = 0 ;
2018-08-02 08:29:55 +02:00
2020-12-08 10:53:37 +00:00
[ViewVariables]
public string DeconstructionNodeIdentifier { get ; private set ; } = "start" ;
2020-07-02 14:50:57 -07:00
/// <inheritdoc />
public override void ExposeData ( ObjectSerializer serializer )
2018-08-02 08:29:55 +02:00
{
2020-07-02 14:50:57 -07:00
base . ExposeData ( serializer ) ;
2020-05-19 13:55:52 +02:00
2020-10-08 17:41:23 +02:00
serializer . DataField ( ref _graphIdentifier , "graph" , string . Empty ) ;
serializer . DataField ( ref _startingNodeIdentifier , "node" , string . Empty ) ;
2020-12-08 10:53:37 +00:00
serializer . DataField ( ref _startingTargetNodeIdentifier , "defaultTarget" , string . Empty ) ;
serializer . DataField ( this , x = > x . DeconstructionNodeIdentifier , "deconstructionTarget" , "start" ) ;
2020-10-08 17:41:23 +02:00
}
2020-12-03 22:49:00 +01:00
/// <summary>
/// Attempts to set a new pathfinding target.
/// </summary>
public void SetNewTarget ( string node )
{
if ( GraphPrototype ! = null & & GraphPrototype . Nodes . TryGetValue ( node , out var target ) )
{
Target = target ;
}
}
2020-10-08 17:41:23 +02:00
public void ClearTarget ( )
{
_target = null ;
TargetNextEdge = null ;
TargetPathfinding = null ;
}
public void UpdateTarget ( )
{
2020-12-03 22:49:00 +01:00
// Can't pathfind without a target or no node.
if ( Target = = null | | Node = = null | | GraphPrototype = = null ) return ;
2020-10-08 17:41:23 +02:00
// If we're at our target, stop pathfinding.
if ( Target = = Node )
{
ClearTarget ( ) ;
return ;
}
// If we don't have the path, set it!
if ( TargetPathfinding = = null )
{
var path = GraphPrototype . Path ( Node . Name , Target . Name ) ;
if ( path = = null )
{
ClearTarget ( ) ;
return ;
}
TargetPathfinding = new Queue < ConstructionGraphNode > ( path ) ;
}
// Dequeue the pathfinding queue if the next is the node we're at.
if ( TargetPathfinding . Peek ( ) = = Node )
TargetPathfinding . Dequeue ( ) ;
// If we went the wrong way, we stop pathfinding.
if ( Edge ! = null & & TargetNextEdge ! = Edge )
{
ClearTarget ( ) ;
return ;
}
// Let's set the next target edge.
2020-12-03 22:49:00 +01:00
if ( Edge = = null & & TargetNextEdge = = null & & TargetPathfinding ! = null )
2020-10-08 17:41:23 +02:00
TargetNextEdge = Node . GetEdge ( TargetPathfinding . Peek ( ) . Name ) ;
}
2021-02-04 17:44:49 +01:00
async Task < bool > IInteractUsing . InteractUsing ( InteractUsingEventArgs eventArgs )
2020-10-08 17:41:23 +02:00
{
if ( _handling )
return true ;
_handlingTask = new TaskCompletionSource < object > ( ) ;
_handling = true ;
bool result ;
if ( Edge = = null )
result = await HandleNode ( eventArgs ) ;
else
result = await HandleEdge ( eventArgs ) ;
_handling = false ;
_handlingTask . SetResult ( null ! ) ;
return result ;
}
private async Task < bool > HandleNode ( InteractUsingEventArgs eventArgs )
{
EdgeStep = 0 ;
2020-12-03 22:49:00 +01:00
if ( Node = = null | | GraphPrototype = = null ) return false ;
2020-10-08 17:41:23 +02:00
foreach ( var edge in Node . Edges )
{
if ( edge . Steps . Count = = 0 )
throw new InvalidDataException ( $"Edge to \" { edge . Target } \ " in node \"{Node.Name}\" of graph \"{GraphPrototype.ID}\" doesn't have any steps!" ) ;
var firstStep = edge . Steps [ 0 ] ;
switch ( firstStep )
{
case MaterialConstructionGraphStep _ :
case ToolConstructionGraphStep _ :
case PrototypeConstructionGraphStep _ :
case ComponentConstructionGraphStep _ :
if ( await HandleStep ( eventArgs , edge , firstStep ) )
{
if ( edge . Steps . Count > 1 )
Edge = edge ;
return true ;
}
break ;
case NestedConstructionGraphStep nestedStep :
throw new IndexOutOfRangeException ( $"Nested construction step not supported as the first step in an edge! Graph: {GraphPrototype.ID} Node: {Node.Name} Edge: {edge.Target}" ) ;
}
}
return false ;
}
private async Task < bool > HandleStep ( InteractUsingEventArgs eventArgs , ConstructionGraphEdge ? edge = null , ConstructionGraphStep ? step = null , bool nested = false )
{
edge ? ? = Edge ;
step ? ? = edge ? . Steps [ EdgeStep ] ;
if ( edge = = null | | step = = null )
return false ;
foreach ( var condition in edge . Conditions )
{
if ( ! await condition . Condition ( Owner ) ) return false ;
}
var handled = false ;
var doAfterSystem = EntitySystem . Get < DoAfterSystem > ( ) ;
var doAfterArgs = new DoAfterEventArgs ( eventArgs . User , step . DoAfter , default , eventArgs . Target )
{
BreakOnDamage = false ,
BreakOnStun = true ,
BreakOnTargetMove = true ,
BreakOnUserMove = true ,
NeedHand = true ,
} ;
switch ( step )
{
case ToolConstructionGraphStep toolStep :
// Gotta take welder fuel into consideration.
if ( toolStep . Tool = = ToolQuality . Welding )
{
if ( eventArgs . Using . TryGetComponent ( out WelderComponent ? welder ) & &
await welder . UseTool ( eventArgs . User , Owner , step . DoAfter , toolStep . Tool , toolStep . Fuel ) )
{
handled = true ;
}
break ;
}
if ( eventArgs . Using . TryGetComponent ( out ToolComponent ? tool ) & &
await tool . UseTool ( eventArgs . User , Owner , step . DoAfter , toolStep . Tool ) )
{
handled = true ;
}
break ;
// To prevent too much code duplication.
case EntityInsertConstructionGraphStep insertStep :
var valid = false ;
var entityUsing = eventArgs . Using ;
switch ( insertStep )
{
case PrototypeConstructionGraphStep prototypeStep :
if ( prototypeStep . EntityValid ( eventArgs . Using )
& & await doAfterSystem . DoAfter ( doAfterArgs ) = = DoAfterStatus . Finished )
{
valid = true ;
}
break ;
case ComponentConstructionGraphStep componentStep :
if ( componentStep . EntityValid ( eventArgs . Using )
& & await doAfterSystem . DoAfter ( doAfterArgs ) = = DoAfterStatus . Finished )
{
valid = true ;
}
break ;
case MaterialConstructionGraphStep materialStep :
if ( materialStep . EntityValid ( eventArgs . Using , out var sharedStack )
& & await doAfterSystem . DoAfter ( doAfterArgs ) = = DoAfterStatus . Finished )
{
var stack = ( StackComponent ) sharedStack ;
valid = stack . Split ( materialStep . Amount , eventArgs . User . Transform . Coordinates , out entityUsing ) ;
}
break ;
}
if ( ! valid | | entityUsing = = null ) break ;
if ( string . IsNullOrEmpty ( insertStep . Store ) )
{
entityUsing . Delete ( ) ;
}
else
{
_containers . Add ( insertStep . Store ) ;
var container = ContainerManagerComponent . Ensure < Container > ( insertStep . Store , Owner ) ;
container . Insert ( entityUsing ) ;
}
handled = true ;
2020-05-19 13:55:52 +02:00
2020-10-08 17:41:23 +02:00
break ;
case NestedConstructionGraphStep nestedStep :
if ( _edgeNestedStepProgress = = null )
_edgeNestedStepProgress = new List < List < ConstructionGraphStep > > ( nestedStep . Steps ) ;
foreach ( var list in _edgeNestedStepProgress . ToArray ( ) )
{
if ( list . Count = = 0 )
{
_edgeNestedStepProgress . Remove ( list ) ;
continue ;
}
if ( ! await HandleStep ( eventArgs , edge , list [ 0 ] , true ) ) continue ;
list . RemoveAt ( 0 ) ;
// We check again...
if ( list . Count = = 0 )
_edgeNestedStepProgress . Remove ( list ) ;
}
if ( _edgeNestedStepProgress . Count = = 0 )
handled = true ;
break ;
}
if ( handled )
{
foreach ( var completed in step . Completed )
{
await completed . PerformAction ( Owner , eventArgs . User ) ;
if ( Owner . Deleted )
return false ;
}
}
if ( nested & & handled ) return true ;
if ( ! handled ) return false ;
EdgeStep + + ;
if ( edge . Steps . Count = = EdgeStep )
{
await HandleCompletion ( edge , eventArgs . User ) ;
}
UpdateTarget ( ) ;
return true ;
}
private async Task < bool > HandleCompletion ( ConstructionGraphEdge edge , IEntity user )
{
2020-12-03 22:49:00 +01:00
if ( edge . Steps . Count ! = EdgeStep | | GraphPrototype = = null )
2020-10-08 17:41:23 +02:00
{
return false ;
}
Edge = edge ;
UpdateTarget ( ) ;
TargetNextEdge = null ;
Edge = null ;
Node = GraphPrototype . Nodes [ edge . Target ] ;
// Perform node actions!
foreach ( var action in Node . Actions )
{
await action . PerformAction ( Owner , user ) ;
if ( Owner . Deleted )
return false ;
}
if ( Target = = Node )
ClearTarget ( ) ;
foreach ( var completed in edge . Completed )
{
await completed . PerformAction ( Owner , user ) ;
if ( Owner . Deleted ) return true ;
}
await HandleEntityChange ( Node , user ) ;
return true ;
}
2020-12-03 22:49:00 +01:00
public void ResetEdge ( )
{
_edgeNestedStepProgress = null ;
TargetNextEdge = null ;
Edge = null ;
EdgeStep = 0 ;
UpdateTarget ( ) ;
}
2020-10-08 17:41:23 +02:00
private async Task < bool > HandleEdge ( InteractUsingEventArgs eventArgs )
{
if ( Edge = = null | | EdgeStep > = Edge . Steps . Count ) return false ;
return await HandleStep ( eventArgs , Edge , Edge . Steps [ EdgeStep ] ) ;
}
private async Task < bool > HandleEntityChange ( ConstructionGraphNode node , IEntity ? user = null )
{
2020-12-03 22:49:00 +01:00
if ( node . Entity = = Owner . Prototype ? . ID | | string . IsNullOrEmpty ( node . Entity )
| | GraphPrototype = = null ) return false ;
2020-10-08 17:41:23 +02:00
2020-11-18 15:45:53 +01:00
var entity = Owner . EntityManager . SpawnEntity ( node . Entity , Owner . Transform . Coordinates ) ;
2020-10-08 17:41:23 +02:00
entity . Transform . LocalRotation = Owner . Transform . LocalRotation ;
if ( entity . TryGetComponent ( out ConstructionComponent ? construction ) )
{
if ( construction . GraphPrototype ! = GraphPrototype )
2020-12-03 22:49:00 +01:00
throw new Exception ( $"New entity {node.Entity}'s graph {construction.GraphPrototype?.ID ?? null} isn't the same as our graph {GraphPrototype.ID} on node {node.Name}!" ) ;
2020-10-08 17:41:23 +02:00
construction . Node = node ;
construction . Target = Target ;
construction . _containers = new HashSet < string > ( _containers ) ;
}
if ( Owner . TryGetComponent ( out ContainerManagerComponent ? containerComp ) )
{
foreach ( var container in _containers )
{
var otherContainer = ContainerManagerComponent . Ensure < Container > ( container , entity ) ;
var ourContainer = containerComp . GetContainer ( container ) ;
foreach ( var ent in ourContainer . ContainedEntities . ToArray ( ) )
{
ourContainer . ForceRemove ( ent ) ;
otherContainer . Insert ( ent ) ;
}
}
}
2020-10-11 16:36:58 +02:00
if ( Owner . TryGetComponent ( out IPhysicsComponent ? physics ) & &
entity . TryGetComponent ( out IPhysicsComponent ? otherPhysics ) )
2020-10-08 17:41:23 +02:00
{
2020-10-11 16:36:58 +02:00
otherPhysics . Anchored = physics . Anchored ;
2020-10-08 17:41:23 +02:00
}
Owner . Delete ( ) ;
foreach ( var action in node . Actions )
{
await action . PerformAction ( entity , user ) ;
if ( entity . Deleted )
return false ;
}
return true ;
}
public bool AddContainer ( string id )
{
return _containers . Add ( id ) ;
}
public override void Initialize ( )
{
base . Initialize ( ) ;
if ( string . IsNullOrEmpty ( _graphIdentifier ) )
{
Logger . Error ( $"Prototype {Owner.Prototype?.ID}'s construction component didn't have a graph identifier!" ) ;
return ;
}
if ( _prototypeManager . TryIndex ( _graphIdentifier , out ConstructionGraphPrototype graph ) )
{
GraphPrototype = graph ;
if ( GraphPrototype . Nodes . TryGetValue ( _startingNodeIdentifier , out var node ) )
{
Node = node ;
}
else
{
Logger . Error ( $"Couldn't find node {_startingNodeIdentifier} in graph {_graphIdentifier} in construction component!" ) ;
}
}
else
{
Logger . Error ( $"Couldn't find prototype {_graphIdentifier} in construction component!" ) ;
}
2020-12-08 10:53:37 +00:00
if ( ! string . IsNullOrEmpty ( _startingTargetNodeIdentifier ) )
SetNewTarget ( _startingTargetNodeIdentifier ) ;
2020-10-08 17:41:23 +02:00
}
2020-12-03 22:49:00 +01:00
protected override void Startup ( )
{
base . Startup ( ) ;
if ( Node = = null ) return ;
foreach ( var action in Node . Actions )
{
action . PerformAction ( Owner , null ) ;
if ( Owner . Deleted )
return ;
}
}
2020-10-08 17:41:23 +02:00
public async Task ChangeNode ( string node )
{
2020-12-03 22:49:00 +01:00
if ( GraphPrototype = = null ) return ;
2020-10-08 17:41:23 +02:00
var graphNode = GraphPrototype . Nodes [ node ] ;
if ( _handling & & _handlingTask ? . Task ! = null )
await _handlingTask . Task ;
Edge = null ;
Node = graphNode ;
foreach ( var action in Node . Actions )
{
await action . PerformAction ( Owner , null ) ;
if ( Owner . Deleted )
return ;
}
await HandleEntityChange ( graphNode ) ;
2018-08-02 08:29:55 +02:00
}
2020-08-01 17:37:12 +02:00
void IExamine . Examine ( FormattedMessage message , bool inDetailsRange )
{
2020-10-08 17:41:23 +02:00
if ( Target ! = null )
message . AddMarkup ( Loc . GetString ( "To create {0}...\n" , Target . Name ) ) ;
if ( Edge = = null & & TargetNextEdge ! = null )
{
2020-12-03 22:49:00 +01:00
var preventStepExamine = false ;
2020-10-08 17:41:23 +02:00
foreach ( var condition in TargetNextEdge . Conditions )
{
2020-12-03 22:49:00 +01:00
preventStepExamine | = condition . DoExamine ( Owner , message , inDetailsRange ) ;
2020-10-08 17:41:23 +02:00
}
2020-12-03 22:49:00 +01:00
if ( ! preventStepExamine )
TargetNextEdge . Steps [ 0 ] . DoExamine ( message , inDetailsRange ) ;
2020-10-08 17:41:23 +02:00
return ;
}
if ( Edge ! = null )
{
2020-12-03 22:49:00 +01:00
var preventStepExamine = false ;
2020-10-08 17:41:23 +02:00
foreach ( var condition in Edge . Conditions )
{
2020-12-03 22:49:00 +01:00
preventStepExamine | = condition . DoExamine ( Owner , message , inDetailsRange ) ;
2020-10-08 17:41:23 +02:00
}
2020-12-03 22:49:00 +01:00
if ( preventStepExamine ) return ;
2020-10-08 17:41:23 +02:00
}
if ( _edgeNestedStepProgress = = null )
{
if ( EdgeStep < Edge ? . Steps . Count )
Edge . Steps [ EdgeStep ] . DoExamine ( message , inDetailsRange ) ;
return ;
}
foreach ( var list in _edgeNestedStepProgress )
{
if ( list . Count = = 0 ) continue ;
list [ 0 ] . DoExamine ( message , inDetailsRange ) ;
}
2020-08-01 17:37:12 +02:00
}
2018-08-02 08:29:55 +02:00
}
}