diff --git a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs index b0d45d7bf1..7af91d114d 100644 --- a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs +++ b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs @@ -10,7 +10,9 @@ using Robust.Client.UserInterface.XAML; using Robust.Client.Utility; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Maths; +using Robust.Shared.Utility; using static Robust.Client.UserInterface.Controls.BaseButton; using static Robust.Client.UserInterface.Controls.BoxContainer; @@ -146,6 +148,15 @@ namespace Content.Client.Cargo.UI foreach (var order in Owner.Orders.Orders) { + var productName = Owner.Market.GetProduct(order.ProductId)?.Name; + + if (productName == null) + { + DebugTools.Assert(false); + Logger.ErrorS("cargo", $"Unable to find product name for {order.ProductId}"); + continue; + } + var row = new CargoOrderRow { Order = order, @@ -154,7 +165,7 @@ namespace Content.Client.Cargo.UI { Text = Loc.GetString( "cargo-console-menu-populate-orders-cargo-order-row-product-name-text", - ("productName", Owner.Market.GetProduct(order.ProductId)?.Name!), + ("productName", productName), ("orderAmount", order.Amount), ("orderRequester", order.Requester)) }, diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index ee39094ede..35feb13d51 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -101,6 +101,7 @@ namespace Content.Client.Entry "Lock", "PresetIdCard", "SolarControlConsole", + "Thruster", "FlashOnTrigger", "SoundOnTrigger", "TriggerOnCollide", diff --git a/Content.Client/Shuttles/ShuttleConsoleComponent.cs b/Content.Client/Shuttles/ShuttleConsoleComponent.cs index e5632721f3..86c4410b86 100644 --- a/Content.Client/Shuttles/ShuttleConsoleComponent.cs +++ b/Content.Client/Shuttles/ShuttleConsoleComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Shuttles; +using Content.Shared.Shuttles.Components; using Robust.Shared.GameObjects; namespace Content.Client.Shuttles diff --git a/Content.Client/Shuttles/ThrusterVisualizer.cs b/Content.Client/Shuttles/ThrusterVisualizer.cs new file mode 100644 index 0000000000..6bc2ae6a6e --- /dev/null +++ b/Content.Client/Shuttles/ThrusterVisualizer.cs @@ -0,0 +1,67 @@ +using Content.Shared.Shuttles.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.Shuttles +{ + public sealed class ThrusterVisualizer : AppearanceVisualizer + { + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out SpriteComponent? spriteComponent)) return; + + component.TryGetData(ThrusterVisualState.State, out bool state); + + switch (state) + { + case true: + spriteComponent.LayerSetVisible(ThrusterVisualLayers.ThrustOn, true); + + if (component.TryGetData(ThrusterVisualState.Thrusting, out bool thrusting) && thrusting) + { + if (spriteComponent.LayerMapTryGet(ThrusterVisualLayers.Thrusting, out _)) + { + spriteComponent.LayerSetVisible(ThrusterVisualLayers.Thrusting, true); + } + + if (spriteComponent.LayerMapTryGet(ThrusterVisualLayers.ThrustingUnshaded, out _)) + { + spriteComponent.LayerSetVisible(ThrusterVisualLayers.ThrustingUnshaded, true); + } + } + else + { + DisableThrusting(component, spriteComponent); + } + + break; + case false: + spriteComponent.LayerSetVisible(ThrusterVisualLayers.ThrustOn, false); + DisableThrusting(component, spriteComponent); + break; + } + } + + private void DisableThrusting(AppearanceComponent component, SpriteComponent spriteComponent) + { + if (spriteComponent.LayerMapTryGet(ThrusterVisualLayers.Thrusting, out _)) + { + spriteComponent.LayerSetVisible(ThrusterVisualLayers.Thrusting, false); + } + + if (spriteComponent.LayerMapTryGet(ThrusterVisualLayers.ThrustingUnshaded, out _)) + { + spriteComponent.LayerSetVisible(ThrusterVisualLayers.ThrustingUnshaded, false); + } + } + } + + public enum ThrusterVisualLayers : byte + { + Base, + ThrustOn, + Thrusting, + ThrustingUnshaded, + } +} diff --git a/Content.IntegrationTests/Tests/ShuttleTest.cs b/Content.IntegrationTests/Tests/ShuttleTest.cs index 1696f36bec..1593082791 100644 --- a/Content.IntegrationTests/Tests/ShuttleTest.cs +++ b/Content.IntegrationTests/Tests/ShuttleTest.cs @@ -1,6 +1,7 @@ #nullable enable using System.Threading.Tasks; using Content.Server.Shuttles; +using Content.Server.Shuttles.Components; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.Map; diff --git a/Content.Server/Alert/Click/StopPiloting.cs b/Content.Server/Alert/Click/StopPiloting.cs index 51d5cb17bc..64b6841a70 100644 --- a/Content.Server/Alert/Click/StopPiloting.cs +++ b/Content.Server/Alert/Click/StopPiloting.cs @@ -1,6 +1,8 @@ using Content.Server.Shuttles; +using Content.Server.Shuttles.EntitySystems; using Content.Shared.Alert; using Content.Shared.Shuttles; +using Content.Shared.Shuttles.Components; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; diff --git a/Content.Server/Cargo/Components/CargoTelepadComponent.cs b/Content.Server/Cargo/Components/CargoTelepadComponent.cs index 4428a96661..d674713a4c 100644 --- a/Content.Server/Cargo/Components/CargoTelepadComponent.cs +++ b/Content.Server/Cargo/Components/CargoTelepadComponent.cs @@ -26,8 +26,6 @@ namespace Content.Server.Cargo.Components public class CargoTelepadComponent : Component { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - public override string Name => "CargoTelepad"; private const float TeleportDuration = 0.5f; @@ -120,11 +118,14 @@ namespace Content.Server.Cargo.Components // spawn the order if (!_prototypeManager.TryIndex(data.ProductId, out CargoProductPrototype? prototype)) return; + var product = Owner.EntityManager.SpawnEntity(prototype.Product, Owner.Transform.Coordinates); + product.Transform.Anchored = false; + // spawn a piece of paper. var printed = Owner.EntityManager.SpawnEntity(PrinterOutput, Owner.Transform.Coordinates); - if (!_entityManager.TryGetComponent(printed.Uid, out PaperComponent paper)) + if (!Owner.EntityManager.TryGetComponent(printed.Uid, out PaperComponent paper)) return; // fill in the order data @@ -137,7 +138,7 @@ namespace Content.Server.Cargo.Components ("approver", data.Approver))); // attempt to attach the label - if (_entityManager.TryGetComponent(product.Uid, out PaperLabelComponent label)) + if (Owner.EntityManager.TryGetComponent(product.Uid, out PaperLabelComponent label)) { EntitySystem.Get().TryInsert(OwnerUid, label.LabelSlot, printed); } diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index f39f1e3585..401a2d2eed 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -3,17 +3,16 @@ using System.Collections.Generic; using Content.Server.Inventory.Components; using Content.Server.Items; using Content.Server.Movement.Components; -using Content.Server.Shuttles; -using Content.Shared.Audio; +using Content.Server.Shuttles.Components; +using Content.Server.Shuttles.EntitySystems; using Content.Shared.CCVar; using Content.Shared.Inventory; using Content.Shared.Maps; using Content.Shared.Movement; using Content.Shared.Movement.Components; -using Content.Shared.Sound; using Content.Shared.Shuttles; +using Content.Shared.Shuttles.Components; using Content.Shared.Tag; -using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; @@ -21,12 +20,7 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Collision.Shapes; -using Robust.Shared.Physics.Dynamics; using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Server.Physics.Controllers @@ -95,44 +89,120 @@ namespace Content.Server.Physics.Controllers return; } - // Depending whether you have "cruise" mode on (tank controls, higher speed) or "docking" mode on (strafing, lower speed) - // inputs will do different things. - // TODO: Do that - float speedCap; - var angularSpeed = 0.075f; - // ShuttleSystem has already worked out the ratio so we'll just multiply it back by the mass. - var movement = (mover.VelocityDir.walking + mover.VelocityDir.sprinting); + var movement = mover.VelocityDir.walking + mover.VelocityDir.sprinting; + var system = EntitySystem.Get(); + + if (movement.Length.Equals(0f)) + { + // TODO: This visualization doesn't work with multiple pilots so need to address that somehow. + system.DisableAllThrustDirections(shuttleComponent); + return; + } + + var speedCap = 0f; switch (shuttleComponent.Mode) { case ShuttleMode.Docking: - if (physicsComponent.LinearVelocity.LengthSquared == 0f) - { - movement *= 5f; - } + system.DisableAllThrustDirections(shuttleComponent); + var dockDirection = movement.ToWorldAngle().GetDir(); + var dockFlag = dockDirection.AsFlag(); - if (movement.Length != 0f) - physicsComponent.ApplyLinearImpulse(physicsComponent.Owner.Transform.WorldRotation.RotateVec(movement) * shuttleComponent.SpeedMultipler * physicsComponent.Mass); + // Won't just do cardinal directions. + foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag))) + { + // Brain no worky but I just want cardinals + switch (dir) + { + case DirectionFlag.South: + case DirectionFlag.East: + case DirectionFlag.North: + case DirectionFlag.West: + break; + default: + continue; + } + + if ((dir & dockFlag) == 0x0) continue; + + system.EnableThrustDirection(shuttleComponent, dir); + + var index = (int) Math.Log2((int) dir); + + var dockSpeed = shuttleComponent.LinearThrusterImpulse[index]; + + // TODO: Cvar for the speed drop + dockSpeed /= 5f; + + if (physicsComponent.LinearVelocity.LengthSquared == 0f) + { + dockSpeed *= 5f; + } + + var moveAngle = movement.ToWorldAngle(); + + physicsComponent.ApplyLinearImpulse(moveAngle.RotateVec(physicsComponent.Owner.Transform.WorldRotation.ToWorldVec()) * + dockSpeed); + } speedCap = _shuttleDockSpeedCap; break; case ShuttleMode.Cruise: - if (movement.Length != 0.0f) + if (movement.Y == 0f) { + system.DisableThrustDirection(shuttleComponent, DirectionFlag.South); + system.DisableThrustDirection(shuttleComponent, DirectionFlag.North); + } + else + { + var direction = movement.Y > 0f ? Direction.North : Direction.South; + var linearSpeed = shuttleComponent.LinearThrusterImpulse[(int) direction / 2]; + if (physicsComponent.LinearVelocity.LengthSquared == 0f) { - movement.Y *= 5f; + linearSpeed *= 5f; } // Currently this is slow BUT we'd have a separate multiplier for docking and cruising or whatever. - physicsComponent.ApplyLinearImpulse((physicsComponent.Owner.Transform.WorldRotation + new Angle(MathF.PI / 2)).ToVec() * - shuttleComponent.SpeedMultipler * - physicsComponent.Mass * - movement.Y * - 2.5f); + physicsComponent.ApplyLinearImpulse(physicsComponent.Owner.Transform.WorldRotation.Opposite().ToWorldVec() * + linearSpeed * + movement.Y); - physicsComponent.ApplyAngularImpulse(-movement.X * angularSpeed * physicsComponent.Mass); + switch (direction) + { + case Direction.North: + system.DisableThrustDirection(shuttleComponent, DirectionFlag.South); + system.EnableThrustDirection(shuttleComponent, DirectionFlag.North); + break; + case Direction.South: + system.DisableThrustDirection(shuttleComponent, DirectionFlag.North); + system.EnableThrustDirection(shuttleComponent, DirectionFlag.South); + break; + } + } + + var angularSpeed = shuttleComponent.AngularThrust; + + if (movement.X == 0f) + { + system.DisableThrustDirection(shuttleComponent, DirectionFlag.West); + system.DisableThrustDirection(shuttleComponent, DirectionFlag.East); + } + else if (movement.X != 0f) + { + physicsComponent.ApplyAngularImpulse(-movement.X * angularSpeed); + + if (movement.X < 0f) + { + system.EnableThrustDirection(shuttleComponent, DirectionFlag.West); + system.DisableThrustDirection(shuttleComponent, DirectionFlag.East); + } + else + { + system.EnableThrustDirection(shuttleComponent, DirectionFlag.East); + system.DisableThrustDirection(shuttleComponent, DirectionFlag.West); + } } // TODO WHEN THIS ACTUALLY WORKS @@ -142,15 +212,20 @@ namespace Content.Server.Physics.Controllers throw new ArgumentOutOfRangeException(); } - // Look don't my ride ass on this stuff most of the PR was just getting the thing working, we can - // ideaguys the shit out of it later. - var velocity = physicsComponent.LinearVelocity; + var angVelocity = physicsComponent.AngularVelocity; if (velocity.Length > speedCap) { physicsComponent.LinearVelocity = velocity.Normalized * speedCap; } + + /* TODO: Need to suss something out but this PR already BEEG + if (angVelocity > speedCap) + { + physicsComponent.AngularVelocity = speedCap; + } + */ } protected override void HandleFootsteps(IMoverComponent mover, IMobMoverComponent mobMover) diff --git a/Content.Server/Shuttles/Components/DockingComponent.cs b/Content.Server/Shuttles/Components/DockingComponent.cs new file mode 100644 index 0000000000..35c0374d31 --- /dev/null +++ b/Content.Server/Shuttles/Components/DockingComponent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Shuttles; +using Content.Shared.Shuttles.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Physics.Dynamics.Joints; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Shuttles.Components +{ + [RegisterComponent] + public sealed class DockingComponent : SharedDockingComponent + { + [ViewVariables] + public DockingComponent? DockedWith; + + [ViewVariables] + public Joint? DockJoint; + + [ViewVariables] + public override bool Docked => DockedWith != null; + } +} diff --git a/Content.Server/Shuttles/Components/ShuttleComponent.cs b/Content.Server/Shuttles/Components/ShuttleComponent.cs new file mode 100644 index 0000000000..6f77caf198 --- /dev/null +++ b/Content.Server/Shuttles/Components/ShuttleComponent.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Content.Shared.Shuttles.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Shuttles.Components +{ + [RegisterComponent] + public sealed class ShuttleComponent : SharedShuttleComponent + { + /// + /// The cached impulse available for each cardinal direction + /// + [ViewVariables] + public readonly float[] LinearThrusterImpulse = new float[4]; + + /// + /// The thrusters contributing to each direction for impulse. + /// + public readonly List[] LinearThrusters = new List[4]; + + /// + /// The thrusters contributing to the angular impulse of the shuttle. + /// + public readonly List AngularThrusters = new List(); + + [ViewVariables] + public float AngularThrust = 0f; + + /// + /// A bitmask of all the directions we are considered thrusting. + /// + [ViewVariables] + public DirectionFlag ThrustDirections = DirectionFlag.None; + } +} diff --git a/Content.Server/Shuttles/ShuttleConsoleComponent.cs b/Content.Server/Shuttles/Components/ShuttleConsoleComponent.cs similarity index 87% rename from Content.Server/Shuttles/ShuttleConsoleComponent.cs rename to Content.Server/Shuttles/Components/ShuttleConsoleComponent.cs index 4a7e1ffc4d..0be8e40d6e 100644 --- a/Content.Server/Shuttles/ShuttleConsoleComponent.cs +++ b/Content.Server/Shuttles/Components/ShuttleConsoleComponent.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; using Content.Shared.Shuttles; +using Content.Shared.Shuttles.Components; using Robust.Shared.GameObjects; using Robust.Shared.ViewVariables; -namespace Content.Server.Shuttles +namespace Content.Server.Shuttles.Components { [RegisterComponent] [ComponentReference(typeof(SharedShuttleConsoleComponent))] diff --git a/Content.Server/Shuttles/Components/ThrusterComponent.cs b/Content.Server/Shuttles/Components/ThrusterComponent.cs new file mode 100644 index 0000000000..9a04851d8e --- /dev/null +++ b/Content.Server/Shuttles/Components/ThrusterComponent.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using Content.Server.Shuttles.EntitySystems; +using Content.Shared.Damage; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Shuttles.Components +{ + [RegisterComponent] + [Friend(typeof(ThrusterSystem))] + public sealed class ThrusterComponent : Component + { + public override string Name => "Thruster"; + + /// + /// Whether the thruster has been force to be enabled / disable (e.g. VV, interaction, etc.) + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("enabled")] + public bool Enabled + { + get => _enabled; + set + { + if (_enabled == value) return; + _enabled = value; + + var system = EntitySystem.Get(); + + if (!_enabled) + { + system.DisableThruster(OwnerUid, this); + } + else if (system.CanEnable(OwnerUid, this)) + { + system.EnableThruster(OwnerUid, this); + } + } + } + + private bool _enabled = true; + + /// + /// This determines whether the thruster is actually enabled for the purposes of thrust + /// + public bool IsOn; + + [ViewVariables] + [DataField("impulse")] + public float Impulse = 5f; + + [ViewVariables] + [DataField("thrusterType")] + public ThrusterType Type = ThrusterType.Linear; + + [DataField("burnShape")] public List BurnPoly = new() + { + new Vector2(-0.4f, 0.5f), + new Vector2(-0.1f, 1.2f), + new Vector2(0.1f, 1.2f), + new Vector2(0.4f, 0.5f) + }; + + /// + /// How much damage is done per second to anything colliding with our thrust. + /// + [ViewVariables] [DataField("damage")] public DamageSpecifier? Damage = new(); + + // Used for burns + + public List Colliding = new(); + + public bool Firing = false; + } + + public enum ThrusterType + { + Linear, + // Angular meaning rotational. + Angular, + } +} diff --git a/Content.Server/Shuttles/DockingSystem.cs b/Content.Server/Shuttles/EntitySystems/DockingSystem.cs similarity index 97% rename from Content.Server/Shuttles/DockingSystem.cs rename to Content.Server/Shuttles/EntitySystems/DockingSystem.cs index 0e77d19890..8d2a1d4e63 100644 --- a/Content.Server/Shuttles/DockingSystem.cs +++ b/Content.Server/Shuttles/EntitySystems/DockingSystem.cs @@ -1,8 +1,7 @@ -using System; using Content.Server.Doors.Components; using Content.Server.Power.Components; +using Content.Server.Shuttles.Components; using Content.Shared.Doors; -using Content.Shared.Shuttles; using Content.Shared.Verbs; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -13,25 +12,10 @@ using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Dynamics; -using Robust.Shared.Physics.Dynamics.Joints; using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; -namespace Content.Server.Shuttles +namespace Content.Server.Shuttles.EntitySystems { - [RegisterComponent] - public sealed class DockingComponent : SharedDockingComponent - { - [ViewVariables] - public DockingComponent? DockedWith; - - [ViewVariables] - public Joint? DockJoint; - - [ViewVariables] - public override bool Docked => DockedWith != null; - } - public sealed class DockingSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; diff --git a/Content.Server/Shuttles/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/EntitySystems/ShuttleConsoleSystem.cs similarity index 97% rename from Content.Server/Shuttles/ShuttleConsoleSystem.cs rename to Content.Server/Shuttles/EntitySystems/ShuttleConsoleSystem.cs index cbcbe867ef..e028546819 100644 --- a/Content.Server/Shuttles/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/EntitySystems/ShuttleConsoleSystem.cs @@ -1,20 +1,19 @@ -using System.Linq; using Content.Server.Alert; using Content.Server.Power.Components; +using Content.Server.Shuttles.Components; using Content.Shared.ActionBlocker; using Content.Shared.Alert; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Shuttles; +using Content.Shared.Shuttles.Components; using Content.Shared.Tag; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Log; -using Robust.Shared.Physics; using Robust.Shared.Utility; -namespace Content.Server.Shuttles +namespace Content.Server.Shuttles.EntitySystems { internal sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem { diff --git a/Content.Server/Shuttles/ShuttleSystem.cs b/Content.Server/Shuttles/EntitySystems/ShuttleSystem.cs similarity index 52% rename from Content.Server/Shuttles/ShuttleSystem.cs rename to Content.Server/Shuttles/EntitySystems/ShuttleSystem.cs index 45d3e1c4c9..e1a259fec9 100644 --- a/Content.Server/Shuttles/ShuttleSystem.cs +++ b/Content.Server/Shuttles/EntitySystems/ShuttleSystem.cs @@ -1,86 +1,54 @@ -using System; -using Content.Shared.Shuttles; +using System.Collections.Generic; +using Content.Server.Shuttles.Components; using JetBrains.Annotations; -using Robust.Server.Physics; -using Robust.Shared.Configuration; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Physics; -namespace Content.Server.Shuttles +namespace Content.Server.Shuttles.EntitySystems { [UsedImplicitly] internal sealed class ShuttleSystem : EntitySystem { - private const float TileMassMultiplier = 1f; + private const float TileMassMultiplier = 4f; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(HandleShuttleStartup); - SubscribeLocalEvent(HandleShuttleShutdown); + SubscribeLocalEvent(OnShuttleAdd); + SubscribeLocalEvent(OnShuttleStartup); + SubscribeLocalEvent(OnShuttleShutdown); - SubscribeLocalEvent(HandleGridInit); - SubscribeLocalEvent(HandleGridFixtureChange); + SubscribeLocalEvent(OnGridInit); + SubscribeLocalEvent(OnGridFixtureChange); } - private void HandleGridFixtureChange(GridFixtureChangeEvent args) + private void OnShuttleAdd(EntityUid uid, ShuttleComponent component, ComponentAdd args) + { + // Easier than doing it in the comp and they don't have constructors. + for (var i = 0; i < component.LinearThrusters.Length; i++) + { + component.LinearThrusters[i] = new List(); + } + } + + private void OnGridFixtureChange(GridFixtureChangeEvent args) { // Look this is jank but it's a placeholder until we design it. if (args.NewFixtures.Count == 0) return; - var body = args.NewFixtures[0].Body; - foreach (var fixture in args.NewFixtures) { fixture.Mass = fixture.Area * TileMassMultiplier; fixture.Restitution = 0.1f; } - - if (body.Owner.TryGetComponent(out ShuttleComponent? shuttleComponent)) - { - RecalculateSpeedMultiplier(shuttleComponent, body); - } - } - private void HandleGridInit(GridInitializeEvent ev) + private void OnGridInit(GridInitializeEvent ev) { EntityManager.GetEntity(ev.EntityUid).EnsureComponent(); } - /// - /// Cache the thrust available to this shuttle. - /// - private void RecalculateSpeedMultiplier(SharedShuttleComponent shuttle, PhysicsComponent physics) - { - // TODO: Need per direction speed (Never Eat Soggy Weetbix). - // TODO: This will need hella tweaking. - var thrusters = physics.FixtureCount; - - if (thrusters == 0) - { - shuttle.SpeedMultipler = 0f; - } - - const float ThrustPerThruster = 0.25f; - const float MinimumThrustRequired = 0.005f; - - // Just so someone can't slap a single thruster on a station and call it a day; need to hit a minimum amount. - var thrustRatio = Math.Max(0, thrusters * ThrustPerThruster / physics.Mass); - - if (thrustRatio < MinimumThrustRequired) - shuttle.SpeedMultipler = 0f; - - const float MaxThrust = 10f; - // This doesn't need to align with MinimumThrustRequired; if you set this higher it just means the first few additional - // thrusters won't do anything. - const float MinThrust = MinimumThrustRequired; - - shuttle.SpeedMultipler = MathF.Max(MinThrust, MathF.Min(MaxThrust, thrustRatio)); - } - - private void HandleShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args) + private void OnShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args) { if (!component.Owner.HasComponent()) { @@ -96,11 +64,6 @@ namespace Content.Server.Shuttles { Enable(physicsComponent); } - - if (component.Owner.TryGetComponent(out ShuttleComponent? shuttleComponent)) - { - RecalculateSpeedMultiplier(shuttleComponent, physicsComponent); - } } public void Toggle(ShuttleComponent component) @@ -136,7 +99,7 @@ namespace Content.Server.Shuttles component.FixedRotation = true; } - private void HandleShuttleShutdown(EntityUid uid, ShuttleComponent component, ComponentShutdown args) + private void OnShuttleShutdown(EntityUid uid, ShuttleComponent component, ComponentShutdown args) { if (!component.Owner.TryGetComponent(out PhysicsComponent? physicsComponent)) { diff --git a/Content.Server/Shuttles/EntitySystems/ThrusterSystem.cs b/Content.Server/Shuttles/EntitySystems/ThrusterSystem.cs new file mode 100644 index 0000000000..ea102f3cb2 --- /dev/null +++ b/Content.Server/Shuttles/EntitySystems/ThrusterSystem.cs @@ -0,0 +1,476 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Audio; +using Content.Server.Power.Components; +using Content.Server.Shuttles.Components; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.Interaction; +using Content.Shared.Maps; +using Content.Shared.Physics; +using Content.Shared.Shuttles.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Shuttles.EntitySystems +{ + public sealed class ThrusterSystem : EntitySystem + { + [Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!; + [Robust.Shared.IoC.Dependency] private readonly AmbientSoundSystem _ambient = default!; + [Robust.Shared.IoC.Dependency] private readonly SharedBroadphaseSystem _broadphase = default!; + [Robust.Shared.IoC.Dependency] private readonly DamageableSystem _damageable = default!; + + // Essentially whenever thruster enables we update the shuttle's available impulses which are used for movement. + // This is done for each direction available. + + public const string BurnFixture = "thruster-burn"; + + private readonly HashSet _activeThrusters = new(); + + // Used for accumulating burn if someone touches a firing thruster. + + private float _accumulator; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnActivateThruster); + SubscribeLocalEvent(OnThrusterInit); + SubscribeLocalEvent(OnThrusterShutdown); + SubscribeLocalEvent(OnPowerChange); + SubscribeLocalEvent(OnAnchorChange); + SubscribeLocalEvent(OnRotate); + + SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnEndCollide); + + _mapManager.TileChanged += OnTileChange; + } + + public override void Shutdown() + { + base.Shutdown(); + _mapManager.TileChanged -= OnTileChange; + } + + private void OnTileChange(object? sender, TileChangedEventArgs e) + { + // If the old tile was space but the new one isn't then disable all adjacent thrusters + if (e.NewTile.IsSpace() || !e.OldTile.IsSpace()) return; + + var tilePos = e.NewTile.GridIndices; + + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) continue; + + var checkPos = tilePos + new Vector2i(x, y); + + foreach (var ent in _mapManager.GetGrid(e.NewTile.GridIndex).GetAnchoredEntities(checkPos)) + { + if (!EntityManager.TryGetComponent(ent, out ThrusterComponent? thruster) || thruster.Type == ThrusterType.Angular) continue; + + // Work out if the thruster is facing this direction + var direction = EntityManager.GetComponent(ent).LocalRotation.ToWorldVec(); + + if (new Vector2i((int) direction.X, (int) direction.Y) != new Vector2i(x, y)) continue; + + DisableThruster(ent, thruster); + } + } + } + } + + private void OnActivateThruster(EntityUid uid, ThrusterComponent component, ActivateInWorldEvent args) + { + component.Enabled ^= true; + } + + /// + /// If the thruster rotates change the direction where the linear thrust is applied + /// + private void OnRotate(EntityUid uid, ThrusterComponent component, ref RotateEvent args) + { + // TODO: Disable visualizer for old direction + + if (!component.IsOn || + component.Type != ThrusterType.Linear || + !EntityManager.TryGetComponent(uid, out TransformComponent? xform) || + !_mapManager.TryGetGrid(xform.GridID, out var grid) || + !EntityManager.TryGetComponent(grid.GridEntityId, out ShuttleComponent? shuttleComponent)) return; + + var oldDirection = (int) args.OldRotation.GetCardinalDir() / 2; + var direction = (int) args.NewRotation.GetCardinalDir() / 2; + + shuttleComponent.LinearThrusterImpulse[oldDirection] -= component.Impulse; + DebugTools.Assert(shuttleComponent.LinearThrusters[oldDirection].Contains(component)); + shuttleComponent.LinearThrusters[oldDirection].Remove(component); + + shuttleComponent.LinearThrusterImpulse[direction] += component.Impulse; + DebugTools.Assert(!shuttleComponent.LinearThrusters[direction].Contains(component)); + shuttleComponent.LinearThrusters[direction].Add(component); + } + + private void OnAnchorChange(EntityUid uid, ThrusterComponent component, ref AnchorStateChangedEvent args) + { + if (args.Anchored && CanEnable(uid, component)) + { + EnableThruster(uid, component); + } + else + { + DisableThruster(uid, component); + } + } + + private void OnThrusterInit(EntityUid uid, ThrusterComponent component, ComponentInit args) + { + _ambient.SetAmbience(uid, false); + + if (!component.Enabled) + { + return; + } + + if (CanEnable(uid, component)) + { + EnableThruster(uid, component); + } + } + + private void OnThrusterShutdown(EntityUid uid, ThrusterComponent component, ComponentShutdown args) + { + DisableThruster(uid, component); + } + + private void OnPowerChange(EntityUid uid, ThrusterComponent component, PowerChangedEvent args) + { + if (args.Powered && CanEnable(uid, component)) + { + EnableThruster(uid, component); + } + else + { + DisableThruster(uid, component); + } + } + + /// + /// Tries to enable the thruster and turn it on. If it's already enabled it does nothing. + /// + public void EnableThruster(EntityUid uid, ThrusterComponent component, TransformComponent? xform = null) + { + if (component.IsOn || + !Resolve(uid, ref xform) || + !_mapManager.TryGetGrid(xform.GridID, out var grid)) return; + + component.IsOn = true; + + if (!EntityManager.TryGetComponent(grid.GridEntityId, out ShuttleComponent? shuttleComponent)) return; + + Logger.DebugS("thruster", $"Enabled thruster {uid}"); + + switch (component.Type) + { + case ThrusterType.Linear: + var direction = (int) xform.LocalRotation.GetCardinalDir() / 2; + + shuttleComponent.LinearThrusterImpulse[direction] += component.Impulse; + DebugTools.Assert(!shuttleComponent.LinearThrusters[direction].Contains(component)); + shuttleComponent.LinearThrusters[direction].Add(component); + + // Don't just add / remove the fixture whenever the thruster fires because perf + if (EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent) && + component.BurnPoly.Count > 0) + { + var shape = new PolygonShape(); + + shape.SetVertices(component.BurnPoly); + + var fixture = new Fixture(physicsComponent, shape) + { + ID = BurnFixture, + Hard = false, + CollisionLayer = (int) CollisionGroup.MobImpassable + }; + + _broadphase.CreateFixture(physicsComponent, fixture); + } + + break; + case ThrusterType.Angular: + shuttleComponent.AngularThrust += component.Impulse; + DebugTools.Assert(!shuttleComponent.AngularThrusters.Contains(component)); + shuttleComponent.AngularThrusters.Add(component); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (EntityManager.TryGetComponent(uid, out SharedAppearanceComponent? appearanceComponent)) + { + appearanceComponent.SetData(ThrusterVisualState.State, true); + } + + _ambient.SetAmbience(uid, true); + } + + /// + /// Tries to disable the thruster. + /// + /// + /// + /// + /// + public void DisableThruster(EntityUid uid, ThrusterComponent component, TransformComponent? xform = null) + { + if (!component.IsOn || + !Resolve(uid, ref xform) || + !_mapManager.TryGetGrid(xform.GridID, out var grid)) return; + + component.IsOn = false; + + if (!EntityManager.TryGetComponent(grid.GridEntityId, out ShuttleComponent? shuttleComponent)) return; + + Logger.DebugS("thruster", $"Disabled thruster {uid}"); + + switch (component.Type) + { + case ThrusterType.Linear: + var direction = ((int) xform.LocalRotation.GetCardinalDir() / 2); + + shuttleComponent.LinearThrusterImpulse[direction] -= component.Impulse; + DebugTools.Assert(shuttleComponent.LinearThrusters[direction].Contains(component)); + shuttleComponent.LinearThrusters[direction].Remove(component); + break; + case ThrusterType.Angular: + shuttleComponent.AngularThrust -= component.Impulse; + DebugTools.Assert(shuttleComponent.AngularThrusters.Contains(component)); + shuttleComponent.AngularThrusters.Remove(component); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (EntityManager.TryGetComponent(uid, out SharedAppearanceComponent? appearanceComponent)) + { + appearanceComponent.SetData(ThrusterVisualState.State, false); + } + + _ambient.SetAmbience(uid, false); + + if (EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent)) + { + _broadphase.DestroyFixture(physicsComponent, BurnFixture); + } + + _activeThrusters.Remove(component); + component.Colliding.Clear(); + } + + public bool CanEnable(EntityUid uid, ThrusterComponent component) + { + if (!component.Enabled) return false; + + var xform = EntityManager.GetComponent(uid); + + if (!xform.Anchored || + EntityManager.TryGetComponent(uid, out ApcPowerReceiverComponent? receiver) && !receiver.Powered) + { + return false; + } + + if (component.Type == ThrusterType.Angular) + return true; + + var (x, y) = xform.LocalPosition + xform.LocalRotation.Opposite().ToWorldVec(); + var tile = _mapManager.GetGrid(xform.GridID).GetTileRef(new Vector2i((int) Math.Floor(x), (int) Math.Floor(y))); + + return tile.Tile.IsSpace(); + } + + #region Burning + + public override void Update(float frameTime) + { + base.Update(frameTime); + + _accumulator += frameTime; + + if (_accumulator < 1) return; + + _accumulator -= 1; + + foreach (var comp in _activeThrusters.ToArray()) + { + if (!comp.Firing || comp.Damage == null || comp.Paused || comp.Deleted) continue; + + DebugTools.Assert(comp.Colliding.Count > 0); + + foreach (var uid in comp.Colliding.ToArray()) + { + _damageable.TryChangeDamage(uid, comp.Damage); + } + } + } + + private void OnStartCollide(EntityUid uid, ThrusterComponent component, StartCollideEvent args) + { + if (args.OurFixture.ID != BurnFixture) return; + + _activeThrusters.Add(component); + component.Colliding.Add(args.OtherFixture.Body.OwnerUid); + } + + private void OnEndCollide(EntityUid uid, ThrusterComponent component, EndCollideEvent args) + { + if (args.OurFixture.ID != BurnFixture) return; + + component.Colliding.Remove(args.OtherFixture.Body.OwnerUid); + + if (component.Colliding.Count == 0) + { + _activeThrusters.Remove(component); + } + } + + /// + /// Considers a thrust direction as being active. + /// + public void EnableThrustDirection(ShuttleComponent component, DirectionFlag direction) + { + if ((component.ThrustDirections & direction) != 0x0) return; + + component.ThrustDirections |= direction; + + if ((direction & (DirectionFlag.East | DirectionFlag.West)) != 0x0) + { + switch (component.Mode) + { + case ShuttleMode.Cruise: + foreach (var comp in component.AngularThrusters) + { + if (!EntityManager.TryGetComponent(comp.OwnerUid, out SharedAppearanceComponent? appearanceComponent)) + continue; + + comp.Firing = true; + appearanceComponent.SetData(ThrusterVisualState.Thrusting, true); + } + break; + case ShuttleMode.Docking: + var index = GetFlagIndex(direction); + + foreach (var comp in component.LinearThrusters[index]) + { + if (!EntityManager.TryGetComponent(comp.OwnerUid, out SharedAppearanceComponent? appearanceComponent)) + continue; + + comp.Firing = true; + appearanceComponent.SetData(ThrusterVisualState.Thrusting, true); + } + + break; + } + } + else + { + var index = GetFlagIndex(direction); + + foreach (var comp in component.LinearThrusters[index]) + { + if (!EntityManager.TryGetComponent(comp.OwnerUid, out SharedAppearanceComponent? appearanceComponent)) + continue; + + comp.Firing = true; + appearanceComponent.SetData(ThrusterVisualState.Thrusting, true); + } + } + } + + /// + /// Disables a thrust direction. + /// + public void DisableThrustDirection(ShuttleComponent component, DirectionFlag direction) + { + if ((component.ThrustDirections & direction) == 0x0) return; + + component.ThrustDirections &= ~direction; + + if ((direction & (DirectionFlag.East | DirectionFlag.West)) != 0x0) + { + switch (component.Mode) + { + case ShuttleMode.Cruise: + foreach (var comp in component.AngularThrusters) + { + if (!EntityManager.TryGetComponent(comp.OwnerUid, out SharedAppearanceComponent? appearanceComponent)) + continue; + + comp.Firing = false; + appearanceComponent.SetData(ThrusterVisualState.Thrusting, false); + } + break; + case ShuttleMode.Docking: + var index = GetFlagIndex(direction); + + foreach (var comp in component.LinearThrusters[index]) + { + if (!EntityManager.TryGetComponent(comp.OwnerUid, out SharedAppearanceComponent? appearanceComponent)) + continue; + + comp.Firing = false; + appearanceComponent.SetData(ThrusterVisualState.Thrusting, false); + } + + break; + } + } + else + { + var index = GetFlagIndex(direction); + + foreach (var comp in component.LinearThrusters[index]) + { + if (!EntityManager.TryGetComponent(comp.OwnerUid, out SharedAppearanceComponent? appearanceComponent)) + continue; + + comp.Firing = false; + appearanceComponent.SetData(ThrusterVisualState.Thrusting, false); + } + } + } + + public void DisableAllThrustDirections(ShuttleComponent component) + { + foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag))) + { + DisableThrustDirection(component, dir); + } + + DebugTools.Assert(component.ThrustDirections == DirectionFlag.None); + } + + #endregion + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetFlagIndex(DirectionFlag flag) + { + return (int) Math.Log2((int) flag); + } + } +} diff --git a/Content.Server/Shuttles/ShuttleComponent.cs b/Content.Server/Shuttles/ShuttleComponent.cs deleted file mode 100644 index 952b3bda19..0000000000 --- a/Content.Server/Shuttles/ShuttleComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Shared.Shuttles; -using Robust.Shared.GameObjects; - -namespace Content.Server.Shuttles -{ - [RegisterComponent] - public sealed class ShuttleComponent : SharedShuttleComponent {} -} diff --git a/Content.Shared/Shuttles/PilotComponent.cs b/Content.Shared/Shuttles/Components/PilotComponent.cs similarity index 97% rename from Content.Shared/Shuttles/PilotComponent.cs rename to Content.Shared/Shuttles/Components/PilotComponent.cs index 889a618140..3521685492 100644 --- a/Content.Shared/Shuttles/PilotComponent.cs +++ b/Content.Shared/Shuttles/Components/PilotComponent.cs @@ -7,7 +7,7 @@ using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; -namespace Content.Shared.Shuttles +namespace Content.Shared.Shuttles.Components { /// /// Stores what shuttle this entity is currently piloting. diff --git a/Content.Shared/Shuttles/SharedDockingComponent.cs b/Content.Shared/Shuttles/Components/SharedDockingComponent.cs similarity index 87% rename from Content.Shared/Shuttles/SharedDockingComponent.cs rename to Content.Shared/Shuttles/Components/SharedDockingComponent.cs index 30baf5be43..de039ddb1b 100644 --- a/Content.Shared/Shuttles/SharedDockingComponent.cs +++ b/Content.Shared/Shuttles/Components/SharedDockingComponent.cs @@ -1,8 +1,7 @@ using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; using Robust.Shared.ViewVariables; -namespace Content.Shared.Shuttles +namespace Content.Shared.Shuttles.Components { public abstract class SharedDockingComponent : Component { diff --git a/Content.Shared/Shuttles/SharedShuttleComponent.cs b/Content.Shared/Shuttles/Components/SharedShuttleComponent.cs similarity index 65% rename from Content.Shared/Shuttles/SharedShuttleComponent.cs rename to Content.Shared/Shuttles/Components/SharedShuttleComponent.cs index 3e3a7765b6..a527715ee5 100644 --- a/Content.Shared/Shuttles/SharedShuttleComponent.cs +++ b/Content.Shared/Shuttles/Components/SharedShuttleComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.ViewVariables; -namespace Content.Shared.Shuttles +namespace Content.Shared.Shuttles.Components { public abstract class SharedShuttleComponent : Component { @@ -10,12 +10,6 @@ namespace Content.Shared.Shuttles [ViewVariables] public virtual bool Enabled { get; set; } = true; - /// - /// How much our linear impulse is multiplied by. - /// - [ViewVariables(VVAccess.ReadWrite)] - public float SpeedMultipler { get; set; } = 200.0f; - [ViewVariables] public ShuttleMode Mode { get; set; } = ShuttleMode.Cruise; } diff --git a/Content.Shared/Shuttles/SharedShuttleConsoleComponent.cs b/Content.Shared/Shuttles/Components/SharedShuttleConsoleComponent.cs similarity index 87% rename from Content.Shared/Shuttles/SharedShuttleConsoleComponent.cs rename to Content.Shared/Shuttles/Components/SharedShuttleConsoleComponent.cs index 13c1c4a211..8a5e51e43e 100644 --- a/Content.Shared/Shuttles/SharedShuttleConsoleComponent.cs +++ b/Content.Shared/Shuttles/Components/SharedShuttleConsoleComponent.cs @@ -1,7 +1,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -namespace Content.Shared.Shuttles +namespace Content.Shared.Shuttles.Components { /// /// Interact with to start piloting a shuttle. diff --git a/Content.Shared/Shuttles/Components/SharedThrusterComponent.cs b/Content.Shared/Shuttles/Components/SharedThrusterComponent.cs new file mode 100644 index 0000000000..7b258be986 --- /dev/null +++ b/Content.Shared/Shuttles/Components/SharedThrusterComponent.cs @@ -0,0 +1,12 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.Shuttles.Components +{ + [Serializable, NetSerializable] + public enum ThrusterVisualState : byte + { + State, + Thrusting, + } +} diff --git a/Content.Shared/Shuttles/SharedShuttleConsoleSystem.cs b/Content.Shared/Shuttles/SharedShuttleConsoleSystem.cs index 8e9070571c..f3bb313daa 100644 --- a/Content.Shared/Shuttles/SharedShuttleConsoleSystem.cs +++ b/Content.Shared/Shuttles/SharedShuttleConsoleSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Movement; +using Content.Shared.Shuttles.Components; using Robust.Shared.GameObjects; namespace Content.Shared.Shuttles diff --git a/Resources/Audio/Effects/shuttle_thruster.ogg b/Resources/Audio/Effects/shuttle_thruster.ogg new file mode 100644 index 0000000000..1100a1e838 Binary files /dev/null and b/Resources/Audio/Effects/shuttle_thruster.ogg differ diff --git a/Resources/Locale/en-US/rotation/components/rotatable-component.ftl b/Resources/Locale/en-US/rotation/components/rotatable-component.ftl index 3c373f130a..bc23e488fb 100644 --- a/Resources/Locale/en-US/rotation/components/rotatable-component.ftl +++ b/Resources/Locale/en-US/rotation/components/rotatable-component.ftl @@ -1,4 +1,4 @@ -# RotatableComponenet +# RotatableComponent rotatable-component-try-rotate-stuck = It's stuck. # RotateVerb diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_shuttle.yml b/Resources/Prototypes/Catalog/Cargo/cargo_shuttle.yml new file mode 100644 index 0000000000..d345a9d770 --- /dev/null +++ b/Resources/Prototypes/Catalog/Cargo/cargo_shuttle.yml @@ -0,0 +1,23 @@ +- type: cargoProduct + name: shuttle thruster + id: ShuttleThruster + description: A thruster that allows a shuttle to move. + icon: + sprite: Structures/Shuttles/thruster.rsi + state: base + product: Thruster + cost: 1500 + category: Shuttle + group: market + +- type: cargoProduct + name: shuttle gyroscope + id: ShuttleGyroscope + description: 1 gyroscope for use in rotating a shuttle. + icon: + sprite: Structures/Shuttles/gyroscope.rsi + state: base + product: Gyroscope + cost: 4000 + category: Shuttle + group: market diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 3f1511c3a8..e7114ab462 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -269,6 +269,8 @@ - EngineSingularityGenerator - EngineSingularityContainment - EngineParticleAccelerator + - ShuttleThruster + - ShuttleGyroscope - AtmosphericsAir - AtmosphericsOxygen - AtmosphericsNitrogen diff --git a/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml new file mode 100644 index 0000000000..83fcb36daa --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml @@ -0,0 +1,85 @@ +- type: entity + id: BaseThruster + parent: BaseStructureDynamic + name: thruster + description: It goes nyooooooom. + abstract: true + components: + - type: AmbientSound + range: 4 + volume: -5 + sound: + path: /Audio/Effects/shuttle_thruster.ogg + - type: Transform + anchored: true + - type: Rotatable + rotateWhileAnchored: true + - type: Thruster + damage: + groups: + Burn: 40 + - type: InteractionOutline + - type: Sprite + netsync: false + - type: Appearance + visuals: + - type: ThrusterVisualizer + - type: ApcPowerReceiver + powerLoad: 1500 + - type: ExtensionCableReceiver + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 300 # Considering we need a lot of thrusters didn't want to make an individual one too tanky + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + placement: + mode: SnapgridCenter + +- type: entity + id: Thruster + parent: BaseThruster + name: thruster + description: It goes nyooooooom. + components: + - type: Sprite + sprite: Structures/Shuttles/thruster.rsi + layers: + - state: base + map: ["enum.ThrusterVisualLayers.Base"] + - state: thrust + map: ["enum.ThrusterVisualLayers.ThrustOn"] + shader: unshaded + - state: thrust_burn_unshaded + map: ["enum.ThrusterVisualLayers.ThrustingUnshaded"] + shader: unshaded + offset: 0, 1 + +- type: entity + id: Gyroscope + parent: BaseThruster + name: Gyroscope + description: Increases the shuttle's potential angular rotation. + components: + - type: Thruster + thrusterType: Angular + impulse: 2 + - type: Sprite + # Listen I'm not the biggest fan of the sprite but it was the most appropriate thing I could find. + sprite: Structures/Shuttles/gyroscope.rsi + layers: + - state: base + map: ["enum.ThrusterVisualLayers.Base"] + - state: thrust + map: ["enum.ThrusterVisualLayers.ThrustOn"] + shader: unshaded + - state: thrust_burn + map: [ "enum.ThrusterVisualLayers.Thrusting" ] + - state: thrust_burn_unshaded + map: ["enum.ThrusterVisualLayers.ThrustingUnshaded"] + shader: unshaded diff --git a/Resources/Textures/Structures/Shuttles/gyroscope.rsi/base.png b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/base.png new file mode 100644 index 0000000000..94cda7007f Binary files /dev/null and b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/base.png differ diff --git a/Resources/Textures/Structures/Shuttles/gyroscope.rsi/meta.json b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/meta.json new file mode 100644 index 0000000000..574272c09a --- /dev/null +++ b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/meta.json @@ -0,0 +1,43 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/tree/23e73e051a838bc190a589d6d464a318c641b6a5", + "license": "CC-BY-SA-3.0", + "states": [ + { + "name": "base" + }, + { + "name": "thrust" + }, + { + "name": "thrust_burn", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "thrust_burn_unshaded", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust.png b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust.png new file mode 100644 index 0000000000..718c9bd738 Binary files /dev/null and b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust.png differ diff --git a/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust_burn.png b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust_burn.png new file mode 100644 index 0000000000..0563da7e62 Binary files /dev/null and b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust_burn.png differ diff --git a/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust_burn_unshaded.png b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust_burn_unshaded.png new file mode 100644 index 0000000000..7c955b68f9 Binary files /dev/null and b/Resources/Textures/Structures/Shuttles/gyroscope.rsi/thrust_burn_unshaded.png differ diff --git a/Resources/Textures/Structures/Shuttles/thruster.rsi/base.png b/Resources/Textures/Structures/Shuttles/thruster.rsi/base.png new file mode 100644 index 0000000000..b7879f970a Binary files /dev/null and b/Resources/Textures/Structures/Shuttles/thruster.rsi/base.png differ diff --git a/Resources/Textures/Structures/Shuttles/thruster.rsi/meta.json b/Resources/Textures/Structures/Shuttles/thruster.rsi/meta.json new file mode 100644 index 0000000000..c676ff523d --- /dev/null +++ b/Resources/Textures/Structures/Shuttles/thruster.rsi/meta.json @@ -0,0 +1,83 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "copyright": "Taken from https://github.com/Baystation12/Baystation12/tree/7274e5654c3cf82d4104dbf818ef00fdc0082aa8", + "license": "CC-BY-SA-3.0", + "states": [ + { + "name": "base", + "directions": 4 + }, + { + "name": "thrust", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "thrust_burn_unshaded", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Shuttles/thruster.rsi/thrust.png b/Resources/Textures/Structures/Shuttles/thruster.rsi/thrust.png new file mode 100644 index 0000000000..15c480707f Binary files /dev/null and b/Resources/Textures/Structures/Shuttles/thruster.rsi/thrust.png differ diff --git a/Resources/Textures/Structures/Shuttles/thruster.rsi/thrust_burn_unshaded.png b/Resources/Textures/Structures/Shuttles/thruster.rsi/thrust_burn_unshaded.png new file mode 100644 index 0000000000..bfdfde09fb Binary files /dev/null and b/Resources/Textures/Structures/Shuttles/thruster.rsi/thrust_burn_unshaded.png differ