From 7df1931ab0c8196c93fe4e64059084dbc00a5b55 Mon Sep 17 00:00:00 2001 From: Timberpoes Date: Sun, 5 Sep 2021 16:23:09 +0100 Subject: [PATCH] Reimagines the imaginary friend as a fancy new smite. (#61218) Fixed imaginary friends being unable to turn by movement or holding down ctrl. I assume this was broken when abstract_move was implemented. Makes some tweaks to the Imaginary Friend mob. It no longer cares about any brain traumas and can exist standalone. Added the ability to take a set of datum prefs as an argument. It will attempt to create an imaginary friend as per those datum prefs. This imaginary friend can be a cyborg or an AI core. Takes this improved behaviour and packages it into a brand new smite that allows admins to inflict anyone with a permanent imaginary friend. This is not tied to a brain trauma. You can probably give imaginary friends imaginary friends. You can probably give the AI eye an imaginary friend. I have not tested this. It is probably a bad idea. You can give simplemobs imaginary friends. Basic mobs. Silicons. You choose whether the imaginary friend takes on the preferences of the person you choose to be the imaginary friend or is random --- code/datums/brain_damage/imaginary_friend.dm | 63 ++++++++++++++++--- .../admin/smites/imaginary_friend_special.dm | 56 +++++++++++++++++ tgstation.dme | 1 + 3 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 code/modules/admin/smites/imaginary_friend_special.dm diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index ac1617dd711..b32c0569a80 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -70,8 +70,7 @@ var/image/current_image var/hidden = FALSE var/move_delay = 0 - var/mob/living/carbon/owner - var/datum/brain_trauma/special/imaginary_friend/trauma + var/mob/living/owner var/datum/action/innate/imaginary_join/join var/datum/action/innate/imaginary_hide/hide @@ -88,16 +87,20 @@ to_chat(src, span_notice("You are absolutely loyal to your friend, no matter what.")) to_chat(src, span_notice("You cannot directly influence the world around you, but you can see what [owner] cannot.")) -/mob/camera/imaginary_friend/Initialize(mapload, _trauma) - if(!_trauma) - stack_trace("Imaginary friend created without trauma, wtf") - return INITIALIZE_HINT_QDEL +/** + * Arguments: + * * 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) . = ..() - trauma = _trauma - owner = trauma.owner + owner = imaginary_friend_owner - INVOKE_ASYNC(src, .proc/setup_friend) + if(appearance_from_prefs) + INVOKE_ASYNC(src, .proc/setup_friend_from_prefs, appearance_from_prefs) + else + INVOKE_ASYNC(src, .proc/setup_friend) join = new join.Grant(src) @@ -110,6 +113,43 @@ name = real_name human_image = get_flat_human_icon(null, pick(SSjob.joinable_occupations)) +/** + * Sets up the imaginary friend's name and look using a set of datum preferences. + * + * Arguments: + * * appearance_from_prefs - If this is a valid set of prefs, the appearance of the imaginary friend is based on the currently selected character in them. Otherwise, it's random. + */ +/mob/camera/imaginary_friend/proc/setup_friend_from_prefs(datum/preferences/appearance_from_prefs) + if(!istype(appearance_from_prefs)) + stack_trace("Attempted to create imaginary friend appearance from null prefs. Using random appearance.") + setup_friend() + return + + real_name = appearance_from_prefs.real_name + name = real_name + + // Determine what job is marked as 'High' priority. + var/datum/job/appearance_job + var/highest_pref = 0 + for(var/job in appearance_from_prefs.job_preferences) + var/this_pref = appearance_from_prefs.job_preferences[job] + if(this_pref > highest_pref) + appearance_job = SSjob.GetJob(job) + highest_pref = this_pref + + if(!appearance_job) + appearance_job = SSjob.GetJob("Assistant") + + if(istype(appearance_job, /datum/job/ai)) + human_image = icon('icons/mob/ai.dmi', icon_state = resolve_ai_icon(appearance_from_prefs.preferred_ai_core_display), dir = SOUTH) + return + + if(istype(appearance_job, /datum/job/cyborg)) + human_image = icon('icons/mob/robots.dmi', icon_state = "robot") + return + + human_image = get_flat_human_icon(null, appearance_job, appearance_from_prefs) + /mob/camera/imaginary_friend/proc/Show() if(!client) //nobody home return @@ -185,6 +225,7 @@ /mob/camera/imaginary_friend/Move(NewLoc, Dir = 0) if(world.time < move_delay) return FALSE + setDir(Dir) if(get_dist(src, owner) > 9) recall() move_delay = world.time + 10 @@ -192,6 +233,10 @@ abstract_move(NewLoc) move_delay = world.time + 1 +/mob/camera/imaginary_friend/keybind_face_direction(direction) + . = ..() + Show() + /mob/camera/imaginary_friend/abstract_move(atom/destination) . = ..() Show() diff --git a/code/modules/admin/smites/imaginary_friend_special.dm b/code/modules/admin/smites/imaginary_friend_special.dm new file mode 100644 index 00000000000..60ab1254417 --- /dev/null +++ b/code/modules/admin/smites/imaginary_friend_special.dm @@ -0,0 +1,56 @@ +/** + * Custom imaginary friend. + * + * Allows the admin to select the ckey to put into the imaginary friend and whether the imaginary friend looks like the + * ckey's character. + * + * Is not tied to the brain trauma and can be used on all mobs, technically. Including cyborgs and simple/basic mobs. + * + * Warranty void if used on AI eyes or other imaginary friends. Please smite responsibly. + **/ +/datum/smite/custom_imaginary_friend + name = "Imaginary Friend (Special)" + var/client/friend_candidate_client + 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", sortList(GLOB.clients)) + + if(QDELETED(friend_candidate_client)) + to_chat(user, span_notice("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")) == "Cancel")) + return + + if(QDELETED(friend_candidate_client)) + to_chat(user, span_notice("Selected player no longer has a client, aborting.")) + return FALSE + + if(friend_candidate_client.prefs) + random_appearance = 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.real_name])?", "Imaginary Friend Appearance?", list("Look-a-like", "Random")) == "Random" + else + random_appearance = TRUE + +/datum/smite/custom_imaginary_friend/effect(client/user, mob/living/target) + . = ..() + + if(QDELETED(target)) + 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.")) + return + + if(isliving(friend_candidate_client.mob)) + friend_candidate_client.mob.ghostize(can_reenter_corpse = TRUE) + + var/mob/camera/imaginary_friend/friend_mob + + 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) + + friend_mob.key = friend_candidate_client.key diff --git a/tgstation.dme b/tgstation.dme index 379e9cfa9b2..c10c9530e05 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1627,6 +1627,7 @@ #include "code\modules\admin\smites\fat.dm" #include "code\modules\admin\smites\fireball.dm" #include "code\modules\admin\smites\gib.dm" +#include "code\modules\admin\smites\imaginary_friend_special.dm" #include "code\modules\admin\smites\immerse.dm" #include "code\modules\admin\smites\knot_shoes.dm" #include "code\modules\admin\smites\lightning.dm"