mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-19 05:26:28 +00:00
**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
572 lines
22 KiB
Plaintext
572 lines
22 KiB
Plaintext
/*!
|
|
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.
|
|
*/
|
|
|
|
/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
|
|
/// Whether or not this material container allows specific amounts from sheets to be inserted
|
|
var/precise_insertion = FALSE
|
|
/// 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
|
|
|
|
if(_mat_container_flags & MATCONTAINER_NO_INSERT)
|
|
return
|
|
|
|
var/atom/atom_target = parent
|
|
atom_target.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
|
|
|
|
RegisterSignal(atom_target, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item))
|
|
|
|
/datum/component/material_container/Destroy(force, silent)
|
|
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/I in materials)
|
|
var/datum/material/M = I
|
|
var/amt = materials[I]
|
|
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/weapon, mob/living/user)
|
|
SIGNAL_HANDLER
|
|
|
|
user_insert(weapon, user)
|
|
|
|
return COMPONENT_NO_AFTERATTACK
|
|
|
|
/**
|
|
* inserts an item from the players hand into the container. Loops through all the contents inside reccursively
|
|
* Arguments
|
|
* * held_item - the item to insert
|
|
* * user - the mob inserting this item
|
|
* * breakdown_flags - how this item and all it's contents inside are broken down during insertion. This is unique to the machine doing the insertion
|
|
*/
|
|
/datum/component/material_container/proc/user_insert(obj/item/held_item, mob/living/user, breakdown_flags = mat_container_flags)
|
|
set waitfor = FALSE
|
|
. = 0
|
|
|
|
//differs from held_item when using TK
|
|
var/active_held = user.get_active_held_item()
|
|
//don't attack the machine
|
|
if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode)
|
|
return
|
|
//user defined conditions
|
|
if(precondition && !precondition.Invoke(user))
|
|
return
|
|
|
|
//loop through all contents inside this atom and salvage their material as well but in reverse so we don't delete parents before processing their children
|
|
var/list/contents = held_item.get_all_contents_type(/obj/item)
|
|
for(var/i = length(contents); i >= 1 ; i--)
|
|
var/obj/item/target = contents[i]
|
|
|
|
//not a solid sub type
|
|
if(target.item_flags & ABSTRACT)
|
|
if(target == active_held) //was this the original item in the players hand? put it back because we coudn't salvage it
|
|
user.put_in_active_hand(target)
|
|
continue
|
|
//item is either not real, not allowed for redemption, not in the allowed types
|
|
if((target.flags_1 & HOLOGRAM_1) || (target.item_flags & NO_MAT_REDEMPTION) || (allowed_item_typecache && !is_type_in_typecache(target, allowed_item_typecache)))
|
|
if(!(mat_container_flags & MATCONTAINER_SILENT))
|
|
to_chat(user, span_warning("[parent] won't accept [target]!"))
|
|
if(target == active_held) //was this the original item in the players hand? put it back because we coudn't salvage it
|
|
user.put_in_active_hand(target)
|
|
continue
|
|
|
|
//if stack, check if we want to read precise amount of sheets to insert
|
|
var/obj/item/stack/item_stack = null
|
|
if(isstack(target) && precise_insertion)
|
|
var/atom/current_parent = parent
|
|
item_stack = target
|
|
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) || QDELETED(user) || QDELETED(src))
|
|
continue
|
|
if(parent != current_parent || user.get_active_held_item() != active_held)
|
|
continue
|
|
if(requested_amount != item_stack.amount) //only split if its not the whole amount
|
|
target = 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
|
|
//if it was split then item_stack has the reference to the original stack/item
|
|
var/original_item = was_stack_split ? item_stack : target
|
|
//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)
|
|
to_chat(user, span_warning("[held_item] is stuck to you and cannot be placed into [parent]."))
|
|
return
|
|
|
|
//insert the item
|
|
var/inserted = insert_item(target, breakdown_flags = mat_container_flags)
|
|
if(inserted > 0)
|
|
. += inserted
|
|
|
|
//stack was either split by the container(!QDELETED(target) means the container only consumed a part of it) or by the player, put whats left back of the original stack back in players hand
|
|
if((!QDELETED(target) || was_stack_split))
|
|
|
|
//stack was split by player and that portion was not fully consumed, merge whats left back with the original stack
|
|
if(!QDELETED(target) && was_stack_split)
|
|
var/obj/item/stack/inserting_stack = target
|
|
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(!isnull(original_item))
|
|
user.put_in_active_hand(original_item)
|
|
|
|
to_chat(user, span_notice("You insert a material total of [inserted] into [parent]."))
|
|
else
|
|
//decode the error & print it
|
|
var/error_msg
|
|
if(inserted == -2)
|
|
error_msg = "[parent] has insufficient space to accept the [target]"
|
|
else
|
|
error_msg = "[target] has insufficient materials to be accepted by [parent]"
|
|
to_chat(user, span_warning(error_msg))
|
|
|
|
//player split the stack by the requested amount but even that split amount could not be salvaged. merge it back with the original
|
|
if(!isnull(item_stack) && was_stack_split)
|
|
var/obj/item/stack/inserting_stack = target
|
|
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(!isnull(original_item))
|
|
user.put_in_active_hand(original_item)
|
|
|
|
/**
|
|
* Splits a stack. we don't use /obj/item/stack/proc/split_stack because Byond complains that should only be called asynchronously.
|
|
* This proc is also more faster because it doesn't deal with mobs, copying evidences or refreshing atom storages
|
|
*/
|
|
/datum/component/material_container/proc/split_stack(obj/item/stack/target, amount)
|
|
if(!target.use(amount, TRUE, FALSE))
|
|
return null
|
|
|
|
. = new target.type(target.drop_location(), amount, FALSE, target.mats_per_unit)
|
|
target.loc.atom_storage?.refresh_views()
|
|
|
|
target.is_zero_amount(delete_if_zero = TRUE)
|
|
|
|
/// Proc specifically for inserting items, returns the amount of materials entered.
|
|
/datum/component/material_container/proc/insert_item(obj/item/weapon, multiplier = 1, breakdown_flags = mat_container_flags)
|
|
if(QDELETED(weapon))
|
|
return -1
|
|
multiplier = CEILING(multiplier, 0.01)
|
|
|
|
var/obj/item/target = weapon
|
|
|
|
var/material_amount = get_item_material_amount(target, breakdown_flags) * multiplier
|
|
if(!material_amount)
|
|
return -1
|
|
var/obj/item/stack/item_stack
|
|
if(isstack(weapon) && !has_space(material_amount)) //not enugh space split and feed as many sheets possible
|
|
item_stack = weapon
|
|
var/space_left = max_amount - total_amount
|
|
if(!space_left)
|
|
return -2
|
|
var/material_per_sheet = material_amount / item_stack.amount
|
|
var/sheets_to_insert = round(space_left / material_per_sheet)
|
|
if(!sheets_to_insert)
|
|
return -2
|
|
target = split_stack(item_stack, sheets_to_insert)
|
|
material_amount = get_item_material_amount(target, breakdown_flags) * multiplier
|
|
if(!has_space(material_amount))
|
|
return -2
|
|
|
|
last_inserted_id = insert_item_materials(target, multiplier, breakdown_flags)
|
|
if(!isnull(last_inserted_id))
|
|
if(after_insert)
|
|
after_insert.Invoke(target, last_inserted_id, material_amount)
|
|
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 0
|
|
|
|
/**
|
|
* 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.id 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
|
|
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=SMALL_MATERIAL_AMOUNT * 2)
|
|
/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/x in mats) //Loop through all required materials
|
|
var/datum/material/req_mat = x
|
|
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[x] * multiplier
|
|
if(amount_required < 0)
|
|
return FALSE //No negative mats
|
|
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.sheet_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/count = 0
|
|
while(sheet_amt > MAX_STACK_SIZE)
|
|
new M.sheet_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.sheet_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/x in mats) //Loop through all required materials
|
|
var/datum/material/req_mat = x
|
|
if(!istype(req_mat))
|
|
if(ispath(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[x], 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.custom_materials)
|
|
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(material_name = list(amount = ..., ref = ..., etc.))
|
|
/datum/component/material_container/ui_data(mob/user)
|
|
var/list/data = list()
|
|
|
|
for(var/datum/material/material as anything in materials)
|
|
var/amount = materials[material]
|
|
|
|
data += list(list(
|
|
"name" = material.name,
|
|
"ref" = REF(material),
|
|
"amount" = amount,
|
|
"sheets" = round(amount / SHEET_MATERIAL_AMOUNT),
|
|
"removable" = amount >= SHEET_MATERIAL_AMOUNT,
|
|
"color" = material.greyscale_colors
|
|
))
|
|
|
|
return data
|
|
|
|
/**
|
|
* Adds context sensitivy directly to the material container file for screentips
|
|
* Arguments:
|
|
* * source - refers to item that will display its screentip
|
|
* * context - refers to, in this case, an item in the users hand hovering over the material container, such as an autolathe
|
|
* * held_item - refers to the item that has materials accepted by the material container
|
|
* * user - refers to user who will see the screentip when the proper context and tool are there
|
|
*/
|
|
/datum/component/material_container/proc/on_requesting_context_from_item(datum/source, list/context, obj/item/held_item, mob/living/user)
|
|
SIGNAL_HANDLER
|
|
|
|
if(isnull(held_item))
|
|
return NONE
|
|
if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode)
|
|
return NONE
|
|
if(held_item.item_flags & ABSTRACT)
|
|
return NONE
|
|
if((held_item.flags_1 & HOLOGRAM_1) || (held_item.item_flags & NO_MAT_REDEMPTION) || (allowed_item_typecache && !is_type_in_typecache(held_item, allowed_item_typecache)))
|
|
return NONE
|
|
var/list/item_materials = held_item.get_material_composition(mat_container_flags)
|
|
if(!length(item_materials))
|
|
return NONE
|
|
for(var/material in item_materials)
|
|
if(can_hold_material(material))
|
|
continue
|
|
return NONE
|
|
|
|
context[SCREENTIP_CONTEXT_LMB] = "Insert"
|
|
|
|
return CONTEXTUAL_SCREENTIP_SET
|