diff --git a/Content.Client/SubFloor/SubFloorHideSystem.cs b/Content.Client/SubFloor/SubFloorHideSystem.cs index 64d3afc640e..f7dad484373 100644 --- a/Content.Client/SubFloor/SubFloorHideSystem.cs +++ b/Content.Client/SubFloor/SubFloorHideSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Atmos.Components; using Content.Shared.DrawDepth; using Content.Shared.SubFloor; using Robust.Client.GameObjects; @@ -9,6 +10,7 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; private bool _showAll; + private bool _showVentPipe; [ViewVariables(VVAccess.ReadWrite)] public bool ShowAll @@ -23,6 +25,19 @@ public bool ShowAll } } + [ViewVariables(VVAccess.ReadWrite)] + public bool ShowVentPipe + { + get => _showVentPipe; + set + { + if (_showVentPipe == value) return; + _showVentPipe = value; + + UpdateAll(); + } + } + public override void Initialize() { base.Initialize(); @@ -40,7 +55,13 @@ private void OnAppearanceChanged(EntityUid uid, SubFloorHideComponent component, scannerRevealed &= !ShowAll; // no transparency for show-subfloor mode. - var revealed = !covered || ShowAll || scannerRevealed; + var showVentPipe = false; + if (HasComp(uid)) + { + showVentPipe = ShowVentPipe; + } + + var revealed = !covered || ShowAll || scannerRevealed || showVentPipe; // set visibility & color of each layer foreach (var layer in args.Sprite.AllLayers) diff --git a/Content.Client/VentCraw/VentCrawVisionSystem.cs b/Content.Client/VentCraw/VentCrawVisionSystem.cs new file mode 100644 index 00000000000..4420e886a12 --- /dev/null +++ b/Content.Client/VentCraw/VentCrawVisionSystem.cs @@ -0,0 +1,33 @@ +using Content.Client.SubFloor; +using Content.Shared.VentCraw; +using Robust.Client.Player; +using Robust.Shared.Timing; + +namespace Content.Client.VentCraw; + +public sealed class VentCrawSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly SubFloorHideSystem _subFloorHideSystem = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + var player = _player.LocalPlayer?.ControlledEntity; + + var ventCraslerQuery = GetEntityQuery(); + + if (!ventCraslerQuery.TryGetComponent(player, out var playerVentCrawlerComponent)) + { + _subFloorHideSystem.ShowVentPipe = false; + return; + } + + _subFloorHideSystem.ShowVentPipe = playerVentCrawlerComponent.InTube; + } +} diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index 9d9862ff1dd..f27a9830dad 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -9,6 +9,8 @@ using Content.Server.DeviceNetwork.Systems; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; +using Content.Server.Power.Components; +using Content.Server.Tools; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Piping.Unary; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index a35cf6c2e30..bd31be97723 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Atmos.EntitySystems; -using Content.Server.Atmos.Monitor.Components; using Content.Server.Atmos.Monitor.Systems; using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Unary.Components; @@ -10,6 +9,7 @@ using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; +using Content.Server.Tools; using Content.Shared.Atmos; using Content.Shared.Atmos.Piping.Unary.Visuals; using Content.Shared.Atmos.Monitor; diff --git a/Content.Server/VentCraw/BeingVentCrawSystem.cs b/Content.Server/VentCraw/BeingVentCrawSystem.cs new file mode 100644 index 00000000000..09d8f0acb1f --- /dev/null +++ b/Content.Server/VentCraw/BeingVentCrawSystem.cs @@ -0,0 +1,106 @@ +using Content.Server.Ghost; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Systems; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.Nodes; +using Content.Shared.Mind; +using Content.Shared.Mobs; +using Content.Shared.VentCraw.Components; +using Robust.Shared.Player; + +namespace Content.Server.VentCraw; + +public sealed class BeingVentCrawSystem : EntitySystem +{ + [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; + [Dependency] private readonly IEntityManager _entities = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInhaleLocation); + SubscribeLocalEvent(OnExhaleLocation); + SubscribeLocalEvent(OnGetAir); + SubscribeLocalEvent(OnMobStateChanged); + } + + private void OnMobStateChanged(EntityUid uid, BeingVentCrawComponent component, MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead || args.OldMobState != MobState.Critical) + return; + + if (TryComp(uid, out var actor)) + { + var session = actor.PlayerSession; + + var minds = _entities.System(); + if (!minds.TryGetMind(session, out var mindId, out var mind)) + { + mindId = minds.CreateMind(session.UserId); + mind = _entities.GetComponent(mindId); + } + + _entities.System().OnGhostAttempt(mindId, true, true, mind); + } + } + + private void OnGetAir(EntityUid uid, BeingVentCrawComponent component, ref AtmosExposedGetAirEvent args) + { + if (!TryComp(component.Holder, out var holder)) + return; + + if (holder.CurrentTube == null) + return; + + if (!TryComp(holder.CurrentTube.Value, out NodeContainerComponent? nodeContainer)) + return; + foreach (var nodeContainerNode in nodeContainer.Nodes) + { + if (!_nodeContainer.TryGetNode(nodeContainer, nodeContainerNode.Key, out PipeNode? pipe)) + continue; + args.Gas = pipe.Air; + args.Handled = true; + return; + } + } + + private void OnInhaleLocation(EntityUid uid, BeingVentCrawComponent component, InhaleLocationEvent args) + { + if (!TryComp(component.Holder, out var holder)) + return; + + if (holder.CurrentTube == null) + return; + + if (!TryComp(holder.CurrentTube.Value, out NodeContainerComponent? nodeContainer)) + return; + foreach (var nodeContainerNode in nodeContainer.Nodes) + { + if (!_nodeContainer.TryGetNode(nodeContainer, nodeContainerNode.Key, out PipeNode? pipe)) + continue; + args.Gas = pipe.Air; + return; + } + } + + private void OnExhaleLocation(EntityUid uid, BeingVentCrawComponent component, ExhaleLocationEvent args) + { + if (!TryComp(component.Holder, out var holder)) + return; + + if (holder.CurrentTube == null) + return; + + if (!TryComp(holder.CurrentTube.Value, out NodeContainerComponent? nodeContainer)) + return; + foreach (var nodeContainerNode in nodeContainer.Nodes) + { + if (!_nodeContainer.TryGetNode(nodeContainer, nodeContainerNode.Key, out PipeNode? pipe)) + continue; + args.Gas = pipe.Air; + return; + } + } +} diff --git a/Content.Server/VentCraw/VentCrawClothingSystem.cs b/Content.Server/VentCraw/VentCrawClothingSystem.cs new file mode 100644 index 00000000000..717ceff63b3 --- /dev/null +++ b/Content.Server/VentCraw/VentCrawClothingSystem.cs @@ -0,0 +1,26 @@ +using Content.Shared.Clothing; +using Content.Shared.VentCraw.Components; +using Content.Shared.VentCraw; + +namespace Content.Server.VentCraw; + +public sealed class VentCrawClothingSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnClothingEquip); + SubscribeLocalEvent(OnClothingUnequip); + } + + private void OnClothingEquip(Entity ent, ref ClothingGotEquippedEvent args) + { + AddComp(args.Wearer); + } + + private void OnClothingUnequip(Entity ent, ref ClothingGotUnequippedEvent args) + { + RemComp(args.Wearer); + } +} \ No newline at end of file diff --git a/Content.Server/VentCraw/VentCrawTubeSystem.cs b/Content.Server/VentCraw/VentCrawTubeSystem.cs new file mode 100644 index 00000000000..d1cee03fbff --- /dev/null +++ b/Content.Server/VentCraw/VentCrawTubeSystem.cs @@ -0,0 +1,223 @@ +using System.Linq; +using Content.Server.Construction.Completions; +using Content.Server.Popups; +using Content.Shared.VentCraw.Tube.Components; +using Content.Shared.VentCraw.Components; +using Content.Shared.Tools.Components; +using Content.Shared.Destructible; +using Content.Shared.DoAfter; +using Content.Shared.Movement.Systems; +using Content.Shared.VentCraw; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Content.Server.VentCraw +{ + public sealed class VentCrawTubeSystem : EntitySystem + { + [Dependency] private readonly SharedVentCrawableSystem _ventCrawableSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentRemove); + + SubscribeLocalEvent(OnAnchorChange); + SubscribeLocalEvent(OnBreak); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnDeconstruct); + SubscribeLocalEvent(OnGetBendConnectableDirections); + SubscribeLocalEvent(OnGetEntryConnectableDirections); + SubscribeLocalEvent(OnGetJunctionConnectableDirections); + SubscribeLocalEvent(OnGetTransitConnectableDirections); + SubscribeLocalEvent>(AddClimbedVerb); + SubscribeLocalEvent(OnDoAfterEnterTube); + } + + private void AddClimbedVerb(EntityUid uid, VentCrawEntryComponent component, GetVerbsEvent args) + { + if (!TryComp(args.User, out var ventCrawlerComponent) || HasComp(args.User)) + return; + + var xform = Transform(uid); + if (!xform.Anchored) + return; + + AlternativeVerb verb = new() + { + Act = () => TryEnter(uid, args.User, ventCrawlerComponent), + Text = Loc.GetString("comp-climbable-verb-climb") + }; + args.Verbs.Add(verb); + } + + private void OnDoAfterEnterTube(EntityUid uid, VentCrawlerComponent component, EnterVentDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null) + return; + + TryInsert(args.Args.Target.Value, args.Args.Used.Value); + + args.Handled = true; + } + + private void TryEnter(EntityUid uid, EntityUid user, VentCrawlerComponent crawler) + { + if (TryComp(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 OnComponentInit(EntityUid uid, VentCrawTubeComponent tube, ComponentInit args) + { + tube.Contents = _containerSystem.EnsureContainer(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 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) + { + args.Connectable = new[] { Transform(uid).LocalRotation.GetDir() }; + } + + 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 OnDeconstruct(EntityUid uid, VentCrawTubeComponent component, ConstructionBeforeDeleteEvent args) + { + DisconnectTube(uid, component); + } + + private void OnStartup(EntityUid uid, VentCrawTubeComponent component, ComponentStartup args) + { + UpdateAnchored(uid, component, Transform(uid).Anchored); + } + + 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 UpdateAnchored(EntityUid uid, VentCrawTubeComponent component, bool anchored) + { + if (anchored) + { + ConnectTube(uid, component); + } + else + { + DisconnectTube(uid, component); + } + } + + 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(); + foreach (var entity in tube.Contents.ContainedEntities.ToArray()) + { + if (query.TryGetComponent(entity, out var holder)) + { + var Exitev = new VentCrawExitEvent(); + RaiseLocalEvent(entity, ref Exitev); + } + } + } + + private bool TryInsert(EntityUid uid, EntityUid entity, VentCrawEntryComponent? entry = null) + { + if (!Resolve(uid, ref entry)) + return false; + + if (!TryComp(entity, out var ventCrawlerComponent)) + return false; + + var holder = Spawn(VentCrawEntryComponent.HolderPrototypeId, _transform.GetMapCoordinates(uid)); + var holderComponent = Comp(holder); + + _ventCrawableSystem.TryInsert(holder, entity, holderComponent); + + _mover.SetRelay(entity, holder); + ventCrawlerComponent.InTube = true; + Dirty(entity, ventCrawlerComponent); + + return _ventCrawableSystem.EnterTube(holder, uid, holderComponent); + } + } +} diff --git a/Content.Server/VentCraw/VentCrawableSystem.cs b/Content.Server/VentCraw/VentCrawableSystem.cs new file mode 100644 index 00000000000..3449655e80f --- /dev/null +++ b/Content.Server/VentCraw/VentCrawableSystem.cs @@ -0,0 +1,75 @@ +using System.Linq; +using Content.Shared.VentCraw.Tube.Components; +using Content.Shared.VentCraw.Components; +using Content.Shared.VentCraw; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Containers; + +namespace Content.Server.VentCraw; + +public sealed class VentCrawableSystem : EntitySystem +{ + [Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnVentCrawExitEvent); + } + + /// + /// Exits the vent craws for the specified VentCrawHolderComponent, removing it and any contained entities from the craws. + /// + /// The EntityUid of the VentCrawHolderComponent. + /// The VentCrawHolderComponent instance. + /// The TransformComponent instance for the VentCrawHolderComponent. + private void OnVentCrawExitEvent(EntityUid uid, VentCrawHolderComponent holder, ref VentCrawExitEvent args) + { + var holderTransform = args.holderTransform; + + if (Terminating(uid)) + return; + + if (!Resolve(uid, ref holderTransform)) + return; + + if (holder.IsExitingVentCraws) + { + Log.Error("Tried exiting VentCraws twice. This should never happen."); + return; + } + + holder.IsExitingVentCraws = true; + + foreach (var entity in holder.Container.ContainedEntities.ToArray()) + { + RemComp(entity); + + var meta = MetaData(entity); + _containerSystem.Remove(entity, holder.Container, reparent: false, force: true); + + var xform = Transform(entity); + if (xform.ParentUid != uid) + continue; + + _xformSystem.AttachToGridOrMap(entity, xform); + + if (TryComp(entity, out var ventCrawComp)) + { + ventCrawComp.InTube = false; + Dirty(entity , ventCrawComp); + } + + if (EntityManager.TryGetComponent(entity, out PhysicsComponent? physics)) + { + _physicsSystem.WakeBody(entity, body: physics); + } + } + + EntityManager.DeleteEntity(uid); + } +} diff --git a/Content.Shared/Movement/Events/MoveInputEvent.cs b/Content.Shared/Movement/Events/MoveInputEvent.cs index 9c49da722cb..5105c116763 100644 --- a/Content.Shared/Movement/Events/MoveInputEvent.cs +++ b/Content.Shared/Movement/Events/MoveInputEvent.cs @@ -11,12 +11,16 @@ public readonly struct MoveInputEvent { public readonly Entity Entity; public readonly MoveButtons OldMovement; + public readonly Direction Dir; + public readonly bool State; public bool HasDirectionalMovement => (Entity.Comp.HeldMoveButtons & MoveButtons.AnyDirection) != MoveButtons.None; - public MoveInputEvent(Entity entity, MoveButtons oldMovement) + public MoveInputEvent(Entity entity, MoveButtons oldMovement, Direction dir, bool state) { Entity = entity; OldMovement = oldMovement; + Dir = dir; + State = state; } } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index c11df709f63..4a3f3b7016f 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -5,6 +5,7 @@ using Content.Shared.Input; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; +using Robust.Shared.Maths; using Robust.Shared.GameStates; using Robust.Shared.Input; using Robust.Shared.Input.Binding; @@ -94,7 +95,11 @@ protected void SetMoveInput(Entity entity, MoveButtons butt // Relay the fact we had any movement event. // TODO: Ideally we'd do these in a tick instead of out of sim. - var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons); + Vector2 vector2 = DirVecForButtons(buttons); + Vector2i vector2i = new Vector2i((int)vector2.X, (int)vector2.Y); + Direction dir = (vector2i == Vector2i.Zero) ? Direction.Invalid : vector2i.AsDirection(); + + var moveEvent = new MoveInputEvent(entity, buttons, dir, buttons != 0); entity.Comp.HeldMoveButtons = buttons; RaiseLocalEvent(entity, ref moveEvent); Dirty(entity, entity.Comp); @@ -115,10 +120,14 @@ private void OnMoverHandleState(Entity entity, ref Componen // Reset entity.Comp.LastInputTick = GameTick.Zero; entity.Comp.LastInputSubTick = 0; + + Vector2 vector2 = DirVecForButtons(entity.Comp.HeldMoveButtons); + Vector2i vector2i = new Vector2i((int)vector2.X, (int)vector2.Y); + Direction dir = (vector2i == Vector2i.Zero) ? Direction.Invalid : vector2i.AsDirection(); if (entity.Comp.HeldMoveButtons != state.HeldMoveButtons) { - var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons); + var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons, dir, state.HeldMoveButtons != 0); entity.Comp.HeldMoveButtons = state.HeldMoveButtons; RaiseLocalEvent(entity.Owner, ref moveEvent); } @@ -313,6 +322,13 @@ private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bo if (!MoverQuery.TryGetComponent(entity, out var moverComp)) return; + + var moverEntity = new Entity(entity, moverComp); + + // Relay the fact we had any movement event. + // TODO: Ideally we'd do these in a tick instead of out of sim. + var moveEvent = new MoveInputEvent(moverEntity, moverComp.HeldMoveButtons, dir, state); + RaiseLocalEvent(entity, ref moveEvent); // For stuff like "Moving out of locker" or the likes // We'll relay a movement input to the parent. diff --git a/Content.Shared/VentCraw/Components/BeingVentCrawComponent.cs b/Content.Shared/VentCraw/Components/BeingVentCrawComponent.cs new file mode 100644 index 00000000000..e7821c91ef0 --- /dev/null +++ b/Content.Shared/VentCraw/Components/BeingVentCrawComponent.cs @@ -0,0 +1,31 @@ +namespace Content.Shared.VentCraw.Components; + +/// +/// A component indicating that the entity is in the process of moving through the venting process +/// +[RegisterComponent] +public sealed partial class BeingVentCrawComponent : Component +{ + /// + /// The entity that contains this object in the vent + /// + [DataField("holder")] + private EntityUid _holder; + /// + /// Gets or sets up a holder entity + /// + public EntityUid Holder + { + get => _holder; + set + { + if (_holder == value) + return; + + if (value == default) + throw new ArgumentException("Holder cannot be default EntityUid"); + + _holder = value; + } + } +} diff --git a/Content.Shared/VentCraw/Components/VentCrawBendComponent.cs b/Content.Shared/VentCraw/Components/VentCrawBendComponent.cs new file mode 100644 index 00000000000..29dfc913fe8 --- /dev/null +++ b/Content.Shared/VentCraw/Components/VentCrawBendComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.VentCraw.Components; + +[RegisterComponent] +public sealed partial class VentCrawBendComponent : Component +{ +} diff --git a/Content.Shared/VentCraw/Components/VentCrawClothingComponent.cs b/Content.Shared/VentCraw/Components/VentCrawClothingComponent.cs new file mode 100644 index 00000000000..8f32e85c2c8 --- /dev/null +++ b/Content.Shared/VentCraw/Components/VentCrawClothingComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.VentCraw.Components; + +[RegisterComponent] +public sealed partial class VentCrawClothingComponent : Component +{ +} diff --git a/Content.Shared/VentCraw/Components/VentCrawEntryComponent.cs b/Content.Shared/VentCraw/Components/VentCrawEntryComponent.cs new file mode 100644 index 00000000000..10c9909eb38 --- /dev/null +++ b/Content.Shared/VentCraw/Components/VentCrawEntryComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.VentCraw.Components; + +[RegisterComponent] +public sealed partial class VentCrawEntryComponent : Component +{ + public const string HolderPrototypeId = "VentCrawHolder"; +} \ No newline at end of file diff --git a/Content.Shared/VentCraw/Components/VentCrawHolderComponent.cs b/Content.Shared/VentCraw/Components/VentCrawHolderComponent.cs new file mode 100644 index 00000000000..6e7e73fd762 --- /dev/null +++ b/Content.Shared/VentCraw/Components/VentCrawHolderComponent.cs @@ -0,0 +1,61 @@ +using Content.Shared.VentCraw.Components; +using Robust.Shared.Audio; +using Robust.Shared.Containers; + +namespace Content.Shared.VentCraw.Components; + +[RegisterComponent] +public sealed partial class VentCrawHolderComponent : Component +{ + private Container? _container; + public Container Container + { + get => _container ?? throw new InvalidOperationException("Container not initialized"); + set => _container = value; + } + + [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 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 static readonly TimeSpan CrawlDelay = TimeSpan.FromSeconds(0.5); + + public TimeSpan LastCrawl; + + [DataField("crawlSound")] + public SoundCollectionSpecifier CrawlSound { get; set; } = new ("VentClaw", AudioParams.Default.WithVolume(5f)); + + [DataField("speed")] + public float Speed = 0.15f; +} + +[ByRefEvent] +public record struct VentCrawExitEvent +{ + public TransformComponent? holderTransform; +} diff --git a/Content.Shared/VentCraw/Components/VentCrawJunctionComponent.cs b/Content.Shared/VentCraw/Components/VentCrawJunctionComponent.cs new file mode 100644 index 00000000000..380361f7a03 --- /dev/null +++ b/Content.Shared/VentCraw/Components/VentCrawJunctionComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.VentCraw.Components; + +[RegisterComponent, Virtual] +public partial class VentCrawJunctionComponent : Component +{ + /// + /// The angles to connect to. + /// + [DataField("degrees")] public List Degrees = new(); +} diff --git a/Content.Shared/VentCraw/Components/VentCrawTransitComponent.cs b/Content.Shared/VentCraw/Components/VentCrawTransitComponent.cs new file mode 100644 index 00000000000..ea8ba5d0585 --- /dev/null +++ b/Content.Shared/VentCraw/Components/VentCrawTransitComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.VentCraw.Components; + +[RegisterComponent, Virtual] +public partial class VentCrawTransitComponent : Component +{ +} diff --git a/Content.Shared/VentCraw/Components/VentCrawTubeComponent.cs b/Content.Shared/VentCraw/Components/VentCrawTubeComponent.cs new file mode 100644 index 00000000000..a186350836c --- /dev/null +++ b/Content.Shared/VentCraw/Components/VentCrawTubeComponent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Containers; + +namespace Content.Shared.VentCraw.Tube.Components; + +/// +/// A component representing a vent that you can crawl through +/// +[RegisterComponent] +public sealed partial class VentCrawTubeComponent : Component +{ + [DataField("containerId")] + public string ContainerId { get; set; } = "VentCrawTube"; + + [DataField("connected")] + public bool Connected; + + [ViewVariables] + public Container Contents { get; set; } = null!; +} + +[ByRefEvent] +public record struct GetVentCrawsConnectableDirectionsEvent +{ + public Direction[] Connectable; +} diff --git a/Content.Shared/VentCraw/SharedVentCrawableSystem.cs b/Content.Shared/VentCraw/SharedVentCrawableSystem.cs new file mode 100644 index 00000000000..98f9f317626 --- /dev/null +++ b/Content.Shared/VentCraw/SharedVentCrawableSystem.cs @@ -0,0 +1,249 @@ +using System.Linq; +using Content.Shared.Body.Components; +using Content.Shared.Tools.Components; +using Content.Shared.Item; +using Content.Shared.Movement.Events; +using Content.Shared.VentCraw.Tube.Components; +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.Shared.VentCraw; + +/// +/// A system that handles the crawling behavior for vent creatures. +/// +public sealed class SharedVentCrawableSystem : EntitySystem +{ + [Dependency] private readonly SharedVentTubeSystem _ventCrawTubeSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnMoveInput); + } + + /// + /// Handles the MoveInputEvent for VentCrawHolderComponent. + /// + /// The EntityUid of the VentCrawHolderComponent. + /// The VentCrawHolderComponent instance. + /// The MoveInputEvent arguments. + private void OnMoveInput(EntityUid uid, VentCrawHolderComponent holder, ref MoveInputEvent args) + { + if (!EntityManager.EntityExists(holder.CurrentTube)) + { + var ev = new VentCrawExitEvent(); + RaiseLocalEvent(uid, ref ev); + } + + holder.IsMoving = args.State; + holder.CurrentDirection = args.Dir; + } + + /// + /// Handles the ComponentStartup event for VentCrawHolderComponent. + /// + /// The EntityUid of the VentCrawHolderComponent. + /// The VentCrawHolderComponent instance. + /// The ComponentStartup arguments. + private void OnComponentStartup(EntityUid uid, VentCrawHolderComponent holder, ComponentStartup args) + { + holder.Container = _containerSystem.EnsureContainer(uid, nameof(VentCrawHolderComponent)); + } + + /// + /// Tries to insert an entity into the VentCrawHolderComponent container. + /// + /// The EntityUid of the VentCrawHolderComponent. + /// The EntityUid of the entity to insert. + /// The VentCrawHolderComponent instance. + /// True if the insertion was successful, otherwise False. + 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(toInsert, out var physBody)) + _physicsSystem.SetCanCollide(toInsert, false, body: physBody); + + return true; + } + + /// + /// Checks whether the specified entity can be inserted into the container of the VentCrawHolderComponent. + /// + /// The EntityUid of the VentCrawHolderComponent. + /// The EntityUid of the entity to be inserted. + /// The VentCrawHolderComponent instance. + /// True if the entity can be inserted into the container; otherwise, False. + private bool CanInsert(EntityUid uid, EntityUid toInsert, VentCrawHolderComponent? holder = null) + { + if (!Resolve(uid, ref holder)) + return false; + + if (!_containerSystem.CanInsert(toInsert, holder.Container)) + return false; + + return HasComp(toInsert) || + HasComp(toInsert); + } + + /// + /// Attempts to make the VentCrawHolderComponent enter a VentCrawTubeComponent. + /// + /// The EntityUid of the VentCrawHolderComponent. + /// The EntityUid of the VentCrawTubeComponent to enter. + /// The VentCrawHolderComponent instance. + /// The TransformComponent instance for the VentCrawHolderComponent. + /// The VentCrawTubeComponent instance to enter. + /// The TransformComponent instance for the VentCrawTubeComponent. + /// True if the VentCrawHolderComponent successfully enters the VentCrawTubeComponent; otherwise, False. + public bool EnterTube(EntityUid holderUid, EntityUid toUid, VentCrawHolderComponent? holder = null, TransformComponent? holderTransform = null, VentCrawTubeComponent? to = null, TransformComponent? toTransform = null) + { + if (!Resolve(holderUid, ref holder, ref holderTransform)) + return false; + if (holder.IsExitingVentCraws) + { + Log.Error("Tried entering tube after exiting VentCraws. This should never happen."); + return false; + } + if (!Resolve(toUid, ref to, ref toTransform)) + { + var ev = new VentCrawExitEvent(); + RaiseLocalEvent(holderUid, ref ev); + return false; + } + + foreach (var ent in holder.Container.ContainedEntities) + { + var comp = EnsureComp(ent); + comp.Holder = holderUid; + } + + if (!_containerSystem.Insert(holderUid, to.Contents)) + { + var ev = new VentCrawExitEvent(); + RaiseLocalEvent(holderUid, ref ev); + return false; + } + if (TryComp(holderUid, out var physBody)) + _physicsSystem.SetCanCollide(holderUid, false, body: physBody); + + if (holder.CurrentTube != null) + { + holder.PreviousTube = holder.CurrentTube; + holder.PreviousDirection = holder.CurrentDirection; + } + holder.CurrentTube = toUid; + + return true; + } + + /// + /// Magic... + /// + public override void Update(float frameTime) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var holder)) + { + if (holder.CurrentDirection == Direction.Invalid || holder.CurrentTube == null) + continue; + + var currentTube = holder.CurrentTube.Value; + + if (holder.IsMoving && holder.NextTube == null) + { + var nextTube = _ventCrawTubeSystem.NextTubeFor(currentTube, holder.CurrentDirection); + + if (nextTube != null) + { + if (!EntityManager.EntityExists(holder.CurrentTube)) + { + var ev = new VentCrawExitEvent(); + RaiseLocalEvent(uid, ref ev); + continue; + } + + holder.NextTube = nextTube; + holder.StartingTime = holder.Speed; + holder.TimeLeft = holder.Speed; + } + else + { + var ev = new GetVentCrawsConnectableDirectionsEvent(); + RaiseLocalEvent(currentTube, ref ev); + if (ev.Connectable.Contains(holder.CurrentDirection)) + { + var Exitev = new VentCrawExitEvent(); + RaiseLocalEvent(uid, ref Exitev); + continue; + } + } + } + + 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).Coordinates; + var target = Transform(holder.NextTube.Value).Coordinates; + var newPosition = (target.Position - origin.Position) * progress; + + _xformSystem.SetCoordinates(uid, origin.Offset(newPosition).WithEntityId(currentTube)); + + holder.TimeLeft -= time; + frameTime -= time; + } + else if (holder.NextTube != null && holder.TimeLeft == 0) + { + var welded = false; + if (TryComp(holder.NextTube.Value, out var weldableComponent)) + welded = weldableComponent.IsWelded; + if (HasComp(holder.NextTube.Value) && !holder.FirstEntry && !welded) + { + var ev = new VentCrawExitEvent(); + RaiseLocalEvent(uid, ref ev); + } + else + { + _containerSystem.Remove(uid, Comp(currentTube).Contents ,reparent: false, force: true); + + if (holder.FirstEntry) + holder.FirstEntry = false; + + if (_gameTiming.CurTime > holder.LastCrawl + VentCrawHolderComponent.CrawlDelay) + { + holder.LastCrawl = _gameTiming.CurTime; + _audioSystem.PlayPvs(holder.CrawlSound, uid); + } + + EnterTube(uid, holder.NextTube.Value, holder); + holder.NextTube = null; + } + } + } + } +} \ No newline at end of file diff --git a/Content.Shared/VentCraw/SharedVentTubeSystem.cs b/Content.Shared/VentCraw/SharedVentTubeSystem.cs new file mode 100644 index 00000000000..90eb9d2994c --- /dev/null +++ b/Content.Shared/VentCraw/SharedVentTubeSystem.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Content.Shared.VentCraw.Tube.Components; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Content.Shared.VentCraw; + +public sealed class SharedVentTubeSystem : EntitySystem +{ + [Dependency] private readonly SharedMapSystem _mapSystem = default!; + + public EntityUid? NextTubeFor(EntityUid target, Direction nextDirection, VentCrawTubeComponent? targetTube = null) + { + if (!Resolve(target, ref targetTube)) + return null; + var oppositeDirection = nextDirection.GetOpposite(); + + var xform = Transform(target); + if (!TryComp(xform.GridUid, out var grid)) + return null; + + if (xform.GridUid == null) + return null; + + var position = xform.Coordinates; + foreach (EntityUid entity in _mapSystem.GetInDir(xform.GridUid.Value, grid ,position, nextDirection)) + { + + if (!TryComp(entity, out VentCrawTubeComponent? tube) + || !CanConnect(target, targetTube, nextDirection) + || !CanConnect(entity, tube, oppositeDirection)) + continue; + + return entity; + } + + return null; + } + + private bool CanConnect(EntityUid tubeId, VentCrawTubeComponent tube, Direction direction) + { + if (!tube.Connected) + { + return false; + } + + var ev = new GetVentCrawsConnectableDirectionsEvent(); + RaiseLocalEvent(tubeId, ref ev); + return ev.Connectable.Contains(direction); + } +} \ No newline at end of file diff --git a/Content.Shared/VentCraw/VentCrawlerComponent.cs b/Content.Shared/VentCraw/VentCrawlerComponent.cs new file mode 100644 index 00000000000..2f64f679f62 --- /dev/null +++ b/Content.Shared/VentCraw/VentCrawlerComponent.cs @@ -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; + + public float EnterDelay = 2.5f; +} + + +[Serializable, NetSerializable] +public sealed partial class EnterVentDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Resources/Audio/VentCrawling/crawling1.ogg b/Resources/Audio/VentCrawling/crawling1.ogg new file mode 100644 index 00000000000..a6ecf69402f Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling1.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling10.ogg b/Resources/Audio/VentCrawling/crawling10.ogg new file mode 100644 index 00000000000..e33843437db Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling10.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling11.ogg b/Resources/Audio/VentCrawling/crawling11.ogg new file mode 100644 index 00000000000..c0035e99f81 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling11.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling12.ogg b/Resources/Audio/VentCrawling/crawling12.ogg new file mode 100644 index 00000000000..de8db0f9f76 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling12.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling13.ogg b/Resources/Audio/VentCrawling/crawling13.ogg new file mode 100644 index 00000000000..cb6576a9ef3 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling13.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling15.ogg b/Resources/Audio/VentCrawling/crawling15.ogg new file mode 100644 index 00000000000..eb86d91dca2 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling15.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling16.ogg b/Resources/Audio/VentCrawling/crawling16.ogg new file mode 100644 index 00000000000..4b63040b521 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling16.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling17.ogg b/Resources/Audio/VentCrawling/crawling17.ogg new file mode 100644 index 00000000000..faa8c24ec9b Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling17.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling18.ogg b/Resources/Audio/VentCrawling/crawling18.ogg new file mode 100644 index 00000000000..13903f12d73 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling18.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling19.ogg b/Resources/Audio/VentCrawling/crawling19.ogg new file mode 100644 index 00000000000..b0164e04664 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling19.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling2.ogg b/Resources/Audio/VentCrawling/crawling2.ogg new file mode 100644 index 00000000000..b1db68872f7 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling2.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling20.ogg b/Resources/Audio/VentCrawling/crawling20.ogg new file mode 100644 index 00000000000..2f7a605a1bb Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling20.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling21.ogg b/Resources/Audio/VentCrawling/crawling21.ogg new file mode 100644 index 00000000000..821569447ca Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling21.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling22.ogg b/Resources/Audio/VentCrawling/crawling22.ogg new file mode 100644 index 00000000000..f2be620322b Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling22.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling23.ogg b/Resources/Audio/VentCrawling/crawling23.ogg new file mode 100644 index 00000000000..ec208ad39ae Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling23.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling24.ogg b/Resources/Audio/VentCrawling/crawling24.ogg new file mode 100644 index 00000000000..5552dfa7502 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling24.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling4.ogg b/Resources/Audio/VentCrawling/crawling4.ogg new file mode 100644 index 00000000000..894a518a4d6 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling4.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling5.ogg b/Resources/Audio/VentCrawling/crawling5.ogg new file mode 100644 index 00000000000..d54a2f5d767 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling5.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling6.ogg b/Resources/Audio/VentCrawling/crawling6.ogg new file mode 100644 index 00000000000..1aa83e13ef2 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling6.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling7.ogg b/Resources/Audio/VentCrawling/crawling7.ogg new file mode 100644 index 00000000000..567dee84b93 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling7.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling8.ogg b/Resources/Audio/VentCrawling/crawling8.ogg new file mode 100644 index 00000000000..62df4425264 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling8.ogg differ diff --git a/Resources/Audio/VentCrawling/crawling9.ogg b/Resources/Audio/VentCrawling/crawling9.ogg new file mode 100644 index 00000000000..0b24c11f5a2 Binary files /dev/null and b/Resources/Audio/VentCrawling/crawling9.ogg differ diff --git a/Resources/Locale/ru-RU/_Cats/store/uplink-catalog.ftl b/Resources/Locale/ru-RU/_Cats/store/uplink-catalog.ftl new file mode 100644 index 00000000000..961c547f18b --- /dev/null +++ b/Resources/Locale/ru-RU/_Cats/store/uplink-catalog.ftl @@ -0,0 +1,5 @@ +uplink-stimulants-implant-name = { ent-StimulantsImplant } +uplink-stimulants-implant-desc = Продвинутый имплант Cybersun, содержащий три капсулы первокласного стимулирующего вещества, моментально вводимые в кровь по желанию носителя. + +uplink-contortionist-jumpsuit-name = Костюм конторциониста +uplink-contortionist-jumpsuit-desc = Он выглядит как скафандр для атмосферы, но на самом деле это не так. Этот скафандр позволяет агенту пролезать через вентиляционные шахты. \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index ae90bf182e5..0cc8cbcc381 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -908,6 +908,7 @@ 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: + - type: VentCrawler - type: Sprite drawdepth: Mobs layers: @@ -1313,6 +1314,7 @@ description: The genetic bipedal ancestor of... Uh... Something. Yeah, there's definitely something on the station that descended from whatever this is. abstract: true components: + - type: VentCrawler - type: LanguageSpeaker currentLanguage: Monkey - type: LanguageKnowledge #backmen: languages @@ -1529,6 +1531,8 @@ parent: MobBaseSyndicateMonkey suffix: syndicate agent components: + # make the player a traitor once its taken + - type: VentCrawler # make the player a traitor once its taken - type: AutoTraitor profile: TraitorReinforcement @@ -1538,6 +1542,7 @@ parent: MobBaseSyndicateMonkey suffix: NukeOps components: + - type: VentCrawler - type: NukeOperative - type: entity @@ -1686,6 +1691,7 @@ id: MobMouse description: Squeak! components: + - type: VentCrawler - type: FelinidFood - type: Body prototype: Mouse @@ -2074,6 +2080,7 @@ id: MobSlug description: And they called this a lizard? components: + - type: VentCrawler - type: MovementSpeedModifier baseWalkSpeed: 2 baseSprintSpeed: 3 @@ -2121,6 +2128,7 @@ id: MobFrog description: Hop hop hop. Lookin' moist. components: + - type: VentCrawler - type: MovementSpeedModifier baseWalkSpeed: 3 baseSprintSpeed: 6 @@ -2369,6 +2377,7 @@ id: MobSnake description: Hissss! Bites aren't poisonous. components: + - type: VentCrawler - type: LanguageSpeaker currentLanguage: Dragon - type: LanguageKnowledge #backmen: languages @@ -2426,6 +2435,7 @@ id: MobGiantSpider description: Widely recognized to be the literal worst thing in existence. components: + - type: VentCrawler - type: LanguageSpeaker currentLanguage: Spider - type: LanguageKnowledge #backmen: languages @@ -2992,6 +3002,7 @@ id: MobCorgiPuppy description: A little corgi! Aww... components: + - type: VentCrawler - type: Sprite drawdepth: Mobs sprite: Mobs/Pets/corgi.rsi @@ -3363,6 +3374,7 @@ id: MobHamster description: A cute, fluffy, robust hamster. components: + - type: VentCrawler - type: GhostRole makeSentient: true allowSpeech: true diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index d98dde2f44f..110f3b32949 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -4,6 +4,7 @@ parent: [ SimpleMobBase, MobCombat ] description: He's da rat. He make da roolz. components: + - type: VentCrawler - type: CombatMode - type: MovementSpeedModifier baseWalkSpeed : 3.00 @@ -184,6 +185,7 @@ 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: + - type: VentCrawler - type: CombatMode - type: MovementSpeedModifier baseWalkSpeed : 3.5 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index d1168ba99e9..b72466ec7a2 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -5,6 +5,12 @@ abstract: true description: It looks so much like jelly. I wonder what it tastes like? components: + - type: VentCrawler + - type: NpcFactionMember + factions: + - SimpleNeutral + - type: HTN + rootTask: SimpleHostileCompound - type: Sprite drawdepth: Mobs sprite: Mobs/Aliens/slimes.rsi diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index d0eb1b7fc2d..8d10e5f7c6f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -199,6 +199,7 @@ parent: MobSpaceBasic description: It's so glowing, it looks dangerous. components: + - type: VentCrawler - type: Sprite drawdepth: Mobs sprite: Mobs/Animals/spacespider.rsi diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml index 4ea2daabc93..503d68c47ce 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml @@ -4,6 +4,7 @@ parent: SimpleSpaceMobBase description: It's a space tick, watch out for its nasty bite. CentComm reports that 90 percent of cargo leg amputations are due to space tick bites. components: + - type: VentCrawler - type: InputMover - type: MobMover - type: HTN diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 82d6a52d25a..b4328fd602e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -31,6 +31,7 @@ groups: Flammable: [Touch] Extinguish: [Touch] + - type: VentCrawler - type: NpcFactionMember factions: - Xeno @@ -388,6 +389,7 @@ layers: - map: ["enum.DamageStateVisualLayers.Base"] state: purple_snake + - type: VentCrawler - type: DamageStateVisuals states: Alive: diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index 9f21e45a963..c49536cf1b7 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -70,6 +70,13 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_pump.ogg + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawTransit + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasBinaryBase @@ -127,6 +134,13 @@ examinableAddress: true prefix: device-address-prefix-volume-pump - type: WiredNetworkConnection + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawTransit + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasBinaryBase @@ -156,6 +170,13 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawTransit + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasBinaryBase @@ -204,6 +225,13 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawTransit + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasBinaryBase @@ -263,6 +291,13 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawTransit + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasBinaryBase @@ -292,6 +327,15 @@ - type: Construction graph: GasBinary node: port + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasVentPump diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml index 4fe5463bff9..f54b704a645 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml @@ -62,6 +62,21 @@ bodyType: static - type: StaticPrice price: 30 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container + +- type: entity + id: VentCrawHolder + categories: [ HideSpawnMenu ] + name: vent craw holder + components: + - type: VentCrawHolder + - type: InputMover + - type: ContainerContainer + containers: + VentCrawHolderComponent: !type:Container + #Note: The PipeDirection of the PipeNode should be the south-facing version, because the entity starts at an angle of 0 (south) @@ -95,6 +110,13 @@ !type:PipeNode nodeGroupID: Pipe pipeDirection: Longitudinal + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawTransit + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: Sprite layers: - state: pipeStraight @@ -128,6 +150,13 @@ !type:PipeNode nodeGroupID: Pipe pipeDirection: SWBend + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawBend + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: Sprite layers: - state: pipeBend @@ -192,6 +221,17 @@ Blunt: 10 soundHit: collection: MetalThud + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - 90 + - -90 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasPipeBase @@ -213,6 +253,18 @@ - type: Construction graph: GasPipe node: fourway + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - 90 + - -90 + - 180 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: Item size: Normal shape: diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml index e8025556aa5..e70a69f24d9 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml @@ -70,6 +70,17 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - -90 + - 180 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasFilter @@ -114,6 +125,17 @@ !type:PipeNode nodeGroupID: Pipe pipeDirection: North + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - -90 + - 180 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasTrinaryBase @@ -158,6 +180,17 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - -90 + - 180 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasMixer @@ -201,6 +234,17 @@ pipeDirection: North - type: Construction node: mixerflipped + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - -90 + - 180 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: GasPipeBase @@ -257,3 +301,14 @@ - type: Construction graph: GasTrinary node: pneumaticvalve + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - -90 + - 180 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index d0ec74dd40a..c997895cf08 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -28,6 +28,15 @@ placement: mode: SnapgridCenter components: + - type: Weldable + time: 3 + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawEntry + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: DeviceNetwork @@ -67,7 +76,6 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_vent.ogg - - type: Weldable - type: entity parent: GasUnaryBase @@ -101,6 +109,15 @@ placement: mode: SnapgridCenter components: + - type: Weldable + time: 3 + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawEntry + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: DeviceNetwork @@ -140,7 +157,6 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_vent.ogg - - type: Weldable - type: entity parent: GasUnaryBase @@ -180,6 +196,15 @@ visibleLayers: - enum.SubfloorLayers.FirstLayer - enum.LightLayers.Unshaded + - type: VentCrawTube + containerId: VentCrawTube + connected: true + - type: VentCrawJunction + degrees: + - 0 + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: [ BaseMachinePowered, ConstructibleMachine ] diff --git a/Resources/Prototypes/SoundCollections/vent_clawling.yml b/Resources/Prototypes/SoundCollections/vent_clawling.yml new file mode 100644 index 00000000000..70a99886783 --- /dev/null +++ b/Resources/Prototypes/SoundCollections/vent_clawling.yml @@ -0,0 +1,25 @@ +- type: soundCollection + id: VentClaw + files: + - /Audio/VentCrawling/crawling1.ogg + - /Audio/VentCrawling/crawling2.ogg + - /Audio/VentCrawling/crawling4.ogg + - /Audio/VentCrawling/crawling5.ogg + - /Audio/VentCrawling/crawling6.ogg + - /Audio/VentCrawling/crawling7.ogg + - /Audio/VentCrawling/crawling8.ogg + - /Audio/VentCrawling/crawling9.ogg + - /Audio/VentCrawling/crawling10.ogg + - /Audio/VentCrawling/crawling11.ogg + - /Audio/VentCrawling/crawling12.ogg + - /Audio/VentCrawling/crawling13.ogg + - /Audio/VentCrawling/crawling15.ogg + - /Audio/VentCrawling/crawling16.ogg + - /Audio/VentCrawling/crawling17.ogg + - /Audio/VentCrawling/crawling18.ogg + - /Audio/VentCrawling/crawling19.ogg + - /Audio/VentCrawling/crawling20.ogg + - /Audio/VentCrawling/crawling21.ogg + - /Audio/VentCrawling/crawling22.ogg + - /Audio/VentCrawling/crawling23.ogg + - /Audio/VentCrawling/crawling24.ogg diff --git a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/TGMC_xeno.yml b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/TGMC_xeno.yml index f374aa99f34..9820390ddc7 100644 --- a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/TGMC_xeno.yml +++ b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/TGMC_xeno.yml @@ -18,6 +18,7 @@ Blunt: 5 soundHit: path: /Audio/Effects/hit_kick.ogg + - type: VentCrawler - type: Sprite drawdepth: Mobs sprite: Mobs/Aliens/Xenos/burrower.rsi diff --git a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/flesh_cult.yml b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/flesh_cult.yml index e4bbf440c5f..c56d2d9f423 100644 --- a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/flesh_cult.yml +++ b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/flesh_cult.yml @@ -95,6 +95,7 @@ soundDeath: "/Audio/Animals/Flesh/flesh_worm_dead.ogg" deathMobSpawnId: "MobFleshWorm" deathMobSpawnCount: 1 + - type: VentCrawler - type: Sprite sprite: Mobs/Aliens/FleshCult/flesh_cult_mobs.rsi layers: @@ -419,6 +420,7 @@ soundDeath: "/Audio/Animals/Flesh/flesh_worm_dead.ogg" deathMobSpawnId: "MobFleshWorm" deathMobSpawnCount: 0 + - type: VentCrawler - type: Sprite sprite: Mobs/Aliens/FleshCult/flesh_cult_mobs.rsi layers: @@ -502,6 +504,7 @@ layers: - map: [ "enum.DamageStateVisualLayers.Base" ] state: worm + - type: VentCrawler - type: DamageStateVisuals states: Alive: diff --git a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/haisenberg.yml b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/haisenberg.yml index 9fd5dc36d1d..934ece37ed3 100644 --- a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/haisenberg.yml +++ b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/haisenberg.yml @@ -27,3 +27,4 @@ - type: InventorySlots - type: Body prototype: BodyHaisenberg + - type: VentCrawler diff --git a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/mutants.yml b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/mutants.yml index 408e10f8d20..7780a957849 100644 --- a/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/mutants.yml +++ b/Resources/Prototypes/_Backmen/Entities/Mobs/NPCs/mutants.yml @@ -104,6 +104,7 @@ layer: - SmallMobLayer - type: Appearance + - type: VentCrawler - type: DamageStateVisuals states: Alive: diff --git a/Resources/Prototypes/_Backmen/Entities/Structures/Atmospherics/supermatter.yml b/Resources/Prototypes/_Backmen/Entities/Structures/Atmospherics/supermatter.yml index fdc03544a31..2b58bc498aa 100644 --- a/Resources/Prototypes/_Backmen/Entities/Structures/Atmospherics/supermatter.yml +++ b/Resources/Prototypes/_Backmen/Entities/Structures/Atmospherics/supermatter.yml @@ -28,6 +28,12 @@ components: - type: GasVentPump pressureLockoutOverride: true # Even if pressure is low we always fill the gas. + - type: VentCrawTube + containerId: VentCrawTube + - type: VentCrawTransit + - type: ContainerContainer + containers: + VentCrawTube: !type:Container - type: entity parent: [AirSensorSupermatterBase, GasVentScrubber] diff --git a/Resources/Prototypes/_SpaceCats/Entities/Clothing/Uniforms/jumpsuits.yml b/Resources/Prototypes/_SpaceCats/Entities/Clothing/Uniforms/jumpsuits.yml new file mode 100644 index 00000000000..149c8b2d640 --- /dev/null +++ b/Resources/Prototypes/_SpaceCats/Entities/Clothing/Uniforms/jumpsuits.yml @@ -0,0 +1,53 @@ +- type: entity + parent: ClothingUniformJumpsuitAtmos + id: ClothingUniformJumpsuitAtmosSyndie + name: Contortionist's Jumpsuit + components: + - type: VentCrawClothing + +- type: entity + parent: ClothingUniformBase + id: ClothingUniformJumpsuitCommandCaptain + name: captain command jumpsuit + description: Black suit with gold shoulder straps. + components: + - type: Sprite + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/captain_command.rsi + - type: Clothing + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/captain_command.rsi + +- type: entity + parent: ClothingUniformBase + id: ClothingUniformJumpsuitWhiteCaptain + name: white captain jumpsuit + description: Captain white jumpsuit symbolizes that your salary is clearly above the norm. + components: + - type: Sprite + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/captain_white.rsi + - type: Clothing + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/captain_white.rsi + +- type: entity + parent: ClothingUniformBase + id: ClothingUniformJumpsuitSheriff + name: sheriff jumpsuit + description: Jumpsuit showing your status is how terrible... the great ruler. + components: + - type: Sprite + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/sheriff.rsi + - type: Clothing + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/sheriff.rsi + +- type: entity + parent: ClothingUniformBase + id: ClothingUniformJumpsuitBridgeOfficer + name: bridge officer uniform + description: More stylish than advanced, this formal suit serves as a great display of power. + components: + - type: Sprite + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/bridgeofficer.rsi + - type: Clothing + sprite: _SpaceCats/Clothing/Uniforms/Jumpsuit/bridgeofficer.rsi + - type: Tag + tags: + - WhitelistChameleon \ No newline at end of file