////////////////////////////////////////////// // // // MIDROUND RULESETS // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround // Can be drafted once in a while during a round ruletype = "Midround" /// If the ruleset should be restricted from ghost roles. var/restrict_ghost_roles = TRUE /// What type the ruleset is restricted to. var/required_type = /mob/living/carbon/human var/list/living_players = list() var/list/living_antags = list() var/list/dead_players = list() var/list/list_observers = list() /datum/dynamic_ruleset/midround/from_ghosts weight = 0 /// Whether the ruleset should call generate_ruleset_body or not. var/makeBody = TRUE /datum/dynamic_ruleset/midround/trim_candidates() // Unlike the previous two types, these rulesets are not meant for /mob/dead/new_player // And since I want those rulesets to be as flexible as possible, I'm not gonna put much here, // // All you need to know is that here, the candidates list contains 4 lists itself, indexed with the following defines: // Candidates = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS) // So for example you can get the list of all current dead players with var/list/dead_players = candidates[CURRENT_DEAD_PLAYERS] // Make sure to properly typecheck the mobs in those lists, as the dead_players list could contain ghosts, or dead players still in their bodies. // We're still gonna trim the obvious (mobs without clients, jobbanned players, etc) living_players = trim_list(mode.current_players[CURRENT_LIVING_PLAYERS]) living_antags = trim_list(mode.current_players[CURRENT_LIVING_ANTAGS]) dead_players = trim_list(mode.current_players[CURRENT_DEAD_PLAYERS]) list_observers = trim_list(mode.current_players[CURRENT_OBSERVERS]) /datum/dynamic_ruleset/midround/proc/trim_list(list/L = list()) var/list/trimmed_list = L.Copy() var/antag_name = initial(antag_flag) for(var/mob/M in trimmed_list) if (!istype(M, required_type)) trimmed_list.Remove(M) continue if (!M.client) // Are they connected? trimmed_list.Remove(M) continue if(!mode.check_age(M.client, minimum_required_age)) trimmed_list.Remove(M) continue if (!(antag_name in M.client.prefs.be_special) || jobban_isbanned(M.ckey, list(antag_name, ROLE_SYNDICATE)))//are they willing and not antag-banned? trimmed_list.Remove(M) continue if (M.mind) if (restrict_ghost_roles && M.mind.assigned_role in GLOB.exp_specialmap[EXP_TYPE_SPECIAL]) // Are they playing a ghost role? trimmed_list.Remove(M) continue if (M.mind.assigned_role in restricted_roles || HAS_TRAIT(M, TRAIT_MINDSHIELD)) // Does their job allow it or are they mindshielded? trimmed_list.Remove(M) continue if ((exclusive_roles.len > 0) && !(M.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job? trimmed_list.Remove(M) continue return trimmed_list // You can then for example prompt dead players in execute() to join as strike teams or whatever // Or autotator someone // IMPORTANT, since /datum/dynamic_ruleset/midround may accept candidates from both living, dead, and even antag players, you need to manually check whether there are enough candidates // (see /datum/dynamic_ruleset/midround/autotraitor/ready(var/forced = FALSE) for example) /datum/dynamic_ruleset/midround/ready(forced = FALSE) if (!forced) var/job_check = 0 if (enemy_roles.len > 0) for (var/mob/M in living_players) if (M.stat == DEAD) continue // Dead players cannot count as opponents if (M.mind && M.mind.assigned_role && (M.mind.assigned_role in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role in restricted_roles))) job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it var/threat = round(mode.threat_level/10) if (job_check < required_enemies[threat]) return FALSE return TRUE /datum/dynamic_ruleset/midround/from_ghosts/execute() var/list/possible_candidates = list() possible_candidates.Add(dead_players) possible_candidates.Add(list_observers) send_applications(possible_candidates) if(assigned.len > 0) return TRUE else return FALSE /// This sends a poll to ghosts if they want to be a ghost spawn from a ruleset. /datum/dynamic_ruleset/midround/from_ghosts/proc/send_applications(list/possible_volunteers = list()) if (possible_volunteers.len <= 0) // This shouldn't happen, as ready() should return FALSE if there is not a single valid candidate message_admins("Possible volunteers was 0. This shouldn't appear, because of ready(), unless you forced it!") return message_admins("Polling [possible_volunteers.len] players to apply for the [name] ruleset.") log_game("DYNAMIC: Polling [possible_volunteers.len] players to apply for the [name] ruleset.") candidates = pollGhostCandidates("The mode is looking for volunteers to become [antag_flag] for [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300) if(!candidates || candidates.len <= 0) message_admins("The ruleset [name] received no applications.") log_game("DYNAMIC: The ruleset [name] received no applications.") mode.refund_threat(cost) mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (no applications)" mode.executed_rules -= src return message_admins("[candidates.len] players volunteered for the ruleset [name].") log_game("DYNAMIC: [candidates.len] players volunteered for [name].") review_applications() /// Here is where you can check if your ghost applicants are valid for the ruleset. /// Called by send_applications(). /datum/dynamic_ruleset/midround/from_ghosts/proc/review_applications() for (var/i = 1, i <= required_candidates, i++) if(candidates.len <= 0) if(i == 1) // We have found no candidates so far and we are out of applicants. mode.refund_threat(cost) mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (all applications invalid)" mode.executed_rules -= src break var/mob/applicant = pick(candidates) candidates -= applicant if(!isobserver(applicant)) if(applicant.stat == DEAD) // Not an observer? If they're dead, make them one. applicant = applicant.ghostize(FALSE) else // Not dead? Disregard them, pick a new applicant i-- continue if(!applicant) i-- continue var/mob/new_character = applicant if (makeBody) new_character = generate_ruleset_body(applicant) finish_setup(new_character, i) assigned += applicant notify_ghosts("[new_character] has been picked for the ruleset [name]!", source = new_character, action = NOTIFY_ORBIT, header="Something Interesting!") /datum/dynamic_ruleset/midround/from_ghosts/proc/generate_ruleset_body(mob/applicant) var/mob/living/carbon/human/new_character = makeBody(applicant) new_character.dna.remove_all_mutations() return new_character /datum/dynamic_ruleset/midround/from_ghosts/proc/finish_setup(mob/new_character, index) var/datum/antagonist/new_role = new antag_datum() setup_role(new_role) new_character.mind.add_antag_datum(new_role) new_character.mind.special_role = antag_flag /datum/dynamic_ruleset/midround/from_ghosts/proc/setup_role(datum/antagonist/new_role) return ////////////////////////////////////////////// // // // SYNDICATE TRAITORS // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround/autotraitor name = "Syndicate Sleeper Agent" config_tag = "midround_traitor" antag_datum = /datum/antagonist/traitor antag_flag = ROLE_TRAITOR restricted_roles = list("AI", "Cyborg", "Positronic Brain") protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_candidates = 1 weight = 7 cost = 10 requirements = list(50,40,30,20,10,10,10,10,10,10) repeatable = TRUE high_population_requirement = 10 flags = TRAITOR_RULESET /datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0) var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len var/antag_count = mode.current_players[CURRENT_LIVING_ANTAGS].len var/max_traitors = round(player_count / 10) + 1 if ((antag_count < max_traitors) && prob(mode.threat_level))//adding traitors if the antag population is getting low return ..() else return FALSE /datum/dynamic_ruleset/midround/autotraitor/trim_candidates() ..() for(var/mob/living/player in living_players) if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon. living_players -= player continue if(is_centcom_level(player.z)) living_players -= player // We don't autotator people in CentCom continue if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0)) living_players -= player // We don't autotator people with roles already /datum/dynamic_ruleset/midround/autotraitor/ready(forced = FALSE) if (required_candidates > living_players.len) return FALSE return ..() /datum/dynamic_ruleset/midround/autotraitor/execute() var/mob/M = pick(living_players) assigned += M living_players -= M var/datum/antagonist/traitor/newTraitor = new M.mind.add_antag_datum(newTraitor) return TRUE ////////////////////////////////////////////// // // // Malfunctioning AI // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround/malf name = "Malfunctioning AI" config_tag = "malf_ai" antag_datum = /datum/antagonist/traitor antag_flag = ROLE_MALF enemy_roles = list("Security Officer", "Warden","Detective","Head of Security", "Captain", "Scientist", "Chemist", "Research Director", "Chief Engineer") exclusive_roles = list("AI") required_enemies = list(4,4,4,4,4,4,2,2,2,0) required_candidates = 1 weight = 3 cost = 35 requirements = list(101,101,80,70,60,60,50,50,40,40) high_population_requirement = 35 required_type = /mob/living/silicon/ai var/ion_announce = 33 var/removeDontImproveChance = 10 /datum/dynamic_ruleset/midround/malf/trim_candidates() ..() candidates = candidates[CURRENT_LIVING_PLAYERS] for(var/mob/living/player in candidates) if(!isAI(player)) candidates -= player continue if(is_centcom_level(player.z)) candidates -= player continue if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0)) candidates -= player /datum/dynamic_ruleset/midround/malf/execute() if(!candidates || !candidates.len) return FALSE var/mob/living/silicon/ai/M = pick(candidates) candidates -= M assigned += M.mind var/datum/antagonist/traitor/AI = new M.mind.special_role = antag_flag M.mind.add_antag_datum(AI) if(prob(ion_announce)) priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", "ionstorm") if(prob(removeDontImproveChance)) M.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) else M.add_ion_law(generate_ion_law()) return TRUE ////////////////////////////////////////////// // // // WIZARD (GHOST) // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround/from_ghosts/wizard name = "Wizard" config_tag = "midround_wizard" antag_datum = /datum/antagonist/wizard antag_flag = ROLE_WIZARD enemy_roles = list("Security Officer","Detective","Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 1 cost = 20 requirements = list(90,90,70,40,30,20,10,10,10,10) high_population_requirement = 50 repeatable = TRUE /datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE) if (required_candidates > (dead_players.len + list_observers.len)) return FALSE if(GLOB.wizardstart.len == 0) log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") return FALSE return ..() /datum/dynamic_ruleset/midround/from_ghosts/wizard/finish_setup(mob/new_character, index) ..() new_character.forceMove(pick(GLOB.wizardstart)) ////////////////////////////////////////////// // // // NUCLEAR OPERATIVES (MIDROUND) // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround/from_ghosts/nuclear name = "Nuclear Assault" config_tag = "midround_nuclear" antag_flag = ROLE_OPERATIVE antag_datum = /datum/antagonist/nukeop enemy_roles = list("AI", "Cyborg", "Security Officer", "Warden","Detective","Head of Security", "Captain") required_enemies = list(3,3,3,3,3,2,1,1,0,0) required_candidates = 5 weight = 5 cost = 35 requirements = list(90,90,90,80,60,40,30,20,10,10) high_population_requirement = 10 var/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/datum/team/nuclear/nuke_team flags = HIGHLANDER_RULESET /datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0) if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules) return FALSE // Unavailable if nuke ops were already sent at roundstart var/indice_pop = min(10,round(living_players.len/5)+1) required_candidates = operative_cap[indice_pop] return ..() /datum/dynamic_ruleset/midround/from_ghosts/nuclear/ready(forced = FALSE) if (required_candidates > (dead_players.len + list_observers.len)) return FALSE return ..() /datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_setup(mob/new_character, index) new_character.mind.special_role = "Nuclear Operative" new_character.mind.assigned_role = "Nuclear Operative" if (index == 1) // Our first guy is the leader var/datum/antagonist/nukeop/leader/new_role = new nuke_team = new_role.nuke_team new_character.mind.add_antag_datum(new_role) else return ..() ////////////////////////////////////////////// // // // BLOB (GHOST) // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround/from_ghosts/blob name = "Blob" config_tag = "blob" antag_datum = /datum/antagonist/blob antag_flag = ROLE_BLOB enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 4 cost = 10 requirements = list(101,101,101,80,60,50,30,20,10,10) high_population_requirement = 50 repeatable = TRUE /datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant) var/body = applicant.become_overmind() return body ////////////////////////////////////////////// // // // XENOMORPH (GHOST) // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround/from_ghosts/xenomorph name = "Alien Infestation" config_tag = "xenos" antag_datum = /datum/antagonist/xeno antag_flag = ROLE_ALIEN enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 3 cost = 10 requirements = list(101,101,101,70,50,40,20,15,10,10) high_population_requirement = 50 repeatable = TRUE var/list/vents = list() /datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute() // 50% chance of being incremented by one required_candidates += prob(50) for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines) if(QDELETED(temp_vent)) continue if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] if(!temp_vent_parent) continue // No parent vent // Stops Aliens getting stuck in small networks. // See: Security, Virology if(temp_vent_parent.other_atmosmch.len > 20) vents += temp_vent if(!vents.len) return FALSE . = ..() /datum/dynamic_ruleset/midround/from_ghosts/xenomorph/generate_ruleset_body(mob/applicant) var/obj/vent = pick_n_take(vents) var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) applicant.transfer_ckey(new_xeno, FALSE) message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.") log_game("DYNAMIC: [key_name(new_xeno)] was spawned as an alien by the midround ruleset.") return new_xeno ////////////////////////////////////////////// // // // NIGHTMARE (GHOST) // // // ////////////////////////////////////////////// /datum/dynamic_ruleset/midround/from_ghosts/nightmare name = "Nightmare" config_tag = "nightmare" antag_datum = /datum/antagonist/nightmare antag_flag = "Nightmare" antag_flag_override = ROLE_ALIEN enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain") required_enemies = list(2,2,1,1,1,1,1,0,0,0) required_candidates = 1 weight = 3 cost = 10 requirements = list(101,101,101,70,50,40,20,15,10,10) high_population_requirement = 50 repeatable = TRUE var/list/spawn_locs = list() /datum/dynamic_ruleset/midround/from_ghosts/nightmare/execute() for(var/X in GLOB.xeno_spawn) var/turf/T = X var/light_amount = T.get_lumcount() if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) spawn_locs += T if(!spawn_locs.len) return FALSE . = ..() /datum/dynamic_ruleset/midround/from_ghosts/nightmare/generate_ruleset_body(mob/applicant) var/datum/mind/player_mind = new /datum/mind(applicant.key) player_mind.active = TRUE var/mob/living/carbon/human/S = new (pick(spawn_locs)) player_mind.transfer_to(S) player_mind.assigned_role = "Nightmare" player_mind.special_role = "Nightmare" player_mind.add_antag_datum(/datum/antagonist/nightmare) S.set_species(/datum/species/shadow/nightmare) playsound(S, 'sound/magic/ethereal_exit.ogg', 50, 1, -1) message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Nightmare by the midround ruleset.") log_game("DYNAMIC: [key_name(S)] was spawned as a Nightmare by the midround ruleset.") return S