Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blood brothers is now a single person conversion antagonist #79971

Merged
merged 2 commits into from Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
Expand Up @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions 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
12 changes: 12 additions & 0 deletions code/datums/memory/general_memories.dm
Expand Up @@ -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
Expand Down
47 changes: 18 additions & 29 deletions code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
Expand Up @@ -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

//////////////////////////////////////////////
Expand Down
132 changes: 81 additions & 51 deletions code/modules/antagonists/brother/brother.dm
Expand Up @@ -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)
Expand All @@ -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()]"

Expand Down Expand Up @@ -74,85 +127,53 @@
brother_text += ", "
return brother_text

/datum/antagonist/brother/proc/give_meeting_area()
if(!owner.current || !team || !team.meeting_area)
return
to_chat(owner.current, "<span class='infoplain'><B>Your designated meeting area:</B> [team.meeting_area]</span>")
antag_memory += "<b>Meeting Area</b>: [team.meeting_area]<br>"

/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()
for(var/datum/mind/team_minds as anything in members)
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()
Expand All @@ -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
15 changes: 4 additions & 11 deletions code/modules/antagonists/cult/cult.dm
Expand Up @@ -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
Expand Down