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:
silicons
2025-12-28 22:05:06 -05:00
committed by GitHub
co-authored by silicons
parent 7707f9e881
commit 30fbcfe9b4
124 changed files with 1176 additions and 1086 deletions
+1 -1
View File
@@ -97,7 +97,7 @@
if(isturf(old_loc))
new /obj/effect/temporary_effect/item_pickup_ghost(old_loc, actually_picked_up, user)
user.trigger_aiming(TARGET_CAN_CLICK)
//* Drag / Drop *//
/obj/item/OnMouseDrop(atom/over, mob/user, proximity, params)
+26 -24
View File
@@ -117,18 +117,6 @@
var/obj/item/worn_inside
/// suppress auto inventory hooks in forceMove
var/worn_hook_suppressed = FALSE
/// Suit storage classes
var/suit_storage_class = NONE
/// Suit storage classes to allow
var/suit_storage_class_allow = NONE
/// Suit storage classes to disallow
var/suit_storage_class_disallow = NONE
/// Suit storage override type-list
/// * only for adminbus really
VAR_PRIVATE/list/suit_storage_types_allow_override
/// Suit storage override type-list
/// * only for adminbus really
VAR_PRIVATE/list/suit_storage_types_disallow_override
//* Environmentals *//
/// Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags.
@@ -165,6 +153,30 @@
/// null to default to weight class
var/weight_volume
//* Storage - Belts *//
/// Belt storage class
/// * IT IS NOT SAFE TO MODIFY THIS WITHOUT USING GETTER / SETTER PATTERN
/// TODO: getter / setter
var/belt_storage_class = BELT_CLASS_INVALID
/// Belt storage size
/// * IT IS NOT SAFE TO MODIFY THIS WITHOUT USING GETTER / SETTER PATTERN
/// TODO: getter / setter
var/belt_storage_size = BELT_SIZE_DEFAULT
//* Storage - Suit Storage *//
/// Suit storage classes
var/suit_storage_class = NONE
/// Suit storage classes to allow
var/suit_storage_class_allow = NONE
/// Suit storage classes to disallow
var/suit_storage_class_disallow = NONE
/// Suit storage override type-list
/// * only for adminbus really
VAR_PRIVATE/list/suit_storage_types_allow_override
/// Suit storage override type-list
/// * only for adminbus really
VAR_PRIVATE/list/suit_storage_types_disallow_override
//? unsorted / legacy
/// This saves our blood splatter overlay, which will be processed not to go over the edges of the sprite
var/image/blood_overlay = null
@@ -323,18 +335,8 @@
. += "It looks like it weighs a lot. You probably will have a hard time running with it."
if(40 to INFINITY)
. += "It looks like it weighs a ton. You really won't be doing much running with it."
// if(resistance_flags & INDESTRUCTIBLE)
// . += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!"
// else
// if(resistance_flags & LAVA_PROOF)
// . += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!"
// if(resistance_flags & (ACID_PROOF | UNACIDABLE))
// . += "[src] looks pretty robust! It'd probably be able to withstand acid!"
// if(resistance_flags & FREEZE_PROOF)
// . += "[src] is made of cold-resistant materials."
// if(resistance_flags & FIRE_PROOF)
// . += "[src] is made of fire-retardant materials."
if(belt_storage_class != BELT_CLASS_INVALID)
. += "It looks like it can fit into a belt, and would use [belt_storage_size] [lowertext(global.belt_class_names[belt_storage_class])]-sized loop(s)."
// if(item_flags & (ITEM_CAN_BLOCK | ITEM_CAN_PARRY))
// var/datum/block_parry_data/data = return_block_parry_datum(block_parry_data)
@@ -13,6 +13,7 @@ GLOBAL_LIST_EMPTY(PDAs)
slot_flags = SLOT_ID | SLOT_BELT
rad_flags = RAD_BLOCK_CONTENTS
item_flags = ITEM_NO_BLUDGEON
belt_storage_class = BELT_CLASS_SMALL
//Main variables
var/pdachoice = 1
+1
View File
@@ -11,6 +11,7 @@
origin_tech = list(TECH_MAGNET = 2, TECH_COMBAT = 1)
worth_intrinsic = 45
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/times_used = 0 //Number of times it's been used.
var/broken = FALSE //Is the flash burnt out?
@@ -10,6 +10,8 @@
light_wedge = LIGHT_WIDE
worth_intrinsic = 25
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_MEDIUM
belt_storage_size = BELT_SIZE_FOR_FLASHLIGHT
var/on = FALSE
/// Luminosity when on
@@ -18,6 +18,7 @@
rad_flags = RAD_NO_CONTAMINATE | RAD_BLOCK_CONTENTS
materials_base = list(MAT_STEEL = 200, MAT_GLASS = 100)
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/grace = RAD_GRACE_PERIOD
var/datum/looping_sound/geiger/soundloop
+1
View File
@@ -28,6 +28,7 @@
materials_base = list(MAT_STEEL = 500)
worth_intrinsic = 40
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
/// our GPS tag
var/gps_tag = "GEN0"
@@ -6,6 +6,7 @@
item_state = "flashtool"
item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
throw_force = 5
w_class = WEIGHT_CLASS_SMALL
throw_speed = 4
@@ -5,6 +5,7 @@
icon_state = "megaphone"
w_class = WEIGHT_CLASS_SMALL
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/spamcheck = 0
var/emagged = 0
@@ -12,6 +12,7 @@
icon_state = "multitool"
damage_force = 5.0
w_class = WEIGHT_CLASS_SMALL
belt_storage_class = BELT_CLASS_SMALL
throw_force = 5.0
throw_range = 15
throw_speed = 3
@@ -5,6 +5,7 @@
icon_state = "beacon"
item_state = "beacon"
base_icon_state = "beacon"
belt_storage_class = BELT_CLASS_SMALL
var/code = "electronic"
var/functioning = TRUE
var/identifier
@@ -8,6 +8,7 @@
subspace_transmission = 1
canhear_range = 0 // Can't hear headsets from very far away
slot_flags = SLOT_EARS
belt_storage_class = BELT_CLASS_SMALL
var/translate_binary = 0
var/translate_hive = 0
@@ -11,6 +11,7 @@
materials_base = list(MAT_STEEL = 150)
origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/scan_range = 1
@@ -11,6 +11,7 @@
throw_range = 20
materials_base = list(MAT_STEEL = 60, MAT_GLASS = 30)
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR
belt_storage_class = BELT_CLASS_SMALL
/// inserted tape
var/obj/item/cassette_tape/tape = /obj/item/cassette_tape/random
@@ -7,6 +7,7 @@
w_class = WEIGHT_CLASS_TINY
slot_flags = SLOT_EARS
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/use_message = "Halt! Security!"
var/spamcheck = 0
@@ -15,6 +15,7 @@
throw_range = 20
slot_flags = SLOT_MASK|SLOT_BELT
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
// todo: legacy var
var/loadable = TRUE
@@ -9,6 +9,7 @@
atom_flags = NOCONDUCT
w_class = WEIGHT_CLASS_SMALL
slot_flags = SLOT_HOLSTER
belt_storage_class = BELT_CLASS_SMALL
throw_force = 0
throw_speed = 4
throw_range = 20
@@ -14,6 +14,7 @@
drop_sound = 'sound/items/drop/sword.ogg'
pickup_sound = 'sound/items/pickup/sword.ogg'
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/can_combine = TRUE
active_damage_force = 30
@@ -12,6 +12,7 @@
materials_base = list(MAT_STEEL = 30, MAT_GLASS = 20)
origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
/obj/item/atmos_analyzer/apidean
name = "\improper Apidean analyzer"
@@ -6,6 +6,7 @@
item_state = "healthanalyzer"
slot_flags = SLOT_BELT
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
throw_force = 3
w_class = WEIGHT_CLASS_SMALL
throw_speed = 5
@@ -4,6 +4,7 @@
icon_state = "hydro"
item_state = "analyzer"
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/datum/seed/last_seed
var/list/last_reagents
w_class = WEIGHT_CLASS_SMALL
@@ -11,6 +11,7 @@
throw_range = 20
materials_base = list(MAT_STEEL = 30, MAT_GLASS = 20)
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
var/details = 0
@@ -9,6 +9,7 @@
throw_force = 5
throw_speed = 4
throw_range = 20
belt_storage_class = BELT_CLASS_SMALL
materials_base = list(MAT_STEEL = 30, MAT_GLASS = 20)
@@ -12,6 +12,7 @@
w_class = WEIGHT_CLASS_TINY
active_weight_class = WEIGHT_CLASS_BULKY
belt_storage_class = BELT_CLASS_SMALL
throw_force = 5.0
throw_speed = 1
@@ -28,6 +28,7 @@ var/list/marker_beacon_colors = list(
w_class = WEIGHT_CLASS_SMALL
skip_legacy_icon_update = TRUE
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/picked_color = "random"
/obj/item/stack/marker_beacon/ten
@@ -10,6 +10,7 @@
throw_speed = 4
throw_range = 20
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
var/heal_brute = 0
var/heal_burn = 0
var/apply_sounds
@@ -16,7 +16,7 @@
max_single_weight_class = WEIGHT_CLASS_NORMAL
max_items = 100
max_combined_volume = 200
ui_expand_when_needed = TRUE
ui_expand_when_needed = 5
materials_base = list(
MAT_STEEL = 8000,
MAT_GLASS = 2500,
+2 -9
View File
@@ -9,15 +9,14 @@
icon_state = "backpack"
w_class = WEIGHT_CLASS_BULKY
slot_flags = SLOT_BACK
max_single_weight_class = WEIGHT_CLASS_BULKY
weight = ITEM_WEIGHT_STORAGE_BACKPACK
encumbrance = ITEM_ENCUMBRANCE_STORAGE_BACKPACK
max_combined_volume = STORAGE_VOLUME_BACKPACK
var/flippable = 0
var/side = 0 //0 = right, 1 = left
var/hide_under_tail = 1
drop_sound = 'sound/items/drop/backpack.ogg'
pickup_sound = 'sound/items/pickup/backpack.ogg'
storage_datum_path = /datum/object_system/storage/backpack
/*
* Backpack Types
@@ -28,7 +27,6 @@
desc = "A backpack that opens into a localized pocket of Blue Space."
origin_tech = list(TECH_BLUESPACE = 4)
icon_state = "holdingpack"
max_single_weight_class = WEIGHT_CLASS_BULKY
max_combined_volume = WEIGHT_VOLUME_NORMAL * 14 // 56
/obj/item/storage/backpack/holding/duffle
@@ -153,7 +151,7 @@
flat_encumbrance = ITEM_FLAT_ENCUMBRANCE_DUFFLEBAG
// todo: remove when weight system is used
slowdown = 0.25
max_combined_volume = STORAGE_VOLUME_DUFFLEBAG
storage_datum_path = /datum/object_system/storage/backpack/dufflebag
/obj/item/storage/backpack/dufflebag/syndie
name = "black dufflebag"
@@ -356,10 +354,8 @@
worn_render_flags = WORN_RENDER_SLOT_ONE_FOR_ALL
worn_bodytypes = BODYTYPE_DEFAULT
icon_override = null
max_single_weight_class = WEIGHT_CLASS_BULKY
weight = ITEM_WEIGHT_STORAGE_BACKPACK
encumbrance = ITEM_ENCUMBRANCE_STORAGE_BACKPACK
max_combined_volume = STORAGE_VOLUME_BACKPACK
drop_sound = 'sound/items/drop/backpack.ogg'
pickup_sound = 'sound/items/pickup/backpack.ogg'
@@ -596,8 +592,6 @@
item_state = "saddlebag"
icon_state = "saddlebag"
var/icon_base = "saddlebag"
encumbrance = ITEM_ENCUMBRANCE_STORAGE_DUFFLEBAG
max_combined_volume = STORAGE_VOLUME_DUFFLEBAG //Saddlebags can hold more, like dufflebags
var/no_message = "You aren't the appropriate taur type to wear this!"
hide_under_tail = -1
@@ -652,7 +646,6 @@
item_state = "taurvest"
icon_state = "taurvest"
icon_base = "taurvest"
max_combined_volume = STORAGE_VOLUME_BACKPACK
encumbrance = ITEM_ENCUMBRANCE_STORAGE_BACKPACK
/obj/item/storage/backpack/dufflebag/fluff //Black dufflebag without syndie buffs.
@@ -0,0 +1,54 @@
/obj/item/storage/bag/trash
name = "trash bag"
desc = "It's the heavy-duty black polymer kind. Time to take out the trash!"
icon = 'icons/obj/janitor.dmi'
icon_state = "trashbag"
item_state_slots = list(SLOT_ID_RIGHT_HAND = "trashbag", SLOT_ID_LEFT_HAND = "trashbag")
drop_sound = 'sound/items/drop/wrapper.ogg'
pickup_sound = 'sound/items/pickup/wrapper.ogg'
w_class = WEIGHT_CLASS_NORMAL
storage_datum_path = /datum/object_system/storage/trash_bag
max_combined_volume = WEIGHT_VOLUME_SMALL * 48
/obj/item/storage/bag/trash/initialize_storage()
. = ..()
obj_storage.update_icon_on_item_change = TRUE
// TODO: this is fucking broken, fix it.
/obj/item/storage/bag/trash/update_icon_state()
switch(w_class)
if(3)
icon_state = "[initial(icon_state)]1"
if(4)
icon_state = "[initial(icon_state)]2"
if(5 to INFINITY)
icon_state = "[initial(icon_state)]3"
else
icon_state = "[initial(icon_state)]"
return ..()
/obj/item/storage/bag/trash/throw_land(atom/A, datum/thrownthing/TT)
. = ..()
// let them shuffle the contents so they can grab other stuff with this if they really want
obj_storage?.hide()
var/list/athena_event_balancing_patch = contents.Copy()
shuffle_inplace(athena_event_balancing_patch)
contents = athena_event_balancing_patch
/obj/item/storage/bag/trash/bluespace
name = "trash bag of holding"
w_class = WEIGHT_CLASS_NORMAL
max_combined_volume = WEIGHT_VOLUME_SMALL * 48 * 3
desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage."
icon_state = "bluetrashbag"
/obj/item/storage/bag/trash/plasticbag
name = "plastic bag"
desc = "It's a very flimsy, very noisy alternative to a bag."
icon = 'icons/obj/trash.dmi'
icon_state = "plasticbag"
drop_sound = 'sound/items/drop/wrapper.ogg'
pickup_sound = 'sound/items/pickup/wrapper.ogg'
w_class = WEIGHT_CLASS_BULKY
max_combined_volume = WEIGHT_VOLUME_NORMAL * 3
+2 -67
View File
@@ -62,71 +62,6 @@
staged = max(staged, contained.get_weight_class())
set_weight_class(staged)
// -----------------------------
// Trash bag
// -----------------------------
/obj/item/storage/bag/trash
name = "trash bag"
desc = "It's the heavy-duty black polymer kind. Time to take out the trash!"
icon = 'icons/obj/janitor.dmi'
icon_state = "trashbag"
item_state_slots = list(SLOT_ID_RIGHT_HAND = "trashbag", SLOT_ID_LEFT_HAND = "trashbag")
drop_sound = 'sound/items/drop/wrapper.ogg'
pickup_sound = 'sound/items/pickup/wrapper.ogg'
w_class = WEIGHT_CLASS_SMALL
max_single_weight_class = WEIGHT_CLASS_NORMAL
max_combined_volume = WEIGHT_CLASS_SMALL * 21
insertion_whitelist = list() // any
insertion_blacklist = list(/obj/item/disk/nuclear)
/obj/item/storage/bag/trash/initialize_storage()
. = ..()
obj_storage.update_icon_on_item_change = TRUE
/obj/item/storage/bag/trash/update_icon_state()
switch(w_class)
if(3)
icon_state = "[initial(icon_state)]1"
if(4)
icon_state = "[initial(icon_state)]2"
if(5 to INFINITY)
icon_state = "[initial(icon_state)]3"
else
icon_state = "[initial(icon_state)]"
return ..()
/obj/item/storage/bag/trash/bluespace
name = "trash bag of holding"
max_single_weight_class = WEIGHT_CLASS_HUGE
max_combined_volume = WEIGHT_CLASS_SMALL * 56
desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage."
icon_state = "bluetrashbag"
/obj/item/storage/bag/trash/bluespace/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W, /obj/item/storage/backpack/holding) || istype(W, /obj/item/storage/bag/trash/bluespace))
to_chat(user, "<span class='warning'>The Bluespace interfaces of the two devices conflict and malfunction.</span>")
qdel(W)
return 1
return ..()
// -----------------------------
// Plastic Bag
// -----------------------------
/obj/item/storage/bag/plasticbag
name = "plastic bag"
desc = "It's a very flimsy, very noisy alternative to a bag."
icon = 'icons/obj/trash.dmi'
icon_state = "plasticbag"
drop_sound = 'sound/items/drop/wrapper.ogg'
pickup_sound = 'sound/items/pickup/wrapper.ogg'
w_class = WEIGHT_CLASS_BULKY
max_single_weight_class = WEIGHT_CLASS_SMALL
insertion_whitelist = list() // any
insertion_blacklist = list(/obj/item/disk/nuclear)
// -----------------------------
// Mining Satchel
// -----------------------------
@@ -147,7 +82,7 @@
max_items = 300
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
storage_datum_path = /datum/object_system/storage/stack
ui_expand_when_needed = TRUE
ui_expand_when_needed = 5
insertion_whitelist = list(/obj/item/stack/ore)
auto_fit_weight_class_to_largest_contained = FALSE
@@ -251,7 +186,7 @@
allow_mass_gather = TRUE
allow_mass_gather_mode_switch = FALSE
auto_fit_weight_class_to_largest_contained = FALSE
ui_expand_when_needed = TRUE
ui_expand_when_needed = 5
// -----------------------------
// Sheet Snatcher (Cyborg)
+30 -500
View File
@@ -3,18 +3,33 @@
desc = "Can hold various things."
icon = 'icons/obj/clothing/belts.dmi'
icon_state = "utility"
max_items = 7
max_combined_volume = WEIGHT_VOLUME_NORMAL * 7 //This should ensure belts always have enough room to store whatever.
max_single_weight_class = WEIGHT_CLASS_NORMAL
ui_force_slot_mode = TRUE
slot_flags = SLOT_BELT
attack_verb = list("whipped", "lashed", "disciplined")
equip_sound = 'sound/items/toolbelt_equip.ogg'
drop_sound = 'sound/items/drop/toolbelt.ogg'
pickup_sound = 'sound/items/pickup/toolbelt.ogg'
worth_intrinsic = 50
storage_datum_path = /datum/object_system/storage/belt
max_single_weight_class = WEIGHT_CLASS_NORMAL
var/set_max_combined_belt_small
var/set_max_combined_belt_medium
var/set_max_combined_belt_large
var/show_above_suit = 0
/obj/item/storage/belt/Initialize(mapload, empty)
. = ..()
if(istype(obj_storage, /datum/object_system/storage/belt))
var/datum/object_system/storage/belt/casted_obj_storage = obj_storage
if(set_max_combined_belt_large != null)
casted_obj_storage.max_combined_belt_large = set_max_combined_belt_large
if(set_max_combined_belt_medium != null)
casted_obj_storage.max_combined_belt_medium = set_max_combined_belt_medium
if(set_max_combined_belt_small != null)
casted_obj_storage.max_combined_belt_small = set_max_combined_belt_small
/obj/item/storage/belt/verb/toggle_layer()
set name = "Switch Belt Layer"
set category = VERB_CATEGORY_OBJECT
@@ -45,148 +60,10 @@
var/mob/M = src.loc
M.update_inv_belt()
/obj/item/storage/belt/utility
name = "tool-belt" //Carn: utility belt is nicer, but it bamboozles the text parsing.
desc = "Can hold various tools."
icon_state = "utility"
item_state = "utility"
insertion_whitelist = list(
///obj/item/combitool,
/obj/item/tool/crowbar,
/obj/item/tool/screwdriver,
/obj/item/weldingtool,
/obj/item/tool/wirecutters,
/obj/item/tool/wrench,
/obj/item/multitool,
/obj/item/flashlight,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/stack/cable_coil,
/obj/item/t_scanner,
/obj/item/atmos_analyzer,
/obj/item/clothing/glasses,
/obj/item/clothing/gloves,
/obj/item/pda,
/obj/item/megaphone,
/obj/item/barrier_tape_roll,
/obj/item/radio/headset,
/obj/item/robotanalyzer,
/obj/item/material/minihoe,
/obj/item/material/knife/machete/hatchet,
/obj/item/plant_analyzer,
/obj/item/extinguisher/mini,
/obj/item/duct_tape_roll,
/obj/item/switchtool,
/obj/item/integrated_electronics/wirer,
/obj/item/integrated_electronics/debugger,
)
/obj/item/storage/belt/utility/full
starts_with = list(
/obj/item/tool/screwdriver,
/obj/item/tool/wrench,
/obj/item/weldingtool,
/obj/item/tool/crowbar,
/obj/item/tool/wirecutters,
/obj/item/stack/cable_coil/random_belt
)
/obj/item/storage/belt/utility/atmostech
starts_with = list(
/obj/item/tool/screwdriver,
/obj/item/tool/wrench,
/obj/item/weldingtool,
/obj/item/tool/crowbar,
/obj/item/tool/wirecutters,
)
/obj/item/storage/belt/utility/chief
name = "chief engineer's toolbelt"
desc = "Holds tools, looks snazzy."
icon_state = "utilitybelt_ce"
item_state = "utility_ce"
/obj/item/storage/belt/utility/chief/full
starts_with = list(
/obj/item/tool/screwdriver/power,
/obj/item/tool/crowbar/power,
/obj/item/weldingtool/experimental,
/obj/item/multitool,
/obj/item/stack/cable_coil/random_belt,
/obj/item/extinguisher/mini,
/obj/item/atmos_analyzer/longrange
)
/obj/item/storage/belt/medical
name = "medical belt"
desc = "Can hold various medical equipment."
icon_state = "medical"
insertion_whitelist = list(
/obj/item/healthanalyzer,
/obj/item/dnainjector,
/obj/item/reagent_containers/dropper,
/obj/item/reagent_containers/glass/beaker,
/obj/item/reagent_containers/glass/bottle,
/obj/item/reagent_containers/pill,
/obj/item/reagent_containers/syringe,
/obj/item/storage/quickdraw/syringe_case,
/obj/item/flame/lighter/zippo,
/obj/item/storage/fancy/cigarettes,
/obj/item/storage/pill_bottle,
/obj/item/stack/medical,
/obj/item/radio/headset,
/obj/item/pda,
/obj/item/barrier_tape_roll,
/obj/item/megaphone,
/obj/item/clothing/mask/surgical,
/obj/item/clothing/head/surgery,
/obj/item/clothing/gloves,
/obj/item/reagent_containers/hypospray,
/obj/item/hypospray,
/obj/item/reagent_containers/glass/hypovial,
/obj/item/clothing/glasses,
/obj/item/tool/crowbar,
/obj/item/flashlight,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/extinguisher/mini,
/obj/item/switchtool/surgery,
/obj/item/storage/quickdraw/syringe_case
)
/obj/item/storage/belt/medical/emt
name = "EMT utility belt"
@@ -199,60 +76,6 @@
icon_state = "security"
max_items = 7
max_single_weight_class = WEIGHT_CLASS_NORMAL
insertion_whitelist = list(
/obj/item/tape_recorder,
/obj/item/barrier_tape_roll,
/obj/item/clothing/glasses,
/obj/item/flashlight,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/reagent_containers/spray/luminol,
/obj/item/sample,
/obj/item/forensics/sample_kit/powder,
/obj/item/forensics/swab,
/obj/item/uv_light,
/obj/item/forensics/sample_kit,
/obj/item/photo,
/obj/item/camera_film,
/obj/item/camera,
/obj/item/autopsy_scanner,
/obj/item/mass_spectrometer,
/obj/item/clothing/accessory/badge,
/obj/item/reagent_scanner,
/obj/item/reagent_containers/dropper,
/obj/item/reagent_containers/syringe,
/obj/item/pda,
/obj/item/hailer,
/obj/item/megaphone,
/obj/item/radio/headset,
/obj/item/clothing/gloves,
/obj/item/barrier_tape_roll,
/obj/item/reagent_containers/spray/pepper,
/obj/item/handcuffs,
/obj/item/flash,
/obj/item/flame/lighter,
/obj/item/reagent_containers/food/snacks/donut/,
/obj/item/ammo_magazine,
/obj/item/gun/projectile/ballistic/colt/detective,
/obj/item/holowarrant
)
/obj/item/storage/belt/explorer
name = "pathfinder's bandolier"
@@ -261,102 +84,16 @@
max_items = 7
max_combined_volume = WEIGHT_VOLUME_NORMAL * 7
show_above_suit = 1
insertion_whitelist = list(
/obj/item/grenade,
/obj/item/tool/crowbar,
/obj/item/tool/screwdriver,
/obj/item/weldingtool,
/obj/item/tool/wirecutters,
/obj/item/tool/wrench,
/obj/item/pickaxe/,
/obj/item/multitool,
/obj/item/stack/cable_coil,
/obj/item/t_scanner,
/obj/item/atmos_analyzer,
/obj/item/flashlight,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/material/butterfly,
/obj/item/material/knife,
/obj/item/melee/transforming/energy/sword,
/obj/item/shield/transforming/energy,
/obj/item/ammo_casing/,
/obj/item/ammo_magazine/,
/obj/item/storage/box/beanbags,
/obj/item/storage/box/shotgunammo,
/obj/item/storage/box/shotgunshells,
/obj/item/healthanalyzer,
/obj/item/robotanalyzer,
/obj/item/reagent_containers/glass/beaker,
/obj/item/reagent_containers/glass/bottle,
/obj/item/reagent_containers/syringe,
/obj/item/reagent_containers/hypospray,
/obj/item/hypospray,
/obj/item/storage/pill_bottle,
/obj/item/stack/medical,
/obj/item/stack/marker_beacon,
/obj/item/flashlight,
/obj/item/extinguisher/mini,
/obj/item/storage/quickdraw/syringe_case,
/obj/item/photo,
/obj/item/camera_film,
/obj/item/camera,
/obj/item/tape_recorder,
/obj/item/barrier_tape_roll,
/obj/item/healthanalyzer,
/obj/item/geiger_counter,
/obj/item/gps,
/obj/item/switchtool,
/obj/item/ano_scanner
)
/obj/item/storage/belt/soulstone
name = "soul stone belt"
desc = "Designed for ease of access to the shards during a fight, as to not let a single enemy spirit slip away"
icon_state = "soulstone"
max_items = 6
insertion_whitelist = list(
/obj/item/soulstone
)
/obj/item/storage/belt/soulstone/full
starts_with = list(/obj/item/soulstone = 6)
/obj/item/storage/belt/utility/alien
name = "alien belt"
desc = "A belt(?) that can hold things."
icon = 'icons/obj/abductor.dmi'
icon_state = "belt"
item_state = "security"
/obj/item/storage/belt/utility/alien/full
starts_with = list(
/obj/item/tool/screwdriver/alien,
/obj/item/tool/wrench/alien,
/obj/item/weldingtool/alien,
/obj/item/tool/crowbar/alien,
/obj/item/tool/wirecutters/alien,
/obj/item/multitool/alien,
/obj/item/stack/cable_coil/alien
)
/obj/item/storage/belt/medical/alien
name = "alien belt"
desc = "A belt(?) that can hold things."
@@ -364,52 +101,6 @@
icon_state = "belt"
item_state = "security"
max_items = 8
insertion_whitelist = list(
/obj/item/healthanalyzer,
/obj/item/dnainjector,
/obj/item/reagent_containers/dropper,
/obj/item/reagent_containers/glass/beaker,
/obj/item/reagent_containers/glass/bottle,
/obj/item/reagent_containers/pill,
/obj/item/reagent_containers/syringe,
/obj/item/flame/lighter/zippo,
/obj/item/storage/fancy/cigarettes,
/obj/item/storage/pill_bottle,
/obj/item/stack/medical,
/obj/item/radio/headset,
/obj/item/pda,
/obj/item/barrier_tape_roll,
/obj/item/megaphone,
/obj/item/clothing/mask/surgical,
/obj/item/clothing/head/surgery,
/obj/item/clothing/gloves,
/obj/item/reagent_containers/hypospray,
/obj/item/hypospray,
/obj/item/clothing/glasses,
/obj/item/tool/crowbar,
/obj/item/flashlight,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/extinguisher/mini,
/obj/item/surgical
)
/obj/item/storage/belt/medical/alien
starts_with = list(
@@ -428,22 +119,6 @@
desc = "Proves to the world that you are the strongest!"
icon_state = "champion"
max_items = 1
insertion_whitelist = list(
"/obj/item/clothing/mask/luchador"
)
/obj/item/storage/belt/security/tactical
name = "combat belt"
desc = "Can hold security gear like handcuffs and flashes, with more pouches for more storage."
icon_state = "swat"
max_items = 9
max_single_weight_class = WEIGHT_CLASS_NORMAL
max_combined_volume = WEIGHT_VOLUME_NORMAL * 7
/obj/item/storage/belt/security/tactical/bandolier
name = "combat belt"
desc = "Can hold security gear like handcuffs and flashes, with more pouches for more storage."
icon_state = "bandolier"
/obj/item/storage/belt/spike_bandolier
name = "spike bandolier"
@@ -462,95 +137,12 @@
max_items = 7
max_single_weight_class = WEIGHT_CLASS_NORMAL
worth_intrinsic = 35
insertion_whitelist = list(
/obj/item/clothing/glasses,
/obj/item/flashlight,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/grenade,
/obj/item/pda,
/obj/item/radio/headset,
/obj/item/clothing/gloves,
/obj/item/clothing/mask/surgical, //sterile mask,
/obj/item/assembly/mousetrap,
/obj/item/light/bulb,
/obj/item/light/tube,
/obj/item/flame/lighter,
/obj/item/megaphone,
/obj/item/barrier_tape_roll,
/obj/item/reagent_containers/spray,
/obj/item/soap
)
/obj/item/storage/belt/archaeology
name = "excavation gear-belt"
desc = "Can hold various excavation gear."
icon_state = "gear"
worth_intrinsic = 65
insertion_whitelist = list(
/obj/item/storage/box/samplebags,
/obj/item/core_sampler,
/obj/item/beacon_locator,
/obj/item/radio/beacon,
/obj/item/gps,
/obj/item/measuring_tape,
/obj/item/flashlight,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/pickaxe,
/obj/item/depth_scanner,
/obj/item/camera,
/obj/item/paper,
/obj/item/photo,
/obj/item/folder,
/obj/item/pen,
/obj/item/folder,
/obj/item/clipboard,
/obj/item/anodevice,
/obj/item/clothing/glasses,
/obj/item/tool/wrench,
/obj/item/storage/excavation,
/obj/item/anobattery,
/obj/item/ano_scanner,
/obj/item/pickaxe/hand,
/obj/item/hand_labeler,
/obj/item/xenoarch_multi_tool,
/obj/item/pickaxe/excavationdrill
)
/obj/item/storage/belt/fannypack
name = "leather fannypack"
@@ -611,13 +203,14 @@
desc = "An ornate sheath designed to hold an officer's blade."
icon_state = "sheath-sabre"
max_items = 1
storage_datum_path = /datum/object_system/storage
insertion_whitelist = list(
/obj/item/material/sword/sabre,
/obj/item/melee/baton/stunsword,
)
)
starts_with = list(
/obj/item/material/sword/sabre,
)
)
/obj/item/storage/belt/sheath/initialize_storage()
. = ..()
@@ -644,93 +237,30 @@
name = "dual holster gunbelt"
desc = "Belts like these were popular on old Earth, but were largely supplanted by modular holsters. This gunbelt is too bulky to be comfortably anchored to clothes without support."
icon_state = "dual_holster"
max_items = 2
max_single_weight_class = WEIGHT_CLASS_NORMAL
insertion_whitelist = list(
/obj/item/gun/projectile/energy/alien,
/obj/item/gun/projectile/energy/captain,
/obj/item/gun/projectile/energy/crossbow,
/obj/item/gun/projectile/energy/decloner,
/obj/item/gun/projectile/energy/floragun,
/obj/item/gun/projectile/energy/gun,
/obj/item/gun/projectile/energy/gun/nuclear,
/obj/item/gun/projectile/energy/ionrifle/pistol,
/obj/item/gun/projectile/energy/lasertag,
/obj/item/gun/projectile/energy/netgun,
/obj/item/gun/projectile/energy/phasegun/pistol,
/obj/item/gun/projectile/energy/nt_pulse/carbine,
/obj/item/gun/projectile/energy/retro,
/obj/item/gun/projectile/energy/nt_pmd/service_revolver,
/obj/item/gun/projectile/energy/stunrevolver,
/obj/item/gun/projectile/energy/taser,
/obj/item/gun/projectile/energy/toxgun,
/obj/item/gun/projectile/energy/zip,
/obj/item/gun/projectile/ballistic/colt,
/obj/item/gun/projectile/ballistic/contender,
/obj/item/gun/projectile/ballistic/dartgun,
/obj/item/gun/projectile/ballistic/deagle,
/obj/item/gun/projectile/ballistic/derringer,
/obj/item/gun/projectile/ballistic/gyropistol,
/obj/item/gun/projectile/ballistic/luger,
/obj/item/gun/projectile/ballistic/r9,
/obj/item/gun/projectile/ballistic/revolver,
/obj/item/gun/projectile/ballistic/sec,
/obj/item/gun/projectile/ballistic/shotgun/doublebarrel/sawn,
/obj/item/gun/projectile/ballistic/shotgun/flare,
/obj/item/gun/projectile/ballistic/silenced,
/obj/item/gun/projectile/ballistic/p92x,
/obj/item/gun/projectile/ballistic/pistol,
/obj/item/gun/projectile/ballistic/pirate
)
set_max_combined_belt_large = 2
/obj/item/storage/belt/quiver
name = "leather quiver"
desc = "A quiver made from the hide of some animal. Used to hold arrows."
icon_state = "quiver"
max_items = 15
max_single_weight_class = WEIGHT_CLASS_NORMAL
set_max_combined_belt_small = 30
insertion_whitelist = list(
/obj/item/ammo_casing/arrow
)
/obj/item/ammo_casing/arrow,
)
/obj/item/storage/belt/quiver/full
name = "leather quiver"
desc = "A quiver made from the hide of some animal. Used to hold arrows."
icon_state = "quiver"
max_items = 15
max_single_weight_class = WEIGHT_CLASS_NORMAL
insertion_whitelist = list(
/obj/item/ammo_casing/arrow
)
starts_with = list(
/obj/item/ammo_casing/arrow = 15
)
/obj/item/ammo_casing/arrow = /obj/item/storage/belt/quiver::set_max_combined_belt_small,
)
/obj/item/storage/belt/quiver/full/ash
name = "leather quiver"
desc = "A quiver made from the hide of some animal. Used to hold arrows."
icon_state = "quiver"
max_items = 15
max_single_weight_class = WEIGHT_CLASS_NORMAL
insertion_whitelist = list(
/obj/item/ammo_casing/arrow
)
starts_with = list(
/obj/item/ammo_casing/arrow/bone = 15
)
/obj/item/storage/belt/utility/crystal
name = "crystalline tool harness"
desc = "A segmented belt of strange crystalline material."
icon_state = "utilitybelt_crystal"
item_state = "utilitybelt_crystal"
/obj/item/storage/belt/utility/crystal/Initialize()
new /obj/item/multitool/crystal(src)
new /obj/item/tool/wrench/crystal(src)
new /obj/item/tool/crowbar/crystal(src)
new /obj/item/tool/screwdriver/crystal(src)
new /obj/item/tool/wirecutters/crystal(src)
new /obj/item/weldingtool/electric/crystal(src)
update_icon()
. = ..()
/obj/item/ammo_casing/arrow/bone = /obj/item/storage/belt/quiver::set_max_combined_belt_small,
)
@@ -5,63 +5,26 @@
desc = "Standard issue belt capable of storing many kinds of tactical gear."
icon_state = "security"
max_single_weight_class = WEIGHT_CLASS_NORMAL
insertion_whitelist = list(
/obj/item/grenade,
/obj/item/reagent_containers/spray/pepper,
/obj/item/handcuffs,
/obj/item/flash,
/obj/item/clothing/glasses,
/obj/item/ammo_casing/a10g,
/obj/item/ammo_casing/a12g,
/obj/item/ammo_magazine,
// TODO: PLEASE FOR THE LOVE OF GOD REFACTOR FUCKING BELTS ALREADY
/obj/item/cell/basic/tier_1/weapon,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_3/weapon,
/obj/item/cell/basic/tier_4/weapon,
/obj/item/cell/basic/tier_5/weapon,
/obj/item/cell/regen/weapon,
/obj/item/cell/regen/fractal/weapon,
/obj/item/cell/infinite/weapon,
/obj/item/cell/microfission/weapon,
/obj/item/cell/basic/tier_1/small,
/obj/item/cell/basic/tier_2/small,
/obj/item/cell/basic/tier_3/small,
/obj/item/cell/basic/tier_4/small,
/obj/item/cell/basic/tier_5/small,
/obj/item/cell/regen/small,
/obj/item/cell/regen/fractal/small,
/obj/item/cell/infinite/small,
/obj/item/cell/microfission/small,
/obj/item/melee/baton,
/obj/item/gun/projectile/energy/taser,
/obj/item/gun/projectile/energy/stunrevolver,
/obj/item/gun/projectile/energy/gun,
/obj/item/flame/lighter,
/obj/item/flashlight,
/obj/item/tape_recorder,
/obj/item/barrier_tape_roll,
/obj/item/pda,
/obj/item/radio/headset,
/obj/item/clothing/gloves,
/obj/item/hailer,
/obj/item/megaphone,
/obj/item/melee,
/obj/item/clothing/accessory/badge,
/obj/item/gun/projectile/ballistic/sec,
/obj/item/gun/projectile/ballistic/p92x,
/obj/item/barrier_tape_roll,
/obj/item/gun/projectile/ballistic/colt/detective,
/obj/item/holowarrant,
/obj/item/gun/projectile/energy/nt_isd/sidearm,
)
set_max_combined_belt_large = /datum/object_system/storage/belt::max_combined_belt_large + 1
set_max_combined_belt_medium = /datum/object_system/storage/belt::max_combined_belt_medium
/obj/item/storage/belt/security/nt_isd_preload
starts_with = list(
/obj/item/handcuffs,
/obj/item/flash,
/obj/item/gun/projectile/energy/nt_isd/sidearm/with_light,
/obj/item/melee/baton,
/obj/item/melee/baton/loaded,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/reagent_containers/spray/pepper,
)
/obj/item/storage/belt/security/tactical
name = "combat belt"
desc = "Can hold security gear like handcuffs and flashes, with more pouches for more storage."
icon_state = "swat"
set_max_combined_belt_medium = /obj/item/storage/belt/security::set_max_combined_belt_medium + 2
/obj/item/storage/belt/security/tactical/bandolier
name = "combat belt"
desc = "Can hold security gear like handcuffs and flashes, with more pouches for more storage."
icon_state = "bandolier"
@@ -0,0 +1,76 @@
/obj/item/storage/belt/utility
name = "tool-belt" //Carn: utility belt is nicer, but it bamboozles the text parsing.
desc = "Can hold various tools."
icon_state = "utility"
item_state = "utility"
/obj/item/storage/belt/utility/full
starts_with = list(
/obj/item/tool/screwdriver,
/obj/item/tool/wrench,
/obj/item/weldingtool,
/obj/item/tool/crowbar,
/obj/item/tool/wirecutters,
/obj/item/stack/cable_coil/random_belt
)
/obj/item/storage/belt/utility/atmostech
starts_with = list(
/obj/item/tool/screwdriver,
/obj/item/tool/wrench,
/obj/item/weldingtool,
/obj/item/tool/crowbar,
/obj/item/tool/wirecutters,
)
/obj/item/storage/belt/utility/chief
name = "chief engineer's toolbelt"
desc = "Holds tools, looks snazzy."
icon_state = "utilitybelt_ce"
item_state = "utility_ce"
/obj/item/storage/belt/utility/chief/full
starts_with = list(
/obj/item/tool/screwdriver/power,
/obj/item/tool/crowbar/power,
/obj/item/weldingtool/experimental,
/obj/item/multitool,
/obj/item/stack/cable_coil/random_belt,
/obj/item/extinguisher/mini,
/obj/item/atmos_analyzer/longrange
)
/obj/item/storage/belt/utility/alien
name = "alien belt"
desc = "A belt(?) that can hold things."
icon = 'icons/obj/abductor.dmi'
icon_state = "belt"
item_state = "security"
/obj/item/storage/belt/utility/alien/full
starts_with = list(
/obj/item/tool/screwdriver/alien,
/obj/item/tool/wrench/alien,
/obj/item/weldingtool/alien,
/obj/item/tool/crowbar/alien,
/obj/item/tool/wirecutters/alien,
/obj/item/multitool/alien,
/obj/item/stack/cable_coil/alien
)
/obj/item/storage/belt/utility/crystal
name = "crystalline tool harness"
desc = "A segmented belt of strange crystalline material."
icon_state = "utilitybelt_crystal"
item_state = "utilitybelt_crystal"
/obj/item/storage/belt/utility/crystal/Initialize()
new /obj/item/multitool/crystal(src)
new /obj/item/tool/wrench/crystal(src)
new /obj/item/tool/crowbar/crystal(src)
new /obj/item/tool/screwdriver/crystal(src)
new /obj/item/tool/wirecutters/crystal(src)
new /obj/item/weldingtool/electric/crystal(src)
update_icon()
. = ..()
@@ -5,6 +5,7 @@
inhand_icon = 'icons/items/storage/firstaid.dmi'
icon_state = "normal"
slot_flags = SLOT_BELT
storage_datum_path = /datum/object_system/storage/hypokit
max_combined_volume = STORAGE_VOLUME_BOX
weight_volume = ITEM_VOLUME_BOX
insertion_whitelist = list(
@@ -7,17 +7,15 @@
pickup_sound = 'sound/items/pickup/pillbottle.ogg'
item_state_slots = list(SLOT_ID_RIGHT_HAND = "contsolid", SLOT_ID_LEFT_HAND = "contsolid")
w_class = WEIGHT_CLASS_SMALL
insertion_whitelist = list(/obj/item/reagent_containers/pill,/obj/item/dice,/obj/item/paper)
allow_quick_empty = TRUE
allow_mass_gather = TRUE
sfx_insert = null
sfx_remove = null
sfx_open = null
max_combined_volume = WEIGHT_VOLUME_TINY * 14
max_single_weight_class = WEIGHT_CLASS_TINY
materials_base = list(MAT_PLASTIC = 80)
item_flags = ITEM_CAREFUL_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD | ITEM_EASY_LATHE_DECONSTRUCT
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR
belt_storage_class = BELT_CLASS_SMALL
storage_datum_path = /datum/object_system/storage/pill_bottle
insertion_whitelist = list(/obj/item/reagent_containers/pill,/obj/item/dice,/obj/item/paper)
var/label_text = ""
var/labeled = 0
@@ -239,6 +239,7 @@
icon_state = "cigpacket"
item_state_slots = list(SLOT_ID_RIGHT_HAND = "cigpacket", SLOT_ID_LEFT_HAND = "cigpacket")
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR
belt_storage_class = BELT_CLASS_SMALL
w_class = WEIGHT_CLASS_TINY
throw_force = 2
slot_flags = SLOT_BELT | SLOT_EARS
@@ -67,6 +67,7 @@
w_class = WEIGHT_CLASS_SMALL
max_single_weight_class = WEIGHT_CLASS_TINY
max_combined_volume = WEIGHT_CLASS_TINY * 6 //Capable of holding six syringes
belt_storage_class = BELT_CLASS_SMALL
//Can hold syringes and autoinjectors, but also pills if you really wanted. Syringe-shaped objects like pens and cigarettes also fit, but why would you do that?
insertion_whitelist = list(/obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray/autoinjector,
+48 -27
View File
@@ -20,29 +20,32 @@
var/list/insertion_blacklist
var/list/insertion_allow
var/max_single_weight_class = WEIGHT_CLASS_SMALL
var/max_single_weight_class
var/max_combined_weight_class
var/max_combined_volume = WEIGHT_VOLUME_SMALL * 4
var/max_combined_volume
var/max_items
var/weight_propagation
// always set on base type for :: purposes
var/weight_subtract = 0
// always set on base type for :: purposes
var/weight_multiply = 1
var/allow_mass_gather = FALSE
var/allow_mass_gather_mode_switch = TRUE
var/mass_gather_mode = STORAGE_QUICK_GATHER_COLLECT_ALL
var/allow_mass_gather
var/allow_mass_gather_mode_switch
var/mass_gather_mode
var/allow_quick_empty = TRUE
var/allow_quick_empty_via_clickdrag = TRUE
var/allow_quick_empty_via_attack_self = FALSE
var/allow_quick_empty
var/allow_quick_empty_via_clickdrag
var/allow_quick_empty_via_attack_self
var/sfx_open = "rustle"
var/sfx_insert = "rustle"
var/sfx_remove = "rustle"
var/ui_numerical_mode = FALSE
var/ui_expand_when_needed = FALSE
var/ui_force_slot_mode = FALSE
var/ui_numerical_mode
var/ui_expand_when_needed
var/ui_force_slot_mode
//* Initialization *//
@@ -98,26 +101,44 @@
obj_storage.set_insertion_whitelist(insertion_whitelist)
obj_storage.set_insertion_blacklist(insertion_blacklist)
obj_storage.max_single_weight_class = max_single_weight_class
obj_storage.max_combined_weight_class = max_combined_weight_class
obj_storage.max_combined_volume = max_combined_volume
obj_storage.max_items = max_items
if(!isnull(max_single_weight_class))
obj_storage.max_single_weight_class = max_single_weight_class
if(!isnull(max_combined_weight_class))
obj_storage.max_combined_weight_class = max_combined_weight_class
if(!isnull(max_combined_volume))
obj_storage.max_combined_volume = max_combined_volume
if(!isnull(max_items))
obj_storage.max_items = max_items
if(!isnull(weight_propagation))
obj_storage.weight_propagation = weight_propagation
obj_storage.weight_subtract = weight_subtract
obj_storage.weight_multiply = weight_multiply
obj_storage.allow_mass_gather = allow_mass_gather
obj_storage.allow_mass_gather_mode_switch = allow_mass_gather_mode_switch
obj_storage.mass_gather_mode = mass_gather_mode
if(!isnull(allow_mass_gather))
obj_storage.allow_mass_gather = allow_mass_gather
if(!isnull(allow_mass_gather_mode_switch))
obj_storage.allow_mass_gather_mode_switch = allow_mass_gather_mode_switch
if(!isnull(mass_gather_mode))
obj_storage.mass_gather_mode = mass_gather_mode
obj_storage.allow_quick_empty = allow_quick_empty
obj_storage.allow_quick_empty_via_clickdrag = allow_quick_empty_via_clickdrag
obj_storage.allow_quick_empty_via_attack_self = allow_quick_empty_via_attack_self
if(!isnull(allow_quick_empty))
obj_storage.allow_quick_empty = allow_quick_empty
if(!isnull(allow_quick_empty_via_clickdrag))
obj_storage.allow_quick_empty_via_clickdrag = allow_quick_empty_via_clickdrag
if(!isnull(allow_quick_empty_via_attack_self))
obj_storage.allow_quick_empty_via_attack_self = allow_quick_empty_via_attack_self
obj_storage.sfx_open = sfx_open
obj_storage.sfx_insert = sfx_insert
obj_storage.sfx_remove = sfx_remove
if(!isnull(sfx_open))
obj_storage.sfx_open = sfx_open
if(!isnull(sfx_insert))
obj_storage.sfx_insert = sfx_insert
if(!isnull(sfx_remove))
obj_storage.sfx_remove = sfx_remove
obj_storage.ui_numerical_mode = ui_numerical_mode
obj_storage.ui_expand_when_needed = ui_expand_when_needed
obj_storage.ui_force_slot_mode = ui_force_slot_mode
if(!isnull(ui_numerical_mode))
obj_storage.ui_numerical_mode = ui_numerical_mode
if(!isnull(ui_expand_when_needed))
obj_storage.ui_expand_when_needed = ui_expand_when_needed
if(!isnull(ui_force_slot_mode))
obj_storage.ui_force_slot_mode = ui_force_slot_mode
@@ -5,6 +5,8 @@
icon = 'icons/obj/wallet.dmi'
icon_state = "wallet-orange"
w_class = WEIGHT_CLASS_SMALL
max_combined_volume = WEIGHT_VOLUME_SMALL * 4
max_single_weight_class = WEIGHT_CLASS_SMALL
insertion_whitelist = list(
/obj/item/spacecash,
/obj/item/card,
+1
View File
@@ -6,6 +6,7 @@
/obj/item/tool
item_flags = ITEM_CAREFUL_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
/obj/item/tool/get_description_info()
. = ..()
@@ -10,6 +10,7 @@
icon_state = "switchtool"
item_state = "switchtool"
desc = "A multi-deployable, multi-instrument, finely crafted multi-purpose tool. The envy of engineers everywhere."
belt_storage_class = BELT_CLASS_SMALL
siemens_coefficient = 1
damage_force = 3
w_class = WEIGHT_CLASS_SMALL
@@ -10,6 +10,7 @@
slot_flags = SLOT_BELT
tool_behavior = TOOL_WELDER
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_MEDIUM
//Amount of OUCH when it's thrown
damage_force = 3.0
@@ -10,6 +10,7 @@
item_state = "autopsy_scanner"
w_class = WEIGHT_CLASS_SMALL
origin_tech = list(TECH_MATERIAL = 1, TECH_BIO = 1)
belt_storage_class = BELT_CLASS_SMALL
var/list/datum/autopsy_data_scanner/wdata = list()
var/list/datum/autopsy_data_scanner/chemtraces = list()
var/target_name = null
@@ -5,6 +5,7 @@
icon_state = "tape"
w_class = WEIGHT_CLASS_SMALL
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
tool_speed = 3 //You can use it in surgery. It's stupid, but you can.
@@ -587,6 +587,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
w_class = WEIGHT_CLASS_TINY
throw_force = 4
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR
belt_storage_class = BELT_CLASS_SMALL
slot_flags = SLOT_BELT
attack_verb = list("burnt", "singed")
var/base_state
@@ -5,6 +5,7 @@
icon_state = "taperoll"
damage_force = 0
w_class = WEIGHT_CLASS_TINY
belt_storage_class = BELT_CLASS_SMALL
drop_sound = 'sound/items/drop/cardboardbox.ogg'
pickup_sound = 'sound/items/pickup/cardboardbox.ogg'
var/amount = 20
@@ -37,6 +37,7 @@
spray_particles = 3
sprite_name = "miniFE"
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR
belt_storage_class = BELT_CLASS_MEDIUM
/obj/item/extinguisher/mini/plasman
name = "emergency phoronoid extinguisher"
@@ -8,6 +8,7 @@
worn_render_flags = WORN_RENDER_SLOT_ONE_FOR_ALL
slot_flags = SLOT_BELT
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
throw_force = 5
w_class = WEIGHT_CLASS_SMALL
throw_speed = 2
@@ -8,6 +8,7 @@
attack_verb = list("patted", "tapped")
force_multiplier = 0.1
throw_force_multiplier = 1.5
belt_storage_class = BELT_CLASS_SMALL
drop_sound = 'sound/items/drop/knife.ogg'
pickup_sound = 'sound/items/pickup/knife.ogg'
var/active = FALSE
@@ -75,6 +76,7 @@
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
material_significance = MATERIAL_SIGNIFICANCE_WEAPON_LIGHT
belt_storage_class = BELT_CLASS_MEDIUM
materials_base = list(MAT_STEEL = 12000)
origin_tech = list(TECH_MATERIAL = 1)
attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
@@ -22,6 +22,7 @@
material_significance = MATERIAL_SIGNIFICANCE_WEAPON_HEAVY
damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
w_class = WEIGHT_CLASS_SMALL
belt_storage_class = BELT_CLASS_MEDIUM
origin_tech = list(TECH_MATERIAL = 2, TECH_COMBAT = 1)
attack_verb = list("chopped", "torn", "cut")
material_color = 0
@@ -80,6 +81,7 @@
w_class = WEIGHT_CLASS_SMALL
attack_verb = list("slashed", "sliced", "cut", "clawed")
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR
belt_storage_class = BELT_CLASS_SMALL
/obj/item/material/minihoe/plasteel
material_parts = /datum/prototype/material/plasteel
@@ -12,6 +12,7 @@
throw_force = 7
atom_flags = NOCONDUCT
w_class = WEIGHT_CLASS_NORMAL
belt_storage_class = BELT_CLASS_LARGE
drop_sound = 'sound/items/drop/metalweapon.ogg'
pickup_sound = 'sound/items/pickup/metalweapon.ogg'
origin_tech = list(TECH_COMBAT = 2)
@@ -15,6 +15,7 @@
w_class = WEIGHT_CLASS_SMALL
item_flags = ITEM_CAREFUL_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR
belt_storage_class = BELT_CLASS_SMALL
var/helpforce = 0 //For help intent things
drop_sound = 'sound/items/drop/weldingtool.ogg'
pickup_sound = 'sound/items/pickup/weldingtool.ogg'
@@ -65,6 +66,7 @@
w_class = WEIGHT_CLASS_NORMAL
origin_tech = list(TECH_MATERIAL = 1, TECH_BIO = 1)
attack_verb = list("drilled")
belt_storage_class = BELT_CLASS_MEDIUM
/*
* Scalpel
@@ -139,6 +141,7 @@
materials_base = list(MAT_STEEL = 20000, MAT_GLASS = 10000)
attack_verb = list("attacked", "slashed", "sawed", "cut")
damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
belt_storage_class = BELT_CLASS_MEDIUM
/obj/item/surgical/circular_saw/manager
name = "energetic bone diverter"
+2
View File
@@ -747,6 +747,8 @@
// todo: context + construction (tool) examines at some point need a better system
if(obj_rotation_flags & OBJ_ROTATION_ENABLED)
. += SPAN_NOTICE("This entity can be rotated[(obj_rotation_flags & OBJ_ROTATION_NO_ANCHOR_CHECK)? "" : " while unanchored"] via context menu (alt click while adjacent).")
if(dist <= 1)
obj_storage?.handle_storage_examine(.)
/obj/proc/examine_integrity(mob/user)
. = list()
@@ -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)
+104 -9
View File
@@ -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
+77 -354
View File
@@ -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