The station AI can be destroyed (#39588)
* Initial commit * Fixing merge conflict * Merge conflict fixed * Anchorable entities can now be marked as 'unanchorable' * Revert "Anchorable entities can now be marked as 'unanchorable'" This reverts commit 6a502e62a703cf06bd36ed3bdefe655fc074cfc5 This functionality will be made into a separate PR * Error sprite * Update AI core appearance with sustained damage, spawn scrap on destroyed * Added intellicard sprite * AI damage overlays * Added fixtures * AI core accent changes when damaged or low on power * Bug fix and pop up messages for inserting AIs into inoperable cores * Updated 'dead' sprite * Destroying the AI core reduces the number of AI job slots available * AI battery duration set to 10 minutes * Initial commit * Allow MMIs used in the construction of AI cores to take them over * Initial resources commit * Initial code commit * Sprite update * Bug fixes and updates * Basic console UI * Code refactor * Added lock screen * Added all outstanding UI features * Added purge sprites * Better appearance handling * Fixed issue with purge sprite * Finalized UI design * Major components finalized * Bit of clean up * Removed some code that was used for testing * Tweaked some text * Removed extra space * Added the circuitboard to the RD's locker * Addressed reviewer comments plus tweaks * Addressed reviewer comments plus tweaks * Removed instances of granular damage * Various improvements * Removed testing code * Fixed issue with disabled buttons * Finalized code * Addressed review comments * Added a spare Station AI core electronics to the research director's locker * Fixing build failure * Addressed review comments * Addressed review comments * Added reverse path for construction graph * Removed unneeded reference * Parts can be purchased through cargo * Fixing merge conflict * Merge conflict resolved * Fixing merge conflict * Code update * Code updates * Increased AI core health and gave it a sell price to fix test fail * Added screen static sprite * Added better support for ghosted AI players plus code tweaks * Various improvements and clean up * Increased purge duration to 60 seconds * Fixed needless complication * Addressed reviewer comments part 1 * Addressed reviewer comments part 2 * Further fixes * Trying lower battery values to see if it fixes the test fail * Adjusted power values again * Addressed review comments * Addressed review comments * Fixed test fail * Fixed bug with endless rebooting. Using rejuvenation on an AI core revives the AI inside. * Added pop up text * Bug fix * Tweaks and fixes * Fixed restoration console not updating when the AI finishes rebooting * Update SharedStationAiSystem.Held.cs --------- Co-authored-by: ScarKy0 <scarky0@onet.eu>
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<StationAiFixerConsoleComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<StationAiFixerConsoleComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
SubscribeLocalEvent<StationAiFixerConsoleComponent, LockToggledEvent>(OnLockToggle);
|
||||
SubscribeLocalEvent<StationAiFixerConsoleComponent, StationAiFixerConsoleMessage>(OnMessage);
|
||||
SubscribeLocalEvent<StationAiFixerConsoleComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<StationAiFixerConsoleComponent, ExaminedEvent>(OnExamined);
|
||||
|
||||
SubscribeLocalEvent<StationAiCustomizationComponent, StationAiCustomizationStateChanged>(OnStationAiCustomizationStateChanged);
|
||||
}
|
||||
|
||||
private void OnInserted(Entity<StationAiFixerConsoleComponent> 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<StationAiFixerConsoleComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ent.Comp.StationAiHolderSlot)
|
||||
return;
|
||||
|
||||
ent.Comp.ActionTarget = null;
|
||||
|
||||
StopAction(ent);
|
||||
}
|
||||
|
||||
private void OnLockToggle(Entity<StationAiFixerConsoleComponent> ent, ref LockToggledEvent args)
|
||||
{
|
||||
if (_userInterface.TryGetOpenUi(ent.Owner, StationAiFixerConsoleUiKey.Key, out var bui))
|
||||
bui.Update<StationAiFixerConsoleBoundUserInterfaceState>();
|
||||
}
|
||||
|
||||
private void OnMessage(Entity<StationAiFixerConsoleComponent> ent, ref StationAiFixerConsoleMessage args)
|
||||
{
|
||||
if (TryComp<LockComponent>(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<StationAiFixerConsoleComponent> ent, ref PowerChangedEvent args)
|
||||
{
|
||||
if (args.Powered)
|
||||
return;
|
||||
|
||||
StopAction(ent);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<StationAiFixerConsoleComponent> 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<StationAiCustomizationComponent> ent, ref StationAiCustomizationStateChanged args)
|
||||
{
|
||||
if (_container.TryGetOuterContainer(ent, Transform(ent), out var outerContainer) &&
|
||||
TryComp<StationAiFixerConsoleComponent>(outerContainer.Owner, out var stationAiFixerConsole))
|
||||
{
|
||||
UpdateAppearance((outerContainer.Owner, stationAiFixerConsole));
|
||||
}
|
||||
}
|
||||
|
||||
private void EjectStationAiHolder(Entity<StationAiFixerConsoleComponent> ent, EntityUid user)
|
||||
{
|
||||
if (!TryComp<ItemSlotsComponent>(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<StationAiFixerConsoleComponent> 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<StationAiFixerConsoleComponent> 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<StationAiFixerConsoleComponent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates an action upon a target entity by the specified console.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
/// <param name="actionType">The action to be enacted on the target.</param>
|
||||
private void StartAction(Entity<StationAiFixerConsoleComponent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current action being conducted by the specified console.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
private void UpdateAction(Entity<StationAiFixerConsoleComponent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminates any action being conducted by the specified console.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
private void StopAction(Entity<StationAiFixerConsoleComponent> ent)
|
||||
{
|
||||
ent.Comp.ActionType = StationAiFixerConsoleAction.None;
|
||||
Dirty(ent);
|
||||
|
||||
UpdateAppearance(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes the action being conducted by the specified console
|
||||
/// (i.e., repairing or purging a target).
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
protected virtual void FinalizeAction(Entity<StationAiFixerConsoleComponent> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the appearance of the specified console based on its current state.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
private void UpdateAppearance(Entity<StationAiFixerConsoleComponent> ent)
|
||||
{
|
||||
if (!TryComp<AppearanceComponent>(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<StationAiCustomizationComponent>(target, out var customization) && !EntityManager.IsQueuedForDeletion(target.Value))
|
||||
{
|
||||
state = customization.State;
|
||||
}
|
||||
|
||||
_appearance.SetData(ent, StationAiFixerConsoleVisuals.Key, state.ToString(), appearance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the current stage of any in-progress actions.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
/// <returns>The current stage.</returns>
|
||||
private int CalculateActionStage(Entity<StationAiFixerConsoleComponent> ent)
|
||||
{
|
||||
var completionPercentage = (_timing.CurTime - ent.Comp.ActionStartTime) / (ent.Comp.ActionEndTime - ent.Comp.ActionStartTime);
|
||||
|
||||
return (int)(completionPercentage * ent.Comp.ActionStageCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find a valid target being stored inside the specified console.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
/// <param name="target">The found target.</param>
|
||||
/// <returns>True if a valid target was found.</returns>
|
||||
public bool TryGetTarget(Entity<StationAiFixerConsoleComponent> 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<MobStateComponent>(stationAi))
|
||||
return false;
|
||||
|
||||
target = stationAi;
|
||||
|
||||
return !EntityManager.IsQueuedForDeletion(target.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find a station AI holder being stored inside the specified console.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
/// <param name="holder">The found holder.</param>
|
||||
/// <returns>True if a valid holder was found.</returns>
|
||||
public bool TryGetStationAiHolder(Entity<StationAiFixerConsoleComponent> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified console can act upon its action target.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
/// <param name="actionType">The action to be enacted on the target.</param>
|
||||
/// <returns>True, if the target is valid for the specified console action.</returns>
|
||||
public bool IsTargetValid(Entity<StationAiFixerConsoleComponent> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether an station AI holder is inserted into the specified console.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
/// <returns>True if a station AI holder is inserted.</returns>
|
||||
public bool IsStationAiHolderInserted(Entity<StationAiFixerConsoleComponent> ent)
|
||||
{
|
||||
return TryGetStationAiHolder(ent, out var _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the specified console has an action in progress.
|
||||
/// </summary>
|
||||
/// <param name="ent">The console.</param>
|
||||
/// <returns>Ture, if an action is in progress.</returns>
|
||||
public bool IsActionInProgress(Entity<StationAiFixerConsoleComponent> ent)
|
||||
{
|
||||
return ent.Comp.ActionType != StationAiFixerConsoleAction.None;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = AllEntityQuery<StationAiFixerConsoleComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var stationAiFixerConsole))
|
||||
{
|
||||
var ent = (uid, stationAiFixerConsole);
|
||||
|
||||
if (!IsActionInProgress(ent))
|
||||
continue;
|
||||
|
||||
UpdateAction(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
using Content.Shared.Holopad;
|
||||
using Content.Shared.Mobs;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
@@ -8,9 +12,15 @@ public abstract partial class SharedStationAiSystem
|
||||
private ProtoId<StationAiCustomizationGroupPrototype> _stationAiCoreCustomGroupProtoId = "StationAiCoreIconography";
|
||||
private ProtoId<StationAiCustomizationGroupPrototype> _stationAiHologramCustomGroupProtoId = "StationAiHolograms";
|
||||
|
||||
private readonly SpriteSpecifier.Rsi _stationAiRebooting = new(new ResPath("Mobs/Silicon/station_ai.rsi"), "ai_fuzz");
|
||||
|
||||
private void InitializeCustomization()
|
||||
{
|
||||
SubscribeLocalEvent<StationAiCoreComponent, StationAiCustomizationMessage>(OnStationAiCustomization);
|
||||
|
||||
SubscribeLocalEvent<StationAiCustomizationComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<StationAiCustomizationComponent, PlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<StationAiCustomizationComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
}
|
||||
|
||||
private void OnStationAiCustomization(Entity<StationAiCoreComponent> entity, ref StationAiCustomizationMessage args)
|
||||
@@ -29,17 +39,53 @@ public abstract partial class SharedStationAiSystem
|
||||
|
||||
stationAiCustomization.ProtoIds[args.GroupProtoId] = args.CustomizationProtoId;
|
||||
|
||||
Dirty(held, stationAiCustomization);
|
||||
Dirty(held.Value, stationAiCustomization);
|
||||
|
||||
// Update hologram
|
||||
if (groupPrototype.Category == StationAiCustomizationType.Hologram)
|
||||
UpdateHolographicAvatar((held, stationAiCustomization));
|
||||
UpdateHolographicAvatar((held.Value, stationAiCustomization));
|
||||
|
||||
// Update core iconography
|
||||
if (groupPrototype.Category == StationAiCustomizationType.CoreIconography && TryComp<StationAiHolderComponent>(entity, out var stationAiHolder))
|
||||
UpdateAppearance((entity, stationAiHolder));
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(Entity<StationAiCustomizationComponent> ent, ref PlayerAttachedEvent args)
|
||||
{
|
||||
var state = _mobState.IsDead(ent) ? StationAiState.Dead : StationAiState.Occupied;
|
||||
SetStationAiState(ent, state);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<StationAiCustomizationComponent> ent, ref PlayerDetachedEvent args)
|
||||
{
|
||||
var state = _mobState.IsDead(ent) ? StationAiState.Dead : StationAiState.Rebooting;
|
||||
SetStationAiState(ent, state);
|
||||
}
|
||||
|
||||
protected virtual void OnMobStateChanged(Entity<StationAiCustomizationComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
var state = (args.NewMobState == MobState.Dead) ? StationAiState.Dead : StationAiState.Rebooting;
|
||||
SetStationAiState(ent, state);
|
||||
}
|
||||
|
||||
protected void SetStationAiState(Entity<StationAiCustomizationComponent> ent, StationAiState state)
|
||||
{
|
||||
if (ent.Comp.State != state)
|
||||
{
|
||||
ent.Comp.State = state;
|
||||
Dirty(ent);
|
||||
|
||||
var ev = new StationAiCustomizationStateChanged(state);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
|
||||
if (_containers.TryGetContainingContainer(ent.Owner, out var container) &&
|
||||
TryComp<StationAiHolderComponent>(container.Owner, out var holder))
|
||||
{
|
||||
UpdateAppearance((container.Owner, holder));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHolographicAvatar(Entity<StationAiCustomizationComponent> entity)
|
||||
{
|
||||
if (!TryComp<HolographicAvatarComponent>(entity, out var avatar))
|
||||
@@ -62,21 +108,36 @@ public abstract partial class SharedStationAiSystem
|
||||
{
|
||||
var stationAi = GetInsertedAI(entity);
|
||||
|
||||
if (stationAi == null)
|
||||
{
|
||||
_appearance.RemoveData(entity.Owner, StationAiVisualState.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<StationAiCustomizationComponent>(stationAi, out var stationAiCustomization) ||
|
||||
!stationAiCustomization.ProtoIds.TryGetValue(_stationAiCoreCustomGroupProtoId, out var protoId) ||
|
||||
!_protoManager.Resolve(protoId, out var prototype) ||
|
||||
!prototype.LayerData.TryGetValue(state.ToString(), out var layerData))
|
||||
!TryGetCustomizedAppearanceData((stationAi.Value, stationAiCustomization), out var layerData) ||
|
||||
!layerData.TryGetValue(state.ToString(), out var stateData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This data is handled manually in the client StationAiSystem
|
||||
_appearance.SetData(entity.Owner, StationAiVisualState.Key, layerData);
|
||||
_appearance.SetData(entity.Owner, StationAiVisualLayers.Icon, stateData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary containing the station AI's appearance for different states.
|
||||
/// </summary>
|
||||
/// <param name="entity">The station AI.</param>
|
||||
/// <param name="layerData">The apperance data, indexed by possible AI states.</param>
|
||||
/// <returns>True if the apperance data was found.</returns>
|
||||
public bool TryGetCustomizedAppearanceData(Entity<StationAiCustomizationComponent> entity, [NotNullWhen(true)] out Dictionary<string, PrototypeLayerData>? layerData)
|
||||
{
|
||||
layerData = null;
|
||||
|
||||
if (!entity.Comp.ProtoIds.TryGetValue(_stationAiCoreCustomGroupProtoId, out var protoId) ||
|
||||
!_protoManager.Resolve(protoId, out var prototype) ||
|
||||
prototype.LayerData.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
layerData = prototype.LayerData;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
@@ -26,6 +27,7 @@ public abstract partial class SharedStationAiSystem
|
||||
SubscribeLocalEvent<StationAiHeldComponent, InteractionAttemptEvent>(OnHeldInteraction);
|
||||
SubscribeLocalEvent<StationAiHeldComponent, AttemptRelayActionComponentChangeEvent>(OnHeldRelay);
|
||||
SubscribeLocalEvent<StationAiHeldComponent, JumpToCoreEvent>(OnCoreJump);
|
||||
|
||||
SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
|
||||
}
|
||||
|
||||
@@ -49,20 +51,23 @@ public abstract partial class SharedStationAiSystem
|
||||
if (!TryGetCore(ent.Owner, out var core) || core.Comp?.RemoteEntity == null)
|
||||
return;
|
||||
|
||||
_xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner) ;
|
||||
_xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the entity held in the AI core using StationAiCore.
|
||||
/// Tries to find an AI being held in by an entity using <see cref="StationAiHolderComponent"/>.
|
||||
/// </summary>
|
||||
public bool TryGetHeld(Entity<StationAiCoreComponent?> entity, out EntityUid held)
|
||||
/// <param name="entity">The station AI holder.</param>
|
||||
/// <param name="held">The found AI.</param>
|
||||
/// <returns>True if an AI is found.</returns>
|
||||
public bool TryGetHeld(Entity<StationAiHolderComponent?> entity, [NotNullWhen(true)] out EntityUid? held)
|
||||
{
|
||||
held = EntityUid.Invalid;
|
||||
|
||||
if (!Resolve(entity.Owner, ref entity.Comp))
|
||||
return false;
|
||||
|
||||
if (!_containers.TryGetContainer(entity.Owner, StationAiCoreComponent.Container, out var container) ||
|
||||
if (!_containers.TryGetContainer(entity.Owner, StationAiHolderComponent.Container, out var container) ||
|
||||
container.ContainedEntities.Count == 0)
|
||||
return false;
|
||||
|
||||
@@ -70,26 +75,32 @@ public abstract partial class SharedStationAiSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the entity held in the AI using StationAiHolder.
|
||||
/// </summary>
|
||||
public bool TryGetHeld(Entity<StationAiHolderComponent?> entity, out EntityUid held)
|
||||
{
|
||||
TryComp<StationAiCoreComponent>(entity.Owner, out var stationAiCore);
|
||||
|
||||
return TryGetHeld((entity.Owner, stationAiCore), out held);
|
||||
/// <summary>
|
||||
/// Tries to find an AI being held in by an entity using <see cref="StationAiCoreComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="entity">The station AI core.</param>
|
||||
/// <param name="held">The found AI.</param>
|
||||
/// <returns>True if an AI is found.</returns>
|
||||
public bool TryGetHeld(Entity<StationAiCoreComponent?> entity, [NotNullWhen(true)] out EntityUid? held)
|
||||
{
|
||||
held = null;
|
||||
|
||||
return TryComp<StationAiHolderComponent>(entity.Owner, out var holder) &&
|
||||
TryGetHeld((entity, holder), out held);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the station AI core holding an AI.
|
||||
/// </summary>
|
||||
/// <param name="entity">The AI.</param>
|
||||
/// <param name="core">The found AI core.</param>
|
||||
/// <returns>True if an AI core is found.</returns>
|
||||
public bool TryGetCore(EntityUid entity, out Entity<StationAiCoreComponent?> core)
|
||||
{
|
||||
var xform = Transform(entity);
|
||||
var meta = MetaData(entity);
|
||||
var ent = new Entity<TransformComponent?, MetaDataComponent?>(entity, xform, meta);
|
||||
|
||||
if (!_containers.TryGetContainingContainer(ent, out var container) ||
|
||||
if (!_containers.TryGetContainingContainer(entity, out var container) ||
|
||||
container.ID != StationAiCoreComponent.Container ||
|
||||
!TryComp(container.Owner, out StationAiCoreComponent? coreComp) ||
|
||||
coreComp.RemoteEntity == null)
|
||||
!TryComp(container.Owner, out StationAiCoreComponent? coreComp))
|
||||
{
|
||||
core = (EntityUid.Invalid, null);
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Electrocution;
|
||||
@@ -11,11 +12,14 @@ using Content.Shared.Intellicard;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Repairable;
|
||||
using Content.Shared.StationAi;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -28,36 +32,36 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminManager _admin = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggles = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedAirlockSystem _airlocks = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly SharedDoorSystem _doors = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedElectrocutionSystem _electrify = default!;
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
[Dependency] private readonly ISharedAdminManager _admin = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggles = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedAirlockSystem _airlocks = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly SharedDoorSystem _doors = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedElectrocutionSystem _electrify = default!;
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
[Dependency] protected readonly SharedMapSystem Maps = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem PowerReceiver = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly StationAiVisionSystem _vision = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedMoverController _mover = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem PowerReceiver = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly StationAiVisionSystem _vision = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
// StationAiHeld is added to anything inside of an AI core.
|
||||
// StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core).
|
||||
@@ -72,8 +76,6 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
private static readonly EntProtoId DefaultAi = "StationAiBrain";
|
||||
private readonly ProtoId<ChatNotificationPrototype> _downloadChatNotificationPrototype = "IntellicardDownload";
|
||||
|
||||
private const float MaxVisionMultiplier = 5f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -102,10 +104,12 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<StationAiCoreComponent, EntInsertedIntoContainerMessage>(OnAiInsert);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, EntRemovedFromContainerMessage>(OnAiRemove);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, MapInitEvent>(OnAiMapInit);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, ComponentShutdown>(OnAiShutdown);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, PowerChangedEvent>(OnCorePower);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, GetVerbsEvent<Verb>>(OnCoreVerbs);
|
||||
|
||||
SubscribeLocalEvent<StationAiCoreComponent, BreakageEventArgs>(OnBroken);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, RepairedEvent>(OnRepaired);
|
||||
}
|
||||
|
||||
private void OnCoreVerbs(Entity<StationAiCoreComponent> ent, ref GetVerbsEvent<Verb> args)
|
||||
@@ -137,7 +141,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("station-ai-customization-menu"),
|
||||
Act = () => _uiSystem.TryOpenUi(ent.Owner, StationAiCustomizationUiKey.Key, insertedAi),
|
||||
Act = () => _uiSystem.TryOpenUi(ent.Owner, StationAiCustomizationUiKey.Key, insertedAi.Value),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/emotes.svg.192dpi.png")),
|
||||
});
|
||||
}
|
||||
@@ -271,8 +275,8 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
if (!TryComp(args.Used, out IntellicardComponent? intelliComp))
|
||||
return;
|
||||
|
||||
var cardHasAi = _slots.CanEject(ent.Owner, args.User, ent.Comp.Slot);
|
||||
var coreHasAi = _slots.CanEject(args.Target.Value, args.User, targetHolder.Slot);
|
||||
var cardHasAi = ent.Comp.Slot.Item != null;
|
||||
var coreHasAi = targetHolder.Slot.Item != null;
|
||||
|
||||
if (cardHasAi && coreHasAi)
|
||||
{
|
||||
@@ -290,7 +294,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
if (TryGetHeld((args.Target.Value, targetHolder), out var held))
|
||||
{
|
||||
var ev = new ChatNotificationEvent(_downloadChatNotificationPrototype, args.Used, args.User);
|
||||
RaiseLocalEvent(held, ref ev);
|
||||
RaiseLocalEvent(held.Value, ref ev);
|
||||
}
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, cardHasAi ? intelliComp.UploadTime : intelliComp.DownloadTime, new IntellicardDoAfterEvent(), args.Target, ent.Owner)
|
||||
@@ -298,7 +302,8 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
NeedHand = true,
|
||||
BreakOnDropItem = true
|
||||
BreakOnDropItem = true,
|
||||
AttemptFrequency = AttemptFrequency.EveryTick,
|
||||
};
|
||||
|
||||
_doAfter.TryStartDoAfter(doAfterArgs);
|
||||
@@ -327,7 +332,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
private void OnHolderMapInit(Entity<StationAiHolderComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
UpdateAppearance(ent.Owner);
|
||||
UpdateAppearance((ent.Owner, ent.Comp));
|
||||
}
|
||||
|
||||
private void OnAiShutdown(Entity<StationAiCoreComponent> ent, ref ComponentShutdown args)
|
||||
@@ -342,24 +347,32 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
private void OnCorePower(Entity<StationAiCoreComponent> ent, ref PowerChangedEvent args)
|
||||
{
|
||||
// TODO: I think in 13 they just straightup die so maybe implement that
|
||||
if (args.Powered)
|
||||
if (!args.Powered)
|
||||
{
|
||||
if (!SetupEye(ent))
|
||||
return;
|
||||
|
||||
AttachEye(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearEye(ent);
|
||||
KillHeldAi(ent);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAiMapInit(Entity<StationAiCoreComponent> ent, ref MapInitEvent args)
|
||||
private void OnBroken(Entity<StationAiCoreComponent> ent, ref BreakageEventArgs args)
|
||||
{
|
||||
SetupEye(ent);
|
||||
AttachEye(ent);
|
||||
KillHeldAi(ent);
|
||||
|
||||
if (TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
_appearance.SetData(ent, StationAiVisuals.Broken, true, appearance);
|
||||
}
|
||||
|
||||
private void OnRepaired(Entity<StationAiCoreComponent> ent, ref RepairedEvent args)
|
||||
{
|
||||
if (TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
_appearance.SetData(ent, StationAiVisuals.Broken, false, appearance);
|
||||
}
|
||||
|
||||
public virtual void KillHeldAi(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (TryGetHeld((ent.Owner, ent.Comp), out var held))
|
||||
{
|
||||
_mobState.ChangeMobState(held.Value, MobState.Dead);
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchRemoteEntityMode(Entity<StationAiCoreComponent?> entity, bool isRemote)
|
||||
@@ -395,7 +408,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
_eye.SetDrawFov(user.Value, !isRemote);
|
||||
}
|
||||
|
||||
private bool SetupEye(Entity<StationAiCoreComponent> ent, EntityCoordinates? coords = null)
|
||||
protected bool SetupEye(Entity<StationAiCoreComponent> ent, EntityCoordinates? coords = null)
|
||||
{
|
||||
if (_net.IsClient)
|
||||
return false;
|
||||
@@ -420,7 +433,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ClearEye(Entity<StationAiCoreComponent> ent)
|
||||
protected void ClearEye(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (_net.IsClient)
|
||||
return;
|
||||
@@ -428,9 +441,16 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
QueueDel(ent.Comp.RemoteEntity);
|
||||
ent.Comp.RemoteEntity = null;
|
||||
Dirty(ent);
|
||||
|
||||
if (TryGetHeld((ent, ent.Comp), out var held) &&
|
||||
TryComp(held, out EyeComponent? eyeComp))
|
||||
{
|
||||
_eye.SetDrawFov(held.Value, true, eyeComp);
|
||||
_eye.SetTarget(held.Value, null, eyeComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachEye(Entity<StationAiCoreComponent> ent)
|
||||
protected void AttachEye(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (ent.Comp.RemoteEntity == null)
|
||||
return;
|
||||
@@ -467,7 +487,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
return container.ContainedEntities[0];
|
||||
}
|
||||
|
||||
private void OnAiInsert(Entity<StationAiCoreComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
protected virtual void OnAiInsert(Entity<StationAiCoreComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != StationAiCoreComponent.Container)
|
||||
return;
|
||||
@@ -475,17 +495,21 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
ClearEye(ent);
|
||||
ent.Comp.Remote = true;
|
||||
SetupEye(ent);
|
||||
|
||||
// Just so text and the likes works properly
|
||||
_metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName);
|
||||
|
||||
AttachEye(ent);
|
||||
if (SetupEye(ent))
|
||||
AttachEye(ent);
|
||||
}
|
||||
|
||||
private void OnAiRemove(Entity<StationAiCoreComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
protected virtual void OnAiRemove(Entity<StationAiCoreComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != StationAiCoreComponent.Container)
|
||||
return;
|
||||
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
@@ -506,26 +530,49 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
ClearEye(ent);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(Entity<StationAiHolderComponent?> entity)
|
||||
protected void UpdateAppearance(Entity<StationAiHolderComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity.Owner, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
// Todo: when AIs can die, add a check to see if the AI is in the 'dead' state
|
||||
var state = StationAiState.Empty;
|
||||
|
||||
if (_containers.TryGetContainer(entity.Owner, StationAiHolderComponent.Container, out var container) && container.Count > 0)
|
||||
state = StationAiState.Occupied;
|
||||
|
||||
// If the entity is a station AI core, attempt to customize its appearance
|
||||
if (TryComp<StationAiCoreComponent>(entity, out var stationAiCore))
|
||||
// Get what visual state the held AI holder is in
|
||||
if (TryGetHeld(entity, out var stationAi) &&
|
||||
TryComp<StationAiCustomizationComponent>(stationAi, out var customization))
|
||||
{
|
||||
CustomizeAppearance((entity, stationAiCore), state);
|
||||
state = customization.State;
|
||||
}
|
||||
|
||||
// If the entity is not an AI core, let generic visualizers handle the appearance update
|
||||
if (!TryComp<StationAiCoreComponent>(entity, out var stationAiCore))
|
||||
{
|
||||
_appearance.SetData(entity.Owner, StationAiVisualLayers.Icon, state);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise let generic visualizers handle the appearance update
|
||||
_appearance.SetData(entity.Owner, StationAiVisualState.Key, state);
|
||||
// The AI core is empty
|
||||
if (state == StationAiState.Empty)
|
||||
{
|
||||
_appearance.RemoveData(entity.Owner, StationAiVisualLayers.Icon);
|
||||
return;
|
||||
}
|
||||
|
||||
// The AI core is rebooting
|
||||
if (state == StationAiState.Rebooting)
|
||||
{
|
||||
var rebootingData = new PrototypeLayerData()
|
||||
{
|
||||
RsiPath = _stationAiRebooting.RsiPath.ToString(),
|
||||
State = _stationAiRebooting.RsiState,
|
||||
};
|
||||
|
||||
_appearance.SetData(entity.Owner, StationAiVisualLayers.Icon, rebootingData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise attempt to set the AI core's appearance
|
||||
CustomizeAppearance((entity, stationAiCore), state);
|
||||
}
|
||||
|
||||
public virtual bool SetVisionEnabled(Entity<StationAiVisionComponent> entity, bool enabled, bool announce = false)
|
||||
@@ -573,15 +620,16 @@ public sealed partial class JumpToCoreEvent : InstantActionEvent
|
||||
public sealed partial class IntellicardDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiVisualState : byte
|
||||
public enum StationAiVisualLayers : byte
|
||||
{
|
||||
Key,
|
||||
Base,
|
||||
Icon,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiSpriteState : byte
|
||||
public enum StationAiVisuals : byte
|
||||
{
|
||||
Key,
|
||||
Broken,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -590,5 +638,6 @@ public enum StationAiState : byte
|
||||
Empty,
|
||||
Occupied,
|
||||
Dead,
|
||||
Rebooting,
|
||||
Hologram,
|
||||
}
|
||||
|
||||
@@ -38,11 +38,19 @@ public sealed partial class StationAiCoreComponent : Component
|
||||
[DataField(readOnly: true)]
|
||||
public EntProtoId? PhysicalEntityProto = "StationAiHoloLocal";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the container slot that holds the inhabiting AI's mind
|
||||
/// </summary>
|
||||
public const string Container = "station_ai_mind_slot";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the container slot that holds the 'brain' used to construct the AI core
|
||||
/// </summary>
|
||||
public const string BrainContainer = "station_ai_brain_slot";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised on a station AI 'eye' that is being replaced with a new one
|
||||
/// This event is raised on a station AI 'eye' that is being replaced with a new one
|
||||
/// </summary>
|
||||
/// <param name="NewRemoteEntity">The entity UID of the replacement entity</param>
|
||||
[ByRefEvent]
|
||||
|
||||
@@ -15,6 +15,12 @@ public sealed partial class StationAiCustomizationComponent : Component
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<ProtoId<StationAiCustomizationGroupPrototype>, ProtoId<StationAiCustomizationPrototype>> ProtoIds = new();
|
||||
|
||||
/// <summary>
|
||||
/// The current visual state of the associated entity.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public StationAiState State = StationAiState.Occupied;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,6 +39,12 @@ public sealed class StationAiCustomizationMessage : BoundUserInterfaceMessage
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the station AI customization visual state changes
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record StationAiCustomizationStateChanged(StationAiState NewState);
|
||||
|
||||
/// <summary>
|
||||
/// Key for opening the station AI customization UI
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Silicons.StationAi;
|
||||
|
||||
/// <summary>
|
||||
/// This component holds data needed for AI Restoration Consoles to function.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
[Access(typeof(SharedStationAiFixerConsoleSystem))]
|
||||
public sealed partial class StationAiFixerConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines how long a repair takes to complete (in seconds).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan RepairDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Determines how long a purge takes to complete (in seconds).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan PurgeDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// The number of stages that a console action (repair or purge)
|
||||
/// progresses through before it concludes. Each stage has an equal
|
||||
/// duration. The appearance data of the entity is updated with
|
||||
/// each new stage reached.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int ActionStageCount = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the current action commenced.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan ActionStartTime = TimeSpan.FromSeconds(0);
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the current action will end.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan ActionEndTime = TimeSpan.FromSeconds(0);
|
||||
|
||||
/// <summary>
|
||||
/// The type of action that is currently in progress.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public StationAiFixerConsoleAction ActionType = StationAiFixerConsoleAction.None;
|
||||
|
||||
/// <summary>
|
||||
/// The target of the current action.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ActionTarget;
|
||||
|
||||
/// <summary>
|
||||
/// The current stage of the action in progress.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int CurrentActionStage;
|
||||
|
||||
/// <summary>
|
||||
/// Sound clip that is played when a repair is completed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? RepairFinishedSound = new SoundPathSpecifier("/Audio/Items/beep.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound clip that is played when a repair is completed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? PurgeFinishedSound = new SoundPathSpecifier("/Audio/Machines/beep.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The name of the console slot which is used to contain station AI holders.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string StationAiHolderSlot = "station_ai_holder";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the station AI holder slot which actually contains the station AI.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string StationAiMindSlot = "station_ai_mind_slot";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Message sent from the server to the client to update the UI of AI Restoration Consoles.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StationAiFixerConsoleBoundUserInterfaceState : BoundUserInterfaceState;
|
||||
|
||||
/// <summary>
|
||||
/// Message sent from the client to the server to handle player UI inputs from AI Restoration Consoles.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class StationAiFixerConsoleMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public StationAiFixerConsoleAction Action;
|
||||
|
||||
public StationAiFixerConsoleMessage(StationAiFixerConsoleAction action)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Potential actions that AI Restoration Consoles can perform.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiFixerConsoleAction
|
||||
{
|
||||
None,
|
||||
Eject,
|
||||
Repair,
|
||||
Purge,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appearance keys for AI Restoration Consoles.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiFixerConsoleVisuals : byte
|
||||
{
|
||||
Key,
|
||||
ActionProgress,
|
||||
MobState,
|
||||
RepairProgress,
|
||||
PurgeProgress,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interactable UI key for AI Restoration Consoles.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum StationAiFixerConsoleUiKey
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user