Files
Bubberstation/code/modules/mining/machine_silo.dm
Ghom a28575aa82 [NO GBP] Fixing more issues with sand (you can make sand walls again, also ghost glass sheets?) (#93215)
## About The Pull Request
Apparently wall construction code is snowflaked and indented as fuck
(and the same goes for door assemblies). I'm not bothering refactoring
everything with them, only to reduce the indentation, changing a couple
vars and overall making it easier to work with them later. This includes
wall construction not being hardcoded to sheets but include the
possibility to use other kind of stacks as well (if you don't count the
snowflake interaction with iron rods). In layman's terms, this means you
can make walls made out of sand (distinct from sandstone) again.

Also I've done some small changes to the materials storage, so that it
can eject ores too if the material doesn't have a sheet type.

Also, I've been told there may be issues with broken, uninteractable
(probably not properly initialized) glass sheets beside the ORM. I'm not
100% sure about the deets, but it may have something to do with spawning
the glass on the same turf the ORM is listening to, when smelting sand,
causing some race conditions, so let's spawn it in nullspace

## Why It's Good For The Game
While I'm sure there may be more elegant solutions (just take a look at
the wall and door construction code, they both use text2path oh god!),
I'm just here to make things a lil' cleaner and be done with issues with
the fact that sand is made of sand.

## Changelog

🆑
fix: You can once again make sand walls.
fix: Deconstructing an autolathe with sand in it should now drop sand.
/🆑
2025-10-02 19:12:11 +03:00

628 lines
24 KiB
Plaintext

// 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
/// Only the station loaded ore silo starts out with ID restrictions, else its optional
var/ID_required = FALSE
/// 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,
)
///List of announcement messages for silo restrictions
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_STACK_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
ID_required = TRUE
register_context()
setup_radio()
configure_default_announcements_policy()
/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/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/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_stack(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(ui.user, banned_user_data)
return TRUE
if("toggle_restrict")
attempt_toggle_restrict(ui.user)
return TRUE
/**
* 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
///User data of the player doing material operations
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()