From 1e4d1ca42f4821d4c806b6ee52f4d6ce4e6fcfb7 Mon Sep 17 00:00:00 2001 From: pubby Date: Sun, 17 Sep 2017 18:02:23 -0500 Subject: [PATCH] Traitorbro gamemode (#30344) A dead-simple traitor variant. It's like traitor, except instead of an uplink you get a buddy: your blood brother. You must team up with your brother to complete your objectives. It runs along side regular traitor mode, thus the name "traitorbro". --- code/__DEFINES/antagonists.dm | 1 + code/__DEFINES/atom_hud.dm | 1 + code/__DEFINES/role_preferences.dm | 4 +- code/controllers/configuration.dm | 6 + code/datums/antagonists/datum_brother.dm | 48 ++ code/datums/hud.dm | 1 + code/datums/mind.dm | 39 +- code/game/gamemodes/brother/traitor_bro.dm | 137 ++++++ .../miniantags/abduction/abduction.dm | 6 +- code/game/gamemodes/objective.dm | 429 ++++++++---------- code/game/gamemodes/objective_team.dm | 13 + code/game/gamemodes/traitor/traitor.dm | 5 +- code/modules/admin/player_panel.dm | 15 + code/modules/client/preferences_savefile.dm | 5 +- code/modules/shuttle/emergency.dm | 2 +- config/game_options.txt | 14 +- tgstation.dme | 3 + 17 files changed, 462 insertions(+), 267 deletions(-) create mode 100644 code/datums/antagonists/datum_brother.dm create mode 100644 code/game/gamemodes/brother/traitor_bro.dm create mode 100644 code/game/gamemodes/objective_team.dm diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 98420bc42948..e6d0ff473217 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -18,3 +18,4 @@ #define ANTAG_DATUM_IAA_HUMAN_CUSTOM /datum/antagonist/traitor/human/internal_affairs/custom #define ANTAG_DATUM_IAA_AI_CUSTOM /datum/antagonist/traitor/AI/internal_affairs/custom #define ANTAG_DATUM_IAA_AI /datum/antagonist/traitor/AI/internal_affairs +#define ANTAG_DATUM_BROTHER /datum/antagonist/brother diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm index 3a2534c00ba4..3c3350d6ab0f 100644 --- a/code/__DEFINES/atom_hud.dm +++ b/code/__DEFINES/atom_hud.dm @@ -40,6 +40,7 @@ #define ANTAG_HUD_SINTOUCHED 16 #define ANTAG_HUD_SOULLESS 17 #define ANTAG_HUD_CLOCKWORK 18 +#define ANTAG_HUD_BROTHER 19 // Notification action types #define NOTIFY_JUMP "jump" diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 622eda530d07..13f9388eed05 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -22,12 +22,14 @@ #define ROLE_REVENANT "revenant" #define ROLE_DEVIL "devil" #define ROLE_SERVANT_OF_RATVAR "servant of Ratvar" +#define ROLE_BROTHER "blood brother" //Missing assignment means it's not a gamemode specific role, IT'S NOT A BUG OR ERROR. //The gamemode specific ones are just so the gamemodes can query whether a player is old enough //(in game days played) to play that role GLOBAL_LIST_INIT(special_roles, list( ROLE_TRAITOR = /datum/game_mode/traitor, + ROLE_BROTHER = /datum/game_mode/traitor/bros, ROLE_OPERATIVE = /datum/game_mode/nuclear, ROLE_CHANGELING = /datum/game_mode/changeling, ROLE_WIZARD = /datum/game_mode/wizard, @@ -48,4 +50,4 @@ GLOBAL_LIST_INIT(special_roles, list( //Job defines for what happens when you fail to qualify for any job during job selection #define BEASSISTANT 1 #define BERANDOMJOB 2 -#define RETURNTOLOBBY 3 \ No newline at end of file +#define RETURNTOLOBBY 3 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 03245b5450ca..c3889b940533 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -147,11 +147,13 @@ GLOBAL_PROTECT(config_dir) var/irc_first_connection_alert = 0 // do we notify the irc channel when somebody is connecting for the first time? var/traitor_scaling_coeff = 6 //how much does the amount of players get divided by to determine traitors + var/brother_scaling_coeff = 25 //how many players per brother team var/changeling_scaling_coeff = 6 //how much does the amount of players get divided by to determine changelings var/security_scaling_coeff = 8 //how much does the amount of players get divided by to determine open security officer positions var/abductor_scaling_coeff = 15 //how many players per abductor team var/traitor_objectives_amount = 2 + var/brother_objectives_amount = 2 var/protect_roles_from_antagonist = 0 //If security and such can be traitor/cult/other var/protect_assistant_from_antagonist = 0 //If assistants can be traitor/cult/other var/enforce_human_authority = 0 //If non-human species are barred from joining as a head of staff @@ -692,6 +694,8 @@ GLOBAL_PROTECT(config_dir) ghost_interaction = 1 if("traitor_scaling_coeff") traitor_scaling_coeff = text2num(value) + if("brother_scaling_coeff") + brother_scaling_coeff = text2num(value) if("changeling_scaling_coeff") changeling_scaling_coeff = text2num(value) if("security_scaling_coeff") @@ -700,6 +704,8 @@ GLOBAL_PROTECT(config_dir) abductor_scaling_coeff = text2num(value) if("traitor_objectives_amount") traitor_objectives_amount = text2num(value) + if("brother_objectives_amount") + brother_objectives_amount = text2num(value) if("probability") var/prob_pos = findtext(value, " ") var/prob_name = null diff --git a/code/datums/antagonists/datum_brother.dm b/code/datums/antagonists/datum_brother.dm new file mode 100644 index 000000000000..1f799b1bf8b0 --- /dev/null +++ b/code/datums/antagonists/datum_brother.dm @@ -0,0 +1,48 @@ +/datum/antagonist/brother + name = "Brother" + var/special_role = "blood brother" + var/datum/objective_team/brother_team/team + +/datum/antagonist/brother/New(datum/mind/new_owner, datum/objective_team/brother_team/T) + team = T + return ..() + +/datum/antagonist/brother/on_gain() + SSticker.mode.brothers += owner + owner.special_role = special_role + owner.objectives += team.objectives + finalize_brother() + return ..() + +/datum/antagonist/brother/on_removal() + SSticker.mode.brothers -= owner + team.members -= owner + owner.objectives -= team.objectives + if(owner.current) + to_chat(owner.current,"You are no longer the [special_role]!") + owner.special_role = null + return ..() + +/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]") + owner.store_memory("Meeting Area: [team.meeting_area]") + +/datum/antagonist/brother/greet() + var/brother_text = "" + var/list/brothers = team.members - owner + for(var/i = 1 to brothers.len) + var/datum/mind/M = brothers[i] + brother_text += M.name + if(i == brothers.len - 1) + brother_text += " and " + else if(i != brothers.len) + brother_text += ", " + to_chat(owner.current, "You are the [owner.special_role] of [brother_text].") + to_chat(owner.current, "The Syndicate only accepts those that have proven themself. Prove yourself and prove your [team.member_name]s by completing your objectives together!") + owner.announce_objectives() + give_meeting_area() + +/datum/antagonist/brother/proc/finalize_brother() + SSticker.mode.update_brother_icons_added(owner) diff --git a/code/datums/hud.dm b/code/datums/hud.dm index 9c37b46d105d..98d2c9258a58 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(huds, list( ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(), ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(), ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(), + ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), )) /datum/atom_hud diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 93857fa0e4f4..809222ec0188 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -127,10 +127,10 @@ memory = null // Datum antag mind procs -/datum/mind/proc/add_antag_datum(datum_type) +/datum/mind/proc/add_antag_datum(datum_type, team) if(!datum_type) return - var/datum/antagonist/A = new datum_type(src) + var/datum/antagonist/A = new datum_type(src, team) if(!A.can_be_owned(src)) qdel(A) return @@ -190,6 +190,11 @@ src.remove_antag_datum(ANTAG_DATUM_TRAITOR) SSticker.mode.update_traitor_icons_removed(src) +/datum/mind/proc/remove_brother() + if(src in SSticker.mode.brothers) + src.remove_antag_datum(ANTAG_DATUM_BROTHER) + SSticker.mode.update_brother_icons_removed(src) + /datum/mind/proc/remove_nukeop() if(src in SSticker.mode.syndicates) SSticker.mode.syndicates -= src @@ -345,6 +350,12 @@ var/obj_count = 1 for(var/datum/objective/objective in objectives) output += "
Objective #[obj_count++]: [objective.explanation_text]" + var/list/datum/mind/other_owners = objective.get_owners() - src + if(other_owners.len) + output += "" if(window) recipient << browse(output,"window=memory") @@ -381,7 +392,7 @@ /** TRAITOR ***/ text = "traitor" - if (SSticker.mode.config_tag=="traitor" || SSticker.mode.config_tag=="traitorchan") + if (SSticker.mode.config_tag=="traitor" || SSticker.mode.config_tag=="traitorchan" || SSticker.mode.config_tag=="traitorbro") text = uppertext(text) text = "[text]: " if (src in SSticker.mode.traitors) @@ -401,6 +412,21 @@ if(ishuman(current) || ismonkey(current)) + /** BROTHER **/ + text = "brother" + if(SSticker.mode.config_tag == "traitorbro") + text = uppertext(text) + text = "[text]: " + if(src in SSticker.mode.brothers) + text += "Brother | no" + + if(current && current.client && (ROLE_BROTHER in current.client.prefs.be_special)) + text += " | Enabled in Prefs" + else + text += " | Disabled in Prefs" + + sections["brother"] = text + /** CHANGELING ***/ text = "changeling" if (SSticker.mode.config_tag=="changeling" || SSticker.mode.config_tag=="traitorchan") @@ -1275,6 +1301,13 @@ if(H) src = H.mind + else if (href_list["brother"]) + switch(href_list["brother"]) + if("clear") + remove_brother() + log_admin("[key_name(usr)] has de-brother'ed [current].") + SSticker.mode.update_brother_icons_removed(src) + else if (href_list["silicon"]) switch(href_list["silicon"]) if("unemag") diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm new file mode 100644 index 000000000000..ccb6b774b3fe --- /dev/null +++ b/code/game/gamemodes/brother/traitor_bro.dm @@ -0,0 +1,137 @@ +/datum/objective_team/brother_team + name = "brotherhood" + member_name = "blood brother" + var/list/objectives = list() + var/meeting_area + +/datum/objective_team/brother_team/is_solo() + return FALSE + +/datum/objective_team/brother_team/proc/add_objective(datum/objective/O, needs_target = FALSE) + O.team = src + if(needs_target) + O.find_target() + O.update_explanation_text() + objectives += O + +/datum/objective_team/brother_team/proc/forge_brother_objectives() + objectives = list() + var/is_hijacker = prob(10) + for(var/i = 1 to max(1, config.brother_objectives_amount + (members.len > 2) - is_hijacker)) + forge_single_objective() + if(is_hijacker) + if(!locate(/datum/objective/hijack) in objectives) + add_objective(new/datum/objective/hijack) + else if(!locate(/datum/objective/escape) in objectives) + add_objective(new/datum/objective/escape) + +/datum/objective_team/brother_team/proc/forge_single_objective() + if(prob(50)) + if(LAZYLEN(active_ais()) && prob(100/GLOB.joined_player_list.len)) + add_objective(new/datum/objective/destroy, TRUE) + else if(prob(30)) + add_objective(new/datum/objective/maroon, TRUE) + else + add_objective(new/datum/objective/assassinate, TRUE) + else + add_objective(new/datum/objective/steal, TRUE) + +/datum/game_mode + var/list/datum/mind/brothers = list() + var/list/datum/objective_team/brother_team/brother_teams = list() + +/datum/game_mode/traitor/bros + name = "traitor+brothers" + config_tag = "traitorbro" + restricted_jobs = list("AI", "Cyborg") + + announce_span = "danger" + announce_text = "There are Syndicate agents and Blood Brothers on the station!\n\ + Traitors: Accomplish your objectives.\n\ + Blood Brothers: Accomplish your objectives.\n\ + Crew: Do not let the traitors or brothers succeed!" + + var/list/datum/objective_team/brother_team/pre_brother_teams = list() + var/const/team_amount = 2 //hard limit on brother teams if scaling is turned off + var/const/min_team_size = 2 + + var/meeting_areas = list("The Bar", "Dorms", "Escape Dock", "Arrivals", "Holodeck", "Primary Tool Storage", "Recreation Area", "Chapel", "Library") + +/datum/game_mode/traitor/bros/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_brothers = get_players_for_role(ROLE_BROTHER) + + var/num_teams = team_amount + if(config.brother_scaling_coeff) + num_teams = max(1, round(num_players()/config.brother_scaling_coeff)) + + for(var/j = 1 to num_teams) + if(possible_brothers.len < min_team_size || antag_candidates.len <= required_enemies) + break + var/datum/objective_team/brother_team/team = new + var/team_size = prob(10) ? min(3, possible_brothers.len) : 2 + for(var/k = 1 to team_size) + var/datum/mind/bro = pick(possible_brothers) + possible_brothers -= bro + antag_candidates -= bro + team.members += bro + bro.restricted_roles = restricted_jobs + log_game("[key_name(bro)] has been selected as a Brother") + pre_brother_teams += team + return ..() + +/datum/game_mode/traitor/bros/post_setup() + for(var/datum/objective_team/brother_team/team in pre_brother_teams) + team.meeting_area = pick(meeting_areas) + meeting_areas -= team.meeting_area + team.forge_brother_objectives() + for(var/datum/mind/M in team.members) + M.add_antag_datum(ANTAG_DATUM_BROTHER, team) + modePlayer += M + brother_teams += pre_brother_teams + return ..() + +/datum/game_mode/proc/auto_declare_completion_brother() + if(!LAZYLEN(brother_teams)) + return + var/text = "
The blood brothers were:" + var/teamnumber = 1 + for(var/datum/objective_team/brother_team/team in brother_teams) + if(!team.members.len) + continue + text += "
Team #[teamnumber++]" + for(var/datum/mind/M in team.members) + text += printplayer(M) + var/win = TRUE + var/objective_count = 1 + for(var/datum/objective/objective in team.objectives) + if(objective.check_completion()) + text += "
Objective #[objective_count]: [objective.explanation_text] Success!" + SSblackbox.add_details("traitor_objective","[objective.type]|SUCCESS") + else + text += "
Objective #[objective_count]: [objective.explanation_text] Fail." + SSblackbox.add_details("traitor_objective","[objective.type]|FAIL") + win = FALSE + objective_count++ + if(win) + text += "
The blood brothers were successful!" + SSblackbox.add_details("brother_success","SUCCESS") + else + text += "
The blood brothers have failed!" + SSblackbox.add_details("brother_success","FAIL") + text += "
" + to_chat(world, text) + +/datum/game_mode/proc/update_brother_icons_added(datum/mind/brother_mind) + var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] + brotherhud.join_hud(brother_mind.current) + set_antag_hud(brother_mind.current, "brother") + +/datum/game_mode/proc/update_brother_icons_removed(datum/mind/brother_mind) + var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] + brotherhud.leave_hud(brother_mind.current) + set_antag_hud(brother_mind.current, null) diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm index 7eacd9c66650..7ff5a9742ded 100644 --- a/code/game/gamemodes/miniantags/abduction/abduction.dm +++ b/code/game/gamemodes/miniantags/abduction/abduction.dm @@ -47,7 +47,7 @@ team_names[team_number] = "Mothership [pick(GLOB.possible_changeling_IDs)]" //TODO Ensure unique and actual alieny names //Team Objective var/datum/objective/experiment/team_objective = new - team_objective.team = team_number + team_objective.team_number = team_number team_objectives[team_number] = team_objective //Team Members @@ -212,13 +212,13 @@ // OBJECTIVES /datum/objective/experiment target_amount = 6 - var/team + var/team_number /datum/objective/experiment/New() explanation_text = "Experiment on [target_amount] humans." /datum/objective/experiment/check_completion() - var/ab_team = team + var/ab_team = team_number if(owner) if(!owner.current || !ishuman(owner.current)) return 0 diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 2108ba527c25..424ea3242340 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -1,6 +1,8 @@ /datum/objective - var/datum/mind/owner = null //Who owns the objective. + var/datum/mind/owner //The primary owner of the objective. !!SOMEWHAT DEPRECATED!! Prefer using 'team' for new code. + var/datum/objective_team/team //An alternative to 'owner': a team. Use this when writing new code. var/explanation_text = "Nothing" //What that person is supposed to do. + var/team_explanation_text //For when there are multiple owners. var/datum/mind/target = null //If they are focused on a particular person. var/target_amount = 0 //If they are focused on a particular number. Steal objectives have their own counter. var/completed = 0 //currently only used for custom objectives. @@ -10,19 +12,48 @@ if(text) explanation_text = text +/datum/objective/proc/get_owners() // Combine owner and team into a single list. + . = (team && team.members) ? team.members.Copy() : list() + if(owner) + . += owner + +/datum/objective/proc/considered_alive(var/datum/mind/M) + if(M && M.current) + var/mob/living/carbon/human/H + if(ishuman(M.current)) + H = M.current + return M.current.stat != DEAD && !issilicon(M.current) && !isbrain(M.current) && (!H || H.dna.species.id != "memezombies") + return FALSE + +/datum/objective/proc/considered_escaped(datum/mind/M) + if(!considered_alive(M)) + return FALSE + if(SSticker.force_ending || SSticker.mode.station_was_nuked) // Just let them win. + return TRUE + if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME) + return FALSE + var/turf/location = get_turf(M.current) + if(!location || istype(location, /turf/open/floor/plasteel/shuttle/red) || istype(location, /turf/open/floor/mineral/plastitanium/brig)) // Fails if they are in the shuttle brig + return FALSE + return location.onCentCom() || location.onSyndieBase() + +/datum/objective/proc/considered_afk(datum/mind/M) + return !M || !M.current || !M.current.client || M.current.client.is_afk() + /datum/objective/proc/check_completion() return completed /datum/objective/proc/is_unique_objective(possible_target) - for(var/datum/objective/O in owner.objectives) - if(istype(O, type) && O.get_target() == possible_target) - return 0 - return 1 + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + for(var/datum/objective/O in M.objectives) + if(istype(O, type) && O.get_target() == possible_target) + return FALSE + return TRUE /datum/objective/proc/get_target() return target - /datum/objective/proc/get_crewmember_minds() . = list() for(var/V in GLOB.data_core.locked) @@ -32,9 +63,10 @@ . += M /datum/objective/proc/find_target() + var/list/datum/mind/owners = get_owners() var/list/possible_targets = list() for(var/datum/mind/possible_target in get_crewmember_minds()) - if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && is_unique_objective(possible_target)) + if(!(possible_target in owners) && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && is_unique_objective(possible_target)) possible_targets += possible_target if(possible_targets.len > 0) target = pick(possible_targets) @@ -42,8 +74,9 @@ return target /datum/objective/proc/find_target_by_role(role, role_type=0, invert=0)//Option sets either to check assigned role or special role. Default to assigned., invert inverts the check, eg: "Don't choose a Ling" + var/list/datum/mind/owners = get_owners() for(var/datum/mind/possible_target in get_crewmember_minds()) - if((possible_target != owner) && ishuman(possible_target.current)) + if(!(possible_target in owners) && ishuman(possible_target.current)) var/is_role = 0 if(role_type) if(possible_target.special_role == role) @@ -64,13 +97,15 @@ update_explanation_text() /datum/objective/proc/update_explanation_text() - //Default does nothing, override where needed + if(team_explanation_text && LAZYLEN(get_owners()) > 1) + explanation_text = team_explanation_text /datum/objective/proc/give_special_equipment(special_equipment) - if(owner && owner.current) - if(ishuman(owner.current)) - var/mob/living/carbon/human/H = owner.current - var/list/slots = list ("backpack" = slot_in_backpack) + var/datum/mind/receiver = pick(get_owners()) + if(receiver && receiver.current) + if(ishuman(receiver.current)) + var/mob/living/carbon/human/H = receiver.current + var/list/slots = list("backpack" = slot_in_backpack) for(var/eq_path in special_equipment) var/obj/O = new eq_path H.equip_in_one_of_slots(O, slots) @@ -86,14 +121,7 @@ return target /datum/objective/assassinate/check_completion() - if(target && target.current) - var/mob/living/carbon/human/H - if(ishuman(target.current)) - H = target.current - if(target.current.stat == DEAD || issilicon(target.current) || isbrain(target.current) || target.current.z > 6 || !target.current.ckey || (H && H.dna.species.id == "memezombies")) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite - return 1 - return 0 - return 1 + return !target || !considered_alive(target) /datum/objective/assassinate/update_explanation_text() ..() @@ -110,7 +138,6 @@ if(target && !target.current) explanation_text = "Assassinate [target.name], who was obliterated" - /datum/objective/mutiny var/target_role_type=0 martyr_compatible = 1 @@ -122,14 +149,10 @@ return target /datum/objective/mutiny/check_completion() - if(target && target.current) - if(target.current.stat == DEAD || !ishuman(target.current) || !target.current.ckey) - return 1 - var/turf/T = get_turf(target.current) - if(T && (!(T.z in GLOB.station_z_levels)) || (target.current.client && target.current.client.is_afk())) //If they leave the station or go afk they count as dead for this - return 2 - return 0 - return 1 + if(!target || !considered_alive(target) || considered_afk(target)) + return TRUE + var/turf/T = get_turf(target.current) + return T && !(T.z in GLOB.station_z_levels) /datum/objective/mutiny/update_explanation_text() ..() @@ -138,8 +161,6 @@ else explanation_text = "Free Objective" - - /datum/objective/maroon var/target_role_type=0 martyr_compatible = 1 @@ -151,15 +172,7 @@ return target /datum/objective/maroon/check_completion() - if(target && target.current) - var/mob/living/carbon/human/H - if(ishuman(target.current)) - H = target.current - if(target.current.stat == DEAD || issilicon(target.current) || isbrain(target.current) || target.current.z > 6 || !target.current.ckey || (H && H.dna.species.id == "memezombies")) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite - return 1 - if(target.current.onCentCom() || target.current.onSyndieBase()) - return 0 - return 1 + return !target || !considered_alive(target) || (!target.current.onCentCom() && !target.current.onSyndieBase()) /datum/objective/maroon/update_explanation_text() if(target && target.current) @@ -167,9 +180,7 @@ else explanation_text = "Free Objective" - - -/datum/objective/debrain//I want braaaainssss +/datum/objective/debrain var/target_role_type=0 /datum/objective/debrain/find_target_by_role(role, role_type=0, invert=0) @@ -180,17 +191,20 @@ /datum/objective/debrain/check_completion() if(!target)//If it's a free objective. - return 1 - if( !owner.current || owner.current.stat==DEAD )//If you're otherwise dead. - return 0 - if( !target.current || !isbrain(target.current) ) - return 0 + return TRUE + + if(!target.current || !isbrain(target.current)) + return FALSE + var/atom/A = target.current - while(A.loc) //check to see if the brainmob is on our person + var/list/datum/mind/owners = get_owners() + + while(A.loc) // Check to see if the brainmob is on our person A = A.loc - if(A == owner.current) - return 1 - return 0 + for(var/datum/mind/M in owners) + if(M.current && M.current.stat != DEAD && A == M.current) + return TRUE + return FALSE /datum/objective/debrain/update_explanation_text() ..() @@ -199,8 +213,6 @@ else explanation_text = "Free Objective" - - /datum/objective/protect//The opposite of killing a dude. var/target_role_type=0 martyr_compatible = 1 @@ -212,13 +224,7 @@ return target /datum/objective/protect/check_completion() - if(!target) //If it's a free objective. - return 1 - if(target.current) - if(target.current.stat == DEAD || issilicon(target.current) || isbrain(target.current)) - return 0 - return 1 - return 0 + return !target || considered_alive(target) /datum/objective/protect/update_explanation_text() ..() @@ -227,76 +233,32 @@ else explanation_text = "Free Objective" - - /datum/objective/hijack explanation_text = "Hijack the shuttle to ensure no loyalist Nanotrasen crew escape alive and out of custody." + team_explanation_text = "Hijack the shuttle to ensure no loyalist Nanotrasen crew escape alive and out of custody. Leave no team member behind." martyr_compatible = 0 //Technically you won't get both anyway. -/datum/objective/hijack/check_completion() - if(!owner.current || owner.current.stat) - return 0 +/datum/objective/hijack/check_completion() // Requires all owners to escape. if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME) - return 0 - if(issilicon(owner.current)) - return 0 - if(!SSshuttle.emergency.shuttle_areas[get_area(owner.current)]) - return 0 + return FALSE + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + if(!considered_alive(M) || !SSshuttle.emergency.shuttle_areas[get_area(M.current)]) + return FALSE return SSshuttle.emergency.is_hijacked() - -/datum/objective/hijackclone - explanation_text = "Hijack the emergency shuttle by ensuring only you (or your copies) escape." - martyr_compatible = 0 - -/datum/objective/hijackclone/check_completion() - if(!owner.current) - return FALSE - if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME) - return FALSE - - var/in_shuttle = FALSE - for(var/mob/living/player in GLOB.player_list) //Make sure nobody else is onboard - if(SSshuttle.emergency.shuttle_areas[get_area(player)]) - if(player.mind && player.mind != owner) - if(player.stat != DEAD) - if(issilicon(player)) //Borgs are technically dead anyways - continue - if(isanimal(player)) //animals don't count - continue - if(isbrain(player)) //also technically dead - continue - var/location = get_turf(player.mind.current) - if(istype(location, /turf/open/floor/plasteel/shuttle/red)) - continue - if(istype(location, /turf/open/floor/mineral/plastitanium/brig)) - continue - if(player.real_name != owner.current.real_name) - return FALSE - else - in_shuttle = TRUE - return in_shuttle - /datum/objective/block explanation_text = "Do not allow any organic lifeforms to escape on the shuttle alive." martyr_compatible = 1 /datum/objective/block/check_completion() - if(!issilicon(owner.current)) - return 0 if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME) - return 1 - + return TRUE for(var/mob/living/player in GLOB.player_list) - if(issilicon(player)) - continue - if(player.mind) - if(player.stat != DEAD) - if(get_area(player) in SSshuttle.emergency.shuttle_areas) - return 0 - - return 1 - + if(player.mind && player.stat != DEAD && !issilicon(player)) + if(get_area(player) in SSshuttle.emergency.shuttle_areas) + return FALSE + return TRUE /datum/objective/purge explanation_text = "Ensure no mutant humanoid species are present aboard the escape shuttle." @@ -304,63 +266,41 @@ /datum/objective/purge/check_completion() if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME) - return 1 - + return TRUE for(var/mob/living/player in GLOB.player_list) if(get_area(player) in SSshuttle.emergency.shuttle_areas && player.mind && player.stat != DEAD && ishuman(player)) var/mob/living/carbon/human/H = player if(H.dna.species.id != "human") - return 0 - - return 1 - + return FALSE + return TRUE /datum/objective/robot_army explanation_text = "Have at least eight active cyborgs synced to you." martyr_compatible = 0 /datum/objective/robot_army/check_completion() - if(!isAI(owner.current)) - return 0 - var/mob/living/silicon/ai/A = owner.current - var/counter = 0 - - for(var/mob/living/silicon/robot/R in A.connected_robots) - if(R.stat != DEAD) - counter++ - - if(counter < 8) - return 0 - return 1 + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + if(!M.current || !isAI(M.current)) + continue + var/mob/living/silicon/ai/A = M.current + for(var/mob/living/silicon/robot/R in A.connected_robots) + if(R.stat != DEAD) + counter++ + return counter >= 8 /datum/objective/escape explanation_text = "Escape on the shuttle or an escape pod alive and without being in custody." + team_explanation_text = "Have all members of your team escape on a shuttle or pod alive, without being in custody." /datum/objective/escape/check_completion() - if(issilicon(owner.current)) - return 0 - if(isbrain(owner.current)) - return 0 - if(!owner.current || owner.current.stat == DEAD) - return 0 - if(SSticker.force_ending) //This one isn't their fault, so lets just assume good faith - return 1 - if(SSticker.mode.station_was_nuked) //If they escaped the blast somehow, let them win - return 1 - if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME) - return 0 - var/turf/location = get_turf(owner.current) - if(!location) - return 0 - - if(istype(location, /turf/open/floor/plasteel/shuttle/red) || istype(location, /turf/open/floor/mineral/plastitanium/brig)) // Fails traitors if they are in the shuttle brig -- Polymorph - return 0 - - if(location.onCentCom() || location.onSyndieBase()) - return 1 - - return 0 + // Require all owners escape safely. + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + if(!considered_escaped(M)) + return FALSE + return TRUE /datum/objective/escape/escape_with_identity var/target_real_name // Has to be stored because the target's real_name can change over the course of the round @@ -387,39 +327,36 @@ explanation_text = "Free Objective." /datum/objective/escape/escape_with_identity/check_completion() - if(!target_real_name) - return 1 - if(!ishuman(owner.current)) - return 0 - var/mob/living/carbon/human/H = owner.current - if(..()) - if(H.dna.real_name == target_real_name) - if(H.get_id_name()== target_real_name || target_missing_id) - return 1 - return 0 - + if(!target || !target_real_name) + return TRUE + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + if(!ishuman(M.current) || !considered_escaped(M)) + continue + var/mob/living/carbon/human/H = M.current + if(H.dna.real_name == target_real_name && (H.get_id_name() == target_real_name || target_missing_id)) + return TRUE + return FALSE /datum/objective/survive explanation_text = "Stay alive until the end." /datum/objective/survive/check_completion() - if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current)) - return 0 //Brains no longer win survive objectives. --NEO - if(!is_special_character(owner.current)) //This fails borg'd traitors - return 0 - return 1 - + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + if(!considered_alive(M)) + return FALSE + return TRUE /datum/objective/martyr explanation_text = "Die a glorious death." /datum/objective/martyr/check_completion() - if(!owner.current) //Gibbed, etc. - return 1 - if(owner.current && owner.current.stat == DEAD) //You're dead! Yay! - return 1 - return 0 - + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + if(considered_alive(M)) + return FALSE + return TRUE /datum/objective/nuclear explanation_text = "Destroy the station with a nuclear device." @@ -427,8 +364,8 @@ /datum/objective/nuclear/check_completion() if(SSticker && SSticker.mode && SSticker.mode.station_was_nuked) - return 1 - return 0 + return TRUE + return FALSE GLOBAL_LIST_EMPTY(possible_items) /datum/objective/steal @@ -446,16 +383,21 @@ GLOBAL_LIST_EMPTY(possible_items) new I /datum/objective/steal/find_target() + var/list/datum/mind/owners = get_owners() var/approved_targets = list() - for(var/datum/objective_item/possible_item in GLOB.possible_items) - if(is_unique_objective(possible_item.targetitem) && !(owner.current.mind.assigned_role in possible_item.excludefromjob)) + check_items: + for(var/datum/objective_item/possible_item in GLOB.possible_items) + if(!is_unique_objective(possible_item.targetitem)) + continue + for(var/datum/mind/M in owners) + if(M.current.mind.assigned_role in possible_item.excludefromjob) + continue check_items approved_targets += possible_item return set_target(safepick(approved_targets)) /datum/objective/steal/proc/set_target(datum/objective_item/item) if(item) targetinfo = item - steal_target = targetinfo.targetitem explanation_text = "Steal [targetinfo.name]" give_special_equipment(targetinfo.special_equipment) @@ -483,23 +425,26 @@ GLOBAL_LIST_EMPTY(possible_items) return steal_target /datum/objective/steal/check_completion() + var/list/datum/mind/owners = get_owners() if(!steal_target) - return 1 - if(!isliving(owner.current)) - return 0 - var/list/all_items = owner.current.GetAllContents() //this should get things in cheesewheels, books, etc. + return TRUE + for(var/datum/mind/M in owners) + if(!isliving(M.current)) + continue - for(var/obj/I in all_items) //Check for items - if(istype(I, steal_target)) - if(!targetinfo) //If there's no targetinfo, then that means it was a custom objective. At this point, we know you have the item, so return 1. - return 1 - else if(targetinfo.check_special_completion(I))//Returns 1 by default. Items with special checks will return 1 if the conditions are fulfilled. - return 1 + var/list/all_items = M.current.GetAllContents() //this should get things in cheesewheels, books, etc. - if(targetinfo && I.type in targetinfo.altitems) //Ok, so you don't have the item. Do you have an alternative, at least? - if(targetinfo.check_special_completion(I))//Yeah, we do! Don't return 0 if we don't though - then you could fail if you had 1 item that didn't pass and got checked first! - return 1 - return 0 + for(var/obj/I in all_items) //Check for items + if(istype(I, steal_target)) + if(!targetinfo) //If there's no targetinfo, then that means it was a custom objective. At this point, we know you have the item, so return 1. + return TRUE + else if(targetinfo.check_special_completion(I))//Returns 1 by default. Items with special checks will return 1 if the conditions are fulfilled. + return TRUE + + if(targetinfo && I.type in targetinfo.altitems) //Ok, so you don't have the item. Do you have an alternative, at least? + if(targetinfo.check_special_completion(I))//Yeah, we do! Don't return 0 if we don't though - then you could fail if you had 1 item that didn't pass and got checked first! + return TRUE + return FALSE GLOBAL_LIST_EMPTY(possible_items_special) @@ -553,31 +498,22 @@ GLOBAL_LIST_EMPTY(possible_items_special) explanation_text = "Download [target_amount] research level\s." return target_amount -/datum/objective/download/check_completion()//NINJACODE - if(!ishuman(owner.current)) - return 0 - - var/mob/living/carbon/human/H = owner.current - if(!H || H.stat == DEAD) - return 0 - - if(!istype(H.wear_suit, /obj/item/clothing/suit/space/space_ninja)) - return 0 - - var/obj/item/clothing/suit/space/space_ninja/SN = H.wear_suit - if(!SN.s_initialized) - return 0 - - var/current_amount - if(!SN.stored_research.len) - return 0 - else +/datum/objective/download/check_completion()//NINJACODE. + var/current_amount = 0 + var/list/datum/mind/owners = get_owners() + for(var/datum/mind/M in owners) + if(!ishuman(owner.current)) + continue + var/mob/living/carbon/human/H = owner.current + if(!H || H.stat == DEAD || !istype(H.wear_suit, /obj/item/clothing/suit/space/space_ninja)) + continue + var/obj/item/clothing/suit/space/space_ninja/SN = H.wear_suit + if(!SN.s_initialized) + continue for(var/datum/tech/current_data in SN.stored_research) if(current_data.level) current_amount += (current_data.level-1) - if(current_amount= target_amount @@ -614,10 +550,7 @@ GLOBAL_LIST_EMPTY(possible_items_special) captured_amount+=1 continue captured_amount+=2 - if(captured_amount= target_amount /datum/objective/absorb @@ -625,13 +558,14 @@ GLOBAL_LIST_EMPTY(possible_items_special) /datum/objective/absorb/proc/gen_amount_goal(lowbound = 4, highbound = 6) target_amount = rand (lowbound,highbound) var/n_p = 1 //autowin + var/list/datum/mind/owners = get_owners() if (SSticker.current_state == GAME_STATE_SETTING_UP) for(var/mob/dead/new_player/P in GLOB.player_list) - if(P.client && P.ready == PLAYER_READY_TO_PLAY && P.mind!=owner) + if(P.client && P.ready == PLAYER_READY_TO_PLAY && !(P.mind in owners)) n_p ++ else if (SSticker.IsRoundInProgress()) for(var/mob/living/carbon/human/P in GLOB.player_list) - if(P.client && !(P.mind in SSticker.mode.changelings) && P.mind!=owner) + if(P.client && !(P.mind in SSticker.mode.changelings) && !(P.mind in owners)) n_p ++ target_amount = min(target_amount, n_p) @@ -639,10 +573,13 @@ GLOBAL_LIST_EMPTY(possible_items_special) return target_amount /datum/objective/absorb/check_completion() - if(owner && owner.changeling && owner.changeling.stored_profiles && (owner.changeling.absorbedcount >= target_amount)) - return 1 - else - return 0 + var/list/datum/mind/owners = get_owners() + var/absorbedcount = 0 + for(var/datum/mind/M in owners) + if(!owner || !owner.changeling || !owner.changeling.stored_profiles) + continue + absorbedcount += M.changeling.absorbedcount + return absorbedcount >= target_amount @@ -658,10 +595,8 @@ GLOBAL_LIST_EMPTY(possible_items_special) /datum/objective/destroy/check_completion() if(target && target.current) - if(target.current.stat == DEAD || target.current.z > 6 || !target.current.ckey) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite - return 1 - return 0 - return 1 + return target.current.stat == DEAD || target.current.z > 6 || !target.current.ckey //Borgs/brains/AIs count as dead for traitor objectives. + return TRUE /datum/objective/destroy/update_explanation_text() ..() @@ -690,18 +625,16 @@ GLOBAL_LIST_EMPTY(possible_items_special) wanted_items = list(/obj/item/spellbook, /obj/item/gun/magic, /obj/item/clothing/suit/space/hardsuit/wizard, /obj/item/scrying, /obj/item/antag_spawner/contract, /obj/item/device/necromantic_stone) /datum/objective/steal_five_of_type/check_completion() - if(!isliving(owner.current)) - return 0 + var/list/datum/mind/owners = get_owners() var/stolen_count = 0 - var/list/all_items = owner.current.GetAllContents() //this should get things in cheesewheels, books, etc. - for(var/obj/I in all_items) //Check for wanted items - if(is_type_in_typecache(I, wanted_items)) - stolen_count++ - if(stolen_count >= 5) - return 1 - else - return 0 - return 0 + for(var/datum/mind/M in owners) + if(!isliving(M.current)) + continue + var/list/all_items = M.current.GetAllContents() //this should get things in cheesewheels, books, etc. + for(var/obj/I in all_items) //Check for wanted items + if(is_type_in_typecache(I, wanted_items)) + stolen_count++ + return stolen_count >= 5 //////////////////////////////// diff --git a/code/game/gamemodes/objective_team.dm b/code/game/gamemodes/objective_team.dm new file mode 100644 index 000000000000..ba6b2546ca81 --- /dev/null +++ b/code/game/gamemodes/objective_team.dm @@ -0,0 +1,13 @@ +//A barebones antagonist team. +/datum/objective_team + var/list/datum/mind/members = list() + var/name = "team" + var/member_name = "member" + +/datum/objective_team/New(starting_members) + . = ..() + if(starting_members) + members += starting_members + +/datum/objective_team/proc/is_solo() + return members.len == 1 diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index 1b8370068f0e..77b91a85717b 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -54,10 +54,7 @@ log_game("[traitor.key] (ckey) has been selected as a [traitor_name]") antag_candidates.Remove(traitor) - - if(pre_traitors.len < required_enemies) - return 0 - return 1 + return pre_traitors.len > 0 /datum/game_mode/traitor/post_setup() diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index 7a29782a4eb8..28603f54a25d 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -517,6 +517,21 @@ dat += "PM" dat += "" + if(SSticker.mode.brother_teams.len > 0) + dat += "
" + for(var/datum/objective_team/brother_team/team in SSticker.mode.brother_teams) + for(var/datum/mind/brother in team.members) + var/mob/M = brother.current + if(M) + dat += "" + dat += "" + dat += "" + dat += "" + else + dat += "" + dat += "" + dat += "
Brothers
[M.real_name][M.client ? "" : " (No Client)"][M.stat == DEAD ? " (DEAD)" : ""]PMFLWShow Objective
[brother.name]([brother.key])Brother body destroyed!PM
" + if(SSticker.mode.abductors.len) dat += "
" for(var/datum/mind/abductor in SSticker.mode.abductors) diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index de41b80f7832..88b4b99bc897 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -48,8 +48,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car var/B_monkey = 2048 var/B_gang = 4096 var/B_abductor = 16384 + var/B_brother = 32768 - var/list/archived = list(B_traitor,B_operative,B_changeling,B_wizard,B_malf,B_rev,B_alien,B_pai,B_cultist,B_blob,B_ninja,B_monkey,B_gang,B_abductor) + var/list/archived = list(B_traitor,B_operative,B_changeling,B_wizard,B_malf,B_rev,B_alien,B_pai,B_cultist,B_blob,B_ninja,B_monkey,B_gang,B_abductor,B_brother) be_special = list() @@ -83,6 +84,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car be_special += ROLE_MONKEY if(16384) be_special += ROLE_ABDUCTOR + if(32768) + be_special += ROLE_BROTHER /datum/preferences/proc/update_preferences(current_version, savefile/S) diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index 7c74d93b767c..55a922efb558 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -262,7 +262,7 @@ if(shuttle_areas[get_area(player)]) has_people = TRUE var/location = get_turf(player.mind.current) - if(!(player.mind.special_role == "traitor" || player.mind.special_role == "Syndicate") && !istype(location, /turf/open/floor/plasteel/shuttle/red) && !istype(location, /turf/open/floor/mineral/plastitanium/brig)) + if(!(player.mind.special_role == "traitor" || player.mind.special_role == "Syndicate" || player.mind.special_role == "blood brother") && !istype(location, /turf/open/floor/plasteel/shuttle/red) && !istype(location, /turf/open/floor/mineral/plastitanium/brig)) return FALSE return has_people diff --git a/config/game_options.txt b/config/game_options.txt index 5870448c5177..1cdd5492099e 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -81,11 +81,11 @@ ALERT_DELTA Destruction of the station is imminent. All crew are instructed to o ## Set to 0 to disable that mode. PROBABILITY TRAITOR 5 +PROBABILITY TRAITORBRO 2 PROBABILITY TRAITORCHAN 4 PROBABILITY INTERNAL_AFFAIRS 3 PROBABILITY NUCLEAR 2 PROBABILITY REVOLUTION 2 -PROBABILITY GANG 2 PROBABILITY CULT 2 PROBABILITY CHANGELING 2 PROBABILITY WIZARD 4 @@ -109,11 +109,11 @@ REPEATED_MODE_ADJUST 45 30 10 ## Modes that aren't continuous will end the instant all antagonists are dead. CONTINUOUS TRAITOR +CONTINUOUS TRAITORBRO CONTINUOUS TRAITORCHAN CONTINUOUS INTERNAL_AFFAIRS #CONTINUOUS NUCLEAR #CONTINUOUS REVOLUTION -CONTINUOUS GANG CONTINUOUS CULT CONTINUOUS CLOCKWORK_CULT CONTINUOUS CHANGELING @@ -134,11 +134,11 @@ CONTINUOUS SECRET_EXTENDED ## In modes that are continuous, if all antagonists should die then a new set of antagonists will be created. MIDROUND_ANTAG TRAITOR +#MIDROUND_ANTAG TRAITORBRO MIDROUND_ANTAG TRAITORCHAN MIDROUND_ANTAG INTERNAL_AFFAIRS #MIDROUND_ANTAG NUCLEAR #MIDROUND_ANTAG REVOLUTION -#MIDROUND_ANTAG GANG MIDROUND_ANTAG CULT MIDROUND_ANTAG CLOCKWORK_CULT MIDROUND_ANTAG CHANGELING @@ -157,6 +157,9 @@ MIDROUND_ANTAG ABDUCTION #MIN_POP TRAITOR 0 #MAX_POP TRAITOR -1 +#MIN_POP TRAITORBRO 0 +#MAX_POP TRAITORBRO -1 + #MIN_POP TRAITORCHAN 15 #MAX_POP TRAITORCHAN -1 @@ -169,9 +172,6 @@ MIDROUND_ANTAG ABDUCTION #MIN_POP REVOLUTION 20 #MAX_POP REVOLUTION -1 -#MIN_POP GANG 20 -#MAX_POP GANG -1 - #MIN_POP CULT 24 #MAX_POP CULT -1 @@ -212,6 +212,7 @@ SHUTTLE_REFUEL_DELAY 12000 ## Used as (Antagonists = Population / Coeff) ## Set to 0 to disable scaling and use default numbers instead. TRAITOR_SCALING_COEFF 6 +BROTHER_SCALING_COEFF 6 CHANGELING_SCALING_COEFF 6 ## Variables calculate how number of open security officer positions will scale to population. @@ -222,6 +223,7 @@ SECURITY_SCALING_COEFF 8 ## The number of objectives traitors get. ## Not including escaping/hijacking. TRAITOR_OBJECTIVES_AMOUNT 2 +BROTHER_OBJECTIVES_AMOUNT 2 ## Uncomment to prohibit jobs that start with loyalty ## implants from being most antagonists. diff --git a/tgstation.dme b/tgstation.dme index fc10234dcbb1..aca3ff00f0d1 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -244,6 +244,7 @@ #include "code\datums\spawners_menu.dm" #include "code\datums\verbs.dm" #include "code\datums\antagonists\antag_datum.dm" +#include "code\datums\antagonists\datum_brother.dm" #include "code\datums\antagonists\datum_clockcult.dm" #include "code\datums\antagonists\datum_cult.dm" #include "code\datums\antagonists\datum_internal_affairs.dm" @@ -374,6 +375,8 @@ #include "code\game\gamemodes\game_mode.dm" #include "code\game\gamemodes\objective.dm" #include "code\game\gamemodes\objective_items.dm" +#include "code\game\gamemodes\objective_team.dm" +#include "code\game\gamemodes\brother\traitor_bro.dm" #include "code\game\gamemodes\blob\blob.dm" #include "code\game\gamemodes\blob\blob_finish.dm" #include "code\game\gamemodes\blob\blob_report.dm"
Abductors