From ebac066b2f003c0e37961db1a5226384cf910c4e Mon Sep 17 00:00:00 2001 From: Putnam Date: Tue, 25 Feb 2020 16:10:23 -0800 Subject: [PATCH] lets add traitor classes --- code/datums/mind.dm | 14 +- .../modules/antagonists/traitor/classes/ai.dm | 70 ++++++++++ .../antagonists/traitor/classes/gorlex.dm | 25 ++++ .../antagonists/traitor/classes/human.dm | 131 ++++++++++++++++++ .../traitor/classes/traitor_class.dm | 35 +++++ .../antagonists/traitor/datum_traitor.dm | 90 +++++------- strings/flavor_objectives/ninja_helping.txt | 1 - strings/flavor_objectives/traitor.txt | 1 - 8 files changed, 306 insertions(+), 61 deletions(-) create mode 100644 code/modules/antagonists/traitor/classes/ai.dm create mode 100644 code/modules/antagonists/traitor/classes/gorlex.dm create mode 100644 code/modules/antagonists/traitor/classes/human.dm create mode 100644 code/modules/antagonists/traitor/classes/traitor_class.dm diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 61d467a20d..5cde9c5115 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -257,9 +257,11 @@ remove_rev() SSticker.mode.update_cult_icons_removed(src) -/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner) +/datum/mind/proc/equip_traitor(traitor_class, silent = FALSE, datum/antagonist/uplink_owner) if(!current) return + if(!traitor_class) + traitor_class = GLOB.traitor_classes[BASIC_TRAITOR] var/mob/living/carbon/human/traitor_mob = current if (!istype(traitor_mob)) return @@ -307,21 +309,21 @@ if (!uplink_loc) if(!silent) - to_chat(traitor_mob, "Unfortunately, [employer] wasn't able to get you an Uplink.") + to_chat(traitor_mob, "Unfortunately, [traitor_class.employer] wasn't able to get you an Uplink.") . = 0 else . = uplink_loc - var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key) + var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key,traitor_class) if(!U) CRASH("Uplink creation failed.") U.setup_unlock_code() if(!silent) if(uplink_loc == R) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.") + to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.") else if(uplink_loc == PDA) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.") + to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.") else if(uplink_loc == P) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [U.unlock_code] from its starting position to unlock its hidden features.") + to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [U.unlock_code] from its starting position to unlock its hidden features.") if(uplink_owner) uplink_owner.antag_memory += U.unlock_note + "
" diff --git a/code/modules/antagonists/traitor/classes/ai.dm b/code/modules/antagonists/traitor/classes/ai.dm new file mode 100644 index 0000000000..60d7dcdeba --- /dev/null +++ b/code/modules/antagonists/traitor/classes/ai.dm @@ -0,0 +1,70 @@ +#define TRAITOR_AI /datum/traitor_class/ai + +/datum/traitor_class/ai // this one is special, so has no weight + name = "Malfunctioning AI" + +/datum/traitor_class/ai/forge_objectives(/datum/antagonist/traitor/T) + var/objective_count = 0 + + if(prob(30)) + objective_count += forge_single_objective() + + for(var/i = objective_count, i < CONFIG_GET(number/traitor_objectives_amount), i++) + var/datum/objective/assassinate/kill_objective = new + kill_objective.owner = owner + kill_objective.find_target() + T.add_objective(kill_objective) + + var/datum/objective/survive/exist/exist_objective = new + exist_objective.owner = owner + T.add_objective(exist_objective) + +/datum/traitor_class/ai/forge_single_objective(/datum/antagonist/traitor/T) + .=1 + var/special_pick = rand(1,4) + switch(special_pick) + if(1) + var/datum/objective/block/block_objective = new + block_objective.owner = owner + T.add_objective(block_objective) + if(2) + var/datum/objective/purge/purge_objective = new + purge_objective.owner = owner + T.add_objective(purge_objective) + if(3) + var/datum/objective/robot_army/robot_objective = new + robot_objective.owner = owner + T.add_objective(robot_objective) + if(4) //Protect and strand a target + var/datum/objective/protect/yandere_one = new + yandere_one.owner = owner + T.add_objective(yandere_one) + yandere_one.find_target() + var/datum/objective/maroon/yandere_two = new + yandere_two.owner = owner + yandere_two.target = yandere_one.target + yandere_two.update_explanation_text() // normally called in find_target() + T.add_objective(yandere_two) + .=2 + +/datum/traitor_class/ai/on_removal(/datum/antagonist/traitor/T) + var/mob/living/silicon/ai/A = T.owner.current + A.set_zeroth_law("") + A.verbs -= /mob/living/silicon/ai/proc/choose_modules + A.malf_picker.remove_malf_verbs(A) + qdel(A.malf_picker) + + +/datum/traitor_class/ai/apply_innate_effects(mob/living/M) + var/mob/living/silicon/ai/A = M + A.hack_software = TRUE + +/datum/traitor_class/ai/remove_innate_effects(mob/living/M) + var/mob/living/silicon/ai/A = M + A.hack_software = FALSE + +/datum/traitor_class/ai/finalize_objectives(/datum/antagonist/traitor/T) + T.add_law_zero() + T.owner.current.playsound_local(get_turf(T.owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE) + T.owner.current.grant_language(/datum/language/codespeak) + return FALSE diff --git a/code/modules/antagonists/traitor/classes/gorlex.dm b/code/modules/antagonists/traitor/classes/gorlex.dm new file mode 100644 index 0000000000..1089d7ef50 --- /dev/null +++ b/code/modules/antagonists/traitor/classes/gorlex.dm @@ -0,0 +1,25 @@ +/datum/traitor_class/human/gorlex + name = "Gorlex Marauders" + employer = "Gorlex Marauders" + weight = 2 + chaos = 20 + TC = 30 + +/datum/traitor_class/proc/forge_objectives(/datum/antagonist/traitor/T) + // Like the old forge_human_objectives. Makes all the objectives for this traitor class. + +/datum/traitor_class/proc/forge_single_objective(/datum/antagonist/traitor/T) + // As forge_single_objective. + +/datum/traitor_class/proc/on_removal(/datum/antagonist/traitor/T) + // What this does to the antag datum on removal. Called before proper removal, obviously. + +/datum/traitor_class/proc/apply_innate_effects(mob/living/M) + // What innate effects it should have. See: AI. + +/datum/traitor_class/proc/remove_innate_effects(mob/living/M) + // Cleaning up the innate effects. + +/datum/traitor_class/proc/finalize_traitor(/datum/antagonist/traitor/T) + // Finalization. Return TRUE if should play standard traitor sound/equip, return FALSE if both are special case + return TRUE diff --git a/code/modules/antagonists/traitor/classes/human.dm b/code/modules/antagonists/traitor/classes/human.dm new file mode 100644 index 0000000000..1f7b64d4f5 --- /dev/null +++ b/code/modules/antagonists/traitor/classes/human.dm @@ -0,0 +1,131 @@ +#define BASIC_TRAITOR /datum/traitor_class/human + +/datum/traitor_class/human + name = "Traitor" + chaos = 1 + +/datum/traitor_class/human/forge_objectives(/datum/antagonist/traitor/T) + var/is_hijacker = FALSE + var/datum/game_mode/dynamic/mode + var/is_dynamic = FALSE + var/hijack_prob = 0 + if(istype(SSticker.mode,/datum/game_mode/dynamic)) + mode = SSticker.mode + is_dynamic = TRUE + if(mode.threat >= CONFIG_GET(number/dynamic_hijack_cost)) + hijack_prob = CLAMP(mode.threat_level-50,0,20) + if(GLOB.joined_player_list.len>=GLOB.dynamic_high_pop_limit) + is_hijacker = (prob(hijack_prob) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement)) + else + var/indice_pop = min(10,round(GLOB.joined_player_list.len/mode.pop_per_requirement)+1) + is_hijacker = (prob(hijack_prob) && (mode.threat_level >= CONFIG_GET(number_list/dynamic_hijack_requirements)[indice_pop])) + if(mode.storyteller.flags & NO_ASSASSIN) + is_hijacker = FALSE + else if (GLOB.joined_player_list.len >= 30) // Less murderboning on lowpop thanks + hijack_prob = 10 + is_hijacker = prob(10) + var/martyr_chance = prob(hijack_prob*2) + var/objective_count = is_hijacker //Hijacking counts towards number of objectives + if(!SSticker.mode.exchange_blue && SSticker.mode.traitors.len >= 8) //Set up an exchange if there are enough traitors + if(!SSticker.mode.exchange_red) + SSticker.mode.exchange_red = T.owner + else + SSticker.mode.exchange_blue = T.owner + assign_exchange_role(SSticker.mode.exchange_red) + assign_exchange_role(SSticker.mode.exchange_blue) + objective_count += 1 //Exchange counts towards number of objectives + var/toa = CONFIG_GET(number/traitor_objectives_amount) + for(var/i = objective_count, i < toa, i++) + forge_single_objective(T) + + if(is_hijacker && objective_count <= toa) //Don't assign hijack if it would exceed the number of objectives set in config.traitor_objectives_amount + if (!(locate(/datum/objective/hijack) in objectives)) + var/datum/objective/hijack/hijack_objective = new + hijack_objective.owner = T.owner + T.add_objective(hijack_objective) + if(is_dynamic) + var/threat_spent = CONFIG_GET(number/dynamic_hijack_cost) + mode.spend_threat(threat_spent) + mode.log_threat("[owner.name] spent [threat_spent] on hijack.") + return + + + var/martyr_compatibility = 1 //You can't succeed in stealing if you're dead. + for(var/datum/objective/O in objectives) + if(!O.martyr_compatible) + martyr_compatibility = 0 + break + + if(martyr_compatibility && martyr_chance) + var/datum/objective/martyr/martyr_objective = new + martyr_objective.owner = T.owner + T.add_objective(martyr_objective) + if(is_dynamic) + var/threat_spent = CONFIG_GET(number/dynamic_hijack_cost) + mode.spend_threat(threat_spent) + mode.log_threat("[owner.name] spent [threat_spent] on glorious death.") + return + + else + if(!(locate(/datum/objective/escape) in objectives)) + var/datum/objective/escape/escape_objective = new + escape_objective.owner = owner + T.add_objective(escape_objective) + return + +/datum/traitor_class/human/forge_single_objective(/datum/antagonist/traitor/T) + .=1 + var/assassin_prob = 50 + var/is_dynamic = FALSE + var/datum/game_mode/dynamic/mode + if(istype(SSticker.mode,/datum/game_mode/dynamic)) + mode = SSticker.mode + is_dynamic = TRUE + assassin_prob = max(0,mode.threat_level-20) + if(prob(assassin_prob)) + if(is_dynamic) + var/threat_spent = CONFIG_GET(number/dynamic_assassinate_cost) + mode.spend_threat(threat_spent) + mode.log_threat("[T.owner.name] spent [threat_spent] on an assassination target.") + 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 = T.owner + destroy_objective.find_target() + T.add_objective(destroy_objective) + else if(prob(30) || (is_dynamic && (mode.storyteller.flags & NO_ASSASSIN))) + var/datum/objective/maroon/maroon_objective = new + maroon_objective.owner = T.owner + maroon_objective.find_target() + T.add_objective(maroon_objective) + else if(prob(max(0,assassin_prob-20))) + var/datum/objective/assassinate/kill_objective = new + kill_objective.owner = T.owner + kill_objective.find_target() + T.add_objective(kill_objective) + else + var/datum/objective/assassinate/once/kill_objective = new + kill_objective.owner = T.owner + kill_objective.find_target() + T.add_objective(kill_objective) + else + if(prob(15) && !(locate(/datum/objective/download) in objectives) && !(owner.assigned_role in list("Research Director", "Scientist", "Roboticist"))) + var/datum/objective/download/download_objective = new + download_objective.owner = T.owner + download_objective.gen_amount_goal() + T.add_objective(download_objective) + else if(prob(40)) // cum. not counting download: 40%. + var/datum/objective/steal/steal_objective = new + steal_objective.owner = T.owner + steal_objective.find_target() + T.add_objective(steal_objective) + else if(prob(100/3)) // cum. not counting download: 20%. + var/datum/objective/sabotage/sabotage_objective = new + sabotage_objective.owner = T.owner + sabotage_objective.find_target() + T.add_objective(sabotage_objective) + else // cum. not counting download: 40% + var/datum/objective/flavor/traitor/flavor_objective = new + flavor_objective.owner = T.owner + flavor_objective.forge_objective() + T.add_objective(flavor_objective) diff --git a/code/modules/antagonists/traitor/classes/traitor_class.dm b/code/modules/antagonists/traitor/classes/traitor_class.dm new file mode 100644 index 0000000000..8107fa5af9 --- /dev/null +++ b/code/modules/antagonists/traitor/classes/traitor_class.dm @@ -0,0 +1,35 @@ +GLOBAL_LIST_EMPTY(traitor_classes) + +/datum/traitor_class + var/name = "Bad Coders Ltd." + var/employer = "The Syndicate" + var/weight = 0 + var/chaos = 0 + var/TC = 20 + +/datum/traitor_class/New() + ..() + if(src.type in traitor_classes) + qdel(src) + else + traitor_classes += src.type + traitor_classes[src.type] = src + +/datum/traitor_class/proc/forge_objectives(/datum/antagonist/traitor/T) + // Like the old forge_human_objectives. Makes all the objectives for this traitor class. + +/datum/traitor_class/proc/forge_single_objective(/datum/antagonist/traitor/T) + // As forge_single_objective. + +/datum/traitor_class/proc/on_removal(/datum/antagonist/traitor/T) + // What this does to the antag datum on removal. Called before proper removal, obviously. + +/datum/traitor_class/proc/apply_innate_effects(mob/living/M) + // What innate effects it should have. See: AI. + +/datum/traitor_class/proc/remove_innate_effects(mob/living/M) + // Cleaning up the innate effects. + +/datum/traitor_class/proc/finalize_traitor(/datum/antagonist/traitor/T) + // Finalization. Return TRUE if should play standard traitor sound/equip, return FALSE if both are special case + return TRUE diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 1c7ee48f71..da35d1ffdc 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -1,5 +1,4 @@ #define TRAITOR_HUMAN "human" -#define TRAITOR_AI "AI" /datum/antagonist/traitor name = "Traitor" @@ -12,21 +11,42 @@ var/give_objectives = TRUE var/should_give_codewords = TRUE var/should_equip = TRUE - var/traitor_kind = TRAITOR_HUMAN //Set on initial assignment + var/datum/traitor_class/traitor_kind = TRAITOR_HUMAN //Set on initial assignment can_hijack = HIJACK_HIJACKER +/datum/antagonist/traitor/New() + ..() + if(!GLOB.traitor_classes.len)//Only need to fill the list when it's needed. + for(var/I in subtypesof(/datum/traitor_class)) + new I + +/datum/antagonist/traitor/proc/set_traitor_kind(var/kind) + traitor_kind = traitor_classes[kind] + /datum/antagonist/traitor/on_gain() if(owner.current && isAI(owner.current)) - traitor_kind = TRAITOR_AI - + set_traitor_kind(TRAITOR_AI) + else + var/chaos_weight = 0 + if(istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + chaos_weight = (mode.threat - 50)/50 + var/list/weights = list() + for(var/C in traitor_classes) + var/datum/traitor_class/class = C + var/weight = class.weight/(1+NUM_E**(-chaos_weight*class.chaos)) // just a logistic function + weights[C] = weight + set_traitor_kind(pickweightAllowZero(weights)) + traitor_kind.weight /= 2 // less likely this round SSticker.mode.traitors += owner owner.special_role = special_role if(give_objectives) - forge_traitor_objectives() - finalize_traitor() + traitor_kind.forge_objectives(src) + traitor_kind.finalize_traitor(src) ..() /datum/antagonist/traitor/apply_innate_effects() + traitor_kind.apply_innate_effects(src) if(owner.assigned_role == "Clown") var/mob/living/carbon/human/traitor_mob = owner.current if(traitor_mob && istype(traitor_mob)) @@ -35,6 +55,7 @@ traitor_mob.dna.remove_mutation(CLOWNMUT) /datum/antagonist/traitor/remove_innate_effects() + traitor_kind.remove_innate_effects(src) if(owner.assigned_role == "Clown") var/mob/living/carbon/human/traitor_mob = owner.current if(traitor_mob && istype(traitor_mob)) @@ -42,12 +63,7 @@ /datum/antagonist/traitor/on_removal() //Remove malf powers. - if(traitor_kind == TRAITOR_AI && owner.current && isAI(owner.current)) - var/mob/living/silicon/ai/A = owner.current - A.set_zeroth_law("") - A.verbs -= /mob/living/silicon/ai/proc/choose_modules - A.malf_picker.remove_malf_verbs(A) - qdel(A.malf_picker) + traitor_kind.on_removal(src) SSticker.mode.traitors -= owner if(!silent && owner.current) to_chat(owner.current," You are no longer the [special_role]! ") @@ -67,6 +83,7 @@ objectives -= O /datum/antagonist/traitor/proc/forge_traitor_objectives() + traitor_kind.forge_objectives(src) switch(traitor_kind) if(TRAITOR_AI) forge_ai_objectives() @@ -142,30 +159,6 @@ add_objective(escape_objective) return -/datum/antagonist/traitor/proc/forge_ai_objectives() - var/objective_count = 0 - - if(prob(30)) - objective_count += forge_single_objective() - - for(var/i = objective_count, i < CONFIG_GET(number/traitor_objectives_amount), i++) - var/datum/objective/assassinate/kill_objective = new - kill_objective.owner = owner - kill_objective.find_target() - add_objective(kill_objective) - - var/datum/objective/survive/exist/exist_objective = new - exist_objective.owner = owner - add_objective(exist_objective) - - -/datum/antagonist/traitor/proc/forge_single_objective() - switch(traitor_kind) - if(TRAITOR_AI) - return forge_single_AI_objective() - else - return forge_single_human_objective() - /datum/antagonist/traitor/proc/forge_single_human_objective() //Returns how many objectives are added .=1 var/assassin_prob = 50 @@ -268,32 +261,24 @@ set_antag_hud(owner.current, null) /datum/antagonist/traitor/proc/finalize_traitor() - switch(traitor_kind) - if(TRAITOR_AI) - add_law_zero() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE) - owner.current.grant_language(/datum/language/codespeak) - if(TRAITOR_HUMAN) - if(should_equip) - equip(silent) - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE) + var/should_base_finalize = traitor_kind.finalize_traitor(src) + if(should_base_finalize) + if(should_equip) + equip(silent) + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE) /datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override) . = ..() update_traitor_icons_added() var/mob/M = mob_override || owner.current - if(isAI(M) && traitor_kind == TRAITOR_AI) - var/mob/living/silicon/ai/A = M - A.hack_software = TRUE + traitor_kind.apply_innate_effects(M) RegisterSignal(M, COMSIG_MOVABLE_HEAR, .proc/handle_hearing) /datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override) . = ..() update_traitor_icons_removed() var/mob/M = mob_override || owner.current - if(isAI(M) && traitor_kind == TRAITOR_AI) - var/mob/living/silicon/ai/A = M - A.hack_software = FALSE + traitor_kind.remove_innate_effects(M) UnregisterSignal(M, COMSIG_MOVABLE_HEAR) /datum/antagonist/traitor/proc/give_codewords() @@ -324,8 +309,7 @@ killer.add_malf_picker() /datum/antagonist/traitor/proc/equip(var/silent = FALSE) - if(traitor_kind == TRAITOR_HUMAN) - owner.equip_traitor(employer, silent, src) + owner.equip_traitor(traitor_kind, silent, src) /datum/antagonist/traitor/proc/assign_exchange_role() //set faction diff --git a/strings/flavor_objectives/ninja_helping.txt b/strings/flavor_objectives/ninja_helping.txt index 1280939b5c..4cca9a2234 100644 --- a/strings/flavor_objectives/ninja_helping.txt +++ b/strings/flavor_objectives/ninja_helping.txt @@ -1,6 +1,5 @@ Nanotrasen want to make sure that their employees are on the up-and-up. Try to find any blackmail you can. Increase productivity however you can. -You are a security ninja. Answer to the Head of Security, and follow space law. You are a cargo ninja. Answer to the Quartermaster, and do what they say. Nanotrasen want you to ensure maximum morale. Protect the members of the crew who are on break. Ensure that all the paperwork is being done. \ No newline at end of file diff --git a/strings/flavor_objectives/traitor.txt b/strings/flavor_objectives/traitor.txt index 6d54c8ed9d..1a08b6cac1 100644 --- a/strings/flavor_objectives/traitor.txt +++ b/strings/flavor_objectives/traitor.txt @@ -2,7 +2,6 @@ The Gorlex Marauders want you to teach the heads of staff a lesson they will nev Show Nanotrasen the utility of a 40% oxygen atmosphere. Waffle Co. wants you To cause as much humorous terrorism against Nanotrasen as possible! How? We don’t care as long as it’s entertaining! Be as creative and exciting as possible when carrying out your dirty deeds. Have fun! Kill one of the station's beloved pets. Make a show of it, though you don't have to reveal yourself. -The Tiger Cooperative want you to get their illegal technology spread through the station. The Animal Rights Consortium needs you to save the innocent non-humanoid creatures aboard Citadel Station by any means necessary. Use your best judgement to decide whether an animal or xenobiological is abused, but if they are, ensure the abuser is punished. Avoid killing too many people if possible, and if you do harm any creatures, you will be terminated upon extraction. Donk Co. wants ransom money, and you are going to get it. Your goal is to kidnap and crewmember you can get your hands on and hold them hostage until you get something of significant value. Try to work out the best deal you can. Remember that Higher Value Targets are generally going to get a better deal so try to prioritize heads of staff if possible. We do not approve of mindless killing of Nanotrasen employees, so don’t do it. The Gorlex Marauders want you to steal as many shoes as possible. Lay broken glass everywhere. \ No newline at end of file