diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 3d3ffaf646c..9ccb93591ba 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -46,6 +46,10 @@
/mob/living/simple_animal/pet/cat/Initialize()
. = ..()
verbs += /mob/living/proc/lay_down
+ add_cell_sample()
+
+/mob/living/simple_animal/pet/cat/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_CAT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/pet/cat/update_mobility()
..()
@@ -80,6 +84,8 @@
unique_pet = TRUE
held_state = "original"
+/mob/living/simple_animal/pet/cat/original/add_cell_sample()
+ return
/mob/living/simple_animal/pet/cat/kitten
name = "kitten"
desc = "D'aaawwww."
@@ -261,6 +267,9 @@
deathsound = "bodyfall"
held_state = "cak"
+/mob/living/simple_animal/pet/cat/cak/add_cell_sample()
+ return
+
/mob/living/simple_animal/pet/cat/cak/CheckParts(list/parts)
..()
var/obj/item/organ/brain/B = locate(/obj/item/organ/brain) in contents
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index 0cef8673dd7..84bf0581113 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -24,6 +24,10 @@
footstep_type = FOOTSTEP_MOB_CLAW
+/mob/living/simple_animal/pet/dog/Initialize()
+ . = ..()
+ add_cell_sample()
+
/mob/living/simple_animal/pet/dog/Life()
..()
@@ -98,6 +102,9 @@
var/shaved = FALSE
var/nofur = FALSE //Corgis that have risen past the material plane of existence.
+/mob/living/simple_animal/pet/dog/corgi/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_CORGI, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
/mob/living/simple_animal/pet/dog/corgi/Destroy()
QDEL_NULL(inventory_head)
QDEL_NULL(inventory_back)
@@ -128,6 +135,9 @@
collar_type = "pug"
held_state = "pug"
+/mob/living/simple_animal/pet/dog/pug/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_PUG, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
/mob/living/simple_animal/pet/dog/pug/mcgriff
name = "McGriff"
desc = "This dog can tell someting smells around here, and that something is CRIME!"
diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
index e45f18a7abe..f78ad951e6f 100644
--- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
+++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
@@ -149,8 +149,12 @@
/mob/living/simple_animal/cow/Initialize()
udder = new()
+ add_cell_sample()
. = ..()
+/mob/living/simple_animal/cow/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_COW, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
/mob/living/simple_animal/cow/Destroy()
qdel(udder)
udder = null
@@ -274,6 +278,10 @@
. = ..()
pixel_x = rand(-6, 6)
pixel_y = rand(0, 10)
+ add_cell_sample()
+
+/mob/living/simple_animal/chick/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_CHICKEN, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/chick/Life()
. =..()
@@ -342,6 +350,10 @@
pixel_x = rand(-6, 6)
pixel_y = rand(0, 10)
++chicken_count
+ add_cell_sample()
+
+/mob/living/simple_animal/chicken/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_CHICKEN, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/chicken/Destroy()
--chicken_count
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index f041ea545ce..bc2d2296693 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -40,7 +40,10 @@
icon_state = "mouse_[body_color]"
icon_living = "mouse_[body_color]"
icon_dead = "mouse_[body_color]_dead"
+ add_cell_sample()
+/mob/living/simple_animal/mouse/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10)
/mob/living/simple_animal/mouse/proc/splat()
src.health = 0
@@ -177,6 +180,10 @@
foodtype = GROSS | MEAT | RAW
grind_results = list(/datum/reagent/blood = 20, /datum/reagent/liquidgibs = 5)
+/obj/item/reagent_containers/food/snacks/deadmouse/Initialize()
+ . = ..()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10)
+
/obj/item/reagent_containers/food/snacks/deadmouse/examine(mob/user)
. = ..()
if (reagents?.has_reagent(/datum/reagent/yuck) || reagents?.has_reagent(/datum/reagent/fuel))
diff --git a/code/modules/mob/living/simple_animal/friendly/snake.dm b/code/modules/mob/living/simple_animal/friendly/snake.dm
index 01182fcf9d5..6f7f76f7449 100644
--- a/code/modules/mob/living/simple_animal/friendly/snake.dm
+++ b/code/modules/mob/living/simple_animal/friendly/snake.dm
@@ -39,6 +39,12 @@
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
+/mob/living/simple_animal/hostile/retaliate/poison/snake/Initialize()
+ . = ..()
+ add_cell_sample()
+
+/mob/living/simple_animal/hostile/retaliate/poison/snake/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_SNAKE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/hostile/retaliate/poison/snake/ListTargets(atom/the_target)
. = oview(vision_range, targets_from) //get list of things in vision range
diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm
index 056a448f349..19633468a30 100644
--- a/code/modules/mob/living/simple_animal/hostile/bear.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bear.dm
@@ -51,6 +51,10 @@
/mob/living/simple_animal/hostile/bear/Initialize()
. = ..()
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
+ add_cell_sample()
+
+/mob/living/simple_animal/hostile/bear/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_BEAR, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/hostile/bear/Life()
. = ..()
@@ -147,6 +151,9 @@
attack_sound = 'sound/weapons/slap.ogg'
attack_verb_continuous = "slaps"
+/mob/living/simple_animal/hostile/bear/butter/add_cell_sample()
+ return //You cannot grow a real bear from butter.
+
/mob/living/simple_animal/hostile/bear/butter/Life() //Heals butter bear really fast when he takes damage.
if(stat)
return
diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm
index d4014bdba25..ff5f58e3911 100644
--- a/code/modules/mob/living/simple_animal/hostile/carp.dm
+++ b/code/modules/mob/living/simple_animal/hostile/carp.dm
@@ -74,6 +74,10 @@
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
carp_randomify(rarechance)
update_icons()
+ add_cell_sample()
+
+/mob/living/simple_animal/hostile/carp/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_CARP, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/hostile/carp/proc/carp_randomify(rarechance)
if(random_color)
@@ -163,6 +167,9 @@
tame_chance = 0
bonus_tame_chance = 0
+/mob/living/simple_animal/hostile/carp/holocarp/add_cell_sample()
+ return
+
/mob/living/simple_animal/hostile/carp/megacarp
icon = 'icons/mob/broadMobs.dmi'
name = "Mega Space Carp"
@@ -195,6 +202,10 @@
melee_damage_upper += rand(10,20)
maxHealth += rand(30,60)
move_to_delay = rand(3,7)
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_MEGACARP, CELL_VIRUS_TABLE_GENERIC_MOB)
+
+/mob/living/simple_animal/hostile/carp/megacarp/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_MEGACARP, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/hostile/carp/megacarp/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
. = ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/cockroach.dm b/code/modules/mob/living/simple_animal/hostile/cockroach.dm
index 43a26bd1158..74fe430447b 100644
--- a/code/modules/mob/living/simple_animal/hostile/cockroach.dm
+++ b/code/modules/mob/living/simple_animal/hostile/cockroach.dm
@@ -33,6 +33,13 @@
faction = list("neutral")
var/squish_chance = 50
+/mob/living/simple_animal/hostile/cockroach/Initialize()
+ . = ..()
+ add_cell_sample()
+
+/mob/living/simple_animal/hostile/cockroach/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_COCKROACH, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 7)
+
/obj/projectile/glockroachbullet
damage = 10 //same damage as a hivebot
damage_type = BRUTE
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 78cfbb89214..cc2984b881d 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -327,7 +327,7 @@
FindTarget()
-/mob/living/simple_animal/hostile/proc/AttackingTarget()
+/mob/living/simple_animal/hostile/proc/AttackingTarget(atom/A)
SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target)
in_melee = TRUE
return target.attack_animal(src)
diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm
new file mode 100644
index 00000000000..7eee035ba4f
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm
@@ -0,0 +1,480 @@
+///Oozes are slime-esque creatures, they are highly gluttonous creatures primarily intended for player controll.
+/mob/living/simple_animal/hostile/ooze
+ name = "Ooze"
+ icon = 'icons/mob/vatgrowing.dmi'
+ icon_state = "gelatinous"
+ icon_dead = "gelatinous_dead"
+ mob_biotypes = MOB_ORGANIC
+ pass_flags = PASSTABLE | PASSGRILLE
+ ventcrawler = VENTCRAWLER_ALWAYS
+ gender = NEUTER
+ emote_see = list("jiggles", "bounces in place")
+ speak_emote = list("blorbles")
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ hud_type = /datum/hud/ooze
+ minbodytemp = 250
+ maxbodytemp = INFINITY
+ faction = list("slime")
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ health = 200
+ maxHealth = 200
+ attack_verb_continuous = "slimes"
+ attack_verb_simple = "slime"
+ attack_sound = 'sound/effects/blobattack.ogg'
+ a_intent = INTENT_HARM
+ environment_smash = ENVIRONMENT_SMASH_STRUCTURES
+ mob_size = MOB_SIZE_LARGE
+ initial_language_holder = /datum/language_holder/slime
+
+ ///Oozes have their own nutrition. Changes based on them eating
+ var/ooze_nutrition = 50
+ var/ooze_nutrition_loss = -0.15
+ var/ooze_metabolism_modifier = 2
+
+/mob/living/simple_animal/hostile/ooze/Initialize()
+ . = ..()
+ create_reagents(300)
+ add_cell_sample()
+ AddComponent(/datum/component/footstep, FOOTSTEP_MOB_SLIME, 7.5)
+
+/mob/living/simple_animal/hostile/ooze/attacked_by(obj/item/I, mob/living/user)
+ if(!check_edible(I))
+ return ..()
+ eat_atom(I)
+
+/mob/living/simple_animal/hostile/ooze/AttackingTarget(atom/A)
+ . = ..()
+ if(!check_edible(A))
+ return ..()
+ eat_atom(A)
+
+///Handles nutrition gain/loss of mob and also makes it take damage if it's too low on nutrition, only happens for sentient mobs.
+/mob/living/simple_animal/hostile/ooze/Life()
+ . = ..()
+
+ if(!mind && stat != DEAD)//no mind no change
+ return
+
+ var/nutrition_change = ooze_nutrition_loss
+
+ //Eat a bit of all the reagents we have. Gaining nutrition for actual nutritional ones.
+ for(var/i in reagents.reagent_list)
+ var/datum/reagent/R = i
+ var/consumption_amount = min(reagents.get_reagent_amount(R.type), ooze_metabolism_modifier * REAGENTS_METABOLISM)
+ if(istype(R, /datum/reagent/consumable))
+ var/datum/reagent/consumable/consumable = R
+ nutrition_change += consumption_amount * consumable.nutriment_factor
+ reagents.remove_reagent(R.type, consumption_amount)
+ adjust_ooze_nutrition(nutrition_change)
+
+ if(ooze_nutrition <= 0)
+ adjustBruteLoss(0.5)
+
+///Returns whether or not the supplied movable atom is edible.
+/mob/living/simple_animal/hostile/ooze/proc/check_edible(atom/movable/AM)
+ if(ismob(AM))
+ return FALSE
+ if(istype(AM, /obj/item/reagent_containers/food))
+ var/obj/item/reagent_containers/food/meal = AM
+ return (meal.foodtype & MEAT) //Dont forget to add edible component compat here later
+
+///Does ooze_nutrition + supplied amount and clamps it within 0 and 500
+/mob/living/simple_animal/hostile/ooze/proc/adjust_ooze_nutrition(amount)
+ ooze_nutrition = clamp(ooze_nutrition + amount, 0, 500)
+ updateNutritionDisplay()
+
+///Tries to transfer the atoms reagents then delete it
+/mob/living/simple_animal/hostile/ooze/proc/eat_atom(obj/item/A)
+ A.reagents.trans_to(src, A.reagents.total_volume, transfered_by = src)
+ src.visible_message("You eat [A].")
+ playsound(loc,'sound/items/eatfood.ogg', rand(30,50), TRUE)
+ qdel(A)
+
+///Updates the display that shows the mobs nutrition
+/mob/living/simple_animal/hostile/ooze/proc/updateNutritionDisplay()
+ if(hud_used) //clientless oozes
+ hud_used.alien_plasma_display.maptext = "
[round(ooze_nutrition)]
"
+
+
+///* Gelatinious Ooze code below *\\\\
+
+
+///Its good stats and high mobility makes this a good assasin type creature. It's vulnerabilites against cold, shotguns and
+/mob/living/simple_animal/hostile/ooze/gelatinous
+ name = "Gelatinous Cube"
+ desc = "A cubic ooze native to Sholus VII.\nSince the advent of space travel this species has established itself in the waste treatment facilities of several space colonies.\nIt is often considered to be the third most infamous invasive species due to its highly agressive and predatory nature."
+ speed = 1
+ damage_coeff = list(BRUTE = 1, BURN = 0.6, TOX = 0.5, CLONE = 1.5, STAMINA = 0, OXY = 1)
+ melee_damage_lower = 20
+ melee_damage_upper = 20
+ armour_penetration = 15
+ obj_damage = 20
+ deathmessage = "collapses into a pile of goo!"
+ ///The ability to give yourself a metabolic speed boost which raises heat
+ var/datum/action/cooldown/metabolicboost/boost
+ ///The ability to consume mobs
+ var/datum/action/consume/consume
+
+///Initializes the mobs abilities and gives them to the mob
+/mob/living/simple_animal/hostile/ooze/gelatinous/Initialize()
+ . = ..()
+ boost = new
+ boost.Grant(src)
+ consume = new
+ consume.Grant(src)
+
+///If this mob gets resisted by something, its trying to escape consumption.
+/mob/living/simple_animal/hostile/ooze/gelatinous/resist_act(mob/living/user)
+ . = ..()
+ if(!do_after(user, 60)) //6 second struggle
+ return FALSE
+ consume.stop_consuming()
+
+/mob/living/simple_animal/hostile/ooze/gelatinous/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_GELATINOUS, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
+///This ability lets the gelatinious ooze speed up for a little bit
+/datum/action/cooldown/metabolicboost
+ name = "Metabolic boost"
+ desc = "Gain a temporary speed boost. Costs 10 nutrition and slowly raises your temperature"
+ background_icon_state = "bg_hive"
+ icon_icon = 'icons/mob/actions/actions_slime.dmi'
+ button_icon_state = "metabolic_boost"
+ check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN
+ cooldown_time = 240
+ var/nutrition_cost = 10
+ var/active = FALSE
+
+
+///Mob needs to have enough nutrition
+/datum/action/cooldown/metabolicboost/IsAvailable()
+ . = ..()
+ var/mob/living/simple_animal/hostile/ooze/ooze = owner
+ if(. && ooze.ooze_nutrition >= nutrition_cost && !active)
+ return TRUE
+
+///Give the mob a speed boost, heat it up every second, and end the ability in 6 seconds
+/datum/action/cooldown/metabolicboost/Trigger()
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/simple_animal/hostile/ooze/ooze = owner
+ ooze.add_movespeed_modifier(/datum/movespeed_modifier/metabolicboost)
+ var/timerid = addtimer(CALLBACK(src, .proc/HeatUp), 1 SECONDS, TIMER_STOPPABLE | TIMER_LOOP) //Heat up every second
+ addtimer(CALLBACK(src, .proc/FinishSpeedup, timerid), 6 SECONDS)
+ to_chat(ooze, "You start slowing down again.")
+ deltimer(timerid)
+
+
+///This action lets you consume the mob you're currently pulling. I'M GONNA CONSUUUUUME (this is considered one of the funny memes in the 2019-2020 era)
+/datum/action/consume
+ name = "Consume"
+ desc = "Consume a mob that you are dragging to gain nutrition from them"
+ background_icon_state = "bg_hive"
+ icon_icon = 'icons/mob/actions/actions_slime.dmi'
+ button_icon_state = "consume"
+ check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN
+ ///The mob thats being consumed by this creature
+ var/mob/living/vored_mob
+
+///Register for owner death
+/datum/action/consume/New(Target)
+ . = ..()
+ RegisterSignal(owner, COMSIG_MOB_DEATH, .proc/on_owner_death)
+
+///Try to consume the pulled mob
+/datum/action/consume/Trigger()
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/simple_animal/hostile/ooze/gelatinous/ooze = owner
+ if(!ooze.pulling)
+ to_chat(src, "You need to be pulling a creature for this to work!")
+ return FALSE
+ if(!isliving(ooze.pulling))
+ to_chat(src, "You need to be pulling a creature for this to work!")
+ return FALSE
+ if(vored_mob)
+ to_chat(src, "You are already consuming another creature!")
+ return FALSE
+ owner.visible_message("You start attempting to devour [target].")
+ if(!do_after(ooze, 15, target = ooze.pulling))
+ return FALSE
+ var/mob/living/eat_target = ooze.pulling
+
+ if(!(eat_target.mob_biotypes & MOB_ORGANIC) || eat_target.stat == DEAD)
+ to_chat(src, "This creature isn't to my tastes!")
+ return FALSE
+ start_consuming(eat_target)
+
+///Start allowing this datum to process to handle the damage done to this mob.
+/datum/action/consume/proc/start_consuming(mob/living/target)
+ vored_mob = target
+ vored_mob.forceMove(owner) ///AAAAAAAAAAAAAAAAAAAAAAHHH!!!
+ playsound(owner,'sound/items/eatfood.ogg', rand(30,50), TRUE)
+ owner.visible_message("You devour [target].")
+ START_PROCESSING(SSprocessing, src)
+
+///Stop consuming the mob; dump them on the floor
+/datum/action/consume/proc/stop_consuming()
+ STOP_PROCESSING(SSprocessing, src)
+ vored_mob.forceMove(get_turf(owner))
+ playsound(get_turf(owner), 'sound/effects/splat.ogg', 50, TRUE)
+ owner.visible_message("You puke out [vored_mob].")
+ vored_mob = null
+
+///Gain health for the consumption and dump some clone loss on the target.
+/datum/action/consume/process()
+ var/mob/living/simple_animal/hostile/ooze/gelatinous/ooze = owner
+ vored_mob.adjustCloneLoss(6)
+ ooze.heal_ordered_damage((ooze.maxHealth * 0.03), list(BRUTE, BURN, OXY)) ///Heal 6% of these specific damage types each process
+ ooze.adjust_ooze_nutrition(3)
+
+ ///Dump em at 200 cloneloss.
+ if(vored_mob.getCloneLoss() >= 200)
+ stop_consuming()
+
+///On owner death dump the current vored mob
+/datum/action/consume/proc/on_owner_death()
+ stop_consuming()
+
+
+///* Gelatinious Grapes code below *\\\\
+
+///Child of the ooze mob which is orientated at being a healer type creature.
+/mob/living/simple_animal/hostile/ooze/grapes
+ name = "Sholean grapes"
+ desc = "A botryoidal ooze from Sholus VII.\nXenobiologists consider it to be one of the calmer and more agreeable species on the planet, but so far little is known about its behaviour in the wild.\nIt undulates in a comforting manner."
+ icon_state = "grapes"
+ icon_dead = "grapes_dead"
+ speed = 1
+ health = 200
+ maxHealth = 200
+ damage_coeff = list(BRUTE = 1, BURN = 0.8, TOX = 0.5, CLONE = 1.5, STAMINA = 0, OXY = 1)
+ melee_damage_lower = 12
+ melee_damage_upper = 12
+ obj_damage = 15
+ deathmessage = "deflates and spills its vital juices!"
+ ///The ability lets you envelop a carbon in a healing cocoon. Useful for saving critical carbons.
+ var/datum/action/cooldown/gel_cocoon/gel_cocoon
+ ///The ability to shoot a mending globule, a sticky projectile that heals over time.
+ var/obj/effect/proc_holder/globules/globules
+
+/mob/living/simple_animal/hostile/ooze/grapes/Initialize()
+ . = ..()
+ globules = new
+ AddAbility(globules)
+ gel_cocoon = new
+ gel_cocoon.Grant(src)
+
+
+/mob/living/simple_animal/hostile/ooze/grapes/check_edible(atom/movable/AM)
+ if(ismob(AM))
+ return FALSE
+ var/foodtype
+ if(istype(AM, /obj/item/reagent_containers/food))
+ var/obj/item/reagent_containers/food/meal = AM
+ foodtype = meal.foodtype
+ return foodtype & MEAT || foodtype & VEGETABLES //Dont forget to add edible component compat here later
+
+/mob/living/simple_animal/hostile/ooze/grapes/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_GRAPE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
+///Ability that allows the owner to fire healing globules at mobs, targetting specific limbs.
+/obj/effect/proc_holder/globules
+ name = "Fire Mending globule"
+ desc = "Fires a mending globule at someone, healing a specific limb of theirs."
+ active = FALSE
+ action_icon = 'icons/mob/actions/actions_slime.dmi'
+ action_icon_state = "globules"
+ action_background_icon_state = "bg_hive"
+ var/cooldown = 5 SECONDS
+ var/current_cooldown = 0
+
+/obj/effect/proc_holder/globules/Click(location, control, params)
+ . = ..()
+ if(!isliving(usr))
+ return TRUE
+ var/mob/living/user = usr
+ fire(user)
+
+/obj/effect/proc_holder/globules/fire(mob/living/carbon/user)
+ var/message
+ if(current_cooldown > world.time)
+ to_chat(user, "This ability is still on cooldown.")
+ return
+ if(active)
+ message = "You stop preparing your mending globules."
+ remove_ranged_ability(message)
+ else
+ message = "You prepare to launch a mending globule. Left-click to fire at a target!"
+ add_ranged_ability(user, message, TRUE)
+
+/obj/effect/proc_holder/globules/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(..())
+ return
+ if(!istype(ranged_ability_user, /mob/living/simple_animal/hostile/ooze) || ranged_ability_user.stat)
+ remove_ranged_ability()
+ return
+
+ var/mob/living/simple_animal/hostile/ooze/ooze = ranged_ability_user
+
+ if(ooze.ooze_nutrition < 5)
+ to_chat(ooze, "You need at least 5 nutrition to launch a mending globule.")
+ remove_ranged_ability()
+ return
+
+ ooze.visible_message("You launch a mending globule.")
+ var/obj/projectile/globule/globule = new (ooze.loc)
+ globule.preparePixelProjectile(target, ooze, params)
+ globule.def_zone = ooze.zone_selected
+ globule.fire()
+ ooze.adjust_ooze_nutrition(-5)
+ remove_ranged_ability()
+ current_cooldown = world.time + cooldown
+
+ return TRUE
+
+/obj/effect/proc_holder/globules/on_lose(mob/living/carbon/user)
+ remove_ranged_ability()
+
+///This projectile embeds into mobs and heals them over time.
+/obj/projectile/globule
+ name = "mending globule"
+ icon_state = "glob_projectile"
+ shrapnel_type = /obj/item/mending_globule
+ nodamage = TRUE
+ damage = 0
+
+///This item is what is embedded into the mob, and actually handles healing of mending globules
+/obj/item/mending_globule
+ name = "mending globule"
+ desc = "It somehow heals those who touch it."
+ icon = 'icons/obj/xenobiology/vatgrowing.dmi'
+ icon_state = "globule"
+ embedding = list("embed_chance" = 100, ignore_throwspeed_threshold = TRUE, "pain_mult" = 0, "jostle_pain_mult" = 0, "fall chance" = 0.5)
+ var/mob/living/carbon/human/healing_target
+ var/obj/item/bodypart/bodypart
+ var/heals_left = 35
+
+/obj/item/mending_globule/embedded(mob/living/carbon/human/embedded_mob)
+ . = ..()
+ if(!istype(loc, /obj/item/bodypart))
+ return
+ healing_target = embedded_mob
+ START_PROCESSING(SSobj, src)
+
+/obj/item/mending_globule/unembedded()
+ . = ..()
+ bodypart = null
+ STOP_PROCESSING(SSobj, src)
+
+///Handles the healing of the mending globule
+/obj/item/mending_globule/process()
+ if(!bodypart) //this is fucked
+ return FALSE
+ bodypart.heal_damage(1,1)
+ heals_left--
+ if(heals_left <= 0)
+ qdel(src)
+
+///This action lets you put a mob inside of a cacoon that will inject it with some chemicals.
+/datum/action/cooldown/gel_cocoon
+ name = "Gel Cocoon"
+ desc = "Puts a mob inside of a cocoon, allowing it to slowly heal."
+ background_icon_state = "bg_hive"
+ icon_icon = 'icons/mob/actions/actions_slime.dmi'
+ button_icon_state = "gel_cocoon"
+ check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN
+ cooldown_time = 10 SECONDS
+
+///Try to put the pulled mob in a cocoon
+/datum/action/cooldown/gel_cocoon/Trigger()
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/simple_animal/hostile/ooze/grapes/ooze = owner
+ if(!ooze.pulling)
+ to_chat(src, "You need to be pulling a creature for this to work!")
+ return FALSE
+ if(!iscarbon(ooze.pulling))
+ to_chat(src, "This creature is not advanced enough to be assisted with a cocoon!")
+ return FALSE
+ owner.visible_message("You start attempting to put [target] into a gel cocoon.")
+ if(!do_after(ooze, 1.5 SECONDS, target = ooze.pulling))
+ return FALSE
+
+ put_in_cocoon(ooze.pulling)
+ ooze.adjust_ooze_nutrition(-30)
+
+///Mob needs to have enough nutrition
+/datum/action/cooldown/gel_cocoon/IsAvailable()
+ . = ..()
+ var/mob/living/simple_animal/hostile/ooze/ooze = owner
+ if(. && ooze.ooze_nutrition >= 30)
+ return TRUE
+
+///Puts the mob in the new cocoon
+/datum/action/cooldown/gel_cocoon/proc/put_in_cocoon(mob/living/carbon/target)
+ var/obj/structure/gel_cocoon/cocoon = new /obj/structure/gel_cocoon(get_turf(target))
+ cocoon.insert_target(target)
+ owner.visible_message("You put [target] into a gel cocoon.")
+
+/obj/structure/gel_cocoon
+ name = "gel cocoon"
+ desc = "It looks gross, but helpful."
+ icon = 'icons/obj/xenobiology/vatgrowing.dmi'
+ icon_state = "gel_cocoon"
+ max_integrity = 50
+ var/mob/living/carbon/inhabitant
+
+/obj/structure/gel_cocoon/Destroy()
+ dump_inhabitant(FALSE)
+ . = ..()
+
+/obj/structure/gel_cocoon/resist_act(mob/living/user)
+ . = ..()
+ user.visible_message("You see [user] breaking out of [src]!", \
+ "You start tearing the soft tissue of the gel cocoon")
+ if(!do_after(user, 1.5 SECONDS, target = src))
+ return FALSE
+ dump_inhabitant()
+
+///This proc handles the insertion of a person into the cocoon
+/obj/structure/gel_cocoon/proc/insert_target(target)
+ inhabitant = target
+ inhabitant.forceMove(src)
+ START_PROCESSING(SSobj, src)
+
+///This proc dumps the mob and handles associated audiovisual feedback
+/obj/structure/gel_cocoon/proc/dump_inhabitant(destroy_after = TRUE)
+ inhabitant.forceMove(get_turf(src))
+ playsound(get_turf(inhabitant), 'sound/effects/splat.ogg', 50, TRUE)
+ inhabitant.Paralyze(10)
+ inhabitant.visible_message("You fall out of [src].")
+ if(destroy_after)
+ qdel(src)
+
+
+/obj/structure/gel_cocoon/process()
+ if(inhabitant.reagents.get_reagent_amount(/datum/reagent/medicine/atropine) < 5)
+ inhabitant.reagents.add_reagent(/datum/reagent/medicine/atropine, 0.5)
+
+ if(inhabitant.reagents.get_reagent_amount(/datum/reagent/medicine/salglu_solution) < 15)
+ inhabitant.reagents.add_reagent(/datum/reagent/medicine/salglu_solution, 1.5)
+
+ if(inhabitant.reagents.get_reagent_amount(/datum/reagent/consumable/milk) < 20)
+ inhabitant.reagents.add_reagent(/datum/reagent/consumable/milk, 2)
diff --git a/code/modules/mob/living/simple_animal/hostile/tree.dm b/code/modules/mob/living/simple_animal/hostile/tree.dm
index b5e5ccfbbed..7d7148c0cc1 100644
--- a/code/modules/mob/living/simple_animal/hostile/tree.dm
+++ b/code/modules/mob/living/simple_animal/hostile/tree.dm
@@ -44,6 +44,10 @@
var/is_tree = TRUE
+/mob/living/simple_animal/hostile/tree/Initialize()
+ . = ..()
+ add_cell_sample()
+
/mob/living/simple_animal/hostile/tree/Life()
..()
if(is_tree && isopenturf(loc))
@@ -65,6 +69,9 @@
C.visible_message("\The [src] knocks down \the [C]!", \
"\The [src] knocks you down!")
+/mob/living/simple_animal/hostile/tree/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_PINE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
/mob/living/simple_animal/hostile/tree/festivus
name = "festivus pole"
desc = "Serenity now... SERENITY NOW!"
@@ -95,3 +102,6 @@
for(var/obj/machinery/power/apc/A in range(2, get_turf(src)))
if(A.cell)
A.cell.give(75)
+
+/mob/living/simple_animal/hostile/tree/festivus/add_cell_sample()
+ return
diff --git a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
new file mode 100644
index 00000000000..b120eca6129
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
@@ -0,0 +1,112 @@
+///Vatbeasts are creatures from vatgrowing and are literaly a beast in a vat, yup. They are designed to be a powerful mount roughly equal to a gorilla in power.
+/mob/living/simple_animal/hostile/vatbeast
+ name = "vatbeast"
+ desc = "A strange molluscoidal creature carrying a busted growing vat.\nYou wonder if this burden is a voluntary undertaking in order to achieve comfort and protection, or simply because the creature is fused to its metal shell?"
+ icon = 'icons/mob/vatgrowing.dmi'
+ icon_state = "vat_beast"
+ icon_dead = "vat_beast_dead"
+ mob_biotypes = MOB_ORGANIC|MOB_BEAST
+ mob_size = MOB_SIZE_LARGE
+ gender = NEUTER
+ environment_smash = ENVIRONMENT_SMASH_STRUCTURES
+ speak_emote = list("roars")
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ health = 250
+ maxHealth = 250
+ damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 1, CLONE = 2, STAMINA = 0, OXY = 1)
+ melee_damage_lower = 25
+ melee_damage_upper = 25
+ obj_damage = 40
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ attack_sound = 'sound/weapons/punch3.ogg'
+ attack_verb_continuous = "slaps"
+ attack_verb_simple = "slap"
+ food_type = list(/obj/item/reagent_containers/food/snacks/fries, /obj/item/reagent_containers/food/snacks/cheesyfries, /obj/item/reagent_containers/food/snacks/cornchips, /obj/item/reagent_containers/food/snacks/carrotfries)
+ tame_chance = 30
+
+ var/obj/effect/proc_holder/tentacle_slap/tentacle_slap
+
+/mob/living/simple_animal/hostile/vatbeast/Initialize()
+ . = ..()
+ tentacle_slap = new
+ AddAbility(tentacle_slap)
+ add_cell_sample()
+
+/mob/living/simple_animal/hostile/vatbeast/Destroy()
+ . = ..()
+ QDEL_NULL(tentacle_slap)
+
+/mob/living/simple_animal/hostile/vatbeast/tamed()
+ . = ..()
+ can_buckle = TRUE
+ buckle_lying = FALSE
+ var/datum/component/riding/D = LoadComponent(/datum/component/riding)
+ D.set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 15), TEXT_SOUTH = list(0, 15), TEXT_EAST = list(-10, 15), TEXT_WEST = list(10, 15)))
+ D.set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
+ D.set_vehicle_dir_layer(NORTH, OBJ_LAYER)
+ D.set_vehicle_dir_layer(EAST, OBJ_LAYER)
+ D.set_vehicle_dir_layer(WEST, OBJ_LAYER)
+ D.drive_verb = "ride"
+ D.override_allow_spacemove = TRUE
+ faction = list("neutral")
+
+/mob/living/simple_animal/hostile/vatbeast/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_VATBEAST, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+
+///Ability that allows the owner to slap other mobs a short distance away
+/obj/effect/proc_holder/tentacle_slap
+ name = "Tentacle slap"
+ desc = "Slap a creature with your tentacles."
+ active = FALSE
+ action_icon = 'icons/mob/actions/actions_animal.dmi'
+ action_icon_state = "tentacle_slap"
+ action_background_icon_state = "bg_revenant"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi'
+ var/cooldown = 12 SECONDS
+ var/current_cooldown = 0
+
+/obj/effect/proc_holder/tentacle_slap/Click(location, control, params)
+ . = ..()
+ if(!isliving(usr))
+ return TRUE
+ var/mob/living/user = usr
+ fire(user)
+
+/obj/effect/proc_holder/tentacle_slap/fire(mob/living/carbon/user)
+ var/message
+ if(current_cooldown > world.time)
+ to_chat(user, "This ability is still on cooldown.")
+ return
+ if(active)
+ message = "You stop preparing to tentacle slap."
+ remove_ranged_ability(message)
+ else
+ message = "You prepare your pimp-tentacle. Left-click to slap a target!"
+ add_ranged_ability(user, message, TRUE)
+
+/obj/effect/proc_holder/tentacle_slap/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(..())
+ return
+
+ if(!istype(ranged_ability_user, /mob/living/simple_animal/hostile/vatbeast) || ranged_ability_user.stat)
+ remove_ranged_ability()
+ return
+
+ if(!caller.Adjacent(target))
+ return
+
+ if(!isliving(target))
+ return
+
+ var/mob/living/living_target = target
+
+ var/mob/living/simple_animal/hostile/vatbeast/vatbeast = ranged_ability_user
+
+ vatbeast.visible_message("You slap [living_target] with your tentacle.")
+ playsound(vatbeast, 'sound/effects/assslap.ogg', 90)
+ var/atom/throw_target = get_edge_target_turf(target, vatbeast.dir)
+ living_target.throw_at(throw_target, 6, 4, vatbeast)
+ living_target.apply_damage(30)
+ current_cooldown = world.time + cooldown
+ remove_ranged_ability()
+ return TRUE
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 9afe35bc144..4198d848fcf 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -702,3 +702,7 @@
if (AIStatus == AI_Z_OFF)
SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src
toggle_ai(initial(AIStatus))
+
+///This proc is used for adding the swabbale element to mobs so that they are able to be biopsied and making sure holograpic and butter-based creatures don't yield viable cells samples.
+/mob/living/simple_animal/proc/add_cell_sample()
+ return
diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm
index 3e18960a75f..9fe6ba3faaf 100644
--- a/code/modules/mob/living/simple_animal/slime/slime.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime.dm
@@ -101,6 +101,7 @@
. = ..()
set_nutrition(700)
AddComponent(/datum/component/footstep, FOOTSTEP_MOB_SLIME, 7.5)
+ add_cell_sample()
/mob/living/simple_animal/slime/Destroy()
for (var/A in actions)
@@ -492,3 +493,6 @@
/mob/living/simple_animal/slime/random/Initialize(mapload, new_colour, new_is_adult)
. = ..(mapload, pick(slime_colours), prob(50))
+
+/mob/living/simple_animal/slime/add_cell_sample()
+ AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLIME, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm
index ac3bb468503..c10573e129f 100644
--- a/code/modules/movespeed/modifiers/mobs.dm
+++ b/code/modules/movespeed/modifiers/mobs.dm
@@ -111,3 +111,6 @@
/datum/movespeed_modifier/mob_config_speedmod
variable = TRUE
flags = IGNORE_NOSLOW
+
+/datum/movespeed_modifier/metabolicboost
+ multiplicative_slowdown = -1.5
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index f3cd37a3a3c..71debee5332 100755
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -110,6 +110,13 @@
M.satiety += 30
. = ..()
+/// The basic resource of vat growing.
+/datum/reagent/consumable/nutriment/protein
+ name = "Protein"
+ description = "A natural polyamide made up of amino acids. An essential constituent of mosts known forms of life."
+ brute_heal = 0.8 //Rewards the player for eating a balanced diet.
+ nutriment_factor = 9 * REAGENTS_METABOLISM //45% as calorie dense as corn oil.
+
/datum/reagent/consumable/cooking_oil
name = "Cooking Oil"
description = "A variety of cooking oil derived from fat or plants. Used in food preparation and frying."
diff --git a/code/modules/research/xenobiology/vatgrowing/biopsy_tool.dm b/code/modules/research/xenobiology/vatgrowing/biopsy_tool.dm
new file mode 100644
index 00000000000..42128bb92f1
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/biopsy_tool.dm
@@ -0,0 +1,18 @@
+///Tool capable of taking biological samples from mobs
+/obj/item/biopsy_tool
+ name = "biopsy tool"
+ desc = "Don't worry, it won't sting."
+ icon = 'icons/obj/xenobiology/vatgrowing.dmi'
+ icon_state = "biopsy"
+
+///Adds the swabbing component to the biopsy tool
+/obj/item/biopsy_tool/Initialize()
+ . = ..()
+ AddComponent(/datum/component/swabbing, FALSE, FALSE, TRUE, CALLBACK(src, .proc/update_swab_icon), max_items = 1)
+
+
+/obj/item/biopsy_tool/proc/update_swab_icon(list/swabbed_items)
+ if(LAZYLEN(swabbed_items))
+ icon_state = "biopsy_full"
+ else
+ icon_state = "biopsy"
diff --git a/code/modules/research/xenobiology/vatgrowing/microscope.dm b/code/modules/research/xenobiology/vatgrowing/microscope.dm
new file mode 100644
index 00000000000..509b054d028
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/microscope.dm
@@ -0,0 +1,81 @@
+/obj/structure/microscope
+ name = "Microscope"
+ desc = "A simple microscope, allowing you to examine micro-organisms."
+ var/obj/item/petri_dish/current_dish
+
+
+/obj/structure/microscope/attacked_by(obj/item/I, mob/living/user)
+ if(!istype(I, /obj/item/petri_dish))
+ return ..()
+ if(current_dish)
+ to_chat(user, "There is already a petridish in \the [src].")
+ return
+ to_chat(user, "You put [I] into \the [src].")
+ current_dish = I
+ current_dish.forceMove(src)
+
+/obj/structure/microscope/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
+ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "Microscope", name, 525, 594, master_ui, state)
+ ui.open()
+
+/obj/structure/microscope/ui_data(mob/user)
+ var/list/data = list()
+
+ data["has_dish"] = current_dish ? TRUE : FALSE
+ data["cell_lines"] = list()
+ data["viruses"] = list()
+
+ if(!current_dish)
+ return data
+ if(!current_dish.sample)
+ return data
+
+ for(var/organism in current_dish.sample.micro_organisms) //All the microorganisms in the dish
+ if(istype(organism, /datum/micro_organism/cell_line))
+ var/datum/micro_organism/cell_line/cell_line = organism
+ var/list/organism_data = list(
+ type = "cell line",
+ name = cell_line.name,
+ desc = cell_line.desc,
+ growth_rate = cell_line.growth_rate,
+ suspectibility = cell_line.virus_suspectibility,
+ requireds = get_reagent_list(cell_line.required_reagents),
+ supplementaries = get_reagent_list(cell_line.supplementary_reagents),
+ suppressives = get_reagent_list(cell_line.suppressive_reagents)
+ )
+ data["cell_lines"] += list(organism_data)
+
+ if(istype(organism, /datum/micro_organism/virus))
+ var/datum/micro_organism/virus/virus = organism
+ var/list/virus_data = list(
+ type = "virus",
+ name = virus.name,
+ desc = virus.desc
+ )
+ data["viruses"] += list(virus_data)
+
+ return data
+
+/obj/structure/microscope/proc/get_reagent_list(list/reagents)
+ var/list/reagent_list = list()
+ for(var/i in reagents) //Convert from assoc to normal. Yeah very shit.
+ var/datum/reagent/reagent = i
+ reagent_list += initial(reagent.name)
+ return reagent_list.Join(", ")
+
+
+/obj/structure/microscope/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("eject_petridish")
+ if(!current_dish)
+ return FALSE
+ current_dish.forceMove(get_turf(src))
+ current_dish = null
+ . = TRUE
+ update_icon()
diff --git a/code/modules/research/xenobiology/vatgrowing/petri_dish.dm b/code/modules/research/xenobiology/vatgrowing/petri_dish.dm
new file mode 100644
index 00000000000..23c42383099
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/petri_dish.dm
@@ -0,0 +1,37 @@
+///Holds a biological sample which can then be put into the growing vat
+/obj/item/petri_dish
+ name = "petri dish"
+ desc = "This makes you feel well-cultured."
+ icon = 'icons/obj/xenobiology/vatgrowing.dmi'
+ icon_state = "petri_dish"
+ w_class = WEIGHT_CLASS_TINY
+ ///The sample stored on the dish
+ var/datum/biological_sample/sample
+
+/obj/item/petri_dish/Destroy()
+ . = ..()
+ QDEL_NULL(sample)
+
+/obj/item/petri_dish/examine(mob/user)
+ . = ..()
+ if(!sample)
+ return
+ . += "You can see the following micro-organisms:"
+ . += sample.GetAllDetails(user.research_scanner) //Get just the names nicely parsed.
+
+/obj/item/petri_dish/update_overlays()
+ . = ..()
+ if(!sample)
+ return
+ var/reagentcolor = sample.sample_color
+ var/mutable_appearance/base_overlay = mutable_appearance(icon, "petri_dish_overlay")
+ base_overlay.appearance_flags = RESET_COLOR
+ base_overlay.color = reagentcolor
+ . += base_overlay
+ var/mutable_appearance/overlay2 = mutable_appearance(icon, "petri_dish_overlay2")
+ . += overlay2
+
+/obj/item/petri_dish/proc/deposit_sample(user, var/datum/biological_sample/deposited_sample)
+ sample = deposited_sample
+ to_chat(user, "You deposit a sample into [src].")
+ update_icon()
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm b/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
new file mode 100644
index 00000000000..8a97da670b5
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/samples/_micro_organism.dm
@@ -0,0 +1,122 @@
+///A single type of growth.
+/datum/micro_organism
+ ///Name, shown on microscope
+ var/name = "Unknown fluids"
+ ///Desc, shown by science goggles
+ var/desc = "White fluid that tastes like salty coins and milk"
+
+///A "mob" cell. Can grow into a mob in a growing vat.
+/datum/micro_organism/cell_line
+ ///Our growth so far, needs to get up to 100
+ var/growth = 0
+ ///All the reagents required for letting this organism grow into whatever it should become
+ var/list/required_reagents
+ ///Reagents that further speed up growth, but aren't needed. Assoc list of reagent datum || bonus growth per tick
+ var/list/supplementary_reagents
+ ///Reagents that surpress growth. Assoc list of reagent datum || lost growth per tick
+ var/list/suppressive_reagents
+ ///This var modifies how much this micro_organism is affected by viruses. Higher is more slowdown
+ var/virus_suspectibility = 1
+ ///This var defines how much % the organism grows per process(), without modifiers, if you have all required reagents
+ var/growth_rate = 4
+ ///Resulting atoms from growing this cell line. List is assoc atom || amount
+ var/list/resulting_atoms = list()
+
+
+///Returns a short description of the cell line
+/datum/micro_organism/proc/GetDetails(show_needs)
+ return "[desc]\n"
+
+///Handles growth of the micro_organism. This only runs if the micro organism is in the growing vat. Reagents is the growing vats reagents
+/datum/micro_organism/cell_line/proc/HandleGrowth(var/obj/machinery/plumbing/growing_vat/vat)
+ if(!try_eat(vat.reagents))
+ return
+ growth = max(growth, growth + calculate_growth(vat.reagents, vat.biological_sample)) //Prevent you from having minus growth.
+ if(growth >= 100)
+ finish_growing(vat)
+
+///Tries to consume the required reagents. Can only do this if all of them are available. Reagents is the growing vats reagents
+/datum/micro_organism/cell_line/proc/try_eat(var/datum/reagents/reagents)
+ for(var/i in required_reagents)
+ if(!reagents.has_reagent(i))
+ return FALSE
+ for(var/i in required_reagents) //Delete the required reagents if used
+ reagents.remove_reagent(i, REAGENTS_METABOLISM)
+ return TRUE
+
+///Apply modifiers on growth_rate based on supplementary and supressive reagents. Reagents is the growing vats reagents
+/datum/micro_organism/cell_line/proc/calculate_growth(var/datum/reagents/reagents, var/datum/biological_sample/biological_sample)
+ . = growth_rate
+
+ //Handle growth based on supplementary reagents here.
+ for(var/i in supplementary_reagents)
+ if(!reagents.has_reagent(i, REAGENTS_METABOLISM))
+ continue
+ . += supplementary_reagents[i]
+ reagents.remove_reagent(i, REAGENTS_METABOLISM)
+
+ //Handle degrowth based on supressive reagents here.
+ for(var/i in suppressive_reagents)
+ if(!reagents.has_reagent(i, REAGENTS_METABOLISM))
+ continue
+ . += suppressive_reagents[i]
+ reagents.remove_reagent(i, REAGENTS_METABOLISM)
+
+ //Handle debuffing growth based on viruses here.
+ for(var/datum/micro_organism/cell_line/virus in biological_sample)
+ if(reagents.has_reagent(/datum/reagent/medicine/spaceacillin, REAGENTS_METABOLISM))
+ reagents.remove_reagent(/datum/reagent/medicine/spaceacillin, REAGENTS_METABOLISM)
+ continue //This virus is stopped, We have antiviral stuff
+ . -= virus_suspectibility
+
+///Called once a cell line reaches 100 growth. Then we check if any cell_line is too far so we can perform an epic fail roll
+/datum/micro_organism/cell_line/proc/finish_growing(var/obj/machinery/plumbing/growing_vat/vat)
+ var/risk = 0 //Penalty for failure, goes up based on how much growth the other cell_lines have
+
+ for(var/datum/micro_organism/cell_line/cell_line in vat.biological_sample.micro_organisms)
+ if(cell_line == src) //well duh
+ continue
+ if(cell_line.growth >= VATGROWING_DANGER_MINIMUM)
+ risk += cell_line.growth * 0.6 //60% per cell_line potentially. Kryson should probably tweak this
+ playsound(vat, 'sound/effects/splat.ogg', 50, TRUE)
+ if(rand(1, 100) < risk) //Fail roll!
+ fuck_up_growing(vat)
+ return FALSE
+ succeed_growing(vat)
+ return TRUE
+
+/datum/micro_organism/cell_line/proc/fuck_up_growing(var/obj/machinery/plumbing/growing_vat/vat)
+ vat.visible_message("The biological sample in [vat] seems to have dissipated!")
+ QDEL_NULL(vat.biological_sample) //Kill off the sample, we're done
+ if(prob(50))
+ new /obj/effect/gibspawner/generic(get_turf(vat)) //Spawn some gibs.
+
+
+/datum/micro_organism/cell_line/proc/succeed_growing(var/obj/machinery/plumbing/growing_vat/vat)
+ var/datum/effect_system/smoke_spread/smoke = new
+ smoke.set_up(0, vat.loc)
+ smoke.start()
+ for(var/created_thing in resulting_atoms)
+ for(var/x in 1 to resulting_atoms[created_thing])
+ var/atom/A = new created_thing(get_turf(vat))
+ vat.visible_message("[A] pops out of [vat]!")
+
+ QDEL_NULL(vat.biological_sample) //Kill off the sample, we're done
+
+///Overriden to show more info like needs, supplementary and supressive reagents
+/datum/micro_organism/cell_line/GetDetails(show_needs)
+ . = ..()
+ if(show_needs)
+ . += ReturnReagentText("It requires:", required_reagents)
+ . += ReturnReagentText("It likes:", supplementary_reagents)
+ . += ReturnReagentText("It hates:", suppressive_reagents)
+
+///Return a nice list of all the reagents in a specific category with a specific prefix. This needs to be reworked because the formatting sucks ass.
+/datum/micro_organism/cell_line/proc/ReturnReagentText(var/prefix_text = "It requires:", var/list/reagentlist)
+ if(!reagentlist.len)
+ return
+ var/all_reagents_text
+ for(var/i in reagentlist)
+ var/datum/reagent/R = i
+ all_reagents_text += " - [initial(R.name)]\n"
+ return "[prefix_text]\n[all_reagents_text]"
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/_sample.dm b/code/modules/research/xenobiology/vatgrowing/samples/_sample.dm
new file mode 100644
index 00000000000..2b1fc9a4ced
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/samples/_sample.dm
@@ -0,0 +1,44 @@
+///This datum is a simple holder for the micro_organisms in a sample.
+/datum/biological_sample
+ ///List of all micro_organisms in the sample
+ var/list/micro_organisms = list()
+ ///Prevents someone from stacking too many layers onto a swabber
+ var/sample_layers = 1
+ ///Picked from a specific group of colors, limited to a specific group.
+ var/sample_color = SAMPLE_YELLOW
+
+///Gets info from each of it's micro_organisms.
+/datum/biological_sample/proc/GetAllDetails(show_needs)
+ var/info
+ for(var/i in micro_organisms)
+ var/datum/micro_organism/MO = i
+ info += MO.GetDetails(show_needs)
+ return info
+
+///Generate a sample from a specific weighted list, and a specific amount of cell line with a chance for a virus
+/datum/biological_sample/proc/GenerateSample(cell_line_define, virus_define, cell_line_amount, virus_chance)
+ sample_color = pick(GLOB.xeno_sample_colors)
+
+ var/list/temp_weight_list = GLOB.cell_line_tables[cell_line_define].Copy() //Temp list to prevent double picking
+ for(var/i in 1 to cell_line_amount)
+ var/datum/micro_organism/chosen_type = pickweight(temp_weight_list)
+ temp_weight_list -= chosen_type
+ micro_organisms += new chosen_type
+ if(virus_chance)
+ if(!GLOB.cell_virus_tables[virus_define])
+ return
+ var/datum/micro_organism/chosen_type = pickweight(GLOB.cell_virus_tables[virus_define])
+ micro_organisms += new chosen_type
+
+///Takes another sample and merges it into use. This can cause one very big sample but we limit it to 3 layers.
+/datum/biological_sample/proc/Merge(var/datum/biological_sample/other_sample)
+ if(sample_layers >= 3)//No more than 3 layers, at that point you're entering danger zone.
+ return FALSE
+ micro_organisms += other_sample.micro_organisms
+ qdel(other_sample)
+ return TRUE
+
+///Call HandleGrowth on all our microorganisms.
+/datum/biological_sample/proc/HandleGrowth(var/obj/machinery/plumbing/growing_vat/vat)
+ for(var/datum/micro_organism/cell_line/organism in micro_organisms) //Types because we don't grow viruses.
+ organism.HandleGrowth(vat)
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
new file mode 100644
index 00000000000..2f9e3019dba
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
@@ -0,0 +1,377 @@
+#define VAT_GROWTH_RATE 4
+
+////////////////////////////////
+//// VERTEBRATES ////
+////////////////////////////////
+
+/datum/micro_organism/cell_line/mouse //nuisance cell line designed to complicate the growing of animal type cell lines.
+ desc = "Murine cells"
+ required_reagents = list(/datum/reagent/consumable/nutriment/protein)
+ supplementary_reagents = list(
+ /datum/reagent/growthserum = 2,
+ /datum/reagent/liquidgibs = 2,
+ /datum/reagent/consumable/cornoil = 2,
+ /datum/reagent/consumable/nutriment = 1,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ /datum/reagent/consumable/sugar = 1,
+ /datum/reagent/consumable/cooking_oil = 1,
+ /datum/reagent/consumable/rice = 1,
+ /datum/reagent/consumable/eggyolk = 1)
+
+ suppressive_reagents = list(
+ /datum/reagent/toxin/heparin = -6,
+ /datum/reagent/consumable/astrotame = -4, //Saccarin gives rats cancer.
+ /datum/reagent/consumable/ethanol/rubberneck = -3,
+ /datum/reagent/consumable/grey_bull = -1)
+
+ virus_suspectibility = 2
+ growth_rate = VAT_GROWTH_RATE
+ resulting_atoms = list(/mob/living/simple_animal/mouse = 2)
+
+/datum/micro_organism/cell_line/chicken //basic cell line designed as a good source of protein and eggyolk.
+ desc = "Galliform skin cells."
+ required_reagents = list(/datum/reagent/consumable/nutriment/protein)
+ supplementary_reagents = list(
+ /datum/reagent/consumable/rice = 4,
+ /datum/reagent/growthserum = 3,
+ /datum/reagent/consumable/eggyolk = 1,
+ /datum/reagent/consumable/nutriment/vitamin = 2)
+
+ suppressive_reagents = list(/datum/reagent/fuel/oil = -4,
+ /datum/reagent/toxin = -2)
+
+ virus_suspectibility = 1
+ growth_rate = VAT_GROWTH_RATE
+ resulting_atoms = list(/mob/living/simple_animal/chicken = 1)
+
+/datum/micro_organism/cell_line/cow
+ desc = "Bovine stem cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/consumable/nutriment,
+ /datum/reagent/cellulose)
+
+ supplementary_reagents = list(
+ /datum/reagent/growthserum = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 2,
+ /datum/reagent/consumable/rice = 2,
+ /datum/reagent/consumable/flour = 1)
+
+ suppressive_reagents = list(/datum/reagent/toxin = -2,
+ /datum/reagent/toxin/carpotoxin = -5)
+
+ virus_suspectibility = 1
+ resulting_atoms = list(/mob/living/simple_animal/cow = 1)
+
+/datum/micro_organism/cell_line/cat
+ desc = "Feliform cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/liquidgibs
+ )
+ supplementary_reagents = list(
+ /datum/reagent/growthserum = 3,
+ /datum/reagent/consumable/nutriment/vitamin = 2,
+ /datum/reagent/medicine/oculine = 2,
+ /datum/reagent/consumable/milk = 1) //milkies
+ suppressive_reagents = list(
+ /datum/reagent/consumable/coco = -4,
+ /datum/reagent/consumable/hot_coco = -2,
+ /datum/reagent/consumable/chocolatepudding = -2,
+ /datum/reagent/consumable/milk/chocolate_milk = -1)
+
+ virus_suspectibility = 1.5
+ resulting_atoms = list(/mob/living/simple_animal/pet/cat = 1) //The basic cat mobs are all male, so you mightt need a gender swap potion if you want to fill the fortress with kittens.
+
+/datum/micro_organism/cell_line/corgi
+ desc = "Canid cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/liquidgibs)
+
+ supplementary_reagents = list(
+ /datum/reagent/growthserum = 3,
+ /datum/reagent/barbers_aid = 3,
+ /datum/reagent/consumable/nutriment/vitamin = 2)
+
+ suppressive_reagents = list(
+ /datum/reagent/consumable/garlic = -2,
+ /datum/reagent/consumable/tearjuice = -3,
+ /datum/reagent/consumable/coco = -2)
+ virus_suspectibility = 1
+ resulting_atoms = list(/mob/living/simple_animal/pet/dog/corgi = 1)
+
+/datum/micro_organism/cell_line/pug
+ desc = "Squat canid cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/liquidgibs)
+
+ supplementary_reagents = list(
+ /datum/reagent/growthserum = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 3)
+
+ suppressive_reagents = list(
+ /datum/reagent/consumable/garlic = -2,
+ /datum/reagent/consumable/tearjuice = -3,
+ /datum/reagent/consumable/coco = -2)
+
+ virus_suspectibility = 3
+ resulting_atoms = list(/mob/living/simple_animal/pet/dog/pug = 1)
+
+/datum/micro_organism/cell_line/bear //bears can't really compete directly with more powerful creatures, so i made it possible to grow them real fast.
+ desc = "Ursine cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/liquidgibs,
+ /datum/reagent/medicine/C2/instabitaluri) //Nuke this if the dispenser becomes xenobio meta.
+
+ supplementary_reagents = list(
+ /datum/reagent/consumable/honey = 8, //Hunny.
+ /datum/reagent/growthserum = 5,
+ /datum/reagent/medicine/morphine = 4, //morphine is a vital nutrient for space bears, but it is better as a supplemental for gameplay reasons.
+ /datum/reagent/consumable/nutriment/vitamin = 3)
+
+ suppressive_reagents = list(
+ /datum/reagent/consumable/condensedcapsaicin = -4, //bear mace, steal it from the sec checkpoint.
+ /datum/reagent/toxin/carpotoxin = -2,
+ /datum/reagent/medicine/insulin = -2) //depletes hunny.
+
+ virus_suspectibility = 2
+ resulting_atoms = list(/mob/living/simple_animal/hostile/bear = 1)
+
+/datum/micro_organism/cell_line/carp
+ desc = "Cyprinid cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/consumable/nutriment)
+
+ supplementary_reagents = list(
+ /datum/reagent/consumable/cornoil = 4, //Carp are oily fish
+ /datum/reagent/toxin/carpotoxin = 3,
+ /datum/reagent/consumable/cooking_oil = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 2)
+
+ suppressive_reagents = list(
+ /datum/reagent/toxin/bungotoxin = -6,
+ /datum/reagent/mercury = -4,
+ /datum/reagent/oxygen = -3)
+
+ virus_suspectibility = 2
+ resulting_atoms = list(/mob/living/simple_animal/hostile/carp = 1)
+
+/datum/micro_organism/cell_line/megacarp
+ desc = "Cartilaginous cyprinid cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/medicine/C2/instabitaluri,
+ /datum/reagent/consumable/nutriment)
+
+ supplementary_reagents = list(
+ /datum/reagent/consumable/cornoil = 4,
+ /datum/reagent/growthserum = 3,
+ /datum/reagent/toxin/carpotoxin = 2,
+ /datum/reagent/consumable/cooking_oil = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 2)
+
+ suppressive_reagents = list(
+ /datum/reagent/toxin/bungotoxin = -6,
+ /datum/reagent/oxygen = -3)
+
+ virus_suspectibility = 1
+ resulting_atoms = list(/mob/living/simple_animal/hostile/carp/megacarp = 1)
+
+/datum/micro_organism/cell_line/snake
+ desc = "Ophidic cells"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/liquidgibs)
+
+ supplementary_reagents = list(
+ /datum/reagent/growthserum = 3,
+ /datum/reagent/consumable/nutriment/peptides = 3,
+ /datum/reagent/consumable/eggyolk = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 2)
+
+ suppressive_reagents = list(
+ /datum/reagent/consumable/corn_syrup = -6,
+ /datum/reagent/sulfur = -3) //sulfur repels snakes according to professor google.
+
+ resulting_atoms = list(/mob/living/simple_animal/hostile/retaliate/poison/snake = 1)
+
+
+///////////////////////////////////////////
+/// SLIMES, OOZES & BLOBS ///
+//////////////////////////////////////////
+
+/datum/micro_organism/cell_line/slime
+ desc = "Slime particles"
+ required_reagents = list(/datum/reagent/consumable/nutriment/protein)
+
+ supplementary_reagents = list(
+ /datum/reagent/toxin/slimejelly = 2,
+ /datum/reagent/liquidgibs = 2,
+ /datum/reagent/consumable/enzyme = 1)
+
+ suppressive_reagents = list(
+ /datum/reagent/consumable/frostoil = -4,
+ /datum/reagent/cryostylane = -4,
+ /datum/reagent/medicine/morphine = -2,
+ /datum/reagent/consumable/ice = -2) //Brrr!
+
+ virus_suspectibility = 0
+ resulting_atoms = list(/mob/living/simple_animal/slime = 1)
+
+/datum/micro_organism/cell_line/blob_spore //shitty cell line to dilute the pool, feel free to make easier to grow if it doesn't interfer with growing the powerful mobs enough.
+ desc = "Immature blob spores"
+ required_reagents = list(/datum/reagent/consumable/nutriment/protein)
+
+ supplementary_reagents = list(
+ /datum/reagent/consumable/nutriment/vitamin = 3,
+ /datum/reagent/liquidgibs = 2,
+ /datum/reagent/sulfur = 2)
+
+ suppressive_reagents = list(
+ /datum/reagent/consumable/tinlux = -6,
+ /datum/reagent/napalm = -4)
+ virus_suspectibility = 0
+ resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobspore/independent = 2) //These are useless so we might as well spawn 2.
+
+/datum/micro_organism/cell_line/blobbernaut
+ desc = "Blobular myocytes"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/medicine/C2/instabitaluri,
+ /datum/reagent/sulfur) //grind flares to get this
+
+ supplementary_reagents = list(
+ /datum/reagent/growthserum = 3,
+ /datum/reagent/consumable/nutriment/vitamin = 2,
+ /datum/reagent/liquidgibs = 2,
+ /datum/reagent/consumable/eggyolk = 2,
+ /datum/reagent/consumable/shamblers = 1)
+
+ suppressive_reagents = list(/datum/reagent/consumable/tinlux = -6)
+
+ virus_suspectibility = 0
+ resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobbernaut/independent = 1)
+
+/datum/micro_organism/cell_line/gelatinous_cube
+ desc = "Cubic ooze particles"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/toxin/slimejelly,
+ /datum/reagent/yuck,
+ /datum/reagent/consumable/enzyme) //Powerful enzymes helps the cube digest prey.
+
+ supplementary_reagents = list(
+ /datum/reagent/water/hollowwater = 4,
+ /datum/reagent/consumable/corn_syrup = 3,
+ /datum/reagent/gold = 2, //This is why they eat so many adventurers.
+ /datum/reagent/consumable/nutriment/peptides = 2,
+ /datum/reagent/consumable/potato_juice = 1,
+ /datum/reagent/liquidgibs = 1,
+ /datum/reagent/consumable/nutriment/vitamin = 1)
+
+ suppressive_reagents = list(
+ /datum/reagent/toxin/minttoxin = -3,
+ /datum/reagent/consumable/frostoil = -2,
+ /datum/reagent/consumable/ice = -1)
+ virus_suspectibility = 0
+ resulting_atoms = list(/mob/living/simple_animal/hostile/ooze/gelatinous = 1)
+
+/datum/micro_organism/cell_line/sholean_grapes
+ desc = "Globular ooze particles"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/toxin/slimejelly,
+ /datum/reagent/yuck,
+ /datum/reagent/consumable/vitfro)
+
+ supplementary_reagents = list(
+ /datum/reagent/medicine/omnizine = 4,
+ /datum/reagent/consumable/nutriment/peptides = 3,
+ /datum/reagent/consumable/corn_syrup = 2,
+ /datum/reagent/consumable/ethanol/squirt_cider = 2,
+ /datum/reagent/consumable/doctor_delight = 1,
+ /datum/reagent/medicine/salglu_solution = 1,
+ /datum/reagent/liquidgibs = 1,
+ /datum/reagent/consumable/nutriment/vitamin = 1)
+
+ suppressive_reagents = list(
+ /datum/reagent/toxin/carpotoxin = -3,
+ /datum/reagent/toxin/coffeepowder = -2,
+ /datum/reagent/consumable/frostoil = -2,
+ /datum/reagent/consumable/ice = -1)
+ virus_suspectibility = 0
+ resulting_atoms = list(/mob/living/simple_animal/hostile/ooze/grapes = 1)
+
+////////////////////
+//// MISC ////
+////////////////////
+/datum/micro_organism/cell_line/cockroach //nuisance cell line designed to complicate the growing of slime type cell lines.
+ desc = "Blattodeoid anthropod cells"
+ required_reagents = list(/datum/reagent/consumable/nutriment/protein)
+ supplementary_reagents = list(
+ /datum/reagent/yuck = 4,
+ /datum/reagent/growthserum = 2,
+ /datum/reagent/toxin/slimejelly = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 1)
+
+ suppressive_reagents = list(
+ /datum/reagent/toxin/pestkiller = -2,
+ /datum/reagent/consumable/poisonberryjuice = -4,
+ /datum/reagent/consumable/ethanol/bug_spray = -4)
+
+ virus_suspectibility = 0
+ resulting_atoms = list(/mob/living/simple_animal/hostile/cockroach = 5)
+
+/datum/micro_organism/cell_line/pine
+ desc = "Coniferous plant cells"
+ required_reagents = list(
+ /datum/reagent/ammonia,
+ /datum/reagent/ash,
+ /datum/reagent/plantnutriment/robustharvestnutriment) //A proper source of phosphorous like would be thematically more appropriate but this is what we have.
+
+ supplementary_reagents = list(
+ /datum/reagent/saltpetre = 5,
+ /datum/reagent/carbondioxide = 2,
+ /datum/reagent/consumable/nutriment = 2,
+ /datum/reagent/consumable/space_cola = 2, //A little extra phosphorous
+ /datum/reagent/water/holywater = 2,
+ /datum/reagent/water = 1,
+ /datum/reagent/cellulose = 1)
+
+ suppressive_reagents = list(/datum/reagent/toxin/plantbgone = -8)
+ virus_suspectibility = 1
+ resulting_atoms = list(/mob/living/simple_animal/hostile/tree = 1)
+
+/datum/micro_organism/cell_line/vat_beast
+ desc = "Hypergenic xenocytes"
+ required_reagents = list(
+ /datum/reagent/consumable/nutriment/protein,
+ /datum/reagent/consumable/nutriment/vitamin,
+ /datum/reagent/consumable/nutriment/peptides,
+ /datum/reagent/consumable/liquidelectricity,
+ /datum/reagent/growthserum,
+ /datum/reagent/yuck)
+
+ supplementary_reagents = list(
+ /datum/reagent/medicine/rezadone = 3,
+ /datum/reagent/consumable/entpoly = 3,
+ /datum/reagent/consumable/red_queen = 2,
+ /datum/reagent/consumable/peachjuice = 2,
+ /datum/reagent/uranium = 1,
+ /datum/reagent/liquidgibs = 1)
+
+ suppressive_reagents = list(
+ /datum/reagent/consumable/sodiumchloride = -3,
+ /datum/reagent/medicine/C2/syriniver = -2)
+ virus_suspectibility = 0.5
+ resulting_atoms = list(/mob/living/simple_animal/hostile/vatbeast = 1)
+
+/datum/micro_organism/cell_line/vat_beast/succeed_growing(obj/machinery/plumbing/growing_vat/vat)
+ . = ..()
+ qdel(vat)
+
+#undef VAT_GROWTH_RATE
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/viruses/_virus.dm b/code/modules/research/xenobiology/vatgrowing/samples/viruses/_virus.dm
new file mode 100644
index 00000000000..d33e2243fb9
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/samples/viruses/_virus.dm
@@ -0,0 +1,3 @@
+///A micro_organism that supports the ability to be converted to a real virus, allowing virology to get new symptoms
+/datum/micro_organism/virus
+ desc = "A virus of unknown origin"
diff --git a/code/modules/research/xenobiology/vatgrowing/swab.dm b/code/modules/research/xenobiology/vatgrowing/swab.dm
new file mode 100644
index 00000000000..38e3f46dc5e
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/swab.dm
@@ -0,0 +1,19 @@
+///Tool capable of taking biological samples from mobs
+/obj/item/swab
+ name = "swab"
+ desc = "Some men use these for different reasons."
+ icon = 'icons/obj/xenobiology/vatgrowing.dmi'
+ icon_state = "swab"
+ w_class = WEIGHT_CLASS_TINY
+
+///Adds the swabbing component to the biopsy tool
+/obj/item/swab/Initialize()
+ . = ..()
+ AddComponent(/datum/component/swabbing, TRUE, TRUE, FALSE, null, CALLBACK(src, .proc/update_swab_icon), max_items = 1)
+
+/obj/item/swab/proc/update_swab_icon(overlays, var/list/swabbed_items)
+ if(LAZYLEN(swabbed_items))
+ var/datum/biological_sample/sample = LAZYACCESS(swabbed_items, 1) //Use the first one as our target
+ var/mutable_appearance/swab_overlay = mutable_appearance(icon, "swab_[sample.sample_color]")
+ swab_overlay.appearance_flags = RESET_COLOR
+ overlays += swab_overlay
diff --git a/code/modules/research/xenobiology/vatgrowing/vatgrower.dm b/code/modules/research/xenobiology/vatgrowing/vatgrower.dm
new file mode 100644
index 00000000000..88a211c51d1
--- /dev/null
+++ b/code/modules/research/xenobiology/vatgrowing/vatgrower.dm
@@ -0,0 +1,78 @@
+///Used to make mobs from microbiological samples. Grow grow grow.
+/obj/machinery/plumbing/growing_vat
+ name = "growing vat"
+ desc = "Tastes just like the chef's soup."
+ icon_state = "growing_vat"
+ buffer = 200
+ ///List of all microbiological samples in this soup.
+ var/datum/biological_sample/biological_sample
+
+///Add that sexy demnand component
+/obj/machinery/plumbing/growing_vat/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt)
+
+///When we process, we make use of our reagents to try and feed the samples we have.
+/obj/machinery/plumbing/growing_vat/process()
+ if(!is_operational() && !biological_sample)
+ return
+ if(biological_sample)
+ biological_sample.HandleGrowth(src)
+ if(prob(10))
+ playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE)
+ audible_message(pick(list("[src] grumbles!", "[src] makes a splashing noise!", "[src] sloshes!")))
+
+///Handles the petri dish depositing into the vat.
+/obj/machinery/plumbing/growing_vat/attacked_by(obj/item/I, mob/living/user)
+ if(!istype(I, /obj/item/petri_dish))
+ return ..()
+
+ var/obj/item/petri_dish/petri = I
+
+ if(!petri.sample)
+ return ..()
+
+ if(biological_sample)
+ to_chat(user, "There is already a sample in the vat!")
+ return
+ deposit_sample(user, petri)
+
+///Creates a clone of the supplied sample and puts it in the vat
+/obj/machinery/plumbing/growing_vat/proc/deposit_sample(mob/user, var/obj/item/petri_dish/petri)
+ biological_sample = new
+ for(var/datum/micro_organism/m in petri.sample.micro_organisms)
+ biological_sample.micro_organisms += new m.type()
+ biological_sample.sample_layers = petri.sample.sample_layers
+ biological_sample.sample_color = petri.sample.sample_color
+ to_chat(user, "You put some of the sample in the vat!")
+ playsound(src, 'sound/effects/bubbles.ogg', 50, TRUE)
+ update_icon()
+
+///Adds text for when there is a sample in the vat
+/obj/machinery/plumbing/growing_vat/examine(mob/user)
+ . = ..()
+ if(biological_sample)
+ . += "It seems to have a sample in it!"
+
+/obj/machinery/plumbing/growing_vat/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
+ . = ..()
+ QDEL_NULL(biological_sample)
+
+///Call update icon when reagents change to update the reagent content icons
+/obj/machinery/plumbing/growing_vat/on_reagent_change(changetype)
+ update_icon()
+ return ..()
+
+///Adds overlays to show the reagent contents
+/obj/machinery/plumbing/growing_vat/update_overlays()
+ . = ..()
+ if(!reagents.total_volume)
+ return
+ var/reagentcolor = mix_color_from_reagents(reagents.reagent_list)
+ var/mutable_appearance/base_overlay = mutable_appearance(icon, "vat_reagent")
+ base_overlay.appearance_flags = RESET_COLOR
+ base_overlay.color = reagentcolor
+ . += base_overlay
+ if(biological_sample && is_operational())
+ var/mutable_appearance/bubbles_overlay = mutable_appearance(icon, "vat_bubbles")
+ . += bubbles_overlay
diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm
index 0bed6e3ccdc..0d9ffb6e7e6 100755
--- a/code/modules/surgery/organs/liver.dm
+++ b/code/modules/surgery/organs/liver.dm
@@ -14,6 +14,7 @@
decay_factor = STANDARD_ORGAN_DECAY
food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/iron = 5)
+ grind_results = list(/datum/reagent/consumable/nutriment/peptides = 5)
var/alcohol_tolerance = ALCOHOL_RATE//affects how much damage the liver takes from alcohol
var/toxTolerance = LIVER_DEFAULT_TOX_TOLERANCE//maximum amount of toxins the liver can just shrug off
diff --git a/tgui/packages/tgui/interfaces/Microscope.js b/tgui/packages/tgui/interfaces/Microscope.js
new file mode 100644
index 00000000000..a1793ed7b04
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Microscope.js
@@ -0,0 +1,108 @@
+import { useBackend, useSharedState } from '../backend';
+import { Button, LabeledList, NoticeBox, Section, Tabs } from '../components';
+import { Window } from '../layouts';
+
+export const Microscope = (props, context) => {
+ const { act, data } = useBackend(context);
+ const [tab, setTab] = useSharedState(context, 'tab', 1);
+ const {
+ has_dish,
+ cell_lines = [],
+ viruses = [],
+ } = data;
+ return (
+
+
+
+
+
+
+
+
+
+ setTab(1)}>
+ Micro-Organisms ({cell_lines.length})
+
+ setTab(2)}>
+ Viruses ({viruses.length})
+
+
+ {tab === 1 && (
+
+ )}
+ {tab === 2 && (
+
+ )}
+
+
+ );
+};
+
+const Organisms = (props, context) => {
+ const { cell_lines } = props;
+ const { act, data } = useBackend(context);
+ if (!cell_lines.length) {
+ return (
+
+ No micro-organisms found
+
+ );
+ }
+ return cell_lines.map(cell_line => {
+ return (
+
+
+
+ {cell_line.growth_rate}
+
+
+ {cell_line.suspectibility}
+
+
+ {cell_line.requireds}
+
+
+ {cell_line.supplementaries}
+
+
+ {cell_line.suppressives}
+
+
+
+ );
+ });
+};
+
+const Viruses = (props, context) => {
+ const { viruses } = props;
+ const { act } = useBackend(context);
+ if (!viruses.length) {
+ return (
+
+ No viruses found
+
+ );
+ }
+ return viruses.map(virus => {
+ return (
+
+ );
+ });
+};