2023-08-28 16:53:24 -07:00
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
2023-06-28 05:02:06 -07:00
using Content.Server.Administration.Logs ;
2023-11-18 04:18:37 +01:00
using Content.Server.Administration.Managers ;
2023-06-28 05:02:06 -07:00
using Content.Server.Ame.Components ;
using Content.Server.Chat.Managers ;
using Content.Server.NodeContainer ;
using Content.Server.Power.Components ;
2024-02-29 22:44:28 +01:00
using Content.Shared.Ame.Components ;
using Content.Shared.Containers.ItemSlots ;
2023-06-28 05:02:06 -07:00
using Content.Shared.Database ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Mind.Components ;
2023-06-28 05:02:06 -07:00
using Robust.Server.GameObjects ;
using Robust.Shared.Audio ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2023-06-28 05:02:06 -07:00
using Robust.Shared.Containers ;
2023-11-18 04:18:37 +01:00
using Robust.Shared.Player ;
2023-06-28 05:02:06 -07:00
using Robust.Shared.Timing ;
namespace Content.Server.Ame.EntitySystems ;
public sealed class AmeControllerSystem : EntitySystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2023-11-18 04:18:37 +01:00
[Dependency] private readonly IAdminManager _adminManager = default ! ;
2023-06-28 05:02:06 -07:00
[Dependency] private readonly IChatManager _chatManager = default ! ;
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
[Dependency] private readonly AppearanceSystem _appearanceSystem = default ! ;
[Dependency] private readonly SharedAudioSystem _audioSystem = default ! ;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default ! ;
2024-02-29 22:44:28 +01:00
[Dependency] private readonly ItemSlotsSystem _itemSlots = default ! ;
2023-06-28 05:02:06 -07:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-02-29 22:44:28 +01:00
SubscribeLocalEvent < AmeControllerComponent , ComponentInit > ( OnInit ) ;
SubscribeLocalEvent < AmeControllerComponent , ComponentRemove > ( OnRemove ) ;
SubscribeLocalEvent < AmeControllerComponent , EntInsertedIntoContainerMessage > ( OnItemSlotChanged ) ;
SubscribeLocalEvent < AmeControllerComponent , EntRemovedFromContainerMessage > ( OnItemSlotChanged ) ;
2023-06-28 05:02:06 -07:00
SubscribeLocalEvent < AmeControllerComponent , PowerChangedEvent > ( OnPowerChanged ) ;
SubscribeLocalEvent < AmeControllerComponent , UiButtonPressedMessage > ( OnUiButtonPressed ) ;
}
2024-02-29 22:44:28 +01:00
private void OnInit ( EntityUid uid , AmeControllerComponent component , ComponentInit args )
{
_itemSlots . AddItemSlot ( uid , SharedAmeControllerComponent . FuelSlotId , component . FuelSlot ) ;
UpdateUi ( uid , component ) ;
}
2023-06-28 05:02:06 -07:00
public override void Update ( float frameTime )
{
var curTime = _gameTiming . CurTime ;
var query = EntityQueryEnumerator < AmeControllerComponent , NodeContainerComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var controller , out var nodes ) )
{
if ( controller . NextUpdate < = curTime )
UpdateController ( uid , curTime , controller , nodes ) ;
2023-10-18 19:46:32 +02:00
else if ( controller . NextUIUpdate < = curTime )
UpdateUi ( uid , controller ) ;
2023-06-28 05:02:06 -07:00
}
}
2024-02-29 22:44:28 +01:00
private void OnRemove ( EntityUid uid , AmeControllerComponent component , ComponentRemove args )
{
_itemSlots . RemoveItemSlot ( uid , component . FuelSlot ) ;
}
private void OnItemSlotChanged ( EntityUid uid , AmeControllerComponent component , ContainerModifiedMessage args )
{
if ( ! component . Initialized )
return ;
if ( args . Container . ID ! = component . FuelSlot . ID )
return ;
UpdateUi ( uid , component ) ;
}
2023-06-28 05:02:06 -07:00
private void UpdateController ( EntityUid uid , TimeSpan curTime , AmeControllerComponent ? controller = null , NodeContainerComponent ? nodes = null )
{
if ( ! Resolve ( uid , ref controller ) )
return ;
controller . LastUpdate = curTime ;
controller . NextUpdate = curTime + controller . UpdatePeriod ;
2023-10-18 19:46:32 +02:00
// update the UI regardless of other factors to update the power readings
UpdateUi ( uid , controller ) ;
2023-06-28 05:02:06 -07:00
if ( ! controller . Injecting )
return ;
2024-02-29 22:44:28 +01:00
2023-06-28 05:02:06 -07:00
if ( ! TryGetAMENodeGroup ( uid , out var group , nodes ) )
return ;
2024-02-29 22:44:28 +01:00
if ( TryComp < AmeFuelContainerComponent > ( controller . FuelSlot . Item , out var fuelContainer ) )
2023-06-28 05:02:06 -07:00
{
2023-09-24 22:39:49 +02:00
// if the jar is empty shut down the AME
2024-02-29 22:44:28 +01:00
if ( fuelContainer . FuelAmount < = 0 )
2023-09-24 22:39:49 +02:00
{
SetInjecting ( uid , false , null , controller ) ;
}
else
{
2024-02-29 22:44:28 +01:00
var availableInject = Math . Min ( controller . InjectionAmount , fuelContainer . FuelAmount ) ;
2023-09-24 22:39:49 +02:00
var powerOutput = group . InjectFuel ( availableInject , out var overloading ) ;
if ( TryComp < PowerSupplierComponent > ( uid , out var powerOutlet ) )
powerOutlet . MaxSupply = powerOutput ;
2024-02-29 22:44:28 +01:00
fuelContainer . FuelAmount - = availableInject ;
2023-09-24 22:39:49 +02:00
// only play audio if we actually had an injection
if ( availableInject > 0 )
_audioSystem . PlayPvs ( controller . InjectSound , uid , AudioParams . Default . WithVolume ( overloading ? 10f : 0f ) ) ;
UpdateUi ( uid , controller ) ;
}
2023-06-28 05:02:06 -07:00
}
controller . Stability = group . GetTotalStability ( ) ;
2023-09-24 22:39:49 +02:00
group . UpdateCoreVisuals ( ) ;
2023-06-28 05:02:06 -07:00
UpdateDisplay ( uid , controller . Stability , controller ) ;
if ( controller . Stability < = 0 )
group . ExplodeCores ( ) ;
}
public void UpdateUi ( EntityUid uid , AmeControllerComponent ? controller = null )
{
if ( ! Resolve ( uid , ref controller ) )
return ;
if ( ! _userInterfaceSystem . TryGetUi ( uid , AmeControllerUiKey . Key , out var bui ) )
return ;
var state = GetUiState ( uid , controller ) ;
2023-09-11 09:42:41 +10:00
_userInterfaceSystem . SetUiState ( bui , state ) ;
2023-10-18 19:46:32 +02:00
controller . NextUIUpdate = _gameTiming . CurTime + controller . UpdateUIPeriod ;
2023-06-28 05:02:06 -07:00
}
private AmeControllerBoundUserInterfaceState GetUiState ( EntityUid uid , AmeControllerComponent controller )
{
var powered = ! TryComp < ApcPowerReceiverComponent > ( uid , out var powerSource ) | | powerSource . Powered ;
2023-10-18 19:46:32 +02:00
var coreCount = 0 ;
// how much power can be produced at the current settings, in kW
// we don't use max. here since this is what is set in the Controller, not what the AME is actually producing
float targetedPowerSupply = 0 ;
if ( TryGetAMENodeGroup ( uid , out var group ) )
{
coreCount = group . CoreCount ;
targetedPowerSupply = group . CalculatePower ( controller . InjectionAmount , group . CoreCount ) / 1000 ;
}
// set current power statistics in kW
float currentPowerSupply = 0 ;
if ( TryComp < PowerSupplierComponent > ( uid , out var powerOutlet ) & & coreCount > 0 )
{
currentPowerSupply = powerOutlet . CurrentSupply / 1000 ;
}
2023-06-28 05:02:06 -07:00
2024-02-29 22:44:28 +01:00
var fuelContainerInSlot = controller . FuelSlot . Item ;
var hasFuelContainerInSlot = Exists ( fuelContainerInSlot ) ;
if ( ! hasFuelContainerInSlot | | ! TryComp < AmeFuelContainerComponent > ( fuelContainerInSlot , out var fuelContainer ) )
return new AmeControllerBoundUserInterfaceState ( powered ,
IsMasterController ( uid ) ,
false ,
hasFuelContainerInSlot ,
0 ,
controller . InjectionAmount ,
coreCount ,
currentPowerSupply ,
targetedPowerSupply ) ;
return new AmeControllerBoundUserInterfaceState ( powered ,
IsMasterController ( uid ) ,
controller . Injecting ,
hasFuelContainerInSlot ,
fuelContainer . FuelAmount ,
controller . InjectionAmount ,
coreCount ,
currentPowerSupply ,
targetedPowerSupply ) ;
2023-06-28 05:02:06 -07:00
}
private bool IsMasterController ( EntityUid uid )
{
return TryGetAMENodeGroup ( uid , out var group ) & & group . MasterController = = uid ;
}
private bool TryGetAMENodeGroup ( EntityUid uid , [ MaybeNullWhen ( false ) ] out AmeNodeGroup group , NodeContainerComponent ? nodes = null )
{
if ( ! Resolve ( uid , ref nodes ) )
{
group = null ;
return false ;
}
group = nodes . Nodes . Values
. Select ( node = > node . NodeGroup )
. OfType < AmeNodeGroup > ( )
. FirstOrDefault ( ) ;
return group ! = null ;
}
public void TryEject ( EntityUid uid , EntityUid ? user = null , AmeControllerComponent ? controller = null )
{
if ( ! Resolve ( uid , ref controller ) )
return ;
2024-02-29 22:44:28 +01:00
2023-06-28 05:02:06 -07:00
if ( controller . Injecting )
return ;
2024-02-29 22:44:28 +01:00
if ( ! Exists ( controller . FuelSlot . Item ) )
2023-06-28 05:02:06 -07:00
return ;
2024-02-29 22:44:28 +01:00
_itemSlots . TryEjectToHands ( uid , controller . FuelSlot , user ) ;
2023-06-28 05:02:06 -07:00
UpdateUi ( uid , controller ) ;
}
public void SetInjecting ( EntityUid uid , bool value , EntityUid ? user = null , AmeControllerComponent ? controller = null )
{
if ( ! Resolve ( uid , ref controller ) )
return ;
2024-02-29 22:44:28 +01:00
2023-06-28 05:02:06 -07:00
if ( controller . Injecting = = value )
return ;
controller . Injecting = value ;
2023-09-24 22:39:49 +02:00
UpdateDisplay ( uid , controller . Stability , controller ) ;
2023-06-28 05:02:06 -07:00
if ( ! value & & TryComp < PowerSupplierComponent > ( uid , out var powerOut ) )
powerOut . MaxSupply = 0 ;
UpdateUi ( uid , controller ) ;
2024-03-03 06:37:34 +01:00
_itemSlots . SetLock ( uid , controller . FuelSlot , value ) ;
2023-06-28 05:02:06 -07:00
// Logging
if ( ! HasComp < MindContainerComponent > ( user ) )
return ;
var humanReadableState = value ? "Inject" : "Not inject" ;
_adminLogger . Add ( LogType . Action , LogImpact . Extreme , $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to {humanReadableState}" ) ;
}
public void ToggleInjecting ( EntityUid uid , EntityUid ? user = null , AmeControllerComponent ? controller = null )
{
if ( ! Resolve ( uid , ref controller ) )
return ;
SetInjecting ( uid , ! controller . Injecting , user , controller ) ;
}
public void SetInjectionAmount ( EntityUid uid , int value , EntityUid ? user = null , AmeControllerComponent ? controller = null )
{
if ( ! Resolve ( uid , ref controller ) )
return ;
if ( controller . InjectionAmount = = value )
return ;
var oldValue = controller . InjectionAmount ;
controller . InjectionAmount = value ;
UpdateUi ( uid , controller ) ;
// Logging
if ( ! TryComp < MindContainerComponent > ( user , out var mindContainer ) )
return ;
var humanReadableState = controller . Injecting ? "Inject" : "Not inject" ;
_adminLogger . Add ( LogType . Action , LogImpact . Extreme , $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to inject {controller.InjectionAmount} while set to {humanReadableState}" ) ;
// Admin alert
var safeLimit = 0 ;
if ( TryGetAMENodeGroup ( uid , out var group ) )
safeLimit = group . CoreCount * 2 ;
if ( oldValue < = safeLimit & & value > safeLimit )
2023-11-18 04:18:37 +01:00
{
if ( _gameTiming . CurTime > controller . EffectCooldown )
{
_chatManager . SendAdminAlert ( user . Value , $"increased AME over safe limit to {controller.InjectionAmount}" ) ;
_audioSystem . PlayGlobal ( "/Audio/Misc/adminlarm.ogg" ,
Filter . Empty ( ) . AddPlayers ( _adminManager . ActiveAdmins ) , false , AudioParams . Default . WithVolume ( - 8f ) ) ;
controller . EffectCooldown = _gameTiming . CurTime + controller . CooldownDuration ;
}
}
2023-06-28 05:02:06 -07:00
}
public void AdjustInjectionAmount ( EntityUid uid , int delta , int min = 0 , int max = int . MaxValue , EntityUid ? user = null , AmeControllerComponent ? controller = null )
{
if ( Resolve ( uid , ref controller ) )
SetInjectionAmount ( uid , MathHelper . Clamp ( controller . InjectionAmount + delta , min , max ) , user , controller ) ;
}
private void UpdateDisplay ( EntityUid uid , int stability , AmeControllerComponent ? controller = null , AppearanceComponent ? appearance = null )
{
if ( ! Resolve ( uid , ref controller , ref appearance ) )
return ;
2023-09-24 22:39:49 +02:00
var ameControllerState = stability switch
{
< 10 = > AmeControllerState . Fuck ,
< 50 = > AmeControllerState . Critical ,
2024-02-01 09:40:57 +01:00
< 80 = > AmeControllerState . Warning ,
2023-09-24 22:39:49 +02:00
_ = > AmeControllerState . On ,
} ;
if ( ! controller . Injecting )
ameControllerState = AmeControllerState . Off ;
2023-06-28 05:02:06 -07:00
_appearanceSystem . SetData (
uid ,
AmeControllerVisuals . DisplayState ,
2023-09-24 22:39:49 +02:00
ameControllerState ,
2023-06-28 05:02:06 -07:00
appearance
) ;
}
private void OnPowerChanged ( EntityUid uid , AmeControllerComponent comp , ref PowerChangedEvent args )
{
UpdateUi ( uid , comp ) ;
}
private void OnUiButtonPressed ( EntityUid uid , AmeControllerComponent comp , UiButtonPressedMessage msg )
{
var user = msg . Session . AttachedEntity ;
if ( ! Exists ( user ) )
return ;
var needsPower = msg . Button switch
{
UiButton . Eject = > false ,
_ = > true ,
} ;
if ( ! PlayerCanUseController ( uid , user ! . Value , needsPower , comp ) )
return ;
_audioSystem . PlayPvs ( comp . ClickSound , uid , AudioParams . Default . WithVolume ( - 2f ) ) ;
switch ( msg . Button )
{
case UiButton . Eject :
TryEject ( uid , user : user , controller : comp ) ;
break ;
case UiButton . ToggleInjection :
ToggleInjecting ( uid , user : user , controller : comp ) ;
break ;
case UiButton . IncreaseFuel :
2023-06-30 16:24:23 -07:00
AdjustInjectionAmount ( uid , + 2 , user : user , controller : comp ) ;
2023-06-28 05:02:06 -07:00
break ;
case UiButton . DecreaseFuel :
2023-06-30 16:24:23 -07:00
AdjustInjectionAmount ( uid , - 2 , user : user , controller : comp ) ;
2023-06-28 05:02:06 -07:00
break ;
}
if ( TryGetAMENodeGroup ( uid , out var group ) )
group . UpdateCoreVisuals ( ) ;
UpdateUi ( uid , comp ) ;
}
/// <summary>
/// Checks whether the player entity is able to use the controller.
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the controller, and false if it cannot.</returns>
private bool PlayerCanUseController ( EntityUid uid , EntityUid playerEntity , bool needsPower = true , AmeControllerComponent ? controller = null )
{
if ( ! Resolve ( uid , ref controller ) )
return false ;
//Need player entity to check if they are still able to use the dispenser
if ( ! Exists ( playerEntity ) )
return false ;
//Check if device is powered
if ( needsPower & & TryComp < ApcPowerReceiverComponent > ( uid , out var powerSource ) & & ! powerSource . Powered )
return false ;
return true ;
}
}