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