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:
@@ -8,6 +8,8 @@ using Content.Shared.Chat.TypingIndicator;
|
||||
using Content.Shared.Holopad;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Content.Shared.Speech;
|
||||
@@ -38,6 +40,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
private float _updateTimer = 1.0f;
|
||||
private const float UpdateTime = 1.0f;
|
||||
@@ -77,6 +80,8 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
SubscribeLocalEvent<HolopadComponent, EntRemovedFromContainerMessage>(OnAiRemove);
|
||||
SubscribeLocalEvent<HolopadComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<HolopadComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<HolopadUserComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
}
|
||||
|
||||
#region: Holopad UI bound user interface messages
|
||||
@@ -226,7 +231,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (!_stationAiSystem.TryGetHeld((receiver, receiverStationAiCore), out var insertedAi))
|
||||
continue;
|
||||
|
||||
if (_userInterfaceSystem.TryOpenUi(receiverUid, HolopadUiKey.AiRequestWindow, insertedAi))
|
||||
if (_userInterfaceSystem.TryOpenUi(receiverUid, HolopadUiKey.AiRequestWindow, insertedAi.Value))
|
||||
LinkHolopadToUser(entity, args.Actor);
|
||||
}
|
||||
|
||||
@@ -446,6 +451,17 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
UpdateHolopadControlLockoutStartTime(entity);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<HolopadUserComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (!HasComp<StationAiHeldComponent>(ent))
|
||||
return;
|
||||
|
||||
foreach (var holopad in ent.Comp.LinkedHolopads)
|
||||
{
|
||||
ShutDownHolopad(holopad);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -605,25 +621,23 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
if (entity.Comp.Hologram != null)
|
||||
DeleteHologram(entity.Comp.Hologram.Value, entity);
|
||||
|
||||
if (entity.Comp.User != null)
|
||||
// Check if the associated holopad user is an AI
|
||||
if (HasComp<StationAiHeldComponent>(entity.Comp.User) &&
|
||||
_stationAiSystem.TryGetCore(entity.Comp.User.Value, out var stationAiCore))
|
||||
{
|
||||
// Check if the associated holopad user is an AI
|
||||
if (TryComp<StationAiHeldComponent>(entity.Comp.User, out var stationAiHeld) &&
|
||||
_stationAiSystem.TryGetCore(entity.Comp.User.Value, out var stationAiCore))
|
||||
// Return the AI eye to free roaming
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore, true);
|
||||
|
||||
// If the AI core is still broadcasting, end its calls
|
||||
if (TryComp<TelephoneComponent>(stationAiCore, out var stationAiCoreTelephone) &&
|
||||
_telephoneSystem.IsTelephoneEngaged((stationAiCore.Owner, stationAiCoreTelephone)))
|
||||
{
|
||||
// Return the AI eye to free roaming
|
||||
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore, true);
|
||||
|
||||
// If the AI core is still broadcasting, end its calls
|
||||
if (entity.Owner != stationAiCore.Owner &&
|
||||
TryComp<TelephoneComponent>(stationAiCore, out var stationAiCoreTelephone) &&
|
||||
_telephoneSystem.IsTelephoneEngaged((stationAiCore.Owner, stationAiCoreTelephone)))
|
||||
{
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore.Owner, stationAiCoreTelephone));
|
||||
}
|
||||
_telephoneSystem.EndTelephoneCalls((stationAiCore.Owner, stationAiCoreTelephone));
|
||||
}
|
||||
|
||||
UnlinkHolopadFromUser(entity, entity.Comp.User.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnlinkHolopadFromUser(entity, entity.Comp.User);
|
||||
}
|
||||
|
||||
Dirty(entity);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Mind;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Server.Player;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Server.Silicons.StationAi;
|
||||
|
||||
public sealed partial class StationAiFixerConsoleSystem : SharedStationAiFixerConsoleSystem
|
||||
{
|
||||
[Dependency] private readonly EuiManager _eui = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
protected override void FinalizeAction(Entity<StationAiFixerConsoleComponent> ent)
|
||||
{
|
||||
if (IsActionInProgress(ent) && ent.Comp.ActionTarget != null)
|
||||
{
|
||||
switch (ent.Comp.ActionType)
|
||||
{
|
||||
case StationAiFixerConsoleAction.Repair:
|
||||
|
||||
// Send message to disembodied player that they are being revived
|
||||
if (_mind.TryGetMind(ent.Comp.ActionTarget.Value, out _, out var mind) &&
|
||||
mind.IsVisitingEntity &&
|
||||
_player.TryGetSessionById(mind.UserId, out var session))
|
||||
{
|
||||
_eui.OpenEui(new ReturnToBodyEui(mind, _mind, _player), session);
|
||||
_popup.PopupEntity(Loc.GetString("station-ai-fixer-console-repair-finished"), ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("station-ai-fixer-console-repair-successful"), ent);
|
||||
}
|
||||
|
||||
// TODO: make predicted once a user is not required
|
||||
if (ent.Comp.RepairFinishedSound != null)
|
||||
{
|
||||
_audio.PlayPvs(ent.Comp.RepairFinishedSound, ent);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case StationAiFixerConsoleAction.Purge:
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("station-ai-fixer-console-purge-successful"), ent);
|
||||
|
||||
// TODO: make predicted once a user is not required
|
||||
if (ent.Comp.PurgeFinishedSound != null)
|
||||
{
|
||||
_audio.PlayPvs(ent.Comp.PurgeFinishedSound, ent);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
base.FinalizeAction(ent);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,34 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Spawners.EntitySystems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Rejuvenate;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Content.Shared.Speech.Components;
|
||||
using Content.Shared.StationAi;
|
||||
using Content.Shared.Turrets;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -16,19 +40,300 @@ public sealed class StationAiSystem : SharedStationAiSystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly RoleSystem _roles = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly BatterySystem _battery = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popups = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
private readonly HashSet<Entity<StationAiCoreComponent>> _stationAiCores = new();
|
||||
|
||||
private readonly ProtoId<ChatNotificationPrototype> _turretIsAttackingChatNotificationPrototype = "TurretIsAttacking";
|
||||
private readonly ProtoId<ChatNotificationPrototype> _aiWireSnippedChatNotificationPrototype = "AiWireSnipped";
|
||||
private readonly ProtoId<ChatNotificationPrototype> _aiLosingPowerChatNotificationPrototype = "AiLosingPower";
|
||||
private readonly ProtoId<ChatNotificationPrototype> _aiCriticalPowerChatNotificationPrototype = "AiCriticalPower";
|
||||
|
||||
private readonly ProtoId<JobPrototype> _stationAiJob = "StationAi";
|
||||
private readonly EntProtoId _stationAiBrain = "StationAiBrain";
|
||||
|
||||
private readonly ProtoId<AlertPrototype> _batteryAlert = "BorgBattery";
|
||||
private readonly ProtoId<AlertPrototype> _damageAlert = "BorgHealth";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StationAiCoreComponent, AfterConstructionChangeEntityEvent>(AfterConstructionChangeEntity);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, ContainerSpawnEvent>(OnContainerSpawn);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, ApcPowerReceiverBatteryChangedEvent>(OnApcBatteryChanged);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, ChargeChangedEvent>(OnChargeChanged);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, DestructionEventArgs>(OnDestruction);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, DoAfterAttemptEvent<IntellicardDoAfterEvent>>(OnDoAfterAttempt);
|
||||
SubscribeLocalEvent<StationAiCoreComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
|
||||
SubscribeLocalEvent<ExpandICChatRecipientsEvent>(OnExpandICChatRecipients);
|
||||
SubscribeLocalEvent<StationAiTurretComponent, AmmoShotEvent>(OnAmmoShot);
|
||||
}
|
||||
|
||||
private void AfterConstructionChangeEntity(Entity<StationAiCoreComponent> ent, ref AfterConstructionChangeEntityEvent args)
|
||||
{
|
||||
if (!_container.TryGetContainer(ent, StationAiCoreComponent.BrainContainer, out var container) ||
|
||||
container.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var brain = container.ContainedEntities[0];
|
||||
|
||||
if (_mind.TryGetMind(brain, out var mindId, out var mind))
|
||||
{
|
||||
// Found an existing mind to transfer into the AI core
|
||||
var aiBrain = Spawn(_stationAiBrain, Transform(ent.Owner).Coordinates);
|
||||
_roles.MindAddJobRole(mindId, mind, false, _stationAiJob);
|
||||
_mind.TransferTo(mindId, aiBrain);
|
||||
|
||||
if (!TryComp<StationAiHolderComponent>(ent, out var targetHolder) ||
|
||||
!_slots.TryInsert(ent, targetHolder.Slot, aiBrain, null))
|
||||
{
|
||||
QueueDel(aiBrain);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We should consider keeping the borg brain inside the AI core.
|
||||
// When the core is destroyed, the station AI can be transferred into the brain,
|
||||
// then dropped on the ground. The deceased AI can then be revived later,
|
||||
// instead of being lost forever.
|
||||
QueueDel(brain);
|
||||
}
|
||||
|
||||
private void OnContainerSpawn(Entity<StationAiCoreComponent> ent, ref ContainerSpawnEvent args)
|
||||
{
|
||||
// Ensure that players that recently joined the round will spawn
|
||||
// into an AI core that has a full battery and full integrity.
|
||||
if (TryComp<BatteryComponent>(ent, out var battery))
|
||||
{
|
||||
_battery.SetCharge(ent, battery.MaxCharge);
|
||||
}
|
||||
|
||||
if (TryComp<DamageableComponent>(ent, out var damageable))
|
||||
{
|
||||
_damageable.SetAllDamage(ent, damageable, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAiInsert(Entity<StationAiCoreComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
base.OnAiInsert(ent, ref args);
|
||||
|
||||
UpdateBatteryAlert(ent);
|
||||
UpdateCoreIntegrityAlert(ent);
|
||||
UpdateDamagedAccent(ent);
|
||||
}
|
||||
|
||||
protected override void OnAiRemove(Entity<StationAiCoreComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
base.OnAiRemove(ent, ref args);
|
||||
|
||||
_alerts.ClearAlert(args.Entity, _batteryAlert);
|
||||
_alerts.ClearAlert(args.Entity, _damageAlert);
|
||||
|
||||
if (TryComp<DamagedSiliconAccentComponent>(args.Entity, out var accent))
|
||||
{
|
||||
accent.OverrideChargeLevel = null;
|
||||
accent.OverrideTotalDamage = null;
|
||||
accent.DamageAtMaxCorruption = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMobStateChanged(Entity<StationAiCustomizationComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Alive)
|
||||
{
|
||||
SetStationAiState(ent, StationAiState.Dead);
|
||||
return;
|
||||
}
|
||||
|
||||
var state = StationAiState.Rebooting;
|
||||
|
||||
if (_mind.TryGetMind(ent, out var _, out var mind) && !mind.IsVisitingEntity)
|
||||
{
|
||||
state = StationAiState.Occupied;
|
||||
}
|
||||
|
||||
if (TryGetCore(ent, out var aiCore) && aiCore.Comp != null)
|
||||
{
|
||||
var aiCoreEnt = (aiCore.Owner, aiCore.Comp);
|
||||
|
||||
if (SetupEye(aiCoreEnt))
|
||||
AttachEye(aiCoreEnt);
|
||||
}
|
||||
|
||||
SetStationAiState(ent, state);
|
||||
}
|
||||
|
||||
private void OnDestruction(Entity<StationAiCoreComponent> ent, ref DestructionEventArgs args)
|
||||
{
|
||||
var station = _station.GetOwningStation(ent);
|
||||
|
||||
if (station == null)
|
||||
return;
|
||||
|
||||
if (!HasComp<ContainerSpawnPointComponent>(ent))
|
||||
return;
|
||||
|
||||
// If the destroyed core could act as a player spawn point,
|
||||
// reduce the number of available AI jobs by one
|
||||
_stationJobs.TryAdjustJobSlot(station.Value, _stationAiJob, -1, false, true);
|
||||
}
|
||||
|
||||
private void OnApcBatteryChanged(Entity<StationAiCoreComponent> ent, ref ApcPowerReceiverBatteryChangedEvent args)
|
||||
{
|
||||
if (!args.Enabled)
|
||||
return;
|
||||
|
||||
if (!TryGetHeld((ent.Owner, ent.Comp), out var held))
|
||||
return;
|
||||
|
||||
var ev = new ChatNotificationEvent(_aiLosingPowerChatNotificationPrototype, ent);
|
||||
RaiseLocalEvent(held.Value, ref ev);
|
||||
}
|
||||
|
||||
private void OnChargeChanged(Entity<StationAiCoreComponent> entity, ref ChargeChangedEvent args)
|
||||
{
|
||||
UpdateBatteryAlert(entity);
|
||||
UpdateDamagedAccent(entity);
|
||||
}
|
||||
|
||||
private void OnDamageChanged(Entity<StationAiCoreComponent> entity, ref DamageChangedEvent args)
|
||||
{
|
||||
UpdateCoreIntegrityAlert(entity);
|
||||
UpdateDamagedAccent(entity);
|
||||
}
|
||||
|
||||
private void UpdateDamagedAccent(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (!TryGetHeld((ent.Owner, ent.Comp), out var held))
|
||||
return;
|
||||
|
||||
if (!TryComp<DamagedSiliconAccentComponent>(held, out var accent))
|
||||
return;
|
||||
|
||||
if (TryComp<BatteryComponent>(ent, out var battery))
|
||||
accent.OverrideChargeLevel = battery.CurrentCharge / battery.MaxCharge;
|
||||
|
||||
if (TryComp<DamageableComponent>(ent, out var damageable))
|
||||
accent.OverrideTotalDamage = damageable.TotalDamage;
|
||||
|
||||
if (TryComp<DestructibleComponent>(ent, out var destructible))
|
||||
accent.DamageAtMaxCorruption = _destructible.DestroyedAt(ent, destructible);
|
||||
|
||||
Dirty(held.Value, accent);
|
||||
}
|
||||
|
||||
private void UpdateBatteryAlert(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (!TryComp<BatteryComponent>(ent, out var battery))
|
||||
return;
|
||||
|
||||
if (!TryGetHeld((ent.Owner, ent.Comp), out var held))
|
||||
return;
|
||||
|
||||
if (!_proto.TryIndex(_batteryAlert, out var proto))
|
||||
return;
|
||||
|
||||
var chargePercent = battery.CurrentCharge / battery.MaxCharge;
|
||||
var chargeLevel = Math.Round(chargePercent * proto.MaxSeverity);
|
||||
|
||||
_alerts.ShowAlert(held.Value, _batteryAlert, (short)Math.Clamp(chargeLevel, 0, proto.MaxSeverity));
|
||||
|
||||
if (TryComp<ApcPowerReceiverBatteryComponent>(ent, out var apcBattery) &&
|
||||
apcBattery.Enabled &&
|
||||
chargePercent < 0.2)
|
||||
{
|
||||
var ev = new ChatNotificationEvent(_aiCriticalPowerChatNotificationPrototype, ent);
|
||||
RaiseLocalEvent(held.Value, ref ev);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCoreIntegrityAlert(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
if (!TryComp<DamageableComponent>(ent, out var damageable))
|
||||
return;
|
||||
|
||||
if (!TryComp<DestructibleComponent>(ent, out var destructible))
|
||||
return;
|
||||
|
||||
if (!TryGetHeld((ent.Owner, ent.Comp), out var held))
|
||||
return;
|
||||
|
||||
if (!_proto.TryIndex(_damageAlert, out var proto))
|
||||
return;
|
||||
|
||||
var damagePercent = damageable.TotalDamage / _destructible.DestroyedAt(ent, destructible);
|
||||
var damageLevel = Math.Round(damagePercent.Float() * proto.MaxSeverity);
|
||||
|
||||
_alerts.ShowAlert(held.Value, _damageAlert, (short)Math.Clamp(damageLevel, 0, proto.MaxSeverity));
|
||||
}
|
||||
|
||||
private void OnDoAfterAttempt(Entity<StationAiCoreComponent> ent, ref DoAfterAttemptEvent<IntellicardDoAfterEvent> args)
|
||||
{
|
||||
if (TryGetHeld((ent.Owner, ent.Comp), out _))
|
||||
return;
|
||||
|
||||
// Prevent AIs from being uploaded into an unpowered or broken AI core.
|
||||
|
||||
if (TryComp<ApcPowerReceiverComponent>(ent, out var apcPower) && !apcPower.Powered)
|
||||
{
|
||||
_popups.PopupEntity(Loc.GetString("station-ai-has-no-power-for-upload"), ent, args.Event.User);
|
||||
args.Cancel();
|
||||
}
|
||||
else if (TryComp<DestructibleComponent>(ent, out var destructible) && destructible.IsBroken)
|
||||
{
|
||||
_popups.PopupEntity(Loc.GetString("station-ai-is-too-damaged-for-upload"), ent, args.Event.User);
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public override void KillHeldAi(Entity<StationAiCoreComponent> ent)
|
||||
{
|
||||
base.KillHeldAi(ent);
|
||||
|
||||
if (TryGetHeld((ent.Owner, ent.Comp), out var held) &&
|
||||
_mind.TryGetMind(held.Value, out var mindId, out var mind))
|
||||
{
|
||||
_ghost.OnGhostAttempt(mindId, canReturnGlobal: true, mind: mind);
|
||||
RemComp<StationAiOverlayComponent>(held.Value);
|
||||
}
|
||||
|
||||
ClearEye(ent);
|
||||
}
|
||||
|
||||
private void OnRejuvenate(Entity<StationAiCoreComponent> ent, ref RejuvenateEvent args)
|
||||
{
|
||||
if (TryGetHeld((ent.Owner, ent.Comp), out var held))
|
||||
{
|
||||
_mobState.ChangeMobState(held.Value, MobState.Alive);
|
||||
EnsureComp<StationAiOverlayComponent>(held.Value);
|
||||
}
|
||||
|
||||
if (TryComp<StationAiHolderComponent>(ent, out var holder))
|
||||
{
|
||||
_appearance.SetData(ent, StationAiVisuals.Broken, false);
|
||||
UpdateAppearance((ent, holder));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExpandICChatRecipients(ExpandICChatRecipientsEvent ev)
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
@@ -147,7 +452,7 @@ public sealed class StationAiSystem : SharedStationAiSystem
|
||||
if (!TryGetHeld((stationAiCore, stationAiCore.Comp), out var insertedAi))
|
||||
continue;
|
||||
|
||||
hashSet.Add(insertedAi);
|
||||
hashSet.Add(insertedAi.Value);
|
||||
}
|
||||
|
||||
return hashSet;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -87,6 +86,9 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
|
||||
if (!_container.Insert(args.SpawnResult.Value, container, containerXform: xform))
|
||||
continue;
|
||||
|
||||
var ev = new ContainerSpawnEvent(args.SpawnResult.Value);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,3 +96,9 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
|
||||
args.SpawnResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a container when a player is spawned into it.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ContainerSpawnEvent(EntityUid Player);
|
||||
|
||||
Reference in New Issue
Block a user