Files
Matt Atlas d5931c5ffe Adds an universal object health system. (#21266)
https://www.youtube.com/watch?v=ZpUYjpKg9KY

As per title. The goal is to add health to most objects in the game and
thus allow them to be dynamically destructible, while also avoiding
shitcode like each structure having its own snowflake health update
proc.

---------

Signed-off-by: Matt Atlas <mattiathebest2000@hotmail.it>
Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: SleepyGemmy <99297919+SleepyGemmy@users.noreply.github.com>
2026-04-16 23:22:08 +00:00

145 lines
5.5 KiB
Plaintext

#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(SPAN_DANGER("\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(SPAN_DANGER("\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
// 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
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(SPAN_DANGER("[src] starts to devour [victim]!"), \
SPAN_DANGER("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_REF(devouring_equals), victim)))
face_atom(victim)
victim.apply_damage(damage_dealt, DAMAGE_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(SPAN_DANGER("[src] bites a chunk out of [victim]!"),
SPAN_DANGER("[bitemessage(victim)]"))
if(messes < victim.mob_size - 1 && prob(50))
handle_devour_mess(src, victim)
if(victim.getBruteLoss() >= victim_maxhealth)
visible_message(SPAN_DANGER("[src] finishes devouring [victim]."),SPAN_WARNING("You finish devouring [victim]."))
handle_devour_mess(src, victim, TRUE)
qdel(victim)
else
if(nutrition >= (max_nutrition - 60))
to_chat(src, SPAN_DANGER("Your stomach is full!"))
if (victim && !QDELETED(victim) && victimloc != victim.loc) //This procs when the victim gets moved to nullspace.
to_chat(src, SPAN_DANGER("[victim] moved away, you need to keep it still. Try grabbing, stunning or killing it first."))
else if (ourloc != src.loc)
to_chat(src, SPAN_DANGER("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/finish = 0)
if(ishuman(user))
var/mob/living/carbon/human/H = user
H.add_blood(victim)
if (!turf_hasblood(get_turf(victim)))
blood_splatter(get_turf(victim), victim, TRUE)
if (finish)
//A bigger victim makes more gibs
if (victim.mob_size >= MOB_TINY)
new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
if (victim.mob_size >= MOB_SMALL)
new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
if (victim.mob_size >= MOB_MEDIUM)
new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
if (victim.mob_size >= MOB_LARGE)
new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
return TRUE
return FALSE
/proc/turf_hasblood(var/turf/test)
for (var/obj/effect/decal/cleanable/blood/b in test)
return TRUE
return FALSE
#undef PPM