mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-31 11:57:12 +01:00
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>
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/**
|
||||
* todo: refactor?
|
||||
*
|
||||
* @return TRUE / FALSE; if true, caller should stop clickchain.
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_interacted_insertion(obj/item/inserting, datum/event_args/actor/actor, silent, suppressed)
|
||||
if(!actor.performer.is_holding(inserting))
|
||||
// something probably yanked it, don't bother
|
||||
return FALSE
|
||||
if(is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(!actor.performer.Reachability(indirection || parent))
|
||||
return TRUE
|
||||
if(!try_insert(inserting, actor, silent, suppressed))
|
||||
return TRUE
|
||||
// sound
|
||||
// todo: put this in interacted_insert()..?
|
||||
if(!suppressed && !isnull(actor))
|
||||
if(sfx_insert)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_insert, 50, 1, -5)
|
||||
actor.visible_feedback(
|
||||
target = parent,
|
||||
range = MESSAGE_RANGE_INVENTORY_SOFT,
|
||||
visible = "[actor.performer] puts [inserting] into [parent].",
|
||||
visible_self = "You put [inserting] into [parent]",
|
||||
)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Called by inventory procs; skips some checks of interacted insertion.
|
||||
*
|
||||
* todo: refactor?
|
||||
*
|
||||
* @return TRUE on success, FALSE on failure.
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_inventory_insertion(obj/item/inserting, datum/event_args/actor/actor, silent, suppressed)
|
||||
if(is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(!actor.performer.Reachability(indirection || parent))
|
||||
return TRUE
|
||||
if(!try_insert(inserting, actor, silent, suppressed))
|
||||
return TRUE
|
||||
// sound
|
||||
// todo: put this in interacted_insert()..?
|
||||
if(!suppressed && !isnull(actor))
|
||||
if(sfx_insert)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_insert, 50, 1, -5)
|
||||
actor.visible_feedback(
|
||||
target = parent,
|
||||
range = MESSAGE_RANGE_INVENTORY_SOFT,
|
||||
visible = "[actor.performer] puts [inserting] into [parent].",
|
||||
visible_self = "You put [inserting] into [parent]",
|
||||
)
|
||||
return TRUE
|
||||
|
||||
/datum/object_system/storage/proc/try_insert(obj/item/inserting, datum/event_args/actor/actor, silent, suppressed, no_update)
|
||||
if(!can_be_inserted(inserting, actor, silent))
|
||||
return FALSE
|
||||
// point of no return
|
||||
if(actor && (inserting.get_worn_mob() == actor.performer && !actor.performer.temporarily_remove_from_inventory(inserting, user = actor.performer)))
|
||||
if(!silent)
|
||||
actor?.chat_feedback(
|
||||
msg = SPAN_WARNING("[inserting] is stuck to your hand / body!"),
|
||||
target = parent,
|
||||
)
|
||||
return FALSE
|
||||
// point of no return (real)
|
||||
return insert(inserting, actor, suppressed, no_update)
|
||||
|
||||
/datum/object_system/storage/proc/can_be_inserted(obj/item/inserting, datum/event_args/actor/actor, silent)
|
||||
if(!check_insertion_filters(inserting))
|
||||
if(!silent)
|
||||
actor?.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] can't hold [inserting]!"),
|
||||
target = parent,
|
||||
)
|
||||
return FALSE
|
||||
var/why_insufficient_space = why_failed_insertion_limits(inserting)
|
||||
if(why_insufficient_space)
|
||||
if(!silent)
|
||||
actor?.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] can't fit [inserting]! ([why_insufficient_space])"),
|
||||
target = parent,
|
||||
)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* inserts something
|
||||
*
|
||||
* @params
|
||||
* * inserting - thing being inserted
|
||||
* * actor - who's inserting it
|
||||
* * suppressed - suppress sounds
|
||||
* * no_update - do not update uis
|
||||
* * no_move - much more than not moving; basically makes an abstract contents operation without otherwise doing normal logic. use with care.
|
||||
*/
|
||||
/datum/object_system/storage/proc/insert(obj/item/inserting, datum/event_args/actor/actor, suppressed, no_update, no_move)
|
||||
physically_insert_item(inserting, no_move)
|
||||
|
||||
if(!no_update)
|
||||
if(update_icon_on_item_change)
|
||||
update_icon()
|
||||
refresh()
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* handle moving an item in
|
||||
* we can assume this proc will do potentially literally anything with the item, so.
|
||||
*
|
||||
* @return inserted item or null (null doesn't mean insert failed; for example stacks can be merged in)
|
||||
*/
|
||||
/datum/object_system/storage/proc/physically_insert_item(obj/item/inserting, no_move, from_hook)
|
||||
inserting.item_flags |= ITEM_IN_STORAGE
|
||||
if(!no_move)
|
||||
inserting.forceMove(real_contents_loc())
|
||||
inserting.vis_flags |= VIS_INHERIT_LAYER | VIS_INHERIT_PLANE
|
||||
inserting.reset_pixel_offsets()
|
||||
inserting.on_enter_storage(src)
|
||||
if(weight_propagation)
|
||||
var/inserting_weight = inserting.get_weight()
|
||||
if(inserting_weight)
|
||||
weight_cached += inserting_weight
|
||||
update_containing_weight(inserting_weight)
|
||||
cached_combined_volume += inserting.get_weight_volume()
|
||||
cached_combined_weight_class += inserting.get_weight_class()
|
||||
return inserting
|
||||
|
||||
/**
|
||||
* @return TRUE / FALSE
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_interacted_removal(obj/item/removing, datum/event_args/actor/actor, silent, suppressed, put_in_hands)
|
||||
if(is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(!actor.performer.Reachability(parent))
|
||||
return TRUE
|
||||
if(!try_remove(removing, actor.performer, actor, silent, suppressed))
|
||||
return TRUE
|
||||
if(put_in_hands)
|
||||
actor.performer.put_in_hands_or_drop(removing)
|
||||
else
|
||||
removing.forceMove(actor.performer.drop_location())
|
||||
// todo: put this in interacted_insert()..?
|
||||
if(!suppressed && !isnull(actor))
|
||||
if(sfx_remove)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_remove, 50, 1, -5)
|
||||
actor.visible_feedback(
|
||||
target = parent,
|
||||
range = MESSAGE_RANGE_INVENTORY_SOFT,
|
||||
visible = "[actor.performer] takes [removing] out of [parent].",
|
||||
visible_self = "You take [removing] out of [parent]",
|
||||
)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* try to remove item from self
|
||||
*
|
||||
* @return item removed; not necessarily the item passed in.
|
||||
*/
|
||||
/datum/object_system/storage/proc/try_remove(obj/item/removing, atom/to_where, datum/event_args/actor/actor, silent, suppressed, no_update)
|
||||
return remove(removing, to_where, actor, suppressed, no_update)
|
||||
|
||||
/datum/object_system/storage/proc/can_be_removed(obj/item/removing, atom/to_where, datum/event_args/actor/actor, silent)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* remove item from self
|
||||
*
|
||||
* @return item removed; not necessarily the item passed in.
|
||||
*/
|
||||
/datum/object_system/storage/proc/remove(obj/item/removing, atom/to_where, datum/event_args/actor/actor, suppressed, no_update, no_move)
|
||||
. = physically_remove_item(removing, to_where, no_move)
|
||||
if(isnull(.))
|
||||
return
|
||||
|
||||
if(!no_update)
|
||||
if(update_icon_on_item_change)
|
||||
update_icon()
|
||||
refresh()
|
||||
|
||||
/**
|
||||
* handle moving an item out
|
||||
* we can assume this proc will do potentially literally anything with the item, so..
|
||||
*
|
||||
* @return moved item
|
||||
*/
|
||||
/datum/object_system/storage/proc/physically_remove_item(obj/item/removing, atom/to_where, no_move, from_hook)
|
||||
removing.item_flags &= ~ITEM_IN_STORAGE
|
||||
if(!no_move)
|
||||
if(to_where == null)
|
||||
removing.moveToNullspace()
|
||||
else
|
||||
removing.forceMove(to_where)
|
||||
removing.vis_flags = (removing.vis_flags & ~(VIS_INHERIT_LAYER | VIS_INHERIT_LAYER)) | (initial(removing.vis_flags) & (VIS_INHERIT_LAYER | VIS_INHERIT_PLANE))
|
||||
removing.on_exit_storage(src)
|
||||
// we might have set maptext in render_numerical_display
|
||||
removing.maptext = null
|
||||
if(weight_propagation)
|
||||
var/removing_weight = removing.get_weight()
|
||||
if(removing_weight)
|
||||
weight_cached -= removing_weight
|
||||
update_containing_weight(-removing_weight)
|
||||
cached_combined_volume -= removing.get_weight_volume()
|
||||
cached_combined_weight_class -= removing.get_weight_class()
|
||||
return removing
|
||||
@@ -32,3 +32,34 @@
|
||||
if(candidate.obj_storage && (candidate.w_class >= parent.w_class) && disallow_equal_weight_class_storage_nesting)
|
||||
return "can't nest storage"
|
||||
return null
|
||||
|
||||
/**
|
||||
* generally a bad idea to call, tbh.
|
||||
*
|
||||
* uses max single weight class
|
||||
* uses combined volume
|
||||
*
|
||||
* can use type whitelist
|
||||
* if type_whitelist is FALSE, this just wipes all type whitelists.
|
||||
*/
|
||||
/datum/object_system/storage/proc/fit_to_contents(type_whitelist = FALSE, no_shrink = FALSE)
|
||||
var/scanned_max_single_weight_class = WEIGHT_CLASS_TINY
|
||||
var/scanned_max_combined_volume = 0
|
||||
|
||||
if(!no_shrink)
|
||||
max_single_weight_class = scanned_max_single_weight_class
|
||||
max_combined_volume = scanned_max_combined_volume
|
||||
|
||||
var/list/types = list()
|
||||
for(var/obj/item/item in real_contents_loc())
|
||||
if(type_whitelist)
|
||||
types |= item.type
|
||||
scanned_max_single_weight_class = max(max_single_weight_class, item.w_class)
|
||||
scanned_max_combined_volume += item.get_weight_volume()
|
||||
|
||||
max_single_weight_class = max(max_single_weight_class, scanned_max_single_weight_class)
|
||||
max_combined_volume = max(max_combined_volume, scanned_max_combined_volume)
|
||||
|
||||
set_insertion_whitelist(type_whitelist? types : null)
|
||||
set_insertion_blacklist(null)
|
||||
set_insertion_allow(null)
|
||||
|
||||
@@ -1,6 +1,75 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
//* UI Main *//
|
||||
|
||||
/**
|
||||
* @return TRUE if we did something (to interrupt clickchain)
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_interacted_open(datum/event_args/actor/actor, force, suppressed)
|
||||
if(!force && is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(check_on_found_hooks(actor))
|
||||
return TRUE
|
||||
if(!suppressed && !isnull(actor) && sfx_open)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_open, 50, 1, -5)
|
||||
// todo: jiggle animation
|
||||
show(actor.initiator)
|
||||
return TRUE
|
||||
|
||||
/datum/object_system/storage/proc/show(mob/viewer)
|
||||
if(viewer.active_storage == src)
|
||||
refresh_ui(viewer)
|
||||
return TRUE
|
||||
|
||||
viewer.active_storage?.hide(viewer)
|
||||
viewer.active_storage = src
|
||||
|
||||
create_ui(viewer)
|
||||
|
||||
parent.object_storage_opened(viewer)
|
||||
|
||||
/**
|
||||
* if user not specified, it is 'all'.
|
||||
*/
|
||||
/datum/object_system/storage/proc/hide(mob/viewer)
|
||||
if(isnull(viewer))
|
||||
for(var/mob/iterating as anything in ui_by_mob)
|
||||
hide(iterating)
|
||||
return
|
||||
|
||||
if(viewer.active_storage != src)
|
||||
stack_trace("viewer didn't have active storage set right, wtf?")
|
||||
else
|
||||
viewer.active_storage = null
|
||||
|
||||
cleanup_ui(viewer)
|
||||
|
||||
parent.object_storage_closed(viewer)
|
||||
|
||||
/datum/object_system/storage/proc/refresh(mob/viewer)
|
||||
ui_refresh_queued = FALSE
|
||||
if(isnull(viewer))
|
||||
for(var/mob/iterating as anything in ui_by_mob)
|
||||
refresh_ui(iterating)
|
||||
return
|
||||
refresh_ui(viewer)
|
||||
|
||||
|
||||
/datum/object_system/storage/proc/reconsider_mob_viewable(mob/user)
|
||||
if(isnull(user))
|
||||
for(var/mob/viewer as anything in ui_by_mob)
|
||||
reconsider_mob_viewable(viewer)
|
||||
return
|
||||
if(accessible_by_mob(user))
|
||||
return
|
||||
hide(user)
|
||||
|
||||
//* Helper Datums *//
|
||||
|
||||
/**
|
||||
@@ -89,6 +158,20 @@
|
||||
/datum/object_system/storage/proc/uses_numerical_ui()
|
||||
return ui_numerical_mode
|
||||
|
||||
/**
|
||||
* inspired by CM; allows tinting the background if something is nearly full
|
||||
*/
|
||||
/datum/object_system/storage/proc/get_ui_drawer_tint()
|
||||
return null
|
||||
|
||||
/datum/object_system/storage/proc/get_ui_predicted_max_items()
|
||||
// good ol' widescreen predict length of 9 if max isn't set
|
||||
// this is so things don't break when they expect an answer
|
||||
return max_items || 9
|
||||
|
||||
/**
|
||||
* * Will not respect random access limits in numerical mode.
|
||||
*/
|
||||
/datum/object_system/storage/proc/create_ui_slot_mode(mob/user)
|
||||
. = list()
|
||||
var/atom/movable/screen/storage/closer/closer = new
|
||||
@@ -100,17 +183,20 @@
|
||||
var/view_x = decoded_view[1]
|
||||
// clamp to max items if needed
|
||||
var/rendering_width = STORAGE_UI_TILES_FOR_SCREEN_VIEW_X(view_x)
|
||||
if(max_items)
|
||||
rendering_width = min(max_items, rendering_width)
|
||||
// see if we need to process numerical display
|
||||
var/list/datum/storage_numerical_display/numerical_rendered = uses_numerical_ui()? render_numerical_display() : null
|
||||
// process indirection
|
||||
var/atom/indirection = real_contents_loc()
|
||||
var/obj/item/accessible = get_accessible_items()
|
||||
// predict max items for slot mode
|
||||
var/predicted_max_items = get_ui_predicted_max_items()
|
||||
if(predicted_max_items)
|
||||
rendering_width = min(predicted_max_items, rendering_width)
|
||||
// if we have expand when needed, only show 1 more than the actual amount.
|
||||
if(ui_expand_when_needed)
|
||||
rendering_width = min(rendering_width, (isnull(numerical_rendered)? length(indirection.contents) : length(numerical_rendered)) + 1)
|
||||
var/actually_needed = max((isnull(numerical_rendered)? length(accessible) : length(numerical_rendered)) + 1, ui_expand_when_needed)
|
||||
rendering_width = min(rendering_width, actually_needed)
|
||||
// compute count and rows
|
||||
var/item_count = isnull(numerical_rendered)? length(indirection.contents) : length(numerical_rendered)
|
||||
var/item_count = isnull(numerical_rendered)? length(accessible) : length(numerical_rendered)
|
||||
var/rows_needed = ROUND_UP(item_count / rendering_width) || 1
|
||||
// prepare iteration
|
||||
var/current_row = 1
|
||||
@@ -118,6 +204,7 @@
|
||||
// render boxes
|
||||
boxes.screen_loc = "LEFT+[STORAGE_UI_START_TILE_X]:[STORAGE_UI_START_PIXEL_X],BOTTOM+[STORAGE_UI_START_TILE_Y]:[STORAGE_UI_START_PIXEL_Y] to \
|
||||
LEFT+[STORAGE_UI_START_TILE_X + rendering_width - 1]:[STORAGE_UI_START_PIXEL_X],BOTTOM+[STORAGE_UI_START_TILE_Y + rows_needed - 1]:[STORAGE_UI_START_PIXEL_Y]"
|
||||
boxes.color = get_ui_drawer_tint()
|
||||
// render closer
|
||||
closer.screen_loc = "LEFT+[STORAGE_UI_START_TILE_X + rendering_width]:[STORAGE_UI_START_PIXEL_X],BOTTOM+[STORAGE_UI_START_TILE_Y]:[STORAGE_UI_START_PIXEL_Y]"
|
||||
// render items
|
||||
@@ -138,7 +225,7 @@
|
||||
if(current_row > STORAGE_UI_MAX_ROWS)
|
||||
break
|
||||
else
|
||||
for(var/obj/item/item in indirection)
|
||||
for(var/obj/item/item in accessible)
|
||||
var/atom/movable/screen/storage/item/slot/renderer = new(null, item)
|
||||
. += renderer
|
||||
// position
|
||||
@@ -152,6 +239,10 @@
|
||||
if(current_row > STORAGE_UI_MAX_ROWS)
|
||||
break
|
||||
|
||||
/**
|
||||
* Volumetric rendering.
|
||||
* * Doesn't respect random access limits.
|
||||
*/
|
||||
/datum/object_system/storage/proc/create_ui_volumetric_mode(mob/user)
|
||||
// guard against divide-by-0's
|
||||
if(!max_combined_volume)
|
||||
@@ -187,10 +278,13 @@
|
||||
var/effective_pixel_volume_ratio = VOLUMETRIC_STORAGE_STANDARD_PIXEL_RATIO
|
||||
// we compute how much pixels we ideally will need to display everything
|
||||
var/ideal_width_px = effective_pixel_volume_ratio * effective_max_volume
|
||||
// compute what the sane minimum is so small containers don't explode
|
||||
// 'ui_expand_volumetric_minimum_items' is used here to make tiny containers not too small
|
||||
var/sane_minimum = max((VOLUMETRIC_STORAGE_INFLATE_TO_TILES * WORLD_ICON_SIZE), ui_expand_volumetric_minimum_items * VOLUMETRIC_STORAGE_MINIMUM_PIXELS_PER_ITEM)
|
||||
// if too low,
|
||||
if(ideal_width_px < (VOLUMETRIC_STORAGE_INFLATE_TO_TILES * WORLD_ICON_SIZE))
|
||||
if(ideal_width_px < sane_minimum)
|
||||
// expand our horizons
|
||||
effective_pixel_volume_ratio = (VOLUMETRIC_STORAGE_INFLATE_TO_TILES * WORLD_ICON_SIZE) / effective_max_volume
|
||||
effective_pixel_volume_ratio = sane_minimum / effective_max_volume
|
||||
ideal_width_px = effective_pixel_volume_ratio * effective_max_volume
|
||||
|
||||
// -- prepare for iteration --
|
||||
@@ -211,7 +305,7 @@
|
||||
|
||||
// -- iterate --
|
||||
|
||||
for(var/obj/item/item as anything in indirection)
|
||||
for(var/obj/item/item in indirection)
|
||||
var/item_volume = item.get_weight_volume()
|
||||
// check safety
|
||||
if(--safety <= 0)
|
||||
@@ -286,6 +380,7 @@
|
||||
BOTTOM+[STORAGE_UI_START_TILE_Y]:[STORAGE_UI_START_PIXEL_Y] to \
|
||||
LEFT+[STORAGE_UI_START_TILE_X]:[STORAGE_UI_START_PIXEL_X + middle_shift],\
|
||||
BOTTOM+[STORAGE_UI_START_TILE_Y + current_row - 1]:[STORAGE_UI_START_PIXEL_Y]"
|
||||
p_box.color = get_ui_drawer_tint()
|
||||
// render closer on bottom
|
||||
var/atom/movable/screen/storage/closer/closer = new
|
||||
. += closer
|
||||
|
||||
@@ -11,10 +11,16 @@
|
||||
//* Access *//
|
||||
|
||||
/// if set, limit allowable random access to first n items
|
||||
var/limited_random_access_stack_amount
|
||||
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_stack_bottom_first = FALSE
|
||||
var/limited_random_access_bottom_first = FALSE
|
||||
|
||||
//* Actions *//
|
||||
|
||||
@@ -27,7 +33,7 @@
|
||||
/// use the setter, do not change this manually.
|
||||
var/weight_propagation = TRUE
|
||||
/// carry weight in us prior to mitigation
|
||||
var/weight_cached = 0
|
||||
var/tmp/weight_cached = 0
|
||||
/// carry weight mitigation, static. applied after multiplicative
|
||||
var/weight_subtract = 0
|
||||
/// carry weight mitigation, multiplicative.
|
||||
@@ -80,11 +86,11 @@
|
||||
var/insert_preposition = "in"
|
||||
|
||||
/// allow quick empty at all
|
||||
var/allow_quick_empty = FALSE
|
||||
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 = TRUE
|
||||
var/allow_quick_empty_via_attack_self = FALSE
|
||||
|
||||
/// allow inbound mass transfers
|
||||
var/allow_inbound_mass_transfer = TRUE
|
||||
@@ -140,6 +146,8 @@
|
||||
|
||||
/// 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 *//
|
||||
|
||||
@@ -194,8 +202,12 @@
|
||||
/// 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
|
||||
var/ui_expand_when_needed = FALSE
|
||||
/// * 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
|
||||
@@ -222,33 +234,45 @@
|
||||
|
||||
//* Access *//
|
||||
|
||||
/**
|
||||
* do not mutate returned list
|
||||
*/
|
||||
/datum/object_system/storage/proc/dangerously_accessible_items_immutable(random_access = TRUE)
|
||||
var/atom/redirection = real_contents_loc()
|
||||
if(random_access)
|
||||
if(limited_random_access_stack_amount)
|
||||
var/contents_length = length(redirection.contents)
|
||||
if(limited_random_access_stack_bottom_first)
|
||||
return redirection.contents.Copy(1, min(contents_length + 1, limited_random_access_stack_bottom_first + 1))
|
||||
else
|
||||
return redirection.contents.Copy(max(1, contents_length - limited_random_access_stack_amount + 1), contents_length + 1)
|
||||
return redirection.contents
|
||||
|
||||
/**
|
||||
* you may mutate returned list
|
||||
*/
|
||||
/datum/object_system/storage/proc/accessible_items(random_access = TRUE)
|
||||
/datum/object_system/storage/proc/get_accessible_items(random_access = TRUE)
|
||||
var/atom/redirection = real_contents_loc()
|
||||
if(random_access)
|
||||
if(limited_random_access_stack_amount)
|
||||
var/contents_length = length(redirection.contents)
|
||||
if(limited_random_access_stack_bottom_first)
|
||||
return redirection.contents.Copy(1, min(contents_length + 1, limited_random_access_stack_bottom_first + 1))
|
||||
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
|
||||
return redirection.contents.Copy(max(1, contents_length - limited_random_access_stack_amount + 1), contents_length + 1)
|
||||
return redirection.contents.Copy()
|
||||
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)
|
||||
@@ -305,11 +329,20 @@
|
||||
//* 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 real_contents_loc())
|
||||
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()
|
||||
@@ -365,6 +398,11 @@
|
||||
else
|
||||
rebuild_caches()
|
||||
|
||||
//* Examine *//
|
||||
|
||||
/datum/object_system/storage/proc/handle_storage_examine(list/examine_list)
|
||||
// TODO: proper examine. implement on /stack storage too
|
||||
|
||||
//* Hooks *//
|
||||
|
||||
/**
|
||||
@@ -433,260 +471,6 @@
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
//* Insertion & Removal *//
|
||||
|
||||
/**
|
||||
* todo: refactor?
|
||||
*
|
||||
* @return TRUE / FALSE; if true, caller should stop clickchain.
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_interacted_insertion(obj/item/inserting, datum/event_args/actor/actor, silent, suppressed)
|
||||
if(!actor.performer.is_holding(inserting))
|
||||
// something probably yanked it, don't bother
|
||||
return FALSE
|
||||
if(is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(!actor.performer.Reachability(indirection || parent))
|
||||
return TRUE
|
||||
if(!try_insert(inserting, actor, silent, suppressed))
|
||||
return TRUE
|
||||
// sound
|
||||
// todo: put this in interacted_insert()..?
|
||||
if(!suppressed && !isnull(actor))
|
||||
if(sfx_insert)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_insert, 50, 1, -5)
|
||||
actor.visible_feedback(
|
||||
target = parent,
|
||||
range = MESSAGE_RANGE_INVENTORY_SOFT,
|
||||
visible = "[actor.performer] puts [inserting] into [parent].",
|
||||
visible_self = "You put [inserting] into [parent]",
|
||||
)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Called by inventory procs; skips some checks of interacted insertion.
|
||||
*
|
||||
* todo: refactor?
|
||||
*
|
||||
* @return TRUE on success, FALSE on failure.
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_inventory_insertion(obj/item/inserting, datum/event_args/actor/actor, silent, suppressed)
|
||||
if(is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(!actor.performer.Reachability(indirection || parent))
|
||||
return TRUE
|
||||
if(!try_insert(inserting, actor, silent, suppressed))
|
||||
return TRUE
|
||||
// sound
|
||||
// todo: put this in interacted_insert()..?
|
||||
if(!suppressed && !isnull(actor))
|
||||
if(sfx_insert)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_insert, 50, 1, -5)
|
||||
actor.visible_feedback(
|
||||
target = parent,
|
||||
range = MESSAGE_RANGE_INVENTORY_SOFT,
|
||||
visible = "[actor.performer] puts [inserting] into [parent].",
|
||||
visible_self = "You put [inserting] into [parent]",
|
||||
)
|
||||
return TRUE
|
||||
|
||||
/datum/object_system/storage/proc/try_insert(obj/item/inserting, datum/event_args/actor/actor, silent, suppressed, no_update)
|
||||
if(!can_be_inserted(inserting, actor, silent))
|
||||
return FALSE
|
||||
// point of no return
|
||||
if(actor && (inserting.get_worn_mob() == actor.performer && !actor.performer.temporarily_remove_from_inventory(inserting, user = actor.performer)))
|
||||
if(!silent)
|
||||
actor?.chat_feedback(
|
||||
msg = SPAN_WARNING("[inserting] is stuck to your hand / body!"),
|
||||
target = parent,
|
||||
)
|
||||
return FALSE
|
||||
// point of no return (real)
|
||||
return insert(inserting, actor, suppressed, no_update)
|
||||
|
||||
/datum/object_system/storage/proc/can_be_inserted(obj/item/inserting, datum/event_args/actor/actor, silent)
|
||||
if(!check_insertion_filters(inserting))
|
||||
if(!silent)
|
||||
actor?.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] can't hold [inserting]!"),
|
||||
target = parent,
|
||||
)
|
||||
return FALSE
|
||||
var/why_insufficient_space = why_failed_insertion_limits(inserting)
|
||||
if(why_insufficient_space)
|
||||
if(!silent)
|
||||
actor?.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] can't fit [inserting]! ([why_insufficient_space])"),
|
||||
target = parent,
|
||||
)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* inserts something
|
||||
*
|
||||
* @params
|
||||
* * inserting - thing being inserted
|
||||
* * actor - who's inserting it
|
||||
* * suppressed - suppress sounds
|
||||
* * no_update - do not update uis
|
||||
* * no_move - much more than not moving; basically makes an abstract contents operation without otherwise doing normal logic. use with care.
|
||||
*/
|
||||
/datum/object_system/storage/proc/insert(obj/item/inserting, datum/event_args/actor/actor, suppressed, no_update, no_move)
|
||||
physically_insert_item(inserting, no_move)
|
||||
|
||||
if(!no_update)
|
||||
if(update_icon_on_item_change)
|
||||
update_icon()
|
||||
refresh()
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* handle moving an item in
|
||||
*
|
||||
* we can assume this proc will do potentially literally anything with the item, so.
|
||||
*/
|
||||
/datum/object_system/storage/proc/physically_insert_item(obj/item/inserting, no_move, from_hook)
|
||||
inserting.item_flags |= ITEM_IN_STORAGE
|
||||
if(!no_move)
|
||||
inserting.forceMove(real_contents_loc())
|
||||
inserting.vis_flags |= VIS_INHERIT_LAYER | VIS_INHERIT_PLANE
|
||||
inserting.reset_pixel_offsets()
|
||||
inserting.on_enter_storage(src)
|
||||
if(weight_propagation)
|
||||
var/inserting_weight = inserting.get_weight()
|
||||
if(inserting_weight)
|
||||
weight_cached += inserting_weight
|
||||
update_containing_weight(inserting_weight)
|
||||
cached_combined_volume += inserting.get_weight_volume()
|
||||
cached_combined_weight_class += inserting.get_weight_class()
|
||||
|
||||
/**
|
||||
* @return TRUE / FALSE
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_interacted_removal(obj/item/removing, datum/event_args/actor/actor, silent, suppressed, put_in_hands)
|
||||
if(is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(!actor.performer.Reachability(parent))
|
||||
return TRUE
|
||||
if(!try_remove(removing, actor.performer, actor, silent, suppressed))
|
||||
return TRUE
|
||||
if(put_in_hands)
|
||||
actor.performer.put_in_hands_or_drop(removing)
|
||||
else
|
||||
removing.forceMove(actor.performer.drop_location())
|
||||
// todo: put this in interacted_insert()..?
|
||||
if(!suppressed && !isnull(actor))
|
||||
if(sfx_remove)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_remove, 50, 1, -5)
|
||||
actor.visible_feedback(
|
||||
target = parent,
|
||||
range = MESSAGE_RANGE_INVENTORY_SOFT,
|
||||
visible = "[actor.performer] takes [removing] out of [parent].",
|
||||
visible_self = "You take [removing] out of [parent]",
|
||||
)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* try to remove item from self
|
||||
*
|
||||
* @return item removed; not necessarily the item passed in.
|
||||
*/
|
||||
/datum/object_system/storage/proc/try_remove(obj/item/removing, atom/to_where, datum/event_args/actor/actor, silent, suppressed, no_update)
|
||||
return remove(removing, to_where, actor, suppressed, no_update)
|
||||
|
||||
/datum/object_system/storage/proc/can_be_removed(obj/item/removing, atom/to_where, datum/event_args/actor/actor, silent)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* remove item from self
|
||||
*
|
||||
* @return item removed; not necessarily the item passed in.
|
||||
*/
|
||||
/datum/object_system/storage/proc/remove(obj/item/removing, atom/to_where, datum/event_args/actor/actor, suppressed, no_update, no_move)
|
||||
. = physically_remove_item(removing, to_where, no_move)
|
||||
if(isnull(.))
|
||||
return
|
||||
|
||||
if(!no_update)
|
||||
if(update_icon_on_item_change)
|
||||
update_icon()
|
||||
refresh()
|
||||
|
||||
/**
|
||||
* handle moving an item out
|
||||
*
|
||||
* we can assume this proc will do potentially literally anything with the item, so..
|
||||
*/
|
||||
/datum/object_system/storage/proc/physically_remove_item(obj/item/removing, atom/to_where, no_move, from_hook)
|
||||
removing.item_flags &= ~ITEM_IN_STORAGE
|
||||
if(!no_move)
|
||||
if(to_where == null)
|
||||
removing.moveToNullspace()
|
||||
else
|
||||
removing.forceMove(to_where)
|
||||
removing.vis_flags = (removing.vis_flags & ~(VIS_INHERIT_LAYER | VIS_INHERIT_LAYER)) | (initial(removing.vis_flags) & (VIS_INHERIT_LAYER | VIS_INHERIT_PLANE))
|
||||
removing.on_exit_storage(src)
|
||||
// we might have set maptext in render_numerical_display
|
||||
removing.maptext = null
|
||||
if(weight_propagation)
|
||||
var/removing_weight = removing.get_weight()
|
||||
if(removing_weight)
|
||||
weight_cached -= removing_weight
|
||||
update_containing_weight(-removing_weight)
|
||||
cached_combined_volume -= removing.get_weight_volume()
|
||||
cached_combined_weight_class -= removing.get_weight_class()
|
||||
return removing
|
||||
|
||||
//* Limits *//
|
||||
|
||||
/**
|
||||
* generally a bad idea to call, tbh.
|
||||
*
|
||||
* uses max single weight class
|
||||
* uses combined volume
|
||||
*
|
||||
* can use type whitelist
|
||||
* if type_whitelist is FALSE, this just wipes all type whitelists.
|
||||
*/
|
||||
/datum/object_system/storage/proc/fit_to_contents(type_whitelist = FALSE, no_shrink = FALSE)
|
||||
var/scanned_max_single_weight_class = WEIGHT_CLASS_TINY
|
||||
var/scanned_max_combined_volume = 0
|
||||
|
||||
if(!no_shrink)
|
||||
max_single_weight_class = scanned_max_single_weight_class
|
||||
max_combined_volume = scanned_max_combined_volume
|
||||
|
||||
var/list/types = list()
|
||||
for(var/obj/item/item in real_contents_loc())
|
||||
if(type_whitelist)
|
||||
types |= item.type
|
||||
scanned_max_single_weight_class = max(max_single_weight_class, item.w_class)
|
||||
scanned_max_combined_volume += item.get_weight_volume()
|
||||
|
||||
max_single_weight_class = max(max_single_weight_class, scanned_max_single_weight_class)
|
||||
max_combined_volume = max(max_combined_volume, scanned_max_combined_volume)
|
||||
|
||||
set_insertion_whitelist(type_whitelist? types : null)
|
||||
set_insertion_blacklist(null)
|
||||
set_insertion_allow(null)
|
||||
|
||||
//* Locking *//
|
||||
|
||||
/**
|
||||
@@ -1041,9 +825,13 @@
|
||||
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)
|
||||
if(TICK_CHECK || (transferred > pause_after))
|
||||
break
|
||||
var/obj/item/transferring = things[i]
|
||||
// make sure they're still there
|
||||
@@ -1065,6 +853,10 @@
|
||||
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)
|
||||
|
||||
@@ -1083,75 +875,6 @@
|
||||
for(var/obj/item/I in real_contents_loc())
|
||||
I.forceMove(where)
|
||||
|
||||
//* UI *//
|
||||
|
||||
/**
|
||||
* @return TRUE if we did something (to interrupt clickchain)
|
||||
*/
|
||||
/datum/object_system/storage/proc/auto_handle_interacted_open(datum/event_args/actor/actor, force, suppressed)
|
||||
if(!force && is_locked(actor.performer))
|
||||
actor.chat_feedback(
|
||||
msg = SPAN_WARNING("[parent] is locked."),
|
||||
target = parent,
|
||||
)
|
||||
return TRUE
|
||||
if(check_on_found_hooks(actor))
|
||||
return TRUE
|
||||
if(!suppressed && !isnull(actor) && sfx_open)
|
||||
// todo: variable sound
|
||||
playsound(actor.performer, sfx_open, 50, 1, -5)
|
||||
// todo: jiggle animation
|
||||
show(actor.initiator)
|
||||
return TRUE
|
||||
|
||||
/datum/object_system/storage/proc/show(mob/viewer)
|
||||
if(viewer.active_storage == src)
|
||||
refresh_ui(viewer)
|
||||
return TRUE
|
||||
|
||||
viewer.active_storage?.hide(viewer)
|
||||
viewer.active_storage = src
|
||||
|
||||
create_ui(viewer)
|
||||
|
||||
parent.object_storage_opened(viewer)
|
||||
|
||||
/**
|
||||
* if user not specified, it is 'all'.
|
||||
*/
|
||||
/datum/object_system/storage/proc/hide(mob/viewer)
|
||||
if(isnull(viewer))
|
||||
for(var/mob/iterating as anything in ui_by_mob)
|
||||
hide(iterating)
|
||||
return
|
||||
|
||||
if(viewer.active_storage != src)
|
||||
stack_trace("viewer didn't have active storage set right, wtf?")
|
||||
else
|
||||
viewer.active_storage = null
|
||||
|
||||
cleanup_ui(viewer)
|
||||
|
||||
parent.object_storage_closed(viewer)
|
||||
|
||||
/datum/object_system/storage/proc/refresh(mob/viewer)
|
||||
ui_refresh_queued = FALSE
|
||||
if(isnull(viewer))
|
||||
for(var/mob/iterating as anything in ui_by_mob)
|
||||
refresh_ui(iterating)
|
||||
return
|
||||
refresh_ui(viewer)
|
||||
|
||||
|
||||
/datum/object_system/storage/proc/reconsider_mob_viewable(mob/user)
|
||||
if(isnull(user))
|
||||
for(var/mob/viewer as anything in ui_by_mob)
|
||||
reconsider_mob_viewable(viewer)
|
||||
return
|
||||
if(accessible_by_mob(user))
|
||||
return
|
||||
hide(user)
|
||||
|
||||
//? Action
|
||||
|
||||
/datum/action/storage_gather_mode
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/**
|
||||
* Default backpack storage.
|
||||
*/
|
||||
/datum/object_system/storage/backpack
|
||||
max_single_weight_class = WEIGHT_CLASS_BULKY
|
||||
max_combined_volume = STORAGE_VOLUME_BACKPACK
|
||||
|
||||
/datum/object_system/storage/backpack/dufflebag
|
||||
max_combined_volume = STORAGE_VOLUME_DUFFLEBAG
|
||||
@@ -0,0 +1,174 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/**
|
||||
* Belt storage
|
||||
*
|
||||
* Uses a list of class-size-limits.
|
||||
*/
|
||||
/datum/object_system/storage/belt
|
||||
// belts use slot mode for now, pending a new belt UI
|
||||
ui_force_slot_mode = TRUE
|
||||
// belts can be massive so only expand when needed
|
||||
ui_expand_when_needed = 7
|
||||
|
||||
/// max size of small-class belt items
|
||||
var/max_combined_belt_small = 5
|
||||
/// max size of medium-class belt items
|
||||
var/max_combined_belt_medium = 3
|
||||
/// max size of large-class belt items
|
||||
var/max_combined_belt_large = 1
|
||||
|
||||
var/tmp/cached_combined_belt_small_size = 0
|
||||
var/tmp/cached_combined_belt_medium_size = 0
|
||||
var/tmp/cached_combined_belt_large_size = 0
|
||||
|
||||
/datum/object_system/storage/belt/get_ui_predicted_max_items()
|
||||
return min(max_items, max_combined_belt_small + max_combined_belt_medium + max_combined_belt_large)
|
||||
|
||||
/datum/object_system/storage/belt/uses_volumetric_ui()
|
||||
return FALSE
|
||||
|
||||
/datum/object_system/storage/belt/handle_storage_examine(list/examine_list)
|
||||
var/list/used_slots = list(cached_combined_belt_small_size, cached_combined_belt_medium_size, cached_combined_belt_large_size)
|
||||
var/list/max_slots = list(max_combined_belt_small, max_combined_belt_medium, max_combined_belt_large)
|
||||
var/list/rendered_loop_types = list()
|
||||
var/has_overrun
|
||||
for(var/i in 1 to 3)
|
||||
if(used_slots[i] > max_slots[i])
|
||||
has_overrun = TRUE
|
||||
rendered_loop_types += "<b>[used_slots[i] > max_slots[i] ? SPAN_DANGER("[used_slots[i]]") : used_slots[i]]/[max_slots[i]]</b> \
|
||||
[lowertext(global.belt_class_names[i + 1])]"
|
||||
examine_list += SPAN_NOTICE("This is a storage item with <b>belt loops</b>. There are \
|
||||
[english_list(rendered_loop_types)] loops on it.[has_overrun ? SPAN_WARNING(" Some of the larger loops are \
|
||||
currently being filled up by excess items of smaller size.") : ""]")
|
||||
|
||||
/**
|
||||
* @return list(small, medium, large, )
|
||||
*/
|
||||
/datum/object_system/storage/belt/proc/compute_free_slots() as /list
|
||||
var/small = 0
|
||||
var/medium = 0
|
||||
var/large = 0
|
||||
|
||||
// belts are weird
|
||||
// small items can overflow onto medium, medium can overflow onto large
|
||||
|
||||
// the problem arises in that if small is overrunning, you don't know if it's
|
||||
// overrunning into medium or large; this is because storage ui does not yet store, bespoke,
|
||||
// which slot an item is in, so the player has no control over it
|
||||
|
||||
// thus this standard proc is here to enforce a single order of operations
|
||||
// right now, we assume that any items always have dibs on their own slot first,
|
||||
// and overrun 'hops over' if there's not enough.
|
||||
|
||||
small = max_combined_belt_small - cached_combined_belt_small_size
|
||||
medium = max_combined_belt_medium - cached_combined_belt_medium_size
|
||||
large = max_combined_belt_large - cached_combined_belt_large_size
|
||||
|
||||
if(small < 0)
|
||||
if(medium > 0)
|
||||
// overrun as much as we can to medium
|
||||
. = min(medium, -small)
|
||||
small += .
|
||||
medium -= .
|
||||
// overrun rest to large
|
||||
large += small
|
||||
small = 0
|
||||
if(medium < 0)
|
||||
// overrun all medium to large
|
||||
large += medium
|
||||
medium = 0
|
||||
|
||||
return list(small, medium, large)
|
||||
|
||||
/datum/object_system/storage/belt/get_ui_drawer_tint()
|
||||
var/list/remaining = compute_free_slots()
|
||||
var/full_classes = 0
|
||||
for(var/i in 1 to 3)
|
||||
if(remaining[i] <= 0)
|
||||
full_classes += 1
|
||||
switch(full_classes)
|
||||
if(0)
|
||||
return null
|
||||
if(1, 2)
|
||||
return "#888800"
|
||||
if(3)
|
||||
return "#aa0000"
|
||||
|
||||
/datum/object_system/storage/belt/rebuild_caches_impl(list/atom/movable/entities)
|
||||
..()
|
||||
cached_combined_belt_small_size = cached_combined_belt_medium_size = cached_combined_belt_large_size = 0
|
||||
for(var/obj/item/item in entities)
|
||||
switch(item.belt_storage_class)
|
||||
if(BELT_CLASS_SMALL)
|
||||
cached_combined_belt_small_size += item.belt_storage_size
|
||||
if(BELT_CLASS_MEDIUM)
|
||||
cached_combined_belt_medium_size += item.belt_storage_size
|
||||
if(BELT_CLASS_LARGE)
|
||||
cached_combined_belt_large_size += item.belt_storage_size
|
||||
|
||||
/datum/object_system/storage/belt/can_be_inserted(obj/item/inserting, datum/event_args/actor/actor, silent)
|
||||
if(inserting.belt_storage_class == BELT_CLASS_INVALID)
|
||||
if(!silent)
|
||||
actor?.chat_feedback(
|
||||
SPAN_WARNING("[inserting] can't go in belts."),
|
||||
target = inserting,
|
||||
)
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/object_system/storage/belt/proc/room_left_for_belt_class(belt_class, forbid_overrun)
|
||||
if(forbid_overrun)
|
||||
switch(belt_class)
|
||||
if(BELT_CLASS_SMALL)
|
||||
. = 1
|
||||
if(BELT_CLASS_MEDIUM)
|
||||
. = 2
|
||||
if(BELT_CLASS_LARGE)
|
||||
. = 3
|
||||
return . ? compute_free_slots()[.] : 0
|
||||
else
|
||||
var/list/free_slots = compute_free_slots()
|
||||
switch(belt_class)
|
||||
if(BELT_CLASS_SMALL)
|
||||
return free_slots[1] + free_slots[2] + free_slots[3]
|
||||
if(BELT_CLASS_MEDIUM)
|
||||
return free_slots[2] + free_slots[3]
|
||||
if(BELT_CLASS_LARGE)
|
||||
return free_slots[3]
|
||||
return 0
|
||||
|
||||
/datum/object_system/storage/belt/why_failed_insertion_limits(obj/item/candidate)
|
||||
if(!room_left_for_belt_class(candidate.belt_storage_class) >= candidate.belt_storage_size)
|
||||
// incase list index out of bounds
|
||||
. = "runtime errored"
|
||||
return "insufficient belt loops of size [lowertext(global.belt_class_names[candidate.belt_storage_class])] or bigger"
|
||||
return ..()
|
||||
|
||||
/datum/object_system/storage/belt/check_insertion_limits(obj/item/candidate)
|
||||
return room_left_for_belt_class(candidate.belt_storage_class) >= candidate.belt_storage_size && ..()
|
||||
|
||||
/datum/object_system/storage/belt/physically_insert_item(obj/item/inserting, no_move, from_hook)
|
||||
var/obj/item/actually_inserted = . = ..()
|
||||
if(!istype(actually_inserted))
|
||||
return
|
||||
switch(actually_inserted.belt_storage_class)
|
||||
if(BELT_CLASS_SMALL)
|
||||
cached_combined_belt_small_size += actually_inserted.belt_storage_size
|
||||
if(BELT_CLASS_MEDIUM)
|
||||
cached_combined_belt_medium_size += actually_inserted.belt_storage_size
|
||||
if(BELT_CLASS_LARGE)
|
||||
cached_combined_belt_large_size += actually_inserted.belt_storage_size
|
||||
|
||||
/datum/object_system/storage/belt/physically_remove_item(obj/item/removing, atom/to_where, no_move, from_hook)
|
||||
var/obj/item/actually_removed = . = ..()
|
||||
if(!istype(actually_removed))
|
||||
return
|
||||
switch(actually_removed.belt_storage_class)
|
||||
if(BELT_CLASS_SMALL)
|
||||
cached_combined_belt_small_size -= actually_removed.belt_storage_size
|
||||
if(BELT_CLASS_MEDIUM)
|
||||
cached_combined_belt_medium_size -= actually_removed.belt_storage_size
|
||||
if(BELT_CLASS_LARGE)
|
||||
cached_combined_belt_large_size -= actually_removed.belt_storage_size
|
||||
@@ -0,0 +1,5 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/datum/object_system/storage/hypokit
|
||||
ui_expand_volumetric_minimum_items = 14
|
||||
@@ -0,0 +1,10 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/datum/object_system/storage/pill_bottle
|
||||
max_combined_volume = WEIGHT_VOLUME_TINY * 14
|
||||
max_single_weight_class = WEIGHT_CLASS_TINY
|
||||
max_items = 14
|
||||
ui_expand_volumetric_minimum_items = 14
|
||||
allow_quick_empty = TRUE
|
||||
allow_mass_gather = TRUE
|
||||
@@ -19,10 +19,10 @@
|
||||
/datum/object_system/storage/stack/uses_numerical_ui()
|
||||
return TRUE
|
||||
|
||||
/datum/object_system/storage/stack/rebuild_caches()
|
||||
. = ..()
|
||||
/datum/object_system/storage/stack/rebuild_caches_impl(list/atom/movable/entities)
|
||||
..()
|
||||
cached_combined_stack_amount = 0
|
||||
for(var/obj/item/stack/stack in real_contents_loc())
|
||||
for(var/obj/item/stack/stack in entities)
|
||||
cached_combined_stack_amount += stack.amount
|
||||
|
||||
/datum/object_system/storage/stack/why_failed_insertion_limits(obj/item/candidate)
|
||||
@@ -72,7 +72,7 @@
|
||||
other.add(stack.amount, TRUE)
|
||||
// this should delete the stack (?!)
|
||||
stack.use(stack.amount)
|
||||
return TRUE
|
||||
return null
|
||||
inserting = stack
|
||||
return ..()
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/**
|
||||
* Default trash bag storage.
|
||||
*/
|
||||
/datum/object_system/storage/trash_bag
|
||||
mass_operation_dumping_limit_per_second = 8
|
||||
max_single_weight_class = WEIGHT_CLASS_NORMAL
|
||||
limited_random_access_amount = 8
|
||||
limited_random_access_total_weight_volume = WEIGHT_VOLUME_NORMAL * 3
|
||||
// needed for LRA to work
|
||||
ui_force_slot_mode = TRUE
|
||||
Reference in New Issue
Block a user