diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 3b63dd4a5ca..6ed9272a9ff 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -10,6 +10,9 @@
#define NUKE_RESULT_HIJACK_DISK 9
#define NUKE_RESULT_HIJACK_NO_DISK 10
+/// Min players requireed for nukes to declare war
+#define CHALLENGE_MIN_PLAYERS 50
+
//fugitive end results
#define FUGITIVE_RESULT_BADASS_HUNTER 0
#define FUGITIVE_RESULT_POSTMORTEM_HUNTER 1
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 1b601bb0cc9..b52405e204a 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1766,6 +1766,10 @@
var/obj/item/nuclear_challenge/button = locate(href_list["force_war"])
button.force_war()
+ else if(href_list["give_reinforcement"])
+ var/datum/team/nuclear/nuketeam = locate(href_list["give_reinforcement"]) in GLOB.antagonist_teams
+ nuketeam.admin_spawn_reinforcement(usr)
+
else if (href_list["interview"])
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
index eeb4d255bf6..f3d2bde6fb7 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
@@ -1,6 +1,5 @@
#define CHALLENGE_TELECRYSTALS 280
#define CHALLENGE_TIME_LIMIT (5 MINUTES)
-#define CHALLENGE_MIN_PLAYERS 50
#define CHALLENGE_SHUTTLE_DELAY (25 MINUTES) // 25 minutes, so the ops have at least 5 minutes before the shuttle is callable.
GLOBAL_LIST_EMPTY(jam_on_wardec)
@@ -54,7 +53,7 @@ GLOBAL_LIST_EMPTY(jam_on_wardec)
///Admin only proc to bypass checks and force a war declaration. Button on antag panel.
/obj/item/nuclear_challenge/proc/force_war()
- var/are_you_sure = tgui_alert(usr, "Are you sure you wish to force a war declaration?", "Declare war?", list("Yes", "No"))
+ var/are_you_sure = tgui_alert(usr, "Are you sure you wish to force a war declaration?[GLOB.player_list.len < CHALLENGE_MIN_PLAYERS ? " Note, the player count is under the required limit." : ""]", "Declare war?", list("Yes", "No"))
if(are_you_sure != "Yes")
return
@@ -67,9 +66,14 @@ GLOBAL_LIST_EMPTY(jam_on_wardec)
war_declaration = tgui_input_text(usr, "Insert your custom declaration", "Declaration", multiline = TRUE, encode = FALSE)
if(!war_declaration)
- to_chat(usr, span_warning("Invalid war declaration."))
+ tgui_alert(usr, "Invalid war declaration.", "Poor Choice of Words")
return
+ for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards)
+ if(board.challenge)
+ tgui_alert(usr, "War has already been declared!", "War Was Declared")
+ return
+
war_was_declared(memo = war_declaration)
/obj/item/nuclear_challenge/proc/war_was_declared(mob/living/user, memo)
@@ -148,6 +152,9 @@ GLOBAL_LIST_EMPTY(jam_on_wardec)
if(board.moved)
to_chat(user, span_boldwarning("The shuttle has already been moved! You have forfeit the right to declare war."))
return FALSE
+ if(board.challenge)
+ to_chat(user, span_boldwarning("War has already been declared!"))
+ return FALSE
return TRUE
/obj/item/nuclear_challenge/clownops
@@ -189,5 +196,4 @@ GLOBAL_LIST_EMPTY(jam_on_wardec)
#undef CHALLENGE_TELECRYSTALS
#undef CHALLENGE_TIME_LIMIT
-#undef CHALLENGE_MIN_PLAYERS
#undef CHALLENGE_SHUTTLE_DELAY
diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm
index c9ad46b1415..7690ab19efd 100644
--- a/code/modules/antagonists/nukeop/nukeop.dm
+++ b/code/modules/antagonists/nukeop/nukeop.dm
@@ -521,12 +521,116 @@
disk_loc = disk_loc.loc
disk_report += "in [disk_loc.loc] at ([disk_loc.x], [disk_loc.y], [disk_loc.z])
FLW | "
disk_report += ""
- var/common_part = ..()
- var/challenge_report
- var/obj/item/nuclear_challenge/war_button = war_button_ref?.resolve()
- if(war_button)
- challenge_report += "War not declared. \[Force war\]"
- return common_part + disk_report + challenge_report
+
+ var/post_report
+
+ var/war_declared = FALSE
+ for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards)
+ if(board.challenge)
+ war_declared = TRUE
+
+ var/force_war_button = ""
+
+ if(war_declared)
+ post_report += "War declared."
+ force_war_button = "\[Force war\]"
+ else
+ post_report += "War not declared."
+ var/obj/item/nuclear_challenge/war_button = war_button_ref?.resolve()
+ if(war_button)
+ force_war_button = "\[Force war\]"
+ else
+ force_war_button = "\[Cannot declare war, challenge button missing!\]"
+
+ post_report += "\n[force_war_button]"
+ post_report += "\n\[Send Reinforcement\]"
+
+ var/final_report = ..()
+ final_report += disk_report
+ final_report += post_report
+ return final_report
+
+#define SPAWN_AT_BASE "Nuke base"
+#define SPAWN_AT_INFILTRATOR "Infiltrator"
+
+/datum/team/nuclear/proc/admin_spawn_reinforcement(mob/admin)
+ if(!check_rights_for(admin.client, R_ADMIN))
+ return
+
+ var/infil_or_nukebase = tgui_alert(
+ admin,
+ "Spawn them at the nuke base, or in the Infiltrator?",
+ "Where to reinforce?",
+ list(SPAWN_AT_BASE, SPAWN_AT_INFILTRATOR, "Cancel"),
+ )
+
+ if(!infil_or_nukebase || infil_or_nukebase == "Cancel")
+ return
+
+ var/tc_to_spawn = tgui_input_number(admin, "How much TC to spawn with?", "TC", 0, 100)
+
+ var/list/nuke_candidates = poll_ghost_candidates(
+ "Do you want to play as an emergency syndicate reinforcement?",
+ ROLE_OPERATIVE,
+ ROLE_OPERATIVE,
+ 30 SECONDS,
+ POLL_IGNORE_SYNDICATE,
+ )
+
+ nuke_candidates -= admin // may be easy to fat-finger say yes. so just don't
+
+ if(!length(nuke_candidates))
+ tgui_alert(admin, "No candidates found.", "Recruitment Shortage", list("OK"))
+ return
+
+
+ var/turf/spawn_loc
+ if(infil_or_nukebase == SPAWN_AT_INFILTRATOR)
+ var/area/spawn_in
+ // Prioritize EVA then hallway, if neither can be found default to the first area we can find
+ for(var/area_type in list(/area/shuttle/syndicate/eva, /area/shuttle/syndicate/hallway, /area/shuttle/syndicate))
+ spawn_in = locate(area_type) in GLOB.areas // I'd love to use areas_by_type but the Infiltrator is a unique area
+ if(spawn_in)
+ break
+
+ var/list/turf/options = list()
+ for(var/turf/open/open_turf in spawn_in?.get_contained_turfs())
+ if(open_turf.is_blocked_turf())
+ continue
+ options += open_turf
+
+ if(length(options))
+ spawn_loc = pick(options)
+ else
+ infil_or_nukebase = SPAWN_AT_BASE
+
+ if(infil_or_nukebase == SPAWN_AT_BASE)
+ spawn_loc = pick(GLOB.nukeop_start)
+
+ var/mob/dead/observer/picked = pick(nuke_candidates)
+ var/mob/living/carbon/human/nukie = new(spawn_loc)
+ picked.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE)
+ nukie.key = picked.key
+
+ var/datum/antagonist/nukeop/antag_datum = new()
+ antag_datum.send_to_spawnpoint = FALSE
+ antag_datum.nukeop_outfit = /datum/outfit/syndicate/reinforcement
+
+ nukie.mind.add_antag_datum(antag_datum, src)
+
+ var/datum/component/uplink/uplink = nukie.mind.find_syndicate_uplink()
+ uplink?.set_telecrystals(tc_to_spawn)
+
+ // add some pizzazz
+ do_sparks(4, FALSE, spawn_loc)
+ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(spawn_loc)
+ playsound(spawn_loc, SFX_SPARKS, 50, TRUE)
+ playsound(spawn_loc, 'sound/effects/phasein.ogg', 50, TRUE)
+
+ tgui_alert(admin, "Reinforcement spawned at [infil_or_nukebase] with [tc_to_spawn].", "Reinforcements have arrived", list("God speed"))
+
+#undef SPAWN_AT_BASE
+#undef SPAWN_AT_INFILTRATOR
/// Returns whether or not syndicate operatives escaped.
/proc/is_infiltrator_docked_at_syndiebase()