// Always announce this action #define ALWAYS_ANNOUNCE (ALL) // Announced when someone tries to ban someone without QM access #define BAN_ATTEMPT_FAILURE_NO_ACCESS (1<<1) // Announced when someone tries to ban someone with QM access without being the Captain #define BAN_ATTEMPT_FAILURE_CHALLENGING_DA_CHIEF (1<<2) // Announced when a silicon tries to ban someone #define BAN_ATTEMPT_FAILURE_SOULLESS_MACHINE (1<<3) // Announced when a user is banned from the ore silo #define BAN_CONFIRMATION (1<<4) // Announced when a user is unbanned from the ore silo #define UNBAN_CONFIRMATION (1<<5) // Announced when a suspicious(chameleon ID worn by user) log is Among the ore silo logs and someone tries to ban them #define FAILED_OPERATION_SUSPICIOUS (1<<6) // Announced when a user tries to ban someone without a bank account ID #define FAILED_OPERATION_NO_BANK_ID (1<<7) // Announced when a user tries to unrestrict the ore silo without QM access #define UNRESTRICT_FAILURE_NO_ACCESS (1<<8) // Announced when a silicon tries to unrestrict the ore silo #define UNRESTRICT_FAILURE_SOULLESS_MACHINE (1<<9) // Announced when a user removes the worn ID(with valid bank account) requirement from the ore silo #define UNRESTRICT_CONFIRMATION (1<<10) // Announced when a user restricts the ore silo to require a valid ID with bank account #define RESTRICT_CONFIRMATION (1<<11) /obj/machinery/ore_silo name = "ore silo" desc = "An all-in-one bluespace storage and transmission system for the station's mineral distribution needs." icon = 'icons/obj/machines/ore_silo.dmi' icon_state = "silo" density = TRUE circuit = /obj/item/circuitboard/machine/ore_silo interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON processing_flags = NONE /// By default, an ore silo requires you to be wearing an ID to pull materials from it. var/ID_required = TRUE /// List of all connected components that are on hold from accessing materials. var/list/holds = list() /// List of all components that are sharing ores with this silo. var/list/datum/component/remote_materials/ore_connected_machines = list() /// Material Container var/datum/component/material_container/materials /// A list of names of bank account IDs that are banned from using this ore silo. var/list/banned_users = list() ///The machine's internal radio, used to broadcast alerts. var/obj/item/radio/radio ///The channels we announce over var/list/radio_channels = list( RADIO_CHANNEL_COMMON = NONE, RADIO_CHANNEL_COMMAND = NONE, RADIO_CHANNEL_SUPPLY = NONE, RADIO_CHANNEL_SECURITY = NONE, ) var/static/alist/announcement_messages = alist( BAN_ATTEMPT_FAILURE_NO_ACCESS = "ACCESS ENFORCEMENT FAILURE: $SILO_USER_NAME lacks supply command authority.", BAN_ATTEMPT_FAILURE_CHALLENGING_DA_CHIEF = "ACCESS ENFORCEMENT FAILURE: $SILO_USER_NAME attempting subversion of supply command authority.", BAN_ATTEMPT_FAILURE_SOULLESS_MACHINE = "$SILO_USER_NAME INTERFACE_EXCEPTION -> BANNED_USERS+=\[$TARGET_NAME\] => NO_OP", BAN_CONFIRMATION = "ACCESS ENFORCEMENT CONFIRMATION\[$SILO_USER_NAME\]: $TARGET_NAME banned from ore silo access.", UNBAN_CONFIRMATION = "ACCESS ENFORCEMENT CONFIRMATION\[$SILO_USER_NAME\]: $TARGET_NAME unbanned from ore silo access.", FAILED_OPERATION_SUSPICIOUS = "NULL_ACCOUNT_RESOLVE_PTR_#?", FAILED_OPERATION_NO_BANK_ID = "ACCESS ENFORCEMENT FAILURE: No account ID found. Please contact a banker.", UNRESTRICT_FAILURE_NO_ACCESS = "ID ACCESS REQUIREMENT ENFORCED: $SILO_USER_NAME lacks supply command authority; ID ACCESS REQUIREMENT REMOVAL FAILED.", UNRESTRICT_FAILURE_SOULLESS_MACHINE = "$SILO_USER_NAME INTERFACE_EXCEPTION -> ID_ACCESS_REQUIREMENT = !ID_ACCESS_REQUIREMENT => NO_OP", RESTRICT_CONFIRMATION = "ID ACCESS REQUIREMENT ROUTINE STARTED: $SILO_USER_NAME has enforced ID read requirement for this ore silo.", UNRESTRICT_CONFIRMATION = "ID ACCESS REQUIREMENT ROUTINE SUSPENDED: $SILO_USER_NAME has removed ID read requirement for this ore silo.", RESTRICT_FAILURE = "ID ACCESS REQUIREMENT ROUTINE FAILED TO START: $SILO_USER_NAME()" ) /obj/machinery/ore_silo/Initialize(mapload) . = ..() materials = AddComponent( \ /datum/component/material_container, \ SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \ INFINITY, \ MATCONTAINER_EXAMINE, \ container_signals = list( \ 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 \ ) if (!GLOB.ore_silo_default && mapload && is_station_level(z)) GLOB.ore_silo_default = src register_context() setup_radio() configure_default_announcements_policy() /obj/machinery/ore_silo/emag_act(mob/living/user) if(obj_flags & EMAGGED) return FALSE obj_flags |= EMAGGED return TRUE /obj/machinery/ore_silo/proc/setup_radio() radio = new(src) radio.subspace_transmission = TRUE radio.canhear_range = 0 radio.set_listening(FALSE) radio.keyslot = new radio.keyslot.channels[RADIO_CHANNEL_COMMON] = TRUE radio.keyslot.channels[RADIO_CHANNEL_COMMAND] = TRUE radio.keyslot.channels[RADIO_CHANNEL_SUPPLY] = TRUE radio.keyslot.channels[RADIO_CHANNEL_SECURITY] = TRUE radio.recalculateChannels() /obj/machinery/ore_silo/Destroy() if (GLOB.ore_silo_default == src) GLOB.ore_silo_default = null for(var/datum/component/remote_materials/mats as anything in ore_connected_machines) mats.disconnect() ore_connected_machines = null materials = null QDEL_NULL(radio) return ..() /obj/machinery/ore_silo/examine(mob/user) . = ..() . += span_notice("It can be linked to techfabs, circuit printers and protolathes with a multitool.") . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") if(panel_open) . += span_notice("The whole machine can be [EXAMINE_HINT("pried")] apart.") /obj/machinery/ore_silo/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = NONE if(isnull(held_item)) return if(held_item.tool_behaviour == TOOL_SCREWDRIVER) context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" return CONTEXTUAL_SCREENTIP_SET if(held_item.tool_behaviour == TOOL_MULTITOOL) context[SCREENTIP_CONTEXT_LMB] = "Log Silo" return CONTEXTUAL_SCREENTIP_SET if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR) context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" return CONTEXTUAL_SCREENTIP_SET /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, alist/user_data) SIGNAL_HANDLER silo_log(context, "DEPOSITED", amount_inserted, item_inserted.name, mats_consumed, user_data) SEND_SIGNAL(context, COMSIG_SILO_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, alist/user_data) SIGNAL_HANDLER silo_log(context, "WITHDRAWN", -sheets.amount * SHEET_MATERIAL_AMOUNT, "[sheets.name]", sheets.custom_materials, user_data) /obj/machinery/ore_silo/screwdriver_act(mob/living/user, obj/item/tool) . = ITEM_INTERACT_BLOCKING if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) return ITEM_INTERACT_SUCCESS /obj/machinery/ore_silo/crowbar_act(mob/living/user, obj/item/tool) . = ITEM_INTERACT_BLOCKING if(default_deconstruction_crowbar(tool)) return ITEM_INTERACT_SUCCESS /obj/machinery/ore_silo/multitool_act(mob/living/user, obj/item/multitool/I) I.set_buffer(src) balloon_alert(user, "saved to multitool buffer") return ITEM_INTERACT_SUCCESS /** * The logic for disconnecting a remote receptacle (RCD, fabricator, etc.) is collected here for sanity's sake * rather than being on specific types. Serves to agnosticize the remote_materials component somewhat rather than * snowflaking code for silos into the component. * * receptacle - The datum/component/remote_materials component that is getting connected. * * physical_receptacle - the actual object in the game world that was connected to our material supply. Typed as atom/movable for * future-proofing against anything that may conceivably one day have remote silo access, such as a cyborg, an implant, structures, vehicles, * and so-on. */ /obj/machinery/ore_silo/proc/connect_receptacle(datum/component/remote_materials/receptacle, atom/movable/physical_receptacle) ore_connected_machines += receptacle receptacle.mat_container = src.materials receptacle.silo = src RegisterSignal(physical_receptacle, COMSIG_ORE_SILO_PERMISSION_CHECKED, PROC_REF(check_permitted)) /** * The logic for disconnecting a remote receptacle (RCD, fabricator, etc.) is collected here for sanity's sake * rather than being on specific types. Cleans up references to us and to the receptacle. * * receptacle - The datum/component/remote_materials component that is getting destroyed. * * physical_receptacle - the actual object in the game world that was connected to our material supply. Typed as atom/movable for * future-proofing against anything that may conceivably one day have remote silo access, such as a cyborg, an implant, structures, vehicles, * and so-on. */ /obj/machinery/ore_silo/proc/disconnect_receptacle(datum/component/remote_materials/receptacle, atom/movable/physical_receptacle) ore_connected_machines -= receptacle receptacle.mat_container = null receptacle.silo = null holds -= receptacle UnregisterSignal(physical_receptacle, COMSIG_ORE_SILO_PERMISSION_CHECKED) /obj/machinery/ore_silo/proc/check_permitted(datum/source, alist/user_data, atom/movable/physical_receptacle) SIGNAL_HANDLER if(!ID_required) return COMPONENT_ORE_SILO_ALLOW if(!islist(user_data)) // Just allow to salvage the situation . = COMPONENT_ORE_SILO_ALLOW user_data = ID_DATA(null) CRASH("Invalid data passed to check_permitted") if(user_data[SILICON_OVERRIDE] || user_data[CHAMELEON_OVERRIDE] || astype(user_data["accesses"], /list)?.Find(ACCESS_QM)) return COMPONENT_ORE_SILO_ALLOW if(user_data[ID_READ_FAILURE]) physical_receptacle.say("SILO ERR: ID interface failure. Please contact the Head of Personnel.") return COMPONENT_ORE_SILO_DENY if(!user_data["account_id"] || !isnum(user_data["account_id"])) if(prob(5)) physical_receptacle.say("SILO ERR: Bank account ID not found. Initiating anti-communist silo-access policy.") physical_receptacle.say("SILO ERR: No account ID found. Please contact Head of Personnel.") return COMPONENT_ORE_SILO_DENY if(banned_users.Find(user_data["account_id"])) physical_receptacle.say("SILO ERR: You are banned from using this ore silo.") return COMPONENT_ORE_SILO_DENY return COMPONENT_ORE_SILO_ALLOW /obj/machinery/ore_silo/ui_assets(mob/user) return list( get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials) ) /obj/machinery/ore_silo/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "OreSilo", "Ore Silo Control") ui.open() /obj/machinery/ore_silo/ui_static_data(mob/user) return materials.ui_static_data() /obj/machinery/ore_silo/ui_data(mob/user) var/list/data = list() data["materials"] = materials.ui_data() data["machines"] = list() for(var/datum/component/remote_materials/remote as anything in ore_connected_machines) var/atom/parent = remote.parent data["machines"] += list( list( "icon" = icon2base64(icon(initial(parent.icon), initial(parent.icon_state), frame = 1)), "name" = parent.name, "on_hold" = !!holds[remote], "location" = get_area_name(parent, TRUE), ) ) data["logs"] = list() for(var/datum/ore_silo_log/entry as anything in GLOB.silo_access_logs[REF(src)]) data["logs"] += list( list( "raw_materials" = entry.get_raw_materials(""), "machine_name" = entry.machine_name, "area_name" = entry.area_name, "action" = entry.action, "amount" = entry.amount, "time" = entry.timestamp, "noun" = entry.noun, "user_data" = entry.user_data, ) ) data["banned_users"] = banned_users data["ID_required"] = ID_required return data /obj/machinery/ore_silo/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return switch(action) if("remove") var/index = params["id"] if(isnull(index)) return index = text2num(index) if(isnull(index)) return var/datum/component/remote_materials/remote = ore_connected_machines[index] if(isnull(remote)) return remote.disconnect() return TRUE if("hold") var/index = params["id"] if(isnull(index)) return index = text2num(index) if(isnull(index)) return var/datum/component/remote_materials/remote = ore_connected_machines[index] if(isnull(remote)) return remote.toggle_holding() return TRUE if("eject") var/datum/material/ejecting = locate(params["ref"]) if(!istype(ejecting)) return var/amount = params["amount"] if(isnull(amount)) return amount = text2num(amount) if(isnull(amount)) return materials.retrieve_sheets(amount, ejecting, drop_location(), user_data = ID_DATA(ui.user)) return TRUE if("toggle_ban") var/list/banned_user_data = params["user_data"] attempt_ban_toggle(usr, banned_user_data) return TRUE if("toggle_restrict") attempt_toggle_restrict(usr) /** * Called from the ore silo's UI, when someone attempts to (un)ban a user from using the ore silo. * The person doing the banning should have at least QM access. Unless this is emagged. Not modifiable by silicons unless emagged. * Anyone but the Captain attempting to ban someone with QM access from the ore silo gets what is essentially a glorified version * of the permission denied result. * * user - The person who clicked the ban button in the UI. * * target_user_data - Data in the form rendered from ID_DATA(target), passed into the ore silo logs by whatever the target did such * as removing/adding sheets, printing items, etc */ /obj/machinery/ore_silo/proc/attempt_ban_toggle(mob/living/user, list/target_user_data) if(!istype(user) || !istype(target_user_data)) CRASH("Bad arguments passed to [callee]") var/emagged = obj_flags & EMAGGED if((isAI(user) || iscyborg(user) || isdrone(user)) && !emagged) to_chat(user, span_danger("A scroll of red text occludes your vision: ACCESS ENFORCEMENT _disabled_ for SILICON INTERFACE.")) user.flash_act(intensity = 1, affect_silicon = TRUE) handle_access_action_feedback( BAN_ATTEMPT_FAILURE_SOULLESS_MACHINE, ID_DATA(user), target_user_data ) return var/target_bank_id = target_user_data["account_id"] var/target_is_banned = banned_users.Find(target_bank_id) // Agent card bypasses bans but we want to specially handle them anyway // so a bunch of random account IDs don't fill the list and do something // like ban people who haven't joined the round yet var/haxxor_card_ban_immunity = !isnull(target_user_data[CHAMELEON_OVERRIDE]) to_chat(user, span_warning("You press the button to [target_is_banned ? "un" : ""]ban [target_user_data["name"]]'s account...")) // No feedback if emagged if(emagged) if(!haxxor_card_ban_immunity && isnum(target_bank_id)) target_is_banned ? banned_users.Remove(target_bank_id) : banned_users.Add(target_bank_id) return var/alist/silo_user_data = ID_DATA(user) var/list/silo_user_accesses = astype(silo_user_data["accesses"], /list) var/list/target_user_accesses = astype(target_user_data["accesses"], /list) // Even though QM bypasses the access check (or rather always pases) // perhaps the Captain would pre-emptively ban them right before a demotion if(target_user_accesses?.Find(ACCESS_QM) && !silo_user_accesses?.Find(ACCESS_CAPTAIN)) handle_access_action_feedback( BAN_ATTEMPT_FAILURE_CHALLENGING_DA_CHIEF, silo_user_data, target_user_data ) return if(!silo_user_accesses?.Find(ACCESS_QM)) handle_access_action_feedback( BAN_ATTEMPT_FAILURE_NO_ACCESS, silo_user_data, target_user_data ) return if(haxxor_card_ban_immunity) handle_access_action_feedback( FAILED_OPERATION_SUSPICIOUS, silo_user_data, target_user_data ) return if(!target_bank_id || !isnum(target_bank_id)) handle_access_action_feedback( FAILED_OPERATION_NO_BANK_ID, silo_user_data, target_user_data ) return if(target_is_banned) banned_users.Remove(target_bank_id) handle_access_action_feedback( UNBAN_CONFIRMATION, silo_user_data, target_user_data ) return // If we got here, we are banning the user banned_users.Add(target_bank_id) handle_access_action_feedback( BAN_CONFIRMATION, silo_user_data, target_user_data ) /** * Called from the ore silo tgui interface, for when someone attempts to restrict or unrestrict the ore silo from requiring * an ID with an attached bank account (or, a chameleon ID, or, being a silicon) * user - the person who tried to toggle the ore silo's access restriction. Needs to be someone with QM access, unless the * silo is emagged. Shouldn't allow silicons to toggle this unless the silo is emagged. * */ /obj/machinery/ore_silo/proc/attempt_toggle_restrict(mob/living/user) if(!istype(user)) CRASH("No user to check toggle attempt restrictions. .ID_required is unchanged.") var/emagged = obj_flags & EMAGGED if(emagged) ID_required = !ID_required return var/alist/silo_user_data = ID_DATA(user) var/is_a_robot = silo_user_data[SILICON_OVERRIDE] if(is_a_robot) handle_access_action_feedback( UNRESTRICT_FAILURE_SOULLESS_MACHINE, silo_user_data, null ) return var/list/user_accesses = astype(silo_user_data["accesses"], /list) if(!user_accesses?.Find(ACCESS_QM)) handle_access_action_feedback( UNRESTRICT_FAILURE_NO_ACCESS, silo_user_data, null ) return ID_required = !ID_required handle_access_action_feedback( ID_required ? RESTRICT_CONFIRMATION : UNRESTRICT_CONFIRMATION, silo_user_data, null) // I must sacrifice the line diff to the gods of readable code /// Set up the default announcement policy for actions /// radio_channels[channel_name_key] = policy_bitmask /// where channel_name_key is one of RADIO_CHANNEL_(COMMON|COMMAND|SECURITY|SUPPLY) /// and policy_bitmask is a bitmask of actions that will be announced on that channel /// by default /obj/machinery/ore_silo/proc/configure_default_announcements_policy() radio_channels[RADIO_CHANNEL_COMMON] = BAN_ATTEMPT_FAILURE_CHALLENGING_DA_CHIEF radio_channels[RADIO_CHANNEL_COMMON] |= RESTRICT_CONFIRMATION radio_channels[RADIO_CHANNEL_COMMON] |= UNRESTRICT_CONFIRMATION // start off with the common channel bitmask policy as a base radio_channels[RADIO_CHANNEL_COMMAND] = radio_channels[RADIO_CHANNEL_COMMON] radio_channels[RADIO_CHANNEL_COMMAND] |= BAN_ATTEMPT_FAILURE_NO_ACCESS radio_channels[RADIO_CHANNEL_COMMAND] |= BAN_ATTEMPT_FAILURE_SOULLESS_MACHINE radio_channels[RADIO_CHANNEL_COMMAND] |= UNRESTRICT_FAILURE_NO_ACCESS radio_channels[RADIO_CHANNEL_COMMAND] |= UNRESTRICT_FAILURE_SOULLESS_MACHINE radio_channels[RADIO_CHANNEL_COMMAND] |= FAILED_OPERATION_NO_BANK_ID radio_channels[RADIO_CHANNEL_COMMAND] |= BAN_CONFIRMATION radio_channels[RADIO_CHANNEL_COMMAND] |= UNBAN_CONFIRMATION // Security channel is used for security-related announcements // but gets less information than command to avoid over-informing them without // QM involvement radio_channels[RADIO_CHANNEL_SECURITY] = radio_channels[RADIO_CHANNEL_COMMON] radio_channels[RADIO_CHANNEL_SECURITY] |= BAN_ATTEMPT_FAILURE_NO_ACCESS radio_channels[RADIO_CHANNEL_SECURITY] |= UNRESTRICT_FAILURE_NO_ACCESS radio_channels[RADIO_CHANNEL_SECURITY] |= BAN_CONFIRMATION radio_channels[RADIO_CHANNEL_SECURITY] |= UNBAN_CONFIRMATION // Supply channel has the same policy by default as the command channel // due to their usual purview of the ore silo radio_channels[RADIO_CHANNEL_SUPPLY] = radio_channels[RADIO_CHANNEL_COMMAND] /obj/machinery/ore_silo/proc/handle_access_action_feedback(action, alist/silo_user_data, list/target_user_data = null) var/message = announcement_messages[action] message = replacetext(message, "$TARGET_NAME", target_user_data?["name"]) message = replacetext(message, "$SILO_USER_NAME", silo_user_data["name"]) say(message) for(var/channel in radio_channels) // Key is the channel name, value is the bitmask of announced actions if(action & radio_channels[channel]) var/say_cooldown_adherence_timer = 1 SECONDS * radio_channels.Find(channel) // * 1, * 2, * 3, etc. addtimer(CALLBACK(radio, TYPE_PROC_REF(/obj/item, talk_into), src, message, channel), say_cooldown_adherence_timer) #undef ALWAYS_ANNOUNCE #undef BAN_ATTEMPT_FAILURE_NO_ACCESS #undef BAN_ATTEMPT_FAILURE_CHALLENGING_DA_CHIEF #undef BAN_ATTEMPT_FAILURE_SOULLESS_MACHINE #undef BAN_CONFIRMATION #undef UNBAN_CONFIRMATION #undef FAILED_OPERATION_SUSPICIOUS #undef FAILED_OPERATION_NO_BANK_ID #undef UNRESTRICT_FAILURE_NO_ACCESS #undef UNRESTRICT_FAILURE_SOULLESS_MACHINE #undef UNRESTRICT_CONFIRMATION #undef RESTRICT_CONFIRMATION /** * Creates a log entry for depositing/withdrawing from the silo both ingame and in text based log * * Arguments: * - [M][/obj/machinery]: The machine performing the action. * - action: Text that visually describes the action (smelted/deposited/resupplied...) * - amount: The amount of sheets/objects deposited/withdrawn by this action. Positive for depositing, negative for withdrawing. * - noun: Name of the object the action was performed with (sheet, units, ore...) * - [mats][list]: Assoc list in format (material datum = amount of raw materials). Wants the actual amount of raw (iron, glass...) materials involved in this action. If you have 10 metal sheets each worth 100 iron you would pass a list with the iron material datum = 1000 * - user_data - ID_DATA(user), includes details (not currently) rendered to the player, such as bank account #, see the proc on SSid_access */ /obj/machinery/ore_silo/proc/silo_log(obj/machinery/M, action, amount, noun, list/mats, alist/user_data) if (!length(mats)) return var/datum/ore_silo_log/entry = new(M, action, amount, noun, mats, user_data) var/list/datum/ore_silo_log/logs = GLOB.silo_access_logs[REF(src)] if(!LAZYLEN(logs)) GLOB.silo_access_logs[REF(src)] = logs = list(entry) else if(!logs[1].merge(entry)) logs.Insert(1, entry) flick("silo_active", src) ///The log entry for an ore silo action /datum/ore_silo_log ///The time of action var/timestamp ///The name of the machine that remotely acted on the ore silo var/machine_name ///The area of the machine that remotely acted on the ore silo var/area_name ///The actual action performed by the machine var/action ///An short verb describing the action var/noun ///The amount of items affected by this action e.g. print quantity, sheets ejected etc. var/amount ///List of individual materials used in the action var/list/materials var/alist/user_data /datum/ore_silo_log/New(obj/machinery/M, _action, _amount, _noun, list/mats=list(), alist/user_data) timestamp = station_time_timestamp() machine_name = M.name area_name = get_area_name(M, TRUE) action = _action amount = _amount noun = _noun materials = mats.Copy() src.user_data = user_data var/list/data = list( "machine_name" = machine_name, "area_name" = AREACOORD(M), "action" = action, "amount" = abs(amount), "noun" = noun, "raw_materials" = get_raw_materials(""), "direction" = amount < 0 ? "withdrawn" : "deposited", "user_data" = user_data, ) logger.Log( LOG_CATEGORY_SILO, "[machine_name] in \[[AREACOORD(M)]\] [action] [abs(amount)]x [noun] | [get_raw_materials("")] | [user_data["name"]]", data, ) /** * Merges a silo log entry with this one * Arguments * * * datum/ore_silo_log/other - the other silo entry we are trying to merge with this one */ /datum/ore_silo_log/proc/merge(datum/ore_silo_log/other) if (other == src || action != other.action || noun != other.noun) return FALSE if (machine_name != other.machine_name || area_name != other.area_name) return FALSE timestamp = other.timestamp amount += other.amount for(var/each in other.materials) materials[each] += other.materials[each] return TRUE /** * Returns list/materials but with each entry joined by an seperator to create 1 string * Arguments * * * separator - the string used to concatenate all entries in list/materials */ /datum/ore_silo_log/proc/get_raw_materials(separator) var/list/msg = list() for(var/key in materials) var/datum/material/M = key var/val = round(materials[key]) / SHEET_MATERIAL_AMOUNT msg += separator separator = ", " msg += "[amount < 0 ? "-" : "+"][val] [M.name]" return msg.Join()