using Content.Server.Actions;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.PowerCell;
using Content.Shared.Alert;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Pointing;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
using Content.Shared.Roles;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Throwing;
using Content.Shared.Whitelist;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Silicons.Borgs;
///
public sealed partial class BorgSystem : SharedBorgSystem
{
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly IBanManager _banManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly TriggerSystem _trigger = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
///
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnChassisInteractUsing);
SubscribeLocalEvent(OnMindAdded);
SubscribeLocalEvent(OnMindRemoved);
SubscribeLocalEvent(OnMobStateChanged);
SubscribeLocalEvent(OnPowerCellChanged);
SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnGetDeadIC);
SubscribeLocalEvent(OnToggled);
SubscribeLocalEvent(OnBrainMindAdded);
SubscribeLocalEvent(OnBrainPointAttempt);
InitializeModules();
InitializeMMI();
InitializeUI();
InitializeTransponder();
}
private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args)
{
UpdateBatteryAlert((uid, component));
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
private void OnChassisInteractUsing(EntityUid uid, BorgChassisComponent component, AfterInteractUsingEvent args)
{
if (!args.CanReach || args.Handled || uid == args.User)
return;
var used = args.Used;
TryComp(used, out var brain);
TryComp(used, out var module);
if (TryComp(uid, out var panel) && !panel.Open)
{
if (brain != null || module != null)
{
Popup.PopupEntity(Loc.GetString("borg-panel-not-open"), uid, args.User);
}
return;
}
if (component.BrainEntity == null && brain != null &&
_whitelistSystem.IsWhitelistPassOrNull(component.BrainWhitelist, used))
{
if (_mind.TryGetMind(used, out _, out var mind) && mind.Session != null)
{
if (!CanPlayerBeBorged(mind.Session))
{
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed"), used, args.User);
return;
}
}
_container.Insert(used, component.BrainContainer);
_adminLog.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(args.User):player} installed brain {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
args.Handled = true;
UpdateUI(uid, component);
}
if (module != null && CanInsertModule(uid, used, component, module, args.User))
{
InsertModule((uid, component), used);
_adminLog.Add(LogType.Action, LogImpact.Low,
$"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
args.Handled = true;
UpdateUI(uid, component);
}
}
///
/// Inserts a new module into a borg, the same as if a player inserted it manually.
///
///
/// This does not run checks to see if the borg is actually allowed to be inserted, such as whitelists.
///
/// The borg to insert into.
/// The module to insert.
public void InsertModule(Entity ent, EntityUid module)
{
_container.Insert(module, ent.Comp.ModuleContainer);
}
// todo: consider transferring over the ghost role? managing that might suck.
protected override void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
{
base.OnInserted(uid, component, args);
if (HasComp(args.Entity) && _mind.TryGetMind(args.Entity, out var mindId, out var mind) && args.Container == component.BrainContainer)
{
_mind.TransferTo(mindId, uid, mind: mind);
}
}
protected override void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
{
base.OnRemoved(uid, component, args);
if (HasComp(args.Entity) && _mind.TryGetMind(uid, out var mindId, out var mind) && args.Container == component.BrainContainer)
{
_mind.TransferTo(mindId, args.Entity, mind: mind);
}
}
private void OnMindAdded(EntityUid uid, BorgChassisComponent component, MindAddedMessage args)
{
BorgActivate(uid, component);
}
private void OnMindRemoved(EntityUid uid, BorgChassisComponent component, MindRemovedMessage args)
{
BorgDeactivate(uid, component);
}
private void OnMobStateChanged(EntityUid uid, BorgChassisComponent component, MobStateChangedEvent args)
{
if (args.NewMobState == MobState.Alive)
{
if (_mind.TryGetMind(uid, out _, out _))
_powerCell.SetDrawEnabled(uid, true);
}
else
{
_powerCell.SetDrawEnabled(uid, false);
}
}
private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args)
{
UpdateBatteryAlert((uid, component));
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
if (_powerCell.HasDrawCharge(uid))
{
Toggle.TryActivate(uid);
}
UpdateUI(uid, component);
}
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
{
Toggle.TryDeactivate(uid);
UpdateUI(uid, component);
}
private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args)
{
args.Dead = true;
}
private void OnToggled(Entity ent, ref ItemToggledEvent args)
{
var (uid, comp) = ent;
if (args.Activated)
InstallAllModules(uid, comp);
else
DisableAllModules(uid, comp);
// only enable the powerdraw if there is a player in the chassis
var drawing = _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(ent);
_powerCell.SetDrawEnabled(uid, drawing);
UpdateUI(uid, comp);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
private void OnBrainMindAdded(EntityUid uid, BorgBrainComponent component, MindAddedMessage args)
{
if (!Container.TryGetOuterContainer(uid, Transform(uid), out var container))
return;
var containerEnt = container.Owner;
if (!TryComp(containerEnt, out var chassisComponent) ||
container.ID != chassisComponent.BrainContainerId)
return;
if (!_mind.TryGetMind(uid, out var mindId, out var mind) || mind.Session == null)
return;
if (!CanPlayerBeBorged(mind.Session))
{
Popup.PopupEntity(Loc.GetString("borg-player-not-allowed-eject"), uid);
Container.RemoveEntity(containerEnt, uid);
_throwing.TryThrow(uid, _random.NextVector2() * 5, 5f);
return;
}
_mind.TransferTo(mindId, containerEnt, mind: mind);
}
private void OnBrainPointAttempt(EntityUid uid, BorgBrainComponent component, PointAttemptEvent args)
{
args.Cancel();
}
private void UpdateBatteryAlert(Entity ent, PowerCellSlotComponent? slotComponent = null)
{
if (!_powerCell.TryGetBatteryFromSlot(ent, out var battery, slotComponent))
{
_alerts.ClearAlert(ent, ent.Comp.BatteryAlert);
_alerts.ShowAlert(ent, ent.Comp.NoBatteryAlert);
return;
}
var chargePercent = (short) MathF.Round(battery.CurrentCharge / battery.MaxCharge * 10f);
// we make sure 0 only shows if they have absolutely no battery.
// also account for floating point imprecision
if (chargePercent == 0 && _powerCell.HasDrawCharge(ent, cell: slotComponent))
{
chargePercent = 1;
}
_alerts.ClearAlert(ent, ent.Comp.NoBatteryAlert);
_alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
}
///
/// Activates a borg when a player occupies it
///
public void BorgActivate(EntityUid uid, BorgChassisComponent component)
{
Popup.PopupEntity(Loc.GetString("borg-mind-added", ("name", Identity.Name(uid, EntityManager))), uid);
if (_powerCell.HasDrawCharge(uid))
{
Toggle.TryActivate(uid);
_powerCell.SetDrawEnabled(uid, _mobState.IsAlive(uid));
}
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
}
///
/// Deactivates a borg when a player leaves it.
///
public void BorgDeactivate(EntityUid uid, BorgChassisComponent component)
{
Popup.PopupEntity(Loc.GetString("borg-mind-removed", ("name", Identity.Name(uid, EntityManager))), uid);
Toggle.TryDeactivate(uid);
_powerCell.SetDrawEnabled(uid, false);
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
}
///
/// Checks that a player has fulfilled the requirements for the borg job.
/// If they don't have enough hours, they cannot be placed into a chassis.
///
public bool CanPlayerBeBorged(ICommonSession session)
{
return true;
}
}