diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 9646f03252..540ec9fb63 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -205,6 +205,22 @@ return null +/proc/pickweightAllowZero(list/L) //The original pickweight proc will sometimes pick entries with zero weight. I'm not sure if changing the original will break anything, so I left it be. + var/total = 0 + var/item + for (item in L) + if (!L[item]) + L[item] = 0 + total += L[item] + + total = rand(0, total) + for (item in L) + total -=L [item] + if (total <= 0 && L[item]) + return item + + return null + //Pick a random element from the list and remove it from the list. /proc/pick_n_take(list/L) if(L.len) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 180d14853e..d19484fd3f 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -129,6 +129,8 @@ GLOBAL_PROTECT(config_dir) //game_options.txt configs var/force_random_names = 0 var/list/mode_names = list() + var/list/mode_reports = list() + var/list/mode_false_report_weight = list() var/list/modes = list() // allowed modes var/list/votable_modes = list() // votable modes var/list/probabilities = list() // relative probability of each mode @@ -297,6 +299,8 @@ GLOBAL_PROTECT(config_dir) modes += M.config_tag mode_names[M.config_tag] = M.name probabilities[M.config_tag] = M.probability + mode_reports[M.config_tag] = M.generate_report() + mode_false_report_weight[M.config_tag] = M.false_report_weight if(M.votable) votable_modes += M.config_tag qdel(M) diff --git a/code/game/gamemodes/blob/blob.dm b/code/game/gamemodes/blob/blob.dm index ec923c635b..36a23b2bd1 100644 --- a/code/game/gamemodes/blob/blob.dm +++ b/code/game/gamemodes/blob/blob.dm @@ -1,104 +1,109 @@ - - -//Few global vars to track the blob -GLOBAL_LIST_EMPTY(blobs) //complete list of all blobs made. -GLOBAL_LIST_EMPTY(blob_cores) -GLOBAL_LIST_EMPTY(overminds) -GLOBAL_LIST_EMPTY(blob_nodes) -GLOBAL_LIST_EMPTY(blobs_legit) //used for win-score calculations, contains only blobs counted for win condition - -#define BLOB_NO_PLACE_TIME 1800 //time, in deciseconds, blobs are prevented from bursting in the gamemode - -/datum/game_mode/blob - name = "blob" - config_tag = "blob" - antag_flag = ROLE_BLOB - - required_players = 25 - required_enemies = 1 - recommended_enemies = 1 - - round_ends_with_antag_death = 1 - - announce_span = "green" - announce_text = "Dangerous gelatinous organisms are spreading throughout the station!\n\ - Blobs: Consume the station and spread as far as you can.\n\ - Crew: Fight back the blobs and minimize station damage." - - var/message_sent = FALSE - - var/cores_to_spawn = 1 - var/players_per_core = 25 - var/blob_point_rate = 3 - var/blob_base_starting_points = 80 - - var/blobwincount = 250 - - var/messagedelay_low = 2400 //in deciseconds - var/messagedelay_high = 3600 //blob report will be sent after a random value between these (minimum 4 minutes, maximum 6 minutes) - - var/list/blob_overminds = list() - -/datum/game_mode/blob/pre_setup() - cores_to_spawn = max(round(num_players()/players_per_core, 1), 1) - - var/win_multiplier = 1 + (0.1 * cores_to_spawn) - blobwincount = initial(blobwincount) * cores_to_spawn * win_multiplier - - for(var/j = 0, j < cores_to_spawn, j++) - if (!antag_candidates.len) - break - var/datum/mind/blob = pick(antag_candidates) - blob_overminds += blob - blob.assigned_role = "Blob" - blob.special_role = "Blob" - log_game("[blob.key] (ckey) has been selected as a Blob") - antag_candidates -= blob - - if(!blob_overminds.len) - return 0 - - return 1 - -/datum/game_mode/blob/proc/get_blob_candidates() - var/list/candidates = list() - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(!player.stat && player.mind && !player.mind.special_role && !jobban_isbanned(player, "Syndicate") && (ROLE_BLOB in player.client.prefs.be_special)) - if(age_check(player.client)) - candidates += player - return candidates - -/datum/game_mode/blob/proc/show_message(message) - for(var/datum/mind/blob in blob_overminds) - to_chat(blob.current, message) - -/datum/game_mode/blob/post_setup() - set waitfor = FALSE - - for(var/datum/mind/blob in blob_overminds) - var/mob/camera/blob/B = blob.current.become_overmind(TRUE, round(blob_base_starting_points/blob_overminds.len)) - B.mind.name = B.name - var/turf/T = pick(GLOB.blobstart) - B.loc = T - B.base_point_rate = blob_point_rate - - SSshuttle.registerHostileEnvironment(src) - - // Disable the blob event for this round. - var/datum/round_event_control/blob/B = locate() in SSevents.control - if(B) - B.max_occurrences = 0 // disable the event - - . = ..() - - var/message_delay = rand(messagedelay_low, messagedelay_high) //between 4 and 6 minutes with 2400 low and 3600 high. - - sleep(message_delay) - - send_intercept(1) - message_sent = TRUE +//Few global vars to track the blob +GLOBAL_LIST_EMPTY(blobs) //complete list of all blobs made. +GLOBAL_LIST_EMPTY(blob_cores) +GLOBAL_LIST_EMPTY(overminds) +GLOBAL_LIST_EMPTY(blob_nodes) +GLOBAL_LIST_EMPTY(blobs_legit) //used for win-score calculations, contains only blobs counted for win condition + +#define BLOB_NO_PLACE_TIME 1800 //time, in deciseconds, blobs are prevented from bursting in the gamemode + +/datum/game_mode/blob + name = "blob" + config_tag = "blob" + antag_flag = ROLE_BLOB + false_report_weight = 5 + + required_players = 25 + required_enemies = 1 + recommended_enemies = 1 + + round_ends_with_antag_death = 1 + + announce_span = "green" + announce_text = "Dangerous gelatinous organisms are spreading throughout the station!\n\ + Blobs: Consume the station and spread as far as you can.\n\ + Crew: Fight back the blobs and minimize station damage." + + var/message_sent = FALSE + + var/cores_to_spawn = 1 + var/players_per_core = 25 + var/blob_point_rate = 3 + var/blob_base_starting_points = 80 + + var/blobwincount = 250 + + var/messagedelay_low = 2400 //in deciseconds + var/messagedelay_high = 3600 //blob report will be sent after a random value between these (minimum 4 minutes, maximum 6 minutes) + + var/list/blob_overminds = list() + +/datum/game_mode/blob/pre_setup() + cores_to_spawn = max(round(num_players()/players_per_core, 1), 1) + + var/win_multiplier = 1 + (0.1 * cores_to_spawn) + blobwincount = initial(blobwincount) * cores_to_spawn * win_multiplier + + for(var/j = 0, j < cores_to_spawn, j++) + if (!antag_candidates.len) + break + var/datum/mind/blob = pick(antag_candidates) + blob_overminds += blob + blob.assigned_role = "Blob" + blob.special_role = "Blob" + log_game("[blob.key] (ckey) has been selected as a Blob") + antag_candidates -= blob + + if(!blob_overminds.len) + return 0 + + return 1 + +/datum/game_mode/blob/proc/get_blob_candidates() + var/list/candidates = list() + for(var/mob/living/carbon/human/player in GLOB.player_list) + if(!player.stat && player.mind && !player.mind.special_role && !jobban_isbanned(player, "Syndicate") && (ROLE_BLOB in player.client.prefs.be_special)) + if(age_check(player.client)) + candidates += player + return candidates + +/datum/game_mode/blob/proc/show_message(message) + for(var/datum/mind/blob in blob_overminds) + to_chat(blob.current, message) + +/datum/game_mode/blob/post_setup() + set waitfor = FALSE + + for(var/datum/mind/blob in blob_overminds) + var/mob/camera/blob/B = blob.current.become_overmind(TRUE, round(blob_base_starting_points/blob_overminds.len)) + B.mind.name = B.name + var/turf/T = pick(GLOB.blobstart) + B.loc = T + B.base_point_rate = blob_point_rate + + SSshuttle.registerHostileEnvironment(src) + + // Disable the blob event for this round. + var/datum/round_event_control/blob/B = locate() in SSevents.control + if(B) + B.max_occurrences = 0 // disable the event + + . = ..() + + var/message_delay = rand(messagedelay_low, messagedelay_high) //between 4 and 6 minutes with 2400 low and 3600 high. + + sleep(message_delay) + + send_intercept(1) + message_sent = TRUE addtimer(CALLBACK(src, .proc/SendSecondIntercept), 24000) - + /datum/game_mode/blob/proc/SendSecondIntercept() - if(!replacementmode) + if(!replacementmode) send_intercept(2) //if the blob has been alive this long, it's time to bomb it + +/datum/game_mode/blob/generate_report() + return "A CMP scientist by the name of [pick("Griff", "Pasteur", "Chamberland", "Buist", "Rivers", "Stanley")] boasted about his corporation's \"finest creation\" - a macrobiological \ + virus capable of self-reproduction and hellbent on consuming whatever it touches. He went on to query Cybersun for permission to utilize the virus in biochemical warfare, to which \ + CMP subsequently gained. Be vigilant for any large organisms rapidly spreading across the station, as they are classified as a level 5 biohazard and critically dangerous. Note that \ + this organism seems to be weak to extreme heat; concentrated fire (such as welding tools and lasers) will be effective against it." diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index 4d9f1c7f23..14126c69f3 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -1,526 +1,511 @@ -#define LING_FAKEDEATH_TIME 400 //40 seconds -#define LING_DEAD_GENETICDAMAGE_HEAL_CAP 50 //The lowest value of geneticdamage handle_changeling() can take it to while dead. -#define LING_ABSORB_RECENT_SPEECH 8 //The amount of recent spoken lines to gain on absorbing a mob - -GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega")) -GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store")) -GLOBAL_LIST_INIT(slot2slot, list("head" = slot_head, "wear_mask" = slot_wear_mask, "neck" = slot_neck, "back" = slot_back, "wear_suit" = slot_wear_suit, "w_uniform" = slot_w_uniform, "shoes" = slot_shoes, "belt" = slot_belt, "gloves" = slot_gloves, "glasses" = slot_glasses, "ears" = slot_ears, "wear_id" = slot_wear_id, "s_store" = slot_s_store)) -GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling)) - - -/datum/game_mode - var/list/datum/mind/changelings = list() - - -/datum/game_mode/changeling - name = "changeling" - config_tag = "changeling" - antag_flag = ROLE_CHANGELING - restricted_jobs = list("AI", "Cyborg") - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") - required_players = 15 - required_enemies = 1 - recommended_enemies = 4 - reroll_friendly = 1 - - announce_span = "green" - announce_text = "Alien changelings have infiltrated the crew!\n\ - Changelings: Accomplish the objectives assigned to you.\n\ - Crew: Root out and eliminate the changeling menace." - - var/const/prob_int_murder_target = 50 // intercept names the assassination target half the time - var/const/prob_right_murder_target_l = 25 // lower bound on probability of naming right assassination target - var/const/prob_right_murder_target_h = 50 // upper bound on probability of naimg the right assassination target - - var/const/prob_int_item = 50 // intercept names the theft target half the time - var/const/prob_right_item_l = 25 // lower bound on probability of naming right theft target - var/const/prob_right_item_h = 50 // upper bound on probability of naming the right theft target - - var/const/prob_int_sab_target = 50 // intercept names the sabotage target half the time - var/const/prob_right_sab_target_l = 25 // lower bound on probability of naming right sabotage target - var/const/prob_right_sab_target_h = 50 // upper bound on probability of naming right sabotage target - - var/const/prob_right_killer_l = 25 //lower bound on probability of naming the right operative - var/const/prob_right_killer_h = 50 //upper bound on probability of naming the right operative - var/const/prob_right_objective_l = 25 //lower bound on probability of determining the objective correctly - var/const/prob_right_objective_h = 50 //upper bound on probability of determining the objective correctly - - var/const/changeling_amount = 4 //hard limit on changelings if scaling is turned off - - var/changeling_team_objective_type = null //If this is not null, we hand our this objective to all lings - -/datum/game_mode/changeling/pre_setup() - - if(config.protect_roles_from_antagonist) - restricted_jobs += protected_jobs - - if(config.protect_assistant_from_antagonist) - restricted_jobs += "Assistant" - - var/num_changelings = 1 - - if(config.changeling_scaling_coeff) - num_changelings = max(1, min( round(num_players()/(config.changeling_scaling_coeff*2))+2, round(num_players()/config.changeling_scaling_coeff) )) - else - num_changelings = max(1, min(num_players(), changeling_amount)) - - if(antag_candidates.len>0) - for(var/i = 0, i < num_changelings, i++) - if(!antag_candidates.len) break - var/datum/mind/changeling = pick(antag_candidates) - antag_candidates -= changeling - changelings += changeling - changeling.special_role = "Changeling" - changeling.restricted_roles = restricted_jobs - return 1 - else - return 0 - -/datum/game_mode/changeling/post_setup() - - //Decide if it's ok for the lings to have a team objective - //And then set it up to be handed out in forge_changeling_objectives - var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) - var/list/possible_team_objectives = list() - for(var/T in team_objectives) - var/datum/objective/changeling_team_objective/CTO = T - - if(changelings.len >= initial(CTO.min_lings)) - possible_team_objectives += T - - if(possible_team_objectives.len && prob(20*changelings.len)) - changeling_team_objective_type = pick(possible_team_objectives) - - for(var/datum/mind/changeling in changelings) - log_game("[changeling.key] (ckey) has been selected as a changeling") - changeling.current.make_changeling() - forge_changeling_objectives(changeling) - greet_changeling(changeling) - SSticker.mode.update_changeling_icons_added(changeling) - modePlayer += changelings - ..() - -/datum/game_mode/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners - var/changelingcap = min( round(GLOB.joined_player_list.len/(config.changeling_scaling_coeff*2))+2, round(GLOB.joined_player_list.len/config.changeling_scaling_coeff) ) - if(SSticker.mode.changelings.len >= changelingcap) //Caps number of latejoin antagonists - return - if(SSticker.mode.changelings.len <= (changelingcap - 2) || prob(100 - (config.changeling_scaling_coeff*2))) - if(ROLE_CHANGELING in character.client.prefs.be_special) - if(!jobban_isbanned(character, ROLE_CHANGELING) && !jobban_isbanned(character, "Syndicate")) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changling() - -/datum/game_mode/proc/forge_changeling_objectives(datum/mind/changeling, var/team_mode = 0) - //OBJECTIVES - random traitor objectives. Unique objectives "steal brain" and "identity theft". - //No escape alone because changelings aren't suited for it and it'd probably just lead to rampant robusting - //If it seems like they'd be able to do it in play, add a 10% chance to have to escape alone - - var/escape_objective_possible = TRUE - - //if there's a team objective, check if it's compatible with escape objectives - for(var/datum/objective/changeling_team_objective/CTO in changeling.objectives) - if(!CTO.escape_objective_compatible) - escape_objective_possible = FALSE - break - - var/datum/objective/absorb/absorb_objective = new - absorb_objective.owner = changeling - absorb_objective.gen_amount_goal(6, 8) - changeling.objectives += absorb_objective - - if(prob(60)) - var/datum/objective/steal/steal_objective = new - steal_objective.owner = changeling - steal_objective.find_target() - changeling.objectives += steal_objective - - var/list/active_ais = active_ais() - if(active_ais.len && prob(100/GLOB.joined_player_list.len)) - var/datum/objective/destroy/destroy_objective = new - destroy_objective.owner = changeling - destroy_objective.find_target() - changeling.objectives += destroy_objective - else - if(prob(70)) - var/datum/objective/assassinate/kill_objective = new - kill_objective.owner = changeling - if(team_mode) //No backstabbing while in a team - kill_objective.find_target_by_role(role = "Changeling", role_type = 1, invert = 1) - else - kill_objective.find_target() - changeling.objectives += kill_objective - else - var/datum/objective/maroon/maroon_objective = new - maroon_objective.owner = changeling - if(team_mode) - maroon_objective.find_target_by_role(role = "Changeling", role_type = 1, invert = 1) - else - maroon_objective.find_target() - changeling.objectives += maroon_objective - - if (!(locate(/datum/objective/escape) in changeling.objectives) && escape_objective_possible) - var/datum/objective/escape/escape_with_identity/identity_theft = new - identity_theft.owner = changeling - identity_theft.target = maroon_objective.target - identity_theft.update_explanation_text() - changeling.objectives += identity_theft - escape_objective_possible = FALSE - - if (!(locate(/datum/objective/escape) in changeling.objectives) && escape_objective_possible) - if(prob(50)) - var/datum/objective/escape/escape_objective = new - escape_objective.owner = changeling - changeling.objectives += escape_objective - else - var/datum/objective/escape/escape_with_identity/identity_theft = new - identity_theft.owner = changeling - if(team_mode) - identity_theft.find_target_by_role(role = "Changeling", role_type = 1, invert = 1) - else - identity_theft.find_target() - changeling.objectives += identity_theft - escape_objective_possible = FALSE - - - -/datum/game_mode/changeling/forge_changeling_objectives(datum/mind/changeling) - if(changeling_team_objective_type) - var/datum/objective/changeling_team_objective/team_objective = new changeling_team_objective_type - team_objective.owner = changeling - changeling.objectives += team_objective - - ..(changeling,1) - else - ..(changeling,0) - - -/datum/game_mode/proc/greet_changeling(datum/mind/changeling, you_are=1) - if (you_are) - to_chat(changeling.current, "You are [changeling.changeling.changelingID], a changeling! You have absorbed and taken the form of a human.") - to_chat(changeling.current, "Use say \":g message\" to communicate with your fellow changelings.") - to_chat(changeling.current, "You must complete the following tasks:") - changeling.current.playsound_local(get_turf(changeling.current), 'sound/ambience/antag/ling_aler.ogg', 100, FALSE, pressure_affected = FALSE) - - if (changeling.current.mind) - var/mob/living/carbon/human/H = changeling.current - if(H.mind.assigned_role == "Clown") - to_chat(H, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") - H.dna.remove_mutation(CLOWNMUT) - - var/obj_count = 1 - for(var/datum/objective/objective in changeling.objectives) - to_chat(changeling.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ - return - -/*/datum/game_mode/changeling/check_finished() - var/changelings_alive = 0 - for(var/datum/mind/changeling in changelings) - if(!iscarbon(changeling.current)) - continue - if(changeling.current.stat==2) - continue - changelings_alive++ - - if (changelings_alive) - changelingdeath = 0 - return ..() - else - if (!changelingdeath) - changelingdeathtime = world.time - changelingdeath = 1 - if(world.time-changelingdeathtime > TIME_TO_GET_REVIVED) - return 1 - else - return ..() - return 0*/ - -/datum/game_mode/proc/auto_declare_completion_changeling() - if(changelings.len) - var/text = "
The changelings were:" - for(var/datum/mind/changeling in changelings) - var/changelingwin = 1 - if(!changeling.current) - changelingwin = 0 - - text += printplayer(changeling) - - //Removed sanity if(changeling) because we -want- a runtime to inform us that the changelings list is incorrect and needs to be fixed. - text += "
Changeling ID: [changeling.changeling.changelingID]." - text += "
Genomes Extracted: [changeling.changeling.absorbedcount]" - - if(changeling.objectives.len) - var/count = 1 - for(var/datum/objective/objective in changeling.objectives) - if(objective.check_completion()) - text += "
Objective #[count]: [objective.explanation_text] Success!" - SSblackbox.add_details("changeling_objective","[objective.type]|SUCCESS") - else - text += "
Objective #[count]: [objective.explanation_text] Fail." - SSblackbox.add_details("changeling_objective","[objective.type]|FAIL") - changelingwin = 0 - count++ - - if(changelingwin) - text += "
The changeling was successful!" - SSblackbox.add_details("changeling_success","SUCCESS") - else - text += "
The changeling has failed." - SSblackbox.add_details("changeling_success","FAIL") - text += "
" - - to_chat(world, text) - - - return 1 - -/datum/changeling //stores changeling powers, changeling recharge thingie, changeling absorbed DNA and changeling ID (for changeling hivemind) - var/list/stored_profiles = list() //list of datum/changelingprofile - var/datum/changelingprofile/first_prof = null - //var/list/absorbed_dna = list() - //var/list/protected_dna = list() //dna that is not lost when capacity is otherwise full - var/dna_max = 6 //How many extra DNA strands the changeling can store for transformation. - var/absorbedcount = 0 - var/chem_charges = 20 - var/chem_storage = 75 - var/chem_recharge_rate = 1 - var/chem_recharge_slowdown = 0 - var/sting_range = 2 - var/changelingID = "Changeling" - var/geneticdamage = 0 - var/isabsorbing = 0 - var/islinking = 0 - var/geneticpoints = 10 - var/purchasedpowers = list() - var/mimicing = "" - var/canrespec = 0 - var/changeling_speak = 0 - var/datum/dna/chosen_dna - var/obj/effect/proc_holder/changeling/sting/chosen_sting - var/datum/cellular_emporium/cellular_emporium - var/datum/action/innate/cellular_emporium/emporium_action - -/datum/changeling/New(var/gender=FEMALE) - ..() - var/honorific - if(gender == FEMALE) - honorific = "Ms." - else - honorific = "Mr." - if(GLOB.possible_changeling_IDs.len) - changelingID = pick(GLOB.possible_changeling_IDs) - GLOB.possible_changeling_IDs -= changelingID - changelingID = "[honorific] [changelingID]" - else - changelingID = "[honorific] [rand(1,999)]" - - cellular_emporium = new(src) - emporium_action = new(cellular_emporium) - -/datum/changeling/Destroy() - qdel(cellular_emporium) - cellular_emporium = null - qdel(emporium_action) - emporium_action = null - . = ..() - -/datum/changeling/proc/regenerate(var/mob/living/carbon/the_ling) - if(istype(the_ling)) - emporium_action.Grant(the_ling) - if(the_ling.stat == DEAD) - chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), (chem_storage*0.5)) - geneticdamage = max(LING_DEAD_GENETICDAMAGE_HEAL_CAP,geneticdamage-1) - else //not dead? no chem/geneticdamage caps. - chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), chem_storage) - geneticdamage = max(0, geneticdamage-1) - - -/datum/changeling/proc/get_dna(dna_owner) - for(var/datum/changelingprofile/prof in stored_profiles) - if(dna_owner == prof.name) - return prof - -/datum/changeling/proc/has_dna(datum/dna/tDNA) - for(var/datum/changelingprofile/prof in stored_profiles) - if(tDNA.is_same_as(prof.dna)) - return 1 - return 0 - -/datum/changeling/proc/can_absorb_dna(mob/living/carbon/user, mob/living/carbon/human/target, var/verbose=1) - if(stored_profiles.len) - var/datum/changelingprofile/prof = stored_profiles[1] - if(prof.dna == user.dna && stored_profiles.len >= dna_max)//If our current DNA is the stalest, we gotta ditch it. - if(verbose) - to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") - return - if(!target) - return - if((target.disabilities & NOCLONE) || (target.disabilities & HUSK)) - if(verbose) - to_chat(user, "DNA of [target] is ruined beyond usability!") - return - if(!ishuman(target))//Absorbing monkeys is entirely possible, but it can cause issues with transforming. That's what lesser form is for anyway! - if(verbose) - to_chat(user, "We could gain no benefit from absorbing a lesser creature.") - return - if(has_dna(target.dna)) - if(verbose) - to_chat(user, "We already have this DNA in storage!") - return - if(!target.has_dna()) - if(verbose) - to_chat(user, "[target] is not compatible with our biology.") - return - return 1 - -/datum/changeling/proc/create_profile(mob/living/carbon/human/H, mob/living/carbon/human/user, protect = 0) - var/datum/changelingprofile/prof = new - - H.dna.real_name = H.real_name //Set this again, just to be sure that it's properly set. - var/datum/dna/new_dna = new H.dna.type - H.dna.copy_dna(new_dna) - prof.dna = new_dna - prof.name = H.real_name - prof.protected = protect - - prof.underwear = H.underwear - prof.undershirt = H.undershirt - prof.socks = H.socks - - var/list/slots = list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store") - for(var/slot in slots) - if(slot in H.vars) - var/obj/item/I = H.vars[slot] - if(!I) - continue - prof.name_list[slot] = I.name - prof.appearance_list[slot] = I.appearance - prof.flags_cover_list[slot] = I.flags_cover - prof.item_color_list[slot] = I.item_color - prof.item_state_list[slot] = I.item_state - prof.exists_list[slot] = 1 - else - continue - - return prof - -/datum/changeling/proc/add_profile(datum/changelingprofile/prof) - if(stored_profiles.len > dna_max) - if(!push_out_profile()) - return - - stored_profiles += prof - absorbedcount++ - -/datum/changeling/proc/add_new_profile(mob/living/carbon/human/H, mob/living/carbon/human/user, protect = 0) - var/datum/changelingprofile/prof = create_profile(H, protect) - add_profile(prof) - return prof - -/datum/changeling/proc/remove_profile(mob/living/carbon/human/H, force = 0) - for(var/datum/changelingprofile/prof in stored_profiles) - if(H.real_name == prof.name) - if(prof.protected && !force) - continue - stored_profiles -= prof - qdel(prof) - -/datum/changeling/proc/get_profile_to_remove() - for(var/datum/changelingprofile/prof in stored_profiles) - if(!prof.protected) - return prof - -/datum/changeling/proc/push_out_profile() - var/datum/changelingprofile/removeprofile = get_profile_to_remove() - if(removeprofile) - stored_profiles -= removeprofile - return 1 - return 0 - -/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) - var/datum/dna/chosen_dna = chosen_prof.dna - user.real_name = chosen_prof.name - user.underwear = chosen_prof.underwear - user.undershirt = chosen_prof.undershirt - user.socks = chosen_prof.socks - - chosen_dna.transfer_identity(user, 1) - user.updateappearance(mutcolor_update=1) - user.update_body() - user.domutcheck() - - //vars hackery. not pretty, but better than the alternative. - for(var/slot in GLOB.slots) - if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items - qdel(user.vars[slot]) - continue - - if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) - continue - - var/obj/item/C - var/equip = 0 - if(!user.vars[slot]) - var/thetype = GLOB.slot2type[slot] - equip = 1 - C = new thetype(user) - - else if(istype(user.vars[slot], GLOB.slot2type[slot])) - C = user.vars[slot] - - C.appearance = chosen_prof.appearance_list[slot] - C.name = chosen_prof.name_list[slot] - C.flags_cover = chosen_prof.flags_cover_list[slot] - C.item_color = chosen_prof.item_color_list[slot] - C.item_state = chosen_prof.item_state_list[slot] - if(equip) - user.equip_to_slot_or_del(C, GLOB.slot2slot[slot]) - - user.regenerate_icons() - -/datum/changelingprofile - var/name = "a bug" - - var/protected = 0 - - var/datum/dna/dna = null - var/list/name_list = list() //associative list of slotname = itemname - var/list/appearance_list = list() - var/list/flags_cover_list = list() - var/list/exists_list = list() - var/list/item_color_list = list() - var/list/item_state_list = list() - - var/underwear - var/undershirt - var/socks - -/datum/changelingprofile/Destroy() - qdel(dna) - . = ..() - -/datum/changelingprofile/proc/copy_profile(datum/changelingprofile/newprofile) - newprofile.name = name - newprofile.protected = protected - newprofile.dna = new dna.type - dna.copy_dna(newprofile.dna) - newprofile.name_list = name_list.Copy() - newprofile.appearance_list = appearance_list.Copy() - newprofile.flags_cover_list = flags_cover_list.Copy() - newprofile.exists_list = exists_list.Copy() - newprofile.item_color_list = item_color_list.Copy() - newprofile.item_state_list = item_state_list.Copy() - newprofile.underwear = underwear - newprofile.undershirt = undershirt - newprofile.socks = socks - -/datum/game_mode/proc/update_changeling_icons_added(datum/mind/changling_mind) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING] - hud.join_hud(changling_mind.current) - set_antag_hud(changling_mind.current, "changling") - -/datum/game_mode/proc/update_changeling_icons_removed(datum/mind/changling_mind) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING] - hud.leave_hud(changling_mind.current) - set_antag_hud(changling_mind.current, null) +#define LING_FAKEDEATH_TIME 400 //40 seconds +#define LING_DEAD_GENETICDAMAGE_HEAL_CAP 50 //The lowest value of geneticdamage handle_changeling() can take it to while dead. +#define LING_ABSORB_RECENT_SPEECH 8 //The amount of recent spoken lines to gain on absorbing a mob + +GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega")) +GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store")) +GLOBAL_LIST_INIT(slot2slot, list("head" = slot_head, "wear_mask" = slot_wear_mask, "neck" = slot_neck, "back" = slot_back, "wear_suit" = slot_wear_suit, "w_uniform" = slot_w_uniform, "shoes" = slot_shoes, "belt" = slot_belt, "gloves" = slot_gloves, "glasses" = slot_glasses, "ears" = slot_ears, "wear_id" = slot_wear_id, "s_store" = slot_s_store)) +GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling)) + + +/datum/game_mode + var/list/datum/mind/changelings = list() + + +/datum/game_mode/changeling + name = "changeling" + config_tag = "changeling" + antag_flag = ROLE_CHANGELING + false_report_weight = 10 + restricted_jobs = list("AI", "Cyborg") + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") + required_players = 15 + required_enemies = 1 + recommended_enemies = 4 + reroll_friendly = 1 + + announce_span = "green" + announce_text = "Alien changelings have infiltrated the crew!\n\ + Changelings: Accomplish the objectives assigned to you.\n\ + Crew: Root out and eliminate the changeling menace." + + var/const/prob_int_murder_target = 50 // intercept names the assassination target half the time + var/const/prob_right_murder_target_l = 25 // lower bound on probability of naming right assassination target + var/const/prob_right_murder_target_h = 50 // upper bound on probability of naimg the right assassination target + + var/const/prob_int_item = 50 // intercept names the theft target half the time + var/const/prob_right_item_l = 25 // lower bound on probability of naming right theft target + var/const/prob_right_item_h = 50 // upper bound on probability of naming the right theft target + + var/const/prob_int_sab_target = 50 // intercept names the sabotage target half the time + var/const/prob_right_sab_target_l = 25 // lower bound on probability of naming right sabotage target + var/const/prob_right_sab_target_h = 50 // upper bound on probability of naming right sabotage target + + var/const/prob_right_killer_l = 25 //lower bound on probability of naming the right operative + var/const/prob_right_killer_h = 50 //upper bound on probability of naming the right operative + var/const/prob_right_objective_l = 25 //lower bound on probability of determining the objective correctly + var/const/prob_right_objective_h = 50 //upper bound on probability of determining the objective correctly + + var/const/changeling_amount = 4 //hard limit on changelings if scaling is turned off + + var/changeling_team_objective_type = null //If this is not null, we hand our this objective to all lings + +/datum/game_mode/changeling/pre_setup() + + if(config.protect_roles_from_antagonist) + restricted_jobs += protected_jobs + + if(config.protect_assistant_from_antagonist) + restricted_jobs += "Assistant" + + var/num_changelings = 1 + + if(config.changeling_scaling_coeff) + num_changelings = max(1, min( round(num_players()/(config.changeling_scaling_coeff*2))+2, round(num_players()/config.changeling_scaling_coeff) )) + else + num_changelings = max(1, min(num_players(), changeling_amount)) + + if(antag_candidates.len>0) + for(var/i = 0, i < num_changelings, i++) + if(!antag_candidates.len) break + var/datum/mind/changeling = pick(antag_candidates) + antag_candidates -= changeling + changelings += changeling + changeling.special_role = "Changeling" + changeling.restricted_roles = restricted_jobs + return 1 + else + return 0 + +/datum/game_mode/changeling/post_setup() + + //Decide if it's ok for the lings to have a team objective + //And then set it up to be handed out in forge_changeling_objectives + var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) + var/list/possible_team_objectives = list() + for(var/T in team_objectives) + var/datum/objective/changeling_team_objective/CTO = T + + if(changelings.len >= initial(CTO.min_lings)) + possible_team_objectives += T + + if(possible_team_objectives.len && prob(20*changelings.len)) + changeling_team_objective_type = pick(possible_team_objectives) + + for(var/datum/mind/changeling in changelings) + log_game("[changeling.key] (ckey) has been selected as a changeling") + changeling.current.make_changeling() + forge_changeling_objectives(changeling) + greet_changeling(changeling) + SSticker.mode.update_changeling_icons_added(changeling) + modePlayer += changelings + ..() + +/datum/game_mode/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners + var/changelingcap = min( round(GLOB.joined_player_list.len/(config.changeling_scaling_coeff*2))+2, round(GLOB.joined_player_list.len/config.changeling_scaling_coeff) ) + if(SSticker.mode.changelings.len >= changelingcap) //Caps number of latejoin antagonists + return + if(SSticker.mode.changelings.len <= (changelingcap - 2) || prob(100 - (config.changeling_scaling_coeff*2))) + if(ROLE_CHANGELING in character.client.prefs.be_special) + if(!jobban_isbanned(character, ROLE_CHANGELING) && !jobban_isbanned(character, "Syndicate")) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changling() + +/datum/game_mode/proc/forge_changeling_objectives(datum/mind/changeling, var/team_mode = 0) + //OBJECTIVES - random traitor objectives. Unique objectives "steal brain" and "identity theft". + //No escape alone because changelings aren't suited for it and it'd probably just lead to rampant robusting + //If it seems like they'd be able to do it in play, add a 10% chance to have to escape alone + + var/escape_objective_possible = TRUE + + //if there's a team objective, check if it's compatible with escape objectives + for(var/datum/objective/changeling_team_objective/CTO in changeling.objectives) + if(!CTO.escape_objective_compatible) + escape_objective_possible = FALSE + break + + var/datum/objective/absorb/absorb_objective = new + absorb_objective.owner = changeling + absorb_objective.gen_amount_goal(6, 8) + changeling.objectives += absorb_objective + + if(prob(60)) + var/datum/objective/steal/steal_objective = new + steal_objective.owner = changeling + steal_objective.find_target() + changeling.objectives += steal_objective + + var/list/active_ais = active_ais() + if(active_ais.len && prob(100/GLOB.joined_player_list.len)) + var/datum/objective/destroy/destroy_objective = new + destroy_objective.owner = changeling + destroy_objective.find_target() + changeling.objectives += destroy_objective + else + if(prob(70)) + var/datum/objective/assassinate/kill_objective = new + kill_objective.owner = changeling + if(team_mode) //No backstabbing while in a team + kill_objective.find_target_by_role(role = "Changeling", role_type = 1, invert = 1) + else + kill_objective.find_target() + changeling.objectives += kill_objective + else + var/datum/objective/maroon/maroon_objective = new + maroon_objective.owner = changeling + if(team_mode) + maroon_objective.find_target_by_role(role = "Changeling", role_type = 1, invert = 1) + else + maroon_objective.find_target() + changeling.objectives += maroon_objective + + if (!(locate(/datum/objective/escape) in changeling.objectives) && escape_objective_possible) + var/datum/objective/escape/escape_with_identity/identity_theft = new + identity_theft.owner = changeling + identity_theft.target = maroon_objective.target + identity_theft.update_explanation_text() + changeling.objectives += identity_theft + escape_objective_possible = FALSE + + if (!(locate(/datum/objective/escape) in changeling.objectives) && escape_objective_possible) + if(prob(50)) + var/datum/objective/escape/escape_objective = new + escape_objective.owner = changeling + changeling.objectives += escape_objective + else + var/datum/objective/escape/escape_with_identity/identity_theft = new + identity_theft.owner = changeling + if(team_mode) + identity_theft.find_target_by_role(role = "Changeling", role_type = 1, invert = 1) + else + identity_theft.find_target() + changeling.objectives += identity_theft + escape_objective_possible = FALSE + + + +/datum/game_mode/changeling/forge_changeling_objectives(datum/mind/changeling) + if(changeling_team_objective_type) + var/datum/objective/changeling_team_objective/team_objective = new changeling_team_objective_type + team_objective.owner = changeling + changeling.objectives += team_objective + + ..(changeling,1) + else + ..(changeling,0) + +/datum/game_mode/changeling/generate_report() + return "The Gorlex Marauders have announced the successful raid and destruction of Central Command containment ship #S-[rand(1111, 9999)]. This ship housed only a single prisoner - \ + codenamed \"Thing\", and it was highly adaptive and extremely dangerous. We have reason to believe that the Thing has allied with the Syndicate, and you should note that likelihood \ + of the Thing being sent to a station in this sector is highly likely. It may be in the guise of any crew member. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." + +/datum/game_mode/proc/greet_changeling(datum/mind/changeling, you_are=1) + if (you_are) + to_chat(changeling.current, "You are [changeling.changeling.changelingID], a changeling! You have absorbed and taken the form of a human.") + to_chat(changeling.current, "Use say \":g message\" to communicate with your fellow changelings.") + to_chat(changeling.current, "You must complete the following tasks:") + changeling.current.playsound_local(get_turf(changeling.current), 'sound/ambience/antag/ling_aler.ogg', 100, FALSE, pressure_affected = FALSE) + + if (changeling.current.mind) + var/mob/living/carbon/human/H = changeling.current + if(H.mind.assigned_role == "Clown") + to_chat(H, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") + H.dna.remove_mutation(CLOWNMUT) + + var/obj_count = 1 + for(var/datum/objective/objective in changeling.objectives) + to_chat(changeling.current, "Objective #[obj_count]: [objective.explanation_text]") + obj_count++ + return + +/datum/game_mode/proc/auto_declare_completion_changeling() + if(changelings.len) + var/text = "
The changelings were:" + for(var/datum/mind/changeling in changelings) + var/changelingwin = 1 + if(!changeling.current) + changelingwin = 0 + + text += printplayer(changeling) + + //Removed sanity if(changeling) because we -want- a runtime to inform us that the changelings list is incorrect and needs to be fixed. + text += "
Changeling ID: [changeling.changeling.changelingID]." + text += "
Genomes Extracted: [changeling.changeling.absorbedcount]" + + if(changeling.objectives.len) + var/count = 1 + for(var/datum/objective/objective in changeling.objectives) + if(objective.check_completion()) + text += "
Objective #[count]: [objective.explanation_text] Success!" + SSblackbox.add_details("changeling_objective","[objective.type]|SUCCESS") + else + text += "
Objective #[count]: [objective.explanation_text] Fail." + SSblackbox.add_details("changeling_objective","[objective.type]|FAIL") + changelingwin = 0 + count++ + + if(changelingwin) + text += "
The changeling was successful!" + SSblackbox.add_details("changeling_success","SUCCESS") + else + text += "
The changeling has failed." + SSblackbox.add_details("changeling_success","FAIL") + text += "
" + + to_chat(world, text) + + + return 1 + +/datum/changeling //stores changeling powers, changeling recharge thingie, changeling absorbed DNA and changeling ID (for changeling hivemind) + var/list/stored_profiles = list() //list of datum/changelingprofile + var/datum/changelingprofile/first_prof = null + //var/list/absorbed_dna = list() + //var/list/protected_dna = list() //dna that is not lost when capacity is otherwise full + var/dna_max = 6 //How many extra DNA strands the changeling can store for transformation. + var/absorbedcount = 0 + var/chem_charges = 20 + var/chem_storage = 75 + var/chem_recharge_rate = 1 + var/chem_recharge_slowdown = 0 + var/sting_range = 2 + var/changelingID = "Changeling" + var/geneticdamage = 0 + var/isabsorbing = 0 + var/islinking = 0 + var/geneticpoints = 10 + var/purchasedpowers = list() + var/mimicing = "" + var/canrespec = 0 + var/changeling_speak = 0 + var/datum/dna/chosen_dna + var/obj/effect/proc_holder/changeling/sting/chosen_sting + var/datum/cellular_emporium/cellular_emporium + var/datum/action/innate/cellular_emporium/emporium_action + +/datum/changeling/New(var/gender=FEMALE) + ..() + var/honorific + if(gender == FEMALE) + honorific = "Ms." + else + honorific = "Mr." + if(GLOB.possible_changeling_IDs.len) + changelingID = pick(GLOB.possible_changeling_IDs) + GLOB.possible_changeling_IDs -= changelingID + changelingID = "[honorific] [changelingID]" + else + changelingID = "[honorific] [rand(1,999)]" + + cellular_emporium = new(src) + emporium_action = new(cellular_emporium) + +/datum/changeling/Destroy() + qdel(cellular_emporium) + cellular_emporium = null + qdel(emporium_action) + emporium_action = null + . = ..() + +/datum/changeling/proc/regenerate(var/mob/living/carbon/the_ling) + if(istype(the_ling)) + emporium_action.Grant(the_ling) + if(the_ling.stat == DEAD) + chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), (chem_storage*0.5)) + geneticdamage = max(LING_DEAD_GENETICDAMAGE_HEAL_CAP,geneticdamage-1) + else //not dead? no chem/geneticdamage caps. + chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), chem_storage) + geneticdamage = max(0, geneticdamage-1) + + +/datum/changeling/proc/get_dna(dna_owner) + for(var/datum/changelingprofile/prof in stored_profiles) + if(dna_owner == prof.name) + return prof + +/datum/changeling/proc/has_dna(datum/dna/tDNA) + for(var/datum/changelingprofile/prof in stored_profiles) + if(tDNA.is_same_as(prof.dna)) + return 1 + return 0 + +/datum/changeling/proc/can_absorb_dna(mob/living/carbon/user, mob/living/carbon/human/target, var/verbose=1) + if(stored_profiles.len) + var/datum/changelingprofile/prof = stored_profiles[1] + if(prof.dna == user.dna && stored_profiles.len >= dna_max)//If our current DNA is the stalest, we gotta ditch it. + if(verbose) + to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") + return + if(!target) + return + if((target.disabilities & NOCLONE) || (target.disabilities & HUSK)) + if(verbose) + to_chat(user, "DNA of [target] is ruined beyond usability!") + return + if(!ishuman(target))//Absorbing monkeys is entirely possible, but it can cause issues with transforming. That's what lesser form is for anyway! + if(verbose) + to_chat(user, "We could gain no benefit from absorbing a lesser creature.") + return + if(has_dna(target.dna)) + if(verbose) + to_chat(user, "We already have this DNA in storage!") + return + if(!target.has_dna()) + if(verbose) + to_chat(user, "[target] is not compatible with our biology.") + return + return 1 + +/datum/changeling/proc/create_profile(mob/living/carbon/human/H, mob/living/carbon/human/user, protect = 0) + var/datum/changelingprofile/prof = new + + H.dna.real_name = H.real_name //Set this again, just to be sure that it's properly set. + var/datum/dna/new_dna = new H.dna.type + H.dna.copy_dna(new_dna) + prof.dna = new_dna + prof.name = H.real_name + prof.protected = protect + + prof.underwear = H.underwear + prof.undershirt = H.undershirt + prof.socks = H.socks + + var/list/slots = list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store") + for(var/slot in slots) + if(slot in H.vars) + var/obj/item/I = H.vars[slot] + if(!I) + continue + prof.name_list[slot] = I.name + prof.appearance_list[slot] = I.appearance + prof.flags_cover_list[slot] = I.flags_cover + prof.item_color_list[slot] = I.item_color + prof.item_state_list[slot] = I.item_state + prof.exists_list[slot] = 1 + else + continue + + return prof + +/datum/changeling/proc/add_profile(datum/changelingprofile/prof) + if(stored_profiles.len > dna_max) + if(!push_out_profile()) + return + + stored_profiles += prof + absorbedcount++ + +/datum/changeling/proc/add_new_profile(mob/living/carbon/human/H, mob/living/carbon/human/user, protect = 0) + var/datum/changelingprofile/prof = create_profile(H, protect) + add_profile(prof) + return prof + +/datum/changeling/proc/remove_profile(mob/living/carbon/human/H, force = 0) + for(var/datum/changelingprofile/prof in stored_profiles) + if(H.real_name == prof.name) + if(prof.protected && !force) + continue + stored_profiles -= prof + qdel(prof) + +/datum/changeling/proc/get_profile_to_remove() + for(var/datum/changelingprofile/prof in stored_profiles) + if(!prof.protected) + return prof + +/datum/changeling/proc/push_out_profile() + var/datum/changelingprofile/removeprofile = get_profile_to_remove() + if(removeprofile) + stored_profiles -= removeprofile + return 1 + return 0 + +/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) + var/datum/dna/chosen_dna = chosen_prof.dna + user.real_name = chosen_prof.name + user.underwear = chosen_prof.underwear + user.undershirt = chosen_prof.undershirt + user.socks = chosen_prof.socks + + chosen_dna.transfer_identity(user, 1) + user.updateappearance(mutcolor_update=1) + user.update_body() + user.domutcheck() + + //vars hackery. not pretty, but better than the alternative. + for(var/slot in GLOB.slots) + if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items + qdel(user.vars[slot]) + continue + + if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) + continue + + var/obj/item/C + var/equip = 0 + if(!user.vars[slot]) + var/thetype = GLOB.slot2type[slot] + equip = 1 + C = new thetype(user) + + else if(istype(user.vars[slot], GLOB.slot2type[slot])) + C = user.vars[slot] + + C.appearance = chosen_prof.appearance_list[slot] + C.name = chosen_prof.name_list[slot] + C.flags_cover = chosen_prof.flags_cover_list[slot] + C.item_color = chosen_prof.item_color_list[slot] + C.item_state = chosen_prof.item_state_list[slot] + if(equip) + user.equip_to_slot_or_del(C, GLOB.slot2slot[slot]) + + user.regenerate_icons() + +/datum/changelingprofile + var/name = "a bug" + + var/protected = 0 + + var/datum/dna/dna = null + var/list/name_list = list() //associative list of slotname = itemname + var/list/appearance_list = list() + var/list/flags_cover_list = list() + var/list/exists_list = list() + var/list/item_color_list = list() + var/list/item_state_list = list() + + var/underwear + var/undershirt + var/socks + +/datum/changelingprofile/Destroy() + qdel(dna) + . = ..() + +/datum/changelingprofile/proc/copy_profile(datum/changelingprofile/newprofile) + newprofile.name = name + newprofile.protected = protected + newprofile.dna = new dna.type + dna.copy_dna(newprofile.dna) + newprofile.name_list = name_list.Copy() + newprofile.appearance_list = appearance_list.Copy() + newprofile.flags_cover_list = flags_cover_list.Copy() + newprofile.exists_list = exists_list.Copy() + newprofile.item_color_list = item_color_list.Copy() + newprofile.item_state_list = item_state_list.Copy() + newprofile.underwear = underwear + newprofile.undershirt = undershirt + newprofile.socks = socks + +/datum/game_mode/proc/update_changeling_icons_added(datum/mind/changling_mind) + var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING] + hud.join_hud(changling_mind.current) + set_antag_hud(changling_mind.current, "changling") + +/datum/game_mode/proc/update_changeling_icons_removed(datum/mind/changling_mind) + var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING] + hud.leave_hud(changling_mind.current) + set_antag_hud(changling_mind.current, null) + diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm index 3392f46578..af33b649ed 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -1,76 +1,82 @@ -/datum/game_mode/traitor/changeling - name = "traitor+changeling" - config_tag = "traitorchan" - traitors_possible = 3 //hard limit on traitors if scaling is turned off - restricted_jobs = list("AI", "Cyborg") - required_players = 25 - required_enemies = 1 // how many of each type are required - recommended_enemies = 3 - reroll_friendly = 1 - - var/list/possible_changelings = list() - var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off - -/datum/game_mode/traitor/changeling/announce() - to_chat(world, "The current game mode is - Traitor+Changeling!") - to_chat(world, "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!") - -/datum/game_mode/traitor/changeling/can_start() - if(!..()) - return 0 - possible_changelings = get_players_for_role(ROLE_CHANGELING) - if(possible_changelings.len < required_enemies) - return 0 - return 1 - -/datum/game_mode/traitor/changeling/pre_setup() - if(config.protect_roles_from_antagonist) - restricted_jobs += protected_jobs - - if(config.protect_assistant_from_antagonist) - restricted_jobs += "Assistant" - - var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) - - var/num_changelings = 1 - - if(config.changeling_scaling_coeff) - num_changelings = max(1, min( round(num_players()/(config.changeling_scaling_coeff*4))+2, round(num_players()/(config.changeling_scaling_coeff*2)) )) - else - num_changelings = max(1, min(num_players(), changeling_amount/2)) - - if(possible_changelings.len>0) - for(var/j = 0, j < num_changelings, j++) - if(!possible_changelings.len) break - var/datum/mind/changeling = pick(possible_changelings) - antag_candidates -= changeling - possible_changelings -= changeling - changeling.special_role = "Changeling" - changelings += changeling - changeling.restricted_roles = restricted_jobs - return ..() - else - return 0 - -/datum/game_mode/traitor/changeling/post_setup() - modePlayer += changelings - for(var/datum/mind/changeling in changelings) - changeling.current.make_changeling() - forge_changeling_objectives(changeling) - greet_changeling(changeling) - SSticker.mode.update_changeling_icons_added(changeling) - ..() - return - -/datum/game_mode/traitor/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners - var/changelingcap = min( round(GLOB.joined_player_list.len/(config.changeling_scaling_coeff*4))+2, round(GLOB.joined_player_list.len/(config.changeling_scaling_coeff*2)) ) - if(SSticker.mode.changelings.len >= changelingcap) //Caps number of latejoin antagonists - ..() - return - if(SSticker.mode.changelings.len <= (changelingcap - 2) || prob(100 / (config.changeling_scaling_coeff * 4))) - if(ROLE_CHANGELING in character.client.prefs.be_special) - if(!jobban_isbanned(character, ROLE_CHANGELING) && !jobban_isbanned(character, "Syndicate")) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changling() - ..() +/datum/game_mode/traitor/changeling + name = "traitor+changeling" + config_tag = "traitorchan" + false_report_weight = 10 + traitors_possible = 3 //hard limit on traitors if scaling is turned off + restricted_jobs = list("AI", "Cyborg") + required_players = 25 + required_enemies = 1 // how many of each type are required + recommended_enemies = 3 + reroll_friendly = 1 + + var/list/possible_changelings = list() + var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off + +/datum/game_mode/traitor/changeling/announce() + to_chat(world, "The current game mode is - Traitor+Changeling!") + to_chat(world, "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!") + +/datum/game_mode/traitor/changeling/can_start() + if(!..()) + return 0 + possible_changelings = get_players_for_role(ROLE_CHANGELING) + if(possible_changelings.len < required_enemies) + return 0 + return 1 + +/datum/game_mode/traitor/changeling/pre_setup() + if(config.protect_roles_from_antagonist) + restricted_jobs += protected_jobs + + if(config.protect_assistant_from_antagonist) + restricted_jobs += "Assistant" + + var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) + + var/num_changelings = 1 + + if(config.changeling_scaling_coeff) + num_changelings = max(1, min( round(num_players()/(config.changeling_scaling_coeff*4))+2, round(num_players()/(config.changeling_scaling_coeff*2)) )) + else + num_changelings = max(1, min(num_players(), changeling_amount/2)) + + if(possible_changelings.len>0) + for(var/j = 0, j < num_changelings, j++) + if(!possible_changelings.len) break + var/datum/mind/changeling = pick(possible_changelings) + antag_candidates -= changeling + possible_changelings -= changeling + changeling.special_role = "Changeling" + changelings += changeling + changeling.restricted_roles = restricted_jobs + return ..() + else + return 0 + +/datum/game_mode/traitor/changeling/post_setup() + modePlayer += changelings + for(var/datum/mind/changeling in changelings) + changeling.current.make_changeling() + forge_changeling_objectives(changeling) + greet_changeling(changeling) + SSticker.mode.update_changeling_icons_added(changeling) + ..() + return + +/datum/game_mode/traitor/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners + var/changelingcap = min( round(GLOB.joined_player_list.len/(config.changeling_scaling_coeff*4))+2, round(GLOB.joined_player_list.len/(config.changeling_scaling_coeff*2)) ) + if(SSticker.mode.changelings.len >= changelingcap) //Caps number of latejoin antagonists + ..() + return + if(SSticker.mode.changelings.len <= (changelingcap - 2) || prob(100 / (config.changeling_scaling_coeff * 4))) + if(ROLE_CHANGELING in character.client.prefs.be_special) + if(!jobban_isbanned(character, ROLE_CHANGELING) && !jobban_isbanned(character, "Syndicate")) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changling() + ..() + +/datum/game_mode/traitor/changeling/generate_report() + return "The Syndicate has started some experimental research regarding humanoid shapeshifting. There are rumors that this technology will be field tested on a Nanotrasen station \ + for infiltration purposes. Be advised that support personel may also be deployed to defend these shapeshifters. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm index ec387a71e5..0fb720f9d6 100644 --- a/code/game/gamemodes/clock_cult/clock_cult.dm +++ b/code/game/gamemodes/clock_cult/clock_cult.dm @@ -94,6 +94,7 @@ Credit where due: name = "clockwork cult" config_tag = "clockwork_cult" antag_flag = ROLE_SERVANT_OF_RATVAR + false_report_weight = 10 required_players = 24 required_enemies = 3 recommended_enemies = 3 @@ -185,6 +186,13 @@ Credit where due: ..() return 0 //Doesn't end until the round does +/datum/game_mode/clockwork_cult/generate_report() + return "We have lost contact with multiple stations in your sector. They have gone dark and do not respond to all transmissions, although they appear intact and the crew's life \ + signs remain uninterrupted. Those that have managed to send a transmission or have had some of their crew escape tell tales of a machine cult creating sapient automatons and seeking \ + to brainwash the crew to summon their god, Ratvar. If evidence of this cult is dicovered aboard your station, extreme caution and extreme vigilance must be taken going forward, and \ + all resources should be devoted to stopping this cult. Note that holy water seems to weaken and eventually return the minds of cultists that ingest it, and mindshield implants will \ + prevent conversion altogether." + /datum/game_mode/proc/auto_declare_completion_clockwork_cult() var/text = "" if(istype(SSticker.mode, /datum/game_mode/clockwork_cult)) //Possibly hacky? diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm index e16f991af7..6406232596 100644 --- a/code/game/gamemodes/cult/cult.dm +++ b/code/game/gamemodes/cult/cult.dm @@ -31,6 +31,7 @@ name = "cult" config_tag = "cult" antag_flag = ROLE_CULTIST + false_report_weight = 10 restricted_jobs = list("Chaplain","AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel") protected_jobs = list() required_players = 24 @@ -258,6 +259,12 @@ ..() return 1 +/datum/game_mode/cult/generate_report() + return "Some stations in your sector have reported evidence of blood sacrifice and strange magic. Ties to the Wizards' Federation have been proven not to exist, and many employees \ + have disappeared; even Central Command employees light-years away have felt strange presences and at times hysterical compulsions. Interrogations point towards this being the work of \ + the cult of Nar-Sie. If evidence of this cult is discovered aboard your station, extreme caution and extreme vigilance must be taken going forward, and all resources should be \ + devoted to stopping this cult. Note that holy water seems to weaken and eventually return the minds of cultists that ingest it, and mindshield implants will prevent conversion \ + altogether." /datum/game_mode/proc/datum_cult_completion() var/text = "" diff --git a/code/game/gamemodes/devil/devil agent/devil_agent.dm b/code/game/gamemodes/devil/devil agent/devil_agent.dm index 5c48406f23..ec09bfc739 100644 --- a/code/game/gamemodes/devil/devil agent/devil_agent.dm +++ b/code/game/gamemodes/devil/devil agent/devil_agent.dm @@ -41,3 +41,7 @@ devil.objectives += outsellobjective return 1 return 0 + +/datum/game_mode/devil/devil_agents/generate_report() + return "Multiple soul merchants have been spotted in the quadrant, and appear to be competing over who can purchase the most souls. Be advised that they are likely to manufacture \ + emergencies to encourage employees to sell their souls. If anyone sells their soul in error, contact an attorney to overrule the sale." diff --git a/code/game/gamemodes/devil/devil_game_mode.dm b/code/game/gamemodes/devil/devil_game_mode.dm index 41016fa770..755fdef87f 100644 --- a/code/game/gamemodes/devil/devil_game_mode.dm +++ b/code/game/gamemodes/devil/devil_game_mode.dm @@ -2,6 +2,7 @@ name = "devil" config_tag = "devil" antag_flag = ROLE_DEVIL + false_report_weight = 1 protected_jobs = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI") required_players = 0 required_enemies = 1 @@ -54,6 +55,10 @@ ..() return 1 +/datum/game_mode/devil/generate_report() + return "Infernal creatures have been seen nearby offering great boons in exchange for souls. This is considered theft against Nanotrasen, as all employment contracts contain a lien on the \ + employee's soul. If anyone sells their soul in error, contact an attorney to overrule the sale. Be warned that if the devil purchases enough souls, a gateway to hell may open." + /datum/game_mode/devil/proc/post_setup_finalize(datum/mind/devil) add_devil(devil.current, ascendable = TRUE) //Devil gamemode devils are ascendable. add_devil_objectives(devil,2) diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm index fdc3aee2ae..72e8b472ed 100644 --- a/code/game/gamemodes/extended/extended.dm +++ b/code/game/gamemodes/extended/extended.dm @@ -1,6 +1,7 @@ /datum/game_mode/extended name = "secret extended" config_tag = "secret_extended" + false_report_weight = 5 required_players = 0 announce_span = "notice" @@ -12,9 +13,13 @@ /datum/game_mode/extended/post_setup() ..() +/datum/game_mode/extended/generate_report() + return "The transmission mostly failed to mention your sector. It is possible that there is nothing in the Syndicate that could threaten your station during this shift." + /datum/game_mode/extended/announced name = "extended" config_tag = "extended" + false_report_weight = 0 /datum/game_mode/extended/announced/generate_station_goals() for(var/T in subtypesof(/datum/station_goal)) diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 311f2e505a..82350726d9 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -17,6 +17,7 @@ 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/explosion_in_progress = 0 //sit back and relax var/round_ends_with_antag_death = 0 //flags the "one verse the station" antags as such @@ -280,19 +281,19 @@ var/intercepttext = "Central Command Status Summary
" intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \ likely threats to appear in your sector." - var/list/possible_modes = list() - possible_modes.Add("blob", "changeling", "clock_cult", "cult", "extended", "malf", "nuclear", "revolution", "traitor", "wizard") - possible_modes -= name //remove the current gamemode to prevent it from being randomly deleted, it will be readded later + var/list/report_weights = config.mode_false_report_weight.Copy() + report_weights[config_tag] = 0 //Prevent the current mode from being falsely selected. + var/list/reports = list() + for(var/i in 1 to rand(3,5)) //Between three and five wrong entries on the list. + var/false_report_type = pickweightAllowZero(report_weights) + report_weights[false_report_type] = 0 //Make it so the same false report won't be selected twice + reports += config.mode_reports[false_report_type] + reports += config.mode_reports[config_tag] + reports = shuffle(reports) //Randomize the order, so the real one is at a random position. - for(var/i in 1 to 6) //Remove a few modes to leave four - possible_modes -= pick(possible_modes) - - possible_modes |= name //Re-add the actual gamemode - the intercept will thus always have the correct mode in its list - possible_modes = shuffle(possible_modes) //Meta prevention - - var/datum/intercept_text/i_text = new /datum/intercept_text - for(var/V in possible_modes) - intercepttext += i_text.build(V) + for(var/report in reports) + intercepttext += "
" + intercepttext += report if(station_goals.len) intercepttext += "
Special Orders for [station_name()]:" @@ -558,3 +559,6 @@ for(var/V in station_goals) var/datum/station_goal/G = V G.print_result() + +/datum/game_mode/proc/generate_report() //Generates a small text blurb for the gamemode in centcom report + return "Gamemode report for [name] not set. Contact a coder." diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index d652d38d11..b7a6580570 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -1,6 +1,7 @@ /datum/game_mode/meteor name = "meteor" config_tag = "meteor" + false_report_weight = 1 var/meteordelay = 2000 var/nometeors = 0 var/rampupdelta = 5 @@ -53,3 +54,7 @@ SSticker.mode_result = "end - evacuation" ..() return 1 + +/datum/game_mode/meteor/generate_report() + return "[pick("Asteroids have", "Meteors have", "Large rocks have", "Stellar minerals have", "Space hail has", "Debris has")] been detected near your station, and a collision is possible, \ + though unlikely. Be prepared for largescale impacts and destruction. Please note that the debris will prevent the escape shuttle from arriving quickly." diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm index ce0ab22373..7eacd9c666 100644 --- a/code/game/gamemodes/miniantags/abduction/abduction.dm +++ b/code/game/gamemodes/miniantags/abduction/abduction.dm @@ -7,6 +7,7 @@ name = "abduction" config_tag = "abduction" antag_flag = ROLE_ABDUCTOR + false_report_weight = 1 recommended_enemies = 2 required_players = 15 maximum_players = 50 @@ -243,3 +244,7 @@ var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR] hud.leave_hud(alien_mind.current) set_antag_hud(alien_mind.current, null) + +/datum/game_mode/abduction/generate_report() + return "Nearby spaceships report crewmembers having been [pick("kidnapped", "abducted", "captured")] and [pick("tortured", "experimented on", "probed", "implanted")] by mysterious \ + grey humanoids, before being sent back. Be advised that the kidnapped crewmembers behave strangely upon return to duties." diff --git a/code/game/gamemodes/miniantags/monkey/monkey.dm b/code/game/gamemodes/miniantags/monkey/monkey.dm index 03793d9f76..b0d78f57a4 100644 --- a/code/game/gamemodes/miniantags/monkey/monkey.dm +++ b/code/game/gamemodes/miniantags/monkey/monkey.dm @@ -5,6 +5,7 @@ name = "monkey" config_tag = "monkey" antag_flag = ROLE_MONKEY + false_report_weight = 1 required_players = 20 required_enemies = 1 @@ -111,3 +112,7 @@ else SSticker.mode_result = "loss - staff stopped the monkeys" to_chat(world, "The staff managed to contain the monkey infestation!") + +/datum/game_mode/monkey/generate_report() + return "Reports of an ancient [pick("retrovirus", "flesh eating bacteria", "disease", "magical curse blamed on viruses", "bananna blight")] outbreak that turn humans into monkies has been \ + reported in your quadrant. Any such infections may be treated with bananna juice. If an outbreak occurs, ensure the station is quarantined to prevent a largescale outbreak at Centcom." diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm index 1b1bec68c1..d48628ddd1 100644 --- a/code/game/gamemodes/nuclear/nuclear.dm +++ b/code/game/gamemodes/nuclear/nuclear.dm @@ -5,6 +5,7 @@ /datum/game_mode/nuclear name = "nuclear emergency" config_tag = "nuclear" + false_report_weight = 10 required_players = 30 // 30 players - 3 players to be the nuke ops = 27 players remaining required_enemies = 2 recommended_enemies = 5 @@ -273,6 +274,10 @@ ..() return +/datum/game_mode/nuclear/generate_report() + return "One of Central Command's trading routes was recently disrupted by a raid carried out by the Gorlex Marauders. They seemed to only be after one ship - a highly-sensitive \ + transport containing a nuclear fission explosive, although it is useless without the proper code and authorization disk. While the code was likely found in minutes, the only disk that \ + can activate this explosive is on your station. Ensure that it is protected at all times, and remain alert for possible intruders." /datum/game_mode/proc/auto_declare_completion_nuclear() if( syndicates.len || (SSticker && istype(SSticker.mode, /datum/game_mode/nuclear)) ) diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm index 55fc571459..43c1b335f4 100644 --- a/code/game/gamemodes/revolution/revolution.dm +++ b/code/game/gamemodes/revolution/revolution.dm @@ -14,6 +14,7 @@ name = "revolution" config_tag = "revolution" antag_flag = ROLE_REV + false_report_weight = 10 restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer") required_players = 20 required_enemies = 1 @@ -394,3 +395,8 @@ text += printplayer(head, 1) text += "
" to_chat(world, text) + +/datum/game_mode/revolution/generate_report() + return "Employee unrest has spiked in recent weeks, with several attempted mutinies on heads of staff. Some crew have been observed using flashbulb devices to blind their colleagues, \ + who then follow their orders without question and work towards dethroning departmental leaders. Watch for behavior such as this with caution. If the crew attempts a mutiny, you and \ + your heads of staff are fully authorized to execute them using lethal weaponry - they will be later cloned and interrogated at Central Command." diff --git a/code/game/gamemodes/sandbox/sandbox.dm b/code/game/gamemodes/sandbox/sandbox.dm index e69682a987..21169b8236 100644 --- a/code/game/gamemodes/sandbox/sandbox.dm +++ b/code/game/gamemodes/sandbox/sandbox.dm @@ -1,18 +1,21 @@ -/datum/game_mode/sandbox - name = "sandbox" - config_tag = "sandbox" - required_players = 0 - - announce_span = "info" - announce_text = "Build your own station... or just shoot each other!" - - allow_persistence_save = FALSE - -/datum/game_mode/sandbox/pre_setup() - for(var/mob/M in GLOB.player_list) - M.CanBuild() - return 1 - -/datum/game_mode/sandbox/post_setup() - ..() - SSshuttle.registerHostileEnvironment(src) +/datum/game_mode/sandbox + name = "sandbox" + config_tag = "sandbox" + required_players = 0 + + announce_span = "info" + announce_text = "Build your own station... or just shoot each other!" + + allow_persistence_save = FALSE + +/datum/game_mode/sandbox/pre_setup() + for(var/mob/M in GLOB.player_list) + M.CanBuild() + return 1 + +/datum/game_mode/sandbox/post_setup() + ..() + SSshuttle.registerHostileEnvironment(src) + +/datum/game_mode/sandbox/generate_report() + return "Sensors indicate that crewmembers have been all given psychic powers from which they can manifest various objects.

This can only end poorly." diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm index 32d7da4072..858f21bd76 100644 --- a/code/game/gamemodes/traitor/double_agents.dm +++ b/code/game/gamemodes/traitor/double_agents.dm @@ -5,6 +5,7 @@ /datum/game_mode/traitor/internal_affairs name = "Internal Affairs" config_tag = "internal_affairs" + false_report_weight = 10 required_players = 25 required_enemies = 5 recommended_enemies = 8 @@ -75,3 +76,6 @@ late_joining_list -= M +/datum/game_mode/traitor/internal_affairs/generate_report() + return "Nanotrasen denies any accusations of placing internal affairs agents onboard your station to eliminate inconvenient employees. Any further accusations against Centcom for such \ + actions will be met with a conversation with an official internal affairs agent." diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index 3b91816ee9..1b8370068f 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -1,164 +1,167 @@ -/datum/game_mode - var/traitor_name = "traitor" - var/list/datum/mind/traitors = list() - - var/datum/mind/exchange_red - var/datum/mind/exchange_blue - -/datum/game_mode/traitor - name = "traitor" - config_tag = "traitor" - antag_flag = ROLE_TRAITOR - restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") - required_players = 0 - required_enemies = 1 - recommended_enemies = 4 - reroll_friendly = 1 - enemy_minimum_age = 0 - - announce_span = "danger" - announce_text = "There are Syndicate agents on the station!\n\ - Traitors: Accomplish your objectives.\n\ - Crew: Do not let the traitors succeed!" - - var/list/datum/mind/pre_traitors = list() - var/traitors_possible = 4 //hard limit on traitors if scaling is turned off - var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual. - var/antag_datum = ANTAG_DATUM_TRAITOR //what type of antag to create - - -/datum/game_mode/traitor/pre_setup() - - if(config.protect_roles_from_antagonist) - restricted_jobs += protected_jobs - - if(config.protect_assistant_from_antagonist) - restricted_jobs += "Assistant" - - var/num_traitors = 1 - - if(config.traitor_scaling_coeff) - num_traitors = max(1, min( round(num_players()/(config.traitor_scaling_coeff*2))+ 2 + num_modifier, round(num_players()/(config.traitor_scaling_coeff)) + num_modifier )) - else - num_traitors = max(1, min(num_players(), traitors_possible)) - - for(var/j = 0, j < num_traitors, j++) - if (!antag_candidates.len) - break - var/datum/mind/traitor = pick(antag_candidates) - pre_traitors += traitor - traitor.special_role = traitor_name - traitor.restricted_roles = restricted_jobs - log_game("[traitor.key] (ckey) has been selected as a [traitor_name]") - antag_candidates.Remove(traitor) - - - if(pre_traitors.len < required_enemies) - return 0 - return 1 - - -/datum/game_mode/traitor/post_setup() - for(var/datum/mind/traitor in pre_traitors) - spawn(rand(10,100)) - traitor.add_antag_datum(antag_datum) - if(!exchange_blue) - exchange_blue = -1 //Block latejoiners from getting exchange objectives - modePlayer += traitors - ..() - return 1 - -/datum/game_mode/traitor/make_antag_chance(mob/living/carbon/human/character) //Assigns traitor to latejoiners - var/traitorcap = min(round(GLOB.joined_player_list.len / (config.traitor_scaling_coeff * 2)) + 2 + num_modifier, round(GLOB.joined_player_list.len/config.traitor_scaling_coeff) + num_modifier ) +/datum/game_mode + var/traitor_name = "traitor" + var/list/datum/mind/traitors = list() + + var/datum/mind/exchange_red + var/datum/mind/exchange_blue + +/datum/game_mode/traitor + name = "traitor" + config_tag = "traitor" + antag_flag = ROLE_TRAITOR + false_report_weight = 20 //Reports of traitors are pretty common. + restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") + required_players = 0 + required_enemies = 1 + recommended_enemies = 4 + reroll_friendly = 1 + enemy_minimum_age = 0 + + announce_span = "danger" + announce_text = "There are Syndicate agents on the station!\n\ + Traitors: Accomplish your objectives.\n\ + Crew: Do not let the traitors succeed!" + + var/list/datum/mind/pre_traitors = list() + var/traitors_possible = 4 //hard limit on traitors if scaling is turned off + var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual. + var/antag_datum = ANTAG_DATUM_TRAITOR //what type of antag to create + + +/datum/game_mode/traitor/pre_setup() + + if(config.protect_roles_from_antagonist) + restricted_jobs += protected_jobs + + if(config.protect_assistant_from_antagonist) + restricted_jobs += "Assistant" + + var/num_traitors = 1 + + if(config.traitor_scaling_coeff) + num_traitors = max(1, min( round(num_players()/(config.traitor_scaling_coeff*2))+ 2 + num_modifier, round(num_players()/(config.traitor_scaling_coeff)) + num_modifier )) + else + num_traitors = max(1, min(num_players(), traitors_possible)) + + for(var/j = 0, j < num_traitors, j++) + if (!antag_candidates.len) + break + var/datum/mind/traitor = pick(antag_candidates) + pre_traitors += traitor + traitor.special_role = traitor_name + traitor.restricted_roles = restricted_jobs + log_game("[traitor.key] (ckey) has been selected as a [traitor_name]") + antag_candidates.Remove(traitor) + + + if(pre_traitors.len < required_enemies) + return 0 + return 1 + + +/datum/game_mode/traitor/post_setup() + for(var/datum/mind/traitor in pre_traitors) + spawn(rand(10,100)) + traitor.add_antag_datum(antag_datum) + if(!exchange_blue) + exchange_blue = -1 //Block latejoiners from getting exchange objectives + modePlayer += traitors + ..() + return 1 + +/datum/game_mode/traitor/make_antag_chance(mob/living/carbon/human/character) //Assigns traitor to latejoiners + var/traitorcap = min(round(GLOB.joined_player_list.len / (config.traitor_scaling_coeff * 2)) + 2 + num_modifier, round(GLOB.joined_player_list.len/config.traitor_scaling_coeff) + num_modifier ) if((SSticker.mode.traitors.len + pre_traitors.len) >= traitorcap) //Upper cap for number of latejoin antagonists - return + return if((SSticker.mode.traitors.len + pre_traitors.len) <= (traitorcap - 2) || prob(100 / (config.traitor_scaling_coeff * 2))) - if(ROLE_TRAITOR in character.client.prefs.be_special) - if(!jobban_isbanned(character, ROLE_TRAITOR) && !jobban_isbanned(character, "Syndicate")) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - add_latejoin_traitor(character.mind) - -/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) - character.add_antag_datum(antag_datum) - - - -/datum/game_mode/traitor/declare_completion() - ..() - return//Traitors will be checked as part of check_extra_completion. Leaving this here as a reminder. - - -/datum/game_mode/proc/auto_declare_completion_traitor() - if(traitors.len) - var/text = "
The [traitor_name]s were:" - for(var/datum/mind/traitor in traitors) - var/traitorwin = 1 - - text += printplayer(traitor) - - var/TC_uses = 0 - var/uplink_true = 0 - var/purchases = "" - for(var/obj/item/device/uplink/H in GLOB.uplinks) - if(H && H.owner && H.owner == traitor.key) - TC_uses += H.spent_telecrystals - uplink_true = 1 - purchases += H.purchase_log - - var/objectives = "" - if(traitor.objectives.len)//If the traitor had no objectives, don't need to process this. - var/count = 1 - for(var/datum/objective/objective in traitor.objectives) - if(objective.check_completion()) - objectives += "
Objective #[count]: [objective.explanation_text] Success!" - SSblackbox.add_details("traitor_objective","[objective.type]|SUCCESS") - else - objectives += "
Objective #[count]: [objective.explanation_text] Fail." - SSblackbox.add_details("traitor_objective","[objective.type]|FAIL") - traitorwin = 0 - count++ - - if(uplink_true) - text += " (used [TC_uses] TC) [purchases]" - if(TC_uses==0 && traitorwin) - var/static/icon/badass = icon('icons/badass.dmi', "badass") + if(ROLE_TRAITOR in character.client.prefs.be_special) + if(!jobban_isbanned(character, ROLE_TRAITOR) && !jobban_isbanned(character, "Syndicate")) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + add_latejoin_traitor(character.mind) + +/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) + character.add_antag_datum(antag_datum) + + + +/datum/game_mode/traitor/declare_completion() + ..() + return//Traitors will be checked as part of check_extra_completion. Leaving this here as a reminder. + + +/datum/game_mode/proc/auto_declare_completion_traitor() + if(traitors.len) + var/text = "
The [traitor_name]s were:" + for(var/datum/mind/traitor in traitors) + var/traitorwin = 1 + + text += printplayer(traitor) + + var/TC_uses = 0 + var/uplink_true = 0 + var/purchases = "" + for(var/obj/item/device/uplink/H in GLOB.uplinks) + if(H && H.owner && H.owner == traitor.key) + TC_uses += H.spent_telecrystals + uplink_true = 1 + purchases += H.purchase_log + + var/objectives = "" + if(traitor.objectives.len)//If the traitor had no objectives, don't need to process this. + var/count = 1 + for(var/datum/objective/objective in traitor.objectives) + if(objective.check_completion()) + objectives += "
Objective #[count]: [objective.explanation_text] Success!" + SSblackbox.add_details("traitor_objective","[objective.type]|SUCCESS") + else + objectives += "
Objective #[count]: [objective.explanation_text] Fail." + SSblackbox.add_details("traitor_objective","[objective.type]|FAIL") + traitorwin = 0 + count++ + + if(uplink_true) + text += " (used [TC_uses] TC) [purchases]" + if(TC_uses==0 && traitorwin) + var/static/icon/badass = icon('icons/badass.dmi', "badass") text += "[icon2html(badass, world)]" - - text += objectives - - var/special_role_text - if(traitor.special_role) - special_role_text = lowertext(traitor.special_role) - else - special_role_text = "antagonist" - - - if(traitorwin) - text += "
The [special_role_text] was successful!" - SSblackbox.add_details("traitor_success","SUCCESS") - else - text += "
The [special_role_text] has failed!" - SSblackbox.add_details("traitor_success","FAIL") - - text += "
" - - text += "
The code phrases were: [GLOB.syndicate_code_phrase]
\ - The code responses were: [GLOB.syndicate_code_response]
" - to_chat(world, text) - - return 1 - - - - -/datum/game_mode/proc/update_traitor_icons_added(datum/mind/traitor_mind) - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.join_hud(traitor_mind.current) - set_antag_hud(traitor_mind.current, "traitor") - -/datum/game_mode/proc/update_traitor_icons_removed(datum/mind/traitor_mind) - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.leave_hud(traitor_mind.current) - set_antag_hud(traitor_mind.current, null) + + text += objectives + + var/special_role_text + if(traitor.special_role) + special_role_text = lowertext(traitor.special_role) + else + special_role_text = "antagonist" + + + if(traitorwin) + text += "
The [special_role_text] was successful!" + SSblackbox.add_details("traitor_success","SUCCESS") + else + text += "
The [special_role_text] has failed!" + SSblackbox.add_details("traitor_success","FAIL") + + text += "
" + + text += "
The code phrases were: [GLOB.syndicate_code_phrase]
\ + The code responses were: [GLOB.syndicate_code_response]
" + to_chat(world, text) + + return 1 + +/datum/game_mode/traitor/generate_report() + return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \ + Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions." + + +/datum/game_mode/proc/update_traitor_icons_added(datum/mind/traitor_mind) + var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] + traitorhud.join_hud(traitor_mind.current) + set_antag_hud(traitor_mind.current, "traitor") + +/datum/game_mode/proc/update_traitor_icons_removed(datum/mind/traitor_mind) + var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] + traitorhud.leave_hud(traitor_mind.current) + set_antag_hud(traitor_mind.current, null) diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index b342ab4009..eea7afcb4b 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -6,6 +6,7 @@ name = "wizard" config_tag = "wizard" antag_flag = ROLE_WIZARD + false_report_weight = 10 required_players = 20 required_enemies = 1 recommended_enemies = 1 @@ -46,6 +47,10 @@ ..() return +/datum/game_mode/wizard/generate_report() + return "A dangerous Wizards' Federation individual by the name of [pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)] has recently escaped confinement from an unlisted prison facility. This \ + man is a dangerous mutant with the ability to alter himself and the world around him by what he and his leaders believe to be magic. If this man attempts an attack on your station, \ + his execution is highly encouraged, as is the preservation of his body for later study." /datum/game_mode/proc/forge_wizard_objectives(datum/mind/wizard) switch(rand(1,100)) diff --git a/tgstation.dme b/tgstation.dme index 105c1f47a0..f799570e1f 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -404,7 +404,6 @@ #include "code\game\gamemodes\events.dm" #include "code\game\gamemodes\factions.dm" #include "code\game\gamemodes\game_mode.dm" -#include "code\game\gamemodes\intercept_report.dm" #include "code\game\gamemodes\objective.dm" #include "code\game\gamemodes\objective_items.dm" #include "code\game\gamemodes\blob\blob.dm"