using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Lock;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Power;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Silicons.StationAi;
///
/// This system is used to handle the actions of AI Restoration Consoles.
/// These consoles can be used to revive dead station AIs, or destroy them.
///
public abstract partial class SharedStationAiFixerConsoleSystem : EntitySystem
{
[Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnInserted);
SubscribeLocalEvent(OnRemoved);
SubscribeLocalEvent(OnLockToggle);
SubscribeLocalEvent(OnMessage);
SubscribeLocalEvent(OnPowerChanged);
SubscribeLocalEvent(OnExamined);
SubscribeLocalEvent(OnStationAiCustomizationStateChanged);
}
private void OnInserted(Entity ent, ref EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != ent.Comp.StationAiHolderSlot)
return;
if (TryGetTarget(ent, out var target))
{
ent.Comp.ActionTarget = target;
Dirty(ent);
}
UpdateAppearance(ent);
}
private void OnRemoved(Entity ent, ref EntRemovedFromContainerMessage args)
{
if (args.Container.ID != ent.Comp.StationAiHolderSlot)
return;
ent.Comp.ActionTarget = null;
StopAction(ent);
}
private void OnLockToggle(Entity ent, ref LockToggledEvent args)
{
if (_userInterface.TryGetOpenUi(ent.Owner, StationAiFixerConsoleUiKey.Key, out var bui))
bui.Update();
}
private void OnMessage(Entity ent, ref StationAiFixerConsoleMessage args)
{
if (TryComp(ent, out var lockable) && lockable.Locked)
return;
switch (args.Action)
{
case StationAiFixerConsoleAction.Eject:
EjectStationAiHolder(ent, args.Actor);
break;
case StationAiFixerConsoleAction.Repair:
RepairStationAi(ent, args.Actor);
break;
case StationAiFixerConsoleAction.Purge:
PurgeStationAi(ent, args.Actor);
break;
case StationAiFixerConsoleAction.Cancel:
CancelAction(ent, args.Actor);
break;
}
}
private void OnPowerChanged(Entity ent, ref PowerChangedEvent args)
{
if (args.Powered)
return;
StopAction(ent);
}
private void OnExamined(Entity ent, ref ExaminedEvent args)
{
var message = TryGetStationAiHolder(ent, out var holder) ?
Loc.GetString("station-ai-fixer-console-examination-station-ai-holder-present", ("holder", Name(holder.Value))) :
Loc.GetString("station-ai-fixer-console-examination-station-ai-holder-absent");
args.PushMarkup(message);
}
private void OnStationAiCustomizationStateChanged(Entity ent, ref StationAiCustomizationStateChanged args)
{
if (_container.TryGetOuterContainer(ent, Transform(ent), out var outerContainer) &&
TryComp(outerContainer.Owner, out var stationAiFixerConsole))
{
UpdateAppearance((outerContainer.Owner, stationAiFixerConsole));
}
}
private void EjectStationAiHolder(Entity ent, EntityUid user)
{
if (!TryComp(ent, out var slots))
return;
if (!_itemSlots.TryGetSlot(ent, ent.Comp.StationAiHolderSlot, out var holderSlot, slots))
return;
if (_itemSlots.TryEjectToHands(ent, holderSlot, user, true))
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user):user} ejected a station AI holder from AI restoration console ({ToPrettyString(ent.Owner)})");
}
private void RepairStationAi(Entity ent, EntityUid user)
{
if (ent.Comp.ActionTarget == null)
return;
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user):user} started a repair of {ToPrettyString(ent.Comp.ActionTarget)} using an AI restoration console ({ToPrettyString(ent.Owner)})");
StartAction(ent, StationAiFixerConsoleAction.Repair);
}
private void PurgeStationAi(Entity ent, EntityUid user)
{
if (ent.Comp.ActionTarget == null)
return;
_adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(user):user} started a purge of {ToPrettyString(ent.Comp.ActionTarget)} using {ToPrettyString(ent.Owner)}");
StartAction(ent, StationAiFixerConsoleAction.Purge);
}
private void CancelAction(Entity ent, EntityUid user)
{
if (!IsActionInProgress(ent))
return;
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user):user} canceled operation involving {ToPrettyString(ent.Comp.ActionTarget)} and {ToPrettyString(ent.Owner)} ({ent.Comp.ActionType} action)");
StopAction(ent);
}
///
/// Initiates an action upon a target entity by the specified console.
///
/// The console.
/// The action to be enacted on the target.
private void StartAction(Entity ent, StationAiFixerConsoleAction actionType)
{
if (IsActionInProgress(ent))
{
StopAction(ent);
}
if (IsTargetValid(ent, actionType))
{
var duration = actionType == StationAiFixerConsoleAction.Repair ?
ent.Comp.RepairDuration :
ent.Comp.PurgeDuration;
ent.Comp.ActionType = actionType;
ent.Comp.ActionStartTime = _timing.CurTime;
ent.Comp.ActionEndTime = _timing.CurTime + duration;
ent.Comp.CurrentActionStage = 0;
Dirty(ent);
}
UpdateAppearance(ent);
}
///
/// Updates the current action being conducted by the specified console.
///
/// The console.
private void UpdateAction(Entity ent)
{
if (IsActionInProgress(ent))
{
if (ent.Comp.ActionTarget == null)
{
StopAction(ent);
return;
}
if (_timing.CurTime >= ent.Comp.ActionEndTime)
{
FinalizeAction(ent);
return;
}
var currentStage = CalculateActionStage(ent);
if (currentStage != ent.Comp.CurrentActionStage)
{
ent.Comp.CurrentActionStage = currentStage;
Dirty(ent);
}
}
UpdateAppearance(ent);
}
///
/// Terminates any action being conducted by the specified console.
///
/// The console.
private void StopAction(Entity ent)
{
ent.Comp.ActionType = StationAiFixerConsoleAction.None;
Dirty(ent);
UpdateAppearance(ent);
}
///
/// Finalizes the action being conducted by the specified console
/// (i.e., repairing or purging a target).
///
/// The console.
protected virtual void FinalizeAction(Entity ent)
{
if (IsActionInProgress(ent) && ent.Comp.ActionTarget != null)
{
if (ent.Comp.ActionType == StationAiFixerConsoleAction.Repair)
{
_mobState.ChangeMobState(ent.Comp.ActionTarget.Value, MobState.Alive);
}
else if (ent.Comp.ActionType == StationAiFixerConsoleAction.Purge &&
TryGetStationAiHolder(ent, out var holder))
{
_container.RemoveEntity(holder.Value, ent.Comp.ActionTarget.Value, force: true);
PredictedQueueDel(ent.Comp.ActionTarget);
ent.Comp.ActionTarget = null;
Dirty(ent);
}
}
StopAction(ent);
}
///
/// Updates the appearance of the specified console based on its current state.
///
/// The console.
private void UpdateAppearance(Entity ent)
{
if (!TryComp(ent, out var appearance))
return;
if (IsActionInProgress(ent))
{
var currentStage = ent.Comp.ActionType + ent.Comp.CurrentActionStage.ToString();
if (!_appearance.TryGetData(ent, StationAiFixerConsoleVisuals.Key, out string oldStage, appearance) ||
oldStage != currentStage)
{
_appearance.SetData(ent, StationAiFixerConsoleVisuals.Key, currentStage, appearance);
}
return;
}
var target = ent.Comp.ActionTarget;
var state = StationAiState.Empty;
if (TryComp(target, out var customization) && !EntityManager.IsQueuedForDeletion(target.Value))
{
state = customization.State;
}
_appearance.SetData(ent, StationAiFixerConsoleVisuals.Key, state.ToString(), appearance);
}
///
/// Calculates the current stage of any in-progress actions.
///
/// The console.
/// The current stage.
private int CalculateActionStage(Entity ent)
{
var completionPercentage = (_timing.CurTime - ent.Comp.ActionStartTime) / (ent.Comp.ActionEndTime - ent.Comp.ActionStartTime);
return (int)(completionPercentage * ent.Comp.ActionStageCount);
}
///
/// Try to find a valid target being stored inside the specified console.
///
/// The console.
/// The found target.
/// True if a valid target was found.
public bool TryGetTarget(Entity ent, [NotNullWhen(true)] out EntityUid? target)
{
target = null;
if (!TryGetStationAiHolder(ent, out var holder))
return false;
if (!_container.TryGetContainer(holder.Value, ent.Comp.StationAiMindSlot, out var stationAiMindSlot) || stationAiMindSlot.Count == 0)
return false;
var stationAi = stationAiMindSlot.ContainedEntities[0];
if (!HasComp(stationAi))
return false;
target = stationAi;
return !EntityManager.IsQueuedForDeletion(target.Value);
}
///
/// Try to find a station AI holder being stored inside the specified console.
///
/// The console.
/// The found holder.
/// True if a valid holder was found.
public bool TryGetStationAiHolder(Entity ent, [NotNullWhen(true)] out EntityUid? holder)
{
holder = null;
if (!_container.TryGetContainer(ent, ent.Comp.StationAiHolderSlot, out var holderContainer) ||
holderContainer.Count == 0)
{
return false;
}
holder = holderContainer.ContainedEntities[0];
return true;
}
///
/// Determines if the specified console can act upon its action target.
///
/// The console.
/// The action to be enacted on the target.
/// True, if the target is valid for the specified console action.
public bool IsTargetValid(Entity ent, StationAiFixerConsoleAction actionType)
{
if (ent.Comp.ActionTarget == null)
return false;
if (actionType == StationAiFixerConsoleAction.Purge)
return true;
if (actionType == StationAiFixerConsoleAction.Repair &&
_mobState.IsDead(ent.Comp.ActionTarget.Value))
{
return true;
}
return false;
}
///
/// Returns whether an station AI holder is inserted into the specified console.
///
/// The console.
/// True if a station AI holder is inserted.
public bool IsStationAiHolderInserted(Entity ent)
{
return TryGetStationAiHolder(ent, out var _);
}
///
/// Returns whether the specified console has an action in progress.
///
/// The console.
/// Ture, if an action is in progress.
public bool IsActionInProgress(Entity ent)
{
return ent.Comp.ActionType != StationAiFixerConsoleAction.None;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = AllEntityQuery();
while (query.MoveNext(out var uid, out var stationAiFixerConsole))
{
var ent = (uid, stationAiFixerConsole);
if (!IsActionInProgress(ent))
continue;
UpdateAction(ent);
}
}
}