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"