diff --git a/Content.Client/Vehicle/ClientVehicleSystem.cs b/Content.Client/Vehicle/ClientVehicleSystem.cs new file mode 100644 index 00000000000..0716f7294bb --- /dev/null +++ b/Content.Client/Vehicle/ClientVehicleSystem.cs @@ -0,0 +1,27 @@ +using Content.Shared.Vehicle; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Client.Vehicle +{ + public sealed class ClientVehicleSystem : EntitySystem + { + [Dependency] private readonly IEntityManager _entityManager = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnHandleState); + } + + private void OnHandleState(EntityUid uid, RiddenVehicleComponent component, ref ComponentHandleState args) + { + if (args.Current is not RiddenVehicleComponentState state) + return; + + component.Riders.Clear(); + component.Riders.AddRange(state.Riders); + } + } +} diff --git a/Content.Server/Vehicles/Components/VehicleComponent.cs b/Content.Server/Vehicles/Components/VehicleComponent.cs index fc1602225f0..dd670434a4a 100644 --- a/Content.Server/Vehicles/Components/VehicleComponent.cs +++ b/Content.Server/Vehicles/Components/VehicleComponent.cs @@ -1,19 +1,26 @@ -// /Content.Server/Vehicles/Components/VehicleComponent.cs using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; -namespace Content.Server.Vehicles.Components +namespace Content.Shared.Vehicle { [RegisterComponent] public sealed class VehicleComponent : Component { + public override string Name => "Vehicle"; + [DataField("maxOccupants")] - public int MaxOccupants { get; set; } = 4; + public int MaxOccupants = 1; [DataField("maxDrivers")] - public int MaxDrivers { get; set; } = 1; + public int MaxDrivers = 1; + + [DataField("canMove")] + public bool CanMove = true; + + [DataField("keyType")] + public string? KeyType; - public List Occupants { get; set; } = new(); - public EntityUid? Driver { get; set; } + [ViewVariables] + public List Occupants = new(); } } diff --git a/Content.Server/Vehicles/Systems/VehicleSystem.cs b/Content.Server/Vehicles/Systems/VehicleSystem.cs index fe54fac0b81..baab4cafd1c 100644 --- a/Content.Server/Vehicles/Systems/VehicleSystem.cs +++ b/Content.Server/Vehicles/Systems/VehicleSystem.cs @@ -1,6 +1,11 @@ -// /Content.Server/Vehicles/Systems/VehicleSystem.cs using Content.Server.Vehicles.Components; +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Vehicle; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Player; @@ -13,12 +18,16 @@ public sealed class VehicleSystem : EntitySystem [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly SharedBuckleSystem _buckleSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent>(AddEnterVehicleVerb); SubscribeLocalEvent(OnMoveInput); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnBuckleChange); } private void AddEnterVehicleVerb(EntityUid uid, VehicleComponent component, GetVerbsEvent args) @@ -40,12 +49,20 @@ private void AddEnterVehicleVerb(EntityUid uid, VehicleComponent component, GetV private void EnterVehicle(EntityUid user, EntityUid vehicle, VehicleComponent component) { - component.Occupants.Add(user); - if (component.Driver == null) + if (component.Occupants.Count >= component.MaxOccupants) + { + _popupSystem.PopupEntity("The vehicle is full.", vehicle, Filter.Entities(user)); + return; + } + + if (_buckleSystem.TryBuckle(user, user, vehicle)) { - component.Driver = user; + component.Occupants.Add(user); + if (component.Driver == null) + { + component.Driver = user; + } } - //TODO Additional logic to attach user to vehicle, update UI, etc maybe. } private void OnMoveInput(EntityUid uid, VehicleComponent component, ref MoveInputEvent args) @@ -53,7 +70,43 @@ private void OnMoveInput(EntityUid uid, VehicleComponent component, ref MoveInpu if (component.Driver == null || component.Driver != args.User) return; - // Handle vehicle movement logic here. Surely theres a component for that im just tossing ideas + // Handle vehicle movement logic here. + var transform = EntityManager.GetComponent(uid); + var direction = args.Direction.ToVec(); + transform.Coordinates += direction * component.Speed * _gameTiming.FrameTime; + } + + private void OnInteractHand(EntityUid uid, VehicleComponent component, InteractHandEvent args) + { + if (args.Handled) + return; + + if (component.Occupants.Contains(args.User)) + { + _buckleSystem.TryUnbuckle(args.User, args.User, false); + } + else + { + EnterVehicle(args.User, uid, component); + } + + args.Handled = true; + } + + private void OnBuckleChange(EntityUid uid, VehicleComponent component, BuckleChangeEvent args) + { + if (args.Buckled) + { + component.Occupants.Add(args.BuckleEntity); + } + else + { + component.Occupants.Remove(args.BuckleEntity); + if (component.Driver == args.BuckleEntity) + { + component.Driver = component.Occupants.Count > 0 ? component.Occupants[0] : null; + } + } } } } diff --git a/Content.Shared/Buckle/Components/StrapComponent.cs b/Content.Shared/Buckle/Components/StrapComponent.cs index 7585ba0fe88..e393cfd588b 100644 --- a/Content.Shared/Buckle/Components/StrapComponent.cs +++ b/Content.Shared/Buckle/Components/StrapComponent.cs @@ -21,6 +21,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] public float MaxBuckleDistance = 1.0f; /// @@ -42,6 +43,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] public int Size = 100; /// @@ -55,6 +57,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] public bool Enabled = true; /// @@ -62,6 +65,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] public AlertType BuckledAlertType = AlertType.Buckled; /// @@ -69,6 +73,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] public SoundSpecifier BuckleSound = new SoundPathSpecifier("/Audio/Effects/buckle.ogg"); /// @@ -76,6 +81,7 @@ public sealed partial class StrapComponent : Component /// [DataField] [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] public SoundSpecifier UnbuckleSound = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg"); } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.cs b/Content.Shared/Buckle/SharedBuckleSystem.cs index a7ab4a4b10f..77b24115dff 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.cs @@ -1,3 +1,11 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Verbs; +using Content.Shared.Physics; +using Content.Shared.Throwing; +using Content.Shared.Movement; +using Content.Shared.Movement.Events; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.Alert; @@ -15,6 +23,7 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Player; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Shared.Buckle; @@ -51,6 +60,11 @@ public override void Initialize() InitializeStrap(); } + private void InitializeStrap() + { + // Initialization logic for straps... + } + private void InitializeBuckle() { SubscribeLocalEvent(OnBuckleStartup); @@ -159,15 +173,15 @@ private void UpdateBuckleStatus(EntityUid uid, BuckleComponent component) strap.BuckledEntities.Add(uid); strap.OccupiedSize += component.Size; - Dirty(strap); + Dirty(component.BuckledTo.Value, strap); } - private bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid, BuckleComponent? buckleComp = null, StrapComponent? strapComp = null) + public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid, BuckleComponent? buckleComp = null, StrapComponent? strapComp = null) { if (!Resolve(buckleUid, ref buckleComp, false) || !Resolve(strapUid, ref strapComp, false)) return false; - if (buckleComp.Buckled || !CanBuckle(buckleUid, strapUid, buckleComp, strapComp)) + if (buckleComp.Buckled || !CanBuckle(buckleUid, userUid, strapUid, out strapComp, buckleComp)) return false; buckleComp.Buckled = true; @@ -189,7 +203,7 @@ private bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUi return true; } - private bool TryUnbuckle(EntityUid buckleUid, EntityUid userUid, bool force, BuckleComponent? buckleComp = null, StrapComponent? strapComp = null) + public bool TryUnbuckle(EntityUid buckleUid, EntityUid userUid, bool force, BuckleComponent? buckleComp = null, StrapComponent? strapComp = null) { if (!Resolve(buckleUid, ref buckleComp, false) || !buckleComp.Buckled || !Resolve(buckleComp.BuckledTo, ref strapComp, false)) return false; @@ -206,18 +220,18 @@ private bool TryUnbuckle(EntityUid buckleUid, EntityUid userUid, bool force, Buc if (strapComp.BuckledEntities.Remove(buckleUid)) { strapComp.OccupiedSize -= buckleComp.Size; - Dirty(strapComp); + Dirty(strapComp.Owner, strapComp); } _joints.RefreshRelay(buckleUid); - Appearance.SetData(strapUid, StrapVisuals.State, strapComp.BuckledEntities.Count != 0); + Appearance.SetData(strapComp.Owner, StrapVisuals.State, strapComp.BuckledEntities.Count != 0); - if (!TerminatingOrDeleted(strapUid)) - _audio.PlayPredicted(strapComp.UnbuckleSound, strapUid, userUid); + if (!TerminatingOrDeleted(strapComp.Owner)) + _audio.PlayPredicted(strapComp.UnbuckleSound, strapComp.Owner, userUid); - var ev = new BuckleChangeEvent(strapUid, buckleUid, false); + var ev = new BuckleChangeEvent(strapComp.Owner, buckleUid, false); RaiseLocalEvent(buckleUid, ref ev); - RaiseLocalEvent(strapUid, ref ev); + RaiseLocalEvent(strapComp.Owner, ref ev); return true; } diff --git a/Content.Shared/Key/KeyComponent.cs b/Content.Shared/Key/KeyComponent.cs new file mode 100644 index 00000000000..1caed5bf69f --- /dev/null +++ b/Content.Shared/Key/KeyComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Key +{ + [RegisterComponent] + public partial class KeyComponent : Component + { + public override string Name => "Key"; + + [DataField("keyId")] + public string KeyId = string.Empty; + } +} diff --git a/Content.Shared/Key/KeyRequiredComponent.cs b/Content.Shared/Key/KeyRequiredComponent.cs new file mode 100644 index 00000000000..2b8500c7ad6 --- /dev/null +++ b/Content.Shared/Key/KeyRequiredComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Key +{ + [RegisterComponent] + public partial class KeyRequiredComponent : Component + { + public override string Name => "KeyRequired"; + + [DataField("requiredKeyId")] + public string RequiredKeyId = string.Empty; + } +} diff --git a/Content.Shared/Key/KeySystem.cs b/Content.Shared/Key/KeySystem.cs new file mode 100644 index 00000000000..b91a63533c6 --- /dev/null +++ b/Content.Shared/Key/KeySystem.cs @@ -0,0 +1,28 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Shared.Key +{ + public sealed class KeySystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnKeyRequiredStartup); + } + + private void OnKeyRequiredStartup(EntityUid uid, KeyRequiredComponent component, ComponentStartup args) + { + // Logic to handle key requirements + } + + public bool HasValidKey(EntityUid uid, string requiredKeyId) + { + if (EntityManager.TryGetComponent(uid, out KeyComponent? keyComp)) + { + return keyComp.KeyId == requiredKeyId; + } + return false; + } + } +} diff --git a/Content.Shared/Vehicles/Components/KeyRequiredComponent b/Content.Shared/Vehicles/Components/KeyRequiredComponent new file mode 100644 index 00000000000..e06101ac6f9 --- /dev/null +++ b/Content.Shared/Vehicles/Components/KeyRequiredComponent @@ -0,0 +1,18 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.Vehicle +{ + [RegisterComponent] + public sealed class KeyRequiredComponent : Component + { + public override string Name => "KeyRequired"; + + [DataField("keyType")] + public string KeyType = string.Empty; + + [ViewVariables] + public EntityUid? InsertedKey; + } +} diff --git a/Content.Shared/Vehicles/Components/RiddenVehicleComponent.cs b/Content.Shared/Vehicles/Components/RiddenVehicleComponent.cs new file mode 100644 index 00000000000..23a60c5166e --- /dev/null +++ b/Content.Shared/Vehicles/Components/RiddenVehicleComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.Vehicle +{ + [RegisterComponent] + public partial class RiddenVehicleComponent : Component + { + public override string Name => "RiddenVehicle"; + + [DataField("maxRiders")] + public int MaxRiders = 1; + + [ViewVariables] + public List Riders = new(); + } +} diff --git a/Content.Shared/Vehicles/Components/VehicleComponent.cs b/Content.Shared/Vehicles/Components/VehicleComponent.cs new file mode 100644 index 00000000000..e6a323da79b --- /dev/null +++ b/Content.Shared/Vehicles/Components/VehicleComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Vehicles.Components +{ + [RegisterComponent] + public sealed class VehicleComponent : Component + { + public override string Name => "Vehicle"; + + [DataField("maxOccupants")] + public int MaxOccupants = 1; + + [DataField("speed")] + public float Speed = 1.0f; + + [ViewVariables] + public List Occupants = new(); + + [ViewVariables] + public EntityUid? Driver; + } +} diff --git a/Content.Shared/Vehicles/KeySystem.cs b/Content.Shared/Vehicles/KeySystem.cs new file mode 100644 index 00000000000..fa7e09f64e3 --- /dev/null +++ b/Content.Shared/Vehicles/KeySystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Vehicle; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Player; +using Content.Shared.Vehicles.Components; +using Content.Shared.Interaction.Events; + + +namespace Content.Shared.Vehicle +{ + public sealed class KeySystem : EntitySystem + { + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnAltInteract); + } + + private void OnInteractUsing(EntityUid uid, KeyRequiredComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + if (component.InsertedKey == null && args.Used.HasComponent()) + { + component.InsertedKey = args.Used; + _popupSystem.PopupEntity("You insert the key.", uid, Filter.Entities(args.User)); + args.Handled = true; + } + } + + private void OnAltInteract(EntityUid uid, KeyRequiredComponent component, AltInteractEvent args) + { + if (args.Handled) + return; + + if (component.InsertedKey != null) + { + _popupSystem.PopupEntity("You remove the key.", uid, Filter.Entities(args.User)); + component.InsertedKey = null; + args.Handled = true; + } + } + } +} diff --git a/Content.Shared/Vehicles/RiddenVehicleComponentState.cs b/Content.Shared/Vehicles/RiddenVehicleComponentState.cs new file mode 100644 index 00000000000..e28e9a8fc80 --- /dev/null +++ b/Content.Shared/Vehicles/RiddenVehicleComponentState.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.Vehicle +{ + [Serializable, NetSerializable] + public sealed class RiddenVehicleComponentState : ComponentState + { + public List Riders; + + public RiddenVehicleComponentState(List riders) + { + Riders = riders; + } + } +} diff --git a/Content.Shared/Vehicles/RiddenVehicleSystem.cs b/Content.Shared/Vehicles/RiddenVehicleSystem.cs new file mode 100644 index 00000000000..a3e623fc2c6 --- /dev/null +++ b/Content.Shared/Vehicles/RiddenVehicleSystem.cs @@ -0,0 +1,53 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Vehicle; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Player; + +namespace Content.Shared.Vehicle +{ + public sealed class RiddenVehicleSystem : EntitySystem + { + [Dependency] private readonly SharedBuckleSystem _buckleSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnBuckleChange); + } + + private void OnInteractHand(EntityUid uid, RiddenVehicleComponent component, InteractHandEvent args) + { + if (args.Handled) + return; + + if (component.Riders.Contains(args.User)) + { + _buckleSystem.TryUnbuckle(args.User, args.User, false); + } + else + { + _buckleSystem.TryBuckle(args.User, args.User, uid); + } + + args.Handled = true; + } + + private void OnBuckleChange(EntityUid uid, RiddenVehicleComponent component, BuckleChangeEvent args) + { + if (args.Buckling) + { + component.Riders.Add(args.BuckledEntity); + } + else + { + component.Riders.Remove(args.BuckledEntity); + } + } + } +} diff --git a/Resources/Prototypes/Entities/Vehicles/vehicles.yml b/Resources/Prototypes/Entities/Vehicles/vehicles.yml index 1201f2a3ad0..075b7229016 100644 --- a/Resources/Prototypes/Entities/Vehicles/vehicles.yml +++ b/Resources/Prototypes/Entities/Vehicles/vehicles.yml @@ -24,3 +24,27 @@ - type: ContainerContainer containers: vehicle_storage: !type:Container + +- type: entity + id: VehicleSecway + name: Secway + description: A simple personal transportation device. + components: + - type: Sprite + sprite: Objects/Vehicles/secway.rsi + state: secway + - type: Physics + bodyType: Dynamic + - type: Vehicle + maxOccupants: 1 + maxDrivers: 1 + speed: 2.0 + - type: Buckle + range: 1.0 + delay: 0.5 + - type: Pullable + - type: Interactable + - type: UseDelay + delay: 0.5 + - type: Item + size: Large diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/Vehicles.yml b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/Vehicles.yml new file mode 100644 index 00000000000..f5c8d41e9ca --- /dev/null +++ b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/Vehicles.yml @@ -0,0 +1,13 @@ +- type: entity + name: Secway Spawner + id: SpawnVehicleSecway + parent: MarkerBase + components: + - type: Sprite + layers: + - state: green + - sprite: Objects/Vehicles/secway.rsi + state: keys + - type: ConditionalSpawner + prototypes: + - VehicleSecway