Files
Aurora.3/code/modules/reagents/Chemistry-Holder.dm
T
NanakoACandskull132 ecf8425a19 Animal Feeding and Hunger (#634)
changes:

rscadd: "Added hunger and feeding system for simple animals, this includes cats, dogs, mice, lizards, chickens, cows, etc"
rscadd: "Animals can now actually consume food instead of nibbling them eternally."
rscadd: "Animals can now be hand-fed by using food on them."
rscadd: "Animals will move more slowly when starving. Examining an animal will show if its hungry."
Basically added a very simple metabolising system for animals, and fixed up various eating/feeding functions. They only metabolise food, any other reagents are just removed and ignored for now, since most chemicals were designed for carbon mobs and often aren't valid
2016-08-02 21:43:03 +03:00

427 lines
14 KiB
Plaintext

/datum/reagents
var/list/datum/reagent/reagent_list = list()
var/total_volume = 0
var/maximum_volume = 100
var/atom/my_atom = null
/datum/reagents/New(var/max = 100, atom/A = null)
maximum_volume = max
my_atom = A
//I dislike having these here but map-objects are initialised before world/New() is called. >_>
if(!chemical_reagents_list)
//Chemical Reagents - Initialises all /datum/reagent into a list indexed by reagent id
var/paths = typesof(/datum/reagent) - /datum/reagent
chemical_reagents_list = list()
for(var/path in paths)
var/datum/reagent/D = new path()
if(!D.name)
continue
chemical_reagents_list[D.id] = D
if(!chemical_reactions_list)
//Chemical Reactions - Initialises all /datum/chemical_reaction into a list
// It is filtered into multiple lists within a list.
// For example:
// chemical_reaction_list["phoron"] is a list of all reactions relating to phoron
var/paths = typesof(/datum/chemical_reaction) - /datum/chemical_reaction
chemical_reactions_list = list()
for(var/path in paths)
var/datum/chemical_reaction/D = new path()
var/list/reaction_ids = list()
if(D.required_reagents && D.required_reagents.len)
for(var/reaction in D.required_reagents)
reaction_ids += reaction
// Create filters based on each reagent id in the required reagents list
for(var/id in reaction_ids)
if(!chemical_reactions_list[id])
chemical_reactions_list[id] = list()
chemical_reactions_list[id] += D
break // Don't bother adding ourselves to other reagent ids, it is redundant.
/datum/reagents/Destroy()
..()
for(var/datum/reagent/R in reagent_list)
qdel(R)
reagent_list.Cut()
reagent_list = null
if(my_atom && my_atom.reagents == src)
my_atom.reagents = null
/* Internal procs */
/datum/reagents/proc/get_free_space() // Returns free space.
return maximum_volume - total_volume
/datum/reagents/proc/get_master_reagent() // Returns reference to the reagent with the biggest volume.
var/the_reagent = null
var/the_volume = 0
for(var/datum/reagent/A in reagent_list)
if(A.volume > the_volume)
the_volume = A.volume
the_reagent = A
return the_reagent
/datum/reagents/proc/get_master_reagent_name() // Returns the name of the reagent with the biggest volume.
var/the_name = null
var/the_volume = 0
for(var/datum/reagent/A in reagent_list)
if(A.volume > the_volume)
the_volume = A.volume
the_name = A.name
return the_name
/datum/reagents/proc/get_master_reagent_id() // Returns the id of the reagent with the biggest volume.
var/the_id = null
var/the_volume = 0
for(var/datum/reagent/A in reagent_list)
if(A.volume > the_volume)
the_volume = A.volume
the_id = A.id
return the_id
/datum/reagents/proc/update_total() // Updates volume.
total_volume = 0
for(var/datum/reagent/R in reagent_list)
if(R.volume < MINIMUM_CHEMICAL_VOLUME)
del_reagent(R.id)
else
total_volume += R.volume
return
/datum/reagents/proc/delete()
for(var/datum/reagent/R in reagent_list)
R.holder = null
if(my_atom)
my_atom.reagents = null
/datum/reagents/proc/handle_reactions()
if(!my_atom) // No reactions in temporary holders
return
if(my_atom.flags & NOREACT) // No reactions here
return
var/reaction_occured = 0
do
reaction_occured = 0
for(var/datum/reagent/R in reagent_list)
for(var/datum/chemical_reaction/C in chemical_reactions_list[R.id])
var/reagents_suitable = 1
for(var/B in C.required_reagents)
if(!has_reagent(B, C.required_reagents[B]))
reagents_suitable = 0
for(var/B in C.catalysts)
if(!has_reagent(B, C.catalysts[B]))
reagents_suitable = 0
for(var/B in C.inhibitors)
if(has_reagent(B, C.inhibitors[B]))
reagents_suitable = 0
if(!reagents_suitable || !C.can_happen(src))
continue
var/use = -1
for(var/B in C.required_reagents)
if(use == -1)
use = get_reagent_amount(B) / C.required_reagents[B]
else
use = min(use, get_reagent_amount(B) / C.required_reagents[B])
var/newdata = C.send_data(src) // We need to get it before reagents are removed. See blood paint.
for(var/B in C.required_reagents)
remove_reagent(B, use * C.required_reagents[B], safety = 1)
if(C.result)
add_reagent(C.result, C.result_amount * use, newdata)
if(!ismob(my_atom) && C.mix_message)
var/list/seen = viewers(4, get_turf(my_atom))
for(var/mob/M in seen)
M << "<span class='notice'>\icon[my_atom] [C.mix_message]</span>"
playsound(get_turf(my_atom), 'sound/effects/bubbles.ogg', 80, 1)
C.on_reaction(src, C.result_amount * use)
reaction_occured = 1
while(reaction_occured)
update_total()
return
/* Holder-to-chemical */
/datum/reagents/proc/add_reagent(var/id, var/amount, var/data = null, var/safety = 0)
if(!isnum(amount) || amount <= 0)
return 0
update_total()
amount = min(amount, get_free_space())
for(var/datum/reagent/current in reagent_list)
if(current.id == id)
current.volume += amount
if(!isnull(data)) // For all we know, it could be zero or empty string and meaningful
current.mix_data(data, amount)
update_total()
if(!safety)
handle_reactions()
if(my_atom)
my_atom.on_reagent_change()
return 1
var/datum/reagent/D = chemical_reagents_list[id]
if(D)
var/datum/reagent/R = new D.type()
reagent_list += R
R.holder = src
R.volume = amount
R.initialize_data(data)
update_total()
if(!safety)
handle_reactions()
if(my_atom)
my_atom.on_reagent_change()
return 1
else
warning("[my_atom] attempted to add a reagent called '[id]' which doesn't exist. ([usr])")
return 0
/datum/reagents/proc/remove_reagent(var/id, var/amount, var/safety = 0)
if(!isnum(amount))
return 0
for(var/datum/reagent/current in reagent_list)
if(current.id == id)
current.volume -= amount // It can go negative, but it doesn't matter
update_total() // Because this proc will delete it then
if(!safety)
handle_reactions()
if(my_atom)
my_atom.on_reagent_change()
return 1
return 0
/datum/reagents/proc/del_reagent(var/id)
for(var/datum/reagent/current in reagent_list)
if (current.id == id)
reagent_list -= current
qdel(current)
update_total()
if(my_atom)
my_atom.on_reagent_change()
return 0
/datum/reagents/proc/has_reagent(var/id, var/amount = 0)
for(var/datum/reagent/current in reagent_list)
if(current.id == id)
if(current.volume >= amount)
return 1
else
return 0
return 0
/datum/reagents/proc/clear_reagents()
for(var/datum/reagent/current in reagent_list)
del_reagent(current.id)
return
/datum/reagents/proc/get_reagent_amount(var/id)
for(var/datum/reagent/current in reagent_list)
if(current.id == id)
return current.volume
return 0
/datum/reagents/proc/get_data(var/id)
for(var/datum/reagent/current in reagent_list)
if(current.id == id)
return current.get_data()
return 0
/datum/reagents/proc/get_reagents()
. = list()
for(var/datum/reagent/current in reagent_list)
. += "[current.id] ([current.volume])"
return english_list(., "EMPTY", "", ", ", ", ")
/* Holder-to-holder and similar procs */
/datum/reagents/proc/remove_any(var/amount = 1) // Removes up to [amount] of reagents from [src]. Returns actual amount removed.
amount = min(amount, total_volume)
if(!amount)
return
var/part = amount / total_volume
for(var/datum/reagent/current in reagent_list)
var/amount_to_remove = current.volume * part
remove_reagent(current.id, amount_to_remove, 1)
update_total()
handle_reactions()
return amount
/datum/reagents/proc/trans_to_holder(var/datum/reagents/target, var/amount = 1, var/multiplier = 1, var/copy = 0) // Transfers [amount] reagents from [src] to [target], multiplying them by [multiplier]. Returns actual amount removed from [src] (not amount transferred to [target]).
if(!target || !istype(target))
return
amount = max(0, min(amount, total_volume, target.get_free_space() / multiplier))
if(!amount)
return
var/part = amount / total_volume
for(var/datum/reagent/current in reagent_list)
var/amount_to_transfer = current.volume * part
target.add_reagent(current.id, amount_to_transfer * multiplier, current.get_data(), safety = 1) // We don't react until everything is in place
if(!copy)
remove_reagent(current.id, amount_to_transfer, 1)
if(!copy)
handle_reactions()
target.handle_reactions()
return amount
/* Holder-to-atom and similar procs */
//The general proc for applying reagents to things. This proc assumes the reagents are being applied externally,
//not directly injected into the contents. It first calls touch, then the appropriate trans_to_*() or splash_mob().
//If for some reason touch effects are bypassed (e.g. injecting stuff directly into a reagent container or person),
//call the appropriate trans_to_*() proc.
/datum/reagents/proc/trans_to(var/atom/target, var/amount = 1, var/multiplier = 1, var/copy = 0)
touch(target) //First, handle mere touch effects
if(ismob(target))
return splash_mob(target, amount, copy)
if(isturf(target))
return trans_to_turf(target, amount, multiplier, copy)
if(isobj(target) && target.is_open_container())
return trans_to_obj(target, amount, multiplier, copy)
return 0
//Using this in case we want to differentiate splashing an atom from transferring reagents to it later down the road.
//For now it just calls trans_to.
/datum/reagents/proc/splash(var/atom/target, var/amount = 1, var/multiplier = 1, var/copy = 0)
trans_to(target, amount, multiplier, copy)
/datum/reagents/proc/trans_id_to(var/atom/target, var/id, var/amount = 1)
if (!target || !target.reagents || !target.simulated)
return
amount = min(amount, get_reagent_amount(id))
if(!amount)
return
var/datum/reagents/F = new /datum/reagents(amount)
var/tmpdata = get_data(id)
F.add_reagent(id, amount, tmpdata)
remove_reagent(id, amount)
return F.trans_to(target, amount) // Let this proc check the atom's type
// When applying reagents to an atom externally, touch() is called to trigger any on-touch effects of the reagent.
// This does not handle transferring reagents to things.
// For example, splashing someone with water will get them wet and extinguish them if they are on fire,
// even if they are wearing an impermeable suit that prevents the reagents from contacting the skin.
/datum/reagents/proc/touch(var/atom/target)
if(ismob(target))
touch_mob(target)
if(isturf(target))
touch_turf(target)
if(isobj(target))
touch_obj(target)
return
/datum/reagents/proc/touch_mob(var/mob/target)
if(!target || !istype(target) || !target.simulated)
return
for(var/datum/reagent/current in reagent_list)
current.touch_mob(target, current.volume)
update_total()
/datum/reagents/proc/touch_turf(var/turf/target)
if(!target || !istype(target) || !target.simulated)
return
for(var/datum/reagent/current in reagent_list)
current.touch_turf(target, current.volume)
update_total()
/datum/reagents/proc/touch_obj(var/obj/target)
if(!target || !istype(target) || !target.simulated)
return
for(var/datum/reagent/current in reagent_list)
current.touch_obj(target, current.volume)
update_total()
// Attempts to place a reagent on the mob's skin.
// Reagents are not guaranteed to transfer to the target.
// Do not call this directly, call trans_to() instead.
/datum/reagents/proc/splash_mob(var/mob/target, var/amount = 1, var/copy = 0)
var/perm = 1
if(isliving(target)) //will we ever even need to tranfer reagents to non-living mobs?
var/mob/living/L = target
perm = L.reagent_permeability()
return trans_to_mob(target, amount, CHEM_TOUCH, perm, copy)
/datum/reagents/proc/trans_to_mob(var/mob/target, var/amount = 1, var/type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0) // Transfer after checking into which holder...
if(!target || !istype(target) || !target.simulated)
return
if(iscarbon(target))
var/mob/living/carbon/C = target
if(type == CHEM_BLOOD)
var/datum/reagents/R = C.reagents
return trans_to_holder(R, amount, multiplier, copy)
if(type == CHEM_INGEST)
var/datum/reagents/R = C.ingested
return trans_to_holder(R, amount, multiplier, copy)
if(type == CHEM_TOUCH)
var/datum/reagents/R = C.touching
return trans_to_holder(R, amount, multiplier, copy)
else
//If the target has a reagent holder, we'll try to put it there instead. This allows feeding simple animals
if(target.reagents && type == CHEM_BLOOD || type == CHEM_INGEST)
return trans_to_holder(target.reagents, amount, multiplier, copy)
else
var/datum/reagents/R = new /datum/reagents(amount)
. = trans_to_holder(R, amount, multiplier, copy)
R.touch_mob(target)
/datum/reagents/proc/trans_to_turf(var/turf/target, var/amount = 1, var/multiplier = 1, var/copy = 0) // Turfs don't have any reagents (at least, for now). Just touch it.
if(!target || !target.simulated)
return
var/datum/reagents/R = new /datum/reagents(amount * multiplier)
. = trans_to_holder(R, amount, multiplier, copy)
R.touch_turf(target)
return
/datum/reagents/proc/trans_to_obj(var/turf/target, var/amount = 1, var/multiplier = 1, var/copy = 0) // Objects may or may not; if they do, it's probably a beaker or something and we need to transfer properly; otherwise, just touch.
if(!target || !target.simulated)
return
if(!target.reagents)
var/datum/reagents/R = new /datum/reagents(amount * multiplier)
. = trans_to_holder(R, amount, multiplier, copy)
R.touch_obj(target)
return
return trans_to_holder(target.reagents, amount, multiplier, copy)
/* Atom reagent creation - use it all the time */
/atom/proc/create_reagents(var/max_vol)
reagents = new/datum/reagents(max_vol, src)