diff --git a/code/datums/components/material/material_container.dm b/code/datums/components/material/material_container.dm index d26e427b1db..2d78576fa09 100644 --- a/code/datums/components/material/material_container.dm +++ b/code/datums/components/material/material_container.dm @@ -9,6 +9,9 @@ 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 @@ -131,7 +134,9 @@ * - [source][/obj/item]: The source of the materials we are inserting. * - multiplier: The multiplier for the materials extract from this item being inserted. * - breakdown_flags: The breakdown bitflags that will be used to retrieve the materials from the source - * - context: the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_ITEM_CONSUMED and is used mostly for silo logging + * - 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, breakdown_flags = mat_container_flags, atom/context = parent) var/primary_mat @@ -256,154 +261,227 @@ set waitfor = FALSE . = 0 + //All items that do not have any contents + var/list/obj/item/child_items = list() + //All items that do have contents but they were already processed by the above list + var/list/obj/item/parent_items = list(held_item) + //is this the first item we are ever processing + var/first_checks = TRUE + //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/active_held = user.get_active_held_item() - //don't attack the machine - if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode) - return - //can't allow abstract, hologram items - if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) - return - //untouchable - if(held_item.resistance_flags & INDESTRUCTIBLE) - return - //user defined conditions - if(SEND_SIGNAL(src, COMSIG_MATCONTAINER_PRE_USER_INSERT, held_item, user) & MATCONTAINER_BLOCK_INSERT) - return - //get all contents of this item reccursively - var/list/contents = held_item.get_all_contents_type(/obj/item) - //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 - if(!isstack(held_item)) - var/total_amount = 0 - for(var/obj/item/weapon in contents) - total_amount += get_item_material_amount(weapon, breakdown_flags) - if(!has_space(total_amount)) - to_chat(user, span_warning("[parent] doesn't have enough space for [held_item] [contents.len > 1 ? "And it's contents" : ""]!")) - return + var/static/list/storage_items + if(isnull(storage_items)) + storage_items = list( + /obj/item/storage/backpack, + /obj/item/storage/bag, + /obj/item/storage/box, + ) - /** - * to reduce chat spams we group all messages and print them after everything is over - * usefull when we are trying to insert all stock parts of an RPED into the autolathe for example - */ - var/list/inserts = list() - var/list/errors = list() + //1st iteration consumes all items that do not have contents inside + //2nd iteration consumes items who do have contents inside(but they were consumed in the 1st iteration si its empty now) + for(var/i in 1 to 2) + //no point inserting more items + if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE) + break - //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 - for(var/i = length(contents); i >= 1 ; i--) - var/obj/item/target = contents[i] + //transfer all items for processing + if(!parent_items.len) + break + child_items += parent_items + parent_items.Cut() - //not a solid subtype or an hologram - if((target.item_flags & ABSTRACT) || (target.flags_1 & HOLOGRAM_1)) - continue + while(child_items.len) + //Pop the 1st item out from the list + var/obj/item/target_item = child_items[1] + child_items -= target_item - //item is either not allowed for redemption, not in the allowed types - if((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]!")) - continue - - //untouchable, move it out the way, code copied from recycler - if(target.resistance_flags & INDESTRUCTIBLE) - if(!isturf(target.loc) && !isliving(target.loc)) - target.forceMove(get_turf(parent)) - 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)) + //e.g. projectiles inside bullets are not objects + if(!istype(target_item)) continue - if(parent != current_parent || user.get_active_held_item() != active_held) + //can't allow abstract, hologram items + if((target_item.item_flags & ABSTRACT) || (target_item.flags_1 & HOLOGRAM_1)) continue - if(requested_amount != item_stack.amount) //only split if its not the whole amount - target = fast_split_stack(item_stack, requested_amount) //split off the requested amount - requested_amount = 0 + //untouchable, move it out the way, code copied from recycler + if(target_item.resistance_flags & INDESTRUCTIBLE) + target_item.forceMove(get_turf(parent)) + continue + //user defined conditions + if(SEND_SIGNAL(src, COMSIG_MATCONTAINER_PRE_USER_INSERT, target_item, user) & MATCONTAINER_BLOCK_INSERT) + continue + //item is either not allowed for redemption, not in the allowed types + if((target_item.item_flags & NO_MAT_REDEMPTION) || (allowed_item_typecache && !is_type_in_typecache(target_item, allowed_item_typecache))) + var/list/chat_data = null + if(!(mat_container_flags & MATCONTAINER_SILENT)) + chat_data = chat_msgs[target_item.name] || list() + chat_data["status"] = MATERIAL_INSERT_ITEM_FAILURE + if(i == 1) //items like backpacks/anything that has contents come here a 2nd time so we don't count them again + chat_data["count"] += 1 + chat_msgs[target_item.name] = chat_data - //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 + //storage items usually come here but we make the exception only on the 1st iteration + //this is so players can insert items from their bags into machines for convinience + if(!is_type_in_list(target_item, storage_items)) + continue + else if(!target_item.contents.len || i == 2) + continue + //at this point we can check if we have enough for all items & other stuff + if(first_checks) + //duffle bags needs to be unzipped + if(target_item.atom_storage?.locked) + if(!(mat_container_flags & MATCONTAINER_SILENT)) + to_chat(user, span_warning("[target_item] has its storage locked")) + return - //insert the item - var/item_name = target.name - var/inserted = insert_item(target, 1, mat_container_flags, context) - if(inserted > 0) - . += inserted - inserted /= SHEET_MATERIAL_AMOUNT // display units inserted as sheets for improved readability - var/message = null + //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(!isstack(target_item) && !is_type_in_list(target_item, storage_items)) + 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, breakdown_flags) + 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 - //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)) + first_checks = FALSE - //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 + //All hard checks have passed, at this point we can consume the item + //If it has children then we will process them first and then the item 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 && i == 1) + if(target_item.atom_storage?.locked) //can't access contents of locked storage(like duffle bags) + continue + //process children + child_items += target_item.contents + //in the 2nd round only after its children are consumed do we consume this next, FIFO order + parent_items.Insert(1, target_item) + //leave it here till we get to its children + 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_item) && 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_held_item() != 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/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 + if(isstack(target_item)) + var/obj/item/stack/the_stack = target_item + item_name = the_stack.singular_name + item_count = the_stack.amount + is_stack = TRUE + inserted = insert_item(target_item, 1, mat_container_flags, context) + if(inserted > 0) + . += inserted + inserted /= SHEET_MATERIAL_AMOUNT // display units inserted as sheets for improved readability + + //stack was either split by the container(!QDELETED(target_item) 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_item) || 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_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(!isnull(original_item)) + user.put_in_active_hand(original_item) + + //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["amount"] += inserted + if(!is_stack) //count will match with amount so its not required + item_data["count"] += item_count + else + item_data["stack"] = TRUE + status_data[item_name] = item_data + chat_msgs["[MATERIAL_INSERT_ITEM_SUCCESS]"] = status_data + + 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(!isnull(item_stack) && 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 + //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) - message = "Only [inserted] sheets of [item_name] was consumed by [parent]." - //collect all messages to print later - if(!message) - message = "[item_name] worth [inserted] sheets of material was consumed by [parent]." - if(inserts[message]) - inserts[message] += 1 - else - inserts[message] = 1 - else - var/error_msg - if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE) - error_msg = "[parent] has insufficient space to accept [target]" - else if(inserted == MATERIAL_INSERT_ITEM_NO_MATS) - error_msg = "[target] has insufficient materials to be accepted by [parent]" - else - error_msg = "[parent] refuses to accept [target]" + //we can stop here as remaining items will fail to insert as well + if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE) + break - //collect all messages to print later - if(errors[error_msg]) - errors[error_msg] += 1 - else - errors[error_msg] = 1 + //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] - //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) + 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"] - //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) - - //print successfull inserts - for(var/success_msg in inserts) - var/count = inserts[success_msg] - for(var/i in 1 to count) - to_chat(user, span_notice(success_msg)) - - //print errors last - for(var/error_msg in errors) - var/count = errors[error_msg] - for(var/i in 1 to count) - to_chat(user, span_warning(error_msg)) + //decode the message + switch(text2num(status)) + if(MATERIAL_INSERT_ITEM_SUCCESS) //no problems full item was consumed + if(chat_data["stack"]) + to_chat(user, span_notice("[amount > 1 ? amount : ""] [item_name][amount > 1 ? "'s" : ""] was consumed by [parent]")) + else + to_chat(user, span_notice("[count > 1 ? count : ""] [item_name][count > 1 ? "'s" : ""] worth [amount] sheets of material was consumed by [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("[count > 1 ? count : ""] [item_name][count > 1 ? "'s" : ""] has no materials to 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("[count > 1 ? count : ""] [item_name][count > 1 ? "'s" : ""] was rejected by [parent]")) /// 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 + //Allows you to attack the machine with iron sheets for e.g. + if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode) + return + user_insert(weapon, user) return COMPONENT_NO_AFTERATTACK @@ -417,7 +495,7 @@ * Proc that returns TRUE if the container has space * * Arguments: - * - amt: can this container hold this much amount of materials + * * 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 @@ -684,3 +762,5 @@ context[SCREENTIP_CONTEXT_LMB] = "Insert" return CONTEXTUAL_SCREENTIP_SET + +#undef MATERIAL_INSERT_ITEM_SUCCESS diff --git a/code/datums/components/material/remote_materials.dm b/code/datums/components/material/remote_materials.dm index 373b1af7bee..e57a212cbde 100644 --- a/code/datums/components/material/remote_materials.dm +++ b/code/datums/components/material/remote_materials.dm @@ -124,9 +124,13 @@ handles linking back and forth. _MakeLocal() ///Insert mats into silo -/datum/component/remote_materials/proc/SiloAttackBy(datum/source, obj/item/target, mob/user) +/datum/component/remote_materials/proc/SiloAttackBy(datum/source, obj/item/target, mob/living/user) SIGNAL_HANDLER + //Allows you to attack the machine with iron sheets for e.g. + if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode) + return + if(silo) mat_container.user_insert(target, user, mat_container_flags, parent) diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm index 5aca0ade708..8ecbcb80074 100644 --- a/code/modules/mining/machine_redemption.dm +++ b/code/modules/mining/machine_redemption.dm @@ -58,6 +58,8 @@ mat_container_flags = BREAKDOWN_FLAGS_ORM \ ) + RegisterSignal(src, COMSIG_MATCONTAINER_ITEM_CONSUMED, TYPE_PROC_REF(/obj/machinery/mineral/ore_redemption, redeem_points)) + /obj/machinery/mineral/ore_redemption/Destroy() stored_research = null materials = null @@ -68,34 +70,12 @@ 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 +/obj/machinery/mineral/ore_redemption/proc/redeem_points(obj/machinery/mineral/ore_redemption/machine, container, obj/item/stack/ore/gathered_ore) + SIGNAL_HANDLER - 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/ore_amount = gathered_ore.amount - var/ore_points= gathered_ore.points - var/refined_type = gathered_ore?.refined_type - if(mat_container.insert_item(gathered_ore, ore_multiplier, breakdown_flags = BREAKDOWN_FLAGS_ORM, context = src) > 0) //increase points only if insertion was successfull - if(refined_type) - points += ore_points * point_upgrade * ore_amount - - SEND_SIGNAL(src, COMSIG_ORM_COLLECTED_ORE) + if(istype(gathered_ore) && gathered_ore.refined_type) + points += gathered_ore.points * point_upgrade * gathered_ore.amount /// 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) @@ -124,11 +104,6 @@ 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 @@ -169,15 +144,27 @@ if(!materials.mat_container || panel_open || !powered()) return + //gethering the ore + var/list/obj/item/stack/ore/ore_list = list() if(istype(target, /obj/structure/ore_box)) var/obj/structure/ore_box/box = target - process_ores(box.contents) + for(var/obj/item/stack/ore/ore_item in box.contents) + ore_list += ore_item else if(istype(target, /obj/item/stack/ore)) - var/obj/item/stack/ore/O = target - smelt_ore(O) + ore_list += target else return + //smelting the ore + for(var/obj/item/stack/ore/gathered_ore as anything in ore_list) + if(isnull(gathered_ore.refined_type)) + continue + + if(materials.mat_container.insert_item(gathered_ore, ore_multiplier, breakdown_flags = BREAKDOWN_FLAGS_ORM, context = src) <= 0) + unload_mineral(gathered_ore) //if rejected unload + + SEND_SIGNAL(src, COMSIG_ORM_COLLECTED_ORE) + 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) @@ -191,28 +178,17 @@ 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) +/obj/machinery/mineral/ore_redemption/screwdriver_act(mob/living/user, obj/item/tool) + default_deconstruction_screwdriver(user, "ore_redemption-open", "ore_redemption", tool) return ITEM_INTERACT_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 +/obj/machinery/mineral/ore_redemption/crowbar_act(mob/living/user, obj/item/tool) + default_deconstruction_crowbar(tool) + return ITEM_INTERACT_SUCCESS - 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 - smelt_ore(O) - return TRUE - return ..() +/obj/machinery/mineral/ore_redemption/wrench_act(mob/living/user, obj/item/tool) + default_unfasten_wrench(user, tool) + return ITEM_INTERACT_SUCCESS /obj/machinery/mineral/ore_redemption/AltClick(mob/living/user) . = ..() diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index 7661549a77d..61e340d3540 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -39,7 +39,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs) materials_list, \ INFINITY, \ container_signals = list( \ - COMSIG_MATCONTAINER_ITEM_CONSUMED = TYPE_PROC_REF(/obj/machinery/ore_silo, log_item_consumed), \ + COMSIG_MATCONTAINER_ITEM_CONSUMED = TYPE_PROC_REF(/obj/machinery/ore_silo, on_item_consumed), \ COMSIG_MATCONTAINER_SHEETS_RETRIEVED = TYPE_PROC_REF(/obj/machinery/ore_silo, log_sheets_ejected), \ ), \ allowed_items = /obj/item/stack \ @@ -59,11 +59,13 @@ GLOBAL_LIST_EMPTY(silo_access_logs) return ..() -/obj/machinery/ore_silo/proc/log_item_consumed(datum/component/material_container/container, obj/item/item_inserted, last_inserted_id, mats_consumed, amount_inserted, atom/context) +/obj/machinery/ore_silo/proc/on_item_consumed(datum/component/material_container/container, obj/item/item_inserted, last_inserted_id, mats_consumed, amount_inserted, atom/context) SIGNAL_HANDLER silo_log(context, "deposited", amount_inserted, item_inserted.name, mats_consumed) + SEND_SIGNAL(context, COMSIG_MATCONTAINER_ITEM_CONSUMED, container, item_inserted, last_inserted_id, mats_consumed, amount_inserted) + /obj/machinery/ore_silo/proc/log_sheets_ejected(datum/component/material_container/container, obj/item/stack/sheet/sheets, atom/context) SIGNAL_HANDLER