Cult Master title is now granted to the most experienced cult player, improvements and fixes (#90824)

## About The Pull Request

When the cult roundstart ruleset is picked, the most experienced cult
player is granted the cult Master title instead of it being determined
by voting. They can pass the role onto another willing cultist by using
an action once. Fixed cult masters not receiving their large speech
(instead not having any text spans whatsoever) and the Eldritch Pulse
spell being not usable whatsoever. Mark Target now displays an arrow
similar to heretic's living hearbeat to all cultists upon being cast.
This is a commission by Improvedname, but I do stand by the ideas
myself.

## Why It's Good For The Game

Cult Master, since its very inception, has been used for one thing and
one thing only - casting Final Reckoning on the summon rune by whoever
got to the area first and quickly used the vote that the cult reserved
for the finale. This results in other spells almost never being used, as
can be seen by the fact that one of them has been broken for the past 2
years. This change should make cult master more of a commander role akin
to how nuclear operative leader works, and make them a valuable target
for non-cultists to hunt down as they'll be present since roundstart.

## Changelog
🆑
add: Cult Master is now assigned to the player with most hours as a
cultist upon roundstart, instead of being votable.
add: Cult Masters can transfer their role once.
balance: Mark Target now gives all cultists a noticable directional
indicator, similar to heretics' living hearbeat
fix: Fixed cult master's broken speech spans
fix: Fixed Eldritch Pulse not working
/🆑
This commit is contained in:
SmArtKar
2025-05-04 16:30:34 +02:00
committed by GitHub
parent fb16a85940
commit 963ca5b1ee
7 changed files with 120 additions and 175 deletions
-3
View File
@@ -1,6 +1,3 @@
///Time before being allowed to select a new cult leader again
#define CULT_POLL_WAIT (240 SECONDS)
/// Returns either the error landmark or the location of the room. Needless to say, if this is used, it means things have gone awry.
#define GET_ERROR_ROOM ((locate(/obj/effect/landmark/error) in GLOB.landmarks_list) || locate(4,4,1))
@@ -364,6 +364,11 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
M.add_antag_datum(new_cultist)
GLOB.pre_setup_antags -= M
main_cult.setup_objectives()
var/datum/mind/most_experienced = get_most_experienced(assigned, antag_flag)
if(!most_experienced)
most_experienced = assigned[1]
var/datum/antagonist/cult/leader = most_experienced.has_antag_datum(/datum/antagonist/cult)
leader.make_cult_leader()
return TRUE
/datum/dynamic_ruleset/roundstart/bloodcult/round_result()
+71 -126
View File
@@ -54,7 +54,7 @@
var/span = "cult italic"
var/datum/antagonist/cult/cult_datum = user.mind.has_antag_datum(/datum/antagonist/cult)
if(cult_datum.is_cult_leader())
span = "cultlarge"
span = "cult_large"
title = "Master"
else if(!ishuman(user))
title = "Construct"
@@ -89,115 +89,6 @@
var/link = FOLLOW_LINK(player_list, user)
to_chat(player_list, "[link] [my_message]")
/datum/action/innate/cult/mastervote
name = "Assert Leadership"
button_icon_state = "cultvote"
// So you can use it while your hands are cuffed or you are bucked
// If you want to assert your leadership while handcuffed to a chair, be my guest
check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
/datum/action/innate/cult/mastervote/IsAvailable(feedback = FALSE)
if(!owner || !owner.mind)
return FALSE
var/datum/antagonist/cult/mind_cult_datum = owner.mind.has_antag_datum(/datum/antagonist/cult)
if(!mind_cult_datum || mind_cult_datum.cult_team.cult_leader_datum || mind_cult_datum.cult_team.cult_vote_called || !ishuman(owner))
return FALSE
return ..()
/datum/action/innate/cult/mastervote/Activate()
var/choice = tgui_alert(owner, "The mantle of leadership is heavy. Success in this role requires an expert level of communication and experience. Are you sure?",, list("Yes", "No"))
if(choice == "Yes" && IsAvailable())
var/datum/antagonist/cult/mind_cult_datum = owner.mind.has_antag_datum(/datum/antagonist/cult)
start_poll_cultists_for_leader(owner, mind_cult_datum.cult_team)
///Start the poll for Cult Leaeder.
/proc/start_poll_cultists_for_leader(mob/living/nominee, datum/team/cult/team)
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
team.cult_vote_called = TRUE
for(var/datum/mind/team_member as anything in team.members)
if(!team_member.current)
continue
team_member.current.update_mob_action_buttons()
if(team_member.current.incapacitated)
continue
SEND_SOUND(team_member.current, 'sound/effects/hallucinations/im_here1.ogg')
to_chat(team_member.current, span_cult_large("Acolyte [nominee] has asserted that [nominee.p_theyre()] worthy of leading the cult. A vote will be called shortly."))
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(poll_cultists_for_leader), nominee, team), 10 SECONDS)
///Polls all Cultists on whether the person putting themselves forward should be made the Cult Leader, if they can actually be such.
/proc/poll_cultists_for_leader(mob/living/nominee, datum/team/cult/team)
if(QDELETED(nominee) || nominee.incapacitated)
team.cult_vote_called = FALSE
for(var/datum/mind/team_member as anything in team.members)
if(!team_member.current)
continue
team_member.current.update_mob_action_buttons()
if(team_member.current.incapacitated)
continue
to_chat(team_member.current,span_cult_large("[nominee] has died in the process of attempting to start a vote!"))
return FALSE
var/list/mob/living/asked_cultists = list()
for(var/datum/mind/team_member as anything in team.members)
if(!team_member.current || team_member.current == nominee || team_member.current.incapacitated)
continue
SEND_SOUND(team_member.current, 'sound/effects/magic/exit_blood.ogg')
asked_cultists += team_member.current
var/list/yes_voters = SSpolling.poll_candidates(
question = "[span_notice(nominee.name)] seeks to lead your cult, do you support [nominee.p_them()]?",
poll_time = 30 SECONDS,
group = asked_cultists,
alert_pic = nominee,
role_name_text = "cult master nomination",
custom_response_messages = list(
POLL_RESPONSE_SIGNUP = "You have pledged your allegience to [nominee].",
POLL_RESPONSE_ALREADY_SIGNED = "You have already pledged your allegience!",
POLL_RESPONSE_NOT_SIGNED = "You aren't nominated for this.",
POLL_RESPONSE_TOO_LATE_TO_UNREGISTER = "It's too late to unregister yourself, voting has already begun!",
POLL_RESPONSE_UNREGISTERED = "You have been removed your pledge to [nominee].",
chat_text_border_icon = mutable_appearance('icons/effects/effects.dmi', "cult_master_logo")
)
)
if(QDELETED(nominee) || nominee.incapacitated)
team.cult_vote_called = FALSE
for(var/datum/mind/team_member as anything in team.members)
if(!team_member.current)
continue
team_member.current.update_mob_action_buttons()
if(team_member.current.incapacitated)
continue
to_chat(team_member.current,span_cult_large("[nominee] has died in the process of attempting to win the cult's support!"))
return FALSE
if(!nominee.mind)
team.cult_vote_called = FALSE
for(var/datum/mind/team_member as anything in team.members)
if(!team_member.current)
continue
team_member.current.update_mob_action_buttons()
if(team_member.current.incapacitated)
continue
to_chat(team_member.current,span_cult_large("[nominee] has gone catatonic in the process of attempting to win the cult's support!"))
return FALSE
if(LAZYLEN(yes_voters) <= LAZYLEN(asked_cultists) * 0.5)
team.cult_vote_called = FALSE
for(var/datum/mind/team_member as anything in team.members)
if(!team_member.current)
continue
team_member.current.update_mob_action_buttons()
if(team_member.current.incapacitated)
continue
to_chat(team_member.current, span_cult_large("[nominee] could not win the cult's support and shall continue to serve as an acolyte."))
return FALSE
team.cult_vote_called = FALSE
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 || GLOB.cult_narsie)
return FALSE
@@ -206,6 +97,63 @@
return FALSE
return ..()
/datum/action/innate/cult/master/pass_role
name = "Pass the Mantle"
desc = "Pass the Master role onto another willing cultist. This can only be done once!"
button_icon_state = "cultvote"
check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
/datum/action/innate/cult/master/pass_role/IsAvailable(feedback = FALSE)
. = ..()
if (!.)
return
var/datum/antagonist/cult/mind_cult_datum = owner.mind.has_antag_datum(/datum/antagonist/cult)
if(!mind_cult_datum || mind_cult_datum.cult_team.leader_passed_on)
return FALSE
/datum/action/innate/cult/master/pass_role/Activate()
var/list/choices = list()
var/datum/antagonist/cult/owner_datum = owner.mind.has_antag_datum(/datum/antagonist/cult)
for(var/datum/mind/team_member as anything in owner_datum.cult_team.members)
if(!team_member.current || !ishuman(team_member.current) || team_member.current == owner || team_member.current.stat == DEAD)
continue
choices[team_member.current.real_name] = team_member
var/new_master = tgui_input_list(owner, "Select another cult member to pass the cult's Master role onto.", "Pass the Mantle", choices)
if (!new_master || !IsAvailable())
return
var/confirmation = tgui_alert(owner, "Are you sure that you want to make [new_master] the new Master? This can only be done once!", "Pass the Mantle", list("Yes", "No"))
if (confirmation != "Yes" || !IsAvailable())
return
var/datum/mind/master_mind = choices[new_master]
var/mob/living/carbon/human/master = master_mind.current
if (!master || master.stat == DEAD || !master.client)
to_chat(owner, span_cult("[new_master] can no longer take on the role of a Master."))
return
var/datum/antagonist/cult/target_datum = master_mind.has_antag_datum(/datum/antagonist/cult)
if(!target_datum || owner_datum.cult_team != target_datum?.cult_team)
to_chat(owner, span_cult("[new_master] can no longer take on the role of a Master."))
return
SEND_SOUND(master, sound('sound/effects/magic.ogg', volume = 33))
confirmation = tgui_alert(master, "[owner.real_name] is offering their role as the cult's Master to you! Do you wish to accept it?", "Take the Mantle", list("Yes", "No"))
if (confirmation != "Yes")
to_chat(owner, span_cult("[new_master] has declined your offer."))
return
if (!IsAvailable() || !master_mind.has_antag_datum(/datum/antagonist/cult) || !master.client)
to_chat(owner, span_cult("[new_master] can no longer take on the role of a Master."))
return
target_datum.cult_team.leader_passed_on = TRUE
owner_datum.demote_from_leader()
target_datum.make_cult_leader()
/datum/action/innate/cult/master/finalreck
name = "Final Reckoning"
desc = "A single-use spell that brings the entire cult to the master's location."
@@ -438,8 +386,7 @@
/datum/action/innate/cult/master/pulse/do_ability(mob/living/clicker, atom/clicked_on)
var/atom/throwee = throwee_ref?.resolve()
if(QDELETED(throwee))
if(throwee && QDELING(throwee))
to_chat(clicker, span_cult("You lost your target!"))
throwee = null
throwee_ref = null
@@ -483,19 +430,17 @@
return TRUE
else
if(isliving(clicked_on))
var/mob/living/living_clicked = clicked_on
if(!IS_CULTIST(living_clicked))
return FALSE
SEND_SOUND(clicker, sound('sound/items/weapons/thudswoosh.ogg'))
to_chat(clicker, span_cult_bold("You reach through the veil with your mind's eye and seize [clicked_on]! <b>Click anywhere nearby to teleport [clicked_on.p_them()]!</b>"))
throwee_ref = WEAKREF(clicked_on)
return TRUE
if(istype(clicked_on, /obj/structure/destructible/cult))
to_chat(clicker, span_cult_bold("You reach through the veil with your mind's eye and lift [clicked_on]! <b>Click anywhere nearby to teleport it!</b>"))
throwee_ref = WEAKREF(clicked_on)
return TRUE
if(isliving(clicked_on))
var/mob/living/living_clicked = clicked_on
if(!IS_CULTIST(living_clicked))
return FALSE
SEND_SOUND(clicker, sound('sound/items/weapons/thudswoosh.ogg'))
to_chat(clicker, span_cult_bold("You reach through the veil with your mind's eye and seize [clicked_on]! <b>Click anywhere nearby to teleport [clicked_on.p_them()]!</b>"))
throwee_ref = WEAKREF(clicked_on)
return TRUE
if(istype(clicked_on, /obj/structure/destructible/cult))
to_chat(clicker, span_cult_bold("You reach through the veil with your mind's eye and lift [clicked_on]! <b>Click anywhere nearby to teleport it!</b>"))
throwee_ref = WEAKREF(clicked_on)
return TRUE
return FALSE
@@ -8,8 +8,8 @@
///Timer for the blood mark expiration
var/blood_target_reset_timer
///Has a vote been called for a leader?
var/cult_vote_called = FALSE
///Has the cult leader passed on their responsibilities to someone else?
var/leader_passed_on = FALSE
///The cult leader
var/datum/antagonist/cult/cult_leader_datum
///Has the mass teleport been used yet?
@@ -161,12 +161,15 @@
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_cult_large("[marker] has marked [blood_target] in \the [target_area] as the cult's top priority, get there immediately!")))
SEND_SOUND(cultist.current, sound(SFX_HALLUCINATION_OVER_HERE, 0, 1, 75))
cultist.current.client.images += blood_target_image
if (cultist.current.hud_used)
new /atom/movable/screen/navigate_arrow(null, cultist.current.hud_used, get_turf(new_target), COLOR_CULT_RED)
if(duration != INFINITY)
blood_target_reset_timer = addtimer(CALLBACK(src, PROC_REF(unset_blood_target)), duration, TIMER_STOPPABLE)
+11 -20
View File
@@ -9,9 +9,6 @@
antag_hud_name = "cult"
stinger_sound = 'sound/music/antag/bloodcult/bloodcult_gain.ogg'
///The vote ability Cultists have to elect someone to be the leader.
var/datum/action/innate/cult/mastervote/vote_ability
///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.
@@ -35,9 +32,6 @@
var/datum/action/innate/cult/comm/communion = new(owner)
communion.Grant(current)
if(isnull(cult_team.cult_leader_datum))
vote_ability = new(owner)
vote_ability.Grant(current)
if(ishuman(current))
var/datum/action/innate/cult/blood_magic/magic = new(owner)
magic.Grant(current)
@@ -50,8 +44,6 @@
to_chat(owner.current, span_userdanger("An unfamiliar white light flashes through your mind, cleansing the taint of the Geometer and all your memories as her servant."))
owner.current.log_message("has renounced the cult of Nar'Sie!", LOG_ATTACK, color=COLOR_CULT_RED)
if(vote_ability)
QDEL_NULL(vote_ability)
for(var/datum/action/innate/cult/cult_buttons in owner.current.actions)
qdel(cult_buttons)
@@ -185,8 +177,8 @@
/datum/antagonist/cult/proc/make_cult_leader()
if(cult_team.cult_leader_datum)
return FALSE
cult_team.cult_leader_datum = src
cult_team.cult_leader_datum = src
antag_hud_name = "cultmaster"
add_team_hud(owner.current)
RegisterSignal(owner.current, COMSIG_MOB_STATCHANGE, PROC_REF(deathrattle))
@@ -198,13 +190,15 @@
var/datum/action/innate/cult/master/pulse/throwing = new
bloodmark.Grant(owner.current)
throwing.Grant(owner.current)
if(!cult_team.leader_passed_on)
var/datum/action/innate/cult/master/pass_role/pass_role = new
pass_role.Grant(owner.current)
owner.current.update_mob_action_buttons()
for(var/datum/mind/cult_mind as anything in cult_team.members)
var/datum/antagonist/cult/cult_datum = cult_mind.has_antag_datum(/datum/antagonist/cult)
cult_datum.vote_ability.Remove(cult_mind.current)
to_chat(cult_mind.current, span_cult_large("[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!"))
if (cult_mind != owner)
to_chat(cult_mind.current, span_cult_large("[owner.current] is your cult's Master! \
Follow [owner.current.p_their()] orders to the best of your ability!"))
to_chat(owner.current, span_cult_large("<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 \
@@ -215,7 +209,6 @@
return TRUE
///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
@@ -234,13 +227,11 @@
var/datum/action/innate/cult/master/pulse/throwing = locate() in owner.current.actions
if(throwing)
throwing.Remove(owner.current)
var/datum/action/innate/cult/master/pass_role/pass_role = locate() in owner.current.actions
if(pass_role)
pass_role.Remove(owner.current)
owner.current.update_mob_action_buttons()
for(var/datum/mind/cult_mind as anything in cult_team.members)
var/datum/antagonist/cult/cult_datum = cult_mind.has_antag_datum(/datum/antagonist/cult)
cult_datum.vote_ability.Grant(cult_mind.current)
to_chat(owner.current, span_cult_large("You have been demoted from being the cult's Master, you are now an acolyte once more!"))
to_chat(owner.current, span_cult_large("You have been demoted from being the cult's Master, you are now a mere acolyte!"))
return TRUE
///If dead (and Narsie isn't summoned), will alert all Cultists of their death, sending their location out.
@@ -180,26 +180,6 @@
StartCooldown()
return TRUE
/datum/action/cooldown/track_target/proc/make_navigate_arrow(turf/tracked_turf, arrow_color)
var/datum/hud/user_hud = owner.hud_used
if(!user_hud)
return
var/atom/movable/screen/heretic_arrow/arrow = new /atom/movable/screen/heretic_arrow(null, user_hud)
animate(arrow, transform = matrix(dir2angle(get_dir(owner, tracked_turf)), MATRIX_ROTATE), 0.2 SECONDS)
arrow.screen_loc = around_player
arrow.color = arrow_color
user_hud.infodisplay += arrow
user_hud.show_hud(user_hud.hud_version)
addtimer(CALLBACK(src, PROC_REF(end_effect), user_hud, arrow), 1.6 SECONDS)
/datum/action/cooldown/track_target/proc/end_effect(datum/hud/user_hud, atom/movable/screen/heretic_arrow/arrow)
arrow.icon_state = "heretic_arrow_disappear"
addtimer(CALLBACK(src, PROC_REF(null_arrow), user_hud, arrow), 0.4 SECONDS)
/datum/action/cooldown/track_target/proc/null_arrow(datum/hud/user_hud, atom/movable/screen/heretic_arrow/arrow)
user_hud.infodisplay -= arrow
user_hud.show_hud(user_hud.hud_version)
/// Callback for the radial to ensure it's closed when not allowed.
/datum/action/cooldown/track_target/proc/check_menu()
if(QDELETED(src))
@@ -268,7 +248,8 @@
balloon_message = "very far!"
arrow_color = COLOR_RED
make_navigate_arrow(their_turf, arrow_color)
if(owner.hud_used)
new /atom/movable/screen/navigate_arrow(null, owner.hud_used, their_turf, arrow_color)
if(ismob(tracked_thing))
var/mob/tracked_mob = tracked_thing
@@ -277,9 +258,32 @@
return balloon_message
/atom/movable/screen/heretic_arrow
/atom/movable/screen/navigate_arrow
icon = 'icons/effects/96x96.dmi'
name = "heretic arrow"
icon_state = "heretic_arrow_appear"
name = "farsight arrow"
icon_state = "navigate_arrow_appear"
pixel_x = -32
pixel_y = -32
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/atom/movable/screen/navigate_arrow/Initialize(mapload, datum/hud/hud_owner, turf/tracked_turf, arrow_color)
. = ..()
var/mob/owner = get_mob()
if (owner)
animate(src, transform = matrix(get_angle(owner, tracked_turf), MATRIX_ROTATE), 0.2 SECONDS)
screen_loc = around_player
color = arrow_color
if (hud)
hud.infodisplay += src
hud.show_hud(hud.hud_version)
addtimer(CALLBACK(src, PROC_REF(end_effect)), 1.6 SECONDS)
/atom/movable/screen/navigate_arrow/proc/end_effect()
icon_state = "navigate_arrow_disappear"
addtimer(CALLBACK(src, PROC_REF(null_arrow)), 0.4 SECONDS)
/atom/movable/screen/navigate_arrow/proc/null_arrow()
if (hud)
hud.infodisplay -= src
hud.show_hud(hud.hud_version)
qdel(src)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB