Techwebs & Experi-Sci (#17987)

* Techwebs

* Add basic stock parts

* Techweb production machines

* Circuit Imprinter

* More work

* More designs

* More designs, nodes'

* More more

* Better sorting for designs

* Add the exosuit fab'

* More conversions

* More and more and more

* mechfab parts

* Modsuit Designs

* Pinnace baron

* Equipment

* more mechfab

* vehicles

* more mecha

* Micro mechs

* Update mechfab_designs.dm

* no time

* zero

* Update mechfab_designs.dm

* Prosthetics Fabricator and fixes

* Update maps to use new parts

* Whoops, fix a few maps

* Fix lint error

* Port scene tools

* Fireworks~~

* Don't label the sex toys as sex toys

* Fixup maps in TGM format

26517b7122: maps/virgo_minitest/virgo_minitest-1.dmm

Automatically commited by: tools\mapmerge2\fixup.py

* Significant portion of designs converted

* Fix paths

* Fix tgui lints

* Just a whole bunch of designs

* Almost doneeee

* ALL DESIGNS CONVERTED

* Merk all of the old stuff

* Fix a few errors

* Fix more errors

* MORE FIX

* Make server control work

* Undo changes to archive

* Fix a TODO

* Make disks work

* Start the long process of finishing the node tree

* One more oops

* All mechfab designs

* Add departmental protolathes

* Down to 200 designs unsorted

* FINISH TECHWEBS

* Fuck whoops

* Remove the debug verbs

* Little map tweaks

* Fix mousedrop

* More map fixes

* Upstream bug lmao

* guess CI hates this

* Fixup maps in TGM format

fe062e7bc4: maps/tether/tether-03-surface3.dmm

Automatically commited by: tools\mapmerge2\fixup.py

* I gotta stop committing this

* Change scree requested

* Make science able to print implants too

* Fix ghost spawns

* Move NSFW gun

* Merk partbundles, make janus module work

* Pretty animation when you complete the tree :)

* Don't display designs you can't print

* Move security protolathes to warden's office

* Make servers recover from power failure gracefully

* Make fabricators drop their mats on the ground when destroyed

* Ore Silo

* Make CI happy

* Fixup maps in TGM format

fe062e7bc4: maps/tether/tether-05-station1.dmm

Automatically commited by: tools\mapmerge2\fixup.py

* Fix MC crash

* Make experiscanner work for borgs

* I REALLY gotta stop committing this

* Make scientists and the RD spawn with an experiscanner

* Lock NIFs behind scanning mice

* Make Biome happy

* Allow scanning Cocoa and Kendrick in case they can't find mice

* Change from Kendrick to Ratthew, ;help maint, or Brick

* Change the name of the /rat/pet class so the experiscanner is more helpful

* Add departmental_flags to a bunch of things

* Remove a few things from security's techfab

* Add a second engineering lathe to atmos

* Fix runtime recycling rig modules, add name to cost sequence

* Add a discount experiment for scanning slime cores

* Change some fluff and shuffle chameleon clothes to bluespace yadayada

* Make closets alt click immune

* Do a bunch of sorting

* Silly

* Hopefully fix all infinite material bugs in techwebs

* bad merge

* quick ai fix

* fix some robot stuff

* axe downlaod fully

* list

* no ref

* easier readable

---------

Co-authored-by: C.L. <killer65311@gmail.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
ShadowLarkens
2025-07-25 21:30:08 -07:00
committed by GitHub
parent 93efaa653a
commit b60cd47309
271 changed files with 19410 additions and 11158 deletions
@@ -1,491 +0,0 @@
/*!
This datum should be used for handling mineral contents of machines and whatever else is supposed to hold minerals and make use of them.
Variables:
amount - raw amount of the mineral this container is holding, calculated by the defined value SHEET_MATERIAL_AMOUNT=2000.
max_amount - max raw amount of mineral this container can hold.
stack_type - type of the mineral sheet the container handles, used for output.
parent - object that this container is being used by, used for output.
MAX_STACK_SIZE - size of a stack of mineral sheets. Constant.
*/
/datum/component/material_container
/// The total amount of materials this material container contains
var/total_amount = 0
/// The maximum amount of materials this material container can contain
var/max_amount
/// Map of material ref -> amount
var/list/materials //Map of key = material ref | Value = amount
/// The list of materials that this material container can accept
var/list/allowed_materials
/// The typecache of things that this material container can accept
var/list/allowed_item_typecache
/// The last main material that was inserted into this container
var/last_inserted_id
/// A callback for checking wheter we can insert a material into this container
var/datum/callback/insertion_check
/// A callback invoked before materials are inserted into this container
var/datum/callback/precondition
/// A callback invoked after materials are inserted into this container
var/datum/callback/after_insert
/// The material container flags. See __DEFINES/materials.dm.
var/mat_container_flags
/// Sets up the proper signals and fills the list of materials with the appropriate references.
/datum/component/material_container/Initialize(list/init_mats, max_amt = 0, _mat_container_flags=NONE, list/allowed_mats=init_mats, list/allowed_items, datum/callback/_insertion_check, datum/callback/_precondition, datum/callback/_after_insert)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
materials = list()
max_amount = max(0, max_amt)
mat_container_flags = _mat_container_flags
allowed_materials = allowed_mats || list()
if(allowed_items)
if(ispath(allowed_items) && allowed_items == /obj/item/stack)
allowed_item_typecache = GLOB.typecache_stack
else
allowed_item_typecache = typecacheof(allowed_items)
insertion_check = _insertion_check
precondition = _precondition
after_insert = _after_insert
for(var/mat in init_mats) //Make the assoc list material reference -> amount
var/mat_ref = GET_MATERIAL_REF(mat)
if(isnull(mat_ref))
continue
var/mat_amt = init_mats[mat]
if(isnull(mat_amt))
mat_amt = 0
materials[mat_ref] += mat_amt
/datum/component/material_container/Destroy(force)
materials = null
allowed_materials = null
if(insertion_check)
QDEL_NULL(insertion_check)
if(precondition)
QDEL_NULL(precondition)
if(after_insert)
QDEL_NULL(after_insert)
return ..()
/datum/component/material_container/RegisterWithParent()
. = ..()
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
if(mat_container_flags & MATCONTAINER_EXAMINE)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
/datum/component/material_container/vv_edit_var(var_name, var_value)
var/old_flags = mat_container_flags
. = ..()
if(var_name == NAMEOF(src, mat_container_flags) && parent)
if(!(old_flags & MATCONTAINER_EXAMINE) && mat_container_flags & MATCONTAINER_EXAMINE)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
else if(old_flags & MATCONTAINER_EXAMINE && !(mat_container_flags & MATCONTAINER_EXAMINE))
UnregisterSignal(parent, COMSIG_PARENT_EXAMINE)
if(old_flags & MATCONTAINER_NO_INSERT && !(mat_container_flags & MATCONTAINER_NO_INSERT))
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
else if(!(old_flags & MATCONTAINER_NO_INSERT) && mat_container_flags & MATCONTAINER_NO_INSERT)
UnregisterSignal(parent, COMSIG_PARENT_ATTACKBY)
/datum/component/material_container/proc/on_examine(datum/source, mob/user, list/examine_texts)
SIGNAL_HANDLER
for(var/datum/material/M as anything in materials)
var/amt = materials[M]
if(amt)
examine_texts += span_notice("It has [amt] units of [lowertext(M.name)] stored.")
/// Proc that allows players to fill the parent with mats
/datum/component/material_container/proc/on_attackby(datum/source, obj/item/I, mob/living/user)
SIGNAL_HANDLER
var/list/tc = allowed_item_typecache
if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.a_intent != I_HELP)
return
if(tc && !is_type_in_typecache(I, tc))
if(!(mat_container_flags & MATCONTAINER_SILENT))
to_chat(user, span_warning("[parent] won't accept [I]!"))
return
. = COMPONENT_CANCEL_ATTACK_CHAIN
var/datum/callback/pc = precondition
if(pc && !pc.Invoke(user))
return
if(istype(I, /obj/item/stack))
var/obj/item/stack/S = I
user_insert_stack(S, user, mat_container_flags)
return
var/material_amount = get_item_material_amount(I, mat_container_flags)
if(!material_amount)
to_chat(user, span_warning("[I] does not contain sufficient materials to be accepted by [parent]."))
return
if(!has_space(material_amount))
to_chat(user, span_warning("[parent] is full. Please remove materials from [parent] in order to insert more."))
return
user_insert(I, user, mat_container_flags)
/// Proc used for when player inserts a stack
/datum/component/material_container/proc/user_insert_stack(obj/item/stack/S, mob/living/user, breakdown_flags = mat_container_flags)
var/sheets = S.get_amount()
if(sheets < 1)
to_chat(user, span_warning("[S] does not contain sufficient materials to be accepted by [parent]."))
return
// Cache this since S may go away after use()
var/list/sheet_matter = S.matter
// Calculate total amount of material for one sheet
var/matter_per_sheet = 0
for(var/material in sheet_matter)
matter_per_sheet += sheet_matter[material]
// If any part of a sheet can't go in us, the whole sheet is invalid
if(!can_hold_material(GET_MATERIAL_REF(material)))
to_chat(user, span_warning("[parent] cannot contain [material]."))
return
// Our sheet had no material. Whoops.
if(!matter_per_sheet)
to_chat(user, span_warning("[S] does not contain any matter acceptable by [parent]."))
return
// If we can't fit the material for one sheet, we're full.
if(!has_space(matter_per_sheet))
to_chat(user, span_warning("[parent] is full. Please remove materials from [parent] in order to insert more."))
return
// Calculate the maximum amount of sheets we could possibly accept.
var/max_sheets = round((max_amount - total_amount) / matter_per_sheet)
if(max_sheets <= 0)
to_chat(user, span_warning("[parent] is full. Please remove materials from [parent] in order to insert more."))
return
// Calculate the amount of sheets we're actually going to use.
var/sheets_to_use = min(sheets, max_sheets)
// It shouldn't be possible to add more matter than our max
ASSERT((total_amount + (matter_per_sheet * sheets_to_use)) <= max_amount)
// Use the amount of sheets from the stack
if(!S.use(sheets_to_use))
to_chat(user, span_warning("Something went wrong with your stack. Split it manually and try again."))
return
// We're going to blindly insert all of the materials, our assertion above says it shouldn't be possible to overflow
var/inserted = 0
for(var/matter in sheet_matter)
var/datum/material/MAT = GET_MATERIAL_REF(matter)
inserted += insert_amount_mat(sheet_matter[matter] * sheets_to_use, MAT)
last_inserted_id = matter
// Tell the user and wrap up.
to_chat(user, span_notice("You insert a material total of [inserted] into [parent]."))
if(after_insert)
after_insert.Invoke(S, last_inserted_id, inserted)
/// Proc used for when player inserts materials
/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user, breakdown_flags = mat_container_flags)
set waitfor = FALSE
var/active_held = user.get_active_hand() // differs from I when using TK
if(!user.unEquip(I))
to_chat(user, span_warning("[I] is stuck to you and cannot be placed into [parent]."))
return
var/inserted = insert_item(I, breakdown_flags = mat_container_flags)
if(inserted)
to_chat(user, span_notice("You insert a material total of [inserted] into [parent]."))
qdel(I)
if(after_insert)
after_insert.Invoke(I, last_inserted_id, inserted)
else if(I == active_held)
user.put_in_active_hand(I)
/// Proc specifically for inserting items, returns the amount of materials entered.
/datum/component/material_container/proc/insert_item(obj/item/I, multiplier = 1, breakdown_flags = mat_container_flags)
if(QDELETED(I))
return FALSE
multiplier = CEILING(multiplier, 0.01)
var/material_amount = get_item_material_amount(I, breakdown_flags)
if(!material_amount || !has_space(material_amount))
return FALSE
last_inserted_id = insert_item_materials(I, multiplier, breakdown_flags)
return material_amount
/**
* Inserts the relevant materials from an item into this material container.
*
* Arguments:
* - [source][/obj/item]: The source of the materials we are inserting.
* - multiplier: The multiplier for the materials being inserted.
* - breakdown_flags: The breakdown bitflags that will be used to retrieve the materials from the source
*/
/datum/component/material_container/proc/insert_item_materials(obj/item/source, multiplier = 1, breakdown_flags = mat_container_flags)
var/primary_mat
var/max_mat_value = 0
var/list/item_materials = source.get_material_composition(breakdown_flags)
for(var/MAT in item_materials)
if(!can_hold_material(MAT))
continue
materials[MAT] += item_materials[MAT] * multiplier
total_amount += item_materials[MAT] * multiplier
if(item_materials[MAT] > max_mat_value)
max_mat_value = item_materials[MAT]
primary_mat = MAT
return primary_mat
/**
* The default check for whether we can add materials to this material container.
*
* Arguments:
* - [mat][/atom/material]: The material we are checking for insertability.
*/
/datum/component/material_container/proc/can_hold_material(datum/material/mat)
if(mat in allowed_materials)
return TRUE
if(istype(mat) && ((mat.name in allowed_materials) || (mat.type in allowed_materials)))
allowed_materials += mat // This could get messy with passing lists by ref... but if you're doing that the list expansion is probably being taken care of elsewhere anyway...
return TRUE
if(insertion_check?.Invoke(mat))
allowed_materials += mat
return TRUE
return FALSE
/// For inserting an amount of material
/datum/component/material_container/proc/insert_amount_mat(amt, datum/material/mat)
if(amt <= 0 || !has_space(amt))
return 0
var/total_amount_saved = total_amount
if(mat)
if(!istype(mat))
mat = GET_MATERIAL_REF(mat)
materials[mat] += amt
total_amount += amt
else
var/num_materials = length(materials)
if(!num_materials)
return 0
amt /= num_materials
for(var/i in materials)
materials[i] += amt
total_amount += amt
return (total_amount - total_amount_saved)
/// Uses an amount of a specific material, effectively removing it.
/datum/component/material_container/proc/use_amount_mat(amt, datum/material/mat)
if(!istype(mat))
mat = GET_MATERIAL_REF(mat)
if(!mat)
return 0
var/amount = materials[mat]
if(amount < amt)
return 0
materials[mat] -= amt
total_amount -= amt
return amt
/// Proc for transfering materials to another container.
/datum/component/material_container/proc/transer_amt_to(datum/component/material_container/T, amt, datum/material/mat)
if(!istype(mat))
mat = GET_MATERIAL_REF(mat)
if((amt==0)||(!T)||(!mat))
return FALSE
if(amt<0)
return T.transer_amt_to(src, -amt, mat)
var/tr = min(amt, materials[mat], T.can_insert_amount_mat(amt, mat))
if(tr)
use_amount_mat(tr, mat)
T.insert_amount_mat(tr, mat)
return tr
return FALSE
/// Proc for checking if there is room in the component, returning the amount or else the amount lacking.
/datum/component/material_container/proc/can_insert_amount_mat(amt, datum/material/mat)
if(!amt || !mat)
return 0
if((total_amount + amt) <= max_amount)
return amt
else
return (max_amount - total_amount)
/// For consuming a dictionary of materials. mats is the map of materials to use and the corresponding amounts, example: list(M/datum/material/glass =100, datum/material/iron=200)
/datum/component/material_container/proc/use_materials(list/mats, multiplier=1)
if(!mats || !length(mats))
return FALSE
var/list/mats_to_remove = list() //Assoc list MAT | AMOUNT
for(var/datum/material/req_mat as anything in mats) //Loop through all required materials
var/imat = req_mat
if(!istype(req_mat))
req_mat = GET_MATERIAL_REF(req_mat) //Get the ref if necesary
if(!materials[req_mat]) //Do we have the resource?
return FALSE //Can't afford it
var/amount_required = mats[imat] * multiplier
if(!(materials[req_mat] >= amount_required)) // do we have enough of the resource?
return FALSE //Can't afford it
mats_to_remove[req_mat] += amount_required //Add it to the assoc list of things to remove
continue
var/total_amount_save = total_amount
for(var/i in mats_to_remove)
total_amount_save -= use_amount_mat(mats_to_remove[i], i)
return total_amount_save - total_amount
/// For spawning mineral sheets at a specific location. Used by machines to output sheets.
/datum/component/material_container/proc/retrieve_sheets(sheet_amt, datum/material/M, atom/target = null)
if(!M.stack_type)
return 0 //Add greyscale sheet handling here later
if(sheet_amt <= 0)
return 0
if(!target)
var/atom/parent_atom = parent
target = parent_atom.drop_location()
if(materials[M] < (sheet_amt * SHEET_MATERIAL_AMOUNT))
sheet_amt = round(materials[M] / SHEET_MATERIAL_AMOUNT)
var/obj/item/stack/S = M.stack_type
var/max_stack_size = initial(S.max_amount)
var/count = 0
while(sheet_amt > max_stack_size)
new M.stack_type(target, max_stack_size, null, list((M) = SHEET_MATERIAL_AMOUNT))
count += max_stack_size
use_amount_mat(sheet_amt * SHEET_MATERIAL_AMOUNT, M)
sheet_amt -= max_stack_size
if(sheet_amt >= 1)
new M.stack_type(target, sheet_amt, null, list((M) = SHEET_MATERIAL_AMOUNT))
count += sheet_amt
use_amount_mat(sheet_amt * SHEET_MATERIAL_AMOUNT, M)
return count
/// Proc to get all the materials and dump them as sheets
/datum/component/material_container/proc/retrieve_all(target = null)
var/result = 0
for(var/MAT in materials)
var/amount = materials[MAT]
result += retrieve_sheets(amount2sheet(amount), MAT, target)
return result
/// Proc that returns TRUE if the container has space
/datum/component/material_container/proc/has_space(amt = 0)
return (total_amount + amt) <= max_amount
/// Checks if its possible to afford a certain amount of materials. Takes a dictionary of materials.
/datum/component/material_container/proc/has_materials(list/mats, multiplier=1)
if(!mats || !mats.len)
return FALSE
for(var/datum/material/req_mat as anything in mats) //Loop through all required materials
var/imat = req_mat
if(!istype(req_mat))
if(ispath(req_mat) || istext(req_mat)) //Is this an actual material, or is it a category?
req_mat = GET_MATERIAL_REF(req_mat) //Get the ref
// else // Its a category. (For example MAT_CATEGORY_RIGID)
// if(!has_enough_of_category(req_mat, mats[x], multiplier)) //Do we have enough of this category?
// return FALSE
// else
// continue
if(!has_enough_of_material(req_mat, mats[imat], multiplier))//Not a category, so just check the normal way
return FALSE
return TRUE
/// Returns all the categories in a recipe.
/datum/component/material_container/proc/get_categories(list/mats)
var/list/categories = list()
for(var/x in mats) //Loop through all required materials
if(!istext(x)) //This means its not a category
continue
categories += x
return categories
/// Returns TRUE if you have enough of the specified material.
/datum/component/material_container/proc/has_enough_of_material(datum/material/req_mat, amount, multiplier=1)
if(!materials[req_mat]) //Do we have the resource?
return FALSE //Can't afford it
var/amount_required = amount * multiplier
if(materials[req_mat] >= amount_required) // do we have enough of the resource?
return TRUE
return FALSE //Can't afford it
/// Returns TRUE if you have enough of a specified material category (Which could be multiple materials)
// /datum/component/material_container/proc/has_enough_of_category(category, amount, multiplier=1)
// for(var/i in SSmaterials.materials_by_category[category])
// var/datum/material/mat = i
// if(materials[mat] >= amount) //we have enough
// return TRUE
// return FALSE
/// Turns a material amount into the amount of sheets it should output
/datum/component/material_container/proc/amount2sheet(amt)
if(amt >= SHEET_MATERIAL_AMOUNT)
return round(amt / SHEET_MATERIAL_AMOUNT)
return FALSE
/// Turns an amount of sheets into the amount of material amount it should output
/datum/component/material_container/proc/sheet2amount(sheet_amt)
if(sheet_amt > 0)
return sheet_amt * SHEET_MATERIAL_AMOUNT
return FALSE
///returns the amount of material relevant to this container; if this container does not support glass, any glass in 'I' will not be taken into account
/datum/component/material_container/proc/get_item_material_amount(obj/item/I, breakdown_flags = mat_container_flags)
if(!istype(I) || !I.matter)
return 0
var/material_amount = 0
var/list/item_materials = I.get_material_composition(breakdown_flags)
for(var/MAT in item_materials)
if(!can_hold_material(MAT))
continue
material_amount += item_materials[MAT]
return material_amount
/// Returns the amount of a specific material in this container.
/datum/component/material_container/proc/get_material_amount(datum/material/mat)
if(!istype(mat))
mat = GET_MATERIAL_REF(mat)
return materials[mat]
/// List format is list(list(name = ..., amount = ..., ref = ..., etc.), list(...))
/datum/component/material_container/tgui_data(mob/user, skip_empty = FALSE)
var/list/data = list()
for(var/datum/material/material as anything in materials)
var/amount = materials[material]
if(amount == 0 && skip_empty)
continue
data += list(list(
"name" = material.name,
"ref" = REF(material),
"amount" = amount,
"sheets" = round(amount / SHEET_MATERIAL_AMOUNT),
"removable" = amount >= SHEET_MATERIAL_AMOUNT,
))
return data
@@ -0,0 +1,745 @@
/*!
This datum should be used for handling mineral contents of machines and whatever else is supposed to hold minerals and make use of them.
Variables:
amount - raw amount of the mineral this container is holding, calculated by the defined value SHEET_MATERIAL_AMOUNT=SHEET_MATERIAL_AMOUNT.
max_amount - max raw amount of mineral this container can hold.
sheet_type - type of the mineral sheet the container handles, used for output.
parent - object that this container is being used by, used for output.
MAX_STACK_SIZE - size of a stack of mineral sheets. Constant.
*/
//The full item was consumed
#define MATERIAL_INSERT_ITEM_SUCCESS 1
/datum/component/material_container
/// The maximum amount of materials this material container can contain
var/max_amount
/// Map of material ref -> amount
var/list/materials //Map of key = material ref | Value = amount
/// The list of materials that this material container can accept
var/list/allowed_materials
/// The typecache of things that this material container can accept
var/list/allowed_item_typecache
/// The last main material that was inserted into this container
var/last_inserted_id
/// Whether or not this material container allows specific amounts from sheets to be inserted
var/precise_insertion = FALSE
/// The material container flags. See __DEFINES/construction/materials.dm.
var/mat_container_flags
/// Signals that are registered with this contained
var/list/registered_signals
/// Sets up the proper signals and fills the list of materials with the appropriate references.
/datum/component/material_container/Initialize(
list/init_mats,
max_amt = 0,
_mat_container_flags = NONE,
list/allowed_mats = init_mats,
list/allowed_items,
list/container_signals
)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
materials = list()
max_amount = max(0, max_amt)
mat_container_flags = _mat_container_flags
allowed_materials = allowed_mats || list()
if(allowed_items)
if(ispath(allowed_items) && allowed_items == /obj/item/stack)
allowed_item_typecache = GLOB.typecache_stack
else
allowed_item_typecache = typecacheof(allowed_items)
for(var/mat in init_mats) //Make the assoc list material reference -> amount
var/mat_ref = GET_MATERIAL_REF(mat)
if(isnull(mat_ref))
continue
var/mat_amt = init_mats[mat]
if(isnull(mat_amt))
mat_amt = 0
materials[mat_ref] += mat_amt
if(length(container_signals))
for(var/signal in container_signals)
parent.RegisterSignal(src, signal, container_signals[signal])
/datum/component/material_container/Destroy(force)
materials = null
allowed_materials = null
return ..()
/datum/component/material_container/RegisterWithParent()
. = ..()
// can we insert into this container
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
//to see available materials
if(mat_container_flags & MATCONTAINER_EXAMINE)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
//drop sheets when object is deconstructed but not deleted
RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, PROC_REF(drop_sheets))
/datum/component/material_container/UnregisterFromParent()
var/list/signals = list()
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
signals += COMSIG_PARENT_ATTACKBY
if(mat_container_flags & MATCONTAINER_EXAMINE)
signals += COMSIG_PARENT_EXAMINE
signals += COMSIG_OBJ_DECONSTRUCT
UnregisterSignal(parent, signals)
/datum/component/material_container/proc/drop_sheets()
SIGNAL_HANDLER
retrieve_all()
/datum/component/material_container/proc/on_examine(datum/source, mob/user, list/examine_texts)
SIGNAL_HANDLER
for(var/datum/material/M as anything in materials)
var/amt = materials[M]
if(amt)
examine_texts += span_notice("It has [amt] units of [lowertext(M.name)] stored.")
/datum/component/material_container/vv_edit_var(var_name, var_value)
var/old_flags = mat_container_flags
. = ..()
if(var_name == NAMEOF(src, mat_container_flags) && parent)
if(!(old_flags & MATCONTAINER_EXAMINE) && mat_container_flags & MATCONTAINER_EXAMINE)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
else if(old_flags & MATCONTAINER_EXAMINE && !(mat_container_flags & MATCONTAINER_EXAMINE))
UnregisterSignal(parent, COMSIG_PARENT_EXAMINE)
if(old_flags & MATCONTAINER_NO_INSERT && !(mat_container_flags & MATCONTAINER_NO_INSERT))
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
else if(!(old_flags & MATCONTAINER_NO_INSERT) && mat_container_flags & MATCONTAINER_NO_INSERT)
UnregisterSignal(parent, COMSIG_PARENT_ATTACKBY)
/**
* 3 Types of Procs
* Material Insertion : Insert materials into the container
* Material Validation : Checks how much materials are available, Extracts materials from items if the container can hold them
* Material Removal : Removes material from the container
*
* Each Proc further belongs to a specific category
* LOW LEVEL: Procs that are used internally & should not be used anywhere else unless you know what your doing
* MID LEVEL: Procs that can be used by machines(like recycler, stacking machines) to bypass majority of checks
* HIGH LEVEL: Procs that can be used by anyone publicly and guarantees safety checks & limits
*/
//================================Material Insertion procs==============================
//======================================LOW LEVEL=========================================
/**
* Inserts the relevant materials from an item into this material container.
* This low level proc should not be used directly by anyone
*
* Arguments:
* - [source][/obj/item]: The source of the materials we are inserting.
* - multiplier: The multiplier for the materials extract from this item being inserted.
* - context: the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_ITEM_CONSUMED
* and is used mostly for silo logging, the silo resends this signal on the context to give it a
* chance to process the item
*/
/datum/component/material_container/proc/insert_item_materials(obj/item/source, multiplier = 1, atom/context = parent)
var/primary_mat
var/max_mat_value = 0
var/material_amount = 0
var/list/item_materials = source.get_material_composition(mat_container_flags)
var/list/mats_consumed = list()
for(var/MAT in item_materials)
if(!can_hold_material(MAT))
continue
var/mat_amount = OPTIMAL_COST(item_materials[MAT] * multiplier)
materials[MAT] += mat_amount
if(item_materials[MAT] > max_mat_value)
max_mat_value = item_materials[MAT]
primary_mat = MAT
mats_consumed[MAT] = mat_amount
material_amount += mat_amount
if(length(mats_consumed))
SEND_SIGNAL(src, COMSIG_MATCONTAINER_ITEM_CONSUMED, source, primary_mat, mats_consumed, material_amount, context)
return primary_mat
//===================================================================================
//===============================MID LEVEL===================================================
/**
* For inserting an amount of material. Use this to add materials to the container directly
*
* Arguments:
* - amt: amount of said material to insert
* - mat: the material type to insert
*/
/datum/component/material_container/proc/insert_amount_mat(amt, datum/material/mat)
if(amt <= 0)
return 0
amt = OPTIMAL_COST(amt)
if(!has_space(amt))
return 0
var/total_amount_saved = total_amount()
if(mat)
if(!istype(mat))
mat = GET_MATERIAL_REF(mat)
materials[mat] += amt
else
var/num_materials = length(materials)
if(!num_materials)
return 0
amt /= num_materials
for(var/i in materials)
materials[i] += amt
return (total_amount() - total_amount_saved)
/**
* Proc specifically for inserting items, use this when you want to insert any item into the container
* this bypasses most of the material flag checks so much be used by machines like recycler, stacking machine etc that
* does not care for such checks
*
* Arguments:
* - [weapon][obj/item]: the item you are trying to insert
* - multiplier: The multiplier for the materials being inserted
* - context: the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_ITEM_CONSUMED and is used mostly for silo logging
* * - delete_item: should we delete the item after its materials are consumed. does not apply to stacks if they were split due to lack of space
*/
/datum/component/material_container/proc/insert_item(obj/item/weapon, multiplier = 1, atom/context = parent, delete_item = TRUE)
if(QDELETED(weapon))
return MATERIAL_INSERT_ITEM_NO_MATS
multiplier = CEILING(multiplier, 0.01)
var/obj/item/target = weapon
var/material_amount = OPTIMAL_COST(get_item_material_amount(target, mat_container_flags) * multiplier)
if(!material_amount)
return MATERIAL_INSERT_ITEM_NO_MATS
var/obj/item/stack/item_stack
if(istype(target, /obj/item/stack) && !has_space(material_amount)) //not enough space split and feed as many sheets possible
item_stack = weapon
var/space_left = max_amount - total_amount()
if(!space_left)
return MATERIAL_INSERT_ITEM_NO_SPACE
var/material_per_sheet = material_amount / item_stack.amount
var/sheets_to_insert = round(space_left / material_per_sheet)
if(!sheets_to_insert)
return MATERIAL_INSERT_ITEM_NO_SPACE
target = fast_split_stack(item_stack, sheets_to_insert)
material_amount = get_item_material_amount(target, mat_container_flags) * multiplier
material_amount = OPTIMAL_COST(material_amount)
last_inserted_id = insert_item_materials(target, multiplier, context)
if(!isnull(last_inserted_id))
if(delete_item || target != weapon) //we could have split the stack ourselves
qdel(target) //item gone
return material_amount
else if(!isnull(item_stack) && item_stack != target) //insertion failed, merge the split stack back into the original
var/obj/item/stack/inserting_stack = target
item_stack.add(inserting_stack.amount)
qdel(inserting_stack)
return MATERIAL_INSERT_ITEM_FAILURE
//============================================================================================
//===================================HIGH LEVEL===================================================
/**
* inserts an item from the players hand into the container. Loops through all the contents inside recursively
* Does all explicit checking for mat flags & callbacks to check if insertion is valid
* This proc is what you should be using for almost all cases
*
* Arguments:
* * held_item - the item to insert
* * user - the mob inserting this item
* * context - the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_ITEM_CONSUMED and is used mostly for silo logging
*/
/datum/component/material_container/proc/user_insert(obj/item/held_item, mob/living/user, atom/context = parent)
set waitfor = FALSE
. = 0
//All items that do not have any contents
var/list/obj/item/items = list(held_item)
//is this the first item we are ever processing
var/first_checks = TRUE
//list of items to delete
var/list/obj/item/to_delete = list()
//The status of the last insert attempt
var/inserted = 0
//All messages to be displayed to chat
var/list/chat_msgs = list()
//differs from held_item when using TK
var/obj/item/active_held = user.get_active_hand()
while(items.len)
//no point inserting more items
if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE)
break
//Pop the 1st item out from the list
var/obj/item/target_item = items[1]
items -= target_item
//e.g. projectiles inside bullets are not objects
if(!istype(target_item))
continue
//can't allow abstract items
if(target_item.abstract)
continue
// user defined conditions
if(SEND_SIGNAL(src, COMSIG_MATCONTAINER_PRE_USER_INSERT, target_item, user) & MATCONTAINER_BLOCK_INSERT)
continue
//item is either indestructible, not allowed for redemption or not in the allowed types
if(allowed_item_typecache && !is_type_in_typecache(target_item, allowed_item_typecache))
if(!(mat_container_flags & MATCONTAINER_SILENT))
var/list/status_data = chat_msgs["[MATERIAL_INSERT_ITEM_FAILURE]"] || list()
var/list/item_data = status_data[target_item.name] || list()
item_data["count"] += 1
status_data[target_item.name] = item_data
chat_msgs["[MATERIAL_INSERT_ITEM_FAILURE]"] = status_data
//storage items usually come here
//this is so players can insert items from their bags into machines for convinience
if(!target_item.contents.len)
continue
//at this point we can check if we have enough for all items & other stuff
if(first_checks)
//anything that isn't a stack cannot be split so find out if we have enough space, we don't want to consume half the contents of an object & leave it in a broken state
//for duffle bags and other storage items we can check for space 1 item at a time
if(!istype(target_item, /obj/item/stack))
var/total_amount = 0
for(var/obj/item/weapon as anything in target_item.get_all_contents_type(/obj/item))
total_amount += get_item_material_amount(weapon)
if(!has_space(total_amount))
if(!(mat_container_flags & MATCONTAINER_SILENT))
to_chat(user, span_warning("[parent] does not have enough space for [target_item]!"))
return
first_checks = FALSE
//if stack, check if we want to read precise amount of sheets to insert
var/obj/item/stack/item_stack = null
if(istype(target_item, /obj/item/stack) && precise_insertion)
var/atom/current_parent = parent
item_stack = target_item
var/requested_amount = tgui_input_number(user, "How much do you want to insert?", "Inserting [item_stack.singular_name]s", item_stack.amount, item_stack.amount)
if(!requested_amount || QDELETED(target_item) || QDELETED(user) || QDELETED(src))
continue
if(parent != current_parent || user.get_active_hand() != active_held)
continue
if(requested_amount != item_stack.amount) //only split if its not the whole amount
target_item = fast_split_stack(item_stack, requested_amount) //split off the requested amount
requested_amount = 0
//is this item a stack and was it split by the player?
var/was_stack_split = !isnull(item_stack) && item_stack != target_item
//if it was split then item_stack has the reference to the original stack/item
var/obj/item/original_item = was_stack_split ? item_stack : target_item
//if this item is not the one the player is holding then don't remove it from their hand
if(original_item != active_held)
original_item = null
if(!isnull(original_item) && !user.temporarilyRemoveItemFromInventory(original_item)) //remove from hand(if split remove the original stack else the target)
return
//insert the item
var/item_name = target_item.name
var/item_count = 1
var/is_stack = FALSE
var/obj/item/stack/the_stack
if(istype(target_item, /obj/item/stack))
the_stack = target_item
item_name = the_stack.singular_name
item_count = the_stack.amount
is_stack = TRUE
//we typically don't want to consume bags, boxes but only their contents. so we skip processing
inserted = !istype(target_item, /obj/item/storage) ? insert_item(target_item, 1, context, is_stack) : 0
if(inserted > 0)
. += inserted
inserted /= SHEET_MATERIAL_AMOUNT // display units inserted as sheets for improved readability
//collect all messages to print later
var/list/status_data = chat_msgs["[MATERIAL_INSERT_ITEM_SUCCESS]"] || list()
var/list/item_data = status_data[item_name] || list()
item_data["count"] += item_count
item_data["amount"] += inserted
item_data["stack"] = is_stack
status_data[item_name] = item_data
chat_msgs["[MATERIAL_INSERT_ITEM_SUCCESS]"] = status_data
//delete the item or merge stacks if any left over
if(is_stack)
//player split it & machine further split that due to lack of space? merge with remaining stack
if(!QDELETED(target_item) && was_stack_split)
var/obj/item/stack/inserting_stack = target_item
item_stack.add(inserting_stack.amount)
qdel(inserting_stack)
//was this the original item in the players hand? put what's left back in the player's hand
if(!QDELETED(original_item))
user.put_in_active_hand(original_item)
//skip processing children & other stuff. irrelevant for stacks
continue
//queue the object for deletion
to_delete += target_item
else
//collect all messages to print later
var/list/status_data = chat_msgs["[inserted]"] || list()
var/list/item_data = status_data[item_name] || list()
item_data["count"] += item_count
status_data[item_name] = item_data
chat_msgs["[inserted]"] = status_data
//player split the stack by the requested amount but even that split amount could not be salvaged. merge it back with the original
if(was_stack_split)
var/obj/item/stack/inserting_stack = target_item
item_stack.add(inserting_stack.amount)
qdel(inserting_stack)
//was this the original item in the players hand? put it back because we coudn't salvage it
if(!QDELETED(original_item))
user.put_in_active_hand(original_item)
//we can stop here as remaining items will fail to insert as well
if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE)
break
//we failed to process the item so don't bother going into its contents
//but if we are dealing with storage items like bags, boxes etc then we make a exception
if(!istype(target_item, /obj/item/storage))
continue
//If any mats were consumed we can proceed to delete the parent
//If it has children then we will process them first in the 2nd round
//This is done so we don't delete the children when the parent is consumed
//We only do this on the 1st iteration so we don't re-iterate through its children again
if(target_item.contents.len)
//process children
items += target_item.contents
//we now summarize the chat msgs collected
if(!(mat_container_flags & MATCONTAINER_SILENT))
for(var/status as anything in chat_msgs)
var/list/status_data = chat_msgs[status]
for(var/item_name as anything in status_data)
//read the params
var/list/chat_data = status_data[item_name]
var/count = chat_data["count"]
var/amount = chat_data["amount"]
//decode the message
switch(text2num(status))
if(MATERIAL_INSERT_ITEM_SUCCESS) //no problems full item was consumed
if(chat_data["stack"])
var/sheets = min(count, amount) //minimum between sheets inserted vs sheets consumed(values differ for alloys)
to_chat(user, span_notice("[sheets > 1 ? "[sheets] " : ""][item_name][sheets > 1 ? "s were" : " was"] added to [parent]."))
else
to_chat(user, span_notice("[count > 1 ? "[count] " : ""][item_name][count > 1 ? "s" : ""], worth [amount] sheets, [count > 1 ? "were" : "was"] added to [parent]."))
if(MATERIAL_INSERT_ITEM_NO_SPACE) //no space
to_chat(user, span_warning("[parent] has no space to accept [item_name]!"))
if(MATERIAL_INSERT_ITEM_NO_MATS) //no materials inside these items
to_chat(user, span_warning("[item_name][count > 1 ? "s have" : " has"] no materials that can be accepted by [parent]!"))
if(MATERIAL_INSERT_ITEM_FAILURE) //could be because the material type was not accepted or other stuff
to_chat(user, span_warning("[item_name][count > 1 ? "s were" : " was"] rejected by [parent]!"))
//finally delete the items
for(var/obj/item/deleting as anything in to_delete)
if(!QDELETED(deleting)) //deleting parents also delete their children so we check
qdel(deleting)
/datum/component/material_container/proc/on_attackby(datum/source, obj/item/I, mob/living/user)
SIGNAL_HANDLER
return attempt_insert(user, I)
/// Proc that allows players to fill the parent with mats
/datum/component/material_container/proc/attempt_insert(mob/living/user, obj/item/weapon)
if(istype(parent, /obj/machinery))
var/obj/machinery/machine = parent
if(machine.stat || machine.panel_open)
return
user_insert(weapon, user)
return TRUE
//===============================================================================================
//======================================Material Validation=======================================
//=========================================LOW LEVEL===================================
/**
* Proc that returns TRUE if the container has space
*
* Arguments:
* * amt - can this container hold this much amount of materials
*/
/datum/component/material_container/proc/has_space(amt = 0)
return (total_amount() + amt) <= max_amount
/**
* The default check for whether we can add materials to this material container.
*
* Arguments:
* - [mat][/atom/material]: The material we are checking for insertability.
*/
/datum/component/material_container/proc/can_hold_material(datum/material/mat)
if(mat in allowed_materials)
return TRUE
if(istype(mat) && ((mat.name in allowed_materials) || (mat.type in allowed_materials)))
allowed_materials += mat // This could get messy with passing lists by ref... but if you're doing that the list expansion is probably being taken care of elsewhere anyway...
return TRUE
if(SEND_SIGNAL(src, COMSIG_MATCONTAINER_MAT_CHECK, mat) & MATCONTAINER_ALLOW_MAT)
allowed_materials += mat
return TRUE
return FALSE
//========================================================================================
//===================================MID LEVEL=============================================
/**
* Returns the amount of a specific material in this container.
*
* Arguments:
* -[mat][datum/material] : the material type to check for 3 cases
* a) If it's an path its ref is retrieved
* b) If it's text then its an category material & there is no way to deal with it so return 0
* c) If normal material proceeds as usual
*/
/datum/component/material_container/proc/get_material_amount(datum/material/mat)
if(!istype(mat))
mat = GET_MATERIAL_REF(mat)
return materials[mat]
/**
* Returns the amount of material relevant to this container;
* if this container does not support glass, any glass in 'I' will not be taken into account
*
* Arguments:
* - [I][obj/item]: the item whos materials must be retrieved
*/
/datum/component/material_container/proc/get_item_material_amount(obj/item/I, breakdown_flags = mat_container_flags)
if(!istype(I) || !I.matter)
return 0
var/material_amount = 0
var/list/item_materials = I.get_material_composition(breakdown_flags)
for(var/MAT in item_materials)
if(!can_hold_material(MAT))
continue
material_amount += item_materials[MAT]
return material_amount
//================================================================================================
//=========================================HIGH LEVEL==========================================
/// returns the total amount of material in the container
/datum/component/material_container/proc/total_amount()
. = 0
for(var/i in materials)
. += get_material_amount(i)
/**
* Returns TRUE if you have enough of the specified material.
*
* Arguments:
* - [req_mat][datum/material]: the material to check for
* - amount: how much material do we need
*/
/datum/component/material_container/proc/has_enough_of_material(datum/material/req_mat, amount = 1)
return get_material_amount(req_mat) >= OPTIMAL_COST(amount)
/**
* Checks if its possible to afford a certain amount of materials. Takes a dictionary of materials.
* coefficient can be thought of as the machines efficiency & multiplier as the print quantity
*
* Arguments:
* - mats: list of materials(key=material, value= 1 unit of material) to check for
* - coefficient: scaling applied to 1 unit of material in the mats list
* - multiplier: how many units(after scaling) do we require
*/
/datum/component/material_container/proc/has_materials(list/mats, coefficient = 1, multiplier = 1)
if(!length(mats))
return FALSE
for(var/x in mats) //Loop through all required materials
var/wanted = OPTIMAL_COST(mats[x] * coefficient) * multiplier
if(!has_enough_of_material(x, wanted))//Not a category, so just check the normal way
testing("didn't have: [x] wanted: [wanted]")
return FALSE
return TRUE
//==========================================================================================================
//================================================Material Usage============================================
//==================================================LOW LEVEL=======================================
/**
* Uses an amount of a specific material, effectively removing it.
*
* Arguments:
* - amt: amount of said material to use
* - [mat][datum/material]: type of mat to use
*/
/datum/component/material_container/proc/use_amount_mat(amt, datum/material/mat)
//round amount
amt = OPTIMAL_COST(amt)
//get ref if necessary
if(!istype(mat))
mat = GET_MATERIAL_REF(mat)
if(materials[mat] < amt)
return 0
materials[mat] -= amt
return amt
//==============================================================================================
//=========================================MID LEVEL==========================================
/**
* For consuming a dictionary of materials.
*
* Arguments:
* - mats: map of materials to consume(key = material type, value = amount)
* - coefficient: how much fraction of unit material in the mats list must be consumed. This is usually your machines efficiency
* - multiplier: how many units of material in the mats list(after each unit is multiplied and rounded with coefficient) must be consumed, This is usually your print quantity
*/
/datum/component/material_container/proc/use_materials(list/mats, coefficient = 1, multiplier = 1)
if(!mats || !length(mats))
return FALSE
var/amount_removed = 0
for(var/i in mats)
amount_removed += use_amount_mat(OPTIMAL_COST(mats[i] * coefficient) * multiplier, i)
return amount_removed
//============================================================================================
//===========================================HIGH LEVEL=======================================
/**
* For spawning mineral sheets at a specific location. Used by machines to output sheets.
*
* Arguments:
* sheet_amt: number of sheets to extract
* [material][datum/material]: type of sheets present in this container to extract
* [target][atom]: drop location
* [atom][context]: context - the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_SHEETS_RETRIEVED and is used mostly for silo logging
*/
/datum/component/material_container/proc/retrieve_sheets(sheet_amt, datum/material/material, atom/target = null, atom/context = parent)
//do we support sheets of this material
if(!material.stack_type)
return 0 //Add greyscale sheet handling here later
if(!can_hold_material(material))
return 0
//requested amount greater than available amount or just an invalid value
sheet_amt = min(round(materials[material] / SHEET_MATERIAL_AMOUNT), sheet_amt)
if(sheet_amt <= 0)
return 0
//auto drop location
if(!target)
var/atom/parent_atom = parent
target = parent_atom.drop_location()
if(!target)
return 0
//eject sheets based on available amount after each iteration
var/count = 0
while(sheet_amt > 0)
//don't merge yet. we need to do stuff with it first
var/obj/item/stack/material/new_sheets = new material.stack_type(target, min(sheet_amt, MAX_STACK_SIZE), FALSE)
count += new_sheets.amount
//use material & deduct work needed
use_amount_mat(new_sheets.amount * SHEET_MATERIAL_AMOUNT, material)
sheet_amt -= new_sheets.amount
//send signal
SEND_SIGNAL(src, COMSIG_MATCONTAINER_SHEETS_RETRIEVED, new_sheets, context)
//no point merging anything into an already full stack
if(new_sheets.amount == new_sheets.max_amount)
continue
//now we can merge since we are done with it
for(var/obj/item/stack/item_stack in target)
if(item_stack == new_sheets || item_stack.type != material.stack_type) //don't merge with self or different type
continue
//speed merge
var/merge_amount = min(item_stack.amount, new_sheets.max_amount - new_sheets.get_amount())
item_stack.use(merge_amount)
new_sheets.add(merge_amount)
break
return count
/**
* Proc to get all the materials and dump them as sheets
*
* Arguments:
* - target: drop location of the sheets
* - context: the atom which is ejecting the sheets. Used mostly in silo logging
*/
/datum/component/material_container/proc/retrieve_all(target = null, atom/context = parent)
var/result = 0
for(var/MAT in materials)
result += retrieve_sheets(amount2sheet(materials[MAT]), MAT, target, context)
return result
//============================================================================================
/// Turns a material amount into the amount of sheets it should output
/datum/component/material_container/proc/amount2sheet(amt)
if(amt >= SHEET_MATERIAL_AMOUNT)
return round(amt / SHEET_MATERIAL_AMOUNT)
return FALSE
/// Turns an amount of sheets into the amount of material amount it should output
/datum/component/material_container/proc/sheet2amount(sheet_amt)
if(sheet_amt > 0)
return sheet_amt * SHEET_MATERIAL_AMOUNT
return FALSE
/datum/component/material_container/tgui_static_data(mob/user)
var/list/data = list()
data["SHEET_MATERIAL_AMOUNT"] = SHEET_MATERIAL_AMOUNT
return data
/// List format is list(list(name = ..., amount = ..., ref = ..., etc.), list(...))
/datum/component/material_container/tgui_data(mob/user, skip_empty = FALSE)
var/list/data = list()
for(var/datum/material/material as anything in materials)
var/amount = materials[material]
if(amount == 0 && skip_empty)
continue
data += list(list(
"name" = material.name,
"ref" = REF(material),
"amount" = amount,
"color" = material.icon_colour,
"sheets" = round(amount / SHEET_MATERIAL_AMOUNT),
"removable" = amount >= SHEET_MATERIAL_AMOUNT,
))
return data
#undef MATERIAL_INSERT_ITEM_SUCCESS
@@ -0,0 +1,277 @@
/*
This component allows machines to connect remotely to a material container
(namely an /obj/machinery/ore_silo) elsewhere. It offers optional graceful
fallback to a local material storage in case remote storage is unavailable, and
handles linking back and forth.
*/
/datum/component/remote_materials
// Three possible states:
// 1. silo exists, materials is parented to silo
// 2. silo is null, materials is parented to parent
// 3. silo is null, materials is null
///The silo machine this container is connected to
var/obj/machinery/ore_silo/silo
///Material container. the value is either the silo or local
var/datum/component/material_container/mat_container
///Should we create a local storage if we can't connect to silo
var/allow_standalone
///Local size of container when silo = null
var/local_size = INFINITY
///Flags used for the local material container(exceptions for item insert & intent flags)
var/mat_container_flags = NONE
///List of signals to hook onto the local container
var/list/mat_container_signals
/datum/component/remote_materials/Initialize(
mapload,
allow_standalone = TRUE,
force_connect = FALSE,
mat_container_flags = NONE,
list/mat_container_signals = null,
)
if (!isatom(parent))
return COMPONENT_INCOMPATIBLE
src.allow_standalone = allow_standalone
src.mat_container_flags = mat_container_flags
src.mat_container_signals = mat_container_signals
var/turf/T = get_turf(parent)
var/connect_to_silo = FALSE
if(force_connect || (mapload && (T.z in using_map.station_levels)))
connect_to_silo = TRUE
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_item_insert))
if(mapload) // wait for silo to initialize during mapload
SSticker.OnRoundstart(CALLBACK(src, PROC_REF(_PrepareStorage), connect_to_silo))
else //directly register in round
_PrepareStorage(connect_to_silo)
/**
* Internal proc. prepares local storage if onnect_to_silo = FALSE
*
* Arguments
* connect_to_silo- if true connect to global silo. If not successfull then go to local storage
* only if allow_standalone = TRUE, else you a null mat_container
*/
/datum/component/remote_materials/proc/_PrepareStorage(connect_to_silo)
PRIVATE_PROC(TRUE)
if (connect_to_silo)
silo = GLOB.ore_silo_default
if (silo)
silo.ore_connected_machines += src
mat_container = silo.materials
if(!mat_container && allow_standalone)
_MakeLocal()
/datum/component/remote_materials/Destroy()
if(silo)
allow_standalone = FALSE
disconnect()
mat_container = null
return ..()
/datum/component/remote_materials/proc/_MakeLocal()
PRIVATE_PROC(TRUE)
silo = null
mat_container = parent.AddComponent( \
/datum/component/material_container, \
subtypesof(/datum/material), \
local_size, \
mat_container_flags, \
container_signals = mat_container_signals, \
allowed_items = /obj/item/stack \
)
/// Adds/Removes this connection from the silo
/datum/component/remote_materials/proc/toggle_holding()
if(isnull(silo))
return
if(!silo.holds[src])
silo.holds[src] = TRUE
else
silo.holds -= src
/**
* Sets the storage size for local materials when not linked with silo
* Arguments
*
* * size - the new size for local storage. measured in SHEET_MATERIAL_SIZE units
*/
/datum/component/remote_materials/proc/set_local_size(size)
local_size = size
if (!silo && mat_container)
mat_container.max_amount = size
///Disconnects this component from the silo
/datum/component/remote_materials/proc/disconnect()
if(isnull(silo))
return
silo.ore_connected_machines -= src
silo = null
mat_container = null
if (allow_standalone)
_MakeLocal()
/datum/component/remote_materials/proc/OnMultitool(datum/source, mob/user, obj/item/multitool/M)
SIGNAL_HANDLER
. = NONE
if (!QDELETED(M.buffer) && istype(M.buffer, /obj/machinery/ore_silo))
if (silo == M.buffer)
to_chat(user, span_warning("[parent] is already connected to [silo]!"))
return FALSE
if(!check_z_level(M.buffer))
to_chat(user, span_warning("[parent] is too far away to get a connection signal!"))
return FALSE
var/obj/machinery/ore_silo/new_silo = M.buffer
var/datum/component/material_container/new_container = new_silo.GetComponent(/datum/component/material_container)
if (silo)
silo.ore_connected_machines -= src
silo.holds -= src
else if (mat_container)
//transfer all mats to silo. whatever cannot be transfered is dumped out as sheets
if(mat_container.total_amount())
for(var/datum/material/mat as anything in mat_container.materials)
var/mat_amount = mat_container.materials[mat]
if(!mat_amount || !new_container.has_space(mat_amount) || !new_container.can_hold_material(mat))
continue
new_container.materials[mat] += mat_amount
mat_container.materials[mat] = 0
qdel(mat_container)
silo = new_silo
silo.ore_connected_machines += src
mat_container = new_container
to_chat(user, span_notice("You connect [parent] to [silo] from the multitool's buffer."))
return TRUE
/datum/component/remote_materials/proc/on_item_insert(datum/source, obj/item/target, mob/living/user)
SIGNAL_HANDLER
if(istype(target, /obj/item/multitool))
return OnMultitool(source, user, target)
if(mat_container_flags & MATCONTAINER_NO_INSERT)
return
return attempt_insert(user, target)
/// Insert mats into silo
/datum/component/remote_materials/proc/attempt_insert(mob/living/user, obj/item/target)
if(silo)
mat_container.user_insert(target, user, parent)
return TRUE
/**
* Checks if the param silo is in the same level as this components parent i.e. connected machine, rcd, etc
*
* Arguments
* silo_to_check- Is this components parent in the same Z level as this param silo. If null
* then check this components connected silo
*
* Returns true if both are on the station or same z level
*/
/datum/component/remote_materials/proc/check_z_level(obj/silo_to_check = silo)
if(isnull(silo_to_check))
return FALSE
return is_valid_z_level(get_turf(silo_to_check), get_turf(parent))
/// returns TRUE if this connection put on hold by the silo
/datum/component/remote_materials/proc/on_hold()
return check_z_level() ? silo.holds[src] : FALSE
/**
* Check if this connection can use any materials from the silo
* Returns true only if
* - The parent is of type movable atom
* - A mat container is actually present
* - The silo in not on hold
* Arguments
* * check_hold - should we check if the silo is on hold
*/
/datum/component/remote_materials/proc/can_use_resource(check_hold = TRUE)
var/atom/movable/movable_parent = parent
if (!istype(movable_parent))
return FALSE
if (!mat_container) //no silolink & local storage not supported
movable_parent.atom_say("No access to material storage, please contact the quartermaster.")
return FALSE
if(check_hold && on_hold()) //silo on hold
movable_parent.atom_say("Mineral access is on hold, please contact the quartermaster.")
return FALSE
return TRUE
/**
* Use materials from either the silo(if connected) or from the local storage. If silo then this action
* is logged else not e.g. action="build" & name="matter bin" means you are trying to build a matter bin
*
* Arguments
* [mats][list]- list of materials to use
* coefficient- each mat unit is scaled by this value then rounded. This value if usually your machine efficiency e.g. upgraded protolathe has reduced costs
* multiplier- each mat unit is scaled by this value then rounded after it is scaled by coefficient. This value is your print quatity e.g. printing multiple items
* action- For logging only. e.g. build, create, i.e. the action you are trying to perform
* name- For logging only. the design you are trying to build e.g. matter bin, etc.
*/
/datum/component/remote_materials/proc/use_materials(list/mats, coefficient = 1, multiplier = 1, action = "build", name = "design")
if(!can_use_resource())
return 0
var/list/rebuilt_mats = list()
for(var/datum/material/req_mat as anything in mats)
var/imat = mats[req_mat]
if(!istype(req_mat))
req_mat = GET_MATERIAL_REF(req_mat)
rebuilt_mats[req_mat] = imat
var/amount_consumed = mat_container.use_materials(rebuilt_mats, coefficient, multiplier)
if (silo)//log only if silo is linked
var/list/scaled_mats = list()
for(var/i in rebuilt_mats)
scaled_mats[i] = OPTIMAL_COST(OPTIMAL_COST(rebuilt_mats[i] * coefficient) * multiplier)
silo.silo_log(parent, action, -multiplier, name, scaled_mats)
return amount_consumed
/**
* Ejects the given material ref and logs it
*
* Arguments
* [material_ref][datum/material]- The material type you are trying to eject
* eject_amount- how many sheets to eject
* [drop_target][atom]- optional where to drop the sheets. null means it is dropped at this components parent location
*/
/datum/component/remote_materials/proc/eject_sheets(datum/material/material_ref, eject_amount, atom/drop_target = null)
if(!can_use_resource())
return 0
var/atom/movable/movable_parent = parent
if(isnull(drop_target))
drop_target = movable_parent.drop_location()
return mat_container.retrieve_sheets(eject_amount, material_ref, target = drop_target, context = parent)
/**
* Insert an item into the mat container, helper proc to insert items with the correct context
*
* Arguments
* * obj/item/weapon - the item you are trying to insert
* * multiplier - the multiplier applied on the materials consumed
*/
/datum/component/remote_materials/proc/insert_item(obj/item/weapon, multiplier = 1)
if(!can_use_resource(FALSE))
return MATERIAL_INSERT_ITEM_FAILURE
return mat_container.insert_item(weapon, multiplier, parent)