diff --git a/aurorastation.dme b/aurorastation.dme index c88d1bdfec5..4fa9333c9c7 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -1773,7 +1773,6 @@ #include "code\modules\mob\living\carbon\carbon.dm" #include "code\modules\mob\living\carbon\carbon_defense.dm" #include "code\modules\mob\living\carbon\carbon_defines.dm" -#include "code\modules\mob\living\carbon\carbon_powers.dm" #include "code\modules\mob\living\carbon\diona_base.dm" #include "code\modules\mob\living\carbon\give.dm" #include "code\modules\mob\living\carbon\resist.dm" @@ -1805,6 +1804,7 @@ #include "code\modules\mob\living\carbon\brain\say.dm" #include "code\modules\mob\living\carbon\human\appearance.dm" #include "code\modules\mob\living\carbon\human\death.dm" +#include "code\modules\mob\living\carbon\human\devour.dm" #include "code\modules\mob\living\carbon\human\diona_gestalt.dm" #include "code\modules\mob\living\carbon\human\emote.dm" #include "code\modules\mob\living\carbon\human\examine.dm" diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm index 02fa9e20e0d..36a0f6ccc70 100644 --- a/code/__defines/mobs.dm +++ b/code/__defines/mobs.dm @@ -209,11 +209,13 @@ #define GLUT_TINY 1 // Eat anything tiny and smaller #define GLUT_SMALLER 2 // Eat anything smaller than we are #define GLUT_ANYTHING 4 // Eat anything, ever +#define GLUT_MESSY 8 // Only eat mobs, and eat them in chunks. + +#define GLUT_ITEM_TINY 16 // Eat items with a w_class of small or smaller +#define GLUT_ITEM_NORMAL 32 // Eat items with a w_class of normal or smaller +#define GLUT_ITEM_ANYTHING 64 // Eat any item +#define GLUT_PROJECTILE_VOMIT 128 // When vomitting, does it fly out? -#define GLUT_ITEM_TINY 8 // Eat items with a w_class of small or smaller -#define GLUT_ITEM_NORMAL 16 // Eat items with a w_class of normal or smaller -#define GLUT_ITEM_ANYTHING 32 // Eat any item -#define GLUT_PROJECTILE_VOMIT 64 // When vomitting, does it fly out? // Devour speeds, returned by can_devour() #define DEVOUR_SLOW 1 diff --git a/code/modules/mob/living/carbon/carbon_powers.dm b/code/modules/mob/living/carbon/carbon_powers.dm deleted file mode 100644 index 6ec908e4606..00000000000 --- a/code/modules/mob/living/carbon/carbon_powers.dm +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Attempt to devour victim - * - * Returns TRUE on success, FALSE on failure - */ -/mob/living/carbon/proc/devour(atom/movable/victim) - var/can_eat = can_devour(victim) - if(!can_eat) - return FALSE - - var/eat_speed = 100 - if(can_eat == DEVOUR_FAST) - eat_speed = 30 - src.visible_message("\The [src] is attempting to devour \the [victim] whole!") - var/mob/target = victim - if(isobj(victim)) - target = src - if(!do_mob(src,target,eat_speed)) - return FALSE - src.visible_message("\The [src] devours \the [victim] whole!") - if(ismob(victim)) - admin_attack_log(src, victim, "Devoured.", "Was devoured by.", "devoured") - else - src.drop_from_inventory(victim) - move_to_stomach(victim) - - return TRUE - -/mob/living/carbon/proc/move_to_stomach(atom/movable/victim) - return diff --git a/code/modules/mob/living/carbon/human/devour.dm b/code/modules/mob/living/carbon/human/devour.dm new file mode 100644 index 00000000000..819bb74b740 --- /dev/null +++ b/code/modules/mob/living/carbon/human/devour.dm @@ -0,0 +1,189 @@ + #define PPM 9 //Protein per meat, used for calculating the quantity of protein in an animal + +/** + * Attempt to devour victim + * + * Returns TRUE on success, FALSE on failure + */ +/mob/living/carbon/human/proc/devour(atom/movable/victim) + var/can_eat = can_devour(victim) + if(!can_eat) + return FALSE + + var/eat_speed = 100 + if(can_eat == DEVOUR_FAST) + eat_speed = 30 + if((species?.gluttonous & GLUT_MESSY) && ismob(victim)) + eat_speed *= 3 //We want our bites to take a lot so as to not spam the chat. + do_gradual_devour(victim, eat_speed) + return + src.visible_message("\The [src] is attempting to devour \the [victim] whole!") + var/mob/target = victim + if(isobj(victim)) + target = src + if(!do_mob(src,target,eat_speed)) + return FALSE + src.visible_message("\The [src] devours \the [victim] whole!") + if(ismob(victim)) + admin_attack_log(src, victim, "Devoured.", "Was devoured by.", "devoured") + else + src.drop_from_inventory(victim) + move_to_stomach(victim) + + return TRUE + +/mob/living/carbon/human/proc/move_to_stomach(atom/movable/victim) + return + +// Snowflake procs for unathi. Because lore said so. + +/mob/living/carbon/human/proc/do_gradual_devour(var/mob/living/victim, var/eat_speed) + set waitfor = FALSE + + //This function will start consuming the victim by taking bites out of them. + //Victim or attacker moving will interrupt it + //A bite will be taken every 4 seconds + var/bite_delay = 4 + var/bite_size = mouth_size * 0.5 + var/num_bites_needed = (victim.mob_size * 2)/bite_size //Total bites needed to eat it from full health + var/PEPB = 1/num_bites_needed //Percentage eaten per bite + var/turf/ourloc = src.loc + var/turf/victimloc = victim.loc + var/messes = 0 //Number of bloodstains we've placed + var/datum/reagents/vessel = victim.get_vessel(1) + if(!victim.composition_reagent_quantity) + victim.calculate_composition() + var/dam_multiplier = 1 + + var/victim_maxhealth = victim.maxHealth //We cache this here in case we need to edit it. + + var/damage_dealt = victim_maxhealth * PEPB * dam_multiplier + + //Now, incase we're resuming an earlier feeding session on the same creature + //We calculate the actual bites needed to fully eat it based on how eaten it already is + if(victim.getBruteLoss()) + var/percentageDamaged = victim.getBruteLoss() / victim_maxhealth + var/percentageRemaining = 1 - percentageDamaged + num_bites_needed = percentageRemaining / PEPB + + var/time_needed_seconds = num_bites_needed*bite_delay//in seconds for now + var/time_needed_minutes + var/time_needed_string + if (time_needed_seconds > 60) + time_needed_minutes = round((time_needed_seconds/60)) + time_needed_seconds = time_needed_seconds % 60 + time_needed_string = "[time_needed_minutes] minutes and [time_needed_seconds] seconds" + else + time_needed_string = "[time_needed_seconds] seconds" + + src.visible_message("[src] starts to devour [victim]!", \ + "You start devouring [victim], this will take approximately [time_needed_string]. You and the victim must remain still to continue, \ + but you can interrupt feeding anytime and leave with what you've already eaten.") + + for (var/i = 0 to num_bites_needed) + if(do_mob(src, victim, bite_delay * 10, extra_checks = CALLBACK(src, .proc/devouring_equals, victim))) + face_atom(victim) + victim.apply_damage(damage_dealt, BRUTE) + var/obj/item/organ/internal/stomach/S = internal_organs_by_name[BP_STOMACH] + if(S) + S.ingested.add_reagent(victim.composition_reagent, (victim.composition_reagent_quantity * 0.5) * PEPB) + visible_message("[src] bites a chunk out of [victim]!","[bitemessage(victim)]") + if(messes < victim.mob_size - 1 && prob(50)) + handle_devour_mess(src, victim, vessel) + if(victim.getBruteLoss() >= victim_maxhealth) + visible_message("[src] finishes devouring [victim].","You finish devouring [victim].") + handle_devour_mess(src, victim, vessel, 1) + qdel(victim) + else + if(nutrition >= (max_nutrition - 60)) + to_chat(src, "Your stomach is full!") + if (victim && !QDELETED(victim) && victimloc != victim.loc) //This procs when the victim gets moved to nullspace. + to_chat(src, "[victim] moved away, you need to keep it still. Try grabbing, stunning or killing it first.") + else if (ourloc != src.loc) + to_chat(src, "You moved! Devouring cancelled.") + break + +/mob/living/carbon/human/proc/devouring_equals(var/mob/victim) + for(var/obj/item/grab/G in victim.grabbed_by) + if(G && G.state >= GRAB_NECK) + return TRUE + return FALSE + +//Helpers +/proc/bitemessage(var/mob/living/victim) + return pick( + "You take a bite out of \the [victim].", + "You rip a chunk off of \the [victim].", + "You consume a piece of \the [victim].", + "You feast upon your prey.", + "You chow down on \the [victim].", + "You gobble \the [victim]'s flesh.") + +/proc/handle_devour_mess(var/mob/user, var/mob/living/victim, var/datum/reagents/vessel, var/finish = 0) + //The maximum number of blood placements is equal to the mob size of the victim + //We will use one blood placement on each of the following, in this order + //Bloodying the victim's tile + //Bloodying the attacker, if possible + //Bloodying the attacker's tile + //After that, we will allocate the remaining blood placements to random tiles around the victim and attacker, until either all are used or victim is dead + var/datum/reagent/blood/B = vessel.get_master_reagent() + + if (!turf_hasblood(get_turf(victim))) + devour_add_blood(victim, get_turf(victim), vessel) + return 1 + + else if (istype(user, /mob/living/carbon/human) && !user.blood_DNA) + //if this blood isn't already in the list, add it + user.blood_DNA = list(B.data["blood_DNA"]) + user.blood_color = B.data["blood_color"] + user.update_inv_gloves() //handles bloody hands overlays and updating + user.verbs += /mob/living/carbon/human/proc/bloody_doodle + return 1 + + else if (!turf_hasblood(get_turf(user))) + devour_add_blood(victim, get_turf(user), vessel) + return 1 + + if (finish) + //A bigger victim makes more gibs + if (victim.mob_size >= 3) + new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim)) + if (victim.mob_size >= 5) + new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim)) + if (victim.mob_size >= 7) + new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim)) + if (victim.mob_size >= 9) + new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim)) + return 1 + return 0 + +/proc/devour_add_blood(var/mob/living/M, var/turf/location, var/datum/reagents/vessel) + for(var/datum/reagent/blood/source in vessel.reagent_list) + var/obj/effect/decal/cleanable/blood/B = new /obj/effect/decal/cleanable/blood(location) + + // Update appearance. + if(source.data["blood_colour"]) + B.basecolor = source.data["blood_colour"] + B.update_icon() + + // Update blood information. + if(source.data["blood_DNA"]) + B.blood_DNA = list() + if(source.data["blood_type"]) + B.blood_DNA[source.data["blood_DNA"]] = source.data["blood_type"] + else + B.blood_DNA[source.data["blood_DNA"]] = "O+" + + // Update virus information. + if(source.data["virus2"]) + B.virus2 = virus_copylist(source.data["virus2"]) + + B.fluorescent = 0 + B.invisibility = 0 + +/proc/turf_hasblood(var/turf/test) + for (var/obj/effect/decal/cleanable/blood/b in test) + return 1 + return 0 + +#undef PPM diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 6d352dd264f..68e097ceea5 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -134,6 +134,20 @@ to_chat(src, span("warning", "Your [stomach.name] is full!")) return FALSE + if(species?.gluttonous & GLUT_MESSY) + if(ismob(victim)) + var/mob/M = victim + if(ishuman(victim)) + to_chat(src, span("warning", "You can't devour humanoids!")) + return FALSE + for(var/obj/item/grab/G in M.grabbed_by) + if(G && G.state < GRAB_NECK) + if(!silent) + to_chat(src, span("warning", "You need a tighter hold on \the [M]!")) + return FALSE + else + return FALSE + . = stomach.get_devour_time(victim) || ..() /mob/living/carbon/human/get_ingested_reagents() diff --git a/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm b/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm index 77bab661890..01159cea24d 100644 --- a/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm +++ b/code/modules/mob/living/carbon/human/species/station/unathi/unathi.dm @@ -16,7 +16,8 @@ ) primitive_form = "Stok" darksight = 3 - gluttonous = GLUT_SMALLER|GLUT_ITEM_TINY + gluttonous = GLUT_MESSY + stomach_capacity = 7 slowdown = 0.5 brute_mod = 0.8 diff --git a/code/modules/organs/internal/stomach.dm b/code/modules/organs/internal/stomach.dm index 0dcfa6fa660..73962f35775 100644 --- a/code/modules/organs/internal/stomach.dm +++ b/code/modules/organs/internal/stomach.dm @@ -13,6 +13,9 @@ /obj/item/organ/internal/stomach/Destroy() QDEL_NULL(ingested) + for(var/mob/M in contents) + if(M.stat != DEAD) + M.forceMove(owner.loc) . = ..() /obj/item/organ/internal/stomach/Initialize() @@ -50,6 +53,8 @@ /obj/item/organ/internal/stomach/proc/is_full(var/atom/movable/food) var/total = Floor(ingested.total_volume / 10) + if(species.gluttonous & GLUT_MESSY) + return FALSE //Don't need to check if the stomach is full if we're not using the contents. for(var/a in contents + food) if(ismob(a)) var/mob/M = a @@ -68,7 +73,7 @@ var/mob/living/L = food if((species.gluttonous & GLUT_TINY) && (L.mob_size <= MOB_TINY) && !ishuman(food)) // Anything MOB_TINY or smaller return DEVOUR_SLOW - else if((species.gluttonous & GLUT_SMALLER) && owner.mob_size > L.mob_size) // Anything we're larger than + else if((species.gluttonous & (GLUT_SMALLER|GLUT_MESSY)) && owner.mob_size > L.mob_size) // Anything we're larger than return DEVOUR_SLOW else if(species.gluttonous & GLUT_ANYTHING) // Eat anything ever return DEVOUR_FAST diff --git a/html/changelogs/mattatlas-ihatelore.yml b/html/changelogs/mattatlas-ihatelore.yml new file mode 100644 index 00000000000..072e0a01349 --- /dev/null +++ b/html/changelogs/mattatlas-ihatelore.yml @@ -0,0 +1,43 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: MattAtlas + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscdel: "Unathi can no longer swallow mobs or items whole." + - rscadd: "Instead, they now have their old devour, still tied to a grab. You need a red grab now, however. It does not work on humans." + - tweak: "Unathi devour is now overall faster."