Ползанье по трубам (#243)

* ventcrawling

* codesorting

* namespacekhem

* khem2

* minifixes
This commit is contained in:
Zekins
2025-11-08 18:25:49 +03:00
committed by GitHub
parent 1e7fb462d6
commit 649e15a853
49 changed files with 1354 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
using Content.Client.UserInterface.Systems.Sandbox;
using Content.Shared.Atmos.Components; // Corvax-Wega-VentCrawling
using Content.Shared.SubFloor;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
@@ -13,6 +14,7 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
[Dependency] private readonly IUserInterfaceManager _ui = default!;
private bool _showAll;
private bool _showVentPipe; // Corvax-Wega-VentCrawling
[ViewVariables(VVAccess.ReadWrite)]
public bool ShowAll
@@ -32,6 +34,20 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
}
}
// Corvax-Wega-VentCrawling-start
[ViewVariables(VVAccess.ReadWrite)]
public bool ShowVentPipe
{
get => _showVentPipe;
set
{
if (_showVentPipe == value) return;
_showVentPipe = value;
UpdateAll();
}
}
// Corvax-Wega-VentCrawling-end
public override void Initialize()
{
base.Initialize();
@@ -63,7 +79,13 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
scannerRevealed &= !ShowAll; // no transparency for show-subfloor mode.
var revealed = !covered || ShowAll || scannerRevealed;
// Corvax-Wega-VentCrawling-start
var showVentPipe = false;
if (HasComp<PipeAppearanceComponent>(uid))
showVentPipe = ShowVentPipe;
// Corvax-Wega-VentCrawling-end
var revealed = !covered || ShowAll || scannerRevealed || showVentPipe; // Corvax-Wega-VentCrawling-Edit
// set visibility & color of each layer
foreach (var layer in args.Sprite.AllLayers)

View File

@@ -0,0 +1,53 @@
using Content.Client.SubFloor;
using Content.Shared.VentCraw;
using Robust.Client.Player;
using Robust.Shared.Timing;
namespace Content.Client.VentCraw;
public sealed class VentCrawVisionSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SubFloorHideSystem _subFloorHideSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VentCrawlerComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<VentCrawlerComponent, ComponentShutdown>(OnComponentShutdown);
}
private void OnComponentStartup(EntityUid uid, VentCrawlerComponent component, ComponentStartup args)
{
UpdateVision(component.InTube);
}
private void OnComponentShutdown(EntityUid uid, VentCrawlerComponent component, ComponentShutdown args)
{
UpdateVision(false);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (!_timing.IsFirstTimePredicted)
return;
var player = _player.LocalSession?.AttachedEntity;
if (player == null)
return;
if (!TryComp<VentCrawlerComponent>(player, out var ventCrawler))
return;
UpdateVision(ventCrawler.InTube);
}
private void UpdateVision(bool inTube)
{
_subFloorHideSystem.ShowVentPipe = inTube;
}
}

View File

@@ -0,0 +1,35 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.NodeContainer;
using Content.Shared.VentCraw.Components;
namespace Content.Server.VentCraw;
public sealed class BeingVentCrawSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BeingVentCrawComponent, AtmosExposedGetAirEvent>(OnGetAir);
}
private void OnGetAir(EntityUid uid, BeingVentCrawComponent component, ref AtmosExposedGetAirEvent args)
{
if (!TryComp<VentCrawHolderComponent>(component.Holder, out var holder) || holder.CurrentTube == null)
return;
if (!TryComp(holder.CurrentTube.Value, out NodeContainerComponent? nodeContainer))
return;
foreach (var (_, node) in nodeContainer.Nodes)
{
if (node is PipeNode pipe)
{
args.Gas = pipe.Air;
args.Handled = true;
return;
}
}
}
}

View File

@@ -0,0 +1,469 @@
using System.Linq;
using Content.Server.Construction.Completions;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Popups;
using Content.Shared.Atmos;
using Content.Shared.Database;
using Content.Shared.Destructible;
using Content.Shared.DoAfter;
using Content.Shared.Movement.Systems;
using Content.Shared.NodeContainer;
using Content.Shared.Tools.Components;
using Content.Shared.VentCraw;
using Content.Shared.VentCraw.Components;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.VentCraw;
public sealed class VentCrawTubeSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedMoverController _mover = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly VentCrawableSystem _ventCrawableSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VentCrawTubeComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<VentCrawTubeComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<VentCrawTubeComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<VentCrawTubeComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<VentCrawTubeComponent, ConstructionBeforeDeleteEvent>(OnDeconstruct);
SubscribeLocalEvent<VentCrawTubeComponent, AnchorStateChangedEvent>(OnAnchorChange);
SubscribeLocalEvent<VentCrawTubeComponent, BreakageEventArgs>(OnBreak);
SubscribeLocalEvent<VentCrawBendComponent, GetVentCrawsConnectableDirectionsEvent>(OnGetBendConnectableDirections);
SubscribeLocalEvent<VentCrawEntryComponent, GetVentCrawsConnectableDirectionsEvent>(OnGetEntryConnectableDirections);
SubscribeLocalEvent<VentCrawEntryComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbedVerb);
SubscribeLocalEvent<VentCrawJunctionComponent, GetVentCrawsConnectableDirectionsEvent>(OnGetJunctionConnectableDirections);
SubscribeLocalEvent<VentCrawManifoldComponent, GetVerbsEvent<Verb>>(OnGetManifoldVerbs);
SubscribeLocalEvent<VentCrawTransitComponent, GetVentCrawsConnectableDirectionsEvent>(OnGetTransitConnectableDirections);
SubscribeLocalEvent<VentCrawlerComponent, EnterVentDoAfterEvent>(OnDoAfterEnterTube);
}
public EntityUid? NextTubeFor(EntityUid target, Direction nextDirection, int pipeLayer = 0, VentCrawTubeComponent? targetTube = null)
{
if (!Resolve(target, ref targetTube))
return null;
var oppositeDirection = nextDirection.GetOpposite();
var xform = Transform(target);
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
return null;
var currentTile = _mapSystem.CoordinatesToTile(xform.GridUid.Value, grid, xform.Coordinates);
var offset = nextDirection.ToIntVec();
var targetTile = currentTile + offset;
var anchoredEntities = _mapSystem.GetAnchoredEntities(xform.GridUid.Value, grid, targetTile);
foreach (var entity in anchoredEntities)
{
if (!TryComp(entity, out VentCrawTubeComponent? tube))
continue;
if (ArePipesActuallyConnected(target, entity, nextDirection, oppositeDirection, pipeLayer))
return entity;
}
return null;
}
public bool TryInsert(EntityUid uid, EntityUid entity, VentCrawEntryComponent? entry = null)
{
if (!Resolve(uid, ref entry))
return false;
if (!TryComp<VentCrawlerComponent>(entity, out var ventCrawlerComponent))
return false;
var xform = Transform(uid);
var mapPos = _transformSystem.GetMapCoordinates(uid, xform: xform);
var holder = Spawn(VentCrawEntryComponent.HolderPrototypeId, mapPos);
var holderComponent = Comp<VentCrawHolderComponent>(holder);
_ventCrawableSystem.TryInsert(holder, entity, holderComponent);
_mover.SetRelay(entity, holder);
ventCrawlerComponent.InTube = true;
Dirty(entity, ventCrawlerComponent);
return _ventCrawableSystem.EnterTube(holder, uid, holderComponent);
}
private void OnComponentInit(EntityUid uid, VentCrawTubeComponent tube, ComponentInit args)
{
tube.Contents = _containerSystem.EnsureContainer<Container>(uid, tube.ContainerId);
}
private void OnComponentRemove(EntityUid uid, VentCrawTubeComponent tube, ComponentRemove args)
{
DisconnectTube(uid, tube);
}
private void OnShutdown(EntityUid uid, VentCrawTubeComponent tube, ComponentShutdown args)
{
DisconnectTube(uid, tube);
}
private void OnStartup(EntityUid uid, VentCrawTubeComponent component, ComponentStartup args)
{
UpdateAnchored(uid, component, Transform(uid).Anchored);
}
private void OnDeconstruct(EntityUid uid, VentCrawTubeComponent component, ConstructionBeforeDeleteEvent args)
{
DisconnectTube(uid, component);
}
private void OnBreak(EntityUid uid, VentCrawTubeComponent component, BreakageEventArgs args)
{
DisconnectTube(uid, component);
}
private void OnAnchorChange(EntityUid uid, VentCrawTubeComponent component, ref AnchorStateChangedEvent args)
{
UpdateAnchored(uid, component, args.Anchored);
}
private void OnGetBendConnectableDirections(EntityUid uid, VentCrawBendComponent component, ref GetVentCrawsConnectableDirectionsEvent args)
{
var direction = Transform(uid).LocalRotation;
var side = new Angle(MathHelper.DegreesToRadians(direction.Degrees - 90));
args.Connectable = new[] { direction.GetDir(), side.GetDir() };
}
private void OnGetEntryConnectableDirections(EntityUid uid, VentCrawEntryComponent component, ref GetVentCrawsConnectableDirectionsEvent args)
{
// Entry points should connect in all directions for easy exit
args.Connectable = new[] {
Direction.North, Direction.South, Direction.East, Direction.West,
Direction.NorthEast, Direction.NorthWest, Direction.SouthEast, Direction.SouthWest
};
}
private void OnGetJunctionConnectableDirections(EntityUid uid, VentCrawJunctionComponent component, ref GetVentCrawsConnectableDirectionsEvent args)
{
var direction = Transform(uid).LocalRotation;
args.Connectable = component.Degrees
.Select(degree => new Angle(degree.Theta + direction.Theta).GetDir())
.ToArray();
}
private void OnGetTransitConnectableDirections(EntityUid uid, VentCrawTransitComponent component, ref GetVentCrawsConnectableDirectionsEvent args)
{
var rotation = Transform(uid).LocalRotation;
var opposite = new Angle(rotation.Theta + Math.PI);
args.Connectable = new[] { rotation.GetDir(), opposite.GetDir() };
}
private void AddClimbedVerb(EntityUid uid, VentCrawEntryComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!TryComp<VentCrawlerComponent>(args.User, out var ventCrawlerComponent))
return;
if (TryComp(uid, out TransformComponent? transformComponent) && !transformComponent.Anchored)
return;
var isInside = ventCrawlerComponent.InTube && TryComp<BeingVentCrawComponent>(args.User, out var beingVentCraw)
&& TryComp<VentCrawHolderComponent>(beingVentCraw.Holder, out var holder) && holder.CurrentTube == uid;
AlternativeVerb verb = new()
{
Act = isInside ?
() => TryExit(uid, args.User) :
() => TryEnter(uid, args.User),
Text = isInside ?
Loc.GetString("vent-craw-verb-exit") :
Loc.GetString("vent-craw-verb-enter"),
Icon = isInside ?
new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")) :
new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/insert.svg.192dpi.png"))
};
args.Verbs.Add(verb);
}
private void OnGetManifoldVerbs(EntityUid uid, VentCrawManifoldComponent component, GetVerbsEvent<Verb> args)
{
if (!TryComp<VentCrawlerComponent>(args.User, out var ventCrawler) || !ventCrawler.InTube)
return;
if (!TryComp<BeingVentCrawComponent>(args.User, out var beingVentCraw) ||
!TryComp<VentCrawHolderComponent>(beingVentCraw.Holder, out var holder) ||
holder.CurrentTube != uid)
return;
if (!TryComp<VentCrawJunctionComponent>(uid, out var junction))
return;
var rotation = Transform(uid).LocalRotation;
var availableDirs = junction.Degrees
.Select(degree => new Angle(degree.Theta + rotation.Theta).GetDir())
.ToList();
for (int layer = 0; layer < 3; layer++)
{
if (layer == holder.PreviousPipeLayer)
continue;
foreach (var direction in availableDirs)
{
var nextTube = NextTubeFor(uid, direction, layer);
if (nextTube == null)
continue;
var layerName = layer switch
{
0 => Loc.GetString("vent-craw-layer-primary"),
1 => Loc.GetString("vent-craw-layer-secondary"),
2 => Loc.GetString("vent-craw-layer-tertiary"),
_ => Loc.GetString("vent-craw-unknown")
};
var currentLayer = layer;
var v = new Verb
{
Priority = 1,
Category = VerbCategory.SelectType,
Text = $"{GetDirectionName(direction)} ({layerName})",
Impact = LogImpact.Low,
DoContactInteraction = true,
Act = () =>
{
TryMoveToDirection(uid, args.User, direction, holder, currentLayer);
}
};
args.Verbs.Add(v);
}
}
}
private void OnDoAfterEnterTube(EntityUid uid, VentCrawlerComponent component, EnterVentDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Target == null)
return;
TryInsert(args.Target.Value, args.User);
args.Handled = true;
}
private void TryExit(EntityUid uid, EntityUid user)
{
if (!TryComp<BeingVentCrawComponent>(user, out var beingVentCraw) ||
!TryComp<VentCrawHolderComponent>(beingVentCraw.Holder, out var holder))
return;
if (holder.CurrentTube != uid)
return;
if (TryComp<WeldableComponent>(uid, out var weldableComponent) && weldableComponent.IsWelded)
{
_popup.PopupEntity(Loc.GetString("entity-storage-component-welded-shut-message"), user);
return;
}
_ventCrawableSystem.ExitVentCraws(beingVentCraw.Holder, holder);
}
private void TryEnter(EntityUid uid, EntityUid user, VentCrawlerComponent? crawler = null)
{
if (!Resolve(user, ref crawler))
return;
if (TryComp<WeldableComponent>(uid, out var weldableComponent))
{
if (weldableComponent.IsWelded)
{
_popup.PopupEntity(Loc.GetString("entity-storage-component-welded-shut-message"), user);
return;
}
}
var args = new DoAfterArgs(EntityManager, user, crawler.EnterDelay, new EnterVentDoAfterEvent(), user, uid, user)
{
BreakOnMove = true,
BreakOnDamage = false
};
_doAfterSystem.TryStartDoAfter(args);
}
private void TryMoveToDirection(EntityUid manifoldUid, EntityUid user, Direction direction, VentCrawHolderComponent holder, int pipeLayer)
{
if (holder.CurrentTube != manifoldUid)
return;
if (!TryComp<BeingVentCrawComponent>(user, out var beingVentCraw))
return;
var nextTube = NextTubeFor(manifoldUid, direction, pipeLayer);
if (nextTube == null)
return;
holder.PreviousPipeLayer = pipeLayer;
if (_ventCrawableSystem.EnterTube(beingVentCraw.Holder, nextTube.Value, holder))
{
if (_gameTiming.CurTime > holder.LastCrawl + VentCrawableSystem.CrawlDelay)
{
holder.LastCrawl = _gameTiming.CurTime;
_audioSystem.PlayPvs(holder.CrawlSound, user);
}
}
}
private void UpdateAnchored(EntityUid uid, VentCrawTubeComponent component, bool anchored)
{
if (anchored)
{
ConnectTube(uid, component);
}
else
{
DisconnectTube(uid, component);
}
}
private bool ArePipesActuallyConnected(EntityUid pipeA, EntityUid pipeB, Direction directionFromA, Direction directionFromB, int pipeLayer = 0)
{
if (!TryComp<NodeContainerComponent>(pipeA, out var nodeContainerA) ||
!TryComp<NodeContainerComponent>(pipeB, out var nodeContainerB))
return false;
var nodesA = nodeContainerA.Nodes.Values;
var nodesB = nodeContainerB.Nodes.Values;
foreach (var nodeA in nodesA)
{
if (nodeA is not PipeNode pipeNodeA)
continue;
if ((int)pipeNodeA.CurrentPipeLayer != pipeLayer)
continue;
if (!PipeDirectionHasDirection(pipeNodeA.CurrentPipeDirection, directionFromA))
continue;
foreach (var nodeB in nodesB)
{
if (nodeB is not PipeNode pipeNodeB)
continue;
if ((int)pipeNodeB.CurrentPipeLayer != pipeLayer)
continue;
if (!PipeDirectionHasDirection(pipeNodeB.CurrentPipeDirection, directionFromB))
continue;
if (pipeNodeA.NodeGroup != null && pipeNodeB.NodeGroup != null &&
pipeNodeA.NodeGroup == pipeNodeB.NodeGroup)
return true;
if (WouldPipesNormallyConnect(pipeNodeA, pipeNodeB, directionFromA, directionFromB))
return true;
}
}
return false;
}
/// <summary>
/// Checks if a PipeDirection has a specific Direction
/// </summary>
private bool PipeDirectionHasDirection(PipeDirection pipeDirection, Direction direction)
{
var targetPipeDir = DirectionToPipeDirection(direction);
return (pipeDirection & targetPipeDir) == targetPipeDir;
}
/// <summary>
/// Fallback method to check if pipes would connect based on their directions and layers
/// </summary>
private bool WouldPipesNormallyConnect(PipeNode pipeA, PipeNode pipeB, Direction directionFromA, Direction directionFromB)
{
// Check if they have the same node group ID
if (pipeA.NodeGroupID != pipeB.NodeGroupID)
return false;
// Check if they're on the same pipe layer
if (pipeA.CurrentPipeLayer != pipeB.CurrentPipeLayer)
return false;
return PipeDirectionHasDirection(pipeA.CurrentPipeDirection, directionFromA)
&& PipeDirectionHasDirection(pipeB.CurrentPipeDirection, directionFromB);
}
/// <summary>
/// Convert Direction to PipeDirection
/// </summary>
private PipeDirection DirectionToPipeDirection(Direction direction)
{
return direction switch
{
Direction.North => PipeDirection.North,
Direction.South => PipeDirection.South,
Direction.East => PipeDirection.East,
Direction.West => PipeDirection.West,
Direction.NorthEast => PipeDirection.North | PipeDirection.East,
Direction.NorthWest => PipeDirection.North | PipeDirection.West,
Direction.SouthEast => PipeDirection.South | PipeDirection.East,
Direction.SouthWest => PipeDirection.South | PipeDirection.West,
_ => PipeDirection.None
};
}
private static void ConnectTube(EntityUid _, VentCrawTubeComponent tube)
{
if (tube.Connected)
return;
tube.Connected = true;
}
private void DisconnectTube(EntityUid _, VentCrawTubeComponent tube)
{
if (!tube.Connected)
return;
tube.Connected = false;
var query = GetEntityQuery<VentCrawHolderComponent>();
foreach (var entity in tube.Contents.ContainedEntities.ToArray())
{
if (query.TryGetComponent(entity, out var holder))
_ventCrawableSystem.ExitVentCraws(entity, holder);
}
}
private string GetDirectionName(Direction direction)
{
return direction switch
{
Direction.North => Loc.GetString("vent-craw-direction-north"),
Direction.South => Loc.GetString("vent-craw-direction-south"),
Direction.East => Loc.GetString("vent-craw-direction-east"),
Direction.West => Loc.GetString("vent-craw-direction-west"),
Direction.NorthEast => Loc.GetString("vent-craw-direction-northeast"),
Direction.NorthWest => Loc.GetString("vent-craw-direction-northwest"),
Direction.SouthEast => Loc.GetString("vent-craw-direction-southeast"),
Direction.SouthWest => Loc.GetString("vent-craw-direction-southwest"),
_ => Loc.GetString("vent-craw-unknown")
};
}
}

View File

@@ -0,0 +1,340 @@
using System.Linq;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Body.Components;
using Content.Shared.Item;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.NodeContainer;
using Content.Shared.Tools.Components;
using Content.Shared.VentCraw;
using Content.Shared.VentCraw.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Content.Server.VentCraw;
public sealed class VentCrawableSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly VentCrawTubeSystem _ventCrawTubeSystem = default!;
public static readonly TimeSpan CrawlDelay = TimeSpan.FromSeconds(0.5);
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VentCrawHolderComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<VentCrawHolderComponent, MoveInputEvent>(OnMoveInput);
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<VentCrawHolderComponent>();
while (query.MoveNext(out var uid, out var holder))
{
if (holder.CurrentDirection == Direction.Invalid)
continue;
var currentTube = holder.CurrentTube;
if (currentTube == null)
continue;
if (holder.IsMoving && holder.NextTube == null)
{
if (HasComp<VentCrawManifoldComponent>(currentTube.Value))
{
holder.NextTube = null;
holder.CurrentDirection = Direction.Invalid;
continue;
}
var nextTube = _ventCrawTubeSystem.NextTubeFor(currentTube.Value, holder.CurrentDirection, holder.PreviousPipeLayer);
if (nextTube != null)
{
if (!EntityManager.EntityExists(holder.CurrentTube))
{
ExitVentCraws(uid, holder);
continue;
}
holder.NextTube = nextTube;
holder.StartingTime = holder.Speed;
holder.TimeLeft = holder.Speed;
}
else
{
var ev = new GetVentCrawsConnectableDirectionsEvent();
RaiseLocalEvent(currentTube.Value, ref ev);
if (ev.Connectable.Contains(holder.CurrentDirection))
{
if (HasComp<VentCrawEntryComponent>(currentTube.Value))
{
ExitVentCraws(uid, holder);
continue;
}
var oppositeDirection = holder.CurrentDirection.GetOpposite();
var canGoBack = _ventCrawTubeSystem.NextTubeFor(currentTube.Value, oppositeDirection) != null;
if (!canGoBack)
{
ExitVentCraws(uid, holder);
continue;
}
}
holder.NextTube = null;
holder.CurrentDirection = Direction.Invalid;
}
}
if (holder.NextTube != null && holder.TimeLeft > 0)
{
var time = frameTime;
if (time > holder.TimeLeft)
time = holder.TimeLeft;
var progress = 1 - holder.TimeLeft / holder.StartingTime;
var origin = Transform(currentTube.Value).Coordinates;
var target = Transform(holder.NextTube.Value).Coordinates;
var newPosition = (target.Position - origin.Position) * progress;
var newCoords = origin.Offset(newPosition);
_xformSystem.SetCoordinates(uid, _xformSystem.WithEntityId(newCoords, currentTube.Value));
holder.TimeLeft -= time;
}
else if (holder.NextTube != null && holder.TimeLeft <= 0)
{
var tubeComp = Comp<VentCrawTubeComponent>(currentTube.Value);
_containerSystem.Remove(uid, tubeComp.Contents, force: true);
if (holder.FirstEntry)
holder.FirstEntry = false;
if (HasComp<VentCrawEntryComponent>(holder.NextTube.Value))
{
var welded = false;
if (TryComp<WeldableComponent>(holder.NextTube.Value, out var weldableComponent))
welded = weldableComponent.IsWelded;
if (!welded)
{
ExitVentCraws(uid, holder);
continue;
}
else
{
_containerSystem.Insert(uid, tubeComp.Contents);
holder.NextTube = null;
holder.CurrentDirection = Direction.Invalid;
continue;
}
}
if (_gameTiming.CurTime > holder.LastCrawl + CrawlDelay)
{
holder.LastCrawl = _gameTiming.CurTime;
_audioSystem.PlayPvs(holder.CrawlSound, uid);
}
if (EntityManager.EntityExists(holder.NextTube.Value))
{
var success = EnterTube(uid, holder.NextTube.Value, holder);
if (!success)
{
_containerSystem.Insert(uid, tubeComp.Contents);
holder.NextTube = null;
holder.CurrentDirection = Direction.Invalid;
}
else
{
holder.NextTube = null;
}
}
else
{
ExitVentCraws(uid, holder);
}
}
}
}
public bool EnterTube(EntityUid holderUid, EntityUid toUid, VentCrawHolderComponent? holder = null,
VentCrawTubeComponent? to = null)
{
if (!Resolve(holderUid, ref holder))
return false;
if (holder.IsExitingVentCraws)
return false;
if (!Resolve(toUid, ref to))
{
ExitVentCraws(holderUid, holder);
return false;
}
foreach (var ent in holder.Container.ContainedEntities)
{
var comp = EnsureComp<BeingVentCrawComponent>(ent);
comp.Holder = holderUid;
}
if (!_containerSystem.Insert(holderUid, to.Contents))
{
ExitVentCraws(holderUid, holder);
return false;
}
if (holder.CurrentTube != null)
{
holder.PreviousTube = holder.CurrentTube;
holder.PreviousDirection = holder.CurrentDirection;
}
holder.CurrentTube = toUid;
if (TryComp<NodeContainerComponent>(toUid, out var nodeContainer)
&& !HasComp<VentCrawManifoldComponent>(toUid))
{
var firstPipeNode = nodeContainer.Nodes.Values.OfType<PipeNode>().FirstOrDefault();
if (firstPipeNode != null)
{
holder.PreviousPipeLayer = (int)firstPipeNode.CurrentPipeLayer;
}
}
return true;
}
public void ExitVentCraws(EntityUid uid, VentCrawHolderComponent? holder = null)
{
if (!Exists(uid))
return;
if (!Resolve(uid, ref holder))
return;
if (holder.IsExitingVentCraws)
return;
holder.IsExitingVentCraws = true;
foreach (var entity in holder.Container.ContainedEntities.ToArray())
{
RemComp<BeingVentCrawComponent>(entity);
_containerSystem.Remove(entity, holder.Container, force: true);
if (TryComp<VentCrawlerComponent>(entity, out var ventCrawlerComponent))
{
ventCrawlerComponent.InTube = false;
Dirty(entity, ventCrawlerComponent);
}
var xform = Transform(entity);
_xformSystem.AttachToGridOrMap(entity, xform);
if (TryComp<PhysicsComponent>(entity, out var physics))
_physicsSystem.WakeBody(entity, body: physics);
}
EntityManager.DeleteEntity(uid);
}
public bool TryInsert(EntityUid uid, EntityUid toInsert, VentCrawHolderComponent? holder = null)
{
if (!Resolve(uid, ref holder))
return false;
if (!CanInsert(uid, toInsert, holder))
return false;
if (!_containerSystem.Insert(toInsert, holder.Container))
return false;
if (TryComp<PhysicsComponent>(toInsert, out var physBody))
_physicsSystem.SetCanCollide(toInsert, false, body: physBody);
return true;
}
private bool CanInsert(EntityUid uid, EntityUid toInsert, VentCrawHolderComponent? holder = null)
{
if (!Resolve(uid, ref holder))
return false;
return HasComp<ItemComponent>(toInsert) || HasComp<BodyComponent>(toInsert);
}
private void OnComponentStartup(EntityUid uid, VentCrawHolderComponent holder, ComponentStartup args)
{
holder.Container = _containerSystem.EnsureContainer<Container>(uid, nameof(VentCrawHolderComponent));
}
private void OnMoveInput(EntityUid uid, VentCrawHolderComponent component, ref MoveInputEvent args)
{
if (!EntityManager.EntityExists(component.CurrentTube))
{
ExitVentCraws(uid, component);
return;
}
component.IsMoving = (args.OldMovement & MoveButtons.AnyDirection) != MoveButtons.None;
if (component.IsMoving)
{
var newDirection = GetDirectionFromMoveButtons(args.OldMovement);
if (HasComp<VentCrawManifoldComponent>(component.CurrentTube)
&& component.CurrentDirection == Direction.Invalid)
{
var nextTube = _ventCrawTubeSystem.NextTubeFor(component.CurrentTube.Value, newDirection, component.PreviousPipeLayer);
if (nextTube != null)
{
component.CurrentDirection = newDirection;
component.NextTube = nextTube;
component.StartingTime = component.Speed;
component.TimeLeft = component.Speed;
}
}
else
{
component.CurrentDirection = newDirection;
}
}
else
{
component.CurrentDirection = Direction.Invalid;
}
}
private Direction GetDirectionFromMoveButtons(MoveButtons buttons)
{
var hasUp = (buttons & MoveButtons.Up) != MoveButtons.None;
var hasDown = (buttons & MoveButtons.Down) != MoveButtons.None;
var hasLeft = (buttons & MoveButtons.Left) != MoveButtons.None;
var hasRight = (buttons & MoveButtons.Right) != MoveButtons.None;
if (hasUp && hasRight) return Direction.NorthEast;
if (hasUp && hasLeft) return Direction.NorthWest;
if (hasDown && hasRight) return Direction.SouthEast;
if (hasDown && hasLeft) return Direction.SouthWest;
if (hasUp) return Direction.North;
if (hasDown) return Direction.South;
if (hasLeft) return Direction.West;
if (hasRight) return Direction.East;
return Direction.Invalid;
}
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class BeingVentCrawComponent : Component
{
[ViewVariables]
public EntityUid Holder;
}

View File

@@ -0,0 +1,8 @@
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class VentCrawBendComponent : Component
{
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class VentCrawEntryComponent : Component
{
public const string HolderPrototypeId = "VentCrawHolder";
}

View File

@@ -0,0 +1,51 @@
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class VentCrawHolderComponent : Component
{
public Container Container = null!;
[ViewVariables]
public float StartingTime { get; set; }
[ViewVariables]
public float TimeLeft { get; set; }
public bool IsMoving = false;
[ViewVariables]
public EntityUid? PreviousTube { get; set; }
[ViewVariables]
public EntityUid? NextTube { get; set; }
[ViewVariables]
public Direction PreviousDirection { get; set; } = Direction.Invalid;
[ViewVariables]
public int PreviousPipeLayer { get; set; } = 0;
[ViewVariables]
public EntityUid? CurrentTube { get; set; }
[ViewVariables]
public bool FirstEntry { get; set; }
[ViewVariables]
public Direction CurrentDirection { get; set; } = Direction.Invalid;
[ViewVariables]
public bool IsExitingVentCraws { get; set; }
public TimeSpan LastCrawl;
[DataField("crawlSound")]
public SoundCollectionSpecifier CrawlSound { get; set; } = new("VentClaw", AudioParams.Default.WithVolume(5f));
[DataField("speed")]
public float Speed = 0.15f;
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class VentCrawJunctionComponent : Component
{
/// <summary>
/// The angles to connect to.
/// </summary>
[DataField("degrees")]
public List<Angle> Degrees = new();
}

View File

@@ -0,0 +1,6 @@
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class VentCrawManifoldComponent : Component;

View File

@@ -0,0 +1,8 @@
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class VentCrawTransitComponent : Component
{
}

View File

@@ -0,0 +1,23 @@
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
namespace Content.Shared.VentCraw.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class VentCrawTubeComponent : Component
{
[DataField("containerId")]
public string ContainerId { get; set; } = "VentCrawTube";
[ViewVariables]
public bool Connected;
[ViewVariables]
public Container Contents { get; set; } = default!;
}
[ByRefEvent]
public record struct GetVentCrawsConnectableDirectionsEvent
{
public Direction[] Connectable;
}

View File

@@ -0,0 +1,20 @@
using Content.Shared.DoAfter;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.VentCraw;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class VentCrawlerComponent : Component
{
[ViewVariables, AutoNetworkedField]
public bool InTube = false;
[DataField("enterDelay")]
public float EnterDelay = 2.5f;
}
[Serializable, NetSerializable]
public sealed partial class EnterVentDoAfterEvent : SimpleDoAfterEvent
{
}

View File

@@ -0,0 +1,17 @@
vent-craw-verb-exit = Выйти из вентиляции
vent-craw-verb-enter = Войти в вентиляцию
vent-craw-direction-north = север
vent-craw-direction-south = юг
vent-craw-direction-east = восток
vent-craw-direction-west = запад
vent-craw-direction-northeast = северо-восток
vent-craw-direction-northwest = северо-запад
vent-craw-direction-southeast = юго-восток
vent-craw-direction-southwest = юго-запад
vent-craw-layer-primary = Основной
vent-craw-layer-secondary = Вторичный
vent-craw-layer-tertiary = Третичный
vent-craw-unknown = Неизвестно

View File

@@ -441,6 +441,7 @@
damage:
types:
Piercing: 0
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
name: glockroach
@@ -1004,6 +1005,7 @@
task: RuminantCompound
- type: Body
prototype: AnimalHemocyanin
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
name: goat
@@ -1446,6 +1448,7 @@
effects:
- !type:WashCreamPie
- type: Crawler
- type: VentCrawler # Corvax-Wega-VentCrawling
# Corvax-Wega-Surgery-start
- type: Operated
raceGraph: PrimateSurgery
@@ -1917,6 +1920,7 @@
behaviors:
- !type:GibBehavior
recursive: false
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
parent: MobMouse
@@ -2153,6 +2157,7 @@
interactSuccessSpawn: EffectHearts
- type: Bloodstream
bloodMaxVolume: 50
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
name: frog
@@ -2221,6 +2226,7 @@
behaviors:
- !type:GibBehavior
recursive: false
- type: VentCrawler # Corvax-Wega-VentCrawling
# Would be cool to have some functionality for the parrot to be able to sit on stuff
- type: entity
@@ -2496,6 +2502,7 @@
- type: Damageable
damageContainer: Biological
damageModifierSet: Scale
- type: VentCrawler # Corvax-Wega-VentCrawling
# Code unique spider prototypes or combine them all into one spider and get a
# random sprite state when you spawn it.
@@ -2589,6 +2596,7 @@
Brute: -0.07
Burn: -0.07
# Corvax-Wega-start
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: NaturalNightVision # Corvax-Wega-NightVision
tintColor: "#931a32"
visible: true
@@ -3084,6 +3092,7 @@
- type: Grammar
attributes:
gender: epicene
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
name: cat
@@ -3589,6 +3598,7 @@
behaviors:
- !type:GibBehavior
recursive: false
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
name: pig

View File

@@ -144,6 +144,7 @@
- type: Sharp
- type: TTS # Corvax-TTS
voice: Rat
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
id: MobRatKingBuff
@@ -221,7 +222,7 @@
- map: [ "enum.DamageStateVisualLayers.BaseUnshaded"]
state: eyes
shader: unshaded
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: SpriteMovement
movementLayers:
movement:

View File

@@ -110,6 +110,7 @@
speechSounds: Slime
- type: TypingIndicator
proto: slime
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
name: basic slime

View File

@@ -271,6 +271,7 @@
- type: TypingIndicator
proto: spider
- type: NaturalNightVision # Corvax-Wega-NightVision
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
id: MobSpiderSpaceSalvage
@@ -522,6 +523,7 @@
visualType: Large
messages: [ "snail-hurt-by-salt-popup" ]
probability: 0.66
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
parent: MobSnail

View File

@@ -89,6 +89,7 @@
- type: Speech
speechVerb: SmallMob
- type: NonSpreaderZombie
- type: VentCrawler # Corvax-Wega-VentCrawling
- type: entity
id: MobTickSalvage

View File

@@ -91,6 +91,14 @@
- type: GuideHelp
guides:
- Pumps
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasBinaryBase
@@ -164,6 +172,14 @@
- type: GuideHelp
guides:
- Pumps
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasBinaryBase
@@ -225,6 +241,14 @@
- type: GuideHelp
guides:
- PressureRegulator
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasBinaryBase
@@ -267,6 +291,14 @@
- type: GuideHelp
guides:
- PassiveGate
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasBinaryBase
@@ -328,6 +360,14 @@
- type: GuideHelp
guides:
- ManualValve
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasBinaryBase
@@ -400,6 +440,14 @@
- type: GuideHelp
guides:
- SignalValve
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasBinaryBase
@@ -439,6 +487,16 @@
- type: GuideHelp
guides:
- GasCanisters
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-ens
- type: entity
parent: GasVentPump

View File

@@ -58,6 +58,14 @@
guides:
- AtmosphericNetworkMonitor
- DeviceMonitoringAndControl
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasPipeSensor

View File

@@ -65,6 +65,11 @@
bodyType: static
- type: StaticPrice
price: 30
# Corvax-Wega-VentCrawling-start
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
abstract: true
@@ -157,6 +162,14 @@
guides:
- Pipes
- PipeNetworks
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawTransit
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasPipeBase
@@ -207,6 +220,14 @@
guides:
- Pipes
- PipeNetworks
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawBend
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasPipeBase
@@ -250,6 +271,18 @@
guides:
- Pipes
- PipeNetworks
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- 90
- -90
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasPipeBase
@@ -295,6 +328,19 @@
guides:
- Pipes
- PipeNetworks
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- 90
- -90
- 180
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
id: GasPipeBroken
@@ -399,3 +445,15 @@
- type: Tag
tags:
- Unstackable
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- 180
- type: VentCrawManifold
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end

View File

@@ -78,6 +78,18 @@
- type: GuideHelp
guides:
- MixingAndFiltering
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- -90
- 180
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasFilter
@@ -125,6 +137,18 @@
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: North
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- -90
- 180
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasTrinaryBase
@@ -177,6 +201,18 @@
- type: GuideHelp
guides:
- MixingAndFiltering
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- -90
- 180
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasMixer
@@ -220,6 +256,18 @@
pipeDirection: North
- type: Construction
node: mixerflipped
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- -90
- 180
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasPipeBase
@@ -285,3 +333,15 @@
- PneumaticValve
- Pumps
- Valves
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- -90
- 180
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end

View File

@@ -77,6 +77,14 @@
guides:
- AirVent
- DeviceMonitoringAndControl
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawEntry
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasUnaryBase
@@ -109,6 +117,14 @@
- type: GuideHelp
guides:
- PassiveVent
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawEntry
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: [GasUnaryBase, AirSensorBase]
@@ -167,6 +183,14 @@
guides:
- AirScrubber
- DeviceMonitoringAndControl
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawEntry
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: GasUnaryBase
@@ -211,6 +235,16 @@
- type: GuideHelp
guides:
- AirInjector
# Corvax-Wega-VentCrawling-start
- type: VentCrawTube
containerId: VentCrawTube
- type: VentCrawJunction
degrees:
- 0
- type: ContainerContainer
containers:
VentCrawTube: !type:Container
# Corvax-Wega-VentCrawling-end
- type: entity
parent: [ BaseMachinePowered, ConstructibleMachine ]

View File

@@ -0,0 +1,10 @@
- type: entity
id: VentCrawHolder
categories: [ HideSpawnMenu ]
name: vent claw holder
components:
- type: VentCrawHolder
- type: InputMover
- type: ContainerContainer
containers:
VentCrawHolderComponent: !type:Container

View File

@@ -0,0 +1,25 @@
- type: soundCollection
id: VentClaw
files:
- /Audio/_Wega/Effects/VentCrawling/crawling1.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling2.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling4.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling5.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling6.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling7.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling8.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling9.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling10.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling11.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling12.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling13.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling15.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling16.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling17.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling18.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling19.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling20.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling21.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling22.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling23.ogg
- /Audio/_Wega/Effects/VentCrawling/crawling24.ogg