From bf686302ba91883258c516f4fe1983178315f0ed Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Tue, 22 Apr 2025 13:41:30 -0400 Subject: [PATCH 01/22] Generic Vehicle System --- .../Vehicle/Components/VehicleComponent.cs | 54 +++++++ .../Components/VehicleOperatorComponent.cs | 18 +++ Content.Shared/Vehicle/VehicleSystem.cs | 150 ++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 Content.Shared/Vehicle/Components/VehicleComponent.cs create mode 100644 Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs create mode 100644 Content.Shared/Vehicle/VehicleSystem.cs diff --git a/Content.Shared/Vehicle/Components/VehicleComponent.cs b/Content.Shared/Vehicle/Components/VehicleComponent.cs new file mode 100644 index 00000000000..2b6d109228e --- /dev/null +++ b/Content.Shared/Vehicle/Components/VehicleComponent.cs @@ -0,0 +1,54 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Vehicle.Components; + +/// +/// Vehicles are objects that have the behavior of moving when a player "operates" them. +/// The details of when the vehicle can operate and who the operator is are not defined here. +/// This simply contains the baseline behavior of the vehicle itself. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(VehicleSystem))] +public sealed partial class VehicleComponent : Component +{ + /// + /// The driver of this vehicle. + /// + [DataField, AutoNetworkedField] + public EntityUid? Operator; + + /// + /// Simple whitelist for determining who can operator this vehicle. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? OperatorWhitelist; +} + +[Flags] +public enum VehicleVisuals : byte +{ + HasOperator, + CanRun, +} + +/// +/// Event raised on operator when they begin to operate a vehicle +/// Values are configured before this event is raised. +/// +[ByRefEvent] +public readonly record struct OnVehicleEnteredEvent(Entity Vehicle, EntityUid Operator); + +/// +/// Event raised on operator when they stop operating a vehicle. +/// Values are configured after this event is raised. +/// +[ByRefEvent] +public readonly record struct OnVehicleExitedEvent(Entity Vehicle, EntityUid Operator); + +/// +/// Event raised on vehicle after an operator is set. +/// New operator can be null. +/// +[ByRefEvent] +public readonly record struct VehicleOperatorSetEvent(EntityUid? NewOperator); diff --git a/Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs b/Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs new file mode 100644 index 00000000000..9180396a336 --- /dev/null +++ b/Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Vehicle.Components; + +/// +/// Tracking component for handling the operator of a given +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(VehicleSystem))] +public sealed partial class VehicleOperatorComponent : Component +{ + /// + /// The vehicle we are currently operating. + /// + [DataField, AutoNetworkedField] + public EntityUid Vehicle; +} diff --git a/Content.Shared/Vehicle/VehicleSystem.cs b/Content.Shared/Vehicle/VehicleSystem.cs new file mode 100644 index 00000000000..ce9ace8e50c --- /dev/null +++ b/Content.Shared/Vehicle/VehicleSystem.cs @@ -0,0 +1,150 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.ActionBlocker; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Content.Shared.Movement.Systems; +using Content.Shared.Vehicle.Components; +using Content.Shared.Whitelist; +using JetBrains.Annotations; + +namespace Content.Shared.Vehicle; + +/// +/// Handles logic relating to vehicles. +/// +public sealed class VehicleSystem : EntitySystem +{ + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnVehicleUpdateCanMove); + SubscribeLocalEvent(OnVehicleShutdown); + + SubscribeLocalEvent(OnOperatorShutdown); + } + + private void OnVehicleUpdateCanMove(Entity ent, ref UpdateCanMoveEvent args) + { + if (ent.Comp.Operator is null) + args.Cancel(); + } + + private void OnVehicleShutdown(Entity ent, ref ComponentShutdown args) + { + TryRemoveOperator(ent); + } + + private void OnOperatorShutdown(Entity ent, ref ComponentShutdown args) + { + TryRemoveOperator(ent); + } + + public bool TrySetOperator(Entity entity, EntityUid? uid, bool removeExisting = true) + { + if (entity.Comp.Operator == null && uid is null) + return false; + + // Do not run logic if the entity is already operating a vehicle. + // However, if they are operating *this* vehicle, return true (they are indeed the operator) + if (TryComp(uid, out var eOperator)) + return eOperator.Vehicle == entity.Owner; + + if (!removeExisting && entity.Comp.Operator is not null) + return false; + + if (uid != null && !CanOperate(entity, uid.Value)) + return false; + + if (entity.Comp.Operator is { } currentOperator) + { + var exitEvent = new OnVehicleExitedEvent(entity, currentOperator); + RaiseLocalEvent(currentOperator, ref exitEvent); + + RemComp(currentOperator); + RemComp(currentOperator); + } + + entity.Comp.Operator = uid; + + if (uid != null) + { + var vehicleOperator = AddComp(uid.Value); + vehicleOperator.Vehicle = entity.Owner; + Dirty(uid.Value, vehicleOperator); + + _mover.SetRelay(uid.Value, entity); + + var enterEvent = new OnVehicleEnteredEvent(entity, uid.Value); + RaiseLocalEvent(uid.Value, ref enterEvent); + } + else + { + RemComp(entity); + } + + _actionBlocker.UpdateCanMove(entity); + UpdateAppearance(entity); + + var setEvent = new VehicleOperatorSetEvent(uid); + RaiseLocalEvent(entity, ref setEvent); + + Dirty(entity); + return true; + } + + [PublicAPI] + public bool TryRemoveOperator(Entity entity) + { + return TrySetOperator(entity, null); + } + + [PublicAPI] + public bool TryRemoveOperator(Entity operatorEntity) + { + if (!TryComp(operatorEntity.Comp.Vehicle, out var vehicle)) + return false; + + return TrySetOperator((operatorEntity.Comp.Vehicle, vehicle), null); + } + + public bool TryGetOperator(Entity entity, [NotNullWhen(true)] out Entity? operatorEnt) + { + operatorEnt = null; + if (!Resolve(entity, ref entity.Comp, false)) + return false; + + if (entity.Comp.Operator is not { } operatorUid) + return false; + + if (!TryComp(operatorUid, out var operatorComponent)) + return false; + + operatorEnt = (operatorUid, operatorComponent); + return true; + } + + public bool CanOperate(Entity entity, EntityUid uid) + { + if (_entityWhitelist.IsWhitelistFail(entity.Comp.OperatorWhitelist, uid)) + return false; + + return true; + } + + private void UpdateAppearance(Entity entity) + { + var appearance = CompOrNull(entity); + + if (TryComp(entity, out var inputMover)) + { + _appearance.SetData(entity, VehicleVisuals.CanRun, inputMover.CanMove, appearance); + } + + _appearance.SetData(entity, VehicleVisuals.HasOperator, entity.Comp.Operator is not null, appearance); + } +} From c9b25392b41ca349996e88d0fad2d2e5002eaea6 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Wed, 23 Apr 2025 12:32:10 -0400 Subject: [PATCH 02/22] Vehicle Operator Methods --- .../Components/ContainerVehicleComponent.cs | 18 ++++ .../Components/StrapVehicleComponent.cs | 10 +++ .../Vehicle/Components/VehicleComponent.cs | 25 +++++- .../Components/VehicleOperatorComponent.cs | 3 +- Content.Shared/Vehicle/VehicleSystem.cs | 79 ++++++++++++++++-- .../Entities/Objects/Devices/vehicles.yml | 74 ++++++++++++++++ .../Objects/Vehicles/wheelchair.rsi/meta.json | 52 ++++++------ .../{vehicle.png => wheelchair.png} | Bin ...hicle_folded.png => wheelchair_folded.png} | Bin .../wheelchair.rsi/wheelchair_overlay.png | Bin 0 -> 1363 bytes 10 files changed, 226 insertions(+), 35 deletions(-) create mode 100644 Content.Shared/Vehicle/Components/ContainerVehicleComponent.cs create mode 100644 Content.Shared/Vehicle/Components/StrapVehicleComponent.cs create mode 100644 Resources/Prototypes/Entities/Objects/Devices/vehicles.yml rename Resources/Textures/Objects/Vehicles/wheelchair.rsi/{vehicle.png => wheelchair.png} (100%) rename Resources/Textures/Objects/Vehicles/wheelchair.rsi/{vehicle_folded.png => wheelchair_folded.png} (100%) create mode 100644 Resources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair_overlay.png diff --git a/Content.Shared/Vehicle/Components/ContainerVehicleComponent.cs b/Content.Shared/Vehicle/Components/ContainerVehicleComponent.cs new file mode 100644 index 00000000000..e0c5dd1bc3c --- /dev/null +++ b/Content.Shared/Vehicle/Components/ContainerVehicleComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Vehicle.Components; + +/// +/// A whose operator must be inside a specified container. +/// Note that the operator is the first to enter the container and won't be removed until they exit the container. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(VehicleSystem))] +public sealed partial class ContainerVehicleComponent : Component +{ + /// + /// The ID of the container for the operator. + /// + [DataField(required: true)] + public string ContainerId; +} diff --git a/Content.Shared/Vehicle/Components/StrapVehicleComponent.cs b/Content.Shared/Vehicle/Components/StrapVehicleComponent.cs new file mode 100644 index 00000000000..9bd5ef3f12e --- /dev/null +++ b/Content.Shared/Vehicle/Components/StrapVehicleComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Vehicle.Components; + +/// +/// A whose operator must be buckled to it. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(VehicleSystem))] +public sealed partial class StrapVehicleComponent : Component; diff --git a/Content.Shared/Vehicle/Components/VehicleComponent.cs b/Content.Shared/Vehicle/Components/VehicleComponent.cs index 2b6d109228e..57fcab24ca2 100644 --- a/Content.Shared/Vehicle/Components/VehicleComponent.cs +++ b/Content.Shared/Vehicle/Components/VehicleComponent.cs @@ -1,5 +1,9 @@ +using Content.Shared.Damage.Prototypes; using Content.Shared.Whitelist; +using JetBrains.Annotations; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Vehicle.Components; @@ -23,9 +27,22 @@ public sealed partial class VehicleComponent : Component /// [DataField, AutoNetworkedField] public EntityWhitelist? OperatorWhitelist; + + /// + /// If true, damage to the vehicle will be transferred to the operator. + /// This damage is modified by + /// + [DataField, AutoNetworkedField] + public bool TransferDamage; + + /// + /// A damage modifier set that adjusts the damage passed from the vehicle to the operator. + /// + [DataField, AutoNetworkedField] + public ProtoId? TransferDamageModifier; } -[Flags] +[Serializable, NetSerializable] public enum VehicleVisuals : byte { HasOperator, @@ -36,19 +53,19 @@ public enum VehicleVisuals : byte /// Event raised on operator when they begin to operate a vehicle /// Values are configured before this event is raised. /// -[ByRefEvent] +[ByRefEvent, UsedImplicitly] public readonly record struct OnVehicleEnteredEvent(Entity Vehicle, EntityUid Operator); /// /// Event raised on operator when they stop operating a vehicle. /// Values are configured after this event is raised. /// -[ByRefEvent] +[ByRefEvent, UsedImplicitly] public readonly record struct OnVehicleExitedEvent(Entity Vehicle, EntityUid Operator); /// /// Event raised on vehicle after an operator is set. /// New operator can be null. /// -[ByRefEvent] +[ByRefEvent, UsedImplicitly] public readonly record struct VehicleOperatorSetEvent(EntityUid? NewOperator); diff --git a/Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs b/Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs index 9180396a336..74033dd4e1d 100644 --- a/Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs +++ b/Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Whitelist; using Robust.Shared.GameStates; namespace Content.Shared.Vehicle.Components; @@ -14,5 +13,5 @@ public sealed partial class VehicleOperatorComponent : Component /// The vehicle we are currently operating. /// [DataField, AutoNetworkedField] - public EntityUid Vehicle; + public EntityUid? Vehicle; } diff --git a/Content.Shared/Vehicle/VehicleSystem.cs b/Content.Shared/Vehicle/VehicleSystem.cs index ce9ace8e50c..5e6a202ea1e 100644 --- a/Content.Shared/Vehicle/VehicleSystem.cs +++ b/Content.Shared/Vehicle/VehicleSystem.cs @@ -1,11 +1,15 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.ActionBlocker; +using Content.Shared.Buckle.Components; +using Content.Shared.Damage; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; using Content.Shared.Vehicle.Components; using Content.Shared.Whitelist; using JetBrains.Annotations; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; namespace Content.Shared.Vehicle; @@ -14,18 +18,44 @@ namespace Content.Shared.Vehicle; /// public sealed class VehicleSystem : EntitySystem { + [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; [Dependency] private readonly SharedMoverController _mover = default!; /// public override void Initialize() { + SubscribeLocalEvent(OnBeforeDamageChanged); SubscribeLocalEvent(OnVehicleUpdateCanMove); SubscribeLocalEvent(OnVehicleShutdown); SubscribeLocalEvent(OnOperatorShutdown); + + SubscribeLocalEvent(OnVehicleStrapped); + SubscribeLocalEvent(OnVehicleUnstrapped); + + SubscribeLocalEvent(OnContainerEntInserted); + SubscribeLocalEvent(OnContainerEntRemoved); + } + + /// + /// We subscribe to BeforeDamageChangedEvent so that we can access the damage value before the container is added. + /// + private void OnBeforeDamageChanged(Entity ent, ref BeforeDamageChangedEvent args) + { + if (!ent.Comp.TransferDamage || args.Damage.AnyPositive() || ent.Comp.Operator is not { } operatorUid) + return; + + var damage = args.Damage; + if (_prototype.TryIndex(ent.Comp.TransferDamageModifier, out var modifierSet)) + { + damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet); + } + + _damageable.TryChangeDamage(operatorUid, damage, origin: args.Origin); } private void OnVehicleUpdateCanMove(Entity ent, ref UpdateCanMoveEvent args) @@ -44,6 +74,43 @@ private void OnOperatorShutdown(Entity ent, ref Compon TryRemoveOperator(ent); } + private void OnVehicleStrapped(Entity ent, ref StrappedEvent args) + { + if (!TryComp(ent, out var vehicle)) + return; + TrySetOperator((ent, vehicle), args.Buckle); + } + + private void OnVehicleUnstrapped(Entity ent, ref UnstrappedEvent args) + { + if (!TryComp(ent, out var vehicle)) + return; + TrySetOperator((ent, vehicle), null); + } + + private void OnContainerEntInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ContainerId) + return; + + if (!TryComp(ent, out var vehicle)) + return; + + TrySetOperator((ent, vehicle), args.Entity, removeExisting: false); + } + + private void OnContainerEntRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ContainerId) + return; + if (!TryComp(ent, out var vehicle)) + return; + if (vehicle.Operator != args.Entity) + return; + + TryRemoveOperator((ent, vehicle)); + } + public bool TrySetOperator(Entity entity, EntityUid? uid, bool removeExisting = true) { if (entity.Comp.Operator == null && uid is null) @@ -60,13 +127,14 @@ public bool TrySetOperator(Entity entity, EntityUid? uid, bool if (uid != null && !CanOperate(entity, uid.Value)) return false; - if (entity.Comp.Operator is { } currentOperator) + if (entity.Comp.Operator is { } currentOperator && TryComp(currentOperator, out var currentOperatorComponent)) { var exitEvent = new OnVehicleExitedEvent(entity, currentOperator); RaiseLocalEvent(currentOperator, ref exitEvent); - RemComp(currentOperator); - RemComp(currentOperator); + currentOperatorComponent.Vehicle = null; + RemCompDeferred(currentOperator); + RemCompDeferred(currentOperator); } entity.Comp.Operator = uid; @@ -84,7 +152,7 @@ public bool TrySetOperator(Entity entity, EntityUid? uid, bool } else { - RemComp(entity); + RemCompDeferred(entity); } _actionBlocker.UpdateCanMove(entity); @@ -109,9 +177,10 @@ public bool TryRemoveOperator(Entity operatorEntity) if (!TryComp(operatorEntity.Comp.Vehicle, out var vehicle)) return false; - return TrySetOperator((operatorEntity.Comp.Vehicle, vehicle), null); + return TrySetOperator((operatorEntity.Comp.Vehicle.Value, vehicle), null); } + [PublicAPI] public bool TryGetOperator(Entity entity, [NotNullWhen(true)] out Entity? operatorEnt) { operatorEnt = null; diff --git a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml new file mode 100644 index 00000000000..fa5424c3f8c --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml @@ -0,0 +1,74 @@ +- type: entity + id: BaseVehicleStrap + abstract: true + components: + - type: InputMover + - type: Clickable + - type: InteractionOutline + - type: Pullable + - type: Physics + bodyType: KinematicController + - type: Vehicle + - type: StrapVehicle + - type: Strap + maxBuckleDistance: 1 + - type: MovementSpeedModifier + weightlessModifier: 0 + acceleration: 2 + friction: 2 + frictionNoInput: 6 + baseWalkSpeed: 4.5 + baseSprintSpeed: 6 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.4 + density: 360 + restitution: 0.0 + mask: + - MobMask + layer: + - TableLayer + +- type: entity + id: VehicleWheelchair + parent: [BaseVehicleStrap, BaseFoldable, BaseItem] + name: wheelchair + description: A chair with big wheels. It looks like you can move in these on your own. + components: + - type: Sprite + sprite: Objects/Vehicles/wheelchair.rsi + layers: + - state: wheelchair + map: ["unfoldedLayer"] + - state: wheelchair_folded + map: ["foldedLayer"] + visible: false + noRot: true + - type: Appearance + - type: Item + size: Ginormous + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: MovementSpeedModifier + baseWalkSpeed: 2 + baseSprintSpeed: 2 + - type: Strap + buckleOffset: "0,0" + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.2 + density: 360 + restitution: 0.0 + mask: + - MobMask + layer: + - TableLayer + - type: StaticPrice + price: 70 diff --git a/Resources/Textures/Objects/Vehicles/wheelchair.rsi/meta.json b/Resources/Textures/Objects/Vehicles/wheelchair.rsi/meta.json index 8c69fc12253..8625da65c94 100644 --- a/Resources/Textures/Objects/Vehicles/wheelchair.rsi/meta.json +++ b/Resources/Textures/Objects/Vehicles/wheelchair.rsi/meta.json @@ -1,26 +1,30 @@ { - "version": 1, - "size": { - "x": 32, - "y": 32 + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/2c63c11c802b6878c013ad60786ea134204b6736/icons/mob/rideables/vehicles.dmi, https://github.com/tgstation/tgstation/blob/b1edbc1990a98239c6d3fd6871fc365daab61eb5/icons/mob/inhands/items_righthand.dmi, https://github.com/tgstation/tgstation/blob/b1edbc1990a98239c6d3fd6871fc365daab61eb5/icons/mob/inhands/items_lefthand.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "wheelchair", + "directions": 4 }, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/8f6e48e30ff85cb58b5b0a91add4a1741b971f0f", - "states": [ - { - "name": "vehicle", - "directions": 4 - }, - { - "name": "vehicle_folded" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} + { + "name": "wheelchair_overlay", + "directions": 4 + }, + { + "name": "wheelchair_folded" + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Vehicles/wheelchair.rsi/vehicle.png b/Resources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair.png similarity index 100% rename from Resources/Textures/Objects/Vehicles/wheelchair.rsi/vehicle.png rename to Resources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair.png diff --git a/Resources/Textures/Objects/Vehicles/wheelchair.rsi/vehicle_folded.png b/Resources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair_folded.png similarity index 100% rename from Resources/Textures/Objects/Vehicles/wheelchair.rsi/vehicle_folded.png rename to Resources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair_folded.png diff --git a/Resources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair_overlay.png b/Resources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a91a1f6ede8ec037bd2d79a8511152e39fdd14 GIT binary patch literal 1363 zcmV-Z1+4msP)){3($rxk&yTL|ml#_BVacHrDR!yu9Cj^M1R(((KN^=b7L0 z%rmp^j9)C`hzVd3M@#^VIMRZE&dyGbbZTnKN@ZQZcmilZ4-XGo^-(WBKVP)dA;EY8 z01TwLxtZVJ-}9ilU0q$GJ`ooL;|U-uPEJm;x3{;z`lz?Oy!`)60U23YSt;)`Z3uvC z%U)nl2Ubu};G_TuLcL^=HUt1Pcnq|pq(nSZiZY!k;zn8#fPt$9`JE|}HUz-^b?YGr z>LP6j0APL0c|RZ)Bzh33P)z13-PWo0*vj?B8et8X6kd?(VK|VGN2a zjipas4cs1J2SL#W(;!HOu>|z?_VV-dbMZVhG{ldOk6~qtz6ZSA9_1hk_51t#t-QQE z(a+e}m~R{~mVlg`9EJc0TwY!}`PbLifo-Wi+M^#8j3oe|KsGWm!rI&0Sx-+7mv?>y z@&=S&TwI9NVs>^`D-aq@0C@lbg98!3=;)}EZ-4q~wfBP21ds)>w!go>v(3#->-6;W zOJE~1%F4>Dy1F_}dx5m}04#O%D-fbOg4@>C7Qef@V{>zJU+TBDwK0@WPft6m0NSf| zj)1WQ0I(n|if4OQ$cBf9ee23))aK*kBT$CX1Yi#D?d@6iet=(HU9pOa3bweo=qzc- zUteE$-dkH+`O(o4D=jT$$gi%hPTzgN^YinkIb2^~&t6_$oOg=&NXN&=1qoOUP!IV4 z+M^#8j3oege0&t?A1e!M!sF1O@)k zY3~ypx0l=d!Ds>y_z0PqnObe+dUAU|7)!vy!h)4ZBphqn-vwGfbIbKW2KrIeHr);~?(gsOs;a6`NPw&$UFfuk4oUnFNC0(UGc2y0;J11? zU0PZS)$)K$bW2MMck6_7ZEY^o>0$&2Wn1h=&H8s&ICwC)+;RRkYg7i{(fiD4Gf*=H!mFU?P z^r>9uM#fL!B_l{Lm0^4dz~zGC;^I$vbR*mEI_kgj0D{+zalEVk-A3C3Dl04b%E}5O zSDT-o7kP(=hqO=7{=c4|z-<`l09{Bg=qT&uH5M7Z1mIv1_i}*p@^VhkxPM22DJ5ZH zdjRPm>jhmYJvlkCpttIf)RzDR5=77L?rz-85qap4$}wmXhRcWt2M26>d;72N16Eg8 zEjtcy$lTc2_@Wn{NiXP*%a-n-C_`%>P+MEeCMG7nbOK=z0+z$x2S8?!UU>fYJ^&yu zKqu6p8( Date: Wed, 23 Apr 2025 21:16:11 -0400 Subject: [PATCH 03/22] Keys --- .../GenericKeyedVehicleComponent.cs | 30 ++++ .../Vehicle/Components/VehicleComponent.cs | 10 +- Content.Shared/Vehicle/VehicleSystem.Key.cs | 64 +++++++++ .../Vehicle/VehicleSystem.Operator.cs | 56 ++++++++ Content.Shared/Vehicle/VehicleSystem.cs | 129 ++++++++++-------- .../Entities/Objects/Devices/vehicles.yml | 98 ++++++++++++- .../Objects/Vehicles/janicart.rsi/meta.json | 4 + .../Vehicles/janicart.rsi/vehicle-moving.png | Bin 0 -> 3369 bytes .../Objects/Vehicles/janicart.rsi/vehicle.png | Bin 3369 -> 2936 bytes 9 files changed, 332 insertions(+), 59 deletions(-) create mode 100644 Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs create mode 100644 Content.Shared/Vehicle/VehicleSystem.Key.cs create mode 100644 Content.Shared/Vehicle/VehicleSystem.Operator.cs create mode 100644 Resources/Textures/Objects/Vehicles/janicart.rsi/vehicle-moving.png diff --git a/Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs b/Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs new file mode 100644 index 00000000000..fd072c3fbcc --- /dev/null +++ b/Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Vehicle.Components; + +/// +/// This is used for a vehicle which can only be operated when a specific key matching a whitelist is inserted. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(VehicleSystem))] +public sealed partial class GenericKeyedVehicleComponent : Component +{ + /// + /// The ID corresponding to the container where the "key" must be inserted. + /// + [DataField(required: true)] + public string ContainerId; + + /// + /// A whitelist determining what qualifies as a valid key for this vehicle. + /// + [DataField(required: true)] + public EntityWhitelist KeyWhitelist = new(); + + /// + /// If true, prevents kys which do not pass the from being inserted into + /// + [DataField] + public bool PreventInvalidInsertion = true; +} diff --git a/Content.Shared/Vehicle/Components/VehicleComponent.cs b/Content.Shared/Vehicle/Components/VehicleComponent.cs index 57fcab24ca2..23c3448ce5a 100644 --- a/Content.Shared/Vehicle/Components/VehicleComponent.cs +++ b/Content.Shared/Vehicle/Components/VehicleComponent.cs @@ -45,8 +45,8 @@ public sealed partial class VehicleComponent : Component [Serializable, NetSerializable] public enum VehicleVisuals : byte { - HasOperator, - CanRun, + HasOperator, // The vehicle has a valid operator + CanRun, // The vehicle can be moved by the operator (turned on :flushed:) } /// @@ -69,3 +69,9 @@ public enum VehicleVisuals : byte /// [ByRefEvent, UsedImplicitly] public readonly record struct VehicleOperatorSetEvent(EntityUid? NewOperator); + +/// +/// Event raised on a vehicle to check if it can run/move around. +/// +[ByRefEvent, UsedImplicitly] +public record struct VehicleCanRunEvent(Entity Vehicle, bool CanRun = true); diff --git a/Content.Shared/Vehicle/VehicleSystem.Key.cs b/Content.Shared/Vehicle/VehicleSystem.Key.cs new file mode 100644 index 00000000000..a8bd2ca1c69 --- /dev/null +++ b/Content.Shared/Vehicle/VehicleSystem.Key.cs @@ -0,0 +1,64 @@ +using Content.Shared.Vehicle.Components; +using Robust.Shared.Containers; + +namespace Content.Shared.Vehicle; + +public sealed partial class VehicleSystem +{ + public void InitializeKey() + { + SubscribeLocalEvent(OnGenericKeyedInsertAttempt); + SubscribeLocalEvent(OnGenericKeyedEntInserted); + SubscribeLocalEvent(OnGenericKeyedEntRemoved); + SubscribeLocalEvent(OnGenericKeyedCanRun); + } + + private void OnGenericKeyedInsertAttempt(Entity ent, ref ContainerIsInsertingAttemptEvent args) + { + if (args.Cancelled) + return; + + if (args.Container.ID != ent.Comp.ContainerId) + return; + + if (_entityWhitelist.IsWhitelistPass(ent.Comp.KeyWhitelist, args.EntityUid)) + return; + + args.Cancel(); + } + + private void OnGenericKeyedEntInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ContainerId) + return; + RefreshCanRun(ent.Owner); + } + + private void OnGenericKeyedEntRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ContainerId) + return; + RefreshCanRun(ent.Owner); + } + + private void OnGenericKeyedCanRun(Entity ent, ref VehicleCanRunEvent args) + { + if (!args.CanRun) + return; + // We cannot run by default + args.CanRun = false; + + if (!_container.TryGetContainer(ent.Owner, ent.Comp.ContainerId, out var container)) + return; + + foreach (var contained in container.ContainedEntities) + { + if (_entityWhitelist.IsWhitelistFail(ent.Comp.KeyWhitelist, contained)) + continue; + + // If we find a valid key, permit running and exit early. + args.CanRun = true; + break; + } + } +} diff --git a/Content.Shared/Vehicle/VehicleSystem.Operator.cs b/Content.Shared/Vehicle/VehicleSystem.Operator.cs new file mode 100644 index 00000000000..5a79e4bcd5f --- /dev/null +++ b/Content.Shared/Vehicle/VehicleSystem.Operator.cs @@ -0,0 +1,56 @@ +using Content.Shared.Buckle.Components; +using Content.Shared.Vehicle.Components; +using Robust.Shared.Containers; + +namespace Content.Shared.Vehicle; + +public sealed partial class VehicleSystem +{ + public void InitializeOperator() + { + SubscribeLocalEvent(OnVehicleStrapped); + SubscribeLocalEvent(OnVehicleUnstrapped); + + SubscribeLocalEvent(OnContainerEntInserted); + SubscribeLocalEvent(OnContainerEntRemoved); + } + + private void OnVehicleStrapped(Entity ent, ref StrappedEvent args) + { + if (!TryComp(ent, out var vehicle)) + return; + TrySetOperator((ent, vehicle), args.Buckle); + } + + private void OnVehicleUnstrapped(Entity ent, ref UnstrappedEvent args) + { + if (!TryComp(ent, out var vehicle)) + return; + TrySetOperator((ent, vehicle), null); + } + + private void OnContainerEntInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.ContainerId) + return; + + if (!TryComp(ent, out var vehicle)) + return; + + TrySetOperator((ent, vehicle), args.Entity, removeExisting: false); + } + + private void OnContainerEntRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.ContainerId) + return; + + if (!TryComp(ent, out var vehicle)) + return; + + if (vehicle.Operator != args.Entity) + return; + + TryRemoveOperator((ent, vehicle)); + } +} diff --git a/Content.Shared/Vehicle/VehicleSystem.cs b/Content.Shared/Vehicle/VehicleSystem.cs index 5e6a202ea1e..4f832977cd0 100644 --- a/Content.Shared/Vehicle/VehicleSystem.cs +++ b/Content.Shared/Vehicle/VehicleSystem.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.ActionBlocker; -using Content.Shared.Buckle.Components; using Content.Shared.Damage; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; @@ -16,11 +15,12 @@ namespace Content.Shared.Vehicle; /// /// Handles logic relating to vehicles. /// -public sealed class VehicleSystem : EntitySystem +public sealed partial class VehicleSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; [Dependency] private readonly SharedMoverController _mover = default!; @@ -28,17 +28,14 @@ public sealed class VehicleSystem : EntitySystem /// public override void Initialize() { + InitializeOperator(); + InitializeKey(); + SubscribeLocalEvent(OnBeforeDamageChanged); SubscribeLocalEvent(OnVehicleUpdateCanMove); SubscribeLocalEvent(OnVehicleShutdown); SubscribeLocalEvent(OnOperatorShutdown); - - SubscribeLocalEvent(OnVehicleStrapped); - SubscribeLocalEvent(OnVehicleUnstrapped); - - SubscribeLocalEvent(OnContainerEntInserted); - SubscribeLocalEvent(OnContainerEntRemoved); } /// @@ -52,6 +49,7 @@ private void OnBeforeDamageChanged(Entity ent, ref BeforeDamag var damage = args.Damage; if (_prototype.TryIndex(ent.Comp.TransferDamageModifier, out var modifierSet)) { + // Reduce damage to via the specified modifier, if provided. damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet); } @@ -60,7 +58,16 @@ private void OnBeforeDamageChanged(Entity ent, ref BeforeDamag private void OnVehicleUpdateCanMove(Entity ent, ref UpdateCanMoveEvent args) { - if (ent.Comp.Operator is null) + // TODO: determine if this check is necessary + // if (ent.Comp.Operator is null) + // { + // args.Cancel(); + // return; + // } + + var ev = new VehicleCanRunEvent(ent); + RaiseLocalEvent(ent, ref ev); + if (!ev.CanRun) args.Cancel(); } @@ -71,46 +78,16 @@ private void OnVehicleShutdown(Entity ent, ref ComponentShutdo private void OnOperatorShutdown(Entity ent, ref ComponentShutdown args) { - TryRemoveOperator(ent); - } - - private void OnVehicleStrapped(Entity ent, ref StrappedEvent args) - { - if (!TryComp(ent, out var vehicle)) - return; - TrySetOperator((ent, vehicle), args.Buckle); - } - - private void OnVehicleUnstrapped(Entity ent, ref UnstrappedEvent args) - { - if (!TryComp(ent, out var vehicle)) - return; - TrySetOperator((ent, vehicle), null); - } - - private void OnContainerEntInserted(Entity ent, ref EntInsertedIntoContainerMessage args) - { - if (args.Container.ID != ent.Comp.ContainerId) - return; - - if (!TryComp(ent, out var vehicle)) - return; - - TrySetOperator((ent, vehicle), args.Entity, removeExisting: false); - } - - private void OnContainerEntRemoved(Entity ent, ref EntRemovedFromContainerMessage args) - { - if (args.Container.ID != ent.Comp.ContainerId) - return; - if (!TryComp(ent, out var vehicle)) - return; - if (vehicle.Operator != args.Entity) - return; - - TryRemoveOperator((ent, vehicle)); + TryRemoveOperator((ent, ent)); } + /// + /// Set the operator for a given vehicle + /// + /// The vehicle + /// The new operator. If null, will only remove the operator. + /// If true, will remove the current operator when setting the new one. + /// If the new operator was successfully able to be set public bool TrySetOperator(Entity entity, EntityUid? uid, bool removeExisting = true) { if (entity.Comp.Operator == null && uid is null) @@ -141,6 +118,7 @@ public bool TrySetOperator(Entity entity, EntityUid? uid, bool if (uid != null) { + // AddComp used for noisy fail. This should never be an issue. var vehicleOperator = AddComp(uid.Value); vehicleOperator.Vehicle = entity.Owner; Dirty(uid.Value, vehicleOperator); @@ -155,8 +133,7 @@ public bool TrySetOperator(Entity entity, EntityUid? uid, bool RemCompDeferred(entity); } - _actionBlocker.UpdateCanMove(entity); - UpdateAppearance(entity); + RefreshCanRun((entity, entity.Comp)); var setEvent = new VehicleOperatorSetEvent(uid); RaiseLocalEvent(entity, ref setEvent); @@ -165,26 +142,44 @@ public bool TrySetOperator(Entity entity, EntityUid? uid, bool return true; } + /// + /// Attempts to remove the current operator from a vehicle + /// + /// The vehicle whose operator is being removed. + /// If the operator was removed successfully [PublicAPI] public bool TryRemoveOperator(Entity entity) { - return TrySetOperator(entity, null); + return TrySetOperator(entity, null, removeExisting: true); } + /// + /// From an operator, removes it from the vehicle + /// + /// The operator who is riding a vehicle + /// If the operator was removed successfully or if the entity was not operating a vehicle. [PublicAPI] - public bool TryRemoveOperator(Entity operatorEntity) + public bool TryRemoveOperator(Entity operatorEntity) { + if (!Resolve(operatorEntity, ref operatorEntity.Comp, false)) + return true; + if (!TryComp(operatorEntity.Comp.Vehicle, out var vehicle)) - return false; + return true; - return TrySetOperator((operatorEntity.Comp.Vehicle.Value, vehicle), null); + return TrySetOperator((operatorEntity.Comp.Vehicle.Value, vehicle), null, removeExisting: true); } + /// + /// Attempts to get the current operator of a vehicle + /// + /// + /// [PublicAPI] public bool TryGetOperator(Entity entity, [NotNullWhen(true)] out Entity? operatorEnt) { operatorEnt = null; - if (!Resolve(entity, ref entity.Comp, false)) + if (!Resolve(entity, ref entity.Comp)) return false; if (entity.Comp.Operator is not { } operatorUid) @@ -197,17 +192,39 @@ public bool TryGetOperator(Entity entity, [NotNullWhen(true)] return true; } + /// + /// Checks if a given entity is capable of operating a vehicle. + /// Note that the general ability for a vehicle to run (keys, fuel, etc.) is not checked here. + /// This is *only* for checks on the user. + /// public bool CanOperate(Entity entity, EntityUid uid) { if (_entityWhitelist.IsWhitelistFail(entity.Comp.OperatorWhitelist, uid)) return false; - return true; + return _actionBlocker.CanConsciouslyPerformAction(uid); + } + + /// + /// Checks if the vehicle is capable of running (has keys, fuel, etc.) and caches the value. + /// Updates the appearance data. + /// + public void RefreshCanRun(Entity entity) + { + if (TerminatingOrDeleted(entity)) + return; + + if (!Resolve(entity, ref entity.Comp)) + return; + + _actionBlocker.UpdateCanMove(entity); + UpdateAppearance((entity, entity.Comp)); } private void UpdateAppearance(Entity entity) { - var appearance = CompOrNull(entity); + if (!TryComp(entity, out var appearance)) + return; if (TryComp(entity, out var inputMover)) { diff --git a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml index fa5424c3f8c..b8791fa19a4 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml @@ -2,6 +2,8 @@ id: BaseVehicleStrap abstract: true components: + - type: Sprite + noRot: true - type: InputMover - type: Clickable - type: InteractionOutline @@ -31,6 +33,10 @@ - MobMask layer: - TableLayer + - type: Tag + tags: + - DoorBumpOpener + - FootstepSound - type: entity id: VehicleWheelchair @@ -46,7 +52,6 @@ - state: wheelchair_folded map: ["foldedLayer"] visible: false - noRot: true - type: Appearance - type: Item size: Ginormous @@ -72,3 +77,94 @@ - TableLayer - type: StaticPrice price: 70 + +- type: entity + id: VehicleJanicart + parent: BaseVehicleStrap + name: janicart + description: The janitor's trusty steed. + components: + - type: Sprite + sprite: Objects/Vehicles/janicart.rsi + layers: + - state: vehicle + map: ["movement"] + - type: SpriteMovement + movementLayers: + movement: + state: vehicle-moving + noMovementLayers: + movement: + state: vehicle + - type: GenericKeyedVehicle + containerId: key_slot + keyWhitelist: + tags: + - JanicartKeys + - type: UnpoweredFlashlight + - type: PointLight + enabled: false + radius: 3.5 + softness: 2 + mask: /Textures/Effects/LightMasks/cone.png + autoRot: true + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 500 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - trigger: + !type:DamageTrigger + damage: 250 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + - !type:PlaySoundBehavior + sound: + collection: MetalGlassBreak + - !type:ExplodeBehavior + #- !type:SpawnEntitiesBehavior # in future should also emit a cloud of hot gas + # spawn: + # VehicleJanicartDestroyed: + # min: 1 + # max: 1 + - type: ItemSlots + slots: + key_slot: + name: vehicle-slot-component-slot-name-keys + whitelist: + tags: + - JanicartKeys + insertSound: + path: /Audio/Effects/Vehicle/vehiclestartup.ogg + params: + volume: -3 + trashbag_slot: + name: janitorial-trolley-slot-component-slot-name-trashbag + whitelist: + tags: + - TrashBag + - type: ItemMapper + mapLayers: + storage: + whitelist: + tags: + - TrashBag + sprite: Objects/Vehicles/janicart.rsi + +- type: entity + parent: BaseItem + id: VehicleKeyJanicart + name: janicart keys + description: Interesting design. + components: + - type: Sprite + sprite: Objects/Vehicles/janicart.rsi + state: keys + - type: Item + size: Small + - type: Tag + tags: [ JanicartKeys ] diff --git a/Resources/Textures/Objects/Vehicles/janicart.rsi/meta.json b/Resources/Textures/Objects/Vehicles/janicart.rsi/meta.json index ef5d1c28e93..1baca772eef 100644 --- a/Resources/Textures/Objects/Vehicles/janicart.rsi/meta.json +++ b/Resources/Textures/Objects/Vehicles/janicart.rsi/meta.json @@ -9,6 +9,10 @@ "states": [ { "name": "vehicle", + "directions": 4 + }, + { + "name": "vehicle-moving", "directions": 4, "delays": [ [ diff --git a/Resources/Textures/Objects/Vehicles/janicart.rsi/vehicle-moving.png b/Resources/Textures/Objects/Vehicles/janicart.rsi/vehicle-moving.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb087a925f888ce39ef76099516766acb27088f GIT binary patch literal 3369 zcmZXXc{CL49>>Q%DKjI=nmrUTWea2MCSH`tR`zUfBpRl|7$lQ16v;NW2qodoo{Sh< zcCrp;FeBNQ5i+>$J?|gyz4wpb^EMONs(W1OPZiHZ{_>c|=%o3iTTv5jwix31Kx5 zD$~cPYV`kg__dy=xx}K)gO54PDxCZaVO^Rr;_Fyt6{otwvHaHb)tf(WwHCg!j;=KQ zLtx^dRgC|ev`hhP!pFT*Qp2K|eoM&i{_H2 zcnU_tY$8pt3dAHfy+*HJ(NSS*O}rVusTNuhd*Q#gTqZuiRD@**rlk=+_-qqr?h4-7 z?y^ZXniA?mNr~%`9>w9?;FZ9T+&`Y-Yc~}%^>$>oZ9R?avxR*xs9D{k;HqIFi(*A5 zk+_L~H927^t<*j%d#nmEj;+!m_@OU7F2&uG(iqX7*C<*V!5||eLJG%1v``6-sI@og zRSu3-iwca1!%?6o=l&C@JMf7RRxM(@k0q<@$l{3TXqE_QYsQ*MDKU-DHBI+r=@@{iyT&%;LN`2X96muo8+Qc$8WKfSOC~PM*^Jj|P zjyFw&PNd&uLtjyMDEj`&(m#dM0GgG?@(t%E}h?%5YoSaULX?@5=u#z8pszMcS z63K;%aimn#fz|pR!Xe5 z5te+;+3$x^s{QBOUdLB>tkA+ZJE)gc*~%W^Bz>72AVA%!q$6==$^A>|_VohP zg&^&FN(6C8*H0BMa`i(EoK&^jGfB5^M zky>kAYirl1W;)JHF|t21?7_9r4S|w&9MNsY;H6#*$BeS=b%y}Mz#2KkTn5=7+OJlC z*j1ghxDg%Z=gbV6a%Q5p0nVU~g_KPeuZx8CD#CP$!HivTQBtr#X?q!0v7o4JHfgh{ z`2+T)mV|o&2rKxGP0C~u^Iv5r$iDpUmooU_BgAdv?Y+NQny6jFF{s-SKCOX4*!*M@>yfG>+!@en)oc`i(tgBPA244Du3<*;AM2=<6< za8oxR?4^VPl0k`eCc_$XH~NNKjQH^31h~rLrT2#qSck*jC&;HsT)NCO1NG~e4(H@Z z3`zjpszYSA8RBQjS9MUchVJK{iLL#aUk>p`zum=%A25DY?ySifk2a_G8^*RO>ur}H zaHm>|3U7QoBCNwc;#MY{4boi>m*w&nw&uFazOz9vP1L0+I_~lN!U5ZioejLH5+8ry zyj>g^19B$v3ZX*z)_04P<28oF?a9Vs2ft&lc)bF0=uhsY%(7u^2Z@$MIavG>WdboW zK&X4@+n%r@MJcq%j6sTIJP!02NvPV0s&CdI6L+^ zH(yXap{gzUsNO5hm@)OBp^7gm2&eb>7pjL*yMT1&^s>%uz)|Eu)SX_O{MU>O2Pr(n z0xitm-7pbHS0Me?u0y5#O_`8`cwg6Nlkrb8I>2N$LYt68%ezE7{lVSiySIPF^FUze zK5Fcnq{O)9JdXlg0vI84eHJB3P#_`>jQ2c$K875| zk+`67zLedaldcxxCog+UGJvrQ;{tttHP;p*6_woT+uaW}Zouxf- zy!mNz;+Me=4(6$C*oD7eV;fa`I;p8_8IUeIRI38Lr5z6~@)KzDgAXa8%c0(cL3Q|^ z5%q65frle+v1k?|xm*S3hucq&!Pj;w>y|R5O0SsSOxpXSo;{cb9vgWKKgdVE$hGd_ zsTmrak#$S+GqT4@AIs4LkFSXX#dkRS@+f1B^Y_RDhtbVIK@l6lOm;L~43pUzli@k5 z1?-C^{OFHmjc+aXBe|jy8+6Mpbw4t{ftrN$G?vLXG?%D(qJUg>s(2CoK%e(Nw2Gp( zyAXb4#T99^h(EeAz0f?%qz}FyuSq#HcMuU;SjMRk%`ngUYrF&Ek-C9Hk{-QGjq753 zZ{FrfdWg}BvY$8#Myf=%n)3q!f7n?eydRzFrU7qY*uhaU*_R+okiu^!>^i$L>k8W# z(W)WF%tahQ*B0Z#RP(2Gih8%Ox!B9pyW1bj(9JMF|I42KiCCT>NXzFf0mWh21QGu! zWIX-yOZ7h1OA8KyDD>M*XoR@2Z2Oa)jr?bNU848DeBZno$*YZ8?f)H00euGs&MPkh zCTHkt`L+6D_uVR8!+d+8v)K+RuI;EF}l<9QZ;-jVp_ZPZ6@XJf`6@4kLbq98-SLO z+WiHaw{xbQTS2V7^~!hIhqF^Y$PFZi)9>eUwBeR^-w@8(_%+l`m- zgkYVMX`(NpA(Oeuw-?>Tl317yx_et?SCYB|wgEXaNg>1ZlxoU)9G(0nT$v2Nt4;+j zS-rb1?tSm5Zno1Z5dD6AL*N{TCL4eMflbsUDEgPY-JlQ20Ziu4y<^IhkG={-G(0^` zL};!FUIqi7tAPqhCWYE3U{GV{#?w&5UP#epe z47$I)!1c-a{iiqdRhi?dV?^EFZnaghcrRabCu!H(&Gvu&*FWtT9*j+MF+KD3=Q7?? z{ZfOqNC4I@vkEFAxyoz=jqPPpxP#0)u@p`HwoSi|ioEBk^a<(6lQXIYr+358VZXR|Z7 zzm;3LNt|G|di9>^N?7C#GPi8^(eYWB)A&B_USQPtHEezCgUtHAre1d@_)FKr&oy!4 z8T!3jk~Pd36lwo9YVEYicbn0;RiU=8axs(|uG}DOf}n)=xdt+tQz7nxL{ih5wwpD1 z)#)`A+bLNSy0&@#vX1h6hsFtijy%v1Zzb>PuP)1)5^MuW$%LwP=&8Yz-9_xbc!XCL&lY?0 zA&4$vI-~pmKs<>4k+;C1d0%}w9H}jaep$e9f39)TKJTKs+V=UF6X)@iDlR<}o*>t| zh3iUInzT`xWY5Dte^`yYLVj*E+_|D?%2elS+BZ+AP=>e4Tpt1jjWl&(b~Mu>iRzJ= ze2CYsnRmb#krX~tn(_k6BMxm>P{nR5U+ly}$JI_D%aw+_cR5nVsbl$O4ZV#88v<7` z#F(J{x!cXak=lK{_f%n=a!77=a5@t^i;{y7g8yN!|{z)|NQHrb~U* z>|!aZfBDcDmnz?C_(}vZ6TRs5=C3*SYCt<@+R&8LUBNi6dkUMfV Tl3mAtAAqT`rO`(N*QkF1iBWpz literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Vehicles/janicart.rsi/vehicle.png b/Resources/Textures/Objects/Vehicles/janicart.rsi/vehicle.png index 0eb087a925f888ce39ef76099516766acb27088f..f509d18fcba0b1d98df4858ee4dd3e01b46cfad8 100644 GIT binary patch literal 2936 zcmV-;3y1WHP)Px=GD$>1RCt{2nrm!S#}&ta7aW8DCl+8H>)mCqy^f12N&{X8NNfyEMLq>=g-EG{ zQ$>*)MX8ctEJdYBY0^}Y3`D3^MWrZejFd_xHIGlEsj;zn*j@+$RJjgo-_1TQ7z4p} zpu`xaAFgM(`>^+}Ayu3DFIk>Db7s!`&pmTy_RfJxOkxs~n8YL|F^T_EXxVlsch!jT z+S|;AtjEj-%U4v7tN&DcV^;cevJozK)rgmtJWubnZZ6%pfH@Qaz?JJ|-i(D5=9K`5 zZ_5bFS5%8{R#iPsk>66dz5ZPaeO~mU5)L;X zCKe7&XcL_I<4CAANKMWq|-49qsBlS;js_##xM`|8O%R7Pbt( zd_^^29lQXzIRMiiN~jx}KFF!|M!;Gone=CzT!B{?()A^N`efHCfrdQ|Y8S|)e@p{N@@=gxidb7~ z3mqNp_!lopk@qiNLPtk?f^1uB%Y=B~T%w3Oh{ zCRSFiqpIrZarLK-0e15CA3Pl$?ZhL7HvG*u0^q{MOR7((>m2=krq%%hNa(kv?sc}; zzbl+B2TA_anX|;c>?KfIN`Ie8@Z?Fr3c42V{vL^jd(bqE0|)ox^SCS{jLMG&<+S=! zDl(A=$e-^L4?Z-Tn>VimFn#(=1_!Rv-)Cxaj7J|`kBQj0C3*;l2d2S zruBrBCGeNOHWKOmBB4%hb9T&_kw|`U;A)DDJVQt%|AbD5Q)kX9{qp~msfp9fpYKxS z0aZ@+ie&xikYa#H?-#@)2Gtv%1)y>7pXq?Ku6yx96o3T_Jqh*kh@o|WNFK)t(&_i; z#j1UDUl-y|dqO{L%G8uSedWwb$>YbfxHrz5XQ9Q>fv6#3WIewAj|sjssvrhy4%ebpeC!UdB=iw z^1au(6WR_%kEv_%!n_jdx?i9y5Kv!)Ui~7%V@Q5QLWVHk=7rP6YFK~iZmPz0IF8D$=!>1q`?jaiPp}ypO+RYZsp-7@GDpLB`Uy2hrelu>LUnDD(IRfPst5uy5 ziL;UlT)AF$o!geRo+Jo>SC(v3YHVvg0$8~TcATKqaqMw?Jst~(w2Q+dD*Iz^ z_yejpy~x%#-=?kg2zpV8_2(_%1g-2kw@tM(!S4ZE-+Y_uO)s+R+%`pC>S=2|f^3&B z!jMr-U`^#3^>NbPJ`r>oag~Gtpcj=WJ?Yx* zb9HJ2M?!9&Uz7y`^dEbGWpfIZJIYH-k6uj8rW)qv&tvD#?UoUGx|NGM)sqIJmn^Fl z`OTZRaJl~y4SO1h8t3Kw)Ux~RN-z13-J&xXbmR$65+p<{9MY1>1HAl7om&3`1A|=d zzr^OvTWD@+MlULnUvux}9H4ph;uP`&1J-?HdBtj8e#N>LKS3+zP=sZ33h6)g0I_gL zv!5ZxLPi6`!XZtb0VMei>#NW-4bzN~Ki`GV3 zd6KAcUR$z!rO2B(E#XE+_ZG19kur+>es=EMe$QUu(Tm0A&0FXRg*bD%Gey_6>%*Ko zbwcy##UfBzip%BZz`^}0K4jwP$N+Mk*~#16Wy;)#4IiqBNh7@X-VP$YUrfjd-ol5) zYp>O&kbkb`Il7FvOrBC>+l>J5`(5=JIc8h}9=$jr|0f;n+g({8Fq%o)3BV%vLI9Di z5xj*Di$(5*3FPJ2V{LyDa=sqD*zO5wG47=7*UUbTm4_RSTDP#tUI129&Y$n1GZ>^S z5KuR^)f<1JGP*LYNg26oYwKv-`w>9OnCZ6H(d(~-dD^el$I_Q$@0<*ol@X4binu-T zzy=MVt@VhI+oE*C{yvk&y&q9qTc?bW3CZNGv4VE;X$d9IEq}hXQ!vI!G-{mJ)Dn>S zHxlyBJsdU3n?0AjnbYK8(l8>~vgH-4X=^>A5)@r7H~oDkdQpkp2xCTOx6dymdA+Cv zqo+IJ8oaIbh|*z)T>Elv{e33at`8GDc~WJI*vA1laBx46#)Xn0$tPnx*OFNrOCKpq z2womv5z%nZn9IQJ^9v~hcY~=MW2}C1sV3vv^ff~3x}98H3v=8&@_#*K!8{{ zG%k@?IHbkGAp)hPs_&GkIqEk@rg>Q%DKjI=nmrUTWea2MCSH`tR`zUfBpRl|7$lQ16v;NW2qodoo{Sh< zcCrp;FeBNQ5i+>$J?|gyz4wpb^EMONs(W1OPZiHZ{_>c|=%o3iTTv5jwix31Kx5 zD$~cPYV`kg__dy=xx}K)gO54PDxCZaVO^Rr;_Fyt6{otwvHaHb)tf(WwHCg!j;=KQ zLtx^dRgC|ev`hhP!pFT*Qp2K|eoM&i{_H2 zcnU_tY$8pt3dAHfy+*HJ(NSS*O}rVusTNuhd*Q#gTqZuiRD@**rlk=+_-qqr?h4-7 z?y^ZXniA?mNr~%`9>w9?;FZ9T+&`Y-Yc~}%^>$>oZ9R?avxR*xs9D{k;HqIFi(*A5 zk+_L~H927^t<*j%d#nmEj;+!m_@OU7F2&uG(iqX7*C<*V!5||eLJG%1v``6-sI@og zRSu3-iwca1!%?6o=l&C@JMf7RRxM(@k0q<@$l{3TXqE_QYsQ*MDKU-DHBI+r=@@{iyT&%;LN`2X96muo8+Qc$8WKfSOC~PM*^Jj|P zjyFw&PNd&uLtjyMDEj`&(m#dM0GgG?@(t%E}h?%5YoSaULX?@5=u#z8pszMcS z63K;%aimn#fz|pR!Xe5 z5te+;+3$x^s{QBOUdLB>tkA+ZJE)gc*~%W^Bz>72AVA%!q$6==$^A>|_VohP zg&^&FN(6C8*H0BMa`i(EoK&^jGfB5^M zky>kAYirl1W;)JHF|t21?7_9r4S|w&9MNsY;H6#*$BeS=b%y}Mz#2KkTn5=7+OJlC z*j1ghxDg%Z=gbV6a%Q5p0nVU~g_KPeuZx8CD#CP$!HivTQBtr#X?q!0v7o4JHfgh{ z`2+T)mV|o&2rKxGP0C~u^Iv5r$iDpUmooU_BgAdv?Y+NQny6jFF{s-SKCOX4*!*M@>yfG>+!@en)oc`i(tgBPA244Du3<*;AM2=<6< za8oxR?4^VPl0k`eCc_$XH~NNKjQH^31h~rLrT2#qSck*jC&;HsT)NCO1NG~e4(H@Z z3`zjpszYSA8RBQjS9MUchVJK{iLL#aUk>p`zum=%A25DY?ySifk2a_G8^*RO>ur}H zaHm>|3U7QoBCNwc;#MY{4boi>m*w&nw&uFazOz9vP1L0+I_~lN!U5ZioejLH5+8ry zyj>g^19B$v3ZX*z)_04P<28oF?a9Vs2ft&lc)bF0=uhsY%(7u^2Z@$MIavG>WdboW zK&X4@+n%r@MJcq%j6sTIJP!02NvPV0s&CdI6L+^ zH(yXap{gzUsNO5hm@)OBp^7gm2&eb>7pjL*yMT1&^s>%uz)|Eu)SX_O{MU>O2Pr(n z0xitm-7pbHS0Me?u0y5#O_`8`cwg6Nlkrb8I>2N$LYt68%ezE7{lVSiySIPF^FUze zK5Fcnq{O)9JdXlg0vI84eHJB3P#_`>jQ2c$K875| zk+`67zLedaldcxxCog+UGJvrQ;{tttHP;p*6_woT+uaW}Zouxf- zy!mNz;+Me=4(6$C*oD7eV;fa`I;p8_8IUeIRI38Lr5z6~@)KzDgAXa8%c0(cL3Q|^ z5%q65frle+v1k?|xm*S3hucq&!Pj;w>y|R5O0SsSOxpXSo;{cb9vgWKKgdVE$hGd_ zsTmrak#$S+GqT4@AIs4LkFSXX#dkRS@+f1B^Y_RDhtbVIK@l6lOm;L~43pUzli@k5 z1?-C^{OFHmjc+aXBe|jy8+6Mpbw4t{ftrN$G?vLXG?%D(qJUg>s(2CoK%e(Nw2Gp( zyAXb4#T99^h(EeAz0f?%qz}FyuSq#HcMuU;SjMRk%`ngUYrF&Ek-C9Hk{-QGjq753 zZ{FrfdWg}BvY$8#Myf=%n)3q!f7n?eydRzFrU7qY*uhaU*_R+okiu^!>^i$L>k8W# z(W)WF%tahQ*B0Z#RP(2Gih8%Ox!B9pyW1bj(9JMF|I42KiCCT>NXzFf0mWh21QGu! zWIX-yOZ7h1OA8KyDD>M*XoR@2Z2Oa)jr?bNU848DeBZno$*YZ8?f)H00euGs&MPkh zCTHkt`L+6D_uVR8!+d+8v)K+RuI;EF}l<9QZ;-jVp_ZPZ6@XJf`6@4kLbq98-SLO z+WiHaw{xbQTS2V7^~!hIhqF^Y$PFZi)9>eUwBeR^-w@8(_%+l`m- zgkYVMX`(NpA(Oeuw-?>Tl317yx_et?SCYB|wgEXaNg>1ZlxoU)9G(0nT$v2Nt4;+j zS-rb1?tSm5Zno1Z5dD6AL*N{TCL4eMflbsUDEgPY-JlQ20Ziu4y<^IhkG={-G(0^` zL};!FUIqi7tAPqhCWYE3U{GV{#?w&5UP#epe z47$I)!1c-a{iiqdRhi?dV?^EFZnaghcrRabCu!H(&Gvu&*FWtT9*j+MF+KD3=Q7?? z{ZfOqNC4I@vkEFAxyoz=jqPPpxP#0)u@p`HwoSi|ioEBk^a<(6lQXIYr+358VZXR|Z7 zzm;3LNt|G|di9>^N?7C#GPi8^(eYWB)A&B_USQPtHEezCgUtHAre1d@_)FKr&oy!4 z8T!3jk~Pd36lwo9YVEYicbn0;RiU=8axs(|uG}DOf}n)=xdt+tQz7nxL{ih5wwpD1 z)#)`A+bLNSy0&@#vX1h6hsFtijy%v1Zzb>PuP)1)5^MuW$%LwP=&8Yz-9_xbc!XCL&lY?0 zA&4$vI-~pmKs<>4k+;C1d0%}w9H}jaep$e9f39)TKJTKs+V=UF6X)@iDlR<}o*>t| zh3iUInzT`xWY5Dte^`yYLVj*E+_|D?%2elS+BZ+AP=>e4Tpt1jjWl&(b~Mu>iRzJ= ze2CYsnRmb#krX~tn(_k6BMxm>P{nR5U+ly}$JI_D%aw+_cR5nVsbl$O4ZV#88v<7` z#F(J{x!cXak=lK{_f%n=a!77=a5@t^i;{y7g8yN!|{z)|NQHrb~U* z>|!aZfBDcDmnz?C_(}vZ6TRs5=C3*SYCt<@+R&8LUBNi6dkUMfV Tl3mAtAAqT`rO`(N*QkF1iBWpz From 063337814b7ca2fd42821fa23c757dbc52cbaf15 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Wed, 13 Aug 2025 10:59:55 -0400 Subject: [PATCH 04/22] Port carboard boxes to new vehicle system --- .../CardboardBox/CardboardBoxSystem.cs | 91 +------------------ .../Components/CardboardBoxComponent.cs | 6 -- .../Vehicle/Components/VehicleComponent.cs | 2 +- Content.Shared/Vehicle/VehicleSystem.cs | 33 ++++++- .../Structures/Storage/Closets/big_boxes.yml | 5 +- 5 files changed, 39 insertions(+), 98 deletions(-) diff --git a/Content.Server/CardboardBox/CardboardBoxSystem.cs b/Content.Server/CardboardBox/CardboardBoxSystem.cs index 836dc485d92..11e1dbdf59e 100644 --- a/Content.Server/CardboardBox/CardboardBoxSystem.cs +++ b/Content.Server/CardboardBox/CardboardBoxSystem.cs @@ -1,20 +1,10 @@ -using Content.Server.Storage.Components; -using Content.Server.Storage.EntitySystems; -using Content.Shared.Access.Components; using Content.Shared.CardboardBox; using Content.Shared.CardboardBox.Components; -using Content.Shared.Damage; -using Content.Shared.Interaction; -using Content.Shared.Movement.Components; -using Content.Shared.Movement.Systems; using Content.Shared.Stealth; using Content.Shared.Stealth.Components; using Content.Shared.Storage.Components; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; +using Content.Shared.Vehicle; using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; -using Robust.Shared.Player; using Robust.Shared.Timing; namespace Content.Server.CardboardBox; @@ -22,11 +12,9 @@ namespace Content.Server.CardboardBox; public sealed class CardboardBoxSystem : SharedCardboardBoxSystem { [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedStealthSystem _stealth = default!; - [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly EntityStorageSystem _storage = default!; + [Dependency] private readonly VehicleSystem _vehicle = default!; public override void Initialize() { @@ -34,43 +22,6 @@ public override void Initialize() SubscribeLocalEvent(AfterStorageOpen); SubscribeLocalEvent(BeforeStorageOpen); SubscribeLocalEvent(AfterStorageClosed); - SubscribeLocalEvent(OnGetAdditionalAccess); - SubscribeLocalEvent(OnInteracted); - SubscribeLocalEvent(OnEntInserted); - SubscribeLocalEvent(OnEntRemoved); - - SubscribeLocalEvent(OnDamage); - } - - private void OnInteracted(EntityUid uid, CardboardBoxComponent component, ActivateInWorldEvent args) - { - if (args.Handled) - return; - - if (!TryComp(uid, out var box)) - return; - - if (!args.Complex) - { - if (box.Open || !box.Contents.Contains(args.User)) - return; - } - - args.Handled = true; - _storage.ToggleOpen(args.User, uid, box); - - if (box.Contents.Contains(args.User) && !box.Open) - { - _mover.SetRelay(args.User, uid); - component.Mover = args.User; - } - } - - private void OnGetAdditionalAccess(EntityUid uid, CardboardBoxComponent component, ref GetAdditionalAccessEvent args) - { - if (component.Mover == null) - return; - args.Entities.Add(component.Mover.Value); } private void BeforeStorageOpen(EntityUid uid, CardboardBoxComponent component, ref StorageBeforeOpenEvent args) @@ -79,11 +30,11 @@ private void BeforeStorageOpen(EntityUid uid, CardboardBoxComponent component, r return; //Play effect & sound - if (component.Mover != null) + if (_vehicle.TryGetOperator(uid, out var operatorUid)) { if (_timing.CurTime > component.EffectCooldown) { - RaiseNetworkEvent(new PlayBoxEffectMessage(GetNetEntity(uid), GetNetEntity(component.Mover.Value))); + RaiseNetworkEvent(new PlayBoxEffectMessage(GetNetEntity(uid), GetNetEntity(operatorUid.Value))); _audio.PlayPvs(component.EffectSound, uid); component.EffectCooldown = _timing.CurTime + component.CooldownDuration; } @@ -105,38 +56,4 @@ private void AfterStorageClosed(EntityUid uid, CardboardBoxComponent component, _stealth.SetEnabled(uid, true, stealth); } } - - //Relay damage to the mover - private void OnDamage(EntityUid uid, CardboardBoxComponent component, DamageChangedEvent args) - { - if (args.DamageDelta != null && args.DamageIncreased) - { - _damageable.TryChangeDamage(component.Mover, args.DamageDelta, origin: args.Origin); - } - } - - private void OnEntInserted(EntityUid uid, CardboardBoxComponent component, EntInsertedIntoContainerMessage args) - { - if (!TryComp(args.Entity, out MobMoverComponent? mover)) - return; - - if (component.Mover == null) - { - _mover.SetRelay(args.Entity, uid); - component.Mover = args.Entity; - } - } - - /// - /// Through e.g. teleporting, it's possible for the mover to exit the box without opening it. - /// Handle those situations but don't play the sound. - /// - private void OnEntRemoved(EntityUid uid, CardboardBoxComponent component, EntRemovedFromContainerMessage args) - { - if (args.Entity != component.Mover) - return; - - RemComp(component.Mover.Value); - component.Mover = null; - } } diff --git a/Content.Shared/CardboardBox/Components/CardboardBoxComponent.cs b/Content.Shared/CardboardBox/Components/CardboardBoxComponent.cs index 0e607f50769..e681ee65bfd 100644 --- a/Content.Shared/CardboardBox/Components/CardboardBoxComponent.cs +++ b/Content.Shared/CardboardBox/Components/CardboardBoxComponent.cs @@ -11,12 +11,6 @@ namespace Content.Shared.CardboardBox.Components; [RegisterComponent, NetworkedComponent] public sealed partial class CardboardBoxComponent : Component { - /// - /// The person in control of this box - /// - [DataField("mover")] - public EntityUid? Mover; - /// /// The entity used for the box opening effect /// diff --git a/Content.Shared/Vehicle/Components/VehicleComponent.cs b/Content.Shared/Vehicle/Components/VehicleComponent.cs index 23c3448ce5a..e7351c185c8 100644 --- a/Content.Shared/Vehicle/Components/VehicleComponent.cs +++ b/Content.Shared/Vehicle/Components/VehicleComponent.cs @@ -33,7 +33,7 @@ public sealed partial class VehicleComponent : Component /// This damage is modified by /// [DataField, AutoNetworkedField] - public bool TransferDamage; + public bool TransferDamage = true; /// /// A damage modifier set that adjusts the damage passed from the vehicle to the operator. diff --git a/Content.Shared/Vehicle/VehicleSystem.cs b/Content.Shared/Vehicle/VehicleSystem.cs index 4f832977cd0..4c395044a43 100644 --- a/Content.Shared/Vehicle/VehicleSystem.cs +++ b/Content.Shared/Vehicle/VehicleSystem.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared.Access.Components; using Content.Shared.ActionBlocker; using Content.Shared.Damage; using Content.Shared.Movement.Components; @@ -34,19 +35,22 @@ public override void Initialize() SubscribeLocalEvent(OnBeforeDamageChanged); SubscribeLocalEvent(OnVehicleUpdateCanMove); SubscribeLocalEvent(OnVehicleShutdown); + SubscribeLocalEvent(OnVehicleGetAdditionalAccess); SubscribeLocalEvent(OnOperatorShutdown); + SubscribeLocalEvent(OnOperatorGetAdditionalAccess); } /// - /// We subscribe to BeforeDamageChangedEvent so that we can access the damage value before the container is added. + /// We subscribe to BeforeDamageChangedEvent so that we can access the damage value before the container is applied. /// private void OnBeforeDamageChanged(Entity ent, ref BeforeDamageChangedEvent args) { - if (!ent.Comp.TransferDamage || args.Damage.AnyPositive() || ent.Comp.Operator is not { } operatorUid) + if (!ent.Comp.TransferDamage || !args.Damage.AnyPositive() || ent.Comp.Operator is not { } operatorUid) return; - var damage = args.Damage; + var damage = DamageSpecifier.GetPositive(args.Damage); + if (_prototype.TryIndex(ent.Comp.TransferDamageModifier, out var modifierSet)) { // Reduce damage to via the specified modifier, if provided. @@ -76,11 +80,26 @@ private void OnVehicleShutdown(Entity ent, ref ComponentShutdo TryRemoveOperator(ent); } + private void OnVehicleGetAdditionalAccess(Entity ent, ref GetAdditionalAccessEvent args) + { + // Vehicles inherit access from whoever is driving them + if (ent.Comp.Operator is { } operatorUid) + args.Entities.Add(operatorUid); + } + private void OnOperatorShutdown(Entity ent, ref ComponentShutdown args) { TryRemoveOperator((ent, ent)); } + private void OnOperatorGetAdditionalAccess(Entity ent, ref GetAdditionalAccessEvent args) + { + // Operators inherit access from whatever the vehicle has + // (Used to support vehicles having intrinsic access associated with them) + if (ent.Comp.Vehicle is { } vehicle) + args.Entities.Add(vehicle); + } + /// /// Set the operator for a given vehicle /// @@ -192,6 +211,14 @@ public bool TryGetOperator(Entity entity, [NotNullWhen(true)] return true; } + /// + /// Checks if the current vehicle has an operator. + /// + public bool HasOperator(Entity entity) + { + return TryGetOperator(entity, out _); + } + /// /// Checks if a given entity is capable of operating a vehicle. /// Note that the general ability for a vehicle to run (keys, fuel, etc.) is not checked here. diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/big_boxes.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/big_boxes.yml index ac7f053b631..6009453c64c 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/big_boxes.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/big_boxes.yml @@ -22,6 +22,9 @@ - MobLayer hard: true - type: Pullable + - type: Vehicle + - type: ContainerVehicle + containerId: entity_storage - type: CardboardBox effectSound: /Audio/Effects/chime.ogg - type: InputMover @@ -107,4 +110,4 @@ - type: EntityStorage isCollidableWhenOpen: false openOnMove: false - airtight: false \ No newline at end of file + airtight: false From bd61a818504e5158d605a79984d4fdb2d27467c2 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Thu, 14 Aug 2025 00:15:58 -0400 Subject: [PATCH 05/22] Port mechs to new system --- .../EntitySystems/MechGrabberSystem.cs | 8 +- .../Mech/Systems/MechEquipmentSystem.cs | 4 +- Content.Server/Mech/Systems/MechSystem.cs | 19 +--- .../Mech/Components/MechComponent.cs | 10 --- .../EntitySystems/SharedMechSystem.Relay.cs | 40 +++++++++ .../Mech/EntitySystems/SharedMechSystem.cs | 89 +++++++------------ .../Vehicle/Components/VehicleComponent.cs | 7 +- Content.Shared/Vehicle/VehicleSystem.cs | 32 ++++--- .../Entities/Objects/Specific/Mech/mechs.yml | 59 +++++++++--- 9 files changed, 157 insertions(+), 111 deletions(-) create mode 100644 Content.Shared/Mech/EntitySystems/SharedMechSystem.Relay.cs diff --git a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs index 08120f5c296..eb9d4b9e7d6 100644 --- a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs +++ b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs @@ -8,9 +8,9 @@ using Content.Shared.Mech.Components; using Content.Shared.Mech.Equipment.Components; using Content.Shared.Mobs.Components; +using Content.Shared.Vehicle; using Content.Shared.Wall; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -30,6 +30,7 @@ public sealed class MechGrabberSystem : EntitySystem [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly VehicleSystem _vehicle = default!; /// public override void Initialize() @@ -145,7 +146,10 @@ private void OnInteract(EntityUid uid, MechGrabberComponent component, UserActiv if (component.ItemContainer.ContainedEntities.Count >= component.MaxContents) return; - if (!TryComp(args.User, out var mech) || mech.PilotSlot.ContainedEntity == target) + if (_vehicle.GetOperatorOrNull(args.User) == target) + return; + + if (!TryComp(args.User, out var mech)) return; if (mech.Energy + component.GrabEnergyDelta < 0) diff --git a/Content.Server/Mech/Systems/MechEquipmentSystem.cs b/Content.Server/Mech/Systems/MechEquipmentSystem.cs index f9fe5e46413..d17b4cda076 100644 --- a/Content.Server/Mech/Systems/MechEquipmentSystem.cs +++ b/Content.Server/Mech/Systems/MechEquipmentSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Interaction; using Content.Shared.Mech.Components; using Content.Shared.Mech.Equipment.Components; +using Content.Shared.Vehicle; using Content.Shared.Whitelist; namespace Content.Server.Mech.Systems; @@ -16,6 +17,7 @@ public sealed class MechEquipmentSystem : EntitySystem [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly VehicleSystem _vehicle = default!; /// public override void Initialize() @@ -36,7 +38,7 @@ private void OnUsed(EntityUid uid, MechEquipmentComponent component, AfterIntera if (mechComp.Broken) return; - if (args.User == mechComp.PilotSlot.ContainedEntity) + if (args.User == _vehicle.GetOperatorOrNull(mech)) return; if (mechComp.EquipmentContainer.ContainedEntities.Count >= mechComp.MaxEquipmentAmount) diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index b7bbc4ad473..c12f58360eb 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -33,11 +33,9 @@ public sealed partial class MechSystem : SharedMechSystem [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly ContainerSystem _container = default!; - [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; - [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly SharedToolSystem _toolSystem = default!; /// @@ -197,7 +195,7 @@ private void OnAlternativeVerb(EntityUid uid, MechComponent component, GetVerbsE args.Verbs.Add(enterVerb); args.Verbs.Add(openUiVerb); } - else if (!IsEmpty(component)) + else if (Vehicle.HasOperator(uid)) { var ejectVerb = new AlternativeVerb { @@ -205,7 +203,7 @@ private void OnAlternativeVerb(EntityUid uid, MechComponent component, GetVerbsE Priority = 1, // Promote to top to make ejecting the ALT-click action Act = () => { - if (args.User == uid || args.User == component.PilotSlot.ContainedEntity) + if (args.User == uid || args.User == Vehicle.GetOperatorOrNull(uid)) { TryEject(uid, component); return; @@ -226,14 +224,13 @@ private void OnMechEntry(EntityUid uid, MechComponent component, MechEntryEvent if (args.Cancelled || args.Handled) return; - if (_whitelistSystem.IsWhitelistFail(component.PilotWhitelist, args.User)) + if (!Vehicle.CanOperate(uid, args.User)) { _popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User); return; } TryInsert(uid, args.Args.User, component); - _actionBlocker.UpdateCanMove(uid); args.Handled = true; } @@ -250,21 +247,13 @@ private void OnDamageChanged(EntityUid uid, MechComponent component, DamageChang { var integrity = component.MaxIntegrity - args.Damageable.TotalDamage; SetIntegrity(uid, integrity, component); - - if (args.DamageIncreased && - args.DamageDelta != null && - component.PilotSlot.ContainedEntity != null) - { - var damage = args.DamageDelta * component.MechToPilotDamageMultiplier; - _damageable.TryChangeDamage(component.PilotSlot.ContainedEntity, damage); - } } private void ToggleMechUi(EntityUid uid, MechComponent? component = null, EntityUid? user = null) { if (!Resolve(uid, ref component)) return; - user ??= component.PilotSlot.ContainedEntity; + user ??= Vehicle.GetOperatorOrNull(uid); if (user == null) return; diff --git a/Content.Shared/Mech/Components/MechComponent.cs b/Content.Shared/Mech/Components/MechComponent.cs index 6ebfde5f999..a21c71f5858 100644 --- a/Content.Shared/Mech/Components/MechComponent.cs +++ b/Content.Shared/Mech/Components/MechComponent.cs @@ -55,13 +55,6 @@ public sealed partial class MechComponent : Component [ViewVariables] public readonly string BatterySlotId = "mech-battery-slot"; - /// - /// A multiplier used to calculate how much of the damage done to a mech - /// is transfered to the pilot - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MechToPilotDamageMultiplier; - /// /// Whether the mech has been destroyed and is no longer pilotable. /// @@ -96,9 +89,6 @@ public sealed partial class MechComponent : Component [DataField] public EntityWhitelist? EquipmentWhitelist; - [DataField] - public EntityWhitelist? PilotWhitelist; - /// /// A container for storing the equipment entities. /// diff --git a/Content.Shared/Mech/EntitySystems/SharedMechSystem.Relay.cs b/Content.Shared/Mech/EntitySystems/SharedMechSystem.Relay.cs new file mode 100644 index 00000000000..c4b81871e02 --- /dev/null +++ b/Content.Shared/Mech/EntitySystems/SharedMechSystem.Relay.cs @@ -0,0 +1,40 @@ +using Content.Shared.Interaction.Events; +using Content.Shared.Mech.Components; + +namespace Content.Shared.Mech.EntitySystems; + +public abstract partial class SharedMechSystem +{ + private void InitializeRelay() + { + SubscribeLocalEvent(RelayRefToPilot); + } + + private void RelayToPilot(Entity uid, T args) where T : class + { + if (!Vehicle.TryGetOperator(uid.Owner, out var operatorEnt)) + return; + + var ev = new MechPilotRelayedEvent(args); + + RaiseLocalEvent(operatorEnt.Value, ref ev); + } + + private void RelayRefToPilot(Entity uid, ref T args) where T :struct + { + if (!Vehicle.TryGetOperator(uid.Owner, out var operatorEnt)) + return; + + var ev = new MechPilotRelayedEvent(args); + + RaiseLocalEvent(operatorEnt.Value, ref ev); + + args = ev.Args; + } +} + +[ByRefEvent] +public record struct MechPilotRelayedEvent(TEvent Args) +{ + public TEvent Args = Args; +} diff --git a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs index f977a2eeb4e..391009a7b18 100644 --- a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs +++ b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Shared.Access.Components; using Content.Shared.ActionBlocker; using Content.Shared.Actions; using Content.Shared.Destructible; @@ -11,9 +10,9 @@ using Content.Shared.Interaction.Events; using Content.Shared.Mech.Components; using Content.Shared.Mech.Equipment.Components; -using Content.Shared.Movement.Components; -using Content.Shared.Movement.Systems; using Content.Shared.Popups; +using Content.Shared.Vehicle; +using Content.Shared.Vehicle.Components; using Content.Shared.Weapons.Melee; using Content.Shared.Whitelist; using Robust.Shared.Containers; @@ -46,9 +45,9 @@ public abstract class SharedMechSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; - [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] protected readonly VehicleSystem Vehicle = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; // Goobstation Change [Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!; // Goobstation Change @@ -56,7 +55,7 @@ public abstract class SharedMechSystem : EntitySystem // Goobstation: Local variable for checking if mech guns can be used out of them. private bool _canUseMechGunOutside; - + /// public override void Initialize() { @@ -65,10 +64,9 @@ public override void Initialize() SubscribeLocalEvent(RelayInteractionEvent); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnDestruction); - SubscribeLocalEvent(OnGetAdditionalAccess); SubscribeLocalEvent(OnDragDrop); SubscribeLocalEvent(OnCanDragDrop); - SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnOperatorSet); SubscribeLocalEvent(OnGetMeleeWeapon); SubscribeLocalEvent(OnCanAttackFromContainer); @@ -102,8 +100,7 @@ private void OnEjectPilotEvent(EntityUid uid, MechComponent component, MechEject private void RelayInteractionEvent(EntityUid uid, MechComponent component, UserActivateInWorldEvent args) { - var pilot = component.PilotSlot.ContainedEntity; - if (pilot == null) + if (!Vehicle.HasOperator(uid)) return; // TODO why is this being blocked? @@ -129,15 +126,6 @@ private void OnDestruction(EntityUid uid, MechComponent component, DestructionEv BreakMech(uid, component); } - private void OnGetAdditionalAccess(EntityUid uid, MechComponent component, ref GetAdditionalAccessEvent args) - { - var pilot = component.PilotSlot.ContainedEntity; - if (pilot == null) - return; - - args.Entities.Add(pilot.Value); - } - private void SetupUser(EntityUid mech, EntityUid pilot, MechComponent? component = null) { if (!Resolve(mech, ref component)) @@ -148,7 +136,6 @@ private void SetupUser(EntityUid mech, EntityUid pilot, MechComponent? component // Warning: this bypasses most normal interaction blocking components on the user, like drone laws and the like. var irelay = EnsureComp(pilot); - _mover.SetRelay(pilot, mech); _interaction.SetRelay(pilot, mech, irelay); rider.Mech = mech; Dirty(pilot, rider); @@ -166,7 +153,6 @@ private void RemoveUser(EntityUid mech, EntityUid pilot) { if (!RemComp(pilot)) return; - RemComp(pilot); RemComp(pilot); _actions.RemoveProvidedActions(pilot, mech); @@ -340,16 +326,6 @@ public void SetIntegrity(EntityUid uid, FixedPoint2 value, MechComponent? compon UpdateUserInterface(uid, component); } - /// - /// Checks if the pilot is present - /// - /// - /// Whether or not the pilot is present - public bool IsEmpty(MechComponent component) - { - return component.PilotSlot.ContainedEntity == null; - } - /// /// Checks if an entity can be inserted into the mech. /// @@ -362,7 +338,16 @@ public bool CanInsert(EntityUid uid, EntityUid toInsert, MechComponent? componen if (!Resolve(uid, ref component)) return false; - return IsEmpty(component) && _actionBlocker.CanMove(toInsert); + if (!_actionBlocker.CanMove(toInsert)) + return false; + + if (Vehicle.GetOperatorOrNull(uid) == toInsert) + return false; + + if (!_container.CanInsert(toInsert, component.PilotSlot)) + return false; + + return true; } /// @@ -382,21 +367,15 @@ public virtual void UpdateUserInterface(EntityUid uid, MechComponent? component /// /// /// Whether or not the entity was inserted - public bool TryInsert(EntityUid uid, EntityUid? toInsert, MechComponent? component = null) + public bool TryInsert(EntityUid uid, EntityUid toInsert, MechComponent? component = null) { if (!Resolve(uid, ref component)) return false; - if (toInsert == null || component.PilotSlot.ContainedEntity == toInsert) - return false; - - if (!CanInsert(uid, toInsert.Value, component)) + if (!CanInsert(uid, toInsert, component)) return false; - SetupUser(uid, toInsert.Value); - _container.Insert(toInsert.Value, component.PilotSlot); - UpdateAppearance(uid, component); - UpdateHands(toInsert.Value, uid, true); // Goobstation + _container.Insert(toInsert, component.PilotSlot); return true; } @@ -412,16 +391,10 @@ public bool TryEject(EntityUid uid, MechComponent? component = null, EntityUid? if (!Resolve(uid, ref component)) return false; - if (component.PilotSlot.ContainedEntity != null) - pilot = component.PilotSlot.ContainedEntity.Value; - - if (pilot == null) + if (!Vehicle.TryGetOperator(uid, out var operatorEnt)) return false; - RemoveUser(uid, pilot.Value); - _container.RemoveEntity(uid, pilot.Value); - UpdateAppearance(uid, component); - UpdateHands(pilot.Value, uid, false); // Goobstation + _container.RemoveEntity(uid, operatorEnt.Value); return true; } @@ -515,7 +488,7 @@ private void UpdateAppearance(EntityUid uid, MechComponent? component = null, if (!Resolve(uid, ref component, ref appearance, false)) return; - _appearance.SetData(uid, MechVisuals.Open, IsEmpty(component), appearance); + _appearance.SetData(uid, MechVisuals.Open, !Vehicle.HasOperator(uid), appearance); _appearance.SetData(uid, MechVisuals.Broken, component.Broken, appearance); } @@ -541,13 +514,19 @@ private void OnCanDragDrop(EntityUid uid, MechComponent component, ref CanDropTa args.CanDrop |= !component.Broken && CanInsert(uid, args.Dragged, component); } - private void OnEmagged(EntityUid uid, MechComponent component, ref GotEmaggedEvent args) // Goobstation + private void OnOperatorSet(Entity ent, ref VehicleOperatorSetEvent args) { - if (!component.BreakOnEmag) - return; - args.Handled = true; - component.EquipmentWhitelist = null; - Dirty(uid, component); + if (args.OldOperator is { } oldOperator) + { + RemoveUser(ent, oldOperator); + } + + if (args.NewOperator is { } newOperator) + { + SetupUser(ent, newOperator, ent); + } + + UpdateAppearance(ent); } } diff --git a/Content.Shared/Vehicle/Components/VehicleComponent.cs b/Content.Shared/Vehicle/Components/VehicleComponent.cs index e7351c185c8..98371371e30 100644 --- a/Content.Shared/Vehicle/Components/VehicleComponent.cs +++ b/Content.Shared/Vehicle/Components/VehicleComponent.cs @@ -1,8 +1,7 @@ -using Content.Shared.Damage.Prototypes; +using Content.Shared.Damage; using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Vehicle.Components; @@ -39,7 +38,7 @@ public sealed partial class VehicleComponent : Component /// A damage modifier set that adjusts the damage passed from the vehicle to the operator. /// [DataField, AutoNetworkedField] - public ProtoId? TransferDamageModifier; + public DamageModifierSet? TransferDamageModifier; } [Serializable, NetSerializable] @@ -68,7 +67,7 @@ public enum VehicleVisuals : byte /// New operator can be null. /// [ByRefEvent, UsedImplicitly] -public readonly record struct VehicleOperatorSetEvent(EntityUid? NewOperator); +public readonly record struct VehicleOperatorSetEvent(EntityUid? NewOperator, EntityUid? OldOperator); /// /// Event raised on a vehicle to check if it can run/move around. diff --git a/Content.Shared/Vehicle/VehicleSystem.cs b/Content.Shared/Vehicle/VehicleSystem.cs index 4c395044a43..697c186aa5b 100644 --- a/Content.Shared/Vehicle/VehicleSystem.cs +++ b/Content.Shared/Vehicle/VehicleSystem.cs @@ -9,7 +9,6 @@ using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Containers; -using Robust.Shared.Prototypes; namespace Content.Shared.Vehicle; @@ -18,7 +17,6 @@ namespace Content.Shared.Vehicle; /// public sealed partial class VehicleSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedContainerSystem _container = default!; @@ -51,7 +49,7 @@ private void OnBeforeDamageChanged(Entity ent, ref BeforeDamag var damage = DamageSpecifier.GetPositive(args.Damage); - if (_prototype.TryIndex(ent.Comp.TransferDamageModifier, out var modifierSet)) + if (ent.Comp.TransferDamageModifier is { } modifierSet) { // Reduce damage to via the specified modifier, if provided. damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet); @@ -62,13 +60,6 @@ private void OnBeforeDamageChanged(Entity ent, ref BeforeDamag private void OnVehicleUpdateCanMove(Entity ent, ref UpdateCanMoveEvent args) { - // TODO: determine if this check is necessary - // if (ent.Comp.Operator is null) - // { - // args.Cancel(); - // return; - // } - var ev = new VehicleCanRunEvent(ent); RaiseLocalEvent(ent, ref ev); if (!ev.CanRun) @@ -120,9 +111,11 @@ public bool TrySetOperator(Entity entity, EntityUid? uid, bool if (!removeExisting && entity.Comp.Operator is not null) return false; - if (uid != null && !CanOperate(entity, uid.Value)) + if (uid != null && !CanOperate(entity.AsNullable(), uid.Value)) return false; + var oldOperator = entity.Comp.Operator; + if (entity.Comp.Operator is { } currentOperator && TryComp(currentOperator, out var currentOperatorComponent)) { var exitEvent = new OnVehicleExitedEvent(entity, currentOperator); @@ -154,7 +147,7 @@ public bool TrySetOperator(Entity entity, EntityUid? uid, bool RefreshCanRun((entity, entity.Comp)); - var setEvent = new VehicleOperatorSetEvent(uid); + var setEvent = new VehicleOperatorSetEvent(uid, oldOperator); RaiseLocalEvent(entity, ref setEvent); Dirty(entity); @@ -211,9 +204,19 @@ public bool TryGetOperator(Entity entity, [NotNullWhen(true)] return true; } + /// + /// Returns the operator of the vehicle or none if there isn't one present + /// + public EntityUid? GetOperatorOrNull(Entity entity) + { + TryGetOperator(entity, out var operatorEnt); + return operatorEnt; + } + /// /// Checks if the current vehicle has an operator. /// + [PublicAPI] public bool HasOperator(Entity entity) { return TryGetOperator(entity, out _); @@ -224,8 +227,11 @@ public bool HasOperator(Entity entity) /// Note that the general ability for a vehicle to run (keys, fuel, etc.) is not checked here. /// This is *only* for checks on the user. /// - public bool CanOperate(Entity entity, EntityUid uid) + public bool CanOperate(Entity entity, EntityUid uid) { + if (!Resolve(entity, ref entity.Comp)) + return false; + if (_entityWhitelist.IsWhitelistFail(entity.Comp.OperatorWhitelist, uid)) return false; diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml index 90f048bafd8..f6254d3d278 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml @@ -6,6 +6,9 @@ - type: MobMover - type: Mech - type: MechAir + - type: Vehicle + - type: ContainerVehicle + containerId: "mech-pilot-slot" - type: AirFilter # everything except oxygen and nitrogen gases: @@ -163,8 +166,16 @@ baseState: ripley openState: ripley-open brokenState: ripley-broken - mechToPilotDamageMultiplier: 0.75 - pilotWhitelist: + - type: Vehicle + transferDamageModifier: + coefficients: + Blunt: 0.75 + Slash: 0.75 + Piercing: 0.75 + Cold: 0.75 + Heat: 0.75 + Shock: 0.75 + operatorWhitelist: components: - HumanoidAppearance - type: MeleeWeapon @@ -209,9 +220,16 @@ baseState: honker openState: honker-open brokenState: honker-broken - mechToPilotDamageMultiplier: 0.5 - airtight: true # Goobstation - Space Honk is real. - pilotWhitelist: + - type: Vehicle + transferDamageModifier: + coefficients: + Blunt: 0.5 + Slash: 0.5 + Piercing: 0.5 + Cold: 0.5 + Heat: 0.5 + Shock: 0.5 + operatorWhitelist: components: - HumanoidAppearance @@ -245,10 +263,21 @@ baseState: hamtr openState: hamtr-open brokenState: hamtr-broken - mechToPilotDamageMultiplier: 0.2 maxEquipmentAmount: 2 airtight: true - pilotWhitelist: + equipmentWhitelist: + tags: + - SmallMech + - type: Vehicle + transferDamageModifier: + coefficients: + Blunt: 0.2 + Slash: 0.2 + Piercing: 0.2 + Cold: 0.2 + Heat: 0.2 + Shock: 0.2 + operatorWhitelist: tags: - Hamster - type: MeleeWeapon @@ -307,11 +336,19 @@ baseState: vim openState: vim-open brokenState: vim-broken - maxEquipmentAmount: 5 - # keep mouse safe - mechToPilotDamageMultiplier: 0.1 + maxEquipmentAmount: 0 airtight: true - pilotWhitelist: + - type: Vehicle + # keep mouse safe + transferDamageModifier: + coefficients: + Blunt: 0.1 + Slash: 0.1 + Piercing: 0.1 + Cold: 0.1 + Heat: 0.1 + Shock: 0.1 + operatorWhitelist: tags: - VimPilot equipmentWhitelist: From 2794ed26e60f986a2a81596a54ac8a77477157bf Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Thu, 14 Aug 2025 14:14:57 -0400 Subject: [PATCH 06/22] Fix mover stuff and finish last of vehicle code --- Content.Client/Buckle/BuckleSystem.cs | 51 +++ .../Physics/Controllers/MoverController.cs | 83 ++-- .../Buckle/Components/StrapComponent.cs | 6 + .../Buckle/SharedBuckleSystem.Buckle.cs | 8 + .../Movement/Systems/SharedMoverController.cs | 418 ++++++++++-------- Content.Shared/Vehicle/VehicleSystem.cs | 9 - Resources/Locale/en-US/vehicle/vehicle.ftl | 1 + .../Entities/Objects/Devices/vehicles.yml | 7 +- 8 files changed, 360 insertions(+), 223 deletions(-) create mode 100644 Resources/Locale/en-US/vehicle/vehicle.ftl diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index c26976ffbe4..82895a6fdce 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -61,6 +61,15 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE // This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking // sprite rendering for entity layers & direction dependent sorting. + // Future notes: + // Right now this doesn't handle: other grids, other grids rotating, the camera rotation changing, and many other fun rotation specific things + // The entire thing should be a concern of the engine, or something engine helps to implement properly. + // Give some of the sprite rotations their own drawdepth, maybe as an offset within the rsi, or something like this + // And we won't ever need to set the draw depth manually + + if (!component.ModifyBuckleDrawDepth) + return; + if (args.NewRotation == args.OldRotation) return; @@ -89,6 +98,48 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE } } + /// + /// Lower the draw depth of the buckled entity without needing for the strap entity to rotate/move. + /// Only do so when the entity is facing screen-local north + /// + private void OnBuckledEvent(Entity ent, ref BuckledEvent args) + { + if (!args.Strap.Comp.ModifyBuckleDrawDepth) + return; + + if (!TryComp(args.Strap, out var strapSprite)) + return; + + if (!TryComp(ent.Owner, out var buckledSprite)) + return; + + var angle = _xformSystem.GetWorldRotation(args.Strap) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough + + if (angle.GetCardinalDir() != Direction.North) + return; + + ent.Comp.OriginalDrawDepth ??= buckledSprite.DrawDepth; + _sprite.SetDrawDepth((ent.Owner, buckledSprite), strapSprite.DrawDepth - 1); + } + + /// + /// Was the draw depth of the buckled entity lowered? Reset it upon unbuckling. + /// + private void OnUnbuckledEvent(Entity ent, ref UnbuckledEvent args) + { + if (!args.Strap.Comp.ModifyBuckleDrawDepth) + return; + + if (!TryComp(ent.Owner, out var buckledSprite)) + return; + + if (!ent.Comp.OriginalDrawDepth.HasValue) + return; + + _sprite.SetDrawDepth((ent.Owner, buckledSprite), ent.Comp.OriginalDrawDepth.Value); + ent.Comp.OriginalDrawDepth = null; + } + private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args) { if (!TryComp(uid, out var rotVisuals) diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index f927e717a9d..842b29f2167 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -2,10 +2,12 @@ using System.Runtime.CompilerServices; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Systems; +using Content.Shared.Friction; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; +using Prometheus; using Robust.Shared.Physics.Components; using Robust.Shared.Player; using DroneConsoleComponent = Content.Server.Shuttles.DroneConsoleComponent; @@ -16,6 +18,10 @@ namespace Content.Server.Physics.Controllers; public sealed class MoverController : SharedMoverController { + private static readonly Gauge ActiveMoverGauge = Metrics.CreateGauge( + "physics_active_mover_count", + "Active amount of InputMovers being processed by MoverController"); + [Dependency] private readonly ThrusterSystem _thruster = default!; [Dependency] private readonly SharedTransformSystem _xformSystem = default!; @@ -57,50 +63,47 @@ protected override bool CanSound() return true; } + private HashSet _moverAdded = new(); + private List> _movers = new(); + + private void InsertMover(Entity source) + { + if (TryComp(source, out MovementRelayTargetComponent? relay)) + { + if (TryComp(relay.Source, out InputMoverComponent? relayMover) && relayMover.CanMove) + { + InsertMover((relay.Source, relayMover)); + } + } + + // Already added + if (!_moverAdded.Add(source.Owner)) + return; + + _movers.Add(source); + } + public override void UpdateBeforeSolve(bool prediction, float frameTime) { base.UpdateBeforeSolve(prediction, frameTime); + _moverAdded.Clear(); + _movers.Clear(); var inputQueryEnumerator = AllEntityQuery(); + // Need to order mob movement so that movers don't run before their relays. while (inputQueryEnumerator.MoveNext(out var uid, out var mover)) { - var physicsUid = uid; - - if (RelayQuery.HasComponent(uid)) - continue; - - if (!XformQuery.TryGetComponent(uid, out var xform)) - { - continue; - } - - PhysicsComponent? body; - var xformMover = xform; - - if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) - { - if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || - !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) - { - continue; - } - - physicsUid = xform.ParentUid; - } - else if (!PhysicsQuery.TryGetComponent(uid, out body)) - { - continue; - } + InsertMover((uid, mover)); + } - HandleMobMovement(uid, - mover, - physicsUid, - body, - xformMover, - frameTime); + foreach (var mover in _movers) + { + HandleMobMovement(mover, frameTime); } + ActiveMoverGauge.Set(_movers.Count); + HandleShuttleMovement(frameTime); } @@ -314,6 +317,9 @@ private void HandleShuttleMovement(float frameTime) var linearInput = Vector2.Zero; var brakeInput = 0f; var angularInput = 0f; + var linearCount = 0; + var brakeCount = 0; + var angularCount = 0; foreach (var (pilotUid, pilot, _, consoleXform) in pilots) { @@ -322,24 +328,27 @@ private void HandleShuttleMovement(float frameTime) if (brakes > 0f) { brakeInput += brakes; + brakeCount++; } if (strafe.Length() > 0f) { var offsetRotation = consoleXform.LocalRotation; linearInput += offsetRotation.RotateVec(strafe); + linearCount++; } if (rotation != 0f) { angularInput += rotation; + angularCount++; } } - var count = pilots.Count; - linearInput /= count; - angularInput /= count; - brakeInput /= count; + // Don't slow down the shuttle if there's someone just looking at the console + linearInput /= Math.Max(1, linearCount); + angularInput /= Math.Max(1, angularCount); + brakeInput /= Math.Max(1, brakeCount); // Handle shuttle movement if (brakeInput > 0f) diff --git a/Content.Shared/Buckle/Components/StrapComponent.cs b/Content.Shared/Buckle/Components/StrapComponent.cs index 79dc686c7d8..6bb30c17272 100644 --- a/Content.Shared/Buckle/Components/StrapComponent.cs +++ b/Content.Shared/Buckle/Components/StrapComponent.cs @@ -84,6 +84,12 @@ public sealed partial class StrapComponent : Component /// [DataField] public bool BuckleOnInteractHand = true; + + /// + /// Whether being buckled to this entity should change the buckled ent's drawdepth. + /// + [DataField] + public bool ModifyBuckleDrawDepth = true; } public enum StrapPosition diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index ce28559384a..970b496dd05 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -7,6 +7,7 @@ using Content.Shared.DoAfter; using Content.Shared.Hands.Components; using Content.Shared.IdentityManagement; +using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Popups; @@ -169,6 +170,13 @@ private void OnBuckleThrowPushbackAttempt(EntityUid uid, BuckleComponent compone private void OnBuckleUpdateCanMove(EntityUid uid, BuckleComponent component, UpdateCanMoveEvent args) { + // If we're relaying then don't cancel. + // NOTE: I don't love this solution. It's by far the easiest but i hate having it be a consideration. + // We need to have a more logical way of distinguishing between a "physical" movement being blocked + // And simply being unable to move due to being unconscious, dead, etc. -EMO + if (HasComp(uid)) + return; + if (component.Buckled) args.Cancel(); } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 432a71d15c7..ec6cba54a2e 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; -using Content.Shared.Alert; -using Content.Shared.Bed.Sleep; +using Content.Shared.ActionBlocker; using Content.Shared.CCVar; using Content.Shared.Friction; using Content.Shared.Gravity; @@ -10,9 +9,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; -using Content.Shared.StepTrigger.Components; using Content.Shared.Tag; -using Content.Shared.Traits.Assorted.Components; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; @@ -22,7 +19,7 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Controllers; -using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent; @@ -36,11 +33,9 @@ namespace Content.Shared.Movement.Systems; public abstract partial class SharedMoverController : VirtualController { [Dependency] private readonly IConfigurationManager _configManager = default!; - [Dependency] private readonly IEntityManager _entities = default!; [Dependency] protected readonly IGameTiming Timing = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; - [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly MobStateSystem _mobState = default!; @@ -48,37 +43,40 @@ public abstract partial class SharedMoverController : VirtualController [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!; - [Dependency] protected readonly SharedPhysicsSystem Physics = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly TagSystem _tags = default!; + protected EntityQuery CanMoveInAirQuery; + protected EntityQuery FootstepModifierQuery; protected EntityQuery MoverQuery; + protected EntityQuery MapQuery; + protected EntityQuery MapGridQuery; protected EntityQuery MobMoverQuery; protected EntityQuery RelayTargetQuery; protected EntityQuery ModifierQuery; + protected EntityQuery NoRotateQuery; protected EntityQuery PhysicsQuery; protected EntityQuery RelayQuery; protected EntityQuery PullableQuery; protected EntityQuery XformQuery; - protected EntityQuery CanMoveInAirQuery; - protected EntityQuery NoRotateQuery; - protected EntityQuery FootstepModifierQuery; - protected EntityQuery MapGridQuery; - /// - /// - /// - private float _stopSpeed; + private static readonly ProtoId FootstepSoundTag = "FootstepSound"; private bool _relativeMovement; + private float _minDamping; + private float _airDamping; + private float _offGridDamping; /// /// Cache the mob movement calculation to re-use elsewhere. /// public Dictionary UsedMobMovement = new(); + private readonly HashSet _aroundColliderSet = []; + public override void Initialize() { + UpdatesBefore.Add(typeof(TileFrictionController)); base.Initialize(); MoverQuery = GetEntityQuery(); @@ -93,13 +91,16 @@ public override void Initialize() CanMoveInAirQuery = GetEntityQuery(); FootstepModifierQuery = GetEntityQuery(); MapGridQuery = GetEntityQuery(); + MapQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnTileFriction); InitializeInput(); InitializeRelay(); - InitializeCVars(); Subs.CVar(_configManager, CCVars.RelativeMovement, value => _relativeMovement = value, true); - Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true); - UpdatesBefore.Add(typeof(TileFrictionController)); + Subs.CVar(_configManager, CCVars.MinFriction, value => _minDamping = value, true); + Subs.CVar(_configManager, CCVars.AirFriction, value => _airDamping = value, true); + Subs.CVar(_configManager, CCVars.OffgridFriction, value => _offGridDamping = value, true); } public override void Shutdown() @@ -118,184 +119,223 @@ public override void UpdateAfterSolve(bool prediction, float frameTime) /// Movement while considering actionblockers, weightlessness, etc. /// protected void HandleMobMovement( - EntityUid uid, - InputMoverComponent mover, - EntityUid physicsUid, - PhysicsComponent physicsComponent, - TransformComponent xform, + Entity entity, float frameTime) { - var canMove = mover.CanMove; - if (RelayTargetQuery.TryGetComponent(uid, out var relayTarget)) + var uid = entity.Owner; + var mover = entity.Comp; + + // If we're a relay then apply all of our data to the parent instead and go next. + if (RelayQuery.TryComp(uid, out var relay)) { - if (_mobState.IsIncapacitated(relayTarget.Source) || - TryComp(relayTarget.Source, out _) || - // Shitmed Change - !PhysicsQuery.TryGetComponent(relayTarget.Source, out var relayedPhysicsComponent) || - !MoverQuery.TryGetComponent(relayTarget.Source, out var relayedMover) || - !XformQuery.TryGetComponent(relayTarget.Source, out var relayedXform)) + if (!MoverQuery.TryComp(relay.RelayEntity, out var relayTargetMover)) + return; + + // Always lerp rotation so relay entities aren't cooked. + LerpRotation(uid, mover, frameTime); + var dirtied = false; + + if (relayTargetMover.RelativeEntity != mover.RelativeEntity) { - canMove = false; + relayTargetMover.RelativeEntity = mover.RelativeEntity; + dirtied = true; } - else + + if (relayTargetMover.RelativeRotation != mover.RelativeRotation) + { + relayTargetMover.RelativeRotation = mover.RelativeRotation; + dirtied = true; + } + + if (relayTargetMover.TargetRelativeRotation != mover.TargetRelativeRotation) + { + relayTargetMover.TargetRelativeRotation = mover.TargetRelativeRotation; + dirtied = true; + } + + if (dirtied) { - mover.LerpTarget = relayedMover.LerpTarget; - mover.RelativeEntity = relayedMover.RelativeEntity; - mover.RelativeRotation = relayedMover.RelativeRotation; - mover.TargetRelativeRotation = relayedMover.TargetRelativeRotation; - HandleMobMovement(relayTarget.Source, relayedMover, relayTarget.Source, relayedPhysicsComponent, relayedXform, frameTime); + Dirty(relay.RelayEntity, relayTargetMover); } + + return; } - // Update relative movement - // Shitmed Change Start - else + if (!XformQuery.TryComp(entity.Owner, out var xform)) + return; + + RelayTargetQuery.TryComp(uid, out var relayTarget); + var relaySource = relayTarget?.Source; + + // If we're not the target of a relay then handle lerp data. + if (relaySource == null) { + // Update relative movement if (mover.LerpTarget < Timing.CurTime) { - if (TryComp(uid, out RelayInputMoverComponent? relay) - && TryComp(relay.RelayEntity, out TransformComponent? relayXform)) - { - if (TryUpdateRelative(mover, relayXform)) - Dirty(uid, mover); - } - else - { - if (TryUpdateRelative(mover, xform)) - Dirty(uid, mover); - } + TryUpdateRelative(uid, mover, xform); } LerpRotation(uid, mover, frameTime); } - // Shitmed Change End - if (!canMove - || physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid) + // If we can't move then just use tile-friction / no movement handling. + if (!mover.CanMove + || !PhysicsQuery.TryComp(uid, out var physicsComponent) || PullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled) { UsedMobMovement[uid] = false; return; } + // If the body is in air but isn't weightless then it can't move + // TODO: MAKE ISWEIGHTLESS EVENT BASED + var weightless = _gravity.IsWeightless(uid, physicsComponent, xform); + var inAirHelpless = false; - UsedMobMovement[uid] = true; - // Specifically don't use mover.Owner because that may be different to the actual physics body being moved. - var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform); - var (walkDir, sprintDir) = GetVelocityInput(mover); - var touching = false; - - // Handle wall-pushes. - if (weightless) + if (physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid)) { - if (xform.GridUid != null) - touching = true; - - if (!touching) + if (!weightless) { - var ev = new CanWeightlessMoveEvent(uid); - RaiseLocalEvent(uid, ref ev, true); - // No gravity: is our entity touching anything? - touching = ev.CanMove; - - if (!touching && TryComp(uid, out var mobMover)) - touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsUid, physicsComponent); + UsedMobMovement[uid] = false; + return; } + inAirHelpless = true; } + UsedMobMovement[uid] = true; + + var moveSpeedComponent = ModifierQuery.CompOrNull(uid); + + float friction; + float accel; + Vector2 wishDir; + var velocity = physicsComponent.LinearVelocity; + // Get current tile def for things like speed/friction mods ContentTileDefinition? tileDef = null; - // Don't bother getting the tiledef here if we're weightless or in-air - // since no tile-based modifiers should be applying in that situation - if (MapGridQuery.TryComp(xform.GridUid, out var gridComp) - && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile) - && !(weightless || physicsComponent.BodyStatus == BodyStatus.InAir)) + var touching = false; + // Whether we use tilefriction or not + if (weightless || inAirHelpless) { - tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId]; - } + // Find the speed we should be moving at and make sure we're not trying to move faster than that + var walkSpeed = moveSpeedComponent?.WeightlessWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed; + var sprintSpeed = moveSpeedComponent?.WeightlessSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed; - // Regular movement. - // Target velocity. - // This is relative to the map / grid we're on. - var moveSpeedComponent = ModifierQuery.CompOrNull(uid); + wishDir = AssertValidWish(mover, walkSpeed, sprintSpeed); - var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed; - var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed; + var ev = new CanWeightlessMoveEvent(uid); + RaiseLocalEvent(uid, ref ev, true); - var total = walkDir * walkSpeed + sprintDir * sprintSpeed; + touching = ev.CanMove || xform.GridUid != null || MapGridQuery.HasComp(xform.GridUid); - var parentRotation = GetParentGridAngle(mover); - var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total; + // If we're not on a grid, and not able to move in space check if we're close enough to a grid to touch. + if (!touching && MobMoverQuery.TryComp(uid, out var mobMover)) + touching |= IsAroundCollider(_lookup, (uid, physicsComponent, mobMover, xform)); - DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), worldTotal.Length())); - - var velocity = physicsComponent.LinearVelocity; - float friction; - float weightlessModifier; - float accel; - - if (weightless) - { - if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid)) - friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction; - else if (worldTotal != Vector2.Zero && touching) - friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction; + // If we're touching then use the weightless values + if (touching) + { + touching = true; + if (wishDir != Vector2.Zero) + friction = moveSpeedComponent?.WeightlessFriction ?? _airDamping; + else + friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? _airDamping; + } + // Otherwise use the off-grid values. else - friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput; + { + friction = moveSpeedComponent?.OffGridFriction ?? _offGridDamping; + } - weightlessModifier = moveSpeedComponent?.WeightlessModifier ?? MovementSpeedModifierComponent.DefaultWeightlessModifier; accel = moveSpeedComponent?.WeightlessAcceleration ?? MovementSpeedModifierComponent.DefaultWeightlessAcceleration; } else { - if (worldTotal != Vector2.Zero) + if (MapGridQuery.TryComp(xform.GridUid, out var gridComp) + && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile) + && physicsComponent.BodyStatus == BodyStatus.OnGround) + tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId]; + + var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed; + var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed; + + wishDir = AssertValidWish(mover, walkSpeed, sprintSpeed); + + if (wishDir != Vector2.Zero) { - friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction; + friction = moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction; + friction *= tileDef?.MobFriction ?? tileDef?.Friction ?? 1f; } else { - friction = tileDef?.MobFrictionNoInput ?? moveSpeedComponent?.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput; + friction = moveSpeedComponent?.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput; + friction *= tileDef?.Friction ?? 1f; } - weightlessModifier = 1f; - accel = tileDef?.MobAcceleration ?? moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration; + accel = moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration; + accel *= tileDef?.MobAcceleration ?? 1f; } + // This way friction never exceeds acceleration when you're trying to move. + // If you want to slow down an entity with "friction" you shouldn't be using this system. + if (wishDir != Vector2.Zero) + friction = Math.Min(friction, accel); + friction = Math.Max(friction, _minDamping); var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed; Friction(minimumFrictionSpeed, frameTime, friction, ref velocity); - if (worldTotal != Vector2.Zero) + if (!weightless || touching) + Accelerate(ref velocity, in wishDir, accel, frameTime); + + SetWishDir((uid, mover), wishDir); + + /* + * SNAKING!!! >-( 0 ================> + * Snaking is a feature where you can move faster by strafing in a direction perpendicular to the + * direction you intend to move while still holding the movement key for the direction you're trying to move. + * Snaking only works if acceleration exceeds friction, and it's effectiveness scales as acceleration continues + * to exceed friction. + * Snaking works because friction is applied first in the direction of our current velocity, while acceleration + * is applied after in our "Wish Direction" and is capped by the dot of our wish direction and current direction. + * This means when you change direction, you're technically able to accelerate more than what the velocity cap + * allows, but friction normally eats up the extra movement you gain. + * By strafing as stated above you can increase your speed by about 1.4 (square root of 2). + * This only works if friction is low enough so be sure that anytime you are letting a mob move in a low friction + * environment you take into account the fact they can snake! Also be sure to lower acceleration as well to + * prevent jerky movement! + */ + PhysicsSystem.SetLinearVelocity(uid, velocity, body: physicsComponent); + + // Ensures that players do not spiiiiiiin + PhysicsSystem.SetAngularVelocity(uid, 0, body: physicsComponent); + + // Handle footsteps at the end + if (wishDir != Vector2.Zero) { if (!NoRotateQuery.HasComponent(uid)) { // TODO apparently this results in a duplicate move event because "This should have its event run during // island solver"??. So maybe SetRotation needs an argument to avoid raising an event? var worldRot = _transform.GetWorldRotation(xform); - _transform.SetLocalRotation(xform, xform.LocalRotation + worldTotal.ToWorldAngle() - worldRot); + + _transform.SetLocalRotation(uid, xform.LocalRotation + wishDir.ToWorldAngle() - worldRot, xform); } if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) && TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef)) { var soundModifier = mover.Sprinting ? 3.5f : 1.5f; - var volume = sound.Params.Volume + soundModifier; - - if (_entities.TryGetComponent(uid, out FootstepVolumeModifierComponent? volumeModifier)) - { - volume += mover.Sprinting - ? volumeModifier.SprintVolumeModifier - : volumeModifier.WalkVolumeModifier; - } var audioParams = sound.Params - .WithVolume(volume) + .WithVolume(sound.Params.Volume + soundModifier) .WithVariation(sound.Params.Variation ?? mobMover.FootstepVariation); // If we're a relay target then predict the sound for all relays. - if (relayTarget != null) + if (relaySource != null) { - _audio.PlayPredicted(sound, uid, relayTarget.Source, audioParams); + _audio.PlayPredicted(sound, uid, relaySource.Value, audioParams); } else { @@ -303,21 +343,23 @@ protected void HandleMobMovement( } } } + } - worldTotal *= weightlessModifier; - - if (!weightless || touching) - Accelerate(ref velocity, in worldTotal, accel, frameTime); - - PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent); + public Vector2 GetWishDir(Entity mover) + { + if (!MoverQuery.Resolve(mover.Owner, ref mover.Comp, false)) + return Vector2.Zero; - // Ensures that players do not spiiiiiiin - PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent); + return mover.Comp.WishDir; } - private void WalkingAlert(Entity entity) + public void SetWishDir(Entity mover, Vector2 wishDir) { - _alerts.ShowAlert(entity, entity.Comp.WalkingAlert, entity.Comp.Sprinting ? (short) 1 : (short) 0); + if (mover.Comp.WishDir.Equals(wishDir)) + return; + + mover.Comp.WishDir = wishDir; + Dirty(mover); } public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime) @@ -341,40 +383,44 @@ public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTi adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff); } - mover.RelativeRotation += adjustment; - mover.RelativeRotation.FlipPositive(); + mover.RelativeRotation = (mover.RelativeRotation + adjustment).FlipPositive(); Dirty(uid, mover); } else if (!angleDiff.Equals(Angle.Zero)) { - mover.TargetRelativeRotation.FlipPositive(); - mover.RelativeRotation = mover.TargetRelativeRotation; + mover.RelativeRotation = mover.TargetRelativeRotation.FlipPositive(); Dirty(uid, mover); } } - private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity) + public void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity) { var speed = velocity.Length(); if (speed < minimumFrictionSpeed) return; - var drop = 0f; + // This equation is lifted from the Physics Island solver. + // We re-use it here because Kinematic Controllers can't/shouldn't use the Physics Friction + velocity *= Math.Clamp(1.0f - frameTime * friction, 0.0f, 1.0f); - var control = MathF.Max(_stopSpeed, speed); - drop += control * friction * frameTime; - - var newSpeed = MathF.Max(0f, speed - drop); + } - if (newSpeed.Equals(speed)) + public void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref float velocity) + { + if (Math.Abs(velocity) < minimumFrictionSpeed) return; - newSpeed /= speed; - velocity *= newSpeed; + // This equation is lifted from the Physics Island solver. + // We re-use it here because Kinematic Controllers can't/shouldn't use the Physics Friction + velocity *= Math.Clamp(1.0f - frameTime * friction, 0.0f, 1.0f); + } - private void Accelerate(ref Vector2 currentVelocity, in Vector2 velocity, float accel, float frameTime) + /// + /// Adjusts the current velocity to the target velocity based on the specified acceleration. + /// + public static void Accelerate(ref Vector2 currentVelocity, in Vector2 velocity, float accel, float frameTime) { var wishDir = velocity != Vector2.Zero ? velocity.Normalized() : Vector2.Zero; var wishSpeed = velocity.Length(); @@ -397,23 +443,29 @@ public bool UseMobMovement(EntityUid uid) } /// - /// Used for weightlessness to determine if we are near a wall. + /// Used for weightlessness to determine if we are near a wall. /// - private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformComponent transform, MobMoverComponent mover, EntityUid physicsUid, PhysicsComponent collider) + private bool IsAroundCollider(EntityLookupSystem lookupSystem, Entity entity) { - var enlargedAABB = _lookup.GetWorldAABB(physicsUid, transform).Enlarged(mover.GrabRangeVV); + var (uid, collider, mover, transform) = entity; + var enlargedAABB = _lookup.GetWorldAABB(entity.Owner, transform).Enlarged(mover.GrabRange); - foreach (var otherCollider in broadPhaseSystem.GetCollidingEntities(transform.MapID, enlargedAABB)) + _aroundColliderSet.Clear(); + lookupSystem.GetEntitiesIntersecting(transform.MapID, enlargedAABB, _aroundColliderSet); + foreach (var otherEntity in _aroundColliderSet) { - if (otherCollider == collider) + if (otherEntity == uid) continue; // Don't try to push off of yourself! + if (!PhysicsQuery.TryComp(otherEntity, out var otherCollider)) + continue; + // Only allow pushing off of anchored things that have collision. if (otherCollider.BodyType != BodyType.Static || !otherCollider.CanCollide || ((collider.CollisionMask & otherCollider.CollisionLayer) == 0 && - (otherCollider.CollisionMask & collider.CollisionLayer) == 0) || - (TryComp(otherCollider.Owner, out PullableComponent? pullable) && pullable.BeingPulled)) + (otherCollider.CollisionMask & collider.CollisionLayer) == 0) || + (TryComp(otherEntity, out PullableComponent? pullable) && pullable.BeingPulled)) { continue; } @@ -437,7 +489,7 @@ private bool TryGetSound( { sound = null; - if (!CanSound() || !_tags.HasTag(uid, "FootstepSound")) + if (!CanSound() || !_tags.HasTag(uid, FootstepSoundTag)) return false; var coordinates = xform.Coordinates; @@ -470,28 +522,19 @@ private bool TryGetSound( if (mobMover.StepSoundDistance < distanceNeeded) return false; - var soundEv = new MakeFootstepSoundEvent(); - RaiseLocalEvent(uid, soundEv); - mobMover.StepSoundDistance -= distanceNeeded; if (FootstepModifierQuery.TryComp(uid, out var moverModifier)) { sound = moverModifier.FootstepSoundCollection; - return true; - } - - if (_entities.TryGetComponent(uid, out NoShoesSilentFootstepsComponent? _) & - !_inventory.TryGetSlotEntity(uid, "shoes", out var _)) - { - return false; + return sound != null; } if (_inventory.TryGetSlotEntity(uid, "shoes", out var shoes) && FootstepModifierQuery.TryComp(shoes, out var modifier)) { sound = modifier.FootstepSoundCollection; - return true; + return sound != null; } return TryGetFootstepSound(uid, xform, shoes != null, out sound, tileDef: tileDef); @@ -512,18 +555,17 @@ private bool TryGetFootstepSound( if (FootstepModifierQuery.TryComp(xform.MapUid, out var modifier)) { sound = modifier.FootstepSoundCollection; - return true; } - return false; + return sound != null; } - var position = grid.LocalToTile(xform.Coordinates); + var position = _mapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates); var soundEv = new GetFootstepSoundEvent(uid); // If the coordinates have a FootstepModifier component // i.e. component that emit sound on footsteps emit that sound - var anchored = grid.GetAnchoredEntitiesEnumerator(position); + var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(xform.GridUid.Value, grid, position); while (anchored.MoveNext(out var maybeFootstep)) { @@ -538,16 +580,16 @@ private bool TryGetFootstepSound( if (FootstepModifierQuery.TryComp(maybeFootstep, out var footstep)) { sound = footstep.FootstepSoundCollection; - return true; + return sound != null; } } // Walking on a tile. // Tile def might have been passed in already from previous methods, so use that // if we have it - if (tileDef == null && grid.TryGetTileRef(position, out var tileRef)) + if (tileDef == null && _mapSystem.TryGetTileRef(xform.GridUid.Value, grid, position, out var tileRef)) { - tileDef = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId]; + tileDef = (ContentTileDefinition)_tileDefinitionManager[tileRef.Tile.TypeId]; } if (tileDef == null) @@ -556,4 +598,30 @@ private bool TryGetFootstepSound( sound = haveShoes ? tileDef.FootstepSounds : tileDef.BarestepSounds; return sound != null; } + + private Vector2 AssertValidWish(InputMoverComponent mover, float walkSpeed, float sprintSpeed) + { + var (walkDir, sprintDir) = GetVelocityInput(mover); + + var total = walkDir * walkSpeed + sprintDir * sprintSpeed; + + var parentRotation = GetParentGridAngle(mover); + var wishDir = _relativeMovement ? parentRotation.RotateVec(total) : total; + + DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), wishDir.Length())); + + return wishDir; + } + + private void OnTileFriction(Entity ent, ref TileFrictionEvent args) + { + if (!TryComp(ent, out var physicsComponent) || !XformQuery.TryComp(ent, out var xform)) + return; + + // TODO: Make IsWeightless event based!!! + if (physicsComponent.BodyStatus != BodyStatus.OnGround || _gravity.IsWeightless(ent, physicsComponent, xform)) + args.Modifier *= ent.Comp.BaseWeightlessFriction; + else + args.Modifier *= ent.Comp.BaseFriction; + } } diff --git a/Content.Shared/Vehicle/VehicleSystem.cs b/Content.Shared/Vehicle/VehicleSystem.cs index 697c186aa5b..5766ea57e63 100644 --- a/Content.Shared/Vehicle/VehicleSystem.cs +++ b/Content.Shared/Vehicle/VehicleSystem.cs @@ -36,7 +36,6 @@ public override void Initialize() SubscribeLocalEvent(OnVehicleGetAdditionalAccess); SubscribeLocalEvent(OnOperatorShutdown); - SubscribeLocalEvent(OnOperatorGetAdditionalAccess); } /// @@ -83,14 +82,6 @@ private void OnOperatorShutdown(Entity ent, ref Compon TryRemoveOperator((ent, ent)); } - private void OnOperatorGetAdditionalAccess(Entity ent, ref GetAdditionalAccessEvent args) - { - // Operators inherit access from whatever the vehicle has - // (Used to support vehicles having intrinsic access associated with them) - if (ent.Comp.Vehicle is { } vehicle) - args.Entities.Add(vehicle); - } - /// /// Set the operator for a given vehicle /// diff --git a/Resources/Locale/en-US/vehicle/vehicle.ftl b/Resources/Locale/en-US/vehicle/vehicle.ftl new file mode 100644 index 00000000000..44353580475 --- /dev/null +++ b/Resources/Locale/en-US/vehicle/vehicle.ftl @@ -0,0 +1 @@ +vehicle-slot-component-slot-name-keys = keys diff --git a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml index b8791fa19a4..0406b037c88 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml @@ -13,7 +13,6 @@ - type: Vehicle - type: StrapVehicle - type: Strap - maxBuckleDistance: 1 - type: MovementSpeedModifier weightlessModifier: 0 acceleration: 2 @@ -62,7 +61,7 @@ baseWalkSpeed: 2 baseSprintSpeed: 2 - type: Strap - buckleOffset: "0,0" + buckleOffset: "0,-0.05" - type: Fixtures fixtures: fix1: @@ -86,6 +85,7 @@ components: - type: Sprite sprite: Objects/Vehicles/janicart.rsi + drawdepth: Mobs # fixes layering like magic. layers: - state: vehicle map: ["movement"] @@ -131,6 +131,9 @@ # VehicleJanicartDestroyed: # min: 1 # max: 1 + - type: Strap + buckleOffset: "0.01,0.15" + modifyBuckleDrawDepth: false - type: ItemSlots slots: key_slot: From 9d7a2cc9ea7ad5d19aee838aefaf61e5f9a18e15 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Thu, 14 Aug 2025 14:38:50 -0400 Subject: [PATCH 07/22] this, i think? --- Resources/Prototypes/Entities/Objects/Devices/vehicles.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml index 0406b037c88..09d802a2e02 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml @@ -150,6 +150,10 @@ whitelist: tags: - TrashBag + - type: ContainerContainer + containers: + key_slot: !type:ContainerSlot + trashbag_slot: !type:ContainerSlot - type: ItemMapper mapLayers: storage: From d60ac1d3367081a26e25b862d81419d5beb454dc Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Thu, 14 Aug 2025 17:48:18 -0400 Subject: [PATCH 08/22] actually use this value --- .../Vehicle/Components/GenericKeyedVehicleComponent.cs | 2 +- Content.Shared/Vehicle/VehicleSystem.Key.cs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs b/Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs index fd072c3fbcc..72df15a8256 100644 --- a/Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs +++ b/Content.Shared/Vehicle/Components/GenericKeyedVehicleComponent.cs @@ -23,7 +23,7 @@ public sealed partial class GenericKeyedVehicleComponent : Component public EntityWhitelist KeyWhitelist = new(); /// - /// If true, prevents kys which do not pass the from being inserted into + /// If true, prevents keys which do not pass the from being inserted into /// [DataField] public bool PreventInvalidInsertion = true; diff --git a/Content.Shared/Vehicle/VehicleSystem.Key.cs b/Content.Shared/Vehicle/VehicleSystem.Key.cs index a8bd2ca1c69..b931f7b8c01 100644 --- a/Content.Shared/Vehicle/VehicleSystem.Key.cs +++ b/Content.Shared/Vehicle/VehicleSystem.Key.cs @@ -15,10 +15,7 @@ public void InitializeKey() private void OnGenericKeyedInsertAttempt(Entity ent, ref ContainerIsInsertingAttemptEvent args) { - if (args.Cancelled) - return; - - if (args.Container.ID != ent.Comp.ContainerId) + if (args.Cancelled || !ent.Comp.PreventInvalidInsertion || args.Container.ID != ent.Comp.ContainerId) return; if (_entityWhitelist.IsWhitelistPass(ent.Comp.KeyWhitelist, args.EntityUid)) From f8e4d87e1763fab60febabb15ffe69486d2025ed Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Wed, 10 Sep 2025 15:03:45 -0400 Subject: [PATCH 09/22] review --- Content.Server/Physics/Controllers/MoverController.cs | 2 +- .../Movement/Systems/SharedMoverController.Input.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index 842b29f2167..5c87de18638 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -70,7 +70,7 @@ private void InsertMover(Entity source) { if (TryComp(source, out MovementRelayTargetComponent? relay)) { - if (TryComp(relay.Source, out InputMoverComponent? relayMover) && relayMover.CanMove) + if (TryComp(relay.Source, out InputMoverComponent? relayMover)) { InsertMover((relay.Source, relayMover)); } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index abd7772fadd..c94e726ab6b 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -319,15 +319,15 @@ private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bo { DebugTools.Assert(relayMover.RelayEntity != entity); DebugTools.AssertNotNull(relayMover.RelayEntity); + DebugTools.Assert(TryComp(relayMover.RelayEntity, out var relayInputMover)); if (MoverQuery.TryGetComponent(entity, out var mover)) SetMoveInput((entity, mover), MoveButtons.None); - if (_mobState.IsDead(entity) - || _mobState.IsCritical(entity) && !_configManager.GetCVar(CCVars.AllowMovementWhileCrit)) - return; - - HandleDirChange(relayMover.RelayEntity, dir, subTick, state); + if (mover?.CanMove == true) + HandleDirChange(relayMover.RelayEntity, dir, subTick, state); + else // Cancel movement if our relay source cannot move + SetMoveInput((relayMover.RelayEntity, relayInputMover), MoveButtons.None); return; } From 4e5368b030fc59e77b80192baf8d0b7b754b12f6 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Wed, 10 Sep 2025 19:47:04 -0400 Subject: [PATCH 10/22] more logical, yet bug is still present --- .../Systems/SharedMoverController.Input.cs | 102 +++++++----------- 1 file changed, 38 insertions(+), 64 deletions(-) diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index c94e726ab6b..00573f18316 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -5,16 +5,15 @@ using Content.Shared.Input; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; -using Robust.Shared.Configuration; using Robust.Shared.GameStates; using Robust.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.Map.Components; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; -using Robust.Shared.Maths; // Shitmed Change namespace Content.Shared.Movement.Systems { @@ -25,6 +24,8 @@ public abstract partial class SharedMoverController { public bool CameraRotationLocked { get; set; } + public static ProtoId WalkingAlert = "Walking"; + private void InitializeInput() { var moveUpCmdHandler = new MoverDirInputCmdHandler(this, Direction.North); @@ -92,17 +93,12 @@ 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. - // Shitmed Change Start - 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); - // Shitmed Change End + var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons); entity.Comp.HeldMoveButtons = buttons; RaiseLocalEvent(entity, ref moveEvent); Dirty(entity, entity.Comp); - var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None); + var ev = new SpriteMoveEvent(entity.Comp.HasDirectionalMovement); RaiseLocalEvent(entity, ref ev); } @@ -117,23 +113,18 @@ private void OnMoverHandleState(Entity entity, ref Componen entity.Comp.TargetRelativeRotation = state.TargetRelativeRotation; entity.Comp.CanMove = state.CanMove; entity.Comp.RelativeEntity = EnsureEntity(state.RelativeEntity, entity.Owner); - entity.Comp.DefaultSprinting = state.DefaultSprinting; // Reset entity.Comp.LastInputTick = GameTick.Zero; entity.Comp.LastInputSubTick = 0; - // Shitmed Change Start - 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(); - // Shitmed Change End + if (entity.Comp.HeldMoveButtons != state.HeldMoveButtons) { - var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons, dir, state.HeldMoveButtons != 0); // Shitmed Change + var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons); entity.Comp.HeldMoveButtons = state.HeldMoveButtons; RaiseLocalEvent(entity.Owner, ref moveEvent); - var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None); + var ev = new SpriteMoveEvent(entity.Comp.HasDirectionalMovement); RaiseLocalEvent(entity, ref ev); } } @@ -148,7 +139,6 @@ private void OnMoverGetState(Entity entity, ref ComponentGe HeldMoveButtons = entity.Comp.HeldMoveButtons, RelativeRotation = entity.Comp.RelativeRotation, TargetRelativeRotation = entity.Comp.TargetRelativeRotation, - DefaultSprinting = entity.Comp.DefaultSprinting }; } @@ -178,24 +168,16 @@ public void ResetCamera(EntityUid uid) return; } - // Shitmed Change Start - var xform = XformQuery.GetComponent(uid); - if (TryComp(uid, out RelayInputMoverComponent? relay) - && TryComp(relay.RelayEntity, out TransformComponent? relayXform) - && MoverQuery.TryGetComponent(relay.RelayEntity, out var relayMover)) - xform = relayXform; - // If we updated parent then cancel the accumulator and force it now. - if (!TryUpdateRelative(mover, xform) && mover.TargetRelativeRotation.Equals(Angle.Zero)) + if (!TryUpdateRelative(uid, mover, XformQuery.GetComponent(uid)) && mover.TargetRelativeRotation.Equals(Angle.Zero)) return; - // Shitmed Change End mover.LerpTarget = TimeSpan.Zero; mover.TargetRelativeRotation = Angle.Zero; Dirty(uid, mover); } - private bool TryUpdateRelative(InputMoverComponent mover, TransformComponent xform) + private bool TryUpdateRelative(EntityUid uid, InputMoverComponent mover, TransformComponent xform) { var relative = xform.GridUid; relative ??= xform.MapUid; @@ -210,38 +192,42 @@ private bool TryUpdateRelative(InputMoverComponent mover, TransformComponent xfo // Okay need to get our old relative rotation with respect to our new relative rotation // e.g. if we were right side up on our current grid need to get what that is on our new grid. - var currentRotation = Angle.Zero; - var targetRotation = Angle.Zero; + var oldRelativeRot = Angle.Zero; + var relativeRot = Angle.Zero; // Get our current relative rotation if (XformQuery.TryGetComponent(mover.RelativeEntity, out var oldRelativeXform)) { - currentRotation = _transform.GetWorldRotation(oldRelativeXform, XformQuery) + mover.RelativeRotation; + oldRelativeRot = _transform.GetWorldRotation(oldRelativeXform); } if (XformQuery.TryGetComponent(relative, out var relativeXform)) { // This is our current rotation relative to our new parent. - mover.RelativeRotation = (currentRotation - _transform.GetWorldRotation(relativeXform)).FlipPositive(); + relativeRot = _transform.GetWorldRotation(relativeXform); } - // If we went from grid -> map we'll preserve our worldrotation - if (relative != null && HasComp(relative.Value)) + var diff = relativeRot - oldRelativeRot; + + // If we're going from a grid -> map then preserve the relative rotation so it's seamless if they go into space and back. + if (MapQuery.HasComp(relative) && MapGridQuery.HasComp(mover.RelativeEntity)) { - targetRotation = currentRotation.FlipPositive().Reduced(); + mover.TargetRelativeRotation -= diff; } - // If we went from grid -> grid OR grid -> map then snap the target to cardinal and lerp there. - // OR just rotate to zero (depending on cvar) - else if (relative != null && _mapManager.IsGrid(relative.Value)) + // Snap to nearest cardinal if map -> grid or grid -> grid + else if (MapGridQuery.HasComp(relative) && (MapQuery.HasComp(mover.RelativeEntity) || MapGridQuery.HasComp(mover.RelativeEntity))) { - if (CameraRotationLocked) - targetRotation = Angle.Zero; - else - targetRotation = mover.RelativeRotation.GetCardinalDir().ToAngle().Reduced(); + var targetDir = mover.TargetRelativeRotation - diff; + targetDir = targetDir.GetCardinalDir().ToAngle().Reduced(); + mover.TargetRelativeRotation = targetDir; } + // Preserve target rotation in relation to the new parent. + // Regardless of what the target is don't want the eye to move at all (from the player's perspective). + mover.RelativeRotation -= diff; + mover.RelativeEntity = relative; - mover.TargetRelativeRotation = targetRotation; + Dirty(uid, mover); return true; } @@ -315,32 +301,23 @@ private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bo // Relayed movement just uses the same keybinds given we're moving the relayed entity // the same as us. - if (TryComp(entity, out var relayMover)) + if (!MoverQuery.TryGetComponent(entity, out var moverComp)) + return; + + // TODO: Should move this into HandleMobMovement itself. + if (moverComp.CanMove && + TryComp(entity, out var relayMover)) { DebugTools.Assert(relayMover.RelayEntity != entity); DebugTools.AssertNotNull(relayMover.RelayEntity); - DebugTools.Assert(TryComp(relayMover.RelayEntity, out var relayInputMover)); if (MoverQuery.TryGetComponent(entity, out var mover)) SetMoveInput((entity, mover), MoveButtons.None); - if (mover?.CanMove == true) - HandleDirChange(relayMover.RelayEntity, dir, subTick, state); - else // Cancel movement if our relay source cannot move - SetMoveInput((relayMover.RelayEntity, relayInputMover), MoveButtons.None); - + HandleDirChange(relayMover.RelayEntity, dir, subTick, state); return; } - if (!MoverQuery.TryGetComponent(entity, out var moverComp)) - return; - - // Shitmed Change Start - var moverEntity = new Entity(entity, moverComp); - var moveEvent = new MoveInputEvent(moverEntity, moverComp.HeldMoveButtons, dir, state); - RaiseLocalEvent(entity, ref moveEvent); - // Shitmed Change End - // For stuff like "Moving out of locker" or the likes // We'll relay a movement input to the parent. if (_container.IsEntityInContainer(entity) && @@ -364,7 +341,6 @@ private void OnInputInit(Entity entity, ref ComponentInit a entity.Comp.RelativeEntity = xform.GridUid ?? xform.MapUid; entity.Comp.TargetRelativeRotation = Angle.Zero; - WalkingAlert(entity); } private void HandleRunChange(EntityUid uid, ushort subTick, bool walking) @@ -377,7 +353,6 @@ private void HandleRunChange(EntityUid uid, ushort subTick, bool walking) if (moverComp != null) { SetMoveInput((uid, moverComp), MoveButtons.None); - WalkingAlert((uid, moverComp)); } HandleRunChange(relayMover.RelayEntity, subTick, walking); @@ -493,12 +468,11 @@ private void ResetSubtick(InputMoverComponent component) component.LastInputSubTick = 0; } - public void SetSprinting(Entity entity, ushort subTick, bool walking) + public virtual void SetSprinting(Entity entity, ushort subTick, bool walking) { // Logger.Info($"[{_gameTiming.CurTick}/{subTick}] Sprint: {enabled}"); SetMoveInput(entity, subTick, walking, MoveButtons.Walk); - WalkingAlert(entity); } /// @@ -650,7 +624,7 @@ public enum MoveButtons : byte Down = 2, Left = 4, Right = 8, - Walk = 16, // This may be either a sprint button or a walk button, depending on mover config + Walk = 16, AnyDirection = Up | Down | Left | Right, } From cdb7a5f168f000906f68a6f5e232f55395c976d8 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Sat, 13 Sep 2025 01:03:00 -0400 Subject: [PATCH 11/22] Create event for reacting to CanMove updates --- .../ActionBlocker/ActionBlockerSystem.cs | 4 +- .../Components/InputMoverComponent.cs | 40 +++++++++---------- .../Movement/Events/UpdateCanMoveEvent.cs | 3 ++ .../Systems/SharedMoverController.Input.cs | 14 ++++--- .../Systems/SharedMoverController.Relay.cs | 7 ++++ 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index dcf26223016..7055b844dec 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -60,7 +60,9 @@ public bool UpdateCanMove(EntityUid uid, InputMoverComponent? component = null) Dirty(uid, component); component.CanMove = !ev.Cancelled; - return !ev.Cancelled; + var updatedEv = new CanMoveUpdatedEvent(component.CanMove); + RaiseLocalEvent(uid, ref updatedEv); + return component.CanMove; } /// diff --git a/Content.Shared/Movement/Components/InputMoverComponent.cs b/Content.Shared/Movement/Components/InputMoverComponent.cs index 40cb532e60a..a5d16f899ed 100644 --- a/Content.Shared/Movement/Components/InputMoverComponent.cs +++ b/Content.Shared/Movement/Components/InputMoverComponent.cs @@ -1,13 +1,9 @@ using System.Numerics; -using Content.Shared.Alert; -using Content.Shared.CCVar; using Content.Shared.Movement.Systems; -using Robust.Shared.Configuration; using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Timing; -using Robust.Shared.Prototypes; namespace Content.Shared.Movement.Components { @@ -33,20 +29,31 @@ public sealed partial class InputMoverComponent : Component // (well maybe we do but the code is designed such that MoverSystem applies movement speed) // (and I'm not changing that) - /// - /// Should our velocity be applied to our parent? - /// - [ViewVariables(VVAccess.ReadWrite), DataField("toParent")] - public bool ToParent = false; - public GameTick LastInputTick; public ushort LastInputSubTick; public Vector2 CurTickWalkMovement; public Vector2 CurTickSprintMovement; + [ViewVariables] public MoveButtons HeldMoveButtons = MoveButtons.None; + /// + /// Does our input indicate actual movement, and not just modifiers? + /// + /// + /// This can be useful to filter out input from just pressing the walk button with no directions, for example. + /// + [ViewVariables] + public bool HasDirectionalMovement => (HeldMoveButtons & MoveButtons.AnyDirection) != MoveButtons.None; + + // I don't know if we even need this networked? It's mostly so conveyors can calculate properly. + /// + /// Direction to move this tick. + /// + [ViewVariables] + public Vector2 WishDir; + /// /// Entity our movement is relative to. /// @@ -69,22 +76,15 @@ public sealed partial class InputMoverComponent : Component /// If we traverse on / off a grid then set a timer to update our relative inputs. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [ViewVariables(VVAccess.ReadWrite)] public TimeSpan LerpTarget; public const float LerpTime = 1.0f; - public bool Sprinting => DefaultSprinting - ? (HeldMoveButtons & MoveButtons.Walk) != 0x0 - : (HeldMoveButtons & MoveButtons.Walk) == 0x0; - - public bool DefaultSprinting = true; + [ViewVariables] + public bool Sprinting => (HeldMoveButtons & MoveButtons.Walk) == 0x0; [ViewVariables(VVAccess.ReadWrite)] public bool CanMove = true; - - [DataField] - public ProtoId WalkingAlert = "Walking"; } [Serializable, NetSerializable] @@ -95,6 +95,6 @@ public sealed class InputMoverComponentState : ComponentState public Angle TargetRelativeRotation; public Angle RelativeRotation; public TimeSpan LerpTarget; - public bool CanMove, DefaultSprinting; + public bool CanMove; } } diff --git a/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs b/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs index d9772c1cf4c..bca625e35b1 100644 --- a/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs +++ b/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs @@ -15,3 +15,6 @@ public UpdateCanMoveEvent(EntityUid uid) public EntityUid Uid { get; } } + +[ByRefEvent] +public readonly record struct CanMoveUpdatedEvent(bool CanMove); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 00573f18316..3578739f5c5 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -305,17 +305,19 @@ private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bo return; // TODO: Should move this into HandleMobMovement itself. - if (moverComp.CanMove && - TryComp(entity, out var relayMover)) + if (TryComp(entity, out var relayMover)) { DebugTools.Assert(relayMover.RelayEntity != entity); DebugTools.AssertNotNull(relayMover.RelayEntity); - if (MoverQuery.TryGetComponent(entity, out var mover)) - SetMoveInput((entity, mover), MoveButtons.None); + if (moverComp.CanMove) + { + if (MoverQuery.TryGetComponent(entity, out var mover)) + SetMoveInput((entity, mover), MoveButtons.None); - HandleDirChange(relayMover.RelayEntity, dir, subTick, state); - return; + HandleDirChange(relayMover.RelayEntity, dir, subTick, state); + return; + } } // For stuff like "Moving out of locker" or the likes diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs b/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs index 81569553772..9bb4e401d7c 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs @@ -1,4 +1,5 @@ using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; namespace Content.Shared.Movement.Systems; @@ -10,6 +11,7 @@ private void InitializeRelay() SubscribeLocalEvent(OnTargetRelayShutdown); SubscribeLocalEvent(OnAfterRelayTargetState); SubscribeLocalEvent(OnAfterRelayState); + SubscribeLocalEvent(OnRelayCanMoveUpdated); } private void OnAfterRelayTargetState(Entity entity, ref AfterAutoHandleStateEvent args) @@ -22,6 +24,11 @@ private void OnAfterRelayState(Entity entity, ref Afte Physics.UpdateIsPredicted(entity.Owner); } + private void OnRelayCanMoveUpdated(Entity ent, ref CanMoveUpdatedEvent args) + { + Log.Debug("Updated CanMove!"); + } + /// /// Sets the relay entity and marks the component as dirty. This only exists because people have previously /// forgotten to Dirty(), so fuck you, you have to use this method now. From f76b7c2845aa1e50953b3bd00a51cff75c9ce0f7 Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Sat, 13 Sep 2025 01:12:26 -0400 Subject: [PATCH 12/22] Add relay movement cancelling behavior, reformat functions --- .../Movement/Events/UpdateCanMoveEvent.cs | 3 +++ .../Movement/Systems/SharedMoverController.Input.cs | 13 +++++-------- .../Movement/Systems/SharedMoverController.Relay.cs | 6 +++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs b/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs index bca625e35b1..257bb0c3932 100644 --- a/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs +++ b/Content.Shared/Movement/Events/UpdateCanMoveEvent.cs @@ -16,5 +16,8 @@ public UpdateCanMoveEvent(EntityUid uid) public EntityUid Uid { get; } } +/// +/// Event raised directed on an entity when their value of is updated. +/// [ByRefEvent] public readonly record struct CanMoveUpdatedEvent(bool CanMove); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 3578739f5c5..2202423ca40 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -305,19 +305,16 @@ private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bo return; // TODO: Should move this into HandleMobMovement itself. - if (TryComp(entity, out var relayMover)) + if (moverComp.CanMove && TryComp(entity, out var relayMover)) { DebugTools.Assert(relayMover.RelayEntity != entity); DebugTools.AssertNotNull(relayMover.RelayEntity); - if (moverComp.CanMove) - { - if (MoverQuery.TryGetComponent(entity, out var mover)) - SetMoveInput((entity, mover), MoveButtons.None); + if (MoverQuery.TryGetComponent(entity, out var mover)) + SetMoveInput((entity, mover), MoveButtons.None); - HandleDirChange(relayMover.RelayEntity, dir, subTick, state); - return; - } + HandleDirChange(relayMover.RelayEntity, dir, subTick, state); + return; } // For stuff like "Moving out of locker" or the likes diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs b/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs index 9bb4e401d7c..0f70556266d 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs @@ -26,7 +26,11 @@ private void OnAfterRelayState(Entity entity, ref Afte private void OnRelayCanMoveUpdated(Entity ent, ref CanMoveUpdatedEvent args) { - Log.Debug("Updated CanMove!"); + if (args.CanMove) + return; + + if (MoverQuery.TryComp(ent.Comp.RelayEntity, out var inputMoverComponent)) + SetMoveInput((ent.Comp.RelayEntity, inputMoverComponent), MoveButtons.None); } /// From cbbad6ad8bef942973a31a3b86eb0fc9fe02f0fb Mon Sep 17 00:00:00 2001 From: EmoGarbage404 Date: Sat, 13 Sep 2025 10:59:34 -0400 Subject: [PATCH 13/22] Change TryComp to Resolve in SharedMoverController.Input.cs --- .../Movement/Systems/SharedMoverController.Input.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 2202423ca40..2110e94d01d 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -296,18 +296,17 @@ private void OnInputParentChange(Entity entity, ref EntPare Dirty(entity.Owner, entity.Comp); } - private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bool state) + private void HandleDirChange(Entity entity, Direction dir, ushort subTick, bool state) { // Relayed movement just uses the same keybinds given we're moving the relayed entity // the same as us. - - if (!MoverQuery.TryGetComponent(entity, out var moverComp)) + if (!MoverQuery.Resolve(entity, ref entity.Comp)) return; // TODO: Should move this into HandleMobMovement itself. - if (moverComp.CanMove && TryComp(entity, out var relayMover)) + if (entity.Comp.CanMove && RelayQuery.TryComp(entity, out var relayMover)) { - DebugTools.Assert(relayMover.RelayEntity != entity); + DebugTools.Assert(relayMover.RelayEntity != entity.Owner); DebugTools.AssertNotNull(relayMover.RelayEntity); if (MoverQuery.TryGetComponent(entity, out var mover)) @@ -328,7 +327,7 @@ private void HandleDirChange(EntityUid entity, Direction dir, ushort subTick, bo RaiseLocalEvent(xform.ParentUid, ref relayMoveEvent); } - SetVelocityDirection((entity, moverComp), dir, subTick, state); + SetVelocityDirection((entity, entity.Comp), dir, subTick, state); } private void OnInputInit(Entity entity, ref ComponentInit args) From 041e2c0af5afd79f0411de26b2c7bcb2efa5cd9a Mon Sep 17 00:00:00 2001 From: jb1361 Date: Tue, 23 Sep 2025 23:38:56 -0400 Subject: [PATCH 14/22] Fix errors --- Content.Client/Buckle/BuckleSystem.cs | 57 ++- Content.Client/Eye/EyeLerpingSystem.cs | 2 +- Content.Client/NPC/NPCSteeringSystem.cs | 1 + .../Physics/Controllers/MoverController.cs | 61 ++-- .../_Goobstation/Vehicles/VehicleSystem.cs | 60 ---- .../_Goobstation/Vehicles/VehicleSystem.cs | 7 - Content.Shared/CCVar/CCVars.Movement.cs | 67 ++++ Content.Shared/CCVar/CCVars.Physics.cs | 9 + .../CCVar/CVarAccess/CVarControl.cs | 38 ++ Content.Shared/Damage/DamageSpecifier.cs | 16 + .../Mech/EntitySystems/SharedMechSystem.cs | 2 +- .../Components/MobCollisionComponent.cs | 60 ++++ .../MovementSpeedModifierComponent.cs | 17 + .../Movement/Events/MoveInputEvent.cs | 7 +- .../Systems/SharedMobCollisionSystem.cs | 339 ++++++++++++++++++ .../Systems/SharedMoverController.CVars.cs | 43 --- .../Systems/SharedMoverController.Relay.cs | 20 +- .../Vehicles/SharedVehicleComponent.cs | 73 ---- .../Vehicles/SharedVehicleSystem.cs | 236 ------------ .../en-US/_Goobstation/vehicle/vehicle.ftl | 1 - .../Entities/Structures/Furniture/chairs.yml | 2 +- .../Entities/Objects/Vehicles/keys.yml | 67 ---- .../Entities/Objects/Vehicles/vehicles.yml | 236 ------------ 23 files changed, 607 insertions(+), 814 deletions(-) delete mode 100644 Content.Client/_Goobstation/Vehicles/VehicleSystem.cs delete mode 100644 Content.Server/_Goobstation/Vehicles/VehicleSystem.cs create mode 100644 Content.Shared/CCVar/CCVars.Movement.cs create mode 100644 Content.Shared/CCVar/CVarAccess/CVarControl.cs create mode 100644 Content.Shared/Movement/Components/MobCollisionComponent.cs create mode 100644 Content.Shared/Movement/Systems/SharedMobCollisionSystem.cs delete mode 100644 Content.Shared/Movement/Systems/SharedMoverController.CVars.cs delete mode 100644 Content.Shared/_Goobstation/Vehicles/SharedVehicleComponent.cs delete mode 100644 Content.Shared/_Goobstation/Vehicles/SharedVehicleSystem.cs delete mode 100644 Resources/Locale/en-US/_Goobstation/vehicle/vehicle.ftl delete mode 100644 Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/keys.yml delete mode 100644 Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/vehicles.yml diff --git a/Content.Client/Buckle/BuckleSystem.cs b/Content.Client/Buckle/BuckleSystem.cs index 82895a6fdce..5e62a605e10 100644 --- a/Content.Client/Buckle/BuckleSystem.cs +++ b/Content.Client/Buckle/BuckleSystem.cs @@ -1,15 +1,19 @@ using Content.Client.Rotation; using Content.Shared.Buckle; using Content.Shared.Buckle.Components; +using Content.Shared.Movement.Systems; using Content.Shared.Rotation; using Robust.Client.GameObjects; -using Robust.Shared.GameStates; +using Robust.Client.Graphics; namespace Content.Client.Buckle; internal sealed class BuckleSystem : SharedBuckleSystem { [Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!; + [Dependency] private readonly IEyeManager _eye = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; public override void Initialize() { @@ -19,36 +23,14 @@ public override void Initialize() SubscribeLocalEvent(OnStrapMoveEvent); SubscribeLocalEvent(OnBuckledEvent); SubscribeLocalEvent(OnUnbuckledEvent); + SubscribeLocalEvent(OnMobCollide); } - /// - /// Is the strap entity already rotated north? Lower the draw depth of the buckled entity. - /// - private void OnBuckledEvent(Entity ent, ref BuckledEvent args) + private void OnMobCollide(Entity ent, ref AttemptMobCollideEvent args) { - if (!TryComp(args.Strap, out var strapSprite) || - !TryComp(ent.Owner, out var buckledSprite)) - return; - - if (Transform(args.Strap.Owner).LocalRotation.GetCardinalDir() == Direction.North) + if (ent.Comp.Buckled) { - ent.Comp.OriginalDrawDepth ??= buckledSprite.DrawDepth; - buckledSprite.DrawDepth = strapSprite.DrawDepth - 1; - } - } - - /// - /// Was the draw depth of the buckled entity lowered? Reset it upon unbuckling. - /// - private void OnUnbuckledEvent(Entity ent, ref UnbuckledEvent args) - { - if (!TryComp(ent.Owner, out var buckledSprite)) - return; - - if (ent.Comp.OriginalDrawDepth.HasValue) - { - buckledSprite.DrawDepth = ent.Comp.OriginalDrawDepth.Value; - ent.Comp.OriginalDrawDepth = null; + args.Cancelled = true; } } @@ -76,7 +58,9 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE if (!TryComp(uid, out var strapSprite)) return; - var isNorth = Transform(uid).LocalRotation.GetCardinalDir() == Direction.North; + var angle = _xformSystem.GetWorldRotation(uid) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough + + var isNorth = angle.GetCardinalDir() == Direction.North; foreach (var buckledEntity in component.BuckledEntities) { if (!TryComp(buckledEntity, out var buckle)) @@ -87,12 +71,13 @@ private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveE if (isNorth) { + // This will only assign if empty, it won't get overwritten by new depth on multiple calls, which do happen easily buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth; - buckledSprite.DrawDepth = strapSprite.DrawDepth - 1; + _sprite.SetDrawDepth((buckledEntity, buckledSprite), strapSprite.DrawDepth - 1); } else if (buckle.OriginalDrawDepth.HasValue) { - buckledSprite.DrawDepth = buckle.OriginalDrawDepth.Value; + _sprite.SetDrawDepth((buckledEntity, buckledSprite), buckle.OriginalDrawDepth.Value); buckle.OriginalDrawDepth = null; } } @@ -142,11 +127,17 @@ private void OnUnbuckledEvent(Entity ent, ref UnbuckledEvent ar private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args) { - if (!TryComp(uid, out var rotVisuals) - || !Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) - || !buckled || args.Sprite == null) + if (!TryComp(uid, out var rotVisuals)) return; + if (!Appearance.TryGetData(uid, BuckleVisuals.Buckled, out var buckled, args.Component) || + !buckled || + args.Sprite == null) + { + _rotationVisualizerSystem.SetHorizontalAngle((uid, rotVisuals), rotVisuals.DefaultRotation); + return; + } + // Animate strapping yourself to something at a given angle // TODO: Dump this when buckle is better _rotationVisualizerSystem.AnimateSpriteRotation(uid, args.Sprite, rotVisuals.HorizontalRotation, 0.125f); diff --git a/Content.Client/Eye/EyeLerpingSystem.cs b/Content.Client/Eye/EyeLerpingSystem.cs index ac32299dca7..c0a7c016966 100644 --- a/Content.Client/Eye/EyeLerpingSystem.cs +++ b/Content.Client/Eye/EyeLerpingSystem.cs @@ -34,7 +34,7 @@ public override void Initialize() SubscribeLocalEvent(OnDetached); UpdatesAfter.Add(typeof(TransformSystem)); - UpdatesAfter.Add(typeof(PhysicsSystem)); + UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem)); UpdatesBefore.Add(typeof(SharedEyeSystem)); UpdatesOutsidePrediction = true; } diff --git a/Content.Client/NPC/NPCSteeringSystem.cs b/Content.Client/NPC/NPCSteeringSystem.cs index eda3d74cf71..9ca3ec1a7ea 100644 --- a/Content.Client/NPC/NPCSteeringSystem.cs +++ b/Content.Client/NPC/NPCSteeringSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Client.Physics.Controllers; +using Content.Client.PhysicsSystem.Controllers; using Content.Shared.Movement.Components; using Content.Shared.NPC; using Content.Shared.NPC.Events; diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index ba31c5f6ffd..0f95a817c99 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -1,23 +1,23 @@ +using Content.Shared.Alert; using Content.Shared.CCVar; +using Content.Shared.Friction; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; -using Robust.Client.GameObjects; using Robust.Client.Physics; using Robust.Client.Player; using Robust.Shared.Configuration; -using Robust.Shared.Physics.Components; using Robust.Shared.Player; using Robust.Shared.Timing; -namespace Content.Client.Physics.Controllers; +namespace Content.Client.PhysicsSystem.Controllers; public sealed class MoverController : SharedMoverController { - [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; public override void Initialize() { @@ -30,8 +30,6 @@ public override void Initialize() SubscribeLocalEvent(OnUpdatePredicted); SubscribeLocalEvent(OnUpdateRelayTargetPredicted); SubscribeLocalEvent(OnUpdatePullablePredicted); - - Subs.CVar(_config, CCVars.DefaultWalk, _ => RaiseNetworkEvent(new UpdateInputCVarsMessage())); } private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args) @@ -63,16 +61,16 @@ private void OnUpdatePullablePredicted(Entity entity, ref Upd private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.RelayEntity); + PhysicsSystem.UpdateIsPredicted(entity.Owner); + PhysicsSystem.UpdateIsPredicted(entity.Comp.RelayEntity); if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); } private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.RelayEntity); + PhysicsSystem.UpdateIsPredicted(entity.Owner); + PhysicsSystem.UpdateIsPredicted(entity.Comp.RelayEntity); if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); } @@ -102,43 +100,28 @@ public override void UpdateBeforeSolve(bool prediction, float frameTime) private void HandleClientsideMovement(EntityUid player, float frameTime) { - if (!MoverQuery.TryGetComponent(player, out var mover) || - !XformQuery.TryGetComponent(player, out var xform)) - { - return; - } - - var physicsUid = player; - PhysicsComponent? body; - var xformMover = xform; - - if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) - { - if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || - !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) - { - return; - } - - physicsUid = xform.ParentUid; - } - else if (!PhysicsQuery.TryGetComponent(player, out body)) + if (!MoverQuery.TryGetComponent(player, out var mover)) { return; } // Server-side should just be handled on its own so we'll just do this shizznit - HandleMobMovement( - player, - mover, - physicsUid, - body, - xformMover, - frameTime); + HandleMobMovement((player, mover), frameTime); } protected override bool CanSound() { return _timing is { IsFirstTimePredicted: true, InSimulation: true }; } + + public override void SetSprinting(Entity entity, ushort subTick, bool walking) + { + // Logger.Info($"[{_gameTiming.CurTick}/{subTick}] Sprint: {enabled}"); + base.SetSprinting(entity, subTick, walking); + + if (walking && _cfg.GetCVar(CCVars.ToggleWalk)) + _alerts.ShowAlert(entity.Owner, WalkingAlert, showCooldown: false, autoRemove: false); + else + _alerts.ClearAlert(entity.Owner, WalkingAlert); + } } diff --git a/Content.Client/_Goobstation/Vehicles/VehicleSystem.cs b/Content.Client/_Goobstation/Vehicles/VehicleSystem.cs deleted file mode 100644 index 937531fd2d1..00000000000 --- a/Content.Client/_Goobstation/Vehicles/VehicleSystem.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Content.Shared.Vehicles; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; - -namespace Content.Client.Vehicles; - -public sealed class VehicleSystem : SharedVehicleSystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly IEyeManager _eye = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnAppearanceChange); - SubscribeLocalEvent(OnMove); - } - - private void OnAppearanceChange(EntityUid uid, VehicleComponent comp, ref AppearanceChangeEvent args) - { - if (args.Sprite == null || !_appearance.TryGetData(uid, VehicleState.Animated, out bool animated) || !TryComp(uid, out var spriteComp)) - return; - - SpritePos(uid, comp); - spriteComp.LayerSetAutoAnimated(0, animated); - } - - private void OnMove(EntityUid uid, VehicleComponent component, ref MoveEvent args) - { - SpritePos(uid, component); - } - - private void SpritePos(EntityUid uid, VehicleComponent comp) - { - if (!TryComp(uid, out var spriteComp)) - return; - - if (!_appearance.TryGetData(uid, VehicleState.DrawOver, out bool depth)) - return; - - spriteComp.DrawDepth = (int) Shared.DrawDepth.DrawDepth.Objects; - - if (comp.RenderOver == VehicleRenderOver.None) - return; - - var eye = _eye.CurrentEye; - Direction vehicleDir = (Transform(uid).LocalRotation + eye.Rotation).GetCardinalDir(); - - VehicleRenderOver renderOver = (VehicleRenderOver) (1 << (int) vehicleDir); - - if ((comp.RenderOver & renderOver) == renderOver) - { - spriteComp.DrawDepth = (int) Shared.DrawDepth.DrawDepth.OverMobs; - } - else - { - spriteComp.DrawDepth = (int) Shared.DrawDepth.DrawDepth.Objects; - } - } -} diff --git a/Content.Server/_Goobstation/Vehicles/VehicleSystem.cs b/Content.Server/_Goobstation/Vehicles/VehicleSystem.cs deleted file mode 100644 index 7eaab46432a..00000000000 --- a/Content.Server/_Goobstation/Vehicles/VehicleSystem.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Content.Shared.Vehicles; - -namespace Content.Server.Vehicles; - -public sealed class VehicleSystem : SharedVehicleSystem -{ -} diff --git a/Content.Shared/CCVar/CCVars.Movement.cs b/Content.Shared/CCVar/CCVars.Movement.cs new file mode 100644 index 00000000000..96ceada0994 --- /dev/null +++ b/Content.Shared/CCVar/CCVars.Movement.cs @@ -0,0 +1,67 @@ +using Content.Shared.Administration; +using Content.Shared.CCVar.CVarAccess; +using Robust.Shared.Configuration; + +namespace Content.Shared.CCVar; + +public sealed partial class CCVars +{ + /// + /// Is mob pushing enabled. + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementMobPushing = + CVarDef.Create("movement.mob_pushing", false, CVar.SERVER | CVar.REPLICATED); + + /// + /// Can we push mobs not moving. + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementPushingStatic = + CVarDef.Create("movement.pushing_static", true, CVar.SERVER | CVar.REPLICATED); + + /// + /// Dot product for the pushed entity's velocity to a target entity's velocity before it gets moved. + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementPushingVelocityProduct = + CVarDef.Create("movement.pushing_velocity_product", -9999f, CVar.SERVER | CVar.REPLICATED); + + /// + /// Cap for how much an entity can be pushed per second. + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementPushingCap = + CVarDef.Create("movement.pushing_cap", 25f, CVar.SERVER | CVar.REPLICATED); + + /// + /// Minimum pushing impulse per tick. If the value is below this it rounds to 0. + /// This is an optimisation to avoid pushing small values that won't actually move the mobs. + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementMinimumPush = + CVarDef.Create("movement.minimum_push", 0f, CVar.SERVER | CVar.REPLICATED); + + // Really this just exists because hot reloading is cooked on rider. + /// + /// Penetration depth cap for considering mob collisions. + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementPenetrationCap = + CVarDef.Create("movement.penetration_cap", 0.5f, CVar.SERVER | CVar.REPLICATED); + + /// + /// Based on the mass difference multiplies the push amount by this proportionally. + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementPushMassCap = + CVarDef.Create("movement.push_mass_cap", 1.75f, CVar.SERVER | CVar.REPLICATED); + + /// + /// Is crawling enabled + /// + [CVarControl(AdminFlags.VarEdit)] + public static readonly CVarDef MovementCrawling = + CVarDef.Create("movement.crawling", true, CVar.SERVER | CVar.REPLICATED); + +} diff --git a/Content.Shared/CCVar/CCVars.Physics.cs b/Content.Shared/CCVar/CCVars.Physics.cs index 379676b5df9..f67a4203bca 100644 --- a/Content.Shared/CCVar/CCVars.Physics.cs +++ b/Content.Shared/CCVar/CCVars.Physics.cs @@ -10,6 +10,15 @@ public sealed partial class CCVars public static readonly CVarDef RelativeMovement = CVarDef.Create("physics.relative_movement", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + public static readonly CVarDef MinFriction = + CVarDef.Create("physics.min_friction", 0.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + + public static readonly CVarDef AirFriction = + CVarDef.Create("physics.air_friction", 0.2f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + + public static readonly CVarDef OffgridFriction = + CVarDef.Create("physics.offgrid_friction", 0.05f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + public static readonly CVarDef TileFrictionModifier = CVarDef.Create("physics.tile_friction", 40.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); diff --git a/Content.Shared/CCVar/CVarAccess/CVarControl.cs b/Content.Shared/CCVar/CVarAccess/CVarControl.cs new file mode 100644 index 00000000000..799738cf3d8 --- /dev/null +++ b/Content.Shared/CCVar/CVarAccess/CVarControl.cs @@ -0,0 +1,38 @@ +using Content.Shared.Administration; +using Robust.Shared.Reflection; + +namespace Content.Shared.CCVar.CVarAccess; + +/// +/// Manages what admin flags can change the cvar value. With optional mins and maxes. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +[Reflect(discoverable: true)] +public sealed class CVarControl : Attribute +{ + public AdminFlags AdminFlags { get; } + public object? Min { get; } + public object? Max { get; } + + public CVarControl(AdminFlags adminFlags, object? min = null, object? max = null, string? helpText = null) + { + AdminFlags = adminFlags; + Min = min; + Max = max; + + // Not actually sure if its a good idea to throw exceptions in attributes. + + if (min != null && max != null) + { + if (min.GetType() != max.GetType()) + { + throw new ArgumentException("Min and max must be of the same type."); + } + } + + if (min == null && max != null || min != null && max == null) + { + throw new ArgumentException("Min and max must both be null or both be set."); + } + } +} diff --git a/Content.Shared/Damage/DamageSpecifier.cs b/Content.Shared/Damage/DamageSpecifier.cs index 7f505b807f7..a67e7714d5c 100644 --- a/Content.Shared/Damage/DamageSpecifier.cs +++ b/Content.Shared/Damage/DamageSpecifier.cs @@ -183,6 +183,22 @@ public static DamageSpecifier ApplyModifierSets(DamageSpecifier damageSpec, IEnu return newDamage; } + /// + /// Returns a new DamageSpecifier that only contains the entries with positive value. + /// + public static DamageSpecifier GetPositive(DamageSpecifier damageSpec) + { + DamageSpecifier newDamage = new(); + + foreach (var (key, value) in damageSpec.DamageDict) + { + if (value > 0) + newDamage.DamageDict[key] = value; + } + + return newDamage; + } + /// /// Remove any damage entries with zero damage. /// diff --git a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs index 391009a7b18..8e0840df5a7 100644 --- a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs +++ b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs @@ -36,7 +36,7 @@ namespace Content.Shared.Mech.EntitySystems; /// /// Handles all of the interactions, UI handling, and items shennanigans for /// -public abstract class SharedMechSystem : EntitySystem +public abstract partial class SharedMechSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _net = default!; diff --git a/Content.Shared/Movement/Components/MobCollisionComponent.cs b/Content.Shared/Movement/Components/MobCollisionComponent.cs new file mode 100644 index 00000000000..437cdfd409d --- /dev/null +++ b/Content.Shared/Movement/Components/MobCollisionComponent.cs @@ -0,0 +1,60 @@ +using System.Numerics; +using Content.Shared.Movement.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Movement.Components; + +/// +/// Handles mobs pushing against each other. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)] +public sealed partial class MobCollisionComponent : Component +{ + // If you want to tweak the feel of the pushing use SpeedModifier and Strength. + // Strength goes both ways and affects how much the other mob is pushed by so controls static pushing a lot. + // Speed mod affects your own mob primarily. + + /// + /// Is this mob currently colliding? Used for SpeedModifier. + /// + [DataField, AutoNetworkedField] + public bool Colliding; + + // TODO: I hate this but also I couldn't quite figure out a way to avoid having to dirty it every tick. + // The issue is it's a time target that changes constantly so we can't just use a timespan. + // However that doesn't mean it should be modified every tick if we're still colliding. + + /// + /// Buffer time for to keep applying after the entities are no longer colliding. + /// Without this you will get jittering unless you are very specific with your values. + /// + [DataField, AutoNetworkedField] + public float BufferAccumulator = SharedMobCollisionSystem.BufferTime; + + /// + /// The speed modifier for mobs currently pushing. + /// By setting this low you can ensure you don't have to set the push-strength too high if you can push static entities. + /// + [DataField, AutoNetworkedField] + public float SpeedModifier = 1f; + + [DataField, AutoNetworkedField] + public float MinimumSpeedModifier = 0.35f; + + /// + /// Strength of the pushback for entities. This is combined between the 2 entities being pushed. + /// + [DataField, AutoNetworkedField] + public float Strength = 50f; + + // Yes I know, I will deal with it if I ever refactor collision layers due to misuse. + // If anything it probably needs some assurance on mobcollisionsystem for it. + /// + /// Fixture to listen to for mob collisions. + /// + [DataField, AutoNetworkedField] + public string FixtureId = "flammable"; + + [DataField, AutoNetworkedField] + public Vector2 Direction; +} diff --git a/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs b/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs index 88067f54aa4..1bb1f8a841c 100644 --- a/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs +++ b/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs @@ -119,5 +119,22 @@ private float _baseSprintSpeedVV public float CurrentWalkSpeed => WalkSpeedModifier * BaseWalkSpeed; [ViewVariables] public float CurrentSprintSpeed => SprintSpeedModifier * BaseSprintSpeed; + + /// + /// The body's base friction modifier that is applied in *all* circumstances. + /// + [AutoNetworkedField, DataField] + public float BaseFriction = DefaultFriction; + + /// + /// These base values should be defined in yaml and rarely if ever modified directly. + /// + [AutoNetworkedField, DataField] + public float BaseWeightlessFriction = DefaultWeightlessFriction; + + [ViewVariables] + public float WeightlessWalkSpeed => WeightlessModifier * BaseWalkSpeed; + [ViewVariables] + public float WeightlessSprintSpeed => WeightlessModifier * BaseSprintSpeed; } } diff --git a/Content.Shared/Movement/Events/MoveInputEvent.cs b/Content.Shared/Movement/Events/MoveInputEvent.cs index 08cb6a4b9f1..9c49da722cb 100644 --- a/Content.Shared/Movement/Events/MoveInputEvent.cs +++ b/Content.Shared/Movement/Events/MoveInputEvent.cs @@ -11,17 +11,12 @@ public readonly struct MoveInputEvent { public readonly Entity Entity; public readonly MoveButtons OldMovement; - public readonly Direction Dir; // Shitmed Change - public readonly bool State; // Shitmed Change public bool HasDirectionalMovement => (Entity.Comp.HeldMoveButtons & MoveButtons.AnyDirection) != MoveButtons.None; - public MoveInputEvent(Entity entity, MoveButtons oldMovement, Direction dir, bool state) // Shitmed Change + public MoveInputEvent(Entity entity, MoveButtons oldMovement) { Entity = entity; OldMovement = oldMovement; - // Shitmed Change - Dir = dir; - State = state; } } diff --git a/Content.Shared/Movement/Systems/SharedMobCollisionSystem.cs b/Content.Shared/Movement/Systems/SharedMobCollisionSystem.cs new file mode 100644 index 00000000000..fab9552271b --- /dev/null +++ b/Content.Shared/Movement/Systems/SharedMobCollisionSystem.cs @@ -0,0 +1,339 @@ +using System.Numerics; +using Content.Shared.CCVar; +using Content.Shared.Movement.Components; +using Robust.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Random; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Movement.Systems; + +public abstract class SharedMobCollisionSystem : EntitySystem +{ + [Dependency] protected readonly IConfigurationManager CfgManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MovementSpeedModifierSystem _moveMod = default!; + [Dependency] protected readonly SharedPhysicsSystem Physics = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + + protected EntityQuery MobQuery; + protected EntityQuery PhysicsQuery; + + /// + /// + /// + private float _pushingCap; + + /// + /// + /// + private float _pushingDotProduct; + + /// + /// + /// + private float _minimumPushSquared = 0.01f; + + private float _penCap; + + /// + /// Time after we stop colliding with another mob before adjusting the movespeedmodifier. + /// This is required so if we stop colliding for a frame we don't fully reset and get jerky movement. + /// + public const float BufferTime = 0.2f; + + private float _massDiffCap; + + public override void Initialize() + { + base.Initialize(); + + UpdatePushCap(); + Subs.CVar(CfgManager, CVars.NetTickrate, _ => UpdatePushCap()); + Subs.CVar(CfgManager, CCVars.MovementMinimumPush, val => _minimumPushSquared = val * val, true); + Subs.CVar(CfgManager, CCVars.MovementPenetrationCap, val => _penCap = val, true); + Subs.CVar(CfgManager, CCVars.MovementPushingCap, _ => UpdatePushCap()); + Subs.CVar(CfgManager, CCVars.MovementPushingVelocityProduct, + value => + { + _pushingDotProduct = value; + }, true); + Subs.CVar(CfgManager, CCVars.MovementPushMassCap, val => _massDiffCap = val, true); + + MobQuery = GetEntityQuery(); + PhysicsQuery = GetEntityQuery(); + SubscribeAllEvent(OnCollision); + SubscribeLocalEvent(OnMoveModifier); + + UpdatesBefore.Add(typeof(SharedPhysicsSystem)); + } + + private void UpdatePushCap() + { + _pushingCap = (1f / CfgManager.GetCVar(CVars.NetTickrate)) * CfgManager.GetCVar(CCVars.MovementPushingCap); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var comp)) + { + if (!comp.Colliding) + continue; + + comp.BufferAccumulator -= frameTime; + DirtyField(uid, comp, nameof(MobCollisionComponent.BufferAccumulator)); + var direction = comp.Direction; + + if (comp.BufferAccumulator <= 0f) + { + SetColliding((uid, comp), false, 1f); + } + // Apply the mob collision; if it's too low ignore it (e.g. if mob friction would overcome it). + // This is so we don't spam velocity changes every tick. It's not that expensive for physics but + // avoids the networking side. + else if (direction != Vector2.Zero && PhysicsQuery.TryComp(uid, out var physics)) + { + DebugTools.Assert(direction.LengthSquared() >= _minimumPushSquared); + + if (direction.Length() > _pushingCap) + { + direction = direction.Normalized() * _pushingCap; + } + + Physics.ApplyLinearImpulse(uid, direction * physics.Mass, body: physics); + comp.Direction = Vector2.Zero; + DirtyField(uid, comp, nameof(MobCollisionComponent.Direction)); + } + } + } + + private void OnMoveModifier(Entity ent, ref RefreshMovementSpeedModifiersEvent args) + { + if (!ent.Comp.Colliding) + return; + + args.ModifySpeed(ent.Comp.SpeedModifier); + } + + private void SetColliding(Entity entity, bool value, float speedMod) + { + if (value) + { + entity.Comp.BufferAccumulator = BufferTime; + DirtyField(entity.Owner, entity.Comp, nameof(MobCollisionComponent.BufferAccumulator)); + } + else + { + DebugTools.Assert(speedMod.Equals(1f)); + } + + if (entity.Comp.Colliding != value) + { + entity.Comp.Colliding = value; + DirtyField(entity.Owner, entity.Comp, nameof(MobCollisionComponent.Colliding)); + } + + if (!entity.Comp.SpeedModifier.Equals(speedMod)) + { + entity.Comp.SpeedModifier = speedMod; + _moveMod.RefreshMovementSpeedModifiers(entity.Owner); + DirtyField(entity.Owner, entity.Comp, nameof(MobCollisionComponent.SpeedModifier)); + } + } + + private void OnCollision(MobCollisionMessage msg, EntitySessionEventArgs args) + { + var player = args.SenderSession.AttachedEntity; + + if (!MobQuery.TryComp(player, out var comp)) + return; + + var xform = Transform(player.Value); + + // If not parented directly to a grid then fail it. + if (xform.ParentUid != xform.GridUid && xform.ParentUid != xform.MapUid) + return; + + var direction = msg.Direction; + + MoveMob((player.Value, comp, xform), direction, msg.SpeedModifier); + } + + protected void MoveMob(Entity entity, Vector2 direction, float speedMod) + { + // Length too short to do anything. + var pushing = true; + + if (direction.LengthSquared() < _minimumPushSquared) + { + pushing = false; + direction = Vector2.Zero; + speedMod = 1f; + } + else if (float.IsNaN(direction.X) || float.IsNaN(direction.Y)) + { + direction = Vector2.Zero; + } + + speedMod = Math.Clamp(speedMod, 0f, 1f); + + SetColliding(entity, pushing, speedMod); + + if (direction == entity.Comp1.Direction) + return; + + entity.Comp1.Direction = direction; + DirtyField(entity.Owner, entity.Comp1, nameof(MobCollisionComponent.Direction)); + } + + protected bool HandleCollisions(Entity entity, float frameTime) + { + var physics = entity.Comp2; + + if (physics.ContactCount == 0) + return false; + + var ourVelocity = entity.Comp2.LinearVelocity; + + if (ourVelocity == Vector2.Zero && !CfgManager.GetCVar(CCVars.MovementPushingStatic)) + return false; + + var xform = Transform(entity.Owner); + + if (xform.ParentUid != xform.GridUid && xform.ParentUid != xform.MapUid) + return false; + + var ev = new AttemptMobCollideEvent(); + + RaiseLocalEvent(entity.Owner, ref ev); + + if (ev.Cancelled) + return false; + + var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform); + var ourTransform = new Transform(worldPos, worldRot); + var contacts = Physics.GetContacts(entity.Owner); + var direction = Vector2.Zero; + var contactCount = 0; + var ourMass = physics.FixturesMass; + var speedMod = 1f; + + while (contacts.MoveNext(out var contact)) + { + if (!contact.IsTouching) + continue; + + var ourFixture = contact.OurFixture(entity.Owner); + + if (ourFixture.Id != entity.Comp1.FixtureId) + continue; + + var other = contact.OtherEnt(entity.Owner); + + if (!MobQuery.TryComp(other, out var otherComp) || !PhysicsQuery.TryComp(other, out var otherPhysics)) + continue; + + var velocityProduct = Vector2.Dot(ourVelocity, otherPhysics.LinearVelocity); + + // If we're moving opposite directions for example then ignore (based on cvar). + if (velocityProduct < _pushingDotProduct) + { + continue; + } + + var targetEv = new AttemptMobTargetCollideEvent(); + RaiseLocalEvent(other, ref targetEv); + + if (targetEv.Cancelled) + continue; + + // TODO: More robust overlap detection. + var otherTransform = Physics.GetPhysicsTransform(other); + var diff = ourTransform.Position - otherTransform.Position; + + if (diff == Vector2.Zero) + { + diff = _random.NextVector2(0.01f); + } + + // 0.7 for 0.35 + 0.35 for mob bounds (see TODO above). + // Clamp so we don't get a heap of penetration depth and suddenly lurch other mobs. + // This is also so we don't have to trigger the speed-cap above. + // Maybe we just do speedcap and dump this? Though it's less configurable and the cap is just there for cheaters. + var penDepth = Math.Clamp(0.7f - diff.Length(), 0f, _penCap); + + // Sum the strengths so we get pushes back the same amount (impulse-wise, ignoring prediction). + var mobMovement = penDepth * diff.Normalized() * (entity.Comp1.Strength + otherComp.Strength); + + // Big mob push smaller mob, needs fine-tuning and potentially another co-efficient. + if (_massDiffCap > 0f) + { + var modifier = Math.Clamp( + otherPhysics.FixturesMass / ourMass, + 1f / _massDiffCap, + _massDiffCap); + + mobMovement *= modifier; + + var speedReduction = 1f - entity.Comp1.MinimumSpeedModifier; + speedReduction /= _penCap / penDepth; + var speedModifier = Math.Clamp( + 1f - speedReduction * modifier, + entity.Comp1.MinimumSpeedModifier, 1f); + + speedMod = MathF.Min(speedModifier, 1f); + } + + // Need the push strength proportional to penetration depth. + direction += mobMovement; + contactCount++; + } + + if (direction == Vector2.Zero) + { + return contactCount > 0; + } + + direction *= frameTime; + RaiseCollisionEvent(entity.Owner, direction, speedMod); + return true; + } + + protected abstract void RaiseCollisionEvent(EntityUid uid, Vector2 direction, float speedmodifier); + + /// + /// Raised from client -> server indicating mob push direction OR server -> server for NPC mob pushes. + /// + [Serializable, NetSerializable] + protected sealed class MobCollisionMessage : EntityEventArgs + { + public Vector2 Direction; + public float SpeedModifier; + } +} + +/// +/// Raised on the entity itself when attempting to handle mob collisions. +/// +[ByRefEvent] +public record struct AttemptMobCollideEvent +{ + public bool Cancelled; +} + +/// +/// Raised on the other entity when attempting mob collisions. +/// +[ByRefEvent] +public record struct AttemptMobTargetCollideEvent +{ + public bool Cancelled; +} diff --git a/Content.Shared/Movement/Systems/SharedMoverController.CVars.cs b/Content.Shared/Movement/Systems/SharedMoverController.CVars.cs deleted file mode 100644 index 4228b778475..00000000000 --- a/Content.Shared/Movement/Systems/SharedMoverController.CVars.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Shared.CCVar; -using Content.Shared.Mind.Components; -using Content.Shared.Movement.Components; -using Content.Shared.Movement.Events; -using Robust.Shared.Configuration; - -namespace Content.Shared.Movement.Systems; - -public abstract partial class SharedMoverController -{ - [Dependency] private readonly INetConfigurationManager _netConfig = default!; - - private void InitializeCVars() - { - SubscribeLocalEvent(OnMindAdded); - SubscribeLocalEvent(OnMindRemoved); - SubscribeNetworkEvent(OnUpdateCVars); - } - - private void OnMindAdded(Entity ent, ref MindAddedMessage args) - { - if (args.Mind.Comp.Session?.Channel is not { } channel) - return; - - ent.Comp.DefaultSprinting = _netConfig.GetClientCVar(channel, CCVars.DefaultWalk); - WalkingAlert(ent); - } - - private void OnMindRemoved(Entity ent, ref MindRemovedMessage args) - { - // If it's an ai-controlled mob, we probably want them sprinting by default. - ent.Comp.DefaultSprinting = true; - } - - private void OnUpdateCVars(UpdateInputCVarsMessage msg, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity is not { } uid || !TryComp(uid, out var mover)) - return; - - mover.DefaultSprinting = _netConfig.GetClientCVar(args.SenderSession.Channel, CCVars.DefaultWalk); - WalkingAlert((uid, mover)); - } -} diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs b/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs index 0f70556266d..55a1f138cb5 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Relay.cs @@ -16,12 +16,12 @@ private void InitializeRelay() private void OnAfterRelayTargetState(Entity entity, ref AfterAutoHandleStateEvent args) { - Physics.UpdateIsPredicted(entity.Owner); + PhysicsSystem.UpdateIsPredicted(entity.Owner); } private void OnAfterRelayState(Entity entity, ref AfterAutoHandleStateEvent args) { - Physics.UpdateIsPredicted(entity.Owner); + PhysicsSystem.UpdateIsPredicted(entity.Owner); } private void OnRelayCanMoveUpdated(Entity ent, ref CanMoveUpdatedEvent args) @@ -53,7 +53,7 @@ public void SetRelay(EntityUid uid, EntityUid relayEntity) { oldTarget.Source = EntityUid.Invalid; RemComp(component.RelayEntity, oldTarget); - Physics.UpdateIsPredicted(component.RelayEntity); + PhysicsSystem.UpdateIsPredicted(component.RelayEntity); } var targetComp = EnsureComp(relayEntity); @@ -61,11 +61,11 @@ public void SetRelay(EntityUid uid, EntityUid relayEntity) { oldRelay.RelayEntity = EntityUid.Invalid; RemComp(targetComp.Source, oldRelay); - Physics.UpdateIsPredicted(targetComp.Source); + PhysicsSystem.UpdateIsPredicted(targetComp.Source); } - Physics.UpdateIsPredicted(uid); - Physics.UpdateIsPredicted(relayEntity); + PhysicsSystem.UpdateIsPredicted(uid); + PhysicsSystem.UpdateIsPredicted(relayEntity); component.RelayEntity = relayEntity; targetComp.Source = uid; Dirty(uid, component); @@ -74,8 +74,8 @@ public void SetRelay(EntityUid uid, EntityUid relayEntity) private void OnRelayShutdown(Entity entity, ref ComponentShutdown args) { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.RelayEntity); + PhysicsSystem.UpdateIsPredicted(entity.Owner); + PhysicsSystem.UpdateIsPredicted(entity.Comp.RelayEntity); if (TryComp(entity.Comp.RelayEntity, out var inputMover)) SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); @@ -89,8 +89,8 @@ private void OnRelayShutdown(Entity entity, ref Compon private void OnTargetRelayShutdown(Entity entity, ref ComponentShutdown args) { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.Source); + PhysicsSystem.UpdateIsPredicted(entity.Owner); + PhysicsSystem.UpdateIsPredicted(entity.Comp.Source); if (Timing.ApplyingState) return; diff --git a/Content.Shared/_Goobstation/Vehicles/SharedVehicleComponent.cs b/Content.Shared/_Goobstation/Vehicles/SharedVehicleComponent.cs deleted file mode 100644 index 4e1984c2674..00000000000 --- a/Content.Shared/_Goobstation/Vehicles/SharedVehicleComponent.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Vehicles; - -[RegisterComponent, NetworkedComponent] -public sealed partial class VehicleComponent : Component -{ - [ViewVariables] - public EntityUid? Driver; - - [ViewVariables] - public EntityUid? HornAction; - - [ViewVariables] - public EntityUid? SirenAction; - - public bool SirenEnabled = false; - - public EntityUid? SirenStream; - - /// - /// If non-zero how many virtual items to spawn on the driver - /// unbuckles them if they dont have enough - /// - [DataField] - public int RequiredHands = 1; - - /// - /// Will the vehicle move when a driver buckles - /// - [DataField] - public bool EngineRunning = false; - - /// - /// What sound to play when the driver presses the horn action (plays once) - /// - [DataField] - public SoundSpecifier? HornSound; - - /// - /// What sound to play when the driver presses the siren action (loops) - /// - [DataField] - public SoundSpecifier? SirenSound; - - /// - /// If they should be rendered ontop of the vehicle if true or behind - /// - [DataField] - public VehicleRenderOver RenderOver = VehicleRenderOver.None; -} -[Serializable, NetSerializable] -public enum VehicleState : byte -{ - Animated, - DrawOver -} - -[Serializable, NetSerializable, Flags] -public enum VehicleRenderOver -{ - None = 0, - North = 1, - NorthEast = 2, - East = 4, - SouthEast = 8, - South = 16, - SouthWest = 32, - West = 64, - NorthWest = 128, -} diff --git a/Content.Shared/_Goobstation/Vehicles/SharedVehicleSystem.cs b/Content.Shared/_Goobstation/Vehicles/SharedVehicleSystem.cs deleted file mode 100644 index 2d627bb06c0..00000000000 --- a/Content.Shared/_Goobstation/Vehicles/SharedVehicleSystem.cs +++ /dev/null @@ -1,236 +0,0 @@ -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; -using Content.Shared.Actions; -using Content.Shared.Audio; -using Content.Shared.Buckle; -using Content.Shared.Buckle.Components; -using Content.Shared.Hands; -using Content.Shared.Inventory.VirtualItem; -using Content.Shared.Movement.Components; -using Content.Shared.Movement.Systems; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Vehicles; - -public abstract partial class SharedVehicleSystem : EntitySystem -{ - [Dependency] private readonly AccessReaderSystem _access = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedBuckleSystem _buckle = default!; - [Dependency] private readonly SharedMoverController _mover = default!; - [Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!; - - public static readonly EntProtoId HornActionId = "ActionHorn"; - public static readonly EntProtoId SirenActionId = "ActionSiren"; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnRemove); - SubscribeLocalEvent(OnStrapAttempt); - SubscribeLocalEvent(OnStrapped); - SubscribeLocalEvent(OnUnstrapped); - SubscribeLocalEvent(OnDropped); - - SubscribeLocalEvent(OnInsert); - SubscribeLocalEvent(OnEject); - - SubscribeLocalEvent(OnHorn); - SubscribeLocalEvent(OnSiren); - } - - private void OnInit(EntityUid uid, VehicleComponent component, ComponentInit args) - { - _appearance.SetData(uid, VehicleState.Animated, component.EngineRunning); - _appearance.SetData(uid, VehicleState.DrawOver, false); - } - - private void OnRemove(EntityUid uid, VehicleComponent component, ComponentRemove args) - { - if (component.Driver == null) - return; - - _buckle.TryUnbuckle(component.Driver.Value, component.Driver.Value); - Dismount(component.Driver.Value, uid); - _appearance.SetData(uid, VehicleState.DrawOver, false); - } - - private void OnInsert(EntityUid uid, VehicleComponent component, ref EntInsertedIntoContainerMessage args) - { - if (HasComp(args.Entity)) - return; - - component.EngineRunning = true; - _appearance.SetData(uid, VehicleState.Animated, true); - - _ambientSound.SetAmbience(uid, true); - - if (component.Driver == null) - return; - - Mount(component.Driver.Value, uid); - } - - private void OnEject(EntityUid uid, VehicleComponent component, ref EntRemovedFromContainerMessage args) - { - component.EngineRunning = false; - _appearance.SetData(uid, VehicleState.Animated, false); - - _ambientSound.SetAmbience(uid, false); - - if (component.Driver == null) - return; - - Dismount(component.Driver.Value, uid); - } - - private void OnHorn(EntityUid uid, VehicleComponent component, InstantActionEvent args) - { - if (args.Handled == true || component.Driver != args.Performer || component.HornSound == null) - return; - - _audio.PlayPvs(component.HornSound, uid); - args.Handled = true; - } - - private void OnSiren(EntityUid uid, VehicleComponent component, InstantActionEvent args) - { - if (args.Handled == true || component.Driver != args.Performer || component.SirenSound == null) - return; - - if (component.SirenEnabled) - { - component.SirenStream = _audio.Stop(component.SirenStream); - } - else - { - component.SirenStream = _audio.PlayPvs(component.SirenSound, uid)?.Entity; - } - - component.SirenEnabled = !component.SirenEnabled; - args.Handled = true; - } - - - private void OnStrapAttempt(Entity ent, ref StrapAttemptEvent args) - { - var driver = args.Buckle.Owner; // i dont want to re write this shit 100 fucking times - - if (ent.Comp.Driver != null) - { - args.Cancelled = true; - return; - } - - if (ent.Comp.RequiredHands != 0) - { - for (int hands = 0; hands < ent.Comp.RequiredHands; hands++) - { - if (!_virtualItem.TrySpawnVirtualItemInHand(ent.Owner, driver, false)) - { - args.Cancelled = true; - _virtualItem.DeleteInHandsMatching(driver, ent.Owner); - return; - } - } - } - - AddHorns(driver, ent); - } - - private void OnStrapped(Entity ent, ref StrappedEvent args) - { - var driver = args.Buckle.Owner; - - if (!TryComp(driver, out MobMoverComponent? mover) || ent.Comp.Driver != null) - return; - - ent.Comp.Driver = driver; - _appearance.SetData(ent.Owner, VehicleState.DrawOver, true); - - if (!ent.Comp.EngineRunning) - return; - - Mount(driver, ent.Owner); - } - - private void OnUnstrapped(Entity ent, ref UnstrappedEvent args) - { - if (ent.Comp.Driver != args.Buckle.Owner) - return; - - Dismount(args.Buckle.Owner, ent); - _appearance.SetData(ent.Owner, VehicleState.DrawOver, false); - } - - private void OnDropped(EntityUid uid, VehicleComponent comp, VirtualItemDeletedEvent args) - { - if (comp.Driver != args.User) - return; - - _buckle.TryUnbuckle(args.User, args.User); - - Dismount(args.User, uid); - _appearance.SetData(uid, VehicleState.DrawOver, false); - } - - private void AddHorns(EntityUid driver, EntityUid vehicle) - { - if (!TryComp(vehicle, out var vehicleComp)) - return; - - if (vehicleComp.HornSound != null) - _actions.AddAction(driver, ref vehicleComp.HornAction, HornActionId, vehicle); - - if (vehicleComp.SirenSound != null) - _actions.AddAction(driver, ref vehicleComp.SirenAction, SirenActionId, vehicle); - } - - private void Mount(EntityUid driver, EntityUid vehicle) - { - if (TryComp(vehicle, out var accessComp)) - { - var accessSources = _access.FindPotentialAccessItems(driver); - var access = _access.FindAccessTags(driver, accessSources); - - foreach (var tag in access) - { - accessComp.Tags.Add(tag); - } - } - - _mover.SetRelay(driver, vehicle); - } - - private void Dismount(EntityUid driver, EntityUid vehicle) - { - if (!TryComp(vehicle, out var vehicleComp) || vehicleComp.Driver != driver) - return; - - RemComp(driver); - - vehicleComp.Driver = null; - - if (vehicleComp.HornAction != null) - _actions.RemoveAction(driver, vehicleComp.HornAction); - - if (vehicleComp.SirenAction != null) - _actions.RemoveAction(driver, vehicleComp.SirenAction); - - _virtualItem.DeleteInHandsMatching(driver, vehicle); - - if (TryComp(vehicle, out var accessComp)) - accessComp.Tags.Clear(); - } -} - -public sealed partial class HornActionEvent : InstantActionEvent; - -public sealed partial class SirenActionEvent : InstantActionEvent; diff --git a/Resources/Locale/en-US/_Goobstation/vehicle/vehicle.ftl b/Resources/Locale/en-US/_Goobstation/vehicle/vehicle.ftl deleted file mode 100644 index 36c2fbfb699..00000000000 --- a/Resources/Locale/en-US/_Goobstation/vehicle/vehicle.ftl +++ /dev/null @@ -1 +0,0 @@ -vehicle-slot-component-slot-name-keys = Keys diff --git a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml index 0b6df61bb41..86ba80f8658 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml @@ -89,7 +89,7 @@ name: chair id: RollingOfficeChairBase description: Scoot from desk to desk because standing is for interns. - parent: BaseVehicle + parent: BaseVehicleStrap abstract: true components: - type: Anchorable diff --git a/Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/keys.yml b/Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/keys.yml deleted file mode 100644 index 9af4a6c062d..00000000000 --- a/Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/keys.yml +++ /dev/null @@ -1,67 +0,0 @@ -- type: entity - parent: BaseItem - id: BaseKey - abstract: true - categories: [ HideSpawnMenu ] - components: - - type: Item - size: Tiny - - type: Tag - tags: - - VehicleKey - -- type: entity - parent: BaseKey - id: VehicleKeySecway - name: secway keys - description: The keys to the future. - components: - - type: Sprite - sprite: Objects/Vehicles/secway.rsi - state: keys - - type: Tag - tags: - - VehicleKey - - SecwayKeys - -- type: entity - parent: BaseKey - id: VehicleKeySyndicateSegway - name: syndicate segway keys - description: Patterned after the iconic EMAG design. - components: - - type: Sprite - sprite: Objects/Vehicles/syndicatesegway.rsi - state: keys - - type: Tag - tags: - - VehicleKey - - SyndicateSegwayKeys - -- type: entity - parent: BaseKey - id: VehicleKeyATV - name: ATV keys - description: Think this looks like just one key? ATV keys means "actually two vehicle keys." - components: - - type: Sprite - sprite: Objects/Vehicles/atv.rsi - state: keys - - type: Tag - tags: - - VehicleKey - - ATVKeys - -- type: entity - parent: BaseKey - id: VehicleKeyJanicart - name: janicart keys - description: Interesting design. - components: - - type: Sprite - sprite: Objects/Vehicles/janicart.rsi - state: keys - - type: Tag - tags: - - VehicleKey - - JanicartKeys diff --git a/Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/vehicles.yml b/Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/vehicles.yml deleted file mode 100644 index b0232354255..00000000000 --- a/Resources/Prototypes/_Goobstation/Entities/Objects/Vehicles/vehicles.yml +++ /dev/null @@ -1,236 +0,0 @@ -- type: entity - id: BaseVehicle - abstract: true - save: false - categories: [ HideSpawnMenu ] - components: - - type: Vehicle - renderOver: East, SouthEast, South, SouthWest, West - - type: Strap - position: Stand - - type: Appearance - - type: AmbientSound - sound: "/Audio/Effects/Vehicle/vehicleengineidle.ogg" - range: 10 - volume: -10 - enabled: false - - type: InputMover - - type: Clickable - - type: InteractionOutline - - type: Access - - type: Physics - bodyType: Dynamic - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeCircle - radius: 0.45 - density: 100 - mask: - - MobMask - layer: - - MobLayer - hard: true - - type: Pullable - - type: Damageable - damageContainer: Inorganic - damageModifierSet: Metallic - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 100 - behaviors: - - !type:DoActsBehavior - acts: [ "Destruction" ] - - trigger: - !type:DamageTrigger - damage: 50 - behaviors: - - !type:DoActsBehavior - acts: ["Destruction"] - - !type:PlaySoundBehavior - sound: - collection: MetalBreak - - type: MovementSpeedModifier - acceleration: 8 - friction: 5 # wheels dont stop instantly - baseSprintSpeed: 6 - baseWalkSpeed: 4.5 # default walking speed - - type: ItemSlots - slots: - key_slot: - name: vehicle-slot-component-slot-name-keys - whitelist: - requireAll: true - tags: - - VehicleKey - insertSound: - path: /Audio/Effects/Vehicle/vehiclestartup.ogg - params: - volume: -3 - - type: StaticPrice - price: 2500 - - type: Tag - tags: - - DoorBumpOpener - - type: RequireProjectileTarget - -- type: entity - id: VehicleSecway - parent: BaseVehicle - name: secway - description: The future of transportation. Popularized by St. James, the patron saint of security officers and internet forum moderators. - components: - - type: Sprite - sprite: Objects/Vehicles/secway.rsi - state: vehicle - noRot: true - - type: Vehicle - renderOver: North, NorthEast, NorthWest - hornSound: - collection: DeskBell - params: - variation: 0.125 - sirenSound: - collection: PoliceSiren - params: - variation: 0.125 - - type: ItemSlots - slots: - key_slot: - name: vehicle-slot-component-slot-name-keys - whitelist: - requireAll: true - tags: - - VehicleKey - - SecwayKeys - insertSound: - path: /Audio/Effects/Vehicle/vehiclestartup.ogg - params: - volume: -3 - -- type: entity - id: VehicleSyndicateSegway - parent: VehicleSecway - name: syndicate segway - description: Be an enemy of the corporation, in style. - components: - - type: Sprite - sprite: Objects/Vehicles/syndicatesegway.rsi - state: vehicle - renderOver: North, NorthEast, NorthWest - noRot: true - - type: ItemSlots - slots: - key_slot: - name: vehicle-slot-component-slot-name-keys - insertSound: - path: /Audio/Effects/Vehicle/vehiclestartup.ogg - params: - volume: -3 - whitelist: - requireAll: true - tags: - - VehicleKey - - SyndicateSegwayKeys - -- type: entity - id: VehicleATV - parent: BaseVehicle - name: ATV - description: All-Tile Vehicle. - components: - - type: Sprite - sprite: Objects/Vehicles/atv.rsi - state: vehicle - noRot: true - - type: Vehicle - hornSound: - collection: BikeHorn - params: - variation: 0.125 - renderOver: North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest - - type: ItemSlots - slots: - key_slot: - name: vehicle-slot-component-slot-name-keys - whitelist: - requireAll: true - tags: - - VehicleKey - - ATVKeys - insertSound: - path: /Audio/Effects/Vehicle/vehiclestartup.ogg - params: - volume: -3 - -- type: entity - id: VehicleJanicart - parent: BaseVehicle - name: janicart - description: The janitor's trusty steed. - components: - - type: Sprite - sprite: Objects/Vehicles/janicart.rsi - state: vehicle - noRot: true - - type: Vehicle - renderOver: North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest - - type: ItemSlots - slots: - key_slot: - name: vehicle-slot-component-slot-name-keys - whitelist: - requireAll: true - tags: - - VehicleKey - - JanicartKeys - insertSound: - path: /Audio/Effects/Vehicle/vehiclestartup.ogg - params: - volume: -3 - - type: UnpoweredFlashlight - - type: PointLight - enabled: false - mask: /Textures/Effects/LightMasks/cone.png - autoRot: true - radius: 3.5 - softness: 2 - netsync: false - -- type: entity - id: VehicleWheelchair - parent: [ BaseVehicle, BaseFoldable, BaseItem ] - name: wheelchair - description: A chair with big wheels. It looks like you can move in these on your own. - components: - - type: Sprite - sprite: Objects/Vehicles/wheelchair.rsi - layers: - - state: vehicle - map: ["unfoldedLayer"] - - state: vehicle_folded - map: ["foldedLayer"] - visible: false - noRot: true - - type: Vehicle - requiredHands: 0 - engineRunning: true - renderOver: South, SouthEast, SouthWest - - type: MovementSpeedModifier - acceleration: 10 - friction: 10 - baseSprintSpeed: 3.5 - baseWalkSpeed: 2.5 - - type: StaticPrice - price: 75 - -- type: entity - parent: VehicleWheelchair - id: VehicleWheelchairFolded - suffix: folded - components: - - type: Foldable - folded: true From 4afa6640a0a60360ad2eb04ac3ae0a0645a56ee8 Mon Sep 17 00:00:00 2001 From: jb1361 Date: Fri, 26 Sep 2025 21:54:59 -0400 Subject: [PATCH 15/22] Add horn event --- Content.Shared/Vehicle/VehicleSystem.cs | 1 + .../_Encore/Actions/HornActionEvent.cs | 16 ++++++++++ .../_Encore/Vehicle/VehicleSystem.cs | 31 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 Content.Shared/_Encore/Actions/HornActionEvent.cs create mode 100644 Content.Shared/_Encore/Vehicle/VehicleSystem.cs diff --git a/Content.Shared/Vehicle/VehicleSystem.cs b/Content.Shared/Vehicle/VehicleSystem.cs index 5766ea57e63..8228c5f3425 100644 --- a/Content.Shared/Vehicle/VehicleSystem.cs +++ b/Content.Shared/Vehicle/VehicleSystem.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Access.Components; using Content.Shared.ActionBlocker; +using Content.Shared.Actions; using Content.Shared.Damage; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; diff --git a/Content.Shared/_Encore/Actions/HornActionEvent.cs b/Content.Shared/_Encore/Actions/HornActionEvent.cs new file mode 100644 index 00000000000..a4333b1ea37 --- /dev/null +++ b/Content.Shared/_Encore/Actions/HornActionEvent.cs @@ -0,0 +1,16 @@ +using Content.Shared._Encore.Vehicle; +using Content.Shared.Actions; +using Content.Shared.Vehicle.Components; + + +namespace Content.Shared._Encore.Actions; + + +// public sealed class HornActionEvent : InstantActionEvent, EntitySystem +// { +// public override void Initialize() +// { +// SubscribeLocalEvent(OnHorn); +// +// } +// } diff --git a/Content.Shared/_Encore/Vehicle/VehicleSystem.cs b/Content.Shared/_Encore/Vehicle/VehicleSystem.cs new file mode 100644 index 00000000000..0370ecd57b7 --- /dev/null +++ b/Content.Shared/_Encore/Vehicle/VehicleSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.Actions; +using Content.Shared.Vehicle; +using Content.Shared.Vehicle.Components; +using Robust.Shared.Audio.Systems; + + +namespace Content.Shared._Encore.Vehicle; + + +public sealed class VehicleSystem : EntitySystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + public override void Initialize() + { + SubscribeLocalEvent(OnHorn); + } + + private void OnHorn(EntityUid uid, VehicleComponent component, InstantActionEvent args) + { + if (args.Handled == true || component.Driver != args.Performer || component.HornSound == null) + return; + + _audio.PlayPvs(component.HornSound, uid); + args.Handled = true; + } + +} + +// public sealed partial class HornActionEvent : InstantActionEvent; +// +// public sealed partial class SirenActionEvent : InstantActionEvent; From 071e79b280e3c2ab3971b30dfd1e08af151cd62f Mon Sep 17 00:00:00 2001 From: jb1361 Date: Sat, 27 Sep 2025 01:01:49 -0400 Subject: [PATCH 16/22] Implement horns --- .../_Encore/Actions/HornActionEvent.cs | 14 +--- .../_Encore/Actions/SirenActionEvent.cs | 5 ++ .../Vehicle/Components/VehicleComponent.cs | 41 +++++++++++ .../_Encore/Vehicle/VehicleSystem.cs | 72 +++++++++++++++++-- .../Entities/Objects/Devices/vehicles.yml | 2 + .../Prototypes/_Encore/Actions/types.yml | 19 +++++ .../Prototypes/_Goobstation/Actions/types.yml | 20 ------ .../Entities/Markers/Spawners/vehicles.yml | 4 +- 8 files changed, 136 insertions(+), 41 deletions(-) create mode 100644 Content.Shared/_Encore/Actions/SirenActionEvent.cs create mode 100644 Content.Shared/_Encore/Vehicle/Components/VehicleComponent.cs create mode 100644 Resources/Prototypes/_Encore/Actions/types.yml diff --git a/Content.Shared/_Encore/Actions/HornActionEvent.cs b/Content.Shared/_Encore/Actions/HornActionEvent.cs index a4333b1ea37..c46c4dbe137 100644 --- a/Content.Shared/_Encore/Actions/HornActionEvent.cs +++ b/Content.Shared/_Encore/Actions/HornActionEvent.cs @@ -1,16 +1,4 @@ -using Content.Shared._Encore.Vehicle; using Content.Shared.Actions; -using Content.Shared.Vehicle.Components; - namespace Content.Shared._Encore.Actions; - - -// public sealed class HornActionEvent : InstantActionEvent, EntitySystem -// { -// public override void Initialize() -// { -// SubscribeLocalEvent(OnHorn); -// -// } -// } +public sealed partial class HornActionEvent : InstantActionEvent; diff --git a/Content.Shared/_Encore/Actions/SirenActionEvent.cs b/Content.Shared/_Encore/Actions/SirenActionEvent.cs new file mode 100644 index 00000000000..1d74c0f8181 --- /dev/null +++ b/Content.Shared/_Encore/Actions/SirenActionEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared._Encore.Actions; + +public sealed partial class SirenActionEvent : InstantActionEvent; diff --git a/Content.Shared/_Encore/Vehicle/Components/VehicleComponent.cs b/Content.Shared/_Encore/Vehicle/Components/VehicleComponent.cs new file mode 100644 index 00000000000..4f4c6851f17 --- /dev/null +++ b/Content.Shared/_Encore/Vehicle/Components/VehicleComponent.cs @@ -0,0 +1,41 @@ +using Robust.Shared.Audio; + + +namespace Content.Shared.Vehicle.Components; + +public sealed partial class VehicleComponent : Component +{ + [ViewVariables] + public EntityUid? HornAction; + + [ViewVariables] + public EntityUid? SirenAction; + + public bool SirenEnabled = false; + + public EntityUid? SirenStream; + + /// + /// What sound to play when the driver presses the horn action (plays once) + /// + [DataField] + public SoundSpecifier? HornSound; + + /// + /// What sound to play when the driver presses the siren action (loops) + /// + [DataField] + public SoundSpecifier? SirenSound; + + // /// + // /// What sound to play when the driver presses the horn action (plays once) + // /// + // [DataField] + // public SoundSpecifier? HornSound = new SoundPathSpecifier("/Audio/Animals/goose_honk.ogg"); + // + // /// + // /// What sound to play when the driver presses the siren action (loops) + // /// + // [DataField] + // public SoundSpecifier? SirenSound = new SoundPathSpecifier("/Audio/Animals/goose_honk.ogg"); +} diff --git a/Content.Shared/_Encore/Vehicle/VehicleSystem.cs b/Content.Shared/_Encore/Vehicle/VehicleSystem.cs index 0370ecd57b7..a65205a2ef9 100644 --- a/Content.Shared/_Encore/Vehicle/VehicleSystem.cs +++ b/Content.Shared/_Encore/Vehicle/VehicleSystem.cs @@ -1,7 +1,9 @@ +using Content.Shared._Encore.Actions; using Content.Shared.Actions; -using Content.Shared.Vehicle; +using Content.Shared.Buckle.Components; using Content.Shared.Vehicle.Components; using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; namespace Content.Shared._Encore.Vehicle; @@ -10,22 +12,80 @@ namespace Content.Shared._Encore.Vehicle; public sealed class VehicleSystem : EntitySystem { [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + + public static readonly EntProtoId HornActionId = "ActionHorn"; + public static readonly EntProtoId SirenActionId = "ActionSiren"; public override void Initialize() { SubscribeLocalEvent(OnHorn); + SubscribeLocalEvent(OnSiren); + SubscribeLocalEvent(OnEnteredVehicle); + SubscribeLocalEvent(OnExitVehicle); } private void OnHorn(EntityUid uid, VehicleComponent component, InstantActionEvent args) { - if (args.Handled == true || component.Driver != args.Performer || component.HornSound == null) + if (args.Handled || component.Operator != args.Performer || component.HornSound == null) return; _audio.PlayPvs(component.HornSound, uid); args.Handled = true; } -} + private void OnSiren(EntityUid uid, VehicleComponent component, InstantActionEvent args) + { + if (args.Handled || component.Operator != args.Performer || component.SirenSound == null) + return; + + if (component.SirenEnabled) + { + #pragma warning disable RA0002 + component.SirenStream = _audio.Stop(component.SirenStream); + } + else + { + component.SirenStream = _audio.PlayPvs(component.SirenSound, uid)?.Entity; + } + + component.SirenEnabled = !component.SirenEnabled; + args.Handled = true; + } + + private void OnEnteredVehicle(Entity ent, ref StrappedEvent args) + { + if (ent.Comp.Operator != null) + { + AddHorns(ent); + } + } -// public sealed partial class HornActionEvent : InstantActionEvent; -// -// public sealed partial class SirenActionEvent : InstantActionEvent; + private void OnExitVehicle(Entity ent, ref UnstrappedEvent args) + { + var vehicleComp = ent.Comp; + var driver = args.Buckle.Owner; + + if (vehicleComp.HornAction != null) + _actions.RemoveAction((EntityUid)driver, vehicleComp.HornAction); + + if (vehicleComp.SirenAction != null) + _actions.RemoveAction((EntityUid)driver, vehicleComp.SirenAction); + } + + private void AddHorns(Entity entity) + { + var vehicleComp = entity.Comp; + var driver = entity.Comp.Operator; + if (driver == null) + { + return; + } + + if (vehicleComp.HornSound != null) + _actions.AddAction((EntityUid)driver, ref vehicleComp.HornAction, HornActionId, entity); + + if (vehicleComp.SirenSound != null) + _actions.AddAction((EntityUid)driver, ref vehicleComp.SirenAction, SirenActionId, entity); + } + +} diff --git a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml index 09d802a2e02..c69d27dcffc 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml @@ -131,6 +131,8 @@ # VehicleJanicartDestroyed: # min: 1 # max: 1 + - type: Vehicle + hornSound: "/Audio/Animals/goose_honk.ogg" - type: Strap buckleOffset: "0.01,0.15" modifyBuckleDrawDepth: false diff --git a/Resources/Prototypes/_Encore/Actions/types.yml b/Resources/Prototypes/_Encore/Actions/types.yml new file mode 100644 index 00000000000..93c14246540 --- /dev/null +++ b/Resources/Prototypes/_Encore/Actions/types.yml @@ -0,0 +1,19 @@ +- type: entity + id: ActionHorn + name: Honk! + description: Beep the horn at whoever you will run over. + components: + - type: InstantAction + useDelay: 1 + icon: { sprite: Objects/Fun/bikehorn.rsi, state: icon } + event: !type:HornActionEvent + +- type: entity + id: ActionSiren + name: Siren + description: Alert your victim to your presence. + components: + - type: InstantAction + useDelay: 1 + icon: { sprite: Objects/Fun/bikehorn.rsi, state: icon } + event: !type:SirenActionEvent diff --git a/Resources/Prototypes/_Goobstation/Actions/types.yml b/Resources/Prototypes/_Goobstation/Actions/types.yml index 6c9c43cff67..93ae697adb5 100644 --- a/Resources/Prototypes/_Goobstation/Actions/types.yml +++ b/Resources/Prototypes/_Goobstation/Actions/types.yml @@ -13,23 +13,3 @@ sprite: /Prototypes/_Goobstation/Textures/Effects/bluespace_lifeline.rsi state: bluespace_lifeline event: !type:ActivateImplantEvent - -- type: entity - id: ActionHorn - name: Honk! - description: Beep the horn at whoever you will run over. - components: - - type: InstantAction - useDelay: 1 - icon: { sprite: Objects/Fun/bikehorn.rsi, state: icon } - event: !type:HornActionEvent - -- type: entity - id: ActionSiren - name: Siren - description: Alert your victim to your presence. - components: - - type: InstantAction - useDelay: 1 - icon: { sprite: Objects/Fun/bikehorn.rsi, state: icon } - event: !type:SirenActionEvent diff --git a/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml b/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml index e97d8efcd05..12604a8753b 100644 --- a/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml +++ b/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml @@ -49,7 +49,7 @@ layers: - state: green - sprite: Objects/Vehicles/wheelchair.rsi - state: vehicle + state: wheelchair - type: ConditionalSpawner prototypes: - VehicleWheelchair @@ -63,7 +63,7 @@ layers: - state: green - sprite: Objects/Vehicles/wheelchair.rsi - state: vehicle_folded + state: wheelchair_folded - type: ConditionalSpawner prototypes: - VehicleWheelchairFolded From 59e6da5bca98f65a563c57c194eeb21a7ff40e7e Mon Sep 17 00:00:00 2001 From: jb1361 Date: Sat, 27 Sep 2025 01:02:27 -0400 Subject: [PATCH 17/22] Remove goose horne from janicart --- Resources/Prototypes/Entities/Objects/Devices/vehicles.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml index c69d27dcffc..09d802a2e02 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/vehicles.yml @@ -131,8 +131,6 @@ # VehicleJanicartDestroyed: # min: 1 # max: 1 - - type: Vehicle - hornSound: "/Audio/Animals/goose_honk.ogg" - type: Strap buckleOffset: "0.01,0.15" modifyBuckleDrawDepth: false From 2e8bb7839eec849ddc5974e090049b77c0dd9a34 Mon Sep 17 00:00:00 2001 From: jb1361 Date: Fri, 3 Oct 2025 19:03:01 -0400 Subject: [PATCH 18/22] Remove workflow --- .github/workflows/conflict-labeler.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/conflict-labeler.yml diff --git a/.github/workflows/conflict-labeler.yml b/.github/workflows/conflict-labeler.yml deleted file mode 100644 index 1bba6770222..00000000000 --- a/.github/workflows/conflict-labeler.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Check Merge Conflicts - -on: - pull_request_target: - types: - - opened - - synchronize - - reopened - - ready_for_review - -jobs: - Label: - if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' && github.actor != 'DeltaV-Bot' && github.actor != 'SimpleStation14' ) - runs-on: ubuntu-latest - steps: - - name: Check for Merge Conflicts - uses: eps1lon/actions-label-merge-conflict@v3.0.0 - with: - dirtyLabel: "Status: Merge Conflict" - repoToken: "${{ secrets.GITHUB_TOKEN }}" - commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." From aefb0e0c317d9fa45d79a27dc065a91440ca74d8 Mon Sep 17 00:00:00 2001 From: jb1361 Date: Fri, 3 Oct 2025 19:23:09 -0400 Subject: [PATCH 19/22] Remove some invalid properties --- .../Entities/Objects/Specific/Mech/mechs.yml | 3 --- .../Entities/Structures/Furniture/chairs.yml | 3 --- .../Entities/Objects/Specific/Mech/mechs.yml | 12 ++---------- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml index f6254d3d278..0ffd0a7878c 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml @@ -265,9 +265,6 @@ brokenState: hamtr-broken maxEquipmentAmount: 2 airtight: true - equipmentWhitelist: - tags: - - SmallMech - type: Vehicle transferDamageModifier: coefficients: diff --git a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml index 86ba80f8658..8a1accd2a0a 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml @@ -102,9 +102,6 @@ position: Stand buckleOffset: "0,-0.05" - type: Vehicle - requiredHands: 0 - engineRunning: true - renderOver: South, SouthEast, SouthWest - type: MovementSpeedModifier acceleration: 10 friction: 3.5 diff --git a/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml index fb4c6b5deda..5f1ba2730da 100644 --- a/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml @@ -62,7 +62,6 @@ baseState: ripleymkii openState: ripleymkii-open brokenState: ripleymkii-broken - mechToPilotDamageMultiplier: 0.4 airtight: true pilotWhitelist: components: @@ -85,7 +84,7 @@ - DoorBumpOpener - FootstepSound - RipleyMkII - + - type: entity id: MechRipley2Battery parent: MechRipley2 @@ -120,7 +119,6 @@ baseState: clarke openState: clarke-open brokenState: clarke-broken - mechToPilotDamageMultiplier: 0.5 airtight: true pilotWhitelist: components: @@ -191,7 +189,6 @@ baseState: gygax openState: gygax-open brokenState: gygax-broken - mechToPilotDamageMultiplier: 0.3 airtight: true pilotWhitelist: components: @@ -204,7 +201,7 @@ Blunt: 25 Structural: 180 - type: CanMoveInAir - - type: MovementAlwaysTouching + - type: MovementAlwaysTouching - type: MovementSpeedModifier baseWalkSpeed: 2 baseSprintSpeed: 2.6 @@ -250,7 +247,6 @@ baseState: durand openState: durand-open brokenState: durand-broken - mechToPilotDamageMultiplier: 0.25 airtight: true maxIntegrity: 400 pilotWhitelist: @@ -317,7 +313,6 @@ baseState: marauder openState: marauder-open brokenState: marauder-broken - mechToPilotDamageMultiplier: 0.1 airtight: true maxIntegrity: 500 maxEquipmentAmount: 4 @@ -390,7 +385,6 @@ baseState: seraph openState: seraph-open brokenState: seraph-broken - mechToPilotDamageMultiplier: 0.05 airtight: true maxIntegrity: 550 maxEquipmentAmount: 5 @@ -466,7 +460,6 @@ baseState: darkgygax openState: darkgygax-open brokenState: darkgygax-broken - mechToPilotDamageMultiplier: 0.15 airtight: true maxIntegrity: 300 maxEquipmentAmount: 4 @@ -539,7 +532,6 @@ baseState: mauler openState: mauler-open brokenState: mauler-broken - mechToPilotDamageMultiplier: 0.1 airtight: true maxIntegrity: 500 maxEquipmentAmount: 5 From 4952eb6b2ce4e57b8bcfe1a87e5c0c8bf9cb4e1c Mon Sep 17 00:00:00 2001 From: jb1361 Date: Fri, 3 Oct 2025 19:29:46 -0400 Subject: [PATCH 20/22] Remove invalid properties --- .../Entities/Clothing/Shoes/magboots.yml | 1 - .../Entities/Objects/Tools/jetpacks.yml | 1 - .../Recipes/Lathes/Packs/medical.yml | 3 -- .../_Goobstation/Catalog/Fills/Crates/fun.yml | 10 ----- .../Entities/Markers/Spawners/vehicles.yml | 42 ------------------- .../Entities/Objects/Specific/Mech/mechs.yml | 24 ----------- .../_Goobstation/Recipes/Lathes/medical.yml | 9 ---- 7 files changed, 90 deletions(-) diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml index 49acb2a7328..c9b4963328b 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml @@ -207,7 +207,6 @@ moleUsage: 0.00085 - type: CanMoveInAir - type: InputMover - toParent: true - type: MovementSpeedModifier weightlessAcceleration: 1 weightlessFriction: 0.3 diff --git a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml index e0e68f80928..804c0cafade 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml @@ -26,7 +26,6 @@ description: It's a jetpack. It can hold 5 L of gas. components: - type: InputMover - toParent: true - type: MovementSpeedModifier weightlessAcceleration: 1 weightlessFriction: 0.3 diff --git a/Resources/Prototypes/Recipes/Lathes/Packs/medical.yml b/Resources/Prototypes/Recipes/Lathes/Packs/medical.yml index 35fddf9fd5d..d9709c637c5 100644 --- a/Resources/Prototypes/Recipes/Lathes/Packs/medical.yml +++ b/Resources/Prototypes/Recipes/Lathes/Packs/medical.yml @@ -66,9 +66,6 @@ - RollerBedSpawnFolded - CheapRollerBedSpawnFolded - EmergencyRollerBedSpawnFolded - # EE EDIT START - - VehicleWheelchairFolded - # EE EDIT END - type: latheRecipePack id: MedicalClothingStatic diff --git a/Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/fun.yml b/Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/fun.yml index 860b80580ae..077879cd5f2 100644 --- a/Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/fun.yml +++ b/Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/fun.yml @@ -3,20 +3,10 @@ parent: CrateLivestock name: ATV crate description: An Absolutely Taxable Vehicle to help cargo with hauling. - components: - - type: StorageFill - contents: - - id: VehicleATV - - id: VehicleKeyATV - type: entity id: CrateFunSyndicateSegway parent: CrateLivestock name: Syndicate segway crate description: A crate containing a two-wheeler that will help you escape from the security officers. Or not. - components: - - type: StorageFill - contents: - - id: VehicleSyndicateSegway - - id: VehicleKeySyndicateSegway diff --git a/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml b/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml index 12604a8753b..3d37db6bdbd 100644 --- a/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml +++ b/Resources/Prototypes/_Goobstation/Entities/Markers/Spawners/vehicles.yml @@ -1,31 +1,3 @@ -- 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 - -- type: entity - name: ATV Spawner - id: SpawnVehicleATV - parent: MarkerBase - components: - - type: Sprite - layers: - - state: green - - sprite: Objects/Vehicles/atv.rsi - state: keys - - type: ConditionalSpawner - prototypes: - - VehicleATV - - type: entity name: Janicart Spawner id: SpawnVehicleJanicart @@ -53,17 +25,3 @@ - type: ConditionalSpawner prototypes: - VehicleWheelchair - -- type: entity - name: Wheelchair [Folded] Spawner - id: SpawnVehicleWheelchairFolded - parent: MarkerBase - components: - - type: Sprite - layers: - - state: green - - sprite: Objects/Vehicles/wheelchair.rsi - state: wheelchair_folded - - type: ConditionalSpawner - prototypes: - - VehicleWheelchairFolded diff --git a/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml index 5f1ba2730da..d55e7962a5c 100644 --- a/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml @@ -63,9 +63,6 @@ openState: ripleymkii-open brokenState: ripleymkii-broken airtight: true - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 @@ -120,9 +117,6 @@ openState: clarke-open brokenState: clarke-broken airtight: true - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 @@ -190,9 +184,6 @@ openState: gygax-open brokenState: gygax-broken airtight: true - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 @@ -249,9 +240,6 @@ brokenState: durand-broken airtight: true maxIntegrity: 400 - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 @@ -316,9 +304,6 @@ airtight: true maxIntegrity: 500 maxEquipmentAmount: 4 - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 @@ -388,9 +373,6 @@ airtight: true maxIntegrity: 550 maxEquipmentAmount: 5 - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 @@ -463,9 +445,6 @@ airtight: true maxIntegrity: 300 maxEquipmentAmount: 4 - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 @@ -535,9 +514,6 @@ airtight: true maxIntegrity: 500 maxEquipmentAmount: 5 - pilotWhitelist: - components: - - HumanoidAppearance - type: MeleeWeapon hidden: true attackRate: 1 diff --git a/Resources/Prototypes/_Goobstation/Recipes/Lathes/medical.yml b/Resources/Prototypes/_Goobstation/Recipes/Lathes/medical.yml index 32806287eba..b76547ee7d7 100644 --- a/Resources/Prototypes/_Goobstation/Recipes/Lathes/medical.yml +++ b/Resources/Prototypes/_Goobstation/Recipes/Lathes/medical.yml @@ -1,12 +1,3 @@ -- type: latheRecipe - id: VehicleWheelchairFolded - result: VehicleWheelchairFolded - completetime: 1 - materials: - Plastic: 500 - Cloth: 500 - Steel: 1000 - - type: latheRecipe id: ParamedHypo result: ParamedHypo From 907d92616f4e8eb300cc4843e92b5cbd37b9d031 Mon Sep 17 00:00:00 2001 From: jb1361 Date: Fri, 3 Oct 2025 19:35:12 -0400 Subject: [PATCH 21/22] Remove properties --- .../Catalog/VendingMachines/Inventories/medical.yml | 1 - .../Entities/Objects/Specific/Mech/mechs.yml | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml index 7d1cc98c935..fe64df89c98 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml @@ -14,4 +14,3 @@ ClothingEyesHudMedical: 2 ClothingEyesEyepatchHudMedical: 2 ParamedHypo: 2 #Goobstation - ParamedHypo - VehicleWheelchairFolded: 3 # Goobstation - Vehicles diff --git a/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml index d55e7962a5c..5cf163226ee 100644 --- a/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Mech/mechs.yml @@ -4,9 +4,6 @@ categories: [ HideSpawnMenu ] components: - type: Mech - equipmentWhitelist: - tags: - - CombatMech - type: entity id: IndustrialMech @@ -14,9 +11,6 @@ categories: [ HideSpawnMenu ] components: - type: Mech - equipmentWhitelist: - tags: - - IndustrialMech - type: entity id: SpecialMech @@ -24,9 +18,6 @@ categories: [ HideSpawnMenu ] components: - type: Mech - equipmentWhitelist: - tags: - - SpecialMech - type: entity id: SmallMech @@ -34,9 +25,6 @@ categories: [ HideSpawnMenu ] components: - type: Mech - equipmentWhitelist: - tags: - - SmallMech # Ripley MK-II - type: entity From 816abe365ad8cb881a0cf6998ebcfb8063bcab3e Mon Sep 17 00:00:00 2001 From: jb1361 Date: Fri, 3 Oct 2025 19:36:40 -0400 Subject: [PATCH 22/22] Remove property --- Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml index 0ffd0a7878c..bbed0199f5b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml @@ -348,9 +348,6 @@ operatorWhitelist: tags: - VimPilot - equipmentWhitelist: - tags: - - CombatMech - type: MeleeWeapon hidden: true attackRate: 1.25