Commit One

This commit is contained in:
Rykka
2020-07-19 16:24:56 -04:00
parent 8428ac45e5
commit 454cfda0b6
33 changed files with 8958 additions and 3594 deletions

View File

@@ -0,0 +1,728 @@
// This folder contains code that was originally ported from Apollo Station and then refactored/optimized/changed.
// Tracks precooked food to stop deep fried baked grilled grilled grilled diona nymph cereal.
/obj/item/weapon/reagent_containers/food/snacks
var/tmp/list/cooked = list()
// Root type for cooking machines. See following files for specific implementations.
/obj/machinery/appliance
name = "cooker"
desc = "You shouldn't be seeing this!"
icon = 'icons/obj/cooking_machines.dmi'
var/appliancetype = 0
density = 1
anchored = 1
use_power = USE_POWER_IDLE
idle_power_usage = 5 // Power used when turned on, but not processing anything
active_power_usage = 1000 // Power used when turned on and actively cooking something
var/initial_active_power_usage = 1000
var/cooking_power = 1
var/initial_cooking_power = 1
var/max_contents = 1 // Maximum number of things this appliance can simultaneously cook
var/on_icon // Icon state used when cooking.
var/off_icon // Icon state used when not cooking.
var/cooking // Whether or not the machine is currently operating.
var/cook_type // A string value used to track what kind of food this machine makes.
var/can_cook_mobs // Whether or not this machine accepts grabbed mobs.
var/mobdamagetype = BRUTE // Burn damage for cooking appliances, brute for cereal/candy
var/food_color // Colour of resulting food item.
var/cooked_sound = 'sound/machines/ding.ogg' // Sound played when cooking completes.
var/can_burn_food // Can the object burn food that is left inside?
var/burn_chance = 10 // How likely is the food to burn?
var/list/cooking_objs = list() // List of things being cooked
// If the machine has multiple output modes, define them here.
var/selected_option
var/list/output_options = list()
var/list/datum/recipe/available_recipes
var/container_type = null
var/combine_first = 0//If 1, this appliance will do combinaiton cooking before checking recipes
/obj/machinery/appliance/Initialize()
. = ..()
component_parts = list()
component_parts += /obj/item/weapon/circuitboard/cooking
component_parts += /obj/item/weapon/stock_parts/capacitor
component_parts += /obj/item/weapon/stock_parts/capacitor
component_parts += /obj/item/weapon/stock_parts/capacitor
component_parts += /obj/item/weapon/stock_parts/scanning_module
component_parts += /obj/item/weapon/stock_parts/matter_bin
component_parts += /obj/item/weapon/stock_parts/matter_bin
if(output_options.len)
verbs += /obj/machinery/appliance/proc/choose_output
if (!available_recipes)
available_recipes = new
for (var/type in subtypesof(/datum/recipe))
var/datum/recipe/test = new type
if ((appliancetype & test.appliance))
available_recipes += test
else
qdel(test)
/obj/machinery/appliance/Destroy()
for (var/a in cooking_objs)
var/datum/cooking_item/CI = a
qdel(CI.container)//Food is fragile, it probably doesnt survive the destruction of the machine
cooking_objs -= CI
qdel(CI)
return ..()
/obj/machinery/appliance/examine(var/mob/user)
..()
if(Adjacent(usr))
list_contents(user)
return 1
/obj/machinery/appliance/proc/list_contents(var/mob/user)
if (cooking_objs.len)
var/string = "Contains..."
for (var/a in cooking_objs)
var/datum/cooking_item/CI = a
string += "-\a [CI.container.label(null, CI.combine_target)], [report_progress(CI)]</br>"
usr << string
else
usr << span("notice","It is empty.")
/obj/machinery/appliance/proc/report_progress(var/datum/cooking_item/CI)
if (!CI || !CI.max_cookwork)
return null
if (!CI.cookwork)
return "It is cold."
var/progress = CI.cookwork / CI.max_cookwork
if (progress < 0.25)
return "It's barely started cooking."
if (progress < 0.75)
return span("notice","It's cooking away nicely.")
if (progress < 1)
return span("notice", "<b>It's almost ready!</b>")
var/half_overcook = (CI.overcook_mult - 1)*0.5
if (progress < 1+half_overcook)
return span("soghun","<b>It is done !</b>")
if (progress < CI.overcook_mult)
return span("warning","It looks overcooked, get it out!")
else
return span("danger","It is burning!!")
/obj/machinery/appliance/update_icon()
if (!stat && cooking_objs.len)
icon_state = on_icon
else
icon_state = off_icon
/obj/machinery/appliance/verb/toggle_power()
set name = "Toggle Power"
set category = "Object"
set src in view()
attempt_toggle_power(usr)
/obj/machinery/appliance/proc/attempt_toggle_power(mob/user)
if (!isliving(user))
return
if (!user.IsAdvancedToolUser())
user << "You lack the dexterity to do that!"
return
if (user.stat || user.restrained() || user.incapacitated())
return
if (!Adjacent(user) && !issilicon(user))
user << "You can't reach [src] from here."
return
if (stat & POWEROFF)//Its turned off
stat &= ~POWEROFF
use_power = 1
user.visible_message("[user] turns [src] on.", "You turn on [src].")
else //Its on, turn it off
stat |= POWEROFF
use_power = 0
user.visible_message("[user] turns [src] off.", "You turn off [src].")
playsound(src, 'sound/machines/click.ogg', 40, 1)
update_icon()
/obj/machinery/appliance/AICtrlClick(mob/user)
attempt_toggle_power(user)
/obj/machinery/appliance/proc/choose_output()
set src in view()
set name = "Choose output"
set category = "Object"
if (!isliving(usr))
return
if (!usr.IsAdvancedToolUser())
usr << "You lack the dexterity to do that!"
return
if (usr.stat || usr.restrained() || usr.incapacitated())
return
if (!Adjacent(usr) && !issilicon(usr))
usr << "You can't adjust the [src] from this distance, get closer!"
return
if(output_options.len)
var/choice = input("What specific food do you wish to make with \the [src]?") as null|anything in output_options+"Default"
if(!choice)
return
if(choice == "Default")
selected_option = null
usr << "<span class='notice'>You decide not to make anything specific with \the [src].</span>"
else
selected_option = choice
usr << "<span class='notice'>You prepare \the [src] to make \a [selected_option] with the next thing you put in. Try putting several ingredients in a container!</span>"
//Handles all validity checking and error messages for inserting things
/obj/machinery/appliance/proc/can_insert(var/obj/item/I, var/mob/user)
if (istype(I.loc, /mob/living/silicon))
return 0
else if (istype(I.loc, /obj/item/rig_module))
return 0
// We are trying to cook a grabbed mob.
var/obj/item/weapon/grab/G = I
if(istype(G))
if(!can_cook_mobs)
user << "<span class='warning'>That's not going to fit.</span>"
return 0
if(!isliving(G.affecting))
user << "<span class='warning'>You can't cook that.</span>"
return 0
return 2
if (!has_space(I))
user << "<span class='warning'>There's no room in [src] for that!</span>"
return 0
if (container_type && istype(I, container_type))
return 1
// We're trying to cook something else. Check if it's valid.
var/obj/item/weapon/reagent_containers/food/snacks/check = I
if(istype(check) && islist(check.cooked) && (cook_type in check.cooked))
user << "<span class='warning'>\The [check] has already been [cook_type].</span>"
return 0
else if(istype(check, /obj/item/weapon/reagent_containers/glass))
user << "<span class='warning'>That would probably break [src].</span>"
return 0
else if(istype(check, /obj/item/weapon/disk/nuclear))
user << "<span class='warning'>You can't cook that.</span>"
return 0
else if(!istype(check) && !istype(check, /obj/item/weapon/holder))
user << "<span class='warning'>That's not edible.</span>"
return 0
return 1
//This function is overridden by cookers that do stuff with containers
/obj/machinery/appliance/proc/has_space(var/obj/item/I)
if (cooking_objs.len >= max_contents)
return 0
else return 1
/obj/machinery/appliance/attackby(var/obj/item/I, var/mob/user)
if(!cook_type || (stat & (BROKEN)))
user << "<span class='warning'>\The [src] is not working.</span>"
return
var/result = can_insert(I, user)
if(!result)
if(default_deconstruction_screwdriver(user, I))
return
else if(default_part_replacement(user, I))
return
else
return
if(result == 2)
var/obj/item/weapon/grab/G = I
if (G && istype(G) && G.affecting)
cook_mob(G.affecting, user)
return
//From here we can start cooking food
add_content(I, user)
update_icon()
//Override for container mechanics
/obj/machinery/appliance/proc/add_content(var/obj/item/I, var/mob/user)
if(!user.unEquip(I))
return
var/datum/cooking_item/CI = has_space(I)
if (istype(I, /obj/item/weapon/reagent_containers/cooking_container) && CI == 1)
var/obj/item/weapon/reagent_containers/cooking_container/CC = I
CI = new /datum/cooking_item/(CC)
I.forceMove(src)
cooking_objs.Add(CI)
user.visible_message("<span class='notice'>\The [user] puts \the [I] into \the [src].</span>")
if (CC.check_contents() == 0)//If we're just putting an empty container in, then dont start any processing.
return
else
if (CI && istype(CI))
I.forceMove(CI.container)
else //Something went wrong
return
if (selected_option)
CI.combine_target = selected_option
// We can actually start cooking now.
user.visible_message("<span class='notice'>\The [user] puts \the [I] into \the [src].</span>")
get_cooking_work(CI)
cooking = 1
return CI
/obj/machinery/appliance/proc/get_cooking_work(var/datum/cooking_item/CI)
for (var/obj/item/J in CI.container)
oilwork(J, CI)
for (var/r in CI.container.reagents.reagent_list)
var/datum/reagent/R = r
if (istype(R, /datum/reagent/nutriment))
CI.max_cookwork += R.volume *2//Added reagents contribute less than those in food items due to granular form
//Nonfat reagents will soak oil
if (!istype(R, /datum/reagent/nutriment/triglyceride))
CI.max_oil += R.volume * 0.25
else
CI.max_cookwork += R.volume
CI.max_oil += R.volume * 0.10
//Rescaling cooking work to avoid insanely long times for large things
var/buffer = CI.max_cookwork
CI.max_cookwork = 0
var/multiplier = 1
var/step = 4
while (buffer > step)
buffer -= step
CI.max_cookwork += step*multiplier
multiplier *= 0.95
CI.max_cookwork += buffer*multiplier
//Just a helper to save code duplication in the above
/obj/machinery/appliance/proc/oilwork(var/obj/item/I, var/datum/cooking_item/CI)
var/obj/item/weapon/reagent_containers/food/snacks/S = I
var/work = 0
if (istype(S))
if (S.reagents)
for (var/r in S.reagents.reagent_list)
var/datum/reagent/R = r
if (istype(R, /datum/reagent/nutriment))
work += R.volume *3//Core nutrients contribute much more than peripheral chemicals
//Nonfat reagents will soak oil
if (!istype(R, /datum/reagent/nutriment/triglyceride))
CI.max_oil += R.volume * 0.35
else
work += R.volume
CI.max_oil += R.volume * 0.15
else if(istype(I, /obj/item/weapon/holder))
var/obj/item/weapon/holder/H = I
if (H.held_mob)
work += (H.held_mob.mob_size * H.held_mob.mob_size * 2)+2
CI.max_cookwork += work
//Called every tick while we're cooking something
/obj/machinery/appliance/proc/do_cooking_tick(var/datum/cooking_item/CI)
if (!istype(CI) || !CI.max_cookwork)
return 0
var/was_done = 0
if (CI.cookwork >= CI.max_cookwork)
was_done = 1
CI.cookwork += cooking_power
if (!was_done && CI.cookwork >= CI.max_cookwork)
//If cookwork has gone from above to below 0, then this item finished cooking
finish_cooking(CI)
else if (!CI.burned && CI.cookwork > CI.max_cookwork * CI.overcook_mult)
burn_food(CI)
// Gotta hurt.
for(var/obj/item/weapon/holder/H in CI.container.contents)
var/mob/living/M = H.held_mob
if (M)
M.apply_damage(rand(1,3), mobdamagetype, "chest")
return 1
/obj/machinery/appliance/process()
if (cooking_power > 0 && cooking)
for (var/i in cooking_objs)
do_cooking_tick(i)
/obj/machinery/appliance/proc/finish_cooking(var/datum/cooking_item/CI)
src.visible_message("<span class='notice'>\The [src] pings!</span>")
if(cooked_sound)
playsound(get_turf(src), cooked_sound, 50, 1)
//Check recipes first, a valid recipe overrides other options
var/datum/recipe/recipe = null
var/atom/C = null
if (CI.container)
C = CI.container
else
C = src
recipe = select_recipe(available_recipes,C)
if (recipe)
CI.result_type = 4//Recipe type, a specific recipe will transform the ingredients into a new food
var/list/results = recipe.make_food(C)
var/obj/temp = new /obj(src) //To prevent infinite loops, all results will be moved into a temporary location so they're not considered as inputs for other recipes
for (var/atom/movable/AM in results)
AM.forceMove(temp)
//making multiple copies of a recipe from one container. For example, tons of fries
while (select_recipe(available_recipes,C) == recipe)
var/list/TR = list()
TR += recipe.make_food(C)
for (var/atom/movable/AM in TR) //Move results to buffer
AM.forceMove(temp)
results += TR
for (var/r in results)
var/obj/item/weapon/reagent_containers/food/snacks/R = r
R.forceMove(C) //Move everything from the buffer back to the container
R.cooked |= cook_type
QDEL_NULL(temp) //delete buffer object
. = 1 //None of the rest of this function is relevant for recipe cooking
else if(CI.combine_target)
CI.result_type = 3//Combination type. We're making something out of our ingredients
. = combination_cook(CI)
else
//Otherwise, we're just doing standard modification cooking. change a color + name
for (var/obj/item/i in CI.container)
modify_cook(i, CI)
//Final step. Cook function just cooks batter for now.
for (var/obj/item/weapon/reagent_containers/food/snacks/S in CI.container)
S.cook()
//Combination cooking involves combining the names and reagents of ingredients into a predefined output object
//The ingredients represent flavours or fillings. EG: donut pizza, cheese bread
/obj/machinery/appliance/proc/combination_cook(var/datum/cooking_item/CI)
var/cook_path = output_options[CI.combine_target]
var/list/words = list()
var/list/cooktypes = list()
var/datum/reagents/buffer = new /datum/reagents(1000)
var/totalcolour
for (var/obj/item/I in CI.container)
var/obj/item/weapon/reagent_containers/food/snacks/S
if (istype(I, /obj/item/weapon/holder))
S = create_mob_food(I, CI)
else if (istype(I, /obj/item/weapon/reagent_containers/food/snacks))
S = I
if (!S)
continue
words |= text2list(S.name," ")
cooktypes |= S.cooked
if (S.reagents && S.reagents.total_volume > 0)
if (S.filling_color)
if (!totalcolour || !buffer.total_volume)
totalcolour = S.filling_color
else
var/t = buffer.total_volume + S.reagents.total_volume
t = buffer.total_volume / y
totalcolour = BlendRGB(totalcolour, S.filling_color, t)
//Blend colours in order to find a good filling color
S.reagents.trans_to_holder(buffer, S.reagents.total_volume)
//Cleanup these empty husk ingredients now
if (I)
qdel(I)
if (S)
qdel(S)
CI.container.reagents.trans_to_holder(buffer, CI.container.reagents.total_volume)
var/obj/item/weapon/reagent_containers/food/snacks/result = new cook_path(CI.container)
buffer.trans_to(result, buffer.total_volume)
//Filling overlay
var/image/I = image(result.icon, "[result.icon_state]_filling")
I.color = totalcolour
result.add_overlay(I)
result.filling_color = totalcolour
//Set the name.
words -= list("and", "the", "in", "is", "bar", "raw", "sticks", "boiled", "fried", "deep", "-o-", "warm", "two", "flavored")
//Remove common connecting words and unsuitable ones from the list. Unsuitable words include those describing
//the shape, cooked-ness/temperature or other state of an ingredient which doesn't apply to the finished product
words.Remove(result.name)
shuffle(words)
var/num = 6 //Maximum number of words
while (num > 0)
num--
if (!words.len)
break
//Add prefixes from the ingredients in a random order until we run out or hit limit
result.name = "[pop(words)] [result.name]"
//This proc sets the size of the output result
result.update_icon()
return result
//Helper proc for standard modification cooking
/obj/machinery/appliance/proc/modify_cook(var/obj/item/input, var/datum/cooking_item/CI)
var/obj/item/weapon/reagent_containers/food/snacks/result
if (istype(input, /obj/item/weapon/holder))
result = create_mob_food(input, CI)
else if (istype(input, /obj/item/weapon/reagent_containers/food/snacks))
result = input
else
//Nonviable item
return
if (!result)
return
result.cooked |= cook_type
// Set icon and appearance.
change_product_appearance(result, CI)
// Update strings.
change_product_strings(result, CI)
/obj/machinery/appliance/proc/burn_food(var/datum/cooking_item/CI)
// You dun goofed.
CI.burned = 1
CI.container.clear()
new /obj/item/weapon/reagent_containers/food/snacks/badrecipe(CI.container)
// Produce nasty smoke.
visible_message("<span class='danger'>\The [src] vomits a gout of rancid smoke!</span>")
var/datum/effect/effect/system/smoke_spread/bad/smoke = new /datum/effect/effect/system/smoke_spread/bad
smoke.attach(src)
smoke.set_up(10, 0, get_turf(src), 300)
smoke.start()
/obj/machinery/appliance/attack_hand(var/mob/user)
if (cooking_objs.len)
if (removal_menu(user))
return
else
..()
/obj/machinery/appliance/proc/removal_menu(var/mob/user)
if (can_remove_items(user))
var/list/menuoptions = list()
for (var/a in cooking_objs)
var/datum/cooking_item/CI = a
if (CI.container)
menuoptions[CI.container.label(menuoptions.len)] = CI
var/selection = input(user, "Which item would you like to remove?", "Remove ingredients") as null|anything in menuoptions
if (selection)
var/datum/cooking_item/CI = menuoptions[selection]
eject(CI, user)
update_icon()
return 1
return 0
/obj/machinery/appliance/proc/can_remove_items(var/mob/user)
if (!Adjacent(user))
return 0
if (isanimal(user))
return 0
return 1
/obj/machinery/appliance/proc/eject(var/datum/cooking_item/CI, var/mob/user = null)
var/obj/item/thing
var/delete = 1
var/status = CI.container.check_contents()
if (status == 1)//If theres only one object in a container then we extract that
thing = locate(/obj/item) in CI.container
delete = 0
else//If the container is empty OR contains more than one thing, then we must extract the container
thing = CI.container
if (!user || !user.put_in_hands(thing))
thing.forceMove(get_turf(src))
if (delete)
cooking_objs -= CI
qdel(CI)
else
CI.reset()//reset instead of deleting if the container is left inside
/obj/machinery/appliance/proc/cook_mob(var/mob/living/victim, var/mob/user)
return
/obj/machinery/appliance/proc/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product, var/datum/cooking_item/CI)
product.name = "[cook_type] [product.name]"
product.desc = "[product.desc]\nIt has been [cook_type]."
/obj/machinery/appliance/proc/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product, var/datum/cooking_item/CI)
if (!product.coating) //Coatings change colour through a new sprite
product.color = food_color
product.filling_color = food_color
/mob/living/proc/calculate_composition() // moved from devour.dm on aurora's side
if (!composition_reagent)//if no reagent has been set, then we'll set one
if (isSynthetic())
src.composition_reagent = "iron"
else
if(istype(src, /mob/living/carbon/human/diona) || istype(src, /mob/living/carbon/alien/diona))
src.composition_reagent = "nutriment" // diona are plants, not meat
else
src.composition_reagent = "protein"
if(istype(src, /mob/living/carbon/human))
var/mob/living/carbon/human/H = src
if(istype(H.species, /datum/species/diona))
src.composition_reagent = "nutriment"
//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*9
//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
//This function creates a food item which represents a dead mob
/obj/machinery/appliance/proc/create_mob_food(var/obj/item/weapon/holder/H, var/datum/cooking_item/CI)
if (!istype(H) || !H.held_mob)
qdel(H)
return null
var/mob/living/victim = H.held_mob
if (victim.stat != DEAD)
return null //Victim somehow survived the cooking, they do not become food
victim.calculate_composition()
var/obj/item/weapon/reagent_containers/food/snacks/variable/mob/result = new /obj/item/weapon/reagent_containers/food/snacks/variable/mob(CI.container)
result.w_class = victim.mob_size
result.reagents.add_reagent(victim.composition_reagent, victim.composition_reagent_quantity)
if (victim.reagents)
victim.reagents.trans_to_holder(result.reagents, victim.reagents.total_volume)
if (isanimal(victim))
var/mob/living/simple_animal/SA = victim
result.kitchen_tag = SA.kitchen_tag
result.appearance = victim
var/matrix/M = matrix()
M.Turn(45)
M.Translate(1,-2)
result.transform = M
// all done, now delete the old objects
H.held_mob = null
qdel(victim)
victim = null
qdel(H)
H = null
return result
/datum/cooking_item
var/max_cookwork
var/cookwork
var/overcook_mult = 5
var/result_type = 0
var/obj/item/weapon/reagent_containers/cooking_container/container = null
var/combine_target = null
//Result type is one of the following:
//0 unfinished, no result yet
//1 Standard modification cooking. eg Fried Donk Pocket, Baked wheat, etc
//2 Modification but with a new object that's an inert copy of the old. Generally used for deepfried mice
//3 Combination cooking, EG Donut Bread, Donk pocket pizza, etc
//4:Specific recipe cooking. EG: Turning raw potato sticks into fries
var/burned = 0
var/oil = 0
var/max_oil = 0//Used for fryers.
/datum/cooking_item/New(var/obj/item/I)
container = I
//This is called for containers whose contents are ejected without removing the container
/datum/cooking_item/proc/reset()
max_cookwork = 0
cookwork = 0
result_type = 0
burned = 0
max_oil = 0
oil = 0
combine_target = null
//Container is not reset
/obj/machinery/appliance/RefreshParts()
..()
var/scan_rating = 0
var/cap_rating = 0
for(var/obj/item/weapon/stock_parts/P in src.component_parts)
if(istype(P, /obj/item/weapon/stock_parts/scanning_module))
scan_rating += P.rating
else if(istype(P, /obj/item/weapon/stock_parts/capacitor))
cap_rating += P.rating
active_power_usage = initial(active_power_usage) - scan_rating*10
cooking_power = initial(cooking_power) + (scan_rating+cap_rating)/10
/obj/item/weapon/circuitboard/cooking
name = "kitchen appliance circuitry"
desc = "The circuitboard for many kitchen appliances. Not of much use."
origin_tech = list(TECH_MAGNET = 2, TECH_ENGINEERING = 2)
req_components = list(
/obj/item/weapon/stock_parts/capacitor = 3,
/obj/item/weapon/stock_parts/scanning_module = 1,
/obj/item/weapon/stock_parts/matter_bin = 2)

View File

@@ -1,261 +1,132 @@
// This folder contains code that was originally ported from Apollo Station and then refactored/optimized/changed.
/obj/machinery/appliance/cooker
var/temperature = T20C
var/min_temp = 80 + T0C //Minimum temperature to do any cooking
var/optimal_temp = 200 + T0C //Temperature at which we have 100% efficiency. efficiency is lowered on either side of this
var/optimal_power = 0.1//cooking power at 100%
// Tracks precooked food to stop deep fried baked grilled grilled grilled diona nymph cereal.
/obj/item/weapon/reagent_containers/food/snacks/var/list/cooked
var/loss = 1 //Temp lost per proc when equalising
var/resistance = 320000 //Resistance to heating. combines with active power usage to determine how long heating takes
// Root type for cooking machines. See following files for specific implementations.
/obj/machinery/cooker
name = "cooker"
desc = "You shouldn't be seeing this!"
icon = 'icons/obj/cooking_machines.dmi'
density = 1
anchored = 1
use_power = USE_POWER_IDLE
idle_power_usage = 5
var/light_x = 0
var/light_y = 0
cooking_power = 0
var/on_icon // Icon state used when cooking.
var/off_icon // Icon state used when not cooking.
var/cooking // Whether or not the machine is currently operating.
var/cook_type // A string value used to track what kind of food this machine makes.
var/cook_time = 200 // How many ticks the cooking will take.
var/can_cook_mobs // Whether or not this machine accepts grabbed mobs.
var/food_color // Colour of resulting food item.
var/cooked_sound // Sound played when cooking completes.
var/can_burn_food // Can the object burn food that is left inside?
var/burn_chance = 10 // How likely is the food to burn?
var/obj/item/cooking_obj // Holder for the currently cooking object.
// If the machine has multiple output modes, define them here.
var/selected_option
var/list/output_options = list()
/obj/machinery/cooker/Destroy()
if(cooking_obj)
qdel(cooking_obj)
cooking_obj = null
return ..()
/obj/machinery/cooker/proc/set_cooking(new_setting)
cooking = new_setting
icon_state = new_setting ? on_icon : off_icon
/obj/machinery/cooker/examine(mob/user)
/obj/machinery/appliance/cooker/examine(var/mob/user)
. = ..()
if(cooking_obj && Adjacent(user))
. += "You can see \a [cooking_obj] inside."
/obj/machinery/cooker/attackby(var/obj/item/I, var/mob/user)
if(!cook_type || (stat & (NOPOWER|BROKEN)))
to_chat(user, "<span class='warning'>\The [src] is not working.</span>")
return
if(cooking)
to_chat(user, "<span class='warning'>\The [src] is running!</span>")
return
if(default_unfasten_wrench(user, I, 20))
return
// We are trying to cook a grabbed mob.
var/obj/item/weapon/grab/G = I
if(istype(G))
if(!can_cook_mobs)
to_chat(user, "<span class='warning'>That's not going to fit.</span>")
return
if(!isliving(G.affecting))
to_chat(user, "<span class='warning'>You can't cook that.</span>")
return
cook_mob(G.affecting, user)
return
// We're trying to cook something else. Check if it's valid.
var/obj/item/weapon/reagent_containers/food/snacks/check = I
if(istype(check) && islist(check.cooked) && (cook_type in check.cooked))
to_chat(user, "<span class='warning'>\The [check] has already been [cook_type].</span>")
return 0
else if(istype(check, /obj/item/weapon/reagent_containers/glass))
to_chat(user, "<span class='warning'>That would probably break [src].</span>")
return 0
else if(istype(check, /obj/item/weapon/disk/nuclear))
to_chat(user, "Central Command would kill you if you [cook_type] that.")
return 0
else if(!istype(check) && !istype(check, /obj/item/weapon/holder) && !istype(check, /obj/item/organ)) //Gripper check has to go here, else it still just cuts it off. ~Mechoid
// Is it a borg using a gripper?
if(istype(check, /obj/item/weapon/gripper)) // Grippers. ~Mechoid.
var/obj/item/weapon/gripper/B = check //B, for Borg.
if(!B.wrapped)
to_chat(user, "\The [B] is not holding anything.")
return 0
if(.) //no need to duplicate adjacency check
if(!stat)
if (temperature < min_temp)
to_chat(user, span("warning", "\The [src] is still heating up and is too cold to cook anything yet."))
else
var/B_held = B.wrapped
to_chat(user, "You use \the [B] to put \the [B_held] into \the [src].")
return 0
to_chat(user, span("notice", "It is running at [round(get_efficiency(), 0.1)]% efficiency!"))
to_chat(user, "Temperature: [round(temperature - T0C, 0.1)]C / [round(optimal_temp - T0C, 0.1)]C")
else
to_chat(user, "<span class='warning'>That's not edible.</span>")
return 0
if(istype(I, /obj/item/organ))
var/obj/item/organ/O = I
if(O.robotic)
to_chat(user, "<span class='warning'>That would probably break [src].</span>")
return
// Gotta hurt.
if(istype(cooking_obj, /obj/item/weapon/holder))
for(var/mob/living/M in cooking_obj.contents)
M.apply_damage(rand(30,40), BURN, "chest")
// Not sure why a food item that passed the previous checks would fail to drop, but safety first. (Hint: Borg grippers. That is why. ~Mechoid.)
if(!user.unEquip(I) && !istype(user,/mob/living/silicon/robot))
return
// We can actually start cooking now.
user.visible_message("<span class='notice'>\The [user] puts \the [I] into \the [src].</span>")
cooking_obj = I
cooking_obj.forceMove(src)
set_cooking(TRUE)
icon_state = on_icon
// Doop de doo. Jeopardy theme goes here.
sleep(cook_time)
// Sanity checks.
if(!cooking_obj || cooking_obj.loc != src)
cooking_obj = null
icon_state = off_icon
set_cooking(FALSE)
return
// RIP slow-moving held mobs.
if(istype(cooking_obj, /obj/item/weapon/holder))
for(var/mob/living/M in cooking_obj.contents)
M.death()
qdel(M)
// Cook the food.
var/cook_path
if(selected_option && output_options.len)
cook_path = output_options[selected_option]
if(!cook_path)
cook_path = /obj/item/weapon/reagent_containers/food/snacks/variable
var/obj/item/weapon/reagent_containers/food/snacks/result = new cook_path(src) //Holy typepaths, Batman.
// Set icon and appearance.
change_product_appearance(result)
// Update strings.
change_product_strings(result)
// Copy reagents over. trans_to_obj must be used, as trans_to fails for snacks due to is_open_container() failing.
if(cooking_obj.reagents && cooking_obj.reagents.total_volume)
cooking_obj.reagents.trans_to_obj(result, cooking_obj.reagents.total_volume)
// Set cooked data.
var/obj/item/weapon/reagent_containers/food/snacks/food_item = cooking_obj
if(istype(food_item) && islist(food_item.cooked))
result.cooked = food_item.cooked.Copy()
to_chat(user, span("warning", "It is switched off."))
/obj/machinery/appliance/cooker/list_contents(var/mob/user)
if (cooking_objs.len)
var/string = "Contains...</br>"
var/num = 0
for (var/a in cooking_objs)
num++
var/datum/cooking_item/CI = a
if (CI && CI.container)
string += "- [CI.container.label(num)], [report_progress(CI)]</br>"
to_chat(user, string)
else
result.cooked = list()
result.cooked |= cook_type
to_chat(user, span("notice","It is empty."))
/obj/machinery/appliance/cooker/proc/get_efficiency()
//RefreshParts()
return (cooking_power / optimal_power) * 100
// Reset relevant variables.
qdel(cooking_obj)
src.visible_message("<span class='notice'>\The [src] pings!</span>")
if(cooked_sound)
playsound(src, cooked_sound, 50, 1)
/obj/machinery/appliance/cooker/New()
. = ..()
loss = (active_power_usage / resistance)*0.5
cooking_objs = list()
for (var/i = 0, i < max_contents, i++)
cooking_objs.Add(new /datum/cooking_item/(new container_type(src)))
cooking = 0
if(!can_burn_food)
icon_state = off_icon
set_cooking(FALSE)
result.forceMove(get_turf(src))
cooking_obj = null
update_icon() // this probably won't cause issues, but Aurora used SSIcons and queue_icon_update() instead
/obj/machinery/appliance/cooker/update_icon()
cut_overlays()
var/image/light
if (use_power == 2 && !stat)
light = image(icon, "light_on")
else
var/failed
var/overcook_period = max(FLOOR(cook_time/5, 1),1)
cooking_obj = result
var/count = overcook_period
while(1)
sleep(overcook_period)
count += overcook_period
if(!cooking || !result || result.loc != src)
failed = 1
else if(prob(burn_chance) || count == cook_time) //Fail before it has a chance to cook again.
// You dun goofed.
qdel(cooking_obj)
cooking_obj = new /obj/item/weapon/reagent_containers/food/snacks/badrecipe(src)
// Produce nasty smoke.
visible_message("<span class='danger'>\The [src] vomits a gout of rancid smoke!</span>")
var/datum/effect/effect/system/smoke_spread/bad/smoke = new /datum/effect/effect/system/smoke_spread/bad()
smoke.attach(src)
smoke.set_up(10, 0, usr.loc)
smoke.start()
failed = 1
if(failed)
set_cooking(FALSE)
icon_state = off_icon
break
/obj/machinery/cooker/attack_hand(var/mob/user)
if(cooking_obj && user.Adjacent(src)) //Fixes borgs being able to teleport food in these machines to themselves.
to_chat(user, "<span class='notice'>You grab \the [cooking_obj] from \the [src].</span>")
user.put_in_hands(cooking_obj)
set_cooking(FALSE)
cooking_obj = null
icon_state = off_icon
return
if(output_options.len)
if(cooking)
to_chat(user, "<span class='warning'>\The [src] is in use!</span>")
return
var/choice = input("What specific food do you wish to make with \the [src]?") as null|anything in output_options+"Default"
if(!choice)
return
if(choice == "Default")
selected_option = null
to_chat(user, "<span class='notice'>You decide not to make anything specific with \the [src].</span>")
else
selected_option = choice
to_chat(user, "<span class='notice'>You prepare \the [src] to make \a [selected_option].</span>")
light = image(icon, "light_off")
light.pixel_x = light_x
light.pixel_y = light_y
add_overlay(light)
/obj/machinery/appliance/cooker/process()
if (!stat)
heat_up()
else
var/turf/T = get_turf(src)
if (temperature > T.temperature)
equalize_temperature()
..()
/obj/machinery/appliance/cooker/power_change()
. = ..()
update_icon() // this probably won't cause issues, but Aurora used SSIcons and queue_icon_update() instead
/obj/machinery/appliance/cooker/proc/update_cooking_power()
var/temp_scale = 0
if(temperature > min_temp)
temp_scale = (temperature - min_temp) / (optimal_temp - min_temp) // If we're between min and optimal this will yield a value in the range 0-1
/obj/machinery/cooker/proc/cook_mob(var/mob/living/victim, var/mob/user)
return
/obj/machinery/cooker/proc/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product)
if(product.type == /obj/item/weapon/reagent_containers/food/snacks/variable) // Base type, generic.
product.name = "[cook_type] [cooking_obj.name]"
product.desc = "[cooking_obj.desc] It has been [cook_type]."
if(temp_scale > 1) // We're above optimal, efficiency goes down as we pass too much over it
if(temp_scale >= 2)
temp_scale = 0
else
temp_scale = 1 - (temp_scale - 1)
cooking_power = optimal_power * temp_scale
// RefreshParts()
/obj/machinery/appliance/cooker/proc/heat_up()
if(temperature < optimal_temp)
if(use_power == 1 && ((optimal_temp - temperature) > 5))
playsound(src, 'sound/machines/click.ogg', 20, 1)
use_power = 2.//If we're heating we use the active power
update_icon()
temperature += active_power_usage / resistance
update_cooking_power()
return 1
else
product.name = "[cooking_obj.name] [product.name]"
if(use_power == 2)
use_power = 1
playsound(src, 'sound/machines/click.ogg', 20, 1)
update_icon()
//We're holding steady. temperature falls more slowly
if(prob(25))
equalize_temperature()
return -1
/obj/machinery/cooker/proc/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product)
if(product.type == /obj/item/weapon/reagent_containers/food/snacks/variable) // Base type, generic.
product.appearance = cooking_obj
product.color = food_color
product.filling_color = food_color
/obj/machinery/appliance/cooker/proc/equalize_temperature()
temperature -= loss//Temperature will fall somewhat slowly
update_cooking_power()
// Make 'em into a corpse.
if(istype(cooking_obj, /obj/item/weapon/holder))
var/matrix/M = matrix()
M.Turn(90)
M.Translate(1,-6)
product.transform = M
//Cookers do differently, they use containers
/obj/machinery/appliance/cooker/has_space(var/obj/item/I)
if(istype(I, /obj/item/weapon/reagent_containers/cooking_container))
//Containers can go into an empty slot
if(cooking_objs.len < max_contents)
return 1
else
var/image/I = image(product.icon, "[product.icon_state]_filling")
if(istype(cooking_obj, /obj/item/weapon/reagent_containers/food/snacks))
var/obj/item/weapon/reagent_containers/food/snacks/S = cooking_obj
I.color = S.filling_color
if(!I.color)
I.color = food_color
product.overlays += I
//Any food items directly added need an empty container. A slot without a container cant hold food
for (var/datum/cooking_item/CI in cooking_objs)
if (CI.container.check_contents() == 0)
return CI
return 0
/obj/machinery/appliance/cooker/add_content(var/obj/item/I, var/mob/user)
var/datum/cooking_item/CI = ..()
if (CI && CI.combine_target)
to_chat(user, "\The [I] will be used to make a [selected_option]. Output selection is returned to default for future items.")
selected_option = null

View File

@@ -1,72 +1,160 @@
// Wrapper obj for cooked food. Appearance is set in the cooking code, not on spawn.
/obj/item/weapon/reagent_containers/food/snacks/variable
name = "cooked food"
icon = 'icons/obj/food_custom.dmi'
desc = "If you can see this description then something is wrong. Please report the bug on the tracker."
nutriment_amt = 5
bitesize = 2
/obj/item/weapon/reagent_containers/food/snacks/variable/pizza
name = "personal pizza"
desc = "A personalized pan pizza meant for only one person."
icon_state = "personal_pizza"
/obj/item/weapon/reagent_containers/food/snacks/variable/bread
name = "bread"
desc = "Tasty bread."
icon_state = "breadcustom"
/obj/item/weapon/reagent_containers/food/snacks/variable/pie
name = "pie"
desc = "Tasty pie."
icon_state = "piecustom"
/obj/item/weapon/reagent_containers/food/snacks/variable/cake
name = "cake"
desc = "A popular band."
icon_state = "cakecustom"
/obj/item/weapon/reagent_containers/food/snacks/variable/pocket
name = "hot pocket"
desc = "You wanna put a bangin- oh, nevermind."
icon_state = "donk"
/obj/item/weapon/reagent_containers/food/snacks/variable/kebab
name = "kebab"
desc = "Remove this!"
icon_state = "kabob"
/obj/item/weapon/reagent_containers/food/snacks/variable/waffles
name = "waffles"
desc = "Made with love."
icon_state = "waffles"
/obj/item/weapon/reagent_containers/food/snacks/variable/cookie
name = "cookie"
desc = "Sugar snap!"
icon_state = "cookie"
/obj/item/weapon/reagent_containers/food/snacks/variable/donut
name = "filled donut"
desc = "Donut eat this!" // kill me
icon_state = "donut"
/obj/item/weapon/reagent_containers/food/snacks/variable/jawbreaker
name = "flavored jawbreaker"
desc = "It's like cracking a molar on a rainbow."
icon_state = "jawbreaker"
/obj/item/weapon/reagent_containers/food/snacks/variable/candybar
name = "flavored chocolate bar"
desc = "Made in a factory downtown."
icon_state = "bar"
/obj/item/weapon/reagent_containers/food/snacks/variable/sucker
name = "flavored sucker"
desc = "Suck, suck, suck."
icon_state = "sucker"
/obj/item/weapon/reagent_containers/food/snacks/variable/jelly
name = "jelly"
desc = "All your friends will be jelly."
icon_state = "jellycustom"
// Wrapper obj for cooked food. Appearance is set in the cooking code, not on spawn.
/obj/item/weapon/reagent_containers/food/snacks/variable
name = "cooked food"
icon = 'icons/obj/food_custom.dmi'
desc = "If you can see this description then something is wrong. Please report the bug on the tracker."
bitesize = 2
var/size = 5 //The quantity of reagents which is considered "normal" for this kind of food
//These objects will change size depending on the ratio of reagents to this value
var/min_scale = 0.5
var/max_scale = 2
var/scale = 1
w_class = 2
var/prefix
/obj/item/weapon/reagent_containers/food/snacks/variable/initialize()
. = ..()
if (reagents)
reagents.maximum_volume = size*8 + 10
else
create_reagents(size*8 + 10)
/obj/item/weapon/reagent_containers/food/snacks/variable/update_icon()
if (reagents && reagents.total_volume)
var/ratio = reagents.total_volume / size
scale = ratio**(1/3) //Scaling factor is square root of desired area
scale = Clamp(scale, min_scale, max_scale)
else
scale = min_scale
var/matrix/M = matrix()
M.Scale(scale)
src.transform = M
w_class *= scale
if (!prefix)
if (scale == min_scale)
prefix = "tiny"
else if (scale <= 0.8)
prefix = "small"
else
if (scale >= 1.2)
prefix = "large"
if (scale >= 1.4)
prefix = "extra large"
if (scale >= 1.6)
prefix = "huge"
if (scale >= max_scale)
prefix = "massive"
name = "[prefix] [name]"
/obj/item/weapon/reagent_containers/food/snacks/variable/pizza
name = "personal pizza"
desc = "A personalized pan pizza meant for only one person."
icon_state = "personal_pizza"
size = 20
w_class = 3
/obj/item/weapon/reagent_containers/food/snacks/variable/bread
name = "bread"
desc = "Tasty bread."
icon_state = "breadcustom"
size = 40
w_class = 3
/obj/item/weapon/reagent_containers/food/snacks/variable/pie
name = "pie"
desc = "Tasty pie."
icon_state = "piecustom"
size = 25
/obj/item/weapon/reagent_containers/food/snacks/variable/cake
name = "cake"
desc = "A popular band."
icon_state = "cakecustom"
size = 40
w_class = 3
/obj/item/weapon/reagent_containers/food/snacks/variable/pocket
name = "hot pocket"
desc = "You wanna put a bangin- oh, nevermind."
icon_state = "donk"
size = 8
w_class = 1
/obj/item/weapon/reagent_containers/food/snacks/variable/kebab
name = "kebab"
desc = "Remove this!"
icon_state = "kabob"
size = 10
/obj/item/weapon/reagent_containers/food/snacks/variable/waffles
name = "waffles"
desc = "Made with love."
icon_state = "waffles"
size = 12
/obj/item/weapon/reagent_containers/food/snacks/variable/cookie
name = "cookie"
desc = "Sugar snap!"
icon_state = "cookie"
size = 6
w_class = 1
/obj/item/weapon/reagent_containers/food/snacks/variable/donut
name = "filled donut"
desc = "Donut eat this!" // kill me
icon_state = "donut"
size = 8
w_class = 1
/obj/item/weapon/reagent_containers/food/snacks/variable/jawbreaker
name = "flavored jawbreaker"
desc = "It's like cracking a molar on a rainbow."
icon_state = "jawbreaker"
size = 4
w_class = 1
/obj/item/weapon/reagent_containers/food/snacks/variable/candybar
name = "flavored chocolate bar"
desc = "Made in a factory downtown."
icon_state = "bar"
size = 6
w_class = 1
/obj/item/weapon/reagent_containers/food/snacks/variable/sucker
name = "flavored sucker"
desc = "Suck, suck, suck."
icon_state = "sucker"
size = 4
w_class = 1
/obj/item/weapon/reagent_containers/food/snacks/variable/jelly
name = "jelly"
desc = "All your friends will be jelly."
icon_state = "jellycustom"
size = 8
/obj/item/weapon/reagent_containers/food/snacks/variable/cereal
name = "cereal"
desc = "Crispy and flaky"
icon_state = "cereal_box"
size = 30
w_class = 3
/obj/item/weapon/reagent_containers/food/snacks/variable/cereal/initialize()
. =..()
name = pick(list("flakes", "krispies", "crunch", "pops", "O's", "crisp", "loops", "jacks", "clusters"))
/obj/item/weapon/reagent_containers/food/snacks/variable/mob
desc = "Poor little thing."
size = 5
w_class = 1
var/kitchen_tag = "animal"

View File

@@ -0,0 +1,141 @@
/*
The mixer subtype is used for the candymaker and cereal maker. They are similar to cookers but with a few
fundamental differences
1. They have a single container which cant be removed. it will eject multiple contents
2. Items can't be added or removed once the process starts
3. Items are all placed in the same container when added directly
4. They do combining mode only. And will always combine the entire contents of the container into an output
*/
/obj/machinery/appliance/mixer
max_contents = 1
stat = POWEROFF
cooking_power = 0.4
active_power_usage = 3000
idle_power_usage = 50
/obj/machinery/appliance/mixer/examine(var/mob/user)
..()
to_chat(user, "<span class='notice'>It is currently set to make a [selected_option]</span>")
/obj/machinery/appliance/mixer/New()
. = ..()
cooking_objs += new /datum/cooking_item(new /obj/item/weapon/reagent_containers/cooking_container(src))
cooking = 0
selected_option = pick(output_options)
//Mixers cannot-not do combining mode. So the default option is removed from this. A combine target must be chosen
/obj/machinery/appliance/mixer/choose_output()
set src in oview(1)
set name = "Choose output"
set category = "Object"
if (!isliving(usr))
return
if (!usr.IsAdvancedToolUser())
to_chat(user, "<span class='notice'>You can't operate [src].</span>")
return
if(output_options.len)
var/choice = input("What specific food do you wish to make with \the [src]?") as null|anything in output_options
if(!choice)
return
else
selected_option = choice
to_chat(user, "<span class='notice'>You prepare \the [src] to make \a [selected_option].</span>")
var/datum/cooking_item/CI = cooking_objs[1]
CI.combine_target = selected_option
/obj/machinery/appliance/mixer/has_space(var/obj/item/I)
var/datum/cooking_item/CI = cooking_objs[1]
if (!CI || !CI.container)
return 0
if (CI.container.can_fit(I))
return CI
return 0
/obj/machinery/appliance/mixer/can_remove_items(var/mob/user)
if (stat)
return 1
else
to_chat(user, "<span class='warning'>You can't remove ingredients while it's turned on! Turn it off first or wait for it to finish.</span>")
//Container is not removable
/obj/machinery/appliance/mixer/removal_menu(var/mob/user)
if (can_remove_items(user))
var/list/menuoptions = list()
for (var/a in cooking_objs)
var/datum/cooking_item/CI = a
if (CI.container)
if (!CI.container.check_contents())
to_chat(user, "There's nothing in [src] you can remove!")
return
for (var/obj/item/I in CI.container)
menuoptions[I.name] = I
var/selection = input(user, "Which item would you like to remove? If you want to remove chemicals, use an empty beaker.", "Remove ingredients") as null|anything in menuoptions
if (selection)
var/obj/item/I = menuoptions[selection]
if (!user || !user.put_in_hands(I))
I.forceMove(get_turf(src))
update_icon()
return 1
return 0
/obj/machinery/appliance/mixer/toggle_power()
set src in view()
set name = "Toggle Power"
set category = "Object"
var/datum/cooking_item/CI = cooking_objs[1]
if(!CI.container.check_contents())
to_chat("There's nothing in it! Add ingredients before turning [src] on!")
return
if(stat & POWEROFF)//Its turned off
stat &= ~POWEROFF
if(usr)
usr.visible_message("[usr] turns the [src] on", "You turn on \the [src].")
get_cooking_work(CI)
use_power = 2
else //Its on, turn it off
stat |= POWEROFF
use_power = 0
if(usr)
usr.visible_message("[usr] turns the [src] off", "You turn off \the [src].")
playsound(src, 'sound/machines/click.ogg', 40, 1)
update_icon()
/obj/machinery/appliance/mixer/can_insert(var/obj/item/I, var/mob/user)
if(!stat)
to_chat(user, "<span class='warning'>,You can't add items while \the [src] is running. Wait for it to finish or turn the power off to abort.</span>")
return 0
else
return ..()
/obj/machinery/appliance/mixer/finish_cooking(var/datum/cooking_item/CI)
..()
stat |= POWEROFF
playsound(src, 'sound/machines/click.ogg', 40, 1)
use_power = 0
CI.reset()
update_icon()
/obj/machinery/appliance/mixer/update_icon()
if (!stat)
icon_state = on_icon
else
icon_state = off_icon
/obj/machinery/appliance/mixer/process()
if (!stat)
for (var/i in cooking_objs)
do_cooking_tick(i)

View File

@@ -1,10 +1,12 @@
/obj/machinery/cooker/candy
/obj/machinery/appliance/mixer/candy
name = "candy machine"
desc = "Get yer candied cheese wheels here!"
icon_state = "mixer_off"
off_icon = "mixer_off"
on_icon = "mixer_on"
cook_type = "candied"
appliancetype = CANDYMAKER
cooking_power = 0.6
cooked_sound = 'sound/machines/ding.ogg'
output_options = list(
@@ -14,6 +16,6 @@
"Jelly" = /obj/item/weapon/reagent_containers/food/snacks/variable/jelly
)
/obj/machinery/cooker/candy/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/cooked/product)
/obj/machinery/appliance/mixer/candy/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/cooked/product)
food_color = get_random_colour(1)
. = ..()

View File

@@ -1,4 +1,4 @@
/obj/machinery/cooker/cereal
/obj/machinery/appliance/mixer/cereal
name = "cereal maker"
desc = "Now with Dann O's available!"
icon = 'icons/obj/cooking_machines.dmi'
@@ -7,20 +7,57 @@
on_icon = "cereal_on"
off_icon = "cereal_off"
cooked_sound = 'sound/machines/ding.ogg'
appliancetype = CEREALMAKER
/obj/machinery/cooker/cereal/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product)
output_options = list(
"Cereal" = /obj/item/weapon/reagent_containers/food/snacks/variable/cereal
)
/*
/obj/machinery/appliance/mixer/cereal/change_product_strings(var/obj/item/weapon/reagent_containers/food/snacks/product, var/datum/cooking_item/CI)
. = ..()
product.name = "box of [cooking_obj.name] cereal"
product.name = "box of [CI.object.name] cereal"
/obj/machinery/cooker/cereal/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product)
/obj/machinery/appliance/mixer/cereal/change_product_appearance(var/obj/item/weapon/reagent_containers/food/snacks/product)
product.icon = 'icons/obj/food.dmi'
product.icon_state = "cereal_box"
product.filling_color = cooking_obj.color
product.filling_color = CI.object.color
var/image/food_image = image(cooking_obj.icon, cooking_obj.icon_state)
food_image.color = cooking_obj.color
food_image.overlays += cooking_obj.overlays
var/image/food_image = image(CI.object.icon, CI.object.icon_state)
food_image.color = CI.object.color
food_image.overlays += CI.object.overlays
food_image.transform *= 0.7
product.overlays += food_image
*/
/obj/machinery/appliance/mixer/cereal/combination_cook(var/datum/cooking_item/CI)
var/list/images = list()
var/num = 0
for (var/obj/item/I in CI.container).
if (istype(I, /obj/item/weapon/reagent_containers/food/snacks/variable/cereal))
//Images of cereal boxes on cereal boxes is dumb
continue
var/image/food_image = image(I.icon, I.icon_state)
food_image.color = I.color
food_image.add_overlay(I.overlays)
food_image.transform *= 0.7 - (num * 0.05)
food_image.pixel_x = rand(-2,2)
food_image.pixel_y = rand(-3,5)
if (!images[I.icon_state])
images[I.icon_state] = food_image
num++
if (num > 3)
continue
var/obj/item/weapon/reagent_containers/food/snacks/result = ..()
result.color = result.filling_color
for (var/i in images)
result.overlays += images[i]

View File

@@ -0,0 +1,157 @@
//Cooking containers are used in ovens and fryers, to hold multiple ingredients for a recipe.
//They work fairly similar to the microwave - acting as a container for objects and reagents,
//which can be checked against recipe requirements in order to cook recipes that require several things
/obj/item/weapon/reagent_containers/cooking_container
icon = 'modular_citadel/icons/obj/cooking_machines.dmi'
var/shortname
var/max_space = 20//Maximum sum of w-classes of foods in this container at once
var/max_reagents = 80//Maximum units of reagents
flags = OPENCONTAINER | NOREACT
var/list/insertable = list(
/obj/item/weapon/reagent_containers/food/snacks,
/obj/item/weapon/holder,
/obj/item/weapon/paper
)
/obj/item/weapon/reagent_containers/cooking_container/initialize()
. = ..()
create_reagents(max_reagents)
flags |= OPENCONTAINER | NOREACT
/obj/item/weapon/reagent_containers/cooking_container/examine(var/mob/user)
..()
if (contents.len)
var/string = "It contains....</br>"
for (var/atom/movable/A in contents)
string += "[A.name] </br>"
user << span("notice", string)
if (reagents.total_volume)
user << span("notice", "It contains [reagents.total_volume]u of reagents.")
/obj/item/weapon/reagent_containers/cooking_container/attackby(var/obj/item/I as obj, var/mob/user as mob)
for (var/possible_type in insertable)
if (istype(I, possible_type))
if (!can_fit(I))
to_chat(user, "<span class='warning'>There's no more space in the [src] for that!</span>")
return 0
if(!user.unEquip(I))
return
I.forceMove(src)
to_chat(user, "<span class='notice'>You put the [I] into the [src]</span>")
return
/obj/item/weapon/reagent_containers/cooking_container/verb/empty()
set src in oview(1)
set name = "Empty Container"
set category = "Object"
set desc = "Removes items from the container, excluding reagents."
do_empty(usr)
/obj/item/weapon/reagent_containers/cooking_container/proc/do_empty(mob/user)
if (!isliving(user))
//Here we only check for ghosts. Animals are intentionally allowed to remove things from oven trays so they can eat it
return
if (user.stat || user.restrained())
to_chat(user, "<span class='notice'>You are in no fit state to do this.</span>")
return
if (!Adjacent(user))
to_chat(user, "You can't reach [src] from here.")
return
if (!contents.len)
to_chat(user, "<span class='warning'>There's nothing in the [src] you can remove!</span>")
return
for (var/atom/movable/A in contents)
A.forceMove(get_turf(src))
to_chat(user, "<span class='notice'>You remove all the solid items from the [src].</span>")
/obj/item/weapon/reagent_containers/cooking_container/proc/check_contents()
if (contents.len == 0)
if (!reagents || reagents.total_volume == 0)
return 0//Completely empty
else if (contents.len == 1)
if (!reagents || reagents.total_volume == 0)
return 1//Contains only a single object which can be extracted alone
return 2//Contains multiple objects and/or reagents
/obj/item/weapon/reagent_containers/cooking_container/AltClick(var/mob/user)
do_empty(user)
//Deletes contents of container.
//Used when food is burned, before replacing it with a burned mess
/obj/item/weapon/reagent_containers/cooking_container/proc/clear()
for (var/atom/a in contents)
qdel(a)
if (reagents)
reagents.clear_reagents()
/obj/item/weapon/reagent_containers/cooking_container/proc/label(var/number, var/CT = null)
//This returns something like "Fryer basket 1 - empty"
//The latter part is a brief reminder of contents
//This is used in the removal menu
. = shortname
if (!isnull(number))
.+= " [number]"
.+= " - "
if (CT)
.+=CT
else if (contents.len)
for (var/obj/O in contents)
.+=O.name//Just append the name of the first object
return
else if (reagents && reagents.total_volume > 0)
var/datum/reagent/R = reagents.get_master_reagent()
.+=R.name//Append name of most voluminous reagent
return
else
. += "empty"
/obj/item/weapon/reagent_containers/cooking_container/proc/can_fit(var/obj/item/I)
var/total = 0
for (var/obj/item/J in contents)
total += J.w_class
if((max_space - total) >= I.w_class)
return 1
//Takes a reagent holder as input and distributes its contents among the items in the container
//Distribution is weighted based on the volume already present in each item
/obj/item/weapon/reagent_containers/cooking_container/proc/soak_reagent(var/datum/reagents/holder)
var/total = 0
var/list/weights = list()
for (var/obj/item/I in contents)
if (I.reagents && I.reagents.total_volume)
total += I.reagents.total_volume
weights[I] = I.reagents.total_volume
if (total > 0)
for (var/obj/item/I in contents)
if (weights[I])
holder.trans_to(I, weights[I] / total)
/obj/item/weapon/reagent_containers/cooking_container/oven
name = "oven dish"
shortname = "shelf"
desc = "Put ingredients in this; designed for use with an oven. Warranty void if used."
icon_state = "ovendish"
max_space = 30
max_reagents = 120
/obj/item/weapon/reagent_containers/cooking_container/fryer
name = "fryer basket"
shortname = "basket"
desc = "Put ingredients in this; designed for use with a deep fryer. Warranty void if used."
icon_state = "basket"

View File

@@ -1,4 +1,4 @@
/obj/machinery/cooker/fryer
/obj/machinery/appliance/cooker/fryer
name = "deep fryer"
desc = "Deep fried <i>everything</i>."
icon_state = "fryer_off"
@@ -9,30 +9,157 @@
food_color = "#FFAD33"
cooked_sound = 'sound/machines/ding.ogg'
var/datum/looping_sound/deep_fryer/fry_loop
appliancetype = FRYER
active_power_usage = 12 KILOWATTS
optimal_power = 0.35
idle_power_usage = 3.6 KILOWATTS
// Power used to maintain temperature once it's heated.
// Going with 25% of the active power. This is a somewhat arbitrary value.
/obj/machinery/cooker/fryer/Initialize()
resistance = 20000 // Approx. 8-9 minutes to heat up.
max_contents = 2
container_type = /obj/item/weapon/reagent_containers/cooking_container/fryer
stat = POWEROFF // Starts turned off
var/datum/reagents/oil
var/optimal_oil = 9000 //90 litres of cooking oil
/obj/machinery/appliance/cooker/fryer/Initialize()
. = ..()
fry_loop = new(list(src), FALSE)
return ..()
oil = new/datum/reagents(optimal_oil * 1.25, src)
var/variance = rand()*0.15
// Fryer is always a little below full, but its usually negligible
/obj/machinery/cooker/fryer/Destroy()
if(prob(20))
// Sometimes the fryer will start with much less than full oil, significantly impacting efficiency until filled
variance = rand()*0.5
oil.add_reagent("cornoil", optimal_oil*(1 - variance))
/obj/machinery/appliance/cooker/fryer/Destroy()
QDEL_NULL(fry_loop)
return ..()
/obj/machinery/appliance/cooker/fryer/examine(var/mob/user)
if (..())//no need to duplicate adjacency check
to_chat(user, "Oil Level: [oil.total_volume]/[optimal_oil]")
/obj/machinery/appliance/cooker/fryer/heat_up()
if (..())
//Set temperature of oil reagent
var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent()
if (OL && istype(OL))
OL.data["temperature"] = temperature
/obj/machinery/cooker/fryer/set_cooking(new_setting)
/obj/machinery/appliance/cooker/fryer/equalize_temperature()
if (..())
//Set temperature of oil reagent
var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent()
if (OL && istype(OL))
OL.data["temperature"] = temperature
/obj/machinery/appliance/cooker/fryer/set_cooking(new_setting)
..()
if(new_setting)
fry_loop.start()
else
fry_loop.stop()
/obj/machinery/appliance/cooker/fryer/update_cooking_power()
..()//In addition to parent temperature calculation
//Fryer efficiency also drops when oil levels arent optimal
var/oil_level = 0
var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent()
if(OL && istype(OL))
oil_level = OL.volume
/obj/machinery/cooker/fryer/cook_mob(var/mob/living/victim, var/mob/user)
var/oil_efficiency = 0
if(oil_level)
oil_efficiency = oil_level / optimal_oil
if(oil_efficiency > 1)
//We're above optimal, efficiency goes down as we pass too much over it
oil_efficiency = 1 - (oil_efficiency - 1)
cooking_power *= oil_efficiency
/obj/machinery/appliance/cooker/fryer/update_icon()
if(cooking)
icon_state = on_icon
else
icon_state = off_icon
..()
//Fryer gradually infuses any cooked food with oil. Moar calories
//This causes a slow drop in oil levels, encouraging refill after extended use
/obj/machinery/appliance/cooker/fryer/do_cooking_tick(var/datum/cooking_item/CI)
if(..() && (CI.oil < CI.max_oil) && prob(20))
var/datum/reagents/buffer = new /datum/reagents(2)
oil.trans_to_holder(buffer, min(0.5, CI.max_oil - CI.oil))
CI.oil += buffer.total_volume
CI.container.soak_reagent(buffer)
//To solve any odd logic problems with results having oil as part of their compiletime ingredients.
//Upon finishing a recipe the fryer will analyse any oils in the result, and replace them with our oil
//As well as capping the total to the max oil
/obj/machinery/appliance/cooker/fryer/finish_cooking(var/datum/cooking_item/CI)
..()
var/total_oil = 0
var/total_our_oil = 0
var/total_removed = 0
var/datum/reagent/our_oil = oil.get_master_reagent()
for (var/obj/item/I in CI.container)
if (I.reagents && I.reagents.total_volume)
for (var/datum/reagent/R in I.reagents.reagent_list)
if (istype(R, /datum/reagent/nutriment/triglyceride/oil))
total_oil += R.volume
if (R.id != our_oil.id)
total_removed += R.volume
I.reagents.remove_reagent(R.id, R.volume)
else
total_our_oil += R.volume
if (total_removed > 0 || total_oil != CI.max_oil)
total_oil = min(total_oil, CI.max_oil)
if (total_our_oil < total_oil)
//If we have less than the combined total, then top up from our reservoir
var/datum/reagents/buffer = new /datum/reagents(INFINITY)
oil.trans_to_holder(buffer, total_oil - total_our_oil)
CI.container.soak_reagent(buffer)
else if (total_our_oil > total_oil)
//If we have more than the maximum allowed then we delete some.
//This could only happen if one of the objects spawns with the same type of oil as ours
var/portion = 1 - (total_oil / total_our_oil) //find the percentage to remove
for (var/obj/item/I in CI.container)
if (I.reagents && I.reagents.total_volume)
for (var/datum/reagent/R in I.reagents.reagent_list)
if (R.id == our_oil.id)
I.reagents.remove_reagent(R.id, R.volume*portion)
/obj/machinery/appliance/cooker/fryer/cook_mob(var/mob/living/victim, var/mob/user)
if(!istype(victim))
return
user.visible_message("<span class='danger'>\The [user] starts pushing \the [victim] into \the [src]!</span>")
icon_state = on_icon
cooking = 1
// user.visible_message("<span class='danger'>\The [user] starts pushing \the [victim] into \the [src]!</span>")
//Removed delay on this action in favour of a cooldown after it
//If you can lure someone close to the fryer and grab them then you deserve success.
//And a delay on this kind of niche action just ensures it never happens
//Cooldown ensures it can't be spammed to instakill someone
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN*3)
fry_loop.start()
if(!do_mob(user, victim, 20))
@@ -48,38 +175,71 @@
fry_loop.stop()
return
var/damage = rand(7,13) // Though this damage seems reduced, some hot oil is transferred to the victim and will burn them for a while after
var/datum/reagent/nutriment/triglyceride/oil/OL = oil.get_master_reagent()
damage *= OL.heatdamage(victim)
var/obj/item/organ/external/E
var/nopain
if(ishuman(victim) && user.zone_sel.selecting != "groin" && user.zone_sel.selecting != "chest")
var/mob/living/carbon/human/H = victim
if(H.species.flags & NO_PAIN)
nopain = 2
E = H.get_organ(user.zone_sel.selecting)
if(E.robotic >= ORGAN_ROBOT)
if(!E || E.species.flags & NO_PAIN)
nopain = 2
else if(E.robotic >= ORGAN_ROBOT)
nopain = 1
user.visible_message("<span class='danger'>\The [user] shoves \the [victim][E ? "'s [E.name]" : ""] into \the [src]!</span>")
if (damage > 0)
if(E)
if(E.children && E.children.len)
for(var/obj/item/organ/external/child in E.children)
if(nopain && nopain < 2 && !(child.robotic >= ORGAN_ROBOT))
nopain = 0
child.take_damage(0, damage)
damage -= (damage*0.5)//IF someone's arm is plunged in, the hand should take most of it
E.take_damage(0, damage)
else
victim.apply_damage(damage, BURN, user.zone_sel.selecting)
if(E)
E.take_damage(0, rand(20,30))
if(E.children && E.children.len)
for(var/obj/item/organ/external/child in E.children)
if(nopain && nopain < 2 && !(child.robotic >= ORGAN_ROBOT))
nopain = 0
child.take_damage(0, rand(20,30))
else
victim.apply_damage(rand(30,40), BURN, user.zone_sel.selecting)
if(!nopain)
victim << "<span class='danger'>Agony consumes you as searing hot oil scorches your [E ? E.name : "flesh"] horribly!</span>"
victim.emote("scream")
else
victim << "<span class='danger'>Searing hot oil scorches your [E ? E.name : "flesh"]!</span>"
if(!nopain)
to_chat(victim, "<span class='danger'>Agony consumes you as searing hot oil scorches your [E ? E.name : "flesh"] horribly!</span>")
victim.emote("scream")
else
to_chat(victim, "<span class='danger'>Searing hot oil scorches your [E ? E.name : "flesh"]!</span>")
user.attack_log += text("\[[time_stamp()]\] <font color='red'>Has [cook_type] \the [victim] ([victim.ckey]) in \a [src]</font>")
victim.attack_log += text("\[[time_stamp()]\] <font color='orange'>Has been [cook_type] in \a [src] by [user.name] ([user.ckey])</font>")
msg_admin_attack("[key_name_admin(user)] [cook_type] \the [victim] ([victim.ckey]) in \a [src]. (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)",ckey=key_name(user),ckey_target=key_name(victim))
if(victim.client)
add_attack_logs(user,victim,"[cook_type] in [src]")
icon_state = off_icon
cooking = 0
//Coat the victim in some oil
oil.trans_to(victim, 40)
fry_loop.stop()
return
/obj/machinery/appliance/cooker/fryer/attackby(var/obj/item/I, var/mob/user)
if(istype(I, /obj/item/weapon/reagent_containers/glass) && I.reagents)
if (I.reagents.total_volume <= 0 && oil)
//Its empty, handle scooping some hot oil out of the fryer
oil.trans_to(I, I.reagents.maximum_volume)
user.visible_message("[user] scoops some oil out of \the [src].", span("notice","You scoop some oil out of \the [src]."))
return 1
else
//It contains stuff, handle pouring any oil into the fryer
//Possibly in future allow pouring non-oil reagents in, in order to sabotage it and poison food.
//That would really require coding some sort of filter or better replacement mechanism first
//So for now, restrict to oil only
var/amount = 0
for (var/datum/reagent/R in I.reagents.reagent_list)
if (istype(R, /datum/reagent/nutriment/triglyceride/oil))
var/delta = oil.get_free_space()
delta = min(delta, R.volume)
oil.add_reagent(R.id, delta)
I.reagents.remove_reagent(R.id, delta)
amount += delta
if (amount > 0)
user.visible_message("[user] pours some oil into \the [src].", span("notice","You pour [amount]u of oil into \the [src]."), "<span class='notice'>You hear something viscous being poured into a metal container.</span>")
return 1
//If neither of the above returned, then call parent as normal
..()

View File

@@ -1,4 +1,4 @@
/obj/machinery/cooker/grill
/obj/machinery/appliance/cooker/grill
name = "grill"
desc = "Backyard grilling, IN SPACE."
icon_state = "grill_off"

View File

@@ -1,4 +1,4 @@
/obj/machinery/cooker/oven
/obj/machinery/appliance/cooker/oven
name = "oven"
desc = "Cookies are ready, dear."
icon = 'icons/obj/cooking_machines.dmi'