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

🆑
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.
/🆑
This commit is contained in:
John Willard
2023-07-07 06:27:31 -04:00
committed by GitHub
parent 40be92b1fd
commit 32afa856db
10 changed files with 499 additions and 500 deletions
@@ -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.
+167 -443
View File
@@ -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.</span>")
/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.</span>")
///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, "<span class='warningplain'><span class='cultlarge'>You are the cult's Master</span>. 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 <b><i>once</i></b>. Use these abilities to direct the cult to victory at any cost.</span>")
to_chat(owner.current, span_cultlarge("<span class='warningplain'>You are the cult's Master</span>. \
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 <b><i>once</i></b>. 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 += "<span class='redtext big'>Nar'sie has been killed! The cult will haunt the universe no longer!</span>"
else if(victory)
parts += "<span class='greentext big'>The cult has succeeded! Nar'Sie has snuffed out another torch in the void!</span>"
else
parts += "<span class='redtext big'>The staff managed to stop the cult! Dark words and heresy are no match for Nanotrasen's finest!</span>"
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 += "<b>The cultists' objectives were:</b>"
var/count = 1
for(var/datum/objective/objective in objectives)
if(objective.check_completion())
parts += "<b>Objective #[count]</b>: [objective.explanation_text] [span_greentext("Success!")]"
else
parts += "<b>Objective #[count]</b>: [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 += "<span class='header'>The cultists were:</span>"
if(length(true_cultists))
parts += printplayerlist(true_cultists)
else
parts += printplayerlist(members)
return "<div class='panel redborder'>[parts.Join("<br>")]</div>"
/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
+12 -18
View File
@@ -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 = "<span class='[span]'><b>[title] [findtextEx(user.name, user.real_name) ? user.name : "[user.real_name] (as [user.name])"]:</b> [message]</span>"
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 ..()
@@ -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
+196
View File
@@ -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 += "<span class='redtext big'>Nar'sie has been killed! The cult will haunt the universe no longer!</span>"
else if(victory)
parts += "<span class='greentext big'>The cult has succeeded! Nar'Sie has snuffed out another torch in the void!</span>"
else
parts += "<span class='redtext big'>The staff managed to stop the cult! Dark words and heresy are no match for Nanotrasen's finest!</span>"
if(objectives.len)
parts += "<b>The cultists' objectives were:</b>"
var/count = 1
for(var/datum/objective/objective in objectives)
if(objective.check_completion())
parts += "<b>Objective #[count]</b>: [objective.explanation_text] [span_greentext("Success!")]"
else
parts += "<b>Objective #[count]</b>: [objective.explanation_text] [span_redtext("Fail.")]"
count++
if(members.len)
parts += "<span class='header'>The cultists were:</span>"
if(length(true_cultists))
parts += printplayerlist(true_cultists)
else
parts += printplayerlist(members)
return "<div class='panel redborder'>[parts.Join("<br>")]</div>"
/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()
+1 -1
View File
@@ -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!"
..()
@@ -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