diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 81d250ef79f..03badb20ea5 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -92,30 +92,34 @@ * * imaginary_friend_owner - The living mob that owns the imaginary friend. * * appearance_from_prefs - If this is a valid set of prefs, the appearance of the imaginary friend is based on these prefs. */ -/mob/camera/imaginary_friend/Initialize(mapload, mob/living/imaginary_friend_owner, datum/preferences/appearance_from_prefs = null) +/mob/camera/imaginary_friend/Initialize(mapload) . = ..() - - owner = imaginary_friend_owner - - if(appearance_from_prefs) - INVOKE_ASYNC(src, PROC_REF(setup_friend_from_prefs), appearance_from_prefs) - else - INVOKE_ASYNC(src, PROC_REF(setup_friend)) - join = new join.Grant(src) hide = new hide.Grant(src) +/// Links this imaginary friend to the provided mob +/mob/camera/imaginary_friend/proc/attach_to_owner(mob/living/imaginary_friend_owner) + owner = imaginary_friend_owner if(!owner.imaginary_group) owner.imaginary_group = list(owner) owner.imaginary_group += src +/// Copies appearance from passed player prefs, or randomises them if none are provided +/mob/camera/imaginary_friend/proc/setup_appearance(datum/preferences/appearance_from_prefs = null) + if(appearance_from_prefs) + INVOKE_ASYNC(src, PROC_REF(setup_friend_from_prefs), appearance_from_prefs) + else + INVOKE_ASYNC(src, PROC_REF(setup_friend)) + +/// Randomise friend name and appearance /mob/camera/imaginary_friend/proc/setup_friend() var/gender = pick(MALE, FEMALE) real_name = random_unique_name(gender) name = real_name human_image = get_flat_human_icon(null, pick(SSjob.joinable_occupations)) + Show() /** * Sets up the imaginary friend's name and look using a set of datum preferences. @@ -146,13 +150,11 @@ if(istype(appearance_job, /datum/job/ai)) human_image = icon('icons/mob/silicon/ai.dmi', icon_state = resolve_ai_icon(appearance_from_prefs.read_preference(/datum/preference/choiced/ai_core_display)), dir = SOUTH) - return - - if(istype(appearance_job, /datum/job/cyborg)) + else if(istype(appearance_job, /datum/job/cyborg)) human_image = icon('icons/mob/silicon/robots.dmi', icon_state = "robot") - return - - human_image = get_flat_human_icon(null, appearance_job, appearance_from_prefs) + else + human_image = get_flat_human_icon(null, appearance_job, appearance_from_prefs) + Show() /// Returns all member clients of the imaginary_group /mob/camera/imaginary_friend/proc/group_clients() @@ -163,7 +165,7 @@ return group_clients /mob/camera/imaginary_friend/proc/Show() - if(!client) //nobody home + if(!client || !owner) //nobody home return var/list/friend_clients = group_clients() - src.client diff --git a/code/modules/admin/smites/imaginary_friend_special.dm b/code/modules/admin/smites/imaginary_friend_special.dm index b5784b3c527..e95f8c52cb1 100644 --- a/code/modules/admin/smites/imaginary_friend_special.dm +++ b/code/modules/admin/smites/imaginary_friend_special.dm @@ -1,3 +1,8 @@ +#define CHOICE_RANDOM_APPEARANCE "Random" +#define CHOICE_PREFS_APPEARANCE "Look-a-like" +#define CHOICE_POLL_GHOSTS "Offer to ghosts" +#define CHOICE_CANCEL "Cancel" + /** * Custom imaginary friend. * @@ -10,34 +15,67 @@ **/ /datum/smite/custom_imaginary_friend name = "Imaginary Friend (Special)" - var/client/friend_candidate_client + /// Who are we going to add to your head today? + var/list/friend_candidates + /// Do we randomise friend appearances or not? var/random_appearance /datum/smite/custom_imaginary_friend/configure(client/user) - friend_candidate_client = tgui_input_list(user, "Pick the player to put in control", "New Imaginary Friend", sort_list(GLOB.clients)) - if(isnull(friend_candidate_client)) + var/appearance_choice = tgui_alert(user, + "Do you want the imaginary friend(s) to share name and appearance with their currently selected character preferences?", + "Imaginary Friend Appearance?", + list(CHOICE_PREFS_APPEARANCE, CHOICE_RANDOM_APPEARANCE, CHOICE_CANCEL)) + if (isnull(appearance_choice) || appearance_choice == CHOICE_CANCEL) + return FALSE + random_appearance = appearance_choice == CHOICE_RANDOM_APPEARANCE + + var/picked_client = tgui_input_list(user, "Pick the player to put in control", "New Imaginary Friend", list(CHOICE_POLL_GHOSTS) + sort_list(GLOB.clients)) + if(isnull(picked_client)) return FALSE + if(picked_client == CHOICE_POLL_GHOSTS) + return poll_ghosts(user) + + var/client/friend_candidate_client = picked_client if(QDELETED(friend_candidate_client)) - to_chat(user, span_notice("Selected player no longer has a client, aborting.")) + to_chat(user, span_warning("Selected player no longer has a client, aborting.")) return FALSE if(isliving(friend_candidate_client.mob) && (tgui_alert(user, "This player already has a living mob ([friend_candidate_client.mob]). Do you still want to turn them into an Imaginary Friend?", "Remove player from mob?", list("Do it!", "Cancel")) != "Do it!")) return FALSE if(QDELETED(friend_candidate_client)) - to_chat(user, span_notice("Selected player no longer has a client, aborting.")) + to_chat(user, span_warning("Selected player no longer has a client, aborting.")) return FALSE - if(friend_candidate_client.prefs) - var/choice = tgui_alert(user, "Do you want the imaginary friend to look like and be named after [friend_candidate_client]'s current preferences ([friend_candidate_client.prefs.read_preference(/datum/preference/name/real_name)])?", "Imaginary Friend Appearance?", list("Look-a-like", "Random", "Cancel")) - if(choice != "Look-a-like" && choice != "Random") - return FALSE - random_appearance = choice == "Random" - else - if(tgui_alert(user, "The preferences for the friend could not be loaded, defaulting to random appearance. Is that okay?", "Preference error", list("Yes", "No")) != "Yes") - return FALSE - random_appearance = TRUE + friend_candidates = list(friend_candidate_client) + return TRUE + +/// Try to offer the role to ghosts +/datum/smite/custom_imaginary_friend/proc/poll_ghosts(client/user) + var/how_many = tgui_input_number(user, "How many imaginary friends should be added?", "Imaginary friend count", default = 1, min_value = 1) + if (isnull(how_many) || how_many < 1) + return FALSE + + var/list/volunteers = poll_ghost_candidates( + question = "Do you want to play as an imaginary friend?", + jobban_type = ROLE_PAI, + poll_time = 10 SECONDS, + ignore_category = POLL_IGNORE_IMAGINARYFRIEND, + ) + var/volunteer_count = length(volunteers) + if (volunteer_count == 0) + to_chat(user, span_warning("No candidates volunteered, aborting.")) + return FALSE + + shuffle_inplace(volunteers) + friend_candidates = list() + while (how_many > 0 && length(volunteers) > 0) + var/mob/dead/observer/lucky_ghost = pop(volunteers) + if (!lucky_ghost.client) + continue + how_many-- + friend_candidates += lucky_ghost.client return TRUE /datum/smite/custom_imaginary_friend/effect(client/user, mob/living/target) @@ -47,18 +85,34 @@ to_chat(user, span_warning("The target mob no longer exists, aborting.")) return - if(QDELETED(friend_candidate_client)) - to_chat(user, span_warning("Imaginary friend candidate no longer has a client, aborting.")) + if(!length(friend_candidates)) + to_chat(user, span_warning("No provided imaginary friend candidates, aborting.")) return - if(isliving(friend_candidate_client.mob)) - friend_candidate_client.mob.ghostize(can_reenter_corpse = TRUE) + var/list/final_clients = list() + for (var/client/client as anything in friend_candidates) + if (QDELETED(client)) + continue + final_clients += client - var/mob/camera/imaginary_friend/friend_mob + if(!length(final_clients)) + to_chat(user, span_warning("No provided imaginary friend candidates had clients, aborting.")) + return - if(random_appearance) - friend_mob = new /mob/camera/imaginary_friend(get_turf(target), target) - else - friend_mob = new /mob/camera/imaginary_friend(get_turf(target), target, friend_candidate_client.prefs) + for (var/client/friend_candidate_client as anything in final_clients) + var/mob/client_mob = friend_candidate_client.mob + if(isliving(client_mob)) + client_mob.ghostize() - friend_mob.key = friend_candidate_client.key + var/mob/camera/imaginary_friend/friend_mob = client_mob.change_mob_type( + new_type = /mob/camera/imaginary_friend, + location = get_turf(client_mob), + delete_old_mob = TRUE, + ) + friend_mob.attach_to_owner(target) + friend_mob.setup_appearance(random_appearance ? null : friend_candidate_client.prefs) + +#undef CHOICE_RANDOM_APPEARANCE +#undef CHOICE_PREFS_APPEARANCE +#undef CHOICE_POLL_GHOSTS +#undef CHOICE_CANCEL