diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 98420bc429..e6d0ff4732 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 8a107c23ee..d4b2dfb2db 100644
--- a/code/__DEFINES/atom_hud.dm
+++ b/code/__DEFINES/atom_hud.dm
@@ -41,6 +41,7 @@
#define ANTAG_HUD_SOULLESS 17
#define ANTAG_HUD_CLOCKWORK 18
#define ANTAG_HUD_BORER 19
+#define ANTAG_HUD_BROTHER 20
// Notification action types
#define NOTIFY_JUMP "jump"
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index fbefab15bb..cea6bc5e36 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -23,12 +23,14 @@
#define ROLE_DEVIL "devil"
#define ROLE_SERVANT_OF_RATVAR "servant of Ratvar"
#define ROLE_BORER "borer"
+#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,
@@ -50,4 +52,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 4ba51a9fcb..6a9cf9b963 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
@@ -694,6 +696,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")
@@ -702,6 +706,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 0000000000..1f799b1bf8
--- /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 0d4f7810c6..60a53967ae 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -21,6 +21,7 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
ANTAG_HUD_BORER = 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 5ad9f4aa6d..e824cc9914 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -131,10 +131,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
@@ -194,6 +194,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
@@ -348,6 +353,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 += "
"
+ for(var/datum/mind/M in other_owners)
+ output += "- Conspirator: [M.name]
"
+ output += "
"
if(window)
recipient << browse(output,"window=memory")
@@ -384,7 +395,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)
@@ -404,6 +415,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")
@@ -1284,6 +1310,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 0000000000..ccb6b774b3
--- /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 7eacd9c666..7ff5a9742d 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 baf0f69ab9..8329298910 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.mind
/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 0000000000..ba6b2546ca
--- /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 1b8370068f..77b91a8571 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 b0119b32d6..467f846b3a 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 += "
| Brothers | | |
"
+ 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 += "| [M.real_name][M.client ? "" : " (No Client)"][M.stat == DEAD ? " (DEAD)" : ""] | "
+ dat += "PM | "
+ dat += "FLW | "
+ dat += "Show Objective |
"
+ else
+ dat += "| [brother.name]([brother.key])Brother body destroyed! | "
+ dat += "PM |
"
+ dat += "
"
+
if(SSticker.mode.abductors.len)
dat += "
| Abductors | | |
"
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 8fba45b705..947372bfcb 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 8bc90cf787..b4dee0b59b 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 bc178fcfcd..7d12225f47 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -77,11 +77,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
@@ -103,11 +103,11 @@ PROBABILITY SANDBOX 0
## 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
@@ -129,11 +129,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
@@ -153,6 +153,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
@@ -165,9 +168,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
@@ -208,6 +208,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.
@@ -218,6 +219,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 67ab66c452..eed2515ce3 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -277,6 +277,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"
@@ -410,6 +411,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"