From 32afa856dbb5bcc0a38bc48ee9f4b383c7ca0f86 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Fri, 7 Jul 2023 06:27:31 -0400 Subject: [PATCH] Makes cult leader handling work off of the Cult datum (#76556) ## About The Pull Request Removes Cult master's datum, it's not handled by the Cultist itself, using a helper to promote/demote people to leader. In practice, the only way someone would be demoted is through Admins, so this adds support for Admins to intervene in this Cult stuff if necessary. Moves cult objectives and cult team to their own files Removes the cult master's status effect that constantly processes to send a deathrattle, and instead moves it to a signal hooked to stat change. Also moves some things from ``get_antag_minds`` to checking the team, which doesn't change anything in-game but it does help add the currently non-functional support for several cult teams. Iunno. https://github.com/tgstation/tgstation/assets/53777086/573a4f13-35e1-4f34-9952-62fed10b49c9 ## Why It's Good For The Game Having the cult leader be its own datum has actually been handled like shit. To promote someone to cult leader, we currently make their current cult datum silent, then remove it, and finally add the cult leader datum. This means they lose their spells unless manually given back post-promotion, which sucks (and also, no one has done yet, meaning they just lose all their spells). It also means there's a lot more snowflake things, did you know there's a var to bypass converting mindshielded people? That's so cult masters can be promoted by Cultists who were mindshielded, and they have to be "ownable", that var is to bypass the check for mindshield to "convert" them to leader cultist. ## Changelog :cl: fix: Cultists promoted to Leader no longer lose their spells (rip whoever tried saving up blood rites) admin: Admins can now force promote/demote people from Cult Leader if necessary. /:cl: --- code/__DEFINES/cult.dm | 5 + code/datums/status_effects/buffs.dm | 29 - .../antagonists/_common/antag_datum.dm | 2 +- code/modules/antagonists/cult/cult.dm | 610 +++++------------- code/modules/antagonists/cult/cult_comms.dm | 30 +- .../antagonists/cult/cult_objectives.dm | 108 ++++ code/modules/antagonists/cult/cult_team.dm | 196 ++++++ code/modules/antagonists/cult/runes.dm | 2 +- .../antagonists/revolution/revolution.dm | 15 +- tgstation.dme | 2 + 10 files changed, 499 insertions(+), 500 deletions(-) create mode 100644 code/modules/antagonists/cult/cult_objectives.dm create mode 100644 code/modules/antagonists/cult/cult_team.dm diff --git a/code/__DEFINES/cult.dm b/code/__DEFINES/cult.dm index 93437e31bb0..8042b438ff7 100644 --- a/code/__DEFINES/cult.dm +++ b/code/__DEFINES/cult.dm @@ -43,3 +43,8 @@ GLOBAL_DATUM(cult_narsie, /obj/narsie) #define CULT_VICTORY_MASS_CONVERSION 2 #define CULT_FAILURE_NARSIE_KILLED 1 #define CULT_VICTORY_NUKE 0 + +// Used to determine the roundend report. +#define CULT_VICTORY 1 +#define CULT_LOSS 0 +#define CULT_NARSIE_KILLED -1 diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 62dd6ca4b46..f88afc5fe88 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -72,35 +72,6 @@ desc = "You are being resurrected!" icon_state = "wish_granter" -/datum/status_effect/cult_master - id = "The Cult Master" - duration = -1 - alert_type = null - on_remove_on_mob_delete = TRUE - var/alive = TRUE - -/datum/status_effect/cult_master/proc/deathrattle() - if(!QDELETED(GLOB.cult_narsie)) - return //if Nar'Sie is alive, don't even worry about it - var/area/A = get_area(owner) - for(var/datum/mind/B as anything in get_antag_minds(/datum/antagonist/cult)) - if(isliving(B.current)) - var/mob/living/M = B.current - SEND_SOUND(M, sound('sound/hallucinations/veryfar_noise.ogg')) - to_chat(M, span_cultlarge("The Cult's Master, [owner], has fallen in \the [A]!")) - -/datum/status_effect/cult_master/tick() - if(owner.stat != DEAD && !alive) - alive = TRUE - return - if(owner.stat == DEAD && alive) - alive = FALSE - deathrattle() - -/datum/status_effect/cult_master/on_remove() - deathrattle() - . = ..() - /datum/status_effect/blooddrunk id = "blooddrunk" duration = 10 diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 709741abd96..3c3e30dd45c 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -155,13 +155,13 @@ GLOBAL_LIST_EMPTY(antagonists) return TRUE /datum/antagonist/proc/can_be_owned(datum/mind/new_owner) - . = TRUE var/datum/mind/tested = new_owner || owner if(tested.has_antag_datum(type)) return FALSE for(var/datum/antagonist/badguy as anything in tested.antag_datums) if(is_type_in_typecache(src, badguy.typecache_datum_blacklist)) return FALSE + return TRUE //This will be called in add_antag_datum before owner assignment. //Should return antag datum without owner. diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index affc05f6a4e..7f2e72c4a56 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -1,8 +1,3 @@ -#define SUMMON_POSSIBILITIES 3 -#define CULT_VICTORY 1 -#define CULT_LOSS 0 -#define CULT_NARSIE_KILLED -1 - /datum/antagonist/cult name = "Cultist" roundend_category = "cultists" @@ -10,47 +5,21 @@ antag_moodlet = /datum/mood_event/cult suicide_cry = "FOR NAR'SIE!!" preview_outfit = /datum/outfit/cultist - var/datum/action/innate/cult/comm/communion = new - var/datum/action/innate/cult/mastervote/vote = new - var/datum/action/innate/cult/blood_magic/magic = new job_rank = ROLE_CULTIST antag_hud_name = "cult" - var/ignore_implant = FALSE + + ///The vote ability Cultists have to elect someone to be the leader. + var/datum/action/innate/cult/mastervote/vote_ability = new + + ///Boolean on whether the starting equipment should be given to their inventory. var/give_equipment = FALSE + ///Reference to the Blood cult team they are part of. var/datum/team/cult/cult_team - -/datum/antagonist/cult/get_team() - return cult_team - -/datum/antagonist/cult/create_team(datum/team/cult/new_team) - if(!new_team) - //todo remove this and allow admin buttons to create more than one cult - for(var/datum/antagonist/cult/H in GLOB.antagonists) - if(!H.owner) - continue - if(H.cult_team) - cult_team = H.cult_team - return - cult_team = new /datum/team/cult - cult_team.setup_objectives() - return - if(!istype(new_team)) - stack_trace("Wrong team type passed to [type] initialization.") - cult_team = new_team - -/datum/antagonist/cult/proc/add_objectives() - objectives |= cult_team.objectives - -/datum/antagonist/cult/Destroy() - QDEL_NULL(communion) - QDEL_NULL(vote) - return ..() - /datum/antagonist/cult/can_be_owned(datum/mind/new_owner) - . = ..() - if(. && !ignore_implant) - . = is_convertable_to_cult(new_owner.current,cult_team) + if(!is_convertable_to_cult(new_owner.current, cult_team)) + return FALSE + return ..() /datum/antagonist/cult/greet() . = ..() @@ -58,7 +27,7 @@ owner.announce_objectives() /datum/antagonist/cult/on_gain() - add_objectives() + objectives |= cult_team.objectives . = ..() var/mob/living/current = owner.current if(give_equipment) @@ -81,67 +50,21 @@ return ..() -/datum/antagonist/cult/get_preview_icon() - var/icon/icon = render_preview_outfit(preview_outfit) - - // The longsword is 64x64, but getFlatIcon crunches to 32x32. - // So I'm just going to add it in post, screw it. - - // Center the dude, because item icon states start from the center. - // This makes the image 64x64. - icon.Crop(-15, -15, 48, 48) - - var/obj/item/melee/cultblade/longsword = new - icon.Blend(icon(longsword.lefthand_file, longsword.inhand_icon_state), ICON_OVERLAY) - qdel(longsword) - - // Move the guy back to the bottom left, 32x32. - icon.Crop(17, 17, 48, 48) - - return finish_preview_icon(icon) - -/datum/antagonist/cult/proc/equip_cultist(metal=TRUE) - var/mob/living/carbon/H = owner.current - if(!istype(H)) - return - . += cult_give_item(/obj/item/melee/cultblade/dagger, H) - if(metal) - . += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H) - to_chat(owner, "These will help you start the cult on this station. Use them well, and remember - you are not the only one.") - - -/datum/antagonist/cult/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/mob) - var/list/slots = list( - "backpack" = ITEM_SLOT_BACKPACK, - "left pocket" = ITEM_SLOT_LPOCKET, - "right pocket" = ITEM_SLOT_RPOCKET - ) - - var/T = new item_path(mob) - var/item_name = initial(item_path.name) - var/where = mob.equip_in_one_of_slots(T, slots, indirect_action = TRUE) - if(!where) - to_chat(mob, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).")) - return FALSE - else - to_chat(mob, span_danger("You have a [item_name] in your [where].")) - if(where == "backpack") - mob.back.atom_storage?.show_contents(mob) - return TRUE - /datum/antagonist/cult/apply_innate_effects(mob/living/mob_override) . = ..() - var/mob/living/current = owner.current - if(mob_override) - current = mob_override + var/mob/living/current = owner.current || mob_override handle_clown_mutation(current, mob_override ? null : "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") current.faction |= FACTION_CULT current.grant_language(/datum/language/narsie, TRUE, TRUE, LANGUAGE_CULTIST) - if(!cult_team.cult_master) - vote.Grant(current) + + var/datum/action/innate/cult/comm/communion = new communion.Grant(current) + if(isnull(cult_team.cult_leader_datum)) + vote_ability.Grant(current) if(ishuman(current)) + var/datum/action/innate/cult/blood_magic/magic = new magic.Grant(current) + current.throw_alert("bloodsense", /atom/movable/screen/alert/bloodsense) if(cult_team.cult_risen) current.AddElement(/datum/element/cult_eyes, initial_delay = 0 SECONDS) @@ -152,15 +75,16 @@ /datum/antagonist/cult/remove_innate_effects(mob/living/mob_override) . = ..() - var/mob/living/current = owner.current - if(mob_override) - current = mob_override + var/mob/living/current = owner.current || mob_override handle_clown_mutation(current, removing = FALSE) current.faction -= FACTION_CULT current.remove_language(/datum/language/narsie, TRUE, TRUE, LANGUAGE_CULTIST) - vote.Remove(current) - communion.Remove(current) - magic.Remove(current) + + QDEL_NULL(vote_ability) + for(var/datum/action/innate/cult/cult_buttons in owner.current.actions) + qdel(cult_buttons) + current.update_mob_action_buttons() + current.clear_alert("bloodsense") if (HAS_TRAIT(current, TRAIT_UNNATURAL_RED_GLOWY_EYES)) current.RemoveElement(/datum/element/cult_eyes) @@ -188,12 +112,66 @@ .["Dagger and Metal"] = CALLBACK(src, PROC_REF(admin_give_metal)) .["Remove Dagger and Metal"] = CALLBACK(src, PROC_REF(admin_take_all)) + if(is_cult_leader()) + .["Demote From Leader"] = CALLBACK(src, PROC_REF(demote_from_leader)) + else if(isnull(cult_team.cult_leader_datum)) + .["Make Cult Leader"] = CALLBACK(src, PROC_REF(make_cult_leader)) + +/datum/antagonist/cult/get_team() + return cult_team + +/datum/antagonist/cult/create_team(datum/team/cult/new_team) + if(!new_team) + //todo remove this and allow admin buttons to create more than one cult + for(var/datum/antagonist/cult/H in GLOB.antagonists) + if(!H.owner) + continue + if(H.cult_team) + cult_team = H.cult_team + return + cult_team = new /datum/team/cult + cult_team.setup_objectives() + return + if(!istype(new_team)) + stack_trace("Wrong team type passed to [type] initialization.") + cult_team = new_team + +///Equips the cultist with a dagger and runed metal. +/datum/antagonist/cult/proc/equip_cultist(metal = TRUE) + var/mob/living/carbon/H = owner.current + if(!istype(H)) + return + . += cult_give_item(/obj/item/melee/cultblade/dagger, H) + if(metal) + . += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H) + to_chat(owner, "These will help you start the cult on this station. Use them well, and remember - you are not the only one.") + +///Attempts to make a new item and put it in a potential inventory slot in the provided mob. +/datum/antagonist/cult/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/mob) + var/list/slots = list( + "backpack" = ITEM_SLOT_BACKPACK, + "left pocket" = ITEM_SLOT_LPOCKET, + "right pocket" = ITEM_SLOT_RPOCKET, + ) + + var/T = new item_path(mob) + var/item_name = initial(item_path.name) + var/where = mob.equip_in_one_of_slots(T, slots) + if(!where) + to_chat(mob, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).")) + return FALSE + else + to_chat(mob, span_danger("You have a [item_name] in your [where].")) + if(where == "backpack") + mob.back.atom_storage?.show_contents(mob) + return TRUE + /datum/antagonist/cult/proc/admin_give_dagger(mob/admin) - if(!equip_cultist(metal=FALSE)) + if(!equip_cultist(metal = FALSE)) to_chat(admin, span_danger("Spawning dagger failed!")) /datum/antagonist/cult/proc/admin_give_metal(mob/admin) - if (!equip_cultist(metal=TRUE)) + if (!equip_cultist(metal = TRUE)) to_chat(admin, span_danger("Spawning runed metal failed!")) /datum/antagonist/cult/proc/admin_take_all(mob/admin) @@ -202,295 +180,120 @@ if(istype(o, /obj/item/melee/cultblade/dagger) || istype(o, /obj/item/stack/sheet/runed_metal)) qdel(o) -/datum/antagonist/cult/master - ignore_implant = TRUE - show_in_antagpanel = FALSE //Feel free to add this later +///Returns whether or not this datum is its team's leader. +/datum/antagonist/cult/proc/is_cult_leader() + return (cult_team.cult_leader_datum == src) + +///Turns this antag datum into its team's leader, assigning them their unique abilities, hud, and deathrattle. +/datum/antagonist/cult/proc/make_cult_leader() + if(cult_team.cult_leader_datum) + return FALSE + cult_team.cult_leader_datum = src + antag_hud_name = "cultmaster" - var/datum/action/innate/cult/master/finalreck/reckoning = new + add_team_hud(owner.current) + RegisterSignal(owner.current, COMSIG_MOB_STATCHANGE, PROC_REF(deathrattle)) + + if(!cult_team.reckoning_complete) + var/datum/action/innate/cult/master/finalreck/reckoning = new + reckoning.Grant(owner.current) var/datum/action/innate/cult/master/cultmark/bloodmark = new var/datum/action/innate/cult/master/pulse/throwing = new + bloodmark.Grant(owner.current) + throwing.Grant(owner.current) + owner.current.update_mob_action_buttons() -/datum/antagonist/cult/master/Destroy() - QDEL_NULL(reckoning) - QDEL_NULL(bloodmark) - QDEL_NULL(throwing) - return ..() + for(var/datum/mind/cult_mind as anything in cult_team.members) + vote_ability.Remove(cult_mind.current) + to_chat(cult_mind.current, span_cultlarge("[owner.current] has won the cult's support and is now their master. \ + Follow [owner.current.p_their()] orders to the best of your ability!")) -/datum/antagonist/cult/master/greet() - to_chat(owner.current, "You are the cult's Master. As the cult's Master, you have a unique title and loud voice when communicating, are capable of marking \ - targets, such as a location or a noncultist, to direct the cult to them, and, finally, you are capable of summoning the entire living cult to your location once. Use these abilities to direct the cult to victory at any cost.") + to_chat(owner.current, span_cultlarge("You are the cult's Master. \ + As the cult's Master, you have a unique title and loud voice when communicating, are capable of marking \ + targets, such as a location or a noncultist, to direct the cult to them, and, finally, you are capable of \ + summoning the entire living cult to your location once. Use these abilities to direct the cult \ + to victory at any cost.")) -/datum/antagonist/cult/master/apply_innate_effects(mob/living/mob_override) - . = ..() - var/mob/living/current = owner.current - if(mob_override) - current = mob_override - if(!cult_team.reckoning_complete) - reckoning.Grant(current) - bloodmark.Grant(current) - throwing.Grant(current) - current.update_mob_action_buttons() - current.apply_status_effect(/datum/status_effect/cult_master) - if(cult_team.cult_risen) - current.AddElement(/datum/element/cult_eyes, initial_delay = 0 SECONDS) - if(cult_team.cult_ascendent) - current.AddElement(/datum/element/cult_halo, initial_delay = 0 SECONDS) - add_team_hud(current, /datum/antagonist/cult) + return TRUE -/datum/antagonist/cult/master/remove_innate_effects(mob/living/mob_override) - . = ..() - var/mob/living/current = owner.current - if(mob_override) - current = mob_override - reckoning.Remove(current) - bloodmark.Remove(current) - throwing.Remove(current) - current.update_mob_action_buttons() - current.remove_status_effect(/datum/status_effect/cult_master) +///Admin-only helper to demote someone from Cult leader, taking away their HUD, abilities, and deathrattle +///And gives all cultists from their team back their ability to vote for a new leader. +/datum/antagonist/cult/proc/demote_from_leader() + if(!cult_team.cult_leader_datum) + return FALSE + cult_team.cult_leader_datum = null -/datum/team/cult - name = "\improper Cult" + antag_hud_name = initial(antag_hud_name) + add_team_hud(owner.current) + UnregisterSignal(owner.current, COMSIG_MOB_STATCHANGE) - ///The blood mark target - var/atom/blood_target - ///Image of the blood mark target - var/image/blood_target_image - ///Timer for the blood mark expiration - var/blood_target_reset_timer + var/datum/action/innate/cult/master/finalreck/reckoning = locate() in owner.current.actions + if(reckoning) + reckoning.Remove(owner.current) + var/datum/action/innate/cult/master/cultmark/bloodmark = locate() in owner.current.actions + if(bloodmark) + bloodmark.Remove(owner.current) + var/datum/action/innate/cult/master/pulse/throwing = locate() in owner.current.actions + if(throwing) + throwing.Remove(owner.current) + owner.current.update_mob_action_buttons() + for(var/datum/mind/cult_mind as anything in cult_team.members) + vote_ability.Grant(cult_mind.current) - ///Has a vote been called for a leader? - var/cult_vote_called = FALSE - ///The cult leader - var/mob/living/cult_master - ///Has the mass teleport been used yet? - var/reckoning_complete = FALSE - ///Has the cult risen, and gotten red eyes? - var/cult_risen = FALSE - ///Has the cult asceneded, and gotten halos? - var/cult_ascendent = FALSE + to_chat(owner.current, span_cultlarge("You have been demoted from being the cult's Master, you are now an acolyte once more!")) - ///Has narsie been summoned yet? - var/narsie_summoned = FALSE - ///How large were we at max size. - var/size_at_maximum = 0 - ///list of cultists just before summoning Narsie - var/list/true_cultists = list() + return TRUE -/datum/team/cult/proc/check_size() - if(cult_ascendent) - return - - // This proc is unnecessary clutter whilst running cult related unit tests - // Remove this if, at some point, someone decides to test that halos and eyes are added at expected ratios -#ifndef UNIT_TESTS - var/alive = 0 - var/cultplayers = 0 - for(var/I in GLOB.player_list) - var/mob/M = I - if(M.stat != DEAD) - if(IS_CULTIST(M)) - ++cultplayers - else - ++alive - - ASSERT(cultplayers) //we shouldn't be here. - var/ratio = alive ? cultplayers/alive : 1 - if(ratio > CULT_RISEN && !cult_risen) - for(var/datum/mind/mind as anything in members) - if(mind.current) - SEND_SOUND(mind.current, 'sound/hallucinations/i_see_you2.ogg') - to_chat(mind.current, span_cultlarge(span_warning("The veil weakens as your cult grows, your eyes begin to glow..."))) - mind.current.AddElement(/datum/element/cult_eyes) - cult_risen = TRUE - log_game("The blood cult has risen with [cultplayers] players.") - - if(ratio > CULT_ASCENDENT && !cult_ascendent) - for(var/datum/mind/mind as anything in members) - if(mind.current) - SEND_SOUND(mind.current, 'sound/hallucinations/im_here1.ogg') - to_chat(mind.current, span_cultlarge(span_warning("Your cult is ascendent and the red harvest approaches - you cannot hide your true nature for much longer!!"))) - mind.current.AddElement(/datum/element/cult_halo) - cult_ascendent = TRUE - log_game("The blood cult has ascended with [cultplayers] players.") -#endif - -/datum/team/cult/add_member(datum/mind/new_member) - . = ..() - // A little hacky, but this checks that cult ghosts don't contribute to the size at maximum value. - if(is_unassigned_job(new_member.assigned_role)) - return - size_at_maximum++ - -/datum/team/cult/proc/make_image(datum/objective/sacrifice/sac_objective) - var/datum/job/job_of_sacrifice = sac_objective.target.assigned_role - var/datum/preferences/prefs_of_sacrifice = sac_objective.target.current.client.prefs - var/icon/reshape = get_flat_human_icon(null, job_of_sacrifice, prefs_of_sacrifice, list(SOUTH)) - reshape.Shift(SOUTH, 4) - reshape.Shift(EAST, 1) - reshape.Crop(7,4,26,31) - reshape.Crop(-5,-3,26,30) - sac_objective.sac_image = reshape - -/datum/team/cult/proc/setup_objectives() - var/datum/objective/sacrifice/sacrifice_objective = new - sacrifice_objective.team = src - sacrifice_objective.find_target() - objectives += sacrifice_objective - - var/datum/objective/eldergod/summon_objective = new - summon_objective.team = src - objectives += summon_objective - -/datum/objective/sacrifice - var/sacced = FALSE - var/sac_image - -/// Unregister signals from the old target so it doesn't cause issues when sacrificed of when a new target is found. -/datum/objective/sacrifice/proc/clear_sacrifice() - if(!target) - return - UnregisterSignal(target, COMSIG_MIND_TRANSFERRED) - if(target.current) - UnregisterSignal(target.current, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO)) - target = null - -/datum/objective/sacrifice/find_target(dupe_search_range, list/blacklist) - clear_sacrifice() - if(!istype(team, /datum/team/cult)) - return - var/datum/team/cult/cult = team - var/list/target_candidates = list() - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(player.mind && !player.mind.has_antag_datum(/datum/antagonist/cult) && !is_convertable_to_cult(player) && player.stat != DEAD) - target_candidates += player.mind - if(target_candidates.len == 0) - message_admins("Cult Sacrifice: Could not find unconvertible target, checking for convertible target.") - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(player.mind && !player.mind.has_antag_datum(/datum/antagonist/cult) && player.stat != DEAD) - target_candidates += player.mind - list_clear_nulls(target_candidates) - if(LAZYLEN(target_candidates)) - target = pick(target_candidates) - update_explanation_text() - // Register a bunch of signals to both the target mind and its body - // to stop cult from softlocking everytime the target is deleted before being actually sacrificed. - RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(on_mind_transfer)) - RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del)) - RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap)) - else - message_admins("Cult Sacrifice: Could not find unconvertible or convertible target. WELP!") - sacced = TRUE // Prevents another hypothetical softlock. This basically means every PC is a cultist. - if(!sacced) - cult.make_image(src) - for(var/datum/mind/mind in cult.members) - if(mind.current) - mind.current.clear_alert("bloodsense") - mind.current.throw_alert("bloodsense", /atom/movable/screen/alert/bloodsense) - -/datum/objective/sacrifice/proc/on_target_body_del() +///If dead (and Narsie isn't summoned), will alert all Cultists of their death, sending their location out. +/datum/antagonist/cult/proc/deathrattle(datum/source) SIGNAL_HANDLER - INVOKE_ASYNC(src, PROC_REF(find_target)) -/datum/objective/sacrifice/proc/on_mind_transfer(datum/source, mob/previous_body) - SIGNAL_HANDLER - //If, for some reason, the mind was transferred to a ghost (better safe than sorry), find a new target. - if(!isliving(target.current)) - INVOKE_ASYNC(src, PROC_REF(find_target)) + if(owner.current.stat != DEAD) return - UnregisterSignal(previous_body, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO)) - RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del)) - RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap)) - -/datum/objective/sacrifice/proc/on_possible_mindswap(mob/source) - SIGNAL_HANDLER - UnregisterSignal(target.current, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO)) - //we check if the mind is bodyless only after mindswap shenanigeans to avoid issues. - addtimer(CALLBACK(src, PROC_REF(do_we_have_a_body)), 0 SECONDS) - -/datum/objective/sacrifice/proc/do_we_have_a_body() - if(!target.current) //The player was ghosted and the mind isn't probably going to be transferred to another mob at this point. - find_target() + if(!QDELETED(GLOB.cult_narsie)) + return + if(!is_cult_leader()) return - RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del)) - RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap)) -/datum/objective/sacrifice/check_completion() - return sacced || completed + var/area/current_area = get_area(owner.current) + for(var/datum/mind/cult_mind as anything in cult_team.members) + SEND_SOUND(cult_mind, sound('sound/hallucinations/veryfar_noise.ogg')) + to_chat(cult_mind, span_cultlarge("The Cult's Master, [owner.current.name], has fallen in \the [current_area]!")) -/datum/objective/sacrifice/update_explanation_text() - if(target) - explanation_text = "Sacrifice [target], the [target.assigned_role.title] via invoking an Offer rune with [target.p_them()] on it and three acolytes around it." - else - explanation_text = "The veil has already been weakened here, proceed to the final objective." +/datum/antagonist/cult/get_preview_icon() + var/icon/icon = render_preview_outfit(preview_outfit) -/datum/objective/eldergod - var/summoned = FALSE - var/killed = FALSE - var/list/summon_spots = list() + // The longsword is 64x64, but getFlatIcon crunches to 32x32. + // So I'm just going to add it in post, screw it. -/datum/objective/eldergod/New() - ..() - var/sanity = 0 - while(summon_spots.len < SUMMON_POSSIBILITIES && sanity < 100) - var/area/summon_area = pick(GLOB.areas - summon_spots) - if(summon_area && is_station_level(summon_area.z) && (summon_area.area_flags & VALID_TERRITORY)) - summon_spots += summon_area - sanity++ - update_explanation_text() + // Center the dude, because item icon states start from the center. + // This makes the image 64x64. + icon.Crop(-15, -15, 48, 48) -/datum/objective/eldergod/update_explanation_text() - explanation_text = "Summon Nar'Sie by invoking the rune 'Summon Nar'Sie'. The summoning can only be accomplished in [english_list(summon_spots)] - where the veil is weak enough for the ritual to begin." + var/obj/item/melee/cultblade/longsword = new + icon.Blend(icon(longsword.lefthand_file, longsword.inhand_icon_state), ICON_OVERLAY) + qdel(longsword) -/datum/objective/eldergod/check_completion() - if(killed) - return CULT_NARSIE_KILLED // You failed so hard that even the code went backwards. - return summoned || completed + // Move the guy back to the bottom left, 32x32. + icon.Crop(17, 17, 48, 48) -/datum/team/cult/proc/check_cult_victory() - for(var/datum/objective/O in objectives) - if(O.check_completion() == CULT_NARSIE_KILLED) - return CULT_NARSIE_KILLED - else if(!O.check_completion()) - return CULT_LOSS - return CULT_VICTORY + return finish_preview_icon(icon) -/datum/team/cult/roundend_report() - var/list/parts = list() - var/victory = check_cult_victory() +/datum/outfit/cultist + name = "Cultist (Preview only)" - if(victory == CULT_NARSIE_KILLED) // Epic failure, you summoned your god and then someone killed it. - parts += "Nar'sie has been killed! The cult will haunt the universe no longer!" - else if(victory) - parts += "The cult has succeeded! Nar'Sie has snuffed out another torch in the void!" - else - parts += "The staff managed to stop the cult! Dark words and heresy are no match for Nanotrasen's finest!" + uniform = /obj/item/clothing/under/color/black + suit = /obj/item/clothing/suit/hooded/cultrobes/alt + head = /obj/item/clothing/head/hooded/cult_hoodie/alt + shoes = /obj/item/clothing/shoes/cult/alt + r_hand = /obj/item/melee/blood_magic/stun - if(objectives.len) - parts += "The cultists' objectives were:" - var/count = 1 - for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - parts += "Objective #[count]: [objective.explanation_text] [span_greentext("Success!")]" - else - parts += "Objective #[count]: [objective.explanation_text] [span_redtext("Fail.")]" - count++ +/datum/outfit/cultist/post_equip(mob/living/carbon/human/equipped, visualsOnly) + equipped.eye_color_left = BLOODCULT_EYE + equipped.eye_color_right = BLOODCULT_EYE + equipped.update_body() - if(members.len) - parts += "The cultists were:" - if(length(true_cultists)) - parts += printplayerlist(true_cultists) - else - parts += printplayerlist(members) - - return "
[parts.Join("
")]
" - -/datum/team/cult/proc/is_sacrifice_target(datum/mind/mind) - for(var/datum/objective/sacrifice/sac_objective in objectives) - if(mind == sac_objective.target) - return TRUE - return FALSE - -/// Returns whether the given mob is convertable to the blood cult +///Returns whether the given mob is convertable to the blood cult /proc/is_convertable_to_cult(mob/living/target, datum/team/cult/specific_cult) if(!istype(target)) return FALSE @@ -510,82 +313,3 @@ if(HAS_TRAIT(target, TRAIT_MINDSHIELD) || issilicon(target) || isbot(target) || isdrone(target)) return FALSE //can't convert machines, shielded, or braindead return TRUE - -/// Sets a blood target for the cult. -/datum/team/cult/proc/set_blood_target(atom/new_target, mob/marker, duration = 90 SECONDS) - if(QDELETED(new_target)) - CRASH("A null or invalid target was passed to set_blood_target.") - - if(duration != INFINITY && blood_target_reset_timer) - return FALSE - - deltimer(blood_target_reset_timer) - blood_target = new_target - RegisterSignal(blood_target, COMSIG_QDELETING, PROC_REF(unset_blood_target_and_timer)) - var/area/target_area = get_area(new_target) - - blood_target_image = image('icons/effects/mouse_pointers/cult_target.dmi', new_target, "glow", ABOVE_MOB_LAYER) - blood_target_image.appearance_flags = RESET_COLOR - blood_target_image.pixel_x = -new_target.pixel_x - blood_target_image.pixel_y = -new_target.pixel_y - - for(var/datum/mind/cultist as anything in members) - if(!cultist.current) - continue - if(cultist.current.stat == DEAD || !cultist.current.client) - continue - - to_chat(cultist.current, span_bold(span_cultlarge("[marker] has marked [blood_target] in the [target_area.name] as the cult's top priority, get there immediately!"))) - SEND_SOUND(cultist.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'), 0, 1, 75)) - cultist.current.client.images += blood_target_image - - if(duration != INFINITY) - blood_target_reset_timer = addtimer(CALLBACK(src, PROC_REF(unset_blood_target)), duration, TIMER_STOPPABLE) - return TRUE - -/// Unsets out blood target, clearing the images from all the cultists. -/datum/team/cult/proc/unset_blood_target() - blood_target_reset_timer = null - - for(var/datum/mind/cultist as anything in members) - if(!cultist.current) - continue - if(cultist.current.stat == DEAD || !cultist.current.client) - continue - - if(QDELETED(blood_target)) - to_chat(cultist.current, span_bold(span_cultlarge("The blood mark's target is lost!"))) - else - to_chat(cultist.current, span_bold(span_cultlarge("The blood mark has expired!"))) - cultist.current.client.images -= blood_target_image - - UnregisterSignal(blood_target, COMSIG_QDELETING) - blood_target = null - - QDEL_NULL(blood_target_image) - -/// Unsets our blood target when they get deleted. -/datum/team/cult/proc/unset_blood_target_and_timer(datum/source) - SIGNAL_HANDLER - - deltimer(blood_target_reset_timer) - unset_blood_target() - -/datum/outfit/cultist - name = "Cultist (Preview only)" - - uniform = /obj/item/clothing/under/color/black - suit = /obj/item/clothing/suit/hooded/cultrobes/alt - head = /obj/item/clothing/head/hooded/cult_hoodie/alt - shoes = /obj/item/clothing/shoes/cult/alt - r_hand = /obj/item/melee/blood_magic/stun - -/datum/outfit/cultist/post_equip(mob/living/carbon/human/equipped, visualsOnly) - equipped.eye_color_left = BLOODCULT_EYE - equipped.eye_color_right = BLOODCULT_EYE - equipped.update_body() - -#undef CULT_LOSS -#undef CULT_NARSIE_KILLED -#undef CULT_VICTORY -#undef SUMMON_POSSIBILITIES diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm index 90df1717c80..b61d8b7c197 100644 --- a/code/modules/antagonists/cult/cult_comms.dm +++ b/code/modules/antagonists/cult/cult_comms.dm @@ -46,20 +46,20 @@ /datum/action/innate/cult/comm/proc/cultist_commune(mob/living/user, message) var/my_message - if(!message) + if(!message || !user.mind) return user.whisper("O bidai nabora se[pick("'","`")]sma!", language = /datum/language/common) user.whisper(html_decode(message), filterproof = TRUE) var/title = "Acolyte" var/span = "cult italic" - if(user.mind && user.mind.has_antag_datum(/datum/antagonist/cult/master)) + var/datum/antagonist/cult/cult_datum = user.mind.has_antag_datum(/datum/antagonist/cult) + if(cult_datum.is_cult_leader()) span = "cultlarge" title = "Master" else if(!ishuman(user)) title = "Construct" my_message = "[title] [findtextEx(user.name, user.real_name) ? user.name : "[user.real_name] (as [user.name])"]: [message]" - for(var/i in GLOB.player_list) - var/mob/M = i + for(var/mob/M as anything in GLOB.player_list) if(IS_CULTIST(M)) to_chat(M, my_message) else if(M in GLOB.dead_mob_list) @@ -108,7 +108,7 @@ var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE) pollCultists(owner,C.cult_team) -/proc/pollCultists(mob/living/Nominee,datum/team/cult/team) //Cult Master Poll +/proc/pollCultists(mob/living/Nominee, datum/team/cult/team) //Cult Master Poll if(world.time < CULT_POLL_WAIT) to_chat(Nominee, "It would be premature to select a leader while everyone is still settling in, try again in [DisplayTimeText(CULT_POLL_WAIT-world.time)].") return @@ -150,22 +150,16 @@ if(!B.current.incapacitated()) to_chat(B.current, span_cultlarge("[Nominee] could not win the cult's support and shall continue to serve as an acolyte.")) return FALSE - team.cult_master = Nominee - var/datum/antagonist/cult/cultist = Nominee.mind.has_antag_datum(/datum/antagonist/cult) - if (cultist) - cultist.silent = TRUE - cultist.on_removal() - Nominee.mind.add_antag_datum(/datum/antagonist/cult/master) - for(var/datum/mind/B in team.members) - if(B.current) - for(var/datum/action/innate/cult/mastervote/vote in B.current.actions) - vote.Remove(B.current) - if(!B.current.incapacitated()) - to_chat(B.current,span_cultlarge("[Nominee] has won the cult's support and is now their master. Follow [Nominee.p_their()] orders to the best of your ability!")) + var/datum/antagonist/cult/cult_datum = Nominee.mind.has_antag_datum(/datum/antagonist/cult) + if(!cult_datum.make_cult_leader()) + CRASH("[cult_datum.owner.current] was supposed to turn into the leader, but they didn't for some reason. This isn't supposed to happen unless an Admin messed with it.") return TRUE /datum/action/innate/cult/master/IsAvailable(feedback = FALSE) - if(!owner.mind || !owner.mind.has_antag_datum(/datum/antagonist/cult/master) || GLOB.cult_narsie) + if(!owner.mind || GLOB.cult_narsie) + return FALSE + var/datum/antagonist/cult/cult_datum = owner.mind.has_antag_datum(/datum/antagonist/cult) + if(!cult_datum.is_cult_leader()) return FALSE return ..() diff --git a/code/modules/antagonists/cult/cult_objectives.dm b/code/modules/antagonists/cult/cult_objectives.dm new file mode 100644 index 00000000000..d290b212ab2 --- /dev/null +++ b/code/modules/antagonists/cult/cult_objectives.dm @@ -0,0 +1,108 @@ +#define SUMMON_POSSIBILITIES 3 + +/datum/objective/sacrifice + var/sacced = FALSE + var/sac_image + +/// Unregister signals from the old target so it doesn't cause issues when sacrificed of when a new target is found. +/datum/objective/sacrifice/proc/clear_sacrifice() + if(!target) + return + UnregisterSignal(target, COMSIG_MIND_TRANSFERRED) + if(target.current) + UnregisterSignal(target.current, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO)) + target = null + +/datum/objective/sacrifice/find_target(dupe_search_range, list/blacklist) + clear_sacrifice() + if(!istype(team, /datum/team/cult)) + return + var/datum/team/cult/cult = team + var/list/target_candidates = list() + for(var/mob/living/carbon/human/player in GLOB.player_list) + if(player.mind && !player.mind.has_antag_datum(/datum/antagonist/cult) && !is_convertable_to_cult(player) && player.stat != DEAD) + target_candidates += player.mind + if(target_candidates.len == 0) + message_admins("Cult Sacrifice: Could not find unconvertible target, checking for convertible target.") + for(var/mob/living/carbon/human/player in GLOB.player_list) + if(player.mind && !player.mind.has_antag_datum(/datum/antagonist/cult) && player.stat != DEAD) + target_candidates += player.mind + list_clear_nulls(target_candidates) + if(LAZYLEN(target_candidates)) + target = pick(target_candidates) + update_explanation_text() + // Register a bunch of signals to both the target mind and its body + // to stop cult from softlocking everytime the target is deleted before being actually sacrificed. + RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(on_mind_transfer)) + RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del)) + RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap)) + else + message_admins("Cult Sacrifice: Could not find unconvertible or convertible target. WELP!") + sacced = TRUE // Prevents another hypothetical softlock. This basically means every PC is a cultist. + if(!sacced) + cult.make_image(src) + for(var/datum/mind/mind in cult.members) + if(mind.current) + mind.current.clear_alert("bloodsense") + mind.current.throw_alert("bloodsense", /atom/movable/screen/alert/bloodsense) + +/datum/objective/sacrifice/proc/on_target_body_del() + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(find_target)) + +/datum/objective/sacrifice/proc/on_mind_transfer(datum/source, mob/previous_body) + SIGNAL_HANDLER + //If, for some reason, the mind was transferred to a ghost (better safe than sorry), find a new target. + if(!isliving(target.current)) + INVOKE_ASYNC(src, PROC_REF(find_target)) + return + UnregisterSignal(previous_body, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO)) + RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del)) + RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap)) + +/datum/objective/sacrifice/proc/on_possible_mindswap(mob/source) + SIGNAL_HANDLER + UnregisterSignal(target.current, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO)) + //we check if the mind is bodyless only after mindswap shenanigeans to avoid issues. + addtimer(CALLBACK(src, PROC_REF(do_we_have_a_body)), 0 SECONDS) + +/datum/objective/sacrifice/proc/do_we_have_a_body() + if(!target.current) //The player was ghosted and the mind isn't probably going to be transferred to another mob at this point. + find_target() + return + RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del)) + RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap)) + +/datum/objective/sacrifice/check_completion() + return sacced || completed + +/datum/objective/sacrifice/update_explanation_text() + if(target) + explanation_text = "Sacrifice [target], the [target.assigned_role.title] via invoking an Offer rune with [target.p_them()] on it and three acolytes around it." + else + explanation_text = "The veil has already been weakened here, proceed to the final objective." + +/datum/objective/eldergod + var/summoned = FALSE + var/killed = FALSE + var/list/summon_spots = list() + +/datum/objective/eldergod/New() + ..() + var/sanity = 0 + while(summon_spots.len < SUMMON_POSSIBILITIES && sanity < 100) + var/area/summon_area = pick(GLOB.areas - summon_spots) + if(summon_area && is_station_level(summon_area.z) && (summon_area.area_flags & VALID_TERRITORY)) + summon_spots += summon_area + sanity++ + update_explanation_text() + +/datum/objective/eldergod/update_explanation_text() + explanation_text = "Summon Nar'Sie by invoking the rune 'Summon Nar'Sie'. The summoning can only be accomplished in [english_list(summon_spots)] - where the veil is weak enough for the ritual to begin." + +/datum/objective/eldergod/check_completion() + if(killed) + return CULT_NARSIE_KILLED // You failed so hard that even the code went backwards. + return summoned || completed + +#undef SUMMON_POSSIBILITIES diff --git a/code/modules/antagonists/cult/cult_team.dm b/code/modules/antagonists/cult/cult_team.dm new file mode 100644 index 00000000000..7cfbc51b1a7 --- /dev/null +++ b/code/modules/antagonists/cult/cult_team.dm @@ -0,0 +1,196 @@ +/datum/team/cult + name = "\improper Cult" + + ///The blood mark target + var/atom/blood_target + ///Image of the blood mark target + var/image/blood_target_image + ///Timer for the blood mark expiration + var/blood_target_reset_timer + + ///Has a vote been called for a leader? + var/cult_vote_called = FALSE + ///The cult leader + var/datum/antagonist/cult/cult_leader_datum + ///Has the mass teleport been used yet? + var/reckoning_complete = FALSE + ///Has the cult risen, and gotten red eyes? + var/cult_risen = FALSE + ///Has the cult asceneded, and gotten halos? + var/cult_ascendent = FALSE + + ///Has narsie been summoned yet? + var/narsie_summoned = FALSE + ///How large were we at max size. + var/size_at_maximum = 0 + ///list of cultists just before summoning Narsie + var/list/true_cultists = list() + +/datum/team/cult/proc/check_size() + if(cult_ascendent) + return + + // This proc is unnecessary clutter whilst running cult related unit tests + // Remove this if, at some point, someone decides to test that halos and eyes are added at expected ratios +#ifndef UNIT_TESTS + var/alive = 0 + var/cultplayers = 0 + for(var/I in GLOB.player_list) + var/mob/M = I + if(M.stat != DEAD) + if(IS_CULTIST(M)) + ++cultplayers + else + ++alive + + ASSERT(cultplayers) //we shouldn't be here. + var/ratio = alive ? cultplayers/alive : 1 + if(ratio > CULT_RISEN && !cult_risen) + for(var/datum/mind/mind as anything in members) + if(mind.current) + SEND_SOUND(mind.current, 'sound/hallucinations/i_see_you2.ogg') + to_chat(mind.current, span_cultlarge(span_warning("The veil weakens as your cult grows, your eyes begin to glow..."))) + mind.current.AddElement(/datum/element/cult_eyes) + cult_risen = TRUE + log_game("The blood cult has risen with [cultplayers] players.") + + if(ratio > CULT_ASCENDENT && !cult_ascendent) + for(var/datum/mind/mind as anything in members) + if(mind.current) + SEND_SOUND(mind.current, 'sound/hallucinations/im_here1.ogg') + to_chat(mind.current, span_cultlarge(span_warning("Your cult is ascendent and the red harvest approaches - you cannot hide your true nature for much longer!!"))) + mind.current.AddElement(/datum/element/cult_halo) + cult_ascendent = TRUE + log_game("The blood cult has ascended with [cultplayers] players.") +#endif + +/datum/team/cult/add_member(datum/mind/new_member) + . = ..() + // A little hacky, but this checks that cult ghosts don't contribute to the size at maximum value. + if(is_unassigned_job(new_member.assigned_role)) + return + size_at_maximum++ + +/datum/team/cult/proc/make_image(datum/objective/sacrifice/sac_objective) + var/datum/job/job_of_sacrifice = sac_objective.target.assigned_role + var/datum/preferences/prefs_of_sacrifice = sac_objective.target.current.client.prefs + var/icon/reshape = get_flat_human_icon(null, job_of_sacrifice, prefs_of_sacrifice, list(SOUTH)) + reshape.Shift(SOUTH, 4) + reshape.Shift(EAST, 1) + reshape.Crop(7,4,26,31) + reshape.Crop(-5,-3,26,30) + sac_objective.sac_image = reshape + +/datum/team/cult/proc/setup_objectives() + var/datum/objective/sacrifice/sacrifice_objective = new + sacrifice_objective.team = src + sacrifice_objective.find_target() + objectives += sacrifice_objective + + var/datum/objective/eldergod/summon_objective = new + summon_objective.team = src + objectives += summon_objective + +/datum/team/cult/proc/check_cult_victory() + for(var/datum/objective/O in objectives) + if(O.check_completion() == CULT_NARSIE_KILLED) + return CULT_NARSIE_KILLED + else if(!O.check_completion()) + return CULT_LOSS + return CULT_VICTORY + +/datum/team/cult/roundend_report() + var/list/parts = list() + var/victory = check_cult_victory() + + if(victory == CULT_NARSIE_KILLED) // Epic failure, you summoned your god and then someone killed it. + parts += "Nar'sie has been killed! The cult will haunt the universe no longer!" + else if(victory) + parts += "The cult has succeeded! Nar'Sie has snuffed out another torch in the void!" + else + parts += "The staff managed to stop the cult! Dark words and heresy are no match for Nanotrasen's finest!" + + if(objectives.len) + parts += "The cultists' objectives were:" + var/count = 1 + for(var/datum/objective/objective in objectives) + if(objective.check_completion()) + parts += "Objective #[count]: [objective.explanation_text] [span_greentext("Success!")]" + else + parts += "Objective #[count]: [objective.explanation_text] [span_redtext("Fail.")]" + count++ + + if(members.len) + parts += "The cultists were:" + if(length(true_cultists)) + parts += printplayerlist(true_cultists) + else + parts += printplayerlist(members) + + return "
[parts.Join("
")]
" + +/datum/team/cult/proc/is_sacrifice_target(datum/mind/mind) + for(var/datum/objective/sacrifice/sac_objective in objectives) + if(mind == sac_objective.target) + return TRUE + return FALSE + +/// Sets a blood target for the cult. +/datum/team/cult/proc/set_blood_target(atom/new_target, mob/marker, duration = 90 SECONDS) + if(QDELETED(new_target)) + CRASH("A null or invalid target was passed to set_blood_target.") + + if(duration != INFINITY && blood_target_reset_timer) + return FALSE + + deltimer(blood_target_reset_timer) + blood_target = new_target + RegisterSignal(blood_target, COMSIG_QDELETING, PROC_REF(unset_blood_target_and_timer)) + var/area/target_area = get_area(new_target) + + blood_target_image = image('icons/effects/mouse_pointers/cult_target.dmi', new_target, "glow", ABOVE_MOB_LAYER) + blood_target_image.appearance_flags = RESET_COLOR + blood_target_image.pixel_x = -new_target.pixel_x + blood_target_image.pixel_y = -new_target.pixel_y + + for(var/datum/mind/cultist as anything in members) + if(!cultist.current) + continue + if(cultist.current.stat == DEAD || !cultist.current.client) + continue + + to_chat(cultist.current, span_bold(span_cultlarge("[marker] has marked [blood_target] in the [target_area.name] as the cult's top priority, get there immediately!"))) + SEND_SOUND(cultist.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'), 0, 1, 75)) + cultist.current.client.images += blood_target_image + + if(duration != INFINITY) + blood_target_reset_timer = addtimer(CALLBACK(src, PROC_REF(unset_blood_target)), duration, TIMER_STOPPABLE) + return TRUE + +/// Unsets out blood target, clearing the images from all the cultists. +/datum/team/cult/proc/unset_blood_target() + blood_target_reset_timer = null + + for(var/datum/mind/cultist as anything in members) + if(!cultist.current) + continue + if(cultist.current.stat == DEAD || !cultist.current.client) + continue + + if(QDELETED(blood_target)) + to_chat(cultist.current, span_bold(span_cultlarge("The blood mark's target is lost!"))) + else + to_chat(cultist.current, span_bold(span_cultlarge("The blood mark has expired!"))) + cultist.current.client.images -= blood_target_image + + UnregisterSignal(blood_target, COMSIG_QDELETING) + blood_target = null + + QDEL_NULL(blood_target_image) + +/// Unsets our blood target when they get deleted. +/datum/team/cult/proc/unset_blood_target_and_timer(datum/source) + SIGNAL_HANDLER + + deltimer(blood_target_reset_timer) + unset_blood_target() diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 10fdea9ed1a..368ace5d0ee 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -236,7 +236,7 @@ structure_check() searches for nearby cultist structures required for the invoca var/mob/living/F = invokers[1] var/datum/antagonist/cult/C = F.mind.has_antag_datum(/datum/antagonist/cult,TRUE) var/datum/team/cult/Cult_team = C.cult_team - var/is_convertable = is_convertable_to_cult(L,C.cult_team) + var/is_convertable = is_convertable_to_cult(L, C.cult_team) if(L.stat != DEAD && is_convertable) invocation = "Mah'weyh pleggh at e'ntrath!" ..() diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm index 63a11ecef7c..081596420f5 100644 --- a/code/modules/antagonists/revolution/revolution.dm +++ b/code/modules/antagonists/revolution/revolution.dm @@ -22,14 +22,13 @@ var/victory_message = "The revolution has overpowered the command staff! Viva la revolution! Execute any head of staff and security should you find them alive." /datum/antagonist/rev/can_be_owned(datum/mind/new_owner) - . = ..() - if(.) - if(new_owner.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) - return FALSE - if(new_owner.unconvertable) - return FALSE - if(new_owner.current && HAS_TRAIT(new_owner.current, TRAIT_MINDSHIELD)) - return FALSE + if(new_owner.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) + return FALSE + if(new_owner.unconvertable) + return FALSE + if(new_owner.current && HAS_TRAIT(new_owner.current, TRAIT_MINDSHIELD)) + return FALSE + return ..() /datum/antagonist/rev/admin_add(datum/mind/new_owner, mob/admin) // No revolution exists which means admin adding this will create a new revolution team diff --git a/tgstation.dme b/tgstation.dme index 5a3665c57e0..367d0624a11 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2594,11 +2594,13 @@ #include "code\modules\antagonists\cult\cult_bastard_sword.dm" #include "code\modules\antagonists\cult\cult_comms.dm" #include "code\modules\antagonists\cult\cult_items.dm" +#include "code\modules\antagonists\cult\cult_objectives.dm" #include "code\modules\antagonists\cult\cult_structure_altar.dm" #include "code\modules\antagonists\cult\cult_structure_archives.dm" #include "code\modules\antagonists\cult\cult_structure_forge.dm" #include "code\modules\antagonists\cult\cult_structure_pylon.dm" #include "code\modules\antagonists\cult\cult_structures.dm" +#include "code\modules\antagonists\cult\cult_team.dm" #include "code\modules\antagonists\cult\cult_turf_overlay.dm" #include "code\modules\antagonists\cult\rune_spawn_action.dm" #include "code\modules\antagonists\cult\runes.dm"