From 7ee5ab7041f175f606ad7440e7c7c27ff658226b Mon Sep 17 00:00:00 2001 From: Kashargul <144968721+Kashargul@users.noreply.github.com> Date: Sat, 27 Jul 2024 02:22:43 +0200 Subject: [PATCH] Allow to give control to stored souls in the soulcatcher & ghost join (#8687) --- code/__defines/mob_ch.dm | 1 + code/__defines/nif_ch.dm | 2 + .../station/station_special_abilities_vr.dm | 4 + .../nifsoft/software/13_soulcatcher.dm | 28 +- code/modules/vore/eating/vorepanel_vr.dm | 73 ++-- .../code/modules/vore/eating/soulcatcher.dm | 368 ++++++++++++------ .../modules/vore/eating/soulcatcher_mob.dm | 35 +- .../vore/eating/soulcatcher_observer.dm | 52 +++ .../chompstation/VorePanel/VoreAbilities.tsx | 56 +++ .../VorePanel/VoreSoulcatcher.tsx | 235 ++--------- .../VoreSoulcatcherSettings/CatchSettings.tsx | 70 ++++ .../VoreSoulcatcherSettings/GlobalOptions.tsx | 51 +++ .../GlobalSettings.tsx | 88 +++++ .../VoreSoulcatcherSettings/SoulOptions.tsx | 59 +++ .../VorePanel/VoreUserPreferences.tsx | 15 + .../VoreUserPreferencesSoulcatcher.tsx | 8 +- .../chompstation/VorePanel/index.tsx | 7 +- .../chompstation/VorePanel/types.ts | 21 +- vorestation.dme | 1 + 19 files changed, 807 insertions(+), 367 deletions(-) create mode 100644 modular_chomp/code/modules/vore/eating/soulcatcher_observer.dm create mode 100644 tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreAbilities.tsx create mode 100644 tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/CatchSettings.tsx create mode 100644 tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalOptions.tsx create mode 100644 tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalSettings.tsx create mode 100644 tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/SoulOptions.tsx diff --git a/code/__defines/mob_ch.dm b/code/__defines/mob_ch.dm index b7ee81ea6f..871a0d77f2 100644 --- a/code/__defines/mob_ch.dm +++ b/code/__defines/mob_ch.dm @@ -2,3 +2,4 @@ #define SOULCATCHER_ALLOW_TRANSFER 0x2 #define SOULCATCHER_ALLOW_DELETION 0x4 #define SOULCATCHER_ALLOW_DELETION_INSTANT 0x8 +#define SOULCATCHER_ALLOW_TAKEOVER 0x10 diff --git a/code/__defines/nif_ch.dm b/code/__defines/nif_ch.dm index 84a08b9bdc..ea78284f43 100644 --- a/code/__defines/nif_ch.dm +++ b/code/__defines/nif_ch.dm @@ -6,5 +6,7 @@ #define NIF_SC_ALLOW_EYES 0x8 #define NIF_SC_BACKUPS 0x10 #define NIF_SC_PROJECTING 0x20 +#define SOULGEM_CATCHING_GHOSTS 0x200 #define SOULGEM_ACTIVE 0x400 #define SOULGEM_SHOW_VORE_SFX 0x800 +#define SOULGEM_CATCHING_DRAIN 0x1000 diff --git a/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm b/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm index 88dcbfe078..5293724edc 100644 --- a/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm +++ b/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm @@ -635,6 +635,8 @@ to_chat(T, "An odd sensation flows through your body as you as [src] begins to drain you to dangerous levels!") if(51 to 98) if(T.stat == DEAD) + if(soulgem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_DRAIN, TRUE)) + soulgem.catch_mob(T) T.apply_damage(500, OXY) //Bit of fluff. absorbing_prey = 0 to_chat(src, "You have completely drained [T], killing them.") @@ -649,6 +651,8 @@ if(drain_finalized != 1) stage = 51 if(100) //They shouldn't survive long enough to get here, but just in case. + if(soulgem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_DRAIN, TRUE)) + soulgem.catch_mob(T) T.apply_damage(500, OXY) //Kill them. absorbing_prey = 0 to_chat(src, "You have completely drained [T], killing them in the process.") diff --git a/code/modules/nifsoft/software/13_soulcatcher.dm b/code/modules/nifsoft/software/13_soulcatcher.dm index b0babaf818..9c84f23bde 100644 --- a/code/modules/nifsoft/software/13_soulcatcher.dm +++ b/code/modules/nifsoft/software/13_soulcatcher.dm @@ -329,14 +329,16 @@ if(parent_mob) return //If they're blinded - if(ext_blind) - eye_blind = 5 - client.screen.Remove(global_hud.whitense) - overlay_fullscreen("blind", /obj/screen/fullscreen/blind) - else - eye_blind = 0 - clear_fullscreens() - client.screen.Add(global_hud.whitense) + if(soulcatcher) // CHOMPEdit Start, needs it's own handling to allow vore_fx + if(ext_blind) + eye_blind = 5 + client.screen.Remove(global_hud.whitense) + overlay_fullscreen("blind", /obj/screen/fullscreen/blind) + else + eye_blind = 0 + clear_fullscreens() + client.screen.Add(global_hud.whitense) + //CHOMPEdit End //If they're deaf if(ext_deaf) @@ -484,7 +486,10 @@ var/mob/living/owner = B.owner var/obj/soulgem/gem = owner.soulgem if(owner && gem.flag_check(SOULGEM_ACTIVE | NIF_SC_CATCHING_OTHERS, TRUE)) - gem.catch_mob(L) + var/to_use_custom_name = null + if(isanimal(L)) + to_use_custom_name = L.name + gem.catch_mob(L, to_use_custom_name) return TRUE var/mob/living/carbon/human/HP = B.owner var/mob/living/carbon/human/H = L @@ -495,7 +500,10 @@ else var/obj/soulgem/gem = L.soulgem if(gem && gem.flag_check(SOULGEM_ACTIVE | NIF_SC_CATCHING_ME, TRUE)) - gem.catch_mob(L) + var/to_use_custom_name = null + if(isanimal(L)) + to_use_custom_name = L.name + gem.catch_mob(L, to_use_custom_name) return TRUE var/mob/living/carbon/human/H = L if(!istype(H)) return TRUE diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index c615229c33..22336287d3 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -555,6 +555,7 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", //Soulcatcher "soulcatcher_allow_capture" = host.soulcatcher_pref_flags & SOULCATCHER_ALLOW_CAPTURE, "soulcatcher_allow_transfer" = host.soulcatcher_pref_flags & SOULCATCHER_ALLOW_TRANSFER, + "soulcatcher_allow_takeover" = host.soulcatcher_pref_flags & SOULCATCHER_ALLOW_TAKEOVER, "soulcatcher_allow_deletion" = (global_flag_check(host.soulcatcher_pref_flags, SOULCATCHER_ALLOW_DELETION) + global_flag_check(host.soulcatcher_pref_flags, SOULCATCHER_ALLOW_DELETION_INSTANT)) //CHOMPEdit end ) @@ -572,8 +573,11 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", data["soulcatcher"]["selected_soul"] = host.soulgem.selected_soul data["soulcatcher"]["selected_sfx"] = host.soulgem.linked_belly data["soulcatcher"]["interior_design"] = host.soulgem.inside_flavor + data["soulcatcher"]["taken_over"] = host.soulgem.is_taken_over() data["soulcatcher"]["catch_self"] = host.soulgem.flag_check(NIF_SC_CATCHING_ME) data["soulcatcher"]["catch_prey"] = host.soulgem.flag_check(NIF_SC_CATCHING_OTHERS) + data["soulcatcher"]["catch_drain"] = host.soulgem.flag_check(SOULGEM_CATCHING_DRAIN) + data["soulcatcher"]["catch_ghost"] = host.soulgem.flag_check(SOULGEM_CATCHING_GHOSTS) data["soulcatcher"]["ext_hearing"] = host.soulgem.flag_check(NIF_SC_ALLOW_EARS) data["soulcatcher"]["ext_vision"] = host.soulgem.flag_check(NIF_SC_ALLOW_EYES) data["soulcatcher"]["mind_backups"] = host.soulgem.flag_check(NIF_SC_BACKUPS) @@ -2170,10 +2174,20 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", //Soulcatcher prefs if("toggle_soulcatcher_allow_capture") host.soulcatcher_pref_flags ^= SOULCATCHER_ALLOW_CAPTURE + if(host.client.prefs_vr) + host.client.prefs_vr.soulcatcher_pref_flags = host.soulcatcher_pref_flags unsaved_changes = TRUE return TRUE if("toggle_soulcatcher_allow_transfer") host.soulcatcher_pref_flags ^= SOULCATCHER_ALLOW_TRANSFER + if(host.client.prefs_vr) + host.client.prefs_vr.soulcatcher_pref_flags = host.soulcatcher_pref_flags + unsaved_changes = TRUE + return TRUE + if("toggle_soulcatcher_allow_takeover") + host.soulcatcher_pref_flags ^= SOULCATCHER_ALLOW_TAKEOVER + if(host.client.prefs_vr) + host.client.prefs_vr.soulcatcher_pref_flags = host.soulcatcher_pref_flags unsaved_changes = TRUE return TRUE if("toggle_soulcatcher_allow_deletion") @@ -2188,6 +2202,8 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", host.soulcatcher_pref_flags ^= SOULCATCHER_ALLOW_DELETION if(host.soulcatcher_pref_flags & SOULCATCHER_ALLOW_DELETION_INSTANT) host.soulcatcher_pref_flags ^= SOULCATCHER_ALLOW_DELETION_INSTANT + if(host.client.prefs_vr) + host.client.prefs_vr.soulcatcher_pref_flags = host.soulcatcher_pref_flags unsaved_changes = TRUE return TRUE if("adjust_own_size") @@ -2199,13 +2215,34 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", H.adjust_nutrition(-VORE_RESIZE_COST) H.resize(new_size, uncapped = host.has_large_resize_bounds(), ignore_prefs = TRUE) return TRUE - //Soulcatcher settings - if("soulcatcher_toggle") - host.soulgem.toggle_setting(SOULGEM_ACTIVE) - unsaved_changes = TRUE + //Soulcatcher functions + if("soulcatcher_release_all") + host.soulgem.release_mobs() + return TRUE + if("soulcatcher_erase_all") + host.soulgem.erase_mobs() + return TRUE + if("soulcatcher_release") + host.soulgem.release_selected() + return TRUE + if("soulcatcher_transfer") + host.soulgem.transfer_selected() + return TRUE + if("soulcatcher_delete") + host.soulgem.delete_selected() + return TRUE + if("soulcatcher_transfer_control") + host.soulgem.take_control_selected() + return TRUE + if("soulcatcher_release_control") + host.soulgem.take_control_owner() return TRUE if("soulcatcher_select") host.soulgem.selected_soul = locate(params["selected_soul"]) + return TRUE + //Soulcatcher settings + if("soulcatcher_toggle") + host.soulgem.toggle_setting(SOULGEM_ACTIVE) unsaved_changes = TRUE return TRUE if("soulcatcher_sfx") @@ -2214,18 +2251,6 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", host.soulgem.update_linked_belly(belly) unsaved_changes = TRUE return TRUE - if("soulcatcher_release") - host.soulgem.release_selected() - unsaved_changes = TRUE - return TRUE - if("soulcatcher_transfer") - host.soulgem.transfer_selected() - unsaved_changes = TRUE - return TRUE - if("soulcatcher_delete") - host.soulgem.delete_selected() - unsaved_changes = TRUE - return TRUE if("toggle_self_catching") host.soulgem.toggle_setting(NIF_SC_CATCHING_ME) unsaved_changes = TRUE @@ -2234,6 +2259,14 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", host.soulgem.toggle_setting(NIF_SC_CATCHING_OTHERS) unsaved_changes = TRUE return TRUE + if("toggle_drain_catching") + host.soulgem.toggle_setting(SOULGEM_CATCHING_DRAIN) + unsaved_changes = TRUE + return TRUE + if("toggle_ghost_catching") + host.soulgem.toggle_setting(SOULGEM_CATCHING_GHOSTS) + unsaved_changes = TRUE + return TRUE if("toggle_ext_hearing") host.soulgem.toggle_setting(NIF_SC_ALLOW_EARS) unsaved_changes = TRUE @@ -2254,14 +2287,6 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", host.soulgem.toggle_setting(SOULGEM_SHOW_VORE_SFX) unsaved_changes = TRUE return TRUE - if("soulcatcher_release_all") - unsaved_changes = TRUE - host.soulgem.release_mobs() - return TRUE - if("soulcatcher_erase_all") - unsaved_changes = TRUE - host.soulgem.erase_mobs() - return TRUE if("soulcatcher_rename") var/new_name = tgui_input_text(host, "Adjust the name of your soulcatcher. Limit 60 chars.", \ "New Name", html_decode(host.soulgem.name), 60, prevent_enter = TRUE) diff --git a/modular_chomp/code/modules/vore/eating/soulcatcher.dm b/modular_chomp/code/modules/vore/eating/soulcatcher.dm index 9ef5a65e64..a08e4cba84 100644 --- a/modular_chomp/code/modules/vore/eating/soulcatcher.dm +++ b/modular_chomp/code/modules/vore/eating/soulcatcher.dm @@ -4,6 +4,7 @@ var/mob/living/owner var/datum/own_mind var/obj/belly/linked_belly + var/taken_over_name var/setting_flags = (NIF_SC_ALLOW_EARS|NIF_SC_ALLOW_EYES|NIF_SC_BACKUPS|NIF_SC_PROJECTING) var/mob/selected_soul = null @@ -15,6 +16,7 @@ var/transfer_message = "Transfer Message" var/delete_message = "Delete Message" +// Append the vars to save to our savefile list /obj/soulgem/vars_to_save() var/list/saving = list ( "setting_flags", @@ -28,11 +30,13 @@ ) return ..() + saving +// Load the vars_to_save from the savefile /obj/soulgem/Initialize() . = ..() if(ismob(loc)) owner = loc +// Store the vars_to_save into the save file /obj/soulgem/deserialize(list/data) . = ..() for(var/obj/belly in owner.vore_organs) @@ -41,6 +45,7 @@ return linked_belly = null +// Allows to transfer the soulgem to the given mob /obj/soulgem/proc/transfer_self(var/mob/target) QDEL_NULL(target.soulgem) owner.soulgem = null @@ -48,6 +53,7 @@ owner = target target.soulgem = src +// Cleaning up our refs before deletion /obj/soulgem/Destroy() owner = null selected_soul = null @@ -58,12 +64,14 @@ linked_belly = null ..() +// Sends messages to the owner of the soulcatcher /obj/soulgem/proc/notify_holder(var/message) to_chat(owner, span_nif("[name] displays, \"[message]\"")) for(var/mob/living/carbon/brain/caught_soul/CS as anything in brainmobs) to_chat(CS, span_nif("[name] displays, \"[message]\"")) +// Forwards the speech of captured souls /obj/soulgem/proc/use_speech(var/message, var/mob/living/sender, var/mob/eyeobj) var/sender_name = eyeobj ? eyeobj.name : sender.name @@ -79,6 +87,7 @@ log_nsay(message, owner.real_name, sender) +// Forwards the emotes of captured souls /obj/soulgem/proc/use_emote(var/message, var/mob/living/sender, var/mob/eyeobj) var/sender_name = eyeobj ? eyeobj.name : sender.name @@ -94,43 +103,9 @@ log_nme(message, owner.real_name,sender) -/obj/soulgem/proc/update_linked_belly(var/obj/belly, var/skip_unreg = FALSE) - if(!belly && linked_belly) - UnregisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX) - linked_belly = null - return - if(!isbelly(belly)) - return - if(!linked_belly) - linked_belly = belly - RegisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX, PROC_REF(soulgem_vfx)) - return - if(belly != linked_belly) - if(!skip_unreg) - UnregisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX) - linked_belly = belly - RegisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX, PROC_REF(soulgem_vfx)) - -/obj/soulgem/proc/soulgem_vfx(var/update, var/severity = 0) - if(linked_belly) - for(var/mob/living/L in brainmobs) - if(flag_check(SOULGEM_SHOW_VORE_SFX)) - show_vore_fx(L, update, severity) - else - clear_vore_fx() - -/obj/soulgem/proc/show_vore_fx(var/mob/living/L, var/update, var/severity = 0) - if(!linked_belly || !flag_check(SOULGEM_SHOW_VORE_SFX)) - return - if(!istype(L) || L.eyeobj) - return - linked_belly.vore_fx(L, update, severity) - -/obj/soulgem/proc/clear_vore_fx(var/mob/M) - M.clear_fullscreen("belly") - +// The capture function which transfers the given mob's mind into the soulcatcher /obj/soulgem/proc/catch_mob(var/mob/M, var/custom_name) - if(!(M.soulcatcher_pref_flags & SOULCATCHER_ALLOW_CAPTURE)) return + if(!(M.soulcatcher_pref_flags & SOULCATCHER_ALLOW_CAPTURE) && !isobserver(M)) return // Bypass pref check for observer join if(!M.mind) return if(isbrain(owner)) return //Create a new brain mob @@ -140,6 +115,8 @@ brainmob.stat = 0 brainmob.silent = FALSE dead_mob_list -= brainmob + brainmob.ext_deaf = !flag_check(NIF_SC_ALLOW_EARS) + brainmob.ext_blind = !flag_check(NIF_SC_ALLOW_EYES) brainmob.add_language(LANGUAGE_GALCOM) brainmobs |= brainmob @@ -149,7 +126,7 @@ brainmob.real_name = custom_name ? custom_name : brainmob.mind.name //If we caught our owner, special settings. - if(M == owner) + if(M == owner && !own_mind) // Need some more sanity if we allow takeover brainmob.ext_deaf = FALSE brainmob.ext_blind = FALSE brainmob.parent_mob = TRUE @@ -194,25 +171,206 @@ show_vore_fx(brainmob, TRUE) return TRUE -/obj/soulgem/proc/release_selected() - release_mob(selected_soul) +// Allows to adjust the interior of the soulcatcher +/obj/soulgem/proc/adjust_interior(var/new_flavor) + new_flavor = sanitize(new_flavor, MAX_MESSAGE_LEN * 2) + inside_flavor = new_flavor + notify_holder("Updating environment...") + for(var/mob/living/carbon/brain/caught_soul/vore/CS as anything in brainmobs) + to_chat(CS, span_notice("[transit_message]") + "\n[inside_flavor]") + +// Allows to return to the body after being captured by one's own soulcatcher +/obj/soulgem/proc/return_to_body(var/datum/mind) + if(own_mind != mind) + to_chat(src, span_warning("You aren't in your own soulcatcher!")) + return + var/mob/self = null + for(var/mob/mob in brainmobs) + if(mob.mind == mind) + self = mob + break + if(!self) + return + if(owner.mind) + catch_mob(owner, taken_over_name) + self.mind.transfer_to(owner) + own_mind = null + taken_over_name = null + qdel(self) + +// Sets the custom messages depending on the input +/obj/soulgem/proc/set_custom_message(var/message, var/target) + message = sanitize(message, MAX_MESSAGE_LEN / 4) + switch(target) + if("capture") + capture_message = message + if("transit") + transit_message = message + if("release") + release_message = message + if("transfer") + transfer_message = message + if("delete") + delete_message = message + +// Allows to rename the soulgem +/obj/soulgem/proc/rename(var/new_name) + if(length(new_name) < 3 || length(new_name) > 60) + to_chat(owner, span_warning("Your soulcatcher's name needs to be between 3 and 60 characters long!")) + return + new_name = sanitize(new_name, 60) + name = new_name + +// Toggles the given flag +/obj/soulgem/proc/toggle_setting(var/flag) + setting_flags ^= flag + if(flag & SOULGEM_SHOW_VORE_SFX) + soulgem_vfx(TRUE) + if(flag & NIF_SC_BACKUPS) + soulgem_backup() + if(flag & NIF_SC_ALLOW_EARS) + soulgem_hear() + if(flag & NIF_SC_ALLOW_EYES) + soulgem_sight() + if(flag & NIF_SC_PROJECTING) + soulgem_projecting() + +// Checks a single flag, or if all combined flags are true +/obj/soulgem/proc/flag_check(var/flag, var/match_all = FALSE) + if(match_all) + return (setting_flags & flag) == flag + return setting_flags & flag + +// Updates the selected soul after an interaction which rleased, deleted or transferred the previous one +/obj/soulgem/proc/update_selected_soul() if(brainmobs.len > 1) selected_soul = brainmobs[1] else selected_soul = null -/obj/soulgem/proc/release_mobs() - selected_soul = null - if(!brainmobs.len) return - for(var/mob/M in brainmobs) - release_mob(M) +// Backup toggling +/obj/soulgem/proc/soulgem_backup() + if(flag_check(NIF_SC_BACKUPS)) + notify_holder("External backups enabled.") + else + notify_holder("External backups disabled.") -/obj/soulgem/proc/release_mob(var/mob/M) - to_chat(M, span_notice("[release_message]")) +// Deaf toggling +/obj/soulgem/proc/soulgem_hear() + for(var/mob/living/carbon/brain/caught_soul/L in brainmobs) + L.ext_deaf = !flag_check(NIF_SC_ALLOW_EARS) + if(flag_check(NIF_SC_ALLOW_EARS)) + notify_holder("External sounds enabled.") + else + notify_holder("External sounds disabled.") + +// Sight toggling +/obj/soulgem/proc/soulgem_sight() + for(var/mob/living/carbon/brain/caught_soul/L in brainmobs) + L.ext_blind = !flag_check(NIF_SC_ALLOW_EYES) + if(flag_check(NIF_SC_ALLOW_EYES)) + notify_holder("External vision enabled.") + else + notify_holder("External vision disabled.") + +// Projecting toggling +/obj/soulgem/proc/soulgem_projecting() + if(flag_check(NIF_SC_PROJECTING)) + notify_holder("AR projecting enabled.") + else + notify_holder("AR projecting disabled.") + +// VORE FX Section + +// Updates the vore FX signal links to the new given belly +/obj/soulgem/proc/update_linked_belly(var/obj/belly, var/skip_unreg = FALSE) + if(!belly && linked_belly) + UnregisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX) + linked_belly = null + return + if(!isbelly(belly)) + return + if(!linked_belly) + linked_belly = belly + RegisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX, PROC_REF(soulgem_vfx)) + return + if(belly != linked_belly) + if(!skip_unreg) + UnregisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX) + linked_belly = belly + RegisterSignal(linked_belly, COMSIG_BELLY_UPDATE_VORE_FX, PROC_REF(soulgem_vfx)) + +// Handles the vore fx updates for the captured souls +/obj/soulgem/proc/soulgem_vfx(var/update, var/severity = 0) + if(linked_belly) + for(var/mob/living/L in brainmobs) + if(flag_check(SOULGEM_SHOW_VORE_SFX)) + show_vore_fx(L, update, severity) + else + clear_vore_fx(L) + if(flag_check(SOULGEM_SHOW_VORE_SFX)) + notify_holder("Interior simulation enabled.") + else + notify_holder("Interior simulation disabled.") + +// Function to show the vore fx overlay +/obj/soulgem/proc/show_vore_fx(var/mob/living/L, var/update, var/severity = 0) + if(!linked_belly || !flag_check(SOULGEM_SHOW_VORE_SFX)) + return + if(!istype(L) || L.eyeobj) + return + linked_belly.vore_fx(L, update, severity) + +// Function to clear the vore fx overlay +/obj/soulgem/proc/clear_vore_fx(var/mob/M) + M.clear_fullscreen("belly") + if(M.hud_used && !M.hud_used.hud_shown) + M.toggle_hud_vis(TRUE) + +// Takeover section + +// Give control of the body to the current selected soul +/obj/soulgem/proc/take_control_selected() + if(!selected_soul) return + take_control(selected_soul) + if(owner.mind == own_mind) + own_mind = null + taken_over_name = null + +// Give back control of the body to the owner +/obj/soulgem/proc/take_control_owner() + var/mob/self = null + for(var/mob/mob in brainmobs) + if(mob.mind == own_mind) + self = mob + break + if(!self) + return + take_control(self) + own_mind = null + taken_over_name = null + +/obj/soulgem/proc/take_control(var/mob/M) + if(!(owner.soulcatcher_pref_flags & SOULCATCHER_ALLOW_CAPTURE) || !(owner.soulcatcher_pref_flags & SOULCATCHER_ALLOW_TAKEOVER)) return + if(!(M.soulcatcher_pref_flags & SOULCATCHER_ALLOW_CAPTURE) || !(M.soulcatcher_pref_flags & SOULCATCHER_ALLOW_TAKEOVER)) return + if(!own_mind) + if(issilicon(owner) || isanimal(owner)) + taken_over_name = owner.name + catch_mob(owner, taken_over_name) + taken_over_name = M.name + M.mind.transfer_to(owner) brainmobs -= M - M.ghostize(FALSE) + if(M == selected_soul) + update_selected_soul() qdel(M) +// Funtion to test if the owner's body has been taken over +/obj/soulgem/proc/is_taken_over() + return (own_mind && owner.mind != own_mind) + +// Transfer section to transfer captured souls + +// Returns nearby,valid transfer locations as a list /obj/soulgem/proc/find_transfer_objects() var/list/valid_trasfer_objects = list( /obj/item/device/sleevemate, @@ -244,6 +402,7 @@ return return valid_objects +// Transfer the selected soul to either a valid object or another soulcatcher /obj/soulgem/proc/transfer_selected() if(!selected_soul) return if(!(selected_soul.soulcatcher_pref_flags & SOULCATCHER_ALLOW_TRANSFER)) return @@ -264,7 +423,9 @@ return transfer_mob(selected_soul, target) +// Transfers a captured soul to a valid object (sleevemate, mmi) /obj/soulgem/proc/transfer_mob(var/mob/M, var/obj/target) + if(is_taken_over()) return if(!M || !target) return if(istype(target, /obj/item/device/sleevemate)) var/obj/item/device/sleevemate/mate = target @@ -272,7 +433,7 @@ to_chat(owner, span_notice("You scan yourself to transfer the soul into the [target]!")) to_chat(M, span_notice("[transfer_message]")) mate.get_mind(M) - else if(istype(M, /obj/item/device/mmi)) + else if(istype(target, /obj/item/device/mmi)) var/obj/item/device/mmi/mm = target if(!mm.brainmob) to_chat(owner, span_notice("You transfer the soul into the [target]!")) @@ -284,96 +445,71 @@ own_mind = null brainmobs -= M if(M == selected_soul) - if(brainmobs.len > 1) - selected_soul = brainmobs[1] - else - selected_soul = null + update_selected_soul() qdel(M) +// Transfers a captured soul to another soulcatcher /obj/soulgem/proc/transfer_mob_soulcatcher(var/mob/living/carbon/brain/caught_soul/vore/M, var/obj/soulgem/gem) + if(is_taken_over()) return if(!istype(M) || !gem) return + if(M.mind == own_mind) + own_mind = null brainmobs -= M M.gem = gem M.container = gem gem.brainmobs += M + if(M == selected_soul) + update_selected_soul() +// Release section + +// Release the selected soul as a ghost +/obj/soulgem/proc/release_selected() + if(!selected_soul) return + if(release_mob(selected_soul)) + update_selected_soul() + +// Release all captured souls as ghosts +/obj/soulgem/proc/release_mobs() + selected_soul = null + if(!brainmobs.len) return + for(var/mob/M in brainmobs) + release_mob(M) + +// Proc to release the soul as ghost, returns TRUE on success +/obj/soulgem/proc/release_mob(var/mob/M) + if(is_taken_over()) return FALSE + to_chat(M, span_notice("[release_message]")) + brainmobs -= M + M.ghostize(FALSE) + qdel(M) + return TRUE + +// Delete section to delete captured souls from the soulcatcher + +// Delete the selected mob /obj/soulgem/proc/delete_selected() if(!selected_soul) return - delete_mob(selected_soul) - if(brainmobs.len > 1) - selected_soul = brainmobs[1] - else - selected_soul = null + if(delete_mob(selected_soul)) + update_selected_soul() +// Delete all captured mobs /obj/soulgem/proc/erase_mobs() if(!brainmobs.len) return for(var/mob/M in brainmobs) delete_mob(M) +// The function handling the actual delete, returns TRUE on success /obj/soulgem/proc/delete_mob(var/mob/M) + if(is_taken_over()) return FALSE if(!(M.soulcatcher_pref_flags & SOULCATCHER_ALLOW_DELETION)) - release_mob(M) - return + return release_mob(M) if(!(M.soulcatcher_pref_flags & SOULCATCHER_ALLOW_DELETION_INSTANT)) - if(!(tgui_alert(M, "Do you really want to allow [owner] to delete you? On decline, you'll be ghosted.", "Allow Deletion", list("No", "Yes")) == "Yes")) - release_mob(M) - return + if(tgui_alert(M, "Do you really want to allow [owner] to delete you? On decline, you'll be ghosted.", "Allow Deletion", list("No", "Yes"), timeout=1 MINUTES) != "Yes") + return release_mob(M) to_chat(M, span_danger("[delete_message]")) brainmobs -= M var/mob/observer/dead/ghost = M.ghostize(FALSE) ghost.abandon_mob() qdel(M) - -/obj/soulgem/proc/adjust_interior(var/new_flavor) - new_flavor = sanitize(new_flavor, MAX_MESSAGE_LEN * 2) - inside_flavor = new_flavor - notify_holder("Updating environment...") - for(var/mob/living/carbon/brain/caught_soul/vore/CS as anything in brainmobs) - to_chat(CS, span_notice("[transit_message]") + "\n[inside_flavor]") - -/obj/soulgem/proc/return_to_body(var/datum/mind) - if(own_mind != mind) - to_chat(src, span_warning("You aren't in your own soulcatcher!")) - return - var/mob/self = null - for(var/mob/mob in brainmobs) - if(mob.mind == mind) - self = mob - break - if(!self) - return - self.mind.transfer_to(owner) - own_mind = null - qdel(self) - -/obj/soulgem/proc/set_custom_message(var/message, var/target) - message = sanitize(message, MAX_MESSAGE_LEN / 4) - switch(target) - if("capture") - capture_message = message - if("transit") - transit_message = message - if("release") - release_message = message - if("transfer") - transfer_message = message - if("delete") - delete_message = message - -/obj/soulgem/proc/rename(var/new_name) - if(length(new_name) < 3 || length(new_name) > 60) - to_chat(owner, span_warning("Your soulcatcher's name needs to be between 3 and 60 characters long!")) - return - new_name = sanitize(new_name, 60) - name = new_name - -/obj/soulgem/proc/toggle_setting(var/flag) - setting_flags ^= flag - if(flag & SOULGEM_SHOW_VORE_SFX) - soulgem_vfx(TRUE) - -// Checks a single flag, or an entire list if all elements in it are true -/obj/soulgem/proc/flag_check(var/flag, var/match_all = FALSE) - if(match_all) - return (setting_flags & flag) == flag - return setting_flags & flag + return TRUE diff --git a/modular_chomp/code/modules/vore/eating/soulcatcher_mob.dm b/modular_chomp/code/modules/vore/eating/soulcatcher_mob.dm index 25fa178e8e..0a107dd216 100644 --- a/modular_chomp/code/modules/vore/eating/soulcatcher_mob.dm +++ b/modular_chomp/code/modules/vore/eating/soulcatcher_mob.dm @@ -4,6 +4,7 @@ var/obj/soulgem/gem +// Cleaning up the refs during deletion /mob/living/carbon/brain/caught_soul/vore/Destroy() if(gem) gem.notify_holder("Mind unloaded: [name]") @@ -15,26 +16,48 @@ container = null return ..() +// Handling the automatic transcore backups in a set interval /mob/living/carbon/brain/caught_soul/vore/Life() . = ..() + if(QDELETED(src)) + return if(!parent_mob && !transient &&(life_tick % 150 == 0) && gem.setting_flags & NIF_SC_BACKUPS) SStranscore.m_backup(mind,0) //Passed 0 means "Don't touch the nif fields on the mind record" + if(!client) + return + + if(ext_blind) + eye_blind = 5 + client.screen.Remove(global_hud.whitense) + overlay_fullscreen("blind", /obj/screen/fullscreen/blind) + else + eye_blind = 0 + clear_fullscreen("blind") + if(!gem.flag_check(SOULGEM_SHOW_VORE_SFX)) + client.screen.Add(global_hud.whitense) + if(gem.flag_check(SOULGEM_SHOW_VORE_SFX)) + client.screen.Remove(global_hud.whitense) + +// Say proc for captures souls /mob/living/carbon/brain/caught_soul/vore/say(var/message, var/datum/language/speaking = null, var/whispering = 0) if(silent) return FALSE gem.use_speech(message, src, eyeobj) +// Emote proc for captured souls /mob/living/carbon/brain/caught_soul/vore/custom_emote(var/m_type, var/message) if(silent) return FALSE gem.use_emote(message,src,eyeobj) +// Resist override, only returning a message that one is stuck for now /mob/living/carbon/brain/caught_soul/vore/resist() set name = "Resist" set category = "IC.Game" //CHOMPEdit to_chat(src, span_warning("There's no way out! You're stuck inside your predator.")) +// Allows the predator to enter their own soulcatcher /mob/proc/enter_soulcatcher() set name = "Enter Soulcatcher" set desc = "Enter your own Soulcatcher." @@ -43,8 +66,12 @@ if(!soulgem) // Only sanity... return if(soulgem && soulgem.flag_check(SOULGEM_ACTIVE)) - soulgem.catch_mob(src) + var/to_use_custom_name = null + if(issilicon(src) || isanimal(src)) + to_use_custom_name = src.name + soulgem.catch_mob(src, to_use_custom_name) +// Speak to the captured souls within the own soulcatcher /mob/proc/nsay_vore() set name = "NSay Vore" set desc = "Speak into your Soulcatcher." @@ -67,6 +94,7 @@ var/sane_message = sanitize(message) gem.use_speech(sane_message, src) +// Emote to the captured souls within the soulcatcher /mob/proc/nme_vore() set name = "NMe Vore" set desc = "Emote into your Soulcatcher." @@ -90,6 +118,7 @@ var/sane_message = sanitize(message) gem.use_emote(sane_message, src) +// AR project as captured soul /mob/living/carbon/brain/caught_soul/vore/ar_project() set name = "AR Project" set desc = "Project your form into Augmented Reality for those around your predator with the appearance of your loaded character." @@ -110,6 +139,7 @@ gem.notify_holder("[src] now AR projecting.") gem.clear_vore_fx(src) +// Jump to the owner as AR projection /mob/living/carbon/brain/caught_soul/vore/jump_to_owner() set name = "Jump to Owner" set desc = "Jump your projection back to the owner of the soulcatcher you're inside." @@ -121,6 +151,7 @@ eyeobj.forceMove(get_turf(gem)) +// End AR projecting and return to the soulcatcher containing the soul /mob/living/carbon/brain/caught_soul/vore/reenter_soulcatcher() set name = "Re-enter Soulcatcher" set desc = "Leave AR projection and drop back into the soulcatcher." @@ -154,6 +185,7 @@ var/sane_message = sanitize(message) gem.use_emote(sane_message, src) +// Allows the captured owner to transfer themselves to valid nearby objects /mob/living/carbon/brain/caught_soul/vore/proc/transfer_self() set name = "Transfer Self" set desc = "Transfer youself while being in your own soulcatcher into a nearby Sleevemate or MMI." @@ -172,6 +204,7 @@ var/obj/item/target = tgui_input_list(src, "Select where you want to store your own mind into.", "Mind Transfer Target", valid_objects) gem.transfer_mob(src, target) +// Allows the owner to reenter the body after being caught or having given away control /mob/living/carbon/brain/caught_soul/vore/proc/reenter_body() set name = "Re-enter Body" set desc = "Return to your body after self capturing." diff --git a/modular_chomp/code/modules/vore/eating/soulcatcher_observer.dm b/modular_chomp/code/modules/vore/eating/soulcatcher_observer.dm new file mode 100644 index 0000000000..6b0580ab40 --- /dev/null +++ b/modular_chomp/code/modules/vore/eating/soulcatcher_observer.dm @@ -0,0 +1,52 @@ +/mob/observer/dead/verb/soulcatcherjoin() + set category = "Ghost.Join" + set name = "Join Into Soulcatcher(Vore)" + set desc = "Select a player with enabled Soulcatcher to join." + + var/list/valid_players = list() + for(var/mob/player in player_list) + if(player.soulgem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_GHOSTS, TRUE)) + valid_players += player + + if(!valid_players.len) + to_chat(src, span_warning("No valid players found.")) + return + + var/picked = tgui_input_list(usr, "Pick a friend with enabled Soulcatcher to join into. Harrass strangers, get banned.","Select a player", valid_players) + + if(!picked) + return + + if(!ismob(picked)) + to_chat(src, span_warning("Target is no mob.")) + return + + var/mob/M = picked + + var/obj/soulgem/gem = M.soulgem + + if(!gem?.flag_check(SOULGEM_ACTIVE)) + to_chat(src, span_warning("[M] has no enabled Soulcatcher.")) + return + + var/req_time = world.time + gem.notify_holder("Transient mindstate detected, analyzing...") + sleep(15) //So if they are typing they get interrupted by sound and message, and don't type over the box + if(tgui_alert(M,"[src.name] wants to join into your Soulcatcher.","Soulcatcher Request",list("Deny","Allow"), timeout=1 MINUTES) != "Allow") + to_chat(src, span_warning("[M] has denied your request.")) + return + + if((world.time - req_time) > 1 MINUTES) + to_chat(M, span_warning("The request had already expired. (1 minute waiting max)")) + return + + //Final check since we waited for input a couple times. + if(M && src && src.key && !M.stat && gem?.flag_check(SOULGEM_ACTIVE | SOULGEM_CATCHING_GHOSTS, TRUE)) + if(!mind) //No mind yet, aka haven't played in this round. + mind = new(key) + + mind.name = name + mind.current = src + mind.active = TRUE + + gem.catch_mob(src) //This will result in us being deleted so... diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreAbilities.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreAbilities.tsx new file mode 100644 index 0000000000..ce56c8a631 --- /dev/null +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreAbilities.tsx @@ -0,0 +1,56 @@ +import { toFixed } from 'common/math'; + +import { useBackend } from '../../../backend'; +import { Flex, LabeledList, Section, Slider } from '../../../components'; +import { abilities } from './types'; + +export const VoreAbilities = (props: { abilities: abilities }) => { + const { act } = useBackend(); + + const { abilities } = props; + + const { nutrition, current_size, minimum_size, maximum_size, resize_cost } = + abilities; + + function is_enabled(nutri: number, cost: number): boolean { + return nutri >= cost; + } + + return ( +
+ + + + + toFixed(value, 2) + '%'} + value={current_size * 100} + minValue={minimum_size * 100} + maxValue={maximum_size * 100} + onChange={(e, value: number) => + act('adjust_own_size', { + new_mob_size: value / 100, + }) + } + /> + +   Cost:  + {resize_cost} + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcher.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcher.tsx index 57241950c6..797f1e5868 100644 --- a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcher.tsx +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcher.tsx @@ -1,25 +1,27 @@ -import { toFixed } from 'common/math'; - import { useBackend } from '../../../backend'; import { Box, Button, Dropdown, - Flex, LabeledList, Section, - Slider, Stack, } from '../../../components'; -import { bellyData, Data, DropdownEntry, soulcatcherData } from './types'; +import { abilities, bellyData, DropdownEntry, soulcatcherData } from './types'; +import { VoreAbilities } from './VoreAbilities'; +import { CatchSettings } from './VoreSoulcatcherSettings/CatchSettings'; +import { GlobalOptions } from './VoreSoulcatcherSettings/GlobalOptions'; +import { GlobalSettings } from './VoreSoulcatcherSettings/GlobalSettings'; +import { SoulOptions } from './VoreSoulcatcherSettings/SoulOptions'; export const VoreSoulcatcher = (props: { soulcatcher: soulcatcherData | null; our_bellies: Required & { map(arg0: (belly: bellyData) => DropdownEntry): DropdownEntry[]; }; + abilities: abilities; }) => { - const { soulcatcher, our_bellies } = props; + const { soulcatcher, our_bellies, abilities } = props; const getBellies = our_bellies.map((belly) => { return { ...belly, displayText: belly.name, value: belly.ref }; @@ -33,7 +35,7 @@ export const VoreSoulcatcher = (props: { overlayBellies={getBellies} /> )} - + ); }; @@ -42,7 +44,7 @@ const VoreSoulcatcherSection = (props: { soulcatcher: soulcatcherData; overlayBellies: DropdownEntry[]; }) => { - const { act } = useBackend(); + const { act } = useBackend(); const { soulcatcher, overlayBellies } = props; @@ -54,12 +56,15 @@ const VoreSoulcatcherSection = (props: { interior_design, catch_self, catch_prey, + catch_drain, + catch_ghost, ext_hearing, ext_vision, mind_backups, ar_projecting, selected_sfx, show_vore_sfx, + taken_over, } = soulcatcher; return ( @@ -108,157 +113,21 @@ const VoreSoulcatcherSection = (props: { - {selected_soul ? ( - - - - - act('soulcatcher_delete')} - > - Delete - - - - ) : ( - '' - )} - {caught_souls.length ? ( - - - act('soulcatcher_release_all')} - > - Release Souls - - act('soulcatcher_erase_all')} - > - Erase Souls - - - - ) : ( - '' - )} - - - - - - - - - - - + {selected_soul ? : ''} + {caught_souls.length ? : ''} + + ); }; - -const VoreAbilities = (props) => { - const { act, data } = useBackend(); - const { nutrition, current_size, minimum_size, maximum_size, resize_cost } = - data.abilities; - - function is_enabled(nutri: number, cost: number): boolean { - return nutri >= cost; - } - - return ( -
- - - - - toFixed(value, 2) + '%'} - value={current_size * 100} - minValue={minimum_size * 100} - maxValue={maximum_size * 100} - onChange={(e, value: number) => - act('adjust_own_size', { - new_mob_size: value / 100, - }) - } - /> - -   Cost:  - {resize_cost} - - - -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/CatchSettings.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/CatchSettings.tsx new file mode 100644 index 0000000000..8cd73ab825 --- /dev/null +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/CatchSettings.tsx @@ -0,0 +1,70 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../../../backend'; +import { Box, Button, LabeledList } from '../../../../components'; + +export const CatchSettings = (props: { + catch_self: BooleanLike; + catch_prey: BooleanLike; + catch_drain: BooleanLike; + catch_ghost: BooleanLike; +}) => { + const { act } = useBackend(); + + const { catch_self, catch_prey, catch_drain, catch_ghost } = props; + + return ( + + + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalOptions.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalOptions.tsx new file mode 100644 index 0000000000..1fbe70d4a7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalOptions.tsx @@ -0,0 +1,51 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../../../backend'; +import { Box, Button, LabeledList } from '../../../../components'; + +export const GlobalOptions = (props: { taken_over: BooleanLike }) => { + const { act } = useBackend(); + + const { taken_over } = props; + + return ( + + + {!taken_over ? ( + <> + act('soulcatcher_release_all')} + > + Release Souls + + act('soulcatcher_erase_all')} + > + Erase Souls + + + ) : ( + + )} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalSettings.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalSettings.tsx new file mode 100644 index 0000000000..65e3baf6f6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/GlobalSettings.tsx @@ -0,0 +1,88 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../../../backend'; +import { Box, Button, LabeledList } from '../../../../components'; + +export const GlobalSettings = (props: { + ext_hearing: BooleanLike; + ext_vision: BooleanLike; + mind_backups: BooleanLike; + ar_projecting: BooleanLike; + show_vore_sfx: BooleanLike; +}) => { + const { act } = useBackend(); + + const { + ext_hearing, + ext_vision, + mind_backups, + ar_projecting, + show_vore_sfx, + } = props; + + return ( + + + + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/SoulOptions.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/SoulOptions.tsx new file mode 100644 index 0000000000..9131ac3696 --- /dev/null +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreSoulcatcherSettings/SoulOptions.tsx @@ -0,0 +1,59 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../../../backend'; +import { Box, Button, LabeledList } from '../../../../components'; + +export const SoulOptions = (props: { taken_over: BooleanLike }) => { + const { act } = useBackend(); + + const { taken_over } = props; + + return ( + + + {!taken_over ? ( + <> + + + act('soulcatcher_delete')} + > + Delete + + + ) : ( + '' + )} + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferences.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferences.tsx index d04ce9d009..a9e4e57443 100644 --- a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferences.tsx +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferences.tsx @@ -66,6 +66,7 @@ export const VoreUserPreferences = (props: { soulcatcher_allow_capture, soulcatcher_allow_transfer, soulcatcher_allow_deletion, + soulcatcher_allow_takeover, } = prefs; const preferences: localPrefs = { @@ -628,6 +629,20 @@ export const VoreUserPreferences = (props: { disabled: 'Do Not Allow Soul Capturing', }, }, + soulcatcher_takeover: { + action: 'toggle_soulcatcher_allow_takeover', + test: soulcatcher_allow_takeover, + tooltip: { + main: 'This button is for allowing or preventing to give body control to captured souls.', + enable: + 'Click here to allow body takeovers. (Both parties need it enabled to function)', + disable: 'Click here to prevent body takeovers.', + }, + content: { + enabled: 'Body Takeover Allowed', + disabled: 'Do Not Allow Body Takeover', + }, + }, soulcatcher_transfer: { action: 'toggle_soulcatcher_allow_transfer', test: soulcatcher_allow_transfer, diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesSoulcatcher.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesSoulcatcher.tsx index f4a5affb8e..f764e8ebf1 100644 --- a/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesSoulcatcher.tsx +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesSoulcatcher.tsx @@ -32,10 +32,16 @@ export const VoreUserPreferencesSoulcatcher = (props: { + + + ) : ( '' diff --git a/tgui/packages/tgui/interfaces/chompstation/VorePanel/index.tsx b/tgui/packages/tgui/interfaces/chompstation/VorePanel/index.tsx index dea76770ea..8a6c5b0a7a 100644 --- a/tgui/packages/tgui/interfaces/chompstation/VorePanel/index.tsx +++ b/tgui/packages/tgui/interfaces/chompstation/VorePanel/index.tsx @@ -157,6 +157,7 @@ export const VorePanel = () => { our_bellies, selected, soulcatcher, + abilities, prefs, show_pictures, icon_overflow, @@ -177,7 +178,11 @@ export const VorePanel = () => { /> ); tabs[1] = ( - + ); tabs[2] = (