/* * GAMEMODES (by Rastaf0) * * In the new mode system all special roles are fully supported. * You can have proper wizards/traitors/changelings/cultists during any mode. * Only two things really depends on gamemode: * 1. Starting roles, equipment and preparations * 2. Conditions of finishing the round. * */ /datum/game_mode var/name = "invalid" var/config_tag = null var/votable = 1 var/probability = 0 var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report? var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm var/nuke_off_station = 0 //Used for tracking where the nuke hit var/round_ends_with_antag_death = 0 //flags the "one verse the station" antags as such var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist var/list/protected_jobs = list() // Jobs that can't be traitors because var/required_players = 0 var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations var/required_enemies = 0 var/recommended_enemies = 0 var/antag_flag = null //preferences flag such as BE_WIZARD that need to be turned on for players to be antag var/mob/living/living_antag_player = null var/list/datum/game_mode/replacementmode = null var/round_converted = 0 //0: round not converted, 1: round going to convert, 2: round converted var/reroll_friendly //During mode conversion only these are in the running var/continuous_sanity_checked //Catches some cases where config options could be used to suggest that modes without antagonists should end when all antagonists die var/enemy_minimum_age = 7 //How many days must players have been playing before they can play this antagonist var/announce_span = "warning" //The gamemode's name will be in this span during announcement. var/announce_text = "This gamemode forgot to set a descriptive text! Uh oh!" //Used to describe a gamemode when it's announced. var/const/waittime_l = 600 var/const/waittime_h = 1800 // started at 1800 var/list/datum/station_goal/station_goals = list() var/allow_persistence_save = TRUE /datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description. to_chat(world, "The gamemode is: [name]!") to_chat(world, "[announce_text]") ///Checks to see if the game can be setup and ran with the current number of players or whatnot. /datum/game_mode/proc/can_start() var/playerC = 0 for(var/mob/dead/new_player/player in GLOB.player_list) if((player.client)&&(player.ready == PLAYER_READY_TO_PLAY)) playerC++ if(!GLOB.Debug2) if(playerC < required_players || (maximum_players >= 0 && playerC > maximum_players)) return 0 antag_candidates = get_players_for_role(antag_flag) if(!GLOB.Debug2) if(antag_candidates.len < required_enemies) return 0 return 1 else message_admins("DEBUG: GAME STARTING WITHOUT PLAYER NUMBER CHECKS, THIS WILL PROBABLY BREAK SHIT.") return 1 ///Attempts to select players for special roles the mode might have. /datum/game_mode/proc/pre_setup() return 1 ///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things /datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report. if(!report) report = !CONFIG_GET(flag/no_intercept_report) addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) if(SSdbcore.Connect()) var/sql if(SSticker.mode) sql += "game_mode = '[SSticker.mode]'" if(GLOB.revdata.originmastercommit) if(sql) sql += ", " sql += "commit_hash = '[GLOB.revdata.originmastercommit]'" if(sql) var/datum/DBQuery/query_round_game_mode = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET [sql] WHERE id = [GLOB.round_id]") query_round_game_mode.Execute() if(report) addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h)) generate_station_goals() return 1 ///Handles late-join antag assignments /datum/game_mode/proc/make_antag_chance(mob/living/carbon/human/character) if(replacementmode && round_converted == 2) replacementmode.make_antag_chance(character) return ///Allows rounds to basically be "rerolled" should the initial premise fall through. Also known as mulligan antags. /datum/game_mode/proc/convert_roundtype() set waitfor = FALSE var/list/living_crew = list() for(var/mob/Player in GLOB.mob_list) if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player) && Player.client) living_crew += Player var/malc = CONFIG_GET(number/midround_antag_life_check) if(living_crew.len / GLOB.joined_player_list.len <= malc) //If a lot of the player base died, we start fresh message_admins("Convert_roundtype failed due to too many dead people. Limit is [malc * 100]% living crew") return null var/list/datum/game_mode/runnable_modes = config.get_runnable_midround_modes(living_crew.len) var/list/datum/game_mode/usable_modes = list() for(var/datum/game_mode/G in runnable_modes) if(G.reroll_friendly && living_crew.len >= G.required_players) usable_modes += G else qdel(G) if(!usable_modes) message_admins("Convert_roundtype failed due to no valid modes to convert to. Please report this error to the Coders.") return null replacementmode = pickweight(usable_modes) switch(SSshuttle.emergency.mode) //Rounds on the verge of ending don't get new antags, they just run out if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE) return 1 if(SHUTTLE_CALL) if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergencyCallTime)*0.5) return 1 var/matc = CONFIG_GET(number/midround_antag_time_check) if(world.time >= (matc * 600)) message_admins("Convert_roundtype failed due to round length. Limit is [matc] minutes.") return null var/list/antag_candidates = list() for(var/mob/living/carbon/human/H in living_crew) if(H.client && H.client.prefs.allow_midround_antag) antag_candidates += H if(!antag_candidates) message_admins("Convert_roundtype failed due to no antag candidates.") return null antag_candidates = shuffle(antag_candidates) if(CONFIG_GET(flag/protect_roles_from_antagonist)) replacementmode.restricted_jobs += replacementmode.protected_jobs if(CONFIG_GET(flag/protect_assistant_from_antagonist)) replacementmode.restricted_jobs += "Assistant" message_admins("The roundtype will be converted. If you have other plans for the station or feel the station is too messed up to inhabit stop the creation of antags or end the round now.") . = 1 sleep(rand(600,1800)) if(!SSticker.IsRoundInProgress()) message_admins("Roundtype conversion cancelled, the game appears to have finished!") round_converted = 0 return //somewhere between 1 and 3 minutes from now if(!CONFIG_GET(keyed_flag_list/midround_antag)[SSticker.mode.config_tag]) round_converted = 0 return 1 for(var/mob/living/carbon/human/H in antag_candidates) if(H.client) replacementmode.make_antag_chance(H) round_converted = 2 message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --") ///Called by the gameSSticker /datum/game_mode/process() return 0 //For things that do not die easily /datum/game_mode/proc/are_special_antags_dead() return TRUE /datum/game_mode/proc/check_finished(force_ending) //to be called by SSticker if(!SSticker.setup_done) return FALSE if(replacementmode && round_converted == 2) return replacementmode.check_finished() if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)) return TRUE if(station_was_nuked) return TRUE var/list/continuous = CONFIG_GET(keyed_flag_list/continuous) var/list/midround_antag = CONFIG_GET(keyed_flag_list/midround_antag) if(!round_converted && (!continuous[config_tag] || (continuous[config_tag] && midround_antag[config_tag]))) //Non-continuous or continous with replacement antags if(!continuous_sanity_checked) //make sure we have antags to be checking in the first place for(var/mob/Player in GLOB.mob_list) if(Player.mind) if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) continuous_sanity_checked = 1 return 0 if(!continuous_sanity_checked) message_admins("The roundtype ([config_tag]) has no antagonists, continuous round has been defaulted to on and midround_antag has been defaulted to off.") continuous[config_tag] = TRUE midround_antag[config_tag] = FALSE SSshuttle.clearHostileEnvironment(src) return 0 if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player)) return 0 //A resource saver: once we find someone who has to die for all antags to be dead, we can just keep checking them, cycling over everyone only when we lose our mark. for(var/mob/Player in GLOB.alive_mob_list) if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) &&!isbrain(Player) && Player.client) if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) //Someone's still antaging! living_antag_player = Player return 0 if(!are_special_antags_dead()) return FALSE if(!continuous[config_tag] || force_ending) return 1 else round_converted = convert_roundtype() if(!round_converted) if(round_ends_with_antag_death) return 1 else midround_antag[config_tag] = 0 return 0 return 0 /datum/game_mode/proc/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere. return 0 /datum/game_mode/proc/send_intercept() var/intercepttext = "Central Command Status Summary