diff --git a/code/__DEFINES/~skyrat_defines/signals.dm b/code/__DEFINES/~skyrat_defines/signals.dm index 7ccaad2e592240..bb3e678bbf9b48 100644 --- a/code/__DEFINES/~skyrat_defines/signals.dm +++ b/code/__DEFINES/~skyrat_defines/signals.dm @@ -87,3 +87,24 @@ /// Whenever we need to get the soul of the mob inside of the soulcatcher. #define COMSIG_SOULCATCHER_SCAN_BODY "soulcatcher_scan_body" + +/// Whenever we need to change the current room of a soulcatcher soul. +#define COMSIG_CARRIER_MOB_CHANGE_ROOM "carrier_mob_change_room" + +/// Whenever we need to toggle the senses of a soulcatcher soul. +#define COMSIG_CARRIER_MOB_TOGGLE_SENSE "carrier_mob_toggle_sense" + +/// Whenever we need to rename a soulcatcher soul. +#define COMSIG_CARRIER_MOB_RENAME "carrier_mob_rename" + +/// Whenever we need to reset the name of a soulcatcher soul. +#define COMSIG_CARRIER_MOB_RESET_NAME "carrier_mob_reset_name" + +/// Whenever we need to check if our soulcatcher soul is able to internally hear/see? +#define COMSIG_CARRIER_MOB_CHECK_INTERNAL_SENSES "carrier_mob_internal_senses" + +/// Whenever we need to refresh the internal appearance of a soulcatcher soul.area +#define COMSIG_CARRIER_MOB_REFRESH_APPEARANCE "carrier_mob_refresh_appearance" + +/// Whenever we need the soulcatcher soul to communicate something. +#define COMSIG_CARRIER_MOB_SAY "carrier_mob_communicate" diff --git a/code/__DEFINES/~skyrat_defines/traits.dm b/code/__DEFINES/~skyrat_defines/traits.dm index f49d0bbe413348..3a9a180fbee074 100644 --- a/code/__DEFINES/~skyrat_defines/traits.dm +++ b/code/__DEFINES/~skyrat_defines/traits.dm @@ -3,3 +3,4 @@ #define SLIPPERY_MIN 5 /// The maximum amount of tiles a TRAIT_SLIPPERY haver will slide on slip #define SLIPPERY_MAX 9 + diff --git a/code/__DEFINES/~skyrat_defines/traits/declarations.dm b/code/__DEFINES/~skyrat_defines/traits/declarations.dm index c005247fb552df..15a532cbd28c5c 100644 --- a/code/__DEFINES/~skyrat_defines/traits/declarations.dm +++ b/code/__DEFINES/~skyrat_defines/traits/declarations.dm @@ -109,6 +109,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait that was granted by a NIFSoft #define TRAIT_NIFSOFT "nifsoft" +/// Trait that was granted by a soulcatcher +#define TRAIT_CARRIER "soulcatcher" + /// Trait given to a piece of eyewear that allows the user to use NIFSoft HUDs #define TRAIT_NIFSOFT_HUD_GRANTER "nifsoft_hud_granter" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index d11de3d00d09e8..35d8b079721f91 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -631,6 +631,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NEVERBONER" = TRAIT_NEVERBONER, "TRAIT_NIFSOFT" = TRAIT_NIFSOFT, "TRAIT_NIFSOFT_HUD_GRANTER" = TRAIT_NIFSOFT_HUD_GRANTER, + "TRAIT_CARRIER" = TRAIT_CARRIER, "TRAIT_NO_HUSK" = TRAIT_NO_HUSK, "TRAIT_NORUNNING" = TRAIT_NORUNNING, "TRAIT_NUMBED" = TRAIT_NUMBED, diff --git a/modular_skyrat/modules/carriers/code/carrier_component.dm b/modular_skyrat/modules/carriers/code/carrier_component.dm new file mode 100644 index 00000000000000..a7225f1833837a --- /dev/null +++ b/modular_skyrat/modules/carriers/code/carrier_component.dm @@ -0,0 +1,386 @@ +///Global list containing any and all soulcatchers +GLOBAL_LIST_EMPTY(soulcatchers) + +#define SOULCATCHER_DEFAULT_COLOR "#75D5E1" +#define SOULCATCHER_WARNING_MESSAGE "You have entered a soulcatcher, do not share any information you have received while a ghost. If you have died within the round, you do not know your identity until your body has been scanned, standard blackout policy also applies." + +/** + * Carrier Component + * + * This component functions as a bridge between the `carrier_room` attached to itself and the parented datum. + * It handles the creation of new carrier rooms, TGUI, and relaying messages to the parent datum. + * If the component is deleted, any carrier rooms inside of `carrier_rooms` will be deleted. + */ +/datum/component/carrier + /// What is the name of the carrier? + var/name = "carrier" + /// What rooms are linked to this carrier + var/list/carrier_rooms = list() + /// What carrier room are verbs sending messages to? + var/datum/carrier_room/targeted_carrier_room + /// What theme are we using for our carrier UI? + var/ui_theme = "default" + /// Do we want to ask the user permission before the mob enters? + var/require_approval = TRUE + /// Are are the mobs inside able to emote/speak as the parent? + var/communicate_as_parent = FALSE + /// Is this carrier going to stay within the possesion of one mob within it's lifespan? + var/single_owner = FALSE + + /// What is the max number of people we can keep in this carrier? If this is set to `FALSE` we don't have a limit + var/max_mobs = FALSE + /// What is the path of user component do we want to give to our mob? This needs to be `/datum/component/carrier_user` or a subtype. + var/component_to_give = /datum/component/carrier_user + /// What 16x16 chat icon do we want our carrier to display in chat messages? + var/chat_icon = "nif-soulcatcher" + /// What is the type of room that we want to create? + var/type_of_room_to_create = /datum/carrier_room + +/datum/component/carrier/New() + . = ..() + if(!parent) + return COMPONENT_INCOMPATIBLE + + create_room() + targeted_carrier_room = carrier_rooms[1] + + if(!single_owner) + return TRUE + + update_targeted_carrier() // Give them the verbs if the soulcatcher is unlikely to change hands + +/datum/component/carrier/Destroy(force, ...) + targeted_carrier_room = null + for(var/datum/carrier_room as anything in carrier_rooms) + carrier_rooms -= carrier_room + qdel(carrier_room) + + if(!single_owner) + return ..() + + var/mob/living/holder = get_current_holder() + if(!holder) + return FALSE + + return ..() + +/// Updates the target carrier component for the carrier me/emote verb to send messages to. +/datum/component/carrier/proc/update_targeted_carrier(mob/living/target_mob, inside_carrier = FALSE) + var/mob/living/holder = get_current_holder() + if(istype(target_mob)) + holder = target_mob + + if(!holder) + return FALSE + + var/datum/component/carrier_communicator/communicator_component = holder.GetComponent(/datum/component/carrier_communicator) + if(!istype(communicator_component)) + communicator_component = holder.AddComponent(/datum/component/carrier_communicator) + + communicator_component.target_carrier = WEAKREF(src) + return communicator_component + +/** + * Creates a `/datum/carrier_room` and adds it to the `carrier_rooms` list. + * + * Arguments + * * target_name - The name that we want to assign to the created room. + * * target_desc - The description that we want to assign to the created room. + */ +/datum/component/carrier/proc/create_room(target_name, target_desc) + var/datum/carrier_room/created_room = new type_of_room_to_create(src) + if(target_name) + created_room.name = target_name + if(target_desc) + created_room.room_description = target_desc + + carrier_rooms += created_room + created_room.master_carrier = WEAKREF(src) + +/// Tries to find out who is currently using the carrier, returns the holder. If no holder can be found, returns FALSE +/datum/component/carrier/proc/get_current_holder() + var/mob/living/holder + var/obj/item/parent_item = parent + if(!istype(parent_item)) + return FALSE + + holder = parent_item.loc + if(!istype(holder)) + return FALSE + + return holder + +/// Recieves a message from a carrier room. +/datum/component/carrier/proc/recieve_message(message_to_recieve) + if(!message_to_recieve) + return FALSE + + var/mob/living/carrier_owner = get_current_holder() + if(!istype(carrier_owner)) + return FALSE + + to_chat(carrier_owner, message_to_recieve) + return TRUE + +/// Attempts to ping the current user of the carrier, asking them if `joiner_name` is allowed in. If they are, the proc returns `TRUE`, otherwise returns FALSE +/datum/component/carrier/proc/get_approval(joiner_name) + if(!require_approval) + return TRUE + + var/mob/living/carrier_owner = get_current_holder() + if(!carrier_owner) + return FALSE + + if(tgui_alert(carrier_owner, "Do you wish to allow [joiner_name] into your soulcatcher?", name, list("Yes", "No"), autofocus = FALSE) != "Yes") + return FALSE + + return TRUE + +/// Returns a list containing all of the mobs currently present within a carrier. +/datum/component/carrier/proc/get_current_mobs() + var/list/current_inhabitants = list() + for(var/datum/carrier_room/room as anything in carrier_rooms) + for(var/mob/living/inhabitant as anything in room.current_mobs) + current_inhabitants += inhabitant + + return current_inhabitants + +/// Checks the total number of mobs present and compares it with `max_mobs` returns `TRUE` if there is room (or no limit), otherwise returns `FALSE` +/datum/component/carrier/proc/check_for_vacancy() + if(!max_mobs) + return TRUE + + if(length(get_current_mobs()) >= max_mobs) + return FALSE + + return TRUE + +/datum/component/carrier/proc/get_open_rooms() + var/list/datum/carrier_room/room_list = list() + for(var/datum/carrier_room/room as anything in carrier_rooms) + if(!check_for_vacancy()) + continue + + room_list += room + + return room_list + +/// Transfers a soul from a carrier room to another carrier room. Returns `FALSE` if the target room or target soul cannot be found. +/datum/component/carrier/proc/transfer_mob(mob/living/target_soul, datum/carrier_room/target_room) + if(!(target_soul in get_current_mobs()) || !target_room) + return FALSE + + var/datum/component/carrier/target_master_carrier = target_room.master_carrier.resolve() + if(!target_master_carrier) + target_room.master_carrier = null + return FALSE + + else if(target_master_carrier != src) + target_soul.forceMove(target_master_carrier.parent) + + var/datum/component/carrier_user/carrier_component = target_soul.GetComponent(/datum/component/carrier_user) + var/datum/carrier_room/original_room = carrier_component?.current_room?.resolve() + if(!istype(carrier_component) || !istype(original_room)) + return FALSE // Don't transfer someone that isn't already inside of a carrier. + + original_room.current_mobs -= target_soul + var/datum/weakref/room_ref = WEAKREF(target_room) + carrier_component.current_room = room_ref + target_room.current_mobs += target_soul + + to_chat(target_soul, span_cyan("you've been transferred to [target_room]!")) + to_chat(target_soul, span_notice(target_room.room_description)) + + return TRUE + +/// Adds `mob_to_add` into the parent carrier, giving them the carrier component and moving their mob into the room. Returns the component added, if successful +/datum/component/carrier/proc/add_mob(mob/living/mob_to_add, datum/carrier_room/target_room) + if(!istype(mob_to_add)) + return FALSE + + var/datum/component/carrier_user/carrier_component = mob_to_add.AddComponent(component_to_give) + if(!carrier_component) + return FALSE + + if(!istype(target_room)) + target_room = carrier_rooms[1] // Put them in the first room we can find if none is provided. + + carrier_component.current_room = target_room + var/datum/component/carrier_communicator/communicator_component = update_targeted_carrier(mob_to_add) + communicator_component.carried_mob = TRUE + + return carrier_component + +/** + * carrier Room + * + * This datum is where souls are sent to when joining soulcatchers. + * It handles sending messages to souls from the outside along with adding new souls, transfering, and removing souls. + * + */ +/datum/carrier_room + /// What is the name of the room? + var/name = "Carrier room" + /// What is the description of the room? + var/room_description = "it feels roomy in here." + /// What souls are currently inside of the room? + var/list/current_mobs = list() + /// Weakref for the master carrier datum + var/datum/weakref/master_carrier + /// What is the name of the person sending the messages? + var/outside_voice = "Host" + /// Can the room be joined at all? + var/joinable = TRUE + /// What is the color of chat messages sent by the room? + var/room_color = SOULCATCHER_DEFAULT_COLOR + +/// Adds a mob into the carrier +/datum/carrier_room/proc/add_mob(mob/living/mob_to_add) + if(!mob_to_add) + return FALSE + + var/datum/component/carrier/parent_carrier = master_carrier.resolve() + var/datum/parent_object = parent_carrier.parent + if(!parent_object) + return FALSE + + var/datum/component/carrier_user/carrier_component = parent_carrier.add_mob(mob_to_add, src) + if(!carrier_component) + return FALSE + current_mobs += mob_to_add + carrier_component.current_room = WEAKREF(src) + mob_to_add.forceMove(parent_carrier.parent) + + to_chat(mob_to_add, span_cyan("You find yourself now inside of: [name]")) + to_chat(mob_to_add, span_notice(room_description)) + + var/atom/parent_atom = parent_object + if(istype(parent_atom)) + var/turf/carrier_turf = get_turf(parent_carrier.parent) + var/message_to_log = "[key_name(mob_to_add)] entered [src] inside of [parent_atom] at [loc_name(carrier_turf)]" + parent_atom.log_message(message_to_log, LOG_GAME) + mob_to_add.log_message(message_to_log, LOG_GAME) + + return TRUE + +/// Removes a mob from a carrier room, leaving it as a ghost. Returns `FALSE` if the `mob_to_remove` cannot be found, otherwise returns `TRUE` after a successful deletion. +/datum/carrier_room/proc/remove_mob(mob/living/mob_to_remove) + if(!mob_to_remove || !(mob_to_remove in current_mobs)) + return FALSE + + var/datum/component/carrier_user/carrier_component = mob_to_remove.GetComponent(/datum/component/carrier_user) + if(carrier_component) + qdel(carrier_component) + + current_mobs -= mob_to_remove + + var/mob/living/soulcatcher_soul/soul_to_remove = mob_to_remove + if(istype(soul_to_remove)) + soul_to_remove.return_to_body() + qdel(soul_to_remove) + + return TRUE + + var/datum/component/carrier/parent_carrier = master_carrier.resolve() + if(!parent_carrier) + master_carrier = null + return FALSE + else if(!parent_carrier.parent) + return FALSE + + var/turf/current_tile = get_turf(parent_carrier.parent) + mob_to_remove.forceMove(current_tile) + + return TRUE + +/** + * Sends a message or emote to all of the souls currently located inside of the carrier room. Returns `FALSE` if a message cannot be sent, otherwise returns `TRUE`. + * + * Arguments + * * message_to_send - The message we want to send to the occupants of the room + * * sender_name - The person that is sending the message. This is not required. + * * sender_mob - The person that is sending the message. This is not required. + * * emote - Is the message sent an emote or not? + */ +/datum/carrier_room/proc/send_message(message_to_send, sender_name, mob/living/sender_mob, emote = FALSE) + if(!message_to_send) //Why say nothing? + return FALSE + + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat) + var/master_resolved = master_carrier.resolve() + if(!master_resolved) + master_carrier = null + return FALSE + + var/datum/component/carrier/parent_carrier = master_resolved + var/tag = sheet.icon_tag(parent_carrier.chat_icon) + var/soulcatcher_icon = "" + + if(tag) + soulcatcher_icon = tag + + var/datum/component/carrier_user/user_component + if(sender_mob && istype(sender_mob)) + user_component = sender_mob.GetComponent(/datum/component/carrier_user) + if(!istype(user_component)) + return FALSE + else + sender_mob = "soulcatcher host" + + if(istype(user_component) && user_component.communicating_externally) + var/obj/item/parent_object = parent_carrier.parent + if(!istype(parent_object)) + return FALSE + + var/temp_name = parent_object.name + parent_object.name = "[parent_object.name] [soulcatcher_icon]" + + if(emote) + parent_object.manual_emote(html_decode(message_to_send)) + log_emote("[sender_mob] in [name] carrier room emoted: [message_to_send], as an external object") + else + parent_object.say(html_decode(message_to_send)) + log_say("[sender_mob] in [name] carrier room said: [message_to_send], as an external object") + + parent_object.name = temp_name + return TRUE + + var/first_room_name_word = splittext(name, " ") + var/message = "" + var/owner_message = "" + if(!emote) + message = "\ [soulcatcher_icon] [sender_name] says, \"[message_to_send]\"" + owner_message = "\ ([first_room_name_word[1]]) [soulcatcher_icon] [sender_name] says, \"[message_to_send]\"" + log_say("[sender_mob] in [name] carrier room said: [message_to_send]") + else + message = "\ [soulcatcher_icon] [sender_name] [message_to_send]" + owner_message = "\ ([first_room_name_word[1]]) [soulcatcher_icon] [sender_name] [message_to_send]" + log_emote("[sender_mob] in [name] carrier room emoted: [message_to_send]") + + for(var/mob/living/soul as anything in current_mobs) + var/message_eligible = SEND_SIGNAL(soul, COMSIG_CARRIER_MOB_CHECK_INTERNAL_SENSES, emote) + if(!message_eligible) + continue + + to_chat(soul, message) + + relay_message_to_carrier(owner_message) + return TRUE + +/// Relays a message sent from the send_message proc to the parent carrier datum +/datum/carrier_room/proc/relay_message_to_carrier(message) + if(!message) + return FALSE + + var/datum/component/carrier/recepient_carrier = master_carrier.resolve() + if(!recepient_carrier) + return FALSE // This really isn't good. + + recepient_carrier.recieve_message(message) + return TRUE + +/datum/carrier_room/Destroy(force, ...) + for(var/mob/living/occupant as anything in current_mobs) + remove_mob(occupant) + + return ..() diff --git a/modular_skyrat/modules/carriers/code/carrier_tgui.dm b/modular_skyrat/modules/carriers/code/carrier_tgui.dm new file mode 100644 index 00000000000000..0a59d19c2bfb76 --- /dev/null +++ b/modular_skyrat/modules/carriers/code/carrier_tgui.dm @@ -0,0 +1,367 @@ +/datum/component/carrier/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(usr, src, ui) + + if(!ui) + ui = new(usr, src, "Soulcatcher", name) + ui.open() + +/datum/component/carrier/soulcatcher/nifsoft/ui_state(mob/user) + return GLOB.conscious_state + +/datum/component/carrier/ui_data(mob/user) + var/list/data = list() + + var/datum/component/carrier/soulcatcher/soulcatcher_carrier = src + if(istype(soulcatcher_carrier)) + data["removable"] = soulcatcher_carrier.removable + data["ghost_joinable"] = soulcatcher_carrier.ghost_joinable + + data["require_approval"] = require_approval + data["theme"] = ui_theme + data["communicate_as_parent"] = communicate_as_parent + data["current_mob_count"] = length(get_current_mobs()) + data["max_mobs"] = max_mobs + + var/carrier_targeted = FALSE + var/datum/component/carrier_communicator/communicator = user.GetComponent(/datum/component/carrier_communicator) + if(istype(communicator) && communicator?.target_carrier?.resolve()) + var/datum/component/carrier/held_carrier = communicator.target_carrier.resolve() + carrier_targeted = (held_carrier == src) + data["carrier_targeted"] = carrier_targeted + + data["current_rooms"] = list() + for(var/datum/carrier_room/room in carrier_rooms) + var/currently_targeted = (room == targeted_carrier_room) + + var/list/room_data = list( + "name" = html_decode(room.name), + "description" = html_decode(room.room_description), + "reference" = REF(room), + "joinable" = room.joinable, + "color" = room.room_color, + "currently_targeted" = currently_targeted, + ) + + for(var/mob/living/soul in room.current_mobs) + var/datum/component/carrier_user/soul_component = soul.GetComponent(/datum/component/carrier_user) + if(!soul_component) + continue + + var/mob/living/soulcatcher_soul/soul_to_check = soul // So that we can check if a body scan is needed if we are working with a soul + var/list/soul_list = list( + "name" = soul_component.name, + "description" = soul_component.desc, + "reference" = REF(soul), + "internal_hearing" = soul_component.internal_hearing, + "internal_sight" = soul_component.internal_sight, + "outside_hearing" = soul_component.outside_hearing, + "outside_sight" = soul_component.outside_sight, + "able_to_emote" = soul_component.able_to_emote, + "able_to_speak" = soul_component.able_to_speak, + "able_to_rename" = soul_component.able_to_rename, + "ooc_notes" = soul_component.ooc_notes, + "able_to_speak_as_container" = soul_component.able_to_speak_as_container, + "able_to_emote_as_container" = soul_component.able_to_emote_as_container, + "scan_needed" = soul_to_check?.body_scan_needed, + "is_soul" = istype(soul_to_check), + ) + room_data["souls"] += list(soul_list) + + data["current_rooms"] += list(room_data) + + return data + +/datum/component/carrier/ui_static_data(mob/user) + var/list/data = list() + + data["current_vessel"] = parent + + return data + +/datum/component/carrier/ui_act(action, list/params) + . = ..() + if(.) + return + + var/datum/component/carrier/soulcatcher/soulcatcher_carrier = src + var/datum/carrier_room/target_room + if(params["room_ref"]) + target_room = locate(params["room_ref"]) in carrier_rooms + if(!target_room) + return FALSE + + var/mob/living/target_mob + if(params["target_mob"]) + target_mob = locate(params["target_mob"]) in target_room.current_mobs + if(!target_mob) + return FALSE + + switch(action) + if("delete_room") + if(length(carrier_rooms) <= 1) + return FALSE + + carrier_rooms -= target_room + targeted_carrier_room = carrier_rooms[1] + qdel(target_room) + return TRUE + + if("change_targeted_room") + targeted_carrier_room = target_room + return TRUE + + if("change_targeted_carrier") + update_targeted_carrier() + return TRUE + + if("create_room") + create_room() + return TRUE + + if("rename_room") + var/new_room_name = tgui_input_text(usr,"Choose a new name for the room", name, target_room.name) + if(!new_room_name) + return FALSE + + target_room.name = new_room_name + return TRUE + + if("redescribe_room") + var/new_room_desc = tgui_input_text(usr,"Choose a new description for the room", name, target_room.room_description, multiline = TRUE) + if(!new_room_desc) + return FALSE + + target_room.room_description = new_room_desc + return TRUE + + if("toggle_joinable_room") + if(!istype(soulcatcher_carrier)) + return FALSE + + target_room.joinable = !target_room.joinable + return TRUE + + if("toggle_joinable") + if(!istype(soulcatcher_carrier)) + return FALSE + + soulcatcher_carrier.ghost_joinable = !soulcatcher_carrier.ghost_joinable + return TRUE + + if("toggle_approval") + require_approval = !require_approval + return TRUE + + if("modify_name") + var/new_name = tgui_input_text(usr,"Choose a new name to send messages as", name, target_room.outside_voice) + if(!new_name) + return FALSE + + target_room.outside_voice = new_name + return TRUE + + if("remove_mob") + target_room.remove_mob(target_mob) + return TRUE + + if("transfer_mob") + var/list/available_rooms = carrier_rooms.Copy() + available_rooms -= target_room + + if(ishuman(usr)) + var/mob/living/carbon/human/human_user = usr + for(var/obj/item/carrier_holder/holder in human_user.contents) + var/datum/component/carrier/holder_carrier = holder.GetComponent(/datum/component/carrier) + if(!istype(holder_carrier)) + continue + + available_rooms += holder_carrier.get_open_rooms() + + for(var/obj/item/held_item in human_user.get_all_gear()) + if(parent == held_item) + continue + + var/datum/component/carrier/carrier_component = held_item.GetComponent(/datum/component/carrier) + if(!carrier_component) + continue + + available_rooms += carrier_component.get_open_rooms() + + var/datum/carrier_room/transfer_room = tgui_input_list(usr, "Choose a room to transfer to", name, available_rooms) + if(!(transfer_room in available_rooms)) + return FALSE + + transfer_mob(target_mob, transfer_room) + return TRUE + + if("change_room_color") + var/new_room_color = input(usr, "", "Choose Color", SOULCATCHER_DEFAULT_COLOR) as color + if(!new_room_color) + return FALSE + + target_room.room_color = new_room_color + + if("toggle_soul_sense") + var/sense_to_change = params["sense_to_change"] + if(!sense_to_change) + return FALSE + + SEND_SIGNAL(target_mob, COMSIG_CARRIER_MOB_TOGGLE_SENSE, sense_to_change) + return TRUE + + if("change_name") + var/new_name = tgui_input_text(usr, "Enter a new name for [target_mob]", "Soulcatcher", target_mob) + if(!new_name) + return FALSE + + return SEND_SIGNAL(target_mob, COMSIG_CARRIER_MOB_RENAME, new_name) + + if("reset_name") + if(tgui_alert(usr, "Do you wish to reset [target_mob]'s name to default?", "Soulcatcher", list("Yes", "No")) != "Yes") + return FALSE + + return SEND_SIGNAL(target_mob, COMSIG_CARRIER_MOB_RESET_NAME) + + if("send_message") + var/message_to_send = "" + var/emote_sent = params["emote"] + var/message_sender = target_room.outside_voice + if(params["narration"]) + message_sender = null + + message_to_send = tgui_input_text(usr, "Input the message you want to send", name, multiline = TRUE) + + if(!message_to_send) + return FALSE + + target_room.send_message(message_to_send, message_sender, emote = emote_sent) + return TRUE + + + if("delete_self") + if(!istype(soulcatcher_carrier)) + return FALSE + + if(tgui_alert(usr, "Are you sure you want to detach the soulcatcher?", parent, list("Yes", "No")) != "Yes") + return FALSE + + soulcatcher_carrier.remove_self() + return TRUE + +/datum/component/carrier_user/New() + . = ..() + var/mob/living/parent_mob = parent + if(!istype(parent_mob)) + return COMPONENT_INCOMPATIBLE + + return TRUE + +/datum/component/carrier_user/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(usr, src, ui) + if(!ui) + ui = new(usr, src, "SoulcatcherUser") + ui.open() + +/datum/component/carrier_user/ui_state(mob/user) + return GLOB.conscious_state + +/datum/component/carrier_user/ui_data(mob/user) + var/list/data = list() + + var/mob/living/parent_mob = parent + if(!istype(parent_mob)) + return FALSE //uhoh + + var/datum/component/carrier_communicator/communicator = parent.GetComponent(/datum/component/carrier_communicator) + data["targeted"] = communicator?.carried_mob + + var/mob/living/soulcatcher_soul/user_soul = parent_mob + data["user_data"] = list( + "name" = name, + "description" = desc, + "reference" = REF(parent_mob), + "internal_hearing" = internal_hearing, + "internal_sight" = internal_sight, + "outside_hearing" = outside_hearing, + "outside_sight" = outside_sight, + "able_to_emote" = able_to_emote, + "able_to_speak" = able_to_speak, + "able_to_rename" = able_to_rename, + "able_to_speak_as_container" = able_to_speak_as_container, + "able_to_emote_as_container" = able_to_emote_as_container, + "communicating_externally" = communicating_externally, + "ooc_notes" = ooc_notes, + "scan_needed" = user_soul?.body_scan_needed, + ) + + var/datum/carrier_room/current_carrier_room = current_room.resolve() + if(!current_carrier_room) + current_room = null + return FALSE + + data["current_room"] = list( + "name" = html_decode(current_carrier_room.name), + "description" = html_decode(current_carrier_room.room_description), + "reference" = REF(current_carrier_room), + "color" = current_carrier_room.room_color, + "owner" = current_carrier_room.outside_voice, + ) + + var/datum/component/carrier/master_carrier = current_carrier_room.master_carrier.resolve() + if(!master_carrier) + current_carrier_room.master_carrier = null + + var/datum/component/carrier/soulcatcher/master_soulcatcher + if(istype(master_soulcatcher)) + data["communicate_as_parent"] = master_soulcatcher.communicate_as_parent + + for(var/mob/living/soul in current_carrier_room.current_mobs) + if(soul == user_soul) + continue + + var/datum/component/carrier_user/soul_component = soul.GetComponent(/datum/component/carrier_user) + if(!soul_component) + continue + + var/list/soul_list = list( + "name" = soul_component.name, + "description" = soul_component.desc, + "ooc_notes" = soul_component.ooc_notes, + "reference" = REF(soul), + ) + data["souls"] += list(soul_list) + + return data + +/datum/component/carrier_user/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("change_name") + var/new_name = tgui_input_text(usr, "Enter a new name", "Soulcatcher", name) + if(!new_name) + return FALSE + + change_name(new_name = new_name) + return TRUE + + if("reset_name") + if(tgui_alert(usr, "Do you wish to reset your name to default?", "Soulcatcher", list("Yes", "No")) != "Yes") + return FALSE + + reset_name() + return TRUE + + if("toggle_external_communication") + communicating_externally = !communicating_externally + return TRUE + + if("toggle_target") + var/datum/component/carrier_communicator/communicator = parent.GetComponent(/datum/component/carrier_communicator) + if(!istype(communicator)) + return FALSE + + communicator.carried_mob = !communicator.carried_mob + return TRUE diff --git a/modular_skyrat/modules/carriers/code/carrier_user_component.dm b/modular_skyrat/modules/carriers/code/carrier_user_component.dm new file mode 100644 index 00000000000000..075b9d3c2fda31 --- /dev/null +++ b/modular_skyrat/modules/carriers/code/carrier_user_component.dm @@ -0,0 +1,285 @@ +/// The component given to carrier inhabitants +/datum/component/carrier_user + /// What is the name of our mob? + var/name + /// What does our mob look like? + var/desc = "It's a mob." + /// What are the ooc notes for the mob? + var/ooc_notes = "" + + /// What is the weakref of the carrier room are we currently in? + var/datum/weakref/current_room + + /// Is the mob able to see things in the outside world? + var/outside_sight = TRUE + /// Is the mob able to hear things from the outside world? + var/outside_hearing = TRUE + /// Is the mob able to "see" things from inside of the carrier? + var/internal_sight = TRUE + /// Is the mob able to "hear" things from inside of the carrier? + var/internal_hearing = TRUE + /// Is the mob able to emote inside the carrier room? + var/able_to_emote = TRUE + /// Is the mob able to speak inside the carrier room? + var/able_to_speak = TRUE + /// Is the mob able to change their own name? + var/able_to_rename = TRUE + /// Is the mob able to speak as the object it is inside? + var/able_to_speak_as_container = TRUE + /// Is the mob able to emote as the object it is inside? + var/able_to_emote_as_container = TRUE + /// Are emote's and Say's done through the container the mob is in? + var/communicating_externally = FALSE + + /// Is the action to control the HUD given to the mob? + var/hud_action_given = TRUE + /// The coresponding action used to pull up the HUD + var/datum/action/innate/carrier_user/carrier_action + /// Is the action to leave given to the mob? + var/leave_action_given = TRUE + /// The coresponding action used to leave the carrier + var/datum/action/innate/leave_carrier/leave_action + +/datum/component/carrier_user/New() + . = ..() + var/mob/living/parent_mob = parent + if(!istype(parent_mob)) + return COMPONENT_INCOMPATIBLE + + if(hud_action_given) + carrier_action = new + carrier_action.Grant(parent_mob) + carrier_action.carrier_user_component = WEAKREF(src) + + if(leave_action_given) + leave_action = new + leave_action.Grant(parent_mob) + + if(!outside_sight) + outside_sight = TRUE + toggle_sense(sense_to_toggle = "outside_sight") + + if(!outside_hearing) + outside_hearing = TRUE + toggle_sense(sense_to_toggle = "outside_hearing") + + refresh_mob_appearance() + + return TRUE + +/datum/component/carrier_user/RegisterWithParent() + RegisterSignal(parent, COMSIG_CARRIER_MOB_TOGGLE_SENSE, PROC_REF(toggle_sense)) + RegisterSignal(parent, COMSIG_CARRIER_MOB_RENAME, PROC_REF(change_name)) + RegisterSignal(parent, COMSIG_CARRIER_MOB_RESET_NAME, PROC_REF(reset_name)) + RegisterSignal(parent, COMSIG_CARRIER_MOB_CHANGE_ROOM, PROC_REF(set_room)) + RegisterSignal(parent, COMSIG_CARRIER_MOB_CHECK_INTERNAL_SENSES, PROC_REF(check_internal_senses)) + RegisterSignal(parent, COMSIG_CARRIER_MOB_REFRESH_APPEARANCE, PROC_REF(refresh_mob_appearance)) + +/// Configures the settings of the carrier user to be in accordance with the parent mob +/datum/component/carrier_user/proc/refresh_mob_appearance(datum/source) + SIGNAL_HANDLER + + var/mob/living/parent_mob = parent + if(!istype(parent_mob) || !istype(parent_mob.mind)) + return FALSE + + name = parent_mob.mind.name + + var/datum/preferences/preferences = parent_mob?.client?.prefs + if(!preferences) + return FALSE + + ooc_notes = preferences.read_preference(/datum/preference/text/ooc_notes) + desc = preferences.read_preference(/datum/preference/text/flavor_text) + +/// What do we want to do when a mob tries to say something into the carrier? +/datum/component/carrier_user/proc/say(message_to_say) + var/mob/living/parent_mob = parent + if(!istype(parent_mob)) + return FALSE + + if(!can_communicate()) + to_chat(parent, span_warning("You are unable to speak!")) + return FALSE + + if(!message_to_say) + return FALSE + + var/datum/carrier_room/room = current_room.resolve() + if(!room) // uhoh. + current_room = null + return FALSE + + room.send_message(message_to_say, name, parent_mob, FALSE) + return TRUE + +/// What do we want to do when a mob tries to do a `me` emote? +/datum/component/carrier_user/proc/me_verb(message_to_say) + var/mob/living/parent_mob = parent + if(!istype(parent_mob)) + return FALSE + + if(!can_communicate(TRUE)) + to_chat(parent, span_warning("You are unable to speak!")) + return FALSE + + if(!message_to_say) + return FALSE + + var/datum/carrier_room/room = current_room.resolve() + if(!room) // uhoh. + current_room = null + return FALSE + + room.send_message(message_to_say, name, parent_mob, TRUE) + return TRUE + +/// Modifies the sense of the parent mob based on the variable `sense_to_toggle`. Returns the state of the modified variable +/datum/component/carrier_user/proc/toggle_sense(datum/source, sense_to_toggle) + SIGNAL_HANDLER + var/status = FALSE + var/mob/living/parent_mob = parent + if(!istype(parent_mob)) + return FALSE + + switch(sense_to_toggle) + if("external_hearing") + outside_hearing = !outside_hearing + if(outside_hearing) + REMOVE_TRAIT(parent_mob, TRAIT_DEAF, TRAIT_CARRIER) + else + ADD_TRAIT(parent_mob, TRAIT_DEAF, TRAIT_CARRIER) + + status = outside_hearing + + if("external_sight") + outside_sight = !outside_sight + if(outside_sight) + parent_mob.cure_blind(TRAIT_CARRIER) + else + parent_mob.become_blind(TRAIT_CARRIER) + + status = outside_sight + + if("hearing") + internal_hearing = !internal_hearing + status = internal_hearing + + if("sight") + internal_sight = !internal_sight + status = internal_sight + + if("able_to_emote") + able_to_emote = !able_to_emote + status = able_to_emote + + if("able_to_speak") + able_to_speak = !able_to_speak + status = able_to_speak + + if("able_to_rename") + able_to_rename = !able_to_rename + status = able_to_rename + + if("able_to_emote_as_container") + able_to_emote_as_container = !able_to_emote_as_container + status = able_to_emote_as_container + + if("able_to_speak_as_container") + able_to_speak_as_container = !able_to_speak_as_container + status = able_to_speak_as_container + + return status + +/// Changes the name show on the component based off `new_name`. Returns `TRUE` if the name has been changed, otherwise returns `FALSE`. +/datum/component/carrier_user/proc/change_name(datum/source, new_name) + SIGNAL_HANDLER + var/mob/living/parent_mob = parent + if(!new_name || !istype(parent_mob) || !able_to_rename) + return FALSE + + var/mob/living/soulcatcher_soul/soul_mob = parent + if(istype(soul_mob) && (soul_mob.round_participant && soul_mob.body_scan_needed)) + return FALSE + + name = new_name + return TRUE + +/// Attempts to reset the mob's name to it's name in prefs. Returns `TRUE` if the name is reset, otherwise returns `FALSE`. +/datum/component/carrier_user/proc/reset_name(datum/source) + SIGNAL_HANDLER + var/mob/living/parent_mob = parent + if(!parent_mob?.mind?.name || !change_name(new_name = parent_mob.mind.name)) + return FALSE + + return TRUE + +/// Is the carrier mob able to communicate? Returns `TRUE` if they can, otherwise returns `FALSE` +/datum/component/carrier_user/proc/can_communicate(emote = FALSE) + if(communicating_externally) + if((emote && !able_to_emote_as_container) || (!emote && !able_to_speak_as_container)) + return FALSE + + if((emote && !able_to_emote) || (!emote && !able_to_speak)) + return FALSE + + return TRUE + +//// Is the carrier mob able to witness a message? `Emote` determines if the message is an emote or not. +/datum/component/carrier_user/proc/check_internal_senses(datum/source, emote = FALSE) + SIGNAL_HANDLER + if(emote) + return internal_sight + + return internal_hearing + +/// Sets the current room of the carrier component based off of `room_to_set` +/datum/component/carrier_user/proc/set_room(datum/source, datum/carrier_room/room_to_set) + SIGNAL_HANDLER + if(!istype(room_to_set)) + return FALSE + + current_room = room_to_set + +/datum/component/carrier_user/Destroy(force, silent) + if(!outside_hearing) + toggle_sense("external_hearing") + + if(!outside_sight) + toggle_sense("external_sight") + + if(carrier_action) + QDEL_NULL(carrier_action) + + if(leave_action) + QDEL_NULL(leave_action) + + return ..() + +/datum/component/carrier_user/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_CARRIER_MOB_TOGGLE_SENSE, + COMSIG_CARRIER_MOB_RENAME, + COMSIG_CARRIER_MOB_RESET_NAME, + COMSIG_CARRIER_MOB_CHANGE_ROOM, + COMSIG_CARRIER_MOB_CHECK_INTERNAL_SENSES, + COMSIG_CARRIER_MOB_REFRESH_APPEARANCE, + )) + +/datum/action/innate/carrier_user + name = "carrier" + background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' + background_icon_state = "android" + button_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_nif.dmi' + button_icon_state = "soulcatcher" + /// What carrier user component are we bringing up the menu for? + var/datum/weakref/carrier_user_component + +/datum/action/innate/carrier_user/Activate() + . = ..() + var/datum/component/carrier_user/user_component = carrier_user_component.resolve() + if(!user_component) + carrier_user_component = null + return FALSE + + user_component.ui_interact(owner) diff --git a/modular_skyrat/modules/carriers/code/carrier_verbs.dm b/modular_skyrat/modules/carriers/code/carrier_verbs.dm new file mode 100644 index 00000000000000..5a8b0eb63339ef --- /dev/null +++ b/modular_skyrat/modules/carriers/code/carrier_verbs.dm @@ -0,0 +1,92 @@ +/datum/component/carrier_communicator + /// What carrier room is the parent mob currently trying to communicate with? + var/datum/weakref/target_carrier + /// Is the mob trying to communicate with the carrier they are inside? + var/carried_mob = FALSE + +/datum/component/carrier_communicator/New() + . = ..() + var/mob/living/parent_mob = parent + if(!istype(parent_mob)) + return COMPONENT_INCOMPATIBLE + + add_verb(parent_mob, list(/mob/living/proc/carrier_say, /mob/living/proc/carrier_emote)) + +/datum/component/carrier_communicator/Destroy(force) + var/mob/living/holder = parent + if(!istype(holder)) + return FALSE + + remove_verb(holder, list(/mob/living/proc/carrier_say, /mob/living/proc/carrier_emote)) + return ..() + +/// Prompts the parent mob to send a say message to the soulcatcher. Returns False if no soulcatcher or message could be found. +/mob/living/proc/carrier_say() + set name = "Carrier Say" + set category = "IC" + set desc = "Send a Say message to your currently targeted carrier room." + + var/datum/carrier_room/room_to_send_to = get_current_carrier_room() + if(!istype(room_to_send_to)) + to_chat(src, span_warning("You do not have a carrier you can send messages to!")) + return FALSE + + var/message_to_send = tgui_input_text(usr, "Input the message you want to send", "Carrier", multiline = TRUE) + if(!message_to_send) + return FALSE + + var/message_sender = room_to_send_to.outside_voice + var/datum/component/carrier_user/carrier_user_component = GetComponent(/datum/component/carrier_user) + var/datum/component/carrier_communicator/communicator_component = GetComponent(/datum/component/carrier_communicator) + if(istype(carrier_user_component) && istype(communicator_component) && communicator_component.carried_mob) + message_sender = carrier_user_component.name + + room_to_send_to.send_message(message_to_send, message_sender) + return TRUE + +/// Prompts the parent mob to send a emote to the soulcatcher. Returns False if no soulcatcher or emote could be found. +/mob/living/proc/carrier_emote() + set name = "Carrier Me" + set category = "IC" + set desc = "Send a emote to your currently targeted carrier room." + + var/datum/carrier_room/room_to_send_to = get_current_carrier_room() + if(!istype(room_to_send_to)) + to_chat(src, span_warning("You do not have a carrier you can send emotes to!")) + return FALSE + + var/message_to_send = tgui_input_text(usr, "Input the emote you want to send", "Soulcatcher", multiline = TRUE) + if(!message_to_send) + return FALSE + + var/message_sender = room_to_send_to.outside_voice + var/datum/component/carrier_user/carrier_user_component = GetComponent(/datum/component/carrier_user) + var/datum/component/carrier_communicator/communicator_component = GetComponent(/datum/component/carrier_communicator) + if(istype(carrier_user_component) && istype(communicator_component) && communicator_component.carried_mob) + message_sender = carrier_user_component.name + + room_to_send_to.send_message(message_to_send, message_sender, TRUE) + return TRUE + +/// Attempts to find and return the current carrier room the mob is using. +/mob/living/proc/get_current_carrier_room() + var/datum/component/carrier_communicator/communicator_component = GetComponent(/datum/component/carrier_communicator) + if(!communicator_component) + return FALSE + + var/datum/component/carrier/master_carrier = communicator_component.target_carrier?.resolve() + if(!istype(master_carrier)) + return FALSE + + var/datum/carrier_room/target_room = master_carrier.targeted_carrier_room + var/datum/component/carrier_user/carrier_user_component = GetComponent(/datum/component/carrier_user) + if(communicator_component.carried_mob && istype(carrier_user_component)) + target_room = carrier_user_component.current_room.resolve() + if(!istype(target_room)) + return FALSE + + if(!Adjacent(master_carrier.parent)) + communicator_component.target_carrier = null + return FALSE + + return target_room diff --git a/modular_skyrat/modules/modular_implants/code/nifsofts/soulcatcher.dm b/modular_skyrat/modules/modular_implants/code/nifsofts/soulcatcher.dm index 716e7e9513c94d..08399f55174266 100644 --- a/modular_skyrat/modules/modular_implants/code/nifsofts/soulcatcher.dm +++ b/modular_skyrat/modules/modular_implants/code/nifsofts/soulcatcher.dm @@ -15,9 +15,9 @@ /// What action to bring up the soulcatcher is linked with this NIFSoft? var/datum/action/innate/soulcatcher/soulcatcher_action /// a list containing saved soulcatcher rooms - var/list/saved_soulcatcher_rooms = list() + var/list/saved_carrier_rooms = list() /// The item we are using to store the souls - var/obj/item/soulcatcher_holder/soul_holder + var/obj/item/carrier_holder/holder /datum/nifsoft/soulcatcher/New() . = ..() @@ -25,15 +25,15 @@ soulcatcher_action.Grant(linked_mob) soulcatcher_action.parent_nifsoft = WEAKREF(src) - soul_holder = new(linked_mob) - var/datum/component/soulcatcher/new_soulcatcher = soul_holder.AddComponent(/datum/component/soulcatcher/nifsoft) - soul_holder.name = linked_mob.name + holder = new(linked_mob) + var/datum/component/carrier/soulcatcher/new_soulcatcher = holder.AddComponent(/datum/component/carrier/soulcatcher/nifsoft) + holder.name = "[linked_mob.name]'s soulcatcher" - for(var/room in saved_soulcatcher_rooms) - new_soulcatcher.create_room(room, saved_soulcatcher_rooms[room]) + for(var/room in saved_carrier_rooms) + new_soulcatcher.create_room(room, saved_carrier_rooms[room]) - if(length(new_soulcatcher.soulcatcher_rooms) > 1) //We don't need the default room anymore. - new_soulcatcher.soulcatcher_rooms -= new_soulcatcher.soulcatcher_rooms[1] + if(length(new_soulcatcher.carrier_rooms) > 1) //We don't need the default room anymore. + new_soulcatcher.carrier_rooms -= new_soulcatcher.carrier_rooms[1] new_soulcatcher.name = "[linked_mob]" @@ -46,7 +46,7 @@ if(!linked_soulcatcher) return FALSE - var/datum/component/soulcatcher/current_soulcatcher = linked_soulcatcher.resolve() + var/datum/component/carrier/current_soulcatcher = linked_soulcatcher.resolve() if(!current_soulcatcher) return FALSE @@ -65,11 +65,11 @@ qdel(soulcatcher_action) if(linked_soulcatcher) - var/datum/component/soulcatcher/current_soulcatcher = linked_soulcatcher.resolve() + var/datum/component/carrier/current_soulcatcher = linked_soulcatcher.resolve() if(current_soulcatcher) qdel(current_soulcatcher) - qdel(soul_holder) + qdel(holder) return ..() @@ -79,7 +79,7 @@ if(!persistence) return FALSE - saved_soulcatcher_rooms = params2list(persistence.nif_soulcatcher_rooms) + saved_carrier_rooms = params2list(persistence.nif_carrier_rooms) return TRUE /datum/nifsoft/soulcatcher/save_persistence_data(datum/modular_persistence/persistence) @@ -88,11 +88,11 @@ return FALSE var/list/room_list = list() - var/datum/component/soulcatcher/current_soulcatcher = linked_soulcatcher.resolve() - for(var/datum/soulcatcher_room/room in current_soulcatcher.soulcatcher_rooms) + var/datum/component/carrier/current_soulcatcher = linked_soulcatcher.resolve() + for(var/datum/carrier_room/room in current_soulcatcher.carrier_rooms) room_list[room.name] = room.room_description - persistence.nif_soulcatcher_rooms = list2params(room_list) + persistence.nif_carrier_rooms = list2params(room_list) return TRUE /datum/nifsoft/soulcatcher/update_theme() @@ -103,7 +103,7 @@ if(isnull(linked_soulcatcher)) return FALSE - var/datum/component/soulcatcher/current_soulcatcher = linked_soulcatcher.resolve() + var/datum/component/carrier/current_soulcatcher = linked_soulcatcher.resolve() if(!istype(current_soulcatcher)) stack_trace("[src] ([REF(src)]) tried to update its theme when it was missing a linked_soulcatcher component!") return FALSE @@ -111,7 +111,7 @@ /datum/modular_persistence ///A param string containing soulcatcher rooms - var/nif_soulcatcher_rooms = "" + var/nif_carrier_rooms = "" /datum/action/innate/soulcatcher name = "Soulcatcher" @@ -131,7 +131,7 @@ soulcatcher_nifsoft.activate() /// This is the object we use if we give a mob soulcatcher. Having the souls directly parented could cause issues. -/obj/item/soulcatcher_holder +/obj/item/carrier_holder name = "Soul Holder" desc = "You probably shouldn't be seeing this..." diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/attachable_soulcatcher.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/attachable_soulcatcher.dm index cd84179e083898..0d2de8f71218ca 100644 --- a/modular_skyrat/modules/modular_implants/code/soulcatcher/attachable_soulcatcher.dm +++ b/modular_skyrat/modules/modular_implants/code/soulcatcher/attachable_soulcatcher.dm @@ -1,19 +1,19 @@ -/datum/component/soulcatcher/small_device - max_souls = 1 +/datum/component/carrier/soulcatcher/small_device + max_mobs = 1 -/datum/component/soulcatcher/attachable_soulcatcher - max_souls = 1 +/datum/component/carrier/soulcatcher/attachable + max_mobs = 1 communicate_as_parent = TRUE removable = TRUE -/datum/component/soulcatcher/attachable_soulcatcher/New() +/datum/component/carrier/soulcatcher/attachable/New() . = ..() var/obj/item/parent_item = parent if(!istype(parent_item)) return COMPONENT_INCOMPATIBLE name = parent_item.name - var/datum/soulcatcher_room/first_room = soulcatcher_rooms[1] + var/datum/carrier_room/first_room = carrier_rooms[1] first_room.name = parent_item.name first_room.room_description = parent_item.desc @@ -22,34 +22,32 @@ RegisterSignal(parent, COMSIG_PREQDELETED, PROC_REF(remove_self)) /// Adds text to the examine text of the parent item, explaining that the item can be used to enable the use of NIFSoft HUDs -/datum/component/soulcatcher/attachable_soulcatcher/proc/on_examine(datum/source, mob/user, list/examine_text) +/datum/component/carrier/soulcatcher/attachable/proc/on_examine(datum/source, mob/user, list/examine_text) SIGNAL_HANDLER examine_text += span_cyan("[source] has a soulcatcher attached to it, Ctrl+Shift+Click to use it.") -/datum/component/soulcatcher/attachable_soulcatcher/proc/bring_up_ui(datum/source, mob/user) +/datum/component/carrier/soulcatcher/attachable/proc/bring_up_ui(datum/source, mob/user) SIGNAL_HANDLER INVOKE_ASYNC(src, PROC_REF(ui_interact), user) -/datum/component/soulcatcher/attachable_soulcatcher/Destroy(force) +/datum/component/carrier/soulcatcher/attachable/Destroy(force) UnregisterSignal(parent, COMSIG_ATOM_EXAMINE) UnregisterSignal(parent, COMSIG_CLICK_CTRL_SHIFT) UnregisterSignal(parent, COMSIG_PREQDELETED) return ..() -/datum/component/soulcatcher/attachable_soulcatcher/remove_self() +/datum/component/carrier/soulcatcher/attachable/remove_self() var/obj/item/parent_item = parent var/turf/drop_turf = get_turf(parent_item) var/obj/item/attachable_soulcatcher/dropped_item = new (drop_turf) - var/datum/component/soulcatcher/dropped_soulcatcher = dropped_item.GetComponent(/datum/component/soulcatcher) - var/datum/soulcatcher_room/target_room = dropped_soulcatcher.soulcatcher_rooms[1] - var/list/current_souls = get_current_souls() + var/datum/component/carrier/dropped_soulcatcher = dropped_item.GetComponent(/datum/component/carrier) + var/datum/carrier_room/target_room = dropped_soulcatcher.carrier_rooms[1] + var/list/current_mobs = get_current_mobs() - if(current_souls) // If we have souls inside of here, they should be transferred to the new object - for(var/mob/living/soulcatcher_soul/soul as anything in current_souls) - var/datum/soulcatcher_room/current_room = soul.current_room.resolve() - if(istype(current_room)) - current_room.transfer_soul(soul, target_room) + if(current_mobs) // If we have souls inside of here, they should be transferred to the new object + for(var/mob/living/soul as anything in current_mobs) + transfer_mob(soul, target_room) return ..() @@ -72,11 +70,11 @@ /obj/item/disk/nuclear, // Woah there ) /// What soulcathcer component is currnetly linked to this object? - var/datum/component/soulcatcher/small_device/linked_soulcatcher + var/datum/component/carrier/soulcatcher/small_device/linked_soulcatcher /obj/item/attachable_soulcatcher/Initialize(mapload) . = ..() - linked_soulcatcher = AddComponent(/datum/component/soulcatcher/small_device) + linked_soulcatcher = AddComponent(/datum/component/carrier/soulcatcher/small_device) linked_soulcatcher.name = name /obj/item/attachable_soulcatcher/attack_self(mob/user, modifiers) @@ -87,7 +85,7 @@ if(!proximity_flag || !istype(target_item)) return FALSE - if(target_item.GetComponent(/datum/component/soulcatcher)) + if(target_item.GetComponent(/datum/component/carrier)) balloon_alert(user, "already attached!") return FALSE @@ -95,17 +93,14 @@ balloon_alert(user, "incompatible!") return FALSE - var/datum/component/soulcatcher/new_soulcatcher = target_item.AddComponent(/datum/component/soulcatcher/attachable_soulcatcher) + var/datum/component/carrier/soulcatcher/attachable/new_soulcatcher = target_item.AddComponent(/datum/component/carrier/soulcatcher/attachable) playsound(target_item.loc, 'sound/weapons/circsawhit.ogg', 50, vary = TRUE) - var/datum/soulcatcher_room/target_room = new_soulcatcher.soulcatcher_rooms[1] - var/list/current_souls = linked_soulcatcher.get_current_souls() - if(current_souls) - for(var/mob/living/soulcatcher_soul/soul as anything in current_souls) - var/datum/soulcatcher_room/current_room = soul.current_room.resolve() - if(istype(current_room)) - current_room.transfer_soul(soul, target_room) - current_room.transfer_soul(soul, target_room) + var/datum/carrier_room/target_room = new_soulcatcher.carrier_rooms[1] + var/list/current_mobs = linked_soulcatcher.get_current_mobs() + if(current_mobs) + for(var/mob/living/soul as anything in current_mobs) + linked_soulcatcher.transfer_mob(soul, target_room) if(destroy_on_use) qdel(src) diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/ghost.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/ghost.dm new file mode 100644 index 00000000000000..28ccc7cdec264f --- /dev/null +++ b/modular_skyrat/modules/modular_implants/code/soulcatcher/ghost.dm @@ -0,0 +1,89 @@ +/datum/action/innate/join_soulcatcher + name = "Enter Soulcatcher" + background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' + background_icon_state = "android" + button_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_nif.dmi' + button_icon_state = "soulcatcher_enter" + +/datum/action/innate/join_soulcatcher/Activate() + . = ..() + var/mob/dead/observer/joining_soul = owner + if(!joining_soul) + return FALSE + + joining_soul.join_soulcatcher() + +/mob/dead/observer/verb/join_soulcatcher() + set name = "Enter Soulcatcher" + set category = "Ghost" + + var/list/joinable_soulcatchers = list() + var/list/rooms_to_join = list() + + for(var/datum/component/carrier/soulcatcher/soulcatcher in GLOB.soulcatchers) + if(!soulcatcher.ghost_joinable || !isobj(soulcatcher.parent) || !soulcatcher.check_for_vacancy()) + continue + + var/list/carrier_rooms = soulcatcher.get_open_rooms(TRUE) + if(!length(carrier_rooms)) + continue + + var/obj/item/soulcatcher_parent = soulcatcher.parent + if(soulcatcher.name != soulcatcher_parent.name) + soulcatcher.name = soulcatcher_parent.name + + joinable_soulcatchers += soulcatcher + rooms_to_join += carrier_rooms + + if(!length(joinable_soulcatchers) || !length(rooms_to_join)) + to_chat(src, span_warning("No soulcatchers are joinable.")) + return FALSE + + var/datum/component/carrier/soulcatcher/soulcatcher_to_join = tgui_input_list(src, "Choose a soulcatcher to join", "Enter a soulcatcher", joinable_soulcatchers) + if(!soulcatcher_to_join || !(soulcatcher_to_join in joinable_soulcatchers)) + return FALSE + + rooms_to_join = soulcatcher_to_join.get_open_rooms(TRUE) + var/datum/carrier_room/soulcatcher/room_to_join = tgui_input_list(src, "Choose a room to enter", "Enter a room", rooms_to_join) + if(!room_to_join) + to_chat(src, span_warning("There no rooms that you can join.")) + return FALSE + + if(soulcatcher_to_join.require_approval) + var/ghost_name = name + if(mind?.current) + ghost_name = "unknown" + + if(!soulcatcher_to_join.get_approval(ghost_name)) + to_chat(src, span_warning("The owner of [soulcatcher_to_join.name] declined your request to join.")) + return FALSE + + room_to_join.add_soul_from_ghost(src) + return TRUE + +/mob/grab_ghost(force) + SEND_SIGNAL(src, COMSIG_SOULCATCHER_CHECK_SOUL) + return ..() + +/mob/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) + if(GetComponent(/datum/component/previous_body)) //Is the soul currently within a soulcatcher? + return TRUE + + return ..() + +/mob/dead/observer/Login() + . = ..() + var/datum/preferences/preferences = client?.prefs + var/soulcatcher_action_given + + if(preferences) + soulcatcher_action_given = preferences.read_preference(/datum/preference/toggle/soulcatcher_join_action) + + if(!soulcatcher_action_given) + return + + if(locate(/datum/action/innate/join_soulcatcher) in actions) + return + + var/datum/action/innate/join_soulcatcher/new_join_action = new(src) + new_join_action.Grant(src) diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/handheld_soulcatcher.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/handheld_soulcatcher.dm index 767f396057e6f0..3ef62e68ac2b4b 100644 --- a/modular_skyrat/modules/modular_implants/code/soulcatcher/handheld_soulcatcher.dm +++ b/modular_skyrat/modules/modular_implants/code/soulcatcher/handheld_soulcatcher.dm @@ -12,7 +12,7 @@ slot_flags = ITEM_SLOT_BELT obj_flags = UNIQUE_RENAME /// What soulcatcher datum is associated with this item? - var/datum/component/soulcatcher/linked_soulcatcher + var/datum/component/carrier/soulcatcher/linked_soulcatcher /// The cooldown for the RSD on scanning a body if the ghost refuses. This is here to prevent spamming. COOLDOWN_DECLARE(rsd_scan_cooldown) @@ -26,7 +26,7 @@ /obj/item/handheld_soulcatcher/New(loc, ...) . = ..() - linked_soulcatcher = AddComponent(/datum/component/soulcatcher) + linked_soulcatcher = AddComponent(/datum/component/carrier/soulcatcher) linked_soulcatcher.name = name /obj/item/handheld_soulcatcher/Destroy(force) @@ -59,7 +59,7 @@ to_chat(user, span_warning("You are unable to get the soul of [target_mob]!")) return FALSE - var/datum/soulcatcher_room/target_room = tgui_input_list(user, "Choose a room to send [target_mob]'s soul to.", name, linked_soulcatcher.soulcatcher_rooms, timeout = 30 SECONDS) + var/datum/carrier_room/soulcatcher/target_room = tgui_input_list(user, "Choose a room to send [target_mob]'s soul to.", name, linked_soulcatcher.carrier_rooms, timeout = 30 SECONDS) if(!target_room) return FALSE @@ -82,7 +82,7 @@ linked_soulcatcher.scan_body(target_mob, user) return TRUE - var/datum/soulcatcher_room/target_room = tgui_input_list(user, "Choose a room to send [target_mob]'s soul to.", name, linked_soulcatcher.soulcatcher_rooms, timeout = 30 SECONDS) + var/datum/carrier_room/target_room = tgui_input_list(user, "Choose a room to send [target_mob]'s soul to.", name, linked_soulcatcher.carrier_rooms, timeout = 30 SECONDS) if(!target_room) return FALSE @@ -97,7 +97,7 @@ if(!target_mob.mind) return FALSE - target_room.add_soul(target_mob.mind, TRUE) + target_room.add_soul_from_mind(target_mob.mind, FALSE) playsound(src, 'modular_skyrat/modules/modular_implants/sounds/default_good.ogg', 50, FALSE, ignore_walls = FALSE) visible_message(span_notice("[src] beeps: [target_mob]'s mind transfer is now complete.")) @@ -129,9 +129,9 @@ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN var/list/soul_list = list() - for(var/datum/soulcatcher_room/room as anything in linked_soulcatcher.soulcatcher_rooms) - for(var/mob/living/soulcatcher_soul/soul as anything in room.current_souls) - if(!soul.round_participant || soul.body_scan_needed) + for(var/datum/carrier_room/room as anything in linked_soulcatcher.carrier_rooms) + for(var/mob/living/soulcatcher_soul/soul as anything in room.current_mobs) + if(!istype(soul) || !soul.round_participant || soul.body_scan_needed) continue soul_list += soul diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_body_component.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_body_component.dm index 241991b61f80a0..bb48dbb2e72f26 100644 --- a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_body_component.dm +++ b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_body_component.dm @@ -35,13 +35,9 @@ return FALSE to_chat(target_soul, span_cyan("Your body has scanned, revealing your true identity.")) - target_soul.name = source_mob.real_name target_soul.body_scan_needed = FALSE - var/datum/preferences/preferences = target_soul.client?.prefs - if(preferences) - target_soul.soul_desc = preferences.read_preference(/datum/preference/text/flavor_text) - + SEND_SIGNAL(target_soul, COMSIG_CARRIER_MOB_REFRESH_APPEARANCE) return TRUE /// Attempts to destroy the component. If `restore_mind` is true, it will attempt to place the mind back inside of the body and delete the soulcatcher soul. diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_component.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_component.dm index 8c14385430a24a..a2f9a35b70c70f 100644 --- a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_component.dm +++ b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_component.dm @@ -1,134 +1,42 @@ -///Global list containing any and all soulcatchers -GLOBAL_LIST_EMPTY(soulcatchers) - -#define SOULCATCHER_DEFAULT_COLOR "#75D5E1" -#define SOULCATCHER_WARNING_MESSAGE "You have entered a soulcatcher, do not share any information you have received while a ghost. If you have died within the round, you do not know your identity until your body has been scanned, standard blackout policy also applies." - -/** - * Soulcatcher Component - * - * This component functions as a bridge between the `soulcatcher_room` attached to itself and the parented datum. - * It handles the creation of new soulcatcher rooms, TGUI, and relaying messages to the parent datum. - * If the component is deleted, any soulcatcher rooms inside of `soulcatcher_rooms` will be deleted. - */ -/datum/component/soulcatcher - /// What is the name of the soulcatcher? - var/name = "soulcatcher" - /// What rooms are linked to this soulcatcher - var/list/soulcatcher_rooms = list() - /// What soulcatcher room are verbs sending messages to? - var/datum/soulcatcher_room/targeted_soulcatcher_room - /// What theme are we using for our soulcatcher UI? - var/ui_theme = "default" - +/datum/component/carrier/soulcatcher /// Are ghosts currently able to join this soulcatcher? var/ghost_joinable = TRUE - /// Do we want to ask the user permission before the ghost joins? - var/require_approval = TRUE - /// What is the max number of people we can keep in this soulcatcher? If this is set to `FALSE` we don't have a limit - var/max_souls = FALSE - /// Are are the souls inside able to emote/speak as the parent? - var/communicate_as_parent = FALSE /// Is the soulcatcher removable from the parent object? var/removable = FALSE -/datum/component/soulcatcher/New() - . = ..() - if(!parent) - return COMPONENT_INCOMPATIBLE + type_of_room_to_create = /datum/carrier_room/soulcatcher - create_room() - targeted_soulcatcher_room = soulcatcher_rooms[1] +/datum/component/carrier/soulcatcher/New() + . = ..() GLOB.soulcatchers += src - var/obj/item/soulcatcher_holder/soul_holder = parent - if(istype(soul_holder) && ismob(soul_holder.loc)) - var/mob/living/soulcatcher_owner = soul_holder.loc - add_verb(soulcatcher_owner, list( - /mob/living/proc/soulcatcher_say, - /mob/living/proc/soulcatcher_emote, - )) - -/datum/component/soulcatcher/Destroy(force, ...) +/datum/component/carrier/soulcatcher/Destroy(force, ...) GLOB.soulcatchers -= src - - targeted_soulcatcher_room = null - for(var/datum/soulcatcher_room as anything in soulcatcher_rooms) - soulcatcher_rooms -= soulcatcher_room - qdel(soulcatcher_room) - - var/mob/living/soulcatcher_owner = parent - var/obj/item/organ/internal/cyberimp/brain/nif/parent_nif = parent - if(istype(parent_nif)) - soulcatcher_owner = parent_nif.linked_mob - - if(istype(soulcatcher_owner)) - remove_verb(soulcatcher_owner, list( - /mob/living/proc/soulcatcher_say, - /mob/living/proc/soulcatcher_emote, - )) - return ..() -/** - * Creates a `/datum/soulcatcher_room` and adds it to the `soulcatcher_rooms` list. - * - * Arguments - * * target_name - The name that we want to assign to the created room. - * * target_desc - The description that we want to assign to the created room. - */ -/datum/component/soulcatcher/proc/create_room(target_name = "Default Room", target_desc = "An orange platform suspended in space orbited by reflective cubes of various sizes. There really isn't much here at the moment.") - var/datum/soulcatcher_room/created_room = new(src) - created_room.name = target_name - created_room.room_description = target_desc - soulcatcher_rooms += created_room - - created_room.master_soulcatcher = WEAKREF(src) - -/// Tries to find out who is currently using the soulcatcher, returns the holder. If no holder can be found, returns FALSE -/datum/component/soulcatcher/proc/get_current_holder() - var/mob/living/holder - - if(!istype(parent, /obj/item)) - return FALSE - - var/obj/item/parent_item = parent - holder = parent_item.loc +/datum/component/carrier/soulcatcher/nifsoft + single_owner = TRUE - if(!istype(holder)) - return FALSE - - return holder - -/// Recieves a message from a soulcatcher room. -/datum/component/soulcatcher/proc/recieve_message(message_to_recieve) - if(!message_to_recieve) - return FALSE - - var/mob/living/soulcatcher_owner = get_current_holder() - if(!soulcatcher_owner) +/// Attempts to remove the carrier from the attached object +/datum/component/carrier/soulcatcher/proc/remove_self() + if(!removable) return FALSE - to_chat(soulcatcher_owner, message_to_recieve) - return TRUE - -/// Attempts to ping the current user of the soulcatcher, asking them if `joiner_name` is allowed in. If they are, the proc returns `TRUE`, otherwise returns FALSE -/datum/component/soulcatcher/proc/get_approval(joiner_name) - if(!require_approval) - return TRUE + qdel(src) - var/mob/living/soulcatcher_owner = get_current_holder() +/// Returns a list of all of the rooms that a soul can join/transfer into. `ghost_join` checks if the room is accessible to ghosts. +/datum/component/carrier/soulcatcher/get_open_rooms(ghost_join = FALSE) + var/list/datum/carrier_room/room_list = list() + for(var/datum/carrier_room/room as anything in carrier_rooms) + if((ghost_join && !room.joinable) || !check_for_vacancy()) + continue - if(!soulcatcher_owner) - return FALSE + room_list += room - if(tgui_alert(soulcatcher_owner, "Do you wish to allow [joiner_name] into your soulcatcher?", name, list("Yes", "No"), autofocus = FALSE) != "Yes") - return FALSE - - return TRUE + return room_list /// Attempts to scan the body for the `previous_body component`, returns FALSE if the body is unable to be scanned, otherwise returns TRUE -/datum/component/soulcatcher/proc/scan_body(mob/living/parent_body, mob/living/user) +/datum/component/carrier/soulcatcher/proc/scan_body(mob/living/parent_body, mob/living/user) if(!parent_body || !user) return FALSE @@ -144,57 +52,14 @@ GLOBAL_LIST_EMPTY(soulcatchers) return TRUE -/// Returns a list containing all of the souls currently present within a soulcatcher. -/datum/component/soulcatcher/proc/get_current_souls() - var/list/current_souls = list() - for(var/datum/soulcatcher_room/room as anything in soulcatcher_rooms) - for(var/mob/living/soulcatcher_soul as anything in room.current_souls) - current_souls += soulcatcher_soul - - return current_souls - -/// Checks the total number of souls present and compares it with `max_souls` returns `TRUE` if there is room (or no limit), otherwise returns `FALSE` -/datum/component/soulcatcher/proc/check_for_vacancy() - if(!max_souls) - return TRUE - - if(length(get_current_souls()) >= max_souls) - return FALSE - - return TRUE - -/// Attempts to remove the soulcatcher from the attached object -/datum/component/soulcatcher/proc/remove_self() - if(!removable) - return FALSE - - qdel(src) - -/** - * Soulcatcher Room - * - * This datum is where souls are sent to when joining soulcatchers. - * It handles sending messages to souls from the outside along with adding new souls, transfering, and removing souls. - * - */ -/datum/soulcatcher_room +/datum/carrier_room/soulcatcher /// What is the name of the room? - var/name = "Default Room" + name = "Default Room" /// What is the description of the room? - var/room_description = "An orange platform suspended in space orbited by reflective cubes of various sizes. There really isn't much here at the moment." - /// What souls are currently inside of the room? - var/list/current_souls = list() - /// Weakref for the master soulcatcher datum - var/datum/weakref/master_soulcatcher - /// What is the name of the person sending the messages? - var/outside_voice = "Host" - /// Can the room be joined at all? - var/joinable = TRUE - /// What is the color of chat messages sent by the room? - var/room_color = SOULCATCHER_DEFAULT_COLOR + room_description = "An orange platform suspended in space orbited by reflective cubes of various sizes. There really isn't much here at the moment." /// Attemps to add a ghost to the soulcatcher room. -/datum/soulcatcher_room/proc/add_soul_from_ghost(mob/dead/observer/ghost) +/datum/carrier_room/soulcatcher/proc/add_soul_from_ghost(mob/dead/observer/ghost) if(!ghost || !ghost.ckey) return FALSE @@ -203,268 +68,53 @@ GLOBAL_LIST_EMPTY(soulcatchers) ghost.mind.name = ghost.name ghost.mind.active = TRUE - if(!add_soul(ghost.mind)) + if(!add_soul_from_mind(ghost.mind)) return FALSE return TRUE /// Converts a mind into a soul and adds the resulting soul to the room. -/datum/soulcatcher_room/proc/add_soul(datum/mind/mind_to_add) +/datum/carrier_room/proc/add_soul_from_mind(datum/mind/mind_to_add, hide_participant_identity = TRUE) if(!mind_to_add) return FALSE - var/datum/component/soulcatcher/parent_soulcatcher = master_soulcatcher.resolve() + var/datum/component/carrier/parent_soulcatcher = master_carrier.resolve() var/datum/parent_object = parent_soulcatcher.parent if(!parent_object) return FALSE var/mob/living/soulcatcher_soul/new_soul = new(parent_object) - new_soul.name = mind_to_add.name - if(mind_to_add.current) var/datum/component/previous_body/body_component = mind_to_add.current.AddComponent(/datum/component/previous_body) body_component.soulcatcher_soul = WEAKREF(new_soul) new_soul.round_participant = TRUE new_soul.body_scan_needed = TRUE - new_soul.previous_body = WEAKREF(mind_to_add.current) - new_soul.name = pick(GLOB.last_names) //Until the body is discovered, the soul is a new person. - new_soul.soul_desc = "[new_soul] lacks a discernible form." - mind_to_add.transfer_to(new_soul, TRUE) - current_souls += new_soul - new_soul.current_room = WEAKREF(src) + var/datum/component/carrier_user/soul_component = parent_soulcatcher.add_mob(new_soul, src) + if(!soul_component) + return FALSE - var/datum/preferences/preferences = new_soul.client?.prefs - if(preferences) - new_soul.ooc_notes = preferences.read_preference(/datum/preference/text/ooc_notes) - if(!new_soul.body_scan_needed) - new_soul.soul_desc = preferences.read_preference(/datum/preference/text/flavor_text) + if(hide_participant_identity && new_soul.round_participant) + soul_component.name = pick(GLOB.last_names) //Until the body is discovered, the soul is a new person. + soul_component.desc = "[new_soul] lacks a discernible form." + + mind_to_add.transfer_to(new_soul, TRUE) + current_mobs += new_soul + soul_component.current_room = WEAKREF(src) to_chat(new_soul, span_cyan("You find yourself now inside of: [name]")) to_chat(new_soul, span_notice(room_description)) to_chat(new_soul, span_doyourjobidiot("You have entered a soulcatcher, do not share any information you have received while a ghost. If you have died within the round, you do not know your identity until your body has been scanned, standard blackout policy also applies.")) - to_chat(new_soul, span_notice("While inside of a soulcatcher, you are able to speak and emote by using the normal hotkeys and verbs, unless disabled by the owner.")) + to_chat(new_soul, span_notice("While inside of [src], you are able to speak and emote by using the normal hotkeys and verbs, unless disabled by the owner.")) to_chat(new_soul, span_notice("You may use the leave soulcatcher verb to leave the soulcatcher and return to your body at any time.")) var/atom/parent_atom = parent_object if(istype(parent_atom)) var/turf/soulcatcher_turf = get_turf(parent_soulcatcher.parent) - var/message_to_log = "[key_name(new_soul)] joined [src] inside of [parent_atom] at [loc_name(soulcatcher_turf)]" + var/message_to_log = "[key_name(new_soul)] entered [src] inside of [parent_atom] at [loc_name(soulcatcher_turf)]" parent_atom.log_message(message_to_log, LOG_GAME) new_soul.log_message(message_to_log, LOG_GAME) return TRUE - -/// Removes a soul from a soulcatcher room, leaving it as a ghost. Returns `FALSE` if the `soul_to_remove` cannot be found, otherwise returns `TRUE` after a successful deletion. -/datum/soulcatcher_room/proc/remove_soul(mob/living/soulcatcher_soul/soul_to_remove) - if(!soul_to_remove || !(soul_to_remove in current_souls)) - return FALSE - - current_souls -= soul_to_remove - soul_to_remove.current_room = null - - soul_to_remove.return_to_body() - qdel(soul_to_remove) - - return TRUE - -/// Transfers a soul from a soulcatcher room to another soulcatcher room. Returns `FALSE` if the target room or target soul cannot be found. -/datum/soulcatcher_room/proc/transfer_soul(mob/living/soulcatcher_soul/target_soul, datum/soulcatcher_room/target_room) - if(!(target_soul in current_souls) || !target_room) - return FALSE - - var/datum/component/soulcatcher/target_master_soulcatcher = target_room.master_soulcatcher.resolve() - if(target_master_soulcatcher != master_soulcatcher.resolve()) - target_soul.forceMove(target_master_soulcatcher.parent) - - target_soul.current_room = WEAKREF(target_room) - current_souls -= target_soul - target_room.current_souls += target_soul - - to_chat(target_soul, span_cyan("you've been transferred to [target_room]!")) - to_chat(target_soul, span_notice(target_room.room_description)) - - return TRUE - -/** - * Sends a message or emote to all of the souls currently located inside of the soulcatcher room. Returns `FALSE` if a message cannot be sent, otherwise returns `TRUE`. - * - * Arguments - * * message_to_send - The message we want to send to the occupants of the room - * * message_sender - The person that is sending the message. This is not required. - * * emote - Is the message sent an emote or not? - */ -/datum/soulcatcher_room/proc/send_message(message_to_send, message_sender, emote = FALSE) - if(!message_to_send) //Why say nothing? - return FALSE - - var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat) - var/tag = sheet.icon_tag("nif-soulcatcher") - var/soulcatcher_icon = "" - - if(tag) - soulcatcher_icon = tag - - var/mob/living/soulcatcher_soul/soul_sender = message_sender - if(istype(soul_sender) && soul_sender.communicating_externally) - var/master_resolved = master_soulcatcher.resolve() - if(!master_resolved) - return FALSE - var/datum/component/soulcatcher/parent_soulcatcher = master_resolved - var/obj/item/parent_object = parent_soulcatcher.parent - if(!istype(parent_object)) - return FALSE - - var/temp_name = parent_object.name - parent_object.name = "[parent_object.name] [soulcatcher_icon]" - - if(emote) - parent_object.manual_emote(html_decode(message_to_send)) - log_emote("[soul_sender] in [name] soulcatcher room emoted: [message_to_send], as an external object") - else - parent_object.say(html_decode(message_to_send)) - log_say("[soul_sender] in [name] soulcatcher room said: [message_to_send], as an external object") - - parent_object.name = temp_name - return TRUE - - var/sender_name = "" - if(message_sender) - sender_name = "[message_sender] " - - var/first_room_name_word = splittext(name, " ") - var/message = "" - var/owner_message = "" - if(!emote) - message = "\ [soulcatcher_icon] [sender_name]says, \"[message_to_send]\"" - owner_message = "\ ([first_room_name_word[1]]) [soulcatcher_icon] [sender_name]says, \"[message_to_send]\"" - log_say("[sender_name] in [name] soulcatcher room said: [message_to_send]") - else - message = "\ [soulcatcher_icon] [sender_name][message_to_send]" - owner_message = "\ ([first_room_name_word[1]]) [soulcatcher_icon] [sender_name][message_to_send]" - log_emote("[sender_name] in [name] soulcatcher room emoted: [message_to_send]") - - for(var/mob/living/soulcatcher_soul/soul as anything in current_souls) - if((emote && !soul.internal_sight) || (!emote && !soul.internal_hearing)) - continue - - to_chat(soul, message) - - relay_message_to_soulcatcher(owner_message) - return TRUE - -/// Relays a message sent from the send_message proc to the parent soulcatcher datum -/datum/soulcatcher_room/proc/relay_message_to_soulcatcher(message) - if(!message) - return FALSE - - var/datum/component/soulcatcher/recepient_soulcatcher = master_soulcatcher.resolve() - recepient_soulcatcher.recieve_message(message) - return TRUE - -/datum/soulcatcher_room/Destroy(force, ...) - for(var/mob/living/soulcatcher_soul/soul as anything in current_souls) - remove_soul(soul) - - return ..() - -/datum/action/innate/join_soulcatcher - name = "Enter Soulcatcher" - background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' - background_icon_state = "android" - button_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_nif.dmi' - button_icon_state = "soulcatcher_enter" - -/datum/action/innate/join_soulcatcher/Activate() - . = ..() - var/mob/dead/observer/joining_soul = owner - if(!joining_soul) - return FALSE - - joining_soul.join_soulcatcher() - -/mob/dead/observer/verb/join_soulcatcher() - set name = "Enter Soulcatcher" - set category = "Ghost" - - var/list/joinable_soulcatchers = list() - for(var/datum/component/soulcatcher/soulcatcher in GLOB.soulcatchers) - if(!soulcatcher.ghost_joinable || !isobj(soulcatcher.parent) || !soulcatcher.check_for_vacancy()) - continue - - var/obj/item/soulcatcher_parent = soulcatcher.parent - if(soulcatcher.name != soulcatcher_parent.name) - soulcatcher.name = soulcatcher_parent.name - - joinable_soulcatchers += soulcatcher - - if(!length(joinable_soulcatchers)) - to_chat(src, span_warning("No soulcatchers are joinable.")) - return FALSE - - var/datum/component/soulcatcher/soulcatcher_to_join = tgui_input_list(src, "Choose a soulcatcher to join", "Enter a soulcatcher", joinable_soulcatchers) - if(!soulcatcher_to_join || !(soulcatcher_to_join in joinable_soulcatchers)) - return FALSE - - var/list/rooms_to_join = list() - for(var/datum/soulcatcher_room/room in soulcatcher_to_join.soulcatcher_rooms) - if(!room.joinable) - continue - - rooms_to_join += room - - var/datum/soulcatcher_room/room_to_join - if(length(rooms_to_join) < 1) - to_chat(src, span_warning("There no rooms that you can join.")) - return FALSE - - if(length(rooms_to_join) == 1) - room_to_join = rooms_to_join[1] - - else - room_to_join = tgui_input_list(src, "Choose a room to enter", "Enter a room", rooms_to_join) - - if(!room_to_join) - to_chat(src, span_warning("There no rooms that you can join.")) - return FALSE - - if(soulcatcher_to_join.require_approval) - var/ghost_name = name - if(mind?.current) - ghost_name = "unknown" - - if(!soulcatcher_to_join.get_approval(ghost_name)) - to_chat(src, span_warning("The owner of [soulcatcher_to_join.name] declined your request to join.")) - return FALSE - - room_to_join.add_soul_from_ghost(src) - return TRUE - -/mob/grab_ghost(force) - SEND_SIGNAL(src, COMSIG_SOULCATCHER_CHECK_SOUL) - return ..() - -/mob/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) - if(GetComponent(/datum/component/previous_body)) //Is the soul currently within a soulcatcher? - return TRUE - - return ..() - -/mob/dead/observer/Login() - . = ..() - var/datum/preferences/preferences = client?.prefs - var/soulcatcher_action_given - - if(preferences) - soulcatcher_action_given = preferences.read_preference(/datum/preference/toggle/soulcatcher_join_action) - - if(!soulcatcher_action_given) - return - - if(locate(/datum/action/innate/join_soulcatcher) in actions) - return - - var/datum/action/innate/join_soulcatcher/new_join_action = new(src) - new_join_action.Grant(src) diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_mob.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_mob.dm index 5127fdfe8d46d3..7ac643b363dd67 100644 --- a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_mob.dm +++ b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_mob.dm @@ -1,92 +1,12 @@ /mob/living/soulcatcher_soul - /// What does our soul look like? - var/soul_desc = "It's a soul." - /// What are the ooc notes for the soul? - var/ooc_notes = "" - - /// Assuming we died inside of the round? What is our previous body? - var/datum/weakref/previous_body - /// What is the weakref of the soulcatcher room are we currently in? - var/datum/weakref/current_room - - /// Is the soul able to see things in the outside world? - var/outside_sight = TRUE - /// Is the soul able to hear things from the outside world? - var/outside_hearing = TRUE - /// Is the soul able to "see" things from inside of the soulcatcher? - var/internal_sight = TRUE - /// Is the soul able to "hear" things from inside of the soulcatcher? - var/internal_hearing = TRUE - /// Is the soul able to emote inside the soulcatcher room? - var/able_to_emote = TRUE - /// Is the soul able to speak inside the soulcatcher room? - var/able_to_speak = TRUE - /// Is the soul able to change their own name? - var/able_to_rename = TRUE - /// Is the soul able to speak as the object it is inside? - var/able_to_speak_as_container = TRUE - /// Is the soul able to emote as the object it is inside? - var/able_to_emote_as_container = TRUE - /// Are emote's and Say's done through the container the mob is in? - var/communicating_externally = FALSE - /// Is the soul able to leave the soulcatcher? var/able_to_leave = TRUE /// Did the soul live within the round? This is checked if we want to transfer the soul to another body. var/round_participant = FALSE /// Does the body need scanned? var/body_scan_needed = FALSE - -/mob/living/soulcatcher_soul/Initialize(mapload) - . = ..() - if(!outside_sight) - become_blind(NO_EYES) - - if(!outside_hearing) - ADD_TRAIT(src, TRAIT_DEAF, INNATE_TRAIT) - - var/datum/action/innate/leave_soulcatcher/leave_action = new(src) - leave_action.Grant(src) - - var/datum/action/innate/soulcatcher_user/soulcatcher_action = new(src) - soulcatcher_action.Grant(src) - var/datum/component/soulcatcher_user/user_component = AddComponent(/datum/component/soulcatcher_user) - soulcatcher_action.soulcatcher_user_component = WEAKREF(user_component) - -/// Toggles whether or not the soul inside the soulcatcher can see the outside world. Returns the state of the `outside_sight` variable. -/mob/living/soulcatcher_soul/proc/toggle_sight() - outside_sight = !outside_sight - if(outside_sight) - cure_blind(NO_EYES) - else - become_blind(NO_EYES) - - return outside_sight - -/// Toggles whether or not the soul inside the soulcatcher can see the outside world. Returns the state of the `outside_hearing` variable. -/mob/living/soulcatcher_soul/proc/toggle_hearing() - outside_hearing = !outside_hearing - if(outside_hearing) - REMOVE_TRAIT(src, TRAIT_DEAF, INNATE_TRAIT) - else - ADD_TRAIT(src, TRAIT_DEAF, INNATE_TRAIT) - - return outside_hearing - -/// Changes the soul's name based off `new_name`. Returns `TRUE` if the name has been changed, otherwise returns `FALSE`. -/mob/living/soulcatcher_soul/proc/change_name(new_name) - if(!new_name || (round_participant && body_scan_needed)) - return FALSE - - name = new_name - return TRUE - -/// Attempts to reset the soul's name to it's name in prefs. Returns `TRUE` if the name is reset, otherwise returns `FALSE`. -/mob/living/soulcatcher_soul/proc/reset_name() - if(!mind?.name || change_name(mind.name)) - return FALSE - - return TRUE + /// Assuming we died inside of the round? What is our previous body? + var/datum/weakref/previous_body /// Checks if the mob wants to leave the soulcatcher. If they do and are able to leave, they are booted out. /mob/living/soulcatcher_soul/verb/leave_soulcatcher() @@ -113,35 +33,25 @@ /mob/living/soulcatcher_soul/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced, filterproof, message_range, datum/saymode/saymode) message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) - if(!message || message == "") - return - - if((!able_to_speak && !communicating_externally) || (!able_to_speak_as_container && communicating_externally)) - to_chat(src, span_warning("You are unable to speak!")) + if(!message) return FALSE - var/datum/soulcatcher_room/room = current_room.resolve() - if(!room) + var/datum/component/carrier_user/soul_component = GetComponent(/datum/component/carrier_user) + if(!soul_component) return FALSE - room.send_message(message, src, FALSE) - return TRUE + return soul_component.say(message) /mob/living/soulcatcher_soul/me_verb(message as text) message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) if(!message) return FALSE - if((!able_to_emote && !communicating_externally) || (!able_to_emote_as_container && communicating_externally)) - to_chat(src, span_warning("You are unable to emote!")) - return FALSE - - var/datum/soulcatcher_room/room = current_room.resolve() - if(!room) + var/datum/component/carrier_user/soul_component = GetComponent(/datum/component/carrier_user) + if(!soul_component) return FALSE - room.send_message(message, src, TRUE) - return TRUE + return soul_component.me_verb(message) /mob/living/soulcatcher_soul/subtle() set hidden = TRUE @@ -180,46 +90,30 @@ /mob/living/soulcatcher_soul/Destroy() log_message("[key_name(src)] has exited a soulcatcher.", LOG_GAME) - if(current_room) - var/datum/soulcatcher_room/room = current_room.resolve() + var/datum/component/carrier_user/soul_component = GetComponent(/datum/component/carrier_user) + if(soul_component && soul_component.current_room) + var/datum/carrier_room/room = soul_component.current_room.resolve() if(room) - room.current_souls -= src + room.current_mobs -= src - current_room = null + soul_component.current_room = null return ..() /datum/emote/living mob_type_blacklist_typecache = list(/mob/living/brain, /mob/living/soulcatcher_soul) -/datum/action/innate/leave_soulcatcher +/datum/action/innate/leave_carrier name = "Leave Soulcatcher" background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' background_icon_state = "android" button_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_nif.dmi' button_icon_state = "soulcatcher_exit" -/datum/action/innate/leave_soulcatcher/Activate() +/datum/action/innate/leave_carrier/Activate() . = ..() var/mob/living/soulcatcher_soul/parent_soul = owner if(!parent_soul) return FALSE parent_soul.leave_soulcatcher() - -/datum/action/innate/soulcatcher_user - name = "Soulcatcher" - background_icon = 'modular_skyrat/master_files/icons/mob/actions/action_backgrounds.dmi' - background_icon_state = "android" - button_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_nif.dmi' - button_icon_state = "soulcatcher" - /// What soulcatcher user component are we bringing up the menu for? - var/datum/weakref/soulcatcher_user_component - -/datum/action/innate/soulcatcher_user/Activate() - . = ..() - var/datum/component/soulcatcher_user/user_component = soulcatcher_user_component.resolve() - if(!user_component) - return FALSE - - user_component.ui_interact(owner) diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_tgui.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_tgui.dm deleted file mode 100644 index 6233f906fb342a..00000000000000 --- a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_tgui.dm +++ /dev/null @@ -1,343 +0,0 @@ -/datum/component/soulcatcher/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(usr, src, ui) - - if(!ui) - ui = new(usr, src, "Soulcatcher", name) - ui.open() - -/datum/component/soulcatcher/nifsoft/ui_state(mob/user) - return GLOB.conscious_state - -/datum/component/soulcatcher/ui_data(mob/user) - var/list/data = list() - - data["ghost_joinable"] = ghost_joinable - data["require_approval"] = require_approval - data["theme"] = ui_theme - data["communicate_as_parent"] = communicate_as_parent - data["current_soul_count"] = length(get_current_souls()) - data["max_souls"] = max_souls - data["removable"] = removable - - data["current_rooms"] = list() - for(var/datum/soulcatcher_room/room in soulcatcher_rooms) - var/currently_targeted = (room == targeted_soulcatcher_room) - - var/list/room_data = list( - "name" = html_decode(room.name), - "description" = html_decode(room.room_description), - "reference" = REF(room), - "joinable" = room.joinable, - "color" = room.room_color, - "currently_targeted" = currently_targeted, - ) - - for(var/mob/living/soulcatcher_soul/soul in room.current_souls) - var/list/soul_list = list( - "name" = soul.name, - "description" = soul.soul_desc, - "reference" = REF(soul), - "internal_hearing" = soul.internal_hearing, - "internal_sight" = soul.internal_sight, - "outside_hearing" = soul.outside_hearing, - "outside_sight" = soul.outside_sight, - "able_to_emote" = soul.able_to_emote, - "able_to_speak" = soul.able_to_speak, - "able_to_rename" = soul.able_to_rename, - "ooc_notes" = soul.ooc_notes, - "scan_needed" = soul.body_scan_needed, - "able_to_speak_as_container" = soul.able_to_speak_as_container, - "able_to_emote_as_container" = soul.able_to_emote_as_container, - ) - room_data["souls"] += list(soul_list) - - data["current_rooms"] += list(room_data) - - return data - -/datum/component/soulcatcher/ui_static_data(mob/user) - var/list/data = list() - - data["current_vessel"] = parent - - return data - -/datum/component/soulcatcher/ui_act(action, list/params) - . = ..() - if(.) - return - - var/datum/soulcatcher_room/target_room - if(params["room_ref"]) - target_room = locate(params["room_ref"]) in soulcatcher_rooms - if(!target_room) - return FALSE - - var/mob/living/soulcatcher_soul/target_soul - if(params["target_soul"]) - target_soul = locate(params["target_soul"]) in target_room.current_souls - if(!target_soul) - return FALSE - - switch(action) - if("delete_room") - if(length(soulcatcher_rooms) <= 1) - return FALSE - - soulcatcher_rooms -= target_room - targeted_soulcatcher_room = soulcatcher_rooms[1] - qdel(target_room) - return TRUE - - if("change_targeted_room") - targeted_soulcatcher_room = target_room - return TRUE - - if("create_room") - create_room() - return TRUE - - if("rename_room") - var/new_room_name = tgui_input_text(usr,"Choose a new name for the room", name, target_room.name) - if(!new_room_name) - return FALSE - - target_room.name = new_room_name - return TRUE - - if("redescribe_room") - var/new_room_desc = tgui_input_text(usr,"Choose a new description for the room", name, target_room.room_description, multiline = TRUE) - if(!new_room_desc) - return FALSE - - target_room.room_description = new_room_desc - return TRUE - - if("toggle_joinable_room") - target_room.joinable = !target_room.joinable - return TRUE - - if("toggle_joinable") - ghost_joinable = !ghost_joinable - return TRUE - - if("toggle_approval") - require_approval = !require_approval - return TRUE - - if("modify_name") - var/new_name = tgui_input_text(usr,"Choose a new name to send messages as", name, target_room.outside_voice, multiline = TRUE) - if(!new_name) - return FALSE - - target_room.outside_voice = new_name - return TRUE - - if("remove_soul") - target_room.remove_soul(target_soul) - return TRUE - - if("transfer_soul") - var/list/available_rooms = soulcatcher_rooms.Copy() - available_rooms -= target_room - - if(ishuman(usr)) - var/mob/living/carbon/human/human_user = usr - var/datum/nifsoft/soulcatcher/soulcatcher_nifsoft = human_user.find_nifsoft(/datum/nifsoft/soulcatcher) - if(soulcatcher_nifsoft && (parent != soulcatcher_nifsoft.parent_nif.resolve())) - var/datum/component/soulcatcher/nifsoft_soulcatcher = soulcatcher_nifsoft.linked_soulcatcher.resolve() - if(istype(nifsoft_soulcatcher)) - available_rooms.Add(nifsoft_soulcatcher.soulcatcher_rooms) - - for(var/obj/item/held_item in human_user.held_items) - if(parent == held_item) - continue - - var/datum/component/soulcatcher/soulcatcher_component = held_item.GetComponent(/datum/component/soulcatcher) - if(!soulcatcher_component || !soulcatcher_component.check_for_vacancy()) - continue - - for(var/datum/soulcatcher_room/room in soulcatcher_component.soulcatcher_rooms) - available_rooms += room - - var/datum/soulcatcher_room/transfer_room = tgui_input_list(usr, "Choose a room to transfer to", name, available_rooms) - if(!(transfer_room in available_rooms)) - return FALSE - - target_room.transfer_soul(target_soul, transfer_room) - return TRUE - - if("change_room_color") - var/new_room_color = input(usr, "", "Choose Color", SOULCATCHER_DEFAULT_COLOR) as color - if(!new_room_color) - return FALSE - - target_room.room_color = new_room_color - - if("toggle_soul_outside_sense") - if(params["sense_to_change"] == "hearing") - target_soul.toggle_hearing() - else - target_soul.toggle_sight() - - return TRUE - - if("toggle_soul_sense") - if(params["sense_to_change"] == "hearing") - target_soul.internal_hearing = !target_soul.internal_hearing - else - target_soul.internal_sight = !target_soul.internal_sight - - return TRUE - - if("toggle_soul_communication") - if(params["communication_type"] == "emote") - target_soul.able_to_emote = !target_soul.able_to_emote - else - target_soul.able_to_speak = !target_soul.able_to_speak - - return TRUE - - if("toggle_soul_external_communication") - if(params["communication_type"] == "emote") - target_soul.able_to_emote_as_container = !target_soul.able_to_emote_as_container - else - target_soul.able_to_speak_as_container = !target_soul.able_to_speak_as_container - - return TRUE - - if("toggle_soul_renaming") - target_soul.able_to_rename = !target_soul.able_to_rename - return TRUE - - if("change_name") - var/new_name = tgui_input_text(usr, "Enter a new name for [target_soul]", "Soulcatcher", target_soul) - if(!new_name) - return FALSE - - target_soul.change_name(new_name) - return TRUE - - if("reset_name") - if(tgui_alert(usr, "Do you wish to reset [target_soul]'s name to default?", "Soulcatcher", list("Yes", "No")) != "Yes") - return FALSE - - target_soul.reset_name() - - if("send_message") - var/message_to_send = "" - var/emote = params["emote"] - var/message_sender = target_room.outside_voice - if(params["narration"]) - message_sender = FALSE - - message_to_send = tgui_input_text(usr, "Input the message you want to send", name, multiline = TRUE) - - if(!message_to_send) - return FALSE - - target_room.send_message(message_to_send, message_sender, emote) - return TRUE - - if("delete_self") - if(tgui_alert(usr, "Are you sure you want to detach the soulcatcher?", parent, list("Yes", "No")) != "Yes") - return FALSE - - remove_self() - return TRUE - -/datum/component/soulcatcher_user/New() - . = ..() - var/mob/living/soulcatcher_soul/parent_soul = parent - if(!istype(parent_soul)) - return COMPONENT_INCOMPATIBLE - - return TRUE - -/datum/component/soulcatcher_user/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(usr, src, ui) - if(!ui) - ui = new(usr, src, "SoulcatcherUser") - ui.open() - -/datum/component/soulcatcher_user/ui_state(mob/user) - return GLOB.conscious_state - -/datum/component/soulcatcher_user/ui_data(mob/user) - var/list/data = list() - - var/mob/living/soulcatcher_soul/user_soul = parent - if(!istype(user_soul)) - return FALSE //uhoh - - data["user_data"] = list( - "name" = user_soul.name, - "description" = user_soul.soul_desc, - "reference" = REF(user_soul), - "internal_hearing" = user_soul.internal_hearing, - "internal_sight" = user_soul.internal_sight, - "outside_hearing" = user_soul.outside_hearing, - "outside_sight" = user_soul.outside_sight, - "able_to_emote" = user_soul.able_to_emote, - "able_to_speak" = user_soul.able_to_speak, - "able_to_rename" = user_soul.able_to_rename, - "able_to_speak_as_container" = user_soul.able_to_speak_as_container, - "able_to_emote_as_container" = user_soul.able_to_emote_as_container, - "communicating_externally" = user_soul.communicating_externally, - "ooc_notes" = user_soul.ooc_notes, - "scan_needed" = user_soul.body_scan_needed, - ) - - var/datum/soulcatcher_room/current_room = user_soul.current_room.resolve() - data["current_room"] = list( - "name" = html_decode(current_room.name), - "description" = html_decode(current_room.room_description), - "reference" = REF(current_room), - "color" = current_room.room_color, - "owner" = current_room.outside_voice, - ) - - var/datum/component/soulcatcher/master_soulcatcher = current_room.master_soulcatcher.resolve() - data["communicate_as_parent"] = master_soulcatcher.communicate_as_parent - - for(var/mob/living/soulcatcher_soul/soul in current_room.current_souls) - if(soul == user_soul) - continue - - var/list/soul_list = list( - "name" = soul.name, - "description" = soul.soul_desc, - "ooc_notes" = soul.ooc_notes, - "reference" = REF(soul), - ) - data["souls"] += list(soul_list) - - return data - -/datum/component/soulcatcher_user/ui_act(action, list/params) - . = ..() - if(.) - return - - var/mob/living/soulcatcher_soul/user_soul = parent - if(!istype(user_soul)) - return FALSE - - switch(action) - if("change_name") - var/new_name = tgui_input_text(usr, "Enter a new name", "Soulcatcher", user_soul.name) - if(!new_name) - return FALSE - - user_soul.change_name(new_name) - return TRUE - - if("reset_name") - if(tgui_alert(usr, "Do you wish to reset your name to default?", "Soulcatcher", list("Yes", "No")) != "Yes") - return FALSE - - user_soul.reset_name() - - if("toggle_external_communication") - user_soul.communicating_externally = !user_soul.communicating_externally - return TRUE diff --git a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_verbs.dm b/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_verbs.dm deleted file mode 100644 index b251a03a3770d3..00000000000000 --- a/modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_verbs.dm +++ /dev/null @@ -1,58 +0,0 @@ -/// Prompts the parent mob to send a say message to the soulcatcher. Returns False if no soulcatcher or message could be found. -/mob/living/proc/soulcatcher_say() - set name = "Soul Say" - set category = "IC" - set desc = "Send a Say message to your currently targeted soulcatcher room." - var/datum/component/soulcatcher/target_soulcatcher = find_soulcatcher() - if(!target_soulcatcher || !target_soulcatcher.targeted_soulcatcher_room) - return FALSE - - var/message_to_send = tgui_input_text(usr, "Input the message you want to send", "Soulcatcher", multiline = TRUE) - if(!message_to_send) - return FALSE - - target_soulcatcher.targeted_soulcatcher_room.send_message(message_to_send, target_soulcatcher.targeted_soulcatcher_room.outside_voice) - return TRUE - -/// Prompts the parent mob to send a emote to the soulcatcher. Returns False if no soulcatcher or emote could be found. -/mob/living/proc/soulcatcher_emote() - set name = "Soul Me" - set category = "IC" - set desc = "Send a emote to your currently targeted soulcatcher room." - var/datum/component/soulcatcher/target_soulcatcher = find_soulcatcher() - if(!target_soulcatcher || !target_soulcatcher.targeted_soulcatcher_room) - return FALSE - - var/message_to_send = tgui_input_text(usr, "Input the emote you want to send", "Soulcatcher", multiline = TRUE) - if(!message_to_send) - return FALSE - - target_soulcatcher.targeted_soulcatcher_room.send_message(message_to_send, target_soulcatcher.targeted_soulcatcher_room.outside_voice, TRUE) - return TRUE - -/// Attempts to find and return the soulcatcher the parent mob is currently using. If none can be found, returns `FALSE` -/mob/living/proc/find_soulcatcher() - var/obj/item/soulcatcher_holder/soul_holder = locate(/obj/item/soulcatcher_holder) in contents - if(!soul_holder) - return FALSE - - var/datum/component/soulcatcher/target_soulcatcher = soul_holder.GetComponent(/datum/component/soulcatcher) - if(!target_soulcatcher) - return FALSE - - return target_soulcatcher - -/mob/living/carbon/human/find_soulcatcher() - . = ..() - if(.) // No need to go searching further if we've already found one. - return . - - var/datum/nifsoft/soulcatcher/souclatcher_nifsoft = find_nifsoft(/datum/nifsoft/soulcatcher) - if(!souclatcher_nifsoft) - return FALSE - - var/datum/component/soulcatcher/target_soulcatcher = souclatcher_nifsoft.linked_soulcatcher.resolve() - if(!target_soulcatcher) - return FALSE - - return target_soulcatcher diff --git a/modular_skyrat/modules/soulstone_changes/readme.md b/modular_skyrat/modules/soulstone_changes/readme.md index 0185eb05827f68..5f3e05acf5eaf0 100644 --- a/modular_skyrat/modules/soulstone_changes/readme.md +++ b/modular_skyrat/modules/soulstone_changes/readme.md @@ -13,7 +13,7 @@ Makes soulstone no longer permakill people, and makes construct's souls return t ### TG Proc/File Changes -- code/modules/antagonist/wizard/equipment/soulstone.dm > /obj/item/soulstone/proc/transfer_soul() +- code/modules/antagonist/wizard/equipment/soulstone.dm > /obj/item/soulstone/proc/transfer_mob() - code/modules/antagonist/wizard/equipment/soulstone.dm > /obj/item/soulstone/proc/init_shade() - code/modules/antagonist/wizard/equipment/soulstone.dm > /obj/item/soulstone/proc/getCultGhost() diff --git a/tgstation.dme b/tgstation.dme index fb091b404151e0..aae8926b5764da 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6812,6 +6812,10 @@ #include "modular_skyrat\modules\cargo\code\items\gbp_punchcard.dm" #include "modular_skyrat\modules\cargo\code\items\improvedRCD.dm" #include "modular_skyrat\modules\cargo_teleporter\code\cargo_teleporter.dm" +#include "modular_skyrat\modules\carriers\code\carrier_component.dm" +#include "modular_skyrat\modules\carriers\code\carrier_tgui.dm" +#include "modular_skyrat\modules\carriers\code\carrier_user_component.dm" +#include "modular_skyrat\modules\carriers\code\carrier_verbs.dm" #include "modular_skyrat\modules\cell_component\code\cell_component.dm" #include "modular_skyrat\modules\cellguns\code\cellgun_cells.dm" #include "modular_skyrat\modules\cellguns\code\cellguns.dm" @@ -7568,12 +7572,11 @@ #include "modular_skyrat\modules\modular_implants\code\nifsofts\soulcatcher.dm" #include "modular_skyrat\modules\modular_implants\code\nifsofts\base_types\action_granter.dm" #include "modular_skyrat\modules\modular_implants\code\soulcatcher\attachable_soulcatcher.dm" +#include "modular_skyrat\modules\modular_implants\code\soulcatcher\ghost.dm" #include "modular_skyrat\modules\modular_implants\code\soulcatcher\handheld_soulcatcher.dm" #include "modular_skyrat\modules\modular_implants\code\soulcatcher\soulcatcher_body_component.dm" #include "modular_skyrat\modules\modular_implants\code\soulcatcher\soulcatcher_component.dm" #include "modular_skyrat\modules\modular_implants\code\soulcatcher\soulcatcher_mob.dm" -#include "modular_skyrat\modules\modular_implants\code\soulcatcher\soulcatcher_tgui.dm" -#include "modular_skyrat\modules\modular_implants\code\soulcatcher\soulcatcher_verbs.dm" #include "modular_skyrat\modules\modular_items\code\bags.dm" #include "modular_skyrat\modules\modular_items\code\cash.dm" #include "modular_skyrat\modules\modular_items\code\ciggies.dm" diff --git a/tgui/packages/tgui/interfaces/Soulcatcher.jsx b/tgui/packages/tgui/interfaces/Soulcatcher.jsx index f797d5b2225136..30706467ced1ab 100644 --- a/tgui/packages/tgui/interfaces/Soulcatcher.jsx +++ b/tgui/packages/tgui/interfaces/Soulcatcher.jsx @@ -19,11 +19,12 @@ export const Soulcatcher = (props) => { require_approval, current_rooms = [], ghost_joinable, - current_soul_count, - max_souls, + current_mob_count, + max_mobs, removable, communicate_as_parent, theme, + carrier_targeted, } = data; return ( @@ -156,23 +157,23 @@ export const Soulcatcher = (props) => { - {room.souls.map((soul) => ( - + {room.souls.map((mob) => ( + - {soul.scan_needed ? ( + {mob.scan_needed ? ( <> ) : ( <> {communicate_as_parent ? ( @@ -321,21 +322,22 @@ export const Soulcatcher = (props) => { @@ -343,21 +345,22 @@ export const Soulcatcher = (props) => { @@ -368,17 +371,18 @@ export const Soulcatcher = (props) => { )} @@ -388,8 +392,8 @@ export const Soulcatcher = (props) => { icon="eject" color="red" onClick={() => - act('remove_soul', { - target_soul: soul.reference, + act('remove_mob', { + target_mob: mob.reference, room_ref: room.reference, }) } @@ -406,16 +410,16 @@ export const Soulcatcher = (props) => { )} ))} - {max_souls ? ( + {max_mobs ? (
- Remaining soul capacity: {max_souls - current_soul_count} + Remaining mob capacity: {max_mobs - current_mob_count}
) : ( @@ -445,6 +449,14 @@ export const Soulcatcher = (props) => { > Approval is {require_approval ? '' : 'not'} required to join + {removable ? (