From 6348fa7cf8e8b620f2b34d2050730beae85fb669 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sun, 22 Oct 2023 00:23:55 +0200 Subject: [PATCH] [MIRROR] Adds a subtle ghost poll [MDB IGNORE] (#24503) * Adds a subtle ghost poll (#79105) ## About The Pull Request This makes a new ghost poll system which doesn't give TGUI popups - instead, users are prompted to follow the POI and one of the orbiters is chosen. The old system remains in place, so you can still prompt if you want to. This gives two things: 1. A deadchat notification: ![image](https://github.com/tgstation/tgstation/assets/42397676/073fcfff-b1ed-47c3-bae0-4abf9c599144) 2. A screen alert: ![image](https://github.com/tgstation/tgstation/assets/42397676/92a4e566-614a-43ca-8680-3cb4ff86ced9) ## Why It's Good For The Game As stated in #76507, popups are pretty annoying. This is halfway between a screen alert with no time limit and an event with more important pings. This is better because: 1. Less popup fatigue 2. You can SEE how many you're competing with 4. DRY ## Changelog :cl: add: Adds a subtle ghost poll. This pings in dead chat and gives a screen alert, but no TGUI popup. Orbit the point of interest to be selected for the role. refactor: A number of ghost spawns now feature this alert. Write an issue report if anything breaks. /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com> * Adds a subtle ghost poll --------- Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com> --- code/datums/brain_damage/imaginary_friend.dm | 25 ++-- code/datums/brain_damage/split_personality.dm | 27 +++-- code/datums/components/orbit_poll.dm | 108 ++++++++++++++++++ code/datums/components/spirit_holding.dm | 27 +++-- .../game/objects/items/devices/aicard_evil.dm | 25 ++-- code/modules/antagonists/blob/powers.dm | 19 ++- .../antagonists/wizard/equipment/soulstone.dm | 42 +++---- .../modules/mining/lavaland/megafauna_loot.dm | 38 +++--- .../carbon/alien/special/alien_embryo.dm | 21 ++-- .../research/xenobiology/xenobiology.dm | 19 ++- tgstation.dme | 1 + 11 files changed, 261 insertions(+), 91 deletions(-) create mode 100644 code/datums/components/orbit_poll.dm diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index fc230632bdd..48fabd701f4 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -45,15 +45,26 @@ /datum/brain_trauma/special/imaginary_friend/proc/make_friend() friend = new(get_turf(owner), owner) +/// Tries an orbit poll for the imaginary friend /datum/brain_trauma/special/imaginary_friend/proc/get_ghost() - set waitfor = FALSE - var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as [owner.real_name]'s imaginary friend?", ROLE_PAI, null, 7.5 SECONDS, friend, POLL_IGNORE_IMAGINARYFRIEND) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - friend.key = C.key - friend_initialized = TRUE - else + var/datum/callback/to_call = CALLBACK(src, PROC_REF(add_friend)) + owner.AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_IMAGINARYFRIEND, \ + job_bans = ROLE_PAI, \ + title = "[owner.real_name]'s imaginary friend", \ + to_call = to_call, \ + ) + +/// Yay more friends! +/datum/brain_trauma/special/imaginary_friend/proc/add_friend(mob/dead/observer/ghost) + if(isnull(ghost)) qdel(src) + return + + friend.key = ghost.key + friend_initialized = TRUE + friend.log_message("became [key_name(owner)]'s split personality.", LOG_GAME) + message_admins("[ADMIN_LOOKUPFLW(friend)] became [ADMIN_LOOKUPFLW(owner)]'s split personality.") /mob/camera/imaginary_friend name = "imaginary friend" diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm index 05b4343a01c..9176884b816 100644 --- a/code/datums/brain_damage/split_personality.dm +++ b/code/datums/brain_damage/split_personality.dm @@ -32,17 +32,26 @@ var/datum/action/cooldown/spell/personality_commune/owner_spell = new(src) owner_spell.Grant(owner_backseat) - +/// Attempts to get a ghost to play the personality /datum/brain_trauma/severe/split_personality/proc/get_ghost() - set waitfor = FALSE - var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as [owner.real_name]'s [poll_role]?", ROLE_PAI, null, 7.5 SECONDS, stranger_backseat, POLL_IGNORE_SPLITPERSONALITY) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - stranger_backseat.key = C.key - stranger_backseat.log_message("became [key_name(owner)]'s split personality.", LOG_GAME) - message_admins("[ADMIN_LOOKUPFLW(stranger_backseat)] became [ADMIN_LOOKUPFLW(owner)]'s split personality.") - else + var/datum/callback/to_call = CALLBACK(src, PROC_REF(schism)) + owner.AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_SPLITPERSONALITY, \ + job_bans = ROLE_PAI, \ + title = "[owner.real_name]'s [poll_role]", \ + to_call = to_call, \ + ) + +/// Ghost poll has concluded +/datum/brain_trauma/severe/split_personality/proc/schism(mob/dead/observer/ghost) + if(isnull(ghost)) qdel(src) + return + + stranger_backseat.key = ghost.key + stranger_backseat.log_message("became [key_name(owner)]'s split personality.", LOG_GAME) + message_admins("[ADMIN_LOOKUPFLW(stranger_backseat)] became [ADMIN_LOOKUPFLW(owner)]'s split personality.") + /datum/brain_trauma/severe/split_personality/on_life(seconds_per_tick, times_fired) if(owner.stat == DEAD) diff --git a/code/datums/components/orbit_poll.dm b/code/datums/components/orbit_poll.dm new file mode 100644 index 00000000000..d9563aaab95 --- /dev/null +++ b/code/datums/components/orbit_poll.dm @@ -0,0 +1,108 @@ +/** + * A replacement for the standard poll_ghost_candidate. + * Use this to subtly ask players to join - it picks from orbiters. + * Please use named arguments for this. + * + * @params ignore_key - Required so it doesn't spam + * @params job_bans - You can insert a list or single items here. + * @params cb - Invokes this proc and appends the poll winner as the last argument, mob/dead/observer/ghost + * @params title - Optional. Useful if the role name does not match the parent. + * + * @usage + * ``` + * var/datum/callback/cb = CALLBACK(src, PROC_REF(do_stuff), arg1, arg2) + * AddComponent(/datum/component/orbit_poll, \ + * ignore_key = POLL_IGNORE_EXAMPLE, \ + * job_bans = ROLE_EXAMPLE or list(ROLE_EXAMPLE, ROLE_EXAMPLE2), \ + * title = "Use this if you want something other than the parent name", \ + * to_call = cb, \ + * ) + */ +/datum/component/orbit_poll + /// Prevent players with this ban from being selected + var/list/job_bans = list() + /// Title of the role to announce after it's done + var/title + /// Proc to invoke whenever the poll is complete + var/datum/callback/to_call + +/datum/component/orbit_poll/Initialize( \ + ignore_key, \ + list/job_bans, \ + datum/callback/to_call, \ + title, \ + header = "Ghost Poll", \ + custom_message, \ + timeout = 20 SECONDS \ +) + . = ..() + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + + var/atom/owner = parent + + src.job_bans |= job_bans + src.title = title || owner.name + src.to_call = to_call + + var/message = custom_message || "[capitalize(src.title)] is looking for volunteers" + + notify_ghosts("[message]. An orbiter will be chosen in [DisplayTimeText(timeout)].\n", \ + action = NOTIFY_ORBIT, \ + enter_link = "(Ignore)", \ + flashwindow = FALSE, \ + header = "Volunteers requested", \ + ignore_key = ignore_key, \ + source = parent \ + ) + + addtimer(CALLBACK(src, PROC_REF(end_poll)), timeout, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE|TIMER_DELETE_ME) + +/datum/component/orbit_poll/Topic(href, list/href_list) + if(!href_list["ignore"]) + return + + var/mob/user = usr + + var/ignore_key = href_list["ignore"] + if(tgui_alert(user, "Ignore further [title] alerts?", "Ignore Alert", list("Yes", "No"), 20 SECONDS, TRUE) != "Yes") + return + + GLOB.poll_ignore[ignore_key] |= user.ckey + +/// Concludes the poll, picking one of the orbiters +/datum/component/orbit_poll/proc/end_poll() + if(QDELETED(parent)) + return + + var/list/candidates = list() + var/atom/owner = parent + + var/datum/component/orbiter/orbiter_comp = owner.GetComponent(/datum/component/orbiter) + if(isnull(orbiter_comp)) + phone_home() + return + + for(var/mob/dead/observer/ghost as anything in orbiter_comp.orbiter_list) + if(QDELETED(ghost) || isnull(ghost.client)) + continue + if(is_banned_from(ghost.ckey, job_bans)) + continue + + candidates += ghost + + if(!length(candidates)) + phone_home() + return + + var/mob/dead/observer/chosen = pick(candidates) + + if(chosen) + deadchat_broadcast("[key_name(chosen, include_name = FALSE)] was selected for the role ([title]).", "Ghost Poll: ", parent) + + phone_home(chosen) + +/// Make sure to call your parents my dude +/datum/component/orbit_poll/proc/phone_home(mob/dead/observer/chosen) + to_call.Invoke(chosen) + qdel(src) diff --git a/code/datums/components/spirit_holding.dm b/code/datums/components/spirit_holding.dm index a4d0e029137..82c37e8f26b 100644 --- a/code/datums/components/spirit_holding.dm +++ b/code/datums/components/spirit_holding.dm @@ -57,21 +57,30 @@ attempting_awakening = TRUE to_chat(awakener, span_notice("You attempt to wake the spirit of [parent]...")) - var/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as the spirit of [awakener.real_name]'s blade?", ROLE_PAI, FALSE, 100, POLL_IGNORE_POSSESSED_BLADE) - if(!LAZYLEN(candidates)) - to_chat(awakener, span_warning("[parent] is dormant. Maybe you can try again later.")) - attempting_awakening = FALSE - return + var/datum/callback/to_call = CALLBACK(src, PROC_REF(affix_spirit), awakener) + parent.AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_POSSESSED_BLADE, \ + job_bans = ROLE_PAI, \ + to_call = to_call, \ + title = "Spirit of [awakener.real_name]'s blade", \ + ) //Immediately unregister to prevent making a new spirit UnregisterSignal(parent, COMSIG_ITEM_ATTACK_SELF) - var/mob/dead/observer/chosen_spirit = pick(candidates) - if(QDELETED(parent)) //if the thing that we're conjuring a spirit in has been destroyed, don't create a spirit - to_chat(chosen_spirit, span_userdanger("The new vessel for your spirit has been destroyed! You remain an unbound ghost.")) +/// On conclusion of the ghost poll +/datum/component/spirit_holding/proc/affix_spirit(mob/awakener, mob/dead/observer/ghost) + if(isnull(ghost)) + to_chat(awakener, span_warning("[parent] is dormant. Maybe you can try again later.")) + attempting_awakening = FALSE return + + if(QDELETED(parent)) //if the thing that we're conjuring a spirit in has been destroyed, don't create a spirit + to_chat(ghost, span_userdanger("The new vessel for your spirit has been destroyed! You remain an unbound ghost.")) + return + bound_spirit = new(parent) - bound_spirit.ckey = chosen_spirit.ckey + bound_spirit.ckey = ghost.ckey bound_spirit.fully_replace_character_name(null, "The spirit of [parent]") bound_spirit.status_flags |= GODMODE bound_spirit.copy_languages(awakener, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the awakener. diff --git a/code/game/objects/items/devices/aicard_evil.dm b/code/game/objects/items/devices/aicard_evil.dm index 1a5fce6897a..f91150bb086 100644 --- a/code/game/objects/items/devices/aicard_evil.dm +++ b/code/game/objects/items/devices/aicard_evil.dm @@ -28,26 +28,29 @@ finding_candidate = FALSE return TRUE +/// Sets up the ghost poll /obj/item/aicard/syndie/loaded/proc/procure_ai(mob/user) var/datum/antagonist/nukeop/op_datum = user.mind?.has_antag_datum(/datum/antagonist/nukeop,TRUE) if(isnull(op_datum)) balloon_alert(user, "invalid access!") return - var/list/nuke_candidates = poll_ghost_candidates( - question = "Do you want to play as a nuclear operative MODsuit AI?", - jobban_type = ROLE_OPERATIVE, - be_special_flag = ROLE_OPERATIVE_MIDROUND, - poll_time = 15 SECONDS, - ignore_category = POLL_IGNORE_SYNDICATE, + + var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user, op_datum) + AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_SYNDICATE, \ + job_bans = ROLE_OPERATIVE, \ + to_call = to_call, \ + title = "Nuclear Operative Modsuit AI" \ ) - if(QDELETED(src)) - return - if(!LAZYLEN(nuke_candidates)) + +/// Poll has concluded with a ghost, create the AI +/obj/item/aicard/syndie/loaded/proc/on_poll_concluded(mob/user, datum/antagonist/nukeop/op_datum, mob/dead/observer/ghost) + if(isnull(ghost)) to_chat(user, span_warning("Unable to connect to S.E.L.F. dispatch. Please wait and try again later or use the intelliCard on your uplink to get your points refunded.")) return + // pick ghost, create AI and transfer - var/mob/dead/observer/ghos = pick(nuke_candidates) - var/mob/living/silicon/ai/weak_syndie/new_ai = new /mob/living/silicon/ai/weak_syndie(get_turf(src), new /datum/ai_laws/syndicate_override, ghos) + var/mob/living/silicon/ai/weak_syndie/new_ai = new /mob/living/silicon/ai/weak_syndie(get_turf(src), new /datum/ai_laws/syndicate_override, ghost) // create and apply syndie datum var/datum/antagonist/nukeop/nuke_datum = new() nuke_datum.send_to_spawnpoint = FALSE diff --git a/code/modules/antagonists/blob/powers.dm b/code/modules/antagonists/blob/powers.dm index 04054f6df85..b35308d092f 100644 --- a/code/modules/antagonists/blob/powers.dm +++ b/code/modules/antagonists/blob/powers.dm @@ -189,14 +189,22 @@ to_chat(src, span_notice("You attempt to produce a blobbernaut.")) pick_blobbernaut_candidate(factory) -/** Polls ghosts to get a blobbernaut candidate. */ +/// Polls ghosts to get a blobbernaut candidate. /mob/camera/blob/proc/pick_blobbernaut_candidate(obj/structure/blob/special/factory/factory) - if(!factory) + if(isnull(factory)) return - var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as a [blobstrain.name] blobbernaut?", ROLE_BLOB, ROLE_BLOB, 50) + var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), factory) + factory.AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_BLOB, \ + job_bans = ROLE_BLOB, \ + to_call = to_call, \ + title = "Blobbernaut", \ + ) - if(!length(candidates)) +/// Called when the ghost poll concludes +/mob/camera/blob/proc/on_poll_concluded(obj/structure/blob/special/factory/factory, mob/dead/observer/ghost) + if(isnull(ghost)) to_chat(src, span_warning("You could not conjure a sentience for your blobbernaut. Your points have been refunded. Try again later.")) add_points(BLOBMOB_BLOBBERNAUT_RESOURCE_COST) factory.assign_blobbernaut(null) @@ -205,8 +213,7 @@ var/mob/living/basic/blob_minion/blobbernaut/minion/blobber = new(get_turf(factory)) assume_direct_control(blobber) factory.assign_blobbernaut(blobber) - var/mob/dead/observer/player = pick(candidates) - blobber.assign_key(player.key, blobstrain) + blobber.assign_key(ghost.key, blobstrain) RegisterSignal(blobber, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(on_blobbernaut_attacked)) /// When one of our boys attacked something, we sometimes want to perform extra effects diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index f7be579ad1a..80dd5a1b2d9 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -323,7 +323,15 @@ return TRUE to_chat(user, "[span_userdanger("Capture failed!")]: The soul has already fled its mortal frame. You attempt to bring it back...") - INVOKE_ASYNC(src, PROC_REF(get_ghost_to_replace_shade), victim, user) + + var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user, victim) + AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_SHADE, \ + job_bans = ROLE_CULTIST, \ + to_call = to_call, \ + title = "A shade" \ + ) + return TRUE //it'll probably get someone ;) ///captures a shade that was previously released from a soulstone. @@ -435,33 +443,19 @@ shade_datum = shade.mind.add_antag_datum(/datum/antagonist/shade_minion) shade_datum.update_master(user.real_name) -/** - * Gets a ghost from dead chat to replace a missing player when a shade is created. - * - * Gets ran if a soulstone is used on a body that has no client to take over the shade. - * - * victim - the body that's being shaded - * user - the mob shading the body - * - * Returns FALSE if no ghosts are available or the replacement fails. - * Returns TRUE otherwise. - */ -/obj/item/soulstone/proc/get_ghost_to_replace_shade(mob/living/carbon/victim, mob/user) - var/mob/dead/observer/chosen_ghost - var/list/consenting_candidates = poll_ghost_candidates("Would you like to play as a Shade?", "Cultist", ROLE_CULTIST, 5 SECONDS, POLL_IGNORE_SHADE) - if(length(consenting_candidates)) - chosen_ghost = pick(consenting_candidates) - - if(!victim || user.incapacitated() || !user.is_holding(src) || !user.CanReach(victim, src)) +/// Called when a ghost is chosen to become a shade. +/obj/item/soulstone/proc/on_poll_concluded(mob/living/master, mob/living/victim, mob/dead/observer/ghost) + if(isnull(victim) || master.incapacitated() || !master.is_holding(src) || !master.CanReach(victim, src)) return FALSE - if(!chosen_ghost || !chosen_ghost.client) - to_chat(user, span_danger("There were no spirits willing to become a shade.")) + if(isnull(ghost?.client)) + to_chat(master, span_danger("There were no spirits willing to become a shade.")) return FALSE - if(contents.len) //If they used the soulstone on someone else in the meantime + if(length(contents)) //If they used the soulstone on someone else in the meantime return FALSE - to_chat(user, "[span_info("Capture successful!:")] A spirit has entered [src], \ + to_chat(master, "[span_info("Capture successful!:")] A spirit has entered [src], \ taking upon the identity of [victim].") - init_shade(victim, user, shade_controller = chosen_ghost) + init_shade(victim, master, shade_controller = ghost) + return TRUE /proc/make_new_construct_from_class(construct_class, theme, mob/target, mob/creator, cultoverride, loc_override) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index bdcf089e606..554002fed54 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -425,19 +425,31 @@ using = TRUE balloon_alert(user, "you hold the scythe up...") ADD_TRAIT(src, TRAIT_NODROP, type) - var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as [user.real_name]'s soulscythe?", ROLE_PAI, FALSE, 100, POLL_IGNORE_POSSESSED_BLADE) - if(LAZYLEN(candidates)) - var/mob/dead/observer/picked_ghost = pick(candidates) - soul.ckey = picked_ghost.ckey - soul.copy_languages(user, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the user. - soul.faction = list("[REF(user)]") - balloon_alert(user, "the scythe glows up") - add_overlay("soulscythe_gem") - density = TRUE - if(!ismob(loc)) - reset_spin() - else - balloon_alert(user, "the scythe is dormant!") + + var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user) + AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_POSSESSED_BLADE, \ + job_bans = ROLE_PAI, \ + to_call = to_call, \ + ) + +/// Ghost poll has concluded and a candidate has been chosen. +/obj/item/soulscythe/proc/on_poll_concluded(mob/living/master, mob/dead/observer/ghost) + if(isnull(ghost)) + balloon_alert(master, "the scythe is dormant!") + REMOVE_TRAIT(src, TRAIT_NODROP, type) + using = FALSE + return + + soul.ckey = ghost.ckey + soul.copy_languages(master, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the master. + soul.faction = list("[REF(master)]") + balloon_alert(master, "the scythe glows") + add_overlay("soulscythe_gem") + density = TRUE + if(!ismob(loc)) + reset_spin() + REMOVE_TRAIT(src, TRAIT_NODROP, type) using = FALSE diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index 3f3809c89b2..19434d888a1 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -84,26 +84,33 @@ return attempt_grow() -///Attempt to burst an alien outside of the host, getting a ghost to play as the xeno. +/// Attempt to burst an alien outside of the host, getting a ghost to play as the xeno. /obj/item/organ/internal/body_egg/alien_embryo/proc/attempt_grow(gib_on_success = TRUE) - if(!owner || bursting) + if(QDELETED(owner) || bursting) return bursting = TRUE - var/list/candidates = poll_ghost_candidates("Do you want to play as an alien larva that will burst out of [owner.real_name]?", ROLE_ALIEN, ROLE_ALIEN, 100, POLL_IGNORE_ALIEN_LARVA) + var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), gib_on_success) + owner.AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_ALIEN_LARVA, \ + job_bans = ROLE_ALIEN, \ + to_call = to_call, \ + custom_message = "An alien is bursting out of [owner.real_name]", \ + title = "alien larva" \ + ) - if(QDELETED(src) || QDELETED(owner)) +/// Poll has concluded with a suitor +/obj/item/organ/internal/body_egg/alien_embryo/proc/on_poll_concluded(gib_on_success, mob/dead/observer/ghost) + if(QDELETED(owner)) return - if(!candidates.len || !owner) + if(isnull(ghost)) bursting = FALSE stage = 5 // If no ghosts sign up for the Larva, let's regress our growth by one minute, we will try again! addtimer(CALLBACK(src, PROC_REF(advance_embryo_stage)), growth_time) return - var/mob/dead/observer/ghost = pick(candidates) - var/mutable_appearance/overlay = mutable_appearance('icons/mob/nonhuman-player/alien.dmi', "burst_lie") owner.add_overlay(overlay) diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index b3945fe60c4..2bbefb64ec3 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -705,19 +705,28 @@ balloon_alert(user, "offering...") being_used = TRUE - var/list/candidates = poll_candidates_for_mob("Do you want to play as [dumb_mob.name]?", ROLE_SENTIENCE, ROLE_SENTIENCE, 5 SECONDS, dumb_mob, POLL_IGNORE_SENTIENCE_POTION) // see poll_ignore.dm - if(!LAZYLEN(candidates)) + var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user, dumb_mob) + dumb_mob.AddComponent(/datum/component/orbit_poll, \ + ignore_key = POLL_IGNORE_SENTIENCE_POTION, \ + job_bans = ROLE_SENTIENCE, \ + to_call = to_call, \ + ) + +/// Assign the chosen ghost to the mob +/obj/item/slimepotion/slime/sentience/proc/on_poll_concluded(mob/user, mob/living/dumb_mob, mob/dead/observer/ghost) + if(isnull(ghost)) balloon_alert(user, "try again later!") being_used = FALSE - return ..() + return - var/mob/dead/observer/C = pick(candidates) - dumb_mob.key = C.key + dumb_mob.key = ghost.key dumb_mob.mind.enslave_mind_to_creator(user) SEND_SIGNAL(dumb_mob, COMSIG_SIMPLEMOB_SENTIENCEPOTION, user) + if(isanimal(dumb_mob)) var/mob/living/simple_animal/smart_animal = dumb_mob smart_animal.sentience_act() + dumb_mob.mind.add_antag_datum(/datum/antagonist/sentient_creature) balloon_alert(user, "success") after_success(user, dumb_mob) diff --git a/tgstation.dme b/tgstation.dme index 6de0909e79a..af514de744a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1146,6 +1146,7 @@ #include "code\datums\components\omen.dm" #include "code\datums\components\on_hit_effect.dm" #include "code\datums\components\onwear_mood.dm" +#include "code\datums\components\orbit_poll.dm" #include "code\datums\components\orbiter.dm" #include "code\datums\components\overlay_lighting.dm" #include "code\datums\components\palette.dm"