diff --git a/code/__HELPERS/events.dm b/code/__HELPERS/events.dm new file mode 100644 index 00000000000..114f67c87f3 --- /dev/null +++ b/code/__HELPERS/events.dm @@ -0,0 +1,52 @@ +#define UNLIT_AREA_BRIGHTNESS 0.2 + +/** + * Finds us a generic maintenance spawn location. + * + * Goes through the list of the generic mainteance landmark locations, checking for atmos safety if required, and returns + * a valid turf. Returns MAP_ERROR if no valid locations are present. + * Returns nothing and alerts admins if no valid points are found. Keep this in mind + * when using this helper. + */ + +/proc/find_maintenance_spawn(atmos_sensitive = FALSE, require_darkness = FALSE) + var/list/possible_spawns = list() + for(var/spawn_location in GLOB.generic_maintenance_landmarks) + var/turf/spawn_turf = get_turf(spawn_location) + + if(atmos_sensitive && !is_safe_turf(spawn_turf)) + continue + + if(require_darkness && spawn_turf.get_lumcount() > UNLIT_AREA_BRIGHTNESS) + continue + + possible_spawns += spawn_turf + + if(!length(possible_spawns)) + message_admins("No valid generic_maintenance_landmark landmarks found, aborting...") + return null + + return pick(possible_spawns) + +/** + * Finds us a generic spawn location in space. + * + * Goes through the list of the space carp spawn locations, picks from the list, and + * returns that turf. Returns MAP_ERROR if no landmarks are found. + */ + +/proc/find_space_spawn() + var/list/possible_spawns = list() + for(var/obj/effect/landmark/carpspawn/spawn_location in GLOB.landmarks_list) + if(!isturf(spawn_location.loc)) + stack_trace("Carp spawn found not on a turf: [spawn_location.type] on [isnull(spawn_location.loc) ? "null" : spawn_location.loc.type]") + continue + possible_spawns += get_turf(spawn_location) + + if(!length(possible_spawns)) + message_admins("No valid carpspawn landmarks found, aborting...") + return null + + return pick(possible_spawns) + +#undef UNLIT_AREA_BRIGHTNESS diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index d40168ef5ff..b212a687491 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -525,14 +525,10 @@ var/list/spawn_locs = list() /datum/dynamic_ruleset/midround/from_ghosts/nightmare/acceptable(population=0, threat=0) - for(var/X in GLOB.generic_maintenance_landmarks) - var/turf/T = X - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - spawn_locs += T - if(!spawn_locs.len) + var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) //Checks if there's a single safe, dark tile on station. + if(!spawn_loc) return FALSE - . = ..() + return ..() /datum/dynamic_ruleset/midround/from_ghosts/nightmare/generate_ruleset_body(mob/applicant) var/datum/mind/player_mind = new /datum/mind(applicant.key) @@ -863,11 +859,8 @@ var/list/possible_spawns = list() ///places the antag can spawn /datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/execute() - for(var/turf/warp_point in GLOB.generic_maintenance_landmarks) - if(istype(warp_point.loc, /area/station/maintenance) && is_safe_turf(warp_point)) - possible_spawns += warp_point + possible_spawns += find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) if(!possible_spawns.len) - message_admins("No valid spawn locations found for Paradox Clone event, aborting...") return MAP_ERROR return ..() diff --git a/code/modules/events/ghost_role/_ghost_role.dm b/code/modules/events/ghost_role/_ghost_role.dm index a2af2c55c6b..90b6b36dca3 100644 --- a/code/modules/events/ghost_role/_ghost_role.dm +++ b/code/modules/events/ghost_role/_ghost_role.dm @@ -2,19 +2,31 @@ /datum/round_event/ghost_role - // We expect 0 or more /clients (or things with .key) in this list - var/list/priority_candidates = list() - var/minimum_required = 1 - var/role_name = "debug rat with cancer" // Q U A L I T Y M E M E S - var/list/spawned_mobs = list() - var/status - var/cached_announcement_chance fakeable = FALSE + /// Members of this list will be placed at the front of the candicacy list, in front of the (shuffled) normal candidates. + var/list/priority_candidates = list() //expected to contain 0 or more /clients (or things with .key) + /// The minimum number of signups required for the event to continue past the polling period + var/minimum_required = 1 + /// The name of the role, to be displayed in logs/polls/etc. + var/role_name = "debug rat with cancer" // Q U A L I T Y M E M E S + /// A list of mobs generated by this event. + var/list/spawned_mobs = list() + /// Used to communicate the progress of the event firing, and whether or not the event was successfuly run. + var/status + /// A stored value of the event's announcement chance. Cached and not immediately used to prevent announcements for a failed event roll. + var/cached_announcement_chance /datum/round_event/ghost_role/start() try_spawning() -/datum/round_event/ghost_role/proc/try_spawning(sanity = 0, retry = 0) +/** + * Attempts to spawn the role, and cancels the event if it fails. + * + * Pauses the event right as it begins, and waits for setup/polling to end. + * If successful, continues running the rest of the event and notifies ghosts. + */ + +/datum/round_event/ghost_role/proc/try_spawning() // The event does not run until the spawning has been attempted // to prevent us from getting gc'd halfway through processing = FALSE @@ -24,15 +36,16 @@ cached_announcement_chance = announce_chance //only announce once we've finished the spawning loop. announce_chance = (status == SUCCESSFUL_SPAWN ? cached_announcement_chance : 0) if((status == WAITING_FOR_SOMETHING)) - if(retry >= MAX_SPAWN_ATTEMPT) + var/retry_count = 0 + if(retry_count >= MAX_SPAWN_ATTEMPT) message_admins("[role_name] event has exceeded maximum spawn attempts. Aborting and refunding.") if(control && control.occurrences > 0) //Don't refund if it hasn't control.occurrences-- return - var/waittime = 300 * (2**retry) + var/waittime = 300 * (2**retry_count) message_admins("The event will not spawn a [role_name] until certain \ conditions are met. Waiting [waittime/10]s and then retrying.") - addtimer(CALLBACK(src, PROC_REF(try_spawning), 0, ++retry), waittime) + addtimer(CALLBACK(src, PROC_REF(try_spawning), 0, ++retry_count), waittime) return if(!status) @@ -60,15 +73,29 @@ processing = TRUE +/** + * Performs the spawning of our role. Entirely specific to the event itself. + * + * Should return SUCCESSFUL_SPAWN if role was successfully spawned, + * return NOT_ENOUGH_PLAYERS if less than mimimum_required was found, + * and return MAP_ERROR if a spawn location could not be found. + */ + /datum/round_event/ghost_role/proc/spawn_role() - // Return true if role was successfully spawned, false if insufficent - // players could be found, and just runtime if anything else happens return FALSE +/** + * Gathers the candidates to select our ghost roles from. + * + * Returns a list of candidates in priority order, with candidates from + * `priority_candidates` first, and ghost roles randomly shuffled and + * appended after. + * + * jobban - The jobban flag to exclude players from the polling pool with. + * be_special - The "special role" flag for the ghost candidacy poll. + */ + /datum/round_event/ghost_role/proc/get_candidates(jobban, be_special) - // Returns a list of candidates in priority order, with candidates from - // `priority_candidates` first, and ghost roles randomly shuffled and - // appended after var/list/mob/dead/observer/regular_candidates // don't get their hopes up if(priority_candidates.len < minimum_required) diff --git a/code/modules/events/ghost_role/fugitive_event.dm b/code/modules/events/ghost_role/fugitive_event.dm index de7a5909e86..24aa4798c79 100644 --- a/code/modules/events/ghost_role/fugitive_event.dm +++ b/code/modules/events/ghost_role/fugitive_event.dm @@ -16,14 +16,9 @@ fakeable = FALSE /datum/round_event/ghost_role/fugitives/spawn_role() - var/list/possible_spawns = list()//Some xeno spawns are in some spots that will instantly kill the refugees, like atmos - for(var/turf/spawn_turf in GLOB.generic_maintenance_landmarks) - if(istype(get_area(spawn_turf), /area/station/maintenance) && is_safe_turf(spawn_turf)) - possible_spawns += spawn_turf - if(!possible_spawns.len) - message_admins("No valid spawn locations found, aborting...") + var/turf/landing_turf = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) + if(isnull(landing_turf)) return MAP_ERROR - var/turf/landing_turf = pick(possible_spawns) var/list/possible_backstories = list() var/list/candidates = get_candidates(ROLE_FUGITIVE, ROLE_FUGITIVE) diff --git a/code/modules/events/ghost_role/morph_event.dm b/code/modules/events/ghost_role/morph_event.dm index dbf476d24ac..29bfe202cc3 100644 --- a/code/modules/events/ghost_role/morph_event.dm +++ b/code/modules/events/ghost_role/morph_event.dm @@ -22,17 +22,11 @@ var/datum/mind/player_mind = new /datum/mind(selected.key) player_mind.active = TRUE - var/list/spawn_locs = list() - for(var/spawn_area in GLOB.generic_maintenance_landmarks) - var/turf/spawn_turf = get_turf(spawn_area) - if(is_safe_turf(spawn_turf)) - spawn_locs += spawn_turf - - if(!length(spawn_locs)) - message_admins("No valid spawn locations found, aborting...") + var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) + if(isnull(spawn_loc)) return MAP_ERROR - var/mob/living/simple_animal/hostile/morph/S = new /mob/living/simple_animal/hostile/morph(pick(spawn_locs)) + var/mob/living/simple_animal/hostile/morph/S = new /mob/living/simple_animal/hostile/morph(spawn_loc) player_mind.transfer_to(S) player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/morph)) player_mind.special_role = ROLE_MORPH diff --git a/code/modules/events/ghost_role/nightmare.dm b/code/modules/events/ghost_role/nightmare.dm index 7a5bb9f3fe9..57b942988cd 100644 --- a/code/modules/events/ghost_role/nightmare.dm +++ b/code/modules/events/ghost_role/nightmare.dm @@ -24,18 +24,11 @@ var/datum/mind/player_mind = new /datum/mind(selected.key) player_mind.active = TRUE - var/list/spawn_locs = list() - for(var/spawn_area in GLOB.generic_maintenance_landmarks) - var/turf/spawn_turf = get_turf(spawn_area) - var/light_amount = spawn_turf.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD && is_safe_turf(spawn_turf)) - spawn_locs += spawn_turf - - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") + var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) + if(isnull(spawn_loc)) return MAP_ERROR - var/mob/living/carbon/human/S = new ((pick(spawn_locs))) + var/mob/living/carbon/human/S = new (spawn_loc) player_mind.transfer_to(S) player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/nightmare)) player_mind.special_role = ROLE_NIGHTMARE diff --git a/code/modules/events/ghost_role/operative.dm b/code/modules/events/ghost_role/operative.dm index f6b9b24ab05..33cd9e059f0 100644 --- a/code/modules/events/ghost_role/operative.dm +++ b/code/modules/events/ghost_role/operative.dm @@ -18,13 +18,11 @@ var/mob/dead/selected = pick_n_take(candidates) - var/list/spawn_locs = list() - for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) - spawn_locs += L.loc - if(!spawn_locs.len) + var/spawn_location = find_space_spawn() + if(isnull(spawn_location)) return MAP_ERROR - var/mob/living/carbon/human/operative = new(pick(spawn_locs)) + var/mob/living/carbon/human/operative = new(spawn_location) operative.randomize_human_appearance(~RANDOMIZE_SPECIES) operative.dna.update_dna_identity() var/datum/mind/Mind = new /datum/mind(selected.key) diff --git a/code/modules/events/ghost_role/revenant_event.dm b/code/modules/events/ghost_role/revenant_event.dm index c7d06db9cd1..27f3597a7ad 100644 --- a/code/modules/events/ghost_role/revenant_event.dm +++ b/code/modules/events/ghost_role/revenant_event.dm @@ -48,9 +48,7 @@ if(T && is_station_level(T.z)) spawn_locs += T if(!spawn_locs.len) //If we can't find any valid spawnpoints, try the carp spawns - for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) - if(isturf(L.loc)) - spawn_locs += L.loc + spawn_locs += find_space_spawn() if(!spawn_locs.len) //If we can't find either, just spawn the revenant at the player's location spawn_locs += get_turf(selected) if(!spawn_locs.len) //If we can't find THAT, then just give up and cry diff --git a/code/modules/events/ghost_role/slaughter_event.dm b/code/modules/events/ghost_role/slaughter_event.dm index 2937bfead5b..ed4a0da6938 100644 --- a/code/modules/events/ghost_role/slaughter_event.dm +++ b/code/modules/events/ghost_role/slaughter_event.dm @@ -25,18 +25,11 @@ var/datum/mind/player_mind = new /datum/mind(selected.key) player_mind.active = TRUE - var/list/spawn_locs = list() - for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) - if(isturf(L.loc)) - spawn_locs += L.loc - - if(!spawn_locs) - message_admins("No valid spawn locations found, aborting...") - return MAP_ERROR - - var/turf/chosen = pick(spawn_locs) - var/mob/living/simple_animal/hostile/imp/slaughter/S = new(chosen) - new /obj/effect/dummy/phased_mob(chosen, S) + var/spawn_location = find_space_spawn() + if(!spawn_location) + return MAP_ERROR //This sends an error message further up. + var/mob/living/simple_animal/hostile/imp/slaughter/S = new(spawn_location) + new /obj/effect/dummy/phased_mob(spawn_location, S) player_mind.transfer_to(S) player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/slaughter_demon)) diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm index 47ecb2c4de3..74db3f84e48 100644 --- a/code/modules/events/ghost_role/space_dragon.dm +++ b/code/modules/events/ghost_role/space_dragon.dm @@ -19,15 +19,6 @@ priority_announce("A large organic energy flux has been recorded near [station_name()], please stand by.", "Lifesign Alert") /datum/round_event/ghost_role/space_dragon/spawn_role() - var/list/spawn_locs = list() - for(var/obj/effect/landmark/carpspawn/carp_spawn in GLOB.landmarks_list) - if(!isturf(carp_spawn.loc)) - stack_trace("Carp spawn found not on a turf: [carp_spawn.type] on [isnull(carp_spawn.loc) ? "null" : carp_spawn.loc.type]") - continue - spawn_locs += carp_spawn.loc - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") - return MAP_ERROR var/list/candidates = get_candidates(ROLE_SPACE_DRAGON, ROLE_SPACE_DRAGON) if(!candidates.len) @@ -36,7 +27,11 @@ var/mob/dead/selected = pick(candidates) var/key = selected.key - var/mob/living/simple_animal/hostile/space_dragon/dragon = new (pick(spawn_locs)) + var/spawn_location = find_space_spawn() + if(isnull(spawn_location)) + return MAP_ERROR + + var/mob/living/simple_animal/hostile/space_dragon/dragon = new (spawn_location) dragon.key = key dragon.mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_dragon)) dragon.mind.special_role = ROLE_SPACE_DRAGON diff --git a/code/modules/events/ghost_role/space_ninja.dm b/code/modules/events/ghost_role/space_ninja.dm index 50840a0e489..3aa7f5ee1ac 100644 --- a/code/modules/events/ghost_role/space_ninja.dm +++ b/code/modules/events/ghost_role/space_ninja.dm @@ -14,14 +14,8 @@ role_name = "Space Ninja" /datum/round_event/ghost_role/space_ninja/spawn_role() - var/list/spawn_locs = list() - for(var/obj/effect/landmark/carpspawn/carp_spawn in GLOB.landmarks_list) - if(!isturf(carp_spawn.loc)) - stack_trace("Carp spawn found not on a turf: [carp_spawn.type] on [isnull(carp_spawn.loc) ? "null" : carp_spawn.loc.type]") - continue - spawn_locs += carp_spawn.loc - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") + var/spawn_location = find_space_spawn() + if(isnull(spawn_location)) return MAP_ERROR //selecting a candidate player @@ -33,7 +27,7 @@ var/key = selected_candidate.key //spawn the ninja and assign the candidate - var/mob/living/carbon/human/ninja = create_space_ninja(pick(spawn_locs)) + var/mob/living/carbon/human/ninja = create_space_ninja(spawn_location) ninja.key = key ninja.mind.add_antag_datum(/datum/antagonist/ninja) spawned_mobs += ninja diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 63668b95bc7..ab518fd3af1 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -24,21 +24,11 @@ create_midwife_eggs(spawncount) /proc/create_midwife_eggs(amount) - var/list/spawn_locs = list() - for(var/x in GLOB.generic_maintenance_landmarks) - var/turf/spawn_turf = x - var/light_amount = spawn_turf.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD && is_safe_turf(spawn_turf)) - spawn_locs += spawn_turf - if(!length(spawn_locs)) - message_admins("No valid spawn locations found in GLOB.generic_maintenance_landmarks, aborting spider spawning...") - return MAP_ERROR while(amount > 0) - var/turf/spawn_loc = pick_n_take(spawn_locs) - if(!spawn_loc) - message_admins("Midwife egg creation ran out of locations to spawn at. Terminating egg spawn with [amount] spawns remaining.") - break - var/obj/effect/mob_spawn/ghost_role/spider/midwife/new_eggs = new /obj/effect/mob_spawn/ghost_role/spider/midwife(spawn_loc) + var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) + if(isnull(spawn_loc)) + return //Admins will have already been notified of the spawning failure at this point + var/obj/effect/mob_spawn/ghost_role/spider/midwife/new_eggs = new (spawn_loc) new_eggs.amount_grown = 98 amount-- log_game("Midwife spider eggs were spawned via an event.") diff --git a/tgstation.dme b/tgstation.dme index 93edfa53db6..6aef59a85cc 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -436,6 +436,7 @@ #include "code\__HELPERS\dna.dm" #include "code\__HELPERS\duplicating.dm" #include "code\__HELPERS\dynamic_human_icon_gen.dm" +#include "code\__HELPERS\events.dm" #include "code\__HELPERS\files.dm" #include "code\__HELPERS\filters.dm" #include "code\__HELPERS\forensics.dm"