diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm index a6b8da39714..af5d63d4c8c 100644 --- a/code/__DEFINES/gamemode.dm +++ b/code/__DEFINES/gamemode.dm @@ -13,6 +13,8 @@ #define TARGET_INVALID_CULTIST 12 #define TARGET_INVALID_CULT_CONVERTABLE 13 #define TARGET_CRYOING 14 +#define TARGET_INVALID_HEAD 15 +#define TARGET_INVALID_ANTAG 16 //gamemode istype helpers #define GAMEMODE_IS_CULT (SSticker && istype(SSticker.mode, /datum/game_mode/cult)) diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index f5e6f225649..fac0edc2ade 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -99,6 +99,34 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) if(owner) . += owner +/** + * Helper proc to find protect objectives targeting the same mind as this objective. + * Returns a list of protect objectives. + */ +/datum/objective/proc/find_protect_objectives_for_target() + if(!target) + return list() + + var/list/protect_objectives = list() + for(var/datum/objective/protect/P in GLOB.all_objectives) + if(P.target == target) + protect_objectives += P + return protect_objectives + +/** + * Helper proc to find assassinate/assassinateonce objectives targeting the same mind as this objective. + * Returns a list of assassination objectives. + */ +/datum/objective/proc/find_assassination_objectives_for_target() + if(!target) + return list() + + var/list/assassination_objectives = list() + for(var/datum/objective/O in GLOB.all_objectives) + if((istype(O, /datum/objective/assassinate) || istype(O, /datum/objective/assassinateonce)) && O.target == target) + assassination_objectives += O + return assassination_objectives + /datum/proc/is_invalid_target(datum/mind/possible_target) // Originally an Objective proc. Changed to a datum proc to allow for the proc to be run on minds, before the objective is created if(!ishuman(possible_target.current)) return TARGET_INVALID_NOT_HUMAN @@ -216,6 +244,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /datum/objective/assassinate/update_explanation_text() if(target?.current) explanation_text = "Assassinate [target.current.real_name], the [target.assigned_role]." + var/list/protect_objectives = find_protect_objectives_for_target() + if(length(protect_objectives) > 0) + explanation_text += " Be warned, it seems they have a guardian angel." else explanation_text = "Free Objective" @@ -241,6 +272,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /datum/objective/assassinateonce/update_explanation_text() if(target?.current) explanation_text = "Teach [target.current.real_name], the [target.assigned_role], a lesson they will not forget. The target only needs to die once for success." + var/list/protect_objectives = find_protect_objectives_for_target() + if(length(protect_objectives) > 0) + explanation_text += " Be warned, it seems they have a guardian angel." establish_signals() else explanation_text = "Free Objective" @@ -359,13 +393,60 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) /datum/objective/protect name = "Protect" martyr_compatible = TRUE + delayed_objective_text = "Your objective is to protect another crewmember. You will receive further information in a few minutes." + completed = TRUE /datum/objective/protect/update_explanation_text() if(target?.current) - explanation_text = "Protect [target.current.real_name], the [target.assigned_role]." + explanation_text = "[target.current.real_name], the [target.assigned_role], is in grave danger. Ensure that they remain alive for the duration of the shift." + // Check if there are existing assassination objectives for this target and notify them + var/list/assassination_objectives = find_assassination_objectives_for_target() + if(length(assassination_objectives) > 0) + addtimer(CALLBACK(src, PROC_REF(notify_assassination_objectives)), 5 SECONDS, TIMER_DELETE_ME) else explanation_text = "Free Objective" +/datum/objective/protect/is_invalid_target(datum/mind/possible_target) + . = ..() + // Heads of staff are already protected by the Blueshield. + if((possible_target in SSticker.mode.get_all_heads())) + return TARGET_INVALID_HEAD + // Antags don't need protection. + if(possible_target.special_role) + return TARGET_INVALID_ANTAG + +/datum/objective/protect/find_target(list/target_blacklist) + . = ..() + if(target) // Already have a target, don't need to find one. + return target + // Try to make the target someone who is the target of an assassinate or teach a lesson objective. + var/list/possible_targets = list() + for(var/datum/mind/possible_target in SSticker.minds) + if(is_invalid_target(possible_target) || (possible_target in target_blacklist)) + continue + for(var/datum/objective/O in GLOB.all_objectives) + if((istype(O, /datum/objective/assassinate) || istype(O, /datum/objective/assassinateonce)) && O.target == possible_target) + possible_targets += O.target + break + if(length(possible_targets) > 0) + target = pick(possible_targets) + update_explanation_text() + return target + +// Notifies assassination objectives that their target has a protector. +/datum/objective/protect/proc/notify_assassination_objectives() + if(!target) + return + + var/list/assassination_objectives = find_assassination_objectives_for_target() + for(var/datum/objective/assassination_obj in assassination_objectives) + assassination_obj.update_explanation_text() + var/list/owners = assassination_obj.get_owners() + for(var/datum/mind/M in owners) + SEND_SOUND(M.current, sound('sound/ambience/alarm4.ogg')) + var/list/messages = M.prepare_announce_objectives(FALSE) + to_chat(M.current, chat_box_red(messages.Join("
"))) + /datum/objective/protect/check_completion() if(!target) //If it's a free objective. return TRUE diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 8fd700b977c..b5a4d8be31c 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -431,6 +431,7 @@ GLOBAL_LIST_EMPTY(antagonists) #define KILL_OBJECTIVE "KILL" #define THEFT_OBJECTIVE "STEAL" +#define PROTECT_OBJECTIVE "PROTECT" #define INCRIMINATE_OBJECTIVE "INCRIMINATE" #define DESTROY_OBJECTIVE "DESTROY" @@ -448,7 +449,7 @@ GLOBAL_LIST_EMPTY(antagonists) */ /datum/antagonist/proc/forge_single_human_objective() var/datum/objective/objective_to_add - var/list/static/the_objective_list = list(KILL_OBJECTIVE = 50, THEFT_OBJECTIVE = 45, INCRIMINATE_OBJECTIVE = 5) + var/list/static/the_objective_list = list(KILL_OBJECTIVE = 47, THEFT_OBJECTIVE = 42, INCRIMINATE_OBJECTIVE = 5, PROTECT_OBJECTIVE = 6) var/list/the_nonstatic_kill_list = list(DEBRAIN_OBJECTIVE = 50, MAROON_OBJECTIVE = 285, ASS_ONCE_OBJECTIVE = 199, ASS_OBJECTIVE = 466) // If our org has an objectives list, give one to us if we pass a roll on the org's focus @@ -476,17 +477,22 @@ GLOBAL_LIST_EMPTY(antagonists) if(ASS_OBJECTIVE) objective_to_add = /datum/objective/assassinate + if(THEFT_OBJECTIVE) objective_to_add = /datum/objective/steal if(INCRIMINATE_OBJECTIVE) objective_to_add = /datum/objective/incriminate + if(PROTECT_OBJECTIVE) + objective_to_add = /datum/objective/protect + if(delayed_objectives) objective_to_add = new /datum/objective/delayed(objective_to_add) add_antag_objective(objective_to_add) #undef KILL_OBJECTIVE #undef THEFT_OBJECTIVE +#undef PROTECT_OBJECTIVE #undef INCRIMINATE_OBJECTIVE #undef DESTROY_OBJECTIVE diff --git a/code/modules/antagonists/changeling/datum_changeling.dm b/code/modules/antagonists/changeling/datum_changeling.dm index 9e0485f98cf..a790503df67 100644 --- a/code/modules/antagonists/changeling/datum_changeling.dm +++ b/code/modules/antagonists/changeling/datum_changeling.dm @@ -157,6 +157,8 @@ RESTRICT_TYPE(/datum/antagonist/changeling) if(prob(60)) add_antag_objective(/datum/objective/steal) + else if(prob(10)) + add_antag_objective(/datum/objective/protect) else add_antag_objective(/datum/objective/debrain) diff --git a/code/modules/antagonists/vampire/vamp_datum.dm b/code/modules/antagonists/vampire/vamp_datum.dm index 535f1f0fb3a..ff54031d582 100644 --- a/code/modules/antagonists/vampire/vamp_datum.dm +++ b/code/modules/antagonists/vampire/vamp_datum.dm @@ -358,7 +358,11 @@ RESTRICT_TYPE(/datum/antagonist/vampire) /datum/antagonist/vampire/give_objectives() add_antag_objective(/datum/objective/blood) add_antag_objective(/datum/objective/assassinate) - add_antag_objective(/datum/objective/steal) + + if(prob(5)) // 5% chance of getting protect. 95% chance of getting steal. + add_antag_objective(/datum/objective/protect) + else + add_antag_objective(/datum/objective/steal) if(prob(20)) // 20% chance of getting survive. 80% chance of getting escape. add_antag_objective(/datum/objective/survive)