mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-22 19:47:22 +01:00
Datumizes Cult. (#24379)
* cult 1 * massive data refactor * progress * More crap * IM SCARED IT COMPILES * oops * more fixes * good comment * hell yeah, team control * lol * blamo * blam * More stuff * team refactor * epic merge fail * src not _src_ * more * progress * cult * more stuff * water * goodbye __IMPLIED_TYPE__ * time to undraft * FUCK FUCK FUCK * okay this is better * goodbye todos * fix * order of operations * last fix? maybe * yeah * oops * okay this should be ALL the fixes * wow * hell yeah * wow * fixes duplicate teams + adds more team safeties * how the fuck did this happen * admin objective improvements * wah more bullshit * guh * fuuuuck * fucking hell * fixes
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
GLOBAL_LIST_EMPTY(antagonists)
|
||||
|
||||
#define SUCCESSFUL_DETACH "dont touch this string numbnuts"
|
||||
|
||||
/datum/antagonist
|
||||
/// The name of the antagonist.
|
||||
var/name = "Antagonist"
|
||||
@@ -33,6 +35,8 @@ GLOBAL_LIST_EMPTY(antagonists)
|
||||
var/clown_gain_text = "You are no longer clumsy."
|
||||
/// If the owner is a clown, this text will be displayed to them when they lose this datum.
|
||||
var/clown_removal_text = "You are clumsy again."
|
||||
/// The spawn class to use for gain/removal clown text
|
||||
var/clown_text_span_class = "boldnotice"
|
||||
/// The url page name for this antagonist, appended to the end of the wiki url in the form of: [GLOB.configuration.url.wiki_url]/index.php/[wiki_page_name]
|
||||
var/wiki_page_name
|
||||
|
||||
@@ -56,8 +60,8 @@ GLOBAL_LIST_EMPTY(antagonists)
|
||||
/datum/antagonist/Destroy(force, ...)
|
||||
qdel(objective_holder)
|
||||
GLOB.antagonists -= src
|
||||
if(!QDELETED(owner))
|
||||
detach_from_owner()
|
||||
if(!QDELETED(owner) && detach_from_owner() != SUCCESSFUL_DETACH)
|
||||
stack_trace("[src] ([type]) failed to detach from owner! This is very bad!")
|
||||
|
||||
return ..()
|
||||
|
||||
@@ -79,6 +83,7 @@ GLOBAL_LIST_EMPTY(antagonists)
|
||||
LAZYREMOVE(owner.antag_datums, src)
|
||||
restore_last_hud_and_role()
|
||||
owner = null
|
||||
return SUCCESSFUL_DETACH
|
||||
|
||||
/**
|
||||
* Adds the owner to their respective gamemode's list. For example `SSticker.mode.traitors |= owner`.
|
||||
@@ -408,3 +413,5 @@ GLOBAL_LIST_EMPTY(antagonists)
|
||||
/// This is the custom blurb message used on login for an antagonist.
|
||||
/datum/antagonist/proc/custom_blurb()
|
||||
return FALSE
|
||||
|
||||
#undef SUCCESSFUL_DETACH
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
|
||||
#define DEFAULT_TEAM_NAME "Generic Team Name"
|
||||
#define DEFAULT_TEAM_NAME "Generic/Custom Team"
|
||||
|
||||
/**
|
||||
* # Antagonist Team
|
||||
@@ -11,7 +11,7 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
/// The name of the team.
|
||||
var/name = DEFAULT_TEAM_NAME
|
||||
/// A list of [minds][/datum/mind] who belong to this team.
|
||||
var/list/datum/mind/members
|
||||
var/list/datum/mind/members = list()
|
||||
/// A list of objectives which all team members share.
|
||||
var/datum/objective_holder/objective_holder
|
||||
/// Type of antag datum members of this team have. Also given to new members added by admins.
|
||||
@@ -21,40 +21,87 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
|
||||
/datum/team/New(list/starting_members)
|
||||
..()
|
||||
members = list()
|
||||
if(!can_create_team())
|
||||
QDEL_IN(src, 0 SECONDS) // Give us time to crash so we can get the full call stack
|
||||
CRASH("[src] ([type]) is not allowed to be created, this may be a duplicate team. Deleting...")
|
||||
// Assign the team before member assignment to prevent duplicate teams
|
||||
assign_team()
|
||||
if(!create_team(starting_members))
|
||||
CRASH("[src] ([type]) somehow failed to create a team!")
|
||||
|
||||
/datum/team/proc/create_team(list/starting_members)
|
||||
PROTECTED_PROC(TRUE)
|
||||
objective_holder = new(src)
|
||||
if(starting_members && !islist(starting_members))
|
||||
starting_members = list(starting_members)
|
||||
for(var/datum/mind/M as anything in starting_members)
|
||||
add_member(M)
|
||||
GLOB.antagonist_teams += src
|
||||
return TRUE
|
||||
|
||||
/datum/team/Destroy(force = FALSE, ...)
|
||||
for(var/datum/mind/member as anything in members)
|
||||
remove_member(member)
|
||||
clear_team_reference() // Team reference must come AFTER removing all members, otherwise antag datums will not get removed
|
||||
qdel(objective_holder)
|
||||
members.Cut()
|
||||
GLOB.antagonist_teams -= src
|
||||
return ..()
|
||||
|
||||
/datum/team/proc/can_create_team()
|
||||
return TRUE
|
||||
|
||||
/datum/team/proc/assign_team()
|
||||
return
|
||||
|
||||
/datum/team/proc/clear_team_reference()
|
||||
return
|
||||
|
||||
/**
|
||||
* Adds `new_member` to this team.
|
||||
*
|
||||
* Generally this should ONLY be called by `add_antag_datum()` to ensure proper order of operations.
|
||||
* This is an interface proc, to prevent handle_removing_member from being called multiple times.
|
||||
* It is better if this is only called from `add_antag_datum()`, but it is not required.
|
||||
*/
|
||||
/datum/team/proc/add_member(datum/mind/new_member)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
var/datum/antagonist/antag = get_antag_datum_from_member(new_member) // make sure they have the antag datum
|
||||
/datum/team/proc/add_member(datum/mind/new_member, force = FALSE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
if(!force && (new_member in members))
|
||||
return FALSE
|
||||
members |= new_member
|
||||
handle_adding_member(new_member)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* An internal proc to allow teams to handle custom parts of adding a member.
|
||||
* This should ONLY be called by `add_member()` to ensure proper order of operations.
|
||||
*/
|
||||
/datum/team/proc/handle_adding_member(datum/mind/new_member)
|
||||
PROTECTED_PROC(TRUE)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
var/datum/antagonist/antag = get_antag_datum_from_member(new_member) // make sure they have the antag datum
|
||||
if(!antag) // this team has no antag role, we'll add it directly to their mind team
|
||||
LAZYDISTINCTADD(new_member.teams, src)
|
||||
|
||||
/**
|
||||
* Removes `member` from this team.
|
||||
* This is an interface proc, to prevent handle_removing_member from being called multiple times.
|
||||
*/
|
||||
/datum/team/proc/remove_member(datum/mind/member)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
/datum/team/proc/remove_member(datum/mind/member, force = FALSE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
if(!force && !(member in members))
|
||||
return FALSE
|
||||
members -= member
|
||||
handle_removing_member(member)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* An internal proc for teams to remove a member.
|
||||
*/
|
||||
/datum/team/proc/handle_removing_member(datum/mind/member, force = FALSE)
|
||||
PROTECTED_PROC(TRUE)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
LAZYREMOVE(member.teams, src)
|
||||
var/datum/antagonist/antag = get_antag_datum_from_member(member)
|
||||
if(!QDELETED(antag))
|
||||
@@ -70,13 +117,17 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
continue
|
||||
valid_minds[H.real_name] = H.mind
|
||||
|
||||
if(!length(valid_minds))
|
||||
to_chat(user, "<span class='warning'>No suitable humanoid targets found!</span>")
|
||||
return
|
||||
var/name = input(user, "Choose a player to add to this team", "Add Team Member") as null|anything in valid_minds
|
||||
if(!name)
|
||||
to_chat(user, "<span class='warning'>No suitable humanoid targets found!</span>")
|
||||
return
|
||||
|
||||
var/datum/mind/new_member = valid_minds[name]
|
||||
add_member(new_member)
|
||||
add_member(new_member, TRUE)
|
||||
log_admin("[key_name(usr)] added [key_name(new_member)] to the team '[src]'.")
|
||||
message_admins("[key_name_admin(usr)] added [key_name(new_member)] to the team '[src]'.")
|
||||
|
||||
/**
|
||||
* Adds a team objective to each member's matching antag datum.
|
||||
@@ -178,21 +229,61 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
message_admins("Team Message: [key_name(user)] -> '[name]' team. Message: [message]")
|
||||
log_admin("Team Message: [key_name(user)] -> '[name]' team. Message: [message]")
|
||||
|
||||
#define SEPERATOR "---"
|
||||
/**
|
||||
* Allows admins to add a team objective.
|
||||
* Minimize overriding this proc please.
|
||||
*/
|
||||
/datum/team/proc/admin_add_objective(mob/user)
|
||||
var/selected = input("Select an objective type:", "Objective Type") as null|anything in GLOB.admin_objective_list
|
||||
if(!selected)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
// available_objectives is assoc, `objective name` = `objective_path`
|
||||
var/list/available_objectives = get_admin_priority_objectives()
|
||||
if(length(available_objectives))
|
||||
available_objectives[SEPERATOR] = "Whatever, we never read this"
|
||||
available_objectives += GLOB.admin_objective_list
|
||||
var/selected = input("Select an objective type:", "Objective Type") as null|anything in available_objectives
|
||||
if(!selected || selected == SEPERATOR)
|
||||
return
|
||||
|
||||
var/objective_type = GLOB.admin_objective_list[selected]
|
||||
var/objective_type = available_objectives[selected]
|
||||
var/return_value = handle_adding_admin_objective(user, objective_type)
|
||||
|
||||
if(istype(return_value, /datum/objective)) // handle_adding_admin_objective can return TRUE if its handled
|
||||
add_team_objective(return_value)
|
||||
|
||||
else
|
||||
if(return_value & TEAM_ADMIN_ADD_OBJ_PURPOSEFUL_CANCEL)
|
||||
return
|
||||
if(!(return_value & TEAM_ADMIN_ADD_OBJ_SUCCESS))
|
||||
to_chat(user, "<span class='warning'>[src] team failed to properly handle your selected objective, if you believe this was an error, tell a coder.</span>")
|
||||
return
|
||||
if(return_value & TEAM_ADMIN_ADD_OBJ_CANCEL_LOG) // Logs are being handled elsewhere
|
||||
return
|
||||
|
||||
message_admins("[key_name_admin(user)] added objective [objective_type] to the team '[name]'.")
|
||||
log_admin("[key_name(user)] added objective [objective_type] to the team '[name]'.")
|
||||
|
||||
#undef SEPERATOR
|
||||
|
||||
/**
|
||||
* Overridable logic for handling how the adding of objectives works works
|
||||
* Can return an objective datum, or a boolean.
|
||||
* Returns a boolean if its already added to the team objectives in a custom way
|
||||
*/
|
||||
/datum/team/proc/handle_adding_admin_objective(mob/user, objective_type)
|
||||
PROTECTED_PROC(TRUE)
|
||||
var/datum/objective/O = new objective_type(team_to_join = src)
|
||||
O.find_target(get_target_excludes()) // Blacklist any team members from being the target.
|
||||
add_team_objective(O)
|
||||
return O
|
||||
|
||||
message_admins("[key_name_admin(user)] added objective [O.type] to the team '[name]'.")
|
||||
log_admin("[key_name(user)] added objective [O.type] to the team '[name]'.")
|
||||
|
||||
/**
|
||||
* Returns an associated list of priority objectives for admins to add to the team, this is like
|
||||
* Must return in the form `objective name` = `objective_path`.
|
||||
*/
|
||||
/datum/team/proc/get_admin_priority_objectives()
|
||||
return list()
|
||||
|
||||
/**
|
||||
* Allows admins to announce objectives to all team members.
|
||||
@@ -239,7 +330,7 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
/datum/team/proc/admin_remove_member(mob/user, datum/mind/M)
|
||||
message_admins("[key_name_admin(user)] removed [key_name_admin(M)] from the team '[name]'.")
|
||||
log_admin("[key_name(user)] removed [key_name(M)] from the team '[name]'.")
|
||||
remove_member(M)
|
||||
remove_member(M, TRUE)
|
||||
|
||||
// Used for running team specific admin commands.
|
||||
/datum/team/Topic(href, href_list)
|
||||
@@ -251,8 +342,35 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
if(href_list["command"] == admin_command)
|
||||
var/datum/callback/C = commands[admin_command]
|
||||
C.Invoke(usr)
|
||||
usr.client.holder.check_teams()
|
||||
return
|
||||
|
||||
/datum/team/proc/get_admin_html()
|
||||
var/list/content = list()
|
||||
content += "<h3>[name] - [type]</h3>"
|
||||
content += "<a href='?_src_=holder;team_command=rename_team;team=[UID()]'>Rename Team</a>"
|
||||
content += "<a href='?_src_=holder;team_command=delete_team;team=[UID()]'>Delete Team</a>"
|
||||
content += "<a href='?_src_=holder;team_command=communicate;team=[UID()]'>OOC Message Team</a>"
|
||||
content += ADMIN_VV(src, "View Variables")
|
||||
for(var/command in get_admin_commands())
|
||||
// src is UID() so it points to `/datum/team/Topic` instead of `/datum/admins/Topic`.
|
||||
content += "<a href='?src=[UID()];command=[command]'>[command]</a>"
|
||||
content += "<br><br>Objectives:<br><ol>"
|
||||
for(var/datum/objective/O as anything in objective_holder.get_objectives())
|
||||
if(!istype(O))
|
||||
stack_trace("Non-objective found in [type]'s objective_holder.get_objectives()")
|
||||
continue
|
||||
content += "<li>[O.explanation_text] - <a href='?_src_=holder;team_command=remove_objective;team=[UID()];objective=[O.UID()]'>Remove</a></li>"
|
||||
content += "</ol><a href='?_src_=holder;team_command=add_objective;team=[UID()]'>Add Objective</a><br>"
|
||||
if(objective_holder.has_objectives())
|
||||
content += "</ol><a href='?_src_=holder;team_command=announce_objectives;team=[UID()]'>Announce Objectives to All Members</a><br><br>"
|
||||
content += "Members: <br><ol>"
|
||||
for(var/datum/mind/M as anything in members)
|
||||
content += "<li>[M.name] - <a href='?_src_=holder;team_command=view_member;team=[UID()];member=[M.UID()]'>Show Player Panel</a>"
|
||||
content += "<a href='?_src_=holder;team_command=remove_member;team=[UID()];member=[M.UID()]'>Remove Member</a></li>"
|
||||
content += "</ol><a href='?_src_=holder;team_command=admin_add_member;team=[UID()]'>Add Member</a>"
|
||||
return content
|
||||
|
||||
/**
|
||||
* A list of team-specific admin commands for this team. Should be in the form of `"command" = CALLBACK(x, PROC_REF(some_proc))`.
|
||||
*/
|
||||
@@ -279,26 +397,22 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
|
||||
if(!length(GLOB.antagonist_teams))
|
||||
content += "There are currently no antag teams.<br/>"
|
||||
content += "<a href='?_src_=holder;team_command=new_custom_team;'>Create new Team</a>"
|
||||
for(var/datum/team/T as anything in GLOB.antagonist_teams) // with multiple teams, this is going to get messy. It should probably be turned into a tabs-like system
|
||||
content += "<h3>[T.name] - [T.type]</h3>"
|
||||
content += "<a href='?_src_=holder;team_command=rename_team;team=[T.UID()]'>Rename Team</a>"
|
||||
content += "<a href='?_src_=holder;team_command=delete_team;team=[T.UID()]'>Delete Team</a>"
|
||||
content += "<a href='?_src_=holder;team_command=communicate;team=[T.UID()]'>Message Team</a>"
|
||||
content += ADMIN_VV(T, "View Variables")
|
||||
for(var/command in T.get_admin_commands())
|
||||
// _src_ is T.UID() so it points to `/datum/team/Topic` instead of `/datum/admins/Topic`.
|
||||
content += "<a href='?_src_=[T.UID()];command=[command]'>[command]</a>"
|
||||
content += "<br><br>Objectives:<br><ol>"
|
||||
for(var/datum/objective/O as anything in T.objective_holder.get_objectives())
|
||||
content += "<li>[O.explanation_text] - <a href='?_src_=holder;team_command=remove_objective;team=[T.UID()];objective=[O.UID()]'>Remove</a></li>"
|
||||
content += "</ol><a href='?_src_=holder;team_command=add_objective;team=[T.UID()]'>Add Objective</a><br>"
|
||||
if(T.objective_holder.has_objectives())
|
||||
content += "</ol><a href='?_src_=holder;team_command=announce_objectives;team=[T.UID()]'>Announce Objectives to All Members</a><br><br>"
|
||||
content += "Members: <br><ol>"
|
||||
for(var/datum/mind/M as anything in T.members)
|
||||
content += "<li>[M.name] - <a href='?_src_=holder;team_command=view_member;team=[T.UID()];member=[M.UID()]'>Show Player Panel</a>"
|
||||
content += "<a href='?_src_=holder;team_command=remove_member;team=[T.UID()];member=[M.UID()]'>Remove Member</a></li>"
|
||||
content += "</ol><a href='?_src_=holder;team_command=admin_add_member;team=[T.UID()]'>Add Member</a><hr>"
|
||||
content += "<a href='?_src_=holder;team_command=reload;'>Reload Menu</a><br>"
|
||||
if(length(GLOB.antagonist_teams) > 1)
|
||||
var/index = 1
|
||||
for(var/datum/team/T as anything in GLOB.antagonist_teams)
|
||||
content += "<a href='?_src_=holder;team_command=switch_team_tab;team_index=[index]'>[T.name]</a>"
|
||||
index++
|
||||
else
|
||||
team_switch_tab_index = 1
|
||||
|
||||
if(length(GLOB.antagonist_teams))
|
||||
content += "<hr>"
|
||||
team_switch_tab_index = clamp(team_switch_tab_index, 1, length(GLOB.antagonist_teams))
|
||||
var/datum/team/T = GLOB.antagonist_teams[team_switch_tab_index]
|
||||
if(istype(T))
|
||||
var/list/stringy_list = T.get_admin_html()
|
||||
content += stringy_list.Join()
|
||||
return content.Join()
|
||||
|
||||
#undef DEFAULT_TEAM_NAME
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/datum/antagonist/cultist
|
||||
name = "Cultist"
|
||||
job_rank = ROLE_CULTIST
|
||||
special_role = SPECIAL_ROLE_CULTIST
|
||||
give_objectives = FALSE
|
||||
antag_hud_name = "hudcultist"
|
||||
antag_hud_type = ANTAG_HUD_CULT
|
||||
clown_gain_text = "A dark power has allowed you to overcome your clownish nature, letting you wield weapons without harming yourself."
|
||||
clown_removal_text = "You are free of the dark power suppressing your clownish nature. You are clumsy again! Honk!"
|
||||
clown_text_span_class = "cultitalic"
|
||||
wiki_page_name = "Cultist"
|
||||
var/remove_gear_on_removal = FALSE
|
||||
|
||||
/datum/antagonist/cultist/on_gain()
|
||||
create_team() // make sure theres a global cult team
|
||||
..()
|
||||
owner.current.faction |= "cult"
|
||||
add_cult_actions()
|
||||
SEND_SOUND(owner.current, sound('sound/ambience/antag/bloodcult.ogg'))
|
||||
owner.current.create_log(CONVERSION_LOG, "Converted to the cult")
|
||||
owner.current.create_attack_log("<span class='danger'>Has been converted to the cult!</span>")
|
||||
|
||||
var/datum/team/cult/cult = get_team()
|
||||
ASSERT(cult)
|
||||
if(cult.cult_risen)
|
||||
rise()
|
||||
if(cult.cult_ascendant)
|
||||
ascend()
|
||||
cult.study_objectives(owner.current)
|
||||
|
||||
/datum/antagonist/cultist/detach_from_owner()
|
||||
if(!owner.current)
|
||||
return ..()
|
||||
owner.current.faction -= "cult"
|
||||
owner.current.create_log(CONVERSION_LOG, "Deconverted from the cult") // yes, this is its own log, instead of the default MISC_LOG
|
||||
for(var/datum/action/innate/cult/C in owner.current.actions)
|
||||
qdel(C)
|
||||
|
||||
if(!ishuman(owner.current))
|
||||
return ..()
|
||||
var/mob/living/carbon/human/H = owner.current
|
||||
REMOVE_TRAIT(H, CULT_EYES, null)
|
||||
H.change_eye_color(H.original_eye_color, FALSE)
|
||||
H.update_eyes()
|
||||
H.remove_overlay(HALO_LAYER)
|
||||
H.update_body()
|
||||
|
||||
if(remove_gear_on_removal)
|
||||
for(var/I in H.contents)
|
||||
if(is_type_in_list(I, CULT_CLOTHING))
|
||||
H.unEquip(I)
|
||||
return ..()
|
||||
|
||||
|
||||
/datum/antagonist/cultist/greet()
|
||||
return "<span class='cultlarge'>You catch a glimpse of the Realm of [GET_CULT_DATA(entity_name, "this is a bug at this point")], [GET_CULT_DATA(entity_title3, "I dont know what else to write")]. \
|
||||
You now see how flimsy the world is, you see that it should be open to the knowledge of [GET_CULT_DATA(entity_name, "making a bug report")].</span>"
|
||||
|
||||
/datum/antagonist/cultist/farewell()
|
||||
if(owner && owner.current)
|
||||
owner.current.visible_message("<span class='cult'>[owner.current] looks like [owner.current.p_they()] just reverted to [owner.current.p_their()] old faith!</span>",
|
||||
"<span class='userdanger'>An unfamiliar white light flashes through your mind, cleansing the taint of [GET_CULT_DATA(entity_title1, "Nar'Sie")] and the memories of your time as their servant with it.</span>")
|
||||
|
||||
/datum/antagonist/cultist/create_team(team)
|
||||
return SSticker.mode.get_cult_team()
|
||||
|
||||
/datum/antagonist/cultist/get_team()
|
||||
return SSticker.mode.cult_team
|
||||
|
||||
/datum/antagonist/cultist/on_body_transfer(old_body, new_body)
|
||||
var/datum/team/cult/cult = get_team()
|
||||
cult.cult_body_transfer(old_body, new_body)
|
||||
add_cult_actions()
|
||||
|
||||
/datum/antagonist/cultist/proc/rise()
|
||||
if(!ishuman(owner.current))
|
||||
return
|
||||
var/mob/living/carbon/human/H = owner.current
|
||||
if(!H.original_eye_color)
|
||||
H.original_eye_color = H.get_eye_color()
|
||||
H.change_eye_color(BLOODCULT_EYE, FALSE)
|
||||
ADD_TRAIT(H, CULT_EYES, CULT_TRAIT)
|
||||
H.update_eyes()
|
||||
H.update_body()
|
||||
|
||||
/datum/antagonist/cultist/proc/ascend()
|
||||
if(!ishuman(owner.current))
|
||||
return
|
||||
var/mob/living/carbon/human/H = owner.current
|
||||
new /obj/effect/temp_visual/cult/sparks(get_turf(H), H.dir)
|
||||
H.update_halo_layer()
|
||||
|
||||
/datum/antagonist/cultist/proc/descend()
|
||||
if(!ishuman(owner.current))
|
||||
return
|
||||
var/mob/living/carbon/human/H = owner.current
|
||||
new /obj/effect/temp_visual/cult/sparks(get_turf(H), H.dir)
|
||||
H.update_halo_layer()
|
||||
to_chat(H, "<span class='userdanger'>The halo above your head shatters!</span>")
|
||||
playsound(H, "shatter", 50, TRUE)
|
||||
|
||||
/datum/antagonist/cultist/proc/add_cult_actions()
|
||||
if(!owner.current)
|
||||
return
|
||||
var/datum/action/innate/cult/comm/communicate_spell = new
|
||||
var/datum/action/innate/cult/check_progress/progress_report = new
|
||||
communicate_spell.Grant(owner.current)
|
||||
progress_report.Grant(owner.current)
|
||||
if(ishuman(owner.current))
|
||||
var/datum/action/innate/cult/blood_magic/magic = new
|
||||
var/datum/action/innate/cult/use_dagger/dagger = new
|
||||
magic.Grant(owner.current)
|
||||
dagger.Grant(owner.current)
|
||||
|
||||
owner.current.update_action_buttons(TRUE)
|
||||
|
||||
/datum/antagonist/cultist/proc/equip_roundstart_cultist()
|
||||
if(!ishuman(owner.current))
|
||||
return FALSE
|
||||
. |= cult_give_item(/obj/item/melee/cultblade/dagger)
|
||||
. |= cult_give_item(/obj/item/stack/sheet/runed_metal/ten)
|
||||
to_chat(owner.current, "<span class='cult'>These will help you start the cult on this station. Use them well, and remember - you are not the only one.</span>")
|
||||
|
||||
/datum/antagonist/cultist/proc/cult_give_item(obj/item/item_path)
|
||||
if(!ishuman(owner.current))
|
||||
return
|
||||
var/mob/living/carbon/human/H = owner.current
|
||||
var/list/slots = list(
|
||||
"backpack" = SLOT_HUD_IN_BACKPACK,
|
||||
"left pocket" = SLOT_HUD_LEFT_STORE,
|
||||
"right pocket" = SLOT_HUD_RIGHT_STORE
|
||||
)
|
||||
|
||||
var/where = H.equip_in_one_of_slots(new item_path(H), slots)
|
||||
if(where)
|
||||
to_chat(H, "<span class='danger'>You have \a [initial(item_path.name)] in your [where].</span>")
|
||||
if(H.s_active) // Update whatever inventory they have open
|
||||
H.s_active.orient2hud(H)
|
||||
H.s_active.show_to(H)
|
||||
return TRUE
|
||||
to_chat(H, "<span class='userdanger'>Unfortunately, you weren't able to get \a [initial(item_path.name)]. This is very bad and you should adminhelp immediately (press F1).</span>")
|
||||
return FALSE
|
||||
@@ -0,0 +1,569 @@
|
||||
/datum/team/cult
|
||||
name = "Cult"
|
||||
antag_datum_type = /datum/antagonist/cultist
|
||||
|
||||
/// Does the cult have glowing eyes
|
||||
var/cult_risen = FALSE
|
||||
/// Does the cult have halos
|
||||
var/cult_ascendant = FALSE
|
||||
/// How many crew need to be converted to rise
|
||||
var/rise_number
|
||||
/// How many crew need to be converted to ascend
|
||||
var/ascend_number
|
||||
/// Used for the CentComm announcement at ascension
|
||||
var/ascend_percent
|
||||
/// Variable used for tracking the progress of the cult's sacrifices & god summonings
|
||||
var/cult_status = NARSIE_IS_ASLEEP
|
||||
|
||||
/// God summon objective added when ready_to_summon() is called
|
||||
var/datum/objective/eldergod/obj_summon
|
||||
var/sacrifices_done = 0
|
||||
var/sacrifices_required = 2
|
||||
|
||||
/// Are cultist mirror shields active yet?
|
||||
var/mirror_shields_active = FALSE
|
||||
|
||||
// Disables the station-wide announcements, unused except for admin editing.
|
||||
var/no_announcements = FALSE
|
||||
|
||||
/datum/team/cult/create_team(list/starting_members)
|
||||
cult_threshold_check() // Set this ALWAYS before any check_cult_size check, or
|
||||
. = ..()
|
||||
|
||||
objective_holder.add_objective(/datum/objective/servecult)
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(cult_threshold_check)), 2 MINUTES) // Check again in 2 minutes for latejoiners
|
||||
|
||||
cult_status = NARSIE_DEMANDS_SACRIFICE
|
||||
|
||||
create_next_sacrifice()
|
||||
|
||||
for(var/datum/mind/M as anything in starting_members)
|
||||
var/datum/antagonist/cultist/cultist = M.has_antag_datum(/datum/antagonist/cultist)
|
||||
cultist.equip_roundstart_cultist()
|
||||
|
||||
/datum/team/cult/can_create_team()
|
||||
return isnull(SSticker.mode.cult_team)
|
||||
|
||||
/datum/team/cult/assign_team()
|
||||
SSticker.mode.cult_team = src
|
||||
|
||||
/datum/team/cult/clear_team_reference()
|
||||
if(SSticker.mode.cult_team == src)
|
||||
SSticker.mode.cult_team = null
|
||||
else
|
||||
CRASH("[src] ([type]) attempted to clear a team reference that wasn't itself!")
|
||||
|
||||
/datum/team/cult/handle_adding_member(datum/mind/new_member)
|
||||
. = ..()
|
||||
check_cult_size()
|
||||
RegisterSignal(new_member.current, COMSIG_MOB_STATCHANGE, PROC_REF(cultist_stat_change))
|
||||
RegisterSignal(new_member.current, COMSIG_PARENT_QDELETING, PROC_REF(cultist_deleting))
|
||||
|
||||
/datum/team/cult/handle_removing_member(datum/mind/member)
|
||||
. = ..()
|
||||
UnregisterSignal(member.current, COMSIG_MOB_STATCHANGE)
|
||||
UnregisterSignal(member.current, COMSIG_PARENT_QDELETING)
|
||||
check_cult_size()
|
||||
|
||||
/datum/team/cult/on_round_end()
|
||||
var/list/endtext = list()
|
||||
endtext += "<br><b>The cultists' objectives were:</b>"
|
||||
for(var/datum/objective/obj in objective_holder.get_objectives())
|
||||
endtext += "<br>[obj.explanation_text] - "
|
||||
if(!obj.check_completion())
|
||||
endtext += "<font color='red'>Fail.</font>"
|
||||
else
|
||||
endtext += "<font color='green'><B>Success!</B></font>"
|
||||
|
||||
to_chat(world, endtext.Join(""))
|
||||
|
||||
/datum/team/cult/proc/add_cult_immunity(mob/living/target)
|
||||
ADD_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
|
||||
addtimer(CALLBACK(src, PROC_REF(remove_cult_immunity), target), 1 MINUTES)
|
||||
|
||||
/datum/team/cult/proc/remove_cult_immunity(mob/living/target)
|
||||
REMOVE_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
|
||||
|
||||
/**
|
||||
* Makes sure that the signal stays on the correct body when a cultist changes bodies
|
||||
*/
|
||||
/datum/team/cult/proc/cult_body_transfer(old_body, new_body)
|
||||
UnregisterSignal(old_body, COMSIG_MOB_STATCHANGE)
|
||||
UnregisterSignal(old_body, COMSIG_PARENT_QDELETING)
|
||||
RegisterSignal(new_body, COMSIG_MOB_STATCHANGE, PROC_REF(cultist_stat_change))
|
||||
RegisterSignal(new_body, COMSIG_PARENT_QDELETING, PROC_REF(cultist_deleting))
|
||||
|
||||
/**
|
||||
* Returns the current number of cultists and constructs.
|
||||
*
|
||||
* Returns the number of cultists and constructs in a list ([1] = Cultists, [2] = Constructs), or as one combined number.
|
||||
*
|
||||
* * separate - Should the number be returned as a list with two separate values (Humans and Constructs) or as one number.
|
||||
*/
|
||||
/datum/team/cult/proc/get_cultists(separate = FALSE)
|
||||
var/cultists = 0
|
||||
var/constructs = 0
|
||||
var/list/minds_to_remove = list()
|
||||
for(var/datum/mind/M as anything in members)
|
||||
if(isnull(M))
|
||||
stack_trace("Found a null mind in /datum/team/cult's members. Removing...")
|
||||
minds_to_remove |= M // I don't really want to remove them while iterating, as I'm not sure how byond would handle that while iterating over members
|
||||
continue
|
||||
if(isnull(M.current))
|
||||
stack_trace("Found a mind with no body in /datum/team/cult's members. Removing...")
|
||||
minds_to_remove |= M // I don't really want to remove them while iterating, as I'm not sure how byond would handle that while iterating over members
|
||||
continue
|
||||
if(QDELETED(M) || M.current.stat == DEAD)
|
||||
continue
|
||||
if(ishuman(M.current) && !M.current.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
|
||||
cultists++
|
||||
else if(isconstruct(M.current))
|
||||
constructs++
|
||||
|
||||
if(length(minds_to_remove))
|
||||
for(var/datum/mind/M as anything in minds_to_remove)
|
||||
remove_member(M)
|
||||
|
||||
if(separate)
|
||||
return list(cultists, constructs)
|
||||
return cultists + constructs
|
||||
|
||||
/datum/team/cult/proc/cultist_stat_change(mob/target_cultist, new_stat, old_stat)
|
||||
SIGNAL_HANDLER
|
||||
if(new_stat == old_stat) // huh, how? whatever, we ignore it
|
||||
return
|
||||
if(new_stat != DEAD && old_stat != DEAD)
|
||||
return // switching between alive and unconcious
|
||||
// switching between dead and alive/unconcious
|
||||
INVOKE_ASYNC(src, PROC_REF(check_cult_size))
|
||||
|
||||
/datum/team/cult/proc/cultist_deleting(mob/deleting_cultist)
|
||||
SIGNAL_HANDLER
|
||||
INVOKE_ASYNC(src, PROC_REF(remove_member), deleting_cultist.mind)
|
||||
|
||||
/datum/team/cult/proc/check_cult_size()
|
||||
if(!ascend_percent)
|
||||
stack_trace("[src]'s check_cult_size was called before cult_threshold_check, which leads to weird logic! This should be fixed ASAP.")
|
||||
cult_threshold_check()
|
||||
|
||||
var/cult_players = get_cultists()
|
||||
|
||||
if(cult_ascendant)
|
||||
// The cult only falls if below 1/2 of the rising, usually pretty low. e.g. 5% on highpop, 10% on lowpop
|
||||
if(cult_players < (rise_number / 2))
|
||||
cult_fall()
|
||||
return
|
||||
|
||||
if((cult_players >= rise_number) && !cult_risen)
|
||||
cult_rise()
|
||||
return
|
||||
|
||||
if(cult_players >= ascend_number)
|
||||
cult_ascend()
|
||||
|
||||
/datum/team/cult/proc/cult_rise()
|
||||
cult_risen = TRUE
|
||||
for(var/datum/mind/M in members)
|
||||
if(!ishuman(M.current))
|
||||
continue
|
||||
SEND_SOUND(M.current, sound('sound/hallucinations/i_see_you2.ogg'))
|
||||
to_chat(M.current, "<span class='cultlarge'>The veil weakens as your cult grows, your eyes begin to glow...</span>")
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(all_members_timer), TYPE_PROC_REF(/datum/antagonist/cultist, rise)), 20 SECONDS)
|
||||
|
||||
/datum/team/cult/proc/cult_ascend()
|
||||
cult_ascendant = TRUE
|
||||
for(var/datum/mind/M in members)
|
||||
if(!ishuman(M.current))
|
||||
continue
|
||||
SEND_SOUND(M.current, sound('sound/hallucinations/im_here1.ogg'))
|
||||
to_chat(M.current, "<span class='cultlarge'>Your cult is ascendant and the red harvest approaches - you cannot hide your true nature for much longer!</span>")
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(all_members_timer), TYPE_PROC_REF(/datum/antagonist/cultist, ascend)), 20 SECONDS)
|
||||
if(!no_announcements)
|
||||
GLOB.major_announcement.Announce("Picking up extradimensional activity related to the Cult of [GET_CULT_DATA(entity_name, "Nar'Sie")] from your station. Data suggests that about [ascend_percent * 100]% of the station has been converted. Security staff are authorized to use lethal force freely against cultists. Non-security staff should be prepared to defend themselves and their work areas from hostile cultists. Self defense permits non-security staff to use lethal force as a last resort, but non-security staff should be defending their work areas, not hunting down cultists. Dead crewmembers must be revived and deconverted once the situation is under control.", "Central Command Higher Dimensional Affairs", 'sound/AI/commandreport.ogg')
|
||||
|
||||
/datum/team/cult/proc/cult_fall()
|
||||
cult_ascendant = FALSE
|
||||
for(var/datum/mind/M in members)
|
||||
if(!ishuman(M.current))
|
||||
continue
|
||||
SEND_SOUND(M.current, sound('sound/hallucinations/wail.ogg'))
|
||||
to_chat(M.current, "<span class='cultlarge'>The veil repairs itself, your power grows weaker...</span>")
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(all_members_timer), TYPE_PROC_REF(/datum/antagonist/cultist, descend)), 20 SECONDS)
|
||||
if(!no_announcements)
|
||||
GLOB.major_announcement.Announce("Paranormal activity has returned to minimal levels. \
|
||||
Security staff should minimize lethal force against cultists, using non-lethals where possible. \
|
||||
All dead cultists should be taken to medbay or robotics for immediate revival and deconversion. \
|
||||
Non-security staff may defend themselves, but should prioritize leaving any areas with cultists and reporting the cultists to security. \
|
||||
Self defense permits non-security staff to use lethal force as a last resort. Hunting down cultists may make you liable for a manslaughter charge. \
|
||||
Any access granted in response to the paranormal threat should be reset. \
|
||||
Any and all security gear that was handed out should be returned. Finally, all weapons (including improvised) should be removed from the crew.",
|
||||
"Central Command Higher Dimensional Affairs", 'sound/AI/commandreport.ogg')
|
||||
/**
|
||||
* This is a magic fuckin proc that takes a proc_ref, and calls it on all the human cultists.
|
||||
* Created so that we don't make 1000 timers, and I'm too lazy to make a proc for all of these.
|
||||
* Used in callbacks for some *magic bullshit*.
|
||||
*/
|
||||
/datum/team/cult/proc/all_members_timer(proc_ref_to_call)
|
||||
for(var/datum/mind/M in members)
|
||||
if(!ishuman(M.current))
|
||||
continue
|
||||
var/datum/antagonist/cultist/cultist = M.has_antag_datum(/datum/antagonist/cultist)
|
||||
if(cultist)
|
||||
call(cultist, proc_ref_to_call)() // yes this is a type proc ref passed by a callback, i know its deranged
|
||||
|
||||
/datum/team/cult/proc/is_convertable_to_cult(datum/mind/mind)
|
||||
if(!mind)
|
||||
return FALSE
|
||||
if(!mind.current)
|
||||
return FALSE
|
||||
if(IS_SACRIFICE_TARGET(mind))
|
||||
return FALSE
|
||||
if(mind.has_antag_datum(/datum/antagonist/cultist))
|
||||
return TRUE //If they're already in the cult, assume they are convertable
|
||||
if(HAS_MIND_TRAIT(mind.current, TRAIT_HOLY))
|
||||
return FALSE
|
||||
if(ishuman(mind.current))
|
||||
var/mob/living/carbon/human/H = mind.current
|
||||
if(ismindshielded(H)) //mindshield protects against conversions unless removed
|
||||
return FALSE
|
||||
if(mind.offstation_role)
|
||||
return FALSE
|
||||
if(issilicon(mind.current))
|
||||
return FALSE //can't convert machines, that's ratvar's thing
|
||||
if(isguardian(mind.current))
|
||||
var/mob/living/simple_animal/hostile/guardian/G = mind.current
|
||||
if(IS_CULTIST(G.summoner))
|
||||
return TRUE //can't convert it unless the owner is converted
|
||||
if(isgolem(mind.current))
|
||||
return FALSE
|
||||
if(isanimal(mind.current))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Decides at the start of the round how many conversions are needed to rise/ascend.
|
||||
*
|
||||
* The number is decided by (Percentage * (Players - Cultists)), so for example at 110 players it would be 11 conversions for rise. (0.1 * (110 - 4))
|
||||
* These values change based on population because 20 cultists are MUCH more powerful if there's only 50 players, compared to 120.
|
||||
*
|
||||
* Below 100 players, [CULT_RISEN_LOW] and [CULT_ASCENDANT_LOW] are used.
|
||||
* Above 100 players, [CULT_RISEN_HIGH] and [CULT_ASCENDANT_HIGH] are used.
|
||||
*/
|
||||
/datum/team/cult/proc/cult_threshold_check()
|
||||
var/list/living_players = get_living_players(exclude_nonhuman = TRUE, exclude_offstation = TRUE)
|
||||
var/players = length(living_players)
|
||||
var/cultists = get_cultists() // Don't count the starting cultists towards the number of needed conversions
|
||||
if(players >= CULT_POPULATION_THRESHOLD)
|
||||
// Highpop
|
||||
ascend_percent = CULT_ASCENDANT_HIGH
|
||||
rise_number = round(CULT_RISEN_HIGH * (players - cultists))
|
||||
ascend_number = round(CULT_ASCENDANT_HIGH * (players - cultists))
|
||||
else
|
||||
// Lowpop
|
||||
ascend_percent = CULT_ASCENDANT_LOW
|
||||
rise_number = round(CULT_RISEN_LOW * (players - cultists))
|
||||
ascend_number = round(CULT_ASCENDANT_LOW * (players - cultists))
|
||||
|
||||
/datum/team/cult/proc/speak_to_all_alive_cultists(...)
|
||||
var/message_to_sent = args.Join("<br>")
|
||||
for(var/datum/mind/cult_mind in members)
|
||||
if(cult_mind?.current)
|
||||
to_chat(cult_mind.current, message_to_sent)
|
||||
|
||||
/datum/team/cult/get_admin_priority_objectives()
|
||||
. = list()
|
||||
.["Sacrifice"] = /datum/objective/sacrifice
|
||||
.["Summon God"] = /datum/objective/eldergod
|
||||
|
||||
/datum/team/cult/handle_adding_admin_objective(mob/user, objective_type)
|
||||
if(objective_type == /datum/objective/sacrifice)
|
||||
if(obj_summon)
|
||||
if(confirm_remove_eldergod_obj(user))
|
||||
return TEAM_ADMIN_ADD_OBJ_SUCCESS
|
||||
|
||||
if(current_sac_objective())
|
||||
var/alert_result = alert(user, "There is already a current sacrifice, reroll the cult's sacrifice target?", "Cult Debug", "Reroll", "Add new sacrifice", "Cancel")
|
||||
if(alert_result == "Reroll")
|
||||
admin_reroll_sac_target(user)
|
||||
return TEAM_ADMIN_ADD_OBJ_SUCCESS | TEAM_ADMIN_ADD_OBJ_CANCEL_LOG
|
||||
else if(alert_result == "Add new sacrifice")
|
||||
return ..()
|
||||
else
|
||||
return TEAM_ADMIN_ADD_OBJ_PURPOSEFUL_CANCEL
|
||||
|
||||
return ..()
|
||||
|
||||
|
||||
else if(objective_type == /datum/objective/eldergod)
|
||||
if(confirm_add_eldergod_obj())
|
||||
return TEAM_ADMIN_ADD_OBJ_SUCCESS
|
||||
return TEAM_ADMIN_ADD_OBJ_PURPOSEFUL_CANCEL
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/team/cult/admin_remove_objective(mob/user, datum/objective/O)
|
||||
if(istype(O, /datum/objective/eldergod))
|
||||
confirm_remove_eldergod_obj(user)
|
||||
return
|
||||
. = ..()
|
||||
|
||||
/datum/team/cult/proc/confirm_add_eldergod_obj(admin_caller, alert_text = "Unlock the ability to summon Nar'Sie?")
|
||||
if(alert(admin_caller, alert_text, "Cult Debug", "Yes", "No") != "Yes")
|
||||
return FALSE
|
||||
|
||||
ready_to_summon()
|
||||
|
||||
message_admins("Admin [key_name_admin(admin_caller)] has unlocked the Cult's ability to summon Nar'Sie.")
|
||||
log_admin("Admin [key_name_admin(admin_caller)] has unlocked the Cult's ability to summon Nar'Sie.")
|
||||
return TRUE
|
||||
|
||||
/datum/team/cult/proc/confirm_remove_eldergod_obj(admin_caller)
|
||||
if(alert(admin_caller, "Revert to pre-summon stage of Cult?", "Cult Debug", "Yes", "No") != "Yes")
|
||||
return FALSE
|
||||
|
||||
sacrifices_required = max(sacrifices_done + 1, sacrifices_required) // make sure we're at least one above the required amount
|
||||
objective_holder.remove_objective(obj_summon) // qdel's the objective too
|
||||
obj_summon = null
|
||||
current_sac_objective() // Create an objective only if needed
|
||||
cult_status = NARSIE_DEMANDS_SACRIFICE
|
||||
|
||||
message_admins("Admin [key_name_admin(admin_caller)] has removed the Cult's ability to summon Nar'Sie.")
|
||||
log_admin("Admin [key_name_admin(admin_caller)] has removed the Cult's ability to summon Nar'Sie.")
|
||||
return TRUE
|
||||
|
||||
/datum/team/cult/proc/study_objectives(mob/living/M, display_members = FALSE) //Called by cultists/cult constructs checking their objectives
|
||||
if(!M)
|
||||
return FALSE
|
||||
|
||||
switch(cult_status)
|
||||
if(NARSIE_IS_ASLEEP)
|
||||
to_chat(M, "<span class='cult'>[GET_CULT_DATA(entity_name, "The Dark One")] is asleep. This is probably a bug.</span>")
|
||||
if(NARSIE_DEMANDS_SACRIFICE)
|
||||
var/list/all_objectives = objective_holder.get_objectives()
|
||||
if(!length(all_objectives))
|
||||
to_chat(M, "<span class='danger'>Error: No objectives. Something went wrong, adminhelp with F1.</span>")
|
||||
else
|
||||
var/datum/objective/sacrifice/current_obj = all_objectives[length(all_objectives)] //get the last obj in the list, ie the current one
|
||||
to_chat(M, "<span class='cult'>The Veil needs to be weakened before we are able to summon [GET_CULT_DATA(entity_title1, "The Dark One")].</span>")
|
||||
to_chat(M, "<span class='cult'>Current goal: [current_obj.explanation_text]</span>")
|
||||
if(NARSIE_NEEDS_SUMMONING)
|
||||
to_chat(M, "<span class='cult'>The Veil is weak! We can summon [GET_CULT_DATA(entity_title3, "The Dark One")]!</span>")
|
||||
to_chat(M, "<span class='cult'>Current goal: [obj_summon.explanation_text]</span>")
|
||||
if(NARSIE_HAS_RISEN)
|
||||
to_chat(M, "<span class='cultlarge'>\"I am here.\"</span>")
|
||||
to_chat(M, "<span class='cult'>Current goal:</span> <span class='cultlarge'>\"Feed me.\"</span>")
|
||||
if(NARSIE_HAS_FALLEN)
|
||||
to_chat(M, "<span class='cultlarge'>[GET_CULT_DATA(entity_name, "The Dark One")] has been banished!</span>")
|
||||
to_chat(M, "<span class='cult'>Current goal: Slaughter the unbelievers!</span>")
|
||||
else
|
||||
to_chat(M, "<span class='danger'>Error: Cult objective status currently unknown. Something went wrong, adminhelp with F1.</span>")
|
||||
|
||||
if(!display_members)
|
||||
return
|
||||
var/list/cult = get_cultists(separate = TRUE)
|
||||
var/total_cult = cult[1] + cult[2]
|
||||
|
||||
var/overview = "<span class='cultitalic'><br><b>Current cult members: [total_cult]"
|
||||
if(!cult_ascendant)
|
||||
var/rise = rise_number - total_cult
|
||||
var/ascend = ascend_number - total_cult
|
||||
if(rise > 0)
|
||||
overview += " | Conversions until Rise: [rise]"
|
||||
else if(ascend > 0)
|
||||
overview += " | Conversions until Ascension: [ascend]"
|
||||
to_chat(M, "[overview]</b></span>")
|
||||
|
||||
if(cult[2]) // If there are any constructs, separate them out
|
||||
to_chat(M, "<span class='cultitalic'><b>Cultists:</b> [cult[1]]")
|
||||
to_chat(M, "<span class='cultitalic'><b>Constructs:</b> [cult[2]]")
|
||||
|
||||
/datum/team/cult/proc/create_next_sacrifice()
|
||||
var/datum/objective/sacrifice/obj_sac = objective_holder.add_objective(/datum/objective/sacrifice)
|
||||
if(!obj_sac.target)
|
||||
objective_holder.remove_objective(obj_sac)
|
||||
ready_to_summon()
|
||||
return
|
||||
return obj_sac
|
||||
|
||||
/// Return the current sacrifice objective datum, if any
|
||||
/datum/team/cult/proc/current_sac_objective()
|
||||
var/list/presummon_objs = objective_holder.get_objectives()
|
||||
if(cult_status == NARSIE_DEMANDS_SACRIFICE && length(presummon_objs))
|
||||
var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)]
|
||||
if(current_obj.sacced)
|
||||
return create_next_sacrifice()
|
||||
if(istype(current_obj))
|
||||
return current_obj
|
||||
|
||||
/datum/team/cult/proc/is_sac_target(datum/mind/mind)
|
||||
var/datum/objective/sacrifice/current_obj = current_sac_objective()
|
||||
return istype(current_obj) && current_obj.target == mind
|
||||
|
||||
/datum/team/cult/proc/find_new_sacrifice_target()
|
||||
var/datum/objective/sacrifice/current_obj = current_sac_objective()
|
||||
if(!current_obj)
|
||||
return FALSE
|
||||
if(!current_obj.find_target(list(current_obj.target)))
|
||||
objective_holder.remove_objective(current_obj)
|
||||
ready_to_summon()
|
||||
return FALSE
|
||||
speak_to_all_alive_cultists("<span class='danger'>[GET_CULT_DATA(entity_name, "Your god")]</span> murmurs, <span class='cultlarge'>Our goal is beyond your reach. Sacrifice [current_obj.target] instead...</span>")
|
||||
return TRUE
|
||||
|
||||
/datum/team/cult/proc/successful_sacrifice()
|
||||
var/datum/objective/sacrifice/current_obj = current_sac_objective()
|
||||
if(!istype(current_obj))
|
||||
return
|
||||
current_obj.sacced = TRUE
|
||||
sacrifices_done++
|
||||
if(sacrifices_done >= sacrifices_required)
|
||||
ready_to_summon()
|
||||
return
|
||||
|
||||
var/datum/objective/sacrifice/obj_sac = create_next_sacrifice()
|
||||
if(!obj_sac)
|
||||
return
|
||||
|
||||
speak_to_all_alive_cultists(
|
||||
"<span class='cult'>You and your acolytes have made progress, but there is more to do still before [GET_CULT_DATA(entity_title1, "The Dark One")] can be summoned!</span>",
|
||||
"<span class='cult'>Current goal: [obj_sac.explanation_text]</span>"
|
||||
)
|
||||
|
||||
/datum/team/cult/proc/ready_to_summon()
|
||||
if(!obj_summon)
|
||||
obj_summon = objective_holder.add_objective(/datum/objective/eldergod)
|
||||
|
||||
cult_status = NARSIE_NEEDS_SUMMONING
|
||||
speak_to_all_alive_cultists(
|
||||
"<span class='cult'>You and your acolytes have succeeded in preparing the station for the ultimate ritual!</span>",
|
||||
"<span class='cult'>Current goal: [obj_summon.explanation_text]</span>"
|
||||
)
|
||||
|
||||
/datum/team/cult/proc/successful_summon()
|
||||
cult_status = NARSIE_HAS_RISEN
|
||||
obj_summon.summoned = TRUE
|
||||
|
||||
/datum/team/cult/proc/narsie_death()
|
||||
cult_status = NARSIE_HAS_FALLEN
|
||||
obj_summon.killed = TRUE
|
||||
speak_to_all_alive_cultists(
|
||||
"<span class='cultlarge'>RETRIBUTION!</span>",
|
||||
"<span class='cult'>Current goal: Slaughter the heretics!</span>"
|
||||
)
|
||||
|
||||
/datum/team/cult/proc/get_cult_status_as_string()
|
||||
var/list/define_to_string = list(
|
||||
"[NARSIE_IS_ASLEEP]" = "NARSIE_IS_ASLEEP",
|
||||
"[NARSIE_DEMANDS_SACRIFICE]" = "NARSIE_DEMANDS_SACRIFICE",
|
||||
"[NARSIE_NEEDS_SUMMONING]" = "NARSIE_NEEDS_SUMMONING",
|
||||
"[NARSIE_HAS_RISEN]" = "NARSIE_HAS_RISEN",
|
||||
"[NARSIE_HAS_FALLEN]" = "NARSIE_HAS_FALLEN",
|
||||
)
|
||||
return define_to_string["[cult_status]"]
|
||||
|
||||
/**
|
||||
* ADMIN STUFF DOWN YONDER
|
||||
*/
|
||||
|
||||
/datum/team/cult/get_admin_commands()
|
||||
return list(
|
||||
"Cult Mindspeak" = CALLBACK(src, PROC_REF(cult_mindspeak))
|
||||
)
|
||||
|
||||
/datum/team/cult/proc/cult_mindspeak(admin_caller)
|
||||
var/input = stripped_input(admin_caller, "Communicate to all the cultists with the voice of [GET_CULT_DATA(entity_name, "a cult god")]", "Voice of [GET_CULT_DATA(entity_name, "Cult God")]")
|
||||
if(!input)
|
||||
return
|
||||
|
||||
speak_to_all_alive_cultists("<span class='cult'>[GET_CULT_DATA(entity_name, "Your god")] murmurs,</span> <span class='cultlarge'>\"[input]\"</span>")
|
||||
|
||||
for(var/mob/dead/observer/O in GLOB.player_list)
|
||||
to_chat(O, "<span class='cult'>[GET_CULT_DATA(entity_name, "Your god")] murmurs,</span> <span class='cultlarge'>\"[input]\"</span>")
|
||||
|
||||
message_admins("Admin [key_name_admin(admin_caller)] has talked with the Voice of [GET_CULT_DATA(entity_name, "Cult God")].")
|
||||
log_admin("[key_name(admin_caller)] Voice of [GET_CULT_DATA(entity_name, "Cult God")]: [input]")
|
||||
|
||||
/datum/team/cult/proc/admin_reroll_sac_target(mob/user)
|
||||
var/datum/objective/sacrifice/current_obj = current_sac_objective()
|
||||
|
||||
var/choice = alert(usr, "How would you like to reroll the cult sacrifice?", "Pick objective", "Pick target", "Random reroll", "Cancel")
|
||||
if(choice == "Pick target")
|
||||
var/new_target = get_admin_objective_targets(user, get_target_excludes(), current_obj.target.current)
|
||||
if(new_target)
|
||||
current_obj.target = new_target
|
||||
current_obj.update_explanation_text()
|
||||
else if(choice == "Random reroll")
|
||||
find_new_sacrifice_target()
|
||||
else
|
||||
return
|
||||
|
||||
message_admins("Admin [key_name_admin(user)] has rerolled the Cult's sacrifice target.")
|
||||
log_admin("Admin [key_name_admin(user)] has rerolled the Cult's sacrifice target.")
|
||||
user.client.holder.check_teams()
|
||||
|
||||
/datum/team/cult/Topic(href, href_list)
|
||||
. = ..()
|
||||
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
// manually cramming some shit in here, because it only conditonally pops up
|
||||
|
||||
switch(href_list["cult_command"])
|
||||
if("cult_adjustsacnumber")
|
||||
var/amount = input("Adjust the amount of sacrifices required before summoning Nar'Sie", "Sacrifice Adjustment", 2) as null | num
|
||||
if(amount > 0)
|
||||
var/old = sacrifices_required
|
||||
sacrifices_required = amount
|
||||
message_admins("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]")
|
||||
log_admin("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]")
|
||||
if(sacrifices_done >= sacrifices_required)
|
||||
confirm_add_eldergod_obj(usr, "Would you also like to unlock the summoning of Nar'sie?")
|
||||
usr.client.holder.check_teams()
|
||||
|
||||
if("cult_newtarget")
|
||||
if(alert(usr, "Reroll the cult's sacrifice target?", "Cult Debug", "Yes", "No") != "Yes")
|
||||
return
|
||||
admin_reroll_sac_target(usr)
|
||||
|
||||
if("cult_newsummonlocations")
|
||||
if(!obj_summon)
|
||||
to_chat(usr, "<span class='danger'>The cult has NO summon objective yet.</span>")
|
||||
return
|
||||
if(alert(usr, "Reroll the cult's summoning locations?", "Cult Debug", "Yes", "No") != "Yes")
|
||||
return
|
||||
|
||||
obj_summon.find_summon_locations(TRUE)
|
||||
if(cult_status == NARSIE_NEEDS_SUMMONING) //Only update cultists if they are already have the summon goal since they arent aware of summon spots till then
|
||||
speak_to_all_alive_cultists(
|
||||
"<span class='cult'>The veil has shifted! Our summoning will need to take place elsewhere.</span>",
|
||||
"<span class='cult'>Current goal: [obj_summon.explanation_text]</span>"
|
||||
)
|
||||
|
||||
message_admins("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
|
||||
log_admin("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
|
||||
usr.client.holder.check_teams()
|
||||
|
||||
/datum/team/cult/get_admin_html()
|
||||
var/list/content = ..()
|
||||
content += "<br><br>Cult Controls:<br>"
|
||||
content += "<br>Cult Status: [get_cult_status_as_string()]"
|
||||
content += "<br>Sacrifices completed: [sacrifices_done]"
|
||||
content += "<br>Sacrifice required for summoning: [sacrifices_required]<br>"
|
||||
if(obj_summon)
|
||||
content += "<br>Summoning locations: [english_list(obj_summon.summon_spots)]"
|
||||
content += "<br><a href='?src=[UID()];cult_command=cult_newsummonlocations'>Reroll summoning locations</a>"
|
||||
else
|
||||
content += "<br>Summoning locations: None, Cult has not yet reached the summoning stage."
|
||||
content += "<br>"
|
||||
if(cult_status == NARSIE_DEMANDS_SACRIFICE)
|
||||
content += "<br><a href='?src=[UID()];cult_command=cult_adjustsacnumber'>Modify amount of sacrifices required</a>"
|
||||
content += "<br><a href='?src=[UID()];cult_command=cult_newtarget'>Reroll sacrifice target</a>"
|
||||
else
|
||||
content += "<br>Cannot modify amount of sacrifices required (Summon available!)"
|
||||
content += "<br>Cannot reroll sacrifice target (Summon available!)"
|
||||
return content
|
||||
@@ -57,8 +57,7 @@
|
||||
|
||||
/datum/antagonist/rev/head/proc/demote()
|
||||
var/datum/mind/old_owner = owner
|
||||
silent = TRUE
|
||||
owner.remove_antag_datum(/datum/antagonist/rev/head)
|
||||
owner.remove_antag_datum(/datum/antagonist/rev/head, silent_removal = TRUE)
|
||||
|
||||
var/datum/antagonist/rev/demoted = new()
|
||||
demoted.silent = TRUE
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
return SSticker.mode.get_rev_team()
|
||||
|
||||
/datum/antagonist/rev/get_team()
|
||||
return SSticker.mode.get_rev_team()
|
||||
return SSticker.mode.rev_team
|
||||
|
||||
/datum/antagonist/rev/give_objectives()
|
||||
var/datum/team/revolution/revolting = get_team()
|
||||
@@ -46,8 +46,7 @@
|
||||
|
||||
/datum/antagonist/rev/proc/promote()
|
||||
var/datum/mind/old_owner = owner
|
||||
silent = TRUE
|
||||
owner.remove_antag_datum(/datum/antagonist/rev, FALSE)
|
||||
owner.remove_antag_datum(/datum/antagonist/rev, FALSE, silent_removal = TRUE)
|
||||
|
||||
var/datum/antagonist/rev/head/new_revhead = new()
|
||||
new_revhead.silent = TRUE
|
||||
|
||||
@@ -4,27 +4,30 @@
|
||||
var/max_headrevs = REVOLUTION_MAX_HEADREVS // adminbus is possible
|
||||
var/have_we_won = FALSE
|
||||
|
||||
/datum/team/revolution/New()
|
||||
..()
|
||||
/datum/team/revolution/create_team()
|
||||
. = ..()
|
||||
update_team_objectives()
|
||||
SSshuttle.registerHostileEnvironment(src)
|
||||
|
||||
/datum/team/revolution/Destroy(force, ...)
|
||||
SSticker.mode.rev_team = null
|
||||
SSshuttle.clearHostileEnvironment(src)
|
||||
return ..()
|
||||
|
||||
/datum/team/revolution/can_create_team()
|
||||
return isnull(SSticker.mode.rev_team)
|
||||
|
||||
/datum/team/revolution/assign_team()
|
||||
SSticker.mode.rev_team = src
|
||||
|
||||
/datum/team/revolution/clear_team_reference()
|
||||
if(SSticker.mode.rev_team == src)
|
||||
SSticker.mode.rev_team = null
|
||||
else
|
||||
CRASH("[src] ([type]) attempted to clear a team reference that wasn't itself!")
|
||||
|
||||
/datum/team/revolution/get_target_excludes()
|
||||
return ..() + get_targetted_head_minds()
|
||||
|
||||
|
||||
/datum/team/revolution/remove_member(datum/mind/member)
|
||||
. = ..()
|
||||
var/datum/antagonist/rev/revolting = member.has_antag_datum(/datum/antagonist/rev) // maybe this should be get_antag_datum_from_member(member)
|
||||
if(!QDELETED(revolting))
|
||||
member.remove_antag_datum(/datum/antagonist/rev)
|
||||
|
||||
/datum/team/revolution/admin_add_objective(mob/user)
|
||||
sanitize_objectives()
|
||||
. = ..()
|
||||
@@ -32,6 +35,10 @@
|
||||
message_admins("[key_name_admin(user)] added a mutiny objective to the team '[name]', and no target was found, removing.")
|
||||
log_admin("[key_name_admin(user)] added a mutiny objective to the team '[name]', and no target was found, removing.")
|
||||
|
||||
/datum/team/revolution/get_admin_priority_objectives()
|
||||
. = list()
|
||||
.["Mutiny"] = /datum/objective/mutiny
|
||||
|
||||
/datum/team/revolution/on_round_end()
|
||||
return // for now... show nothing. Add this in when revs is added to midround/dynamic. Not showing it currently because its dependent on rev gamemode
|
||||
|
||||
|
||||
Reference in New Issue
Block a user