using Content.Server.Actions;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.Hands.Systems;
using Content.Server.PowerCell;
using Content.Server.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.Alert;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
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.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.Wires;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Random;
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 IRobustRandom _random = default!;
[Dependency] private readonly SharedAccessSystem _access = default!;
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = 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!;
[ValidatePrototypeId]
public const string BorgJobId = "Borg";
///
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnChassisInteractUsing);
SubscribeLocalEvent(OnMindAdded);
SubscribeLocalEvent(OnMindRemoved);
SubscribeLocalEvent(OnMobStateChanged);
SubscribeLocalEvent(OnPowerCellChanged);
SubscribeLocalEvent(OnPowerCellSlotEmpty);
SubscribeLocalEvent(OnUIOpenAttempt);
SubscribeLocalEvent(OnGetDeadIC);
SubscribeLocalEvent(OnBrainMindAdded);
InitializeModules();
InitializeMMI();
InitializeUI();
}
private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args)
{
UpdateBatteryAlert(uid);
_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 &&
component.BrainWhitelist?.IsValid(used) != false)
{
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))
{
_container.Insert(used, component.ModuleContainer);
_adminLog.Add(LogType.Action, LogImpact.Low,
$"{ToPrettyString(args.User):player} installed module {ToPrettyString(used)} into borg {ToPrettyString(uid)}");
args.Handled = true;
UpdateUI(uid, component);
}
}
// 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))
{
_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))
{
_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.SetPowerCellDrawEnabled(uid, true);
}
else
{
_powerCell.SetPowerCellDrawEnabled(uid, false);
}
}
private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args)
{
UpdateBatteryAlert(uid);
if (!TryComp(uid, out var draw))
return;
// if we eject the battery or run out of charge, then disable
if (args.Ejected || !_powerCell.HasDrawCharge(uid))
{
DisableBorgAbilities(uid, component);
return;
}
// if we aren't drawing and suddenly get enough power to draw again, reeanble.
if (_powerCell.HasDrawCharge(uid, draw))
{
// only reenable the powerdraw if a player has the role.
if (!draw.Drawing && _mind.TryGetMind(uid, out _, out _) && _mobState.IsAlive(uid))
_powerCell.SetPowerCellDrawEnabled(uid, true);
EnableBorgAbilities(uid, component);
}
UpdateUI(uid, component);
}
private void OnPowerCellSlotEmpty(EntityUid uid, BorgChassisComponent component, ref PowerCellSlotEmptyEvent args)
{
DisableBorgAbilities(uid, component);
UpdateUI(uid, component);
}
private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args)
{
// borgs can't view their own ui
if (args.User == uid)
args.Cancel();
}
private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args)
{
args.Dead = true;
}
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 UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotComponent = null)
{
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery, slotComponent))
{
_alerts.ClearAlert(uid, AlertType.BorgBattery);
_alerts.ShowAlert(uid, AlertType.BorgBatteryNone);
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(uid, cell: slotComponent))
{
chargePercent = 1;
}
_alerts.ClearAlert(uid, AlertType.BorgBatteryNone);
_alerts.ShowAlert(uid, AlertType.BorgBattery, chargePercent);
}
///
/// Activates the borg, enabling all of its modules.
///
public void EnableBorgAbilities(EntityUid uid, BorgChassisComponent component, PowerCellDrawComponent? powerCell = null)
{
if (component.Activated)
return;
component.Activated = true;
InstallAllModules(uid, component);
Dirty(component);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
///
/// Deactivates the borg, disabling all of its modules and decreasing its speed.
///
public void DisableBorgAbilities(EntityUid uid, BorgChassisComponent component)
{
if (!component.Activated)
return;
component.Activated = false;
DisableAllModules(uid, component);
Dirty(component);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
}
///
/// 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);
_powerCell.SetPowerCellDrawEnabled(uid, true);
_access.SetAccessEnabled(uid, true);
_appearance.SetData(uid, BorgVisuals.HasPlayer, true);
Dirty(uid, component);
}
///
/// 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);
_powerCell.SetPowerCellDrawEnabled(uid, false);
_access.SetAccessEnabled(uid, false);
_appearance.SetData(uid, BorgVisuals.HasPlayer, false);
Dirty(uid, component);
}
///
/// 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)
{
if (_banManager.GetJobBans(session.UserId)?.Contains(BorgJobId) == true)
return false;
return true;
}
}