From f5ab9b3e8cdc812673906b506ae4ae86fddb80e4 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Fri, 2 Jul 2021 00:57:53 +0000 Subject: [PATCH 01/14] Automatic changelog compile [ci skip] --- html/changelog.html | 49 +++------------------- html/changelogs/.all_changelog.yml | 3 ++ html/changelogs/AutoChangeLog-pr-14903.yml | 4 -- 3 files changed, 9 insertions(+), 47 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-14903.yml diff --git a/html/changelog.html b/html/changelog.html index bf12874937..78d0f7e129 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,6 +50,12 @@ -->
+

02 July 2021

+

silicons updated:

+ +

30 June 2021

WanderingFox95 updated:

GoonStation 13 Development Team diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 82e2f373c8..5ce5f2a8be 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -29606,3 +29606,6 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. Make sure to try and recover it. - rscadd: Adds an extremely expensive survival pod to mining for people to work towards. Get to work cracking rocks today. +2021-07-02: + silicons: + - bugfix: spray bottles work again. diff --git a/html/changelogs/AutoChangeLog-pr-14903.yml b/html/changelogs/AutoChangeLog-pr-14903.yml deleted file mode 100644 index 4e7b6bfa63..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14903.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - bugfix: "spray bottles work again." From b0d68d5dd5657117ee9c4bd3ca39185221633ee2 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Sat, 3 Jul 2021 00:57:05 +0000 Subject: [PATCH 02/14] Automatic changelog compile [ci skip] --- html/changelog.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index 78d0f7e129..db8a51cb27 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -600,12 +600,6 @@
  • Box Surgery Storage camera is now renamed to be on the network
  • Box Paramedic Station camera is now renamed to be on the network, and no longer steals the Morgue's cam tweak: Box Surgery Storage is now it's own proper room
  • - -

    01 May 2021

    -

    qweq12yt updated:

    - GoonStation 13 Development Team From 190fcaffa90084d9ece8e0485157490a18669534 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Mon, 5 Jul 2021 00:57:35 +0000 Subject: [PATCH 03/14] Automatic changelog compile [ci skip] --- html/changelog.html | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index db8a51cb27..056aa793eb 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -589,17 +589,6 @@
  • Nightmare Shadow Jaunt threshold up to 0.4
  • Vendor and Engraved message light down to 0.3
  • - -

    03 May 2021

    -

    TripleShades updated:

    -
      -
    • Added two air alarms to Pubby Security, one in the evidence locker room and one in the main equipment back room
    • -
    • pAI Card back to outside Research in Meta Station
    • -
    • Pubby Disposals now shunts to space
    • -
    • Maintinence Areas being not applied to certain airlocks as well as stealing minor walls
    • -
    • Box Surgery Storage camera is now renamed to be on the network
    • -
    • Box Paramedic Station camera is now renamed to be on the network, and no longer steals the Morgue's cam tweak: Box Surgery Storage is now it's own proper room
    • -
    GoonStation 13 Development Team From 552d5e3c0b33841a79cdbe6862ade70a883d8ee1 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Wed, 7 Jul 2021 01:01:09 +0000 Subject: [PATCH 04/14] Automatic changelog compile [ci skip] --- html/changelog.html | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index 998af50e93..86e0e58c7f 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -610,21 +610,6 @@
    • lets humans have digi legs (and avian legs)
    - -

    05 May 2021

    -

    The0bserver, with a great amount of advice from TripleZeta/TetraZeta updated:

    -
      -
    • Adds a new crate type, for use with any manner of cheeky breeki shenanigans, as well as with existing Russian contraband.
    • -
    -

    bunny232 updated:

    -
      -
    • There's some new vents and scrubbers in the meta station xenobiology department. Welders and wrenches not included*
    • -
    -

    keronshb updated:

    -
      -
    • Nightmare Shadow Jaunt threshold up to 0.4
    • -
    • Vendor and Engraved message light down to 0.3
    • -
    GoonStation 13 Development Team From 149c263ae5dda2dd30aad850945d06480f390a3e Mon Sep 17 00:00:00 2001 From: Changelogs Date: Sat, 10 Jul 2021 00:58:04 +0000 Subject: [PATCH 05/14] Automatic changelog compile [ci skip] --- html/changelog.html | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index 86e0e58c7f..e8ea59ef01 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -600,16 +600,6 @@
  • pirates now have a medbay and several other things qualifying as a buff
  • pirates lost their toilet
  • - -

    08 May 2021

    -

    Arturlang updated:

    -
      -
    • Synthblood bottles now have the proper color and probably won't poison you anymore
    • -
    -

    timothyteakettle updated:

    -
      -
    • lets humans have digi legs (and avian legs)
    • -
    GoonStation 13 Development Team From d267cedac2198e17f9a7461fb95142393821e9de Mon Sep 17 00:00:00 2001 From: Changelogs Date: Sun, 11 Jul 2021 01:04:18 +0000 Subject: [PATCH 06/14] Automatic changelog compile [ci skip] --- html/changelog.html | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index e8ea59ef01..1544841cb7 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -568,38 +568,6 @@
    • Delta station xenobiology department has received enhanced scrubbing and ventilation capabilities similar to box and meta
    - -

    09 May 2021

    -

    Putnam3145 updated:

    -
      -
    • Priority announcement admeme verb
    • -
    -

    SandPoot updated:

    -
      -
    • Fixed Cyborg examines adding an extra weird line.
    • -
    • Everything can be set to have tooltips, and even coded to have neat tooltips.
    • -
    • Makes it so humans and borgs already have tooltips.
    • -
    -

    TheObserver-sys updated:

    -
      -
    • Fixes most of the weird handling bugs and improves cigarette case handling in general.
    • -
    • The Gorlex Marauders have seen fit to allow you to purchase the .45-70 GOVT rare ammo, at a premium cost. Don't waste it.
    • -
    -

    WanderingFox95 updated:

    -
      -
    • added the unrolling pin, an innovative solution to dough-based mishaps.
    • -
    • added visuals for the unrolling pin
    • -
    -

    dzahlus updated:

    -
      -
    • added new malf AI spawn and doomsday sound
    • -
    • removed old malf AI spawn and doomsday sound
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • pirates now have a medbay and several other things qualifying as a buff
    • -
    • pirates lost their toilet
    • -
    GoonStation 13 Development Team From dcbecbaee6b3e4b18911604d4203a2f275b4cc82 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Tue, 13 Jul 2021 01:01:15 +0000 Subject: [PATCH 07/14] Automatic changelog compile [ci skip] --- html/changelog.html | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index 1544841cb7..87aa03e32c 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -557,17 +557,6 @@
  • find_safe_turf no longer always fails on safe oxygen levels(??)
  • Heretic bladeshatters now actually take the heretic's z into account as intended, instead of always being station z tweak: Message for failing the bladeshatter despite succeeding the do_after tweak: Improves bladeshatter a bit by making it safer codewise
  • - -

    11 May 2021

    -

    LetterN updated:

    -
      -
    • fixes emagging console shuttle purchases
    • -
    • syndie melee simplemobs has no more bullshit shield
    • -
    -

    bunny232 updated:

    -
      -
    • Delta station xenobiology department has received enhanced scrubbing and ventilation capabilities similar to box and meta
    • -
    GoonStation 13 Development Team From 3a11960a352e30f9007c1b8c17434b4f2a3c5314 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Wed, 14 Jul 2021 00:56:08 +0000 Subject: [PATCH 08/14] Automatic changelog compile [ci skip] --- html/changelog.html | 7 ------- 1 file changed, 7 deletions(-) diff --git a/html/changelog.html b/html/changelog.html index 7fc5e7ab5c..670468b057 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -590,13 +590,6 @@
    • vegas style bunny ears
    - -

    12 May 2021

    -

    DeltaFire15 updated:

    -
      -
    • find_safe_turf no longer always fails on safe oxygen levels(??)
    • -
    • Heretic bladeshatters now actually take the heretic's z into account as intended, instead of always being station z tweak: Message for failing the bladeshatter despite succeeding the do_after tweak: Improves bladeshatter a bit by making it safer codewise
    • -
    GoonStation 13 Development Team From 3cb44d41a3d30142facfb9d14b80a9ba828f3997 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Wed, 14 Jul 2021 00:35:30 -0300 Subject: [PATCH 09/14] upload files --- code/__DEFINES/voreconstants.dm | 27 +- code/modules/client/preferences.dm | 1 + code/modules/client/preferences_savefile.dm | 3 + .../living/simple_animal/hostile/hostile.dm | 4 +- code/modules/tgui/states/vorepanel.dm | 18 + code/modules/tgui/tgui_alert.dm | 27 +- code/modules/vore/eating/bellymodes.dm | 2 +- code/modules/vore/eating/living.dm | 73 +- code/modules/vore/eating/vorepanel.dm | 1387 ++++++++--------- tgstation.dme | 1 + tgui/packages/tgui/interfaces/VorePanel.js | 604 +++++++ 11 files changed, 1414 insertions(+), 733 deletions(-) create mode 100644 code/modules/tgui/states/vorepanel.dm create mode 100644 tgui/packages/tgui/interfaces/VorePanel.js diff --git a/code/__DEFINES/voreconstants.dm b/code/__DEFINES/voreconstants.dm index d2eb034813..67ef9bc81f 100644 --- a/code/__DEFINES/voreconstants.dm +++ b/code/__DEFINES/voreconstants.dm @@ -8,17 +8,24 @@ #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) +/// Spontaneous vore. Unused, usable in the future? +#define SPNTVORE (1<<11) -#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<<12)-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..3db66050db 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..ff4a26e0e2 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 | SPNTVORE) 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..489ed39094 100644 --- a/code/modules/vore/eating/vorepanel.dm +++ b/code/modules/vore/eating/vorepanel.dm @@ -2,746 +2,737 @@ // 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 += "
    1. [B.name]" - else - dat += "
    2. [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])
    3. " - - if(user.vore_organs.len < BELLIES_MAX) - dat += "
    4. New+
    5. " - 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), + "allow_spontaneous_tf" = CHECK_BITFIELD(host.vore_flags, SPNTVORE), + "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_allow_spontaneous_tf") + TOGGLE_BITFIELD(host.vore_flags, SPNTVORE) + if(host.client.prefs) + COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, SPNTVORE) + 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..1ab293a9ab --- /dev/null +++ b/tgui/packages/tgui/interfaces/VorePanel.js @@ -0,0 +1,604 @@ +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! + + + + ) || null} + {show_pictures && ( + + {contents.map(thing => ( + + + {thing.name} + + ))} + + ) || ( + + {contents.map(thing => ( + + + + ))} + + )} + + ); +}; + +const VoreUserPreferences = (props, context) => { + const { act, data } = useBackend(context); + + const { + digestable, + devourable, + feeding, + absorbable, + allowmobvore, + allow_spontaneous_tf, + vore_sounds, + digestion_sounds, + lickable, + smellable, + } = data.prefs; + + const { + show_pictures, + } = data; + + return ( +
    act("show_pictures")}> + Contents Preference: {show_pictures ? "Show Pictures" : "Show List"} + + }> + + +
    + + ); +}; From 3cf040bcdfcbf5a78169c86486bb1843622a2e26 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Wed, 14 Jul 2021 00:53:35 -0300 Subject: [PATCH 10/14] Update VorePanel.js --- tgui/packages/tgui/interfaces/VorePanel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tgui/packages/tgui/interfaces/VorePanel.js b/tgui/packages/tgui/interfaces/VorePanel.js index 1ab293a9ab..4a99bada10 100644 --- a/tgui/packages/tgui/interfaces/VorePanel.js +++ b/tgui/packages/tgui/interfaces/VorePanel.js @@ -1,3 +1,4 @@ +/* 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"; From b6a99e120571265f61665791de384f2390fdbdf1 Mon Sep 17 00:00:00 2001 From: SandPoot <43283559+SandPoot@users.noreply.github.com> Date: Wed, 14 Jul 2021 21:25:51 -0300 Subject: [PATCH 11/14] Update changelog.html --- html/changelog.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/html/changelog.html b/html/changelog.html index 670468b057..025bf64fb0 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -590,6 +590,13 @@
    • vegas style bunny ears
    + +

    12 May 2021

    +

    DeltaFire15 updated:

    +
      +
    • find_safe_turf no longer always fails on safe oxygen levels(??)
    • +
    • Heretic bladeshatters now actually take the heretic's z into account as intended, instead of always being station z tweak: Message for failing the bladeshatter despite succeeding the do_after tweak: Improves bladeshatter a bit by making it safer codewise
    • +
    GoonStation 13 Development Team From 8b65421eb7f482a0ae1f2b9e51b662dadee5e276 Mon Sep 17 00:00:00 2001 From: SandPoot <43283559+SandPoot@users.noreply.github.com> Date: Wed, 14 Jul 2021 21:26:26 -0300 Subject: [PATCH 12/14] Update changelog.html --- html/changelog.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/changelog.html b/html/changelog.html index 025bf64fb0..7fc5e7ab5c 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -590,7 +590,7 @@
    • vegas style bunny ears
    - +

    12 May 2021

    DeltaFire15 updated:

      From 6445cfdd9d35e29188df2ee151b4fa36dc3ce667 Mon Sep 17 00:00:00 2001 From: SandPoot Date: Thu, 15 Jul 2021 17:32:03 -0300 Subject: [PATCH 13/14] Removes spontaneous vore and reorganizes buttons --- code/__DEFINES/voreconstants.dm | 4 +- code/modules/vore/eating/living.dm | 2 +- code/modules/vore/eating/vorepanel.dm | 7 --- tgui/packages/tgui/interfaces/VorePanel.js | 62 +++++++++------------- 4 files changed, 26 insertions(+), 49 deletions(-) diff --git a/code/__DEFINES/voreconstants.dm b/code/__DEFINES/voreconstants.dm index 67ef9bc81f..372c5b776c 100644 --- a/code/__DEFINES/voreconstants.dm +++ b/code/__DEFINES/voreconstants.dm @@ -21,11 +21,9 @@ #define ABSORBABLE (1<<9) /// Can get simplemob vored? #define MOBVORE (1<<10) -/// Spontaneous vore. Unused, usable in the future? -#define SPNTVORE (1<<11) /// Change this whenever you add a vore flag, must be largest vore flag*2-1 -#define MAX_VORE_FLAG (1<<12)-1 +#define MAX_VORE_FLAG (1<<11)-1 #define isbelly(A) istype(A, /obj/belly) diff --git a/code/modules/vore/eating/living.dm b/code/modules/vore/eating/living.dm index ff4a26e0e2..6bc44e6489 100644 --- a/code/modules/vore/eating/living.dm +++ b/code/modules/vore/eating/living.dm @@ -284,7 +284,7 @@ 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 | SMELLABLE | ABSORBABLE | MOBVORE | SPNTVORE) + 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 diff --git a/code/modules/vore/eating/vorepanel.dm b/code/modules/vore/eating/vorepanel.dm index 489ed39094..a3adc89352 100644 --- a/code/modules/vore/eating/vorepanel.dm +++ b/code/modules/vore/eating/vorepanel.dm @@ -186,7 +186,6 @@ "feeding" = CHECK_BITFIELD(host.vore_flags, FEEDING), "absorbable" = CHECK_BITFIELD(host.vore_flags, ABSORBABLE), "allowmobvore" = CHECK_BITFIELD(host.vore_flags, MOBVORE), - "allow_spontaneous_tf" = CHECK_BITFIELD(host.vore_flags, SPNTVORE), "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), @@ -307,12 +306,6 @@ host.vore_smell = new_smell unsaved_changes = TRUE return TRUE - if("toggle_allow_spontaneous_tf") - TOGGLE_BITFIELD(host.vore_flags, SPNTVORE) - if(host.client.prefs) - COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, SPNTVORE) - unsaved_changes = TRUE - return TRUE if("toggle_digest") TOGGLE_BITFIELD(host.vore_flags, DIGESTABLE) if(host.client.prefs) diff --git a/tgui/packages/tgui/interfaces/VorePanel.js b/tgui/packages/tgui/interfaces/VorePanel.js index 4a99bada10..3e29c8bff4 100644 --- a/tgui/packages/tgui/interfaces/VorePanel.js +++ b/tgui/packages/tgui/interfaces/VorePanel.js @@ -431,7 +431,6 @@ const VoreUserPreferences = (props, context) => { feeding, absorbable, allowmobvore, - allow_spontaneous_tf, vore_sounds, digestion_sounds, lickable, @@ -450,27 +449,6 @@ const VoreUserPreferences = (props, context) => { }> -