diff --git a/code/__DEFINES/voreconstants.dm b/code/__DEFINES/voreconstants.dm new file mode 100644 index 0000000000..cd4204514c --- /dev/null +++ b/code/__DEFINES/voreconstants.dm @@ -0,0 +1,66 @@ +// Overhauled vore system +#define DM_HOLD "Hold" +#define DM_DIGEST "Digest" +#define DM_HEAL "Heal" +#define DM_DIGESTF "Fast Digest" + +#define VORE_STRUGGLE_EMOTE_CHANCE 40 + +// Stance for hostile mobs to be in while devouring someone. +#define HOSTILE_STANCE_EATING 99 + + +var/global/list/player_sizes_list = list("Macro" = SIZESCALE_HUGE, "Big" = SIZESCALE_BIG, "Normal" = SIZESCALE_NORMAL, "Small" = SIZESCALE_SMALL, "Tiny" = SIZESCALE_TINY) +/* // moved to sound.dm + +var/global/list/digestion_sounds = list( + 'sound/vore/digest1.ogg', + 'sound/vore/digest2.ogg', + 'sound/vore/digest3.ogg', + 'sound/vore/digest4.ogg', + 'sound/vore/digest5.ogg', + 'sound/vore/digest6.ogg', + 'sound/vore/digest7.ogg', + 'sound/vore/digest8.ogg', + 'sound/vore/digest9.ogg', + 'sound/vore/digest10.ogg', + 'sound/vore/digest11.ogg', + 'sound/vore/digest12.ogg') + +var/global/list/death_sounds = list( + 'sound/vore/death1.ogg', + 'sound/vore/death2.ogg', + 'sound/vore/death3.ogg', + 'sound/vore/death4.ogg', + 'sound/vore/death5.ogg', + 'sound/vore/death6.ogg', + 'sound/vore/death7.ogg', + 'sound/vore/death8.ogg', + 'sound/vore/death9.ogg', + 'sound/vore/death10.ogg') */ + +var/global/list/vore_sounds = list( + "Gulp" = 'sound/vore/gulp.ogg', + "Insert" = 'sound/vore/insert.ogg', + "Insertion1" = 'sound/vore/insertion1.ogg', + "Insertion2" = 'sound/vore/insertion2.ogg', + "Insertion3" = 'sound/vore/insertion3.ogg', + "Schlorp" = 'sound/vore/schlorp.ogg', + "Squish1" = 'sound/vore/squish1.ogg', + "Squish2" = 'sound/vore/squish2.ogg', + "Squish3" = 'sound/vore/squish3.ogg', + "Squish4" = 'sound/vore/squish4.ogg') +/* also moved to sound.dmi +var/global/list/struggle_sounds = list( + "Squish1" = 'sound/vore/squish1.ogg', + "Squish2" = 'sound/vore/squish2.ogg', + "Squish3" = 'sound/vore/squish3.ogg', + "Squish4" = 'sound/vore/squish4.ogg') + +/proc/log_debug(text) + if (config.log_debug) + diary << "\[[time_stamp()]]DEBUG: [text][log_end]" + + for(var/client/C in admins) + if(C.prefs.toggles & CHAT_DEBUGLOGS) + C << "DEBUG: [text]" */ \ No newline at end of file diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 09f5eb1ccc..b4de0ebb0d 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -347,6 +347,8 @@ GLOBAL_LIST(external_rsc_urls) if(!tooltips) tooltips = new /datum/tooltip(src) + hook_vr("client_new",list(src)) + ////////////// //DISCONNECT// ////////////// diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index f398062cd5..e51e6e1860 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -1535,8 +1535,10 @@ GLOBAL_LIST_EMPTY(preferences_datums) if("load") load_preferences() load_character() + attempt_vr(parent.prefs_vr,"load_vore","") if("changeslot") + attempt_vr(parent.prefs_vr,"load_vore","") if(!load_character(text2num(href_list["num"]))) random_character() real_name = random_unique_name(gender) diff --git a/code/modules/client/preferences_vr.dm b/code/modules/client/preferences_vr.dm new file mode 100644 index 0000000000..0f9b6935d3 --- /dev/null +++ b/code/modules/client/preferences_vr.dm @@ -0,0 +1,8 @@ +//File isn't currently being used. +/datum/preferences + var/biological_gender = MALE + var/identifying_gender = MALE + +/datum/preferences/proc/set_biological_gender(var/gender) + biological_gender = gender + identifying_gender = gender \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 8306372125..230d64c99a 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -257,6 +257,9 @@ msg += "[t_He] looks like a drunken mess.\n" 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 += "" diff --git a/code/modules/mob/living/carbon/human/examine_vr.dm b/code/modules/mob/living/carbon/human/examine_vr.dm new file mode 100644 index 0000000000..8578db809e --- /dev/null +++ b/code/modules/mob/living/carbon/human/examine_vr.dm @@ -0,0 +1,54 @@ +/mob/living/carbon/human/proc/examine_nutrition() + var/message = "" + var/nutrition_examine = round(nutrition) + var/t_He = "It" //capitalised for use at the start of each line. + var/t_His = "Its" + var/t_his = "its" + var/t_is = "is" + var/t_has = "has" + switch(gender) + if(MALE) + t_He = "He" + t_his = "his" + t_His = "His" + if(FEMALE) + t_He = "She" + t_his = "her" + t_His = "Her" + if(PLURAL) + t_He = "They" + t_his = "their" + t_His = "Their" + t_is = "are" + t_has = "have" + if(NEUTER) + t_He = "It" + t_his = "its" + t_His = "Its" + switch(nutrition_examine) + if(0 to 49) + message = "[t_He] [t_is] starving! You can hear [t_his] stomach snarling from across the room!\n" + if(50 to 99) + message = "[t_He] [t_is] extremely hungry. A deep growl occasionally rumbles from [t_his] empty stomach.\n" + if(100 to 499) + return message //Well that's pretty normal, really. + if(500 to 864) // Fat. + message = "[t_He] [t_has] a stuffed belly, bloated fat and round from eating too much.\n" + if(1200 to 1934) // One person fully digested. + message = "[t_He] [t_is] sporting a large, round, sagging stomach. It's contains at least their body weight worth of glorping slush.\n" + if(1935 to 3004) // Two people. + message = "[t_He] [t_is] engorged with a huge stomach that sags and wobbles as they move. [t_He] must have consumed at least twice their body weight. It looks incredibly soft.\n" + if(3005 to 4074) // Three people. + 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_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 41d850429f..d41b90ed3d 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -160,8 +160,8 @@ return ..() /mob/living/carbon/human/grabbedby(mob/living/carbon/user, supress_message = 0) - if(user == src && pulling && !pulling.anchored && grab_state >= GRAB_AGGRESSIVE && (disabilities & FAT) && ismonkey(pulling)) - devour_mob(pulling) + if(user == src && pulling && !pulling.anchored && grab_state >= GRAB_AGGRESSIVE && isliving(pulling)) + vore_attack(user, pulling) else ..() diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index eddd000747..6d3f26c244 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -42,6 +42,9 @@ return if(istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) return + if(ismob(loc)) + 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 02106dd0e9..9bfc9129f9 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -40,6 +40,9 @@ //stuff in the stomach handle_stomach() + // Vore code for belly processes + handle_internal_contents() + update_gravity(mob_has_gravity()) if(machine) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 7c26ab2cda..e1b0a05961 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -550,6 +550,9 @@ if(buckled && last_special <= world.time) resist_buckle() + // climbing out of a gut + if(attempt_vr(src,"vore_process_resist",args)) return TRUE + //Breaking out of a container (Locker, sleeper, cryo...) else if(isobj(loc)) var/obj/C = loc diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index fc7a451c3b..007f28784b 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -10,7 +10,10 @@ playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) if("grab") - grabbedby(M) + if(grab_state >= GRAB_AGGRESSIVE && isliving(pulling)) + vore_attack(M, pulling) + else + grabbedby(M) if("harm", "disarm") M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 1a308f9c99..4910eff67d 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -28,6 +28,7 @@ GLOB.living_mob_list += src prepare_huds() can_ride_typecache = typecacheof(can_ride_typecache) + hook_vr("mob_new",list(src)) ..() /atom/proc/prepare_huds() @@ -110,7 +111,7 @@ if(self_message) msg = self_message else - if(M.see_invisible[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. + src.internal_contents.Add(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) +// if (prey.buckled) +// prey.buckled.unbuckle_mob() + + prey.forceMove(owner) + internal_contents.Add(prey) + + if(inside_flavor) + prey << "[inside_flavor]" + +// 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)) + + 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) + + // 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) + 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) + + if(owner.stat) //If owner is stat (dead, KO) we can actually escape + R << "You attempt to climb out of \the [name]. (This will take around [escapetime/10] seconds.)" + 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 + R << "Your attempt to escape [name] has failed!" + 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 + "" + +// for(var/mob/M in hearers(4, owner)) +// M.visible_message(struggle_outer_message) // hearable + R.visible_message( "[struggle_outer_message]", "[struggle_user_message]") + playsound(R.loc, "struggle_sounds", 50, 0, -5) + + if(escapable && R.a_intent != "help") //If the stomach has escapable enabled and the person is actually trying to kick out + R << "You attempt to climb out of \the [name]." + 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) + R << "You climb out of \the [name]." + owner << "[R] climbs out of your [name]!" + for(var/mob/M in hearers(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. + R << "Your attempt to escape [name] has failed!" + owner << "The attempt to escape from your [name] has failed!" + return + + else //Nothing interesting happened. + R << "But make no progress in escaping [owner]'s [name]." + owner << "But appears to be unable to make any progress in escaping your [name]." + return + else + return + +// 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.escapable = escapable + dupe.escapetime = escapetime + + //// 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 \ No newline at end of file diff --git a/code/modules/vore/eating/bellymodes_vr.dm b/code/modules/vore/eating/bellymodes_vr.dm new file mode 100644 index 0000000000..559388f542 --- /dev/null +++ b/code/modules/vore/eating/bellymodes_vr.dm @@ -0,0 +1,115 @@ +// 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() + +/////////////////////////// Auto-Emotes /////////////////////////// + if((digest_mode in emote_lists) && !emotePend) + emotePend = TRUE + + 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 + +//////////////////////////// DM_DIGEST //////////////////////////// + if(digest_mode == DM_DIGEST) + + if(prob(50)) + playsound(owner.loc, "digestion_sounds", 50, 0, -5) + + for (var/mob/living/M in internal_contents) + //Pref protection! + if (!M.digestable) + continue + + //Person just died in guts! + if(M.stat == DEAD) + var/digest_alert_owner = pick(digest_messages_owner) + var/digest_alert_prey = pick(digest_messages_prey) + + //Replace placeholder vars + digest_alert_owner = replacetext(digest_alert_owner,"%pred",owner) + digest_alert_owner = replacetext(digest_alert_owner,"%prey",M) + digest_alert_owner = replacetext(digest_alert_owner,"%belly",lowertext(name)) + + digest_alert_prey = replacetext(digest_alert_prey,"%pred",owner) + digest_alert_prey = replacetext(digest_alert_prey,"%prey",M) + digest_alert_prey = replacetext(digest_alert_prey,"%belly",lowertext(name)) + + //Send messages + owner << "" + digest_alert_owner + "" + M << "" + digest_alert_prey + "" + + owner.nutrition += 400 // so eating dead mobs gives you *something*. + playsound(owner.loc, "death_gurgles", 50, 0, -5) + digestion_death(M) + owner.update_icons() + continue + + + // Deal digestion damage (and feed the pred) + if(!(M.status_flags & GODMODE)) + M.adjustFireLoss(1) + owner.nutrition += 1 + return + +//////////////////////////// DM_DIGESTF //////////////////////////// + if(digest_mode == DM_DIGESTF) + + if(prob(50)) + playsound(owner.loc, "digestion_sounds", 55, 0, -3) //slightly louder 'cuz heavier gurgles + + for (var/mob/living/M in internal_contents) + //Pref protection! + if (!M.digestable) + continue + + //Person just died in guts! + if(M.stat == DEAD) + var/digest_alert_owner = pick(digest_messages_owner) + var/digest_alert_prey = pick(digest_messages_prey) + + //Replace placeholder vars + digest_alert_owner = replacetext(digest_alert_owner,"%pred",owner) + digest_alert_owner = replacetext(digest_alert_owner,"%prey",M) + digest_alert_owner = replacetext(digest_alert_owner,"%belly",lowertext(name)) + + digest_alert_prey = replacetext(digest_alert_prey,"%pred",owner) + digest_alert_prey = replacetext(digest_alert_prey,"%prey",M) + digest_alert_prey = replacetext(digest_alert_prey,"%belly",lowertext(name)) + + //Send messages + owner << "" + digest_alert_owner + "" + M << "" + digest_alert_prey + "" + M.visible_message("[digest_alert_owner]", "[digest_alert_prey]", + "You watch as [owner]'s form lose its additions.") + owner.nutrition += 400 // so eating dead mobs gives you *something*. + playsound(owner.loc, "death_gurgles", 50, 0, -5) + digestion_death(M) + owner.update_icons() + continue + + // Deal digestion damage (and feed the pred) + if(!(M.status_flags & GODMODE)) + M.adjustBruteLoss(2) + M.adjustFireLoss(3) + owner.nutrition += 1 + return + +///////////////////////////// DM_HEAL ///////////////////////////// + if(digest_mode == DM_HEAL) + if(prob(50)) + playsound(owner.loc, "digestion_sounds", 45, 0, -6) //very quiet gurgles, supposedly. + + for (var/mob/living/M in internal_contents) + if(M.stat != DEAD) + if(owner.nutrition >= NUTRITION_LEVEL_STARVING && (M.health < M.maxHealth)) + M.adjustBruteLoss(-1) + M.adjustFireLoss(-1) + owner.nutrition -= 10 + return \ No newline at end of file diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm new file mode 100644 index 0000000000..9a7dbd6708 --- /dev/null +++ b/code/modules/vore/eating/living_vr.dm @@ -0,0 +1,326 @@ +///////////////////// Mob Living ///////////////////// +/mob/living + var/digestable = 1 // Can the mob be digested inside a belly? + var/datum/belly/vore_selected // Default to no vore capability. + var/list/vore_organs = list() // List of vore containers inside a mob + var/devourable = 0 // Can the mob be vored at all? +// var/feeding = 0 // Are we going to feed someone else? + + +// +// Hook for generic creation of stuff on new creatures +// +/hook/living_new/proc/vore_setup(mob/living/M) + M.verbs += /mob/living/proc/insidePanel + M.verbs += /mob/living/proc/escapeOOC + + //Tries to load prefs if a client is present otherwise gives freebie stomach + if(!M.vore_organs || !M.vore_organs.len) + spawn(20) //Wait a couple of seconds to make sure copy_to or whatever has gone + if(!M) return + + if(M.client && M.client.prefs_vr) + if(!M.copy_from_prefs_vr()) + M << "ERROR: You seem to have saved VOREStation prefs, but they couldn't be loaded." + return FALSE + 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) + 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]." + 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.") + + //Return 1 to hook-caller + return 1 + +// +// Handle being clicked, perhaps with something to devour +// + + // Refactored to use centralized vore code system - Leshana + + // Critical adjustments due to TG grab changes - Poojawa + +/mob/living/proc/vore_attack(var/mob/living/user, var/mob/living/prey) + if(!user || !prey) + return + + if(prey == src && user.zone_selected == "mouth") //you click your target +// if(!feeding(src)) +// return + if(!is_vore_predator(prey)) + user << "They aren't voracious enough." + return + feed_self_to_grabbed(user, src) + + if(user == src) //you click yourself + if(!is_vore_predator(src)) + user << "You aren't voracious enough." + return + user.feed_grabbed_to_self(src, prey) + + else // click someone other than you/prey +// if(!feeding(src)) +// return + if(!is_vore_predator(src)) + user << "They aren't voracious enough." + return + feed_grabbed_to_other(user, prey, src) +// +// Eating procs depending on who clicked what +// +/mob/living/proc/feed_grabbed_to_self(var/mob/living/user, var/mob/living/prey) + var/belly = user.vore_selected + return perform_the_nom(user, prey, user, belly) +/* +/mob/living/proc/eat_held_mob(var/mob/living/user, var/mob/living/prey, var/mob/living/pred) + var/belly + if(user != pred) + belly = input("Choose Belly") in pred.vore_organs + else + belly = pred.vore_selected + return perform_the_nom(user, prey, pred, belly)*/ + +/mob/living/proc/feed_self_to_grabbed(var/mob/living/user, var/mob/living/pred) + var/belly = input("Choose Belly") in pred.vore_organs + return perform_the_nom(user, user, pred, belly) + +/mob/living/proc/feed_grabbed_to_other(var/mob/living/user, var/mob/living/prey, var/mob/living/pred) + return//disabled until I can make that toggle work + var/belly = input("Choose Belly") in pred.vore_organs + return perform_the_nom(user, prey, pred, belly) + +// +// 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) + //Sanity + if(!user || !prey || !pred || !belly || !(belly in pred.vore_organs)) + return + if (!prey.devourable) + 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)" + + // 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)) + 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)) + + // Announce that we start the attempt! + user.visible_message(attempt_msg) + + // Now give the prey time to escape... return if they did + + 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(user, belly_target.vore_sound, 100, 1) + + // Actually shove prey into the belly. + belly_target.nom_mob(prey, user) +// user.update_icons() + stop_pulling() + + // Inform Admins + var/prey_braindead + var/prey_stat + if(prey.ckey) + prey_stat = prey.stat//only return this if they're not an unmonkey or whatever + if(!prey.client)//if they disconnected, tell us + prey_braindead = 1 + if (pred == user) + message_admins("[ADMIN_LOOKUPFLW(pred)] ate [ADMIN_LOOKUPFLW(prey)][!prey_braindead ? "" : " (BRAINDEAD)"][prey_stat ? " (DEAD/UNCONSCIOUS)" : ""].") + log_attack("[key_name(pred)] ate [key_name(prey)]") + else + message_admins("[ADMIN_LOOKUPFLW(user)] forced [ADMIN_LOOKUPFLW(pred)] to eat [ADMIN_LOOKUPFLW(prey)].") + log_attack("[key_name(user)] forced [key_name(pred)] to eat [key_name(prey)].") + return TRUE + +// +//End vore code. +/* + //Handle case: /obj/item/weapon/holder + if(/obj/item/weapon/holder/micro) + var/obj/item/weapon/holder/H = I + + if(!isliving(user)) return 0 // Return 0 to continue upper procs + var/mob/living/attacker = user // Typecast to living + + if (is_vore_predator(src)) + for (var/mob/living/M in H.contents) + attacker.eat_held_mob(attacker, M, src) + return 1 //Return 1 to exit upper procs + else + log_attack("[attacker] attempted to feed [H.contents] to [src] ([src.type]) but it failed.") + + // I just can't imagine this not being complained about + //Handle case: /obj/item/device/radio/beacon + if(/obj/item/device/radio/beacon) + var/confirm = alert(user, "[src == user ? "Eat the beacon?" : "Feed the beacon to [src]?"]", "Confirmation", "Yes!", "Cancel") + if(confirm == "Yes!") + var/bellychoice = input("Which belly?","Select A Belly") in src.vore_organs + var/datum/belly/B = src.vore_organs[bellychoice] + src.visible_message("[user] is trying to stuff a beacon into [src]'s [bellychoice]!","[user] is trying to stuff a beacon into you!") + if(do_after(user,30,src)) + user.drop_item() + I.loc = src + B.internal_contents += I + src.visible_message("[src] is fed the beacon!","You're fed the beacon!") + playsound(src, B.vore_sound, 100, 1) + return 1 + else + return 1 //You don't get to hit someone 'later' + + return 0 +*/ +// +// 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) + 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) + +// OOC Escape code for pref-breaking or AFK preds +// +/mob/living/proc/escapeOOC() + set name = "Animal Escape" + set category = "Vore" + + //You're in an animal! + if(istype(src.loc,/mob/living/simple_animal)) + var/mob/living/simple_animal/pred = src.loc + 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] + B.release_specific_contents(src) + + for(var/mob/living/simple_animal/SA in range(10)) + SA.prey_excludes += src + spawn(18000) + if(src && SA) + SA.prey_excludes -= src + + pred.update_icons() + + else + src << "You aren't inside anything, you clod." + +// +// Verb for saving vore preferences to save file +// +/mob/living/proc/save_vore_prefs() + if(!(client || client.prefs_vr)) + return FALSE + if(!copy_to_prefs_vr()) + return FALSE + if(!client.prefs_vr.save_vore()) + return FALSE + + return TRUE + +/mob/living/proc/apply_vore_prefs() + if(!(client || client.prefs_vr)) + return FALSE + if(!client.prefs_vr.load_vore()) + return FALSE + if(!copy_from_prefs_vr()) + return FALSE + + return TRUE + +/mob/living/proc/copy_to_prefs_vr() + if(!client || !client.prefs_vr) + src << "You attempted to save your vore prefs but somehow you're in this character without a client.prefs_vr variable. Tell a dev." + return FALSE + + var/datum/vore_preferences/P = client.prefs_vr + + P.digestable = src.digestable + P.devourable = src.devourable + P.belly_prefs = src.vore_organs + + return TRUE + +// +// Proc for applying vore preferences, given bellies +// +/mob/living/proc/copy_from_prefs_vr() + if(!client || !client.prefs_vr) + src << "You attempted to apply your vore prefs but somehow you're in this character without a client.prefs_vr variable. Tell a dev." + return FALSE + + var/datum/vore_preferences/P = client.prefs_vr + + src.digestable = P.digestable + src.devourable = P.devourable + src.vore_organs = list() + + for(var/I in P.belly_prefs) + var/datum/belly/Bp = P.belly_prefs[I] + src.vore_organs[Bp.name] = Bp.copy(src) + + return TRUE \ No newline at end of file diff --git a/code/modules/vore/eating/simple_animal_vr.dm b/code/modules/vore/eating/simple_animal_vr.dm new file mode 100644 index 0000000000..33417331f5 --- /dev/null +++ b/code/modules/vore/eating/simple_animal_vr.dm @@ -0,0 +1,39 @@ +///////////////////// Simple Animal ///////////////////// +/mob/living/simple_animal + var/isPredator = 0 //Are they capable of performing and pre-defined vore actions for their species? + var/swallowTime = 30 //How long it takes to eat its prey in 1/10 of a second. The default is 3 seconds. + var/list/prey_excludes = list() //For excluding people from being eaten. + +// +// 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)) + set name = "Animal Nom" + set category = "Vore" + set desc = "Since you can't grab, you get a verb!" + + feed_grabbed_to_self(src,T) +*/ +// +// Simple proc for animals to have their digestion toggled on/off externally +// +/mob/living/simple_animal/verb/toggle_digestion() + set name = "Toggle Animal's Digestion" + set desc = "Enables digestion on this mob for 20 minutes." + set category = "Vore" + set src in oview(1) + + var/datum/belly/B = vore_organs[vore_selected] + if(faction != usr.faction) + usr << "This predator isn't friendly, and doesn't give a shit about your opinions of it digesting you." + return + if(B.digest_mode == "Hold") + var/confirm = alert(usr, "Enabling digestion on [name] will cause it to digest all stomach contents. Using this to break OOC prefs is against the rules. Digestion will disable itself after 20 minutes.", "Enabling [name]'s Digestion", "Enable", "Cancel") + if(confirm == "Enable") + B.digest_mode = "Digest" + spawn(12000) //12000=20 minutes + if(src) B.digest_mode = "Hold" + else + var/confirm = alert(usr, "This mob is currently set to digest all stomach contents. Do you want to disable this?", "Disabling [name]'s Digestion", "Disable", "Cancel") + if(confirm == "Disable") + B.digest_mode = "Hold" \ No newline at end of file diff --git a/code/modules/vore/eating/vore_vr.dm b/code/modules/vore/eating/vore_vr.dm new file mode 100644 index 0000000000..f647a78fb0 --- /dev/null +++ b/code/modules/vore/eating/vore_vr.dm @@ -0,0 +1,125 @@ + +/* +VVVVVVVV VVVVVVVV OOOOOOOOO RRRRRRRRRRRRRRRRR EEEEEEEEEEEEEEEEEEEEEE +V::::::V V::::::V OO:::::::::OO R::::::::::::::::R E::::::::::::::::::::E +V::::::V V::::::V OO:::::::::::::OO R::::::RRRRRR:::::R E::::::::::::::::::::E +V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEEEEE::::E + V:::::V V:::::V O::::::O O::::::O R::::R R:::::R E:::::E EEEEEE + V:::::V V:::::V O:::::O O:::::O R::::R R:::::R E:::::E + V:::::V V:::::V O:::::O O:::::O R::::RRRRRR:::::R E::::::EEEEEEEEEE + V:::::V V:::::V O:::::O O:::::O R:::::::::::::RR E:::::::::::::::E + V:::::V V:::::V O:::::O O:::::O R::::RRRRRR:::::R E:::::::::::::::E + V:::::V V:::::V O:::::O O:::::O R::::R R:::::R E::::::EEEEEEEEEE + V:::::V:::::V O:::::O O:::::O R::::R R:::::R E:::::E + V:::::::::V O::::::O O::::::O R::::R R:::::R E:::::E EEEEEE + V:::::::V O:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEEEE:::::E + V:::::V OO:::::::::::::OO R::::::R R:::::RE::::::::::::::::::::E + V:::V OO:::::::::OO R::::::R R:::::RE::::::::::::::::::::E + VVV OOOOOOOOO RRRRRRRR RRRRRRREEEEEEEEEEEEEEEEEEEEEE + +-Aro <3 */ + +// +// 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 +// +/client + var/datum/vore_preferences/prefs_vr + +/hook/client_new/proc/add_prefs_vr(client/C) + C.prefs_vr = new/datum/vore_preferences(C) + if(C.prefs_vr) + return TRUE + + return FALSE + +/datum/vore_preferences + //Actual preferences + var/digestable = 1 + var/devourable = 0 + var/list/belly_prefs = list() + + //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) + +// +// Check if an object is capable of eating things, based on vore_organs +// +/proc/is_vore_predator(var/mob/living/O) + if(istype(O,/mob/living)) + if(O.vore_organs.len > 0) + return TRUE + + return FALSE + +// +// Belly searching for simplifying other procs +// +/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 + +// +// Save/Load Vore Preferences +// +/datum/vore_preferences/proc/load_vore() + if(!client || !client_ckey) return 0 //No client, how can we save? + + slot = client.prefs.default_slot + + path = client.prefs.path + + if(!path) return 0 //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 0 //Savefile object couldn't be created? + + S.cd = "/character[slot]" + + S["digestable"] >> digestable + S["devourable"] >> devourable + S["belly_prefs"] >> belly_prefs + + if(isnull(digestable)) + digestable = 1 + if(isnull(devourable)) + devourable = 0 + if(isnull(belly_prefs)) + belly_prefs = list() + + return 1 + +/datum/vore_preferences/proc/save_vore() + if(!path) return 0 + if(!slot) return 0 + var/savefile/S = new /savefile(path) + if(!S) return 0 + S.cd = "/character[slot]" + + S["digestable"] << digestable + S["devourable"] << devourable + S["belly_prefs"] << belly_prefs + + return 1 \ No newline at end of file diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm new file mode 100644 index 0000000000..e6d00991c8 --- /dev/null +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -0,0 +1,480 @@ +// +// Vore management panel for players +// + +/mob/living/proc/insidePanel() + set name = "Vore Panel" + set category = "Vore" + + var/datum/vore_look/picker_holder = new() + picker_holder.loop = picker_holder + picker_holder.selected = vore_organs[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/datum/browser/popup + var/loop = null; // Magic self-reference to stop the handler from being GC'd before user takes action. + +/datum/vore_look/Topic(href,href_list[]) + if (vp_interact(href, href_list)) + popup.set_content(gen_vui(usr)) + usr << output(popup.get_content(), "insidePanel.browser") + +/datum/vore_look/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) + + if(inside_belly) + dat += "You are currently inside [eater]'s [inside_belly]!

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

" + + if (inside_belly.internal_contents.len > 1) + dat += "You can see the following around you:
" + for (var/atom/movable/O in inside_belly.internal_contents) + if(istype(O,/mob/living)) + var/mob/living/M = O + //That's just you + if(M == user) + continue + //Anything else + dat += "[O]" + else + dat += "You aren't inside anyone." + + dat += "
" + + dat += "
    " + for(var/K in user.vore_organs) //Fuggin can't iterate over values + var/datum/belly/B = user.vore_organs[K] + if(B == selected) + dat += "
  1. [B.name]" + else + dat += "
  2. [B.name]" + + var/spanstyle + switch(B.digest_mode) + if(DM_HOLD) + spanstyle = "" + if(DM_DIGEST) + spanstyle = "color:red;" + if(DM_DIGESTF) + spanstyle = "color:purple;" + if(DM_HEAL) + spanstyle = "color:green;" + + dat += " ([B.internal_contents.len])
  3. " + + if(user.vore_organs.len < 10) + dat += "
  4. New+
  5. " + dat += "
" + dat += "
" + + // Selected Belly (contents, configuration) + if(!selected) + dat += "No belly selected. Click one to select it." + else + if(selected.internal_contents.len > 0) + dat += "Contents: " + for(var/O in selected.internal_contents) + dat += "[O]" + + //If there's more than one thing, add an [All] button + if(selected.internal_contents.len > 1) + dat += "\[All\]" + + dat += "
" + + //Belly Name Button + dat += "Name:" + dat += " '[selected.name]'" + + //Digest Mode Button + dat += "
Belly Mode:" + dat += " [selected.digest_mode]" + + //Belly verb + dat += "
Vore Verb:" + dat += " '[selected.vore_verb]'" + + //Inside flavortext + dat += "
Flavor Text:" + dat += " '[selected.inside_flavor]'" + + //Belly sound + dat += "
Set Vore Sound" + dat += "Test" + + //Belly messages + dat += "
Belly Messages" + + //Delete button + dat += "
Delete Belly" + + dat += "
" + + //Under the last HR, save and stuff. + dat += "Save Prefs" + dat += "Refresh" + + switch(user.digestable) + if(1) + dat += "Toggle Digestable" + if(0) + dat += "Toggle Digestable" + + switch(user.devourable) + if(1) + dat += "Toggle Devourable" + if(0) + dat += "Toggle Devourable" + + //Returns the dat html to the vore_look + return dat + +/datum/vore_look/proc/vp_interact(href, href_list) + var/mob/living/user = usr + for(var/H in href_list) + + if(href_list["close"]) + del(src) // Cleanup + return + + if(href_list["outsidepick"]) + var/tgt = locate(href_list["outsidepick"]) + var/datum/belly/OB = locate(href_list["outsidebelly"]) + var/intent = "Examine" + + if(istype(tgt,/mob/living)) + var/mob/living/M = tgt + intent = alert("What do you want to do to them?","Query","Examine","Help Out","Devour") + switch(intent) + if("Examine") //Examine a mob inside another mob + M.examine(user) + + if("Help Out") //Help the inside-mob out + user << "You begin to push [M] to freedom!" + M << "[usr] begins to push you to freedom!" + M.loc << "Someone is trying to escape from inside you!" + sleep(50) + if(prob(33)) + OB.release_specific_contents(M) + usr << "You manage to help [M] to safety!" + M << "[user] pushes you free!" + M.loc << "[M] forces free of the confines of your body!" + else + user << "[M] slips back down inside despite your efforts." + M << " Even with [user]'s help, you slip back inside again." + M.loc << "Your body efficiently shoves [M] back where they belong." + + if("Devour") //Eat the inside mob + if(!user.vore_selected) + user << "Pick a belly on yourself first!" + return 1 + + var/datum/belly/TB = user.vore_organs[user.vore_selected] + user << "You begin to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!" + M << "[user] begins to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!" + M.loc << "Someone inside you is eating someone else!" + + sleep(TB.nonhuman_prey_swallow_time) + if((user in OB.internal_contents) && (M in OB.internal_contents)) + user << "You manage to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!" + 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 + TB.nom_mob(M) + OB.internal_contents -= M + + else if(istype(tgt,/obj/item)) + var/obj/item/T = tgt + intent = alert("What do you want to do to that?","Query","Examine","Use Hand") + switch(intent) + if("Examine") + T.examine(user) + + if("Use Hand") + if(user.stat) + user << "You can't do that in your state!" + return 1 + + user.ClickOn(T) + sleep(5) //Seems to exit too fast for the panel to update + + if(href_list["insidepick"]) + var/intent + + //Handle the [All] choice. Ugh inelegant. Someone make this pretty. + if(href_list["pickall"]) + intent = alert("Eject all, Move all?","Query","Eject all","Cancel","Move all") + switch(intent) + if("Cancel") + return 1 + + if("Eject all") + if(user.stat) + user << "You can't do that in your state!" + return 1 + + selected.release_all_contents() + playsound(user, 'sound/effects/splat.ogg', 50, 1) + user.loc << "Everything is released from [user]!" + + if("Move all") + if(user.stat) + user << "You can't do that in your state!" + return 1 + + var/choice = input("Move all where?","Select Belly") in user.vore_organs + "Cancel - Don't Move" + + if(choice == "Cancel - Don't Move") + return 1 + else + var/datum/belly/B = user.vore_organs[choice] + for(var/atom/movable/tgt in selected.internal_contents) + selected.internal_contents -= tgt + B.internal_contents += tgt + + tgt << "You're squished from [user]'s [selected] to their [B]!" + + for(var/mob/hearer in range(1,user)) + hearer << sound('sound/vore/squish2.ogg',volume=80) + return 1 + + + var/atom/movable/tgt = locate(href_list["insidepick"]) + intent = "Examine" + intent = alert("Examine, Eject, Move? Examine if you want to leave this box.","Query","Examine","Eject","Move") + switch(intent) + if("Examine") + tgt.examine(user) + + if("Eject") + if(user.stat) + user << "You can't do that in your state!" + return 1 + + selected.release_specific_contents(tgt) + playsound(user, 'sound/effects/splat.ogg', 50, 1) + user.loc << "[tgt] is released from [user]!" + + if("Move") + if(user.stat) + user << "You can't do that in your state!" + return 1 + + var/choice = input("Move [tgt] where?","Select Belly") in user.vore_organs + "Cancel - Don't Move" + + if(choice == "Cancel - Don't Move") + return 1 + else + var/datum/belly/B = user.vore_organs[choice] + selected.internal_contents -= tgt + B.internal_contents += tgt + + tgt << "You're squished from [user]'s [lowertext(selected.name)] to their [lowertext(B.name)]!" + for(var/mob/hearer in range(1,user)) + hearer << sound('sound/vore/squish2.ogg',volume=80) + + if(href_list["newbelly"]) + if(user.vore_organs.len >= 10) + return 1 + + var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null) + + if(length(new_name) > 12 || length(new_name) < 2) + usr << "Entered belly name is too long." + return 0 + if(new_name in user.vore_organs) + usr << "No duplicate belly names, please." + return 0 + + var/datum/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 + + if(href_list["b_name"]) + var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null) + + if(length(new_name) > 12 || length(new_name) < 2) + usr << "Entered belly name length invalid (must be longer than 2, shorter than 12)." + return 0 + if(new_name in user.vore_organs) + usr << "No duplicate belly names, please." + 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 + + 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) + new_desc = readd_quotes(new_desc) + + if(length(new_desc) > 1024) + usr << "Entered belly desc too long. 1024 character limit." + return FALSE + + selected.inside_flavor = new_desc + + if(href_list["b_msgs"]) + var/list/messages = list( + "Digest Message (to prey)", + "Digest Message (to you)", + "Struggle Message (outside)", + "Struggle Message (inside)", + "Examine Message (when full)", + "Reset All To Default", + "Cancel - No Changes" + ) + + 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/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) + if("Digest Message (to prey)") + var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",selected.get_messages("dmp")) as message + if(new_message) + selected.set_messages(new_message,"dmp") + + if("Digest Message (to you)") + var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",selected.get_messages("dmo")) as message + if(new_message) + selected.set_messages(new_message,"dmo") + + if("Struggle Message (outside)") + var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",selected.get_messages("smo")) as message + if(new_message) + selected.set_messages(new_message,"smo") + + if("Struggle Message (inside)") + var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",selected.get_messages("smi")) as message + if(new_message) + selected.set_messages(new_message,"smi") + + 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'). Do not use %pred or %prey in this type."+help,"Examine Message (when full)",selected.get_messages("em")) as message + if(new_message) + selected.set_messages(new_message,"em") + + if("Reset All To Default") + var/confirm = alert(user,"This will delete any custom messages. Are you sure?","Confirmation","DELETE","Cancel") + if(confirm == "DELETE") + selected.digest_messages_prey = initial(selected.digest_messages_prey) + selected.digest_messages_owner = initial(selected.digest_messages_owner) + selected.struggle_messages_outside = initial(selected.struggle_messages_outside) + selected.struggle_messages_inside = initial(selected.struggle_messages_inside) + + if("Cancel - No Changes") + return 1 + + 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) > 12 || length(new_verb) < 2) + usr << "Entered verb length invalid (must be longer than 2, shorter than 12)." + return FALSE + + selected.vore_verb = new_verb + + if(href_list["b_sound"]) + var/choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") in vore_sounds + "Cancel - No Changes" + + if(choice == "Cancel") + return 1 + + selected.vore_sound = vore_sounds[choice] + + if(href_list["b_soundtest"]) + user << selected.vore_sound + + if(href_list["b_del"]) + if(selected.internal_contents.len) + usr << "Can't delete bellies with contents!" + else if(selected.immutable) + usr << "This belly is marked as undeletable." + else if(user.vore_organs.len == 1) + usr << "You must have at least one belly." + 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] + + if(href_list["saveprefs"]) + if(user.save_vore_prefs()) + user << "Belly Preferences saved!" + + else + user << "ERROR: Belly Preferences were not saved!" + log_admin("Could not save vore prefs on USER: [user].") + + + 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") + switch(choice) + if("Cancel") + return 1 + if("Allow Digestion") + user.digestable = 1 + if("Prevent Digestion") + user.digestable = 0 + + if(user.client.prefs_vr) + user.client.prefs_vr.digestable = user.digestable + + if(href_list["toggledvor"]) + var/choice = alert(user, "This button is for those who don't like vore at all. Devouring you is currently: [user.devourable ? "Allowed" : "Prevented"]", "", "Allow Devourment", "Cancel", "Prevent Devourment") + switch(choice) + if("Cancel") + return 1 + if("Allow Devourment") + user.devourable = 1 + if("Prevent Devourment") + user.devourable = 0 + + if(user.client.prefs_vr) + user.client.prefs_vr.devourable = user.devourable + + //Refresh when interacted with, returning 1 makes vore_look.Topic update + return 1 diff --git a/code/modules/vore/hook-defs_vr.dm b/code/modules/vore/hook-defs_vr.dm new file mode 100644 index 0000000000..629b1ba8f3 --- /dev/null +++ b/code/modules/vore/hook-defs_vr.dm @@ -0,0 +1,37 @@ +//The base hooks themselves + +//New() hooks +/hook/client_new + +/hook/mob_new + +/hook/living_new + +/hook/carbon_new + +/hook/human_new + +/hook/simple_animal_new + +//Hooks for interactions +/hook/living_attackby + +// +//Hook helpers to expand hooks to others +// +/hook/mob_new/proc/chain_hooks(mob/M) + var/result = 1 + if(isliving(M)) + if(!hook_vr("living_new",args)) + result = 0 + + if(iscarbon(M)) + if(!hook_vr("carbon_new",args)) + result = 0 + + if(ishuman(M)) + if(!hook_vr("human_new",args)) + result = 0 + + //Return 1 to superhook + return result \ No newline at end of file diff --git a/code/modules/vore/resizing/grav_pull_vr.dm b/code/modules/vore/resizing/grav_pull_vr.dm new file mode 100644 index 0000000000..921d1ab2b9 --- /dev/null +++ b/code/modules/vore/resizing/grav_pull_vr.dm @@ -0,0 +1,63 @@ +// +// Gravity Pull effect which draws in movable objects to its center. +// In this case, "number" refers to the range. directions is ignored. +// +/datum/effect/effect/system/grav_pull + var/pull_radius = 3 + var/pull_anchored = 0 + var/break_windows = 0 + +/datum/effect/effect/system/grav_pull/set_up(range, num, loca) + pull_radius = range + number = num + if(istype(loca, /turf/)) + location = loca + else + location = get_turf(loca) + +/datum/effect/effect/system/grav_pull/start() + spawn(0) + if(holder) + src.location = get_turf(holder) + for(var/i=0, i < number, i++) + do_pull() + sleep(25) + +/datum/effect/effect/system/grav_pull/proc/do_pull() + //following is adapted from supermatter and singulo code + if(defer_powernet_rebuild != 2) + defer_powernet_rebuild = 1 + + // Let's just make this one loop. + for(var/atom/X in orange(pull_radius, location)) + // Movable atoms only + if(istype(X, /atom/movable)) + if(istype(X, /obj/effect/overlay)) continue + if(X && !istype(X, /mob/living/carbon/human)) + if(break_windows && istype(X, /obj/structure/window)) //shatter windows + var/obj/structure/window/W = X + W.ex_act(2.0) + + if(istype(X, /obj)) + var/obj/O = X + if(O.anchored) + if (!pull_anchored) continue // Don't pull anchored stuff unless configured + step_towards(X, location) // step just once if anchored + continue + + step_towards(X, location) // Step twice + step_towards(X, location) + + else if(istype(X,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = X + if(istype(H.shoes,/obj/item/clothing/shoes/magboots)) + var/obj/item/clothing/shoes/magboots/M = H.shoes + if(M.magpulse) + step_towards(H, location) //step just once with magboots + continue + step_towards(H, location) //step twice + step_towards(H, location) + + if(defer_powernet_rebuild != 2) + defer_powernet_rebuild = 0 + return diff --git a/code/modules/vore/resizing/holder_micro_vr.dm b/code/modules/vore/resizing/holder_micro_vr.dm new file mode 100644 index 0000000000..5cdecd9039 --- /dev/null +++ b/code/modules/vore/resizing/holder_micro_vr.dm @@ -0,0 +1,33 @@ +// Micro Holders - Extends /obj/item/weapon/holder + +/obj/item/weapon/holder/micro + name = "micro" + desc = "Another crewmember, small enough to fit in your hand." + icon_state = "micro" + slot_flags = SLOT_FEET | SLOT_HEAD | SLOT_ID + w_class = 2 + item_icons = null // Override value from parent. We don't have magic sprites. + pixel_y = 0 // Override value from parent. + +/obj/item/weapon/holder/micro/examine(var/mob/user) + for(var/mob/living/M in contents) + M.examine(user) + +/obj/item/weapon/holder/MouseDrop(mob/M as mob) + ..() + if(M != usr) return + if(usr == src) return + if(!Adjacent(usr)) return + if(istype(M,/mob/living/silicon/ai)) return + for(var/mob/living/carbon/human/O in contents) + O.show_inv(usr) + +/obj/item/weapon/holder/micro/attack_self(var/mob/living/user) + for(var/mob/living/carbon/human/M in contents) + M.help_shake_act(user) + +/obj/item/weapon/holder/micro/update_state() + // If any items have been dropped by contained mob, drop them to floor. + for(var/obj/O in contents) + O.forceMove(get_turf(src)) + ..() diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm new file mode 100644 index 0000000000..06b3b1b2e8 --- /dev/null +++ b/code/modules/vore/resizing/resize_vr.dm @@ -0,0 +1,208 @@ + +//these aren't defines so they can stay in this file +var/const/SIZESCALE_HUGE = 2 +var/const/SIZESCALE_BIG = 1.5 +var/const/SIZESCALE_NORMAL = 1 +var/const/SIZESCALE_SMALL = 0.85 +var/const/SIZESCALE_TINY = 0.60 + +//average +var/const/SIZESCALE_A_HUGEBIG = (SIZESCALE_HUGE + SIZESCALE_BIG) / 2 +var/const/SIZESCALE_A_BIGNORMAL = (SIZESCALE_BIG + SIZESCALE_NORMAL) / 2 +var/const/SIZESCALE_A_NORMALSMALL = (SIZESCALE_NORMAL + SIZESCALE_SMALL) / 2 +var/const/SIZESCALE_A_SMALLTINY = (SIZESCALE_SMALL + SIZESCALE_TINY) / 2 + +// Adding needed defines to /mob/living +// Note: Polaris had this on /mob/living/carbon/human We need it higher up for animals and stuff. +/mob/living + var/size_multiplier = 1 //multiplier for the mob's icon size + +// Define holder_type on types we want to be scoop-able +//mob/living/carbon/human +// holder_type = /obj/item/weapon/holder/micro + +/** + * Scale up the size of a mob's icon by the size_multiplier. + * NOTE: mob/living/carbon/human/update_transform() has a more complicated system and + * is already applying this transform. BUT, it does not call ..() + * as long as that is true, we should be fine. If that changes we need to + * re-evaluate. + */ +/mob/living/update_transform() + ASSERT(!iscarbon(src)) + var/matrix/M = matrix() + M.Scale(size_multiplier) + M.Translate(0, 16*(size_multiplier-1)) + src.transform = M + +/** + * Get the effective size of a mob. + * Currently this is based only on size_multiplier for micro/macro stuff, + * but in the future we may also incorporate the "mob_size", so that + * a macro mouse is still only effectively "normal" or a micro dragon is still large etc. + */ +/mob/living/proc/get_effective_size() + return src.size_multiplier + +/** + * Resizes the mob immediately to the desired mod, animating it growing/shrinking. + * It can be used by anything that calls it. + */ +/mob/living/proc/sizescale(var/new_size) + var/matrix/sizescale = matrix() // Defines the matrix to change the player's size + sizescale.Scale(new_size) //Change the size of the matrix + + if(new_size >= SIZESCALE_NORMAL) + sizescale.Translate(0, -1 * (1 - new_size) * 16) //Move the player up in the tile so their feet align with the bottom + + animate(src, transform = sizescale, time = 5) //Animate the player resizing + size_multiplier = new_size //Change size_multiplier so that other items can interact with them + +/* + * Verb proc for a command that lets players change their size OOCly. + * Ace was here! Redid this a little so we'd use math for shrinking characters. This is the old code. + +/mob/living/proc/set_size() + set name = "Set Character Size" + set category = "Vore" + var/nagmessage = "DO NOT ABUSE THESE COMMANDS. They are not here for you to play with. \ + We were originally going to remove them but kept them for popular demand. \ + Do not abuse their existence outside of ERP scenes where they apply, \ + or reverting OOCly unwanted changes like someone lolshooting the crew with a shrink ray. -Ace" + + var/size_name = input(nagmessage, "Pick a Size") in player_sizes_list + if (size_name && player_sizes_list[size_name]) + src.sizescale(player_sizes_list[size_name]) + message_admins("[key_name(src)] used the sizescale command in-game to be [size_name]. \ + ([src ? "JMP" : "null"])") + +/** Add the set_size() proc to usable verbs. */ +/hook/living_new/proc/sizescale_setup(mob/living/M) + M.verbs += /mob/living/proc/set_size + return 1 + + + * Attempt to scoop up this mob up into M's hands, if the size difference is large enough. + * @return false if normal code should continue, 1 to prevent normal code. + +/mob/living/proc/attempt_to_scoop(var/mob/living/carbon/human/M) + if(!istype(M)) + return 0; + if(M.buckled) + usr << "You have to unbuckle \the [M] before you pick them up." + return 0 + if(M.get_effective_size() - src.get_effective_size() >= 0.75) + var/obj/item/weapon/holder/m_holder = get_scooped(M) + if (m_holder) + return 1 + else + return 0; // Unable to scoop, let other code run +*/ +/* + * Handle bumping into someone with helping intent. + * Called from /mob/living/Bump() in the 'brohugs all around' section. + * @return false if normal code should continue, 1 to prevent normal code. + * // TODO - can the now_pushing = 0 be moved up? What does it do anyway? + */ +/mob/living/proc/handle_micro_bump_helping(var/mob/living/tmob) + if(src.get_effective_size() <= SIZESCALE_A_SMALLTINY && tmob.get_effective_size() <= SIZESCALE_A_SMALLTINY) + // Both small! Go ahead and + now_pushing = 0 + src.forceMove(tmob.loc) + return 1 + if(abs(src.get_effective_size() - tmob.get_effective_size()) >= 0.20) + now_pushing = 0 + src.forceMove(tmob.loc) + + if(src.get_effective_size() > tmob.get_effective_size()) +/* var/mob/living/carbon/human/tmob = src + if(istype(tmob) && istype(tmob.tail_style, /datum/sprite_accessory/tail/taur/naga)) + src << "You carefully slither around [tmob]." + M << "[src]'s huge tail slithers past beside you!" + else +*/ + src.forceMove(tmob.loc) + src << "You carefully step over [tmob]." + tmob << "[src] steps over you carefully!" + if(tmob.get_effective_size() > src.get_effective_size()) +/* var/mob/living/carbon/human/M = M + if(istype(M) && istype(M.tail_style, /datum/sprite_accessory/tail/taur/naga)) + src << "You jump over [M]'s thick tail." + M << "[src] bounds over your tail." + else +*/ + src.forceMove(tmob.loc) + src << "You run between [tmob]'s legs." + tmob << "[src] runs between your legs." + return 1 + +/** + * Handle bumping into someone without mutual help intent. + * Called from /mob/living/Bump() + * NW was here, adding even more options for stomping! + * + * @return false if normal code should continue, 1 to prevent normal code. + */ +/mob/living/proc/handle_micro_bump_other(var/mob/living/tmob) + ASSERT(isliving(tmob)) // Baby don't hurt me + + if(src.a_intent == "disarm" && src.canmove && !src.buckled) + // If bigger than them by at least 0.75, move onto them and print message. + if((src.get_effective_size() - tmob.get_effective_size()) >= 0.20) + now_pushing = 0 + src.forceMove(tmob.loc) + tmob.Stun(4) +/* + var/mob/living/carbon/human/H = src + if(istype(H) && istype(H.tail_style, /datum/sprite_accessory/tail/taur/naga)) + src << "You carefully squish [tmob] under your tail!" + tmob << "[src] pins you under their tail!" + else +*/ + src << "You pin [tmob] beneath your foot!" + tmob << "[src] pins you beneath their foot!" + return 1 + + if(src.a_intent == "harm" && src.canmove && !src.buckled) + if((src.get_effective_size() - tmob.get_effective_size()) >= 0.20) + now_pushing = 0 + src.forceMove(tmob.loc) + tmob.adjustStaminaLoss(35) + tmob.adjustBruteLoss(5) +/* var/mob/living/carbon/human/M = src + if(istype(M) && istype(M.tail_style, /datum/sprite_accessory/tail/taur/naga)) + src << "You steamroller over [tmob] with your heavy tail!" + tmob << "[src] ploughs you down mercilessly with their heavy tail!" + else +*/ + src << "You bring your foot down heavily upon [tmob]!" + tmob << "[src] steps carelessly on your body!" + return 1 + + // until I figure out grabbing micros with the godawful pull code... + if(src.a_intent == "grab" && src.canmove && !src.buckled) + if((src.get_effective_size() - tmob.get_effective_size()) >= 0.20) + now_pushing = 0 + tmob.adjustStaminaLoss(15) + src.forceMove(tmob.loc) + src << "You press [tmob] beneath your foot!" + tmob << "[src] presses you beneath their foot!" +/* + var/mob/living/carbon/human/M = src + if(istype(M) && !M.shoes) + // User is a human (capable of scooping) and not wearing shoes! Scoop into foot slot! + equip_to_slot_if_possible(tmob.get_scooped(M), slot_shoes, 0, 1) + if(istype(M.tail_style, /datum/sprite_accessory/tail/taur/naga)) + src << "You wrap up [tmob] with your powerful tail!" + tmob << "[src] binds you with their powerful tail!" + else + src << "You clench your toes around [tmob]'s body!" + tmob << "[src] grabs your body with their toes!" + else if(istype(M) && istype(M.tail_style, /datum/sprite_accessory/tail/taur/naga)) + src << "You carefully squish [tmob] under your tail!" + tmob << "[src] pins you under their tail!" + else + src << "You pin [tmob] beneath your foot!" + tmob << "[src] pins you beneath their foot!" + return 1 +*/ \ No newline at end of file diff --git a/code/modules/vore/resizing/sizechemicals.dm b/code/modules/vore/resizing/sizechemicals.dm new file mode 100644 index 0000000000..78b4bd71ca --- /dev/null +++ b/code/modules/vore/resizing/sizechemicals.dm @@ -0,0 +1,115 @@ + +//////////////////////////// +/// shrinking serum /// +//////////////////////////// + +/datum/reagent/medicine/macrocillin + name = "Macrocillin" + id = "macrocillin" + description = "Glowing yellow liquid." + reagent_state = LIQUID + color = "#FFFF00" // rgb: 255, 255, 0 + overdose_threshold = 20 + +/datum/reagent/medicine/macrocillin/on_mob_life(mob/living/M, method=INGEST) + for(var/size in list(SIZESCALE_SMALL, SIZESCALE_NORMAL, SIZESCALE_BIG, SIZESCALE_HUGE)) + if(M.size_multiplier < size) + M.sizescale(size) + M << "You grow!" + break + if(M.reagents.has_reagent("macrocillin")) + M.reagents.remove_reagent("macrocillin", 20) + ..() + +/datum/reagent/medicine/microcillin + name = "Microcillin" + id = "microcillin" + description = "Murky purple liquid." + reagent_state = LIQUID + color = "#800080" + overdose_threshold = 20 + +/datum/reagent/microcillin/on_mob_life(mob/living/M, method=INGEST) + for(var/size in list(SIZESCALE_BIG, SIZESCALE_NORMAL, SIZESCALE_SMALL, SIZESCALE_TINY)) + if(M.size_multiplier > size) + M.sizescale(size) + M << "You shrink!" + break; + if(M.reagents.has_reagent("microcillin")) + M.reagents.remove_reagent("microcillin", 20) + + ..() + +/datum/reagent/medicine/normalcillin + name = "Normalcillin" + id = "normalcillin" + description = "Translucent cyan liquid." + reagent_state = LIQUID + color = "#00FFFF" + overdose_threshold = 20 + +/datum/reagent/medicine/normalcillin/on_mob_life(mob/living/M, method=INGEST) + if(M.size_multiplier > SIZESCALE_BIG) + M.sizescale(SIZESCALE_BIG) + M << "You shrink!" + else if(M.size_multiplier > SIZESCALE_NORMAL) + M.sizescale(SIZESCALE_NORMAL) + M << "You shrink!" + else if(M.size_multiplier < SIZESCALE_NORMAL) + M.sizescale(SIZESCALE_NORMAL) + M << "You grow!" + else if(M.size_multiplier < SIZESCALE_SMALL) + M.sizescale(SIZESCALE_SMALL) + M << "You grow!" + + if(M.reagents.has_reagent("normalcillin")) + M.reagents.remove_reagent("normalcillin", 20) + ..() + + +/datum/reagent/medicine/sizeoxadone + name = "Sizeoxadone" + id = "sizeoxadone" + description = "A volatile liquid used as a precursor to size-altering chemicals. Causes dizziness if taken unprocessed." + reagent_state = LIQUID + color = "#1E90FF" + overdose_threshold = 30 + metabolization_rate = 0.8 * REAGENTS_METABOLISM + +/datum/reagent/sizeoxadone/on_mob_life(var/mob/living/carbon/M, var/removed) + if(M.hallucination < volume && prob(20)) + M.hallucination += 5 + if(!M.confused) M.confused = 1 + M.confused = max(M.confused, 20) + return + +/datum/reagent/medicine/sizeoxadone/overdose_process(mob/living/M) + M.adjustBrainLoss(1) + M.adjustToxLoss(1) + ..() + . = 1 + +////////////////////////// Anti-Noms Drugs ////////////////////////// + +/datum/reagent/medicine/ickypak + name = "Ickypak" + id = "ickypak" + description = "A foul-smelling green liquid, for inducing muscle contractions to expel accidentally ingested things." + reagent_state = LIQUID + color = "#0E900E" + metabolization_rate = 0.25 * REAGENTS_METABOLISM + +/datum/reagent/medicine/ickypak/on_mob_life(var/mob/living/M, method=INGEST) + ..() + if(M.hallucination < volume && prob(20)) + M.hallucination += 5 + M.adjustToxLoss(-5) + + for(var/I in M.vore_organs) + var/datum/belly/B = M.vore_organs[I] + for(var/atom/movable/A in B.internal_contents) + if(prob(55)) + playsound(M, 'sound/effects/splat.ogg', 50, 1) + B.release_specific_contents(A) + ..() + . = 1 \ No newline at end of file diff --git a/code/modules/vore/resizing/sizegun_vr.dm b/code/modules/vore/resizing/sizegun_vr.dm new file mode 100644 index 0000000000..ace5793336 --- /dev/null +++ b/code/modules/vore/resizing/sizegun_vr.dm @@ -0,0 +1,172 @@ +// +// Size Gun +// +/* +/obj/item/weapon/gun/energy/sizegun + name = "shrink ray" + desc = "A highly advanced ray gun with two settings: Shrink and Grow. Warning: Do not insert into mouth." + icon = 'icons/obj/gun_vr.dmi' + icon_state = "sizegun-shrink100" // Someone can probably do better. -Ace + item_state = null //so the human update icon uses the icon_state instead + fire_sound = 'sound/weapons/wave.ogg' + charge_cost = 100 + projectile_type = /obj/item/projectile/beam/shrinklaser + origin_tech = "redspace=1;bluespace=4" + modifystate = "sizegun-shrink" + selfcharge = 1 + firemodes = list( + list(mode_name = "grow", + projectile_type = /obj/item/projectile/beam/growlaser, + modifystate = "sizegun-grow", + fire_sound = 'sound/weapons/pulse3.ogg' + ), + list(mode_name = "shrink", + projectile_type = /obj/item/projectile/beam/shrinklaser, + modifystate = "sizegun-shrink", + fire_sound = 'sound/weapons/wave.ogg' + )) + +// +// Beams for size gun +// +// tracers TBD + +/obj/item/projectile/beam/shrinklaser + name = "shrink beam" + icon_state = "xray" + nodamage = 1 + damage = 0 + check_armour = "laser" + + muzzle_type = /obj/effect/projectile/xray/muzzle + tracer_type = /obj/effect/projectile/xray/tracer + impact_type = /obj/effect/projectile/xray/impact + +/obj/item/projectile/beam/shrinklaser/on_hit(var/atom/target, var/blocked = 0) + if(istype(target, /mob/living)) + var/mob/living/M = target + switch(M.size_multiplier) + if(SIZESCALE_HUGE to INFINITY) + M.sizescale(SIZESCALE_BIG) + if(SIZESCALE_BIG to SIZESCALE_HUGE) + M.sizescale(SIZESCALE_NORMAL) + if(SIZESCALE_NORMAL to SIZESCALE_BIG) + M.sizescale(SIZESCALE_SMALL) + if((0 - INFINITY) to SIZESCALE_NORMAL) + M.sizescale(SIZESCALE_TINY) + M.update_transform() + return 1 + +/obj/item/projectile/beam/growlaser + name = "growth beam" + icon_state = "bluelaser" + nodamage = 1 + damage = 0 + check_armour = "laser" + + muzzle_type = /obj/effect/projectile/laser_blue/muzzle + tracer_type = /obj/effect/projectile/laser_blue/tracer + impact_type = /obj/effect/projectile/laser_blue/impact + +/obj/item/projectile/beam/growlaser/on_hit(var/atom/target, var/blocked = 0) + if(istype(target, /mob/living)) + var/mob/living/M = target + switch(M.size_multiplier) + if(SIZESCALE_BIG to SIZESCALE_HUGE) + M.sizescale(SIZESCALE_HUGE) + if(SIZESCALE_NORMAL to SIZESCALE_BIG) + M.sizescale(SIZESCALE_BIG) + if(SIZESCALE_SMALL to SIZESCALE_NORMAL) + M.sizescale(SIZESCALE_NORMAL) + if((0 - INFINITY) to SIZESCALE_TINY) + M.sizescale(SIZESCALE_SMALL) + M.update_transform() + return 1 +*/ + +datum/design/sizeray + name = "Size Ray" + desc = "Abuse bluespace tech to alter living matter scale." + id = "sizeray" + req_tech = list("combat" = 5, "materials" = 4, "engineering" = 5, "bluespace" = 4) + build_type = PROTOLATHE + materials = list(MAT_METAL = 1000, MAT_GLASS = 1000, MAT_DIAMOND = 2500, MAT_URANIUM = 2500, MAT_TITANIUM = 1000) + build_path = /obj/item/weapon/gun/energy/laser/sizeray + category = list("Weapons") + +/obj/item/projectile/sizeray + name = "sizeray beam" + icon_state = "omnilaser" + hitsound = null + damage = 0 + damage_type = STAMINA + flag = "laser" + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + +/obj/item/projectile/sizeray/shrinkray + icon_state="bluelaser" + +/obj/item/projectile/sizeray/growthray + icon_state="laser" + +/obj/item/projectile/sizeray/shrinkray/on_hit(var/atom/target, var/blocked = 0) + if(istype(target, /mob/living)) + var/mob/living/M = target + switch(M.size_multiplier) + if(SIZESCALE_HUGE to INFINITY) + M.sizescale(SIZESCALE_BIG) + if(SIZESCALE_BIG to SIZESCALE_HUGE) + M.sizescale(SIZESCALE_NORMAL) + if(SIZESCALE_NORMAL to SIZESCALE_BIG) + M.sizescale(SIZESCALE_SMALL) + if((0 - INFINITY) to SIZESCALE_NORMAL) + M.sizescale(SIZESCALE_TINY) + M.update_transform() + return 1 + +/obj/item/projectile/sizeray/growthray/on_hit(var/atom/target, var/blocked = 0) + if(istype(target, /mob/living)) + var/mob/living/M = target + switch(M.size_multiplier) + if(SIZESCALE_BIG to SIZESCALE_HUGE) + M.sizescale(SIZESCALE_HUGE) + if(SIZESCALE_NORMAL to SIZESCALE_BIG) + M.sizescale(SIZESCALE_BIG) + if(SIZESCALE_SMALL to SIZESCALE_NORMAL) + M.sizescale(SIZESCALE_NORMAL) + if((0 - INFINITY) to SIZESCALE_TINY) + M.sizescale(SIZESCALE_SMALL) + M.update_transform() + return 1 + +/obj/item/ammo_casing/energy/laser/growthray + projectile_type = /obj/item/projectile/sizeray/growthray + select_name = "Growth" + +/obj/item/ammo_casing/energy/laser/shrinkray + projectile_type = /obj/item/projectile/sizeray/shrinkray + select_name = "Shrink" + + +//Gun here +/obj/item/weapon/gun/energy/laser/sizeray + name = "size ray" + icon_state = "bluetag" + desc = "Size manipulator using bluespace breakthroughs." + item_state = null //so the human update icon uses the icon_state instead. + ammo_type = list(/obj/item/ammo_casing/energy/laser/shrinkray, /obj/item/ammo_casing/energy/laser/growthray) + origin_tech = "combat=1;magnets=2" + selfcharge = 1 + charge_delay = 5 + ammo_x_offset = 2 + clumsy_check = 1 + + attackby(obj/item/W, mob/user) + if(W==src) + if(icon_state=="bluetag") + icon_state="redtag" + ammo_type = list(/obj/item/ammo_casing/energy/laser/growthray) + else + icon_state="bluetag" + ammo_type = list(/obj/item/ammo_casing/energy/laser/shrinkray) + return ..() \ No newline at end of file diff --git a/code/modules/vore/trycatch_vr.dm b/code/modules/vore/trycatch_vr.dm new file mode 100644 index 0000000000..1ae5c3bc0c --- /dev/null +++ b/code/modules/vore/trycatch_vr.dm @@ -0,0 +1,62 @@ +/* +This file is for jamming single-line procs into Polaris procs. +It will prevent runtimes and allow their code to run if VOREStation's fails. +It will also log when we mess up our code rather than making it vague. + +Call it at the top of a stock proc with... + +if(attempt_vr(object,proc to call,args)) return + +...if you are replacing an entire proc. + +The proc you're attemping should return nonzero values on success. +*/ + +/proc/attempt_vr(callon, procname, list/args=null) + try + if(!callon || !procname) + CRASH("attempt_vr: Invalid obj/proc: [callon]/[procname]") + return 0 + + var/result = call(callon,procname)(arglist(args)) + + return result + + catch(var/exception/e) + CRASH("attempt_vr runtimed when calling [procname] on [callon].") + CRASH("attempt_vr catch: [e] on [e.file]:[e.line]") + return 0 + +/* +This is the _vr version of calling hooks. +It's meant to have different messages, and also the try/catch block. +For when you want hooks and want to know when you ruin everything, +vs when Polaris ruins everything. + +Call it at the top of a stock proc with... + +if(hook_vr(proc,args)) return + +...if you are replacing an entire proc. + +The hooks you're calling should return nonzero values on success. +*/ +/proc/hook_vr(hook, list/args=null) + try + var/hook_path = text2path("/hook/[hook]") + if(!hook_path) + CRASH("hook_vr: Invalid hook '/hook/[hook]' called.") + return 0 + + var/caller = new hook_path + var/status = 1 + for(var/P in typesof("[hook_path]/proc")) + if(!call(caller, P)(arglist(args))) + CRASH("hook_vr: Hook '[P]' failed or runtimed.") + status = 0 + + return status + + catch(var/exception/e) + CRASH("hook_vr itself failed or runtimed. Exception below.") + CRASH("hook_vr catch: [e] on [e.file]:[e.line]") \ No newline at end of file diff --git a/sound/vore/StomachTransfer.ogg b/sound/vore/StomachTransfer.ogg new file mode 100644 index 0000000000..c81da56f50 Binary files /dev/null and b/sound/vore/StomachTransfer.ogg differ diff --git a/sound/vore/death1.ogg b/sound/vore/death1.ogg new file mode 100644 index 0000000000..34f4e189f5 Binary files /dev/null and b/sound/vore/death1.ogg differ diff --git a/sound/vore/death10.ogg b/sound/vore/death10.ogg new file mode 100644 index 0000000000..92ce0fd9bd Binary files /dev/null and b/sound/vore/death10.ogg differ diff --git a/sound/vore/death2.ogg b/sound/vore/death2.ogg new file mode 100644 index 0000000000..2476b0f08c Binary files /dev/null and b/sound/vore/death2.ogg differ diff --git a/sound/vore/death3.ogg b/sound/vore/death3.ogg new file mode 100644 index 0000000000..d8a666067d Binary files /dev/null and b/sound/vore/death3.ogg differ diff --git a/sound/vore/death4.ogg b/sound/vore/death4.ogg new file mode 100644 index 0000000000..4fce54da79 Binary files /dev/null and b/sound/vore/death4.ogg differ diff --git a/sound/vore/death5.ogg b/sound/vore/death5.ogg new file mode 100644 index 0000000000..371cf17155 Binary files /dev/null and b/sound/vore/death5.ogg differ diff --git a/sound/vore/death6.ogg b/sound/vore/death6.ogg new file mode 100644 index 0000000000..78fc1b0637 Binary files /dev/null and b/sound/vore/death6.ogg differ diff --git a/sound/vore/death7.ogg b/sound/vore/death7.ogg new file mode 100644 index 0000000000..419a5bedd5 Binary files /dev/null and b/sound/vore/death7.ogg differ diff --git a/sound/vore/death8.ogg b/sound/vore/death8.ogg new file mode 100644 index 0000000000..b15ab6a7d2 Binary files /dev/null and b/sound/vore/death8.ogg differ diff --git a/sound/vore/death9.ogg b/sound/vore/death9.ogg new file mode 100644 index 0000000000..3709541d60 Binary files /dev/null and b/sound/vore/death9.ogg differ diff --git a/sound/vore/digest1.ogg b/sound/vore/digest1.ogg new file mode 100644 index 0000000000..058ca78f7e Binary files /dev/null and b/sound/vore/digest1.ogg differ diff --git a/sound/vore/digest10.ogg b/sound/vore/digest10.ogg new file mode 100644 index 0000000000..5db775ae29 Binary files /dev/null and b/sound/vore/digest10.ogg differ diff --git a/sound/vore/digest11.ogg b/sound/vore/digest11.ogg new file mode 100644 index 0000000000..d81ac72dea Binary files /dev/null and b/sound/vore/digest11.ogg differ diff --git a/sound/vore/digest12.ogg b/sound/vore/digest12.ogg new file mode 100644 index 0000000000..6b44127cd8 Binary files /dev/null and b/sound/vore/digest12.ogg differ diff --git a/sound/vore/digest2.ogg b/sound/vore/digest2.ogg new file mode 100644 index 0000000000..0e8bef1db8 Binary files /dev/null and b/sound/vore/digest2.ogg differ diff --git a/sound/vore/digest3.ogg b/sound/vore/digest3.ogg new file mode 100644 index 0000000000..2077f31399 Binary files /dev/null and b/sound/vore/digest3.ogg differ diff --git a/sound/vore/digest4.ogg b/sound/vore/digest4.ogg new file mode 100644 index 0000000000..27a786bc40 Binary files /dev/null and b/sound/vore/digest4.ogg differ diff --git a/sound/vore/digest5.ogg b/sound/vore/digest5.ogg new file mode 100644 index 0000000000..849c1335be Binary files /dev/null and b/sound/vore/digest5.ogg differ diff --git a/sound/vore/digest6.ogg b/sound/vore/digest6.ogg new file mode 100644 index 0000000000..5f0cc86eb1 Binary files /dev/null and b/sound/vore/digest6.ogg differ diff --git a/sound/vore/digest7.ogg b/sound/vore/digest7.ogg new file mode 100644 index 0000000000..f224cc3fa3 Binary files /dev/null and b/sound/vore/digest7.ogg differ diff --git a/sound/vore/digest8.ogg b/sound/vore/digest8.ogg new file mode 100644 index 0000000000..04b742bd49 Binary files /dev/null and b/sound/vore/digest8.ogg differ diff --git a/sound/vore/digest9.ogg b/sound/vore/digest9.ogg new file mode 100644 index 0000000000..866c95b874 Binary files /dev/null and b/sound/vore/digest9.ogg differ diff --git a/sound/vore/gulp.ogg b/sound/vore/gulp.ogg new file mode 100644 index 0000000000..b463e7fb18 Binary files /dev/null and b/sound/vore/gulp.ogg differ diff --git a/sound/vore/insert.ogg b/sound/vore/insert.ogg new file mode 100644 index 0000000000..0574733d4d Binary files /dev/null and b/sound/vore/insert.ogg differ diff --git a/sound/vore/insertion1.ogg b/sound/vore/insertion1.ogg new file mode 100644 index 0000000000..a026591f07 Binary files /dev/null and b/sound/vore/insertion1.ogg differ diff --git a/sound/vore/insertion2.ogg b/sound/vore/insertion2.ogg new file mode 100644 index 0000000000..b682e3aaf8 Binary files /dev/null and b/sound/vore/insertion2.ogg differ diff --git a/sound/vore/insertion3.ogg b/sound/vore/insertion3.ogg new file mode 100644 index 0000000000..7dabf3f077 Binary files /dev/null and b/sound/vore/insertion3.ogg differ diff --git a/sound/vore/schlorp.ogg b/sound/vore/schlorp.ogg new file mode 100644 index 0000000000..c9cd5a3413 Binary files /dev/null and b/sound/vore/schlorp.ogg differ diff --git a/sound/vore/squish1.ogg b/sound/vore/squish1.ogg new file mode 100644 index 0000000000..8a87758048 Binary files /dev/null and b/sound/vore/squish1.ogg differ diff --git a/sound/vore/squish2.ogg b/sound/vore/squish2.ogg new file mode 100644 index 0000000000..c8f96fc6e2 Binary files /dev/null and b/sound/vore/squish2.ogg differ diff --git a/sound/vore/squish3.ogg b/sound/vore/squish3.ogg new file mode 100644 index 0000000000..60e364c80a Binary files /dev/null and b/sound/vore/squish3.ogg differ diff --git a/sound/vore/squish4.ogg b/sound/vore/squish4.ogg new file mode 100644 index 0000000000..e9bd8e7bf5 Binary files /dev/null and b/sound/vore/squish4.ogg differ diff --git a/sound/vore/stomach_loop.ogg b/sound/vore/stomach_loop.ogg new file mode 100644 index 0000000000..b9983b2ab6 Binary files /dev/null and b/sound/vore/stomach_loop.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 1c838f0565..914a289082 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -13,9 +13,7 @@ // END_PREFERENCES // BEGIN_INCLUDE -#include "_maps\basemap.dm" -#include "_maps\loadallmaps.dm" -#include "_maps\tgstation2.dm" +#include "_maps\runtimestation.dm" #include "code\_compile_options.dm" #include "code\world.dm" #include "code\__DATASTRUCTURES\globals.dm" @@ -67,6 +65,7 @@ #include "code\__DEFINES\tgui.dm" #include "code\__DEFINES\tick.dm" #include "code\__DEFINES\typeids.dm" +#include "code\__DEFINES\voreconstants.dm" #include "code\__DEFINES\vv.dm" #include "code\__DEFINES\wires.dm" #include "code\__HELPERS\_lists.dm" @@ -1148,6 +1147,7 @@ #include "code\modules\client\preferences.dm" #include "code\modules\client\preferences_savefile.dm" #include "code\modules\client\preferences_toggles.dm" +#include "code\modules\client\preferences_vr.dm" #include "code\modules\client\verbs\looc.dm" #include "code\modules\client\verbs\ooc.dm" #include "code\modules\client\verbs\ping.dm" @@ -1567,6 +1567,7 @@ #include "code\modules\mob\living\carbon\human\death.dm" #include "code\modules\mob\living\carbon\human\emote.dm" #include "code\modules\mob\living\carbon\human\examine.dm" +#include "code\modules\mob\living\carbon\human\examine_vr.dm" #include "code\modules\mob\living\carbon\human\human.dm" #include "code\modules\mob\living\carbon\human\human_defense.dm" #include "code\modules\mob\living\carbon\human\human_defines.dm" @@ -2130,6 +2131,16 @@ #include "code\modules\vehicles\secway.dm" #include "code\modules\vehicles\speedbike.dm" #include "code\modules\vehicles\vehicle.dm" +#include "code\modules\vore\hook-defs_vr.dm" +#include "code\modules\vore\trycatch_vr.dm" +#include "code\modules\vore\eating\belly_vr.dm" +#include "code\modules\vore\eating\bellymodes_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" +#include "code\modules\vore\eating\vorepanel_vr.dm" +#include "code\modules\vore\resizing\resize_vr.dm" +#include "code\modules\vore\resizing\sizegun_vr.dm" #include "code\modules\VR\vr_human.dm" #include "code\modules\VR\vr_sleeper.dm" #include "code\modules\zombie\items.dm"