Files
Bubberstation/code/modules/mining/machine_redemption.dm
SyncIt21 2922d36500 Refactors for material container, ammo box & recycler (#75422)
**1. Material Container Refactors**

   a. `/datum/component/material_container/proc/insert_item()`

- Will now do stack spliting i.e. it will consume as many sheets from a
stack as possible and leave out the rest, It was moved from a player
interaction feature in `user_insert()` to this low level allowing many
things to take advantage of it
- Will now delete the item for you if it could salvage any materials
from it, you don't have to do it explicitly anymore if insertion was
successfull (i.e. this proc returns an non zero value) If you inserted a
stack and not all of it's sheets were inserted from the above point then
you still have to check it explicitly
  - Will now invoke `after_insert` if any materials were salvaged

   b. `datum/component/material_container/proc/user_insert() `

- Will now split the stack by the requested amount making precise
insertion work again & Fixes #72288
- will now consume all contents inside of the object reccursively, this
means items like ammo boxes will no longer have to adjust their custom
materials based on how much ammo they contain because `user_insert()`
will loop through all its contents and salvage the metal of every bullet
inside the box contents so this means

9686971c76/code/modules/projectiles/boxes_magazines/_box_magazine.dm (L206)
has been removed.

    **The Problem with this proc**
take `/obj/item/ammo_box/foambox/riot` for example. it has 40 darts each
having 1125 worth of iron, this proc will add the total iron of all the
bullets to the box custom material so the box custom materials would
become
`5000(base iron of box. see the definition of
/obj/item/ammo_box/foambox/riot) + 45000(40 bullets each having 1125
worth of iron) = 50000 iron`

What happens when you throw this ammo box in an recycler? The recycler
will recycle this box(Now 50000 worth of iron) AND the iron of each of
it's 40 bullets thus yielding

`50000(iron from box because of update_custom_materials()) + 45000(40
bullets each having 1125 worth of iron) = 95000 iron` `

because of this single proc we got `95000 - 50000 = 45000 extra iron`
from thin air

     **The Solution?**

Remove this proc and set a constant custom material value for the ammo
box(it's now 5000 computed see code) AND allow the material container to
loop through every bullet in the box and salvage iron from them. This
would yield

`5000(base iron of box. see the definition of
/obj/item/ammo_box/foambox/riot) + 45000(40 bullets each having 1125
worth of iron) = 50000 iron`

    From both box & bullets combined and not just from the box alone
    
    Fixes #43570
    Fixes #57548

This also allows you to do cool stuff like fill your bag with iron,
glass, whatever and dump them in the autolathe/ore silo by attacking the
machine with your bag rather than pulling out & inserting each item
individually so hey convinience

**2. Recycler patches**
- Recycler will stop consuming items when it runs out of power mid
recycling(which can happen if it recycles a large amount of items). It
used to previously run through the list of items without breaking so
even when power was lost, it still did it's job
   - Recycler will now Properly recycle all the contents inside an atom.
  
     **The Problem**
        Say we have 2 Items
          - Backpack
          - Glass sheet inside Backpack
If we process the items in the following order while deleting each item
that is processed first "Backpack" then "Glass Sheet" then when
"Backpack" is fully recycled and "Deleted" since the "Glass Sheet" is
inside the "Backpack" it get's deleted as well, so when we actually try
to recycle the "Glass Sheet" next, nothing happens because it was
deleted when we deleted the "Backpack".

      **The Solution**
Recycle the items in the reverse order first recycle the "Glass Sheet"
delete it & then the "Back Pack" so we don't deal with deleted items. So
you should see more materials come out when you put stuff inside storage
mediums & throw them in the recycler

- Recycler will consume only half the power when it's deleting items
that can't be recycled(no material was salvaged). just for convinience
2023-05-29 20:30:16 +00:00

431 lines
15 KiB
Plaintext

/**********************Ore Redemption Unit**************************/
//Turns all the various mining machines into a single unit to speed up mining and establish a point system
/obj/machinery/mineral/ore_redemption
name = "ore redemption machine"
desc = "A machine that accepts ore and instantly transforms it into workable material sheets. Points for ore are generated based on type and can be redeemed at a mining equipment vendor."
icon = 'icons/obj/machines/mining_machines.dmi'
icon_state = "ore_redemption"
density = TRUE
input_dir = NORTH
output_dir = SOUTH
req_access = list(ACCESS_MINERAL_STOREROOM)
layer = BELOW_OBJ_LAYER
circuit = /obj/item/circuitboard/machine/ore_redemption
needs_item_input = TRUE
processing_flags = START_PROCESSING_MANUALLY
///Boolean on whether the ORM can claim points without being connected to an ore silo.
var/requires_silo = TRUE
/// The current amount of unclaimed points in the machine
var/points = 0
/// Smelted ore's amount is multiplied by this
var/ore_multiplier = 1
/// Increases the amount of points the miners gain
var/point_upgrade = 1
/// Details how many credits each smelted ore is worth
var/static/list/ore_values = list(
/datum/material/iron = 1,
/datum/material/glass = 1,
/datum/material/plasma = 15,
/datum/material/silver = 16,
/datum/material/gold = 18,
/datum/material/titanium = 30,
/datum/material/uranium = 30,
/datum/material/diamond = 50,
/datum/material/bluespace = 50,
/datum/material/bananium = 60,
)
/// Variable that holds a timer which is used for callbacks to `send_console_message()`. Used for preventing multiple calls to this proc while the ORM is eating a stack of ores.
var/console_notify_timer
/// References the alloys the smelter can create
var/datum/techweb/stored_research
/// Linkage to the ORM silo
var/datum/component/remote_materials/materials
/obj/machinery/mineral/ore_redemption/offstation
circuit = /obj/item/circuitboard/machine/ore_redemption/offstation
requires_silo = FALSE
/obj/machinery/mineral/ore_redemption/Initialize(mapload)
. = ..()
if(!GLOB.autounlock_techwebs[/datum/techweb/autounlocking/smelter])
GLOB.autounlock_techwebs[/datum/techweb/autounlocking/smelter] = new /datum/techweb/autounlocking/smelter
stored_research = GLOB.autounlock_techwebs[/datum/techweb/autounlocking/smelter]
materials = AddComponent(/datum/component/remote_materials, "orm", mapload, mat_container_flags=BREAKDOWN_FLAGS_ORM)
/obj/machinery/mineral/ore_redemption/Destroy()
stored_research = null
materials = null
return ..()
/obj/machinery/mineral/ore_redemption/examine(mob/user)
. = ..()
if(panel_open)
. += span_notice("Alt-click to rotate the input and output direction.")
/// Turns ore into its refined type, and sends it to its material container
/obj/machinery/mineral/ore_redemption/proc/smelt_ore(obj/item/stack/ore/gathered_ore)
if(QDELETED(gathered_ore))
return
var/datum/component/material_container/mat_container = materials.mat_container
if (!mat_container)
return
if(gathered_ore.refined_type == null)
return
var/material_amount = mat_container.get_item_material_amount(gathered_ore, BREAKDOWN_FLAGS_ORM)
if(!material_amount)
qdel(gathered_ore) //no materials, incinerate it
else if(!mat_container.has_space(material_amount * gathered_ore.amount)) //if there is no space, eject it
unload_mineral(gathered_ore)
else
var/list/stack_mats = gathered_ore.get_material_composition(BREAKDOWN_FLAGS_ORM)
var/mats = stack_mats & mat_container.materials
var/ore_amount = gathered_ore.amount
var/ore_points= gathered_ore.points
var/ore_name = gathered_ore.name
var/refined_type = gathered_ore?.refined_type
if(mat_container.insert_item(gathered_ore, ore_multiplier, breakdown_flags = BREAKDOWN_FLAGS_ORM) > 0) //increase points only if insertion was successfull
if(refined_type)
points += ore_points * point_upgrade * ore_amount
materials.silo_log(src, "smelted", ore_amount, ore_name, mats)
SEND_SIGNAL(src, COMSIG_ORM_COLLECTED_ORE)
/// Returns the amount of a specific alloy design, based on the accessible materials
/obj/machinery/mineral/ore_redemption/proc/can_smelt_alloy(datum/design/D)
var/datum/component/material_container/mat_container = materials.mat_container
if(!mat_container || D.make_reagent)
return FALSE
var/build_amount = 0
for(var/mat in D.materials)
var/amount = D.materials[mat]
var/datum/material/redemption_mat_amount = mat_container.materials[mat]
if(!amount || !redemption_mat_amount)
return FALSE
var/smeltable_sheets = FLOOR(redemption_mat_amount / amount, 1)
if(!smeltable_sheets)
return FALSE
if(!build_amount)
build_amount = smeltable_sheets
build_amount = min(build_amount, smeltable_sheets)
return build_amount
/// Smelts the passed ores one by one
/obj/machinery/mineral/ore_redemption/proc/process_ores(list/ores_to_process)
for(var/ore in ores_to_process)
smelt_ore(ore)
/// Sends a message to the request consoles that signed up for ore updates
/obj/machinery/mineral/ore_redemption/proc/send_console_message()
var/datum/component/material_container/mat_container = materials.mat_container
if(!mat_container || !is_station_level(z))
return
console_notify_timer = null
var/area/our_area = get_area(src)
var/message = "Now available in [our_area]:"
var/has_minerals = FALSE
var/list/appended_list = list()
for(var/current_material in mat_container.materials)
var/datum/material/material_datum = current_material
var/mineral_amount = mat_container.materials[current_material] / SHEET_MATERIAL_AMOUNT
if(mineral_amount)
has_minerals = TRUE
appended_list["[capitalize(material_datum.name)]"] = "[mineral_amount] sheets"
if(!has_minerals)
return
var/datum/signal/subspace/messaging/rc/signal = new(src, list(
"ore_update" = TRUE,
"sender_department" = "Ore Redemption Machine",
"message" = message,
"verified" = "Ore Redemption Machine",
"priority" = REQ_NORMAL_MESSAGE_PRIORITY,
"appended_list" = appended_list,
))
signal.send_to_receivers()
/obj/machinery/mineral/ore_redemption/pickup_item(datum/source, atom/movable/target, direction)
if(QDELETED(target))
return
if(!materials.mat_container || panel_open || !powered())
return
if(istype(target, /obj/structure/ore_box))
var/obj/structure/ore_box/box = target
process_ores(box.contents)
else if(istype(target, /obj/item/stack/ore))
var/obj/item/stack/ore/O = target
smelt_ore(O)
else
return
if(!console_notify_timer)
// gives 5 seconds for a load of ores to be sucked up by the ORM before it sends out request console notifications. This should be enough time for most deposits that people make
console_notify_timer = addtimer(CALLBACK(src, PROC_REF(send_console_message)), 5 SECONDS)
/obj/machinery/mineral/ore_redemption/default_unfasten_wrench(mob/user, obj/item/I)
. = ..()
if(. != SUCCESSFUL_UNFASTEN)
return
if(anchored)
register_input_turf() // someone just wrenched us down, re-register the turf
else
unregister_input_turf() // someone just un-wrenched us, unregister the turf
/obj/machinery/mineral/ore_redemption/wrench_act(mob/living/user, obj/item/tool)
. = ..()
default_unfasten_wrench(user, tool)
return TOOL_ACT_TOOLTYPE_SUCCESS
/obj/machinery/mineral/ore_redemption/attackby(obj/item/W, mob/user, params)
if(default_deconstruction_screwdriver(user, "ore_redemption-open", "ore_redemption", W))
return
if(default_deconstruction_crowbar(W))
return
if(!powered())
return ..()
var/obj/item/stack/ore/O = W
if(istype(O))
if(isnull(O.refined_type))
to_chat(user, span_warning("[O] has already been refined!"))
return
return ..()
/obj/machinery/mineral/ore_redemption/AltClick(mob/living/user)
. = ..()
if(!user.can_perform_action(src))
return
if(panel_open)
input_dir = turn(input_dir, -90)
output_dir = turn(output_dir, -90)
to_chat(user, span_notice("You change [src]'s I/O settings, setting the input to [dir2text(input_dir)] and the output to [dir2text(output_dir)]."))
unregister_input_turf() // someone just rotated the input and output directions, unregister the old turf
register_input_turf() // register the new one
update_appearance(UPDATE_OVERLAYS)
return TRUE
/obj/machinery/mineral/ore_redemption/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "OreRedemptionMachine")
ui.open()
/obj/machinery/mineral/ore_redemption/ui_data(mob/user)
var/list/data = list()
data["unclaimedPoints"] = points
data["materials"] = list()
var/datum/component/material_container/mat_container = materials.mat_container
if (mat_container)
for(var/datum/material/material as anything in mat_container.materials)
var/amount = mat_container.materials[material]
var/sheet_amount = amount / SHEET_MATERIAL_AMOUNT
data["materials"] += list(list(
"name" = material.name,
"id" = REF(material),
"amount" = sheet_amount,
"category" = "material",
"value" = ore_values[material.type],
))
for(var/research in stored_research.researched_designs)
var/datum/design/alloy = SSresearch.techweb_design_by_id(research)
data["materials"] += list(list(
"name" = alloy.name,
"id" = alloy.id,
"category" = "alloy",
"amount" = can_smelt_alloy(alloy),
))
data["disconnected"] = null
if (!mat_container)
data["disconnected"] = "Local mineral storage is unavailable"
else if (!materials.silo && requires_silo)
data["disconnected"] = "No ore silo connection is available; storing locally"
else if (!materials.check_z_level() && requires_silo)
data["disconnected"] = "Unable to connect to ore silo, too far away"
else if (materials.on_hold())
data["disconnected"] = "Mineral withdrawal is on hold"
var/obj/item/card/id/card
if(isliving(user))
var/mob/living/customer = user
card = customer.get_idcard(hand_first = TRUE)
if(card?.registered_account)
data["user"] = list(
"name" = card.registered_account.account_holder,
"cash" = card.registered_account.account_balance,
)
else if(issilicon(user))
var/mob/living/silicon/silicon_player = user
data["user"] = list(
"name" = silicon_player.name,
"cash" = "No valid account",
)
return data
/obj/machinery/mineral/ore_redemption/ui_static_data(mob/user)
var/list/data = list()
var/datum/component/material_container/mat_container = materials.mat_container
if (mat_container)
for(var/datum/material/material as anything in mat_container.materials)
var/obj/material_display = initial(material.sheet_type)
data["material_icons"] += list(list(
"id" = REF(material),
"product_icon" = icon2base64(getFlatIcon(image(icon = initial(material_display.icon), icon_state = initial(material_display.icon_state)), no_anim=TRUE)),
))
for(var/research in stored_research.researched_designs)
var/datum/design/alloy = SSresearch.techweb_design_by_id(research)
var/obj/alloy_display = initial(alloy.build_path)
data["material_icons"] += list(list(
"id" = alloy.id,
"product_icon" = icon2base64(getFlatIcon(image(icon = initial(alloy_display.icon), icon_state = initial(alloy_display.icon_state)), no_anim=TRUE)),
))
return data
/obj/machinery/mineral/ore_redemption/ui_act(action, params)
. = ..()
if(.)
return
var/datum/component/material_container/mat_container = materials.mat_container
switch(action)
if("Claim")
var/obj/item/card/id/user_id_card
if(isliving(usr))
var/mob/living/user = usr
user_id_card = user.get_idcard(TRUE)
if(!materials.check_z_level() && (requires_silo || !user_id_card.registered_account.replaceable))
return TRUE
if(points)
if(user_id_card)
user_id_card.registered_account.mining_points += points
points = 0
else
to_chat(usr, span_warning("No valid ID detected."))
else
to_chat(usr, span_warning("No points to claim."))
return TRUE
if("Release")
if(!mat_container)
return
if(materials.on_hold())
to_chat(usr, span_warning("Mineral access is on hold, please contact the quartermaster."))
else if(!allowed(usr)) //Check the ID inside, otherwise check the user
to_chat(usr, span_warning("Required access not found."))
else
var/datum/material/mat = locate(params["id"])
var/amount = mat_container.materials[mat]
if(!amount)
return
var/stored_amount = CEILING(amount / SHEET_MATERIAL_AMOUNT, 0.1)
if(!stored_amount)
return
var/desired = text2num(params["sheets"])
var/sheets_to_remove = round(min(desired, 50, stored_amount))
var/count = mat_container.retrieve_sheets(sheets_to_remove, mat, get_step(src, output_dir))
var/list/mats = list()
mats[mat] = SHEET_MATERIAL_AMOUNT
materials.silo_log(src, "released", -count, "sheets", mats)
//Logging deleted for quick coding
return TRUE
if("Smelt")
if(!mat_container)
return
if(materials.on_hold())
to_chat(usr, span_warning("Mineral access is on hold, please contact the quartermaster."))
return
var/alloy_id = params["id"]
var/datum/design/alloy = stored_research.isDesignResearchedID(alloy_id)
var/obj/item/card/id/user_id_card
if(isliving(usr))
var/mob/living/user = usr
user_id_card = user.get_idcard(TRUE)
if((check_access(user_id_card) || allowed(usr)) && alloy)
var/amount = round(min(text2num(params["sheets"]), 50, can_smelt_alloy(alloy)))
if(amount < 1) //no negative mats
return
mat_container.use_materials(alloy.materials, amount)
materials.silo_log(src, "released", -amount, "sheets", alloy.materials)
var/output
if(ispath(alloy.build_path, /obj/item/stack/sheet))
output = new alloy.build_path(src, amount)
else
output = new alloy.build_path(src)
unload_mineral(output)
else
to_chat(usr, span_warning("Required access not found."))
return TRUE
/obj/machinery/mineral/ore_redemption/ex_act(severity, target)
do_sparks(5, TRUE, src)
return ..()
/obj/machinery/mineral/ore_redemption/update_icon_state()
icon_state = "[initial(icon_state)][powered() ? null : "-off"]"
return ..()
/obj/machinery/mineral/ore_redemption/update_overlays()
. = ..()
if((machine_stat & NOPOWER))
return
var/image/ore_input = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_[input_dir]")
var/image/ore_output = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_[turn(input_dir, 180)]")
switch(input_dir)
if(NORTH)
ore_input.pixel_y = 32
ore_output.pixel_y = -32
if(SOUTH)
ore_input.pixel_y = -32
ore_output.pixel_y = 32
if(EAST)
ore_input.pixel_x = 32
ore_output.pixel_x = -32
if(WEST)
ore_input.pixel_x = -32
ore_output.pixel_x = 32
ore_input.color = COLOR_MODERATE_BLUE
ore_output.color = COLOR_SECURITY_RED
var/mutable_appearance/light_in = emissive_appearance(ore_input.icon, ore_input.icon_state, offset_spokesman = src, alpha = ore_input.alpha)
light_in.pixel_y = ore_input.pixel_y
light_in.pixel_x = ore_input.pixel_x
var/mutable_appearance/light_out = emissive_appearance(ore_output.icon, ore_output.icon_state, offset_spokesman = src, alpha = ore_output.alpha)
light_out.pixel_y = ore_output.pixel_y
light_out.pixel_x = ore_output.pixel_x
. += ore_input
. += ore_output
. += light_in
. += light_out