Add nodecrawling, implement ventcrawl with it (#5830)
* node crawling movement * more node crawling * quality assurance * do-after it * i'm ratted king * we don't need to serialize node stuff * rename and ser/de fix * review fixes
This commit is contained in:
parent
495107e5e0
commit
c7c6f1a67a
|
|
@ -32,6 +32,21 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
|||
}
|
||||
}
|
||||
|
||||
// Begin DeltaV - node crawling
|
||||
private Type[] _types = new Type[] { };
|
||||
|
||||
[ViewVariables]
|
||||
public Type[] Types
|
||||
{
|
||||
get => _types;
|
||||
set
|
||||
{
|
||||
_types = value;
|
||||
UpdateAll();
|
||||
}
|
||||
}
|
||||
// End DeltaV - node crawling
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -65,6 +80,17 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
|||
|
||||
var revealed = !covered || ShowAll || scannerRevealed;
|
||||
|
||||
// Begin DeltaV - node crawling
|
||||
foreach (var type in _types)
|
||||
{
|
||||
if (!HasComp(uid, type))
|
||||
continue;
|
||||
|
||||
revealed = true;
|
||||
break;
|
||||
}
|
||||
// End DeltaV - node crawling
|
||||
|
||||
// set visibility & color of each layer
|
||||
foreach (var layer in args.Sprite.AllLayers)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
using Content.Client.SubFloor;
|
||||
using Content.Shared._DV.NodeCrawl;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client._DV.NodeCrawl;
|
||||
|
||||
public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly SubFloorHideSystem _subfloor = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, LocalPlayerAttachedEvent>(OnAttached);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, LocalPlayerDetachedEvent>(OnDetached);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
|
||||
}
|
||||
|
||||
private void OnAttached(Entity<NodeCrawlerComponent> ent, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
if (ent.Comp.Mover is not null)
|
||||
_subfloor.Types = ent.Comp.RevealedComponents;
|
||||
}
|
||||
|
||||
private void OnDetached(Entity<NodeCrawlerComponent> ent, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_subfloor.Types = new Type[] { };
|
||||
}
|
||||
|
||||
private void OnAfterAutoHandleState(Entity<NodeCrawlerComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (_player.LocalEntity != ent)
|
||||
return;
|
||||
|
||||
if (ent.Comp.Mover is not null)
|
||||
_subfloor.Types = ent.Comp.RevealedComponents;
|
||||
else
|
||||
_subfloor.Types = new Type[] { };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
using System.Numerics;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared._DV.NodeCrawl;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.NodeContainer;
|
||||
|
||||
namespace Content.Server._DV.NodeCrawl;
|
||||
|
||||
public sealed class NodeCrawlSystem : SharedNodeCrawlSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CrawlableNodeComponent, NodeGroupsRebuilt>(OnNodeGroupsRebuilt);
|
||||
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, InhaleLocationEvent>(OnInhaleLocation);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, ExhaleLocationEvent>(OnExhaleLocation);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, AtmosExposedGetAirEvent>(OnGetAir);
|
||||
}
|
||||
|
||||
private Entity<NodeContainerComponent>? GetNodeContainer(Entity<NodeCrawlerComponent> crawler)
|
||||
{
|
||||
if (!TryComp<NodeCrawlerMovementComponent>(crawler.Comp.Mover, out var mover) || mover.Node is not { } node)
|
||||
return null;
|
||||
|
||||
if (!TryComp<NodeContainerComponent>(node, out var nodeContainer))
|
||||
return null;
|
||||
|
||||
return (node, nodeContainer);
|
||||
}
|
||||
|
||||
private GasMixture? GetAir(Entity<NodeCrawlerComponent> crawler)
|
||||
{
|
||||
if (GetNodeContainer(crawler) is not { } nodeContainer)
|
||||
return null;
|
||||
|
||||
foreach (var containedNode in nodeContainer.Comp.Nodes.Values)
|
||||
{
|
||||
if (containedNode is not PipeNode pipe)
|
||||
continue;
|
||||
|
||||
return pipe.Air;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnInhaleLocation(Entity<NodeCrawlerComponent> ent, ref InhaleLocationEvent args)
|
||||
{
|
||||
if (GetAir(ent) is not { } air)
|
||||
return;
|
||||
|
||||
args.Gas = air;
|
||||
}
|
||||
|
||||
private void OnExhaleLocation(Entity<NodeCrawlerComponent> ent, ref ExhaleLocationEvent args)
|
||||
{
|
||||
if (GetAir(ent) is not { } air)
|
||||
return;
|
||||
|
||||
args.Gas = air;
|
||||
}
|
||||
|
||||
private void OnGetAir(Entity<NodeCrawlerComponent> ent, ref AtmosExposedGetAirEvent args)
|
||||
{
|
||||
if (args.Handled || GetAir(ent) is not { } air)
|
||||
return;
|
||||
|
||||
args.Gas = air;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnNodeGroupsRebuilt(Entity<CrawlableNodeComponent> ent, ref NodeGroupsRebuilt args)
|
||||
{
|
||||
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
|
||||
return;
|
||||
|
||||
ent.Comp.DeadEnd = false;
|
||||
var set = new HashSet<EntityUid>();
|
||||
foreach (var node in nodeContainer.Nodes.Values)
|
||||
{
|
||||
foreach (var reachable in node.ReachableNodes)
|
||||
{
|
||||
set.Add(reachable.Owner);
|
||||
}
|
||||
|
||||
if (node is PipeNode pipeNode &&
|
||||
node.ReachableNodes.Count != BitOperations.PopCount((uint)pipeNode.CurrentPipeDirection))
|
||||
{
|
||||
ent.Comp.DeadEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
ent.Comp.ReachableNodes = set;
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Content.Shared._DV.Movement; // DeltaV
|
||||
using Content.Shared._DV.NodeCrawl; // DeltaV - Node Crawling
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Friction;
|
||||
|
|
@ -50,6 +51,7 @@ public abstract partial class SharedMoverController : VirtualController
|
|||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
[Dependency] private readonly TileMovementSystem _tileMovement = default!; // DeltaV
|
||||
[Dependency] private readonly NodeCrawlerMovementSystem _nodeCrawlerMovement = default!; // DeltaV - node crawling
|
||||
|
||||
protected EntityQuery<CanMoveInAirComponent> CanMoveInAirQuery;
|
||||
protected EntityQuery<FootstepModifierComponent> FootstepModifierQuery;
|
||||
|
|
@ -264,6 +266,11 @@ public abstract partial class SharedMoverController : VirtualController
|
|||
return;
|
||||
// End DeltaV Additions
|
||||
|
||||
// Begin DeltaV Additions - node crawling
|
||||
if (_nodeCrawlerMovement.TryTick((uid, mover, physicsComponent, xform)))
|
||||
return;
|
||||
// End DeltaV Additions
|
||||
|
||||
var touching = false;
|
||||
// Whether we use tilefriction or not
|
||||
if (weightless || inAirHelpless)
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ public enum CollisionGroup
|
|||
TabletopMachineLayer = Opaque | BulletImpassable,
|
||||
|
||||
// Airlocks, windoors, firelocks
|
||||
GlassAirlockLayer = HighImpassable | MidImpassable | BulletImpassable | InteractImpassable,
|
||||
AirlockLayer = Opaque | GlassAirlockLayer,
|
||||
GlassAirlockLayer = HighImpassable | MidImpassable | LowImpassable | BulletImpassable | InteractImpassable, // DeltaV - we have ventcrawling
|
||||
AirlockLayer = Opaque | LowImpassable | GlassAirlockLayer, // DeltaV - we have ventcrawling
|
||||
|
||||
// Airlock assembly
|
||||
HumanoidBlockLayer = HighImpassable | MidImpassable,
|
||||
|
|
|
|||
|
|
@ -101,6 +101,12 @@ public sealed partial class RatKingComponent : Component
|
|||
{ RatKingOrderType.CheeseEm, "RatKingCommandCheeseEm" },
|
||||
{ RatKingOrderType.Loose, "RatKingCommandLoose" }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Servants within this radius will join the Rat King when vent crawling
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float VentCrawlRecruitRadius = 4f;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.NodeCrawl;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an entity in a node network that can be crawled in
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedNodeCrawlSystem), typeof(NodeCrawlerMovementSystem))]
|
||||
public sealed partial class CrawlableNodeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Other entities with <see cref="CrawlableNodeComponent" /> that can be reached from this one
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public HashSet<EntityUid> ReachableNodes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node has an unconnected node and should be exited from on movement
|
||||
/// </summary>
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public bool DeadEnd;
|
||||
|
||||
/// <summary>
|
||||
/// All entities with <see cref="NodeCrawlerMovementComponent" /> that are associated with this one
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<EntityUid> Crawlers = new();
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.NodeCrawl;
|
||||
|
||||
/// <summary>
|
||||
/// Marker component to use on atmos-related devices that node crawlers can enter/exit out of.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class NodeCrawlVentAccessComponent : Component;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.NodeCrawl;
|
||||
|
||||
/// <summary>
|
||||
/// Handles entities that can enter and exit node-constrained movement.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
[Access(typeof(SharedNodeCrawlSystem))]
|
||||
public sealed partial class NodeCrawlerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The mover this crawler is currently being carried by, if any
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Mover;
|
||||
|
||||
/// <summary>
|
||||
/// Components of entities to reveal while inside a mover
|
||||
/// </summary>
|
||||
[DataField(readOnly: true)]
|
||||
public Type[] RevealedComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Whitelist for entities that will be considered as exit nodes.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? ExitNodes;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to enter a node.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan EnterDelay = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class NodeCrawlEnterDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly record struct NodeCrawlerStartedCrawlingEvent(Entity<NodeCrawlerMovementComponent> Mover);
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.NodeCrawl;
|
||||
|
||||
/// <summary>
|
||||
/// Handles node-confined movement for an entity.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
[Access(typeof(NodeCrawlerMovementSystem))]
|
||||
public sealed partial class NodeCrawlerMovementComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The current node.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? Node;
|
||||
|
||||
/// <summary>
|
||||
/// The target node being moved to.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? TargetNode;
|
||||
|
||||
/// <summary>
|
||||
/// The crawler being carried by this entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? HeldCrawler;
|
||||
|
||||
/// <summary>
|
||||
/// The required angle to be within for deciding which node to move from the target direction
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public double RequiredAngle = Math.PI / 4f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a node crawler arrives at a node entity.
|
||||
/// </summary>
|
||||
/// <param name="Node">The arrived-at node.</param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct NodeCrawlerArrivedAtNodeEvent(EntityUid Node);
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Shared._DV.NodeCrawl;
|
||||
|
||||
public sealed class NodeCrawlerMovementSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedNodeCrawlSystem _nodeCrawl = default!;
|
||||
|
||||
public bool TryTick(
|
||||
Entity<InputMoverComponent, PhysicsComponent, TransformComponent> sharedMover)
|
||||
{
|
||||
if (!TryComp<NodeCrawlerMovementComponent>(sharedMover, out var crawler) || crawler.Node is null)
|
||||
return false;
|
||||
|
||||
Entity<InputMoverComponent, PhysicsComponent, TransformComponent, NodeCrawlerMovementComponent> mover = (sharedMover.Owner, sharedMover.Comp1, sharedMover.Comp2, sharedMover.Comp3, crawler);
|
||||
|
||||
if (mover.Comp4.TargetNode is { } target)
|
||||
OngoingMovement(mover, target);
|
||||
else
|
||||
StartMovement(mover);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void StartMovement(
|
||||
Entity<InputMoverComponent, PhysicsComponent, TransformComponent, NodeCrawlerMovementComponent> mover)
|
||||
{
|
||||
if (GetDestination(mover, mover.Comp1.HeldMoveButtons) is not { } target)
|
||||
{
|
||||
if (mover.Comp4.Node is not { } node)
|
||||
return;
|
||||
|
||||
var nodeComp = Comp<CrawlableNodeComponent>(node);
|
||||
if (!nodeComp.DeadEnd)
|
||||
return;
|
||||
|
||||
if (mover.Comp4.HeldCrawler is not { } crawler)
|
||||
return;
|
||||
|
||||
_nodeCrawl.ExitNodeCrawl((crawler, Comp<NodeCrawlerComponent>(crawler)));
|
||||
return;
|
||||
}
|
||||
|
||||
mover.Comp4.TargetNode = target;
|
||||
Dirty(mover, mover.Comp4);
|
||||
|
||||
OngoingMovement(mover, target);
|
||||
}
|
||||
|
||||
private void StopMovement(
|
||||
Entity<InputMoverComponent, PhysicsComponent, TransformComponent, NodeCrawlerMovementComponent> mover)
|
||||
{
|
||||
_physics.SetLinearVelocity(mover, Vector2.Zero, body: mover.Comp2);
|
||||
_physics.SetAngularVelocity(mover, 0, body: mover.Comp2);
|
||||
}
|
||||
|
||||
private void OngoingMovement(
|
||||
Entity<InputMoverComponent, PhysicsComponent, TransformComponent, NodeCrawlerMovementComponent> mover,
|
||||
EntityUid target)
|
||||
{
|
||||
var speed = MoveSpeed(mover);
|
||||
|
||||
if (ReachedDestination(mover, target, speed))
|
||||
{
|
||||
StopMovement(mover);
|
||||
SetNode((mover, mover), target);
|
||||
mover.Comp4.TargetNode = null;
|
||||
Dirty(mover, mover.Comp4);
|
||||
|
||||
if (TryComp<MovementRelayTargetComponent>(mover, out var movementTarget))
|
||||
{
|
||||
var evt = new NodeCrawlerArrivedAtNodeEvent(target);
|
||||
RaiseLocalEvent(movementTarget.Source, ref evt);
|
||||
}
|
||||
|
||||
StartMovement(mover);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateMovement(mover, target, speed);
|
||||
}
|
||||
|
||||
private float MoveSpeed(Entity<InputMoverComponent> mover)
|
||||
{
|
||||
var moveSpeed = CompOrNull<MovementSpeedModifierComponent>(mover);
|
||||
|
||||
var walkSpeed = moveSpeed?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
|
||||
var sprintSpeed = moveSpeed?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
|
||||
return mover.Comp.Sprinting ? sprintSpeed : walkSpeed;
|
||||
}
|
||||
|
||||
private void UpdateMovement(
|
||||
Entity<InputMoverComponent, PhysicsComponent, TransformComponent, NodeCrawlerMovementComponent> mover,
|
||||
EntityUid target,
|
||||
float speed)
|
||||
{
|
||||
var delta = _transform.GetWorldPosition(target) - _transform.GetWorldPosition(mover.Comp3);
|
||||
|
||||
var facing = Angle.FromWorldVec(delta);
|
||||
_transform.SetWorldRotation(mover.Comp3, facing);
|
||||
|
||||
var velocity = delta;
|
||||
velocity.Normalize();
|
||||
velocity *= speed;
|
||||
|
||||
_physics.SetCanCollide(mover, false);
|
||||
_physics.SetLinearVelocity(mover, velocity, body: mover.Comp2);
|
||||
_physics.SetAngularVelocity(mover, 0, body: mover.Comp2);
|
||||
}
|
||||
|
||||
private bool ReachedDestination(
|
||||
Entity<InputMoverComponent, PhysicsComponent, TransformComponent, NodeCrawlerMovementComponent> mover,
|
||||
EntityUid target,
|
||||
float speed)
|
||||
{
|
||||
var delta = _transform.GetWorldPosition(mover.Comp3) - _transform.GetWorldPosition(target);
|
||||
return delta.EqualsApprox(Vector2.Zero, speed * 0.01f);
|
||||
}
|
||||
|
||||
private EntityUid? GetDestination(Entity<InputMoverComponent, PhysicsComponent, TransformComponent, NodeCrawlerMovementComponent> ent, MoveButtons buttons)
|
||||
{
|
||||
if ((buttons & MoveButtons.AnyDirection) == 0)
|
||||
return null;
|
||||
|
||||
var target = _mover.DirVecForButtons(buttons);
|
||||
target = _mover.GetParentGridAngle(ent.Comp1).RotateVec(target);
|
||||
if (ent.Comp4.Node is not { } node || !Exists(node) || !TryComp<CrawlableNodeComponent>(node, out var nodeCrawl))
|
||||
return null;
|
||||
|
||||
var nodeXform = Transform(node);
|
||||
var nodeWorld = _transform.GetWorldPosition(nodeXform);
|
||||
var smallestTarget = EntityUid.Invalid;
|
||||
var largestDot = 0d;
|
||||
|
||||
foreach (var reachable in nodeCrawl.ReachableNodes)
|
||||
{
|
||||
var reachableXform = Transform(reachable);
|
||||
var reachableWorld = _transform.GetWorldPosition(reachableXform);
|
||||
var delta = reachableWorld - nodeWorld;
|
||||
delta.Normalize();
|
||||
|
||||
var deltaTargetDot = Vector2.Dot(delta, target);
|
||||
|
||||
if (deltaTargetDot < largestDot)
|
||||
continue;
|
||||
|
||||
smallestTarget = reachable;
|
||||
largestDot = deltaTargetDot;
|
||||
}
|
||||
|
||||
if (!smallestTarget.Valid || largestDot <= Math.Cos(ent.Comp4.RequiredAngle))
|
||||
return null;
|
||||
|
||||
return smallestTarget;
|
||||
}
|
||||
|
||||
public void SetNode(Entity<NodeCrawlerMovementComponent> ent, EntityUid? node)
|
||||
{
|
||||
if (ent.Comp.Node == node)
|
||||
return;
|
||||
|
||||
if (ent.Comp.Node is { } oldNode)
|
||||
{
|
||||
var oldNodeComp = Comp<CrawlableNodeComponent>(oldNode);
|
||||
oldNodeComp.Crawlers.Remove(ent);
|
||||
Dirty(oldNode, oldNodeComp);
|
||||
}
|
||||
|
||||
if (node is { } newNode)
|
||||
{
|
||||
var newNodeComp = Comp<CrawlableNodeComponent>(newNode);
|
||||
newNodeComp.Crawlers.Add(ent);
|
||||
Dirty(newNode, newNodeComp);
|
||||
}
|
||||
|
||||
ent.Comp.Node = node;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
public void SetHeldCrawler(Entity<NodeCrawlerMovementComponent> ent, EntityUid? held)
|
||||
{
|
||||
if (ent.Comp.HeldCrawler == held)
|
||||
return;
|
||||
|
||||
ent.Comp.HeldCrawler = held;
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Eye;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.RatKing;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.NodeCrawl;
|
||||
|
||||
/// <summary>
|
||||
/// Manages entry & exit of node crawlers into node networks
|
||||
/// </summary>
|
||||
public abstract class SharedNodeCrawlSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
[Dependency] private readonly NodeCrawlerMovementSystem _nodeCrawler = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
|
||||
private const string MoverContainer = "mover-container";
|
||||
private static readonly EntProtoId MoverProto = "DVNodeCrawlMover";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CrawlableNodeComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, NodeCrawlEnterDoAfterEvent>(OnNodeCrawlEntryDoAfter);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, NodeCrawlerArrivedAtNodeEvent>(OnArrivedAtNode);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, GetVisMaskEvent>(OnGetVisMask);
|
||||
|
||||
SubscribeLocalEvent<CrawlableNodeComponent, ComponentShutdown>(OnCrawlableShutdown);
|
||||
SubscribeLocalEvent<NodeCrawlerMovementComponent, ComponentShutdown>(OnMovementShutdown);
|
||||
SubscribeLocalEvent<NodeCrawlerComponent, ComponentShutdown>(OnCrawlerShutdown);
|
||||
|
||||
SubscribeLocalEvent<CrawlableNodeComponent, AnchorStateChangedEvent>(OnCrawlableAnchorChanged);
|
||||
|
||||
SubscribeLocalEvent<RatKingComponent, NodeCrawlerStartedCrawlingEvent>(OnRatKingStartedCrawling);
|
||||
}
|
||||
|
||||
private void OnGetVerbs(Entity<CrawlableNodeComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
var user = args.User;
|
||||
var target = args.Target;
|
||||
if (!TryComp<NodeCrawlerComponent>(user, out var nodeCrawler))
|
||||
return;
|
||||
|
||||
if (!_entityWhitelist.IsWhitelistPass(nodeCrawler.ExitNodes, target))
|
||||
return;
|
||||
|
||||
if (!args.CanAccess)
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new AlternativeVerb
|
||||
{
|
||||
Act = () => StartEntryDoAfter((user, nodeCrawler), target),
|
||||
Text = Loc.GetString("node-crawl-enter", ("target", target)),
|
||||
});
|
||||
}
|
||||
|
||||
private void StartEntryDoAfter(Entity<NodeCrawlerComponent> ent, EntityUid target)
|
||||
{
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, ent.Owner, ent.Comp.EnterDelay, new NodeCrawlEnterDoAfterEvent(), ent.Owner, target)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
};
|
||||
|
||||
_doAfter.TryStartDoAfter(doAfterArgs);
|
||||
}
|
||||
|
||||
private void OnNodeCrawlEntryDoAfter(Entity<NodeCrawlerComponent> ent, ref NodeCrawlEnterDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Target is not { } target)
|
||||
return;
|
||||
|
||||
NodeCrawl(ent, target);
|
||||
}
|
||||
|
||||
private void NodeCrawl(Entity<NodeCrawlerComponent> ent, EntityUid target)
|
||||
{
|
||||
if (!_net.IsServer)
|
||||
return;
|
||||
|
||||
var mover = Spawn(MoverProto, Transform(target).Coordinates);
|
||||
var crawler = Comp<NodeCrawlerMovementComponent>(mover);
|
||||
|
||||
var container = _container.GetContainer(mover, MoverContainer);
|
||||
_container.Insert(ent.Owner, container);
|
||||
|
||||
ent.Comp.Mover = mover;
|
||||
Dirty(ent);
|
||||
|
||||
var evt = new NodeCrawlerStartedCrawlingEvent((mover, crawler));
|
||||
RaiseLocalEvent(ent, ref evt);
|
||||
|
||||
_nodeCrawler.SetNode((mover, crawler), target);
|
||||
_nodeCrawler.SetHeldCrawler((mover, crawler), ent);
|
||||
|
||||
_mover.SetRelay(ent, mover);
|
||||
_physics.SetCanCollide(ent.Owner, false);
|
||||
_physics.SetCanCollide(mover, false);
|
||||
_eye.RefreshVisibilityMask(ent.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes this node crawler to exit its node crawl.
|
||||
/// </summary>
|
||||
/// <param name="ent">The crawler to exit node-crawl from.</param>
|
||||
public void ExitNodeCrawl(Entity<NodeCrawlerComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Mover is not { } mover)
|
||||
return;
|
||||
|
||||
ent.Comp.Mover = null;
|
||||
Dirty(ent);
|
||||
|
||||
var container = _container.GetContainer(mover, MoverContainer);
|
||||
_container.Remove(ent.Owner, container);
|
||||
|
||||
foreach (var other in _container.EmptyContainer(container))
|
||||
{
|
||||
if (!TryComp<NodeCrawlerComponent>(other, out var otherCrawler))
|
||||
continue;
|
||||
|
||||
otherCrawler.Mover = null;
|
||||
Dirty(other, otherCrawler);
|
||||
}
|
||||
|
||||
RemComp<RelayInputMoverComponent>(ent);
|
||||
if (_net.IsServer && !TerminatingOrDeleted(mover))
|
||||
QueueDel(mover); // deletion isn't predicted because client queued deletion doesn't interact well with container stuff
|
||||
|
||||
_physics.SetCanCollide(ent.Owner, true);
|
||||
_eye.RefreshVisibilityMask(ent.Owner);
|
||||
}
|
||||
|
||||
private void OnArrivedAtNode(Entity<NodeCrawlerComponent> ent, ref NodeCrawlerArrivedAtNodeEvent args)
|
||||
{
|
||||
if (!_entityWhitelist.IsWhitelistPass(ent.Comp.ExitNodes, args.Node))
|
||||
return;
|
||||
|
||||
ExitNodeCrawl(ent);
|
||||
}
|
||||
|
||||
private void OnGetVisMask(Entity<NodeCrawlerComponent> ent, ref GetVisMaskEvent args)
|
||||
{
|
||||
if (ent.Comp.Mover is null)
|
||||
return;
|
||||
|
||||
args.VisibilityMask |= (int)VisibilityFlags.Subfloor;
|
||||
}
|
||||
|
||||
private void OnCrawlableShutdown(Entity<CrawlableNodeComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
foreach (var crawler in ent.Comp.Crawlers)
|
||||
{
|
||||
var movement = Comp<NodeCrawlerMovementComponent>(crawler);
|
||||
if (movement.HeldCrawler is not { } held)
|
||||
continue;
|
||||
|
||||
_nodeCrawler.SetNode((crawler, movement), null);
|
||||
ExitNodeCrawl((held, Comp<NodeCrawlerComponent>(held)));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMovementShutdown(Entity<NodeCrawlerMovementComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (ent.Comp.Node is { } node)
|
||||
{
|
||||
var nodeComp = Comp<CrawlableNodeComponent>(node);
|
||||
nodeComp.Crawlers.Remove(ent);
|
||||
Dirty(node, nodeComp);
|
||||
}
|
||||
|
||||
if (ent.Comp.HeldCrawler is { } crawler)
|
||||
{
|
||||
ExitNodeCrawl((crawler, Comp<NodeCrawlerComponent>(crawler)));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCrawlerShutdown(Entity<NodeCrawlerComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
ExitNodeCrawl(ent);
|
||||
}
|
||||
|
||||
private void OnCrawlableAnchorChanged(Entity<CrawlableNodeComponent> ent, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (args.Anchored)
|
||||
return;
|
||||
|
||||
foreach (var crawler in ent.Comp.Crawlers)
|
||||
{
|
||||
var movement = Comp<NodeCrawlerMovementComponent>(crawler);
|
||||
if (movement.HeldCrawler is not { } held)
|
||||
continue;
|
||||
|
||||
ExitNodeCrawl((held, Comp<NodeCrawlerComponent>(held)));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRatKingStartedCrawling(Entity<RatKingComponent> ent, ref NodeCrawlerStartedCrawlingEvent args)
|
||||
{
|
||||
var entities = new HashSet<Entity<RatKingServantComponent>>();
|
||||
_entityLookup.GetEntitiesInRange(Transform(ent).Coordinates,
|
||||
ent.Comp.VentCrawlRecruitRadius,
|
||||
entities);
|
||||
|
||||
var container = _container.GetContainer(args.Mover, MoverContainer);
|
||||
|
||||
foreach (var servant in entities)
|
||||
{
|
||||
if (servant.Comp.King != ent)
|
||||
continue;
|
||||
|
||||
_container.Insert(servant.Owner, container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node-crawl-enter = Enter {$target}
|
||||
|
|
@ -928,7 +928,9 @@
|
|||
|
||||
- type: entity
|
||||
name: crab
|
||||
parent: SimpleMobBase
|
||||
parent:
|
||||
- SimpleMobBase
|
||||
- DVNodeCrawler # DeltaV - node crawling
|
||||
id: MobCrab
|
||||
description: A folk legend goes around that his claw snaps spacemen out of existence over distasteful remarks. Be polite and tolerant for your own safety.
|
||||
components:
|
||||
|
|
@ -1835,7 +1837,9 @@
|
|||
|
||||
- type: entity
|
||||
name: mouse
|
||||
parent: SimpleMobBase
|
||||
parent:
|
||||
- SimpleMobBase
|
||||
- DVNodeCrawler # DeltaV - node crawling
|
||||
id: MobMouse
|
||||
description: Squeak!
|
||||
components:
|
||||
|
|
@ -2228,7 +2232,9 @@
|
|||
|
||||
- type: entity
|
||||
name: slug
|
||||
parent: SimpleMobBase
|
||||
parent:
|
||||
- SimpleMobBase
|
||||
- DVNodeCrawler # DeltaV - node crawling
|
||||
id: MobSlug
|
||||
description: And they called this a lizard?
|
||||
components:
|
||||
|
|
@ -2280,7 +2286,9 @@
|
|||
|
||||
- type: entity
|
||||
name: frog
|
||||
parent: SimpleMobBase
|
||||
parent:
|
||||
- SimpleMobBase
|
||||
- DVNodeCrawler # DeltaV - node crawling
|
||||
id: MobFrog
|
||||
description: Hop hop hop. Lookin' moist.
|
||||
components:
|
||||
|
|
@ -2582,7 +2590,9 @@
|
|||
# I have included a snake_hiss.ogg sound file so if you want to use that be my guest
|
||||
- type: entity
|
||||
name: snake
|
||||
parent: SimpleMobBase
|
||||
parent:
|
||||
- SimpleMobBase
|
||||
- DVNodeCrawler # DeltaV - node crawling
|
||||
id: MobSnake
|
||||
description: Hissss! Bites aren't poisonous.
|
||||
components:
|
||||
|
|
@ -2727,7 +2737,9 @@
|
|||
Burn: -0.07
|
||||
|
||||
- type: entity
|
||||
parent: MobSpiderBase
|
||||
parent:
|
||||
- MobSpiderBase
|
||||
- DVNodeCrawler # DeltaV - node crawling
|
||||
id: MobSpiderAngryBase
|
||||
abstract: true
|
||||
components:
|
||||
|
|
@ -3593,7 +3605,7 @@
|
|||
|
||||
- type: entity
|
||||
name: hamster
|
||||
parent: [ SimpleMobBase, MobCombat, StripableInventoryBase]
|
||||
parent: [ SimpleMobBase, MobCombat, StripableInventoryBase, DVNodeCrawler ] # DeltaV - node crawling
|
||||
id: MobHamster
|
||||
description: A cute, fluffy, robust hamster.
|
||||
components:
|
||||
|
|
@ -3820,7 +3832,7 @@
|
|||
|
||||
- type: entity
|
||||
name: diona nymph
|
||||
parent: [SimpleMobBase, StripableInventoryBase]
|
||||
parent: [SimpleMobBase, StripableInventoryBase, DVNodeCrawler] # DeltaV - node crawling
|
||||
id: MobDionaNymph
|
||||
description: It's like a cat, only.... branch-ier.
|
||||
components:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- type: entity
|
||||
name: rat king
|
||||
id: MobRatKing
|
||||
parent: [ SimpleMobBase, MobCombat ]
|
||||
parent: [ SimpleMobBase, MobCombat, DVNodeCrawler ] # DeltaV - node crawling
|
||||
description: He's da rat. He make da roolz.
|
||||
components:
|
||||
- type: CombatMode
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
- type: entity
|
||||
name: rat servant
|
||||
id: MobRatServant
|
||||
parent: [ SimpleMobBase, MobCombat ]
|
||||
parent: [ SimpleMobBase, MobCombat, DVNodeCrawler ] # DeltaV - node crawling
|
||||
description: He's da mini rat. He don't make da roolz.
|
||||
categories: [ HideSpawnMenu ] #Must be configured to a King or the AI breaks.
|
||||
components:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- type: entity
|
||||
name: basic slime
|
||||
id: BaseMobAdultSlimes
|
||||
parent: [ SimpleMobBase, MobCombat ]
|
||||
parent: [ SimpleMobBase, MobCombat, DVNodeCrawler ] # DeltaV - node crawling
|
||||
abstract: true
|
||||
description: It looks so much like jelly. I wonder what it tastes like?
|
||||
components:
|
||||
|
|
|
|||
|
|
@ -439,6 +439,7 @@
|
|||
- type: GuideHelp
|
||||
guides:
|
||||
- GasCanisters
|
||||
- type: CrawlableNode # DeltaV
|
||||
|
||||
- type: entity
|
||||
parent: GasVentPump
|
||||
|
|
@ -505,6 +506,7 @@
|
|||
- type: GuideHelp
|
||||
guides:
|
||||
- AirVent
|
||||
- type: NodeCrawlVentAccess # DeltaV
|
||||
|
||||
- type: entity
|
||||
parent: [ BaseMachine, ConstructibleMachine ]
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@
|
|||
Primary: Structures/Piping/Atmospherics/pipe.rsi
|
||||
Secondary: Structures/Piping/Atmospherics/pipe_alt1.rsi
|
||||
Tertiary: Structures/Piping/Atmospherics/pipe_alt2.rsi
|
||||
- type: CrawlableNode # DeltaV
|
||||
|
||||
#Note: The PipeDirection of the PipeNode should be the south-facing version, because the entity starts at an angle of 0 (south)
|
||||
|
||||
|
|
@ -413,3 +414,4 @@
|
|||
- type: Tag
|
||||
tags:
|
||||
- Unstackable
|
||||
- type: CrawlableNode # DeltaV
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@
|
|||
Primary: GasVentPump
|
||||
Secondary: GasVentPumpAlt1
|
||||
Tertiary: GasVentPumpAlt2
|
||||
- type: NodeCrawlVentAccess # DeltaV
|
||||
|
||||
- type: entity
|
||||
parent: GasUnaryBase
|
||||
|
|
@ -120,6 +121,7 @@
|
|||
Primary: GasPassiveVent
|
||||
Secondary: GasPassiveVentAlt1
|
||||
Tertiary: GasPassiveVentAlt2
|
||||
- type: NodeCrawlVentAccess # DeltaV
|
||||
|
||||
- type: entity
|
||||
parent: [GasUnaryBase, AirSensorBase]
|
||||
|
|
@ -184,6 +186,7 @@
|
|||
Primary: GasVentScrubber
|
||||
Secondary: GasVentScrubberAlt1
|
||||
Tertiary: GasVentScrubberAlt2
|
||||
- type: NodeCrawlVentAccess # DeltaV
|
||||
|
||||
- type: entity
|
||||
parent: GasUnaryBase
|
||||
|
|
@ -233,6 +236,7 @@
|
|||
Primary: GasOutletInjector
|
||||
Secondary: GasOutletInjectorAlt1
|
||||
Tertiary: GasOutletInjectorAlt2
|
||||
- type: NodeCrawlVentAccess # DeltaV
|
||||
|
||||
- type: entity
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
- type: entity
|
||||
abstract: true
|
||||
id: DVNodeCrawler
|
||||
components:
|
||||
- type: NodeCrawler
|
||||
revealedComponents:
|
||||
- Content.Shared.Atmos.Components.PipeAppearanceComponent
|
||||
exitNodes:
|
||||
components:
|
||||
- NodeCrawlVentAccess
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
- type: entity
|
||||
categories: [ HideSpawnMenu ]
|
||||
id: DVNodeCrawlMover
|
||||
components:
|
||||
- type: InputMover
|
||||
- type: NodeCrawlerMovement
|
||||
- type: Physics
|
||||
bodyType: KinematicController
|
||||
canCollide: false
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
mover-container: !type:Container
|
||||
- type: LagCompensation
|
||||
- type: MovementSpeedModifier
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.35
|
||||
density: 15
|
||||
layer:
|
||||
- GhostImpassable
|
||||
- type: Input
|
||||
context: "human"
|
||||
Loading…
Reference in New Issue