/** * 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, role = 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) new_body.key = harbinger.key to_chat(new_body, span_boldnotice(assumed_control_message)) after_assumed_control?.Invoke(harbinger) qdel(src) /// When someone else assumes control via some other means, get rid of our component /datum/component/ghost_direct_control/proc/on_login() SIGNAL_HANDLER qdel(src)