[PTBF] Protect Objective (#30697)

* protect objective

* fix bugs, add protect into rotation

* make sure other antags can roll protect as well

* get vampire chance on par with other antags

* linter oops

* lintma balls

* weight 100

---------

Signed-off-by: Paul <90473506+pwbokie@users.noreply.github.com>
This commit is contained in:
Paul
2025-10-25 16:53:32 -04:00
committed by GitHub
parent 4c48f18cb1
commit ebc920fb7b
5 changed files with 98 additions and 3 deletions
+2
View File
@@ -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))
+82 -1
View File
@@ -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("<br>")))
/datum/objective/protect/check_completion()
if(!target) //If it's a free objective.
return TRUE
@@ -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
@@ -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)
@@ -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)