Files
RoxyandGitHub 2f4666665e Fix two issues with icebox lizard gas (#5703)
## About The Pull Request

- The area type for lizard gas on icebox had `requires_power = FALSE`
which means when the APC ran out nothing happened, which is dumb because
there's an APC and an SMES and a deployable generator the power is
clearly supposed to matter
- Because the ruin spawns on the station Z level the circuit imprinter
auto connects to the station ore silo, this doesn't make sense and the
lavaland version doesn't do it so it doesn't anymore
## Why It's Good For The Game

Fixes #5370 

## Proof Of Testing

I spawned in and set the APC charge to 0 and verified that the area
actually loses power and lights go out/machines turn off, then I went
and put some mats into the station ore silo and verified that the
circuit imprinter in the gas station didn't have them

## Changelog
🆑
fix: fixed icebox lizard gas having infinite power
fix: fixed icebox lizard gas circuit imprinter connecting to station ore
silo
/🆑
2026-05-30 14:06:15 -05:00

499 lines
17 KiB
Plaintext

/obj/machinery/rnd/production
name = "technology fabricator"
desc = "Makes researched and prototype items with materials and energy."
/// Energy cost per full stack of materials spent. Material insertion is 40% of this.
active_power_usage = 0.05 * STANDARD_CELL_RATE
interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_MOUSEDROP_IGNORE_CHECKS
/// The efficiency coefficient. Material costs and print times are multiplied by this number;
var/efficiency_coeff = 1
/// The material storage used by this fabricator.
var/datum/remote_materials/materials
/// Which departments are allowed to process this design
var/allowed_department_flags = ALL
/// Icon state when production has started
var/production_animation
/// The types of designs this fabricator can print.
var/allowed_buildtypes = NONE
/// All designs in the techweb that can be fabricated by this machine, since the last update.
var/list/datum/design/cached_designs
/// What color is this machine's stripe? Leave null to not have a stripe.
var/stripe_color = null
///direction we output onto (if 0, on top of us)
var/drop_direction = 0
///looping sound for printing items
var/datum/looping_sound/lathe_print/print_sound
///made so we dont call addtimer() 40,000 times in on_techweb_update(). only allows addtimer() to be called on the first update
var/techweb_updating = FALSE
// BUBBER EDIT ADDITION BEGIN - Lizard gas imprinter
/// Whether to force the materials container not to connect to the station ore silo
var/force_disconnect = FALSE
// BUBBER EDIT ADDITION END
/obj/machinery/rnd/production/Initialize(mapload)
print_sound = new(src, FALSE)
materials = new (
src, \
force_disconnect ? FALSE : mapload, // BUBBER EDIT CHANGE - Lizard gas imprinter - ORIGINAL: mapload,
mat_container_signals = list( \
COMSIG_MATCONTAINER_ITEM_CONSUMED = TYPE_PROC_REF(/obj/machinery/rnd/production, local_material_insert)
) \
)
. = ..()
cached_designs = list()
RegisterSignal(src, COMSIG_SILO_ITEM_CONSUMED, TYPE_PROC_REF(/obj/machinery/rnd/production, silo_material_insert))
AddComponent(
/datum/component/payment, \
0, \
SSeconomy.get_dep_account(payment_department), \
PAYMENT_CLINICAL, \
TRUE, \
)
update_icon(UPDATE_OVERLAYS)
/obj/machinery/rnd/production/Destroy()
cached_designs = null
QDEL_NULL(print_sound)
QDEL_NULL(materials)
return ..()
/obj/machinery/rnd/production/update_overlays()
. = ..()
if(!stripe_color)
return
var/mutable_appearance/stripe = mutable_appearance('icons/obj/machines/research.dmi', "protolathe_stripe[panel_open ? "_t" : ""]")
stripe.color = stripe_color
. += stripe
/obj/machinery/rnd/production/examine(mob/user)
. = ..()
if(!in_range(user, src) && !isobserver(user))
return
. += span_notice("Material usage cost at <b>[efficiency_coeff * 100]%</b>.")
. += span_notice("Build time at <b>[efficiency_coeff * 100]%</b>.")
if(drop_direction)
. += span_notice("Currently configured to drop printed objects <b>[dir2text(drop_direction)]</b>.")
. += span_notice("[EXAMINE_HINT("Alt-click")] to reset.")
else
. += span_notice("[EXAMINE_HINT("Drag")] towards a direction (while next to it) to change drop direction.")
/obj/machinery/rnd/production/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if(drop_direction)
context[SCREENTIP_CONTEXT_ALT_LMB] = "Reset Drop"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/rnd/production/connect_techweb(datum/techweb/new_techweb)
if(stored_research)
UnregisterSignal(stored_research, list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN))
return ..()
/obj/machinery/rnd/production/on_connected_techweb()
. = ..()
RegisterSignals(
stored_research,
list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN),
TYPE_PROC_REF(/obj/machinery/rnd/production, on_techweb_update)
)
update_designs()
/// Updates the list of designs this fabricator can print.
/obj/machinery/rnd/production/proc/update_designs()
PROTECTED_PROC(TRUE)
techweb_updating = FALSE
var/previous_design_count = cached_designs.len
cached_designs.Cut()
for(var/design_id in stored_research.researched_designs)
var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
if((isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags)) && (design.build_type & allowed_buildtypes))
cached_designs |= design
var/design_delta = cached_designs.len - previous_design_count
if(design_delta > 0)
say("Received [design_delta] new design[design_delta == 1 ? "" : "s"].")
playsound(src, 'sound/machines/beep/twobeep_high.ogg', 50, TRUE)
update_static_data_for_all_viewers()
/obj/machinery/rnd/production/proc/on_techweb_update()
SIGNAL_HANDLER
if(!techweb_updating) //so we batch these updates together
techweb_updating = TRUE
addtimer(CALLBACK(src, PROC_REF(update_designs)), 2 SECONDS)
///When materials are instered via silo link
/obj/machinery/rnd/production/proc/silo_material_insert(obj/machinery/rnd/machine, container, obj/item/item_inserted, last_inserted_id, list/mats_consumed, amount_inserted)
SIGNAL_HANDLER
process_item(item_inserted, mats_consumed, amount_inserted)
/**
* Consumes power for the item inserted either into silo or local storage.
* Arguments
*
* * obj/item/item_inserted - the item to process
* * list/mats_consumed - list of mats consumed
* * amount_inserted - amount of material actually processed
*/
/obj/machinery/rnd/production/proc/process_item(obj/item/item_inserted, list/mats_consumed, amount_inserted)
PRIVATE_PROC(TRUE)
//we use initial(active_power_usage) because higher tier parts will have higher active usage but we have no benifit from it
if(directly_use_energy(ROUND_UP((amount_inserted / (MAX_STACK_SIZE * SHEET_MATERIAL_AMOUNT)) * 0.4 * initial(active_power_usage))))
var/datum/material/highest_mat_ref
var/highest_mat = 0
for(var/datum/material/mat as anything in mats_consumed)
var/present_mat = mats_consumed[mat]
if(present_mat > highest_mat)
highest_mat = present_mat
highest_mat_ref = mat
flick_animation(highest_mat_ref)
/**
* Plays an visual animation when materials are inserted
* Arguments
*
* * mat - the material ref we are trying to animate on the machine
*/
/obj/machinery/rnd/production/proc/flick_animation(datum/material/mat_ref)
PROTECTED_PROC(TRUE)
SHOULD_CALL_PARENT(FALSE)
//first play the insertion animation
flick_overlay_view(material_insertion_animation(mat_ref), 1 SECONDS)
//now play the progress bar animation
flick_overlay_view(mutable_appearance('icons/obj/machines/research.dmi', "protolathe_progress"), 1 SECONDS)
///When materials are instered into local storage
/obj/machinery/rnd/production/proc/local_material_insert(container, obj/item/item_inserted, last_inserted_id, list/mats_consumed, amount_inserted, atom/context)
SIGNAL_HANDLER
process_item(item_inserted, mats_consumed, amount_inserted)
/obj/machinery/rnd/production/RefreshParts()
. = ..()
var/total_storage = 0
for(var/datum/stock_part/matter_bin/bin in component_parts)
total_storage += bin.tier * 37.5 * SHEET_MATERIAL_AMOUNT
materials.set_local_size(total_storage)
efficiency_coeff = compute_efficiency()
update_static_data_for_all_viewers()
///Computes this machines cost efficiency based on the available parts
/obj/machinery/rnd/production/proc/compute_efficiency()
PROTECTED_PROC(TRUE)
var/efficiency = 1.2
for(var/datum/stock_part/servo/servo in component_parts)
efficiency -= servo.tier * 0.1
return efficiency
/**
* The cost efficiency for an particular design
* Arguments
*
* * path - the design path to check for
*/
/obj/machinery/rnd/production/proc/build_efficiency(path)
PRIVATE_PROC(TRUE)
SHOULD_BE_PURE(TRUE)
if(ispath(path, /obj/item/stack/sheet) || ispath(path, /obj/item/stack/ore/bluespace_crystal))
return 1
else
return efficiency_coeff
/obj/machinery/rnd/production/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/rnd/production/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Fabricator")
ui.open()
/obj/machinery/rnd/production/ui_static_data(mob/user)
var/list/data = materials.mat_container.ui_static_data()
var/list/designs = list()
var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
var/coefficient
for(var/datum/design/design in cached_designs)
var/cost = list()
coefficient = build_efficiency(design.build_path)
for(var/datum/material/mat as anything in design.materials)
var/amount = design.materials[mat]
cost[mat.name] = OPTIMAL_COST(amount * coefficient)
var/icon_size = spritesheet.icon_size_id(design.id)
designs[design.id] = list(
"name" = design.name,
"desc" = design.get_description(),
"cost" = cost,
"id" = design.id,
"categories" = design.category,
"icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]"
)
data["designs"] = designs
data["fabName"] = name
return data
/obj/machinery/rnd/production/ui_data(mob/user)
var/list/data = list()
data["materials"] = materials.mat_container.ui_data()
data["onHold"] = materials.on_hold()
data["busy"] = busy
data["materialMaximum"] = materials.local_size
data["queue"] = list()
return data
/obj/machinery/rnd/production/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
switch (action)
if("remove_mat")
var/datum/material/material = locate(params["ref"])
if(!istype(material))
return
var/amount = params["amount"]
if(isnull(amount))
return
amount = text2num(amount)
if(isnull(amount))
return
//we use initial(active_power_usage) because higher tier parts will have higher active usage but we have no benifit from it
if(!directly_use_energy(ROUND_UP((amount / MAX_STACK_SIZE) * 0.4 * initial(active_power_usage))))
say("No power to dispense sheets")
return
materials.eject_sheets(material_ref = material, eject_amount = amount, user_data = ID_DATA(usr))
return TRUE
if("build")
if(busy)
say("Warning: fabricator is busy!")
return
//validate design
var/design_id = params["ref"]
if(!design_id)
return
var/datum/design/design = stored_research.researched_designs[design_id] ? SSresearch.techweb_design_by_id(design_id) : null
if(!istype(design))
return FALSE
if(!(isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags)))
say("This fabricator does not have the necessary keys to decrypt this design.")
return FALSE
if(design.build_type && !(design.build_type & allowed_buildtypes))
say("This fabricator does not have the necessary manipulation systems for this design.")
return FALSE
//validate print quantity
var/print_quantity = params["amount"]
if(isnull(print_quantity))
return
print_quantity = text2num(print_quantity)
if(isnull(print_quantity))
return
print_quantity = clamp(print_quantity, 1, 50)
//efficiency for this design, stacks use exact materials
var/coefficient = build_efficiency(design.build_path)
//check for materials
if(!materials.can_use_resource(user_data = ID_DATA(usr)))
return
if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity))
say("Not enough materials to complete prototype[print_quantity > 1 ? "s" : ""].")
return FALSE
//compute power & time to print 1 item
var/charge_per_item = 0
for(var/material, amount in design.materials)
charge_per_item += amount
charge_per_item = ROUND_UP((charge_per_item / (MAX_STACK_SIZE * SHEET_MATERIAL_AMOUNT)) * coefficient * active_power_usage)
var/build_time_per_item = (design.construction_time * design.lathe_time_factor * efficiency_coeff) ** 0.8
// SKYRAT EDIT - Faster lathes
if(!speedup_disabled)
build_time_per_item *= 0.1
// SKYRAT EDIT END
//start production
busy = TRUE
SStgui.update_uis(src)
print_sound.start()
update_appearance()
var/turf/target_location
if(drop_direction)
target_location = get_step(src, drop_direction)
if(isclosedturf(target_location))
target_location = get_turf(src)
else
target_location = get_turf(src)
addtimer(CALLBACK(src, PROC_REF(do_make_item), design, print_quantity, build_time_per_item, coefficient, charge_per_item, target_location, ID_DATA(usr)), build_time_per_item)
return TRUE
/**
* Callback for start_making, actually makes the item
* Arguments
*
* * datum/design/design - the design we are trying to print
* * items_remaining - the number of designs left out to print
* * build_time_per_item - the time taken to print 1 item
* * material_cost_coefficient - the cost efficiency to print 1 design
* * charge_per_item - the amount of power to print 1 item
* * turf/target - the location to drop the printed item on
* * user_data - ID_DATA(user), see the proc on SSid_access, served for logging
*/
/obj/machinery/rnd/production/proc/do_make_item(
datum/design/design,
items_remaining,
build_time_per_item,
material_cost_coefficient,
charge_per_item,
turf/target,
alist/user_data)
PROTECTED_PROC(TRUE)
if(!items_remaining) // how
finalize_build()
return
if(!is_operational)
say("Unable to continue production, power failure.")
finalize_build()
return
if(!directly_use_energy(charge_per_item)) // provide the wait time until lathe is ready
var/area/my_area = get_area(src)
var/obj/machinery/power/apc/my_apc = my_area.apc
if(!QDELETED(my_apc))
var/charging_wait = my_apc.time_to_charge(charge_per_item)
if(!isnull(charging_wait))
say("Unable to continue production, APC overload. Wait [DisplayTimeText(charging_wait, round_seconds_to = 1)] and try again.")
else
say("Unable to continue production, power grid overload.")
else
say("Unable to continue production, no APC in area.")
finalize_build()
return
if(!materials.can_use_resource(user_data = user_data))
finalize_build()
return
var/is_stack = ispath(design.build_path, /obj/item/stack)
var/list/design_materials = design.materials
if(!materials.mat_container.has_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1))
say("Unable to continue production, missing materials.")
finalize_build()
return
materials.use_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1, "processed", "[design.name]", user_data = user_data)
var/atom/movable/created
if(is_stack)
var/obj/item/stack/stack_item = initial(design.build_path)
var/max_stack_amount = initial(stack_item.max_amount)
var/number_to_make = (initial(stack_item.amount) * items_remaining)
while(number_to_make > max_stack_amount)
created = design.create_result(target, design_materials, amount = max_stack_amount)
if(isitem(created))
created.pixel_x = created.base_pixel_x + rand(-6, 6)
created.pixel_y = created.base_pixel_y + rand(-6, 6)
number_to_make -= max_stack_amount
created = design.create_result(target, design_materials, amount = number_to_make)
else
created = design.create_result(target, design_materials)
split_materials_uniformly(design_materials, material_cost_coefficient, created)
if(isitem(created))
created.pixel_x = created.base_pixel_x + rand(-6, 6)
created.pixel_y = created.base_pixel_y + rand(-6, 6)
SSblackbox.record_feedback("nested tally", "lathe_printed_items", 1, list("[type]", "[created.type]"))
if(is_stack)
items_remaining = 0
else
items_remaining -= 1
if(!items_remaining)
finalize_build()
return
addtimer(CALLBACK(src, PROC_REF(do_make_item), design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, target, user_data), build_time_per_item)
/// Resets the busy flag
/// Called at the end of do_make_item's timer loop
/obj/machinery/rnd/production/proc/finalize_build()
PROTECTED_PROC(TRUE)
print_sound.stop()
busy = FALSE
SStgui.update_uis(src)
update_appearance()
/obj/machinery/rnd/production/mouse_drop_dragged(atom/over, mob/user, src_location, over_location, params)
if(!can_interact(user) || (!HAS_SILICON_ACCESS(user) && !isAdminGhostAI(user)) && !Adjacent(user))
return
if(busy)
balloon_alert(user, "busy printing!")
return
var/direction = get_dir(src, over_location)
if(!direction)
return
drop_direction = direction
balloon_alert(user, "dropping [dir2text(drop_direction)]")
/obj/machinery/rnd/production/click_alt(mob/user)
if(drop_direction == 0)
return CLICK_ACTION_BLOCKING
if(busy)
balloon_alert(user, "busy printing!")
return CLICK_ACTION_BLOCKING
balloon_alert(user, "drop direction reset")
drop_direction = 0
return CLICK_ACTION_SUCCESS
/obj/machinery/rnd/production/update_icon_state()
. = ..()
if(busy && production_animation)
icon_state = production_animation
else if(!panel_open) // use what is set by parent if panel is open
icon_state = base_icon_state || initial(icon_state)