diff --git a/code/modules/admin/game_master/game_master.dm b/code/modules/admin/game_master/game_master.dm index 0ec9ee7e3e..3e84006c87 100644 --- a/code/modules/admin/game_master/game_master.dm +++ b/code/modules/admin/game_master/game_master.dm @@ -78,6 +78,15 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100) /// The amount of xenos to spawn in the spawn section var/xeno_spawn_count = DEFAULT_XENO_AMOUNT_TO_SPAWN + /// The number of seconds between the placement of a xeno spawner and the moment of their actual spawning. + var/xeno_spawn_delay = 0 + + /// Whether new spawners will loop their spawns. + var/xeno_spawn_looping = FALSE + + /// Whether newly placed spawners (including instant ones) will fail if placed near living, connected humans. + var/xeno_spawn_fail_human + /// If the spawned xeno is an AI in the spawn section var/spawn_ai = TRUE @@ -147,6 +156,25 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100) data["spawn_ai"] = spawn_ai data["spawn_click_intercept"] = spawn_click_intercept data["xeno_spawn_count"] = xeno_spawn_count + data["xeno_spawn_delay"] = xeno_spawn_delay + data["xeno_spawn_looping"] = xeno_spawn_looping + data["xeno_spawn_fail_human"] = xeno_spawn_fail_human + + if(length(GLOB.gm_xeno_spawners)) + data["xeno_spawners"] = list() + for(var/datum/game_master_xeno_spawner/spawner as anything in GLOB.gm_xeno_spawners) + // list addition, gotta double wrap! + data["xeno_spawners"] += list(list( + "ref" = REF(spawner), + "spawn_type" = initial(spawner.spawn_type.name), // name's a little prettier + "spawn_count" = spawner.spawn_count, + "spawn_delay" = spawner.spawn_delay / (1 SECONDS), + "looping" = spawner.looping, + "fail_human" = spawner.fail_if_human_near, + "fail_range" = spawner.fail_range // they can't modify it, but nice to tell them about + )) + else + data["xeno_spawners"] = "" // Behavior stuff data["selected_behavior"] = selected_behavior @@ -186,31 +214,47 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100) return xeno_spawn_count = clamp(new_number, 1, 10) - return + return TRUE + + if("set_xeno_spawn_delay") + var/new_delay = text2num(params["value"]) + if(isnull(new_delay)) // obviously !new_delay won't work, because 0 is valid + return + // gotta set some kind of maximum + xeno_spawn_delay = round(clamp(new_delay, 0, 1 HOURS)) + return TRUE if("set_selected_xeno") selected_xeno = params["new_xeno"] xeno_spawn_count = DEFAULT_XENO_AMOUNT_TO_SPAWN - return + return TRUE if("set_selected_hive") selected_hive = params["new_hive"] xeno_spawn_count = DEFAULT_XENO_AMOUNT_TO_SPAWN - return + return TRUE if("xeno_spawn_ai_toggle") spawn_ai = !spawn_ai - return + return TRUE + + if("xeno_spawn_looping_toggle") + xeno_spawn_looping = !xeno_spawn_looping + return TRUE + + if("xeno_spawn_fail_human_toggle") + xeno_spawn_fail_human = !xeno_spawn_fail_human + return TRUE if("toggle_click_spawn") if(spawn_click_intercept) reset_click_overrides() - return + return TRUE reset_click_overrides() spawn_click_intercept = TRUE current_click_intercept_action = SPAWN_CLICK_INTERCEPT_ACTION - return + return TRUE if("delete_all_xenos") if(tgui_alert(ui.user, "Do you want to delete all xenos?", "Confirmation", list("Yes", "No")) != "Yes") @@ -273,6 +317,30 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100) jumping_client.jump_to_turf(objective_turf) return TRUE + if("jump_to_spawner") + if(!params["spawner_ref"]) + return TRUE + + var/datum/game_master_xeno_spawner/spawner = locate(params["spawner_ref"]) + if(!spawner) + return TRUE + var/turf/spawner_turf = spawner.spawn_loc + + var/client/jumping_client = ui.user.client + jumping_client.jump_to_turf(spawner_turf) + return TRUE + + if("delete_spawner") + if(!params["spawner_ref"]) + return TRUE + + var/datum/game_master_xeno_spawner/spawner = locate(params["spawner_ref"]) + if(!spawner) + return TRUE + + qdel(spawner) + return TRUE + if("remove_objective") if(!params["val"]) return @@ -348,8 +416,16 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100) var/turf/spawn_turf = get_turf(object) - for(var/i = 1 to xeno_spawn_count) - new spawning_xeno_type(spawn_turf, null, selected_hive, !spawn_ai) + new /datum/game_master_xeno_spawner( + spawn_turf, + spawning_xeno_type, + xeno_spawn_count, + (xeno_spawn_delay SECONDS), + xeno_spawn_looping, + selected_hive, + spawn_ai, + xeno_spawn_fail_human + ) return TRUE diff --git a/code/modules/admin/game_master/game_master_xeno_spawner.dm b/code/modules/admin/game_master/game_master_xeno_spawner.dm new file mode 100644 index 0000000000..6a0dc534f5 --- /dev/null +++ b/code/modules/admin/game_master/game_master_xeno_spawner.dm @@ -0,0 +1,103 @@ +GLOBAL_LIST_EMPTY(gm_xeno_spawners) + +/datum/game_master_xeno_spawner + /// The actual image holder that is added to game masters' clients + var/image/gm_image + + /// The location the xenomorphs will spawn at. + var/turf/spawn_loc + + /// The type of xeno to be spawned. + var/mob/living/carbon/xenomorph/spawn_type + + /// The number of xenos to be spawned. + var/spawn_count + + /// The delay until xenos are spawned (may be 0, in which case xenos spawn instantaneously). + var/spawn_delay + + /// If TRUE, the spawner will not delete itself after firing. + var/looping + + /// The hive the spawned xenos will belong to. + var/spawn_hive + + /// Whether or not to spawn xenos with AI: if TRUE, the spawned xenos will have AI. + var/spawn_ai + + /// If TRUE, the spawn will fail if any humans are within "fail_range" tiles. + /// This will cause the spawner to either delete itself or reset its spawn timer, depending on whether it loops. + var/fail_if_human_near + + var/fail_range = 9 + +/datum/game_master_xeno_spawner/New(turf/s_loc, mob/living/carbon/xenomorph/s_type, s_count, s_delay, loop, s_hive, s_ai, fail_human) + GLOB.gm_xeno_spawners += src + + spawn_loc = s_loc + spawn_type = s_type + spawn_count = s_count + spawn_delay = s_delay + looping = loop + spawn_hive = s_hive + spawn_ai = s_ai + fail_if_human_near = fail_human + + // we can return early if we aren't waiting for anything + if(spawn_delay <= 0) + spawn_xenos() + return + + addtimer(CALLBACK(src, PROC_REF(spawn_xenos)), spawn_delay) + + // get the icon state for the xeno type we're trying to spawn + var/derived_icon_state = initial(spawn_type.mutation_type) + " " + initial(spawn_type.icon_state) + gm_image = new( + initial(spawn_type.icon), + spawn_loc, + derived_icon_state, + layer = ABOVE_FLY_LAYER, + ) + // centers the xeno + gm_image.pixel_x = -(initial(spawn_type.icon_size) - 32)/2 + // slightly more visible than observers + gm_image.alpha = 150 + // color matrix. this makes them look sorta overexposed. janky, but it's more important that it looks distinguishable than good + gm_image.color = list( + 1.5, 0, 0, + 0, 1.5, 0, + 0, 0, 1.5, + 0.25, 0.25, 0.25 + ) + + for(var/client/game_master in GLOB.game_masters) + game_master.images |= gm_image + +/datum/game_master_xeno_spawner/Destroy(force, silent, ...) + . = ..() + + GLOB.gm_xeno_spawners -= src + + // cleans up the image + if(gm_image) + for(var/client/game_master in GLOB.game_masters) + game_master.images -= gm_image + QDEL_NULL(gm_image) + +/datum/game_master_xeno_spawner/proc/spawn_xenos() + var/failure = FALSE + + if(fail_if_human_near) + for(var/mob/living/carbon/human as anything in GLOB.alive_human_list) + if(human.client && human.z == spawn_loc.z && (get_dist(human, spawn_loc) <= fail_range)) + failure = TRUE + break + + if(!failure) + for(var/i = 1 to spawn_count) + new spawn_type(spawn_loc, null, spawn_hive, !spawn_ai) + + if(spawn_delay && looping) + addtimer(CALLBACK(src, PROC_REF(spawn_xenos)), spawn_delay) + else + qdel(src) diff --git a/colonialmarines.dme b/colonialmarines.dme index f3f5d9a012..66df19afdf 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1385,6 +1385,7 @@ #include "code\modules\admin\ToRban.dm" #include "code\modules\admin\game_master\game_master.dm" #include "code\modules\admin\game_master\game_master_submenu.dm" +#include "code\modules\admin\game_master\game_master_xeno_spawner.dm" #include "code\modules\admin\game_master\extra_buttons\rappel_menu.dm" #include "code\modules\admin\game_master\extra_buttons\rename_platoon.dm" #include "code\modules\admin\game_master\extra_buttons\toggle_join_xeno.dm" diff --git a/tgui/packages/tgui/interfaces/GameMaster.js b/tgui/packages/tgui/interfaces/GameMaster.js index 32949fea54..022ce83cdd 100644 --- a/tgui/packages/tgui/interfaces/GameMaster.js +++ b/tgui/packages/tgui/interfaces/GameMaster.js @@ -6,7 +6,7 @@ export const GameMaster = (props, context) => { const { data, act } = useBackend(context); return ( - + @@ -63,6 +63,41 @@ export const GameMasterSpawningPanel = (props, context) => { + + + Delay (s) + + { + act('set_xeno_spawn_delay', { value }); + }} + /> + + + { + act('xeno_spawn_looping_toggle'); + }} + /> + + + { + act('xeno_spawn_fail_human_toggle'); + }} + /> + + + @@ -105,6 +140,57 @@ export const GameMasterSpawningPanel = (props, context) => { + {data.xeno_spawners && ( + + + + {data.xeno_spawners.map((spawner) => { + if (spawner) { + let spawner_ref = spawner.ref; + return ( + + + + Caste: {spawner.spawn_type} + Count: {spawner.spawn_count} + Delay: {spawner.spawn_delay}s + + Looping: {spawner.looping ? 'True' : 'False'} + + + Fail if human within {spawner.fail_range}:{' '} + {spawner.fail_human ? 'True' : 'False'} + + + + +