From 57777bf9df7ad721c7065343acf9a87c2fb7a013 Mon Sep 17 00:00:00 2001 From: Mothblocks <35135081+Mothblocks@users.noreply.github.com> Date: Sun, 26 Nov 2023 18:22:42 -0800 Subject: [PATCH] Blood brothers is now a single person conversion antagonist --- .../signals/signals_mob/signals_mob_main.dm | 1 + .../components/can_flash_from_behind.dm | 22 +++ code/datums/memory/general_memories.dm | 12 ++ .../dynamic/dynamic_rulesets_roundstart.dm | 47 +++---- code/modules/antagonists/brother/brother.dm | 132 +++++++++++------- code/modules/antagonists/cult/cult.dm | 15 +- .../antagonists/revolution/revolution.dm | 41 ++---- code/modules/mob/living/carbon/inventory.dm | 18 +++ tgstation.dme | 1 + .../tgui/interfaces/AntagInfoBrother.tsx | 4 +- 10 files changed, 173 insertions(+), 120 deletions(-) create mode 100644 code/datums/components/can_flash_from_behind.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index b564a114b02cca..0ef458cece9580 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -212,6 +212,7 @@ /// Return to stop the flash entirely #define STOP_FLASH (1<<3) /// from /obj/item/assembly/flash/flash_carbon, to the mob flashing another carbon +/// (mob/living/carbon/flashed, obj/item/assembly/flash/flash, deviation (from code/__DEFINES/mobs.dm)) #define COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON "mob_success_flashed_carbon" /// from mob/get_status_tab_items(): (list/items) diff --git a/code/datums/components/can_flash_from_behind.dm b/code/datums/components/can_flash_from_behind.dm new file mode 100644 index 00000000000000..c443d160dfbd30 --- /dev/null +++ b/code/datums/components/can_flash_from_behind.dm @@ -0,0 +1,22 @@ +/// This mob can flash others from behind and still get at least a partial +// Component and not element because elements can't stack. +// I don't want to have a bunch of helpers for that. We need to do this generally +// because this keeps coming up. +/datum/component/can_flash_from_behind + dupe_mode = COMPONENT_DUPE_SOURCES + +/datum/component/can_flash_from_behind/Initialize() + if (!ismob(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/can_flash_from_behind/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_PRE_FLASHED_CARBON, PROC_REF(on_pre_flashed_carbon)) + +/datum/component/can_flash_from_behind/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_MOB_PRE_FLASHED_CARBON) + +/datum/component/can_flash_from_behind/proc/on_pre_flashed_carbon(source, flashed, flash, deviation) + SIGNAL_HANDLER + + // Always partial flash at the very least + return (deviation == DEVIATION_FULL) ? DEVIATION_OVERRIDE_PARTIAL : NONE diff --git a/code/datums/memory/general_memories.dm b/code/datums/memory/general_memories.dm index 3e5eb05cf40269..c71014b63b46dc 100644 --- a/code/datums/memory/general_memories.dm +++ b/code/datums/memory/general_memories.dm @@ -915,6 +915,18 @@ "[antagonist_name] lifts an odd device to [protagonist_name]'s eyes and flashes him, imprinting murderous instructions.", ) +/// Who converted into a blood brother +/datum/memory/recruited_by_blood_brother + +/datum/memory/recruited_by_blood_brother/get_names() + return list("[protagonist_name] is converted into a blood brother by [antagonist_name]") + +/datum/memory/recruited_by_blood_brother/get_starts() + return list( + "[antagonist_name] acts just a bit too friendly with [protagonist_name], moments away from converting them into a blood brother.", + "[protagonist_name] is brought into [antagonist_name]'s life of crime and espionage.", + ) + /// Saw someone play Russian Roulette. /datum/memory/witnessed_gods_wrath memory_flags = MEMORY_CHECK_BLINDNESS|MEMORY_SKIP_UNCONSCIOUS diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index a90eba738c146c..767087467233c8 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -112,45 +112,34 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE) JOB_AI, JOB_CYBORG, ) - required_candidates = 2 - weight = 2 - cost = 12 + weight = 5 + cost = 8 scaling_cost = 15 requirements = list(40,30,30,20,20,15,15,15,10,10) - antag_cap = 2 // Can pick 3 per team, but rare enough it doesn't matter. - var/list/datum/team/brother_team/pre_brother_teams = list() - var/const/min_team_size = 2 - -/datum/dynamic_ruleset/roundstart/traitorbro/forget_startup() - pre_brother_teams = list() - return ..() + antag_cap = 1 /datum/dynamic_ruleset/roundstart/traitorbro/pre_execute(population) . = ..() - var/num_teams = (get_antag_cap(population)/min_team_size) * (scaled_times + 1) // 1 team per scaling - for(var/j = 1 to num_teams) - if(candidates.len < min_team_size || candidates.len < required_candidates) + + for (var/_ in 1 to get_antag_cap(population) * (scaled_times + 1)) + var/mob/candidate = pick_n_take(candidates) + if (isnull(candidate)) break - var/datum/team/brother_team/team = new - var/team_size = prob(10) ? min(3, candidates.len) : 2 - for(var/k = 1 to team_size) - var/mob/bro = pick_n_take(candidates) - assigned += bro.mind - team.add_member(bro.mind) - bro.mind.special_role = "brother" - bro.mind.restricted_roles = restricted_roles - GLOB.pre_setup_antags += bro.mind - pre_brother_teams += team + + assigned += candidate.mind + candidate.mind.restricted_roles = restricted_roles + GLOB.pre_setup_antags += candidate.mind + return TRUE /datum/dynamic_ruleset/roundstart/traitorbro/execute() - for(var/datum/team/brother_team/team in pre_brother_teams) - team.pick_meeting_area() + for (var/datum/mind/mind in assigned) + var/datum/team/brother_team/team = new + team.add_member(mind) team.forge_brother_objectives() - for(var/datum/mind/M in team.members) - M.add_antag_datum(/datum/antagonist/brother, team) - GLOB.pre_setup_antags -= M - team.update_name() + mind.add_antag_datum(/datum/antagonist/brother, team) + GLOB.pre_setup_antags -= mind + return TRUE ////////////////////////////////////////////// diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 67fffb4fa19fa2..baf6b30f6b178f 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -7,9 +7,10 @@ hijack_speed = 0.5 ui_name = "AntagInfoBrother" suicide_cry = "FOR MY BROTHER!!" - var/datum/team/brother_team/team antag_moodlet = /datum/mood_event/focused hardcore_random_bonus = TRUE + VAR_PRIVATE + datum/team/brother_team/team /datum/antagonist/brother/create_team(datum/team/brother_team/new_team) if(!new_team) @@ -25,12 +26,64 @@ objectives += team.objectives owner.special_role = special_role finalize_brother() + + var/is_first_brother = team.members.len == 1 + team.brothers_left -= 1 + + if (is_first_brother || team.brothers_left > 0) + var/mob/living/carbon/carbon_owner = owner.current + if (istype(carbon_owner)) + carbon_owner.equip_conspicuous_item(new /obj/item/assembly/flash) + carbon_owner.AddComponentFrom(REF(src), /datum/component/can_flash_from_behind) + RegisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON, PROC_REF(on_mob_successful_flashed_carbon)) + + if (!is_first_brother) + to_chat(carbon_owner, span_boldwarning("The Syndicate have higher expectations from you than others. They have granted you an extra flash to convert one other person.")) + return ..() /datum/antagonist/brother/on_removal() owner.special_role = null + owner.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind) + UnregisterSignal(owner, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON) + return ..() +/datum/antagonist/brother/proc/on_mob_successful_flashed_carbon(mob/living/source, mob/living/carbon/flashed, obj/item/assembly/flash/flash) + SIGNAL_HANDLER + + if (flashed.stat == DEAD) + return + + if (flashed.stat != CONSCIOUS) + flashed.balloon_alert(source, "unconscious!") + return + + if (isnull(flashed.mind) || !GET_CLIENT(flashed)) + flashed.balloon_alert(source, "[flashed.p_their()] mind is vacant!") + return + + if (flashed.mind.has_antag_datum(/datum/antagonist/brother)) + flashed.balloon_alert(source, "[flashed.p_theyre()] loyal to someone else!") + return + + if (HAS_TRAIT(flashed, TRAIT_MINDSHIELD) || flashed.mind.assigned_role?.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) + flashed.balloon_alert(source, "[flashed.p_they()] resist!") + return + + flashed.mind.add_antag_datum(/datum/antagonist/brother, team) + + flashed.balloon_alert(source, "converted") + flash.burn_out() + flashed.mind.add_memory( \ + /datum/memory/recruited_by_blood_brother, \ + protagonist = flashed, \ + antagonist = owner.current, \ + ) + + UnregisterSignal(source, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON) + source.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind) + /datum/antagonist/brother/antag_panel_data() return "Conspirators : [get_brother_names()]" @@ -74,74 +127,36 @@ brother_text += ", " return brother_text -/datum/antagonist/brother/proc/give_meeting_area() - if(!owner.current || !team || !team.meeting_area) - return - to_chat(owner.current, "Your designated meeting area: [team.meeting_area]") - antag_memory += "Meeting Area: [team.meeting_area]
" - /datum/antagonist/brother/greet() - var/brother_text = get_brother_names() - to_chat(owner.current, span_alertsyndie("You are the [owner.special_role] of [brother_text].")) - to_chat(owner.current, "The Syndicate only accepts those that have proven themselves. Prove yourself and prove your [team.member_name]s by completing your objectives together!") + to_chat(owner.current, span_alertsyndie("You are the [owner.special_role].")) owner.announce_objectives() - give_meeting_area() /datum/antagonist/brother/proc/finalize_brother() owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) team.update_name() /datum/antagonist/brother/admin_add(datum/mind/new_owner,mob/admin) - //show list of possible brothers - var/list/candidates = list() - for(var/mob/living/L in GLOB.alive_mob_list) - if(!L.mind || L.mind == new_owner || !can_be_owned(L.mind)) - continue - candidates[L.mind.name] = L.mind - - sortTim(candidates, GLOBAL_PROC_REF(cmp_text_asc)) - var/choice = tgui_input_list(admin, "Choose the blood brother.", "Brother", candidates) - if(!choice) - return - var/datum/mind/bro = candidates[choice] - var/datum/team/brother_team/T = new - T.add_member(new_owner) - T.add_member(bro) - T.pick_meeting_area() - T.forge_brother_objectives() - new_owner.add_antag_datum(/datum/antagonist/brother,T) - bro.add_antag_datum(/datum/antagonist/brother, T) - message_admins("[key_name_admin(admin)] made [key_name_admin(new_owner)] and [key_name_admin(bro)] into blood brothers.") - log_admin("[key_name(admin)] made [key_name(new_owner)] and [key_name(bro)] into blood brothers.") + var/datum/team/brother_team/team = new + team.add_member(new_owner) + new_owner.add_antag_datum(/datum/antagonist/brother, team) + message_admins("[key_name_admin(admin)] made [key_name_admin(new_owner)] into a blood brother.") + log_admin("[key_name(admin)] made [key_name(new_owner)] into a blood brother.") /datum/antagonist/brother/ui_static_data(mob/user) var/list/data = list() data["antag_name"] = name data["objectives"] = get_objectives() - data["brothers"] = get_brother_names() return data /datum/team/brother_team name = "\improper Blood Brothers" member_name = "blood brother" - ///Selected meeting area given to the team members - var/meeting_area - ///List of meeting areas that are randomly selected. - var/static/meeting_areas = list( - "The Bar", - "Dorms", - "Escape Dock", - "Arrivals", - "Holodeck", - "Primary Tool Storage", - "Recreation Area", - "Chapel", - "Library", - ) + var/brothers_left = 2 -/datum/team/brother_team/proc/pick_meeting_area() - meeting_area = pick(meeting_areas) - meeting_areas -= meeting_area +/datum/team/brother_team/New() + . = ..() + if (prob(10)) + brothers_left += 1 /datum/team/brother_team/proc/update_name() var/list/last_names = list() @@ -149,10 +164,16 @@ var/list/split_name = splittext(team_minds.name," ") last_names += split_name[split_name.len] - name = "[initial(name)] of " + last_names.Join(" & ") + if (last_names.len == 1) + name = "[last_names[1]]'s Isolated Intifada" + else + name = "[initial(name)] of " + last_names.Join(" & ") /datum/team/brother_team/proc/forge_brother_objectives() objectives = list() + + add_objective(new /datum/objective/convert_brother) + var/is_hijacker = prob(10) for(var/i = 1 to max(1, CONFIG_GET(number/brother_objectives_amount) + (members.len > 2) - is_hijacker)) forge_single_objective() @@ -172,3 +193,12 @@ add_objective(new /datum/objective/assassinate, needs_target = TRUE) else add_objective(new /datum/objective/steal, needs_target = TRUE) + +/datum/objective/convert_brother + name = "convert brother" + explanation_text = "Convert someone else using your flash." + admin_grantable = FALSE + martyr_compatible = TRUE + +/datum/objective/convert_brother/check_completion() + return length(team?.members) > 1 diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 0f43b5ae29ef4d..9cd24f8e707c73 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -151,20 +151,13 @@ ///Attempts to make a new item and put it in a potential inventory slot in the provided mob. /datum/antagonist/cult/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/mob) - var/list/slots = list( - "backpack" = ITEM_SLOT_BACKPACK, - "left pocket" = ITEM_SLOT_LPOCKET, - "right pocket" = ITEM_SLOT_RPOCKET, - ) - - var/T = new item_path(mob) - var/item_name = initial(item_path.name) - var/where = mob.equip_in_one_of_slots(T, slots) + var/item = new item_path(mob) + var/where = mob.equip_conspicuous_item(item) if(!where) - to_chat(mob, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).")) + to_chat(mob, span_userdanger("Unfortunately, you weren't able to get [item]. This is very bad and you should adminhelp immediately (press F1).")) return FALSE else - to_chat(mob, span_danger("You have a [item_name] in your [where].")) + to_chat(mob, span_danger("You have [item] in your [where].")) if(where == "backpack") mob.back.atom_storage?.show_contents(mob) return TRUE diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm index 68491617aaa0ec..6ee5ba5dcc7e36 100644 --- a/code/modules/antagonists/revolution/revolution.dm +++ b/code/modules/antagonists/revolution/revolution.dm @@ -196,21 +196,14 @@ /datum/antagonist/rev/head/apply_innate_effects(mob/living/mob_override) . = ..() var/mob/living/real_mob = mob_override || owner.current - RegisterSignal(real_mob, COMSIG_MOB_PRE_FLASHED_CARBON, PROC_REF(on_flash)) + real_mob.AddComponentFrom(REF(src), /datum/component/can_flash_from_behind) RegisterSignal(real_mob, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON, PROC_REF(on_flash_success)) /datum/antagonist/rev/head/remove_innate_effects(mob/living/mob_override) . = ..() var/mob/living/real_mob = mob_override || owner.current - UnregisterSignal(real_mob, list(COMSIG_MOB_PRE_FLASHED_CARBON, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON)) - -/// Signal proc for [COMSIG_MOB_PRE_FLASHED_CARBON]. -/// Flashes will always result in partial success even if it's from behind someone -/datum/antagonist/rev/head/proc/on_flash(mob/living/source, mob/living/carbon/flashed, obj/item/assembly/flash/flash, deviation) - SIGNAL_HANDLER - - // Always partial flash at the very least - return (deviation == DEVIATION_FULL) ? DEVIATION_OVERRIDE_PARTIAL : NONE + real_mob.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind) + UnregisterSignal(real_mob, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON) /// Signal proc for [COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON]. /// Bread and butter of revolution conversion, successfully flashing a carbon will make them a revolutionary @@ -359,31 +352,25 @@ return ..() /datum/antagonist/rev/head/equip_rev() - var/mob/living/carbon/C = owner.current - if(!ishuman(C)) + var/mob/living/carbon/carbon_owner = owner.current + if(!ishuman(carbon_owner)) return if(give_flash) - var/obj/item/assembly/flash/handheld/T = new(C) - var/list/slots = list ( - "backpack" = ITEM_SLOT_BACKPACK, - "left pocket" = ITEM_SLOT_LPOCKET, - "right pocket" = ITEM_SLOT_RPOCKET - ) - var/where = C.equip_in_one_of_slots(T, slots, indirect_action = TRUE) - if (!where) - to_chat(C, "The Syndicate were unfortunately unable to get you a flash.") + var/where = carbon_owner.equip_conspicuous_item(new /obj/item/assembly/flash/handheld) + if (where) + to_chat(carbon_owner, "The flash in your [where] will help you to persuade the crew to join your cause.") else - to_chat(C, "The flash in your [where] will help you to persuade the crew to join your cause.") + to_chat(carbon_owner, "The Syndicate were unfortunately unable to get you a flash.") if(give_hud) - var/obj/item/organ/internal/cyberimp/eyes/hud/security/syndicate/S = new() - S.Insert(C) - if(C.get_quirk(/datum/quirk/body_purist)) - to_chat(C, "Being a body purist, you would never accept cybernetic implants. Upon hearing this, your employers signed you up for a special program, which... for \ + var/obj/item/organ/internal/cyberimp/eyes/hud/security/syndicate/hud = new() + hud.Insert(carbon_owner) + if(carbon_owner.get_quirk(/datum/quirk/body_purist)) + to_chat(carbon_owner, "Being a body purist, you would never accept cybernetic implants. Upon hearing this, your employers signed you up for a special program, which... for \ some odd reason, you just can't remember... either way, the program must have worked, because you have gained the ability to keep track of who is mindshield-implanted, and therefore unable to be recruited.") else - to_chat(C, "Your eyes have been implanted with a cybernetic security HUD which will help you keep track of who is mindshield-implanted, and therefore unable to be recruited.") + to_chat(carbon_owner, "Your eyes have been implanted with a cybernetic security HUD which will help you keep track of who is mindshield-implanted, and therefore unable to be recruited.") /datum/team/revolution name = "\improper Revolution" diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index fc96815045d92e..a375dbe878dfcc 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -455,3 +455,21 @@ covered_flags |= worn_item.body_parts_covered return covered_flags + +/// Attempts to equip the given item in a conspicious place. +/// This is used when, for instance, a character spawning with an item +/// in their hands would be a dead giveaway that they are an antagonist. +/// Returns the human readable name of where it placed the item, or null otherwise. +/mob/living/carbon/proc/equip_conspicuous_item(obj/item/item, delete_item_if_failed = TRUE) + var/list/slots = list ( + "backpack" = ITEM_SLOT_BACKPACK, + "left pocket" = ITEM_SLOT_LPOCKET, + "right pocket" = ITEM_SLOT_RPOCKET + ) + + var/placed_in = equip_in_one_of_slots(item, slots, indirect_action = TRUE) + + if (isnull(placed_in) && delete_item_if_failed) + qdel(item) + + return placed_in diff --git a/tgstation.dme b/tgstation.dme index 9adb663bc48197..3f1d417e836e3a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -995,6 +995,7 @@ #include "code\datums\components\burning.dm" #include "code\datums\components\butchering.dm" #include "code\datums\components\caltrop.dm" +#include "code\datums\components\can_flash_from_behind.dm" #include "code\datums\components\chasm.dm" #include "code\datums\components\chuunibyou.dm" #include "code\datums\components\cleaner.dm" diff --git a/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx b/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx index 327c46c8df91ea..63094344eb6abc 100644 --- a/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx +++ b/tgui/packages/tgui/interfaces/AntagInfoBrother.tsx @@ -11,14 +11,14 @@ type Info = { export const AntagInfoBrother = (props, context) => { const { data } = useBackend(context); - const { antag_name, brothers, objectives } = data; + const { antag_name, objectives } = data; return (
- You are the {antag_name} of {brothers}! + You are the {antag_name}!