mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2025-12-30 20:13:08 +00:00
Removes all \red's and \blues in favor of span classes. \red things that were bold were replaced with danger because it's warning but bold.
313 lines
13 KiB
Plaintext
313 lines
13 KiB
Plaintext
//This file contains variables and helper functions for mobs that can eat other mobs
|
|
|
|
//There are two ways to eat a mob:
|
|
//Swallowing whole can only be done if the mob is sufficiently small
|
|
//It will place the mob inside you, and slowly digest it,
|
|
//Digesting deals genetic damage to the victim,
|
|
//drains blood from it,
|
|
//and adds protein to your stomach, based on the quantitys.
|
|
//Mob will be deleted from your contents when fully digested.
|
|
//Mob is fully digested when it has taken genetic damage equal to its max health. This continues past death if necessary
|
|
|
|
//Devouring eats the mob piece by piece. Taking a bite periodically
|
|
//Each bite deals genetic damage, and drains blood.
|
|
//Adds protein to your stomach based on quantities.
|
|
//Mob is fully digested when it has taken genetic damage equal to its max health. This continues past death if necessary
|
|
//Devouring is interrupted if you or the mob move away from each other, or if the eater gets disabled.
|
|
|
|
#define PPM 9//Protein per meat, used for calculating the quantity of protein in an animal
|
|
|
|
|
|
/mob/living/var/swallowed_mob = 0
|
|
//This is set true if there are any mobs in this mob's contents. they will be slowly digested.
|
|
//just used as a boolean to minimise extra processing
|
|
|
|
/mob/living/var/mob/living/devouring = null
|
|
//If we are currently devouring a mob, a reference to it goes here.
|
|
|
|
/mob/living/proc/attempt_devour(var/mob/living/victim, var/eat_types, var/mouth_size = null)
|
|
|
|
|
|
//This function will attempt to eat the victim,
|
|
//either by swallowing them if they're small enough, or starting to devour them otherwise
|
|
//If a mouth_size is passed in, it will be used instead of this mob's size, for determining whether the victim is small enough to swallow
|
|
//This function is the main gateway to devouring, and will have all the safety checks
|
|
if (!victim)
|
|
return 0
|
|
|
|
face_atom(victim)
|
|
|
|
if (victim == src)
|
|
src << "<span class='warning'>You can't eat yourself!</span>"
|
|
return 0
|
|
|
|
if (devouring == victim)
|
|
src << span("notice","You stop eating [victim].")
|
|
devouring = null
|
|
return
|
|
|
|
if (ishuman(src))
|
|
var/mob/living/carbon/human/H = src
|
|
var/obj/item/blocked = H.check_mouth_coverage()
|
|
if(blocked)
|
|
user << "<span class='warning'>\The [blocked] is in the way!</span>"
|
|
return
|
|
|
|
//This check is exploit prevention.
|
|
//Nymphs have seperate mechanics for gaining biomass from other diona
|
|
//This check prevents the exploit of almost-devouring a nymph, and then absorbing it to gain double biomass
|
|
if (victim.is_diona() && src.is_diona())
|
|
src << "<span class='warning'>You can't eat other diona!</span>"
|
|
return 0
|
|
|
|
if (!src.Adjacent(victim))
|
|
src << "<span class='warning'>That creature is too far away, move closer!</span>"
|
|
return 0
|
|
|
|
if (!is_valid_for_devour(victim, eat_types))
|
|
src << "<span class='warning'>You can't eat that type of creature!</span>"
|
|
return 0
|
|
|
|
if (!victim.mob_size || !src.mob_size)
|
|
src << "<span class='danger'>Error, no mob size defined for [victim.type]! You have encountered a bug, report it on github </span>"
|
|
return 0
|
|
|
|
if (!mouth_size)
|
|
mouth_size = src.mob_size
|
|
|
|
if (victim.mob_size <= mouth_size)
|
|
swallow(victim, mouth_size)
|
|
else
|
|
devour_gradual(victim,mouth_size)
|
|
|
|
/mob/living/proc/swallow(var/mob/living/victim, var/mouth_size)
|
|
//This function will move the victim inside the eater's contents.. There they will be digested over time
|
|
|
|
var/swallow_time = max(3 + (victim.mob_size * victim.mob_size) - mouth_size, 3)
|
|
src.visible_message("[src] starts swallowing \the [victim]!","You start swallowing \the [victim], this will take approximately [swallow_time] seconds.")
|
|
var/turf/ourloc = src.loc
|
|
var/turf/victimloc = victim.loc
|
|
if (do_mob(src, victim, swallow_time*10))
|
|
victim.forceMove(src)
|
|
LAZYADD(stomach_contents, victim)
|
|
else if (victimloc != victim.loc)
|
|
src << "[victim] moved away, you need to keep it still. Try grabbing, stunning or killing it first."
|
|
else if (ourloc != src.loc)
|
|
src << "You moved! Can't eat if you move away from the victim"
|
|
else
|
|
src << "Swallowing failed!"//reason unknown, maybe the eater got stunned?
|
|
|
|
/mob/living/proc/devour_gradual(var/mob/living/victim, var/mouth_size)
|
|
//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
|
|
devouring = victim
|
|
var/bite_delay = 4
|
|
var/bite_size = mouth_size * 0.5
|
|
var/num_bites_needed = (victim.mob_size*victim.mob_size)/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/victim_maxhealth = victim.maxHealth//We cache this here incase we need to edit it, for example, for humans and anything else that doesn't die until negative health
|
|
|
|
//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.cloneloss)
|
|
var/percentageDamaged = victim.cloneloss / 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 class='danger'>[src] starts devouring [victim]</span>","<span class='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.</span>")
|
|
|
|
var/i = 0
|
|
for (i=0;i < num_bites_needed;i++)
|
|
if(do_mob(src, victim, bite_delay*10) && devouring == victim)
|
|
face_atom(victim)
|
|
victim.adjustCloneLoss(victim_maxhealth*PEPB)
|
|
victim.adjustHalLoss(victim_maxhealth*PEPB*5)//Being eaten hurts!
|
|
src.ingested.add_reagent(victim.composition_reagent, victim.composition_reagent_quantity*PEPB)
|
|
src.visible_message("<span class='danger'>[src] bites a chunk out of [victim]</span>","<span class='danger'>[bitemessage(victim)]</span>")
|
|
if (messes < victim.mob_size - 1 && prob(50))
|
|
handle_devour_mess(src, victim, vessel)
|
|
if (victim.cloneloss >= victim_maxhealth)
|
|
src.visible_message("[src] finishes devouring [victim]","You finish devouring [victim]")
|
|
handle_devour_mess(src, victim, vessel, 1)
|
|
qdel(victim)
|
|
devouring = null
|
|
break
|
|
else
|
|
devouring = null
|
|
if (victim && victimloc != victim.loc)
|
|
src << "<span class='danger'>[victim] moved away, you need to keep it still. Try grabbing, stunning or killing it first.</span>"
|
|
else if (ourloc != src.loc)
|
|
src << "<span class='danger'>You moved! Devouring cancelled</span>"
|
|
else
|
|
src << "Devouring Cancelled"//reason unknown, maybe the eater got stunned?
|
|
//This can also happen if you start devouring something else
|
|
break
|
|
|
|
//this function gradually digests things inside the mob's contents.
|
|
//It is called from life.dm. Any creatures that don't want to digest their contents simply don't call it
|
|
/mob/living/proc/handle_stomach()
|
|
for(var/mob/living/M in stomach_contents)
|
|
if(M.loc != src)//if something somehow escaped the stomach, then we remove it
|
|
LAZYREMOVE(stomach_contents, M)
|
|
continue
|
|
|
|
if(!M.composition_reagent_quantity)
|
|
M.calculate_composition()
|
|
|
|
var/digestion_power = (((mob_size * mob_size)/10) / (M.mob_size * M.mob_size))
|
|
var/digestion_time = digestion_power * 60 //Number of seconds it will take to digest in total
|
|
var/DPPP = 1 / (digestion_time / 2.1) //Digestion percentage per proc
|
|
M.adjustCloneLoss(M.maxHealth*DPPP)
|
|
//Digestion power is how much of the creature we can digest per minute. Calculated as a tenth of our mob size squared, divided by the victim's mob size squared
|
|
//If the resulting value is >1, digestion will take under a minute.
|
|
src.ingested.add_reagent(M.composition_reagent, M.composition_reagent_quantity*DPPP)
|
|
if ((M.stat != DEAD) && (M.cloneloss > (M.maxHealth*0.5))) //If we've consumed half of the victim, then it dies
|
|
M.death()
|
|
M.stat = DEAD //Just in case the death function doesn't set it
|
|
src << "Your stomach feels a little more relaxed as \the [M] finally stops fighting."
|
|
|
|
if (M.cloneloss >= M.maxHealth) //If we've consumed all of it, then digestion is finished.
|
|
LAZYREMOVE(stomach_contents, M)
|
|
src << "Your stomach feels a little more empty as you finish digesting \the [M]."
|
|
qdel(M)
|
|
|
|
//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
|
|
|
|
/proc/is_valid_for_devour(var/mob/living/test, var/eat_types)
|
|
var/mobtypes = test.find_type()//We find a bitfield of types for the victim
|
|
|
|
//Then for each type the victim has, we test if we're allowed to eat that type.
|
|
//eat_types must contain all types that the mob has. For example we need both humanoid and synthetic to eat an IPC
|
|
if (mobtypes & TYPE_SYNTHETIC)
|
|
if (!(eat_types & TYPE_SYNTHETIC))
|
|
return 0
|
|
if (mobtypes & TYPE_HUMANOID)
|
|
if (!(eat_types & TYPE_HUMANOID))
|
|
return 0
|
|
if (mobtypes & TYPE_WIERD)
|
|
if (!(eat_types & TYPE_WIERD))
|
|
return 0
|
|
if (mobtypes & TYPE_ORGANIC)
|
|
if (!(eat_types & TYPE_ORGANIC))
|
|
return 0
|
|
|
|
//If we get here, none of the checks have failed, the mob must be valid!
|
|
return 1
|
|
|
|
/mob/living/proc/calculate_composition()
|
|
if (!composition_reagent)//if no reagent has been set, then we'll set one
|
|
var/type = find_type(src)
|
|
if (type & TYPE_SYNTHETIC)
|
|
src.composition_reagent = "iron"
|
|
else
|
|
src.composition_reagent = "protein"
|
|
|
|
//if the mob is a simple animal with a defined meat quantity
|
|
if (istype(src, /mob/living/simple_animal))
|
|
var/mob/living/simple_animal/SA = src
|
|
if (SA.meat_amount)
|
|
src.composition_reagent_quantity = SA.meat_amount*2*PPM
|
|
|
|
//The quantity of protein is based on the meat_amount, but multiplied by 2
|
|
|
|
var/size_reagent = (src.mob_size * src.mob_size) * 3//The quantity of protein is set to 3x mob size squared
|
|
if (size_reagent > src.composition_reagent_quantity)//We take the larger of the two
|
|
src.composition_reagent_quantity = size_reagent
|
|
|
|
#undef PPM
|