From 6e5131cfc59cfee2dbc4274283770bec3157d55f Mon Sep 17 00:00:00 2001 From: Poojawa Date: Tue, 6 Mar 2018 03:59:31 -0600 Subject: [PATCH] Vore code to json and more (#5789) * Vore 2.0 Initial commit * double checked porting * Fixes compile issues * converts Ash Drake bellies to new system digs out lingering datum/belly stuff too * Let's just work on this later * System operational * Update preferences.dm --- code/__DEFINES/preferences.dm | 9 +- code/__DEFINES/sound.dm | 5 +- code/__DEFINES/voreconstants.dm | 18 + code/__HELPERS/type2type_vr.dm | 4 + code/citadel/dogborgstuff.dm | 6 + code/controllers/subsystem/vore.dm | 41 ++ code/modules/client/preferences.dm | 9 + code/modules/client/preferences_vr.dm | 5 +- .../mob/living/carbon/human/examine.dm | 6 +- .../mob/living/carbon/human/examine_vr.dm | 9 - code/modules/mob/living/carbon/human/human.dm | 1 + code/modules/mob/living/carbon/human/life.dm | 10 +- .../carbon/human/species_types/furrypeople.dm | 7 + code/modules/mob/living/carbon/life.dm | 2 + code/modules/mob/living/life.dm | 3 - .../hostile/megafauna/dragon_vore.dm | 27 +- .../mob/living/simple_animal/simple_animal.dm | 3 +- .../living/simple_animal/simple_animal_vr.dm | 53 +- code/modules/unit_tests/_unit_tests.dm | 3 +- code/modules/unit_tests/vore_tests.dm | 218 ++++++ code/modules/vore/eating/belly_dat_vr.dm | 162 +++++ code/modules/vore/eating/belly_obj_vr.dm | 655 ++++++++++++++++++ code/modules/vore/eating/belly_vr.dm | 467 ------------- code/modules/vore/eating/bellymodes_vr.dm | 141 ++-- code/modules/vore/eating/digest_act_vr.dm | 119 ++++ code/modules/vore/eating/living_vr.dm | 203 +++--- code/modules/vore/eating/simple_animal_vr.dm | 2 +- code/modules/vore/eating/vore_vr.dm | 108 +-- code/modules/vore/eating/voreitems.dm | 5 +- code/modules/vore/eating/vorepanel_vr.dm | 427 +++++++----- code/modules/vore/persistence.dm | 90 +++ .../modules/client/preferences_toggles.dm | 22 + tgstation.dme | 7 +- 33 files changed, 1976 insertions(+), 871 deletions(-) create mode 100644 code/controllers/subsystem/vore.dm create mode 100644 code/modules/unit_tests/vore_tests.dm create mode 100644 code/modules/vore/eating/belly_dat_vr.dm create mode 100644 code/modules/vore/eating/belly_obj_vr.dm delete mode 100644 code/modules/vore/eating/belly_vr.dm create mode 100644 code/modules/vore/eating/digest_act_vr.dm create mode 100644 code/modules/vore/persistence.dm create mode 100644 modular_citadel/code/modules/client/preferences_toggles.dm diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index db496f1cba..b4a9f41213 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -14,9 +14,10 @@ #define SOUND_ANNOUNCEMENTS 2048 #define DISABLE_DEATHRATTLE 4096 #define DISABLE_ARRIVALRATTLE 8192 -#define MEDIHOUND_SLEEPER 16384 - -#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|MEMBER_PUBLIC|INTENT_STYLE|MIDROUND_ANTAG|SOUND_INSTRUMENTS|SOUND_SHIP_AMBIENCE|SOUND_PRAYERS|SOUND_ANNOUNCEMENTS|MEDIHOUND_SLEEPER) +#define MEDIHOUND_SLEEPER 16384 //CITADEL EDITS, vore prefs. +#define EATING_NOISES 32768 +#define DIGESTION_NOISES 65536 +#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|MEMBER_PUBLIC|INTENT_STYLE|MIDROUND_ANTAG|SOUND_INSTRUMENTS|SOUND_SHIP_AMBIENCE|SOUND_PRAYERS|SOUND_ANNOUNCEMENTS|MEDIHOUND_SLEEPER|EATING_NOISES|DIGESTION_NOISES) //Chat toggles #define CHAT_OOC 1 @@ -66,4 +67,4 @@ #define EXP_TYPE_GHOST "Ghost" //Flags in the players table in the db -#define DB_FLAG_EXEMPT 1 \ No newline at end of file +#define DB_FLAG_EXEMPT 1 diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index 7766bd2319..453a02db01 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -11,12 +11,13 @@ //CIT CHANNELS - TRY NOT TO REGRESS #define CHANNEL_PRED 1015 -#define CHANNEL_PREYLOOP 1014 +#define CHANNEL_DIGEST 1014 +#define CHANNEL_PREYLOOP 1013 //THIS SHOULD ALWAYS BE THE LOWEST ONE! //KEEP IT UPDATED -#define CHANNEL_HIGHEST_AVAILABLE 1013 //CIT CHANGE - COMPENSATES FOR VORESOUND CHANNELS +#define CHANNEL_HIGHEST_AVAILABLE 1012 //CIT CHANGE - COMPENSATES FOR VORESOUND CHANNELS #define SOUND_MINIMUM_PRESSURE 10 diff --git a/code/__DEFINES/voreconstants.dm b/code/__DEFINES/voreconstants.dm index edcf4f7fd2..19830c9f72 100644 --- a/code/__DEFINES/voreconstants.dm +++ b/code/__DEFINES/voreconstants.dm @@ -5,6 +5,9 @@ #define DM_NOISY "Noisy" #define DM_DRAGON "Dragon" +#define isbelly(A) istype(A, /obj/belly) + +#define QDEL_NULL_LIST(x) if(x) { for(var/y in x) { qdel(y) } ; x = null } #define VORE_STRUGGLE_EMOTE_CHANCE 40 // Stance for hostile mobs to be in while devouring someone. @@ -60,6 +63,10 @@ GLOBAL_LIST_INIT(pred_vore_sounds, list( "Squish3" = 'sound/vore/pred/squish_03.ogg', "Squish4" = 'sound/vore/pred/squish_04.ogg', "Rustle (cloth)" = 'sound/effects/rustle5.ogg', + "rustle2(cloth)" = 'sound/effects/rustle2.ogg', + "rustle3(cloth)" = 'sound/effects/rustle3.ogg', + "rustle4(cloth)" = 'sound/effects/rustle4.ogg', + "rustle5(cloth)" = 'sound/effects/rustle5.ogg', "None" = null)) /* GLOBAL_LIST_INIT(pred_struggle_sounds, list( @@ -121,3 +128,14 @@ GLOBAL_LIST_INIT(death_prey, list( "death9" = 'sound/vore/prey/death_09.ogg', "death10" = 'sound/vore/prey/death_10.ogg')) */ + +GLOBAL_LIST_INIT(release_sound, list( + "rustle (cloth)" = 'sound/effects/rustle1.ogg', + "rustle2 (cloth)" = 'sound/effects/rustle2.ogg', + "rustle3 (cloth)" = 'sound/effects/rustle3.ogg', + "rustle4 (cloth)" = 'sound/effects/rustle4.ogg', + "rustle5 (cloth)" = 'sound/effects/rustle5.ogg', + "Stomach Move" = 'sound/vore/pred/stomachmove.ogg', + "Pred Escape" = 'sound/vore/pred/escape.ogg', + "Splatter" = 'sound/effects/splat.ogg', + "None" = null)) \ No newline at end of file diff --git a/code/__HELPERS/type2type_vr.dm b/code/__HELPERS/type2type_vr.dm index 09ea0a158a..96e04585d7 100644 --- a/code/__HELPERS/type2type_vr.dm +++ b/code/__HELPERS/type2type_vr.dm @@ -105,3 +105,7 @@ . += copytext(text, last_found, found) last_found = found + delim_len while (found) + +// Returns true if val is from min to max, inclusive. +/proc/IsInRange(val, min, max) + return (val >= min) && (val <= max) \ No newline at end of file diff --git a/code/citadel/dogborgstuff.dm b/code/citadel/dogborgstuff.dm index 55eb6fe356..fcefdcc3e3 100644 --- a/code/citadel/dogborgstuff.dm +++ b/code/citadel/dogborgstuff.dm @@ -608,6 +608,12 @@ playsound(get_turf(hound),"death_pred",50,0,-6,0,channel=CHANNEL_PRED,ignore_walls = FALSE) T.stop_sound_channel(CHANNEL_PRED) T.playsound_local("death_prey",60) + for(var/belly in T.vore_organs) + var/obj/belly/B = belly + for(var/atom/movable/thing in B) + thing.forceMove(src) + if(ismob(thing)) + to_chat(thing, "As [T] melts away around you, you find yourself in [hound]'s [name]") for(var/obj/item/W in T) if(!T.dropItemToGround(W)) qdel(W) diff --git a/code/controllers/subsystem/vore.dm b/code/controllers/subsystem/vore.dm new file mode 100644 index 0000000000..faaa297ca3 --- /dev/null +++ b/code/controllers/subsystem/vore.dm @@ -0,0 +1,41 @@ +#define SSBELLIES_PROCESSED 1 +#define SSBELLIES_IGNORED 2 + +// +// Bellies subsystem - Process vore bellies +// + +SUBSYSTEM_DEF(bellies) + name = "Bellies" + priority = 5 + wait = 1 SECONDS + flags = SS_KEEP_TIMING|SS_NO_INIT + runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME + + var/static/list/belly_list = list() + var/list/currentrun = list() + var/ignored_bellies = 0 + +/datum/controller/subsystem/bellies/stat_entry() + ..("#: [belly_list.len] | P: [ignored_bellies]") + +/datum/controller/subsystem/bellies/fire(resumed = 0) + if (!resumed) + ignored_bellies = 0 + src.currentrun = belly_list.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + var/times_fired = src.times_fired + while(currentrun.len) + var/obj/belly/B = currentrun[currentrun.len] + currentrun.len-- + + if(QDELETED(B)) + belly_list -= B + else + if(B.process_belly(times_fired,wait) == SSBELLIES_IGNORED) + ignored_bellies++ + + if (MC_TICK_CHECK) + return diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 06af2abb9d..c565847c18 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -324,6 +324,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "Ghost pda: [(chat_toggles & CHAT_GHOSTPDA) ? "All Messages" : "Nearest Creatures"]
" dat += "Pull requests: [(chat_toggles & CHAT_PULLR) ? "Yes" : "No"]
" dat += "Midround Antagonist: [(toggles & MIDROUND_ANTAG) ? "Yes" : "No"]
" + //VORE SOUNDS + dat += "Hear Vore Sounds: [(toggles & EATING_NOISES) ? "Yes" : "No"]
" + dat += "Hear Vore Digestion Sounds: [(toggles & DIGESTION_NOISES) ? "Yes" : "No"]
" if(CONFIG_GET(flag/allow_metadata)) dat += "OOC Notes: Edit
" @@ -1765,6 +1768,12 @@ GLOBAL_LIST_EMPTY(preferences_datums) user.client.playtitlemusic() else user.stop_sound_channel(CHANNEL_LOBBYMUSIC) + // VORE SOUND TOGGLES + if("toggleeatingnoise") + toggles ^= EATING_NOISES + + if("toggledigestionnoise") + toggles ^= DIGESTION_NOISES if("ghost_ears") chat_toggles ^= CHAT_GHOSTEARS diff --git a/code/modules/client/preferences_vr.dm b/code/modules/client/preferences_vr.dm index 0f9b6935d3..d787e7e9a8 100644 --- a/code/modules/client/preferences_vr.dm +++ b/code/modules/client/preferences_vr.dm @@ -5,4 +5,7 @@ /datum/preferences/proc/set_biological_gender(var/gender) biological_gender = gender - identifying_gender = gender \ No newline at end of file + identifying_gender = gender + + +/obj/item/clothing/var/hides_bulges = FALSE // OwO wats this? diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 82706b46ee..d090b9f0f7 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -252,10 +252,6 @@ if(91.01 to INFINITY) msg += "[t_He] [t_is] a shitfaced, slobbering wreck.\n" - for (var/I in src.vore_organs) - var/datum/belly/B = vore_organs[I] - msg += B.get_examine_msg() - msg += "" if(!appears_dead) @@ -329,7 +325,7 @@ if(print_flavor_text() && get_visible_name() != "Unknown")//Are we sure we know who this is? Don't show flavor text unless we can recognize them. Prevents certain metagaming with impersonation. msg += "[print_flavor_text()]\n" - + msg += "*---------*" to_chat(user, msg) diff --git a/code/modules/mob/living/carbon/human/examine_vr.dm b/code/modules/mob/living/carbon/human/examine_vr.dm index 8578db809e..6ef1b687c2 100644 --- a/code/modules/mob/living/carbon/human/examine_vr.dm +++ b/code/modules/mob/living/carbon/human/examine_vr.dm @@ -42,13 +42,4 @@ message = "[t_His] stomach is firmly packed with digesting slop. [t_He] must have eaten at least a few times worth their body weight! It looks hard for them to stand, and [t_his] gut jiggles when they move.\n" if(4075 to 10000) // Four or more people. message = "[t_He] [t_is] so absolutely stuffed that you aren't sure how it's possible to move. [t_He] can't seem to swell any bigger. The surface of [t_his] belly looks sorely strained!\n" - return message - -/mob/living/carbon/human/proc/examine_bellies() - var/message = "" - - for (var/I in src.vore_organs) - var/datum/belly/B = vore_organs[I] - message += B.get_examine_msg() - return message \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 1321c499f4..2061924951 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -32,6 +32,7 @@ /mob/living/carbon/human/Destroy() QDEL_NULL(physiology) + QDEL_NULL_LIST(vore_organs) // CITADEL EDIT belly stuff return ..() /mob/living/carbon/human/OpenCraftingMenu() diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 030501066e..bf9a4492d6 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -64,6 +64,10 @@ /mob/living/carbon/human/calculate_affecting_pressure(pressure) if((wear_suit && (wear_suit.flags_1 & STOPSPRESSUREDMAGE_1)) && (head && (head.flags_1 & STOPSPRESSUREDMAGE_1))) return ONE_ATMOSPHERE + if(istype(loc, /obj/belly)) + return ONE_ATMOSPHERE + if(istype(loc, /obj/item/device/dogborg/sleeper)) + return ONE_ATMOSPHERE else return pressure @@ -138,6 +142,8 @@ return FIRE_IMMUNITY_SUIT_MAX_TEMP_PROTECT if(ismob(loc)) return FIRE_IMMUNITY_SUIT_MAX_TEMP_PROTECT + if(istype(loc, /obj/belly)) + return FIRE_IMMUNITY_SUIT_MAX_TEMP_PROTECT //END EDIT if(wear_suit) if(wear_suit.max_heat_protection_temperature >= FIRE_SUIT_MAX_TEMP_PROTECT) @@ -246,10 +252,12 @@ /mob/living/carbon/human/proc/get_cold_protection(temperature) if(has_trait(TRAIT_RESISTCOLD)) return TRUE - + //CITADEL EDIT Mandatory for vore code. if(istype(loc, /obj/item/device/dogborg/sleeper)) return 1 //freezing to death in sleepers ruins fun. + if(istype(loc, /obj/belly)) + return 1 if(ismob(loc)) return 1 //because lazy and being inside somemone insulates you from space //END EDIT diff --git a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm index fba69e4e8b..3317bb2cda 100644 --- a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm @@ -351,3 +351,10 @@ H.update_body() else return + +//misc +/mob/living/carbon/human/dummy + no_vore = TRUE + +/mob/living/carbon/human/vore + devourable = TRUE \ No newline at end of file diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index bf10084758..e5c82f0ead 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -52,6 +52,8 @@ return if(ismob(loc)) return + if(istype(loc, /obj/belly)) + return var/datum/gas_mixture/environment if(loc) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 8043f055bb..0c63ad2ab4 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -54,9 +54,6 @@ handle_fire() - // Citadel Vore code for belly processes - handle_internal_contents() - //stuff in the stomach handle_stomach() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm index 898a2ad734..1af22a8960 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/dragon_vore.dm @@ -1,26 +1,27 @@ /mob/living/simple_animal/hostile/megafauna/dragon vore_active = TRUE + no_vore = FALSE /mob/living/simple_animal/hostile/megafauna/dragon/Initialize() // Create and register 'stomachs' - var/datum/belly/megafauna/dragon/maw/maw = new(src) - var/datum/belly/megafauna/dragon/gullet/gullet = new(src) - var/datum/belly/megafauna/dragon/gut/gut = new(src) - for(var/datum/belly/X in list(maw, gullet, gut)) - vore_organs[X.name] = X + var/obj/belly/megafauna/dragon/maw/maw = new(src) + var/obj/belly/megafauna/dragon/gullet/gullet = new(src) + var/obj/belly/megafauna/dragon/gut/gut = new(src) +// for(var/obj/belly/X in list(maw, gullet, gut)) +// vore_organs[X.name] = X // Connect 'stomachs' together maw.transferlocation = gullet gullet.transferlocation = gut - vore_selected = maw.name // NPC eats into maw + vore_selected = maw // NPC eats into maw return ..() -/datum/belly/megafauna/dragon +/obj/belly/megafauna/dragon human_prey_swallow_time = 50 // maybe enough to switch targets if distracted nonhuman_prey_swallow_time = 50 -/datum/belly/megafauna/dragon/maw +/obj/belly/megafauna/dragon/maw name = "maw" - inside_flavor = "The maw of the dreaded Ash drake closes around you, engulfing you into a swelteringly hot, disgusting enviroment. The acidic saliva tingles over your form while that tongue pushes you further back...towards the dark gullet beyond." + desc = "The maw of the dreaded Ash drake closes around you, engulfing you into a swelteringly hot, disgusting enviroment. The acidic saliva tingles over your form while that tongue pushes you further back...towards the dark gullet beyond." vore_verb = "scoop" vore_sound = 'sound/vore/pred/taurswallow.ogg' swallow_time = 20 @@ -30,9 +31,9 @@ autotransferchance = 66 autotransferwait = 200 -/datum/belly/megafauna/dragon/gullet +/obj/belly/megafauna/dragon/gullet name = "gullet" - inside_flavor = "A ripple of muscle and arching of the tongue pushes you down like any other food. No choice in the matter, you're simply consumed. The dark ambiance of the outside world is replaced with working, wet flesh. Your only light being what you brought with you." + desc = "A ripple of muscle and arching of the tongue pushes you down like any other food. No choice in the matter, you're simply consumed. The dark ambiance of the outside world is replaced with working, wet flesh. Your only light being what you brought with you." swallow_time = 60 // costs extra time to eat directly to here escapechance = 5 // From above, will transfer into gut @@ -40,10 +41,10 @@ autotransferchance = 50 autotransferwait = 200 -/datum/belly/megafauna/dragon/gut +/obj/belly/megafauna/dragon/gut name = "stomach" vore_capacity = 5 //I doubt this many people will actually last in the gut, but... - inside_flavor = "With a rush of burning ichor greeting you, you're introduced to the Drake's stomach. Wrinkled walls greedily grind against you, acidic slimes working into your body as you become fuel and nutriton for a superior predator. All that's left is your body's willingness to resist your destiny." + desc = "With a rush of burning ichor greeting you, you're introduced to the Drake's stomach. Wrinkled walls greedily grind against you, acidic slimes working into your body as you become fuel and nutriton for a superior predator. All that's left is your body's willingness to resist your destiny." digest_mode = DM_DRAGON digest_burn = 5 swallow_time = 100 // costs extra time to eat directly to here diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 09eb099ae7..61e7153434 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -103,7 +103,8 @@ stack_trace("Simple animal being instantiated in nullspace") if(vore_active) init_belly() - verbs |= /mob/living/proc/animal_nom + if(!IsAdvancedToolUser()) + verbs |= /mob/living/simple_animal/proc/animal_nom /mob/living/simple_animal/Destroy() GLOB.simple_animals[AIStatus] -= src diff --git a/code/modules/mob/living/simple_animal/simple_animal_vr.dm b/code/modules/mob/living/simple_animal/simple_animal_vr.dm index 2eca841c17..72459cc74d 100644 --- a/code/modules/mob/living/simple_animal/simple_animal_vr.dm +++ b/code/modules/mob/living/simple_animal/simple_animal_vr.dm @@ -7,18 +7,18 @@ var/vore_default_mode = DM_DIGEST // Default bellymode (DM_DIGEST, DM_HOLD, DM_ABSORB) var/vore_digest_chance = 25 // Chance to switch to digest mode if resisted var/vore_escape_chance = 25 // Chance of resisting out of mob + var/vore_absorb_chance = 0 // chance of absorbtion by mob var/vore_stomach_name // The name for the first belly if not "stomach" var/vore_stomach_flavor // The flavortext for the first belly if not the default var/vore_fullness = 0 // How "full" the belly is (controls icons) + var/list/living_mobs = list() // Release belly contents beforey being gc'd! /mob/living/simple_animal/Destroy() - for(var/I in vore_organs) - var/datum/belly/B = vore_organs[I] - B.release_all_contents() // When your stomach is empty + release_vore_contents() prey_excludes.Cut() . = ..() @@ -34,10 +34,10 @@ vore_fullness = new_fullness - +/* /mob/living/simple_animal/proc/swallow_check() for(var/I in vore_organs) - var/datum/belly/B = vore_organs[I] + var/obj/belly/B = vore_organs[I] if(vore_active) update_fullness() if(!vore_fullness) @@ -48,16 +48,14 @@ /mob/living/simple_animal/proc/swallow_mob() for(var/I in vore_organs) - var/datum/belly/B = vore_organs[I] - for(var/mob/living/M in B.internal_contents) - B.transfer_contents(M, B.transferlocation) - + var/obj/belly/B = vore_organs[I] + for(var/mob/living/M in B.contents) + B.transfer_contents(M, transferlocation) +*/ /mob/living/simple_animal/death() - for(var/I in vore_organs) - var/datum/belly/B = vore_organs[I] - B.release_all_contents() // When your stomach is empty - ..() // then you have my permission to die. + release_vore_contents() + . = ..() // Simple animals have only one belly. This creates it (if it isn't already set up) /mob/living/simple_animal/proc/init_belly() @@ -66,18 +64,19 @@ if(no_vore) //If it can't vore, let's not give it a stomach. return - var/datum/belly/B = new /datum/belly(src) - B.immutable = TRUE + var/obj/belly/B = new /obj/belly(src) + vore_selected = B + B.immutable = 1 B.name = vore_stomach_name ? vore_stomach_name : "stomach" - B.inside_flavor = vore_stomach_flavor ? vore_stomach_flavor : "Your surroundings are warm, soft, and slimy. Makes sense, considering you're inside \the [name]." + B.desc = vore_stomach_flavor ? vore_stomach_flavor : "Your surroundings are warm, soft, and slimy. Makes sense, considering you're inside \the [name]." B.digest_mode = vore_default_mode B.escapable = vore_escape_chance > 0 B.escapechance = vore_escape_chance B.digestchance = vore_digest_chance + B.absorbchance = vore_absorb_chance B.human_prey_swallow_time = swallowTime B.nonhuman_prey_swallow_time = swallowTime B.vore_verb = "swallow" - // TODO - Customizable per mob B.emote_lists[DM_HOLD] = list( // We need more that aren't repetitive. I suck at endo. -Ace "The insides knead at you gently for a moment.", "The guts glorp wetly around you as some air shifts.", @@ -98,5 +97,21 @@ "The juices pooling beneath you sizzle against your sore skin.", "The churning walls slowly pulverize you into meaty nutrients.", "The stomach glorps and gurgles as it tries to work you into slop.") - src.vore_organs[B.name] = B - src.vore_selected = B.name +/* B.emote_lists[DM_ITEMWEAK] = list( + "The burning acids eat away at your form.", + "The muscular stomach flesh grinds harshly against you.", + "The caustic air stings your chest when you try to breathe.", + "The slimy guts squeeze inward to help the digestive juices soften you up.", + "The onslaught against your body doesn't seem to be letting up; you're food now.", + "The predator's body ripples and crushes against you as digestive enzymes pull you apart.", + "The juices pooling beneath you sizzle against your sore skin.", + "The churning walls slowly pulverize you into meaty nutrients.", + "The stomach glorps and gurgles as it tries to work you into slop.")*/ + +//Grab = Nomf +/* +/mob/living/simple_animal/UnarmedAttack(var/atom/A, var/proximity) + . = ..() + + if(a_intent == I_GRAB && isliving(A) && !has_hands) + animal_nom(A)*/ \ No newline at end of file diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 47d35108a1..862991c4b8 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -1,7 +1,8 @@ //include unit test files in this module in this ifdef - +// CITADEL EDIT add vore_tests.dm #ifdef UNIT_TESTS #include "unit_test.dm" #include "reagent_recipe_collisions.dm" #include "reagent_id_typos.dm" +//#include "vore_tests.dm" #endif diff --git a/code/modules/unit_tests/vore_tests.dm b/code/modules/unit_tests/vore_tests.dm new file mode 100644 index 0000000000..6549aa9ce7 --- /dev/null +++ b/code/modules/unit_tests/vore_tests.dm @@ -0,0 +1,218 @@ +/datum/unit_test + var/static/default_mobloc = null + +/datum/unit_test/proc/create_test_mob(var/turf/mobloc = null, var/mobtype = /mob/living/carbon/human, var/with_mind = FALSE) + if(isnull(mobloc)) + if(!default_mobloc) + for(var/turf/simulated/floor/tiled/T in world) + var/pressure = T.zone.air.return_pressure() + if(90 < pressure && pressure < 120) // Find a turf between 90 and 120 + default_mobloc = T + break + mobloc = default_mobloc + if(!mobloc) + Fail("Unable to find a location to create test mob") + return FALSE + + var/mob/living/carbon/human/H = new mobtype(mobloc) + + if(with_mind) + H.mind_initialize("TestKey[rand(0,10000)]") + + return H + +/datum/unit_test/space_suffocation + name = "MOB: human mob suffocates in space" + + var/startOxyloss + var/endOxyloss + var/mob/living/carbon/human/H + async = 1 + +/datum/unit_test/space_suffocation/Run() + var/turf/open/space/T = locate() + + H = new(T) + startOxyloss = H.getOxyLoss() + + return 1 + +/datum/unit_test/space_suffocation/check_result() + if(H.life_tick < 10) + return 0 + + endOxyloss = H.getOxyLoss() + + if(!startOxyloss < endOxyloss) + Fail("Human mob is not taking oxygen damage in space. (Before: [startOxyloss]; after: [endOxyloss])") + + qdel(H) + return 1 + +/datum/unit_test/belly_nonsuffocation + name = "MOB: human mob does not suffocate in a belly" + var/startLifeTick + var/startOxyloss + var/endOxyloss + var/mob/living/carbon/human/pred + var/mob/living/carbon/human/prey + +/datum/unit_test/belly_nonsuffocation/Run() + pred = create_test_mob() + if(!istype(pred)) + return FALSE + prey = create_test_mob(pred.loc) + if(!istype(prey)) + return FALSE + + return TRUE + +/datum/unit_test/belly_nonsuffocation/check_result() + // Unfortuantely we need to wait for the pred's belly to initialize. (Currently after a spawn()) + if(!pred.vore_organs || !pred.vore_organs.len) + return FALSE + + // Now that pred belly exists, we can eat the prey. + if(!pred.vore_selected) + Fail("[pred] has no vore_selected.") + return TRUE + + // Attempt to eat the prey + if(prey.loc != pred.vore_selected) + pred.vore_selected.nom_mob(prey) + + if(prey.loc != pred.vore_selected) + Fail("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]") + return TRUE + + // Okay, we succeeded in eating them, now lets wait a bit + startLifeTick = pred.life_tick + startOxyloss = prey.getOxyLoss() + return FALSE + + if(pred.life_tick < (startLifeTick + 10)) + return FALSE // Wait for them to breathe a few times + + // Alright lets check it! + endOxyloss = prey.getOxyLoss() + if(startOxyloss < endOxyloss) + Fail("Prey takes oxygen damage in a pred's belly! (Before: [startOxyloss]; after: [endOxyloss])") + qdel(prey) + qdel(pred) + return TRUE +//////////////////////////////////////////////////////////////// +/datum/unit_test/belly_spacesafe + name = "MOB: human mob protected from space in a belly" + var/startLifeTick + var/startOxyloss + var/startBruteloss + var/endOxyloss + var/endBruteloss + var/mob/living/carbon/human/pred + var/mob/living/carbon/human/prey + +/datum/unit_test/belly_spacesafe/Run() + pred = create_test_mob() + if(!istype(pred)) + return FALSE + prey = create_test_mob(pred.loc) + if(!istype(prey)) + return FALSE + + return TRUE + +/datum/unit_test/belly_spacesafe/check_result() + // Unfortuantely we need to wait for the pred's belly to initialize. (Currently after a spawn()) + if(!pred.vore_organs || !pred.vore_organs.len) + return FALSE + + // Now that pred belly exists, we can eat the prey. + if(!pred.vore_selected) + Fail("[pred] has no vore_selected.") + return TRUE + + // Attempt to eat the prey + if(prey.loc != pred.vore_selected) + pred.vore_selected.nom_mob(prey) + + if(prey.loc != pred.vore_selected) + Fail("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]") + return TRUE + else + var/turf/T = locate(/turf/open/space) + if(!T) + Fail("could not find a space turf for testing") + return TRUE + else + pred.forceMove(T) + + // Okay, we succeeded in eating them, now lets wait a bit + startLifeTick = pred.life_tick + startOxyloss = prey.getOxyLoss() + startBruteloss = prey.getBruteloss() + return FALSE + + if(pred.life_tick < (startLifeTick + 10)) + return FALSE // Wait for them to breathe a few times + + // Alright lets check it! + endOxyloss = prey.getOxyLoss() + endBruteloss = prey.getBruteLoss() + if(startBruteloss < endBruteloss) + Fail("Prey takes brute damage in space! (Before: [startBruteloss]; after: [endBruteloss])") + qdel(prey) + qdel(pred) + return TRUE +//////////////////////////////////////////////////////////////// +/datum/unit_test/belly_damage + name = "MOB: human mob takes damage from digestion" + var/startLifeTick + var/startBruteBurn + var/endBruteBurn + var/mob/living/carbon/human/pred + var/mob/living/carbon/human/prey + +/datum/unit_test/belly_damage/Run() + pred = create_test_mob() + if(!istype(pred)) + return FALSE + prey = create_test_mob(pred.loc) + if(!istype(prey)) + return FALSE + + return TRUE + +/datum/unit_test/belly_damage/check_result() + // Unfortuantely we need to wait for the pred's belly to initialize. (Currently after a spawn()) + if(!pred.vore_organs || !pred.vore_organs.len) + return FALSE + + // Now that pred belly exists, we can eat the prey. + if(!pred.vore_selected) + Fail("[pred] has no vore_selected.") + return TRUE + + // Attempt to eat the prey + if(prey.loc != pred.vore_selected) + pred.vore_selected.nom_mob(prey) + + if(prey.loc != pred.vore_selected) + Fail("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]") + return TRUE + + // Okay, we succeeded in eating them, now lets wait a bit + pred.vore_selected.digest_mode = DM_DIGEST + startLifeTick = pred.life_tick + startBruteBurn = prey.getBruteLoss() + prey.getFireLoss() + return FALSE + + if(pred.life_tick < (startLifeTick + 10)) + return FALSE // Wait a few ticks for damage to happen + + // Alright lets check it! + endBruteBurn = prey.getBruteLoss() + prey.getFireLoss() + if(startBruteBurn >= endBruteBurn) + Fail("Prey doesn't take damage in digesting belly! (Before: [startBruteBurn]; after: [endBruteBurn])") + qdel(prey) + qdel(pred) + return TRUE diff --git a/code/modules/vore/eating/belly_dat_vr.dm b/code/modules/vore/eating/belly_dat_vr.dm new file mode 100644 index 0000000000..3886eb14cf --- /dev/null +++ b/code/modules/vore/eating/belly_dat_vr.dm @@ -0,0 +1,162 @@ +// THIS IS NOW MERELY LEGACY, because memes. hopefully it won't be dumb. + +// +// The belly object is what holds onto a mob while they're inside a predator. +// It takes care of altering the pred's decription, digesting the prey, relaying struggles etc. +// + +// If you change what variables are on this, then you need to update the copy() proc. + +// +// Parent type of all the various "belly" varieties. +// +/datum/belly + var/name // Name of this location + var/inside_flavor // Flavor text description of inside sight/sound/smells/feels. + var/vore_sound = 'sound/vore/pred/swallow_01.ogg' // Sound when ingesting someone + var/vore_verb = "ingest" // Verb for eating with this in messages + var/human_prey_swallow_time = 10 SECONDS // Time in deciseconds to swallow /mob/living/carbon/human + var/nonhuman_prey_swallow_time = 5 SECONDS // Time in deciseconds to swallow anything else + var/emoteTime = 30 SECONDS // How long between stomach emotes at prey + var/digest_brute = 0 // Brute damage per tick in digestion mode + var/digest_burn = 1 // Burn damage per tick in digestion mode + var/digest_tickrate = 9 // Modulus this of air controller tick number to iterate gurgles on + var/immutable = FALSE // Prevents this belly from being deleted + var/escapable = FALSE // Belly can be resisted out of at any time + var/escapetime = 60 SECONDS // Deciseconds, how long to escape this belly + var/digestchance = 0 // % Chance of stomach beginning to digest if prey struggles +// var/silenced = FALSE // Will the heartbeat/fleshy internal loop play? + var/escapechance = 0 // % Chance of prey beginning to escape if prey struggles. + + var/datum/belly/transferlocation = null // Location that the prey is released if they struggle and get dropped off. + var/transferchance = 0 // % Chance of prey being transferred to transfer location when resisting + var/autotransferchance = 0 // % Chance of prey being autotransferred to transfer location + var/autotransferwait = 10 // Time between trying to transfer. + var/can_taste = FALSE // If this belly prints the flavor of prey when it eats someone. + + var/tmp/digest_mode = DM_HOLD // Whether or not to digest. Default to not digest. + var/tmp/list/digest_modes = list(DM_HOLD,DM_DIGEST,DM_HEAL,DM_NOISY) // Possible digest modes + var/tmp/mob/living/owner // The mob whose belly this is. + var/tmp/list/internal_contents = list() // People/Things you've eaten into this belly! + var/tmp/is_full // Flag for if digested remeans are present. (for disposal messages) + var/tmp/emotePend = FALSE // If there's already a spawned thing counting for the next emote + var/swallow_time = 10 SECONDS // for mob transfering automation + var/vore_capacity = 1 // The capacity (in people) this person can hold + + // Don't forget to watch your commas at the end of each line if you change these. + var/list/struggle_messages_outside = list( + "%pred's %belly wobbles with a squirming meal.", + "%pred's %belly jostles with movement.", + "%pred's %belly briefly swells outward as someone pushes from inside.", + "%pred's %belly fidgets with a trapped victim.", + "%pred's %belly jiggles with motion from inside.", + "%pred's %belly sloshes around.", + "%pred's %belly gushes softly.", + "%pred's %belly lets out a wet squelch.") + + var/list/struggle_messages_inside = list( + "Your useless squirming only causes %pred's slimy %belly to squelch over your body.", + "Your struggles only cause %pred's %belly to gush softly around you.", + "Your movement only causes %pred's %belly to slosh around you.", + "Your motion causes %pred's %belly to jiggle.", + "You fidget around inside of %pred's %belly.", + "You shove against the walls of %pred's %belly, making it briefly swell outward.", + "You jostle %pred's %belly with movement.", + "You squirm inside of %pred's %belly, making it wobble around.") + + var/list/digest_messages_owner = list( + "You feel %prey's body succumb to your digestive system, which breaks it apart into soft slurry.", + "You hear a lewd glorp as your %belly muscles grind %prey into a warm pulp.", + "Your %belly lets out a rumble as it melts %prey into sludge.", + "You feel a soft gurgle as %prey's body loses form in your %belly. They're nothing but a soft mass of churning slop now.", + "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your thighs.", + "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your rump.", + "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your belly.", + "Your %belly groans as %prey falls apart into a thick soup. You can feel their remains soon flowing deeper into your body to be absorbed.", + "Your %belly kneads on every fiber of %prey, softening them down into mush to fuel your next hunt.", + "Your %belly churns %prey down into a hot slush. You can feel the nutrients coursing through your digestive track with a series of long, wet glorps.") + + var/list/digest_messages_prey = list( + "Your body succumbs to %pred's digestive system, which breaks you apart into soft slurry.", + "%pred's %belly lets out a lewd glorp as their muscles grind you into a warm pulp.", + "%pred's %belly lets out a rumble as it melts you into sludge.", + "%pred feels a soft gurgle as your body loses form in their %belly. You're nothing but a soft mass of churning slop now.", + "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's thighs.", + "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's rump.", + "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's belly.", + "%pred's %belly groans as you fall apart into a thick soup. Your remains soon flow deeper into %pred's body to be absorbed.", + "%pred's %belly kneads on every fiber of your body, softening you down into mush to fuel their next hunt.", + "%pred's %belly churns you down into a hot slush. Your nutrient-rich remains course through their digestive track with a series of long, wet glorps.") + + var/list/examine_messages = list( + "They have something solid in their %belly!", + "It looks like they have something in their %belly!") + + //Mostly for being overridden on precreated bellies on mobs. Could be VV'd into + //a carbon's belly if someone really wanted. No UI for carbons to adjust this. + //List has indexes that are the digestion mode strings, and keys that are lists of strings. + var/list/emote_lists = list() + +//OLD: This only exists for legacy conversion purposes +//It's called whenever an old datum-style belly is loaded +/datum/belly/proc/copy(obj/belly/new_belly) + + //// Non-object variables + new_belly.name = name + new_belly.desc = inside_flavor + new_belly.vore_sound = vore_sound + new_belly.vore_verb = vore_verb + new_belly.human_prey_swallow_time = human_prey_swallow_time + new_belly.nonhuman_prey_swallow_time = nonhuman_prey_swallow_time + new_belly.emote_time = emoteTime + new_belly.digest_brute = digest_brute + new_belly.digest_burn = digest_burn + new_belly.immutable = immutable + new_belly.can_taste = can_taste + new_belly.escapable = escapable + new_belly.escapetime = escapetime + new_belly.digestchance = digestchance + new_belly.escapechance = escapechance + new_belly.transferchance = transferchance + new_belly.transferlocation = transferlocation + + //// Object-holding variables + //struggle_messages_outside - strings + new_belly.struggle_messages_outside.Cut() + for(var/I in struggle_messages_outside) + new_belly.struggle_messages_outside += I + + //struggle_messages_inside - strings + new_belly.struggle_messages_inside.Cut() + for(var/I in struggle_messages_inside) + new_belly.struggle_messages_inside += I + + //digest_messages_owner - strings + new_belly.digest_messages_owner.Cut() + for(var/I in digest_messages_owner) + new_belly.digest_messages_owner += I + + //digest_messages_prey - strings + new_belly.digest_messages_prey.Cut() + for(var/I in digest_messages_prey) + new_belly.digest_messages_prey += I + + //examine_messages - strings + new_belly.examine_messages.Cut() + for(var/I in examine_messages) + new_belly.examine_messages += I + + //emote_lists - index: digest mode, key: list of strings + new_belly.emote_lists.Cut() + for(var/K in emote_lists) + new_belly.emote_lists[K] = list() + for(var/I in emote_lists[K]) + new_belly.emote_lists[K] += I + + return new_belly + +// // // // // // // // // // // // +// // // LEGACY USE ONLY!! // // // +// // // // // // // // // // // // +// See top of file! // +// // // // // // // // // // // // diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm new file mode 100644 index 0000000000..14ded0b7cc --- /dev/null +++ b/code/modules/vore/eating/belly_obj_vr.dm @@ -0,0 +1,655 @@ +//#define VORE_SOUND_FALLOFF 0.05 + +// +// Belly system 2.0, now using objects instead of datums because EH at datums. +// How many times have I rewritten bellies and vore now? -Aro +// + +// If you change what variables are on this, then you need to update the copy() proc. + +// +// Parent type of all the various "belly" varieties. +// +/obj/belly + name = "belly" // Name of this location + desc = "It's a belly! You're in it!" // Flavor text description of inside sight/sound/smells/feels. + var/vore_sound = 'sound/vore/pred/swallow_01.ogg' // Sound when ingesting someone + var/vore_verb = "ingest" // Verb for eating with this in messages + var/release_sound = 'sound/effects/splat.ogg' + var/human_prey_swallow_time = 100 // Time in deciseconds to swallow /mob/living/carbon/human + var/nonhuman_prey_swallow_time = 30 // Time in deciseconds to swallow anything else + var/emote_time = 60 SECONDS // How long between stomach emotes at prey + var/digest_brute = 2 // Brute damage per tick in digestion mode + var/digest_burn = 2 // Burn damage per tick in digestion mode + var/immutable = FALSE // Prevents this belly from being deleted + var/escapable = TRUE // Belly can be resisted out of at any time + var/escapetime = 20 SECONDS // Deciseconds, how long to escape this belly + var/digestchance = 0 // % Chance of stomach beginning to digest if prey struggles + var/absorbchance = 0 // % Chance of stomach beginning to absorb if prey struggles + var/escapechance = 100 // % Chance of prey beginning to escape if prey struggles. + var/can_taste = FALSE // If this belly prints the flavor of prey when it eats someone. + var/bulge_size = 0.25 // The minimum size the prey has to be in order to show up on examine. +// var/shrink_grow_size = 1 // This horribly named variable determines the minimum/maximum size it will shrink/grow prey to. + var/silent = FALSE + + var/transferlocation = null // Location that the prey is released if they struggle and get dropped off. + var/transferchance = 0 // % Chance of prey being transferred to transfer location when resisting + var/autotransferchance = 0 // % Chance of prey being autotransferred to transfer location + var/autotransferwait = 10 // Time between trying to transfer. + var/swallow_time = 10 SECONDS // for mob transfering automation + var/vore_capacity = 1 // simple animal nom capacity + + //I don't think we've ever altered these lists. making them static until someone actually overrides them somewhere. + var/tmp/static/list/digest_modes = list(DM_HOLD,DM_DIGEST,DM_HEAL,DM_NOISY) // Possible digest modes + + var/tmp/mob/living/owner // The mob whose belly this is. + var/tmp/digest_mode = DM_HOLD // Current mode the belly is set to from digest_modes (+transform_modes if human) + var/tmp/next_process = 0 // Waiting for this SSbellies times_fired to process again. + var/tmp/list/items_preserved = list() // Stuff that wont digest so we shouldn't process it again. + var/tmp/next_emote = 0 // When we're supposed to print our next emote, as a belly controller tick # + var/tmp/recent_sound = FALSE // Prevent audio spam + + // Don't forget to watch your commas at the end of each line if you change these. + var/list/struggle_messages_outside = list( + "%pred's %belly wobbles with a squirming meal.", + "%pred's %belly jostles with movement.", + "%pred's %belly briefly swells outward as someone pushes from inside.", + "%pred's %belly fidgets with a trapped victim.", + "%pred's %belly jiggles with motion from inside.", + "%pred's %belly sloshes around.", + "%pred's %belly gushes softly.", + "%pred's %belly lets out a wet squelch.") + + var/list/struggle_messages_inside = list( + "Your useless squirming only causes %pred's slimy %belly to squelch over your body.", + "Your struggles only cause %pred's %belly to gush softly around you.", + "Your movement only causes %pred's %belly to slosh around you.", + "Your motion causes %pred's %belly to jiggle.", + "You fidget around inside of %pred's %belly.", + "You shove against the walls of %pred's %belly, making it briefly swell outward.", + "You jostle %pred's %belly with movement.", + "You squirm inside of %pred's %belly, making it wobble around.") + + var/list/digest_messages_owner = list( + "You feel %prey's body succumb to your digestive system, which breaks it apart into soft slurry.", + "You hear a lewd glorp as your %belly muscles grind %prey into a warm pulp.", + "Your %belly lets out a rumble as it melts %prey into sludge.", + "You feel a soft gurgle as %prey's body loses form in your %belly. They're nothing but a soft mass of churning slop now.", + "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your thighs.", + "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your rump.", + "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your belly.", + "Your %belly groans as %prey falls apart into a thick soup. You can feel their remains soon flowing deeper into your body to be absorbed.", + "Your %belly kneads on every fiber of %prey, softening them down into mush to fuel your next hunt.", + "Your %belly churns %prey down into a hot slush. You can feel the nutrients coursing through your digestive track with a series of long, wet glorps.") + + var/list/digest_messages_prey = list( + "Your body succumbs to %pred's digestive system, which breaks you apart into soft slurry.", + "%pred's %belly lets out a lewd glorp as their muscles grind you into a warm pulp.", + "%pred's %belly lets out a rumble as it melts you into sludge.", + "%pred feels a soft gurgle as your body loses form in their %belly. You're nothing but a soft mass of churning slop now.", + "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's thighs.", + "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's rump.", + "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's belly.", + "%pred's %belly groans as you fall apart into a thick soup. Your remains soon flow deeper into %pred's body to be absorbed.", + "%pred's %belly kneads on every fiber of your body, softening you down into mush to fuel their next hunt.", + "%pred's %belly churns you down into a hot slush. Your nutrient-rich remains course through their digestive track with a series of long, wet glorps.") + + var/list/examine_messages = list( + "They have something solid in their %belly!", + "It looks like they have something in their %belly!") + + //Mostly for being overridden on precreated bellies on mobs. Could be VV'd into + //a carbon's belly if someone really wanted. No UI for carbons to adjust this. + //List has indexes that are the digestion mode strings, and keys that are lists of strings. + var/tmp/list/emote_lists = list() + +//For serialization, keep this updated, required for bellies to save correctly. +/obj/belly/vars_to_save() + return ..() + list( + "name", + "desc", + "vore_sound", + "vore_verb", + "release_sound", + "human_prey_swallow_time", + "nonhuman_prey_swallow_time", + "emote_time", + "digest_brute", + "digest_burn", + "immutable", + "can_taste", + "escapable", + "escapetime", + "digestchance", + "absorbchance", + "escapechance", + "transferchance", + "transferlocation", + "bulge_size", + "struggle_messages_outside", + "struggle_messages_inside", + "digest_messages_owner", + "digest_messages_prey", + "examine_messages", + "emote_lists", + "silent" + ) + + //ommitted list + // "shrink_grow_size", +/obj/belly/New(var/newloc) + . = ..(newloc) + //If not, we're probably just in a prefs list or something. + if(isliving(newloc)) + owner = loc + owner.vore_organs |= src + SSbellies.belly_list += src + +/obj/belly/Destroy() + SSbellies.belly_list -= src + if(owner) + owner.vore_organs -= src + owner = null + . = ..() + +// Called whenever an atom enters this belly +/obj/belly/Entered(var/atom/movable/thing,var/atom/OldLoc) + if(OldLoc in contents) + return //Someone dropping something (or being stripdigested) + + //Generic entered message + to_chat(owner,"[thing] slides into your [lowertext(name)].") + + //Sound w/ antispam flag setting + if(!silent && !recent_sound) + for(var/mob/M in get_hearers_in_view(5, get_turf(owner))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(owner),"[src.vore_sound]",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) + recent_sound = TRUE + + //Messages if it's a mob + if(isliving(thing)) + var/mob/living/M = thing + if(desc) + to_chat(M, "[desc]") + var/taste + if(can_taste && (taste = M.get_taste_message(FALSE))) + to_chat(owner, "[M] tastes of [taste].") + +// Release all contents of this belly into the owning mob's location. +// If that location is another mob, contents are transferred into whichever of its bellies the owning mob is in. +// Returns the number of mobs so released. +/obj/belly/proc/release_all_contents(var/include_absorbed = FALSE) + var/atom/destination = drop_location() + var/count = 0 + for(var/thing in contents) + var/atom/movable/AM = thing + if(isliving(AM)) + var/mob/living/L = AM + if(L.absorbed && !include_absorbed) + continue + L.absorbed = FALSE + for(var/mob/living/W in AM) + W.stop_sound_channel(CHANNEL_PREYLOOP) + AM.forceMove(destination) // Move the belly contents into the same location as belly's owner. + count++ + for(var/mob/M in get_hearers_in_view(5, get_turf(owner))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(owner),"[src.release_sound]",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) + items_preserved.Cut() + owner.visible_message("[owner] expels everything from their [lowertext(name)]!") + owner.update_icons() + + return count + +// Release a specific atom from the contents of this belly into the owning mob's location. +// If that location is another mob, the atom is transferred into whichever of its bellies the owning mob is in. +// Returns the number of atoms so released. +/obj/belly/proc/release_specific_contents(var/atom/movable/M) + if (!(M in contents)) + return FALSE // They weren't in this belly anyway + + M.forceMove(drop_location()) // Move the belly contents into the same location as belly's owner. + items_preserved -= M + for(var/mob/living/P in M) + P.stop_sound_channel(CHANNEL_PREYLOOP) + if(release_sound) + for(var/mob/H in get_hearers_in_view(5, get_turf(owner))) + if(H.client && H.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(owner),"[src.release_sound]",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) + + if(istype(M,/mob/living)) + var/mob/living/ML = M + var/mob/living/OW = owner + if(ML.absorbed) + ML.absorbed = FALSE + if(ishuman(M) && ishuman(OW)) + var/mob/living/carbon/human/Prey = M + var/mob/living/carbon/human/Pred = OW + var/absorbed_count = 2 //Prey that we were, plus the pred gets a portion + for(var/mob/living/P in contents) + if(P.absorbed) + absorbed_count++ + Pred.reagents.trans_to(Prey, Pred.reagents.total_volume / absorbed_count) + + owner.visible_message("[owner] expels [M] from their [lowertext(name)]!") + owner.update_icons() + return TRUE + +// Actually perform the mechanics of devouring the tasty prey. +// The purpose of this method is to avoid duplicate code, and ensure that all necessary +// steps are taken. +/obj/belly/proc/nom_mob(var/mob/prey, var/mob/user) + var/sound/preyloop = sound('sound/vore/prey/loop.ogg', repeat = TRUE) + if(owner.stat == DEAD) + return + if (prey.buckled) + prey.buckled.unbuckle_mob(prey,TRUE) + + prey.forceMove(src) + prey.playsound_local(loc,preyloop,70,0, channel = CHANNEL_PREYLOOP) + owner.updateVRPanel() + + for(var/mob/living/M in contents) + M.updateVRPanel() + + // Setup the autotransfer checks if needed + if(transferlocation && autotransferchance > 0) + addtimer(CALLBACK(src, /obj/belly/.proc/check_autotransfer, prey), autotransferwait) + +/obj/belly/proc/check_autotransfer(var/mob/prey) + // Some sanity checks + if(transferlocation && (autotransferchance > 0) && (prey in contents)) + if(prob(autotransferchance)) + // Double check transferlocation isn't insane + if(verify_transferlocation()) + transfer_contents(prey, transferlocation) + else + // Didn't transfer, so wait before retrying + addtimer(CALLBACK(src, /obj/belly/.proc/check_autotransfer, prey), autotransferwait) + +/obj/belly/proc/verify_transferlocation() + for(var/I in owner.vore_organs) + var/obj/belly/B = owner.vore_organs[I] + if(B == transferlocation) + return TRUE + + for(var/I in owner.vore_organs) + var/obj/belly/B = owner.vore_organs[I] + if(B == transferlocation) + transferlocation = B + return TRUE + return FALSE + + +// Get the line that should show up in Examine message if the owner of this belly +// is examined. By making this a proc, we not only take advantage of polymorphism, +// but can easily make the message vary based on how many people are inside, etc. +// Returns a string which shoul be appended to the Examine output. +/obj/belly/proc/get_examine_msg() + if(contents.len && examine_messages.len) + var/formatted_message + var/raw_message = pick(examine_messages) + var/total_bulge = 0 + + formatted_message = replacetext(raw_message,"%belly",lowertext(name)) + formatted_message = replacetext(formatted_message,"%pred",owner) + formatted_message = replacetext(formatted_message,"%prey",english_list(contents)) + for(var/mob/living/P in contents) + if(!P.absorbed) //This is required first, in case there's a person absorbed and not absorbed in a stomach. + total_bulge += P.mob_size + if(total_bulge >= bulge_size && bulge_size != 0) + return("[formatted_message]
") + else + return "" + +// The next function gets the messages set on the belly, in human-readable format. +// This is useful in customization boxes and such. The delimiter right now is \n\n so +// in message boxes, this looks nice and is easily delimited. +/obj/belly/proc/get_messages(var/type, var/delim = "\n\n") + ASSERT(type == "smo" || type == "smi" || type == "dmo" || type == "dmp" || type == "em") + var/list/raw_messages + + switch(type) + if("smo") + raw_messages = struggle_messages_outside + if("smi") + raw_messages = struggle_messages_inside + if("dmo") + raw_messages = digest_messages_owner + if("dmp") + raw_messages = digest_messages_prey + if("em") + raw_messages = examine_messages + + var/messages = list2text(raw_messages,delim) + return messages + +// The next function sets the messages on the belly, from human-readable var +// replacement strings and linebreaks as delimiters (two \n\n by default). +// They also sanitize the messages. +/obj/belly/proc/set_messages(var/raw_text, var/type, var/delim = "\n\n") + ASSERT(type == "smo" || type == "smi" || type == "dmo" || type == "dmp" || type == "em") + + var/list/raw_list = text2list(html_encode(raw_text),delim) + if(raw_list.len > 10) + raw_list.Cut(11) + testing("[owner] tried to set [lowertext(name)] with 11+ messages") + + for(var/i = 1, i <= raw_list.len, i++) + if(length(raw_list[i]) > 160 || length(raw_list[i]) < 10) //160 is fudged value due to htmlencoding increasing the size + raw_list.Cut(i,i) + testing("[owner] tried to set [lowertext(name)] with >121 or <10 char message") + else + raw_list[i] = readd_quotes(raw_list[i]) + //Also fix % sign for var replacement + raw_list[i] = replacetext(raw_list[i],"%","%") + + ASSERT(raw_list.len <= 10) //Sanity + + switch(type) + if("smo") + struggle_messages_outside = raw_list + if("smi") + struggle_messages_inside = raw_list + if("dmo") + digest_messages_owner = raw_list + if("dmp") + digest_messages_prey = raw_list + if("em") + examine_messages = raw_list + + return + +// Handle the death of a mob via digestion. +// Called from the process_Life() methods of bellies that digest prey. +// Default implementation calls M.death() and removes from internal contents. +// Indigestable items are removed, and M is deleted. +/obj/belly/proc/digestion_death(var/mob/living/M) + //M.death(1) // "Stop it he's already dead..." Basically redundant and the reason behind screaming mouse carcasses. + if(M.ckey) + message_admins("[key_name(owner)] has digested [key_name(M)] in their [lowertext(name)] ([owner ? "JMP" : "null"])") + log_attack("[key_name(owner)] digested [key_name(M)].") + + // If digested prey is also a pred... anyone inside their bellies gets moved up. + if(is_vore_predator(M)) + for(var/belly in M.vore_organs) + var/obj/belly/B = belly + for(var/thing in B) + var/atom/movable/AM = thing + AM.forceMove(owner.loc) + if(isliving(AM)) + to_chat(AM,"As [M] melts away around you, you find yourself in [owner]'s [lowertext(name)]") + + //Drop all items into the belly + for(var/obj/item/W in M) + if(!M.dropItemToGround(W)) + qdel(W) + +/* //Reagent transfer //maybe someday + if(ishuman(owner)) + var/mob/living/carbon/human/Pred = owner + if(ishuman(M)) + var/mob/living/carbon/human/Prey = M + Prey.bloodstr.del_reagent("numbenzyme") + Prey.bloodstr.trans_to_holder(Pred.bloodstr, Prey.bloodstr.total_volume, 0.5, TRUE) // Copy=TRUE because we're deleted anyway + Prey.ingested.trans_to_holder(Pred.bloodstr, Prey.ingested.total_volume, 0.5, TRUE) // Therefore don't bother spending cpu + Prey.touching.trans_to_holder(Pred.bloodstr, Prey.touching.total_volume, 0.5, TRUE) // On updating the prey's reagents + else if(M.reagents) + M.reagents.trans_to_holder(Pred.bloodstr, M.reagents.total_volume, 0.5, TRUE) */ + + // Delete the digested mob + qdel(M) + +// Handle a mob being absorbed +/obj/belly/proc/absorb_living(var/mob/living/M) + M.absorbed = TRUE + to_chat(M,"[owner]'s [lowertext(name)] absorbs your body, making you part of them.") + to_chat(owner,"Your [lowertext(name)] absorbs [M]'s body, making them part of you.") + +// Reagent sharing is neat, but eh. I'll figure it out later +/* if(ishuman(M) && ishuman(owner)) + var/mob/living/carbon/human/Prey = M + var/mob/living/carbon/human/Pred = owner + //Reagent sharing for absorbed with pred - Copy so both pred and prey have these reagents. + Prey.bloodstr.trans_to_holder(Pred.bloodstr, Prey.bloodstr.total_volume, copy = TRUE) + Prey.ingested.trans_to_holder(Pred.bloodstr, Prey.ingested.total_volume, copy = TRUE) + Prey.touching.trans_to_holder(Pred.bloodstr, Prey.touching.total_volume, copy = TRUE) + // TODO - Find a way to make the absorbed prey share the effects with the pred. + // Currently this is infeasible because reagent containers are designed to have a single my_atom, and we get + // problems when A absorbs B, and then C absorbs A, resulting in B holding onto an invalid reagent container. +*/ + //This is probably already the case, but for sub-prey, it won't be. + if(M.loc != src) + M.forceMove(src) + + //Seek out absorbed prey of the prey, absorb them too. + //This in particular will recurse oddly because if there is absorbed prey of prey of prey... + //it will just move them up one belly. This should never happen though since... when they were + //absobred, they should have been absorbed as well! + for(var/belly in M.vore_organs) + var/obj/belly/B = belly + for(var/mob/living/Mm in B) + if(Mm.absorbed) + absorb_living(Mm) + + //Update owner + owner.updateVRPanel() + +//Digest a single item +//Receives a return value from digest_act that's how much nutrition +//the item should be worth +/obj/belly/proc/digest_item(var/obj/item/item) + var/digested = item.digest_act(src, owner) + if(!digested) + items_preserved |= item + else +// owner.nutrition += (5 * digested) // haha no. + if(iscyborg(owner)) + var/mob/living/silicon/robot/R = owner + R.cell.charge += (50 * digested) + +//Determine where items should fall out of us into. +//Typically just to the owner's location. +/obj/belly/drop_location() + //Should be the case 99.99% of the time + if(owner) + return owner.loc + //Sketchy fallback for safety, put them somewhere safe. + else if(ismob(src)) + testing("[src] (\ref[src]) doesn't have an owner, and dropped someone at a latespawn point!") + SSjob.SendToLateJoin(src) + // wew lad. let's see if this never gets used, hopefully + else + qdel(src) //final option, I guess. + testing("[src] (\ref[src]) was QDEL'd for not having a drop_location!") + +//Handle a mob struggling +// Called from /mob/living/carbon/relaymove() +/obj/belly/proc/relay_resist(var/mob/living/R) + if (!(R in contents)) + return // User is not in this belly + + R.setClickCooldown(50) + + if(owner.stat || !owner.client && R.a_intent != INTENT_HELP) //If owner is stat (dead, KO) we can actually escape + to_chat(R,"You attempt to climb out of \the [lowertext(name)]. (This will take around 5 seconds.)") + to_chat(owner,"Someone is attempting to climb out of your [lowertext(name)]!") + + if(do_after(R, 50, owner)) + if(owner.stat && (R in contents) && R.a_intent != INTENT_HELP) //Can still escape and want to? + release_specific_contents(R) + return + else if(!(R in contents)) //Aren't even in the belly. Quietly fail. + return + else //Belly became inescapable or mob revived + to_chat(R,"Your attempt to escape [lowertext(name)] has failed!") + to_chat(owner,"The attempt to escape from your [lowertext(name)] has failed!") + return + return + var/struggle_outer_message = pick(struggle_messages_outside) + var/struggle_user_message = pick(struggle_messages_inside) + + struggle_outer_message = replacetext(struggle_outer_message,"%pred",owner) + struggle_outer_message = replacetext(struggle_outer_message,"%prey",R) + struggle_outer_message = replacetext(struggle_outer_message,"%belly",lowertext(name)) + + struggle_user_message = replacetext(struggle_user_message,"%pred",owner) + struggle_user_message = replacetext(struggle_user_message,"%prey",R) + struggle_user_message = replacetext(struggle_user_message,"%belly",lowertext(name)) + + struggle_outer_message = "" + struggle_outer_message + "" + struggle_user_message = "" + struggle_user_message + "" + + for(var/mob/M in get_hearers_in_view(3, get_turf(owner))) + M.show_message(struggle_outer_message, 2) // hearable + to_chat(R,struggle_user_message) + + if(!silent) + for(var/mob/M in get_hearers_in_view(5, get_turf(owner))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(owner),"struggle_sound",35,0,-5,1,ignore_walls = FALSE,channel=CHANNEL_PRED) + R.stop_sound_channel(CHANNEL_PRED) + var/sound/prey_struggle = sound(get_sfx("prey_struggle")) + R.playsound_local(get_turf(R),prey_struggle,45,0) + + if(R.a_intent != INTENT_HELP) //If on non help intent + to_chat(R,"You start to climb out of \the [lowertext(name)].") + to_chat(owner,"Someone is attempting to climb out of your [lowertext(name)]!") + if(do_after(R, escapetime, owner)) + if((owner.stat || !owner.client || escapable) && (R in contents)) + release_specific_contents(R) + to_chat(R,"You climb out of \the [lowertext(name)].") + to_chat(owner,"[R] climbs out of your [lowertext(name)]!") + for(var/mob/M in hearers(4, owner)) + M.show_message("[R] climbs out of [owner]'s [lowertext(name)]!", 2) + return + else if(!istype(loc, /obj/belly)) //Aren't even in the belly. Quietly fail. + return + else //Belly became inescapable. + to_chat(R,"Your attempt to escape [lowertext(name)] has failed!") + to_chat(owner,"The attempt to escape from your [lowertext(name)] has failed!") + return + + else if(prob(transferchance) && transferlocation) //Next, let's have it see if they end up getting into an even bigger mess then when they started. + var/obj/belly/dest_belly + for(var/belly in owner.vore_organs) + var/obj/belly/B = belly + if(B.name == transferlocation) + dest_belly = B + break + if(!dest_belly) + to_chat(owner, "Something went wrong with your belly transfer settings. Your [lowertext(name)] has had it's transfer chance and transfer location cleared as a precaution.") + transferchance = 0 + transferlocation = null + return + + to_chat(R,"Your attempt to escape [lowertext(name)] has failed and your struggles only results in you sliding into [owner]'s [transferlocation]!") + to_chat(owner,"Someone slid into your [transferlocation] due to their struggling inside your [lowertext(name)]!") + transfer_contents(R, dest_belly) + return +/* + else if(prob(absorbchance) && digest_mode != DM_ABSORB) //After that, let's have it run the absorb chance. + to_chat(R,"In response to your struggling, \the [lowertext(name)] begins to cling more tightly...") + to_chat(owner,"You feel your [lowertext(name)] start to cling onto its contents...") + digest_mode = DM_ABSORB + return + + else if(prob(digestchance) && digest_mode != DM_ITEMWEAK && digest_mode != DM_DIGEST) //Finally, let's see if it should run the digest chance. + to_chat(R,"In response to your struggling, \the [lowertext(name)] begins to get more active...") + to_chat(owner,"You feel your [lowertext(name)] beginning to become active!") + digest_mode = DM_ITEMWEAK + return + + else if(prob(digestchance) && digest_mode == DM_ITEMWEAK) //Oh god it gets even worse if you fail twice! + to_chat(R,"In response to your struggling, \the [lowertext(name)] begins to get even more active!") + to_chat(owner,"You feel your [lowertext(name)] beginning to become even more active!") + digest_mode = DM_DIGEST + return */ + else if(prob(digestchance)) //Finally, let's see if it should run the digest chance.) + to_chat(R, "In response to your struggling, \the [name] begins to get more active...") + to_chat(owner, "You feel your [name] beginning to become active!") + digest_mode = DM_DIGEST + return + + else //Nothing interesting happened. + to_chat(R,"You make no progress in escaping [owner]'s [lowertext(name)].") + to_chat(owner,"Your prey appears to be unable to make any progress in escaping your [lowertext(name)].") + return + +//Transfers contents from one belly to another +/obj/belly/proc/transfer_contents(var/atom/movable/content, var/obj/belly/target, silent = FALSE) + if(!(content in src) || !istype(target)) + return + target.nom_mob(content, target.owner) + if(!silent) + for(var/mob/M in get_hearers_in_view(5, get_turf(owner))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(owner),"[src.vore_sound]",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) + owner.updateVRPanel() + for(var/mob/living/M in contents) + M.updateVRPanel() + +// Belly copies and then returns the copy +// Needs to be updated for any var changes +/obj/belly/proc/copy(mob/new_owner) + var/obj/belly/dupe = new /obj/belly(new_owner) + + //// Non-object variables + dupe.name = name + dupe.desc = desc + dupe.vore_sound = vore_sound + dupe.vore_verb = vore_verb + dupe.release_sound = release_sound + dupe.human_prey_swallow_time = human_prey_swallow_time + dupe.nonhuman_prey_swallow_time = nonhuman_prey_swallow_time + dupe.emote_time = emote_time + dupe.digest_brute = digest_brute + dupe.digest_burn = digest_burn + dupe.immutable = immutable + dupe.can_taste = can_taste + dupe.escapable = escapable + dupe.escapetime = escapetime + dupe.digestchance = digestchance + dupe.absorbchance = absorbchance + dupe.escapechance = escapechance + dupe.transferchance = transferchance + dupe.transferlocation = transferlocation + dupe.bulge_size = bulge_size +// dupe.shrink_grow_size = shrink_grow_size + + //// Object-holding variables + //struggle_messages_outside - strings + dupe.struggle_messages_outside.Cut() + for(var/I in struggle_messages_outside) + dupe.struggle_messages_outside += I + + //struggle_messages_inside - strings + dupe.struggle_messages_inside.Cut() + for(var/I in struggle_messages_inside) + dupe.struggle_messages_inside += I + + //digest_messages_owner - strings + dupe.digest_messages_owner.Cut() + for(var/I in digest_messages_owner) + dupe.digest_messages_owner += I + + //digest_messages_prey - strings + dupe.digest_messages_prey.Cut() + for(var/I in digest_messages_prey) + dupe.digest_messages_prey += I + + //examine_messages - strings + dupe.examine_messages.Cut() + for(var/I in examine_messages) + dupe.examine_messages += I + + //emote_lists - index: digest mode, key: list of strings + dupe.emote_lists.Cut() + for(var/K in emote_lists) + dupe.emote_lists[K] = list() + for(var/I in emote_lists[K]) + dupe.emote_lists[K] += I + dupe.silent = silent + + return dupe diff --git a/code/modules/vore/eating/belly_vr.dm b/code/modules/vore/eating/belly_vr.dm deleted file mode 100644 index d175f51e02..0000000000 --- a/code/modules/vore/eating/belly_vr.dm +++ /dev/null @@ -1,467 +0,0 @@ -// -// The belly object is what holds onto a mob while they're inside a predator. -// It takes care of altering the pred's decription, digesting the prey, relaying struggles etc. -// - -// If you change what variables are on this, then you need to update the copy() proc. - -// -// Parent type of all the various "belly" varieties. -// -/datum/belly - var/name // Name of this location - var/inside_flavor // Flavor text description of inside sight/sound/smells/feels. - var/vore_sound = 'sound/vore/pred/swallow_01.ogg' // Sound when ingesting someone - var/vore_verb = "ingest" // Verb for eating with this in messages - var/human_prey_swallow_time = 10 SECONDS // Time in deciseconds to swallow /mob/living/carbon/human - var/nonhuman_prey_swallow_time = 5 SECONDS // Time in deciseconds to swallow anything else - var/emoteTime = 30 SECONDS // How long between stomach emotes at prey - var/digest_brute = 0 // Brute damage per tick in digestion mode - var/digest_burn = 1 // Burn damage per tick in digestion mode - var/digest_tickrate = 9 // Modulus this of air controller tick number to iterate gurgles on - var/immutable = FALSE // Prevents this belly from being deleted - var/escapable = FALSE // Belly can be resisted out of at any time - var/escapetime = 60 SECONDS // Deciseconds, how long to escape this belly - var/digestchance = 0 // % Chance of stomach beginning to digest if prey struggles -// var/silenced = FALSE // Will the heartbeat/fleshy internal loop play? - var/escapechance = 0 // % Chance of prey beginning to escape if prey struggles. - - var/datum/belly/transferlocation = null // Location that the prey is released if they struggle and get dropped off. - var/transferchance = 0 // % Chance of prey being transferred to transfer location when resisting - var/autotransferchance = 0 // % Chance of prey being autotransferred to transfer location - var/autotransferwait = 10 // Time between trying to transfer. - var/can_taste = FALSE // If this belly prints the flavor of prey when it eats someone. - - var/tmp/digest_mode = DM_HOLD // Whether or not to digest. Default to not digest. - var/tmp/list/digest_modes = list(DM_HOLD,DM_DIGEST,DM_HEAL,DM_NOISY) // Possible digest modes - var/tmp/mob/living/owner // The mob whose belly this is. - var/tmp/list/internal_contents = list() // People/Things you've eaten into this belly! - var/tmp/is_full // Flag for if digested remeans are present. (for disposal messages) - var/tmp/emotePend = FALSE // If there's already a spawned thing counting for the next emote - var/swallow_time = 10 SECONDS // for mob transfering automation - var/vore_capacity = 1 // The capacity (in people) this person can hold - - // Don't forget to watch your commas at the end of each line if you change these. - var/list/struggle_messages_outside = list( - "%pred's %belly wobbles with a squirming meal.", - "%pred's %belly jostles with movement.", - "%pred's %belly briefly swells outward as someone pushes from inside.", - "%pred's %belly fidgets with a trapped victim.", - "%pred's %belly jiggles with motion from inside.", - "%pred's %belly sloshes around.", - "%pred's %belly gushes softly.", - "%pred's %belly lets out a wet squelch.") - - var/list/struggle_messages_inside = list( - "Your useless squirming only causes %pred's slimy %belly to squelch over your body.", - "Your struggles only cause %pred's %belly to gush softly around you.", - "Your movement only causes %pred's %belly to slosh around you.", - "Your motion causes %pred's %belly to jiggle.", - "You fidget around inside of %pred's %belly.", - "You shove against the walls of %pred's %belly, making it briefly swell outward.", - "You jostle %pred's %belly with movement.", - "You squirm inside of %pred's %belly, making it wobble around.") - - var/list/digest_messages_owner = list( - "You feel %prey's body succumb to your digestive system, which breaks it apart into soft slurry.", - "You hear a lewd glorp as your %belly muscles grind %prey into a warm pulp.", - "Your %belly lets out a rumble as it melts %prey into sludge.", - "You feel a soft gurgle as %prey's body loses form in your %belly. They're nothing but a soft mass of churning slop now.", - "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your thighs.", - "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your rump.", - "Your %belly begins gushing %prey's remains through your system, adding some extra weight to your belly.", - "Your %belly groans as %prey falls apart into a thick soup. You can feel their remains soon flowing deeper into your body to be absorbed.", - "Your %belly kneads on every fiber of %prey, softening them down into mush to fuel your next hunt.", - "Your %belly churns %prey down into a hot slush. You can feel the nutrients coursing through your digestive track with a series of long, wet glorps.") - - var/list/digest_messages_prey = list( - "Your body succumbs to %pred's digestive system, which breaks you apart into soft slurry.", - "%pred's %belly lets out a lewd glorp as their muscles grind you into a warm pulp.", - "%pred's %belly lets out a rumble as it melts you into sludge.", - "%pred feels a soft gurgle as your body loses form in their %belly. You're nothing but a soft mass of churning slop now.", - "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's thighs.", - "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's rump.", - "%pred's %belly begins gushing your remains through their system, adding some extra weight to %pred's belly.", - "%pred's %belly groans as you fall apart into a thick soup. Your remains soon flow deeper into %pred's body to be absorbed.", - "%pred's %belly kneads on every fiber of your body, softening you down into mush to fuel their next hunt.", - "%pred's %belly churns you down into a hot slush. Your nutrient-rich remains course through their digestive track with a series of long, wet glorps.") - - var/list/examine_messages = list( - "They have something solid in their %belly!", - "It looks like they have something in their %belly!") - - //Mostly for being overridden on precreated bellies on mobs. Could be VV'd into - //a carbon's belly if someone really wanted. No UI for carbons to adjust this. - //List has indexes that are the digestion mode strings, and keys that are lists of strings. - var/list/emote_lists = list() - -// Constructor that sets the owning mob -/datum/belly/New(var/mob/living/owning_mob) - owner = owning_mob - -// Toggle digestion on/off and notify user of the new setting. -// If multiple digestion modes are avaliable (i.e. unbirth) then user should be prompted. -/datum/belly/proc/toggle_digestion() - return - -// Checks if any mobs are present inside the belly -// return True if the belly is empty. -/datum/belly/proc/is_empty() - return internal_contents.len == 0 - -// Release all contents of this belly into the owning mob's location. -// If that location is another mob, contents are transferred into whichever of its bellies the owning mob is in. -// Returns the number of mobs so released. -/datum/belly/proc/release_all_contents() - if (internal_contents.len == 0) - return 0 - for (var/atom/movable/M in internal_contents) - M.forceMove(owner.loc) // Move the belly contents into the same location as belly's owner. - for(var/mob/living/W in M) - W.stop_sound_channel(CHANNEL_PREYLOOP) - internal_contents.Remove(M) // Remove from the belly contents - - var/datum/belly/B = check_belly(owner) // This makes sure that the mob behaves properly if released into another mob - if(B) - B.internal_contents.Add(M) - - owner.visible_message("[owner] expels everything from their [lowertext(name)]!") - return TRUE - -// Release a specific atom from the contents of this belly into the owning mob's location. -// If that location is another mob, the atom is transferred into whichever of its bellies the owning mob is in. -// Returns the number of atoms so released. -/datum/belly/proc/release_specific_contents(var/atom/movable/M) - if (!(M in internal_contents)) - return FALSE // They weren't in this belly anyway - - M.forceMove(owner.loc) // Move the belly contents into the same location as belly's owner. - for(var/mob/living/W in M) - W.stop_sound_channel(CHANNEL_PREYLOOP) - src.internal_contents.Remove(M) // Remove from the belly contents - - var/datum/belly/B = check_belly(owner) - if(B) - B.internal_contents.Add(M) - - owner.visible_message("[owner] expels [M] from their [lowertext(name)]!") -// owner.regenerate_icons() - return TRUE - -// Actually perform the mechanics of devouring the tasty prey. -// The purpose of this method is to avoid duplicate code, and ensure that all necessary -// steps are taken. -/datum/belly/proc/nom_mob(var/mob/prey, var/mob/user) - var/sound/preyloop = sound('sound/vore/prey/loop.ogg', repeat = TRUE) - - prey.forceMove(owner) - internal_contents.Add(prey) - prey.playsound_local(get_turf(prey),preyloop,40,0, channel = CHANNEL_PREYLOOP) - - // Handle prey messages - if(inside_flavor) - to_chat(prey, "[src.inside_flavor]") - if(isliving(prey)) - var/mob/living/M = prey - if(can_taste && M.get_taste_message(0)) - to_chat(owner, "[M] tastes of [M.get_taste_message(0)].") - - // Setup the autotransfer checks if needed - if(transferlocation && autotransferchance > 0) - addtimer(CALLBACK(src, /datum/belly/.proc/check_autotransfer, prey), autotransferwait) - -/datum/belly/proc/check_autotransfer(var/mob/prey) - // Some sanity checks - if(transferlocation && (autotransferchance > 0) && (prey in internal_contents)) - if(prob(autotransferchance)) - // Double check transferlocation isn't insane - if(verify_transferlocation()) - transfer_contents(prey, transferlocation) - else - // Didn't transfer, so wait before retrying - addtimer(CALLBACK(src, /datum/belly/.proc/check_autotransfer, prey), autotransferwait) - -/datum/belly/proc/verify_transferlocation() - for(var/I in owner.vore_organs) - var/datum/belly/B = owner.vore_organs[I] - if(B == transferlocation) - return TRUE - - for(var/I in owner.vore_organs) - var/datum/belly/B = owner.vore_organs[I] - if(B.name == transferlocation.name) - transferlocation = B - return TRUE - return FALSE - -// Get the line that should show up in Examine message if the owner of this belly -// is examined. By making this a proc, we not only take advantage of polymorphism, -// but can easily make the message vary based on how many people are inside, etc. -// Returns a string which shoul be appended to the Examine output. -/datum/belly/proc/get_examine_msg() - if(internal_contents.len && examine_messages.len) - var/formatted_message - var/raw_message = pick(examine_messages) - - formatted_message = replacetext(raw_message,"%belly",lowertext(name)) - formatted_message = replacetext(formatted_message,"%pred",owner) - formatted_message = replacetext(formatted_message,"%prey",english_list(internal_contents)) - - return("[formatted_message]
") - -// The next function gets the messages set on the belly, in human-readable format. -// This is useful in customization boxes and such. The delimiter right now is \n\n so -// in message boxes, this looks nice and is easily delimited. -/datum/belly/proc/get_messages(var/type, var/delim = "\n\n") - ASSERT(type == "smo" || type == "smi" || type == "dmo" || type == "dmp" || type == "em") - var/list/raw_messages - - switch(type) - if("smo") - raw_messages = struggle_messages_outside - if("smi") - raw_messages = struggle_messages_inside - if("dmo") - raw_messages = digest_messages_owner - if("dmp") - raw_messages = digest_messages_prey - if("em") - raw_messages = examine_messages - - var/messages = list2text(raw_messages,delim) - return messages - -// The next function sets the messages on the belly, from human-readable var -// replacement strings and linebreaks as delimiters (two \n\n by default). -// They also sanitize the messages. -/datum/belly/proc/set_messages(var/raw_text, var/type, var/delim = "\n\n") - ASSERT(type == "smo" || type == "smi" || type == "dmo" || type == "dmp" || type == "em") - - var/list/raw_list = text2list(html_encode(raw_text),delim) - if(raw_list.len > 10) - raw_list.Cut(11) - - for(var/i = 1, i <= raw_list.len, i++) - if(length(raw_list[i]) > 160 || length(raw_list[i]) < 10) //160 is fudged value due to htmlencoding increasing the size - raw_list.Cut(i,i) - else - raw_list[i] = readd_quotes(raw_list[i]) - //Also fix % sign for var replacement - raw_list[i] = replacetext(raw_list[i],"%","%") - - ASSERT(raw_list.len <= 10) //Sanity - - switch(type) - if("smo") - struggle_messages_outside = raw_list - if("smi") - struggle_messages_inside = raw_list - if("dmo") - digest_messages_owner = raw_list - if("dmp") - digest_messages_prey = raw_list - if("em") - examine_messages = raw_list - - return - -// Handle the death of a mob via digestion. -// Called from the process_Life() methods of bellies that digest prey. -// Default implementation calls M.death() and removes from internal contents. -// Indigestable items are removed, and M is deleted. -/datum/belly/proc/digestion_death(var/mob/living/M) - is_full = TRUE - internal_contents.Remove(M) - M.stop_sound_channel(CHANNEL_PREYLOOP) - // If digested prey is also a pred... anyone inside their bellies gets moved up. - if(is_vore_predator(M)) - for(var/bellytype in M.vore_organs) - var/datum/belly/belly = M.vore_organs[bellytype] - for (var/obj/thing in belly.internal_contents) - thing.loc = owner - internal_contents.Add(thing) - for (var/mob/subprey in belly.internal_contents) - subprey.loc = owner - internal_contents.Add(subprey) - to_chat(subprey, "As [M] melts away around you, you find yourself in [owner]'s [name]") - - //Drop all items into the belly - for(var/obj/item/W in M) - if(!M.dropItemToGround(W)) - qdel(W) - - message_admins("[key_name(owner)] digested [key_name(M)].") - log_attack("[key_name(owner)] digested [key_name(M)].") - - // Delete the digested mob - qdel(M) - -//Handle a mob struggling -// Called from /mob/living/carbon/relaymove() -/datum/belly/proc/relay_resist(var/mob/living/R) - if (!(R in internal_contents)) - return // User is not in this belly, or struggle too soon. - - R.setClickCooldown(50) - var/sound/prey_struggle = sound(get_sfx("prey_struggle")) - - if(owner.stat) //If owner is stat (dead, KO) we can actually escape - to_chat(R, "You attempt to climb out of \the [name]. (This will take around [escapetime/10] seconds.)") - to_chat(owner, "Someone is attempting to climb out of your [name]!") - - if(do_after(R, escapetime, owner)) - if((owner.stat || escapable) && (R in internal_contents)) //Can still escape? - release_specific_contents(R) - return - else if(!(R in internal_contents)) //Aren't even in the belly. Quietly fail. - return - else //Belly became inescapable or mob revived - to_chat(R, "Your attempt to escape [name] has failed!") - to_chat(owner, "The attempt to escape from your [name] has failed!") - return - return - var/struggle_outer_message = pick(struggle_messages_outside) - var/struggle_user_message = pick(struggle_messages_inside) - - struggle_outer_message = replacetext(struggle_outer_message,"%pred",owner) - struggle_outer_message = replacetext(struggle_outer_message,"%prey",R) - struggle_outer_message = replacetext(struggle_outer_message,"%belly",lowertext(name)) - - struggle_user_message = replacetext(struggle_user_message,"%pred",owner) - struggle_user_message = replacetext(struggle_user_message,"%prey",R) - struggle_user_message = replacetext(struggle_user_message,"%belly",lowertext(name)) - - struggle_outer_message = "" + struggle_outer_message + "" - struggle_user_message = "" + struggle_user_message + "" - - R.visible_message( "[struggle_outer_message]", "[struggle_user_message]") - playsound(get_turf(owner),"struggle_sound",35,0,-6,1,channel=151,ignore_walls = FALSE) - R.stop_sound_channel(151) - R.playsound_local(get_turf(R),prey_struggle,45,0) - - if(escapable && R.a_intent != "help") //If the stomach has escapable enabled and the person is actually trying to kick out - to_chat(R, "You attempt to climb out of \the [name].") - to_chat(owner, "Someone is attempting to climb out of your [name]!") - if(prob(escapechance)) //Let's have it check to see if the prey escapes first. - if(do_after(R, escapetime)) - if((escapable) && (R in internal_contents)) //Does the owner still have escapable enabled? - release_specific_contents(R) - to_chat(R, "You climb out of \the [name].") - to_chat(owner, "[R] climbs out of your [name]!") - for(var/mob/M in viewers(4, owner)) - M.visible_message("[R] climbs out of [owner]'s [name]!", 2) - return - else if(!(R in internal_contents)) //Aren't even in the belly. Quietly fail. - return - else //Belly became inescapable. - to_chat(R, "Your attempt to escape [name] has failed!") - to_chat(owner, "The attempt to escape from your [name] has failed!/span>") - return - - else if(prob(transferchance) && istype(transferlocation)) //Next, let's have it see if they end up getting into an even bigger mess then when they started. - var/location_ok = verify_transferlocation() - - if(!location_ok) - to_chat(owner, "Something went wrong with your belly transfer settings.") - transferlocation = null - return - - to_chat(R, "Your attempt to escape [name] has failed and your struggles only results in you sliding into [owner]'s [transferlocation]!") - to_chat(owner, "Someone slid into your [transferlocation] due to their struggling inside your [name]!") - transfer_contents(R, transferlocation) - return - - else if(prob(digestchance)) //Finally, let's see if it should run the digest chance.) - to_chat(R, "In response to your struggling, \the [name] begins to get more active...") - to_chat(owner, "You feel your [name] beginning to become active!") - digest_mode = DM_DIGEST - return - else //Nothing interesting happened. - to_chat(R, "But make no progress in escaping [owner]'s [name].") - to_chat(owner, "But appears to be unable to make any progress in escaping your [name].") - return - -//Transfers contents from one belly to another -/datum/belly/proc/transfer_contents(var/atom/movable/content, var/datum/belly/target, silent = 0) - if(!(content in internal_contents)) - return - internal_contents.Remove(content) - // Re-use nom_mob - target.nom_mob(content, target.owner) - if(!silent) - playsound(get_turf(owner),"[target].vore_sound",35,0,-6,1,ignore_walls = FALSE) -/* -//Handles creation of temporary 'vore chest' upon digestion -/datum/belly/proc/slimy_mass(var/obj/item/content, var/mob/living/M) - if(!content in internal_contents) - return - internal_contents += new /obj/structure/closet/crate/vore(src) - internal_contents.Remove(content) - M.transferItemToLoc(content, /obj/structure/closet/crate/vore) - if(!M.transferItemToLoc(W)) - qdel(W) - -/datum/belly/proc/regurgitate_items(var/obj/structure/closet/crate/vore/C) - */ - -// Belly copies and then returns the copy -// Needs to be updated for any var changes -/datum/belly/proc/copy(mob/new_owner) - var/datum/belly/dupe = new /datum/belly(new_owner) - - //// Non-object variables - dupe.name = name - dupe.inside_flavor = inside_flavor - dupe.vore_sound = vore_sound - dupe.vore_verb = vore_verb - dupe.human_prey_swallow_time = human_prey_swallow_time - dupe.nonhuman_prey_swallow_time = nonhuman_prey_swallow_time - dupe.emoteTime = emoteTime - dupe.digest_brute = digest_brute - dupe.digest_burn = digest_burn - dupe.digest_tickrate = digest_tickrate - dupe.immutable = immutable - dupe.can_taste = can_taste - dupe.escapable = escapable - dupe.escapetime = escapetime - dupe.digestchance = digestchance - dupe.escapechance = escapechance - dupe.transferchance = transferchance - dupe.transferlocation = transferlocation - dupe.autotransferchance = autotransferchance - dupe.autotransferwait = autotransferwait - - //// Object-holding variables - //struggle_messages_outside - strings - dupe.struggle_messages_outside.Cut() - for(var/I in struggle_messages_outside) - dupe.struggle_messages_outside += I - - //struggle_messages_inside - strings - dupe.struggle_messages_inside.Cut() - for(var/I in struggle_messages_inside) - dupe.struggle_messages_inside += I - - //digest_messages_owner - strings - dupe.digest_messages_owner.Cut() - for(var/I in digest_messages_owner) - dupe.digest_messages_owner += I - - //digest_messages_prey - strings - dupe.digest_messages_prey.Cut() - for(var/I in digest_messages_prey) - dupe.digest_messages_prey += I - - //examine_messages - strings - dupe.examine_messages.Cut() - for(var/I in examine_messages) - dupe.examine_messages += I - - //emote_lists - index: digest mode, key: list of strings - dupe.emote_lists.Cut() - for(var/K in emote_lists) - dupe.emote_lists[K] = list() - for(var/I in emote_lists[K]) - dupe.emote_lists[K] += I - - return dupe diff --git a/code/modules/vore/eating/bellymodes_vr.dm b/code/modules/vore/eating/bellymodes_vr.dm index 3d00f9e0fe..3260e2ae99 100644 --- a/code/modules/vore/eating/bellymodes_vr.dm +++ b/code/modules/vore/eating/bellymodes_vr.dm @@ -1,34 +1,53 @@ // Process the predator's effects upon the contents of its belly (i.e digestion/transformation etc) -// Called from /mob/living/Life() proc. -/datum/belly/proc/process_Life() - var/sound/prey_gurgle = sound(get_sfx("digest_prey")) - var/sound/prey_digest = sound(get_sfx("death_prey")) +/obj/belly/proc/process_belly(var/times_fired,var/wait) //Passed by controller + if((times_fired < next_process) || !contents.len) + recent_sound = FALSE + return SSBELLIES_IGNORED + + if(loc != owner) + if(istype(owner)) + loc = owner + else + qdel(src) + return SSBELLIES_PROCESSED + + next_process = times_fired + (6 SECONDS/wait) //Set up our next process time. /////////////////////////// Auto-Emotes /////////////////////////// - if((digest_mode in emote_lists) && !emotePend) - emotePend = TRUE + if(contents.len && next_emote <= times_fired) + next_emote = times_fired + round(emote_time/wait,1) + var/list/EL = emote_lists[digest_mode] + for(var/mob/living/M in contents) + if(M.digestable || !(digest_mode == DM_DIGEST)) // don't give digesty messages to indigestible people + to_chat(M,"[pick(EL)]") + +/////////////////////////// Exit Early //////////////////////////// + var/list/touchable_items = contents - items_preserved + if(!length(touchable_items)) + return SSBELLIES_PROCESSED + +////////////////////////// Sound vars ///////////////////////////// + var/sound/prey_digest = sound(get_sfx("digest_prey")) + var/sound/prey_death = sound(get_sfx("death_prey")) - spawn(emoteTime) - var/list/EL = emote_lists[digest_mode] - for(var/mob/living/M in internal_contents) - M << "[pick(EL)]" - src.emotePend = FALSE ///////////////////////////// DM_HOLD ///////////////////////////// if(digest_mode == DM_HOLD) - return //Pretty boring, huh + return SSBELLIES_PROCESSED //////////////////////////// DM_DIGEST //////////////////////////// - if(digest_mode == DM_DIGEST) - for (var/mob/living/M in internal_contents) + else if(digest_mode == DM_DIGEST) + for (var/mob/living/M in contents) if(prob(25)) - M.stop_sound_channel(CHANNEL_PRED) - playsound(get_turf(owner),"digest_pred",50,0,-6,0,channel=CHANNEL_PRED,ignore_walls = FALSE) - M.stop_sound_channel(CHANNEL_PRED) - M.playsound_local(get_turf(M), null, 45, S = prey_gurgle) + M.stop_sound_channel(CHANNEL_DIGEST) + for(var/mob/H in get_hearers_in_view(5, get_turf(owner))) + if(H.client && H.client.prefs.toggles & DIGESTION_NOISES) + playsound(get_turf(owner),"digest_pred",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_DIGEST) + M.stop_sound_channel(CHANNEL_DIGEST) + M.playsound_local(get_turf(M), prey_digest, 45) //Pref protection! - if (!M.digestable) + if (!M.digestable || M.absorbed) continue //Person just died in guts! @@ -51,10 +70,13 @@ M.visible_message("You watch as [owner]'s form loses its additions.") owner.nutrition += 400 // so eating dead mobs gives you *something*. - M.stop_sound_channel(CHANNEL_PRED) - playsound(get_turf(owner),"death_pred",45,0,-6,0,channel=CHANNEL_PRED,ignore_walls = FALSE) - M.stop_sound_channel(CHANNEL_PRED) - M.playsound_local(get_turf(M), null, 45, S = prey_digest) + M.stop_sound_channel(DIGESTION_NOISES) + for(var/mob/H in get_hearers_in_view(5, get_turf(owner))) + if(H.client && H.client.prefs.toggles & DIGESTION_NOISES) + playsound(get_turf(owner),"death_pred",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_DIGEST) + M.stop_sound_channel(DIGESTION_NOISES) + M.stop_sound_channel(CHANNEL_PREYLOOP) + M.playsound_local(get_turf(M), prey_death, 65) digestion_death(M) owner.update_icons() continue @@ -64,44 +86,57 @@ if(!(M.status_flags & GODMODE)) M.adjustFireLoss(digest_burn) owner.nutrition += 1 - return + + //Contaminate or gurgle items + var/obj/item/T = pick(touchable_items) + if(istype(T)) + if(istype(T,/obj/item/reagent_containers/food) || istype(T,/obj/item/organ)) + digest_item(T) + + owner.updateVRPanel() ///////////////////////////// DM_HEAL ///////////////////////////// if(digest_mode == DM_HEAL) - for (var/mob/living/M in internal_contents) + for (var/mob/living/M in contents) if(prob(25)) - M.stop_sound_channel(CHANNEL_PRED) - playsound(get_turf(owner),"digest_pred",35,0,-6,0,channel=CHANNEL_PRED,ignore_walls = FALSE) - M.stop_sound_channel(CHANNEL_PRED) - M.playsound_local(get_turf(M), null, 45, S = prey_gurgle) + M.stop_sound_channel(CHANNEL_DIGEST) + for(var/mob/H in get_hearers_in_view(5, get_turf(owner))) + if(H.client && H.client.prefs.toggles & DIGESTION_NOISES) + playsound(get_turf(owner),"digest_pred",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_DIGEST) + M.stop_sound_channel(CHANNEL_DIGEST) + M.playsound_local(get_turf(M), prey_digest, 65) if(M.stat != DEAD) if(owner.nutrition >= NUTRITION_LEVEL_STARVING && (M.health < M.maxHealth)) - M.adjustBruteLoss(-1) - M.adjustFireLoss(-1) - owner.nutrition -= 10 + M.adjustBruteLoss(-3) + M.adjustFireLoss(-3) + owner.nutrition -= 5 return ////////////////////////// DM_NOISY ///////////////////////////////// //for when you just want people to squelch around if(digest_mode == DM_NOISY) - for (var/mob/living/M in internal_contents) + for (var/mob/living/M in contents) if(prob(35)) + M.stop_sound_channel(CHANNEL_DIGEST) + for(var/mob/H in get_hearers_in_view(5, get_turf(owner))) + if(H.client && H.client.prefs.toggles & DIGESTION_NOISES) + playsound(get_turf(owner),"digest_pred",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_DIGEST) M.stop_sound_channel(CHANNEL_PRED) - playsound(get_turf(owner),"digest_pred",35,0,-6,0,channel=CHANNEL_PRED,ignore_walls = FALSE) - M.stop_sound_channel(CHANNEL_PRED) - M.playsound_local(get_turf(M), null, 45, S = prey_gurgle) + M.playsound_local(get_turf(M), prey_digest, 65) //////////////////////////DM_DRAGON ///////////////////////////////////// //because dragons need snowflake guts if(digest_mode == DM_DRAGON) - for (var/mob/living/M in internal_contents) + for (var/mob/living/M in contents) if(prob(25)) - M.stop_sound_channel(CHANNEL_PRED) - playsound(get_turf(owner),"digest_pred",50,0,-6,0,channel=CHANNEL_PRED,ignore_walls = FALSE) - M.stop_sound_channel(CHANNEL_PRED) - M.playsound_local(get_turf(M), null, 45, S = prey_gurgle) + M.stop_sound_channel(CHANNEL_DIGEST) + for(var/mob/H in get_hearers_in_view(5, get_turf(owner))) + if(H.client && H.client.prefs.toggles & DIGESTION_NOISES) + playsound(get_turf(owner),"digest_pred",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_DIGEST) + M.stop_sound_channel(CHANNEL_DIGEST) + M.playsound_local(get_turf(M), prey_digest, 65) //No digestion protection for megafauna. @@ -124,12 +159,14 @@ to_chat(M, "[digest_alert_prey]") M.visible_message("You watch as [owner]'s guts loudly rumble as it finishes off a meal.") - M.stop_sound_channel(CHANNEL_PRED) - playsound(get_turf(owner),"death_pred",45,0,-6,0,channel=CHANNEL_PRED) - M.stop_sound_channel(CHANNEL_PRED) - M.playsound_local(get_turf(M), null, 45, S = prey_digest) + M.stop_sound_channel(CHANNEL_DIGEST) + for(var/mob/H in get_hearers_in_view(5, get_turf(owner))) + if(H.client && H.client.prefs.toggles & DIGESTION_NOISES) + playsound(get_turf(owner),"death_pred",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_DIGEST) + M.stop_sound_channel(CHANNEL_DIGEST) + M.playsound_local(get_turf(M), prey_death, 65) M.spill_organs(FALSE,TRUE,TRUE) - M << sound(null, repeat = 0, wait = 0, volume = 80, channel = CHANNEL_PREYLOOP) + M.stop_sound_channel(CHANNEL_PREYLOOP) digestion_death(M) owner.update_icons() continue @@ -138,6 +175,12 @@ // Deal digestion damage (and feed the pred) if(!(M.status_flags & GODMODE)) M.adjustFireLoss(digest_burn) - M.adjustToxLoss(4) // something something plasma based acids - M.adjustCloneLoss(3) // eventually this'll kill you if you're healing everything else, you nerds. - return \ No newline at end of file + M.adjustToxLoss(2) // something something plasma based acids + M.adjustCloneLoss(1) // eventually this'll kill you if you're healing everything else, you nerds. + //Contaminate or gurgle items + var/obj/item/T = pick(touchable_items) + if(istype(T)) + if(istype(T,/obj/item/reagent_containers/food) || istype(T,/obj/item/organ)) + digest_item(T) + + owner.updateVRPanel() \ No newline at end of file diff --git a/code/modules/vore/eating/digest_act_vr.dm b/code/modules/vore/eating/digest_act_vr.dm new file mode 100644 index 0000000000..faa458ad56 --- /dev/null +++ b/code/modules/vore/eating/digest_act_vr.dm @@ -0,0 +1,119 @@ +//Please make sure to: +//return FALSE: You are not going away, stop asking me to digest. +//return non-negative integer: Amount of nutrition/charge gained (scaled to nutrition, other end can multiply for charge scale). + +// Ye default implementation. +/obj/item/proc/digest_act(var/atom/movable/item_storage = null) + for(var/obj/item/O in contents) + if(istype(O,/obj/item/storage/internal)) //Dump contents from dummy pockets. + for(var/obj/item/SO in O) + if(item_storage) + SO.forceMove(item_storage) + qdel(O) + else if(item_storage) + O.forceMove(item_storage) + + qdel(src) + return w_class + +///////////// +// Some indigestible stuff +///////////// +/obj/item/hand_tele/digest_act(...) + return FALSE +/obj/item/card/id/digest_act(...) + return FALSE +/obj/item/aicard/digest_act(...) + return FALSE +/obj/item/paicard/digest_act(...) + return FALSE +/obj/item/pinpointer/digest_act(...) + return FALSE +/obj/item/disk/nuclear/digest_act(...) + return FALSE +/obj/item/device/perfect_tele_beacon/digest_act(...) + return FALSE //Sorta important to not digest your own beacons. +/obj/item/device/pda/digest_act(...) + return FALSE +/obj/item/gun/digest_act(...) + return FALSE +/obj/item/clothing/shoes/magboots/digest_act(...) + return FALSE +/obj/item/clothing/head/helmet/space/digest_act(...) + return FALSE +/obj/item/clothing/suit/space/digest_act(...) + return FALSE +/obj/item/reagent_containers/hypospray/CMO/digest_act(...) + return FALSE +/obj/item/tank/jetpack/oxygen/captain/digest_act(...) + return FALSE +/obj/item/clothing/accessory/medal/gold/captain/digest_act(...) + return FALSE +/obj/item/clothing/suit/armor/digest_act(...) + return FALSE +/obj/item/documents/digest_act(...) + return FALSE +/obj/item/nuke_core/digest_act(...) + return FALSE +/obj/item/nuke_core_container/digest_act(...) + return FALSE +/obj/item/areaeditor/blueprints/digest_act(...) + return FALSE +/obj/item/documents/syndicate/digest_act(...) + return FALSE +/obj/item/bombcore/digest_act(...) + return FALSE +/obj/item/grenade/digest_act(...) + return FALSE +/obj/item/storage/digest_act(...) + return FALSE + +///////////// +// Some special treatment +///////////// +/* +//PDAs need to lose their ID to not take it with them, so we can get a digested ID +/obj/item/device/pda/digest_act(var/atom/movable/item_storage = null) + if(id) + id = null + + . = ..() +*/ + +/obj/item/reagent_containers/food/digest_act(var/atom/movable/item_storage = null) + if(isbelly(item_storage)) + var/obj/belly/B = item_storage + if(ishuman(B.owner)) + var/mob/living/carbon/human/H = B.owner + reagents.trans_to(H, (reagents.total_volume * 0.3), 1, 0) + else if(iscyborg(B.owner)) + var/mob/living/silicon/robot/R = B.owner + R.cell.charge += 150 + + . = ..() + +/* +/obj/item/holder/digest_act(var/atom/movable/item_storage = null) + for(var/mob/living/M in contents) + if(item_storage) + M.forceMove(item_storage) + held_mob = null + + . = ..() */ + +/obj/item/organ/digest_act(var/atom/movable/item_storage = null) + if((. = ..())) + . += 70 //Organs give a little more + +/obj/item/storage/digest_act(var/atom/movable/item_storage = null) + for(var/obj/item/I in contents) + I.screen_loc = null + + . = ..() + +///////////// +// Some more complicated stuff +///////////// +/obj/item/device/mmi/digital/posibrain/digest_act(var/atom/movable/item_storage = null) + //Replace this with a VORE setting so all types of posibrains can/can't be digested on a whim + return FALSE diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index 16a63c40ac..5b2ad312ab 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -1,23 +1,24 @@ ///////////////////// Mob Living ///////////////////// /mob/living var/digestable = TRUE // Can the mob be digested inside a belly? - var/datum/belly/vore_selected // Default to no vore capability. + var/obj/belly/vore_selected // Default to no vore capability. var/list/vore_organs = list() // List of vore containers inside a mob var/devourable = FALSE // Can the mob be vored at all? // var/feeding = FALSE // Are we going to feed someone else? var/vore_taste = null // What the character tastes like var/no_vore = FALSE // If the character/mob can vore. var/openpanel = 0 // Is the vore panel open? + var/noisy = FALSE // tummies are rumbly? + var/absorbed = FALSE //are we absorbed? // // Hook for generic creation of stuff on new creatures // /hook/living_new/proc/vore_setup(mob/living/M) - M.verbs += /mob/living/proc/lick M.verbs += /mob/living/proc/preyloop_refresh - if(M.no_vore) //If the mob isn's supposed to have a stomach, let's not give it an insidepanel so it can make one for itself, or a stomach. - M << "The creature that you are can not eat others." - return TRUE + M.verbs += /mob/living/proc/lick + if(M.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 1 M.verbs += /mob/living/proc/insidePanel //Tries to load prefs if a client is present otherwise gives freebie stomach @@ -27,44 +28,36 @@ if(M.client && M.client.prefs_vr) if(!M.copy_from_prefs_vr()) - M << "ERROR: You seem to have saved vore prefs, but they couldn't be loaded." - return FALSE + to_chat(M,"ERROR: You seem to have saved vore prefs, but they couldn't be loaded.") + return 0 if(M.vore_organs && M.vore_organs.len) M.vore_selected = M.vore_organs[1] if(!M.vore_organs || !M.vore_organs.len) if(!M.vore_organs) M.vore_organs = list() - var/datum/belly/B = new /datum/belly(M) + var/obj/belly/B = new /obj/belly(M) + M.vore_selected = B B.immutable = TRUE B.name = "Stomach" - B.inside_flavor = "It appears to be rather warm and wet. Makes sense, considering it's inside \the [M.name]" - B.can_taste = TRUE - M.vore_organs[B.name] = B - M.vore_selected = B.name - - //Simple_animal gets emotes. move this to that hook instead? - if(istype(src,/mob/living/simple_animal)) - B.emote_lists[DM_HOLD] = list( - "The insides knead at you gently for a moment.", - "The guts glorp wetly around you as some air shifts.", - "Your predator takes a deep breath and sighs, shifting you somewhat.", - "The stomach squeezes you tight for a moment, then relaxes.", - "During a moment of quiet, breathing becomes the most audible thing.", - "The warm slickness surrounds and kneads on you.") - - B.emote_lists[DM_DIGEST] = list( - "The caustic acids eat away at your form.", - "The acrid air burns at your lungs.", - "Without a thought for you, the stomach grinds inwards painfully.", - "The guts treat you like food, squeezing to press more acids against you.", - "The onslaught against your body doesn't seem to be letting up; you're food now.", - "The insides work on you like they would any other food.") + B.desc = "It appears to be rather warm and wet. Makes sense, considering it's inside \the [M.name]." + B.can_taste = FALSE //Return 1 to hook-caller return 1 +/* +// Hide vore organs in contents // +/datum/proc/view_variables_filter_contents(list/L) + return 0 + +/mob/living/view_variables_filter_contents(list/L) + . = ..() + var/len_before = L.len + L -= vore_organs + . += len_before - L.len*/ + // Handle being clicked, perhaps with something to devour // @@ -126,33 +119,37 @@ var/belly = user.vore_selected return perform_dragon(user, prey, user, belly) -/mob/living/proc/perform_dragon(var/mob/living/user, var/mob/living/prey, var/mob/living/pred, var/belly, swallow_time = 20) +/mob/living/proc/perform_dragon(var/mob/living/user, var/mob/living/prey, var/mob/living/pred, var/obj/belly/belly, swallow_time = 20) //Sanity - if(!user || !prey || !pred || !belly || !(belly in pred.vore_organs)) + if(!user || !prey || !pred || !istype(belly) || !(belly in pred.vore_organs)) + testing("[user] attempted to feed [prey] to [pred], via [lowertext(belly.name)] but it went wrong.") return // The belly selected at the time of noms - var/datum/belly/belly_target = pred.vore_organs[belly] var/attempt_msg = "ERROR: Vore message couldn't be created. Notify a dev. (at)" var/success_msg = "ERROR: Vore message couldn't be created. Notify a dev. (sc)" +/* //Final distance check. Time has passed, menus have come and gone. Can't use do_after adjacent because doesn't behave for held micros + var/user_to_pred = get_dist(get_turf(user),get_turf(pred)) + var/user_to_prey = get_dist(get_turf(user),get_turf(prey)) */ + // Prepare messages if(user == pred) //Feeding someone to yourself - attempt_msg = text("[] starts to [] [] into their []!",pred,lowertext(belly_target.vore_verb),prey,lowertext(belly_target.name)) - success_msg = text("[] manages to [] [] into their []!",pred,lowertext(belly_target.vore_verb),prey,lowertext(belly_target.name)) + attempt_msg = text("[] starts to [] [] into their []!",pred,lowertext(belly.vore_verb),prey,lowertext(belly.name)) + success_msg = text("[] manages to [] [] into their []!",pred,lowertext(belly.vore_verb),prey,lowertext(belly.name)) // Announce that we start the attempt! user.visible_message(attempt_msg) - if(!do_mob(src, user, swallow_time)) // one second should be good enough, right? + if(!do_mob(src, user, swallow_time)) return FALSE // Prey escaped (or user disabled) before timer expired. // If we got this far, nom successful! Announce it! user.visible_message(success_msg) - playsound(get_turf(user), belly_target.vore_sound,75,0,-6,0) + playsound(get_turf(user), "[belly.vore_sound]",75,0,-6,0) // Actually shove prey into the belly. - belly_target.nom_mob(prey, user) + belly.nom_mob(prey, user) if (pred == user) message_admins("[key_name(pred)] ate [key_name(prey)].") log_attack("[key_name(pred)] ate [key_name(prey)]") @@ -161,43 +158,55 @@ // Master vore proc that actually does vore procedures // -/mob/living/proc/perform_the_nom(var/mob/living/user, var/mob/living/prey, var/mob/living/pred, var/belly, swallow_time = 100) +/mob/living/proc/perform_the_nom(var/mob/living/user, var/mob/living/prey, var/mob/living/pred, var/obj/belly/belly, var/delay) //Sanity - if(!user || !prey || !pred || !belly || !(belly in pred.vore_organs)) + if(!user || !prey || !pred || !istype(belly) || !(belly in pred.vore_organs)) + testing("[user] attempted to feed [prey] to [pred], via [lowertext(belly.name)] but it went wrong.") return if (!prey.devourable) to_chat(user, "This can't be eaten!") return // The belly selected at the time of noms - var/datum/belly/belly_target = pred.vore_organs[belly] var/attempt_msg = "ERROR: Vore message couldn't be created. Notify a dev. (at)" var/success_msg = "ERROR: Vore message couldn't be created. Notify a dev. (sc)" +/* //Final distance check. Time has passed, menus have come and gone. Can't use do_after adjacent because doesn't behave for held micros + var/user_to_pred = get_dist(get_turf(user),get_turf(pred)) + var/user_to_prey = get_dist(get_turf(user),get_turf(prey)) */ + // Prepare messages if(user == pred) //Feeding someone to yourself - attempt_msg = text("[] is attemping to [] [] into their []!",pred,lowertext(belly_target.vore_verb),prey,lowertext(belly_target.name)) - success_msg = text("[] manages to [] [] into their []!",pred,lowertext(belly_target.vore_verb),prey,lowertext(belly_target.name)) + attempt_msg = text("[] is attemping to [] [] into their []!",pred,lowertext(belly.vore_verb),prey,lowertext(belly.name)) + success_msg = text("[] manages to [] [] into their []!",pred,lowertext(belly.vore_verb),prey,lowertext(belly.name)) else //Feeding someone to another person - attempt_msg = text("[] is attempting to make [] [] [] into their []!",user,pred,lowertext(belly_target.vore_verb),prey,lowertext(belly_target.name)) - success_msg = text("[] manages to make [] [] [] into their []!",user,pred,lowertext(belly_target.vore_verb),prey,lowertext(belly_target.name)) + attempt_msg = text("[] is attempting to make [] [] [] into their []!",user,pred,lowertext(belly.vore_verb),prey,lowertext(belly.name)) + success_msg = text("[] manages to make [] [] [] into their []!",user,pred,lowertext(belly.vore_verb),prey,lowertext(belly.name)) // Announce that we start the attempt! user.visible_message(attempt_msg) // Now give the prey time to escape... return if they did + var/swallow_time = delay || ishuman(prey) ? belly.human_prey_swallow_time : belly.nonhuman_prey_swallow_time + if(!do_mob(src, user, swallow_time)) return FALSE // Prey escaped (or user disabled) before timer expired. // If we got this far, nom successful! Announce it! user.visible_message(success_msg) - playsound(get_turf(user), belly_target.vore_sound,75,0,-6,0,ignore_walls = FALSE) + for(var/mob/M in get_hearers_in_view(5, get_turf(user))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(user),"[belly.vore_sound]",50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) // Actually shove prey into the belly. - belly_target.nom_mob(prey, user) + belly.nom_mob(prey, user) // user.update_icons() stop_pulling() + // Flavor handling + if(belly.can_taste && prey.get_taste_message(FALSE)) + to_chat(belly.owner, "[prey] tastes of [prey.get_taste_message(FALSE)].") + // Inform Admins var/prey_braindead var/prey_stat @@ -250,50 +259,38 @@ return 0 */ + +// +// Release everything in every vore organ +// +/mob/living/proc/release_vore_contents(var/include_absorbed = TRUE) + for(var/belly in vore_organs) + var/obj/belly/B = belly + B.release_all_contents(include_absorbed) + // // Custom resist catches for /mob/living // /mob/living/proc/vore_process_resist() //Are we resisting from inside a belly? - var/datum/belly/B = check_belly(src) - if(B) - spawn() B.relay_resist(src) + if(isbelly(loc)) + var/obj/belly/B = loc + B.relay_resist(src) return TRUE //resist() on living does this TRUE thing. //Other overridden resists go here - return FALSE -// -// Proc for updating vore organs and digestion/healing/absorbing -// -/mob/living/proc/handle_internal_contents() - if(SSmobs.times_fired%6==1) - return //The accursed timer - - for (var/I in vore_organs) - var/datum/belly/B = vore_organs[I] - if(B.internal_contents.len) - B.process_Life() //AKA 'do bellymodes_vr.dm' - - for (var/I in vore_organs) - var/datum/belly/B = vore_organs[I] - if(B.internal_contents.len) - listclearnulls(B.internal_contents) - for(var/atom/movable/M in B.internal_contents) - if(M.loc != src) - B.internal_contents.Remove(M) - // internal slimy button in case the loop stops playing but the player wants to hear it /mob/living/proc/preyloop_refresh() set name = "Internal loop refresh" set category = "Vore" - if(ismob(src.loc)) + if(istype(src.loc, /obj/belly)) src.stop_sound_channel(CHANNEL_PREYLOOP) // sanity just in case var/sound/preyloop = sound('sound/vore/prey/loop.ogg', repeat = TRUE) - src.playsound_local(get_turf(src),preyloop,40,0, channel = CHANNEL_PREYLOOP) + src.playsound_local(get_turf(src),preyloop,80,0, channel = CHANNEL_PREYLOOP) else to_chat(src, "You aren't inside anything, you clod.") @@ -309,7 +306,7 @@ var/confirm = alert(src, "You're in a mob. Use this as a trick to get out of hostile animals. If you are in more than one pred, use this more than once.", "Confirmation", "Okay", "Cancel") if(confirm == "Okay") for(var/I in pred.vore_organs) - var/datum/belly/B = pred.vore_organs[I] + var/obj/belly/B = pred.vore_organs[I] B.release_specific_contents(src) for(var/mob/living/simple_animal/SA in range(10)) @@ -355,9 +352,15 @@ P.digestable = src.digestable P.devourable = src.devourable - P.belly_prefs = src.vore_organs P.vore_taste = src.vore_taste + var/list/serialized = list() + for(var/belly in src.vore_organs) + var/obj/belly/B = belly + serialized += list(B.serialize()) //Can't add a list as an object to another list in Byond. Thanks. + + P.belly_prefs = serialized + return TRUE // @@ -370,16 +373,52 @@ var/datum/vore_preferences/P = client.prefs_vr - src.digestable = P.digestable - src.devourable = P.devourable - src.vore_organs = list() - src.vore_taste = P.vore_taste + digestable = P.digestable + devourable = P.devourable + vore_taste = P.vore_taste - for(var/I in P.belly_prefs) - var/datum/belly/Bp = P.belly_prefs[I] - src.vore_organs[Bp.name] = Bp.copy(src) + vore_organs.Cut() + for(var/entry in P.belly_prefs) + list_to_object(entry,src) return TRUE + +// +// Returns examine messages for bellies +// +/mob/living/proc/examine_bellies() + if(!show_pudge()) //Some clothing or equipment can hide this. + return "" + + var/message = "" + for (var/belly in vore_organs) + var/obj/belly/B = belly + message += B.get_examine_msg() + + return message + +// +// Whether or not people can see our belly messages +// +/mob/living/proc/show_pudge() + return TRUE //Can override if you want. + +/mob/living/carbon/human/show_pudge() + //A uniform could hide it. + if(istype(w_uniform,/obj/item/clothing)) + var/obj/item/clothing/under = w_uniform + if(under.hides_bulges) + return FALSE + + //We return as soon as we find one, no need for 'else' really. + if(istype(wear_suit,/obj/item/clothing)) + var/obj/item/clothing/suit = wear_suit + if(suit.hides_bulges) + return FALSE + + + return ..() + // // Clearly super important. Obviously. // diff --git a/code/modules/vore/eating/simple_animal_vr.dm b/code/modules/vore/eating/simple_animal_vr.dm index 4e7c453371..a93eb2fcf4 100644 --- a/code/modules/vore/eating/simple_animal_vr.dm +++ b/code/modules/vore/eating/simple_animal_vr.dm @@ -31,7 +31,7 @@ // // Simple nom proc for if you get ckey'd into a simple_animal mob! Avoids grabs. // -/mob/living/proc/animal_nom(var/mob/living/T in oview(1)) +/mob/living/simple_animal/proc/animal_nom(var/mob/living/T in oview(1)) set name = "Animal Nom" set category = "Vore" set desc = "Since you can't grab, you get a verb!" diff --git a/code/modules/vore/eating/vore_vr.dm b/code/modules/vore/eating/vore_vr.dm index f6d886e93f..f0ceb97e31 100644 --- a/code/modules/vore/eating/vore_vr.dm +++ b/code/modules/vore/eating/vore_vr.dm @@ -23,6 +23,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE // Overrides/additions to stock defines go here, as well as hooks. Sort them by // the object they are overriding. So all /mob/living together, etc. // + // // The datum type bolted onto normal preferences datums for storing Vore stuff // @@ -40,21 +41,23 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE //Actual preferences var/digestable = TRUE var/devourable = FALSE +// var/allowmobvore = TRUE var/list/belly_prefs = list() - var/vore_taste + var/vore_taste = "nothing in particular" +// var/can_be_drop_prey = FALSE +// var/can_be_drop_pred = FALSE //Mechanically required var/path var/slot var/client/client var/client_ckey - var/client/parent /datum/vore_preferences/New(client/C) if(istype(C)) client = C client_ckey = C.ckey - load_vore(C) + load_vore() // // Check if an object is capable of eating things, based on vore_organs @@ -68,46 +71,60 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE // // Belly searching for simplifying other procs +// Mostly redundant now with belly-objects and isbelly(loc) // /proc/check_belly(atom/movable/A) - if(istype(A.loc,/mob/living)) - var/mob/living/M = A.loc - for(var/I in M.vore_organs) - var/datum/belly/B = M.vore_organs[I] - if(A in B.internal_contents) - return(B) - - return FALSE + return isbelly(A.loc) // // Save/Load Vore Preferences // +/datum/vore_preferences/proc/load_path(ckey,slot,filename="character",ext="json") + if(!ckey || !slot) return + path = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/vore/[filename][slot].[ext]" + + /datum/vore_preferences/proc/load_vore() - if(!client || !client_ckey) return FALSE //No client, how can we save? + if(!client || !client_ckey) + return FALSE //No client, how can we save? + if(!client.prefs || !client.prefs.default_slot) + return FALSE //Need to know what character to load! slot = client.prefs.default_slot - path = client.prefs.path + load_path(client_ckey,slot) if(!path) return FALSE //Path couldn't be set? if(!fexists(path)) //Never saved before save_vore() //Make the file first return TRUE - var/savefile/S = new /savefile(path) - if(!S) return FALSE //Savefile object couldn't be created? + var/list/json_from_file = json_decode(file2text(path)) + if(!json_from_file) + return FALSE //My concern grows - S.cd = "/character[slot]" + var/version = json_from_file["version"] + json_from_file = patch_version(json_from_file,version) - S["digestable"] >> digestable - S["devourable"] >> devourable - S["belly_prefs"] >> belly_prefs - S["vore_taste"] >> vore_taste + digestable = json_from_file["digestable"] + devourable = json_from_file["devourable"] +// allowmobvore = json_from_file["allowmobvore"] + vore_taste = json_from_file["vore_taste"] +// can_be_drop_prey = json_from_file["can_be_drop_prey"] +// can_be_drop_prey = json_from_file["can_be_drop_pred"] + belly_prefs = json_from_file["belly_prefs"] + //Quick sanitize if(isnull(digestable)) digestable = TRUE if(isnull(devourable)) devourable = FALSE +/* if(isnull(allowmobvore)) + allowmobvore = TRUE + if(isnull(can_be_drop_prey)) + allowmobvore = FALSE + if(isnull(can_be_drop_pred)) + allowmobvore = FALSE */ if(isnull(belly_prefs)) belly_prefs = list() @@ -115,28 +132,37 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE /datum/vore_preferences/proc/save_vore() if(!path) return FALSE - if(!slot) return FALSE - var/savefile/S = new /savefile(path) - if(!S) return FALSE - S.cd = "/character[slot]" - WRITE_FILE(S["digestable"], digestable) - WRITE_FILE(S["devourable"], devourable) - WRITE_FILE(S["belly_prefs"], belly_prefs) - WRITE_FILE(S["vore_taste"], vore_taste) + var/version = 1 //For "good times" use in the future + var/list/settings_list = list( + "version" = version, + "digestable" = digestable, + "devourable" = devourable, + "vore_taste" = vore_taste, + "belly_prefs" = belly_prefs, + ) + + /* commented out list things + "allowmobvore" = allowmobvore, + "can_be_drop_prey" = can_be_drop_prey, + "can_be_drop_pred" = can_be_drop_pred, */ + + //List to JSON + var/json_to_file = json_encode(settings_list) + if(!json_to_file) + testing("Saving: [path] failed jsonencode") + return FALSE + + //Write it out + if(fexists(path)) + fdel(path) //Byond only supports APPENDING to files, not replacing. + text2file(json_to_file,path) + if(!fexists(path)) + testing("Saving: [path] failed file write") + return FALSE return TRUE -#ifdef TESTING -//DEBUG -//Some crude tools for testing savefiles -//path is the savefile path -/client/verb/vore_savefile_export(path as text) - var/savefile/S = new /savefile(path) - S.ExportText("/",file("[path].txt")) -//path is the savefile path -/client/verb/vore_savefile_import(path as text) - var/savefile/S = new /savefile(path) - S.ImportText("/",file("[path].txt")) - -#endif \ No newline at end of file +//Can do conversions here +/datum/vore_preferences/proc/patch_version(var/list/json_from_file,var/version) + return json_from_file \ No newline at end of file diff --git a/code/modules/vore/eating/voreitems.dm b/code/modules/vore/eating/voreitems.dm index 5d157c39fe..741782545a 100644 --- a/code/modules/vore/eating/voreitems.dm +++ b/code/modules/vore/eating/voreitems.dm @@ -16,7 +16,7 @@ /obj/item/projectile/sickshot name = "sickshot pulse" icon_state = "e_netting" - damage = 1 + damage = 0 damage_type = STAMINA range = 2 @@ -25,8 +25,7 @@ var/mob/living/carbon/H = target if(prob(5)) for(var/X in H.vore_organs) - var/datum/belly/B = H.vore_organs[X] - B.release_all_contents() + H.release_vore_contents() H.visible_message("[H] contracts strangely, spewing out contents on the floor!", \ "You spew out everything inside you on the floor!") return diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index e9bb44765f..7d7a48ed28 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -14,7 +14,7 @@ var/datum/vore_look/picker_holder = new() picker_holder.loop = picker_holder - picker_holder.selected = vore_organs[vore_selected] + picker_holder.selected = vore_selected var/dat = picker_holder.gen_vui(src) @@ -22,11 +22,23 @@ picker_holder.popup.set_content(dat) picker_holder.popup.open() +/mob/living/proc/updateVRPanel() //Panel popup update call from belly events. + if(src.openpanel == 1) + 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", 400, 600, picker_holder) + picker_holder.popup.set_content(dat) + picker_holder.popup.open() + // // Callback Handler for the Inside form // /datum/vore_look - var/datum/belly/selected + var/obj/belly/selected var/show_interacts = TRUE var/datum/browser/popup var/loop = null; // Magic self-reference to stop the handler from being GC'd before user takes action. @@ -44,29 +56,38 @@ /datum/vore_look/proc/gen_vui(var/mob/living/user) var/dat - if (is_vore_predator(user.loc)) - var/mob/living/eater = user.loc - var/datum/belly/inside_belly - - //This big block here figures out where the prey is - inside_belly = check_belly(user) + var/atom/userloc = user.loc + if (isbelly(userloc)) + var/obj/belly/inside_belly = userloc + var/mob/living/eater = inside_belly.owner + //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 inside [eater]'s [inside_belly]!

" + dat += "You are currently [user.absorbed ? "absorbed into " : "inside "] [eater]'s [inside_belly]!

" - if(inside_belly.inside_flavor) - dat += "[inside_belly.inside_flavor]

" + if(inside_belly.desc) + dat += "[inside_belly.desc]

" - if (inside_belly.internal_contents.len > 1) + if (inside_belly.contents.len > 1) dat += "You can see the following around you:
" - for (var/atom/movable/O in inside_belly.internal_contents) + 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 + + //That's an absorbed person you're checking + if(M.absorbed) + if(user.absorbed) + dat += "[O]" + continue + else + continue + //Anything else - dat += "[O]" + dat += "[O]​" + //Zero-width space, for wrapping dat += "​" else @@ -75,8 +96,8 @@ dat += "
" dat += "
    " - for(var/K in user.vore_organs) //Fuggin can't iterate over values - var/datum/belly/B = user.vore_organs[K] + for(var/belly in user.vore_organs) + var/obj/belly/B = belly if(B == selected) dat += "
  1. [B.name]" else @@ -90,8 +111,10 @@ spanstyle = "color:red;" if(DM_HEAL) spanstyle = "color:green;" + if(DM_NOISY) + spanstyle = "color:purple;" - dat += " ([B.internal_contents.len])
  2. " + dat += " ([B.contents.len])" if(user.vore_organs.len < BELLIES_MAX) dat += "
  3. New+
  4. " @@ -102,15 +125,27 @@ if(!selected) dat += "No belly selected. Click one to select it." else - if(selected.internal_contents.len > 0) + if(selected.contents.len) dat += "Contents: " - for(var/O in selected.internal_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.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.internal_contents.len > 1) + if(selected.contents.len > 1) dat += "\[All\]" dat += "
    " @@ -129,14 +164,15 @@ //Inside flavortext dat += "
    Flavor Text:" - dat += " '[selected.inside_flavor]'" + dat += " '[selected.desc]'" //Belly sound dat += "
    Set Vore Sound" dat += "Test" - // //Belly silence - // dat += "
    Belly Silence ([selected.silenced ? "Silenced" : "Noisy"])" + //Release sound + dat += "
    Set Release Sound" + dat += "Test" //Belly messages dat += "
    Belly Messages" @@ -145,6 +181,10 @@ 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) @@ -173,15 +213,21 @@ dat += " [selected.digestchance]%" dat += "
    " + // Belly Silence + dat += "
    Belly Silence (for not belly bellies):" + dat += " [selected.silent ? "Yes" : "No"]" + //Delete button dat += "
    Delete Belly" + dat += "Set Flavor" + dat += "Toggle Hunger Noises" + dat += "
    " //Under the last HR, save and stuff. dat += "Save Prefs" dat += "Refresh" - dat += "Set Flavor" dat += "
    " switch(user.digestable) @@ -221,8 +267,8 @@ if(href_list["outsidepick"]) var/atom/movable/tgt = locate(href_list["outsidepick"]) - var/datum/belly/OB = locate(href_list["outsidebelly"]) - if(!(tgt in OB.internal_contents)) //Aren't here anymore, need to update menu. + var/obj/belly/OB = locate(href_list["outsidebelly"]) + if(!(tgt in OB)) //Aren't here anymore, need to update menu. return TRUE var/intent = "Examine" @@ -234,42 +280,49 @@ M.examine(user) if("Help Out") //Help the inside-mob out - to_chat(user, "You begin to push [M] to freedom!") - to_chat(M, "[usr] begins to push you to freedom!") - M.loc << "Someone is trying to escape from inside you!" + if(user.stat || user.absorbed || M.absorbed) + to_chat(user,"You can't do that in your state!") + return 1 + + 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!") - M.loc << "[M] forces free of the confines of your body!" + 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.") - M.loc << "Your body efficiently shoves [M] back where they belong." + 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.absorbed || user.stat) + to_chat(user,"You can't do that in your state!") + return 1 + if(!user.vore_selected) - to_chat(user, "Pick a belly on yourself first!") - return + to_chat(user,"Pick a belly on yourself first!") + return 1 - var/datum/belly/TB = user.vore_organs[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)]!") - M.loc << "Someone inside you is eating someone else!" + 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) - if((user in OB.internal_contents) && (M in OB.internal_contents)) - 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)]!") - M.loc << "Someone inside you has eaten someone else!" - M.loc = user + 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) - OB.internal_contents -= M else if(istype(tgt,/obj/item)) var/obj/item/T = tgt - if(!(tgt in OB.internal_contents)) + if(!(tgt in OB.contents)) //Doesn't exist anymore, update. return TRUE intent = alert("What do you want to do to that?","Query","Examine","Use Hand") @@ -301,27 +354,29 @@ return selected.release_all_contents() - playsound(get_turf(user),'sound/vore/pred/escape.ogg',50,0,-5,0,ignore_walls = FALSE) + for(var/mob/M in get_hearers_in_view(5, get_turf(user))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(user),'sound/vore/pred/escape.ogg',50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) to_chat(user.loc,"Everything is released from [user]!") if("Move all") if(user.stat) to_chat(user, "You can't do that in your state!") - return + return FALSE - var/choice = input("Move all where?","Select Belly") in user.vore_organs + "Cancel - Don't Move" + var/obj/belly/choice = input("Move all where?","Select Belly") as null|anything in user.vore_organs + if(!choice) + return FALSE - if(choice == "Cancel - Don't Move") - return - else - var/datum/belly/B = user.vore_organs[choice] - for(var/atom/movable/tgt in selected.internal_contents) - to_chat(tgt, "You're squished from [user]'s [selected] to their [B]!") - selected.transfer_contents(tgt, B, 1) - playsound(get_turf(user),'sound/vore/pred/stomachmove.ogg',50,0,-5,0,ignore_walls = FALSE) + for(var/atom/movable/tgt in selected) + selected.transfer_contents(tgt, choice, 1) + for(var/mob/M in get_hearers_in_view(5, get_turf(user))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(user),'sound/vore/pred/stomachmove.ogg',50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) + to_chat(tgt,"You're squished from [user]'s [lowertext(selected)] to their [lowertext(choice.name)]!") var/atom/movable/tgt = locate(href_list["insidepick"]) - if(!(tgt in selected.internal_contents)) //Old menu, needs updating because they aren't really there. + 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") @@ -335,48 +390,54 @@ return FALSE selected.release_specific_contents(tgt) - playsound(get_turf(user),'sound/effects/splat.ogg',50,0,-5,0,ignore_walls = FALSE) + for(var/mob/M in get_hearers_in_view(5, get_turf(user))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(user),'sound/vore/pred/escape.ogg',50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) user.loc << "[tgt] is released from [user]!" if("Move") if(user.stat) - to_chat(user, "You can't do that in your state!") - return FALSE + to_chat(user,"You can't do that in your state!") + return 0 - var/choice = input("Move [tgt] where?","Select Belly") in user.vore_organs + "Cancel - Don't Move" + var/obj/belly/choice = input("Move [tgt] where?","Select Belly") as null|anything in user.vore_organs + if(!choice || !(tgt in selected)) + return 0 - if(choice == "Cancel - Don't Move") - return - else - var/datum/belly/B = user.vore_organs[choice] - if (!(tgt in selected.internal_contents)) - return FALSE - to_chat(tgt, "You're moved from [user]'s [lowertext(selected.name)] to their [lowertext(B.name)]!") - playsound(get_turf(user),'sound/vore/pred/stomachmove.ogg',50,0,-5,0,ignore_walls = FALSE) - selected.transfer_contents(tgt, B) + to_chat(tgt,"You're squished from [user]'s [lowertext(selected.name)] to their [lowertext(choice.name)]!") + selected.transfer_contents(tgt, choice) + for(var/mob/M in get_hearers_in_view(5, get_turf(user))) + if(M.client && M.client.prefs.toggles & EATING_NOISES) + playsound(get_turf(user),'sound/vore/pred/stomachmove.ogg',50,0,-5,0,ignore_walls = FALSE,channel=CHANNEL_PRED) if(href_list["newbelly"]) if(user.vore_organs.len >= BELLIES_MAX) - return TRUE + return 0 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) - to_chat(usr, "Entered belly name is too long.") - return FALSE - if(new_name in user.vore_organs) - to_chat(usr, "No duplicate belly names, please.") - return FALSE + 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 - var/datum/belly/NB = new(user) + if(failure_msg) //Something went wrong. + alert(user,failure_msg,"Error!") + return 0 + + var/obj/belly/NB = new(user) NB.name = new_name - NB.owner = user //might be the thing we all needed. - user.vore_organs[new_name] = NB selected = NB if(href_list["bellypick"]) selected = locate(href_list["bellypick"]) - user.vore_selected = selected.name + user.vore_selected = selected //// //Please keep these the same order they are on the panel UI for ease of coding @@ -384,41 +445,40 @@ 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) - to_chat(usr, "Entered belly name length invalid (must be longer than 2, shorter than 12).") - return FALSE - if(new_name in user.vore_organs) - to_chat(usr, "No duplicate belly names, please.") - return FALSE + 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 0 - user.vore_organs[new_name] = selected - user.vore_organs -= selected.name selected.name = new_name if(href_list["b_mode"]) var/list/menu_list = selected.digest_modes - if(selected.digest_modes.len == 1) // Don't do anything - return 1 - if(selected.digest_modes.len == 2) // Just toggle... there's probably a more elegant way to do this... - var/index = selected.digest_modes.Find(selected.digest_mode) - switch(index) - if(1) - selected.digest_mode = selected.digest_modes[2] - if(2) - selected.digest_mode = selected.digest_modes[1] - else - selected.digest_mode = input("Choose Mode (currently [selected.digest_mode])") in menu_list + var/new_mode = input("Choose Mode (currently [selected.digest_mode])") as null|anything in menu_list + if(!new_mode) + return 0 + selected.digest_mode = new_mode if(href_list["b_desc"]) - var/new_desc = html_encode(input(usr,"Belly Description (1024 char limit):","New Description",selected.inside_flavor) as message|null) + 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) - to_chat(usr, "Entered belly desc too long. [BELLIES_DESC_MAX] character limit.") + alert("Entered belly desc too long. [BELLIES_DESC_MAX] character limit.","Error") return FALSE - - selected.inside_flavor = new_desc + selected.desc = new_desc else //Returned null return FALSE @@ -429,12 +489,11 @@ "Struggle Message (outside)", "Struggle Message (inside)", "Examine Message (when full)", - "Reset All To Default", - "Cancel - No Changes" + "Reset All To Default" ) 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") in messages + 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." switch(choice) @@ -459,7 +518,7 @@ selected.set_messages(new_message,"smi") 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 + 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") @@ -471,40 +530,57 @@ selected.struggle_messages_outside = initial(selected.struggle_messages_outside) selected.struggle_messages_inside = initial(selected.struggle_messages_inside) - if("Cancel - No Changes") - return - 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) if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN) - to_chat(usr, "Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).") - return FALSE + alert("Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error") + return 0 selected.vore_verb = new_verb - if(href_list["b_sound"]) - var/choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") in GLOB.pred_vore_sounds + "Cancel - No Changes" + if(href_list["b_release"]) + var/choice = input(user,"Currently set to [selected.release_sound]","Select Sound") as null|anything in GLOB.release_sound - if(choice == "Cancel") + if(!choice) + return + + selected.release_sound = GLOB.release_sound[choice] + + if(href_list["b_releasesoundtest"]) + var/soundfile = GLOB.release_sound[selected.release_sound] + if(soundfile) + user << soundfile + + 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 = GLOB.pred_vore_sounds[choice] if(href_list["b_soundtest"]) - user << selected.vore_sound -/* - if(href_list["silenced"]) - if(selected.silenced == FALSE) - selected.silenced = TRUE - to_chat(usr,"The [selected.name] is now silenced, it will not play the internal loop to prey within it.") - else if(selected.silenced == TRUE) - selected.silenced = FALSE - to_chat(usr,"The [selected.name] will play the internal loop to prey within it.") -*/ + var/soundfile = GLOB.pred_vore_sounds[selected.vore_sound] + if(soundfile) + user << soundfile + 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 @@ -515,7 +591,7 @@ show_interacts = FALSE //Force the hiding of the panel else to_chat(usr,"Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.") //If they somehow have a varable that's not 0 or 1 - selected.escapable = FALSE + selected.escapable = TRUE show_interacts = FALSE //Force the hiding of the panel if(href_list["b_escapechance"]) @@ -537,68 +613,73 @@ var/choice = input("Where do you want your [selected.name] to lead if prey resists?","Select Belly") as null|anything in (user.vore_organs + "None - Remove" - selected.name) if(!choice) //They cancelled, no changes - return + return FALSE else if(choice == "None - Remove") selected.transferlocation = null else selected.transferlocation = user.vore_organs[choice] + 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_silent"]) + selected.silent = !selected.silent + if(href_list["b_del"]) - var/dest_for = FALSE //Check to see if it's the destination of another vore organ. - for(var/I in user.vore_organs) - var/datum/belly/B = user.vore_organs[I] + 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(dest_for) - alert("This is the destiantion for at least '[dest_for]' belly transfers. Remove it as the destination from any bellies before deleting it.","Error") - return TRUE - else if(selected.internal_contents.len) - alert("Can't delete bellies with contents!","Error") - return TRUE - if(selected.internal_contents.len) - to_chat(usr, "Can't delete bellies with contents!") - return - else if(selected.immutable) - to_chat(usr, "This belly is marked as undeletable.") - return - else if(user.vore_organs.len == 1) - to_chat(usr, "You must have at least one belly.") - return - else - var/alert = alert("Are you sure you want to delete [selected]?","Confirmation","Delete","Cancel") - if(alert == "Delete" && !selected.internal_contents.len) - user.vore_organs -= selected.name - user.vore_organs.Remove(selected) - selected = user.vore_organs[1] - user.vore_selected = user.vore_organs[1] - to_chat(usr,"Note: If you had this organ selected as a transfer location, please remove the transfer location by selecting Cancel - None - Remove on this stomach.") + 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.save_vore_prefs()) - to_chat(user, "Belly Preferences saved!") + if(!user.save_vore_prefs()) + to_chat(user, "Belly Preferences not saved!") else - to_chat(user, "ERROR: Belly Preferences were not saved!") + to_chat(user, "Belly Preferences were saved!") log_admin("Could not save vore prefs on USER: [user].") 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 0 - if(new_flavor) - new_flavor = readd_quotes(new_flavor) - if(length(new_flavor) > FLAVOR_MAX) - alert("Entered flavor/taste text too long. [FLAVOR_MAX] character limit.","Error") - return FALSE - user.vore_taste = new_flavor - else //Returned null - return FALSE + new_flavor = readd_quotes(new_flavor) + if(length(new_flavor) > FLAVOR_MAX) + alert("Entered flavor/taste text too long. [FLAVOR_MAX] character limit.","Error!") + return 0 + 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.digestable ? "Allowed" : "Prevented"]", "", "Allow Digestion", "Cancel", "Prevent Digestion") @@ -626,5 +707,15 @@ if(user.client.prefs_vr) user.client.prefs_vr.devourable = user.devourable + if(href_list["togglenoisy"]) + var/choice = alert(user, "Toggle audible hunger noises. Currently: [user.noisy ? "Enabled" : "Disabled"]", "", "Enable audible hunger", "Cancel", "Disable audible hunger") + switch(choice) + if("Cancel") + return 0 + if("Enable audible hunger") + user.noisy = TRUE + if("Disable audible hunger") + user.noisy = FALSE + //Refresh when interacted with, returning 1 makes vore_look.Topic update - return TRUE + return 1 \ No newline at end of file diff --git a/code/modules/vore/persistence.dm b/code/modules/vore/persistence.dm new file mode 100644 index 0000000000..078a3f48ee --- /dev/null +++ b/code/modules/vore/persistence.dm @@ -0,0 +1,90 @@ +/* +* Returns a byond list that can be passed to the "deserialize" proc +* to bring a new instance of this atom to its original state +* +* If we want to store this info, we can pass it to `json_encode` or some other +* interface that suits our fancy, to make it into an easily-handled string +*/ +/datum/proc/serialize() + var/data = list("type" = "[type]") + return data + +/* +* This is given the byond list from above, to bring this atom to the state +* described in the list. +* This will be called after `New` but before `initialize`, so linking and stuff +* would probably be handled in `initialize` +* +* Also, this should only be called by `list_to_object` in persistence.dm - at least +* with current plans - that way it can actually initialize the type from the list +*/ +/datum/proc/deserialize(var/list/data) + return + +/atom + // This var isn't actually used for anything, but is present so that + // DM's map reader doesn't forfeit on reading a JSON-serialized map + var/map_json_data + +// This is so specific atoms can override these, and ignore certain ones +/atom/proc/vars_to_save() + return list("color","dir","icon","icon_state","name","pixel_x","pixel_y") + +/atom/proc/map_important_vars() + // A list of important things to save in the map editor + return list("color","dir","icon","icon_state","layer","name","pixel_x","pixel_y") + +/area/map_important_vars() + // Keep the area default icons, to keep things nice and legible + return list("name") + +// No need to save any state of an area by default +/area/vars_to_save() + return list("name") + +/atom/serialize() + var/list/data = ..() + for(var/thing in vars_to_save()) + if(vars[thing] != initial(vars[thing])) + data[thing] = vars[thing] + return data + + +/atom/deserialize(var/list/data) + for(var/thing in vars_to_save()) + if(thing in data) + vars[thing] = data[thing] + ..() + + +/* +Whoops, forgot to put documentation here. +What this does, is take a JSON string produced by running +BYOND's native `json_encode` on a list from `serialize` above, and +turns that string into a new instance of that object. + +You can also easily get an instance of this string by calling "Serialize Marked Datum" +in the "Debug" tab. + +If you're clever, you can do neat things with SDQL and this, though be careful - +some objects, like humans, are dependent that certain extra things are defined +in their list +*/ +/proc/object_to_json(var/atom/movable/thing) + return json_encode(thing.serialize()) + +/proc/json_to_object(var/json_data, var/loc) + return list_to_object(json_decode(json_data), loc) + +/proc/list_to_object(var/list/data, var/loc) + if(!islist(data)) + throw EXCEPTION("You didn't give me a list, bucko") + if(!("type" in data)) + throw EXCEPTION("No 'type' field in the data") + var/path = text2path(data["type"]) + if(!path) + throw EXCEPTION("Path not found: [path]") + + var/atom/movable/thing = new path(loc) + thing.deserialize(data) + return thing \ No newline at end of file diff --git a/modular_citadel/code/modules/client/preferences_toggles.dm b/modular_citadel/code/modules/client/preferences_toggles.dm new file mode 100644 index 0000000000..4b92a1e9c3 --- /dev/null +++ b/modular_citadel/code/modules/client/preferences_toggles.dm @@ -0,0 +1,22 @@ +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleeatingnoise)() + set name = "Toggle Eating Noises" + set category = "Preferences" + set desc = "Hear Eating noises" + usr.client.prefs.toggles ^= EATING_NOISES + usr.client.prefs.save_preferences() + usr.stop_sound_channel(CHANNEL_PRED) + to_chat(usr, "You will [(usr.client.prefs.toggles & EATING_NOISES) ? "now" : "no longer"] hear eating noises.") +/datum/verbs/menu/Settings/Sound/toggleeatingnoise/Get_checked(client/C) + return !(C.prefs.toggles & EATING_NOISES) + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggledigestionnoise)() + set name = "Toggle Digestion Noises" + set category = "Preferences" + set desc = "Hear digestive noises" + usr.client.prefs.toggles ^= DIGESTION_NOISES + usr.client.prefs.save_preferences() + usr.stop_sound_channel(CHANNEL_DIGEST) + to_chat(usr, "You will [(usr.client.prefs.toggles & DIGESTION_NOISES) ? "now" : "no longer"] hear digestion noises.") +/datum/verbs/menu/Settings/Sound/toggledigestionnoise/Get_checked(client/C) + return !(C.prefs.toggles & DIGESTION_NOISES) diff --git a/tgstation.dme b/tgstation.dme index 3a2b9afee0..2bc8d3ec24 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -284,6 +284,7 @@ #include "code\controllers\subsystem\timer.dm" #include "code\controllers\subsystem\title.dm" #include "code\controllers\subsystem\traumas.dm" +#include "code\controllers\subsystem\vore.dm" #include "code\controllers\subsystem\vote.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\processing\circuit.dm" @@ -2587,9 +2588,12 @@ #include "code\modules\vehicles\vehicle_actions.dm" #include "code\modules\vehicles\vehicle_key.dm" #include "code\modules\vore\hook-defs_vr.dm" +#include "code\modules\vore\persistence.dm" #include "code\modules\vore\trycatch_vr.dm" -#include "code\modules\vore\eating\belly_vr.dm" +#include "code\modules\vore\eating\belly_dat_vr.dm" +#include "code\modules\vore\eating\belly_obj_vr.dm" #include "code\modules\vore\eating\bellymodes_vr.dm" +#include "code\modules\vore\eating\digest_act_vr.dm" #include "code\modules\vore\eating\living_vr.dm" #include "code\modules\vore\eating\simple_animal_vr.dm" #include "code\modules\vore\eating\vore_vr.dm" @@ -2659,6 +2663,7 @@ #include "modular_citadel\code\modules\client\client_procs.dm" #include "modular_citadel\code\modules\client\preferences.dm" #include "modular_citadel\code\modules\client\preferences_savefile.dm" +#include "modular_citadel\code\modules\client\preferences_toggles.dm" #include "modular_citadel\code\modules\client\loadout\__donator.dm" #include "modular_citadel\code\modules\client\loadout\_medical.dm" #include "modular_citadel\code\modules\client\loadout\_security.dm"