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".
This commit is contained in:
pubby
2017-09-17 18:02:23 -05:00
committed by oranges
parent 464e92d335
commit 1e4d1ca42f
17 changed files with 462 additions and 267 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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
#define RETURNTOLOBBY 3

View File

@@ -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

View File

@@ -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,"<span class='userdanger'>You are no longer the [special_role]!</span>")
owner.special_role = null
return ..()
/datum/antagonist/brother/proc/give_meeting_area()
if(!owner.current || !team || !team.meeting_area)
return
to_chat(owner.current, "<B>Your designated meeting area:</B> [team.meeting_area]")
owner.store_memory("<b>Meeting Area</b>: [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, "<B><font size=3 color=red>You are the [owner.special_role] of [brother_text].</font></B>")
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)

View File

@@ -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

View File

@@ -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 += "<br><B>Objective #[obj_count++]</B>: [objective.explanation_text]"
var/list/datum/mind/other_owners = objective.get_owners() - src
if(other_owners.len)
output += "<ul>"
for(var/datum/mind/M in other_owners)
output += "<li>Conspirator: [M.name]</li>"
output += "</ul>"
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 = "<i><b>[text]</b></i>: "
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 = "<i><b>[text]</b></i>: "
if(src in SSticker.mode.brothers)
text += "<b>Brother</b> | <a href='?src=\ref[src];brother=clear'>no</a>"
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")

View File

@@ -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\
<span class='danger'>Traitors</span>: Accomplish your objectives.\n\
<span class='danger'>Blood Brothers</span>: Accomplish your objectives.\n\
<span class='notice'>Crew</span>: 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 = "<br><font size=4><b>The blood brothers were:</b></font>"
var/teamnumber = 1
for(var/datum/objective_team/brother_team/team in brother_teams)
if(!team.members.len)
continue
text += "<br><font size=3><b>Team #[teamnumber++]</b></font>"
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 += "<br><B>Objective #[objective_count]</B>: [objective.explanation_text] <font color='green'><B>Success!</B></font>"
SSblackbox.add_details("traitor_objective","[objective.type]|SUCCESS")
else
text += "<br><B>Objective #[objective_count]</B>: [objective.explanation_text] <font color='red'>Fail.</font>"
SSblackbox.add_details("traitor_objective","[objective.type]|FAIL")
win = FALSE
objective_count++
if(win)
text += "<br><font color='green'><B>The blood brothers were successful!</B></font>"
SSblackbox.add_details("brother_success","SUCCESS")
else
text += "<br><font color='red'><B>The blood brothers have failed!</B></font>"
SSblackbox.add_details("brother_success","FAIL")
text += "<br>"
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)

View File

@@ -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

View File

@@ -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)
return 0
return 1
return 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)
return 0
return 1
return 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
////////////////////////////////

View File

@@ -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

View File

@@ -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()

View File

@@ -517,6 +517,21 @@
dat += "<td><A href='?priv_msg=[traitor.key]'>PM</A></td></tr>"
dat += "</table>"
if(SSticker.mode.brother_teams.len > 0)
dat += "<br><table cellspacing=5><tr><td><B>Brothers</B></td><td></td><td></td></tr>"
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 += "<tr><td><a href='?_src_=holder;[HrefToken()];adminplayeropts=\ref[M]'>[M.real_name]</a>[M.client ? "" : " <i>(No Client)</i>"][M.stat == DEAD ? " <b><font color=red>(DEAD)</font></b>" : ""]</td>"
dat += "<td><A href='?priv_msg=[M.ckey]'>PM</A></td>"
dat += "<td><A href='?_src_=holder;[HrefToken()];adminplayerobservefollow=\ref[M]'>FLW</a></td>"
dat += "<td><A HREF='?_src_=holder;[HrefToken()];traitor=\ref[M]'>Show Objective</A></td></tr>"
else
dat += "<tr><td><a href='?_src_=vars;[HrefToken()];Vars=\ref[brother]'>[brother.name]([brother.key])</a><i>Brother body destroyed!</i></td>"
dat += "<td><A href='?priv_msg=[brother.key]'>PM</A></td></tr>"
dat += "</table>"
if(SSticker.mode.abductors.len)
dat += "<br><table cellspacing=5><tr><td><B>Abductors</B></td><td></td><td></td></tr>"
for(var/datum/mind/abductor in SSticker.mode.abductors)

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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"