6
2025-12-13 13:46:17 +03:00

592 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Linq;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction.Completions;
using Content.Server.Mech.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Mech;
using Content.Shared.Mech.Components;
using Content.Shared.Mech.EntitySystems;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
using Content.Shared.Wires;
using Content.Server.Body.Systems;
using Content.Shared.Tools.Systems;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Content.Shared.Whitelist;
using Content.Shared.Mobs.Components; // Frontier
using Content.Shared.NPC.Components; // Frontier
using Content.Shared.Mobs; // Frontier
using Robust.Server.Audio;
using Content.Shared.Access.Systems;
using Content.Shared.Access.Components;
using Robust.Shared.Random;
using Content.Shared.Mech.Equipment.Components;
using Content.Shared.Storage;
using Content.Server._Horizon.Mech.Components;
using Content.Shared.NPC.Systems; // Frontier
namespace Content.Server.Mech.Systems;
/// <inheritdoc/>
public sealed partial class MechSystem : SharedMechSystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[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!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!; // Frontier
// Horizon Mech start
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly IRobustRandom _random = default!;
// Horizon Mech end
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MechComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<MechComponent, EntInsertedIntoContainerMessage>(OnInsertBattery);
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MechComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
SubscribeLocalEvent<MechComponent, MechOpenUiEvent>(OnOpenUi);
SubscribeLocalEvent<MechComponent, RemoveBatteryEvent>(OnRemoveBattery);
SubscribeLocalEvent<MechComponent, MechEntryEvent>(OnMechEntry);
SubscribeLocalEvent<MechComponent, MechExitEvent>(OnMechExit);
SubscribeLocalEvent<MechComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<MechComponent, MechEquipmentRemoveMessage>(OnRemoveEquipmentMessage);
SubscribeLocalEvent<MechComponent, UpdateCanMoveEvent>(OnMechCanMoveEvent);
SubscribeLocalEvent<MechPilotComponent, ToolUserAttemptUseEvent>(OnToolUseAttempt);
// SubscribeLocalEvent<MechPilotComponent, InhaleLocationEvent>(OnInhale); // Horizon Mech Commented
SubscribeLocalEvent<MechPilotComponent, ExhaleLocationEvent>(OnExhale);
SubscribeLocalEvent<MechPilotComponent, AtmosExposedGetAirEvent>(OnExpose);
SubscribeLocalEvent<MechAirComponent, GetFilterAirEvent>(OnGetFilterAir);
SubscribeLocalEvent<MechComponentsTransferEvent>(OnMechComponentsTransfer); // Horizon
#region Equipment UI message relays
// SubscribeLocalEvent<MechComponent, MechGrabberEjectMessage>(ReceiveEquipmentUiMesssages); // Horizon Mech - Moved to Shared
// SubscribeLocalEvent<MechComponent, MechSoundboardPlayMessage>(ReceiveEquipmentUiMesssages); // Horizon Mech - Moved to Shared
#endregion
InitializeADT(); // Horizon Mech
}
// Horizon start
/// <summary>
/// Обработчик события передачи компонентов меха.
/// Вставляет сохраненные модули и батарею в последний созданный мех.
/// </summary>
private void OnMechComponentsTransfer(MechComponentsTransferEvent args)
{
// Поиск нового меха
var mechs = EntityQuery<MechComponent>()
.OrderByDescending(m => m.Owner.Id)
.Select(m => m.Owner)
.ToList();
if (mechs.Count == 0)
return;
var latestMech = mechs[0];
if (!TryComp<MechComponent>(latestMech, out var mechComp))
return;
var component = args.Component;
if (!EntityManager.EntityExists(component))
{
Log.Error($"OnMechComponentsTransfer: Компонент {component} не существует!");
return;
}
// Проверяем, является ли компонент пилотом
if (args.IsPilot)
{
//Log.Debug($"OnMechComponentsTransfer: Переносим пилота {ToPrettyString(component)} в мех {ToPrettyString(latestMech)}");
TryInsert(latestMech, component, mechComp);
}
else if (TryComp<BatteryComponent>(component, out _))
{
InsertBattery(latestMech, component, mechComp);
//Log.Debug($"OnMechComponentsTransfer: Вставлена батарея {ToPrettyString(component)} в {ToPrettyString(latestMech)}");
}
else if (HasComp<MechEquipmentComponent>(component))
{
InsertEquipment(latestMech, component, mechComp);
//Log.Debug($"OnMechComponentsTransfer: Вставлено оборудование {ToPrettyString(component)} в {ToPrettyString(latestMech)}");
}
else if (args.IsStorageItem)
{
// Проверяем, есть ли у нового меха хранилище
if (TryComp<StorageComponent>(latestMech, out var storageComp))
{
// Перемещаем предмет в новое хранилище
_container.InsertOrDrop(component, storageComp.Container);
//Log.Debug($"OnMechComponentsTransfer: Перемещён предмет {ToPrettyString(component)} в хранилище {ToPrettyString(latestMech)}");
}
else
{
// Если у нового меха нет хранилища, выбрасываем предмет на пол
Transform(component).Coordinates = Transform(latestMech).Coordinates;
//Log.Debug($"OnMechComponentsTransfer: Выброшен предмет {ToPrettyString(component)} на пол, т.к. у нового меха нет хранилища");
}
}
UpdateUserInterface(latestMech, mechComp);
}
// Horizon end
private void OnMechCanMoveEvent(EntityUid uid, MechComponent component, UpdateCanMoveEvent args)
{
if (component.Broken || component.Integrity <= 0 || component.Energy <= 0)
args.Cancel();
}
private void OnInteractUsing(EntityUid uid, MechComponent component, InteractUsingEvent args)
{
if (TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
return;
if (component.BatterySlot.ContainedEntity == null && TryComp<BatteryComponent>(args.Used, out var battery))
{
InsertBattery(uid, args.Used, component, battery);
_actionBlocker.UpdateCanMove(uid);
return;
}
if (_toolSystem.HasQuality(args.Used, "Prying") && component.BatterySlot.ContainedEntity != null)
{
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.BatteryRemovalDelay,
new RemoveBatteryEvent(), uid, target: uid, used: args.Target)
{
BreakOnMove = true
};
_doAfter.TryStartDoAfter(doAfterEventArgs);
}
}
private void OnInsertBattery(EntityUid uid, MechComponent component, EntInsertedIntoContainerMessage args)
{
if (args.Container != component.BatterySlot || !TryComp<BatteryComponent>(args.Entity, out var battery))
return;
component.Energy = battery.CurrentCharge;
component.MaxEnergy = battery.MaxCharge;
Dirty(uid, component);
_actionBlocker.UpdateCanMove(uid);
}
private void OnRemoveBattery(EntityUid uid, MechComponent component, RemoveBatteryEvent args)
{
if (args.Cancelled || args.Handled)
return;
RemoveBattery(uid, component);
_actionBlocker.UpdateCanMove(uid);
args.Handled = true;
}
private void OnMapInit(EntityUid uid, MechComponent component, MapInitEvent args)
{
var xform = Transform(uid);
// TODO: this should use containerfill?
foreach (var equipment in component.StartingEquipment)
{
var ent = Spawn(equipment, xform.Coordinates);
InsertEquipment(uid, ent, component);
}
// TODO: this should just be damage and battery
component.Integrity = component.MaxIntegrity;
component.Energy = component.MaxEnergy;
_actionBlocker.UpdateCanMove(uid);
Dirty(uid, component);
}
private void OnRemoveEquipmentMessage(EntityUid uid, MechComponent component, MechEquipmentRemoveMessage args)
{
// Frontier: mechs with fixed equipment
if (!component.CanRemoveEquipment)
return;
// End Frontier: mechs with fixed equipment
var equip = GetEntity(args.Equipment);
if (!Exists(equip) || Deleted(equip))
return;
if (!component.EquipmentContainer.ContainedEntities.Contains(equip))
return;
RemoveEquipment(uid, equip, component);
UpdateUserInterface(uid); // Horizon Mech
}
private void OnOpenUi(EntityUid uid, MechComponent component, MechOpenUiEvent args)
{
args.Handled = true;
ToggleMechUi(uid, component);
}
private void OnToolUseAttempt(EntityUid uid, MechPilotComponent component, ref ToolUserAttemptUseEvent args)
{
if (args.Target == component.Mech)
args.Cancelled = true;
}
private void OnAlternativeVerb(EntityUid uid, MechComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || component.Broken)
return;
if (CanInsert(uid, args.User, component))
{
var enterVerb = new AlternativeVerb
{
Text = Loc.GetString("mech-verb-enter"),
Act = () =>
{
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.EntryDelay, new MechEntryEvent(), uid, target: uid)
{
BreakOnMove = true,
};
_doAfter.TryStartDoAfter(doAfterEventArgs);
}
};
var openUiVerb = new AlternativeVerb //can't hijack someone else's mech
{
Act = () => ToggleMechUi(uid, component, args.User),
Text = Loc.GetString("mech-ui-open-verb")
};
args.Verbs.Add(enterVerb);
args.Verbs.Add(openUiVerb);
}
else if (!IsEmpty(component))
{
var ejectVerb = new AlternativeVerb
{
Text = Loc.GetString("mech-verb-exit"),
Priority = 1, // Promote to top to make ejecting the ALT-click action
Act = () =>
{
if (args.User == uid || args.User == component.PilotSlot.ContainedEntity)
{
TryEject(uid, component);
return;
}
var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.ExitDelay, new MechExitEvent(), uid, target: uid)
{
BreakOnMove = true,
};
_popup.PopupEntity(Loc.GetString("mech-eject-pilot-alert", ("item", uid), ("user", args.User)), uid, PopupType.Large);
_doAfter.TryStartDoAfter(doAfterEventArgs);
}
};
args.Verbs.Add(ejectVerb);
}
}
private void OnMechEntry(EntityUid uid, MechComponent component, MechEntryEvent args)
{
if (args.Cancelled || args.Handled)
return;
if (_whitelistSystem.IsWhitelistFail(component.PilotWhitelist, args.User))
{
_popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User);
return;
}
// Horizon Mech start
if (TryComp<AccessReaderComponent>(uid, out var accesscomponent) && !_accessReader.IsAllowed(args.User, uid, accesscomponent))
{
_popup.PopupEntity(Loc.GetString("gateway-access-denied"), args.User);
_audio.PlayPvs(component.AccessDeniedSound, uid);
args.Handled = true;
return;
}
// Horizon Mech end
// Frontier - Make AI Attack mechs based on user.
if (TryComp<MobStateComponent>(args.User, out var _))
{
component.MobStateAdded = !EnsureComp<MobStateComponent>(uid, out _);
component.MobThresholdsAdded = !EnsureComp<MobThresholdsComponent>(uid, out _);
}
if (TryComp<NpcFactionMemberComponent>(args.User, out var faction))
{
component.NpcFactionAdded = !EnsureComp<NpcFactionMemberComponent>(uid, out var factionMech);
_npcFaction.AddFactions((uid, factionMech), faction.Factions);
}
// End Frontier
TryInsert(uid, args.Args.User, component);
_actionBlocker.UpdateCanMove(uid);
args.Handled = true;
}
private void OnMechExit(EntityUid uid, MechComponent component, MechExitEvent args)
{
if (args.Cancelled || args.Handled)
return;
TryEject(uid, component);
// Frontier: revert state
if (component.MobStateAdded)
{
RemComp<MobStateComponent>(uid);
component.MobStateAdded = false;
}
if (component.MobThresholdsAdded)
{
RemComp<MobThresholdsComponent>(uid);
component.MobThresholdsAdded = false;
}
if (component.NpcFactionAdded)
{
RemComp<NpcFactionMemberComponent>(uid);
component.NpcFactionAdded = false;
}
// End Frontier: revert state
args.Handled = true;
}
private void OnDamageChanged(EntityUid uid, MechComponent component, DamageChangedEvent args)
{
var integrity = component.MaxIntegrity - args.Damageable.TotalDamage;
SetIntegrity(uid, integrity, component);
// Horizon Mech start
if (component.Integrity <= component.DamageToDesEqi && !component.Broken && _random.Prob(0.5f) && component.CurrentSelectedEquipment != null)
{
var ev = new MechEquipmentDestroyedEvent();
RaiseLocalEvent(uid, ref ev);
}
// Horizon Mech end
if (args.DamageIncreased &&
args.DamageDelta != null &&
component.PilotSlot.ContainedEntity != null)
{
var damage = args.DamageDelta * component.MechToPilotDamageMultiplier;
_damageable.TryChangeDamage(component.PilotSlot.ContainedEntity, damage);
}
if (TryComp<MobStateComponent>(component.PilotSlot.ContainedEntity, out var state) && state.CurrentState != MobState.Alive) // Frontier - Eject players from mechs when they go crit
TryEject(uid, component);
}
private void ToggleMechUi(EntityUid uid, MechComponent? component = null, EntityUid? user = null)
{
if (!Resolve(uid, ref component))
return;
user ??= component.PilotSlot.ContainedEntity;
if (user == null)
return;
if (!TryComp<ActorComponent>(user, out var actor))
return;
_ui.TryToggleUi(uid, MechUiKey.Key, actor.PlayerSession);
UpdateUserInterface(uid, component);
}
// Horizon Mech moved to shared
// private void ReceiveEquipmentUiMesssages<T>(EntityUid uid, MechComponent component, T args) where T : MechEquipmentUiMessage
// {
// var ev = new MechEquipmentUiMessageRelayEvent(args);
// var allEquipment = new List<EntityUid>(component.EquipmentContainer.ContainedEntities);
// var argEquip = GetEntity(args.Equipment);
// foreach (var equipment in allEquipment)
// {
// if (argEquip == equipment)
// RaiseLocalEvent(equipment, ev);
// }
// }
// Horizon - поменял немного получение частей интерфейса
public override void UpdateUserInterface(EntityUid uid, MechComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
base.UpdateUserInterface(uid, component);
var states = new Dictionary<NetEntity, BoundUserInterfaceState?>();
var ev = new MechEquipmentUiStateReadyEvent();
foreach (var ent in component.EquipmentContainer.ContainedEntities)
{
RaiseLocalEvent(ent, ev);
states.Add(GetNetEntity(ent), ev.State);
}
var state = new MechBoundUiState
{
EquipmentStates = states
};
Dirty(uid, component); // Horizon Mech
_ui.SetUiState(uid, MechUiKey.Key, state);
}
public override void BreakMech(EntityUid uid, MechComponent? component = null)
{
base.BreakMech(uid, component);
_ui.CloseUi(uid, MechUiKey.Key);
_actionBlocker.UpdateCanMove(uid);
}
public override bool TryChangeEnergy(EntityUid uid, FixedPoint2 delta, MechComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!base.TryChangeEnergy(uid, delta, component))
return false;
var battery = component.BatterySlot.ContainedEntity;
if (battery == null)
return false;
if (!TryComp<BatteryComponent>(battery, out var batteryComp))
return false;
_battery.SetCharge(battery!.Value, batteryComp.CurrentCharge + delta.Float(), batteryComp);
if (batteryComp.CurrentCharge != component.Energy) //if there's a discrepency, we have to resync them
{
Log.Debug($"Battery charge was not equal to mech charge. Battery {batteryComp.CurrentCharge}. Mech {component.Energy}");
component.Energy = batteryComp.CurrentCharge;
Dirty(uid, component);
}
_actionBlocker.UpdateCanMove(uid);
return true;
}
public void InsertBattery(EntityUid uid, EntityUid toInsert, MechComponent? component = null, BatteryComponent? battery = null)
{
if (!Resolve(uid, ref component, false))
return;
if (!Resolve(toInsert, ref battery, false))
return;
_container.Insert(toInsert, component.BatterySlot);
component.Energy = battery.CurrentCharge;
component.MaxEnergy = battery.MaxCharge;
_actionBlocker.UpdateCanMove(uid);
Dirty(uid, component);
UpdateUserInterface(uid, component);
}
public void RemoveBattery(EntityUid uid, MechComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
_container.EmptyContainer(component.BatterySlot);
component.Energy = 0;
component.MaxEnergy = 0;
_actionBlocker.UpdateCanMove(uid);
Dirty(uid, component);
UpdateUserInterface(uid, component);
}
#region Atmos Handling
// private void OnInhale(EntityUid uid, MechPilotComponent component, InhaleLocationEvent args) // Horizon Mech - Moved to shared
// {
// if (!TryComp<MechComponent>(component.Mech, out var mech) ||
// !TryComp<MechAirComponent>(component.Mech, out var mechAir))
// {
// return;
// }
// if (mech.Airtight)
// args.Gas = mechAir.Air;
// }
// Horizon Mech Commented
private void OnExhale(EntityUid uid, MechPilotComponent component, ExhaleLocationEvent args)
{
if (!TryComp<MechComponent>(component.Mech, out var mech) ||
!TryComp<MechAirComponent>(component.Mech, out var mechAir))
{
return;
}
if (mech.Airtight)
args.Gas = mechAir.Air;
}
private void OnExpose(EntityUid uid, MechPilotComponent component, ref AtmosExposedGetAirEvent args)
{
if (args.Handled)
return;
if (!TryComp(component.Mech, out MechComponent? mech))
return;
if (mech.Airtight && TryComp(component.Mech, out MechAirComponent? air))
{
args.Handled = true;
args.Gas = mech.Airtight ? air.Air : _atmosphere.GetContainingMixture(component.Mech); // Horizon Mech
return;
}
args.Gas = _atmosphere.GetContainingMixture(component.Mech, excite: args.Excite);
args.Handled = true;
}
private void OnGetFilterAir(EntityUid uid, MechAirComponent comp, ref GetFilterAirEvent args)
{
if (args.Air != null)
return;
// only airtight mechs get internal air
if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight)
return;
args.Air = comp.Air;
}
#endregion
}