Refactors and sorts Nuclear Bombs and Cinematics. Fixes and unit tests nuke cinematics, and re-adds a missing malf ai cinematic. (#13802)

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
SkyratBot
2022-05-22 09:03:09 -07:00
committed by GitHub
co-authored by MrMelbert
parent 2086b3613f
commit 6bf0de98e0
29 changed files with 1461 additions and 433 deletions
-14
View File
@@ -1,14 +0,0 @@
#define CINEMATIC_DEFAULT 1
#define CINEMATIC_SELFDESTRUCT 2
#define CINEMATIC_SELFDESTRUCT_MISS 3
#define CINEMATIC_NUKE_WIN 4
#define CINEMATIC_NUKE_MISS 5
#define CINEMATIC_ANNIHILATION 6
#define CINEMATIC_MALF 7
#define CINEMATIC_CULT 8
#define CINEMATIC_NUKE_FAKE 9
#define CINEMATIC_NUKE_NO_CORE 10
#define CINEMATIC_NUKE_FAR 11
#define CINEMATIC_NUKE_CLOWNOP 12
#define CINEMATIC_CULT_NUKE 13
#define CINEMATIC_CULT_FAIL 14
+8
View File
@@ -35,3 +35,11 @@
/// Defines for cult item_dispensers.
#define PREVIEW_IMAGE "preview"
#define OUTPUT_ITEMS "output"
/// The global Nar'sie that the cult's summoned
GLOBAL_DATUM(cult_narsie, /obj/narsie)
// Used in determining which cinematic to play when cult ends
#define CULT_VICTORY_MASS_CONVERSION 2
#define CULT_FAILURE_NARSIE_KILLED 1
#define CULT_VICTORY_NUKE 0
-20
View File
@@ -127,26 +127,6 @@
#define HYPERTORUS_EMERGENCY 4 // Integrity < 25%
#define HYPERTORUS_MELTING 5 // Pretty obvious.
//Nuclear bomb stuff
#define NUKESTATE_INTACT 5
#define NUKESTATE_UNSCREWED 4
#define NUKESTATE_PANEL_REMOVED 3
#define NUKESTATE_WELDED 2
#define NUKESTATE_CORE_EXPOSED 1
#define NUKESTATE_CORE_REMOVED 0
#define NUKEUI_AWAIT_DISK 0
#define NUKEUI_AWAIT_CODE 1
#define NUKEUI_AWAIT_TIMER 2
#define NUKEUI_AWAIT_ARM 3
#define NUKEUI_TIMING 4
#define NUKEUI_EXPLODED 5
#define NUKE_OFF_LOCKED 0
#define NUKE_OFF_UNLOCKED 1
#define NUKE_ON_TIMING 2
#define NUKE_ON_EXPLODING 3
#define MACHINE_NOT_ELECTRIFIED 0
#define MACHINE_ELECTRIFIED_PERMANENT -1
#define MACHINE_DEFAULT_ELECTRIFY_TIME 30
+28
View File
@@ -0,0 +1,28 @@
// Nuclear bomb de/construction status
#define NUKESTATE_INTACT 5
#define NUKESTATE_UNSCREWED 4
#define NUKESTATE_PANEL_REMOVED 3
#define NUKESTATE_WELDED 2
#define NUKESTATE_CORE_EXPOSED 1
#define NUKESTATE_CORE_REMOVED 0
// Nuclear bomb UI modes
#define NUKEUI_AWAIT_DISK 0
#define NUKEUI_AWAIT_CODE 1
#define NUKEUI_AWAIT_TIMER 2
#define NUKEUI_AWAIT_ARM 3
#define NUKEUI_TIMING 4
#define NUKEUI_EXPLODED 5
// Nuclear bomb states
#define NUKE_OFF_LOCKED 0
#define NUKE_OFF_UNLOCKED 1
#define NUKE_ON_TIMING 2
#define NUKE_ON_EXPLODING 3
// Nuclear bomb detonation statuses
// These line up with roundend reports
#define DETONATION_HIT_STATION STATION_DESTROYED_NUKE
#define DETONATION_HIT_SYNDIE_BASE NUKE_SYNDICATE_BASE
#define DETONATION_NEAR_MISSED_STATION NUKE_NEAR_MISS
#define DETONATION_MISSED_STATION NUKE_MISS_STATION
-4
View File
@@ -5,9 +5,5 @@ GLOBAL_VAR(survivor_report) //Contains shared survivor report for roundend repor
GLOBAL_VAR_INIT(wavesecret, 0) // meteor mode, delays wave progression, terrible name
GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report
//TODO clear this one up too
GLOBAL_DATUM(cult_narsie, /obj/narsie)
/// We want reality_smash_tracker to exist only once and be accessible from anywhere.
GLOBAL_DATUM_INIT(reality_smash_track, /datum/reality_smash_tracker, new)
-287
View File
@@ -1,287 +0,0 @@
// Use to play cinematics.
// Watcher can be world,mob, or a list of mobs
// Blocks until sequence is done.
/proc/Cinematic(id,watcher,datum/callback/special_callback)
var/datum/cinematic/playing
for(var/V in subtypesof(/datum/cinematic))
var/datum/cinematic/C = V
if(initial(C.id) == id)
playing = new V()
break
if(!playing)
CRASH("Cinematic type not found")
if(special_callback)
playing.special_callback = special_callback
if(watcher == world)
playing.is_global = TRUE
watcher = GLOB.mob_list
playing.play(watcher)
qdel(playing)
/atom/movable/screen/cinematic
icon = 'icons/effects/station_explosion.dmi'
icon_state = "station_intact"
plane = SPLASHSCREEN_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
screen_loc = "BOTTOM,LEFT+50%"
appearance_flags = APPEARANCE_UI | TILE_BOUND
/datum/cinematic
var/id = CINEMATIC_DEFAULT
var/list/watching = list() //List of clients watching this
var/list/locked = list() //Who had notransform set during the cinematic
var/is_global = FALSE //Global cinematics will override mob-specific ones
var/atom/movable/screen/cinematic/screen
var/datum/callback/special_callback //For special effects synced with animation (explosions after the countdown etc)
var/cleanup_time = 300 //How long for the final screen to remain
var/stop_ooc = TRUE //Turns off ooc when played globally.
/datum/cinematic/New()
screen = new(src)
/datum/cinematic/Destroy()
for(var/CC in watching)
if(!CC)
continue
var/client/C = CC
C.mob.clear_fullscreen("cinematic")
C.screen -= screen
watching = null
QDEL_NULL(screen)
QDEL_NULL(special_callback)
for(var/MM in locked)
if(!MM)
continue
var/mob/M = MM
M.notransform = FALSE
locked = null
return ..()
/datum/cinematic/proc/play(watchers)
//Check if cinematic can actually play (stop mob cinematics for global ones)
if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PLAY_CINEMATIC, src) & COMPONENT_GLOB_BLOCK_CINEMATIC)
return
//We are now playing this cinematic
//Handle what happens when a different cinematic tries to play over us
RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, .proc/replacement_cinematic)
//Pause OOC
var/ooc_toggled = FALSE
if(is_global && stop_ooc && GLOB.ooc_allowed)
ooc_toggled = TRUE
toggle_ooc(FALSE)
//Place /atom/movable/screen/cinematic into everyone's screens, prevent them from moving
for(var/MM in watchers)
var/mob/M = MM
show_to(M, M.client)
RegisterSignal(M, COMSIG_MOB_CLIENT_LOGIN, .proc/show_to)
//Close watcher ui's
SStgui.close_user_uis(M)
//Actually play it
content()
//Cleanup
sleep(cleanup_time)
//Restore OOC
if(ooc_toggled)
toggle_ooc(TRUE)
/datum/cinematic/proc/show_to(mob/M, client/C)
SIGNAL_HANDLER
if(!M.notransform)
locked += M
M.notransform = TRUE //Should this be done for non-global cinematics or even at all ?
if(!C)
return
watching += C
M.overlay_fullscreen("cinematic",/atom/movable/screen/fullscreen/cinematic_backdrop)
C.screen += screen
//Sound helper
/datum/cinematic/proc/cinematic_sound(s)
if(is_global)
SEND_SOUND(world,s)
else
for(var/C in watching)
SEND_SOUND(C,s)
//Fire up special callback for actual effects synchronized with animation (eg real nuke explosion happens midway)
/datum/cinematic/proc/special()
if(special_callback)
special_callback.Invoke()
//Actual cinematic goes in here
/datum/cinematic/proc/content()
sleep(50)
/datum/cinematic/proc/replacement_cinematic(datum/source, datum/cinematic/other)
SIGNAL_HANDLER
if(!is_global && other.is_global) //Allow it to play if we're local and it's global
return NONE
return COMPONENT_GLOB_BLOCK_CINEMATIC
/datum/cinematic/nuke_win
id = CINEMATIC_NUKE_WIN
/datum/cinematic/nuke_win/content()
flick("intro_nuke",screen)
sleep(35)
flick("station_explode_fade_red",screen)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
screen.icon_state = "summary_nukewin"
/datum/cinematic/nuke_miss
id = CINEMATIC_NUKE_MISS
/datum/cinematic/nuke_miss/content()
flick("intro_nuke",screen)
sleep(35)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
flick("station_intact_fade_red",screen)
screen.icon_state = "summary_nukefail"
//Also used for blob
/datum/cinematic/nuke_selfdestruct
id = CINEMATIC_SELFDESTRUCT
/datum/cinematic/nuke_selfdestruct/content()
flick("intro_nuke",screen)
sleep(35)
flick("station_explode_fade_red", screen)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
screen.icon_state = "summary_selfdes"
/datum/cinematic/nuke_selfdestruct_miss
id = CINEMATIC_SELFDESTRUCT_MISS
/datum/cinematic/nuke_selfdestruct_miss/content()
flick("intro_nuke",screen)
sleep(35)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
screen.icon_state = "station_intact"
/datum/cinematic/malf
id = CINEMATIC_MALF
/datum/cinematic/malf/content()
flick("intro_malf",screen)
sleep(76)
flick("station_explode_fade_red",screen)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
screen.icon_state = "summary_malf"
/datum/cinematic/cult
id = CINEMATIC_CULT
/datum/cinematic/cult/content()
screen.icon_state = null
flick("intro_cult",screen)
sleep(25)
cinematic_sound(sound('sound/magic/enter_blood.ogg'))
sleep(28)
cinematic_sound(sound('sound/machines/terminal_off.ogg'))
sleep(20)
flick("station_corrupted",screen)
cinematic_sound(sound('sound/effects/ghost.ogg'))
sleep(70)
special()
/datum/cinematic/cult_nuke
id = CINEMATIC_CULT_NUKE
/datum/cinematic/cult_nuke/content()
flick("intro_nuke",screen)
sleep(35)
flick("station_explode_fade_red",screen)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
screen.icon_state = "summary_cult"
/datum/cinematic/cult_fail
id = CINEMATIC_CULT_FAIL
/datum/cinematic/cult_fail/content()
screen.icon_state = "station_intact"
sleep(20)
cinematic_sound(sound('sound/creatures/narsie_rises.ogg'))
sleep(60)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
sleep(10)
cinematic_sound(sound('sound/magic/demon_dies.ogg'))
sleep(30)
special()
/datum/cinematic/nuke_annihilation
id = CINEMATIC_ANNIHILATION
/datum/cinematic/nuke_annihilation/content()
flick("intro_nuke",screen)
sleep(35)
flick("station_explode_fade_red",screen)
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
screen.icon_state = "summary_totala"
/datum/cinematic/fake
id = CINEMATIC_NUKE_FAKE
cleanup_time = 100
/datum/cinematic/fake/content()
flick("intro_nuke",screen)
sleep(35)
cinematic_sound(sound('sound/items/bikehorn.ogg'))
flick("summary_selfdes",screen) //???
special()
/datum/cinematic/no_core
id = CINEMATIC_NUKE_NO_CORE
cleanup_time = 100
/datum/cinematic/no_core/content()
flick("intro_nuke",screen)
sleep(35)
flick("station_intact",screen)
cinematic_sound(sound('sound/ambience/signal.ogg'))
sleep(100)
/datum/cinematic/nuke_far
id = CINEMATIC_NUKE_FAR
cleanup_time = 0
/datum/cinematic/nuke_far/content()
cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special()
/datum/cinematic/clownop
id = CINEMATIC_NUKE_CLOWNOP
cleanup_time = 100
/datum/cinematic/clownop/content()
flick("intro_nuke",screen)
sleep(35)
cinematic_sound(sound('sound/items/airhorn.ogg'))
flick("summary_selfdes",screen) //???
special()
/* Intended usage.
Nuke.Explosion()
-> Cinematic(NUKE_BOOM,world)
-> ActualExplosion()
-> Mode.OnExplosion()
Narsie()
-> Cinematic(CULT,world)
*/
+148
View File
@@ -0,0 +1,148 @@
/**
* Plays a cinematic, duh. Can be to a select few people, or everyone.
*
* cinematic_type - datum typepath to what cinematic you wish to play.
* watchers - a list of all mobs you are playing the cinematic to. If world, the cinematical will play globally to all players.
* special_callback - optional callback to be invoked mid-cinematic.
*/
/proc/play_cinematic(datum/cinematic/cinematic_type, watchers, datum/callback/special_callback)
if(!ispath(cinematic_type, /datum/cinematic))
CRASH("play_cinematic called with a non-cinematic type. (Got: [cinematic_type])")
var/datum/cinematic/playing = new cinematic_type(watchers, special_callback)
if(watchers == world)
watchers = GLOB.mob_list
playing.start_cinematic(watchers)
return playing
/// The cinematic screen showed to everyone
/atom/movable/screen/cinematic
icon = 'icons/effects/station_explosion.dmi'
icon_state = "station_intact"
plane = SPLASHSCREEN_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
screen_loc = "BOTTOM,LEFT+50%"
appearance_flags = APPEARANCE_UI | TILE_BOUND
/// Cinematic datum. Used to show an animation to everyone.
/datum/cinematic
/// A list of all clients watching the cinematic
var/list/client/watching = list()
/// A list of all mobs who have notransform set while watching the cinematic
var/list/datum/weakref/locked = list()
/// Whether the cinematic is a global cinematic or not
var/is_global = FALSE
/// Refernce to the cinematic screen shown to everyohne
var/atom/movable/screen/cinematic/screen
/// Callbacks passed that occur during the animation
var/datum/callback/special_callback
/// How long for the final screen remains shown
var/cleanup_time = 30 SECONDS
/// Whether the cinematic turns off ooc when played globally.
var/stop_ooc = TRUE
/datum/cinematic/New(watcher, datum/callback/special_callback)
screen = new(src)
if(watcher == world)
is_global = TRUE
src.special_callback = special_callback
/datum/cinematic/Destroy()
QDEL_NULL(screen)
QDEL_NULL(special_callback)
watching.Cut()
locked.Cut()
return ..()
/// Actually goes through the process of showing the cinematic to the list of watchers.
/datum/cinematic/proc/start_cinematic(list/watchers)
if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PLAY_CINEMATIC, src) & COMPONENT_GLOB_BLOCK_CINEMATIC)
return
// Register a signal to handle what happens when a different cinematic tries to play over us.
RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, .proc/handle_replacement_cinematics)
// Pause OOC
var/ooc_toggled = FALSE
if(is_global && stop_ooc && GLOB.ooc_allowed)
ooc_toggled = TRUE
toggle_ooc(FALSE)
// Place the /atom/movable/screen/cinematic into everyone's screens, prevent them from moving
for(var/mob/watching_mob in watchers)
show_to(watching_mob, GET_CLIENT(watching_mob))
RegisterSignal(watching_mob, COMSIG_MOB_CLIENT_LOGIN, .proc/show_to)
//Close watcher ui's
SStgui.close_user_uis(watching_mob)
//Actually play it
play_cinematic()
//Cleanup
sleep(cleanup_time)
//Restore OOC
if(ooc_toggled)
toggle_ooc(TRUE)
stop_cinematic()
/// Whenever another cinematic starts to play over us, we have the chacne to block it.
/datum/cinematic/proc/handle_replacement_cinematics(datum/source, datum/cinematic/other)
SIGNAL_HANDLER
// Stop our's and allow others to play if we're local and it's global
if(!is_global && other.is_global)
stop_cinematic()
return NONE
return COMPONENT_GLOB_BLOCK_CINEMATIC
/// Whenever a mob watching the cinematic logs in, show them the ongoing cinematic
/datum/cinematic/proc/show_to(mob/watching_mob, client/watching_client)
SIGNAL_HANDLER
if(!watching_mob.notransform)
locked += WEAKREF(watching_mob)
watching_mob.notransform = TRUE
if(!watching_client)
return
watching += watching_client
watching_mob.overlay_fullscreen("cinematic", /atom/movable/screen/fullscreen/cinematic_backdrop)
watching_client.screen += screen
/// Simple helper for playing sounds from the cinematic.
/datum/cinematic/proc/play_cinematic_sound(sound_to_play)
if(is_global)
SEND_SOUND(world, sound_to_play)
else
for(var/client/watching_client as anything in watching)
SEND_SOUND(watching_client, sound_to_play)
/// Invoke any special callbacks for actual effects synchronized with animation.
/// (Such as a real nuke explosion happening midway)
/datum/cinematic/proc/invoke_special_callback()
special_callback?.Invoke()
/// The actual cinematic occurs here.
/datum/cinematic/proc/play_cinematic()
return
/// Stops the cinematic and removes it from all the viewers.
/datum/cinematic/proc/stop_cinematic()
for(var/client/viewing_client as anything in watching)
viewing_client.mob.clear_fullscreen("cinematic")
viewing_client.screen -= screen
for(var/datum/weakref/locked_ref as anything in locked)
var/mob/locked_mob = locked_ref.resolve()
if(QDELETED(locked_mob))
continue
locked_mob.notransform = FALSE
qdel(src)
+10
View File
@@ -0,0 +1,10 @@
/// A malfunctioning AI has activated the doomsday device and wiped the station!
/datum/cinematic/malf
/datum/cinematic/malf/play_cinematic()
flick("intro_malf", screen)
stoplag(7.6 SECONDS)
flick("station_explode_fade_red", screen)
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special_callback?.Invoke()
screen.icon_state = "summary_malf"
+29
View File
@@ -0,0 +1,29 @@
/// A blood cult summoned Nar'sie, and most of the station was harvested or converted!
/datum/cinematic/cult_arm // Colloquially known as "the arm"
/datum/cinematic/cult_arm/play_cinematic()
screen.icon_state = null
flick("intro_cult", screen)
stoplag(2.5 SECONDS)
play_cinematic_sound(sound('sound/magic/enter_blood.ogg'))
stoplag(2.8 SECONDS)
play_cinematic_sound(sound('sound/machines/terminal_off.ogg'))
stoplag(2 SECONDS)
flick("station_corrupted", screen)
play_cinematic_sound(sound('sound/effects/ghost.ogg'))
stoplag(7 SECONDS)
special_callback?.Invoke()
/// A blood cult summoned Nar'sie, but some badass (or admin) managed to destroy Nar'sie themselves.
/datum/cinematic/cult_fail
/datum/cinematic/cult_fail/play_cinematic()
screen.icon_state = "station_intact"
stoplag(2 SECONDS)
play_cinematic_sound(sound('sound/creatures/narsie_rises.ogg'))
stoplag(6 SECONDS)
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
stoplag(1 SECONDS)
play_cinematic_sound(sound('sound/magic/demon_dies.ogg'))
stoplag(3 SECONDS)
special_callback?.Invoke()
+100
View File
@@ -0,0 +1,100 @@
/// Simple, base cinematic for all animations based around a nuke detonating.
/datum/cinematic/nuke
/// If set, this is the summary screen that pops up after the nuke is done.
var/after_nuke_summary_state
/datum/cinematic/nuke/play_cinematic()
flick("intro_nuke", screen)
stoplag(3.5 SECONDS)
play_nuke_effect()
if(special_callback)
special_callback.Invoke()
if(after_nuke_summary_state)
screen.icon_state = after_nuke_summary_state
/// Specific effects for each type of cinematics goes here.
/datum/cinematic/nuke/proc/play_nuke_effect()
return
/// The syndicate nuclear bomb was activated, and destroyed the station!
/datum/cinematic/nuke/ops_victory
after_nuke_summary_state = "summary_nukewin"
/datum/cinematic/nuke/ops_victory/play_nuke_effect()
flick("station_explode_fade_red", screen)
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
/// The syndicate nuclear bomb was activated, but just barely missed the station!
/datum/cinematic/nuke/ops_miss
after_nuke_summary_state = "summary_nukefail"
/datum/cinematic/nuke/ops_miss/play_nuke_effect()
flick("station_intact_fade_red", screen)
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
/// The self destruct, or another station-destroying entity like a blob, destroyed the station!
/datum/cinematic/nuke/self_destruct
after_nuke_summary_state = "summary_selfdes"
/datum/cinematic/nuke/self_destruct/play_nuke_effect()
flick("station_explode_fade_red", screen)
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
/// The self destruct was activated, yet somehow avoided destroying the station!
/datum/cinematic/nuke/self_destruct_miss
after_nuke_summary_state = "station_intact"
/datum/cinematic/nuke/self_destruct_miss/play_nuke_effect()
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special_callback?.Invoke()
/// The syndicate nuclear bomb was activated, and the nuclear operatives failed to extract on their shuttle before it detonated on the station!
/datum/cinematic/nuke/mutual_destruction
after_nuke_summary_state = "summary_totala"
/datum/cinematic/nuke/mutual_destruction/play_nuke_effect()
flick("station_explode_fade_red", screen)
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
/// A blood cult summoned Nar'sie, but central command deployed a nuclear package to stop them.
/datum/cinematic/nuke/cult
after_nuke_summary_state = "summary_cult"
/datum/cinematic/nuke/cult/play_nuke_effect()
flick("station_explode_fade_red", screen)
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
/// A fake version of the nuclear detonation, where it winds up, but doesn't explode.
/datum/cinematic/nuke/fake
cleanup_time = 10 SECONDS
/datum/cinematic/nuke/fake/play_nuke_effect()
play_cinematic_sound(sound('sound/items/bikehorn.ogg'))
flick("summary_selfdes", screen) //???
/// The clown operative nuclear bomb was activated and clowned the station!
/datum/cinematic/nuke/clown
cleanup_time = 10 SECONDS
/datum/cinematic/nuke/clown/play_nuke_effect()
play_cinematic_sound(sound('sound/items/airhorn.ogg'))
flick("summary_selfdes", screen) //???
/// A fake version of the nuclear detonation, where it winds up, but doesn't explode as the nuke core within was missing.
/datum/cinematic/nuke/no_core
cleanup_time = 10 SECONDS
/datum/cinematic/nuke/no_core/play_nuke_effect()
flick("station_intact", screen)
play_cinematic_sound(sound('sound/ambience/signal.ogg'))
stoplag(10 SECONDS)
/// The syndicate nuclear bomb was activated, but just missed the station by a whole z-level!
/datum/cinematic/nuke/far_explosion
cleanup_time = 0 SECONDS
/datum/cinematic/nuke/far_explosion/play_cinematic()
// This one has no intro sequence.
// It's actually just a global sound, which makes you wonder why it's a cinematic.
play_cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
special_callback?.Invoke()
+6
View File
@@ -5,3 +5,9 @@
/// The view of the client, similar to /client/var/view.
var/view = "15x15"
/// Objects on the screen of the client
var/list/screen = list()
/// The mob the client controls
var/mob/mob
@@ -4,6 +4,7 @@
door_anim_squish = 0.22
door_anim_angle = 123
door_anim_time = 4
/// If FALSE, we will protect the first person in the freezer from an explosion / nuclear blast.
var/jones = FALSE
/obj/structure/closet/secure_closet/freezer/Destroy()
+2 -3
View File
@@ -253,8 +253,8 @@
if(!newtime)
return
N.timer_set = newtime
N.set_safety()
N.set_active()
N.toggle_nuke_safety()
N.toggle_nuke_armed()
log_admin("[key_name(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [AREACOORD(N)].")
message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].")
@@ -323,4 +323,3 @@
message_admins("[key_name_admin(usr)] added mob ability [ability_type] to mob [marked_mob].")
log_admin("[key_name(usr)] added mob ability [ability_type] to mob [marked_mob].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Add Mob Ability") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+6 -3
View File
@@ -3,9 +3,12 @@
set category = "Admin.Fun"
set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted.
set hidden = TRUE
if(!SSticker)
return
var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as null|anything in sort_list(subtypesof(/datum/cinematic), /proc/cmp_typepaths_asc)
if(choice)
Cinematic(initial(choice.id),world,null)
var/datum/cinematic/choice = tgui_input_list(usr, "Chose a cinematic to play to everyone in the server.", "Choose Cinematic", sort_list(subtypesof(/datum/cinematic), /proc/cmp_typepaths_asc))
if(!choice || !ispath(choice, /datum/cinematic))
return
play_cinematic(choice, world)
@@ -1,7 +1,6 @@
/obj/machinery/nuclearbomb/syndicate/bananium
name = "bananium fission explosive"
desc = "You probably shouldn't stick around to see if this is armed."
icon = 'icons/obj/machines/nuke.dmi'
icon_state = "bananiumbomb_base"
base_icon_state = "bananiumbomb"
@@ -19,39 +18,43 @@
if(NUKE_ON_EXPLODING)
icon_state = "[base_icon_state]_exploding"
/obj/machinery/nuclearbomb/syndicate/bananium/get_cinematic_type(off_station)
switch(off_station)
if(0)
return CINEMATIC_NUKE_CLOWNOP
if(1)
return CINEMATIC_NUKE_MISS
if(2)
return CINEMATIC_NUKE_FAKE //it is farther away, so just a bikehorn instead of an airhorn
return CINEMATIC_NUKE_FAKE
/obj/machinery/nuclearbomb/syndicate/bananium/get_cinematic_type(detonation_status)
switch(detonation_status)
if(DETONATION_HIT_STATION)
return /datum/cinematic/nuke/clown
if(DETONATION_NEAR_MISSED_STATION)
return /datum/cinematic/nuke/ops_miss
if(DETONATION_HIT_SYNDIE_BASE, DETONATION_MISSED_STATION)
return /datum/cinematic/nuke/fake //it is farther away, so just a bikehorn instead of an airhorn
/obj/machinery/nuclearbomb/syndicate/bananium/really_actually_explode(off_station)
Cinematic(get_cinematic_type(off_station), world)
for(var/i in GLOB.human_list)
var/mob/living/carbon/human/H = i
var/turf/T = get_turf(H)
if(!T || T.z != z)
continue
H.Stun(10)
var/obj/item/clothing/C
if(!H.w_uniform || H.dropItemToGround(H.w_uniform))
C = new /obj/item/clothing/under/rank/civilian/clown(H)
ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
H.equip_to_slot_or_del(C, ITEM_SLOT_ICLOTHING)
stack_trace("[type] - get_cinematic_type got a detonation_status it was not expecting. (Got: [detonation_status])")
return /datum/cinematic/nuke/fake
if(!H.shoes || H.dropItemToGround(H.shoes))
C = new /obj/item/clothing/shoes/clown_shoes(H)
ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
H.equip_to_slot_or_del(C, ITEM_SLOT_FEET)
/obj/machinery/nuclearbomb/syndicate/bananium/nuke_effects(list/affected_z_levels)
INVOKE_ASYNC(GLOBAL_PROC, /proc/callback_on_everyone_on_z, affected_z_levels, CALLBACK(GLOBAL_PROC, /proc/make_into_clown), src)
if(!H.wear_mask || H.dropItemToGround(H.wear_mask))
C = new /obj/item/clothing/mask/gas/clown_hat(H)
ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
H.equip_to_slot_or_del(C, ITEM_SLOT_MASK)
/**
* Helper proc that handles making someone into a clown after a bananium nuke goes off.
*/
/proc/make_into_clown(mob/living/carbon/human/clowned_on)
if(!istype(clowned_on))
return
H.dna.add_mutation(/datum/mutation/human/clumsy)
H.gain_trauma(/datum/brain_trauma/mild/phobia/clowns, TRAUMA_RESILIENCE_LOBOTOMY) //MWA HA HA
clowned_on.Stun(1 SECONDS)
if(!clowned_on.w_uniform || clowned_on.dropItemToGround(clowned_on.w_uniform))
var/obj/item/clothing/clown_shirt = new /obj/item/clothing/under/rank/civilian/clown(clowned_on)
ADD_TRAIT(clown_shirt, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
clowned_on.equip_to_slot_or_del(clown_shirt, ITEM_SLOT_ICLOTHING)
if(!clowned_on.shoes || clowned_on.dropItemToGround(clowned_on.shoes))
var/obj/item/clothing/clown_shoes = new /obj/item/clothing/shoes/clown_shoes(clowned_on)
ADD_TRAIT(clown_shoes, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
clowned_on.equip_to_slot_or_del(clown_shoes, ITEM_SLOT_FEET)
if(!clowned_on.wear_mask || clowned_on.dropItemToGround(clowned_on.wear_mask))
var/obj/item/clothing/clown_mask = new /obj/item/clothing/mask/gas/clown_hat(clowned_on)
ADD_TRAIT(clown_mask, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
clowned_on.equip_to_slot_or_del(clown_mask, ITEM_SLOT_MASK)
clowned_on.dna.add_mutation(/datum/mutation/human/clumsy)
clowned_on.gain_trauma(/datum/brain_trauma/mild/phobia/clowns, TRAUMA_RESILIENCE_LOBOTOMY) //MWA HA HA
@@ -0,0 +1,136 @@
/obj/item/disk
icon = 'icons/obj/module.dmi'
w_class = WEIGHT_CLASS_TINY
inhand_icon_state = "card-id"
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi'
icon_state = "datadisk0"
drop_sound = 'sound/items/handling/disk_drop.ogg'
pickup_sound = 'sound/items/handling/disk_pickup.ogg'
// DAT FUKKEN DISK.
/obj/item/disk/nuclear
name = "nuclear authentication disk"
desc = "Better keep this safe."
icon_state = "nucleardisk"
max_integrity = 250
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 30, BIO = 0, FIRE = 100, ACID = 100)
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
/// Whether we're a real nuke disk or not.
var/fake = FALSE
/// The last secure location the disk was at.
var/turf/last_secured_location
/// The last world time the disk moved.
var/last_disk_move
/obj/item/disk/nuclear/Initialize(mapload)
. = ..()
AddElement(/datum/element/bed_tuckable, 6, -6, 0)
AddComponent(/datum/component/stationloving, !fake)
if(!fake)
SSpoints_of_interest.make_point_of_interest(src)
last_disk_move = world.time
START_PROCESSING(SSobj, src)
/obj/item/disk/nuclear/process()
if(fake)
STOP_PROCESSING(SSobj, src)
CRASH("A fake nuke disk tried to call process(). Who the fuck and how the fuck")
var/turf/new_turf = get_turf(src)
if (is_secured())
last_secured_location = new_turf
last_disk_move = world.time
var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control
if(istype(loneop) && loneop.occurrences < loneop.max_occurrences && prob(loneop.weight))
loneop.weight = max(loneop.weight - 1, 0)
if(loneop.weight % 5 == 0 && SSticker.totalPlayers > 1)
message_admins("[src] is secured (currently in [ADMIN_VERBOSEJMP(new_turf)]). The weight of Lone Operative is now [loneop.weight].")
log_game("[src] being secured has reduced the weight of the Lone Operative event to [loneop.weight].")
else
/// How comfy is our disk?
var/disk_comfort_level = 0
//Go through and check for items that make disk comfy
for(var/obj/comfort_item in loc)
if(istype(comfort_item, /obj/item/bedsheet) || istype(comfort_item, /obj/structure/bed))
disk_comfort_level++
if(last_disk_move < world.time - 5000 && prob((world.time - 5000 - last_disk_move)*0.0001))
var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control
if(istype(loneop) && loneop.occurrences < loneop.max_occurrences)
loneop.weight += 1
if(loneop.weight % 5 == 0 && SSticker.totalPlayers > 1)
if(disk_comfort_level >= 2)
visible_message(span_notice("[src] sleeps soundly. Sleep tight, disky."))
message_admins("[src] is unsecured in [ADMIN_VERBOSEJMP(new_turf)]. The weight of Lone Operative is now [loneop.weight].")
log_game("[src] is unsecured for too long in [loc_name(new_turf)], and has increased the weight of the Lone Operative event to [loneop.weight].")
/obj/item/disk/nuclear/proc/is_secured()
if (last_secured_location == get_turf(src))
return FALSE
var/mob/holder = pulledby || get(src, /mob)
if (isnull(holder?.client))
return FALSE
return TRUE
/obj/item/disk/nuclear/examine(mob/user)
. = ..()
if(!fake)
return
if(isobserver(user) || HAS_TRAIT(user, TRAIT_DISK_VERIFIER) || (user.mind && HAS_TRAIT(user.mind, TRAIT_DISK_VERIFIER)))
. += span_warning("The serial numbers on [src] are incorrect.")
/*
* You can't accidentally eat the nuke disk, bro
*/
/obj/item/disk/nuclear/on_accidental_consumption(mob/living/carbon/M, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
M.visible_message(span_warning("[M] looks like [M.p_theyve()] just bitten into something important."), \
span_warning("Wait, is this the nuke disk?"))
return discover_after
/obj/item/disk/nuclear/attackby(obj/item/weapon, mob/living/user, params)
if(istype(weapon, /obj/item/claymore/highlander) && !fake)
var/obj/item/claymore/highlander/claymore = weapon
if(claymore.nuke_disk)
to_chat(user, span_notice("Wait... what?"))
qdel(claymore.nuke_disk)
claymore.nuke_disk = null
return
user.visible_message(
span_warning("[user] captures [src]!"),
span_userdanger("You've got the disk! Defend it with your life!"),
)
forceMove(claymore)
claymore.nuke_disk = src
return TRUE
return ..()
/obj/item/disk/nuclear/suicide_act(mob/user)
user.visible_message(span_suicide("[user] is going delta! It looks like [user.p_theyre()] trying to commit suicide!"))
playsound(src, 'sound/machines/alarm.ogg', 50, -1, TRUE)
for(var/i in 1 to 100)
addtimer(CALLBACK(user, /atom/proc/add_atom_colour, (i % 2)? "#00FF00" : "#FF0000", ADMIN_COLOUR_PRIORITY), i)
addtimer(CALLBACK(src, .proc/manual_suicide, user), 101)
return MANUAL_SUICIDE
/obj/item/disk/nuclear/proc/manual_suicide(mob/living/user)
user.remove_atom_colour(ADMIN_COLOUR_PRIORITY)
user.visible_message(span_suicide("[user] is destroyed by the nuclear blast!"))
user.adjustOxyLoss(200)
user.death(0)
/obj/item/disk/nuclear/fake
fake = TRUE
/obj/item/disk/nuclear/fake/obvious
name = "cheap plastic imitation of the nuclear authentication disk"
desc = "How anyone could mistake this for the real thing is beyond you."
@@ -0,0 +1,645 @@
/// Whether the station has been nuked itself. TRUE only if the station was actually hit by the nuke, otherwise FALSE
GLOBAL_VAR_INIT(station_was_nuked, FALSE)
/// The source of the last nuke that went off
GLOBAL_VAR(station_nuke_source)
/obj/machinery/nuclearbomb
name = "nuclear fission explosive"
desc = "You probably shouldn't stick around to see if this is armed."
icon = 'icons/obj/machines/nuke.dmi'
icon_state = "nuclearbomb_base"
anchored = FALSE
density = TRUE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
use_power = NO_POWER_USE
/// What the timer is set to, in seconds
var/timer_set = 90
/// What the min value the timer can be, in seconds
var/minimum_timer_set = 90
/// What the max value the timer can be, in seconds
var/maximum_timer_set = 3600
/// The current input of the numpad on the bomb
var/numeric_input = ""
/// What mode the UI currently is in
var/ui_mode = NUKEUI_AWAIT_DISK
/// Whether we're currently timing an explosive and counting down
var/timing = FALSE
/// Whether the timer has elapsed and we're currently exploding
var/exploding = FALSE
/// Whether we've actually fully exploded
var/exploded = FALSE
/// world time tracker for when we're going to explode
var/detonation_timer = null
/// The code we need to detonate this nuke. Starts as "admin", purposefully un-enterable
var/r_code = "ADMIN"
/// If TRUE, the correct code has been entered and we can start the nuke
var/yes_code = FALSE
/// Whether the nuke safety is on, can't explode if it is
var/safety = TRUE
/// The nuke disk currently inserted into the nuke
var/obj/item/disk/nuclear/auth
/// The alert level that was set before the nuke started, so we can revert to the correct level after
var/previous_level = ""
/// The nuke core within the nuke, created in initialize
var/obj/item/nuke_core/core
/// The current state of deconstructing / opening up the nuke to access the core
var/deconstruction_state = NUKESTATE_INTACT
/// Overlay - flashing lights over the nuke
var/lights = ""
/// Overlay - shows the interior of the nuke
var/interior = ""
/// if TRUE, this nuke is actually a real nuke, and not a prank or toy
var/proper_bomb = TRUE //Please
/// A reference to the countdown that goes up over the nuke
var/obj/effect/countdown/nuclearbomb/countdown
/obj/machinery/nuclearbomb/Initialize(mapload)
. = ..()
countdown = new(src)
GLOB.nuke_list += src
core = new /obj/item/nuke_core(src)
STOP_PROCESSING(SSobj, core)
update_appearance()
SSpoints_of_interest.make_point_of_interest(src)
previous_level = get_security_level()
/obj/machinery/nuclearbomb/Destroy()
safety = FALSE
if(!exploding)
// If we're not exploding, set the alert level back to normal
toggle_nuke_safety()
GLOB.nuke_list -= src
QDEL_NULL(countdown)
QDEL_NULL(core)
return ..()
/obj/machinery/nuclearbomb/examine(mob/user)
. = ..()
if(exploding)
. += span_bolddanger("It is in the process of exploding. Perhaps reviewing your affairs is in order.")
if(timing)
. += span_danger("There are [get_time_left()] seconds until detonation.")
/// Checks if the disk inserted is a real nuke disk or not.
/obj/machinery/nuclearbomb/proc/disk_check(obj/item/disk/nuclear/inserted_disk)
if(inserted_disk.fake)
say("Authentication failure; disk not recognised.")
return FALSE
return TRUE
/obj/machinery/nuclearbomb/attackby(obj/item/weapon, mob/user, params)
if (istype(weapon, /obj/item/disk/nuclear))
if(!disk_check(weapon))
return TRUE
if(!user.transferItemToLoc(weapon, src))
return TRUE
auth = weapon
update_ui_mode()
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
add_fingerprint(user)
return TRUE
switch(deconstruction_state)
if(NUKESTATE_INTACT)
if(istype(weapon, /obj/item/screwdriver/nuke))
to_chat(user, span_notice("You start removing [src]'s front panel's screws..."))
if(weapon.use_tool(src, user, 6 SECONDS, volume = 100))
deconstruction_state = NUKESTATE_UNSCREWED
to_chat(user, span_notice("You remove the screws from [src]'s front panel."))
update_appearance()
return TRUE
if(NUKESTATE_PANEL_REMOVED)
if(weapon.tool_behaviour == TOOL_WELDER)
if(!weapon.tool_start_check(user, amount = 1))
return TRUE
to_chat(user, span_notice("You start cutting [src]'s inner plate..."))
if(weapon.use_tool(src, user, 8 SECONDS, volume=100, amount=1))
to_chat(user, span_notice("You cut [src]'s inner plate."))
deconstruction_state = NUKESTATE_WELDED
update_appearance()
return TRUE
if(NUKESTATE_CORE_EXPOSED)
if(istype(weapon, /obj/item/nuke_core_container))
var/obj/item/nuke_core_container/core_box = weapon
to_chat(user, span_notice("You start loading the plutonium core into [core_box]..."))
if(do_after(user, 5 SECONDS, target=src))
if(core_box.load(core, user))
to_chat(user, span_notice("You load the plutonium core into [core_box]."))
deconstruction_state = NUKESTATE_CORE_REMOVED
update_appearance()
core = null
else
to_chat(user, span_warning("You fail to load the plutonium core into [core_box]. [core_box] has already been used!"))
return TRUE
if(istype(weapon, /obj/item/stack/sheet/iron))
if(!weapon.tool_start_check(user, amount = 20))
return TRUE
to_chat(user, span_notice("You begin repairing [src]'s inner metal plate..."))
if(weapon.use_tool(src, user, 10 SECONDS, amount = 20))
to_chat(user, span_notice("You repair [src]'s inner metal plate. The radiation is contained."))
deconstruction_state = NUKESTATE_PANEL_REMOVED
STOP_PROCESSING(SSobj, core)
update_appearance()
return TRUE
return ..()
/obj/machinery/nuclearbomb/crowbar_act(mob/user, obj/item/tool)
switch(deconstruction_state)
if(NUKESTATE_UNSCREWED)
to_chat(user, span_notice("You start removing [src]'s front panel..."))
if(tool.use_tool(src, user, 30, volume=100))
to_chat(user, span_notice("You remove [src]'s front panel."))
deconstruction_state = NUKESTATE_PANEL_REMOVED
update_appearance()
return TRUE
if(NUKESTATE_WELDED)
to_chat(user, span_notice("You start prying off [src]'s inner plate..."))
if(tool.use_tool(src, user, 30, volume=100))
to_chat(user, span_notice("You pry off [src]'s inner plate. You can see the core's green glow!"))
deconstruction_state = NUKESTATE_CORE_EXPOSED
update_appearance()
START_PROCESSING(SSobj, core)
return TRUE
return FALSE
/obj/machinery/nuclearbomb/can_interact(mob/user)
if(HAS_TRAIT(user, TRAIT_CAN_USE_NUKE))
return TRUE
return ..()
/obj/machinery/nuclearbomb/ui_state(mob/user)
if(HAS_TRAIT(user, TRAIT_CAN_USE_NUKE))
return GLOB.physical_state
return ..()
/// Gets the current state of the nuke.
/obj/machinery/nuclearbomb/proc/get_nuke_state()
if(exploding)
return NUKE_ON_EXPLODING
if(timing)
return NUKE_ON_TIMING
if(safety)
return NUKE_OFF_LOCKED
else
return NUKE_OFF_UNLOCKED
/obj/machinery/nuclearbomb/update_icon_state()
if(deconstruction_state != NUKESTATE_INTACT)
icon_state = "nuclearbomb_base"
return ..()
switch(get_nuke_state())
if(NUKE_OFF_LOCKED, NUKE_OFF_UNLOCKED)
icon_state = "nuclearbomb_base"
if(NUKE_ON_TIMING)
icon_state = "nuclearbomb_timing"
if(NUKE_ON_EXPLODING)
icon_state = "nuclearbomb_exploding"
return ..()
/obj/machinery/nuclearbomb/update_overlays()
. = ..()
if(lights)
cut_overlay(lights)
cut_overlay(interior)
switch(deconstruction_state)
if(NUKESTATE_UNSCREWED)
interior = "panel-unscrewed"
if(NUKESTATE_PANEL_REMOVED)
interior = "panel-removed"
if(NUKESTATE_WELDED)
interior = "plate-welded"
if(NUKESTATE_CORE_EXPOSED)
interior = "plate-removed"
if(NUKESTATE_CORE_REMOVED)
interior = "core-removed"
if(NUKESTATE_INTACT)
return
switch(get_nuke_state())
if(NUKE_OFF_LOCKED)
lights = ""
return
if(NUKE_OFF_UNLOCKED)
lights = "lights-safety"
if(NUKE_ON_TIMING)
lights = "lights-timing"
if(NUKE_ON_EXPLODING)
lights = "lights-exploding"
add_overlay(lights)
add_overlay(interior)
/obj/machinery/nuclearbomb/process()
if(!timing || exploding)
return
if(detonation_timer < world.time)
explode()
return
var/volume = (get_time_left() <= 20 ? 30 : 5)
playsound(loc, 'sound/items/timer.ogg', volume, FALSE)
/// Changes what mode the UI is depending on the state of the nuke.
/obj/machinery/nuclearbomb/proc/update_ui_mode()
if(exploded)
ui_mode = NUKEUI_EXPLODED
return
if(!auth)
ui_mode = NUKEUI_AWAIT_DISK
return
if(timing)
ui_mode = NUKEUI_TIMING
return
if(!safety)
ui_mode = NUKEUI_AWAIT_ARM
return
if(!yes_code)
ui_mode = NUKEUI_AWAIT_CODE
return
ui_mode = NUKEUI_AWAIT_TIMER
/obj/machinery/nuclearbomb/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "NuclearBomb", name)
ui.open()
/obj/machinery/nuclearbomb/ui_data(mob/user)
var/list/data = list()
data["disk_present"] = auth
var/hidden_code = (ui_mode == NUKEUI_AWAIT_CODE && numeric_input != "ERROR")
var/current_code = ""
if(hidden_code)
while(length(current_code) < length(numeric_input))
current_code = "[current_code]*"
else
current_code = numeric_input
while(length(current_code) < 5)
current_code = "[current_code]-"
var/first_status
var/second_status
switch(ui_mode)
if(NUKEUI_AWAIT_DISK)
first_status = "DEVICE LOCKED"
if(timing)
second_status = "TIME: [get_time_left()]"
else
second_status = "AWAIT DISK"
if(NUKEUI_AWAIT_CODE)
first_status = "INPUT CODE"
second_status = "CODE: [current_code]"
if(NUKEUI_AWAIT_TIMER)
first_status = "INPUT TIME"
second_status = "TIME: [current_code]"
if(NUKEUI_AWAIT_ARM)
first_status = "DEVICE READY"
second_status = "TIME: [get_time_left()]"
if(NUKEUI_TIMING)
first_status = "DEVICE ARMED"
second_status = "TIME: [get_time_left()]"
if(NUKEUI_EXPLODED)
first_status = "DEVICE DEPLOYED"
second_status = "THANK YOU"
data["status1"] = first_status
data["status2"] = second_status
data["anchored"] = anchored
return data
/obj/machinery/nuclearbomb/ui_act(action, params)
. = ..()
if(.)
return
playsound(src, SFX_TERMINAL_TYPE, 20, FALSE)
switch(action)
if("eject_disk")
if(auth && auth.loc == src)
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
auth.forceMove(get_turf(src))
auth = null
. = TRUE
else
var/obj/item/I = usr.is_holding_item_of_type(/obj/item/disk/nuclear)
if(I && disk_check(I) && usr.transferItemToLoc(I, src))
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
auth = I
. = TRUE
update_ui_mode()
if("keypad")
if(auth)
var/digit = params["digit"]
switch(digit)
if("C")
if(auth && ui_mode == NUKEUI_AWAIT_ARM)
toggle_nuke_safety()
yes_code = FALSE
playsound(src, 'sound/machines/nuke/confirm_beep.ogg', 50, FALSE)
update_ui_mode()
else
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
numeric_input = ""
. = TRUE
if("E")
switch(ui_mode)
if(NUKEUI_AWAIT_CODE)
if(numeric_input == r_code)
numeric_input = ""
yes_code = TRUE
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
. = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
numeric_input = "ERROR"
if(NUKEUI_AWAIT_TIMER)
var/number_value = text2num(numeric_input)
if(number_value)
timer_set = clamp(number_value, minimum_timer_set, maximum_timer_set)
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
toggle_nuke_safety()
. = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
update_ui_mode()
if("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
if(numeric_input != "ERROR")
numeric_input += digit
if(length(numeric_input) > 5)
numeric_input = "ERROR"
else
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
. = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
if("arm")
if(auth && yes_code && !safety && !exploded)
playsound(src, 'sound/machines/nuke/confirm_beep.ogg', 50, FALSE)
toggle_nuke_armed()
update_ui_mode()
. = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
if("anchor")
if(auth && yes_code)
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
set_anchor(usr)
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
/// Anchors the nuke, duh. Can only be done if the disk is inside.
/obj/machinery/nuclearbomb/proc/set_anchor(mob/anchorer)
if(isinspace() && !anchored)
if(anchorer)
to_chat(anchorer, span_warning("There is nothing to anchor to!"))
return
set_anchored(!anchored)
/// Toggles the safety of the nuke.
/obj/machinery/nuclearbomb/proc/toggle_nuke_safety()
safety = !safety
// We're safe now, so stop any ongoing timers
if(safety)
if(timing)
disarm_nuke()
timing = FALSE
detonation_timer = null
countdown.stop()
/// Arms the nuke, or disarms it if it's already active.
/obj/machinery/nuclearbomb/proc/toggle_nuke_armed()
if(safety)
to_chat(usr, span_danger("The safety is still on."))
return
timing = !timing
if(timing)
arm_nuke(usr)
else
disarm_nuke(usr)
/// Arms the nuke, making it active and triggering all pinpointers to start counting down (+delta alert)
/obj/machinery/nuclearbomb/proc/arm_nuke(mob/armer)
var/turf/our_turf = get_turf(src)
message_admins("\The [src] was armed at [ADMIN_VERBOSEJMP(our_turf)] by [armer ? ADMIN_LOOKUPFLW(armer) : "an unknown user"].")
log_game("\The [src] was armed at [loc_name(our_turf)] by [armer ? key_name(armer) : "an unknown user"].")
previous_level = get_security_level()
detonation_timer = world.time + (timer_set * 10)
for(var/obj/item/pinpointer/nuke/syndicate/nuke_pointer in GLOB.pinpointer_list)
nuke_pointer.switch_mode_to(TRACK_INFILTRATOR)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NUKE_DEVICE_ARMED, src)
countdown.start()
set_security_level("delta")
update_appearance()
/// Disarms the nuke, reverting all pinpointers and the security level
/obj/machinery/nuclearbomb/proc/disarm_nuke(mob/disarmer)
var/turf/our_turf = get_turf(src)
message_admins("\The [src] at [ADMIN_VERBOSEJMP(our_turf)] was disarmed by [disarmer ? ADMIN_LOOKUPFLW(disarmer) : "an unknown user"].")
log_game("\The [src] at [loc_name(our_turf)] was disarmed by [disarmer ? key_name(disarmer) : "an unknown user"].")
detonation_timer = null
set_security_level(previous_level)
for(var/obj/item/pinpointer/nuke/syndicate/nuke_pointer in GLOB.pinpointer_list)
nuke_pointer.switch_mode_to(initial(nuke_pointer.mode))
nuke_pointer.alert = FALSE
countdown.stop()
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NUKE_DEVICE_DISARMED, src)
update_appearance()
/// If the nuke is active, gets how much time is left until it detonates, in seconds.
/// If the nuke is not active, gets how much time the nuke is set for, in seconds.
/obj/machinery/nuclearbomb/proc/get_time_left()
if(timing)
. = round(max(0, detonation_timer - world.time) / 10, 1)
else
. = timer_set
/obj/machinery/nuclearbomb/blob_act(obj/structure/blob/attacking_blob)
if(exploding)
return
qdel(src)
/obj/machinery/nuclearbomb/zap_act(power, zap_flags)
. = ..()
if(zap_flags & ZAP_MACHINE_EXPLOSIVE)
qdel(src)//like the singulo, tesla deletes it. stops it from exploding over and over
#define NUKE_RADIUS 127
/**
* Begins the process of exploding the nuke.
* [proc/explode] -> [proc/actually_explode] -> [proc/really_actually_explode]
*
* Goes through a few timers and plays a cinematic.
*/
/obj/machinery/nuclearbomb/proc/explode()
if(safety)
timing = FALSE
return FALSE
exploding = TRUE
yes_code = FALSE
safety = TRUE
update_appearance()
sound_to_playing_players('sound/machines/alarm.ogg')
if(SSticker?.mode)
SSticker.roundend_check_paused = TRUE
addtimer(CALLBACK(src, .proc/actually_explode), 10 SECONDS)
return TRUE
/obj/machinery/nuclearbomb/proc/actually_explode()
if(!core)
play_cinematic(/datum/cinematic/nuke/no_core, world)
SSticker.roundend_check_paused = FALSE
return
SSlag_switch.set_measure(DISABLE_NON_OBSJOBS, TRUE)
var/detonation_status
var/turf/bomb_location = get_turf(src)
var/area/nuke_area = get_area(bomb_location)
// The nuke was on the station zlevel
if(bomb_location && is_station_level(bomb_location.z))
// Nuke missed, it's in space
if(istype(nuke_area, /area/space))
detonation_status = DETONATION_NEAR_MISSED_STATION
// Nuke missed, it'stoo far from the station
else if((bomb_location.x < (128 - NUKE_RADIUS)) \
|| (bomb_location.x > (128 + NUKE_RADIUS)) \
|| (bomb_location.y < (128 - NUKE_RADIUS)) \
|| (bomb_location.y > (128 + NUKE_RADIUS)))
detonation_status = DETONATION_NEAR_MISSED_STATION
// Confirming good hits, the nuke hit the station
else
detonation_status = DETONATION_HIT_STATION
GLOB.station_was_nuked = TRUE
// The nuke was on the syndicate base
else if(bomb_location.onSyndieBase())
detonation_status = DETONATION_HIT_SYNDIE_BASE
// The nuke was somewhere wacky - deep space, mining z, centcom? Whatever
else
detonation_status = DETONATION_MISSED_STATION
/*
if(detonation_status < NUKE_MISS_STATION)
SSshuttle.registerHostileEnvironment(src)
SSshuttle.lockdown = TRUE
*/
// Missing the station will register a hostile environment, until it actually explodes
if(detonation_status == DETONATION_MISSED_STATION)
SSshuttle.registerHostileEnvironment(src)
SSshuttle.lockdown = TRUE
// Now go play the cinematic
GLOB.station_nuke_source = detonation_status
really_actually_explode(detonation_status)
SSticker.roundend_check_paused = FALSE
return detonation_status
/obj/machinery/nuclearbomb/proc/really_actually_explode(detonation_status)
play_cinematic(get_cinematic_type(detonation_status), world, CALLBACK(SSticker, /datum/controller/subsystem/ticker/proc/station_explosion_detonation, src))
var/turf/bomb_location = get_turf(src)
var/list/z_levels_to_blow = list()
if(detonation_status == DETONATION_HIT_STATION)
z_levels_to_blow |= SSmapping.levels_by_trait(ZTRAIT_STATION)
// Don't kill people in the station if the nuke missed, even if we are technically on the same z-level
else if(detonation_status != DETONATION_NEAR_MISSED_STATION)
z_levels_to_blow |= bomb_location.z
if(length(z_levels_to_blow))
nuke_effects(z_levels_to_blow)
return TRUE
/// Cause nuke effects to the passed z-levels.
/obj/machinery/nuclearbomb/proc/nuke_effects(list/affected_z_levels)
INVOKE_ASYNC(GLOBAL_PROC, /proc/callback_on_everyone_on_z, affected_z_levels, CALLBACK(GLOBAL_PROC, /proc/nuke_gib), src)
/// Gets what type of cinematic this nuke showcases depending on where we detonated.
/obj/machinery/nuclearbomb/proc/get_cinematic_type(detonation_status)
if(isnull(detonation_status))
return /datum/cinematic/nuke/self_destruct_miss
return /datum/cinematic/nuke/self_destruct
#undef NUKE_RADIUS
/**
* Helper proc that handles gibbing someone who has been nuked.
*/
/proc/nuke_gib(mob/living/gibbed, atom/source)
if(istype(gibbed.loc, /obj/structure/closet/secure_closet/freezer))
var/obj/structure/closet/secure_closet/freezer/freezer = gibbed.loc
if(!freezer.jones)
to_chat(gibbed, span_boldannounce("You hold onto [freezer] as [source] goes off. \
Luckily, as [freezer] is lead-lined, you survive."))
freezer.jones = TRUE
return FALSE
if(gibbed.stat == DEAD)
return FALSE
to_chat(gibbed, span_userdanger("You are shredded to atoms by [source]!"))
gibbed.gib()
return TRUE
/**
* Invokes a callback on every living mob on the provided z level.
*/
/proc/callback_on_everyone_on_z(list/z_levels, datum/callback/to_do, atom/optional_source)
if(!islist(z_levels))
CRASH("callback_on_everyone_on_z called [z_levels ? "with an invalid z-level list":"without any z-levels"].")
for(var/mob/living/victim as anything in GLOB.mob_living_list)
if(QDELETED(victim) || isnull(victim.loc))
continue
var/turf/target_turf = get_turf(victim)
if(target_turf && !(target_turf.z in z_levels))
continue
to_do.Invoke(victim, optional_source)
@@ -0,0 +1,77 @@
/// A fake nuke that actually contains beer.
/obj/machinery/nuclearbomb/beer
name = "\improper Nanotrasen-brand nuclear fission explosive"
desc = "One of the more successful achievements of the Nanotrasen Corporate Warfare Division, their nuclear fission explosives are renowned for being cheap to produce and devastatingly effective. Signs explain that though this particular device has been decommissioned, every Nanotrasen station is equipped with an equivalent one, just in case. All Captains carefully guard the disk needed to detonate them - at least, the sign says they do. There seems to be a tap on the back."
proper_bomb = FALSE
/// The keg located within the beer nuke.
var/obj/structure/reagent_dispensers/beerkeg/keg
/obj/machinery/nuclearbomb/beer/Initialize(mapload)
. = ..()
keg = new(src)
QDEL_NULL(core)
/obj/machinery/nuclearbomb/beer/examine(mob/user)
. = ..()
if(keg.reagents.total_volume)
. += span_notice("It has [keg.reagents.total_volume] unit\s left.")
else
. += span_danger("It's empty.")
/obj/machinery/nuclearbomb/beer/attackby(obj/item/weapon, mob/user, params)
if(weapon.is_refillable())
weapon.afterattack(keg, user, TRUE) // redirect refillable containers to the keg, allowing them to be filled
return TRUE // pretend we handled the attack, too.
if(istype(weapon, /obj/item/nuke_core_container))
to_chat(user, span_notice("[src] has had its plutonium core removed as a part of being decommissioned."))
return TRUE
return ..()
/obj/machinery/nuclearbomb/beer/actually_explode()
//Unblock roundend, we're not actually exploding.
SSticker.roundend_check_paused = FALSE
var/turf/bomb_location = get_turf(src)
if(!bomb_location)
disarm_nuke()
return
if(is_station_level(bomb_location.z))
addtimer(CALLBACK(src, .proc/really_actually_explode), 11 SECONDS)
else
visible_message(span_notice("[src] fizzes ominously."))
addtimer(CALLBACK(src, .proc/local_foam), 11 SECONDS)
/obj/machinery/nuclearbomb/beer/disarm_nuke(mob/disarmer)
exploding = FALSE
exploded = TRUE
return ..()
/obj/machinery/nuclearbomb/beer/proc/local_foam()
var/datum/reagents/R = new/datum/reagents(1000)
R.my_atom = src
R.add_reagent(/datum/reagent/consumable/ethanol/beer, 100)
var/datum/effect_system/fluid_spread/foam/foam = new
foam.set_up(200, location = get_turf(src), carry = R)
foam.start()
disarm_nuke()
/obj/machinery/nuclearbomb/beer/proc/stationwide_foam()
priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert")
for (var/obj/machinery/atmospherics/components/unary/vent_scrubber/vent in GLOB.machines)
var/turf/vent_turf = get_turf(vent)
if (!vent_turf || !is_station_level(vent_turf.z) || vent.welded)
continue
var/datum/reagents/beer = new /datum/reagents(1000)
beer.my_atom = vent
beer.add_reagent(/datum/reagent/consumable/ethanol/beer, 100)
beer.create_foam(/datum/effect_system/fluid_spread/foam, DIAMOND_AREA(10))
CHECK_TICK
/obj/machinery/nuclearbomb/beer/really_actually_explode(detonation_status)
disarm_nuke()
stationwide_foam()
@@ -0,0 +1,23 @@
/obj/machinery/nuclearbomb/selfdestruct
name = "station self-destruct terminal"
desc = "For when it all gets too much to bear. Do not taunt."
icon = 'icons/obj/machines/nuke_terminal.dmi'
icon_state = "nuclearbomb_base"
anchored = TRUE //stops it being moved
/obj/machinery/nuclearbomb/selfdestruct/set_anchor()
return
/obj/machinery/nuclearbomb/selfdestruct/toggle_nuke_safety()
. = ..()
if(timing)
SSmapping.add_nuke_threat(src)
else
SSmapping.remove_nuke_threat(src)
/obj/machinery/nuclearbomb/selfdestruct/toggle_nuke_armed()
. = ..()
if(timing)
SSmapping.add_nuke_threat(src)
else
SSmapping.remove_nuke_threat(src)
@@ -0,0 +1,30 @@
/obj/machinery/nuclearbomb/syndicate
/obj/machinery/nuclearbomb/syndicate/get_cinematic_type(detonation_status)
switch(detonation_status)
// The nuke detonated on station
if(DETONATION_HIT_STATION)
// And it was detonated by nuke ops
if(length(get_antag_minds(/datum/antagonist/nukeop)))
if(is_infiltrator_docked_at_centcom())
// And the ops escaped, they won!
return /datum/cinematic/nuke/ops_victory
else
// And the ops failed to escape, mutually assured destruction!
return /datum/cinematic/nuke/mutual_destruction
// It was detonated by something or someone what wasn't nuke ops,
// throw it to the default explosion animation (self destruct)
else
return ..()
// The nuke detonated on station Z, but in space
if(DETONATION_NEAR_MISSED_STATION)
return /datum/cinematic/nuke/ops_miss
// The nuke detonated off station Z, and/or on the syndicate base
if(DETONATION_HIT_SYNDIE_BASE, DETONATION_MISSED_STATION)
return /datum/cinematic/nuke/far_explosion
stack_trace("[type] - get_cinematic_type got a detonation_status it was not expecting. (Got: [detonation_status])")
return /datum/cinematic/nuke/far_explosion
+60 -38
View File
@@ -338,56 +338,77 @@
O.team = src
objectives += O
/datum/team/nuclear/proc/disk_rescued()
for(var/obj/item/disk/nuclear/D in SSpoints_of_interest.real_nuclear_disks)
/datum/team/nuclear/proc/is_disk_rescued()
for(var/obj/item/disk/nuclear/nuke_disk in SSpoints_of_interest.real_nuclear_disks)
//If emergency shuttle is in transit disk is only safe on it
if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE)
if(!SSshuttle.emergency.is_in_shuttle_bounds(D))
if(!SSshuttle.emergency.is_in_shuttle_bounds(nuke_disk))
return FALSE
//If shuttle escaped check if it's on centcom side
else if(SSshuttle.emergency.mode == SHUTTLE_ENDGAME)
if(!D.onCentCom())
if(!nuke_disk.onCentCom())
return FALSE
else //Otherwise disk is safe when on station
var/turf/T = get_turf(D)
if(!T || !is_station_level(T.z))
var/turf/disk_turf = get_turf(nuke_disk)
if(!disk_turf || !is_station_level(disk_turf.z))
return FALSE
return TRUE
/datum/team/nuclear/proc/operatives_dead()
for(var/I in members)
var/datum/mind/operative_mind = I
/datum/team/nuclear/proc/are_all_operatives_dead()
for(var/datum/mind/operative_mind as anything in members)
if(ishuman(operative_mind.current) && (operative_mind.current.stat != DEAD))
return FALSE
return TRUE
/datum/team/nuclear/proc/get_result()
var/evacuation = EMERGENCY_ESCAPED_OR_ENDGAMED
var/disk_rescued = disk_rescued()
var/syndies_didnt_escape = !syndies_escaped()
var/shuttle_evacuated = EMERGENCY_ESCAPED_OR_ENDGAMED
var/disk_rescued = is_disk_rescued()
var/syndies_didnt_escape = !is_infiltrator_docked_at_centcom()
var/team_is_dead = are_all_operatives_dead()
var/station_was_nuked = GLOB.station_was_nuked
var/station_nuke_source = GLOB.station_nuke_source
if(station_nuke_source == NUKE_SYNDICATE_BASE)
// The nuke detonated on the syndicate base
if(station_nuke_source == DETONATION_HIT_SYNDIE_BASE)
return NUKE_RESULT_FLUKE
else if(station_was_nuked && !syndies_didnt_escape)
return NUKE_RESULT_NUKE_WIN
else if (station_was_nuked && syndies_didnt_escape)
return NUKE_RESULT_NOSURVIVORS
else if (!disk_rescued && !station_was_nuked && station_nuke_source && !syndies_didnt_escape)
return NUKE_RESULT_WRONG_STATION
else if (!disk_rescued && !station_was_nuked && station_nuke_source && syndies_didnt_escape)
return NUKE_RESULT_WRONG_STATION_DEAD
else if ((disk_rescued && evacuation) && operatives_dead())
return NUKE_RESULT_CREW_WIN_SYNDIES_DEAD
else if (disk_rescued)
return NUKE_RESULT_CREW_WIN
else if (!disk_rescued && operatives_dead())
return NUKE_RESULT_DISK_LOST
else if (!disk_rescued && evacuation)
return NUKE_RESULT_DISK_STOLEN
// The station was nuked
if(station_was_nuked)
// The station was nuked and the infiltrator failed to escape
if(syndies_didnt_escape)
return NUKE_RESULT_NOSURVIVORS
// The station was nuked and the infiltrator escaped, and the nuke ops won
else
return NUKE_RESULT_NUKE_WIN
// The station was not nuked, but something was
else if(station_nuke_source && !disk_rescued)
// The station was not nuked, but something was, and the syndicates didn't escape it
if(syndies_didnt_escape)
return NUKE_RESULT_WRONG_STATION_DEAD
// The station was not nuked, but something was, and the syndicates returned to their base
else
return NUKE_RESULT_WRONG_STATION
// No nuke went off, the station rescued the disk
else if(disk_rescued)
// No nuke went off, the shuttle left, and the team is dead
if(shuttle_evacuated && team_is_dead)
return NUKE_RESULT_CREW_WIN_SYNDIES_DEAD
// No nuke went off, but the nuke ops survived
else
return NUKE_RESULT_CREW_WIN
// No nuke went off, but the disk was left behind
else
return //Undefined result
// No nuke went off, the disk was left, but all the ops are dead
if(team_is_dead)
return NUKE_RESULT_DISK_LOST
// No nuke went off, the disk was left, there are living ops, but the shuttle left successfully
else if(shuttle_evacuated)
return NUKE_RESULT_DISK_STOLEN
CRASH("[type] - got an undefined / unexpected result.")
/datum/team/nuclear/roundend_report()
var/list/parts = list()
@@ -401,10 +422,10 @@
parts += "<span class='greentext big'>Syndicate Major Victory!</span>"
parts += "<B>[syndicate_name] operatives have destroyed [station_name()]!</B>"
if(NUKE_RESULT_NOSURVIVORS)
parts += "<span class='neutraltext big'>Total Annihilation</span>"
parts += "<span class='neutraltext big'>Total Annihilation!</span>"
parts += "<B>[syndicate_name] operatives destroyed [station_name()] but did not leave the area in time and got caught in the explosion.</B> Next time, don't lose the disk!"
if(NUKE_RESULT_WRONG_STATION)
parts += "<span class='redtext big'>Crew Minor Victory</span>"
parts += "<span class='redtext big'>Crew Minor Victory!</span>"
parts += "<B>[syndicate_name] operatives secured the authentication disk but blew up something that wasn't [station_name()].</B> Next time, don't do that!"
if(NUKE_RESULT_WRONG_STATION_DEAD)
parts += "<span class='redtext big'>[syndicate_name] operatives have earned Darwin Award!</span>"
@@ -413,7 +434,7 @@
parts += "<span class='redtext big'>Crew Major Victory!</span>"
parts += "<B>The Research Staff has saved the disk and killed the [syndicate_name] Operatives</B>"
if(NUKE_RESULT_CREW_WIN)
parts += "<span class='redtext big'>Crew Major Victory</span>"
parts += "<span class='redtext big'>Crew Major Victory!</span>"
parts += "<B>The Research Staff has saved the disk and stopped the [syndicate_name] Operatives!</B>"
if(NUKE_RESULT_DISK_LOST)
parts += "<span class='neutraltext big'>Neutral Victory!</span>"
@@ -438,7 +459,7 @@
text += printplayerlist(members)
text += "<br>"
text += "(Syndicates used [TC_uses] TC) [purchases]"
if(TC_uses == 0 && GLOB.station_was_nuked && !operatives_dead())
if(TC_uses == 0 && GLOB.station_was_nuked && !are_all_operatives_dead())
text += "<BIG>[icon2html('icons/ui_icons/antags/badass.dmi', world, "badass")]</BIG>"
parts += text
@@ -475,7 +496,8 @@
return common_part + disk_report + challenge_report
/// Returns whether or not syndicate operatives escaped.
/proc/syndies_escaped()
var/obj/docking_port/mobile/S = SSshuttle.getShuttle("syndicate")
var/obj/docking_port/stationary/transit/T = locate() in S.loc
return S && (is_centcom_level(S.z) || T)
/proc/is_infiltrator_docked_at_centcom()
var/obj/docking_port/mobile/infiltrator/infiltrator_port = SSshuttle.getShuttle("syndicate")
var/obj/docking_port/stationary/transit/infiltrator_dock = locate() in infiltrator_port.loc
return infiltrator_port && (is_centcom_level(infiltrator_port.z) || infiltrator_dock)
@@ -358,25 +358,25 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
var/sec_left = seconds_remaining()
if(!sec_left)
timing = FALSE
detonate()
sound_to_playing_players('sound/machines/alarm.ogg')
addtimer(CALLBACK(GLOBAL_PROC, /proc/play_cinematic, /datum/cinematic/malf, world, CALLBACK(src, .proc/trigger_doomsday)), 10 SECONDS)
else if(world.time >= next_announce)
minor_announce("[sec_left] SECONDS UNTIL DOOMSDAY DEVICE ACTIVATION!", "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4", TRUE)
next_announce += DOOMSDAY_ANNOUNCE_INTERVAL
/obj/machinery/doomsday_device/proc/detonate()
sound_to_playing_players('sound/machines/alarm.ogg')
sleep(100)
for(var/i in GLOB.mob_living_list)
var/mob/living/L = i
var/turf/T = get_turf(L)
if(!T || !is_station_level(T.z))
continue
if(issilicon(L))
continue
to_chat(L, span_userdanger("The blast wave from [src] tears you atom from atom!"))
L.dust()
to_chat(world, "<B>The AI cleansed the station of life with the doomsday device!</B>")
SSticker.force_ending = 1
/obj/machinery/doomsday_device/proc/trigger_doomsday()
callback_on_everyone_on_z(SSmapping.levels_by_trait(ZTRAIT_STATION), CALLBACK(GLOBAL_PROC, /proc/bring_doomsday), src)
to_chat(world, span_bold("The AI cleansed the station of life with [src]!"))
SSticker.force_ending = TRUE
/proc/bring_doomsday(mob/living/victim, atom/source)
if(issilicon(victim))
return FALSE
to_chat(victim, span_userdanger("The blast wave from [source] tears you atom from atom!"))
victim.dust()
return TRUE
/// Hostile Station Lockdown: Locks, bolts, and electrifies every airlock on the station. After 90 seconds, the doors reset.
/datum/ai_module/destructive/lockdown
+1 -2
View File
@@ -7,5 +7,4 @@
/datum/round_event/wizard/fake_explosion/start()
sound_to_playing_players('sound/machines/alarm.ogg')
sleep(100)
Cinematic(CINEMATIC_NUKE_FAKE,world)
addtimer(CALLBACK(GLOBAL_PROC, /proc/play_cinematic, /datum/cinematic/nuke/fake, world), 10 SECONDS)
+1 -1
View File
@@ -428,7 +428,7 @@
if((GLOB.cult_narsie.souls == GLOB.cult_narsie.soul_goal) && (GLOB.cult_narsie.resolved == FALSE))
GLOB.cult_narsie.resolved = TRUE
sound_to_playing_players('sound/machines/alarm.ogg')
addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, 1), 120)
addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, CULT_VICTORY_MASS_CONVERSION), 120)
addtimer(CALLBACK(GLOBAL_PROC, .proc/ending_helper), 270)
if(client)
makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, src, cultoverride = TRUE)
+4
View File
@@ -43,6 +43,10 @@
ghostize() //False, since we're deleting it currently
if(mind?.current == src) //Let's just be safe yeah? This will occasionally be cleared, but not always. Can't do it with ghostize without changing behavior
mind.set_current(null)
if(mock_client)
mock_client.mob = null
return ..()
+16 -10
View File
@@ -218,7 +218,7 @@
if(QDELETED(GLOB.cult_narsie)) // uno
priority_announce("Status report? We detected an anomaly, but it disappeared almost immediately.","Central Command Higher Dimensional Affairs", 'sound/misc/notice1.ogg')
GLOB.cult_narsie = null
addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, 2), 2 SECONDS)
addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, CULT_FAILURE_NARSIE_KILLED), 2 SECONDS)
return
priority_announce("An acausal dimensional event has been detected in your sector. Event has been flagged EXTINCTION-CLASS. Directing all available assets toward simulating solutions. SOLUTION ETA: 60 SECONDS.","Central Command Higher Dimensional Affairs", 'sound/misc/airraid.ogg')
addtimer(CALLBACK(GLOBAL_PROC, .proc/narsie_end_second_check), 50 SECONDS)
@@ -228,7 +228,7 @@
if(QDELETED(GLOB.cult_narsie)) // dos
priority_announce("Simulations aborted, sensors report that the acasual event is normalizing. Good work, crew.","Central Command Higher Dimensional Affairs", 'sound/misc/notice1.ogg')
GLOB.cult_narsie = null
addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, 2), 2 SECONDS)
addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, CULT_FAILURE_NARSIE_KILLED), 2 SECONDS)
return
priority_announce("Simulations on acausal dimensional event complete. Deploying solution package now. Deployment ETA: ONE MINUTE. ","Central Command Higher Dimensional Affairs")
addtimer(CALLBACK(GLOBAL_PROC, .proc/narsie_start_destroy_station), 5 SECONDS)
@@ -257,7 +257,7 @@
/proc/narsie_last_second_win()
set_security_level("red")
SSshuttle.lockdown = FALSE
INVOKE_ASYNC(GLOBAL_PROC, .proc/cult_ending_helper, 2)
INVOKE_ASYNC(GLOBAL_PROC, .proc/cult_ending_helper, CULT_FAILURE_NARSIE_KILLED)
///Helper to set the round to end asap. Current usage Cult round end code
/proc/ending_helper()
@@ -267,13 +267,19 @@
* Selects cinematic to play as part of the cult end depending on the outcome then ends the round afterward
* called either when narsie eats everyone, or when [/proc/begin_the_end()] reaches it's conclusion
*/
/proc/cult_ending_helper(ending_type = 0)
if(ending_type == 2) //narsie fukkin died
Cinematic(CINEMATIC_CULT_FAIL,world,CALLBACK(GLOBAL_PROC,/proc/ending_helper))
else if(ending_type) //no explosion
Cinematic(CINEMATIC_CULT,world,CALLBACK(GLOBAL_PROC,/proc/ending_helper))
else // explosion
Cinematic(CINEMATIC_CULT_NUKE,world,CALLBACK(GLOBAL_PROC,/proc/ending_helper))
/proc/cult_ending_helper(ending_type = CULT_VICTORY_NUKE)
switch(ending_type)
// Narsie was killed
if(CULT_FAILURE_NARSIE_KILLED)
play_cinematic(/datum/cinematic/cult_fail, world, CALLBACK(GLOBAL_PROC, /proc/ending_helper))
// The cult "converted" (harvested) most of the station
if(CULT_VICTORY_MASS_CONVERSION)
play_cinematic(/datum/cinematic/cult_arm, world, CALLBACK(GLOBAL_PROC, /proc/ending_helper))
// The cult won, but centcom deployed a nuke. Default
if(CULT_VICTORY_NUKE)
play_cinematic(/datum/cinematic/nuke/cult, world, CALLBACK(GLOBAL_PROC, /proc/ending_helper))
#undef NARSIE_CHANCE_TO_PICK_NEW_TARGET
#undef NARSIE_CONSUME_RANGE
+1
View File
@@ -111,6 +111,7 @@
#include "modsuit.dm"
#include "modular_map_loader.dm"
#include "ntnetwork_tests.dm"
#include "nuke_cinematic.dm"
#include "objectives.dm"
#include "outfit_sanity.dm"
#include "paintings.dm"
+68
View File
@@ -0,0 +1,68 @@
// Some defines for tracking if the correct cinematic / animation is playing.
#define PLAYING_CORRECT_ANIMATION 2
#define PLAYING_INCORRECT_NUKE_ANIMATION 1
#define NOT_PLAYING_ANIMATION 0
/**
* Unit tests that a nuke going off plays a cinematic,
* and that it actually kills people.
*/
/datum/unit_test/nuke_cinematic
/// Used to track via signal if the correct cinematic / animation is playing.
var/cinematic_playing = NOT_PLAYING_ANIMATION
/// Tracks what typepath of cinematic is being played.
var/cinematic_playing_type
/datum/unit_test/nuke_cinematic/Run()
var/obj/machinery/nuclearbomb/syndicate/nuke = allocate(/obj/machinery/nuclearbomb/syndicate)
var/mob/living/carbon/human/nuked = allocate(/mob/living/carbon/human)
var/datum/client_interface/mock_client = new()
nuked.mock_client = mock_client
mock_client.mob = nuked
var/obj/effect/landmark/observer_start/observer_point = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list
TEST_ASSERT_NOTNULL(observer_point, "Nuke cinematic test couldn't find observer spawn to place the nuke.")
var/turf/turf_on_station = get_turf(observer_point)
TEST_ASSERT(is_station_level(turf_on_station.z), "Nuke cinematic test didn't get a turf which was located on the station.")
nuke.forceMove(turf_on_station)
nuked.forceMove(turf_on_station)
// Pause the check so we don't, y'know, end the round
SSticker.roundend_check_paused = TRUE
RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, .proc/check_cinematic)
// actually_explode calls really_actually_explode which sleeps, so this will take a moment.
var/nuke_result = nuke.actually_explode()
TEST_ASSERT_EQUAL(nuke_result, DETONATION_HIT_STATION, "A nuke went off on station, but didn't return DETONATION_HIT_STATION (4). (Got: [nuke_result])")
TEST_ASSERT(GLOB.station_was_nuked, "A nuke went off on station, but didn't set station_was_nuked.")
// Reset the nuke var back so we don't end the round
GLOB.station_was_nuked = FALSE
SSticker.roundend_check_paused = FALSE
switch(cinematic_playing)
if(NOT_PLAYING_ANIMATION)
TEST_FAIL("No nuke cinematic was played when a nuke was detonated.")
if(PLAYING_INCORRECT_NUKE_ANIMATION)
TEST_FAIL("An incorrect cinematic was played on nuke detonation. (Expected: /datum/cinematic/nuke/self_destruct, Got: [cinematic_playing_type])")
TEST_ASSERT(QDELETED(nuked), "The nuke victim next to the nuke wasn't gibbed by the nuke.")
TEST_ASSERT(QDELETED(nuke), "The nuke itself was not deleted after successfully exploding.")
mock_client.mob = null
/// Used to track whenever a cinematic starts playing, so we can check if it's the right one.
/datum/unit_test/nuke_cinematic/proc/check_cinematic(datum/source, datum/cinematic/playing)
SIGNAL_HANDLER
cinematic_playing_type = playing.type
if(istype(playing, /datum/cinematic/nuke/self_destruct))
cinematic_playing = PLAYING_CORRECT_ANIMATION
else if(istype(playing, /datum/cinematic/nuke))
cinematic_playing = PLAYING_INCORRECT_NUKE_ANIMATION
#undef PLAYING_CORRECT_ANIMATION
#undef PLAYING_INCORRECT_NUKE_ANIMATION
#undef NOT_PLAYING_ANIMATION
+10 -3
View File
@@ -49,7 +49,6 @@
#include "code\__DEFINES\cargo.dm"
#include "code\__DEFINES\chat.dm"
#include "code\__DEFINES\chat_filter.dm"
#include "code\__DEFINES\cinematics.dm"
#include "code\__DEFINES\cleaning.dm"
#include "code\__DEFINES\clothing.dm"
#include "code\__DEFINES\colors.dm"
@@ -122,6 +121,7 @@
#include "code\__DEFINES\movespeed_modification.dm"
#include "code\__DEFINES\networks.dm"
#include "code\__DEFINES\nitrile.dm"
#include "code\__DEFINES\nuclear_bomb.dm"
#include "code\__DEFINES\obj_flags.dm"
#include "code\__DEFINES\paintings.dm"
#include "code\__DEFINES\paper.dm"
@@ -631,7 +631,6 @@
#include "code\datums\browser.dm"
#include "code\datums\callback.dm"
#include "code\datums\chatmessage.dm"
#include "code\datums\cinematic.dm"
#include "code\datums\dash_weapon.dm"
#include "code\datums\datacore.dm"
#include "code\datums\datum.dm"
@@ -758,6 +757,10 @@
#include "code\datums\brain_damage\special.dm"
#include "code\datums\brain_damage\split_personality.dm"
#include "code\datums\changelog\changelog.dm"
#include "code\datums\cinematics\_cinematic.dm"
#include "code\datums\cinematics\malf_doomsday.dm"
#include "code\datums\cinematics\narsie_summon.dm"
#include "code\datums\cinematics\nuke_cinematics.dm"
#include "code\datums\components\_component.dm"
#include "code\datums\components\acid.dm"
#include "code\datums\components\admin_popup.dm"
@@ -2267,9 +2270,13 @@
#include "code\modules\antagonists\nukeop\nukeop.dm"
#include "code\modules\antagonists\nukeop\outfits.dm"
#include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclear_authentication_disk.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclear_challenge.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclearbomb.dm"
#include "code\modules\antagonists\nukeop\equipment\pinpointer.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\_nuclear_bomb.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\beer_nuke.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\self_destruct.dm"
#include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\syndicate_nuke.dm"
#include "code\modules\antagonists\pirate\pirate.dm"
#include "code\modules\antagonists\revenant\revenant.dm"
#include "code\modules\antagonists\revenant\revenant_abilities.dm"