diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index 13a67e6694..4c8a2654fa 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -23,3 +23,5 @@ GLOBAL_LIST_EMPTY(simple_animals)
GLOBAL_LIST_EMPTY(language_datum_instances)
GLOBAL_LIST_EMPTY(all_languages)
+
+GLOBAL_LIST_EMPTY(latejoiners) //All latejoining people, for traitor-target purposes.
\ No newline at end of file
diff --git a/code/datums/antagonists/datum_traitor.dm b/code/datums/antagonists/datum_traitor.dm
index 4aad416c62..3e90228c19 100644
--- a/code/datums/antagonists/datum_traitor.dm
+++ b/code/datums/antagonists/datum_traitor.dm
@@ -172,10 +172,16 @@
objective_count += forge_single_objective()
for(var/i = objective_count, i < config.traitor_objectives_amount, i++)
- var/datum/objective/assassinate/kill_objective = new
- kill_objective.owner = owner
- kill_objective.find_target()
- add_objective(kill_objective)
+ if(prob(20)) //AI's are less likely to look for a late-joiner than normal traitors
+ var/datum/objective/assassinate/late/late_objective = new
+ late_objective.owner = owner
+ late_objective.find_target()
+ add_objective(late_objective)
+ else
+ var/datum/objective/assassinate/kill_objective = new
+ kill_objective.owner = owner
+ kill_objective.find_target()
+ add_objective(kill_objective)
var/datum/objective/survive/survive_objective = new
survive_objective.owner = owner
@@ -196,6 +202,11 @@
maroon_objective.owner = owner
maroon_objective.find_target()
add_objective(maroon_objective)
+ else if(prob(50))
+ var/datum/objective/assassinate/late/late_objective = new
+ late_objective.owner = owner
+ late_objective.find_target()
+ add_objective(late_objective)
else
var/datum/objective/assassinate/kill_objective = new
kill_objective.owner = owner
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 180b8bb040..0a03835b1b 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -786,7 +786,7 @@
if(!def_value)//If it's a custom objective, it will be an empty string.
def_value = "custom"
- var/new_obj_type = input("Select objective type:", "Objective type", def_value) as null|anything in list("assassinate", "maroon", "debrain", "protect", "destroy", "prevent", "hijack", "escape", "survive", "martyr", "steal", "download", "nuclear", "capture", "absorb", "custom")
+ var/new_obj_type = input("Select objective type:", "Objective type", def_value) as null|anything in list("assassinate", "late-assassinate", "maroon", "debrain", "protect", "destroy", "prevent", "hijack", "escape", "survive", "martyr", "steal", "download", "nuclear", "capture", "absorb", "custom")
if (!new_obj_type)
return
@@ -821,6 +821,12 @@
//Will display as special role if the target is set as MODE. Ninjas/commandos/nuke ops.
new_objective.update_explanation_text()
+ if ("late-assassinate")
+ new_objective = new /datum/objective/assassinate/late
+ new_objective.owner = src
+ new_objective.target = null
+ new_objective.find_target() //This will begin the "find-target" loop and update their explanation text
+
if ("destroy")
var/list/possible_targets = active_ais(1)
if(possible_targets.len)
diff --git a/code/game/gamemodes/cit_objectives.dm b/code/game/gamemodes/cit_objectives.dm
new file mode 100644
index 0000000000..a6b1b4d464
--- /dev/null
+++ b/code/game/gamemodes/cit_objectives.dm
@@ -0,0 +1,85 @@
+#define MIN_LATE_TARGET_TIME 600 //lower bound of re-rolled timer, 1 min
+#define MAX_LATE_TARGET_TIME 6000 //upper bound of re-rolled timer, 10 min
+#define LATE_TARGET_HIT_CHANCE 70 //How often would the find_target succeed, otherwise it re-rolls later and tries again.
+//Hit chance is here to avoid people checking github and then hovering around new arrivals within the max minute range every round.
+
+/datum/objective/assassinate/late
+ martyr_compatible = 0
+
+
+/datum/objective/assassinate/late/find_target()
+ var/list/possible_targets = list()
+ for(var/mob/M in GLOB.latejoiners)
+ var/datum/mind/possible_target = M.mind
+ if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != 2) && is_unique_objective(possible_target))
+ possible_targets += possible_target
+ if(possible_targets.len > 0 && prob(LATE_TARGET_HIT_CHANCE))
+ target = pick(possible_targets)
+ martyr_compatible = 1 //Might never matter, but I guess if an admin gives another random objective, this should now be compatible
+ update_explanation_text()
+
+ message_admins("[target] has been selected as the assassination target of [owner].")
+ log_game("[target] has been selected as the assassination target of [owner].")
+
+ to_chat(owner, "You hear a crackling noise in your ears, as a one-way syndicate message plays:")
+ to_chat(owner, "You target has been located. To succeed, find and eliminate [target], the [!target_role_type ? target.assigned_role : target.special_role].")
+ return target
+ else
+ update_explanation_text()
+ addtimer(CALLBACK(src, .proc/find_target),rand(MIN_LATE_TARGET_TIME, MAX_LATE_TARGET_TIME))
+ return null
+
+/datum/objective/assassinate/late/find_target_by_role(role, role_type=0, invert=0)
+ var/list/possible_targets = list()
+ for(var/mob/M in GLOB.latejoiners)
+ var/datum/mind/possible_target = M.mind
+ if((possible_target != owner) && ishuman(possible_target.current))
+ var/is_role = 0
+ if(role_type)
+ if(possible_target.special_role == role)
+ is_role++
+ else
+ if(possible_target.assigned_role == role)
+ is_role++
+
+ if(invert)
+ if(is_role)
+ continue
+ possible_targets += possible_target
+ //break
+ else if(is_role)
+ possible_targets += possible_target
+ //break
+ if(possible_targets && prob(LATE_TARGET_HIT_CHANCE))
+ target = pick(possible_targets)
+ update_explanation_text()
+
+ message_admins("[target] has been selected as the assassination target of [owner].")
+ log_game("[target] has been selected as the assassination target of [owner].")
+
+ to_chat(owner, "You hear a crackling noise in your ears, as a one-way syndicate message plays:")
+ to_chat(owner, "You target has been located. To succeed, find and eliminate [target], the [!target_role_type ? target.assigned_role : target.special_role].")
+ else
+ update_explanation_text()
+ addtimer(CALLBACK(src, .proc/find_target_by_role, role, role_type, invert),rand(MIN_LATE_TARGET_TIME, MAX_LATE_TARGET_TIME))
+
+
+
+/datum/objective/assassinate/late/check_completion()
+ if(target && target.current) //If target WAS assigned
+ if(target.current.stat == DEAD || issilicon(target.current) || isbrain(target.current) || target.current.z > 6 || !target.current.ckey) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite
+ return 1
+ return 0
+ else //If no target was ever given
+ if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current))
+ return 0
+ if(!is_special_character(owner.current))
+ return 0
+ return 1
+
+/datum/objective/assassinate/late/update_explanation_text()
+ //..()
+ if(target && target.current)
+ explanation_text = "Assassinate [target.name], the [!target_role_type ? target.assigned_role : target.special_role]."
+ else
+ explanation_text = "Stay alive until your target arrives on the station, you will be notified when the target has been identified."
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 33590b0ccd..770f5107fe 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -43,6 +43,7 @@
return target
/datum/objective/proc/find_target_by_role(role, role_type=0, invert=0)//Option sets either to check assigned role or special role. Default to assigned., invert inverts the check, eg: "Don't choose a Ling"
+ var/list/candidates = list() //Let's compile a list and THEN pick one randomly, not just first come first serve...
for(var/datum/mind/possible_target in get_crewmember_minds())
if((possible_target != owner) && ishuman(possible_target.current))
var/is_role = 0
@@ -56,12 +57,11 @@
if(invert)
if(is_role)
continue
- target = possible_target
- break
+ candidates += possible_target
else if(is_role)
- target = possible_target
- break
-
+ candidates += possible_target
+ if(candidates)
+ target = pick(candidates)
update_explanation_text()
/datum/objective/proc/update_explanation_text()
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index d9e41d7916..54eec1dc8a 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -110,7 +110,7 @@
if(SSticker)
var/tready = text2num(href_list["ready"])
//Avoid updating ready if we're after PREGAME (they should use latejoin instead)
- //This is likely not an actual issue but I don't have time to prove that this
+ //This is likely not an actual issue but I don't have time to prove that this
//no longer is required
if(SSticker.current_state <= GAME_STATE_PREGAME)
ready = tready
@@ -368,6 +368,7 @@
humanc.make_scottish()
GLOB.joined_player_list += character.ckey
+ GLOB.latejoiners += character
if(config.allow_latejoin_antagonists && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais.
if(SSshuttle.emergency)
diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm
index 25dfa13597..b8d9381445 100644
--- a/code/modules/shuttle/arrivals.dm
+++ b/code/modules/shuttle/arrivals.dm
@@ -1,3 +1,4 @@
+
/obj/docking_port/mobile/arrivals
name = "arrivals shuttle"
id = "arrivals"
diff --git a/tgstation.dme b/tgstation.dme
index 39c7f9ed67..173baf04fb 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -389,6 +389,7 @@
#include "code\game\gamemodes\antag_hud.dm"
#include "code\game\gamemodes\antag_spawner.dm"
#include "code\game\gamemodes\antag_spawner_cit.dm"
+#include "code\game\gamemodes\cit_objectives.dm"
#include "code\game\gamemodes\events.dm"
#include "code\game\gamemodes\factions.dm"
#include "code\game\gamemodes\game_mode.dm"