Files
Bubberstation/code/modules/hydroponics/biogenerator.dm
Ghom 1be27a4ffe Dunking handle_atom_del() in the trash bin. (#77339)
Whatever you do, if it warrants the use of something like
`handle_atom_del`, chances are `Exited` can do it better, as most of
these cases involve movables that shouldn't be moved out of their loc
(`Destroy` forcefully moves movables to nullspace) without calling
specific procs, and for the remaining few, `handle_atom_del` doesn't
even cover the eventuality of a movable being deleted outside the source
atom, so it's quite garbage.

Beside, I feel confident in saying `handle_atom_del()` is older than the
DCS, an echo on the workarounds done at the time.
2023-08-18 11:02:22 +00:00

571 lines
17 KiB
Plaintext

/// How many more items does `max_items` get increased by per rating point.
#define MAX_ITEMS_PER_RATING 10
/// How many items are converted per cycle, per rating point of the manipulator used.
#define PROCESSED_ITEMS_PER_RATING 5
/obj/machinery/biogenerator
name = "biogenerator"
desc = "Converts plants into biomass, which can be used to construct useful items."
icon = 'icons/obj/machines/biogenerator.dmi'
icon_state = "biogenerator"
density = TRUE
circuit = /obj/item/circuitboard/machine/biogenerator
processing_flags = START_PROCESSING_MANUALLY
/// Whether the biogenerator is currently processing biomass or not.
var/processing = FALSE
/// The reagent container that is currently inside of the biomass generator. Can be null.
var/obj/item/reagent_containers/cup/beaker = null
/// The amount of biomass that's currently stored in the biogenerator.
var/biomass = 0
/// The amount by which the biomass consumption will be divided.
var/efficiency = 1
/// The conversion factor for nutrient to biomass, and the amount of additional items that will be processed at once per cycle.
var/productivity = 1
/// The amount of items that will be converted into biomass per processing cycle.
var/processed_items_per_cycle = 5
/// The maximum amount of items the biogenerator can hold for biomass conversion purposes.
var/max_items = 20
/// The current amount of items that can be converted into biomass that the biogenerator is holding.
var/current_item_count = 0
/// The maximum amount of biomass that will affect the visuals of the biogenerator.
var/max_visual_biomass = 5000
/// The maximum amount of reagents that the biogenerator can output to a container at once.
var/max_output = 50
/// The research that is stored within this biogenerator.
var/datum/techweb/stored_research
/// The different visual categories for the biogenerator, for the tabs.
var/list/show_categories = list(
RND_CATEGORY_BIO_FOOD,
RND_CATEGORY_BIO_CHEMICALS,
RND_CATEGORY_BIO_MATERIALS,
)
/// The category that's currently selected in the UI.
var/selected_cat
/// The sound loop that can be heard when the generator is processing.
var/datum/looping_sound/generator/soundloop
/// Whether the biogen is welded down to the floor disabling unwrenching
var/welded_down = FALSE
/obj/machinery/biogenerator/Initialize(mapload)
. = ..()
if(!GLOB.autounlock_techwebs[/datum/techweb/autounlocking/biogenerator])
GLOB.autounlock_techwebs[/datum/techweb/autounlocking/biogenerator] = new /datum/techweb/autounlocking/biogenerator
stored_research = GLOB.autounlock_techwebs[/datum/techweb/autounlocking/biogenerator]
soundloop = new(src, processing)
if(mapload)
welded_down = TRUE
/obj/machinery/biogenerator/can_be_unfasten_wrench(mob/user, silent)
if(welded_down)
to_chat(user, span_warning("[src] is welded to the floor!"))
return FAILED_UNFASTEN
return ..()
/obj/machinery/biogenerator/set_anchored(anchorvalue)
. = ..()
if(!anchored && welded_down) //make sure they're keep in sync in case it was forcibly unanchored by badmins or by a megafauna.
welded_down = FALSE
/obj/machinery/biogenerator/welder_act(mob/living/user, obj/item/tool)
..()
if(welded_down)
if(!tool.tool_start_check(user, amount=2))
return TRUE
user.visible_message(
span_notice("[user.name] starts to cut the [name] free from the floor."),
span_notice("You start to cut [src] free from the floor..."),
span_hear("You hear welding."),
)
if(!tool.use_tool(src, user, 10 SECONDS, volume=100))
return FALSE
welded_down = FALSE
to_chat(user, span_notice("You cut [src] free from the floor."))
return TRUE
if(!anchored)
to_chat(user, span_warning("[src] needs to be wrenched to the floor!"))
return TRUE
if(!tool.tool_start_check(user, amount=2))
return TRUE
user.visible_message(
span_notice("[user.name] starts to weld the [name] to the floor."),
span_notice("You start to weld [src] to the floor..."),
span_hear("You hear welding."),
)
if(!tool.use_tool(src, user, 10 SECONDS, volume=100))
balloon_alert(user, "cancelled!")
return FALSE
welded_down = TRUE
to_chat(user, span_notice("You weld [src] to the floor."))
return TRUE
/obj/machinery/biogenerator/Destroy()
QDEL_NULL(beaker)
QDEL_NULL(soundloop)
return ..()
/obj/machinery/biogenerator/contents_explosion(severity, target)
. = ..()
if(!beaker)
return
switch(severity)
if(EXPLODE_DEVASTATE)
SSexplosions.high_mov_atom += beaker
if(EXPLODE_HEAVY)
SSexplosions.med_mov_atom += beaker
if(EXPLODE_LIGHT)
SSexplosions.low_mov_atom += beaker
/obj/machinery/biogenerator/Exited(atom/movable/gone, direction)
. = ..()
if(gone == beaker)
beaker = null
update_appearance()
/obj/machinery/biogenerator/RefreshParts()
. = ..()
var/new_efficiency = 0
var/new_productivity = 0
var/new_max_items = 10
var/new_processed_items_per_cycle = 0
for(var/datum/stock_part/matter_bin/bin in component_parts)
new_max_items += MAX_ITEMS_PER_RATING * bin.tier
for(var/datum/stock_part/servo/servo in component_parts)
new_productivity += servo.tier
new_efficiency += servo.tier
new_processed_items_per_cycle += PROCESSED_ITEMS_PER_RATING * servo.tier
max_items = new_max_items
efficiency = new_efficiency
productivity = new_productivity
processed_items_per_cycle = new_processed_items_per_cycle
update_appearance()
/obj/machinery/biogenerator/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
. += span_notice("The status display reads:")
. += span_notice(" - Productivity at <b>[productivity * 100]%</b>.")
. += span_notice(" - Converting <b>[processed_items_per_cycle]</b> pieces of food per cycle.")
. += span_notice(" - Matter consumption at <b>[1 / efficiency * 100]</b>%.")
. += span_notice(" - Internal biomass converter capacity at <b>[max_items]</b> pieces of food, and currently holding <b>[current_item_count]</b>.")
if(welded_down)
. += span_info("It's moored firmly to the floor. You can unsecure its moorings with a <b>welder</b>.")
/obj/machinery/biogenerator/update_appearance()
. = ..()
var/power = machine_stat & (NOPOWER|BROKEN) ? 0 : 1 + min(biomass / max_visual_biomass, 1) + (processing & 1)
set_light(MINIMUM_USEFUL_LIGHT_RANGE, power, LIGHT_COLOR_CYAN)
/obj/machinery/biogenerator/update_overlays()
. = ..()
if(panel_open)
. += mutable_appearance(icon, "[icon_state]_o_panel")
if(beaker)
. += mutable_appearance(icon, "[icon_state]_o_container")
if(biomass > 0)
// Get current biomass volume adjusted with sine function (more biomass = less frequent icon changes)
var/biomass_volume_sin = sin(min(biomass/max_visual_biomass, 1) * 90)
// Round up to get the corresponding overlay icon
var/biomass_level = ROUND_UP(biomass_volume_sin * 7)
. += mutable_appearance(icon, "[icon_state]_o_biomass_[biomass_level]")
. += emissive_appearance(icon, "[icon_state]_o_biomass_[biomass_level]", src)
if(machine_stat & (NOPOWER|BROKEN))
return
if(processing)
. += mutable_appearance(icon, "[icon_state]_o_process")
. += emissive_appearance(icon, "[icon_state]_o_process", src)
. += mutable_appearance(icon, "[icon_state]_o_screen")
. += emissive_appearance(icon, "[icon_state]_o_screen", src)
/obj/machinery/biogenerator/wrench_act(mob/living/user, obj/item/tool)
. = ..()
default_unfasten_wrench(user, tool)
return TOOL_ACT_TOOLTYPE_SUCCESS
/obj/machinery/biogenerator/attackby(obj/item/attacking_item, mob/living/user, params)
if(user.combat_mode)
return ..()
if(default_deconstruction_screwdriver(user, icon_state, icon_state, attacking_item))
if(processing)
stop_process(FALSE)
if(beaker)
beaker.forceMove(drop_location())
beaker = null
update_appearance(UPDATE_ICON)
return
var/turf/drop_location = drop_location()
if(default_deconstruction_crowbar(attacking_item))
if(biomass > 0)
drop_location.visible_message(span_warning("Biomass spills from \the [src]'s biomass tank!"))
playsound(drop_location, 'sound/effects/slosh.ogg', 25, vary = TRUE)
new /obj/effect/decal/cleanable/greenglow(drop_location)
return
if(istype(attacking_item, /obj/item/reagent_containers/cup))
if(panel_open)
to_chat(user, span_warning("Close the maintenance panel first."))
else
insert_beaker(user, attacking_item)
return TRUE
else if(istype(attacking_item, /obj/item/storage/bag))
if(current_item_count >= max_items)
to_chat(user, span_warning("\The [src] is already full! Activate it to free up some space."))
return TRUE
var/obj/item/storage/bag/bag = attacking_item
for(var/obj/item/food/item in bag.contents)
if(current_item_count >= max_items)
break
if(bag.atom_storage.attempt_remove(item, src))
current_item_count++
if(bag.contents.len == 0)
to_chat(user, span_info("You empty \the [bag] into \the [src]."))
else if (current_item_count >= max_items)
to_chat(user, span_info("You fill \the [src] from \the [bag] to its capacity."))
else
to_chat(user, span_info("You fill \the [src] from \the [bag]."))
return TRUE //no afterattack
else if(istype(attacking_item, /obj/item/food))
if(current_item_count >= max_items)
to_chat(user, span_warning("\The [src] is full! Activate it."))
else
if(user.transferItemToLoc(attacking_item, src))
current_item_count++
to_chat(user, span_info("You insert \the [attacking_item] in \the [src]"))
return TRUE //no afterattack
else
to_chat(user, span_warning("You cannot put \the [attacking_item] in \the [src]!"))
/obj/machinery/biogenerator/AltClick(mob/living/user)
. = ..()
if(user.can_perform_action(src, FORBID_TELEKINESIS_REACH) && can_interact(user))
eject_beaker(user)
/// Activates biomass processing and converts all inserted food products into biomass
/obj/machinery/biogenerator/proc/start_process()
if(machine_stat != NONE || panel_open)
return
if(processing)
say("Already working!")
return
if(!(locate(/obj/item/food) in contents))
say("No food items found!")
return
begin_processing()
processing = TRUE
soundloop.start()
update_appearance()
/obj/machinery/biogenerator/process(seconds_per_tick)
if(!processing)
return
if(machine_stat != NONE || panel_open)
stop_process()
return
if(!current_item_count)
stop_process()
return
for(var/i in 1 to processed_items_per_cycle)
var/obj/item/food/food_to_convert = locate(/obj/item/food) in contents
if(!food_to_convert)
break
convert_to_biomass(food_to_convert)
use_power(active_power_usage * seconds_per_tick)
if(!current_item_count)
stop_process(FALSE)
update_appearance()
/**
* Simple helper proc that converts the given food item into biomass for the generator,
* while also handling removing it and modifying the `current_item_count`.
*
* Arguments:
* * food_to_convert - The food item that will be converted into biomass and
* subsequently be deleted.
*/
/obj/machinery/biogenerator/proc/convert_to_biomass(obj/item/food/food_to_convert)
var/static/list/nutrient_subtypes = typesof(/datum/reagent/consumable/nutriment)
var/nutriments = 0
nutriments += ROUND_UP(food_to_convert.reagents.get_multiple_reagent_amounts(nutrient_subtypes))
qdel(food_to_convert)
current_item_count = max(current_item_count - 1, 0)
biomass += nutriments * productivity
/**
* Simple helper to handle stopping the process of the biogenerator.
*
* Arguments:
* * update_appearance - Whether or not we call `update_appearance()` here.
* Defaults to `TRUE`.
*/
/obj/machinery/biogenerator/proc/stop_process(update_appearance = TRUE)
end_processing()
processing = FALSE
soundloop.stop()
if(update_appearance)
update_appearance()
/obj/machinery/biogenerator/proc/use_biomass(list/materials, amount = 1, remove_biomass = TRUE)
if(materials.len != 1 || materials[1] != GET_MATERIAL_REF(/datum/material/biomass))
return FALSE
var/cost = materials[GET_MATERIAL_REF(/datum/material/biomass)] * amount / efficiency
if (cost > biomass)
return FALSE
if(remove_biomass)
biomass -= cost
update_appearance()
return TRUE
/obj/machinery/biogenerator/proc/create_product(datum/design/design, amount)
if(design.make_reagent)
if(!beaker)
return FALSE
if(beaker.reagents.maximum_volume - beaker.reagents.total_volume < amount)
say("Warning: Attached container does not have enough free capacity!")
return FALSE
if(!use_biomass(design.materials, amount))
return FALSE
beaker.reagents.add_reagent(design.make_reagent, amount)
if(design.build_path)
if(!use_biomass(design.materials, amount))
return FALSE
if(istype(design.build_path, /obj/item/stack/sheet))
new design.build_path(drop_location(), amount)
else
var/drop_location = drop_location()
for(var/i in 1 to amount)
new design.build_path(drop_location)
return TRUE
/*
* Insert a new beaker into the biogenerator, replacing/swapping our current beaker if there is one.
*
* user - the mob inserting the beaker
* inserted_beaker - the beaker we're inserting into the biogen
*/
/obj/machinery/biogenerator/proc/insert_beaker(mob/living/user, obj/item/reagent_containers/cup/inserted_beaker)
if(!can_interact(user))
return
if(!user.transferItemToLoc(inserted_beaker, src))
return
if(beaker)
to_chat(user, span_notice("You swap out [beaker] in [src] for [inserted_beaker]."))
eject_beaker(user, silent = TRUE)
else
to_chat(user, span_notice("You add [inserted_beaker] to [src]."))
beaker = inserted_beaker
update_appearance(UPDATE_ICON)
/*
* Eject the current stored beaker either into the user's hands or onto the ground.
*
* user - the mob ejecting the beaker
* silent - whether to give a message to the user that the beaker was ejected.
*/
/obj/machinery/biogenerator/proc/eject_beaker(mob/living/user, silent = FALSE)
if(!beaker)
return
if(!can_interact(user))
return
if(user.put_in_hands(beaker))
if(!silent)
to_chat(user, span_notice("You eject [beaker] from [src]."))
else
if(!silent)
to_chat(user, span_notice("You eject [beaker] from [src] onto the ground."))
beaker.forceMove(drop_location())
beaker = null
update_appearance(UPDATE_ICON)
/obj/machinery/biogenerator/ui_status(mob/user)
if(machine_stat & BROKEN || panel_open)
return UI_CLOSE
return ..()
/obj/machinery/biogenerator/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/research_designs),
)
/obj/machinery/biogenerator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Biogenerator", name)
ui.open()
/obj/machinery/biogenerator/ui_data(mob/user)
var/list/data = list()
data["beaker"] = beaker ? TRUE : FALSE
data["biomass"] = biomass
data["processing"] = processing
data["max_output"] = max_output
data["efficiency"] = efficiency
data["can_process"] = !!current_item_count
if(beaker)
data["beakerCurrentVolume"] = round(beaker.reagents.total_volume, 0.01)
data["beakerMaxVolume"] = beaker.volume
data["reagent_color"] = mix_color_from_reagents(beaker.reagents.reagent_list)
return data
/obj/machinery/biogenerator/ui_static_data(mob/user)
var/list/data = list()
data["categories"] = list()
data["max_visual_biomass"] = max_visual_biomass
var/categories = show_categories.Copy()
for(var/category in categories)
categories[category] = list()
for(var/design_id in stored_research.researched_designs)
var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
for(var/category in categories)
if(category in design.category)
categories[category] += design
for(var/category in categories)
var/list/cat = list(
"name" = category,
"items" = (category == selected_cat ? list() : null))
for(var/item in categories[category])
var/datum/design/design = item
cat["items"] += list(list(
"id" = design.id,
"name" = design.name,
"is_reagent" = design.make_reagent != null,
"cost" = design.materials[GET_MATERIAL_REF(/datum/material/biomass)] / efficiency,
))
data["categories"] += list(cat)
return data
/obj/machinery/biogenerator/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("activate")
start_process()
return TRUE
if("eject")
eject_beaker(usr)
return TRUE
if("create")
var/amount = text2num(params["amount"])
if(!amount)
return
var/id = params["id"]
if(!stored_research.researched_designs.Find(id))
stack_trace("ID did not map to a researched datum [id]")
return
var/datum/design/design = SSresearch.techweb_design_by_id(id)
amount = clamp(amount, 1, (design.make_reagent && beaker ? beaker.reagents.maximum_volume - beaker.reagents.total_volume : max_output))
if(design && !istype(design, /datum/design/error_design))
create_product(design, amount)
else
stack_trace("ID could not be turned into a valid techweb design datum [id]")
return
return TRUE
if("select")
selected_cat = params["category"]
return TRUE
#undef MAX_ITEMS_PER_RATING
#undef PROCESSED_ITEMS_PER_RATING