diff --git a/code/controllers/subsystem/SSjobs.dm b/code/controllers/subsystem/SSjobs.dm index dfbe65c22e2..df1e5a16238 100644 --- a/code/controllers/subsystem/SSjobs.dm +++ b/code/controllers/subsystem/SSjobs.dm @@ -599,11 +599,7 @@ SUBSYSTEM_DEF(jobs) for(var/datum/job_objective/objective as anything in H.mind.job_objectives) objective.owner_account = account - var/remembered_info = "" - remembered_info += "Your account number is: #[account.account_number]
" - remembered_info += "Your account pin is: [account.account_pin]
" - - H.mind.store_memory(remembered_info) + H.mind.store_memory("Your account number is: #[account.account_number]
Your account pin is: [account.account_pin]") H.mind.set_initial_account(account) to_chat(H, "As an employee of Nanotrasen you will receive a paycheck of $[account.payday_amount] credits every 30 minutes") diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm index add0e27f548..97841998cda 100644 --- a/code/controllers/subsystem/SSticker.dm +++ b/code/controllers/subsystem/SSticker.dm @@ -589,6 +589,9 @@ SUBSYSTEM_DEF(ticker) if(findtext("[handler]","auto_declare_completion_")) call(mode, handler)() + for(var/datum/team/team in GLOB.antagonist_teams) + team.on_round_end() + // Display the scoreboard window score.scoreboard() diff --git a/code/datums/mind.dm b/code/datums/mind.dm index be8553c082a..e5353c1e011 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -44,7 +44,7 @@ var/role_alt_title var/datum/job/assigned_job - var/list/datum/objective/objectives = list() + var/datum/objective_holder/objective_holder ///a list of objectives that a player with this job could complete for space credit rewards var/list/job_objectives = list() var/list/datum/objective/special_verbs = list() @@ -54,7 +54,10 @@ var/has_been_rev = FALSE var/miming = 0 // Mime's vow of silence + /// A list of all the antagonist datums that the player is (does not include undatumized antags) var/list/antag_datums + /// A lazy list of all teams the player is part of but doesnt have an antag role for (i.e. a custom admin team) + var/list/teams var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD @@ -79,10 +82,12 @@ /datum/mind/New(new_key) key = new_key + objective_holder = new(src) /datum/mind/Destroy() SSticker.minds -= src remove_all_antag_datums() + qdel(objective_holder) current = null kudos_received_from.Cut() return ..() @@ -154,7 +159,7 @@ SEND_SIGNAL(new_character, COMSIG_BODY_TRANSFER_TO) /datum/mind/proc/store_memory(new_text) - memory += "[new_text]
" + memory += "[new_text]
" /datum/mind/proc/wipe_memory() memory = null @@ -162,77 +167,111 @@ /datum/mind/proc/show_memory(mob/recipient, window = 1) if(!recipient) recipient = current - var/output = "[current.real_name]'s Memories:
" - output += memory + var/list/output = list() + output.Add("[current.real_name]'s Memories:
") + output.Add(memory) - var/antag_datum_objectives = FALSE for(var/datum/antagonist/A in antag_datums) - output += A.antag_memory - if(!antag_datum_objectives && LAZYLEN(A.objectives)) - antag_datum_objectives = TRUE + output.Add(A.antag_memory) - if(LAZYLEN(objectives) || antag_datum_objectives) - output += "
Objectives:
" - output += gen_objective_text() + if(has_objectives()) + output.Add("
Objectives:") + output.Add(gen_objective_text()) if(LAZYLEN(job_objectives)) - output += "
Job Objectives:") + + output = output.Join("
") if(window) recipient << browse(output, "window=memory") else to_chat(recipient, "[output]") /datum/mind/proc/gen_objective_text(admin = FALSE) - . = "" - var/obj_count = 1 + if(!has_objectives()) + return "No Objectives.
" + var/list/text = list() + var/obj_count = 1 // If they don't have any objectives, "" will be returned. for(var/datum/objective/objective in get_all_objectives()) - . += "Objective #[obj_count++]: [objective.explanation_text]" - if(admin) - . += " Edit " // Edit - . += "Delete " // Delete + text.Add("Objective #[obj_count++]: [objective.explanation_text][admin ? get_admin_objective_edit(objective) : ""]") - . += "" // Mark Completed - . += "Toggle Completion" - . += "" - . += "
" + return text.Join("
") + +/datum/mind/proc/get_admin_objective_edit(datum/objective/objective) + return " Edit \ + Delete \ + \ + Toggle Completion" + +/** + * A quicker version of get_all_objectives() but only for seeing if they have any objectives at all + */ +/datum/mind/proc/has_objectives(include_team = TRUE) + if(objective_holder.has_objectives()) + return TRUE + for(var/datum/antagonist/A as anything in antag_datums) + if(A.has_antag_objectives(include_team)) // this checks teams also + return TRUE + // For custom non-antag role teams + if(include_team && LAZYLEN(teams)) + for(var/datum/team/team as anything in teams) + if(team.objective_holder.has_objectives()) + return TRUE + return FALSE /** * Gets every objective this mind owns, including all of those from any antag datums they have, and returns them as a list. */ -/datum/mind/proc/get_all_objectives() +/datum/mind/proc/get_all_objectives(include_team = TRUE) var/list/all_objectives = list() - for(var/antag in antag_datums) - var/datum/antagonist/A = antag - all_objectives += A.objectives // Add all antag datum objectives. + all_objectives += objective_holder.get_objectives() // Get their personal objectives - for(var/objective in objectives) - var/datum/objective/O = objective - all_objectives += O // Add all mind objectives. + for(var/datum/antagonist/A as anything in antag_datums) + all_objectives += A.objective_holder.get_objectives() // Add all antag datum objectives. + if(include_team) + var/datum/team/team = A.get_team() + if(team) // have to make asure a team exists here, team?. does not work below because it will add the null to the list + all_objectives += team.objective_holder.get_objectives() // Get all of their teams' objectives + + // For custom non-antag role teams + if(include_team && LAZYLEN(teams)) + for(var/datum/team/team as anything in teams) + all_objectives += team.objective_holder.get_objectives() return all_objectives /** - * Completely remove the given objective from the src mind and it's antag datums. + * Add an objective to the mind */ -/datum/mind/proc/remove_objective(datum/objective/O) - for(var/antag in antag_datums) - var/datum/antagonist/A = antag - A.objectives -= O - A.assigned_targets -= "[O.target]" - if(istype(O, /datum/objective/steal)) - var/datum/objective/steal/S = O - A.assigned_targets -= "[S.steal_target]" - objectives -= O - qdel(O) +/datum/mind/proc/add_mind_objective(datum/objective/O, _explanation_text, mob/target_override) + if(ispath(O)) + O = new O() + if(O.owner) + stack_trace("[O], [O.type] was assigned as an objective to [src] (mind), but already had an owner: [O.owner] (mind). Overriding.") + O.owner = src + return objective_holder.add_objective(O, _explanation_text, target_override) + +/** + * Completely remove the given objective from the mind, and include antagdatums/teams if remove_from_everything is true + */ +/datum/mind/proc/remove_mind_objective(datum/objective/O, remove_from_everything) + . = objective_holder.remove_objective(O) + + if(!remove_from_everything) + return + for(var/datum/antagonist/A as anything in antag_datums) + A.objective_holder.remove_objective(O) // Add all antag datum objectives. + var/datum/team/team = A.get_team() + team?.objective_holder.remove_objective(O) // Get all of their teams' objectives /datum/mind/proc/_memory_edit_header(gamemode, list/alt) . = gamemode @@ -254,9 +293,10 @@ /datum/mind/proc/memory_edit_revolution(mob/living/carbon/human/H) . = _memory_edit_header("revolution") + var/datum/antagonist/rev = has_antag_datum(/datum/antagonist/rev) if(ismindshielded(H)) . += "NO|headrev|rev" - else if(has_antag_datum(/datum/antagonist/rev/head)) + else if(istype(rev, /datum/antagonist/rev/head)) . += "no|HEADREV|rev" . += "
Flash: give" @@ -271,10 +311,9 @@ . += "." . += " Reequip (gives flash/cham sec hud)." - var/datum/antagonist/rev/revolting = has_antag_datum(/datum/antagonist/rev) - if(!length(revolting.objectives)) + if(!rev.has_antag_objectives()) // if theres anything missing here, we want it to runtime. There should never be a rev without a rev team . += "
Objectives are empty! Unless theres no command, this is likely a bug, please report it! Set to kill all heads." - else if(has_antag_datum(/datum/antagonist/rev)) + else if(rev) . += "no|headrev|REV" else . += "NO|headrev|rev" @@ -296,7 +335,7 @@ if(src in SSticker.mode.wizards) . += "WIZARD|no" . += "
To lair, undress, dress up, let choose name." - if(objectives.len==0) + if(!objective_holder.has_objectives()) . += "
Objectives are empty! Randomize!" else . += "wizard|NO" @@ -308,7 +347,7 @@ var/datum/antagonist/changeling/cling = has_antag_datum(/datum/antagonist/changeling) if(cling) . += "CHANGELING|no" - if(!length(cling.objectives)) + if(!cling.has_antag_objectives()) . += "
Objectives are empty! Randomize!" if(length(cling.absorbed_dna)) var/datum/dna/DNA = cling.absorbed_dna[1] @@ -330,7 +369,7 @@ . += "
Subclass: [has_subclass ? capitalize(vamp.subclass.name) : "None"]" if(has_subclass) . += " | Force full power: [vamp.subclass.full_power_override ? "Yes" : "No"]" - if(!length(vamp.objectives)) + if(!vamp.has_antag_objectives()) . += "
Objectives are empty! Randomize!" else . += "vampire|NO" @@ -382,7 +421,7 @@ if(has_antag_datum(/datum/antagonist/traitor)) . += "TRAITOR|no" var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - if(!length(T.objectives)) + if(!T.has_antag_objectives()) . += "
Objectives are empty! Randomize!" else . += "traitor|NO" @@ -468,10 +507,10 @@ alert("Not before round-start!", "Alert") return - var/out = "[name][(current && (current.real_name != name))?" (as [current.real_name])" : ""]
" - out += "Mind currently owned by key: [key] [active ? "(synced)" : "(not synced)"]
" - out += "Assigned role: [assigned_role]. Edit
" - out += "Factions and special roles:
" + var/list/out = list("[name][(current && (current.real_name != name))?" (as [current.real_name])" : ""]") + out.Add("Mind currently owned by key: [key] [active ? "(synced)" : "(not synced)"]") + out.Add("Assigned role: [assigned_role]. Edit") + out.Add("Factions and special roles:") var/list/sections = list( "implant", @@ -514,42 +553,38 @@ */ if(SSticker.mode.config_tag == "traitorchan") if(sections["traitor"]) - out += sections["traitor"] + "
" + out.Add(sections["traitor"]) if(sections["changeling"]) - out += sections["changeling"] + "
" + out.Add(sections["changeling"]) sections -= "traitor" sections -= "changeling" // Elif technically unnecessary but it makes the following else look better else if(SSticker.mode.config_tag == "traitorvamp") if(sections["traitor"]) - out += sections["traitor"] + "
" + out.Add(sections["traitor"]) if(sections["vampire"]) - out += sections["vampire"] + "
" + out.Add(sections["vampire"]) sections -= "traitor" sections -= "vampire" else if(sections[SSticker.mode.config_tag]) - out += sections[SSticker.mode.config_tag] + "
" + out.Add(sections[SSticker.mode.config_tag]) sections -= SSticker.mode.config_tag for(var/i in sections) if(sections[i]) - out += sections[i] + "
" + out.Add(sections[i]) - out += memory_edit_uplink() - out += "
" + out.Add(memory_edit_uplink()) - out += "Memory:
" - out += memory - out += "
Edit memory
" - out += "Objectives:
" - if(!length(get_all_objectives())) - out += "EMPTY
" - else - out += gen_objective_text(admin = TRUE) - out += "Add objective

" - out += "Announce objectives

" - usr << browse(out, "window=edit_memory[src];size=500x500") + out.Add("Memory:") + out.Add(memory) + out.Add("Edit memory
") + out.Add("Objectives:") + out.Add(gen_objective_text(admin = TRUE)) + out.Add("Add objective
") + out.Add("Announce objectives
") + usr << browse(out.Join("
"), "window=edit_memory[src];size=500x500") /datum/mind/Topic(href, href_list) if(!check_rights(R_ADMIN)) @@ -576,7 +611,6 @@ else if(href_list["obj_edit"] || href_list["obj_add"]) var/datum/objective/objective - var/list/objective_pos var/def_value if(href_list["obj_edit"]) @@ -584,13 +618,6 @@ if(!objective) return - if(objectives.Find(objective)) - objective_pos = list(objectives.Find(objective), null) - else - for(var/datum/antagonist/A as anything in antag_datums) - if(A.objectives.Find(objective)) - objective_pos = list(A.objectives.Find(objective), A) - //Text strings are easy to manipulate. Revised for simplicity. var/temp_obj_type = "[objective.type]"//Convert path into a text string. def_value = copytext(temp_obj_type, 18)//Convert last part of path into an objective keyword. @@ -646,12 +673,10 @@ var/objective_path = text2path("/datum/objective/[new_obj_type]") if(new_target == "Free objective") new_objective = new objective_path - new_objective.owner = src new_objective:target = null new_objective.explanation_text = "Free objective" else new_objective = new objective_path - new_objective.owner = src new_objective:target = new_target:mind //Will display as special role if assigned mode is equal to special role.. Ninjas/commandos/nuke ops. new_objective.explanation_text = "[objective_type] [new_target:real_name], the [new_target:mind:assigned_role == new_target:mind:special_role ? (new_target:mind:special_role) : (new_target:mind:assigned_role)]." @@ -662,35 +687,28 @@ var/mob/new_target = input("Select target:", "Objective target") as null|anything in possible_targets new_objective = new /datum/objective/destroy new_objective.target = new_target.mind - new_objective.owner = src new_objective.explanation_text = "Destroy [new_target.name], the experimental AI." else to_chat(usr, "No active AIs with minds") if("prevent") - new_objective = new /datum/objective/block - new_objective.owner = src + new_objective = /datum/objective/block // we can place paths here because they will be created as needed in the objective holder if("hijack") - new_objective = new /datum/objective/hijack - new_objective.owner = src + new_objective = /datum/objective/hijack if("escape") - new_objective = new /datum/objective/escape - new_objective.owner = src + new_objective = /datum/objective/escape if("survive") - new_objective = new /datum/objective/survive - new_objective.owner = src + new_objective = /datum/objective/survive if("nuclear") - new_objective = new /datum/objective/nuclear - new_objective.owner = src + new_objective = /datum/objective/nuclear if("steal") if(!istype(objective, /datum/objective/steal)) new_objective = new /datum/objective/steal - new_objective.owner = src else new_objective = objective var/datum/objective/steal/steal = new_objective @@ -713,7 +731,6 @@ if("blood") new_objective = new /datum/objective/blood new_objective.explanation_text = "Accumulate at least [target_number] total units of blood." - new_objective.owner = src new_objective.target_amount = target_number if("identity theft") @@ -730,7 +747,6 @@ if(!istype(targ)) CRASH("Invalid target for identity theft objective, cancelling") new_objective = new /datum/objective/escape/escape_with_identity - new_objective.owner = src new_objective.target = new_target new_objective.explanation_text = "Escape on the shuttle or an escape pod with the identity of [targ.current.real_name], the [targ.assigned_role] while wearing [targ.current.p_their()] identification card." var/datum/objective/escape/escape_with_identity/O = new_objective @@ -740,21 +756,15 @@ if(!expl) return new_objective = new /datum/objective - new_objective.owner = src new_objective.explanation_text = expl if(!new_objective) return if(objective) - remove_objective(objective) - if(objective_pos[2]) - var/datum/antagonist/A = objective_pos[2] - A.objectives.Insert(objective_pos[1], new_objective) - else - objectives.Insert(objective_pos[1], new_objective) + objective.holder.replace_objective(objective, new_objective) // replace it in its old holder else - objectives += new_objective + add_mind_objective(new_objective) log_admin("[key_name(usr)] has updated [key_name(current)]'s objectives: [new_objective]") message_admins("[key_name_admin(usr)] has updated [key_name_admin(current)]'s objectives: [new_objective]") @@ -766,7 +776,7 @@ log_admin("[key_name(usr)] has removed one of [key_name(current)]'s objectives: [objective]") message_admins("[key_name_admin(usr)] has removed one of [key_name_admin(current)]'s objectives: [objective]") - remove_objective(objective) + remove_mind_objective(objective, TRUE) else if(href_list["obj_completed"]) var/datum/objective/objective = locate(href_list["obj_completed"]) @@ -1094,9 +1104,7 @@ SSticker.mode.syndicates -= src SSticker.mode.update_synd_icons_removed(src) special_role = null - for(var/datum/objective/nuclear/O in objectives) - objectives-=O - qdel(O) + objective_holder.clear(/datum/objective/nuclear) to_chat(current, "You have been brainwashed! You are no longer a syndicate operative!") log_admin("[key_name(usr)] has de-nuke op'd [key_name(current)]") message_admins("[key_name_admin(usr)] has de-nuke op'd [key_name_admin(current)]") @@ -1111,7 +1119,7 @@ special_role = SPECIAL_ROLE_NUKEOPS to_chat(current, "You are a [syndicate_name()] agent!") SSticker.mode.forge_syndicate_objectives(src) - SSticker.mode.greet_syndicate(src) + SSticker.mode.greet_syndicate(src, FALSE) // False to fix the agent message appearing twice log_admin("[key_name(usr)] has nuke op'd [key_name(current)]") message_admins("[key_name_admin(usr)] has nuke op'd [key_name_admin(current)]") if("lair") @@ -1561,11 +1569,14 @@ else if(A.type == datum_type) return A -/datum/mind/proc/announce_objectives() - if(current) - to_chat(current, "Your current objectives:") - for(var/line in splittext(gen_objective_text(), "
")) - to_chat(current, line) +/datum/mind/proc/announce_objectives(title = TRUE) + if(!current) + return + var/list/text = list() + if(title) + text.Add("Your current objectives:") + text.Add(gen_objective_text()) + to_chat(current, text.Join("
")) /datum/mind/proc/find_syndicate_uplink() var/list/L = current.get_contents() @@ -1595,7 +1606,7 @@ assigned_role = SPECIAL_ROLE_NUKEOPS to_chat(current, "You are a [syndicate_name()] agent!") SSticker.mode.forge_syndicate_objectives(src) - SSticker.mode.greet_syndicate(src) + SSticker.mode.greet_syndicate(src, FALSE) // False to fix the agent message appearing twice current.loc = get_turf(locate("landmark*Syndicate-Spawn")) @@ -1656,13 +1667,8 @@ SSticker.mode.abductors |= src - var/datum/objective/stay_hidden/hidden_obj = new - hidden_obj.owner = src - objectives += hidden_obj - - var/datum/objective/experiment/O = new - O.owner = src - objectives += O + add_mind_objective(/datum/objective/stay_hidden) + add_mind_objective(/datum/objective/experiment) var/mob/living/carbon/human/H = current diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index e6aa485df90..261f86f05b2 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -79,7 +79,7 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E text += "
Changeling ID: [cling.changelingID]." text += "
Genomes Extracted: [cling.absorbed_count]" - var/list/all_objectives = changeling.get_all_objectives() + var/list/all_objectives = changeling.get_all_objectives(include_team = FALSE) if(length(all_objectives)) var/count = 1 diff --git a/code/game/gamemodes/cult/cult_mode.dm b/code/game/gamemodes/cult/cult_mode.dm index da277e12390..953bb5f0f12 100644 --- a/code/game/gamemodes/cult/cult_mode.dm +++ b/code/game/gamemodes/cult/cult_mode.dm @@ -87,9 +87,7 @@ GLOBAL_LIST_EMPTY(all_cults) to_chat(cult_mind.current, CULT_GREETING) equip_cultist(cult_mind.current) cult_mind.current.faction |= "cult" - var/datum/objective/servecult/obj = new - obj.owner = cult_mind - cult_mind.objectives += obj + cult_mind.add_mind_objective(/datum/objective/servecult) if(cult_mind.assigned_role == "Clown") to_chat(cult_mind.current, "A dark power has allowed you to overcome your clownish nature, letting you wield weapons without harming yourself.") @@ -159,9 +157,7 @@ GLOBAL_LIST_EMPTY(all_cults) cult_objs.setup() update_cult_icons_added(cult_mind) add_cult_actions(cult_mind) - var/datum/objective/servecult/obj = new - obj.owner = cult_mind - cult_mind.objectives += obj + cult_mind.add_mind_objective(/datum/objective/servecult) if(cult_risen) rise(cult_mind.current) @@ -182,9 +178,7 @@ GLOBAL_LIST_EMPTY(all_cults) cult -= cult_mind cultist.faction -= "cult" cult_mind.special_role = null - for(var/datum/objective/servecult/S in cult_mind.objectives) - cult_mind.objectives -= S - qdel(S) + cult_mind.objective_holder.clear(/datum/objective/servecult) for(var/datum/action/innate/cult/C in cultist.actions) qdel(C) update_cult_icons_removed(cult_mind) diff --git a/code/game/gamemodes/cult/cult_objectives.dm b/code/game/gamemodes/cult/cult_objectives.dm index ca8921b7534..fe5816e1654 100644 --- a/code/game/gamemodes/cult/cult_objectives.dm +++ b/code/game/gamemodes/cult/cult_objectives.dm @@ -120,6 +120,7 @@ /datum/objective/servecult //Given to cultists on conversion/roundstart explanation_text = "Assist your fellow cultists and Tear the Veil! (Use the Study Veil action to check your progress.)" completed = TRUE + needs_target = FALSE /datum/objective/sacrifice var/sacced = FALSE @@ -150,6 +151,7 @@ /datum/objective/eldergod + needs_target = FALSE var/summoned = FALSE var/killed = FALSE var/list/summon_spots = list() diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 748a81cea02..a661a64262e 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -388,15 +388,6 @@ Think through your actions and make the roleplay immersive! Please remember all \ rules aside from those without explicit exceptions apply to antagonists.") -/proc/show_objectives(datum/mind/player) - if(!player || !player.current) return - - var/obj_count = 1 - to_chat(player.current, "Your current objectives:") - for(var/datum/objective/objective in player.objectives) - to_chat(player.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ - /proc/get_roletext(role) return role @@ -466,7 +457,7 @@ /proc/printobjectives(datum/mind/ply) var/list/objective_parts = list() var/count = 1 - for(var/datum/objective/objective in ply.objectives) + for(var/datum/objective/objective in ply.get_all_objectives(include_team = FALSE)) if(objective.check_completion()) objective_parts += "Objective #[count]: [objective.explanation_text] Success!" else diff --git a/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm b/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm index f0a753d1bb9..82f986f5d4a 100644 --- a/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm +++ b/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm @@ -1,31 +1,36 @@ /datum/objective/abductee - completed = 1 + completed = TRUE + needs_target = FALSE /datum/objective/abductee/steal explanation_text = "Steal all" /datum/objective/abductee/steal/New() + ..() var/target = pick(list("pets","lights","monkeys","fruits","shoes","bars of soap", "weapons", "computers", "organs")) - explanation_text+=" [target]." + explanation_text +=" [target]." /datum/objective/abductee/paint explanation_text = "The station is hideous. You must color it all" /datum/objective/abductee/paint/New() + ..() var/color = pick(list("red", "blue", "green", "yellow", "orange", "purple", "black", "in rainbows", "in blood")) - explanation_text+= " [color]!" + explanation_text += " [color]!" /datum/objective/abductee/speech explanation_text = "Your brain is broken... you can only communicate in" /datum/objective/abductee/speech/New() + ..() var/style = pick(list("pantomime", "rhyme", "haiku", "extended metaphors", "riddles", "extremely literal terms", "sound effects", "military jargon")) - explanation_text+= " [style]." + explanation_text += " [style]." /datum/objective/abductee/capture explanation_text = "Capture" /datum/objective/abductee/capture/New() + ..() var/list/jobs = SSjobs.occupations.Copy() for(var/datum/job/J in jobs) if(J.current_positions < 1) @@ -88,6 +93,7 @@ explanation_text = "Call forth a spirit from the other side." /datum/objective/abductee/calling/New() + ..() var/mob/dead/D = pick(GLOB.dead_mob_list) if(D) explanation_text = "You know that [D] has perished. Hold a seance to call them from the spirit realm." diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm index 367c7883773..6180377f498 100644 --- a/code/game/gamemodes/miniantags/abduction/abduction.dm +++ b/code/game/gamemodes/miniantags/abduction/abduction.dm @@ -151,8 +151,8 @@ /datum/game_mode/abduction/proc/greet_agent(datum/mind/abductor,team_number) var/datum/objective/stay_hidden/O = new - abductor.objectives += O - abductor.objectives += team_objectives[team_number] + abductor.add_mind_objective(O) + abductor.objective_holder.add_objective(team_objectives[team_number]) // this needs to be changed when abductor teams are changed to actual antag teams var/team_name = team_names[team_number] SEND_SOUND(abductor.current, sound('sound/ambience/antag/abductors.ogg')) @@ -166,8 +166,8 @@ /datum/game_mode/abduction/proc/greet_scientist(datum/mind/abductor,team_number) var/datum/objective/stay_hidden/O = new - abductor.objectives += O - abductor.objectives += team_objectives[team_number] + abductor.add_mind_objective(O) + abductor.objective_holder.add_objective(team_objectives[team_number]) // this needs to be changed when abductor teams are changed to actual antag teams var/team_name = team_names[team_number] SEND_SOUND(abductor.current, sound('sound/ambience/antag/abductors.ogg')) @@ -238,19 +238,21 @@ // OBJECTIVES +//No check completion, it defaults to being completed unless an admin sets it to failed. +/datum/objective/stay_hidden + explanation_text = "Limit contact with your targets outside of conducting your experiments and abduction." + completed = TRUE + needs_target = FALSE + /datum/objective/experiment + explanation_text = "Experiment on some humans." target_amount = 6 + needs_target = FALSE /// Which abductor team number does this belong to. var/abductor_team_number -/datum/objective/stay_hidden - -/datum/objective/stay_hidden/New() - explanation_text = "Limit contact with your targets outside of conducting your experiments and abduction." - completed = TRUE -//No check completion, it defaults to being completed unless an admin sets it to failed. - /datum/objective/experiment/New() + ..() explanation_text = "Experiment on [target_amount] humans." /datum/objective/experiment/check_completion() diff --git a/code/game/gamemodes/miniantags/abduction/machinery/experiment.dm b/code/game/gamemodes/miniantags/abduction/machinery/experiment.dm index 445f30c5127..0f73085178e 100644 --- a/code/game/gamemodes/miniantags/abduction/machinery/experiment.dm +++ b/code/game/gamemodes/miniantags/abduction/machinery/experiment.dm @@ -144,15 +144,12 @@ sleep(5) to_chat(H, "Your mind snaps!") to_chat(H, "You can't remember how you got here...") + SSticker.mode.abductees += H.mind + var/objtype = pick(subtypesof(/datum/objective/abductee/)) var/datum/objective/abductee/O = new objtype() - SSticker.mode.abductees += H.mind - H.mind.objectives += O - var/obj_count = 1 - to_chat(H, "Your current objectives:") - for(var/datum/objective/objective in H.mind.objectives) - to_chat(H, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ + H.mind.add_mind_objective(O) + H.mind.announce_objectives() // let the player know they have a new objective SSticker.mode.update_abductor_icons_added(H.mind) for(var/obj/item/organ/internal/heart/gland/G in H.internal_organs) diff --git a/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm b/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm index b8f0a140b0f..875e88295fb 100644 --- a/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm +++ b/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm @@ -51,17 +51,11 @@ to_chat(src, src.playstyle_string) to_chat(src, "You are not currently in the same plane of existence as the station. Use the blood crawl action at a blood pool to manifest.") SEND_SOUND(src, sound('sound/misc/demon_dies.ogg')) - if(!(vialspawned)) - var/datum/objective/slaughter/objective = new - var/datum/objective/demon_fluff/fluffObjective = new + if(!vialspawned) SSticker.mode.traitors |= mind - objective.owner = mind - fluffObjective.owner = mind - //Paradise Port:I added the objective for one spawned like this - mind.objectives += objective - mind.objectives += fluffObjective - to_chat(src, "Objective #[1]: [objective.explanation_text]") - to_chat(src, "Objective #[2]: [fluffObjective.explanation_text]") + mind.add_mind_objective(/datum/objective/slaughter) + mind.add_mind_objective(/datum/objective/demon_fluff) + mind.announce_objectives(title = FALSE) to_chat(src, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Slaughter_Demon)") @@ -156,11 +150,9 @@ SSticker.mode.add_cultist(S.mind) var/obj/effect/proc_holder/spell/sense_victims/SV = new AddSpell(SV) - var/datum/objective/new_objective = new /datum/objective - new_objective.owner = S.mind - new_objective.explanation_text = "Bring forth the Slaughter to the nonbelievers." - S.mind.objectives += new_objective - to_chat(S, "Objective #[1]: [new_objective.explanation_text]") + + S.mind.add_mind_objective(/datum/objective/cult_slaughter) + S.mind.announce_objectives(title = FALSE) ////////////////////The Powers @@ -357,3 +349,7 @@ /datum/objective/demon_fluff/check_completion() return TRUE + +/datum/objective/cult_slaughter + explanation_text = "Bring forth the Slaughter to the nonbelievers." + needs_target = FALSE diff --git a/code/game/gamemodes/miniantags/morph/morph.dm b/code/game/gamemodes/miniantags/morph/morph.dm index 42d7afa799d..9af2b94f59d 100644 --- a/code/game/gamemodes/miniantags/morph/morph.dm +++ b/code/game/gamemodes/miniantags/morph/morph.dm @@ -314,17 +314,19 @@ to_chat(src, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Morph)") SEND_SOUND(src, sound('sound/magic/mutate.ogg')) if(give_default_objectives) - var/datum/objective/eat = new /datum/objective - eat.owner = mind - eat.explanation_text = "Eat as many living beings as possible to still the hunger within you." - eat.completed = TRUE - mind.objectives += eat - var/datum/objective/procreate = new /datum/objective - procreate.owner = mind - procreate.explanation_text = "Split yourself in as many other [name]'s as possible!" - procreate.completed = TRUE - mind.objectives += procreate - mind.announce_objectives() + mind.add_mind_objective(/datum/objective/morph_eat) + mind.add_mind_objective(/datum/objective/morph_procreate) + mind.announce_objectives(title = FALSE) + +/datum/objective/morph_eat + explanation_text = "Eat as many living beings as possible to still the hunger within you." + completed = TRUE + needs_target = FALSE + +/datum/objective/morph_procreate + explanation_text = "Split yourself in as many other morphs as possible!" + completed = TRUE + needs_target = FALSE #undef MORPHED_SPEED #undef ITEM_EAT_COST diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm index 9679383fba7..0dbde41f3c7 100644 --- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm +++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm @@ -248,15 +248,11 @@ greeting.Add("If the wire or power source you're connected to runs out of power you'll start losing health and eventually die, but you are otherwise immune to damage.") greeting.Add("For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Pulse_Demon)") to_chat(src, greeting.Join("
")) - var/amount_of_objectives = 1 - var/list/objective_types = list(/datum/objective/pulse_demon/infest, /datum/objective/pulse_demon/drain, /datum/objective/pulse_demon/tamper) - for(var/p in objective_types) - var/datum/objective/objective_to_give = new p - objective_to_give.owner = mind - mind.objectives += objective_to_give - to_chat(src, "Objective #[amount_of_objectives++]: [objective_to_give.explanation_text]") + for(var/datum/objective/new_obj in list(/datum/objective/pulse_demon/infest, /datum/objective/pulse_demon/drain, /datum/objective/pulse_demon/tamper)) + mind.add_mind_objective(new_obj) + mind.announce_objectives(title = FALSE) SSticker.mode.traitors |= mind - return amount_of_objectives + return /mob/living/simple_animal/demon/pulse_demon/proc/give_spells() AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/cycle_camera) diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm index 1d38ab23961..16cea4b2b29 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant.dm @@ -173,25 +173,21 @@ qdel(src) /mob/living/simple_animal/revenant/proc/giveObjectivesandGoals() - mind.wipe_memory() - SEND_SOUND(src, sound('sound/effects/ghost.ogg')) - to_chat(src, "
") - to_chat(src, "You are a revenant.") - to_chat(src, "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.") - to_chat(src, "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.") - to_chat(src, "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.") - to_chat(src, "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.") - to_chat(src, "You do not remember anything of your past lives, nor will you remember anything about this one after your death.") - to_chat(src, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Revenant)") - var/datum/objective/revenant/objective = new - objective.owner = mind - mind.objectives += objective - to_chat(src, "Objective #1: [objective.explanation_text]") - var/datum/objective/revenantFluff/objective2 = new - objective2.owner = mind - mind.objectives += objective2 - to_chat(src, "Objective #2: [objective2.explanation_text]") - SSticker.mode.traitors |= mind //Necessary for announcing + mind.wipe_memory() // someone kill this and give revenants their own minds please + SEND_SOUND(src, sound('sound/effects/ghost.ogg')) + to_chat(src, "
") + to_chat(src, "You are a revenant.") + to_chat(src, "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.") + to_chat(src, "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.") + to_chat(src, "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.") + to_chat(src, "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.") + to_chat(src, "You do not remember anything of your past lives, nor will you remember anything about this one after your death.") + to_chat(src, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Revenant)") + + SSticker.mode.traitors |= mind //Necessary for announcing + mind.add_mind_objective(/datum/objective/revenant) + mind.add_mind_objective(/datum/objective/revenantFluff) + mind.announce_objectives(title = FALSE) /mob/living/simple_animal/revenant/proc/giveSpells() mind.AddSpell(new /obj/effect/proc_holder/spell/night_vision/revenant(null)) @@ -314,6 +310,7 @@ icon_state = icon_idle /datum/objective/revenant + needs_target = FALSE var/targetAmount = 100 /datum/objective/revenant/New() @@ -333,6 +330,7 @@ return TRUE /datum/objective/revenantFluff + needs_target = FALSE /datum/objective/revenantFluff/New() var/list/explanationTexts = list("Assist and exacerbate existing threats at critical moments.", \ diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm index 8dabb97a2e5..e16eddc3832 100644 --- a/code/game/gamemodes/nuclear/nuclear.dm +++ b/code/game/gamemodes/nuclear/nuclear.dm @@ -65,8 +65,7 @@ if(operative_mind in syndicates) SSticker.mode.syndicates -= operative_mind operative_mind.special_role = null - for(var/datum/objective/nuclear/O in operative_mind.objectives) - operative_mind.objectives -= O + operative_mind.objective_holder.clear(/datum/objective/nuclear) operative_mind.current.create_attack_log("No longer nuclear operative") operative_mind.current.create_log(CONVERSION_LOG, "No longer nuclear operative") if(issilicon(operative_mind.current)) @@ -238,19 +237,14 @@ message_admins("Warning: Operative [key_name_admin(synd_mind.current)] spawned without an ID card!") /datum/game_mode/proc/forge_syndicate_objectives(datum/mind/syndicate) - var/datum/objective/nuclear/syndobj = new - syndobj.owner = syndicate - syndicate.objectives += syndobj - + syndicate.add_mind_objective(/datum/objective/nuclear) /datum/game_mode/proc/greet_syndicate(datum/mind/syndicate, you_are=1) SEND_SOUND(syndicate.current, sound('sound/ambience/antag/ops.ogg')) if(you_are) to_chat(syndicate.current, "You are a [syndicate_name()] agent!") - var/obj_count = 1 - for(var/datum/objective/objective in syndicate.objectives) - to_chat(syndicate.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ + + syndicate.announce_objectives(title = FALSE) to_chat(syndicate.current, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Nuclear_Agent)") syndicate.current.create_log(MISC_LOG, "[syndicate.current] was made into a nuclear operative") diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 55fbefde2dd..d4d9c1d3e60 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -33,7 +33,11 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /// If the objective is compatible with martyr objective, i.e. if you can still do it while dead. var/martyr_compatible = FALSE + var/datum/objective_holder/holder + /datum/objective/New(text, datum/team/team_to_join) + . = ..() + SHOULD_CALL_PARENT(TRUE) GLOB.all_objectives += src if(text) explanation_text = text @@ -50,6 +54,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /datum/objective/proc/check_completion() return completed +/datum/objective/proc/found_target() + return target + /** * Get all owners of the objective, including ones from the objective's team, if it has one. * @@ -122,10 +129,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /datum/objective/proc/post_target_cryo(list/owners) find_target() if(!target) - for(var/datum/mind/M in owners) - M.remove_objective(src) - GLOB.all_objectives -= src - qdel(src) + holder.remove_objective(src) + // even if we have to remove the objective, still announce it for(var/datum/mind/M in owners) M.announce_objectives() @@ -192,7 +197,7 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) // We don't want revs to get objectives that aren't for heads of staff. Letting // them win or lose based on cryo is silly so we remove the objective. if(team) - team.remove_objective_from_team(src) + team.remove_team_objective(src) return qdel(src) @@ -488,6 +493,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) martyr_compatible = 0 var/theft_area +/datum/objective/steal/found_target() + return steal_target + /datum/objective/steal/proc/get_location() return steal_target.location_override || "an unknown area" @@ -495,6 +503,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) var/potential = GLOB.potential_theft_objectives.Copy() while(!steal_target && length(potential)) var/thefttype = pick_n_take(potential) + if(locate(thefttype) in target_blacklist) + continue var/datum/theft_objective/O = new thefttype var/has_invalid_owner = FALSE for(var/datum/mind/M in get_owners()) @@ -575,7 +585,11 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) name = "Absorb DNA" needs_target = FALSE -/datum/objective/absorb/proc/gen_amount_goal(lowbound = 4, highbound = 6) +/datum/objective/absorb/New(text, datum/team/team_to_join) + . = ..() + gen_amount_goal() + +/datum/objective/absorb/proc/gen_amount_goal(lowbound = 6, highbound = 8) target_amount = rand (lowbound,highbound) if(SSticker) var/n_p = 1 //autowin @@ -710,13 +724,14 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) // Traders // These objectives have no check_completion, they exist only to tell Sol Traders what to aim for. -/datum/objective/trade/proc/choose_target() - return +/datum/objective/trade + needs_target = FALSE + completed = TRUE -/datum/objective/trade/plasma/choose_target() +/datum/objective/trade/plasma explanation_text = "Acquire at least 15 sheets of plasma through trade." -/datum/objective/trade/credits/choose_target() +/datum/objective/trade/credits explanation_text = "Acquire at least 10,000 credits through trade." //wizard @@ -724,4 +739,4 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /datum/objective/wizchaos explanation_text = "Wreak havoc upon the station as much you can. Send those wandless Nanotrasen scum a message!" needs_target = FALSE - completed = 1 + completed = TRUE diff --git a/code/game/gamemodes/objective_holder.dm b/code/game/gamemodes/objective_holder.dm new file mode 100644 index 00000000000..9d33db630dc --- /dev/null +++ b/code/game/gamemodes/objective_holder.dm @@ -0,0 +1,122 @@ +/** + * An objective holder for minds, antag datums, and teams. + */ + +/datum/objective_holder + /// Our list of current objectives + var/list/datum/objective/objectives = list() + /// Who do we belong to [mind, antagonist, team] + var/datum/objective_owner + /// A list of strings which contain [targets][/datum/objective/var/target] of the antagonist's objectives. Used to prevent duplicate objectives. + var/list/assigned_targets = list() + /// A callback invoked when a new objective is added. This is required because sometimes objectives are added directly without going through objective_owner. Not currently used. + var/datum/callback/on_add_callback + /// A callback invoked when a new objective is added. This is required because sometimes objectives are removed directly without going through objective_owner (EX: replace_objective(), clear()). Not currently used. + var/datum/callback/on_remove_callback + +/datum/objective_holder/New(new_owner) + . = ..() + objective_owner = new_owner + +/datum/objective_holder/Destroy(force, ...) + clear() + return ..() + +/** + * Clear all objectives of a certain type + * * checktype - The type to check, if null, remoe all objectives. + */ +/datum/objective_holder/proc/clear(check_type) + for(var/datum/objective/Objective as anything in objectives) + if(check_type && !istype(Objective, check_type)) + return + remove_objective(Objective) + . = TRUE + +/** + * Sets the callbacks, not on new because that can be irreliable for subtypes. + */ +/datum/objective_holder/proc/set_callbacks(_on_add_callback, _on_remove_callback) + on_add_callback = _on_add_callback + on_remove_callback = _on_remove_callback + +/** + * Do we have any objectives + */ +/datum/objective_holder/proc/has_objectives() + return length(objectives) > 0 + +/** + * Get all of the objectives we own + */ +/datum/objective_holder/proc/get_objectives() + return objectives + +/** + * Replace old_objective with new_objective + */ +/datum/objective_holder/proc/replace_objective(datum/objective/old_objective, datum/objective/new_objective) + new_objective = add_objective(new_objective, add_to_list = FALSE) + new_objective.owner = old_objective.owner + new_objective.team = old_objective.team + // Replace where the old objective was, with the new one + objectives.Insert(objectives.Find(old_objective), new_objective) + remove_objective(old_objective) + +/** + * Add an objective. + * + * * Objective - The objective to add [/datum/objective, path] + * * _explanation_text - Optional, will assign this text to the objective + * * target_override - A target override, will prevent finding a target + * * add_to_list - Do we add the new objective to our list? Or will it be handled elsewhere (like replace_objective). Should not be set to false outside of this file. + */ + +/datum/objective_holder/proc/add_objective(datum/objective/Objective, _explanation_text, mob/target_override, add_to_list = TRUE) + if(ispath(Objective)) + Objective = new Objective() + + Objective.holder = src + + if(add_to_list) + objectives += Objective + + if(target_override) + Objective.target = target_override + else if(Objective.needs_target && !Objective.found_target()) + handle_objective(Objective) + + var/found = Objective.found_target() // in case we are given a target override + if(found) + assigned_targets |= found + + if(_explanation_text) + Objective.explanation_text = _explanation_text + + on_add_callback?.Invoke(objective_owner, Objective) + return Objective + +/** + * Handles the searching of targets for objectives that need it. + */ + +/datum/objective_holder/proc/handle_objective(datum/objective/Objective) + for(var/loop in 1 to 5) + Objective.find_target(assigned_targets) + if(Objective.found_target()) // handles normal objectives, and steal objectives + return + + // We failed to find any target. Oh well... + Objective.explanation_text = "Free Objective" + Objective.target = null + +/** + * Remove an objective and deletes it. You should never need to transfer an objective. + */ +/datum/objective_holder/proc/remove_objective(datum/objective/Objective) + objectives -= Objective + assigned_targets -= Objective.found_target() + + on_remove_callback?.Invoke(objective_owner, Objective) + if(!QDELETED(Objective)) + qdel(Objective) diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index 094266290f9..55a17cd945b 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -89,7 +89,7 @@ if(used_uplink) text += " (used [TC_uses] TC) [purchases]" - var/all_objectives = traitor.get_all_objectives() + var/all_objectives = traitor.get_all_objectives(include_team = FALSE) if(length(all_objectives))//If the traitor had no objectives, don't need to process this. var/count = 1 diff --git a/code/game/gamemodes/vampire/vampire_gamemode.dm b/code/game/gamemodes/vampire/vampire_gamemode.dm index 799128c72fd..688d8c7da8b 100644 --- a/code/game/gamemodes/vampire/vampire_gamemode.dm +++ b/code/game/gamemodes/vampire/vampire_gamemode.dm @@ -67,7 +67,7 @@ text += "body destroyed" text += ")" - var/list/all_objectives = vampire.get_all_objectives() + var/list/all_objectives = vampire.get_all_objectives(include_team = FALSE) if(length(all_objectives))//If the traitor had no objectives, don't need to process this. var/count = 1 diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm index 4919c85ea75..464fcb600af 100644 --- a/code/game/gamemodes/wizard/artefact.dm +++ b/code/game/gamemodes/wizard/artefact.dm @@ -78,11 +78,13 @@ M.mind.name = newname M.real_name = newname M.name = newname + var/datum/objective/protect/new_objective = new /datum/objective/protect new_objective.owner = M.mind new_objective.target = H.mind new_objective.explanation_text = "Protect [H.real_name], the wizard." - M.mind.objectives += new_objective + M.mind.add_mind_objective(new_objective) + SSticker.mode.apprentices += M.mind M.mind.special_role = SPECIAL_ROLE_WIZARD_APPRENTICE SSticker.mode.update_wiz_icons_added(M.mind) @@ -335,21 +337,23 @@ GLOBAL_LIST_EMPTY(multiverse) if(!usr.mind.special_role) if(prob(probability_evil)) to_chat(user, "With your new found power you could easily conquer the station!") + var/datum/objective/hijackclone/hijack_objective = new /datum/objective/hijackclone - hijack_objective.owner = usr.mind - usr.mind.objectives += hijack_objective hijack_objective.explanation_text = "Ensure only [usr.real_name] and [usr.p_their()] copies are on the shuttle!" - to_chat(usr, "Objective #[1]: [hijack_objective.explanation_text]") + usr.mind.add_mind_objective(hijack_objective) + usr.mind.announce_objectives(title = FALSE) + SSticker.mode.traitors += usr.mind usr.mind.special_role = "[usr.real_name] Prime" evil = TRUE else to_chat(user, "With your new found power you could easily defend the station!") + var/datum/objective/survive/new_objective = new /datum/objective/survive - new_objective.owner = usr.mind new_objective.explanation_text = "Survive, and help defend the innocent from the mobs of multiverse clones." - to_chat(usr, "Objective #[1]: [new_objective.explanation_text]") - usr.mind.objectives += new_objective + usr.mind.add_mind_objective(new_objective) + usr.mind.announce_objectives(title = FALSE) + SSticker.mode.traitors += usr.mind usr.mind.special_role = "[usr.real_name] Prime" evil = FALSE @@ -402,19 +406,19 @@ GLOBAL_LIST_EMPTY(multiverse) if(evil) var/datum/objective/hijackclone/hijack_objective = new /datum/objective/hijackclone - hijack_objective.owner = M.mind - M.mind.objectives += hijack_objective hijack_objective.explanation_text = "Ensure only [usr.real_name] and [usr.p_their()] copies are on the shuttle!" - to_chat(M, "Objective #[1]: [hijack_objective.explanation_text]") + M.mind.add_mind_objective(hijack_objective) + M.mind.announce_objectives(title = FALSE) + M.mind.special_role = SPECIAL_ROLE_MULTIVERSE log_game("[M.key] was made a multiverse traveller with the objective to help [usr.real_name] hijack.") else var/datum/objective/protect/new_objective = new /datum/objective/protect - new_objective.owner = M.mind new_objective.target = usr.mind new_objective.explanation_text = "Protect [usr.real_name], your copy, and help [usr.p_them()] defend the innocent from the mobs of multiverse clones." - M.mind.objectives += new_objective - to_chat(M, "Objective #[1]: [new_objective.explanation_text]") + M.mind.add_mind_objective(new_objective) + M.mind.announce_objectives(title = FALSE) + M.mind.special_role = SPECIAL_ROLE_MULTIVERSE log_game("[M.key] was made a multiverse traveller with the objective to help [usr.real_name] protect the station.") diff --git a/code/game/gamemodes/wizard/raginmages.dm b/code/game/gamemodes/wizard/raginmages.dm index 3c5cb10c03b..bc23c2791b4 100644 --- a/code/game/gamemodes/wizard/raginmages.dm +++ b/code/game/gamemodes/wizard/raginmages.dm @@ -21,11 +21,8 @@ to_chat(wizard.current, "You are the Space Wizard!") to_chat(wizard.current, "The Space Wizard Federation has given you the following tasks:") - var/obj_count = 1 to_chat(wizard.current, "Supreme Objective: Make sure the station pays for its actions against our diplomats. We might send more Wizards to the station if the situation is not developing in our favour.") - for(var/datum/objective/objective in wizard.objectives) - to_chat(wizard.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ + wizard.announce_objectives(title = FALSE) wizard.current.create_log(MISC_LOG, "[wizard.current] was made into a wizard") /datum/game_mode/wizard/raginmages/check_finished() diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index 015f595f634..fdceb146cf2 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -79,10 +79,7 @@ set_antag_hud(wiz_mind.current, null) /datum/game_mode/proc/forge_wizard_objectives(datum/mind/wizard) - var/datum/objective/wizchaos/wiz_objective = new - wiz_objective.owner = wizard - wizard.objectives += wiz_objective - return + wizard.add_mind_objective(/datum/objective/wizchaos) /datum/game_mode/proc/name_wizard(mob/living/carbon/human/wizard_mob) //Allows the wizard to choose a custom name or go with a random one. Spawn 0 so it does not lag the round starting. @@ -105,10 +102,7 @@ to_chat(wizard.current, "You are the Space Wizard!") to_chat(wizard.current, "The Space Wizards Federation has given you the following tasks:") - var/obj_count = 1 - for(var/datum/objective/objective in wizard.objectives) - to_chat(wizard.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ + wizard.announce_objectives(title = FALSE) to_chat(wizard.current, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Wizard)") wizard.current.create_log(MISC_LOG, "[wizard.current] was made into a wizard") @@ -214,7 +208,7 @@ var/count = 1 var/wizardwin = 1 - for(var/datum/objective/objective in wizard.objectives) + for(var/datum/objective/objective in wizard.get_all_objectives(include_team = FALSE)) if(objective.check_completion()) text += "
Objective #[count]: [objective.explanation_text] Success!" SSblackbox.record_feedback("nested tally", "wizard_objective", 1, list("[objective.type]", "SUCCESS")) diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index f8702c0334c..8c98b3928b6 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -375,8 +375,7 @@ SSjobs.FreeRole(job) - if(occupant.mind.objectives.len) - occupant.mind.objectives.Cut() + if(occupant.mind.objective_holder.clear()) occupant.mind.special_role = null else if(SSticker.mode.name == "AutoTraitor") diff --git a/code/game/objects/items/weapons/dice.dm b/code/game/objects/items/weapons/dice.dm index c1de033796a..898eb9cbc09 100644 --- a/code/game/objects/items/weapons/dice.dm +++ b/code/game/objects/items/weapons/dice.dm @@ -283,11 +283,12 @@ H.equipOutfit(/datum/outfit/butler) var/datum/mind/servant_mind = new /datum/mind() + var/datum/objective/O = new - O.owner = servant_mind O.target = user.mind O.explanation_text = "Serve [user.real_name]." - servant_mind.objectives += O + servant_mind.add_mind_objective(O) + servant_mind.transfer_to(H) var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as the servant of [user.real_name]?", poll_time = 30 SECONDS, source = H) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index a90b628b7cb..499d7df9464 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1618,6 +1618,14 @@ if(!check_rights(R_ADMIN)) return var/datum/team/team + if(href_list["team_command"] == "new_custom_team") // this needs to be handled before all the other stuff, as the team doesn't exist yet + message_admins("[key_name_admin(usr)] created a new custom team.") + log_admin("[key_name(usr)] created a new custom team.") + team = new() + team.admin_rename_team(usr) + check_teams() + return + var/datum/mind/member if(href_list["team"]) team = locateUID(href_list["team"]) @@ -1646,6 +1654,8 @@ show_player_panel(member.current) if("add_objective") team.admin_add_objective(usr) + if("announce_objectives") + team.admin_announce_objectives(usr) if("remove_objective") var/datum/objective/O = locateUID(href_list["objective"]) if(O) @@ -2143,11 +2153,12 @@ possible_traitors -= player if(possible_traitors.len) var/datum/mind/newtraitormind = pick(possible_traitors) + var/datum/objective/assassinate/kill_objective = new() kill_objective.target = H.mind - kill_objective.owner = newtraitormind kill_objective.explanation_text = "Assassinate [H.mind.name], the [H.mind.assigned_role]" - newtraitormind.objectives += kill_objective + newtraitormind.add_mind_objective(kill_objective) + var/datum/antagonist/traitor/T = new() T.give_objectives = FALSE to_chat(newtraitormind.current, "ATTENTION: It is time to pay your debt to the Syndicate...") @@ -3467,16 +3478,14 @@ D.implant(hunter_mob) if(killthem) var/datum/objective/assassinate/kill_objective = new - kill_objective.owner = hunter_mind kill_objective.target = H.mind kill_objective.explanation_text = "Kill [H.real_name], the [H.mind.assigned_role]." - hunter_mind.objectives += kill_objective + hunter_mind.add_mind_objective(kill_objective) else var/datum/objective/protect/protect_objective = new - protect_objective.owner = hunter_mind protect_objective.target = H.mind protect_objective.explanation_text = "Protect [H.real_name], the [H.mind.assigned_role]." - hunter_mind.objectives += protect_objective + hunter_mind.add_mind_objective(protect_objective) SSticker.mode.traitors |= hunter_mob.mind to_chat(hunter_mob, "ATTENTION: You are now on a mission!") to_chat(hunter_mob, "Goal: [killthem ? "MURDER" : "PROTECT"] [H.real_name], currently in [get_area(H.loc)]."); diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm index 5da81c15f66..4054ccd3df8 100644 --- a/code/modules/admin/verbs/onlyone.dm +++ b/code/modules/admin/verbs/onlyone.dm @@ -18,15 +18,10 @@ SSticker.mode.traitors += H.mind H.mind.special_role = SPECIAL_ROLE_TRAITOR - var/datum/objective/hijack/hijack_objective = new - hijack_objective.owner = H.mind - H.mind.objectives += hijack_objective + H.mind.add_mind_objective(/datum/objective/hijack) to_chat(H, "You are a Highlander. Kill all other Highlanders. There can be only one.") - var/obj_count = 1 - for(var/datum/objective/OBJ in H.mind.objectives) - to_chat(H, "Objective #[obj_count]: [OBJ.explanation_text]") - obj_count++ + H.mind.announce_objectives(title = FALSE) for(var/obj/item/I in H) if(istype(I, /obj/item/implant)) @@ -75,15 +70,10 @@ SSticker.mode.traitors += H.mind H.mind.special_role = "[H.real_name] Prime" - var/datum/objective/hijackclone/hijack_objective = new /datum/objective/hijackclone - hijack_objective.owner = H.mind - H.mind.objectives += hijack_objective + H.mind.add_mind_objective(/datum/objective/hijackclone) to_chat(H, "You are the multiverse summoner. Activate your blade to summon copies of yourself from another universe to fight by your side.") - var/obj_count = 1 - for(var/datum/objective/OBJ in H.mind.objectives) - to_chat(H, "Objective #[obj_count]: [OBJ.explanation_text]") - obj_count++ + H.mind.announce_objectives(title = FALSE) var/obj/item/slot_item_ID = H.get_item_by_slot(slot_wear_id) qdel(slot_item_ID) diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 7a0b8caf885..5e220387636 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -18,9 +18,7 @@ GLOBAL_LIST_EMPTY(antagonists) /// Should we replace the role-banned player with a ghost? var/replace_banned = TRUE /// List of objectives connected to this datum. - var/list/objectives - /// A list of strings which contain [targets][/datum/objective/var/target] of the antagonist's objectives. Used to prevent duplicate objectives. - var/list/assigned_targets + var/datum/objective_holder/objective_holder /// Antagonist datum specific information that appears in the player's notes. Information stored here will be removed when the datum is removed from the player. var/antag_memory /// The special role that will be applied to the owner's `special_role` var. i.e. `SPECIAL_ROLE_TRAITOR`, `SPECIAL_ROLE_VAMPIRE`. @@ -40,14 +38,10 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/New() GLOB.antagonists += src - objectives = list() - assigned_targets = list() + objective_holder = new(src) /datum/antagonist/Destroy(force, ...) - for(var/datum/objective/O as anything in objectives) - objectives -= O - if(!O.team) - qdel(O) + qdel(objective_holder) remove_owner_from_gamemode() GLOB.antagonists -= src if(!silent) @@ -211,62 +205,44 @@ GLOBAL_LIST_EMPTY(antagonists) * * explanation_text - the explanation text that will be passed into the objective's `New()` proc * * mob/target_override - a target for the objective */ -/datum/antagonist/proc/add_objective(objective_type, explanation_text = "", mob/target_override = null) - var/datum/objective/O = new objective_type(explanation_text) +/datum/antagonist/proc/add_antag_objective(datum/objective/O, explanation_text, mob/target_override) + if(ispath(O)) + O = new O() + if(O.owner) + stack_trace("[O], [O.type] was assigned as an objective to [owner] (mind), but already had an owner: [O.owner] (mind). Overriding.") O.owner = owner - if(!O.needs_target) - objectives += O - return O - - var/found_valid_target = FALSE - - if(target_override) - O.target = target_override - found_valid_target = TRUE - else - var/loops = 5 - // Steal objectives need snowflake handling here unfortunately. - if(istype(O, /datum/objective/steal)) - var/datum/objective/steal/S = O - while(loops--) - S.find_target() - if(S.steal_target && !("[S.steal_target.name]" in assigned_targets)) - found_valid_target = TRUE - break - else - while(loops--) - O.find_target() - if(O.target && !("[O.target]" in assigned_targets)) - found_valid_target = TRUE - break - - if(found_valid_target) - // This is its own seperate section in case someone passes a `target_override`. - if(istype(O, /datum/objective/steal)) - var/datum/objective/steal/S = O - assigned_targets |= "[S.steal_target.name]" - else - assigned_targets |= "[O.target]" - else - O.explanation_text = "Free Objective" - O.target = null - - objectives += O - return O + return objective_holder.add_objective(O, explanation_text, target_override) /** - * Announces all objectives of this datum, and only this datum. + * Complement to add_antag_objective that removes the objective. + * Currently unused. */ -/datum/antagonist/proc/announce_objectives() - if(!length(objectives)) - return FALSE - to_chat(owner.current, "Your current objectives:") - var/objective_num = 1 - for(var/objective in objectives) - var/datum/objective/O = objective - to_chat(owner.current, "Objective #[objective_num++]: [O.explanation_text]
") - return TRUE +/datum/antagonist/proc/remove_antag_objective(datum/objective/O) + return objective_holder.remove_objective(O) + +/** + * Do we have any objectives at all, including from a team. + * Faster than get_antag_objectives() + */ +/datum/antagonist/proc/has_antag_objectives(include_team = TRUE) + . = FALSE + if(include_team) + var/datum/team/team = get_team() + if(istype(team)) + . |= team.objective_holder.has_objectives() + . |= objective_holder.has_objectives() + +/** + * Get all of this antagonist's objectives, including from the team. + */ +/datum/antagonist/proc/get_antag_objectives(include_team = TRUE) + . = list() + if(include_team) + var/datum/team/team = get_team() + if(istype(team)) + . |= team.objective_holder.get_objectives() + . |= objective_holder.get_objectives() /** * Proc called when the datum is given to a mind. @@ -278,7 +254,7 @@ GLOBAL_LIST_EMPTY(antagonists) give_objectives() if(!silent) greet() - announce_objectives() + owner.announce_objectives() apply_innate_effects() finalize_antag() if(wiki_page_name) @@ -372,14 +348,14 @@ GLOBAL_LIST_EMPTY(antagonists) report += printplayer(owner) var/objectives_complete = TRUE - if(owner.objectives.len) + if(objective_holder.has_objectives()) report += printobjectives(owner) - for(var/datum/objective/objective in owner.objectives) + for(var/datum/objective/objective in objective_holder.get_objectives()) if(!objective.check_completion()) objectives_complete = FALSE break - if(owner.objectives.len == 0 || objectives_complete) + if(objectives_complete) report += "The [name] was successful!" else report += "The [name] has failed!" @@ -397,3 +373,4 @@ GLOBAL_LIST_EMPTY(antagonists) // Called when the owner is cryo'd, for when you want things to happen on cryo and not deletion /datum/antagonist/proc/on_cryo() return + diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 4b4bdedd116..f75c7535a3a 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -223,18 +223,19 @@ D.mind.assigned_role = D.name D.mind.special_role = D.name SSticker.mode.traitors += D.mind + var/datum/objective/assassinate/KillDaWiz = new /datum/objective/assassinate - KillDaWiz.owner = D.mind KillDaWiz.target = user.mind KillDaWiz.explanation_text = "[objective_verb] [user.real_name], the one who was foolish enough to summon you." - D.mind.objectives += KillDaWiz + D.mind.add_mind_objective(KillDaWiz) + var/datum/objective/KillDaCrew = new /datum/objective - KillDaCrew.owner = D.mind KillDaCrew.explanation_text = "[objective_verb] everyone else while you're at it." KillDaCrew.completed = TRUE - D.mind.objectives += KillDaCrew - to_chat(D, "Objective #[1]: [KillDaWiz.explanation_text]") - to_chat(D, "Objective #[2]: [KillDaCrew.explanation_text]") + D.mind.add_mind_objective(KillDaCrew) + + D.mind.announce_objectives(title = FALSE) + /obj/item/antag_spawner/slaughter_demon/laughter name = "vial of tickles" @@ -302,18 +303,19 @@ var/mob/living/simple_animal/hostile/morph/wizard/M = new /mob/living/simple_animal/hostile/morph/wizard(pick(GLOB.xeno_spawn)) M.key = C.key M.make_morph_antag(FALSE) + var/datum/objective/assassinate/KillDaWiz = new /datum/objective/assassinate KillDaWiz.owner = M.mind KillDaWiz.target = user.mind KillDaWiz.explanation_text = "[objective_verb] [user.real_name], the one who was foolish enough to awake you." - M.mind.objectives += KillDaWiz + M.mind.add_mind_objective(KillDaWiz) + var/datum/objective/KillDaCrew = new /datum/objective - KillDaCrew.owner = M.mind KillDaCrew.explanation_text = "[objective_verb] everyone and everything else while you're at it." KillDaCrew.completed = TRUE - M.mind.objectives += KillDaCrew - to_chat(M, "Objective #[1]: [KillDaWiz.explanation_text]") - to_chat(M, "Objective #[2]: [KillDaCrew.explanation_text]") + M.mind.add_mind_objective(KillDaCrew) + + M.mind.announce_objectives(title = FALSE) ///////////Revenant @@ -357,18 +359,18 @@ /obj/item/antag_spawner/revenant/spawn_antag(client/C, turf/T, type = "", mob/user) var/mob/living/simple_animal/revenant/M = new /mob/living/simple_animal/revenant(pick(GLOB.xeno_spawn)) M.key = C.key + var/datum/objective/assassinate/KillDaWiz = new /datum/objective/assassinate - KillDaWiz.owner = M.mind KillDaWiz.target = user.mind KillDaWiz.explanation_text = "[objective_verb] [user.real_name], the one who was foolish enough to awake you." - M.mind.objectives += KillDaWiz + M.mind.add_mind_objective(KillDaWiz) + var/datum/objective/KillDaCrew = new /datum/objective - KillDaCrew.owner = M.mind KillDaCrew.explanation_text = "[objective_verb] everyone and everything else while you're at it." KillDaCrew.completed = TRUE - M.mind.objectives += KillDaCrew - to_chat(M, "Objective #[1]: [KillDaWiz.explanation_text]") - to_chat(M, "Objective #[2]: [KillDaCrew.explanation_text]") + M.mind.add_mind_objective(KillDaCrew) + + M.mind.announce_objectives(title = FALSE) ///////////Pulse Demon @@ -422,19 +424,16 @@ player_mind.transfer_to(demon) player_mind.assigned_role = SPECIAL_ROLE_DEMON player_mind.special_role = SPECIAL_ROLE_DEMON - var/i = demon.give_objectives() + demon.give_objectives() - var/datum/objective/assassinate/kill_wiz = new /datum/objective/assassinate - kill_wiz.owner = demon.mind - kill_wiz.target = user.mind - kill_wiz.explanation_text = "[objective_verb] [user.real_name], the one who was foolish enough to free you." - demon.mind.objectives += kill_wiz + var/datum/objective/assassinate/KillDaWiz = new /datum/objective/assassinate + KillDaWiz.target = user.mind + KillDaWiz.explanation_text = "[objective_verb] [user.real_name], the one who was foolish enough to awake you." + demon.mind.add_mind_objective(KillDaWiz) - var/datum/objective/kill_crew = new /datum/objective - kill_crew.owner = demon.mind - kill_crew.explanation_text = "[objective_verb] everyone else while you're at it." - kill_crew.completed = TRUE - demon.mind.objectives += kill_crew + var/datum/objective/KillDaCrew = new /datum/objective + KillDaCrew.explanation_text = "[objective_verb] everyone and everything else while you're at it." + KillDaCrew.completed = TRUE + demon.mind.add_mind_objective(KillDaCrew) - to_chat(demon, "Objective #[i++]: [kill_wiz.explanation_text]") - to_chat(demon, "Objective #[i++]: [kill_crew.explanation_text]") + demon.mind.announce_objectives(title = FALSE) diff --git a/code/modules/antagonists/_common/antag_team.dm b/code/modules/antagonists/_common/antag_team.dm index 8f19c3144eb..66670e8b6d2 100644 --- a/code/modules/antagonists/_common/antag_team.dm +++ b/code/modules/antagonists/_common/antag_team.dm @@ -1,5 +1,7 @@ GLOBAL_LIST_EMPTY(antagonist_teams) +#define DEFAULT_TEAM_NAME "Generic Team Name" + /** * # Antagonist Team * @@ -7,18 +9,20 @@ GLOBAL_LIST_EMPTY(antagonist_teams) */ /datum/team /// The name of the team. - var/name = "Generic Team Name" + var/name = DEFAULT_TEAM_NAME /// A list of [minds][/datum/mind] who belong to this team. - var/list/members + var/list/datum/mind/members /// A list of objectives which all team members share. - var/list/objectives + var/datum/objective_holder/objective_holder /// Type of antag datum members of this team have. Also given to new members added by admins. var/antag_datum_type + /// The name to save objective successes under in the blackboxes. Saves nothing if blank. + var/blackbox_save_name /datum/team/New(list/starting_members) ..() members = list() - objectives = list() + objective_holder = new(src) if(starting_members && !islist(starting_members)) starting_members = list(starting_members) for(var/datum/mind/M as anything in starting_members) @@ -28,7 +32,7 @@ GLOBAL_LIST_EMPTY(antagonist_teams) /datum/team/Destroy(force = FALSE, ...) for(var/datum/mind/member as anything in members) remove_member(member) - QDEL_LIST_CONTENTS(objectives) + qdel(objective_holder) members.Cut() GLOB.antagonist_teams -= src return ..() @@ -40,18 +44,21 @@ GLOBAL_LIST_EMPTY(antagonist_teams) */ /datum/team/proc/add_member(datum/mind/new_member) SHOULD_CALL_PARENT(TRUE) - var/datum/antagonist/team_antag = get_antag_datum_from_member(new_member) + var/datum/antagonist/antag = get_antag_datum_from_member(new_member) // make sure they have the antag datum members |= new_member - team_antag.objectives |= objectives + if(!antag) // this team has no antag role, we'll add it directly to their mind team + LAZYDISTINCTADD(new_member.teams, src) /** * Removes `member` from this team. */ /datum/team/proc/remove_member(datum/mind/member) SHOULD_CALL_PARENT(TRUE) - var/datum/antagonist/A = get_antag_datum_from_member(member) members -= member - A.objectives -= objectives + LAZYREMOVE(member.teams, src) + var/datum/antagonist/antag = get_antag_datum_from_member(member) + if(!QDELETED(antag)) + qdel(antag) /** * Adds a new member to this team from a list of players in the round. @@ -74,22 +81,17 @@ GLOBAL_LIST_EMPTY(antagonist_teams) /** * Adds a team objective to each member's matching antag datum. */ -/datum/team/proc/add_objective_to_team(datum/objective/O) +/datum/team/proc/add_team_objective(datum/objective/O, _explanation_text, mob/target_override) + if(ispath(O)) + O = new O() O.team = src - for(var/datum/mind/M as anything in members) - var/datum/antagonist/A = get_antag_datum_from_member(M) - A.objectives |= O - objectives |= O - RegisterSignal(O, COMSIG_PARENT_QDELETING, PROC_REF(remove_objective_from_team)) + return objective_holder.add_objective(O, _explanation_text, target_override) /** * Remove a team objective from each member's matching antag datum. */ -/datum/team/proc/remove_objective_from_team(datum/objective/O) - for(var/datum/mind/M as anything in members) - var/datum/antagonist/A = get_antag_datum_from_member(M) - A.objectives -= O - objectives -= O +/datum/team/proc/remove_team_objective(datum/objective/O) + . = objective_holder.remove_objective(O) if(!QDELETED(O)) qdel(O) @@ -105,6 +107,58 @@ GLOBAL_LIST_EMPTY(antagonist_teams) if(antag_datum_type) return member.add_antag_datum(antag_datum_type, src) +/** + * Special overrides for teams for target exclusion from objectives. + */ +/datum/team/proc/get_target_excludes() + return members + +/** + * Displays the roundend stats for teams + */ +/datum/team/proc/on_round_end() + if(!length(members)) + return + var/temp_name = name + if(temp_name == DEFAULT_TEAM_NAME || !temp_name) + temp_name = "This team" + else + temp_name = "The [name]" + + var/list/to_send = list("
[temp_name]'s objectives were:") + var/obj_count = 1 + var/team_win = TRUE + for(var/datum/objective/objective in objective_holder.get_objectives()) + + var/text_to_add = "Objective #[obj_count++]: [objective.explanation_text] " + var/failed = "FAIL" + if(objective.check_completion()) + text_to_add += "Success!" + failed = "SUCCESS" + else + text_to_add += "Fail." + team_win = FALSE + to_send += text_to_add + + // handle blackbox stuff + if(initial(blackbox_save_name)) // no im not letting admins var edit shit to the blackbox + if(istype(objective, /datum/objective/steal)) + var/datum/objective/steal/S = objective + SSblackbox.record_feedback("nested tally", "[initial(blackbox_save_name)]_team_steal_objective", 1, list("Steal [S.steal_target]", failed)) + else + SSblackbox.record_feedback("nested tally", "[initial(blackbox_save_name)]_team_objective", 1, list("[objective.type]", failed)) + + if(team_win) + to_send += "[temp_name] were successful!
" + if(initial(blackbox_save_name)) // no im not letting admins var edit shit to the blackbox + SSblackbox.record_feedback("tally", "[initial(blackbox_save_name)]_team_success", 1, "SUCCESS") + else + to_send += "[temp_name] failed!
" + if(initial(blackbox_save_name)) // no im not letting admins var edit shit to the blackbox + SSblackbox.record_feedback("tally", "[initial(blackbox_save_name)]_team_success", 1, "FAIL") + + to_chat(world, to_send.Join("
")) + /** * Allows admins to send a message to all members of this team. */ @@ -130,22 +184,34 @@ GLOBAL_LIST_EMPTY(antagonist_teams) var/objective_type = GLOB.admin_objective_list[selected] var/datum/objective/O = new objective_type(team_to_join = src) O.find_target(get_target_excludes()) // Blacklist any team members from being the target. - add_objective_to_team(O) + add_team_objective(O) message_admins("[key_name_admin(user)] added objective [O.type] to the team '[name]'.") log_admin("[key_name(user)] added objective [O.type] to the team '[name]'.") /** - * Special overrides for teams for target exclusion from objectives. + * Allows admins to announce objectives to all team members. */ -/datum/team/proc/get_target_excludes() - return members +/datum/team/proc/admin_announce_objectives(mob/user) + // This button is right next to the + if(alert(user, "Are you sure you want to announce objectives to all members?", "Are you sure?", "Yes", "No") == "No") + return + + + log_admin("[key_name(usr)] has announced team [src]'s objectives") + message_admins("[key_name_admin(usr)] has announced team [src]'s objectives") + + for(var/datum/mind/member in members) + if(!member.current || !isliving(member.current)) + return + member.announce_objectives() + SEND_SOUND(member.current, sound('sound/ambience/alarm4.ogg')) /** * Allows admins to remove a team objective. */ /datum/team/proc/admin_remove_objective(mob/user, datum/objective/O) - remove_objective_from_team(O) + remove_team_objective(O) message_admins("[key_name_admin(user)] removed objective [O.type] from the team '[name]'.") log_admin("[key_name(user)] removed objective [O.type] from the team '[name]'.") @@ -205,8 +271,9 @@ GLOBAL_LIST_EMPTY(antagonist_teams) /datum/admins/proc/list_teams() var/list/content = list() if(!length(GLOB.antagonist_teams)) - content += "There are currently no antag teams." - for(var/datum/team/T as anything in GLOB.antagonist_teams) + content += "There are currently no antag teams.
" + content += "Create new Team" + for(var/datum/team/T as anything in GLOB.antagonist_teams) // with multiple teams, this is going to get messy. It should probably be turned into a tabs-like system content += "

[T.name] - [T.type]

" content += "Rename Team" content += "Delete Team" @@ -216,9 +283,11 @@ GLOBAL_LIST_EMPTY(antagonist_teams) // _src_ is T.UID() so it points to `/datum/team/Topic` instead of `/datum/admins/Topic`. content += "[command]" content += "

Objectives:
    " - for(var/datum/objective/O as anything in T.objectives) + for(var/datum/objective/O as anything in T.objective_holder.get_objectives()) content += "
  1. [O.explanation_text] - Remove
  2. " - content += "
Add Objective

" + content += "Add Objective
" + if(T.objective_holder.has_objectives()) + content += "Announce Objectives to All Members

" content += "Members:
    " for(var/datum/mind/M as anything in T.members) content += "
  1. [M.name] - Show Player Panel" diff --git a/code/modules/antagonists/changeling/datum_changeling.dm b/code/modules/antagonists/changeling/datum_changeling.dm index 1c4150f9d19..cb1c2cce595 100644 --- a/code/modules/antagonists/changeling/datum_changeling.dm +++ b/code/modules/antagonists/changeling/datum_changeling.dm @@ -149,33 +149,29 @@ * If they have two objectives as well as absorb, they must survive rather than escape. */ /datum/antagonist/changeling/give_objectives() - var/datum/objective/absorb/absorb = new - absorb.gen_amount_goal(6, 8) - absorb.owner = owner - objectives += absorb + add_antag_objective(/datum/objective/absorb) if(prob(60)) - add_objective(/datum/objective/steal) + add_antag_objective(/datum/objective/steal) else - add_objective(/datum/objective/debrain) + add_antag_objective(/datum/objective/debrain) var/list/active_ais = active_ais() if(length(active_ais) && prob(4)) // Leaving this at a flat chance for now, problems with the num_players() proc due to latejoin antags. - add_objective(/datum/objective/destroy) + add_antag_objective(/datum/objective/destroy) else - var/datum/objective/assassinate/kill_objective = add_objective(/datum/objective/assassinate) + var/datum/objective/assassinate/kill_objective = add_antag_objective(/datum/objective/assassinate) var/mob/living/carbon/human/H = kill_objective.target?.current - if(!(locate(/datum/objective/escape) in owner.get_all_objectives()) && H && !HAS_TRAIT(H, TRAIT_GENELESS)) + if(!(locate(/datum/objective/escape) in owner.get_all_objectives(include_team = FALSE)) && H && !HAS_TRAIT(H, TRAIT_GENELESS)) var/datum/objective/escape/escape_with_identity/identity_theft = new(assassinate = kill_objective) - identity_theft.owner = owner - objectives += identity_theft + add_antag_objective(identity_theft) - if(!(locate(/datum/objective/escape) in owner.get_all_objectives())) + if(!(locate(/datum/objective/escape) in owner.get_all_objectives(include_team = FALSE))) if(prob(70)) - add_objective(/datum/objective/escape) + add_antag_objective(/datum/objective/escape) else - add_objective(/datum/objective/escape/escape_with_identity) // If our kill target has no genes, 30% time pick someone else to steal the identity of + add_antag_objective(/datum/objective/escape/escape_with_identity) // If our kill target has no genes, 30% time pick someone else to steal the identity of /datum/antagonist/changeling/process() if(!owner || !owner.current) @@ -329,7 +325,7 @@ * * datum/dna/new_dna - the DNA to store */ /datum/antagonist/changeling/proc/store_dna(datum/dna/new_dna) - for(var/datum/objective/escape/escape_with_identity/E in objectives) + for(var/datum/objective/escape/escape_with_identity/E in owner.get_all_objectives()) // this should consider all objectives, in case admins reroll it if(E.target_real_name == new_dna.real_name) protected_dna |= new_dna return diff --git a/code/modules/antagonists/revolutionary/team_revolution.dm b/code/modules/antagonists/revolutionary/team_revolution.dm index 061140cd20b..00809ab31be 100644 --- a/code/modules/antagonists/revolutionary/team_revolution.dm +++ b/code/modules/antagonists/revolutionary/team_revolution.dm @@ -31,6 +31,9 @@ message_admins("[key_name_admin(user)] added a mutiny objective to the team '[name]', and no target was found, removing.") log_admin("[key_name_admin(user)] added a mutiny objective to the team '[name]', and no target was found, removing.") +/datum/team/revolution/on_round_end() + return // for now... show nothing. Add this in when revs is added to midround/dynamic. Not showing it currently because its dependent on rev gamemode + /datum/team/revolution/proc/update_team_objectives() var/list/heads = SSticker.mode.get_all_heads() - get_targetted_head_minds() @@ -38,18 +41,18 @@ var/datum/objective/mutiny/rev_obj = new rev_obj.target = head_mind rev_obj.explanation_text = "Assassinate or exile [head_mind.name], the [head_mind.assigned_role]." - add_objective_to_team(rev_obj) + add_team_objective(rev_obj) sanitize_objectives() /datum/team/revolution/proc/get_targetted_head_minds() . = list() - for(var/datum/objective/mutiny/O in objectives) + for(var/datum/objective/mutiny/O in objective_holder.get_objectives()) . |= O.target /datum/team/revolution/proc/sanitize_objectives() - for(var/datum/objective/mutiny/O in objectives) + for(var/datum/objective/mutiny/O in objective_holder.get_objectives()) if(!O.target) // revs shouldnt have free objectives - remove_objective_from_team(O) + remove_team_objective(O) . = TRUE /datum/team/revolution/proc/check_all_victory() @@ -58,7 +61,7 @@ check_heads_victory() /datum/team/revolution/proc/check_rev_victory() - for(var/datum/objective/mutiny/objective in objectives) + for(var/datum/objective/mutiny/objective in objective_holder.get_objectives()) if(!(objective.check_completion())) return FALSE @@ -139,6 +142,6 @@ return FALSE if(!ishuman(rev_mind.current)) return FALSE - if(rev_mind.current.incapacitated() || HAS_TRAIT(rev_mind.current, TRAIT_HANDS_BLOCKED)) + if(rev_mind.current.incapacitated() || HAS_TRAIT(rev_mind.current, TRAIT_HANDS_BLOCKED)) // todo for someone else, make sure the rev heads on ON STATION return FALSE return TRUE diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm index 0bda6e5606f..925d176012e 100644 --- a/code/modules/antagonists/survivalist/survivalist.dm +++ b/code/modules/antagonists/survivalist/survivalist.dm @@ -4,7 +4,7 @@ var/greet_message = "" /datum/antagonist/survivalist/give_objectives() - add_objective(/datum/objective/survive) + add_antag_objective(/datum/objective/survive) /datum/antagonist/survivalist/greet() ..() @@ -14,7 +14,7 @@ greet_message = "Your own safety matters above all else, and the only way to ensure your safety is to stockpile weapons! Grab as many guns as possible, by any means necessary. Kill anyone who gets in your way." /datum/antagonist/survivalist/guns/give_objectives() - add_objective(/datum/objective/steal_five_of_type/summon_guns) + add_antag_objective(/datum/objective/steal_five_of_type/summon_guns) ..() /datum/antagonist/survivalist/magic @@ -22,5 +22,5 @@ greet_message = "Grow your newfound talent! Grab as many magical artefacts as possible, by any means necessary. Kill anyone who gets in your way. As a wonderful magician, you should remember that spellbooks don't mean anything if they are used up." /datum/antagonist/survivalist/magic/give_objectives() - add_objective(/datum/objective/steal_five_of_type/summon_magic) + add_antag_objective(/datum/objective/steal_five_of_type/summon_magic) ..() diff --git a/code/modules/antagonists/traitor/datum_mindslave.dm b/code/modules/antagonists/traitor/datum_mindslave.dm index bfb5fc3b624..a81af38ae22 100644 --- a/code/modules/antagonists/traitor/datum_mindslave.dm +++ b/code/modules/antagonists/traitor/datum_mindslave.dm @@ -55,7 +55,7 @@ /datum/antagonist/mindslave/give_objectives() var/explanation_text = "Obey every order from and protect [master.current.real_name], the [master.assigned_role ? master.assigned_role : master.special_role]." - add_objective(/datum/objective/protect/mindslave, explanation_text, master) + add_antag_objective(/datum/objective/protect/mindslave, explanation_text, master) /datum/antagonist/mindslave/greet() var/mob/living/carbon/human/mindslave = owner.current diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 4c650595fd8..5a74a6e19cf 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -93,7 +93,7 @@ /datum/antagonist/traitor/proc/forge_human_objectives() // Hijack objective. if(prob(10) && !(locate(/datum/objective/hijack) in owner.get_all_objectives())) - add_objective(/datum/objective/hijack) + add_antag_objective(/datum/objective/hijack) return // Hijack should be their only objective (normally), so return. // Will give normal steal/kill/etc. type objectives. @@ -109,15 +109,15 @@ // Give them an escape objective if they don't have one already. if(!(locate(/datum/objective/escape) in owner.get_all_objectives()) && (!can_succeed_if_dead || prob(80))) - add_objective(/datum/objective/escape) + add_antag_objective(/datum/objective/escape) /** * Create and assign a full set of AI traitor objectives. */ /datum/antagonist/traitor/proc/forge_ai_objectives() - add_objective(/datum/objective/block) - add_objective(/datum/objective/assassinate) - add_objective(/datum/objective/survive) + add_antag_objective(/datum/objective/block) + add_antag_objective(/datum/objective/assassinate) + add_antag_objective(/datum/objective/survive) /** * Create and assign a single randomized human traitor objective. @@ -125,15 +125,15 @@ /datum/antagonist/traitor/proc/forge_single_human_objective() if(prob(50)) if(length(active_ais()) && prob(100 / length(GLOB.player_list))) - add_objective(/datum/objective/destroy) + add_antag_objective(/datum/objective/destroy) else if(prob(5)) - add_objective(/datum/objective/debrain) + add_antag_objective(/datum/objective/debrain) else if(prob(30)) - add_objective(/datum/objective/maroon) + add_antag_objective(/datum/objective/maroon) else - add_objective(/datum/objective/assassinate) + add_antag_objective(/datum/objective/assassinate) else - add_objective(/datum/objective/steal) + add_antag_objective(/datum/objective/steal) /** * Give human traitors their uplink, and AI traitors their law 0. Play the traitor an alert sound. diff --git a/code/modules/antagonists/vampire/vamp_datum.dm b/code/modules/antagonists/vampire/vamp_datum.dm index 856afa923f0..2567204df00 100644 --- a/code/modules/antagonists/vampire/vamp_datum.dm +++ b/code/modules/antagonists/vampire/vamp_datum.dm @@ -329,14 +329,14 @@ check_vampire_upgrade(TRUE) /datum/antagonist/vampire/give_objectives() - add_objective(/datum/objective/blood) - add_objective(/datum/objective/assassinate) - add_objective(/datum/objective/steal) + add_antag_objective(/datum/objective/blood) + add_antag_objective(/datum/objective/assassinate) + add_antag_objective(/datum/objective/steal) if(prob(20)) // 20% chance of getting survive. 80% chance of getting escape. - add_objective(/datum/objective/survive) + add_antag_objective(/datum/objective/survive) else - add_objective(/datum/objective/escape) + add_antag_objective(/datum/objective/escape) /datum/antagonist/vampire/greet() var/dat diff --git a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm index f086e274cf4..eae5a5719ac 100644 --- a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm +++ b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm @@ -340,11 +340,12 @@ if(T && T.implanted) qdel(T) visible_message("[H] gets an eerie red glow in their eyes!") + var/datum/objective/protect/protect_objective = new - protect_objective.owner = H.mind protect_objective.target = M.mind protect_objective.explanation_text = "Protect [M.real_name]." - H.mind.objectives += protect_objective + H.mind.add_mind_objective(protect_objective) + add_attack_logs(M, H, "Vampire-sired") H.mind.make_vampire() H.revive() diff --git a/code/modules/antagonists/wishgranter/wishgranter_avatar.dm b/code/modules/antagonists/wishgranter/wishgranter_avatar.dm index 8774b487b93..d0a11f661f6 100644 --- a/code/modules/antagonists/wishgranter/wishgranter_avatar.dm +++ b/code/modules/antagonists/wishgranter/wishgranter_avatar.dm @@ -3,7 +3,7 @@ special_role = "Avatar of the Wish Granter" /datum/antagonist/wishgranter/give_objectives() - add_objective(/datum/objective/hijack) + add_antag_objective(/datum/objective/hijack) /datum/antagonist/wishgranter/greet() ..() diff --git a/code/modules/awaymissions/mob_spawn.dm b/code/modules/awaymissions/mob_spawn.dm index cfd95a07f31..a4ed6d78648 100644 --- a/code/modules/awaymissions/mob_spawn.dm +++ b/code/modules/awaymissions/mob_spawn.dm @@ -136,7 +136,7 @@ var/datum/mind/MM = M.mind if(objectives) for(var/objective in objectives) - MM.objectives += new/datum/objective(objective) + M.mind.add_mind_objective(new /datum/objective(objective)) if(assignedrole) M.mind.assigned_role = assignedrole M.mind.offstation_role = offstation_role diff --git a/code/modules/events/traders.dm b/code/modules/events/traders.dm index 52d5b1542da..b787cf3247e 100644 --- a/code/modules/events/traders.dm +++ b/code/modules/events/traders.dm @@ -58,7 +58,8 @@ GLOBAL_LIST_INIT(unused_trade_stations, list("sol")) M.ckey = C.ckey // must be before equipOutfit, or that will runtime due to lack of mind M.equipOutfit(/datum/outfit/admin/sol_trader) M.dna.species.after_equip_job(null, M) - M.mind.objectives += trader_objectives + for(var/datum/objective/O in trader_objectives) + M.mind.objective_holder.add_objective(O) // traders dont have a team, so we manually have to add this objective to all of their minds, without setting an owner M.mind.offstation_role = TRUE greet_trader(M) success_spawn = TRUE @@ -71,18 +72,13 @@ GLOBAL_LIST_INIT(unused_trade_stations, list("sol")) to_chat(M, "You are a trader!") to_chat(M, "You are currently docked at [get_area(M)].") to_chat(M, "You are about to trade with [station_name()].") - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(show_objectives), M.mind), 25) + addtimer(CALLBACK(M.mind, TYPE_PROC_REF(/datum/mind, announce_objectives)), 2.5 SECONDS) M.create_log(MISC_LOG, "[M] was made into a Sol Trader") /datum/event/traders/proc/forge_trader_objectives() var/list/objs = list() - var/datum/objective/trade/plasma/P = new /datum/objective/trade/plasma - P.choose_target() - objs += P - - var/datum/objective/trade/credits/C = new /datum/objective/trade/credits - C.choose_target() - objs += C + objs += new /datum/objective/trade/plasma + objs += new /datum/objective/trade/credits return objs diff --git a/code/modules/mining/lavaland/loot/bubblegum_loot.dm b/code/modules/mining/lavaland/loot/bubblegum_loot.dm index a5c0027c1fb..91c3d797f52 100644 --- a/code/modules/mining/lavaland/loot/bubblegum_loot.dm +++ b/code/modules/mining/lavaland/loot/bubblegum_loot.dm @@ -64,9 +64,7 @@ message_admins("[key_name_admin(L)] has been marked for death by [key_name_admin(user)].") log_admin("[key_name(L)] has been marked for death by [key_name(user)].") - var/datum/objective/survive/survive = new - survive.owner = L.mind - L.mind.objectives += survive + L.mind.add_mind_objective(/datum/objective/survive) to_chat(L, "You've been marked for death! Don't let the demons get you!") L.color = "#FF0000" spawn() diff --git a/code/modules/mob/living/silicon/ai/latejoin.dm b/code/modules/mob/living/silicon/ai/latejoin.dm index a0a4d3f5fa9..7fd3b5d8377 100644 --- a/code/modules/mob/living/silicon/ai/latejoin.dm +++ b/code/modules/mob/living/silicon/ai/latejoin.dm @@ -21,8 +21,7 @@ GLOBAL_LIST_EMPTY(empty_playable_ai_cores) SSjobs.FreeRole(job) - if(mind.objectives.len) - mind.objectives.Cut() + if(mind.objective_holder.clear()) mind.special_role = null else if(SSticker.mode.name == "AutoTraitor") diff --git a/paradise.dme b/paradise.dme index d927463e328..21e2a953b7f 100644 --- a/paradise.dme +++ b/paradise.dme @@ -631,6 +631,7 @@ #include "code\game\gamemodes\game_mode.dm" #include "code\game\gamemodes\intercept_report.dm" #include "code\game\gamemodes\objective.dm" +#include "code\game\gamemodes\objective_holder.dm" #include "code\game\gamemodes\scoreboard.dm" #include "code\game\gamemodes\setupgame.dm" #include "code\game\gamemodes\steal_items.dm"