mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-01-25 08:51:41 +00:00
Vastly improve Protect objective targeting (#31041)
* make protect objective more robusto * small tweak to fix an edge case with timers * add delayed obj text to protect
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#define TARGET_CRYOING 14
|
||||
#define TARGET_INVALID_HEAD 15
|
||||
#define TARGET_INVALID_ANTAG 16
|
||||
#define TARGET_INVALID_CONFLICTING_OBJECTIVE 17
|
||||
|
||||
//gamemode istype helpers
|
||||
#define GAMEMODE_IS_CULT (SSticker && istype(SSticker.mode, /datum/game_mode/cult))
|
||||
|
||||
@@ -109,7 +109,7 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
|
||||
var/list/protect_objectives = list()
|
||||
for(var/datum/objective/protect/P in GLOB.all_objectives)
|
||||
if(P.target == target)
|
||||
if(P.target == target && P.owner && P.holder)
|
||||
protect_objectives += P
|
||||
return protect_objectives
|
||||
|
||||
@@ -123,6 +123,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
|
||||
var/list/assassination_objectives = list()
|
||||
for(var/datum/objective/O in GLOB.all_objectives)
|
||||
if(QDELETED(O) || !O.owner || !O.holder)
|
||||
continue
|
||||
if((istype(O, /datum/objective/assassinate) || istype(O, /datum/objective/assassinateonce)) && O.target == target)
|
||||
assassination_objectives += O
|
||||
return assassination_objectives
|
||||
@@ -282,6 +284,10 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
martyr_compatible = TRUE
|
||||
delayed_objective_text = "Your objective is to assassinate another crewmember. You will receive further information in a few minutes."
|
||||
|
||||
/datum/objective/assassinate/New(text, datum/team/team_to_join, datum/mind/_owner)
|
||||
. = ..()
|
||||
RegisterSignal(src, COMSIG_OBJECTIVE_TARGET_FOUND, PROC_REF(on_target_assigned))
|
||||
|
||||
/datum/objective/assassinate/update_explanation_text()
|
||||
if(target?.current)
|
||||
explanation_text = "Assassinate [target.current.real_name], the [target.assigned_role]."
|
||||
@@ -291,6 +297,41 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
else
|
||||
explanation_text = "Free Objective"
|
||||
|
||||
/datum/objective/assassinate/proc/on_target_assigned(datum/source, datum/mind/new_target)
|
||||
SIGNAL_HANDLER // COMSIG_OBJECTIVE_TARGET_FOUND
|
||||
if(!new_target)
|
||||
return
|
||||
// Notify the first available protect objective that we have a target
|
||||
for(var/datum/objective/protect/protect_obj in GLOB.all_objectives)
|
||||
if(!protect_obj.target && protect_obj.owner && protect_obj.holder)
|
||||
var/datum/mind/assassination_target = protect_obj.try_find_assassination_target()
|
||||
if(assassination_target && !protect_obj.is_invalid_target(assassination_target))
|
||||
protect_obj.target = assassination_target
|
||||
// Cancel the fallback timer since we now have a target
|
||||
if(protect_obj.fallback_timer_id)
|
||||
deltimer(protect_obj.fallback_timer_id)
|
||||
protect_obj.fallback_timer_id = null
|
||||
addtimer(CALLBACK(protect_obj, TYPE_PROC_REF(/datum/objective/protect, notify_protect_objectives)), 1 MINUTES)
|
||||
return
|
||||
|
||||
/datum/objective/assassinate/is_invalid_target(datum/mind/possible_target)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
// Don't assassinate people we're supposed to protect. This shouldn't come up much
|
||||
for(var/datum/mind/M in get_owners())
|
||||
if(QDELETED(M) || !M.current)
|
||||
continue
|
||||
for(var/datum/antagonist/antag in M.antag_datums)
|
||||
if(QDELETED(antag))
|
||||
continue
|
||||
for(var/datum/objective/O in antag.get_antag_objectives(FALSE))
|
||||
if(QDELETED(O))
|
||||
continue
|
||||
if(istype(O, /datum/objective/protect) && O.target == possible_target)
|
||||
return TARGET_INVALID_CONFLICTING_OBJECTIVE
|
||||
|
||||
/datum/objective/assassinate/check_completion()
|
||||
if(..())
|
||||
return TRUE
|
||||
@@ -310,18 +351,58 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
delayed_objective_text = "Your objective is to teach another crewmember a lesson. You will receive further information in a few minutes."
|
||||
var/won = FALSE
|
||||
|
||||
/datum/objective/assassinateonce/New(text, datum/team/team_to_join, datum/mind/_owner)
|
||||
. = ..()
|
||||
RegisterSignal(src, COMSIG_OBJECTIVE_TARGET_FOUND, PROC_REF(on_target_assigned))
|
||||
|
||||
/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()
|
||||
establish_death_signal()
|
||||
else
|
||||
explanation_text = "Free Objective"
|
||||
|
||||
/datum/objective/assassinateonce/establish_signals()
|
||||
RegisterSignal(target.current, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), PROC_REF(check_midround_completion))
|
||||
/datum/objective/assassinateonce/proc/establish_death_signal()
|
||||
if(target?.current)
|
||||
RegisterSignal(target.current, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), PROC_REF(check_midround_completion))
|
||||
|
||||
/datum/objective/assassinateonce/proc/on_target_assigned(datum/source, datum/mind/new_target)
|
||||
SIGNAL_HANDLER // COMSIG_OBJECTIVE_TARGET_FOUND
|
||||
if(!new_target)
|
||||
return
|
||||
// Notify protect objectives that we have a target
|
||||
for(var/datum/objective/protect/protect_obj in GLOB.all_objectives)
|
||||
if(!protect_obj.target && protect_obj.owner && protect_obj.holder)
|
||||
var/datum/mind/assassination_target = protect_obj.try_find_assassination_target()
|
||||
if(assassination_target && !protect_obj.is_invalid_target(assassination_target))
|
||||
protect_obj.target = assassination_target
|
||||
// Cancel the fallback timer since we now have a target
|
||||
if(protect_obj.fallback_timer_id)
|
||||
deltimer(protect_obj.fallback_timer_id)
|
||||
protect_obj.fallback_timer_id = null
|
||||
addtimer(CALLBACK(protect_obj, TYPE_PROC_REF(/datum/objective/protect, notify_protect_objectives)), 1 MINUTES)
|
||||
return
|
||||
|
||||
/datum/objective/assassinateonce/is_invalid_target(datum/mind/possible_target)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
// Don't teach a lesson to people we're supposed to protect. This shouldn't come up much
|
||||
for(var/datum/mind/M in get_owners())
|
||||
if(QDELETED(M) || !M.current)
|
||||
continue
|
||||
for(var/datum/antagonist/antag in M.antag_datums)
|
||||
if(QDELETED(antag))
|
||||
continue
|
||||
for(var/datum/objective/O in antag.get_antag_objectives(FALSE))
|
||||
if(QDELETED(O))
|
||||
continue
|
||||
if(istype(O, /datum/objective/protect) && O.target == possible_target)
|
||||
return TARGET_INVALID_CONFLICTING_OBJECTIVE
|
||||
|
||||
/datum/objective/assassinateonce/check_completion()
|
||||
return won || completed || !target?.current?.ckey
|
||||
@@ -419,6 +500,19 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
if(IS_CHANGELING(possible_target.current))
|
||||
return TARGET_INVALID_CHANGELING
|
||||
|
||||
// Removing someone's brain makes it pretty hard to protect them.
|
||||
for(var/datum/mind/M in get_owners())
|
||||
if(QDELETED(M) || !M.current)
|
||||
continue
|
||||
for(var/datum/antagonist/antag in M.antag_datums)
|
||||
if(QDELETED(antag))
|
||||
continue
|
||||
for(var/datum/objective/O in antag.get_antag_objectives(FALSE))
|
||||
if(QDELETED(O))
|
||||
continue
|
||||
if(istype(O, /datum/objective/protect) && O.target == possible_target)
|
||||
return TARGET_INVALID_CONFLICTING_OBJECTIVE
|
||||
|
||||
/datum/objective/debrain/update_explanation_text()
|
||||
if(target?.current)
|
||||
explanation_text = "Steal the brain of [target.current.real_name], the [target.assigned_role]."
|
||||
@@ -445,17 +539,35 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
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
|
||||
/// Timer for fallback target assignment (randomized between 5-10 minutes)
|
||||
var/fallback_timer_id
|
||||
|
||||
/datum/objective/protect/Destroy()
|
||||
if(fallback_timer_id)
|
||||
deltimer(fallback_timer_id)
|
||||
fallback_timer_id = null
|
||||
return ..()
|
||||
|
||||
/datum/objective/protect/update_explanation_text()
|
||||
if(target?.current)
|
||||
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"
|
||||
// We're waiting for a target to be chosen. Don't want Free Objective to show here.
|
||||
explanation_text = delayed_objective_text
|
||||
|
||||
// Alert protect objective owners, invoked by the kill objectives when their targets are assigned
|
||||
/datum/objective/protect/proc/notify_protect_objectives()
|
||||
update_explanation_text()
|
||||
var/list/protect_owners = get_owners()
|
||||
for(var/datum/mind/M in protect_owners)
|
||||
if(M.current)
|
||||
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/found_target()
|
||||
// Keep from being overridden by Free Objective just because we haven't found a target yet.
|
||||
return target || fallback_timer_id
|
||||
|
||||
/datum/objective/protect/is_invalid_target(datum/mind/possible_target)
|
||||
. = ..()
|
||||
@@ -465,24 +577,101 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
|
||||
// Antags don't need protection.
|
||||
if(possible_target.special_role)
|
||||
return TARGET_INVALID_ANTAG
|
||||
// Don't protect people we're supposed to kill.
|
||||
for(var/datum/mind/M in get_owners())
|
||||
if(QDELETED(M) || !M.current)
|
||||
continue
|
||||
for(var/datum/antagonist/antag in M.antag_datums)
|
||||
if(QDELETED(antag))
|
||||
continue
|
||||
for(var/datum/objective/O in antag.get_antag_objectives(FALSE))
|
||||
if(QDELETED(O))
|
||||
continue
|
||||
if((istype(O, /datum/objective/assassinate) || istype(O, /datum/objective/assassinateonce) || istype(O, /datum/objective/debrain)) && O.target == possible_target)
|
||||
return TARGET_INVALID_CONFLICTING_OBJECTIVE
|
||||
|
||||
// This runs only once, when the objective is created.
|
||||
/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.
|
||||
|
||||
// First, try to find someone who's already targeted by an assassination objective
|
||||
var/datum/mind/assassination_target = try_find_assassination_target()
|
||||
if(assassination_target)
|
||||
target = assassination_target
|
||||
update_explanation_text()
|
||||
// Notify assassin. 1 minute buffer prevents immediately spamming the assassin after their objective block with another notification.
|
||||
addtimer(CALLBACK(src, PROC_REF(notify_assassination_objectives)), 1 MINUTES)
|
||||
// Don't notify the Protect objective, because this path means we found a target immediately, and the initial objectives block will already show the target.
|
||||
return target
|
||||
|
||||
// No assassination target found yet. Set up a fallback timer for 5-10 minutes from now
|
||||
if(!fallback_timer_id)
|
||||
var/fallback_time = rand(5 MINUTES, 10 MINUTES)
|
||||
fallback_timer_id = addtimer(CALLBACK(src, PROC_REF(find_fallback_target)), fallback_time, TIMER_STOPPABLE)
|
||||
|
||||
// Update explanation text to show we're waiting for a target
|
||||
update_explanation_text()
|
||||
return null
|
||||
|
||||
// Try to find a target that's already targeted by assassination objectives
|
||||
/datum/objective/protect/proc/try_find_assassination_target()
|
||||
// Let's prioritize people who are going to be RR'd for protection.
|
||||
var/list/possible_targets = list()
|
||||
|
||||
for(var/datum/objective/O in GLOB.all_objectives)
|
||||
if(QDELETED(O) || !O.owner || !O.holder)
|
||||
continue
|
||||
if((istype(O, /datum/objective/assassinate) && O.target))
|
||||
if(!is_invalid_target(O.target))
|
||||
possible_targets += O.target
|
||||
|
||||
if(length(possible_targets) > 0)
|
||||
return pick(possible_targets)
|
||||
|
||||
// Fall back to people who are going to be taught a lesson.
|
||||
possible_targets = list()
|
||||
|
||||
for(var/datum/objective/O in GLOB.all_objectives)
|
||||
if(QDELETED(O) || !O.owner || !O.holder)
|
||||
continue
|
||||
if((istype(O, /datum/objective/assassinateonce) && O.target))
|
||||
if(!is_invalid_target(O.target))
|
||||
possible_targets += O.target
|
||||
|
||||
if(length(possible_targets) > 0)
|
||||
return pick(possible_targets)
|
||||
|
||||
return null
|
||||
|
||||
// Called at the end of the timer for protect
|
||||
/datum/objective/protect/proc/find_fallback_target(list/target_blacklist)
|
||||
if(!needs_target)
|
||||
return
|
||||
|
||||
deltimer(fallback_timer_id)
|
||||
fallback_timer_id = null
|
||||
|
||||
// First try to find a legitimate assignment one final time
|
||||
var/datum/mind/assassination_target = try_find_assassination_target()
|
||||
if(assassination_target && !is_invalid_target(assassination_target) && !(assassination_target in target_blacklist))
|
||||
target = assassination_target
|
||||
// Both Protect and Assassinate are being jumped with some hot new info. Let's tell them.
|
||||
notify_protect_objectives()
|
||||
notify_assassination_objectives()
|
||||
return
|
||||
|
||||
// Fall back to any valid crew member to protect
|
||||
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
|
||||
possible_targets += possible_target
|
||||
|
||||
if(length(possible_targets) > 0)
|
||||
target = pick(possible_targets)
|
||||
update_explanation_text()
|
||||
return target
|
||||
// No assassin to notify
|
||||
notify_protect_objectives()
|
||||
|
||||
// Notifies assassination objectives that their target has a protector.
|
||||
/datum/objective/protect/proc/notify_assassination_objectives()
|
||||
|
||||
Reference in New Issue
Block a user