Files
30fbcfe9b4 Belts & Trashbags (#7443)
requires #6644 

Entirely replaces how (most) belts perform insertion checks.

This pretty much allows belts to hold most things.

Armor vests are next.

Trashbags will also use the systems we use on main.

Closes #7256 and #7435

---------

Co-authored-by: silicons <silicons@silicons.dev>
2025-12-28 22:05:06 -05:00

918 lines
29 KiB
Plaintext

//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 Citadel Station Developers *//
/**
* storage systems
*
* stores /item's
*/
/datum/object_system/storage
//* Access *//
/// if set, limit allowable random access to first n items
var/limited_random_access_amount
/// if set, limit allowable random access to first n items with this max weight class
/// * requires [limited_random_access_amount] to be set
var/limited_random_access_total_weight_class = INFINITY
/// if set, limit allowable random access to first n items with this max weight volume
/// * requires [limited_random_access_amount] to be set
var/limited_random_access_total_weight_volume = INFINITY
/// if set, limit allowable random access goes from bottom up, not top down
/// * top down is from end of contents list, bottom up is from start of contents list
var/limited_random_access_bottom_first = FALSE
//* Actions *//
/// the action we have for mode switching gathering
var/datum/action/storage_gather_mode/action_mode_switch
//* Carry Weight *//
/// do we propagate weight?
/// use the setter, do not change this manually.
var/weight_propagation = TRUE
/// carry weight in us prior to mitigation
var/tmp/weight_cached = 0
/// carry weight mitigation, static. applied after multiplicative
var/weight_subtract = 0
/// carry weight mitigation, multiplicative.
var/weight_multiply = 1
//* Deconstruction & Integrity *//
/// on deconstruct(method), drop on these method flags
var/drop_on_deconstruction_methods = ALL
/// locks don't work if atom is broken
var/lock_nullified_by_atom_break = FALSE
//* Defense *//
/// pass EMPs in
var/pass_emp_inside = TRUE
/// pass EMPs in but weaken them
var/pass_emp_weaken = TRUE
//* Economy *//
/// allow selling stuff in worth intrinsic
var/use_worth_containing = TRUE
//* Filters *//
/// protected var because we want to cache.
/// set to a list of typepaths to initialize it at New().
/// * things in this list are the only things that can fit in us
/// * overruled by blacklist
/// * Never, ever edit this list directly.
VAR_PROTECTED/list/insertion_whitelist_typecache
/// protected var because we want to cache.
/// set to a list of typepaths to initialize it at New().
/// * things in this list can never fit in us, even if in whitelist
/// * Never, ever edit this list directly.
VAR_PROTECTED/list/insertion_blacklist_typecache
/// protected var because we want to cache.
/// set to a list of typepaths to initialize it at New().
/// * in whitelist mode, things in this list can also be put in
/// * in blacklist mode, things in this list can always be put in regardless
/// * overrules both whitelist and blacklist
/// * Never, ever edit this list directly.
/// this is so overriding things can be easier.
VAR_PROTECTED/list/insertion_allow_typecache
//* Interaction *//
/// insert proposition: 'on', 'in', etc
var/insert_preposition = "in"
/// allow quick empty at all
var/allow_quick_empty = TRUE
/// allow quick empty via clickdrag
var/allow_quick_empty_via_clickdrag = TRUE
/// allow quick empty via attack self
var/allow_quick_empty_via_attack_self = FALSE
/// allow inbound mass transfers
var/allow_inbound_mass_transfer = TRUE
/// allow outbound mass transfer
var/allow_outbound_mass_transfer = TRUE
/// allow clickdrag to initiate mass transfer
var/allow_clickdrag_mass_transfer = TRUE
/// allow mass gather at all
var/allow_mass_gather = FALSE
/// allow mass gather via click
var/allow_mass_gather_via_click = TRUE
/// allow switching mass gathering mode
var/allow_mass_gather_mode_switch = TRUE
/// mass gather mode
var/mass_gather_mode = STORAGE_QUICK_GATHER_COLLECT_ALL
/// allow opening when clicking from other hand
var/allow_open_via_offhand_click = TRUE
/// allow opening via alt click
var/allow_open_via_alt_click = TRUE
/// allow opening via context menu click
var/allow_open_via_context_click = TRUE
/// allow opening when clicking from hand if this is equipped
var/allow_open_via_equipped_click = TRUE
/// allow opening via clickdrag to self
var/allow_open_via_clickdrag_to_self = TRUE
/// ghosts can always see inside
var/always_allow_observer_view = TRUE
//* Limits *//
/// if set, limit to a certain volume
var/max_combined_volume
/// if set, allow only a certain amount of items
var/max_items
/// if set, max weight class we can hold
var/max_single_weight_class
/// if set, max combined weight class of all containing items we can hold
var/max_combined_weight_class
/// disallow nesting storage items of same or bigger weight class
var/disallow_equal_weight_class_storage_nesting = TRUE
//* Locking *//
/// locked storage can't be accessed, unless show() is called with force
/// however, you can continue viewing even if it's locked.
/// use set_locked() to modify.
var/locked = FALSE
//* Mass Operations *//
/// mutex to prevent mass operation spam
var/mass_operation_interaction_mutex = FALSE
/// max items dropped per second for mass dumping
var/mass_operation_dumping_limit_per_second = INFINITY
//* Redirection *//
/// When set, we treat this as the real parent object.
/// **Warning**: This is an advanced feature.
/// It is your responsibility to understand what that means in context of storage, not ours.
///
/// The storage backend this was inspired from used to have a real / virtual system,
/// where not all storage components were 'real' and could link to a 'concrete'
/// storage component where items are actually stored, so remote storages work.
///
/// That, however, is too complex and awful, so, we just have a single redirection var now
/// if you mess it up, it is not my fault. ~silicons
///
/// * If you use this, you should probably set [use_worth_containing] to FALSE if it isn't logically in here.
/// * If you don't, double (or even infinite) selling can happen.
///
/// todo: this literally doesn't work due to no Reachability hooks. please implement this properly later via reachability signal hooks.
var/atom/movable/storage_indirection/indirection
//* Radiation *//
/// pass clean radiation calls inside?
var/pass_clean_radiation_inside = FALSE
//* Rendering *//
/// update icon on item change
var/update_icon_on_item_change = FALSE
//* State Caches *//
/// cached combined w class
var/tmp/cached_combined_weight_class
/// cached combined volume
var/tmp/cached_combined_volume
//* Sound Effects *//
/// open / access sound passed into playsound
var/sfx_open = "rustle"
/// removal sound passed into playsoud
var/sfx_remove = "rustle"
/// insert sound passed into playsound
var/sfx_insert = "rustle"
//* UIs *//
/// lazy list of UIs open; mob ref = list(ui objects)
var/list/ui_by_mob
/// stack stuff by number; defaults to types, please override the requisite proc to implement yours.
var/ui_numerical_mode = FALSE
/// ui force slot mode.
var/ui_force_slot_mode = FALSE
/// show minimum number of slots necessary, expand as needed
/// * currently only works for slot mode
/// * this should be set to a number to determine how many minimum slots to show despite this
/// setting; the minimum enabled number of '1' just shows a single slot.
var/ui_expand_when_needed = 0
/// used to hint the number of minimum items to assume we have. useful for pill bottles.
var/ui_expand_volumetric_minimum_items
/// ui update queued?
// todo: this is only needed because redirection is halfassed.
var/ui_refresh_queued = FALSE
/// Queued a reconsider_mob_viewable?
/// * Done because moving diagonally is weird and we're technically 'detached'
/// for a moment.
var/ui_move_reconsideration_queued = FALSE
/datum/object_system/storage/New(obj/parent)
src.parent = parent
// we use typelists to detect if it's been init'd
if(!is_typelist(insertion_whitelist_typecache))
insertion_whitelist_typecache = cached_typecacheof(insertion_whitelist_typecache)
if(!is_typelist(insertion_blacklist_typecache))
insertion_blacklist_typecache = cached_typecacheof(insertion_blacklist_typecache)
if(!is_typelist(insertion_allow_typecache))
insertion_allow_typecache = cached_typecacheof(insertion_allow_typecache)
/datum/object_system/storage/Destroy()
hide()
QDEL_NULL(action_mode_switch)
QDEL_NULL(indirection)
return ..()
//* Access *//
/**
* you may mutate returned list
*/
/datum/object_system/storage/proc/get_accessible_items(random_access = TRUE)
var/atom/redirection = real_contents_loc()
if(!length(redirection.contents))
return list()
if(random_access && limited_random_access_amount)
var/list/copied_contents = . = list()
if(limited_random_access_amount)
var/original_contents_length = length(redirection.contents)
var/lra_start
var/lra_end
var/lra_step
if(limited_random_access_bottom_first)
lra_start = 1
lra_end = min(limited_random_access_amount, original_contents_length)
lra_step = 1
else
lra_start = original_contents_length
lra_end = max(1, original_contents_length - limited_random_access_amount + 1)
lra_step = -1
var/list/lra_ref = redirection.contents
var/lra_twc = 0
var/lra_twv = 0
for(var/i in lra_start to lra_end step lra_step)
var/obj/item/maybe_item = lra_ref[i]
// it is valid albeit silly to have non-items in a storage object
if(!istype(maybe_item))
continue
lra_twc += maybe_item.w_class
lra_twv += maybe_item.weight_volume
if(lra_twc > limited_random_access_total_weight_class)
break
if(lra_twv > limited_random_access_total_weight_volume)
break
copied_contents += maybe_item
else
. = redirection.contents.Copy()
/**
* Recursively return all inventory in this or nested storage (without indirection)
*/
/datum/object_system/storage/proc/recursively_return_contents(list/returning = list())
var/atom/redirection = real_contents_loc()
for(var/obj/item/iterating in redirection)
returning += iterating
iterating.obj_storage?.recursively_return_contents(returning)
return returning
/**
* iterate through what's considered inside
*
* this is a mutable / copy of a list, so you're free to mess with it.
*/
/datum/object_system/storage/proc/contents()
. = list()
for(var/obj/item/inside in real_contents_loc())
. += inside
/datum/object_system/storage/proc/accessible_by_mob(mob/user)
if(IsAdminGhost(user) || (isobserver(user) && always_allow_observer_view))
return TRUE
return user.CheapReachability(parent)
/datum/object_system/storage/proc/top_entity_in_contents()
RETURN_TYPE(/atom/movable)
var/atom/indirection = real_contents_loc()
var/amt = length(indirection.contents)
return amt? indirection.contents[amt] : null
//* Buttons *//
/datum/object_system/storage/proc/ensure_buttons()
if(!allow_mass_gather || !allow_mass_gather_mode_switch)
return
if(!isnull(action_mode_switch))
return
action_mode_switch = new(src)
action_mode_switch.button_additional_only = TRUE
/datum/object_system/storage/proc/grant_buttons(mob/wearer)
ensure_buttons()
if(allow_mass_gather && allow_mass_gather_mode_switch)
action_mode_switch.grant(wearer.inventory.actions)
/datum/object_system/storage/proc/revoke_buttons(mob/wearer)
action_mode_switch?.revoke(wearer.inventory.actions)
/datum/object_system/storage/ui_action_click(datum/action/action, datum/event_args/actor/actor)
switch_gathering_modes(actor.performer)
//* Caches *//
/datum/object_system/storage/proc/rebuild_caches()
SHOULD_NOT_SLEEP(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
var/atom/indirection = real_contents_loc()
rebuild_caches_impl(indirection ? indirection.contents : list())
/datum/object_system/storage/proc/rebuild_caches_impl(list/atom/movable/entities)
SHOULD_CALL_PARENT(TRUE)
SHOULD_NOT_SLEEP(TRUE)
cached_combined_volume = 0
cached_combined_weight_class = 0
var/old_weight = weight_cached
weight_cached = 0
for(var/obj/item/item in entities)
cached_combined_volume += item.get_weight_volume()
cached_combined_weight_class += item.get_weight_class()
weight_cached += item.get_weight()
update_containing_weight(weight_cached - old_weight)
/**
* rebuild full state, used when shit explodes, vv use only. this is not totally
* idempotent due to on_enter_storage() not being 100% idempotent, so, this is a debug proc.
*/
/datum/object_system/storage/proc/dangerously_kind_of_rebuild_state()
// incase it wasn't clear enough: **DO NOT USE THIS UNLESS YOU ARE AN ADMIN TRYING TO FIX SOMETHING WITH SDQL.**
PRIVATE_PROC(TRUE)
for(var/obj/item/item in real_contents_loc())
// assert variables
physically_insert_item(item, no_move = TRUE)
// rebuild the caches we just fucked up
rebuild_caches()
//* Checks *//
/datum/object_system/storage/proc/remaining_volume()
return isnull(max_combined_volume)? INFINITY : (max_combined_volume - cached_combined_volume)
/datum/object_system/storage/proc/remaining_weight_class()
return isnull(max_combined_weight_class)? INFINITY : (max_combined_weight_class - cached_combined_weight_class)
/datum/object_system/storage/proc/remaining_items()
var/atom/indirection = real_contents_loc()
return isnull(max_items)? INFINITY : (max_items - length(indirection.contents))
//* Carry Weight *//
/datum/object_system/storage/proc/get_containing_weight()
return max(0, weight_cached * weight_multiply - weight_subtract)
/datum/object_system/storage/proc/update_containing_weight(change)
if(!isitem(parent))
return
// todo: rewrite the weight system
var/obj/item/item = parent
item.update_weight()
var/current = item.get_weight()
item.propagate_weight(current - change, current)
/datum/object_system/storage/proc/set_weight_propagation(value)
if(value == weight_propagation)
return
weight_propagation = value
if(!weight_propagation)
var/old_weight_cached = weight_cached
weight_cached = 0
update_containing_weight(-old_weight_cached)
else
rebuild_caches()
//* Examine *//
/datum/object_system/storage/proc/handle_storage_examine(list/examine_list)
// TODO: proper examine. implement on /stack storage too
//* Hooks *//
/**
* Hooked into obj/Moved().
*/
/datum/object_system/storage/proc/on_parent_moved(atom/old_loc, forced)
if(ui_move_reconsideration_queued)
return
ui_move_reconsideration_queued = TRUE
spawn(0)
ui_move_reconsideration_queued = FALSE
if(QDELETED(src))
return
reconsider_mob_viewable()
/datum/object_system/storage/proc/on_pickup(mob/user)
grant_buttons(user)
/datum/object_system/storage/proc/on_dropped(mob/user)
revoke_buttons(user)
/**
* Called by our object when an item exits us
*/
/datum/object_system/storage/proc/on_item_exited(obj/item/exiting)
if(!(exiting.item_flags & ITEM_IN_STORAGE))
return
physically_remove_item(exiting, no_move = TRUE, from_hook = TRUE)
ui_queue_refresh()
/**
* Called by our object when an item enters us
*/
/datum/object_system/storage/proc/on_item_entered(obj/item/entering)
if(entering.item_flags & ITEM_IN_STORAGE)
return
physically_insert_item(entering, no_move = TRUE, from_hook = TRUE)
ui_queue_refresh()
/datum/object_system/storage/proc/on_contents_weight_class_change(obj/item/item, old_weight_class, new_weight_class)
cached_combined_weight_class += (new_weight_class - old_weight_class)
if(isnull(item.weight_volume))
on_contents_weight_volume_change(item, global.w_class_to_volume[old_weight_class], global.w_class_to_volume[new_weight_class])
/datum/object_system/storage/proc/on_contents_weight_volume_change(obj/item/item, old_weight_volume, new_weight_volume)
cached_combined_volume += (new_weight_volume - old_weight_volume)
/datum/object_system/storage/proc/on_contents_weight_change(obj/item/item, old_weight, new_weight)
weight_cached += (new_weight - old_weight)
update_containing_weight(new_weight - old_weight)
/datum/object_system/storage/proc/on_contents_item_new(obj/item/item)
if(item.item_flags & ITEM_IN_STORAGE) // somehow
return
physically_insert_item(item, no_move = TRUE)
ui_queue_refresh()
//* Interaction *//
/**
* returns TRUE if we should interrupt an open
*/
/datum/object_system/storage/proc/check_on_found_hooks(datum/event_args/actor/actor)
for(var/obj/item/thing in real_contents_loc())
if(thing.on_containing_storage_opening(actor, src))
return TRUE
return FALSE
//* Locking *//
/**
* @params
* * accessing - the mob physically trying to reach in
*/
/datum/object_system/storage/proc/is_locked(mob/accessing)
return locked && (!lock_nullified_by_atom_break || !(parent.atom_flags & ATOM_BROKEN))
/datum/object_system/storage/proc/set_locked(value)
if(locked == value)
return
locked = value
if(locked)
hide()
//* Quickdraw *//
// todo: quickdraw
//* Redirection *//
/datum/object_system/storage/proc/real_contents_loc()
return indirection || parent
//* Rendering - Object *//
/datum/object_system/storage/proc/update_icon()
parent.update_icon()
action_mode_switch?.button_additional_overlay = parent
action_mode_switch?.update_buttons(TRUE)
//* Transfer *//
/datum/object_system/storage/proc/switch_gathering_modes(mob/user)
switch(mass_gather_mode)
if(STORAGE_QUICK_GATHER_COLLECT_ONE)
mass_gather_mode = STORAGE_QUICK_GATHER_COLLECT_ALL
to_chat(user, SPAN_NOTICE("[parent] is now collecting all items from a tile."))
if(STORAGE_QUICK_GATHER_COLLECT_ALL)
mass_gather_mode = STORAGE_QUICK_GATHER_COLLECT_SAME
to_chat(user, SPAN_NOTICE("[parent] is now collecting all items of a given type from a tile."))
if(STORAGE_QUICK_GATHER_COLLECT_SAME)
mass_gather_mode = STORAGE_QUICK_GATHER_COLLECT_ONE
to_chat(user, SPAN_NOTICE("[parent] is now collecting one item at a time."))
/**
* Actor is mandatory.
*/
/datum/object_system/storage/proc/auto_handle_interacted_mass_transfer(datum/event_args/actor/actor, datum/object_system/storage/to_storage)
if(to_storage == src)
return FALSE
if(is_locked(actor.performer))
actor?.chat_feedback(
msg = SPAN_WARNING("[parent] is locked."),
target = parent,
)
return TRUE
parent.add_fingerprint(actor.performer)
interacted_mass_transfer(actor, to_storage)
return TRUE
/**
* Actor is mandatory.
*/
/datum/object_system/storage/proc/auto_handle_interacted_mass_pickup(datum/event_args/actor/actor, atom/from_where)
if(is_locked(actor.performer))
actor?.chat_feedback(
msg = SPAN_WARNING("[parent] is locked."),
target = parent,
)
return TRUE
parent.add_fingerprint(actor.performer)
interacted_mass_pickup(actor, from_where)
return TRUE
/**
* Actor is mandatory.
*/
/datum/object_system/storage/proc/auto_handle_interacted_mass_dumping(datum/event_args/actor/actor, atom/to_where)
if(is_locked(actor.performer))
actor?.chat_feedback(
msg = SPAN_WARNING("[parent] is locked."),
target = parent,
)
return TRUE
parent.add_fingerprint(actor.performer)
interacted_mass_dumping(actor, to_where)
return TRUE
/**
* Actor is mandatory.
*/
/datum/object_system/storage/proc/interacted_mass_transfer(datum/event_args/actor/actor, datum/object_system/storage/to_storage, silent, suppressed)
if(mass_operation_interaction_mutex)
return
mass_operation_interaction_mutex = TRUE
var/list/obj/item/transferring = list()
for(var/obj/item/item in real_contents_loc())
transferring += item
if(!length(transferring))
if(!silent)
actor.chat_feedback(
msg = "There's nothing to transfer out of [parent].",
target = src,
)
mass_operation_interaction_mutex = FALSE
return
if(!silent)
actor.chat_feedback(
msg = "You start transferring the contents of [parent] to [to_storage.parent]",
target = src,
)
if(!suppressed && sfx_insert)
playsound(parent, sfx_insert, 50, TRUE, -4)
var/list/rejections = list()
while(do_after(actor.performer, 0.5 SECONDS, parent, NONE, MOBILITY_CAN_STORAGE | MOBILITY_CAN_PICKUP))
if(!mass_storage_transfer_handler(transferring, to_storage, actor, rejections))
break
stoplag(1)
if(!silent)
if(!length(rejections))
actor.chat_feedback(
msg = "You finish transferring everything out from [parent].",
target = src,
)
else
actor.chat_feedback(
msg = "You fail to transfer some things out of [parent].",
target = src,
)
ui_queue_refresh()
mass_operation_interaction_mutex = FALSE
/**
* Actor is mandatory.
*/
/datum/object_system/storage/proc/interacted_mass_pickup(datum/event_args/actor/actor, atom/from_loc, silent, suppressed)
if(mass_operation_interaction_mutex)
return
mass_operation_interaction_mutex = TRUE
var/list/transferring
switch(mass_gather_mode)
if(STORAGE_QUICK_GATHER_COLLECT_ONE)
if(!isitem(from_loc))
return
try_insert(from_loc, actor)
mass_operation_interaction_mutex = FALSE
return
if(STORAGE_QUICK_GATHER_COLLECT_ALL)
transferring = list()
from_loc = get_turf(from_loc)
for(var/obj/item/item in from_loc)
transferring += item
if(STORAGE_QUICK_GATHER_COLLECT_SAME)
transferring = list()
from_loc = get_turf(from_loc)
for(var/obj/item/item in from_loc)
if(item.type != from_loc.type)
continue
transferring += item
if(!length(transferring))
mass_operation_interaction_mutex = FALSE
return
if(!silent)
actor.chat_feedback(
msg = "You start gathering things from [from_loc] with [parent].",
target = src,
)
if(!suppressed && sfx_insert)
playsound(parent, sfx_insert, 50, TRUE, -4)
var/list/rejections = list()
while(do_after(actor.performer, 0.5 SECONDS, parent, NONE, MOBILITY_CAN_STORAGE | MOBILITY_CAN_PICKUP))
if(!mass_storage_pickup_handler(transferring, from_loc, actor, rejections))
break
stoplag(1)
ui_queue_refresh()
mass_operation_interaction_mutex = FALSE
/**
* Actor is mandatory.
*/
/datum/object_system/storage/proc/interacted_mass_dumping(datum/event_args/actor/actor, atom/to_loc, silent, suppressed)
if(mass_operation_interaction_mutex)
return
mass_operation_interaction_mutex = TRUE
var/list/obj/item/transferring = mass_dumping_query()
if(!length(transferring))
if(!silent)
actor.chat_feedback(
msg = "There's nothing to dump out of [parent].",
target = src,
)
mass_operation_interaction_mutex = FALSE
return
if(!silent)
actor.chat_feedback(
msg = "You start dumping out the contents of [parent].",
target = src,
)
if(!suppressed && sfx_insert)
playsound(parent, sfx_remove, 50, TRUE, -4)
var/list/rejections = list()
while(do_after(actor.performer, 0.5 SECONDS, parent, NONE, MOBILITY_CAN_STORAGE | MOBILITY_CAN_PICKUP))
var/keep_going = mass_storage_dumping_handler(transferring, to_loc, actor, rejections)
if(!keep_going)
break
if(!silent)
if(length(rejections))
actor.chat_feedback(
msg = "You fail to dump some things out of [parent].",
target = src,
)
else if(!length(transferring))
actor.chat_feedback(
msg = "You finish dumping everything out of [parent].",
target = src,
)
ui_queue_refresh()
mass_operation_interaction_mutex = FALSE
/**
* handles mass storage transfers
*
* this will CHECK_TICK.
*
* progressbar should be initialized with goal value being the total item count.
*
* @params
* * things - things to transfer
* * to_storage - destination storage
* * progress - (optional) progressbar to update
* * actor - (optional) person doing it
* * rejections_out - (optional) rejected items
* * trigger_on_found - trigger on found?
*
* This proc does not update / refresh UIs, please do that manually when using this proc.
*
* @return TRUE if not done, FALSE if done.
*/
/datum/object_system/storage/proc/mass_storage_transfer_handler(list/obj/item/things, datum/object_system/storage/to_storage, datum/event_args/actor/actor, list/obj/item/rejections_out = list(), trigger_on_found = TRUE)
if(to_storage == src)
return FALSE
var/atom/indirection_from = real_contents_loc()
var/atom/indirection_to = to_storage.real_contents_loc()
var/i = length(things)
. = TRUE
while(i > 0)
// stop if overtaxed
if(TICK_CHECK)
break
var/obj/item/transferring = things[i]
// make sure they're still there
if(transferring.loc != indirection_from)
i--
continue
// handle on open hooks if needed
if(trigger_on_found && actor?.performer.active_storage != src && transferring.on_containing_storage_opening(actor, src))
. = FALSE
break
// see if receiver can accept it
if(!to_storage.can_be_inserted(transferring, actor, TRUE))
rejections_out += transferring
i--
continue
// see if we can remove it
if(!can_be_removed(transferring, indirection_to, actor, TRUE))
rejections_out += transferring
i--
continue
// transfer; the on enter/exit hooks will handle the rest (awful but whatever!)
if(transferring == remove(transferring, indirection_to, actor, TRUE, TRUE))
// but only go down if we got rid of the real item
i--
things.Cut(i + 1, length(things) + 1)
return . && length(things)
/**
* handles mass item pickups
*
* this will CHECK_TICK
*
* progressbar should be initialized with goal value being the total item count.
*
* @params
* * things - things to transfer
* * from_loc - source loc
* * progress - (optional) progressbar to update
* * actor - (optional) person doing it
* * rejections_out - (optional) list to add rejected items to
*
* This proc does not update / refresh UIs, please do that manually when using this proc.
*
* @return TRUE if not done, FALSE if done
*/
/datum/object_system/storage/proc/mass_storage_pickup_handler(list/obj/item/things, atom/from_loc, datum/event_args/actor/actor, list/rejections_out = list())
var/i
. = TRUE
for(i in length(things) to 1 step -1)
// stop if overtaxed
if(TICK_CHECK)
break
var/obj/item/transferring = things[i]
// make sure they're still there
if(transferring.loc != from_loc)
continue
// see if it can be picked up
if(!transferring.should_allow_pickup(actor, TRUE))
rejections_out += transferring
continue
// see if we can accept it
if(!try_insert(transferring, actor, TRUE, TRUE, TRUE))
rejections_out += transferring
continue
things.Cut(i, length(things) + 1)
return . && length(things)
/**
* handles mass item dumps
*
* this will CHECK_TICK
*
* progressbar should be initialized with goal value being the total item count.
*
* @params
* * things - things to transfer
* * to_loc - where to transfer
* * progress - (optional) progressbar to update
* * actor - (optional) person doing it
* * rejections_out - (optional) list to add rejected items to
* * trigger_on_found - trigger on found?
*
* This proc does not update / refresh UIs, please do that manually when using this proc.
*
* @return TRUE if not done, FALSE if done
*/
/datum/object_system/storage/proc/mass_storage_dumping_handler(list/obj/item/things, atom/to_loc, datum/event_args/actor/actor, list/rejections_out = list(), trigger_on_found = TRUE)
var/atom/indirection = real_contents_loc()
var/i = length(things)
. = TRUE
// we're driven on 0.5 second ticks; we want to pause after half a MODLPS
// might want to adjust this later and use a parameter or something lol
var/pause_after = mass_operation_dumping_limit_per_second * 0.5
var/transferred = 0
while(i > 0)
// stop if overtaxed
if(TICK_CHECK || (transferred > pause_after))
break
var/obj/item/transferring = things[i]
// make sure they're still there
if(transferring.loc != indirection)
i--
continue
// handle on open hooks if needed
if(trigger_on_found && actor?.performer.active_storage != src && transferring.on_containing_storage_opening(actor, src))
. = FALSE
break
// see if we can remove it
var/obj/item/removed = try_remove(transferring, to_loc, actor, TRUE, TRUE, TRUE)
if(isnull(removed))
// failed
rejections_out += transferring
i--
continue
// succeeded
if(removed == transferring)
// but only go down if we got rid of the real item
i--
// always count the cycle as transferred,
// if you have invalid objects and it gets in the way of MODLPS
// then that's on you, stop powergaming.
transferred++
things.Cut(i + 1, length(things) + 1)
return . && length(things)
/**
* what to drop
*/
/datum/object_system/storage/proc/mass_dumping_query()
var/atom/indirection = real_contents_loc()
// holy shit do a copy please
return indirection.contents.Copy()
/**
* drop everything at
*/
/datum/object_system/storage/proc/drop_everything_at(atom/where)
for(var/obj/item/I in real_contents_loc())
I.forceMove(where)
//? Action
/datum/action/storage_gather_mode
name = "Switch Gather Mode"
/datum/action/storage_gather_mode/pre_render_hook()
if(istype(target, /datum/object_system/storage))
var/datum/object_system/storage/storage_datum = target
var/obj/storage_target = storage_datum.parent
var/image/generated = new
generated.appearance = storage_target
generated.layer = FLOAT_LAYER
generated.plane = FLOAT_PLANE
button_additional_overlay = generated
return ..()
//? Hooks
/obj/proc/object_storage_opened(mob/user)
return
/obj/proc/object_storage_closed(mob/user)
return
//? Wrapper for init
/**
* Use this to init, not new()!
*/
/obj/proc/init_storage(path = /datum/object_system/storage, indirected = FALSE)
RETURN_TYPE(/datum/object_system/storage)
ASSERT(isnull(obj_storage))
obj_storage = new path(src)
if(!indirected)
// get all items inside / registered
for(var/obj/item/item in contents)
obj_storage.on_item_entered(item)
else
obj_storage.indirect(src)
return obj_storage