using System.Linq; using Content.Shared.Access.Components; using Content.Shared.ActionBlocker; using Content.Shared.Actions; using Content.Shared.Destructible; using Content.Shared.DoAfter; using Content.Shared.DragDrop; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Interaction.Components; 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.Weapons.Melee; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Random; using Content.Shared.Overlays; using Content.Shared.Whitelist; using Content.Shared.Mobs.Components; // Frontier using Content.Shared.NPC.Components; using Content.Shared._NF.Mech.Equipment.Events; // Frontier namespace Content.Shared.Mech.EntitySystems; /// /// Handles all of the interactions, UI handling, and items shennanigans for /// public abstract partial class SharedMechSystem : EntitySystem // Horizon Mech { [Dependency] private readonly IRobustRandom _random = default!; // Horizon Mech [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [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] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() { // SubscribeLocalEvent(OnToggleEquipmentAction);// Horizon Mech Commented SubscribeLocalEvent(OnEjectPilotEvent); SubscribeLocalEvent(RelayInteractionEvent); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnDestruction); SubscribeLocalEvent(OnGetAdditionalAccess); SubscribeLocalEvent(OnDragDrop); SubscribeLocalEvent(OnCanDragDrop); SubscribeLocalEvent(OnGetMeleeWeapon); SubscribeLocalEvent(OnCanAttackFromContainer); SubscribeLocalEvent(OnAttackAttempt); // Horizon Mech start SubscribeNetworkEvent(OnMechEquipSelected); SubscribeLocalEvent(ReceiveEquipmentUiMesssages); SubscribeLocalEvent(ReceiveEquipmentUiMesssages); InitializeADT(); // Horizon Mech end } // Horizon Mech commented // private void OnToggleEquipmentAction(EntityUid uid, MechComponent component, MechToggleEquipmentEvent args) // { // if (args.Handled) // return; // args.Handled = true; // CycleEquipment(uid); // } private void OnEjectPilotEvent(EntityUid uid, MechComponent component, MechEjectPilotEvent args) { if (args.Handled) return; args.Handled = true; TryEject(uid, component); } private void RelayInteractionEvent(EntityUid uid, MechComponent component, UserActivateInWorldEvent args) { var pilot = component.PilotSlot.ContainedEntity; if (pilot == null) return; // TODO why is this being blocked? if (!_timing.IsFirstTimePredicted) return; if (component.CurrentSelectedEquipment != null) { RaiseLocalEvent(component.CurrentSelectedEquipment.Value, args); } } private void OnStartup(EntityUid uid, MechComponent component, ComponentStartup args) { component.PilotSlot = _container.EnsureContainer(uid, component.PilotSlotId); component.EquipmentContainer = _container.EnsureContainer(uid, component.EquipmentContainerId); component.BatterySlot = _container.EnsureContainer(uid, component.BatterySlotId); UpdateAppearance(uid, component); } private void OnDestruction(EntityUid uid, MechComponent component, DestructionEventArgs args) { 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)) return; var rider = EnsureComp(pilot); // 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); if (_net.IsClient) return; _actions.AddAction(pilot, ref component.MechCycleActionEntity, component.MechCycleAction, mech); _actions.AddAction(pilot, ref component.MechUiActionEntity, component.MechUiAction, mech); _actions.AddAction(pilot, ref component.MechEjectActionEntity, component.MechEjectAction, mech); // Horizon Mech start _actions.AddAction(pilot, ref component.MechInhaleActionEntity, component.MechInhaleAction, mech); _actions.AddAction(pilot, ref component.MechTurnLightsActionEntity, component.MechTurnLightsAction, mech); var ev = new SetupMechUserEvent(pilot); RaiseLocalEvent(mech, ref ev); // Horizon Mech end RaiseEquipmentEquippedEvent((mech, component), pilot); // Frontier (note: must send pilot separately, not yet in their seat) } private void RemoveUser(EntityUid mech, EntityUid pilot) { if (!RemComp(pilot)) return; RemComp(pilot); RemComp(pilot); _actions.RemoveProvidedActions(pilot, mech); // Frontier if (TryComp(mech, out var mechComp) && mechComp.CurrentSelectedEquipment != null) _actions.RemoveProvidedActions(pilot, mechComp.CurrentSelectedEquipment.Value); // End Frontier } /// /// Destroys the mech, removing the user and ejecting all installed equipment. /// /// /// public virtual void BreakMech(EntityUid uid, MechComponent? component = null) { if (!Resolve(uid, ref component)) return; TryEject(uid, component); var equipment = new List(component.EquipmentContainer.ContainedEntities); // Frontier: optionally removable equipment if (component.CanRemoveEquipment) { foreach (var ent in equipment) { RemoveEquipment(uid, ent, component, forced: true); } } // End Frontier component.Broken = true; UpdateAppearance(uid, component); } /// /// Cycles through the currently selected equipment. /// /// /// public void CycleEquipment(EntityUid uid, MechComponent? component = null) { if (!Resolve(uid, ref component)) return; var allEquipment = component.EquipmentContainer.ContainedEntities.ToList(); var equipmentIndex = -1; if (component.CurrentSelectedEquipment != null) { bool StartIndex(EntityUid u) => u == component.CurrentSelectedEquipment; equipmentIndex = allEquipment.FindIndex(StartIndex); } // Frontier if (component.PilotSlot.ContainedEntity != null && component.CurrentSelectedEquipment != null) _actions.RemoveProvidedActions(component.PilotSlot.ContainedEntity.Value, component.CurrentSelectedEquipment.Value); // End Frontier // Horizon start var mechEv = new MechEquipmentSelectedEvent(component.CurrentSelectedEquipment); RaiseLocalEvent(uid, ref mechEv); if (component.CurrentSelectedEquipment.HasValue) { var equipEv = new MechEquipmentGotDeselectedEvent(uid); RaiseLocalEvent(component.CurrentSelectedEquipment.Value, ref equipEv); } // Horizon end equipmentIndex++; component.CurrentSelectedEquipment = equipmentIndex >= allEquipment.Count ? null : allEquipment[equipmentIndex]; // Horizon Mech start while (TryComp(component.CurrentSelectedEquipment, out var equipment) && equipment.CanBeUsed == false) { equipmentIndex++; component.CurrentSelectedEquipment = equipmentIndex >= allEquipment.Count ? null : allEquipment[equipmentIndex]; } if (component.CurrentSelectedEquipment.HasValue) { var equipEv = new MechEquipmentGotSelectedEvent(uid); RaiseLocalEvent(component.CurrentSelectedEquipment.Value, ref equipEv); } // Horizon Mech end var popupString = component.CurrentSelectedEquipment != null ? Loc.GetString("mech-equipment-select-popup", ("item", component.CurrentSelectedEquipment)) : Loc.GetString("mech-equipment-select-none-popup"); if (_net.IsServer) _popup.PopupEntity(popupString, uid); RaiseEquipmentEquippedEvent((uid, component)); // Frontier Dirty(uid, component); } /// /// Inserts an equipment item into the mech. /// /// /// /// /// public void InsertEquipment(EntityUid uid, EntityUid toInsert, MechComponent? component = null, MechEquipmentComponent? equipmentComponent = null) { if (!Resolve(uid, ref component)) return; if (!Resolve(toInsert, ref equipmentComponent)) return; if (component.EquipmentContainer.ContainedEntities.Count >= component.MaxEquipmentAmount) return; if (_whitelistSystem.IsWhitelistFail(component.EquipmentWhitelist, toInsert)) return; equipmentComponent.EquipmentOwner = uid; _container.Insert(toInsert, component.EquipmentContainer); var ev = new MechEquipmentInsertedEvent(uid); RaiseLocalEvent(toInsert, ref ev); UpdateUserInterface(uid, component); } /// /// Removes an equipment item from a mech. /// /// /// /// /// /// Whether or not the removal can be cancelled public void RemoveEquipment(EntityUid uid, EntityUid toRemove, MechComponent? component = null, MechEquipmentComponent? equipmentComponent = null, bool forced = false) { if (!Resolve(uid, ref component)) return; if (!Resolve(toRemove, ref equipmentComponent)) return; if (!forced) { var attemptev = new AttemptRemoveMechEquipmentEvent(); RaiseLocalEvent(toRemove, ref attemptev); if (attemptev.Cancelled) return; } var ev = new MechEquipmentRemovedEvent(uid); RaiseLocalEvent(toRemove, ref ev); if (component.CurrentSelectedEquipment == toRemove) CycleEquipment(uid, component); equipmentComponent.EquipmentOwner = null; _container.Remove(toRemove, component.EquipmentContainer); UpdateUserInterface(uid, component); } /// /// Attempts to change the amount of energy in the mech. /// /// The mech itself /// The change in energy /// /// If the energy was successfully changed. public virtual bool TryChangeEnergy(EntityUid uid, FixedPoint2 delta, MechComponent? component = null) { if (!Resolve(uid, ref component)) return false; // if (component.Energy + delta < 0) // Horizon Mech Commented // return false; // Почему тут стоит эта проверка, если используется Math.Clamp()? component.Energy = FixedPoint2.Clamp(component.Energy + delta, 0, component.MaxEnergy); Dirty(uid, component); UpdateUserInterface(uid, component); return true; } /// /// Sets the integrity of the mech. /// /// The mech itself /// The value the integrity will be set at /// public void SetIntegrity(EntityUid uid, FixedPoint2 value, MechComponent? component = null) { if (!Resolve(uid, ref component)) return; component.Integrity = FixedPoint2.Clamp(value, 0, component.MaxIntegrity); if (component.Integrity <= 0) { BreakMech(uid, component); } else if (component.Broken) { component.Broken = false; UpdateAppearance(uid, component); } Dirty(uid, component); 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. /// /// /// /// /// public bool CanInsert(EntityUid uid, EntityUid toInsert, MechComponent? component = null) { if (!Resolve(uid, ref component)) return false; return IsEmpty(component) && _actionBlocker.CanMove(toInsert); } /// /// Updates the user interface /// /// /// This is defined here so that UI updates can be accessed from shared. /// public virtual void UpdateUserInterface(EntityUid uid, MechComponent? component = null) { } /// /// Attempts to insert a pilot into the mech. /// /// /// /// /// Whether or not the entity was inserted 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)) return false; SetupUser(uid, toInsert.Value); _container.Insert(toInsert.Value, component.PilotSlot); UpdateAppearance(uid, component); return true; } /// /// Attempts to eject the current pilot from the mech /// /// /// /// Whether or not the pilot was ejected. public bool TryEject(EntityUid uid, MechComponent? component = null) { if (!Resolve(uid, ref component)) return false; if (component.PilotSlot.ContainedEntity == null) return false; var pilot = component.PilotSlot.ContainedEntity.Value; RemoveUser(uid, pilot); _container.RemoveEntity(uid, pilot); UpdateAppearance(uid, component); // Horizon Mech start if (_net.IsClient && _timing.IsFirstTimePredicted) { var ev = new CloseMechMenuEvent(); RaiseLocalEvent(pilot, ev); } if (HasComp(pilot)) { RemComp(pilot); } // Horizon Mech end // Horizon RemotePilot start var ejectEv = new OnPilotEjectEvent(GetNetEntity(uid)); RaiseLocalEvent(pilot, ejectEv); // Horizon RemotePilot end return true; } private void OnGetMeleeWeapon(EntityUid uid, MechPilotComponent component, GetMeleeWeaponEvent args) { if (args.Handled) return; if (!TryComp(component.Mech, out var mech)) return; var weapon = mech.CurrentSelectedEquipment ?? component.Mech; args.Weapon = weapon; args.Handled = true; } private void OnCanAttackFromContainer(EntityUid uid, MechPilotComponent component, CanAttackFromContainerEvent args) { args.CanAttack = true; } private void OnAttackAttempt(EntityUid uid, MechPilotComponent component, AttackAttemptEvent args) { if (args.Target == component.Mech) args.Cancel(); } private void UpdateAppearance(EntityUid uid, MechComponent? component = null, AppearanceComponent? appearance = null) { if (!Resolve(uid, ref component, ref appearance, false)) return; _appearance.SetData(uid, MechVisuals.Open, IsEmpty(component), appearance); _appearance.SetData(uid, MechVisuals.Broken, component.Broken, appearance); } private void OnDragDrop(EntityUid uid, MechComponent component, ref DragDropTargetEvent args) { if (args.Handled) return; args.Handled = true; var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Dragged, component.EntryDelay, new MechEntryEvent(), uid, target: uid) { BreakOnMove = true, }; _doAfter.TryStartDoAfter(doAfterEventArgs); } private void OnCanDragDrop(EntityUid uid, MechComponent component, ref CanDropTargetEvent args) { args.Handled = true; args.CanDrop |= !component.Broken && CanInsert(uid, args.Dragged, component); } // Frontier private void RaiseEquipmentEquippedEvent(Entity ent, EntityUid? pilot = null) { if (_net.IsServer && ent.Comp.CurrentSelectedEquipment != null) { var ev = new MechEquipmentEquippedAction { Mech = ent, Pilot = pilot ?? ent.Comp.PilotSlot.ContainedEntity }; RaiseLocalEvent(ent.Comp.CurrentSelectedEquipment.Value, ev); } } // End Frontier } /// /// Event raised when the battery is successfully removed from the mech, /// on both success and failure /// [Serializable, NetSerializable] public sealed partial class RemoveBatteryEvent : SimpleDoAfterEvent { } /// /// Event raised when a person removes someone from a mech, /// on both success and failure /// [Serializable, NetSerializable] public sealed partial class MechExitEvent : SimpleDoAfterEvent { } /// /// Event raised when a person enters a mech, on both success and failure /// [Serializable, NetSerializable] public sealed partial class MechEntryEvent : SimpleDoAfterEvent { }