2022-05-17 21:05:31 -07:00
using Content.Server.AlertLevel ;
2022-07-04 01:29:38 -05:00
using Content.Server.Audio ;
2022-06-23 20:11:03 +10:00
using Content.Server.Chat.Systems ;
2022-04-01 15:39:26 +13:00
using Content.Server.Explosion.EntitySystems ;
2023-09-23 20:15:05 +01:00
using Content.Server.Pinpointer ;
2021-11-11 04:29:11 +03:00
using Content.Server.Popups ;
2022-05-17 21:05:31 -07:00
using Content.Server.Station.Systems ;
2021-12-05 18:09:01 +01:00
using Content.Shared.Audio ;
2021-11-11 04:29:11 +03:00
using Content.Shared.Containers.ItemSlots ;
2023-06-29 08:35:54 -04:00
using Content.Shared.Coordinates.Helpers ;
2023-02-24 19:01:25 -05:00
using Content.Shared.DoAfter ;
2024-02-16 01:26:45 +01:00
using Content.Shared.Examine ;
2023-08-05 17:28:06 -04:00
using Content.Shared.Maps ;
2021-11-11 04:29:11 +03:00
using Content.Shared.Nuke ;
2022-07-09 02:09:52 -07:00
using Content.Shared.Popups ;
2023-02-24 19:01:25 -05:00
using Robust.Server.GameObjects ;
2023-08-26 10:41:07 +03:00
using Robust.Shared.Audio ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2021-12-05 18:09:01 +01:00
using Robust.Shared.Containers ;
2023-08-05 17:28:06 -04:00
using Robust.Shared.Map ;
2024-03-22 03:08:40 -04:00
using Robust.Shared.Map.Components ;
2021-11-11 04:29:11 +03:00
using Robust.Shared.Player ;
2022-10-09 12:28:08 -07:00
using Robust.Shared.Random ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
namespace Content.Server.Nuke ;
public sealed class NukeSystem : EntitySystem
2021-11-11 04:29:11 +03:00
{
2023-07-17 19:37:33 +00:00
[Dependency] private readonly AlertLevelSystem _alertLevel = default ! ;
[Dependency] private readonly ChatSystem _chatSystem = default ! ;
[Dependency] private readonly ExplosionSystem _explosions = default ! ;
[Dependency] private readonly IRobustRandom _random = default ! ;
2023-08-05 17:28:06 -04:00
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default ! ;
2023-07-17 19:37:33 +00:00
[Dependency] private readonly ItemSlotsSystem _itemSlots = default ! ;
2023-09-23 20:15:05 +01:00
[Dependency] private readonly NavMapSystem _navMap = default ! ;
2023-09-04 15:14:10 +01:00
[Dependency] private readonly PointLightSystem _pointLight = default ! ;
2023-07-17 19:37:33 +00:00
[Dependency] private readonly PopupSystem _popups = default ! ;
[Dependency] private readonly ServerGlobalSoundSystem _sound = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default ! ;
[Dependency] private readonly SharedTransformSystem _transform = default ! ;
[Dependency] private readonly StationSystem _station = default ! ;
[Dependency] private readonly UserInterfaceSystem _ui = default ! ;
2024-02-16 01:26:45 +01:00
[Dependency] private readonly AppearanceSystem _appearance = default ! ;
2023-07-17 19:37:33 +00:00
/// <summary>
/// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx
/// </summary>
2024-03-12 19:50:34 +01:00
private float _nukeSongLength ;
private string _selectedNukeSong = String . Empty ;
2023-07-17 19:37:33 +00:00
/// <summary>
/// Time to leave between the nuke song and the nuke alarm playing.
/// </summary>
private const float NukeSongBuffer = 1.5f ;
public override void Initialize ( )
2021-11-11 04:29:11 +03:00
{
2023-07-17 19:37:33 +00:00
base . Initialize ( ) ;
SubscribeLocalEvent < NukeComponent , ComponentInit > ( OnInit ) ;
SubscribeLocalEvent < NukeComponent , ComponentRemove > ( OnRemove ) ;
SubscribeLocalEvent < NukeComponent , MapInitEvent > ( OnMapInit ) ;
SubscribeLocalEvent < NukeComponent , EntInsertedIntoContainerMessage > ( OnItemSlotChanged ) ;
SubscribeLocalEvent < NukeComponent , EntRemovedFromContainerMessage > ( OnItemSlotChanged ) ;
2024-02-16 01:26:45 +01:00
SubscribeLocalEvent < NukeComponent , ExaminedEvent > ( OnExaminedEvent ) ;
2023-07-17 19:37:33 +00:00
// Shouldn't need re-anchoring.
SubscribeLocalEvent < NukeComponent , AnchorStateChangedEvent > ( OnAnchorChanged ) ;
// ui events
SubscribeLocalEvent < NukeComponent , NukeAnchorMessage > ( OnAnchorButtonPressed ) ;
SubscribeLocalEvent < NukeComponent , NukeArmedMessage > ( OnArmButtonPressed ) ;
SubscribeLocalEvent < NukeComponent , NukeKeypadMessage > ( OnKeypadButtonPressed ) ;
SubscribeLocalEvent < NukeComponent , NukeKeypadClearMessage > ( OnClearButtonPressed ) ;
SubscribeLocalEvent < NukeComponent , NukeKeypadEnterMessage > ( OnEnterButtonPressed ) ;
// Doafter events
SubscribeLocalEvent < NukeComponent , NukeDisarmDoAfterEvent > ( OnDoAfter ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void OnInit ( EntityUid uid , NukeComponent component , ComponentInit args )
{
component . RemainingTime = component . Timer ;
_itemSlots . AddItemSlot ( uid , SharedNukeComponent . NukeDiskSlotId , component . DiskSlot ) ;
2021-12-16 23:42:02 +13:00
2023-07-17 19:37:33 +00:00
UpdateStatus ( uid , component ) ;
UpdateUserInterface ( uid , component ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
var query = EntityQueryEnumerator < NukeComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var nuke ) )
{
switch ( nuke . Status )
2022-02-24 03:00:51 +03:00
{
2023-07-17 19:37:33 +00:00
case NukeStatus . ARMED :
TickTimer ( uid , frameTime , nuke ) ;
break ;
case NukeStatus . COOLDOWN :
TickCooldown ( uid , frameTime , nuke ) ;
break ;
2021-11-11 04:29:11 +03:00
}
}
2023-07-17 19:37:33 +00:00
}
private void OnMapInit ( EntityUid uid , NukeComponent nuke , MapInitEvent args )
{
var originStation = _station . GetOwningStation ( uid ) ;
if ( originStation ! = null )
nuke . OriginStation = originStation ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
else
2022-10-09 12:28:08 -07:00
{
2023-07-17 19:37:33 +00:00
var transform = Transform ( uid ) ;
nuke . OriginMapGrid = ( transform . MapID , transform . GridUid ) ;
}
2022-10-09 12:28:08 -07:00
2023-07-17 19:37:33 +00:00
nuke . Code = GenerateRandomNumberString ( nuke . CodeLength ) ;
}
2022-12-19 21:38:34 -05:00
2023-07-17 19:37:33 +00:00
private void OnRemove ( EntityUid uid , NukeComponent component , ComponentRemove args )
{
_itemSlots . RemoveItemSlot ( uid , component . DiskSlot ) ;
}
2022-10-09 12:28:08 -07:00
2023-07-17 19:37:33 +00:00
private void OnItemSlotChanged ( EntityUid uid , NukeComponent component , ContainerModifiedMessage args )
{
if ( ! component . Initialized )
return ;
2022-10-09 12:28:08 -07:00
2023-07-17 19:37:33 +00:00
if ( args . Container . ID ! = component . DiskSlot . ID )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
UpdateStatus ( uid , component ) ;
UpdateUserInterface ( uid , component ) ;
}
2021-12-16 23:42:02 +13:00
2023-07-17 19:37:33 +00:00
#region Anchor
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void OnAnchorChanged ( EntityUid uid , NukeComponent component , ref AnchorStateChangedEvent args )
{
UpdateUserInterface ( uid , component ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( args . Anchored = = false & & component . Status = = NukeStatus . ARMED & & component . RemainingTime > component . DisarmDoafterLength )
2021-11-11 04:29:11 +03:00
{
2023-07-17 19:37:33 +00:00
// yes, this means technically if you can find a way to unanchor the nuke, you can disarm it
// without the doafter. but that takes some effort, and it won't allow you to disarm a nuke that can't be disarmed by the doafter.
DisarmBomb ( uid , component ) ;
2021-11-11 04:29:11 +03:00
}
2024-02-16 01:26:45 +01:00
UpdateAppearance ( uid , component ) ;
2023-07-17 19:37:33 +00:00
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
#endregion
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
#region UI Events
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private async void OnAnchorButtonPressed ( EntityUid uid , NukeComponent component , NukeAnchorMessage args )
{
// malicious client sanity check
if ( component . Status = = NukeStatus . ARMED )
return ;
// manually set transform anchor (bypassing anchorable)
// todo: it will break pullable system
var xform = Transform ( uid ) ;
if ( xform . Anchored )
2021-11-11 04:29:11 +03:00
{
2023-07-17 19:37:33 +00:00
_transform . Unanchor ( uid , xform ) ;
}
else
{
2024-03-22 03:08:40 -04:00
if ( ! TryComp < MapGridComponent > ( xform . GridUid , out var grid ) )
2023-08-05 17:28:06 -04:00
return ;
var worldPos = _transform . GetWorldPosition ( xform ) ;
foreach ( var tile in grid . GetTilesIntersecting ( new Circle ( worldPos , component . RequiredFloorRadius ) , false ) )
{
if ( ! tile . IsSpace ( _tileDefManager ) )
continue ;
var msg = Loc . GetString ( "nuke-component-cant-anchor-floor" ) ;
_popups . PopupEntity ( msg , uid , args . Session , PopupType . MediumCaution ) ;
return ;
}
2023-07-17 19:37:33 +00:00
_transform . SetCoordinates ( uid , xform , xform . Coordinates . SnapToGrid ( ) ) ;
_transform . AnchorEntity ( uid , xform ) ;
2021-11-11 04:29:11 +03:00
}
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
UpdateUserInterface ( uid , component ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void OnEnterButtonPressed ( EntityUid uid , NukeComponent component , NukeKeypadEnterMessage args )
{
if ( component . Status ! = NukeStatus . AWAIT_CODE )
return ;
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
UpdateStatus ( uid , component ) ;
UpdateUserInterface ( uid , component ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void OnKeypadButtonPressed ( EntityUid uid , NukeComponent component , NukeKeypadMessage args )
{
PlayNukeKeypadSound ( uid , args . Value , component ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( component . Status ! = NukeStatus . AWAIT_CODE )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( component . EnteredCode . Length > = component . CodeLength )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
component . EnteredCode + = args . Value . ToString ( ) ;
UpdateUserInterface ( uid , component ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void OnClearButtonPressed ( EntityUid uid , NukeComponent component , NukeKeypadClearMessage args )
{
2023-11-27 22:12:34 +11:00
_audio . PlayEntity ( component . KeypadPressSound , Filter . Pvs ( uid ) , uid , true ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( component . Status ! = NukeStatus . AWAIT_CODE )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
component . EnteredCode = "" ;
UpdateUserInterface ( uid , component ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void OnArmButtonPressed ( EntityUid uid , NukeComponent component , NukeArmedMessage args )
{
if ( ! component . DiskSlot . HasItem )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( component . Status = = NukeStatus . AWAIT_ARM & & Transform ( uid ) . Anchored )
ArmBomb ( uid , component ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
else
2021-11-11 04:29:11 +03:00
{
2023-07-17 19:37:33 +00:00
if ( args . Session . AttachedEntity is not { } user )
2021-11-11 04:29:11 +03:00
return ;
2023-07-17 19:37:33 +00:00
DisarmBombDoafter ( uid , user , component ) ;
2021-11-11 04:29:11 +03:00
}
2023-07-17 19:37:33 +00:00
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
#endregion
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
#region Doafter Events
2022-12-19 21:38:34 -05:00
2023-07-17 19:37:33 +00:00
private void OnDoAfter ( EntityUid uid , NukeComponent component , DoAfterEvent args )
{
if ( args . Handled | | args . Cancelled )
return ;
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
DisarmBomb ( uid , component ) ;
var ev = new NukeDisarmSuccessEvent ( ) ;
RaiseLocalEvent ( ev ) ;
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
args . Handled = true ;
}
#endregion
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
private void TickCooldown ( EntityUid uid , float frameTime , NukeComponent ? nuke = null )
{
if ( ! Resolve ( uid , ref nuke ) )
return ;
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
nuke . CooldownTime - = frameTime ;
if ( nuke . CooldownTime < = 0 )
2022-07-04 23:49:19 -07:00
{
2023-07-17 19:37:33 +00:00
// reset nuke to default state
nuke . CooldownTime = 0 ;
nuke . Status = NukeStatus . AWAIT_ARM ;
UpdateStatus ( uid , nuke ) ;
}
2023-02-24 19:01:25 -05:00
2023-07-17 19:37:33 +00:00
UpdateUserInterface ( uid , nuke ) ;
}
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
private void TickTimer ( EntityUid uid , float frameTime , NukeComponent ? nuke = null )
{
if ( ! Resolve ( uid , ref nuke ) )
return ;
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
nuke . RemainingTime - = frameTime ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
// Start playing the nuke event song so that it ends a couple seconds before the alert sound
// should play
2024-03-12 19:50:34 +01:00
if ( nuke . RemainingTime < = _nukeSongLength + nuke . AlertSoundTime + NukeSongBuffer & & ! nuke . PlayedNukeSong & & ! string . IsNullOrEmpty ( _selectedNukeSong ) )
2022-02-24 03:00:51 +03:00
{
2024-03-12 19:50:34 +01:00
_sound . DispatchStationEventMusic ( uid , _selectedNukeSong , StationEventMusicType . Nuke ) ;
2023-07-17 19:37:33 +00:00
nuke . PlayedNukeSong = true ;
2022-02-24 03:00:51 +03:00
}
2023-07-17 19:37:33 +00:00
// play alert sound if time is running out
if ( nuke . RemainingTime < = nuke . AlertSoundTime & & ! nuke . PlayedAlertSound )
2022-02-24 03:00:51 +03:00
{
2023-08-26 10:41:07 +03:00
_sound . PlayGlobalOnStation ( uid , _audio . GetSound ( nuke . AlertSound ) , new AudioParams { Volume = - 5f } ) ;
2023-07-17 19:37:33 +00:00
_sound . StopStationEventMusic ( uid , StationEventMusicType . Nuke ) ;
nuke . PlayedAlertSound = true ;
2024-02-16 01:26:45 +01:00
UpdateAppearance ( uid , nuke ) ;
2023-07-17 19:37:33 +00:00
}
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
if ( nuke . RemainingTime < = 0 )
{
nuke . RemainingTime = 0 ;
ActivateBomb ( uid , nuke ) ;
}
2022-02-24 03:00:51 +03:00
2023-07-17 19:37:33 +00:00
else
UpdateUserInterface ( uid , nuke ) ;
}
2022-12-19 21:38:34 -05:00
2023-07-17 19:37:33 +00:00
private void UpdateStatus ( EntityUid uid , NukeComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
2022-02-24 03:00:51 +03:00
2023-07-17 19:37:33 +00:00
switch ( component . Status )
2021-11-11 04:29:11 +03:00
{
2023-07-17 19:37:33 +00:00
case NukeStatus . AWAIT_DISK :
if ( component . DiskSlot . HasItem )
component . Status = NukeStatus . AWAIT_CODE ;
break ;
case NukeStatus . AWAIT_CODE :
if ( ! component . DiskSlot . HasItem )
{
component . Status = NukeStatus . AWAIT_DISK ;
component . EnteredCode = "" ;
2021-11-11 04:29:11 +03:00
break ;
2023-07-17 19:37:33 +00:00
}
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
// var isValid = _codes.IsCodeValid(uid, component.EnteredCode);
if ( component . EnteredCode = = component . Code )
{
component . Status = NukeStatus . AWAIT_ARM ;
component . RemainingTime = component . Timer ;
2023-11-27 22:12:34 +11:00
_audio . PlayEntity ( component . AccessGrantedSound , Filter . Pvs ( uid ) , uid , true ) ;
2023-07-17 19:37:33 +00:00
}
else
{
component . EnteredCode = "" ;
2023-11-27 22:12:34 +11:00
_audio . PlayEntity ( component . AccessDeniedSound , Filter . Pvs ( uid ) , uid , true ) ;
2023-07-17 19:37:33 +00:00
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
break ;
case NukeStatus . AWAIT_ARM :
// do nothing, wait for arm button to be pressed
break ;
case NukeStatus . ARMED :
// do nothing, wait for arm button to be unpressed
break ;
}
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void UpdateUserInterface ( EntityUid uid , NukeComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
var ui = _ui . GetUiOrNull ( uid , NukeUiKey . Key ) ;
if ( ui = = null )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
var anchored = Transform ( uid ) . Anchored ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
var allowArm = component . DiskSlot . HasItem & &
( component . Status = = NukeStatus . AWAIT_ARM | |
component . Status = = NukeStatus . ARMED ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
var state = new NukeUiState
2022-07-05 00:24:37 -07:00
{
2023-07-17 19:37:33 +00:00
Status = component . Status ,
RemainingTime = ( int ) component . RemainingTime ,
DiskInserted = component . DiskSlot . HasItem ,
IsAnchored = anchored ,
AllowArm = allowArm ,
EnteredCodeLength = component . EnteredCode . Length ,
MaxCodeLength = component . CodeLength ,
CooldownTime = ( int ) component . CooldownTime
} ;
2023-09-11 09:42:41 +10:00
_ui . SetUiState ( ui , state ) ;
2023-07-17 19:37:33 +00:00
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
private void PlayNukeKeypadSound ( EntityUid uid , int number , NukeComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
// This is a C mixolydian blues scale.
// 1 2 3 C D Eb
// 4 5 6 E F F#
// 7 8 9 G A Bb
var semitoneShift = number switch
2022-10-09 12:28:08 -07:00
{
2023-07-17 19:37:33 +00:00
1 = > 0 ,
2 = > 2 ,
3 = > 3 ,
4 = > 4 ,
5 = > 5 ,
6 = > 6 ,
7 = > 7 ,
8 = > 9 ,
9 = > 10 ,
0 = > component . LastPlayedKeypadSemitones + 12 ,
_ = > 0
} ;
// Don't double-dip on the octave shifting
component . LastPlayedKeypadSemitones = number = = 0 ? component . LastPlayedKeypadSemitones : semitoneShift ;
2023-11-27 22:12:34 +11:00
_audio . PlayEntity ( component . KeypadPressSound , Filter . Pvs ( uid ) , uid , true , AudioHelpers . ShiftSemitone ( semitoneShift ) . WithVolume ( - 5f ) ) ;
2023-07-17 19:37:33 +00:00
}
2022-10-09 12:28:08 -07:00
2023-07-17 19:37:33 +00:00
public string GenerateRandomNumberString ( int length )
{
var ret = "" ;
for ( var i = 0 ; i < length ; i + + )
{
var c = ( char ) _random . Next ( '0' , '9' + 1 ) ;
ret + = c ;
2022-10-09 12:28:08 -07:00
}
2023-07-17 19:37:33 +00:00
return ret ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
#region Public API
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
/// <summary>
/// Force a nuclear bomb to start a countdown timer
/// </summary>
public void ArmBomb ( EntityUid uid , NukeComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
if ( component . Status = = NukeStatus . ARMED )
return ;
var nukeXform = Transform ( uid ) ;
var stationUid = _station . GetStationInMap ( nukeXform . MapID ) ;
// The nuke may not be on a station, so it's more important to just
// let people know that a nuclear bomb was armed in their vicinity instead.
// Otherwise, you could set every station to whatever AlertLevelOnActivate is.
if ( stationUid ! = null )
_alertLevel . SetLevel ( stationUid . Value , component . AlertLevelOnActivate , true , true , true , true ) ;
2024-02-28 00:51:20 +11:00
var pos = nukeXform . MapPosition ;
2023-07-17 19:37:33 +00:00
var x = ( int ) pos . X ;
var y = ( int ) pos . Y ;
var posText = $"({x}, {y})" ;
2024-03-12 19:50:34 +01:00
// We are collapsing the randomness here, otherwise we would get separate random song picks for checking duration and when actually playing the song afterwards
_selectedNukeSong = _audio . GetSound ( component . ArmMusic ) ;
2023-07-17 19:37:33 +00:00
// warn a crew
var announcement = Loc . GetString ( "nuke-component-announcement-armed" ,
( "time" , ( int ) component . RemainingTime ) , ( "position" , posText ) ) ;
var sender = Loc . GetString ( "nuke-component-announcement-sender" ) ;
_chatSystem . DispatchStationAnnouncement ( stationUid ? ? uid , announcement , sender , false , null , Color . Red ) ;
_sound . PlayGlobalOnStation ( uid , _audio . GetSound ( component . ArmSound ) ) ;
2024-03-12 19:50:34 +01:00
_nukeSongLength = ( float ) _audio . GetAudioLength ( _selectedNukeSong ) . TotalSeconds ;
2023-07-17 19:37:33 +00:00
2023-09-04 15:14:10 +01:00
// turn on the spinny light
_pointLight . SetEnabled ( uid , true ) ;
2023-09-23 20:15:05 +01:00
// enable the navmap beacon for people to find it
_navMap . SetBeaconEnabled ( uid , true ) ;
2023-09-04 15:14:10 +01:00
2023-07-17 19:37:33 +00:00
_itemSlots . SetLock ( uid , component . DiskSlot , true ) ;
2023-08-09 07:16:41 +02:00
if ( ! nukeXform . Anchored )
{
// Admin command shenanigans, just make sure.
_transform . AnchorEntity ( uid , nukeXform ) ;
}
2023-07-17 19:37:33 +00:00
component . Status = NukeStatus . ARMED ;
UpdateUserInterface ( uid , component ) ;
2024-02-16 01:26:45 +01:00
UpdateAppearance ( uid , component ) ;
2023-07-17 19:37:33 +00:00
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
/// <summary>
/// Stop nuclear bomb timer
/// </summary>
public void DisarmBomb ( EntityUid uid , NukeComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( component . Status ! = NukeStatus . ARMED )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
var stationUid = _station . GetOwningStation ( uid ) ;
if ( stationUid ! = null )
_alertLevel . SetLevel ( stationUid . Value , component . AlertLevelOnDeactivate , true , true , true ) ;
2022-05-17 21:05:31 -07:00
2023-07-17 19:37:33 +00:00
// warn a crew
var announcement = Loc . GetString ( "nuke-component-announcement-unarmed" ) ;
var sender = Loc . GetString ( "nuke-component-announcement-sender" ) ;
_chatSystem . DispatchStationAnnouncement ( uid , announcement , sender , false ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
component . PlayedNukeSong = false ;
_sound . PlayGlobalOnStation ( uid , _audio . GetSound ( component . DisarmSound ) ) ;
_sound . StopStationEventMusic ( uid , StationEventMusicType . Nuke ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
// disable sound and reset it
component . PlayedAlertSound = false ;
2023-11-27 22:12:34 +11:00
component . AlertAudioStream = _audio . Stop ( component . AlertAudioStream ) ;
2021-11-11 04:29:11 +03:00
2023-09-04 15:14:10 +01:00
// turn off the spinny light
_pointLight . SetEnabled ( uid , false ) ;
2023-09-23 20:15:05 +01:00
// disable the navmap beacon now that its disarmed
_navMap . SetBeaconEnabled ( uid , false ) ;
2023-09-04 15:14:10 +01:00
2023-07-17 19:37:33 +00:00
// start bomb cooldown
_itemSlots . SetLock ( uid , component . DiskSlot , false ) ;
component . Status = NukeStatus . COOLDOWN ;
component . CooldownTime = component . Cooldown ;
2022-02-24 03:00:51 +03:00
2023-07-17 19:37:33 +00:00
UpdateUserInterface ( uid , component ) ;
2024-02-16 01:26:45 +01:00
UpdateAppearance ( uid , component ) ;
2023-07-17 19:37:33 +00:00
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
/// <summary>
/// Toggle bomb arm button
/// </summary>
public void ToggleBomb ( EntityUid uid , NukeComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( component . Status = = NukeStatus . ARMED )
DisarmBomb ( uid , component ) ;
else
ArmBomb ( uid , component ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
/// <summary>
/// Force bomb to explode immediately
/// </summary>
public void ActivateBomb ( EntityUid uid , NukeComponent ? component = null ,
TransformComponent ? transform = null )
{
if ( ! Resolve ( uid , ref component , ref transform ) )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
if ( component . Exploded )
return ;
2022-03-04 13:48:01 -06:00
2023-07-17 19:37:33 +00:00
component . Exploded = true ;
2022-04-01 15:39:26 +13:00
2023-07-17 19:37:33 +00:00
_explosions . QueueExplosion ( uid ,
component . ExplosionType ,
component . TotalIntensity ,
component . IntensitySlope ,
component . MaxIntensity ) ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
RaiseLocalEvent ( new NukeExplodedEvent ( )
{
OwningStation = transform . GridUid ,
} ) ;
2022-05-18 04:48:35 +02:00
2023-07-17 19:37:33 +00:00
_sound . StopStationEventMusic ( uid , StationEventMusicType . Nuke ) ;
Del ( uid ) ;
}
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
/// <summary>
/// Set remaining time value
/// </summary>
public void SetRemainingTime ( EntityUid uid , float timer , NukeComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
2021-11-11 04:29:11 +03:00
2023-07-17 19:37:33 +00:00
component . RemainingTime = timer ;
UpdateUserInterface ( uid , component ) ;
}
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
#endregion
2022-07-04 01:29:38 -05:00
2023-07-17 19:37:33 +00:00
private void DisarmBombDoafter ( EntityUid uid , EntityUid user , NukeComponent nuke )
{
2023-09-11 09:42:41 +10:00
var doAfter = new DoAfterArgs ( EntityManager , user , nuke . DisarmDoafterLength , new NukeDisarmDoAfterEvent ( ) , uid , target : uid )
2022-07-04 23:49:19 -07:00
{
2023-07-17 19:37:33 +00:00
BreakOnDamage = true ,
2024-03-19 12:09:00 +02:00
BreakOnMove = true ,
2023-07-17 19:37:33 +00:00
NeedHand = true
} ;
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
if ( ! _doAfter . TryStartDoAfter ( doAfter ) )
return ;
2023-04-03 13:13:48 +12:00
2023-07-17 19:37:33 +00:00
_popups . PopupEntity ( Loc . GetString ( "nuke-component-doafter-warning" ) , user ,
user , PopupType . LargeCaution ) ;
2021-11-11 04:29:11 +03:00
}
2024-02-16 01:26:45 +01:00
private void UpdateAppearance ( EntityUid uid , NukeComponent nuke )
{
var xform = Transform ( uid ) ;
_appearance . SetData ( uid , NukeVisuals . Deployed , xform . Anchored ) ;
NukeVisualState state ;
if ( nuke . PlayedAlertSound )
state = NukeVisualState . YoureFucked ;
else if ( nuke . Status = = NukeStatus . ARMED )
state = NukeVisualState . Armed ;
else
state = NukeVisualState . Idle ;
_appearance . SetData ( uid , NukeVisuals . State , state ) ;
}
private void OnExaminedEvent ( EntityUid uid , NukeComponent component , ExaminedEvent args )
{
if ( component . PlayedAlertSound )
args . PushMarkup ( Loc . GetString ( "nuke-examine-exploding" ) ) ;
else if ( component . Status = = NukeStatus . ARMED )
args . PushMarkup ( Loc . GetString ( "nuke-examine-armed" ) ) ;
if ( Transform ( uid ) . Anchored )
args . PushMarkup ( Loc . GetString ( "examinable-anchored" ) ) ;
else
args . PushMarkup ( Loc . GetString ( "examinable-unanchored" ) ) ;
}
2023-07-17 19:37:33 +00:00
}
2022-05-18 04:48:35 +02:00
2023-07-17 19:37:33 +00:00
public sealed class NukeExplodedEvent : EntityEventArgs
{
public EntityUid ? OwningStation ;
}
2022-07-04 23:49:19 -07:00
2023-07-17 19:37:33 +00:00
/// <summary>
/// Raised directed on the nuke when its disarm doafter is successful.
/// So the game knows not to end.
/// </summary>
public sealed class NukeDisarmSuccessEvent : EntityEventArgs
{
2022-12-19 21:38:34 -05:00
2021-11-11 04:29:11 +03:00
}
2023-07-17 19:37:33 +00:00