diff --git a/code/__DEFINES/voreconstants.dm b/code/__DEFINES/voreconstants.dm
index d2eb034813..372c5b776c 100644
--- a/code/__DEFINES/voreconstants.dm
+++ b/code/__DEFINES/voreconstants.dm
@@ -8,17 +8,22 @@
#define DM_UNABSORB "Un-absorb"
#define DIGESTABLE (1<<0)
-#define SHOW_VORE_PREFS (1<<1)
-#define DEVOURABLE (1<<2)
-#define FEEDING (1<<3)
-#define NO_VORE (1<<4)
-#define OPEN_PANEL (1<<5)
-#define ABSORBED (1<<6)
-#define VORE_INIT (1<<7)
-#define VOREPREF_INIT (1<<8)
-#define LICKABLE (1<<9)
+#define DEVOURABLE (1<<1)
+#define FEEDING (1<<2)
+#define NO_VORE (1<<3)
+#define ABSORBED (1<<4)
+#define VORE_INIT (1<<5)
+#define VOREPREF_INIT (1<<6)
+#define LICKABLE (1<<7)
+/// Can be smelled?
+#define SMELLABLE (1<<8)
+/// Can get absorbed?
+#define ABSORBABLE (1<<9)
+/// Can get simplemob vored?
+#define MOBVORE (1<<10)
-#define MAX_VORE_FLAG (1<<10)-1 // change this whenever you add a vore flag, must be largest vore flag*2-1
+/// Change this whenever you add a vore flag, must be largest vore flag*2-1
+#define MAX_VORE_FLAG (1<<11)-1
#define isbelly(A) istype(A, /obj/belly)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 251bcc72ad..9f0ac7f183 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -187,6 +187,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/vore_flags = 0
var/list/belly_prefs = list()
var/vore_taste = "nothing in particular"
+ var/vore_smell = null
var/toggleeatingnoise = TRUE
var/toggledigestionnoise = TRUE
var/hound_sleeper = TRUE
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 45c5357c4d..d4d13dc40f 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -821,6 +821,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["vore_flags"] >> vore_flags
S["vore_taste"] >> vore_taste
+ S["vore_smell"] >> vore_smell
var/char_vr_path = "[vr_path]/character_[default_slot]_v2.json"
if(fexists(char_vr_path))
var/list/json_from_file = json_decode(file2text(char_vr_path))
@@ -994,6 +995,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
vore_flags = sanitize_integer(vore_flags, 0, MAX_VORE_FLAG, 0)
vore_taste = copytext(vore_taste, 1, MAX_TASTE_LEN)
+ vore_smell = copytext(vore_smell, 1, MAX_TASTE_LEN)
belly_prefs = SANITIZE_LIST(belly_prefs)
cit_character_pref_load(S)
@@ -1147,6 +1149,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["vore_flags"] , vore_flags)
WRITE_FILE(S["vore_taste"] , vore_taste)
+ WRITE_FILE(S["vore_smell"] , vore_smell)
var/char_vr_path = "[vr_path]/character_[default_slot]_v2.json"
var/belly_prefs_json = safe_json_encode(list("belly_prefs" = belly_prefs))
if(fexists(char_vr_path))
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 95f8f2acc1..01a30bb90a 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -364,11 +364,10 @@
/mob/living/simple_animal/hostile/proc/AttackingTarget()
SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target)
in_melee = TRUE
- /* sorry for the simplemob vore fans
if(vore_active)
if(isliving(target))
var/mob/living/L = target
- if(!client && L.Adjacent(src) && CHECK_BITFIELD(L.vore_flags,DEVOURABLE)) // aggressive check to ensure vore attacks can be made
+ if(!client && L.Adjacent(src) && CHECK_BITFIELD(L.vore_flags, DEVOURABLE) && CHECK_BITFIELD(L.vore_flags, MOBVORE)) // aggressive check to ensure vore attacks can be made
if(prob(voracious_chance))
vore_attack(src,L,src)
else
@@ -379,7 +378,6 @@
return target.attack_animal(src)
else
return target.attack_animal(src)
- */
return target.attack_animal(src)
/mob/living/simple_animal/hostile/proc/Aggro()
diff --git a/code/modules/tgui/states/vorepanel.dm b/code/modules/tgui/states/vorepanel.dm
new file mode 100644
index 0000000000..b68dfb970a
--- /dev/null
+++ b/code/modules/tgui/states/vorepanel.dm
@@ -0,0 +1,18 @@
+ /**
+ * tgui state: vorepanel_state
+ *
+ * Only checks that the user and src_object are the same.
+ **/
+
+GLOBAL_DATUM_INIT(ui_vorepanel_state, /datum/ui_state/vorepanel_state, new)
+
+/datum/ui_state/vorepanel_state/can_use_topic(src_object, mob/user)
+ if(src_object != user)
+ // Note, in order to allow others to look at others vore panels, change this to
+ // UI_UPDATE
+ return UI_CLOSE
+ if(!user.client)
+ return UI_CLOSE
+ if(user.stat == DEAD)
+ return UI_DISABLED
+ return UI_INTERACTIVE
diff --git a/code/modules/tgui/tgui_alert.dm b/code/modules/tgui/tgui_alert.dm
index 1a86cca705..d144588ad9 100644
--- a/code/modules/tgui/tgui_alert.dm
+++ b/code/modules/tgui/tgui_alert.dm
@@ -9,7 +9,7 @@
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
*/
-/proc/tgui_alert(mob/user, message, title, list/buttons, timeout = 60 SECONDS)
+/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0)
if (!user)
user = usr
if (!istype(user))
@@ -35,9 +35,9 @@
* * title - The of the alert modal, shown on the top of the TGUI window.
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * callback - The callback to be invoked when a choice is made.
- * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Disabled by default, can be set to seconds otherwise.
*/
-/proc/tgui_alert_async(mob/user, message, title, list/buttons, datum/callback/callback, timeout = 60 SECONDS)
+/proc/tgui_alert_async(mob/user, message = null, title = null, list/buttons = list("Ok"), datum/callback/callback, timeout = 0)
if (!user)
user = usr
if (!istype(user))
@@ -90,7 +90,7 @@
* the window was closed by the user.
*/
/datum/tgui_modal/proc/wait()
- while (!choice && !closed)
+ while (!choice && !closed && !QDELETED(src))
stoplag(1)
/datum/tgui_modal/ui_interact(mob/user, datum/tgui/ui)
@@ -124,10 +124,13 @@
if("choose")
if (!(params["choice"] in buttons))
return
- choice = params["choice"]
+ set_choice(params["choice"])
SStgui.close_uis(src)
return TRUE
+/datum/tgui_modal/proc/set_choice(choice)
+ src.choice = choice
+
/**
* # async tgui_modal
*
@@ -138,23 +141,17 @@
var/datum/callback/callback
/datum/tgui_modal/async/New(mob/user, message, title, list/buttons, callback, timeout)
- ..(user, title, message, buttons, timeout)
+ ..(user, message, title, buttons, timeout)
src.callback = callback
/datum/tgui_modal/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
-/datum/tgui_modal/async/ui_close(mob/user)
+/datum/tgui_modal/async/set_choice(choice)
. = ..()
- qdel(src)
-
-/datum/tgui_modal/async/ui_act(action, list/params)
- . = ..()
- if (!. || choice == null)
- return
- callback.InvokeAsync(choice)
- qdel(src)
+ if(!isnull(src.choice))
+ callback?.InvokeAsync(src.choice)
/datum/tgui_modal/async/wait()
return
diff --git a/code/modules/vore/eating/bellymodes.dm b/code/modules/vore/eating/bellymodes.dm
index 7cf36ebc50..8d64ee28f1 100644
--- a/code/modules/vore/eating/bellymodes.dm
+++ b/code/modules/vore/eating/bellymodes.dm
@@ -150,7 +150,7 @@
SEND_SOUND(M,prey_digest)
play_sound = pick(pred_digest)
- if(M.vore_flags & ABSORBED)
+ if(M.vore_flags & ABSORBED || !(M.vore_flags & ABSORBABLE)) //Negative.
continue
if(M.nutrition >= 100) //Drain them until there's no nutrients left. Slowly "absorb" them.
diff --git a/code/modules/vore/eating/living.dm b/code/modules/vore/eating/living.dm
index ca5ee0f476..6bc44e6489 100644
--- a/code/modules/vore/eating/living.dm
+++ b/code/modules/vore/eating/living.dm
@@ -1,17 +1,24 @@
///////////////////// Mob Living /////////////////////
/mob/living
var/vore_flags = 0
- var/showvoreprefs = TRUE // Determines if the mechanical vore preferences button will be displayed on the mob or not.
- var/obj/belly/vore_selected // Default to no vore capability.
- var/list/vore_organs = list() // List of vore containers inside a mob
- var/vore_taste = null // What the character tastes like
+ // Determines if the mechanical vore preferences button will be displayed on the mob or not.
+ var/showvoreprefs = TRUE
+ /// Default to no vore capability.
+ var/obj/belly/vore_selected
+ /// List of vore containers inside a mob
+ var/list/vore_organs = list()
+ /// What the character tastes like
+ var/vore_taste = null
+ /// What the character smells like
+ var/vore_smell = null
+ /// Next time vore sounds get played for the prey, do not change manually as it is intended to be set automatically
var/next_preyloop
//
// Hook for generic creation of stuff on new creatures
//
/hook/living_new/proc/vore_setup(mob/living/M)
- add_verb(M, list(/mob/living/proc/preyloop_refresh, /mob/living/proc/lick, /mob/living/proc/escapeOOC))
+ add_verb(M, list(/mob/living/proc/preyloop_refresh, /mob/living/proc/lick, /mob/living/proc/smell, /mob/living/proc/escapeOOC))
if(M.vore_flags & NO_VORE) //If the mob isn't supposed to have a stomach, let's not give it an insidepanel so it can make one for itself, or a stomach.
return TRUE
@@ -256,6 +263,7 @@
client.prefs.vore_flags = vore_flags // there's garbage data in here, but it doesn't matter
client.prefs.vore_taste = vore_taste
+ client.prefs.vore_smell = vore_smell
var/list/serialized = list()
for(var/belly in vore_organs)
@@ -264,6 +272,8 @@
client.prefs.belly_prefs = serialized
+ client.prefs.save_character()
+
return TRUE
//
@@ -274,8 +284,9 @@
to_chat(src,"You attempted to apply your vore prefs but somehow you're in this character without a client.prefs variable. Tell a dev. ")
return FALSE
ENABLE_BITFIELD(vore_flags,VOREPREF_INIT)
- COPY_SPECIFIC_BITFIELDS(vore_flags,client.prefs.vore_flags,DIGESTABLE | DEVOURABLE | FEEDING | LICKABLE)
+ COPY_SPECIFIC_BITFIELDS(vore_flags, client.prefs.vore_flags, DIGESTABLE | DEVOURABLE | FEEDING | LICKABLE | SMELLABLE | ABSORBABLE | MOBVORE)
vore_taste = client.prefs.vore_taste
+ vore_smell = client.prefs.vore_smell
release_vore_contents(silent = TRUE)
QDEL_LIST(vore_organs)
@@ -379,6 +390,56 @@
else
taste_message += "a plain old normal [src]"
return taste_message
+
+//
+// Equally important as the above
+//
+/mob/living/proc/smell()
+ set name = "Smell Someone"
+ set category = "Vore"
+ set desc = "Smell someone nearby!"
+
+ if(incapacitated(ignore_restraints = TRUE))
+ to_chat(src, "You can't do that while incapacitated. ")
+ return
+ if(!CheckActionCooldown())
+ to_chat(src, "You can't do that so fast, slow down. ")
+ return
+
+ DelayNextAction(CLICK_CD_MELEE, flush = TRUE)
+
+ var/list/smellable = list()
+ for(var/mob/living/L in view(1))
+ if(L != src && (!L.ckey || L.client?.prefs.vore_flags & SMELLABLE) && Adjacent(L))
+ LAZYADD(smellable, L)
+ for(var/mob/living/listed in smellable)
+ smellable[listed] = new /mutable_appearance(listed)
+
+ if(!smellable)
+ return
+
+ var/mob/living/sniffed = show_radial_menu(src, src, smellable, radius = 40, require_near = TRUE)
+
+ if(QDELETED(sniffed) || (sniffed.ckey && !(sniffed.client?.prefs.vore_flags & SMELLABLE)) || !Adjacent(sniffed) || incapacitated(ignore_restraints = TRUE))
+ return
+
+ visible_message("[src] smells [sniffed]! ","You smell [sniffed]. They smell like [sniffed.get_smell_message()]. ","Sniff! ")
+
+/mob/living/proc/get_smell_message(allow_generic = TRUE, datum/species/mrace)
+ if(!vore_smell && !allow_generic)
+ return FALSE
+
+ var/smell_message = ""
+ if(vore_smell && (vore_smell != ""))
+ smell_message += "[vore_smell]"
+ else
+ if(ishuman(src))
+ var/mob/living/carbon/human/H = src
+ smell_message += "a normal [H.custom_species ? H.custom_species : H.dna.species]"
+ else
+ smell_message += "a plain old normal [src]"
+ return smell_message
+
// Check if an object is capable of eating things, based on vore_organs
//
/proc/has_vore_belly(var/mob/living/O)
diff --git a/code/modules/vore/eating/vorepanel.dm b/code/modules/vore/eating/vorepanel.dm
index aac8cbe745..a3adc89352 100644
--- a/code/modules/vore/eating/vorepanel.dm
+++ b/code/modules/vore/eating/vorepanel.dm
@@ -2,746 +2,730 @@
// Vore management panel for players
//
-#define BELLIES_MAX 20
+#define BELLIES_MAX 40
#define BELLIES_NAME_MIN 2
-#define BELLIES_NAME_MAX 24
+#define BELLIES_NAME_MAX 40
#define BELLIES_DESC_MAX 4096
+#define FLAVOR_MAX 400
+
+/mob/living
+ var/datum/vore_look/vorePanel
/mob/living/proc/insidePanel()
set name = "Vore Panel"
set category = "Vore"
- var/datum/vore_look/picker_holder = new()
- picker_holder.loop = picker_holder
- picker_holder.selected = vore_selected
+ if(!vorePanel)
+ log_game("VORE: [src] ([type], \ref[src]) didn't have a vorePanel and tried to use the verb.")
+ vorePanel = new(src)
- var/dat = picker_holder.gen_vui(src)
-
- picker_holder.popup = new(src, "insidePanel","Vore Panel", 450, 700, picker_holder)
- picker_holder.popup.set_content(dat)
- picker_holder.popup.open()
- vore_flags |= OPEN_PANEL
+ vorePanel.ui_interact(src)
/mob/living/proc/updateVRPanel() //Panel popup update call from belly events.
- if(vore_flags & OPEN_PANEL)
- var/datum/vore_look/picker_holder = new()
- picker_holder.loop = picker_holder
- picker_holder.selected = vore_selected
-
- var/dat = picker_holder.gen_vui(src)
-
- picker_holder.popup = new(src, "insidePanel","Vore Panel", 450, 700, picker_holder)
- picker_holder.popup.set_content(dat)
- picker_holder.popup.open()
+ SStgui.update_uis(vorePanel)
//
// Callback Handler for the Inside form
//
/datum/vore_look
- var/obj/belly/selected
- var/show_interacts = FALSE
- var/datum/browser/popup
- var/loop = null; // Magic self-reference to stop the handler from being GC'd before user takes action.
+ var/mob/living/host // Note, we do this in case we ever want to allow people to view others vore panels
+ var/unsaved_changes = FALSE
+ var/show_pictures = TRUE
+
+/datum/vore_look/New(mob/living/new_host)
+ if(istype(new_host))
+ host = new_host
+ . = ..()
/datum/vore_look/Destroy()
- loop = null
- selected = null
- ..() //this is a must
- return QDEL_HINT_HARDDEL
+ host = null
+ . = ..()
-/datum/vore_look/Topic(href,href_list[])
- if (vp_interact(href, href_list))
- popup.set_content(gen_vui(usr))
- usr << output(popup.get_content(), "insidePanel.browser")
+/datum/vore_look/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "VorePanel", "Vore Panel")
+ ui.open()
-/datum/vore_look/proc/gen_vui(var/mob/living/user)
- var/dat
- dat += "Remember to toggle the vore mode, it's to the left of your combat toggle. Open mouth means you're voracious! "
- dat += "Remember that the prey is blind, use audible mode subtle messages to communicate to them with posts! "
- dat += "
"
- var/atom/userloc = user.loc
- if (isbelly(userloc))
- var/obj/belly/inside_belly = userloc
- var/mob/living/eater = inside_belly.owner
+// This looks weird, but all tgui_host is used for is state checking
+// So this allows us to use the self_state just fine.
+/datum/vore_look/ui_host(mob/user)
+ return host
- //Don't display this part if we couldn't find the belly since could be held in hand.
- if(inside_belly)
- dat += "You are currently [(user.vore_flags & ABSORBED) ? "absorbed into " : "inside "] [eater]'s [inside_belly] ! "
+// Note, in order to allow others to look at others vore panels, this state would need
+// to be modified.
+/datum/vore_look/ui_state(mob/user)
+ return GLOB.ui_vorepanel_state
- if(inside_belly.desc)
- dat += "[inside_belly.desc] "
+/datum/vore_look
+ var/static/list/nom_icons
- if (inside_belly.contents.len > 1)
- dat += "You can see the following around you: "
- for (var/atom/movable/O in inside_belly)
- if(istype(O,/mob/living))
- var/mob/living/M = O
- //That's just you
- if(M == user)
- continue
+/datum/vore_look/proc/cached_nom_icon(atom/target)
+ LAZYINITLIST(nom_icons)
- //That's an absorbed person you're checking
- if(M.vore_flags & ABSORBED)
- if(user.vore_flags & ABSORBED)
- dat += "[O] "
- continue
- else
- continue
-
- //Anything else
- dat += "[O] "
-
- //Zero-width space, for wrapping
- dat += ""
+ var/key = ""
+ if(isobj(target))
+ key = "[target.type]"
+ else if(ismob(target))
+ var/mob/M = target
+ key = "\ref[target][M.real_name]"
+ if(nom_icons[key])
+ . = nom_icons[key]
else
- dat += "You aren't inside anyone."
-
- dat += " "
-
- dat += ""
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(B == selected)
- dat += "[B.name] "
- else
- dat += "[B.name]"
- var/spanstyle
- switch(B.digest_mode)
- if(DM_HOLD)
- spanstyle = ""
- if(DM_DIGEST)
- spanstyle = "color:red;"
- if(DM_HEAL)
- spanstyle = "color:darkgreen;"
- if(DM_NOISY)
- spanstyle = "color:purple;"
- if(DM_ABSORB)
- spanstyle = "color:purple;"
- if(DM_DRAGON)
- spanstyle = "color:blue;"
-
- dat += " ([B.contents.len]) "
-
- if(user.vore_organs.len < BELLIES_MAX)
- dat += "New+ "
- dat += " "
- dat += " "
-
- // Selected Belly (contents, configuration)
- if(!selected)
- dat += "No belly selected. Click one to select it."
- else
- if(selected.contents.len)
- dat += "Contents: "
- for(var/O in selected)
-
- //Mobs can be absorbed, so treat them separately from everything else
- if(istype(O,/mob/living))
- var/mob/living/M = O
-
- //Absorbed gets special color OOoOOOOoooo
- if(M.vore_flags & ABSORBED)
- dat += "[O] "
- continue
-
- //Anything else
- dat += "[O] "
-
- //Zero-width space, for wrapping
- dat += ""
-
- //If there's more than one thing, add an [All] button
- if(selected.contents.len > 1)
- dat += "\[All\] "
-
- dat += " "
-
- //Belly Name Button
- dat += "Name: "
- dat += " '[selected.name]'"
-
- //Belly Type button
- dat += "Is Fleshy: "
- dat += "[selected.is_wet ? "Yes" : "No"]"
- if(selected.is_wet)
- dat += "Internal loop for prey?: "
- dat += "[selected.wet_loop ? "Yes" : "No"]"
-
- //Digest Mode Button
- dat += "Belly Mode: "
- dat += " [selected.digest_mode]"
-
- //Belly verb
- dat += "Vore Verb: "
- dat += " '[selected.vore_verb]'"
-
- //Inside flavortext
- dat += "Flavor Text: "
- dat += " '[selected.desc]'"
-
- //Belly sound
- dat += "Vore Sound: [selected.vore_sound] "
- dat += "Test "
-
- //Release sound
- dat += "Release Sound: [selected.release_sound] "
- dat += "Test "
-
- //Belly messages
- dat += "Belly Messages "
-
- //Can belly taste?
- dat += "Can Taste: "
- dat += " [selected.can_taste ? "Yes" : "No"]"
-
- //Minimum size prey must be to show up.
- dat += "Required examine size: "
- dat += " [selected.bulge_size*100]%"
-
- //Belly escapability
- dat += "Belly Interactions ([selected.escapable ? "On" : "Off"]) "
- if(selected.escapable)
- dat += "[show_interacts ? "Hide" : "Show"] "
-
- if(show_interacts && selected.escapable)
- dat += " "
- dat += "Interaction Settings ? "
- dat += "Set Belly Escape Chance "
- dat += " [selected.escapechance]%"
-
- dat += "Set Belly Escape Time "
- dat += " [selected.escapetime/10]s"
-
- //Special here to add a gap
- dat += " "
- dat += "Set Belly Transfer Chance "
- dat += " [selected.transferchance]%"
-
- dat += "Set Belly Transfer Location "
- dat += " [selected.transferlocation ? selected.transferlocation : "Disabled"]"
-
- //Special here to add a gap
- dat += " "
- dat += "Set Belly Absorb Chance "
- dat += " [selected.absorbchance]%"
-
- dat += "Set Belly Digest Chance "
- dat += " [selected.digestchance]%"
- dat += " "
-
- //Delete button
- dat += "Delete Belly "
-
- dat += "Set Flavor "
-
- dat += " "
-
- //Under the last HR, save and stuff.
- dat += "Save Prefs "
- dat += "Refresh "
- dat += "Reload Slot Prefs "
-
- dat += " "
- var/pref_on = "#173d15"
- var/pref_off = "#990000"
- dat += "Toggle Digestable (Currently: [(user.vore_flags & DIGESTABLE) ? "ON" : "OFF"]) "
- dat += "Toggle Devourable (Currently: [(user.vore_flags & DEVOURABLE) ? "ON" : "OFF"]) "
- dat += "Toggle Feeding (Currently: [(user.vore_flags & FEEDING) ? "ON" : "OFF"]) "
- if(user.client.prefs)
- dat += "Toggle Licking (Currently: [(user.client.prefs.vore_flags & LICKABLE) ? "ON" : "OFF"]) "
- //Returns the dat html to the vore_look
- return dat
-
-/datum/vore_look/proc/vp_interact(href, href_list)
- var/mob/living/user = usr
- for(var/H in href_list)
-
- if(href_list["close"])
- qdel(src) // Cleanup
- user.vore_flags &= ~OPEN_PANEL
- return
-
- if(href_list["show_int"])
- show_interacts = !show_interacts
- return TRUE //Force update
-
- if(href_list["int_help"])
- alert("These control how your belly responds to someone using 'resist' while inside you. The percent chance to trigger each is listed below, \
- and you can change them to whatever you see fit. Setting them to 0% will disable the possibility of that interaction. \
- These only function as long as interactions are turned on in general. Keep in mind, the 'belly mode' interactions (digest/absorb) \
- will affect all prey in that belly, if one resists and triggers digestion/absorption. If multiple trigger at the same time, \
- only the first in the order of 'Escape > Transfer > Absorb > Digest' will occur.","Interactions Help")
- return FALSE //Force update
-
- if(href_list["outsidepick"])
- var/atom/movable/tgt = locate(href_list["outsidepick"])
- var/obj/belly/OB = locate(href_list["outsidebelly"])
- if(!istype(OB))
- return
- if(!(tgt in OB)) //Aren't here anymore, need to update menu.
- return TRUE
- var/intent = "Examine"
-
- if(istype(tgt,/mob/living))
- var/mob/living/M = tgt
- intent = alert("What do you want to do to them?","Query","Examine","Help Out","Devour")
- switch(intent)
- if("Examine") //Examine a mob inside another mob
- M.examine(user)
-
- if("Help Out") //Help the inside-mob out
- if(user.stat || user.vore_flags & ABSORBED || M.vore_flags & ABSORBED)
- to_chat(user,"You can't do that in your state! ")
- return TRUE
-
- to_chat(user,"You begin to push [M] to freedom! ")
- to_chat(M,"[usr] begins to push you to freedom!")
- to_chat(M.loc,"Someone is trying to escape from inside you! ")
- sleep(50)
- if(prob(33))
- OB.release_specific_contents(M)
- to_chat(usr,"You manage to help [M] to safety! ")
- to_chat(M,"[user] pushes you free! ")
- to_chat(OB.owner,"[M] forces free of the confines of your body! ")
- else
- to_chat(user,"[M] slips back down inside despite your efforts. ")
- to_chat(M," Even with [user]'s help, you slip back inside again. ")
- to_chat(OB.owner,"Your body efficiently shoves [M] back where they belong. ")
-
- if("Devour") //Eat the inside mob
- if(user.vore_flags & ABSORBED || user.stat)
- to_chat(user,"You can't do that in your state! ")
- return TRUE
-
- if(!user.vore_selected)
- to_chat(user,"Pick a belly on yourself first! ")
- return TRUE
-
- var/obj/belly/TB = user.vore_selected
- to_chat(user,"You begin to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]! ")
- to_chat(M,"[user] begins to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]! ")
- to_chat(OB.owner,"Someone inside you is eating someone else! ")
-
- sleep(TB.nonhuman_prey_swallow_time) //Can't do after, in a stomach, weird things abound.
- if((user in OB) && (M in OB)) //Make sure they're still here.
- to_chat(user,"You manage to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]! ")
- to_chat(M,"[user] manages to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]! ")
- to_chat(OB.owner,"Someone inside you has eaten someone else! ")
- TB.nom_mob(M)
-
- else if(istype(tgt,/obj/item))
- var/obj/item/T = tgt
- if(!(tgt in OB))
- //Doesn't exist anymore, update.
- return TRUE
- intent = alert("What do you want to do to that?","Query","Examine","Use Hand")
- switch(intent)
- if("Examine")
- T.examine(user)
-
- if("Use Hand")
- if(user.stat)
- to_chat(user,"You can't do that in your state! ")
- return TRUE
-
- user.ClickOn(T)
- sleep(5) //Seems to exit too fast for the panel to update
-
- if(href_list["insidepick"])
- var/intent
-
- //Handle the [All] choice. Ugh inelegant. Someone make this pretty.
- if(href_list["pickall"])
- intent = alert("Eject all, Move all?","Query","Eject all","Cancel","Move all")
- switch(intent)
- if("Cancel")
- return FALSE
-
- if("Eject all")
- if(user.stat)
- to_chat(user,"You can't do that in your state! ")
- return FALSE
-
- selected.release_all_contents()
-
- if("Move all")
- if(user.stat)
- to_chat(user,"You can't do that in your state! ")
- return FALSE
-
- var/obj/belly/choice = input("Move all where?","Select Belly") as null|anything in user.vore_organs
- if(!choice)
- return FALSE
-
- for(var/atom/movable/tgt in selected)
- to_chat(tgt,"You're squished from [user]'s [lowertext(selected)] to their [lowertext(choice.name)]! ")
- selected.transfer_contents(tgt, choice, 1)
-
- var/atom/movable/tgt = locate(href_list["insidepick"])
- if(!(tgt in selected)) //Old menu, needs updating because they aren't really there.
- return TRUE //Forces update
- intent = "Examine"
- intent = alert("Examine, Eject, Move? Examine if you want to leave this box.","Query","Examine","Eject","Move")
- switch(intent)
- if("Examine")
- tgt.examine(user)
-
- if("Eject")
- if(user.stat)
- to_chat(user,"You can't do that in your state! ")
- return FALSE
-
- selected.release_specific_contents(tgt)
-
- if("Move")
- if(user.stat)
- to_chat(user,"You can't do that in your state! ")
- return FALSE
-
- var/obj/belly/choice = input("Move [tgt] where?","Select Belly") as null|anything in user.vore_organs
- if(!choice || !(tgt in selected))
- return FALSE
-
- to_chat(tgt,"You're squished from [user]'s [lowertext(selected.name)] to their [lowertext(choice.name)]! ")
- selected.transfer_contents(tgt, choice)
-
- if(href_list["newbelly"])
- if(user.vore_organs.len >= BELLIES_MAX)
- return FALSE
-
- var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null)
-
- var/failure_msg
- if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
- failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
- // else if(whatever) //Next test here.
- else
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(lowertext(new_name) == lowertext(B.name))
- failure_msg = "No duplicate belly names, please."
- break
-
- if(failure_msg) //Something went wrong.
- alert(user,failure_msg,"Error!")
- return FALSE
-
- var/obj/belly/NB = new(user)
- NB.name = new_name
- selected = NB
-
- if(href_list["bellypick"])
- selected = locate(href_list["bellypick"])
- user.vore_selected = selected
-
- ////
- //Please keep these the same order they are on the panel UI for ease of coding
- ////
- if(href_list["b_name"])
- var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null)
-
- var/failure_msg
- if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
- failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
- // else if(whatever) //Next test here.
- else
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(lowertext(new_name) == lowertext(B.name))
- failure_msg = "No duplicate belly names, please."
- break
-
- if(failure_msg) //Something went wrong.
- alert(user,failure_msg,"Error!")
- return FALSE
-
- selected.name = new_name
-
- if(href_list["b_wetness"])
- selected.is_wet = !selected.is_wet
-
- if(href_list["b_wetloop"])
- selected.wet_loop = !selected.wet_loop
-
- if(href_list["b_mode"])
- var/list/menu_list = selected.digest_modes
-
- var/new_mode = input("Choose Mode (currently [selected.digest_mode])") as null|anything in menu_list
- if(!new_mode)
- return FALSE
- selected.digest_mode = new_mode
-
- if(href_list["b_desc"])
- var/new_desc = html_encode(input(usr,"Belly Description ([BELLIES_DESC_MAX] char limit):","New Description",selected.desc) as message|null)
-
- if(new_desc)
- new_desc = readd_quotes(new_desc)
- if(length(new_desc) > BELLIES_DESC_MAX)
- alert("Entered belly desc too long. [BELLIES_DESC_MAX] character limit.","Error")
- return FALSE
- selected.desc = new_desc
- else //Returned null
- return FALSE
-
- if(href_list["b_msgs"])
- var/list/messages = list(
- "Digest Message (to prey)",
- "Digest Message (to you)",
- "Struggle Message (outside)",
- "Struggle Message (inside)",
- "Examine Message (when full)",
- "Reset All To Default"
+ . = icon2base64(getFlatIcon(target,defdir=SOUTH,no_anim=TRUE))
+ nom_icons[key] = .
+
+/datum/vore_look/ui_data(mob/user)
+ var/list/data = list()
+
+ if(!host)
+ return data
+
+ data["unsaved_changes"] = unsaved_changes
+ data["show_pictures"] = show_pictures
+
+ var/atom/hostloc = host.loc
+ var/list/inside = list()
+ if(isbelly(hostloc))
+ var/obj/belly/inside_belly = hostloc
+ var/mob/living/pred = inside_belly.owner
+
+ inside = list(
+ "absorbed" = host.vore_flags & ABSORBED,
+ "belly_name" = inside_belly.name,
+ "belly_mode" = inside_belly.digest_mode,
+ "desc" = inside_belly.desc || "No description.",
+ "pred" = pred,
+ "ref" = "\ref[inside_belly]",
)
- alert(user,"Setting abusive or deceptive messages will result in a ban. Consider this your warning. Max 150 characters per message, max 10 messages per topic.","Really, don't.")
- var/choice = input(user,"Select a type to modify. Messages from each topic are pulled at random when needed.","Pick Type") as null|anything in messages
- var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name."
+ var/list/inside_contents = list()
+ for(var/atom/movable/O in inside_belly)
+ if(O == host)
+ continue
- switch(choice)
- if("Digest Message (to prey)")
- var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",selected.get_messages("dmp")) as message
- if(new_message)
- selected.set_messages(new_message,"dmp")
+ var/list/info = list(
+ "name" = "[O]",
+ "absorbed" = FALSE,
+ "stat" = 0,
+ "ref" = "\ref[O]",
+ "outside" = FALSE,
+ )
+ if(show_pictures)
+ info["icon"] = cached_nom_icon(O)
+ if(isliving(O))
+ var/mob/living/M = O
+ info["stat"] = M.stat
+ if(M.vore_flags & ABSORBED)
+ info["absorbed"] = TRUE
+ inside_contents.Add(list(info))
+ inside["contents"] = inside_contents
+ data["inside"] = inside
- if("Digest Message (to you)")
- var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",selected.get_messages("dmo")) as message
- if(new_message)
- selected.set_messages(new_message,"dmo")
+ var/list/our_bellies = list()
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ our_bellies.Add(list(list(
+ "selected" = (B == host.vore_selected),
+ "name" = B.name,
+ "ref" = "\ref[B]",
+ "digest_mode" = B.digest_mode,
+ "contents" = LAZYLEN(B.contents),
+ )))
+ data["our_bellies"] = our_bellies
- if("Struggle Message (outside)")
- var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",selected.get_messages("smo")) as message
- if(new_message)
- selected.set_messages(new_message,"smo")
+ var/list/selected_list = null
+ if(host.vore_selected)
+ var/obj/belly/selected = host.vore_selected
+ selected_list = list(
+ "belly_name" = selected.name,
+ "is_wet" = selected.is_wet,
+ "wet_loop" = selected.wet_loop,
+ "mode" = selected.digest_mode,
+ "verb" = selected.vore_verb,
+ "desc" = selected.desc,
+ "sound" = selected.vore_sound,
+ "release_sound" = selected.release_sound,
+ "can_taste" = selected.can_taste,
+ "bulge_size" = selected.bulge_size,
+ )
- if("Struggle Message (inside)")
- var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",selected.get_messages("smi")) as message
- if(new_message)
- selected.set_messages(new_message,"smi")
+ selected_list["escapable"] = selected.escapable
+ selected_list["interacts"] = list()
+ if(selected.escapable)
+ selected_list["interacts"]["escapechance"] = selected.escapechance
+ selected_list["interacts"]["escapetime"] = selected.escapetime
+ selected_list["interacts"]["transferchance"] = selected.transferchance
+ selected_list["interacts"]["transferlocation"] = selected.transferlocation
+ selected_list["interacts"]["absorbchance"] = selected.absorbchance
+ selected_list["interacts"]["digestchance"] = selected.digestchance
- if("Examine Message (when full)")
- var/new_message = input(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",selected.get_messages("em")) as message
- if(new_message)
- selected.set_messages(new_message,"em")
+ var/list/selected_contents = list()
+ for(var/O in selected)
+ var/list/info = list(
+ "name" = "[O]",
+ "absorbed" = FALSE,
+ "stat" = 0,
+ "ref" = "\ref[O]",
+ "outside" = TRUE,
+ )
+ if(show_pictures)
+ info["icon"] = cached_nom_icon(O)
+ if(isliving(O))
+ var/mob/living/M = O
+ info["stat"] = M.stat
+ if(M.vore_flags & ABSORBED)
+ info["absorbed"] = TRUE
+ selected_contents.Add(list(info))
+ selected_list["contents"] = selected_contents
- if("Reset All To Default")
- var/confirm = alert(user,"This will delete any custom messages. Are you sure?","Confirmation","DELETE","Cancel")
- if(confirm == "DELETE")
- selected.digest_messages_prey = initial(selected.digest_messages_prey)
- selected.digest_messages_owner = initial(selected.digest_messages_owner)
- selected.struggle_messages_outside = initial(selected.struggle_messages_outside)
- selected.struggle_messages_inside = initial(selected.struggle_messages_inside)
+ data["selected"] = selected_list
+ data["prefs"] = list(
+ "digestable" = CHECK_BITFIELD(host.vore_flags, DIGESTABLE),
+ "devourable" = CHECK_BITFIELD(host.vore_flags, DEVOURABLE),
+ "feeding" = CHECK_BITFIELD(host.vore_flags, FEEDING),
+ "absorbable" = CHECK_BITFIELD(host.vore_flags, ABSORBABLE),
+ "allowmobvore" = CHECK_BITFIELD(host.vore_flags, MOBVORE),
+ "vore_sounds" = CHECK_BITFIELD(host.client.prefs.cit_toggles, EATING_NOISES),
+ "digestion_sounds" = CHECK_BITFIELD(host.client.prefs.cit_toggles, DIGESTION_NOISES),
+ "lickable" = CHECK_BITFIELD(host.vore_flags, LICKABLE),
+ "smellable" = CHECK_BITFIELD(host.vore_flags, SMELLABLE),
+ )
- if(href_list["b_verb"])
- var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null)
+ return data
- if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN)
- alert("Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error")
- return FALSE
+/datum/vore_look/ui_act(action, params)
+ if(..())
+ return TRUE
- selected.vore_verb = new_verb
+ switch(action)
+ if("show_pictures")
+ show_pictures = !show_pictures
+ return TRUE
+ if("int_help")
+ tgui_alert(usr, "These control how your belly responds to someone using 'resist' while inside you. The percent chance to trigger each is listed below, \
+ and you can change them to whatever you see fit. Setting them to 0% will disable the possibility of that interaction. \
+ These only function as long as interactions are turned on in general. Keep in mind, the 'belly mode' interactions (digest/absorb) \
+ will affect all prey in that belly, if one resists and triggers digestion/absorption. If multiple trigger at the same time, \
+ only the first in the order of 'Escape > Transfer > Absorb > Digest' will occur.","Interactions Help")
+ return TRUE
- if(href_list["b_release"])
- var/choice = input(user,"Currently set to [selected.release_sound]","Select Sound") as null|anything in GLOB.pred_release_sounds
+ // Host is inside someone else, and is trying to interact with something else inside that person.
+ if("pick_from_inside")
+ return pick_from_inside(usr, params)
- if(!choice)
- return
+ // Host is trying to interact with something in host's belly.
+ if("pick_from_outside")
+ return pick_from_outside(usr, params)
- selected.release_sound = choice
-
- if(href_list["b_releasesoundtest"])
- var/sound/releasetest = GLOB.prey_release_sounds[selected.release_sound]
- if(releasetest)
- SEND_SOUND(user, releasetest)
-
- if(href_list["b_sound"])
- var/choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") as null|anything in GLOB.pred_vore_sounds
-
- if(!choice)
- return
-
- selected.vore_sound = choice
-
- if(href_list["b_soundtest"])
- var/sound/voretest = GLOB.prey_vore_sounds[selected.vore_sound]
- if(voretest)
- SEND_SOUND(user, voretest)
-
- if(href_list["b_tastes"])
- selected.can_taste = !selected.can_taste
-
- if(href_list["b_bulge_size"])
- var/new_bulge = input(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.") as num|null
- if(new_bulge == null)
- return
- if(new_bulge == 0) //Disable.
- selected.bulge_size = 0
- to_chat(user,"Your stomach will not be seen on examine. ")
- else if (!ISINRANGE(new_bulge,25,200))
- selected.bulge_size = 0.25 //Set it to the default.
- to_chat(user,"Invalid size. ")
- else if(new_bulge)
- selected.bulge_size = (new_bulge/100)
-
- if(href_list["b_escapable"])
- if(selected.escapable == FALSE) //Possibly escapable and special interactions.
- selected.escapable = TRUE
- to_chat(usr,"Prey now have special interactions with your [lowertext(selected.name)] depending on your settings. ")
- else if(selected.escapable == TRUE) //Never escapable.
- selected.escapable = FALSE
- to_chat(usr,"Prey will not be able to have special interactions with your [lowertext(selected.name)]. ")
- show_interacts = FALSE //Force the hiding of the panel
- else
- alert("Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.","Error") //If they somehow have a varable that's not 0 or 1
- selected.escapable = FALSE
- show_interacts = FALSE //Force the hiding of the panel
-
- if(href_list["b_escapechance"])
- var/escape_chance_input = input(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance") as num|null
- if(!isnull(escape_chance_input)) //These have to be 'null' because both cancel and 0 are valid, separate options
- selected.escapechance = sanitize_integer(escape_chance_input, 0, 100, initial(selected.escapechance))
-
- if(href_list["b_escapetime"])
- var/escape_time_input = input(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time") as num|null
- if(!isnull(escape_time_input))
- selected.escapetime = sanitize_integer(escape_time_input*10, 10, 600, initial(selected.escapetime))
-
- if(href_list["b_transferchance"])
- var/transfer_chance_input = input(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null
- if(!isnull(transfer_chance_input))
- selected.transferchance = sanitize_integer(transfer_chance_input, 0, 100, initial(selected.transferchance))
-
- if(href_list["b_transferlocation"])
- var/obj/belly/choice = input("Where do you want your [lowertext(selected.name)] to lead if prey resists?","Select Belly") as null|anything in (user.vore_organs + "None - Remove" - selected)
-
- if(!choice) //They cancelled, no changes
- return FALSE
- else if(choice == "None - Remove")
- selected.transferlocation = null
- else
- selected.transferlocation = choice.name
-
- if(href_list["b_absorbchance"])
- var/absorb_chance_input = input(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance") as num|null
- if(!isnull(absorb_chance_input))
- selected.absorbchance = sanitize_integer(absorb_chance_input, 0, 100, initial(selected.absorbchance))
-
- if(href_list["b_digestchance"])
- var/digest_chance_input = input(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance") as num|null
- if(!isnull(digest_chance_input))
- selected.digestchance = sanitize_integer(digest_chance_input, 0, 100, initial(selected.digestchance))
-
- if(href_list["b_del"])
- var/alert = alert("Are you sure you want to delete your [lowertext(selected.name)]?","Confirmation","Delete","Cancel")
- if(!alert == "Delete")
- return FALSE
-
- var/failure_msg = ""
-
- var/dest_for //Check to see if it's the destination of another vore organ.
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(B.transferlocation == selected)
- dest_for = B.name
- failure_msg += "This is the destiantion for at least '[dest_for]' belly transfers. Remove it as the destination from any bellies before deleting it. "
- break
-
- if(selected.contents.len)
- failure_msg += "You cannot delete bellies with contents! " //These end with spaces, to be nice looking. Make sure you do the same.
- if(selected.immutable)
- failure_msg += "This belly is marked as undeletable. "
- if(user.vore_organs.len == 1)
- failure_msg += "You must have at least one belly. "
-
- if(failure_msg)
- alert(user,failure_msg,"Error!")
- return FALSE
-
- qdel(selected)
- selected = user.vore_organs[1]
- user.vore_selected = user.vore_organs[1]
-
- if(href_list["saveprefs"])
- if(!(user.client?.prefs))
- return FALSE
- if(!user.copy_to_prefs_vr() || !user.client.prefs.save_character())
- to_chat(user, "Belly Preferences not saved! ")
- log_admin("Could not save vore prefs on USER: [user].")
- else
- to_chat(user, "Belly Preferences were saved! ")
-
- if(href_list["applyprefs"])
- var/alert = alert("Are you sure you want to reload the current slot preferences? This will remove your current vore organs and eject their contents.","Confirmation","Reload","Cancel")
- if(!alert == "Reload")
- return FALSE
- if(!user.copy_from_prefs_vr())
- alert("ERROR: Vore preferences failed to apply!","Error")
- else
- to_chat(user,"Vore preferences applied from active slot! ")
-
- if(href_list["setflavor"])
- var/new_flavor = html_encode(input(usr,"What your character tastes like (40ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",user.vore_taste) as text|null)
- if(!new_flavor)
- return FALSE
-
- new_flavor = readd_quotes(new_flavor)
- if(length(new_flavor) > MAX_TASTE_LEN)
- alert("Entered flavor/taste text too long. [MAX_TASTE_LEN] character limit.","Error!")
- return FALSE
- user.vore_taste = new_flavor
-
- if(href_list["toggledg"])
- var/choice = alert(user, "This button is for those who don't like being digested. It can make you undigestable to all mobs. Digesting you is currently: [(user.vore_flags & DIGESTABLE) ? "Allowed" : "Prevented"]", "", "Allow Digestion", "Cancel", "Prevent Digestion")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ if("newbelly")
+ if(host.vore_organs.len >= BELLIES_MAX)
return FALSE
- if("Allow Digestion")
- user.vore_flags |= DIGESTABLE
- user.client.prefs.vore_flags |= DIGESTABLE
- if("Prevent Digestion")
- user.vore_flags &= ~DIGESTABLE
- user.client.prefs.vore_flags &= ~DIGESTABLE
- if(href_list["toggledvor"])
- var/choice = alert(user, "This button is for those who don't like vore at all. Devouring you is currently: [(user.vore_flags & DEVOURABLE) ? "Allowed" : "Prevented"]", "", "Allow Devourment", "Cancel", "Prevent Devourment")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null)
+
+ var/failure_msg
+ if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
+ failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
+ // else if(whatever) //Next test here.
+ else
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ if(lowertext(new_name) == lowertext(B.name))
+ failure_msg = "No duplicate belly names, please."
+ break
+
+ if(failure_msg) //Something went wrong.
+ tgui_alert_async(usr, failure_msg, "Error!")
+ return TRUE
+
+ var/obj/belly/NB = new(host)
+ NB.name = new_name
+ host.vore_selected = NB
+ unsaved_changes = TRUE
+ return TRUE
+
+ if("bellypick")
+ host.vore_selected = locate(params["bellypick"])
+ return TRUE
+ if("move_belly")
+ var/dir = text2num(params["dir"])
+ if(LAZYLEN(host.vore_organs) <= 1)
+ to_chat(usr, "You can't sort bellies with only one belly to sort... ")
+ return TRUE
+
+ var/current_index = host.vore_organs.Find(host.vore_selected)
+ if(current_index)
+ var/new_index = clamp(current_index + dir, 1, LAZYLEN(host.vore_organs))
+ host.vore_organs.Swap(current_index, new_index)
+ unsaved_changes = TRUE
+ return TRUE
+
+ if("set_attribute")
+ return set_attr(usr, params)
+
+ if("saveprefs")
+ if(!host.copy_to_prefs_vr())
+ tgui_alert_async(usr, "Belly Preferences not saved! ", "Error")
+ log_admin("Could not save vore prefs on USER: [usr].")
+ else
+ to_chat(usr, "Belly Preferences were saved! ")
+ unsaved_changes = FALSE
+ return TRUE
+ if("reloadprefs")
+ var/alert = tgui_alert(usr, "Are you sure you want to reload character slot preferences? This will remove your current vore organs and eject their contents.","Confirmation",list("Reload","Cancel"))
+ if(alert != "Reload")
return FALSE
- if("Allow Devourment")
- user.vore_flags |= DEVOURABLE
- user.client.prefs.vore_flags |= DEVOURABLE
- if("Prevent Devourment")
- user.vore_flags &= ~DEVOURABLE
- user.client.prefs.vore_flags &= ~DEVOURABLE
-
- if(href_list["toggledfeed"])
- var/choice = alert(user, "This button is to toggle your ability to be fed to others. Feeding predators is currently: [(user.vore_flags & FEEDING) ? "Allowed" : "Prevented"]", "", "Allow Feeding", "Cancel", "Prevent Feeding")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ if(!host.copy_from_prefs_vr())
+ tgui_alert_async(usr, "ERROR: Virgo-specific preferences failed to apply!","Error")
+ else
+ to_chat(usr, "Vore preferences applied from active slot! ")
+ unsaved_changes = FALSE
+ return TRUE
+ if("setflavor")
+ var/new_flavor = html_encode(input(usr,"What your character tastes like (400ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",host.vore_taste) as text|null)
+ if(!new_flavor)
return FALSE
- if("Allow Feeding")
- user.vore_flags |= FEEDING
- user.client.prefs.vore_flags |= FEEDING
- if("Prevent Feeding")
- user.vore_flags &= ~FEEDING
- user.client.prefs.vore_flags &= ~FEEDING
- if(href_list["toggledlickable"])
- var/choice = alert(user, "This button is to toggle your ability to be licked. Being licked is currently: [(user.client.prefs.vore_flags & LICKABLE) ? "Allowed" : "Prevented"]", "", "Allow Licking", "Cancel", "Prevent Licking")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ new_flavor = readd_quotes(new_flavor)
+ if(length(new_flavor) > FLAVOR_MAX)
+ tgui_alert_async(usr, "Entered flavor/taste text too long. [FLAVOR_MAX] character limit.","Error!")
+ return FALSE
+ host.vore_taste = new_flavor
+ unsaved_changes = TRUE
+ return TRUE
+ if("setsmell")
+ var/new_smell = html_encode(input(usr,"What your character smells like (400ch limit). This text will be printed to the pred after 'X smells of...' so just put something like 'strawberries and cream':","Character Smell",host.vore_smell) as text|null)
+ if(!new_smell)
return FALSE
- if("Allow Licking")
- user.client.prefs.vore_flags |= LICKABLE
- if("Prevent Licking")
- user.client.prefs.vore_flags &= ~LICKABLE
- //Refresh when interacted with, returning 1 makes vore_look.Topic update
- return TRUE
+ new_smell = readd_quotes(new_smell)
+ if(length(new_smell) > FLAVOR_MAX)
+ tgui_alert_async(usr, "Entered perfume/smell text too long. [FLAVOR_MAX] character limit.","Error!")
+ return FALSE
+ host.vore_smell = new_smell
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_digest")
+ TOGGLE_BITFIELD(host.vore_flags, DIGESTABLE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, DIGESTABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_devour")
+ TOGGLE_BITFIELD(host.vore_flags, DEVOURABLE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, DEVOURABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_feed")
+ TOGGLE_BITFIELD(host.vore_flags, FEEDING)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, FEEDING)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_absorbable")
+ TOGGLE_BITFIELD(host.vore_flags, ABSORBABLE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, ABSORBABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_mobvore")
+ TOGGLE_BITFIELD(host.vore_flags, MOBVORE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, MOBVORE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_vore_sounds")
+ TOGGLE_BITFIELD(host.client.prefs.cit_toggles, EATING_NOISES)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_digestion_sounds")
+ TOGGLE_BITFIELD(host.client.prefs.cit_toggles, DIGESTION_NOISES)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_lickable")
+ TOGGLE_BITFIELD(host.vore_flags, LICKABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_smellable")
+ TOGGLE_BITFIELD(host.vore_flags, SMELLABLE)
+ unsaved_changes = TRUE
+ return TRUE
+
+/datum/vore_look/proc/pick_from_inside(mob/user, params)
+ var/atom/movable/target = locate(params["pick"])
+ var/obj/belly/OB = locate(params["belly"])
+
+ if(!(target in OB))
+ return TRUE // Aren't here anymore, need to update menu
+
+ var/intent = "Examine"
+ if(isliving(target))
+ intent = tgui_alert(usr, "What do you want to do to them?","Query",list("Examine","Help Out","Devour"))
+
+ else if(istype(target, /obj/item))
+ intent = tgui_alert(usr, "What do you want to do to that?","Query",list("Examine","Use Hand"))
+
+ switch(intent)
+ if("Examine") //Examine a mob inside another mob
+ var/list/results = target.examine(host)
+ if(!results || !results.len)
+ results = list("You were unable to examine that. Tell a developer!")
+ to_chat(user, jointext(results, " "))
+ return TRUE
+
+ if("Use Hand")
+ if(host.stat)
+ to_chat(user, "You can't do that in your state! ")
+ return TRUE
+
+ host.ClickOn(target)
+ return TRUE
+
+ if(!isliving(target))
+ return
+
+ var/mob/living/M = target
+ switch(intent)
+ if("Help Out") //Help the inside-mob out
+ if(host.stat || host.vore_flags & ABSORBED || M.vore_flags & ABSORBED)
+ to_chat(user, "You can't do that in your state! ")
+ return TRUE
+
+ to_chat(user,"You begin to push [M] to freedom! ")
+ to_chat(M,"[host] begins to push you to freedom!")
+ to_chat(M.loc,"Someone is trying to escape from inside you! ")
+ sleep(50)
+ if(prob(33))
+ OB.release_specific_contents(M)
+ to_chat(user,"You manage to help [M] to safety! ")
+ to_chat(M,"[host] pushes you free! ")
+ to_chat(OB.owner,"[M] forces free of the confines of your body! ")
+ else
+ to_chat(user,"[M] slips back down inside despite your efforts. ")
+ to_chat(M," Even with [host]'s help, you slip back inside again. ")
+ to_chat(OB.owner,"Your body efficiently shoves [M] back where they belong. ")
+ return TRUE
+
+ if("Devour") //Eat the inside mob
+ if(host.stat || host.vore_flags & ABSORBED)
+ to_chat(user,"You can't do that in your state! ")
+ return TRUE
+
+ if(!host.vore_selected)
+ to_chat(user,"Pick a belly on yourself first! ")
+ return TRUE
+
+ var/obj/belly/TB = host.vore_selected
+ to_chat(user,"You begin to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]! ")
+ to_chat(M,"[host] begins to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]! ")
+ to_chat(OB.owner,"Someone inside you is eating someone else! ")
+
+ sleep(TB.nonhuman_prey_swallow_time) //Can't do after, in a stomach, weird things abound.
+ if((host in OB) && (M in OB)) //Make sure they're still here.
+ to_chat(user,"You manage to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]! ")
+ to_chat(M,"[host] manages to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]! ")
+ to_chat(OB.owner,"Someone inside you has eaten someone else! ")
+ TB.nom_mob(M)
+
+/datum/vore_look/proc/pick_from_outside(mob/user, params)
+ var/intent
+
+ //Handle the [All] choice. Ugh inelegant. Someone make this pretty.
+ if(params["pickall"])
+ intent = tgui_alert(usr, "Eject all, Move all?","Query",list("Eject all","Cancel","Move all"))
+ switch(intent)
+ if("Cancel")
+ return TRUE
+
+ if("Eject all")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state! ")
+ return TRUE
+
+ host.vore_selected.release_all_contents()
+ return TRUE
+
+ if("Move all")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state! ")
+ return TRUE
+
+ var/obj/belly/choice = tgui_input_list(usr, "Move all where?","Select Belly", host.vore_organs)
+ if(!choice)
+ return FALSE
+
+ for(var/atom/movable/target in host.vore_selected)
+ to_chat(target,"You're squished from [host]'s [lowertext(host.vore_selected)] to their [lowertext(choice.name)]! ")
+ host.vore_selected.transfer_contents(target, choice, 1)
+ return TRUE
+ return
+
+ var/atom/movable/target = locate(params["pick"])
+ if(!(target in host.vore_selected))
+ return TRUE // Not in our X anymore, update UI
+ var/list/available_options = list("Examine", "Eject", "Move")
+ if(ishuman(target))
+ available_options += "Transform"
+ intent = tgui_alert(user, "What would you like to do with [target]?", "Vore Pick", available_options)
+ switch(intent)
+ if("Examine")
+ var/list/results = target.examine(host)
+ if(!results || !results.len)
+ results = list("You were unable to examine that. Tell a developer!")
+ to_chat(user, jointext(results, " "))
+ return TRUE
+
+ if("Eject")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state! ")
+ return TRUE
+
+ host.vore_selected.release_specific_contents(target)
+ return TRUE
+
+ if("Move")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state! ")
+ return TRUE
+
+ var/obj/belly/choice = tgui_input_list(usr, "Move [target] where?","Select Belly", host.vore_organs)
+ if(!choice || !(target in host.vore_selected))
+ return TRUE
+
+ to_chat(target,"You're squished from [host]'s [lowertext(host.vore_selected.name)] to their [lowertext(choice.name)]! ")
+ host.vore_selected.transfer_contents(target, choice)
+ return TRUE
+
+ if("Transform")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state! ")
+ return TRUE
+
+ var/mob/living/carbon/human/H = target
+ if(!istype(H))
+ return
+
+ return TRUE
+
+/datum/vore_look/proc/set_attr(mob/user, params)
+ if(!host.vore_selected)
+ tgui_alert_async(usr, "No belly selected to modify.")
+ return FALSE
+
+ var/attr = params["attribute"]
+ switch(attr)
+ if("b_name")
+ var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null)
+
+ var/failure_msg
+ if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
+ failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
+ // else if(whatever) //Next test here.
+ else
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ if(lowertext(new_name) == lowertext(B.name))
+ failure_msg = "No duplicate belly names, please."
+ break
+
+ if(failure_msg) //Something went wrong.
+ tgui_alert_async(user,failure_msg,"Error!")
+ return FALSE
+
+ host.vore_selected.name = new_name
+ . = TRUE
+ if("b_wetness")
+ host.vore_selected.is_wet = !host.vore_selected.is_wet
+ . = TRUE
+ if("b_wetloop")
+ host.vore_selected.wet_loop = !host.vore_selected.wet_loop
+ . = TRUE
+ if("b_mode")
+ var/list/menu_list = host.vore_selected.digest_modes.Copy()
+ var/new_mode = tgui_input_list(usr, "Choose Mode (currently [host.vore_selected.digest_mode])", "Mode Choice", menu_list)
+ if(!new_mode)
+ return FALSE
+
+ host.vore_selected.digest_mode = new_mode
+ . = TRUE
+ if("b_desc")
+ var/new_desc = html_encode(input(usr,"Belly Description ([BELLIES_DESC_MAX] char limit):","New Description",host.vore_selected.desc) as message|null)
+
+ if(new_desc)
+ new_desc = readd_quotes(new_desc)
+ if(length(new_desc) > BELLIES_DESC_MAX)
+ tgui_alert_async(usr, "Entered belly desc too long. [BELLIES_DESC_MAX] character limit.","Error")
+ return FALSE
+ host.vore_selected.desc = new_desc
+ . = TRUE
+ if("b_msgs")
+ tgui_alert(user,"Setting abusive or deceptive messages will result in a ban. Consider this your warning. Max 150 characters per message (500 for idle messages), max 10 messages per topic.","Really, don't.") // Should remain tgui_alert() (blocking)
+ var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name. '%count' will be replaced with the number of anything in your belly (will not work for absorbed examine). '%countprey' will be replaced with the number of living prey in your belly (or absorbed prey for absorbed examine)."
+ switch(params["msgtype"])
+ if("dmp")
+ var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",host.vore_selected.get_messages("dmp")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"dmp")
+
+ if("dmo")
+ var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",host.vore_selected.get_messages("dmo")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"dmo")
+
+ if("smo")
+ var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",host.vore_selected.get_messages("smo")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"smo")
+
+ if("smi")
+ var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",host.vore_selected.get_messages("smi")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"smi")
+
+ if("em")
+ var/new_message = input(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",host.vore_selected.get_messages("em")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"em")
+
+ if("reset")
+ var/confirm = tgui_alert(user,"This will delete any custom messages. Are you sure?","Confirmation",list("Cancel","DELETE"))
+ if(confirm == "DELETE")
+ host.vore_selected.digest_messages_prey = initial(host.vore_selected.digest_messages_prey)
+ host.vore_selected.digest_messages_owner = initial(host.vore_selected.digest_messages_owner)
+ host.vore_selected.struggle_messages_outside = initial(host.vore_selected.struggle_messages_outside)
+ host.vore_selected.struggle_messages_inside = initial(host.vore_selected.struggle_messages_inside)
+ host.vore_selected.examine_messages = initial(host.vore_selected.examine_messages)
+ host.vore_selected.emote_lists = initial(host.vore_selected.emote_lists)
+ . = TRUE
+ if("b_verb")
+ var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null)
+
+ if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN)
+ tgui_alert_async(usr, "Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error")
+ return FALSE
+
+ host.vore_selected.vore_verb = new_verb
+ . = TRUE
+ if("b_release")
+ var/choice = tgui_input_list(user,"Currently set to [host.vore_selected.release_sound]","Select Sound", GLOB.pred_release_sounds)
+
+ if(!choice)
+ return FALSE
+
+ host.vore_selected.release_sound = choice
+ . = TRUE
+ if("b_releasesoundtest")
+ var/sound/releasetest = GLOB.pred_release_sounds[host.vore_selected.release_sound]
+
+ if(releasetest)
+ SEND_SOUND(user, releasetest)
+ . = FALSE //Testing sound, no changes.
+ if("b_sound")
+ var/choice = tgui_input_list(user,"Currently set to [host.vore_selected.vore_sound]","Select Sound", GLOB.prey_vore_sounds)
+
+ if(!choice)
+ return FALSE
+
+ host.vore_selected.vore_sound = choice
+ . = TRUE
+ if("b_soundtest")
+ var/sound/voretest = GLOB.prey_vore_sounds[host.vore_selected.vore_sound]
+ if(voretest)
+ SEND_SOUND(user, voretest)
+ . = FALSE //Testing sound, no changes.
+ if("b_tastes")
+ host.vore_selected.can_taste = !host.vore_selected.can_taste
+ . = TRUE
+ if("b_bulge_size")
+ var/new_bulge = input(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.") as num|null
+ if(new_bulge == null)
+ return FALSE
+ if(new_bulge == 0) //Disable.
+ host.vore_selected.bulge_size = 0
+ to_chat(user,"Your stomach will not be seen on examine. ")
+ else if (!ISINRANGE(new_bulge,25,200))
+ host.vore_selected.bulge_size = 0.25 //Set it to the default.
+ to_chat(user,"Invalid size. ")
+ else if(new_bulge)
+ host.vore_selected.bulge_size = (new_bulge/100)
+ . = TRUE
+ if("b_escapable")
+ if(host.vore_selected.escapable == 0) //Possibly escapable and special interactions.
+ host.vore_selected.escapable = 1
+ to_chat(usr,"Prey now have special interactions with your [lowertext(host.vore_selected.name)] depending on your settings. ")
+ else if(host.vore_selected.escapable == 1) //Never escapable.
+ host.vore_selected.escapable = 0
+ to_chat(usr,"Prey will not be able to have special interactions with your [lowertext(host.vore_selected.name)]. ")
+ else
+ tgui_alert_async(usr, "Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.","Error") //If they somehow have a varable that's not 0 or 1
+ host.vore_selected.escapable = 0
+ . = TRUE
+ if("b_escapechance")
+ var/escape_chance_input = input(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance") as num|null
+ if(!isnull(escape_chance_input)) //These have to be 'null' because both cancel and 0 are valid, separate options
+ host.vore_selected.escapechance = sanitize_integer(escape_chance_input, 0, 100, initial(host.vore_selected.escapechance))
+ . = TRUE
+ if("b_escapetime")
+ var/escape_time_input = input(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time") as num|null
+ if(!isnull(escape_time_input))
+ host.vore_selected.escapetime = sanitize_integer(escape_time_input*10, 10, 600, initial(host.vore_selected.escapetime))
+ . = TRUE
+ if("b_transferchance")
+ var/transfer_chance_input = input(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null
+ if(!isnull(transfer_chance_input))
+ host.vore_selected.transferchance = sanitize_integer(transfer_chance_input, 0, 100, initial(host.vore_selected.transferchance))
+ . = TRUE
+ if("b_transferlocation")
+ var/obj/belly/choice = tgui_input_list(usr, "Where do you want your [lowertext(host.vore_selected.name)] to lead if prey resists?","Select Belly", (host.vore_organs + "None - Remove" - host.vore_selected))
+
+ if(!choice) //They cancelled, no changes
+ return FALSE
+ else if(choice == "None - Remove")
+ host.vore_selected.transferlocation = null
+ else
+ host.vore_selected.transferlocation = choice.name
+ . = TRUE
+ if("b_absorbchance")
+ var/absorb_chance_input = input(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance") as num|null
+ if(!isnull(absorb_chance_input))
+ host.vore_selected.absorbchance = sanitize_integer(absorb_chance_input, 0, 100, initial(host.vore_selected.absorbchance))
+ . = TRUE
+ if("b_digestchance")
+ var/digest_chance_input = input(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance") as num|null
+ if(!isnull(digest_chance_input))
+ host.vore_selected.digestchance = sanitize_integer(digest_chance_input, 0, 100, initial(host.vore_selected.digestchance))
+ . = TRUE
+ if("b_del")
+ var/alert = tgui_alert(usr, "Are you sure you want to delete your [lowertext(host.vore_selected.name)]?","Confirmation",list("Cancel","Delete"))
+ if(!(alert == "Delete"))
+ return FALSE
+
+ var/failure_msg = ""
+
+ var/dest_for //Check to see if it's the destination of another vore organ.
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ if(B.transferlocation == host.vore_selected)
+ dest_for = B.name
+ failure_msg += "This is the destiantion for at least '[dest_for]' belly transfers. Remove it as the destination from any bellies before deleting it. "
+ break
+
+ if(host.vore_selected.contents.len)
+ failure_msg += "You cannot delete bellies with contents! " //These end with spaces, to be nice looking. Make sure you do the same.
+ if(host.vore_selected.immutable)
+ failure_msg += "This belly is marked as undeletable. "
+ if(host.vore_organs.len == 1)
+ failure_msg += "You must have at least one belly. "
+
+ if(failure_msg)
+ tgui_alert_async(user,failure_msg,"Error!")
+ return FALSE
+
+ qdel(host.vore_selected)
+ host.vore_selected = host.vore_organs[1]
+ . = TRUE
+
+ if(.)
+ unsaved_changes = TRUE
diff --git a/tgstation.dme b/tgstation.dme
index a9b83f27b6..5c2dbd3748 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3593,6 +3593,7 @@
#include "code\modules\tgui\states\observer.dm"
#include "code\modules\tgui\states\physical.dm"
#include "code\modules\tgui\states\self.dm"
+#include "code\modules\tgui\states\vorepanel.dm"
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\tgui_panel\audio.dm"
#include "code\modules\tgui_panel\external.dm"
diff --git a/tgui/packages/tgui/interfaces/VorePanel.js b/tgui/packages/tgui/interfaces/VorePanel.js
new file mode 100644
index 0000000000..3e29c8bff4
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/VorePanel.js
@@ -0,0 +1,591 @@
+/* eslint-disable max-len */
+import { Fragment } from 'inferno';
+import { useBackend, useLocalState } from "../backend";
+import { Box, Button, Flex, Collapsible, Icon, LabeledList, NoticeBox, Section, Tabs } from "../components";
+import { Window } from "../layouts";
+
+const stats = [
+ null,
+ 'average',
+ 'bad',
+];
+
+const digestModeToColor = {
+ "Hold": null,
+ "Dragon": "blue",
+ "Digest": "red",
+ "Absorb": "purple",
+ "Unabsorb": "purple",
+};
+
+const digestModeToPreyMode = {
+ "Hold": "being held.",
+ "Digest": "being digested.",
+ "Absorb": "being absorbed.",
+ "Unabsorb": "being unabsorbed.",
+ "Dragon": "being digested by a powerful creature.",
+};
+
+/**
+ * There are three main sections to this UI.
+ * - The Inside Panel, where all relevant data for interacting with a belly you're in is located.
+ * - The Belly Selection Panel, where you can select what belly people will go into and customize the active one.
+ * - User Preferences, where you can adjust all of your vore preferences on the fly.
+ */
+export const VorePanel = (props, context) => {
+ const { act, data } = useBackend(context);
+ return (
+
+
+ {data.unsaved_changes && (
+
+
+
+ Warning: Unsaved Changes!
+
+
+ act("saveprefs")} />
+
+
+
+ ) || null}
+
+
+
+
+
+ );
+};
+
+const VoreInsidePanel = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ absorbed,
+ belly_name,
+ belly_mode,
+ desc,
+ pred,
+ contents,
+ ref,
+ } = data.inside;
+
+ if (!belly_name) {
+ return (
+
+ You aren't inside anyone.
+
+ );
+ }
+
+ return (
+
+ You are currently {absorbed ? "absorbed into" : "inside"}
+ {pred}'s
+ {belly_name}
+ and you are
+ {digestModeToPreyMode[belly_mode]}
+
+ {desc}
+
+ {contents.length && (
+
+
+
+ ) || "There is nothing else around you."}
+
+ );
+};
+
+const VoreBellySelectionAndCustomization = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ our_bellies,
+ selected,
+ } = data;
+
+ return (
+
+
+ {our_bellies.map(belly => (
+ act("bellypick", { bellypick: belly.ref })}>
+
+ {belly.name} ({belly.contents})
+
+
+ ))}
+ act("newbelly")}>
+ New
+
+
+
+ {selected && }
+
+ );
+};
+
+/**
+ * Subtemplate of VoreBellySelectionAndCustomization
+ */
+const VoreSelectedBelly = (props, context) => {
+ const { act } = useBackend(context);
+
+ const { belly } = props;
+ const {
+ belly_name,
+ is_wet,
+ wet_loop,
+ mode,
+ verb,
+ desc,
+ sound,
+ release_sound,
+ can_taste,
+ bulge_size,
+ escapable,
+ interacts,
+ contents,
+ } = belly;
+
+ const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
+
+ return (
+
+
+ setTabIndex(0)}>
+ Controls
+
+ setTabIndex(1)}>
+ Options
+
+ setTabIndex(2)}>
+ Contents ({contents.length})
+
+ setTabIndex(3)}>
+ Interactions
+
+
+ {tabIndex === 0 && (
+
+
+ act("move_belly", { dir: -1 })} />
+ act("move_belly", { dir: 1 })} />
+
+ }>
+ act("set_attribute", { attribute: "b_name" })}
+ content={belly_name} />
+
+
+ act("set_attribute", { attribute: "b_mode" })}
+ content={mode} />
+
+ act("set_attribute", { attribute: "b_desc" })}
+ icon="pen" />
+ }>
+ {desc}
+
+
+ act("set_attribute", { attribute: "b_verb" })}
+ content={verb} />
+
+
+ act("set_attribute", { attribute: "b_msgs", msgtype: "dmp" })}
+ content="Digest Message (to prey)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "dmo" })}
+ content="Digest Message (to you)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "smo" })}
+ content="Struggle Message (outside)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "smi" })}
+ content="Struggle Message (inside)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "em" })}
+ content="Examine Message (when full)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "reset" })}
+ content="Reset Messages" />
+
+
+ ) || tabIndex === 1 && (
+
+
+
+
+ act("set_attribute", { attribute: "b_tastes" })}
+ icon={can_taste ? "toggle-on" : "toggle-off"}
+ selected={can_taste}
+ content={can_taste ? "Yes" : "No"} />
+
+
+ act("set_attribute", { attribute: "b_wetness" })}
+ icon={is_wet ? "toggle-on" : "toggle-off"}
+ selected={is_wet}
+ content={is_wet ? "Yes" : "No"} />
+
+
+ act("set_attribute", { attribute: "b_wetloop" })}
+ icon={wet_loop ? "toggle-on" : "toggle-off"}
+ selected={wet_loop}
+ content={wet_loop ? "Yes" : "No"} />
+
+
+
+
+
+
+ act("set_attribute", { attribute: "b_sound" })}
+ content={sound} />
+ act("set_attribute", { attribute: "b_soundtest" })}
+ icon="volume-up" />
+
+
+ act("set_attribute", { attribute: "b_release" })}
+ content={release_sound} />
+ act("set_attribute", { attribute: "b_releasesoundtest" })}
+ icon="volume-up" />
+
+
+ act("set_attribute", { attribute: "b_bulge_size" })}
+ content={bulge_size * 100 + "%"} />
+
+
+
+
+ act("set_attribute", { attribute: "b_del" })} />
+
+
+ ) || tabIndex === 2 && (
+
+ ) || tabIndex === 3 && (
+ act("set_attribute", { attribute: "b_escapable" })}
+ icon={escapable ? "toggle-on" : "toggle-off"}
+ selected={escapable}
+ content={escapable ? "Interactions On" : "Interactions Off"} />
+ }>
+ {escapable ? (
+
+
+ act("set_attribute", { attribute: "b_escapechance" })} />
+
+
+ act("set_attribute", { attribute: "b_escapetime" })} />
+
+
+
+ act("set_attribute", { attribute: "b_transferchance" })} />
+
+
+ act("set_attribute", { attribute: "b_transferlocation" })} />
+
+
+
+ act("set_attribute", { attribute: "b_absorbchance" })} />
+
+
+ act("set_attribute", { attribute: "b_digestchance" })} />
+
+
+ ) : "These options only display while interactions are turned on."}
+
+ ) || "Error"}
+
+ );
+};
+
+const VoreContentsPanel = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ show_pictures,
+ } = data;
+ const {
+ contents,
+ belly,
+ outside = false,
+ } = props;
+
+ return (
+
+ {outside && (
+ act("pick_from_outside", { "pickall": true })}>
+ All
+
+ ) || null}
+ {show_pictures && (
+
+ {contents.map(thing => (
+
+ act(thing.outside ? "pick_from_outside" : "pick_from_inside", {
+ "pick": thing.ref,
+ "belly": belly,
+ })}>
+
+
+ {thing.name}
+
+ ))}
+
+ ) || (
+
+ {contents.map(thing => (
+
+ act(thing.outside ? "pick_from_outside" : "pick_from_inside", {
+ "pick": thing.ref,
+ "belly": belly,
+ })}>
+ Interact
+
+
+ ))}
+
+ )}
+
+ );
+};
+
+const VoreUserPreferences = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ digestable,
+ devourable,
+ feeding,
+ absorbable,
+ allowmobvore,
+ vore_sounds,
+ digestion_sounds,
+ lickable,
+ smellable,
+ } = data.prefs;
+
+ const {
+ show_pictures,
+ } = data;
+
+ return (
+ act("show_pictures")}>
+ Contents Preference: {show_pictures ? "Show Pictures" : "Show List"}
+
+ }>
+
+
+ act("toggle_devour")}
+ icon={devourable ? "toggle-on" : "toggle-off"}
+ selected={devourable}
+ fluid
+ tooltip={"This button is to toggle your ability to be devoured by others. "
+ + (devourable ? "Click here to prevent being devoured." : "Click here to allow being devoured.")}
+ content={devourable ? "Devouring Allowed" : "No Devouring"} />
+
+
+ act("toggle_mobvore")}
+ icon={allowmobvore ? "toggle-on" : "toggle-off"}
+ selected={allowmobvore}
+ fluid
+ tooltip={"This button is for those who don't like being eaten by mobs. "
+ + (allowmobvore
+ ? "Click here to prevent being eaten by mobs."
+ : "Click here to allow being eaten by mobs.")}
+ tooltipPosition="bottom-right"
+ content={allowmobvore ? "Mobs eating you allowed" : "No Mobs eating you"} />
+
+
+ act("toggle_feed")}
+ icon={feeding ? "toggle-on" : "toggle-off"}
+ selected={feeding}
+ fluid
+ tooltip={"This button is to toggle your ability to be fed to or by others vorishly. "
+ + (feeding
+ ? "Click here to prevent being fed to/by other people."
+ : "Click here to allow being fed to/by other people.")}
+ content={feeding ? "Feeding Allowed" : "No Feeding"} />
+
+
+ act("toggle_digest")}
+ icon={digestable ? "toggle-on" : "toggle-off"}
+ selected={digestable}
+ fluid
+ tooltip={"This button is for those who don't like being digested. It can make you undigestable."
+ + (digestable ? " Click here to prevent digestion." : " Click here to allow digestion.")}
+ tooltipPosition="bottom-right"
+ content={digestable ? "Digestion Allowed" : "No Digestion"} />
+
+
+ act("toggle_absorbable")}
+ icon={absorbable ? "toggle-on" : "toggle-off"}
+ selected={absorbable}
+ fluid
+ tooltip={"This button allows preds to know whether you prefer or don't prefer to be absorbed. "
+ + (absorbable ? "Click here to disallow being absorbed." : "Click here to allow being absorbed.")}
+ content={absorbable ? "Absorption Allowed" : "No Absorption"} />
+
+
+ act("toggle_vore_sounds")}
+ icon={vore_sounds ? "volume-up" : "volume-mute"}
+ selected={vore_sounds}
+ fluid
+ tooltip={"Be able to hear vore sounds. "
+ + (vore_sounds
+ ? "Click here to turn off vore sounds."
+ : "Click here to turn on vore sounds.")}
+ content={vore_sounds ? "Vore Sounds Enabled" : "Vore Sounds Disabled"} />
+
+
+ act("toggle_digestion_sounds")}
+ icon={digestion_sounds ? "volume-up" : "volume-mute"}
+ selected={digestion_sounds}
+ fluid
+ tooltip={"Be able to hear digestion sounds. "
+ + (digestion_sounds
+ ? "Click here to turn off digestion sounds."
+ : "Click here to turn on digestion sounds.")}
+ content={digestion_sounds ? "Digestion Sounds Enabled" : "Digestion Sounds Disabled"} />
+
+
+ act("toggle_lickable")}
+ icon={lickable ? "toggle-on" : "toggle-off"}
+ selected={lickable}
+ fluid
+ tooltip={"Be able to be licked by others. "
+ + (lickable
+ ? "Click here to turn off being able to be licked."
+ : "Click here to turn on being able to be licked.")}
+ tooltipPosition="bottom-right"
+ content={lickable ? "Lickable" : "Unlickable"} />
+
+
+ act("setflavor")} />
+
+
+ act("toggle_smellable")}
+ icon={smellable ? "toggle-on" : "toggle-off"}
+ selected={smellable}
+ fluid
+ tooltip={"Be able to be smelled by others. "
+ + (smellable
+ ? "Click here to turn off being able to be smelled."
+ : "Click here to turn on being able to be smelled.")}
+ content={smellable ? "Smellable" : "Unsmellable"} />
+
+
+ act("setsmell")} />
+
+
+
+
+
+ act("saveprefs")} />
+
+
+ act("reloadprefs")} />
+
+
+
+
+ );
+};