Files
Bubberstation/code/datums/components/ghost_direct_control.dm
2025-04-29 18:33:35 -06:00

161 lines
6.2 KiB
Plaintext

/**
* Component which lets ghosts click on a mob to take control of it
*/
/datum/component/ghost_direct_control
/// Message to display upon successful possession
var/assumed_control_message
/// Type of ban you can get to prevent you from accepting this role
var/ban_type
/// Any extra checks which need to run before we take over
var/datum/callback/extra_control_checks
/// Callback run after someone successfully takes over the body
var/datum/callback/after_assumed_control
/// If we're currently awaiting the results of a ghost poll
var/awaiting_ghosts = FALSE
/datum/component/ghost_direct_control/Initialize(
ban_type = ROLE_SENTIENCE,
role_name = null,
poll_question = null,
poll_candidates = TRUE,
poll_announce_chosen = TRUE,
poll_length = 10 SECONDS,
poll_chat_border_icon = null,
poll_ignore_key = POLL_IGNORE_SENTIENCE_POTION,
assumed_control_message = null,
datum/callback/extra_control_checks,
datum/callback/after_assumed_control,
)
. = ..()
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
src.ban_type = ban_type
src.assumed_control_message = assumed_control_message || "You are [parent]!"
src.extra_control_checks = extra_control_checks
src.after_assumed_control = after_assumed_control
var/mob/mob_parent = parent
LAZYADD(GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")], mob_parent)
if (poll_candidates)
INVOKE_ASYNC(src, PROC_REF(request_ghost_control), poll_question, role_name || "[parent]", poll_length, poll_ignore_key, poll_announce_chosen, poll_chat_border_icon)
/datum/component/ghost_direct_control/RegisterWithParent()
. = ..()
RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, PROC_REF(on_ghost_clicked))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined))
RegisterSignal(parent, COMSIG_MOB_LOGIN, PROC_REF(on_login))
/datum/component/ghost_direct_control/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ATOM_ATTACK_GHOST, COMSIG_ATOM_EXAMINE, COMSIG_MOB_LOGIN))
return ..()
/datum/component/ghost_direct_control/Destroy(force)
extra_control_checks = null
after_assumed_control = null
var/mob/mob_parent = parent
var/list/spawners = GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")]
LAZYREMOVE(spawners, mob_parent)
if(!LAZYLEN(spawners))
GLOB.joinable_mobs -= format_text("[initial(mob_parent.name)]")
return ..()
/// Inform ghosts that they can possess this
/datum/component/ghost_direct_control/proc/on_examined(datum/source, mob/user, list/examine_text)
SIGNAL_HANDLER
if (!isobserver(user))
return
var/mob/living/our_mob = parent
if (our_mob.stat == DEAD || our_mob.key || awaiting_ghosts)
return
examine_text += span_boldnotice("You could take control of this mob by clicking on it.")
/// Send out a request for a brain
/datum/component/ghost_direct_control/proc/request_ghost_control(poll_question, role_name, poll_length, poll_ignore_key, poll_announce_chosen, poll_chat_border_icon)
if(!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER))
return
awaiting_ghosts = TRUE
var/mob/chosen_one = SSpolling.poll_ghosts_for_target(
question = poll_question,
check_jobban = ban_type,
poll_time = poll_length,
checked_target = parent,
ignore_category = poll_ignore_key,
alert_pic = parent,
role_name_text = role_name,
chat_text_border_icon = poll_chat_border_icon,
announce_chosen = poll_announce_chosen,
)
awaiting_ghosts = FALSE
if(isnull(chosen_one))
return
assume_direct_control(chosen_one)
/// A ghost clicked on us, they want to get in this body
/datum/component/ghost_direct_control/proc/on_ghost_clicked(mob/our_mob, mob/dead/observer/hopeful_ghost)
SIGNAL_HANDLER
if (our_mob.key)
qdel(src)
return
if (!hopeful_ghost.client)
return
if (!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER))
to_chat(hopeful_ghost, span_warning("Ghost roles have been temporarily disabled!"))
return
if (awaiting_ghosts)
to_chat(hopeful_ghost, span_warning("Ghost candidate selection currently in progress!"))
return COMPONENT_CANCEL_ATTACK_CHAIN
if (!SSticker.HasRoundStarted())
to_chat(hopeful_ghost, span_warning("You cannot assume control of this until after the round has started!"))
return COMPONENT_CANCEL_ATTACK_CHAIN
INVOKE_ASYNC(src, PROC_REF(attempt_possession), our_mob, hopeful_ghost)
return COMPONENT_CANCEL_ATTACK_CHAIN
/// We got far enough to establish that this mob is a valid target, let's try to posssess it
/datum/component/ghost_direct_control/proc/attempt_possession(mob/our_mob, mob/dead/observer/hopeful_ghost)
var/ghost_asked = tgui_alert(usr, "Become [our_mob]?", "Are you sure?", list("Yes", "No"))
if (ghost_asked != "Yes" || QDELETED(our_mob))
return
assume_direct_control(hopeful_ghost)
/// Grant possession of our mob, component is now no longer required
/datum/component/ghost_direct_control/proc/assume_direct_control(mob/harbinger)
if (QDELETED(src))
to_chat(harbinger, span_warning("Offer to possess creature has expired!"))
return
if (is_banned_from(harbinger.ckey, list(ban_type)))
to_chat(harbinger, span_warning("You are banned from playing as this role!"))
return
if (!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER))
to_chat(harbinger, span_warning("Ghost roles have been temporarily disabled!"))
return
var/mob/living/new_body = parent
if (new_body.stat == DEAD)
to_chat(harbinger, span_warning("This body has passed away, it is of no use!"))
return
if (new_body.key)
to_chat(harbinger, span_warning("[parent] has already become sapient!"))
qdel(src)
return
if (extra_control_checks && !extra_control_checks.Invoke(harbinger))
return
harbinger.log_message("took control of [new_body].", LOG_GAME)
// doesn't transfer mind because that transfers antag datum as well
new_body.PossessByPlayer(harbinger.ckey)
// Already qdels due to below proc but just in case
qdel(src)
/// When someone assumes control, get rid of our component
/datum/component/ghost_direct_control/proc/on_login(mob/harbinger)
SIGNAL_HANDLER
// This proc is called the very moment .key is set, so we need to force mind to initialize here if we want the invoke to affect the mind of the mob
if(isnull(harbinger.mind))
harbinger.mind_initialize()
to_chat(harbinger, span_boldnotice(assumed_control_message))
after_assumed_control?.Invoke(harbinger)
qdel(src)