6
2025-12-18 02:55:17 +03:00

390 lines
15 KiB
C#

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 Content.Shared._NF.Vehicle.Components; // Frontier
using Content.Shared.ActionBlocker; // Frontier
using Content.Shared.Actions.Components; // Frontier
using Content.Shared.Light.Components; // Frontier
using Content.Shared.Light.EntitySystems; // Frontier
using Content.Shared.Movement.Pulling.Components; // Frontier
using Content.Shared.Movement.Pulling.Events; // Frontier
using Content.Shared.Popups; // Frontier
using Robust.Shared.Network; // Frontier
using Robust.Shared.Prototypes; // Frontier
using Robust.Shared.Timing; // Frontier
using Content.Shared.Weapons.Melee.Events; // Frontier
using Content.Shared.Emag.Systems; // Frontier
namespace Content.Shared._Goobstation.Vehicles; // Frontier: migrate under _Goobstation
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!;
[Dependency] private readonly IGameTiming _timing = default!; // Frontier
[Dependency] private readonly INetManager _net = default!; // Frontier
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; // Frontier
[Dependency] private readonly ActionContainerSystem _actionContainer = default!; // Frontier
[Dependency] private readonly EmagSystem _emag = default!; // Frontier
[Dependency] private readonly SharedPopupSystem _popup = default!; // Frontier
[Dependency] private readonly UnpoweredFlashlightSystem _flashlight = default!; // Frontier
public static readonly EntProtoId HornActionId = "ActionHorn";
public static readonly EntProtoId SirenActionId = "ActionSiren";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VehicleComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<VehicleComponent, MapInitEvent>(OnMapInit); // Frontier
SubscribeLocalEvent<VehicleComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<VehicleComponent, StrapAttemptEvent>(OnStrapAttempt);
SubscribeLocalEvent<VehicleComponent, StrappedEvent>(OnStrapped);
SubscribeLocalEvent<VehicleComponent, UnstrappedEvent>(OnUnstrapped);
SubscribeLocalEvent<VehicleComponent, VirtualItemDeletedEvent>(OnDropped);
SubscribeLocalEvent<VehicleComponent, MeleeHitEvent>(OnMeleeHit); // Frontier
SubscribeLocalEvent<VehicleComponent, GotEmaggedEvent>(OnGotEmagged, before: [typeof(UnpoweredFlashlightSystem)]); // Frontier
SubscribeLocalEvent<VehicleComponent, GotUnEmaggedEvent>(OnGotUnemagged, before: [typeof(UnpoweredFlashlightSystem)]); // Frontier
SubscribeLocalEvent<VehicleComponent, EntInsertedIntoContainerMessage>(OnInsert);
SubscribeLocalEvent<VehicleComponent, EntRemovedFromContainerMessage>(OnEject);
SubscribeLocalEvent<VehicleComponent, HornActionEvent>(OnHorn);
SubscribeLocalEvent<VehicleComponent, SirenActionEvent>(OnSiren);
SubscribeLocalEvent<VehicleRiderComponent, PullAttemptEvent>(OnRiderPull); // Frontier
}
private void OnInit(EntityUid uid, VehicleComponent component, ComponentInit args)
{
_appearance.SetData(uid, VehicleState.Animated, component.EngineRunning && component.Driver != null); // Frontier: add Driver != null
_appearance.SetData(uid, VehicleState.DrawOver, false);
}
// Frontier
private void OnMapInit(EntityUid uid, VehicleComponent component, MapInitEvent args)
{
bool actionsUpdated = false;
if (component.HornSound != null)
{
_actionContainer.EnsureAction(uid, ref component.HornAction, HornActionId);
actionsUpdated = true;
}
if (component.SirenSound != null)
{
_actionContainer.EnsureAction(uid, ref component.SirenAction, SirenActionId);
actionsUpdated = true;
}
if (actionsUpdated)
Dirty(uid, component);
}
// End Frontier
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<InstantActionComponent>(args.Entity))
return;
// Frontier: check key slot
if (args.Container.ID != component.KeySlotId)
return;
if (!_timing.IsFirstTimePredicted)
return;
// End Frontier: check key slot
component.EngineRunning = true;
_appearance.SetData(uid, VehicleState.Animated, component.Driver != null);
_ambientSound.SetAmbience(uid, true);
if (component.Driver == null)
return;
Mount(component.Driver.Value, uid);
}
private void OnEject(EntityUid uid, VehicleComponent component, ref EntRemovedFromContainerMessage args)
{
// Frontier: check key slot
if (args.Container.ID != component.KeySlotId)
return;
if (!_timing.IsFirstTimePredicted)
return;
// End Frontier: check key slot
component.EngineRunning = false;
_appearance.SetData(uid, VehicleState.Animated, false);
_ambientSound.SetAmbience(uid, false);
if (component.Driver == null)
return;
Dismount(component.Driver.Value, uid, removeDriver: false); // Frontier: add removeDriver: false - the driver is still around.
}
private void OnHorn(EntityUid uid, VehicleComponent component, InstantActionEvent args)
{
if (args.Handled == true || component.Driver != args.Performer || component.HornSound == null)
return;
_audio.PlayPredicted(component.HornSound, uid, args.Performer); // Frontier: PlayPvs<PlayPredicted, add args.Performer
args.Handled = true;
}
private void OnSiren(EntityUid uid, VehicleComponent component, InstantActionEvent args)
{
if (_net.IsClient) // Frontier: _audio.Stop hates client-side entities, only create this serverside
return; // Frontier
if (args.Handled == true || component.Driver != args.Performer || component.SirenSound == null)
return;
if (component.SirenStream != null) // Frontier: SirenEnabled<SirenStream != null
{
component.SirenStream = _audio.Stop(component.SirenStream);
}
else
{
var sirenParams = component.SirenSound.Params.WithLoop(true); // Frontier: force loop
component.SirenStream = _audio.PlayPvs(component.SirenSound, uid, audioParams: sirenParams)?.Entity; // Frontier: set params
}
// component.SirenEnabled = component.SirenStream != null; // Frontier: remove (unneeded state)
args.Handled = true;
}
private void OnStrapAttempt(Entity<VehicleComponent> 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;
}
// Frontier: no pulling when riding
if (TryComp<PullerComponent>(args.Buckle, out var puller) && puller.Pulling != null)
{
_popup.PopupPredicted(Loc.GetString("vehicle-cannot-pull", ("object", puller.Pulling), ("vehicle", ent)), ent, args.Buckle);
args.Cancelled = true;
return;
}
// End Frontier
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); // Frontier: delay until mounted
}
protected virtual void OnStrapped(Entity<VehicleComponent> ent, ref StrappedEvent args) // Frontier: private<protected virtual
{
var driver = args.Buckle.Owner;
if (!TryComp(driver, out MobMoverComponent? mover) || ent.Comp.Driver != null)
return;
ent.Comp.Driver = driver;
Dirty(ent); // Frontier
_appearance.SetData(ent.Owner, VehicleState.DrawOver, true);
_appearance.SetData(ent.Owner, VehicleState.Animated, ent.Comp.EngineRunning); // Frontier
var rider = EnsureComp<VehicleRiderComponent>(driver); // Frontier
Dirty(driver, rider); // Frontier
if (!ent.Comp.EngineRunning)
return;
Mount(driver, ent.Owner);
}
protected virtual void OnUnstrapped(Entity<VehicleComponent> ent, ref UnstrappedEvent args) // Frontier: private<protected virtual
{
if (ent.Comp.Driver != args.Buckle.Owner)
return;
Dismount(args.Buckle.Owner, ent);
_appearance.SetData(ent.Owner, VehicleState.DrawOver, false);
_appearance.SetData(ent.Owner, VehicleState.Animated, false); // Frontier
RemComp<VehicleRiderComponent>(args.Buckle.Owner); // Frontier
}
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);
_appearance.SetData(uid, VehicleState.Animated, false); // Frontier
RemComp<VehicleRiderComponent>(args.User); // Frontier
}
// Frontier: do not hit your own vehicle
private void OnMeleeHit(Entity<VehicleComponent> ent, ref MeleeHitEvent args)
{
if (args.User == ent.Comp.Driver) // Don't hit your own vehicle
args.Handled = true;
}
// End Frontier: do not hit your own vehicle
private void AddHorns(EntityUid driver, EntityUid vehicle)
{
if (!TryComp<VehicleComponent>(vehicle, out var vehicleComp))
return;
// Frontier: grant existing actions
List<EntityUid> grantedActions = new();
if (vehicleComp.HornAction != null)
grantedActions.Add(vehicleComp.HornAction.Value);
if (vehicleComp.SirenAction != null)
grantedActions.Add(vehicleComp.SirenAction.Value);
if (TryComp<UnpoweredFlashlightComponent>(vehicle, out var flashlight) && flashlight.ToggleActionEntity != null)
{
grantedActions.Add(flashlight.ToggleActionEntity.Value);
_flashlight.SetLight((vehicle, flashlight), flashlight.LightOn, quiet: true);
}
// Only try to grant actions if the vehicle actually has them.
if (grantedActions.Count > 0)
_actions.GrantActions(driver, grantedActions, vehicle);
// End Frontier
}
private void Mount(EntityUid driver, EntityUid vehicle)
{
if (TryComp<AccessComponent>(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);
AddHorns(driver, vehicle); // Frontier
}
private void Dismount(EntityUid driver, EntityUid vehicle, bool removeDriver = true) // Frontier: add removeDriver
{
if (!TryComp<VehicleComponent>(vehicle, out var vehicleComp) || vehicleComp.Driver != driver)
return;
RemComp<RelayInputMoverComponent>(driver);
_actionBlocker.UpdateCanMove(driver); // Frontier: bugfix, relay input mover only updates on shutdown, not remove
if (removeDriver) // Frontier
vehicleComp.Driver = null;
_actions.RemoveProvidedActions(driver, vehicle); // Frontier: don't remove actions, just provide/revoke them
if (removeDriver) // Frontier
_virtualItem.DeleteInHandsMatching(driver, vehicle);
if (TryComp<AccessComponent>(vehicle, out var accessComp))
accessComp.Tags.Clear();
}
// Frontier: prevent drivers from pulling things, emag handlers
private void OnRiderPull(Entity<VehicleRiderComponent> ent, ref PullAttemptEvent args)
{
if (args.PullerUid == ent.Owner)
args.Cancelled = true;
}
private void OnGotEmagged(Entity<VehicleComponent> ent, ref GotEmaggedEvent args)
{
if (args.Handled)
return;
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
return;
if (ent.Comp.RadarBlip)
{
ent.Comp.RadarBlip = false;
Dirty(ent);
HandleEmag(ent);
// Hack: assuming the only other emaggable component on the vehicle is a flashlight
args.Repeatable = HasComp<UnpoweredFlashlightComponent>(ent);
args.Handled = true;
}
}
private void OnGotUnemagged(Entity<VehicleComponent> ent, ref GotUnEmaggedEvent args)
{
if (args.Handled)
return;
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
return;
if (!ent.Comp.RadarBlip)
{
ent.Comp.RadarBlip = true;
Dirty(ent);
HandleUnemag(ent);
args.Handled = true;
}
}
protected abstract void HandleEmag(Entity<VehicleComponent> ent);
protected abstract void HandleUnemag(Entity<VehicleComponent> ent);
// End Frontier
}
public sealed partial class HornActionEvent : InstantActionEvent;
public sealed partial class SirenActionEvent : InstantActionEvent;