mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-14 19:51:59 +00:00
[Like 99% Modular] Soulcatchers (#20576)
* wew * Update soulcatcher.dm * Update soulcatcher.dm * Update soulcatcher.dm * Update soulcatcher.dm * Update soulcatcher.dm * file moving * tgui * tgui_work * wew * more tweaks * more stuff * fancy * nifsoft * mob * whoopsie * man * mind transfer * ooc and flavor text * Update soulcatcher_component.dm * Update soulcatcher_mob.dm * persistence * hud * a little bit of admin logging, as a treat * Update soulcatcher_component.dm * Update devices.dmi * research * some things I missed. * whooops * Update soulcatcher_component.dm * Update soulcatcher_items.dm * Update soulcatcher_component.dm * lore * Update soulcatcher_component.dm * Update soulcatcher.dm * Apply suggestions from code review Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * Update soulcatcher_items.dm * Apply suggestions from code review Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Co-authored-by: nikothedude <59709059+nikothedude@users.noreply.github.com> * Update soulcatcher_component.dm * Update tgui/packages/tgui/interfaces/Soulcatcher.js Co-authored-by: nikothedude <59709059+nikothedude@users.noreply.github.com> * >w> * moves the body component * Update tgstation.dme * renaming * file move * oh wow, I didn't need to make this non modular * signals * Update handheld_soulcatcher.dm * more touches * Update soulcatcher_mob.dm * code suggestions * Update handheld_soulcatcher.dm * Update handheld_soulcatcher.dm * Apply suggestions from code review Co-authored-by: nikothedude <59709059+nikothedude@users.noreply.github.com> * Update soulcatcher_component.dm * a * more signals :3 * This is a surprise tool that will be added back later. * better UX * Update soulcatcher.dm * QoL * Spamming reduction. * Update nifs.dm * Update handheld_soulcatcher.dm * Update soulcatcher_component.dm * Update soulcatcher_component.dm * Update modular_skyrat/modules/modular_implants/code/soulcatcher/soulcatcher_component.dm Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * Update soulcatcher_component.dm * Update soulcatcher.dm * no more empty bodies * 1984 * whoopsie * transfer fix * Update mobs.dm * more logging * Update soulcatcher_component.dm * QoL --------- Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Co-authored-by: nikothedude <59709059+nikothedude@users.noreply.github.com>
This commit is contained in:
@@ -82,3 +82,9 @@
|
|||||||
|
|
||||||
/// Engineering Override Access manual toggle
|
/// Engineering Override Access manual toggle
|
||||||
#define COMSIG_GLOB_FORCE_ENG_OVERRIDE "force_engineering_override"
|
#define COMSIG_GLOB_FORCE_ENG_OVERRIDE "force_engineering_override"
|
||||||
|
|
||||||
|
/// Whenever we need to check if a mob is currently inside of soulcatcher.
|
||||||
|
#define COMSIG_SOULCATCHER_CHECK_SOUL "soulcatcher_check_soul"
|
||||||
|
|
||||||
|
/// Whenever we need to get the soul of the mob inside of the soulcatcher.
|
||||||
|
#define COMSIG_SOULCATCHER_SCAN_BODY "soulcatcher_scan_body"
|
||||||
|
|||||||
@@ -606,6 +606,10 @@ GLOBAL_LIST_EMPTY(species_list)
|
|||||||
moblist += mob_to_sort
|
moblist += mob_to_sort
|
||||||
for(var/mob/living/basic/mob_to_sort in sortmob)
|
for(var/mob/living/basic/mob_to_sort in sortmob)
|
||||||
moblist += mob_to_sort
|
moblist += mob_to_sort
|
||||||
|
// SKYRAT EDIT START - SOULCATCHERS
|
||||||
|
for(var/mob/living/soulcatcher_soul/mob_to_sort in sortmob)
|
||||||
|
moblist += mob_to_sort
|
||||||
|
// SKYRAT EDIT END - SOULCATCHERS
|
||||||
return moblist
|
return moblist
|
||||||
|
|
||||||
///returns a mob type controlled by a specified ckey
|
///returns a mob type controlled by a specified ckey
|
||||||
|
|||||||
@@ -266,6 +266,7 @@
|
|||||||
/obj/item/surgicaldrill,
|
/obj/item/surgicaldrill,
|
||||||
/obj/item/tank/internals/emergency_oxygen,
|
/obj/item/tank/internals/emergency_oxygen,
|
||||||
/obj/item/weaponcell/medical, //SKYRAT EDIT MEDIGUNS
|
/obj/item/weaponcell/medical, //SKYRAT EDIT MEDIGUNS
|
||||||
|
/obj/item/handheld_soulcatcher, // SKYRAT EDIT SOULCATCHERS
|
||||||
/obj/item/wrench/medical,
|
/obj/item/wrench/medical,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
/* EMOTE DATUMS */
|
/* EMOTE DATUMS */
|
||||||
/datum/emote/living
|
/datum/emote/living
|
||||||
mob_type_allowed_typecache = /mob/living
|
mob_type_allowed_typecache = /mob/living
|
||||||
mob_type_blacklist_typecache = list(/mob/living/brain)
|
mob_type_blacklist_typecache = list(/mob/living/brain) //SKYRAT EDIT - OVERWIRTTEN BY `modular_skyrat\modules\modular_implants\code\soulcatcher\soulcatcher_mob.dm`
|
||||||
|
|
||||||
/datum/emote/living/blush
|
/datum/emote/living/blush
|
||||||
key = "blush"
|
key = "blush"
|
||||||
|
|||||||
@@ -969,6 +969,7 @@
|
|||||||
prereq_ids = list("biotech", "datatheory")
|
prereq_ids = list("biotech", "datatheory")
|
||||||
design_ids = list(
|
design_ids = list(
|
||||||
"skill_station",
|
"skill_station",
|
||||||
|
"soulcatcher_device", //SKYRAT EDIT SOULCATCHERS
|
||||||
)
|
)
|
||||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||||
|
|
||||||
|
|||||||
@@ -48,3 +48,9 @@
|
|||||||
name = "medical labcoat"
|
name = "medical labcoat"
|
||||||
desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder."
|
desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder."
|
||||||
icon_state = "labcoat_gen"
|
icon_state = "labcoat_gen"
|
||||||
|
|
||||||
|
/obj/item/clothing/suit/toggle/labcoat/Initialize(mapload)
|
||||||
|
. = ..()
|
||||||
|
allowed += list(
|
||||||
|
/obj/item/handheld_soulcatcher,
|
||||||
|
)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 543 B |
@@ -18,3 +18,13 @@
|
|||||||
category = list(RND_CATEGORY_EQUIPMENT)
|
category = list(RND_CATEGORY_EQUIPMENT)
|
||||||
departmental_flags = DEPARTMENT_BITFLAG_CARGO
|
departmental_flags = DEPARTMENT_BITFLAG_CARGO
|
||||||
|
|
||||||
|
/datum/design/soulcatcher_device
|
||||||
|
name = "Evoker-Type RSD"
|
||||||
|
desc = "An RSD instrument that lets the user pull the consciousness from a body and store it virtually."
|
||||||
|
id = "soulcatcher_device"
|
||||||
|
build_type = PROTOLATHE | AWAY_LATHE
|
||||||
|
build_path = /obj/item/handheld_soulcatcher
|
||||||
|
materials = list(/datum/material/iron = 6000, /datum/material/silver = 2000, /datum/material/bluespace = 2000)
|
||||||
|
category = list(RND_CATEGORY_EQUIPMENT)
|
||||||
|
departmental_flags = DEPARTMENT_BITFLAG_MEDICAL
|
||||||
|
|
||||||
|
|||||||
@@ -503,6 +503,7 @@
|
|||||||
new /obj/item/disk/nifsoft_uploader/summoner(src)
|
new /obj/item/disk/nifsoft_uploader/summoner(src)
|
||||||
new /obj/item/disk/nifsoft_uploader/money_sense(src)
|
new /obj/item/disk/nifsoft_uploader/money_sense(src)
|
||||||
new /obj/item/disk/nifsoft_uploader/dorms(src)
|
new /obj/item/disk/nifsoft_uploader/dorms(src)
|
||||||
|
new /obj/item/disk/nifsoft_uploader/soulcatcher(src)
|
||||||
|
|
||||||
#undef NIF_CALIBRATION_STAGE_1
|
#undef NIF_CALIBRATION_STAGE_1
|
||||||
#undef NIF_CALIBRATION_STAGE_1_END
|
#undef NIF_CALIBRATION_STAGE_1_END
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ GLOBAL_LIST_INIT(purchasable_nifsofts, list(
|
|||||||
/datum/nifsoft/shapeshifter,
|
/datum/nifsoft/shapeshifter,
|
||||||
/datum/nifsoft/summoner/dorms,
|
/datum/nifsoft/summoner/dorms,
|
||||||
/datum/nifsoft/soul_poem,
|
/datum/nifsoft/soul_poem,
|
||||||
|
/datum/nifsoft/soulcatcher,
|
||||||
))
|
))
|
||||||
|
|
||||||
/datum/computer_file/program/nifsoft_downloader
|
/datum/computer_file/program/nifsoft_downloader
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/obj/item/disk/nifsoft_uploader/soul_poem
|
/obj/item/disk/nifsoft_uploader/soul_poem
|
||||||
name = "Automatic Appraisal"
|
name = "Soul Poem"
|
||||||
loaded_nifsoft = /datum/nifsoft/soul_poem
|
loaded_nifsoft = /datum/nifsoft/soul_poem
|
||||||
|
|
||||||
//Modular Persistence variables for the soul_poem NIFSoft
|
//Modular Persistence variables for the soul_poem NIFSoft
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/obj/item/disk/nifsoft_uploader/soulcatcher
|
||||||
|
name = "Soulcatcher"
|
||||||
|
loaded_nifsoft = /datum/nifsoft/soulcatcher
|
||||||
|
|
||||||
|
/datum/nifsoft/soulcatcher
|
||||||
|
name = "Soulcatcher"
|
||||||
|
program_desc = "The 'Soulcatcher' coreware is a near-complete upgrade of the nanomachine systems in a NIF, meant for one purpose; supposedly, channeling the dead. This upgrade, in truth, functions as a Resonance Simulation Device; an RSD for short, an instrument capable of hosting someone's consciousness, context or otherwise. 'Resonance', a term for the specific pattern of neural activity that gives way to someone's consciousness, was discovered in the early 2500s by researchers Yun-Seo Jin and Kamakshi Padmanabhan, coining what is now called 'Jin-Padmanabhan Resonance,' or 'JP/Soul Resonance.' This 'Resonance' gives off a sophont's consciousness, their sense of continuation, and their 'I am me.' This Resonance can vary in structure and 'strength' from person to person, and even change over someone's life. When the brain of a sophont undergoes death and stops neural activity, then Resonance dissipates entirely and lingering consciousness becomes essentially an echo, rapidly fading over time.\n\nThe earliest RSDs were massive machines, drawing incredible power and utilizing bleeding-edge, clunky software to 'play' someone's Resonance at 1:1 accuracy with their original brain. However, complications arose that are still being studied. Resonance is replicable and can be re-created artificially; however, like trying to duplicate genetic code, the capture needs to be extremely accurate, and rapidly put into place. Instruments such as RSDs are capable of picking up on lingering consciousness after the end of Resonance, and resuming it through artificial neural activity can give it strength to continue once more. RSDs such as Soulcatchers can only work at such a distance, otherwise running the risk of the Resonance essentially corrupting due to poor signal.\n\nIt is currently impossible to run Resonance in two places at once, because the same Resonance over two places experiences interference; like noise canceling headphones. Slimes and other gestalt consciousnesses can modulate their harmonics to a degree, bearing a partial disconnect and bringing themselves into constructive interference with similar harmonic signatures. A deepscan of the person's brain is necessary to give their consciousness 'context;' running their Resonance and capturing their consciousness alone results in a person with their same original intelligence, but zero memories or identity. These scans rapidly become outdated due to the growth of the brain, and it is prohibitively complex to store them in their entirety.\n\nThe first portable RSD, or Soulcatcher, was developed by the Spider Clan. These were initially designed for the captive interrogation of a person's consciousness without having to worry about the struggling of their body, and for dead or aging members of the mysterious group of orbital shinobi to be able to guide field operatives. These Soulcatchers are the main instrument to play Resonance, but recent advances in medical science have been leading to more. Occasionally, it is known for unusual sources of 'wild' Resonance, called Phantoms, to end up inside of the nearest Soulcatcher, a key finding its own lock; with a wide array of theories as to how these come into existence. Much as how some people intentionally become stable Engrams to achieve digital immortality, such as the witches of the Altspace Coven, it is possible for others to forcibly enter a Soulcatcher and act as a sort of Phantom by hacking their way in."
|
||||||
|
purchase_price = 150 //RP tool
|
||||||
|
persistence = TRUE
|
||||||
|
ui_icon = "ghost"
|
||||||
|
|
||||||
|
/// What is the linked soulcatcher datum used by this NIFSoft?
|
||||||
|
var/datum/weakref/linked_soulcatcher
|
||||||
|
/// 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()
|
||||||
|
|
||||||
|
/datum/nifsoft/soulcatcher/New()
|
||||||
|
. = ..()
|
||||||
|
soulcatcher_action = new
|
||||||
|
soulcatcher_action.Grant(linked_mob)
|
||||||
|
soulcatcher_action.parent_nifsoft = WEAKREF(src)
|
||||||
|
|
||||||
|
var/obj/item/organ/internal/cyberimp/brain/nif/target_nif = parent_nif
|
||||||
|
var/datum/component/soulcatcher/new_soulcatcher = target_nif.AddComponent(/datum/component/soulcatcher/nifsoft)
|
||||||
|
|
||||||
|
for(var/room in saved_soulcatcher_rooms)
|
||||||
|
new_soulcatcher.create_room(room, saved_soulcatcher_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]
|
||||||
|
|
||||||
|
new_soulcatcher.name = "[linked_mob]'s soulcatcher"
|
||||||
|
|
||||||
|
RegisterSignal(new_soulcatcher, COMSIG_PARENT_QDELETING, .proc/no_soulcatcher_component)
|
||||||
|
linked_soulcatcher = WEAKREF(new_soulcatcher)
|
||||||
|
|
||||||
|
/datum/nifsoft/soulcatcher/activate()
|
||||||
|
. = ..()
|
||||||
|
if(!linked_soulcatcher)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/datum/component/soulcatcher/current_soulcatcher = linked_soulcatcher.resolve()
|
||||||
|
if(!current_soulcatcher)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
current_soulcatcher.ui_interact(linked_mob)
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/// If the linked soulcatcher is being deleted we want to set the current linked soulcatcher to `FALSE`
|
||||||
|
/datum/nifsoft/soulcatcher/proc/no_soulcatcher_component()
|
||||||
|
SIGNAL_HANDLER
|
||||||
|
|
||||||
|
linked_soulcatcher = null
|
||||||
|
|
||||||
|
/datum/nifsoft/soulcatcher/Destroy()
|
||||||
|
if(soulcatcher_action)
|
||||||
|
soulcatcher_action.Remove()
|
||||||
|
qdel(soulcatcher_action)
|
||||||
|
|
||||||
|
if(linked_soulcatcher)
|
||||||
|
var/datum/component/soulcatcher/current_soulcatcher = linked_soulcatcher.resolve()
|
||||||
|
if(current_soulcatcher)
|
||||||
|
qdel(current_soulcatcher)
|
||||||
|
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/datum/nifsoft/soulcatcher/load_persistence_data()
|
||||||
|
. = ..()
|
||||||
|
var/datum/modular_persistence/persistence = .
|
||||||
|
if(!persistence)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
saved_soulcatcher_rooms = params2list(persistence.nif_soulcatcher_rooms)
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/datum/nifsoft/soulcatcher/save_persistence_data(datum/modular_persistence/persistence)
|
||||||
|
. = ..()
|
||||||
|
if(!.)
|
||||||
|
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)
|
||||||
|
room_list[room.name] = room.room_description
|
||||||
|
|
||||||
|
persistence.nif_soulcatcher_rooms = list2params(room_list)
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/datum/modular_persistence
|
||||||
|
///A param string containing soulcatcher rooms
|
||||||
|
var/nif_soulcatcher_rooms = ""
|
||||||
|
|
||||||
|
/datum/action/innate/soulcatcher
|
||||||
|
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"
|
||||||
|
/// The weakref of the parent NIFSoft we belong to.
|
||||||
|
var/datum/weakref/parent_nifsoft
|
||||||
|
|
||||||
|
/datum/action/innate/soulcatcher/Activate()
|
||||||
|
. = ..()
|
||||||
|
var/datum/nifsoft/soulcatcher/soulcatcher_nifsoft = parent_nifsoft.resolve()
|
||||||
|
if(!soulcatcher_nifsoft)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
soulcatcher_nifsoft.activate()
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#define RSD_ATTEMPT_COOLDOWN 2 MINUTES
|
||||||
|
|
||||||
|
/obj/item/handheld_soulcatcher
|
||||||
|
name = "\improper Evoker-type RSD"
|
||||||
|
desc = "The Evoker-Type Resonance Simulation Device is a sort of 'Soulcatcher' instrument that's been designated for handheld usage. These RSDs were designed with the Medical field in mind, a tool meant to offer comfort to the temporarily-departed while their bodies are being repaired, healed, or produced. The Evoker is essentially a very specialized handheld NIF, still using the same nanomachinery for the software and hardware. This careful instrument is able to host a virtual space for a great number of Engrams for an essentially indefinite amount of time in an unlimited variety of simulations, even able to transfer them to and from a NIF. However, it's best Medical practice to not lollygag."
|
||||||
|
icon = 'modular_skyrat/modules/modular_implants/icons/obj/devices.dmi'
|
||||||
|
icon_state = "soulcatcher-device"
|
||||||
|
inhand_icon_state = "electronic"
|
||||||
|
worn_icon_state = "electronic"
|
||||||
|
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
|
||||||
|
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
|
||||||
|
slot_flags = ITEM_SLOT_BELT
|
||||||
|
/// What soulcatcher datum is associated with this item?
|
||||||
|
var/datum/component/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)
|
||||||
|
|
||||||
|
/obj/item/handheld_soulcatcher/attack_self(mob/user, modifiers)
|
||||||
|
linked_soulcatcher.ui_interact(user)
|
||||||
|
|
||||||
|
/obj/item/handheld_soulcatcher/New(loc, ...)
|
||||||
|
. = ..()
|
||||||
|
linked_soulcatcher = AddComponent(/datum/component/soulcatcher)
|
||||||
|
linked_soulcatcher.name = "[src] soulcatcher"
|
||||||
|
|
||||||
|
/obj/item/handheld_soulcatcher/Destroy(force)
|
||||||
|
if(linked_soulcatcher)
|
||||||
|
qdel(linked_soulcatcher)
|
||||||
|
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/obj/item/handheld_soulcatcher/attack(mob/living/target_mob, mob/living/user, params)
|
||||||
|
if(!target_mob)
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
if(target_mob.GetComponent(/datum/component/previous_body))
|
||||||
|
linked_soulcatcher.scan_body(target_mob, user)
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
if(!target_mob.mind)
|
||||||
|
to_chat(user, span_warning("You are unable to remove a mind from an empty body."))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(!COOLDOWN_FINISHED(src, rsd_scan_cooldown))
|
||||||
|
var/time_left = round((COOLDOWN_TIMELEFT(src, rsd_scan_cooldown)) / (1 MINUTES), 0.01)
|
||||||
|
to_chat(user, span_warning("You are currently unable to grab the soul of [target_mob], please wait [time_left] minutes before trying again."))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(target_mob.stat == DEAD) //We can temporarily store souls of dead mobs.
|
||||||
|
target_mob.ghostize(TRUE) //Incase they are staying in the body.
|
||||||
|
var/mob/dead/observer/target_ghost = target_mob.get_ghost(TRUE, TRUE)
|
||||||
|
if(!target_ghost)
|
||||||
|
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)
|
||||||
|
if(!target_room)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
SEND_SOUND(target_ghost, 'sound/misc/notice2.ogg')
|
||||||
|
window_flash(target_ghost.client)
|
||||||
|
|
||||||
|
if(tgui_alert(target_ghost, "[user] wants to transfer you to [target_room] inside of a soulcatcher, do you accept?", name, list("Yes", "No"), 30 SECONDS, autofocus = FALSE) != "Yes")
|
||||||
|
to_chat(user, span_warning("[target_mob] doesn't seem to want to enter."))
|
||||||
|
COOLDOWN_START(src, rsd_scan_cooldown, RSD_ATTEMPT_COOLDOWN)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(!target_room.add_soul_from_ghost(target_ghost))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(!target_mob.GetComponent(/datum/component/previous_body))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/turf/source_turf = get_turf(user)
|
||||||
|
log_admin("[key_name(user)] used [src] to put [key_name(target_mob)]'s mind into a soulcatcher at [AREACOORD(source_turf)]")
|
||||||
|
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)
|
||||||
|
if(!target_room)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
SEND_SOUND(target_mob, 'sound/misc/notice2.ogg')
|
||||||
|
window_flash(target_mob.client)
|
||||||
|
|
||||||
|
if((tgui_alert(target_mob, "Do you wish to enter [target_room]? This will remove you from your body until you leave.", name, list("Yes", "No"), 30 SECONDS, FALSE) != "Yes") || (tgui_alert(target_mob, "Are you sure about this?", name, list("Yes", "No"), 30 SECONDS, FALSE) != "Yes"))
|
||||||
|
COOLDOWN_START(src, rsd_scan_cooldown, RSD_ATTEMPT_COOLDOWN)
|
||||||
|
to_chat(user, span_warning("[target_mob] doesn't seem to want to enter."))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(!target_mob.mind)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
target_room.add_soul(target_mob.mind, TRUE)
|
||||||
|
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."))
|
||||||
|
|
||||||
|
if(!target_mob.GetComponent(/datum/component/previous_body))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
linked_soulcatcher.scan_body(target_mob, user)
|
||||||
|
|
||||||
|
var/turf/source_turf = get_turf(user)
|
||||||
|
log_admin("[key_name(user)] used [src] to put [key_name(target_mob)]'s mind into a soulcatcher while they were still alive at [AREACOORD(source_turf)]")
|
||||||
|
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
#undef RSD_ATTEMPT_COOLDOWN
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/// A component that is given to a body when the soul inside is inhabiting a soulcatcher. this is mostly here so that the bodies of souls can be revived.
|
||||||
|
/datum/component/previous_body
|
||||||
|
/// What soulcatcher soul do we need to return to the body?
|
||||||
|
var/datum/weakref/soulcatcher_soul
|
||||||
|
/// Do we want to try and restore the mind when this is destroyed?
|
||||||
|
var/restore_mind = TRUE
|
||||||
|
|
||||||
|
/datum/component/previous_body/Initialize(...)
|
||||||
|
. = ..()
|
||||||
|
if(!ismob(parent))
|
||||||
|
return COMPONENT_INCOMPATIBLE
|
||||||
|
|
||||||
|
RegisterSignal(parent, COMSIG_SOULCATCHER_CHECK_SOUL, .proc/signal_destroy)
|
||||||
|
RegisterSignal(parent, COMSIG_SOULCATCHER_SCAN_BODY, .proc/scan_body)
|
||||||
|
|
||||||
|
/// Destroys the source component through a signal. `mind_restored` controls whether or not the mind will be grabbed upon deletion.
|
||||||
|
/datum/component/previous_body/proc/signal_destroy(mob/source_mob, mind_restored = TRUE)
|
||||||
|
SIGNAL_HANDLER
|
||||||
|
if(!mind_restored)
|
||||||
|
restore_mind = FALSE
|
||||||
|
|
||||||
|
qdel(src)
|
||||||
|
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/// Attempts to scan the soul referenced in the `soulcatcher_soul` variable. Returns TRUE if the soul has been scanned, otherwise returns FALSE
|
||||||
|
/datum/component/previous_body/proc/scan_body(mob/source_mob)
|
||||||
|
SIGNAL_HANDLER
|
||||||
|
|
||||||
|
if(!soulcatcher_soul)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/mob/living/soulcatcher_soul/target_soul = soulcatcher_soul.resolve()
|
||||||
|
if(!target_soul || !target_soul.body_scan_needed)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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.
|
||||||
|
/datum/component/previous_body/Destroy(force, silent)
|
||||||
|
UnregisterSignal(parent, COMSIG_SOULCATCHER_CHECK_SOUL)
|
||||||
|
UnregisterSignal(parent, COMSIG_SOULCATCHER_SCAN_BODY)
|
||||||
|
|
||||||
|
if(restore_mind)
|
||||||
|
var/mob/living/original_body = parent
|
||||||
|
var/mob/living/soulcatcher_soul/soul = soulcatcher_soul.resolve()
|
||||||
|
if(original_body && soul && !original_body.mind)
|
||||||
|
var/datum/mind/mind_to_tranfer = soul.mind
|
||||||
|
if(mind_to_tranfer)
|
||||||
|
mind_to_tranfer.transfer_to(original_body)
|
||||||
|
|
||||||
|
soul.previous_body = FALSE
|
||||||
|
qdel(soul)
|
||||||
|
|
||||||
|
return ..()
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
///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()
|
||||||
|
/// 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
|
||||||
|
|
||||||
|
/datum/component/soulcatcher/New()
|
||||||
|
. = ..()
|
||||||
|
if(!parent)
|
||||||
|
return COMPONENT_INCOMPATIBLE
|
||||||
|
|
||||||
|
create_room()
|
||||||
|
GLOB.soulcatchers += src
|
||||||
|
|
||||||
|
/datum/component/soulcatcher/Destroy(force, ...)
|
||||||
|
GLOB.soulcatchers -= src
|
||||||
|
for(var/datum/soulcatcher_room as anything in soulcatcher_rooms)
|
||||||
|
soulcatcher_rooms -= soulcatcher_room
|
||||||
|
qdel(soulcatcher_room)
|
||||||
|
|
||||||
|
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 = "it's a room")
|
||||||
|
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/organ/internal/cyberimp/brain/nif))
|
||||||
|
var/obj/item/organ/internal/cyberimp/brain/nif/target_nif = parent
|
||||||
|
holder = target_nif.linked_mob
|
||||||
|
|
||||||
|
else if(istype(parent, /obj/item))
|
||||||
|
var/obj/item/parent_item = parent
|
||||||
|
holder = parent_item.loc
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
var/mob/living/soulcatcher_owner = get_current_holder()
|
||||||
|
|
||||||
|
if(!soulcatcher_owner)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
if(!parent_body || !user)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/signal_result = SEND_SIGNAL(parent_body, COMSIG_SOULCATCHER_SCAN_BODY, parent_body)
|
||||||
|
if(!signal_result)
|
||||||
|
to_chat(user, span_warning("[parent_body] has already been scanned!"))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(istype(parent, /obj/item/handheld_soulcatcher))
|
||||||
|
var/obj/item/handheld_soulcatcher/parent_device = parent
|
||||||
|
playsound(parent_device, 'modular_skyrat/modules/modular_implants/sounds/default_good.ogg', 50, FALSE, ignore_walls = FALSE)
|
||||||
|
parent_device.visible_message(span_notice("[parent_device] beeps: [parent_body] is now scanned."))
|
||||||
|
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
/// What is the name of the room?
|
||||||
|
var/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
|
||||||
|
|
||||||
|
/// Attemps to add a ghost to the soulcatcher room.
|
||||||
|
/datum/soulcatcher_room/proc/add_soul_from_ghost(mob/dead/observer/ghost)
|
||||||
|
if(!ghost || !ghost.ckey)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(!ghost.mind)
|
||||||
|
ghost.mind = new /datum/mind(ghost.key)
|
||||||
|
ghost.mind.name = ghost.name
|
||||||
|
ghost.mind.active = TRUE
|
||||||
|
|
||||||
|
if(!add_soul(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)
|
||||||
|
if(!mind_to_add)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/datum/component/soulcatcher/parent_soulcatcher = master_soulcatcher.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/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)
|
||||||
|
|
||||||
|
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("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)]"
|
||||||
|
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
|
||||||
|
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 transfered 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/sender_name = ""
|
||||||
|
if(message_sender)
|
||||||
|
sender_name = "[message_sender] "
|
||||||
|
|
||||||
|
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/first_room_name_word = splittext(name, " ")
|
||||||
|
var/message = ""
|
||||||
|
var/owner_message = ""
|
||||||
|
if(!emote)
|
||||||
|
message = "<font color=[room_color]>\ [soulcatcher_icon] <b>[sender_name]</b>says, \"[message_to_send]\"</font>"
|
||||||
|
owner_message = "<font color=[room_color]>\ <b>([first_room_name_word[1]])</b> [soulcatcher_icon] <b>[sender_name]</b>says, \"[message_to_send]\"</font>"
|
||||||
|
log_say("[sender_name] in [name] soulcatcher room said: [message_to_send]")
|
||||||
|
else
|
||||||
|
message = "<font color=[room_color]>\ [soulcatcher_icon] <b>[sender_name]</b>[message_to_send]</font>"
|
||||||
|
owner_message = "<font color=[room_color]>\ <b>([first_room_name_word[1]])</b> [soulcatcher_icon] <b>[sender_name]</b>[message_to_send]</font>"
|
||||||
|
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 ..()
|
||||||
|
|
||||||
|
/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)
|
||||||
|
continue
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 ..()
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/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 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)
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
|
||||||
|
/// Attemp to leave the soulcatcher.
|
||||||
|
/mob/living/soulcatcher_soul/verb/leave_soulcatcher()
|
||||||
|
set name = "Leave Soulcatcher"
|
||||||
|
set category = "IC"
|
||||||
|
|
||||||
|
if(!able_to_leave)
|
||||||
|
to_chat(src, span_warning("You are unable to leave the soulcatcher."))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(tgui_alert(src, "Are you sure you wish to leave the soulcatcher? IF you had a body, this will return you to your body", "Soulcatcher", list("Yes", "No")) != "Yes")
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
if(tgui_alert(src, "Are you really sure about this?", "Soulcatcher", list("Yes", "No")) != "Yes")
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
qdel(src)
|
||||||
|
|
||||||
|
/mob/living/soulcatcher_soul/ghost()
|
||||||
|
. = ..()
|
||||||
|
qdel(src)
|
||||||
|
|
||||||
|
/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)
|
||||||
|
to_chat(src, span_warning("You are unable to speak!"))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/datum/soulcatcher_room/room = current_room.resolve()
|
||||||
|
if(!room)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
room.send_message(message, src, FALSE)
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/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)
|
||||||
|
to_chat(src, span_warning("You are unable to emote!"))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/datum/soulcatcher_room/room = current_room.resolve()
|
||||||
|
if(!room)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
room.send_message(message, src, TRUE)
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/mob/living/soulcatcher_soul/subtle()
|
||||||
|
set hidden = TRUE
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
/mob/living/soulcatcher_soul/subtler()
|
||||||
|
set hidden = TRUE
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
/mob/living/soulcatcher_soul/whisper_verb()
|
||||||
|
set hidden = TRUE
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
/mob/living/soulcatcher_soul/container_emote()
|
||||||
|
set hidden = TRUE
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
/mob/living/soulcatcher_soul/resist()
|
||||||
|
set hidden = TRUE
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
/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()
|
||||||
|
if(room)
|
||||||
|
room.current_souls -= src
|
||||||
|
|
||||||
|
current_room = null
|
||||||
|
|
||||||
|
if(previous_body && mind)
|
||||||
|
var/mob/target_body = previous_body.resolve()
|
||||||
|
if(!target_body)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
mind.transfer_to(target_body)
|
||||||
|
SEND_SIGNAL(target_body, COMSIG_SOULCATCHER_CHECK_SOUL, FALSE)
|
||||||
|
|
||||||
|
if(target_body.stat != DEAD)
|
||||||
|
target_body.grab_ghost(TRUE)
|
||||||
|
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/datum/emote/living
|
||||||
|
mob_type_blacklist_typecache = list(/mob/living/brain, /mob/living/soulcatcher_soul)
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/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["current_rooms"] = list()
|
||||||
|
for(var/datum/soulcatcher_room/room in soulcatcher_rooms)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
"ooc_notes" = soul.ooc_notes
|
||||||
|
)
|
||||||
|
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
|
||||||
|
qdel(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.room_description, 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))
|
||||||
|
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)
|
||||||
|
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("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
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 332 B |
Binary file not shown.
|
Before Width: | Height: | Size: 987 B After Width: | Height: | Size: 1.2 KiB |
@@ -6583,6 +6583,12 @@
|
|||||||
#include "modular_skyrat\modules\modular_implants\code\nifsofts\prop_summoner.dm"
|
#include "modular_skyrat\modules\modular_implants\code\nifsofts\prop_summoner.dm"
|
||||||
#include "modular_skyrat\modules\modular_implants\code\nifsofts\shapeshifter.dm"
|
#include "modular_skyrat\modules\modular_implants\code\nifsofts\shapeshifter.dm"
|
||||||
#include "modular_skyrat\modules\modular_implants\code\nifsofts\soul_poem.dm"
|
#include "modular_skyrat\modules\modular_implants\code\nifsofts\soul_poem.dm"
|
||||||
|
#include "modular_skyrat\modules\modular_implants\code\nifsofts\soulcatcher.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_items\code\bags.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\cash.dm"
|
||||||
#include "modular_skyrat\modules\modular_items\code\ciggies.dm"
|
#include "modular_skyrat\modules\modular_items\code\ciggies.dm"
|
||||||
|
|||||||
@@ -107,7 +107,9 @@ export const NifPanel = (props, context) => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
<br />
|
<br />
|
||||||
<BlockQuote>{nifsoft.desc}</BlockQuote>
|
<BlockQuote preserveWhitespace>
|
||||||
|
{nifsoft.desc}
|
||||||
|
</BlockQuote>
|
||||||
<box>
|
<box>
|
||||||
<br />
|
<br />
|
||||||
<Button.Confirm
|
<Button.Confirm
|
||||||
|
|||||||
290
tgui/packages/tgui/interfaces/Soulcatcher.js
Normal file
290
tgui/packages/tgui/interfaces/Soulcatcher.js
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
import { useBackend } from '../backend';
|
||||||
|
import { Window } from '../layouts';
|
||||||
|
import { BlockQuote, Button, Divider, Section, Box, Flex, Collapsible, LabeledList } from '../components';
|
||||||
|
|
||||||
|
export const Soulcatcher = (props, context) => {
|
||||||
|
const { act, data } = useBackend(context);
|
||||||
|
const { require_approval, current_rooms = [], ghost_joinable } = data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Window width={520} height={400} resizable>
|
||||||
|
<Window.Content scrollable>
|
||||||
|
{current_rooms.map((room) => (
|
||||||
|
<Section
|
||||||
|
key={room.key}
|
||||||
|
title={<span style={{ color: room.color }}>{room.name}</span>}
|
||||||
|
buttons={
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
icon="palette"
|
||||||
|
tooltip="Change the color of the room"
|
||||||
|
onClick={() =>
|
||||||
|
act('change_room_color', { room_ref: room.reference })
|
||||||
|
}>
|
||||||
|
Recolor
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
icon="pen"
|
||||||
|
tooltip="Change the name of the room"
|
||||||
|
onClick={() =>
|
||||||
|
act('rename_room', { room_ref: room.reference })
|
||||||
|
}>
|
||||||
|
Rename
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
icon="trash"
|
||||||
|
tooltip="Delete the room"
|
||||||
|
color="red"
|
||||||
|
onClick={() =>
|
||||||
|
act('delete_room', { room_ref: room.reference })
|
||||||
|
}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}>
|
||||||
|
<BlockQuote preserveWhitespace> {room.description}</BlockQuote>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
icon="scroll"
|
||||||
|
tooltip="Performs an emote, without sending a name."
|
||||||
|
onClick={() =>
|
||||||
|
act('send_message', {
|
||||||
|
room_ref: room.reference,
|
||||||
|
emote: true,
|
||||||
|
narration: true,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Narrate
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
icon="comment"
|
||||||
|
tooltip="Speak inside of the room."
|
||||||
|
onClick={() =>
|
||||||
|
act('send_message', {
|
||||||
|
room_ref: room.reference,
|
||||||
|
emote: false,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Say
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
icon="face-smile"
|
||||||
|
tooltip="Do an emote inside of the room."
|
||||||
|
onClick={() =>
|
||||||
|
act('send_message', {
|
||||||
|
room_ref: room.reference,
|
||||||
|
emote: true,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Emote
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
icon="user-gear"
|
||||||
|
tooltip="Edits the name that is sent when emoting and saying."
|
||||||
|
onClick={() =>
|
||||||
|
act('modify_name', {
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Edit Name
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
icon="book"
|
||||||
|
tooltip="Changes the description of the room"
|
||||||
|
onClick={() =>
|
||||||
|
act('redescribe_room', { room_ref: room.reference })
|
||||||
|
}>
|
||||||
|
Redecorate
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={room.joinable ? 'green' : 'red'}
|
||||||
|
icon={room.joinable ? 'door-open' : 'door-closed'}
|
||||||
|
onClick={() =>
|
||||||
|
act('toggle_joinable_room', { room_ref: room.reference })
|
||||||
|
}>
|
||||||
|
{room.joinable ? 'Room joinable' : 'Room unjoinable'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
{room.souls ? (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<Box textAlign="center" fontSize="15px" opacity={0.8}>
|
||||||
|
<b>Current Souls</b>
|
||||||
|
</Box>
|
||||||
|
<Divider />
|
||||||
|
<Flex direction="column">
|
||||||
|
{room.souls.map((soul) => (
|
||||||
|
<Flex.Item key={soul.key}>
|
||||||
|
<Collapsible
|
||||||
|
title={soul.name}
|
||||||
|
buttons={
|
||||||
|
<Button
|
||||||
|
icon="paper-plane"
|
||||||
|
tooltip="Transfer a soul to another room"
|
||||||
|
onClick={() =>
|
||||||
|
act('transfer_soul', {
|
||||||
|
room_ref: room.reference,
|
||||||
|
target_soul: soul.reference,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}>
|
||||||
|
<Box textAlign="center" fontSize="13px" opacity={0.8}>
|
||||||
|
<b>Flavor Text</b>
|
||||||
|
</Box>
|
||||||
|
<Divider />
|
||||||
|
<BlockQuote preserveWhitespace>
|
||||||
|
{soul.description}
|
||||||
|
</BlockQuote>
|
||||||
|
<br />
|
||||||
|
<Box textAlign="center" fontSize="13px" opacity={0.8}>
|
||||||
|
<b>OOC Notes</b>
|
||||||
|
</Box>
|
||||||
|
<Divider />
|
||||||
|
<BlockQuote preserveWhitespace>
|
||||||
|
{soul.ooc_notes}
|
||||||
|
</BlockQuote>
|
||||||
|
<br />
|
||||||
|
<LabeledList>
|
||||||
|
<LabeledList.Item label="Outside Hearing">
|
||||||
|
<Button
|
||||||
|
color={soul.outside_hearing ? 'green' : 'red'}
|
||||||
|
fluid
|
||||||
|
tooltip="Is the soul able to hear the outside world?"
|
||||||
|
onClick={() =>
|
||||||
|
act('toggle_soul_outside_sense', {
|
||||||
|
target_soul: soul.reference,
|
||||||
|
sense_to_change: 'hearing',
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{soul.outside_hearing ? 'Enabled' : 'Disabled'}
|
||||||
|
</Button>
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item label="Outside Sight">
|
||||||
|
<Button
|
||||||
|
color={soul.outside_sight ? 'green' : 'red'}
|
||||||
|
fluid
|
||||||
|
tooltip="Is the soul able to see the outside world?"
|
||||||
|
onClick={() =>
|
||||||
|
act('toggle_soul_outside_sense', {
|
||||||
|
target_soul: soul.reference,
|
||||||
|
sense_to_change: 'sight',
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{soul.outside_sight ? 'Enabled' : 'Disabled'}
|
||||||
|
</Button>
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item label="Hearing">
|
||||||
|
<Button
|
||||||
|
color={soul.internal_hearing ? 'green' : 'red'}
|
||||||
|
fluid
|
||||||
|
tooltip="Is the soul able to hear inside the room?"
|
||||||
|
onClick={() =>
|
||||||
|
act('toggle_soul_sense', {
|
||||||
|
target_soul: soul.reference,
|
||||||
|
sense_to_change: 'hearing',
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{soul.internal_hearing ? 'Enabled' : 'Disabled'}
|
||||||
|
</Button>
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item label="Sight">
|
||||||
|
<Button
|
||||||
|
color={soul.internal_sight ? 'green' : 'red'}
|
||||||
|
fluid
|
||||||
|
tooltip="Is the soul able to see inside the room?"
|
||||||
|
onClick={() =>
|
||||||
|
act('toggle_soul_sense', {
|
||||||
|
target_soul: soul.reference,
|
||||||
|
sense_to_change: 'sight',
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{soul.internal_sight ? 'Enabled' : 'Disabled'}
|
||||||
|
</Button>
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item label="Speech">
|
||||||
|
<Button
|
||||||
|
color={soul.able_to_speak ? 'green' : 'red'}
|
||||||
|
fluid
|
||||||
|
tooltip="Is the soul able to speak?"
|
||||||
|
onClick={() =>
|
||||||
|
act('toggle_soul_communication', {
|
||||||
|
target_soul: soul.reference,
|
||||||
|
communication_type: 'speech',
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{soul.able_to_speak ? 'Enabled' : 'Disabled'}
|
||||||
|
</Button>
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item label="Emote">
|
||||||
|
<Button
|
||||||
|
color={soul.able_to_emote ? 'green' : 'red'}
|
||||||
|
fluid
|
||||||
|
tooltip="Is the soul able to emote?"
|
||||||
|
onClick={() =>
|
||||||
|
act('toggle_soul_communication', {
|
||||||
|
target_soul: soul.reference,
|
||||||
|
communication_type: 'emote',
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{soul.able_to_emote ? 'Enabled' : 'Disabled'}
|
||||||
|
</Button>
|
||||||
|
</LabeledList.Item>
|
||||||
|
</LabeledList>
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
icon="eject"
|
||||||
|
color="red"
|
||||||
|
onClick={() =>
|
||||||
|
act('remove_soul', {
|
||||||
|
target_soul: soul.reference,
|
||||||
|
room_ref: room.reference,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Remove Soul
|
||||||
|
</Button>
|
||||||
|
</Collapsible>
|
||||||
|
</Flex.Item>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<> </>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
color="green"
|
||||||
|
icon="plus"
|
||||||
|
onClick={() => act('create_room', {})}>
|
||||||
|
Create new room
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
color={ghost_joinable ? 'green' : 'red'}
|
||||||
|
icon={ghost_joinable ? 'door-open' : 'door-closed'}
|
||||||
|
onClick={() => act('toggle_joinable', {})}>
|
||||||
|
{ghost_joinable ? 'Opened' : 'Closed'} to ghosts
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
color={require_approval ? 'green' : 'red'}
|
||||||
|
icon={require_approval ? 'lock' : 'lock-open'}
|
||||||
|
onClick={() => act('toggle_approval', {})}>
|
||||||
|
Approval is {require_approval ? '' : 'not'} required to join
|
||||||
|
</Button>
|
||||||
|
</Window.Content>
|
||||||
|
</Window>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user