diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm
index fc558de63e..6f46d25eb9 100644
--- a/code/game/objects/items/weapons/storage/bags.dm
+++ b/code/game/objects/items/weapons/storage/bags.dm
@@ -73,121 +73,6 @@
can_hold = list() // any
cant_hold = list(/obj/item/disk/nuclear)
-// -----------------------------
-// Mining Satchel
-// -----------------------------
-/*
- * Mechoid - Orebags are the most common quick-gathering thing, and also have tons of lag associated with it. Their checks are going to be hyper-simplified due to this, and their INCREDIBLY singular target contents.
- */
-
-/obj/item/storage/bag/ore
- name = "mining satchel"
- desc = "This little bugger can be used to store and transport ores."
- icon = 'icons/obj/mining.dmi'
- icon_state = "satchel"
- slot_flags = SLOT_BELT | SLOT_POCKET
- w_class = ITEMSIZE_NORMAL
- max_storage_space = ITEMSIZE_COST_NORMAL * 25
- max_w_class = ITEMSIZE_NORMAL
- can_hold = list(/obj/item/ore)
- var/stored_ore = list()
- var/last_update = 0
-
-/obj/item/storage/bag/ore/remove_from_storage(obj/item/W as obj, atom/new_location)
- if(!istype(W)) return 0
-
- if(new_location)
- if(ismob(loc))
- W.dropped(usr)
- if(ismob(new_location))
- W.hud_layerise()
- else
- W.reset_plane_and_layer()
- W.forceMove(new_location)
- else
- W.forceMove(get_turf(src))
-
- W.on_exit_storage(src)
- update_icon()
- return 1
-
-/obj/item/storage/bag/ore/gather_all(turf/T as turf, mob/user as mob, var/silent = 0)
- var/success = 0
- var/failure = 0
- for(var/obj/item/ore/I in T) //Only ever grabs ores. Doesn't do any extraneous checks, as all ore is the same size. Tons of checks means it causes hanging for up to three seconds.
- if(contents.len >= max_storage_space)
- failure = 1
- break
- I.forceMove(src)
- success = 1
- if(success && !failure && !silent)
- to_chat(user, "You put everything in [src].")
- else if(success && (!silent || (silent && contents.len >= max_storage_space)))
- to_chat(user, "You fill the [src].")
- else if(!silent)
- to_chat(user, "You fail to pick anything up with \the [src].")
- if(istype(user.pulling, /obj/structure/ore_box)) //Bit of a crappy way to do this, as it doubles spam for the user, but it works.
- var/obj/structure/ore_box/O = user.pulling
- O.attackby(src, user)
-
-/obj/item/storage/bag/ore/equipped(mob/user)
- ..()
- if(user.get_inventory_slot(src) == slot_wear_suit || slot_l_hand || slot_l_hand || slot_belt) //Basically every place they can go. Makes sure it doesn't unregister if moved to other slots.
- GLOB.moved_event.register(user, src, /obj/item/storage/bag/ore/proc/autoload, user)
-
-/obj/item/storage/bag/ore/dropped(mob/user)
- ..()
- if(user.get_inventory_slot(src) == slot_wear_suit || slot_l_hand || slot_l_hand || slot_belt) //See above. This should really be a define.
- GLOB.moved_event.register(user, src, /obj/item/storage/bag/ore/proc/autoload, user)
- else
- GLOB.moved_event.unregister(user, src)
-
-/obj/item/storage/bag/ore/proc/autoload(mob/user)
- var/obj/item/ore/O = locate() in get_turf(src)
- if(O)
- gather_all(get_turf(src), user)
-
-/obj/item/storage/bag/ore/proc/rangedload(atom/A, mob/user)
- var/obj/item/ore/O = locate() in get_turf(A)
- if(O)
- gather_all(get_turf(A), user)
-
-/obj/item/storage/bag/ore/examine(mob/user)
- . = ..()
-
- if(!Adjacent(user)) //Can only check the contents of ore bags if you can physically reach them.
- return .
-
- if(istype(user, /mob/living))
- add_fingerprint(user)
-
- if(!contents.len)
- . += "It is empty."
-
- else if(world.time > last_update + 10)
- update_ore_count()
- last_update = world.time
-
- . += "It holds:"
- for(var/ore in stored_ore)
- . += "- [stored_ore[ore]] [ore]"
-
-/obj/item/storage/bag/ore/open(mob/user as mob) //No opening it for the weird UI of having shit-tons of ore inside it.
- if(world.time > last_update + 10)
- update_ore_count()
- last_update = world.time
- user.examinate(src)
-
-/obj/item/storage/bag/ore/proc/update_ore_count() //Stolen from ore boxes.
-
- stored_ore = list()
-
- for(var/obj/item/ore/O in contents)
- if(stored_ore[O.name])
- stored_ore[O.name]++
- else
- stored_ore[O.name] = 1
-
// -----------------------------
// Plant bag
// -----------------------------
diff --git a/code/game/objects/items/weapons/storage/bags/ore.dm b/code/game/objects/items/weapons/storage/bags/ore.dm
new file mode 100644
index 0000000000..af57b00941
--- /dev/null
+++ b/code/game/objects/items/weapons/storage/bags/ore.dm
@@ -0,0 +1,115 @@
+/obj/item/storage/bag/ore
+ name = "mining satchel"
+ desc = "A handy-dandy container for your rock collection. Gneiss."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "satchel"
+ slot_flags = SLOT_BELT | SLOT_POCKET
+ w_class = ITEMSIZE_NORMAL
+ max_storage_space = ITEMSIZE_COST_NORMAL * 25
+ max_w_class = ITEMSIZE_NORMAL
+ can_hold = list(/obj/item/ore)
+
+ /// Rebuild stored_ore if true. Becomes true when contents changes.
+ var/stored_ore_dirty
+
+ /// The current ore contents of the bag formatted by english_list.
+ var/stored_ore
+
+
+/obj/item/storage/bag/ore/examine(mob/user)
+ . = ..()
+ if (!Adjacent(user) && !isobserver(user))
+ return
+ if (isliving(user))
+ add_fingerprint(user)
+ if (stored_ore_dirty)
+ stored_ore_dirty = FALSE
+ stored_ore = null
+ var/list/ores = list()
+ for (var/obj/item/ore/ore in contents)
+ ++ores[ore.name]
+ var/list/chunks = list()
+ for (var/name in ores)
+ chunks += "[ores[name]] [name]"
+ if (length(chunks))
+ var/full = length(contents) >= max_storage_space
+ stored_ore = "[full ? "It is full! " : ""]It contains [english_list(chunks)]"
+ . += SPAN_ITALIC(stored_ore || "It is empty.")
+
+
+/obj/item/storage/bag/ore/equipped(mob/living/user, into_slot)
+ ..()
+ switch (into_slot)
+ if (slot_wear_suit, slot_l_hand, slot_r_hand, slot_belt)
+ GLOB.moved_event.register(user, src, /obj/item/storage/bag/ore/proc/autoload, user)
+
+
+/obj/item/storage/bag/ore/dropped(mob/living/user)
+ ..()
+ switch (user.get_inventory_slot(src))
+ if (slot_wear_suit, slot_l_hand, slot_r_hand, slot_belt)
+ . = . //noop
+ else
+ GLOB.moved_event.unregister(user, src)
+
+
+/obj/item/storage/bag/ore/remove_from_storage(obj/item/item, atom/into)
+ if (!istype(item))
+ return FALSE
+ if (isloc(into))
+ if (ismob(loc))
+ item.dropped(usr)
+ if (ismob(into))
+ item.hud_layerise()
+ else
+ item.reset_plane_and_layer()
+ else
+ item.forceMove(get_turf(src))
+ item.on_exit_storage(src)
+ stored_ore_dirty = TRUE
+ update_icon()
+ return TRUE
+
+
+/obj/item/storage/bag/ore/gather_all(turf/from, mob/living/user, silent, autoload)
+ var/obj/structure/ore_box/box = user.pulling
+ if (istype(box))
+ var/gathered = length(contents)
+ if (gathered)
+ box.contents += contents
+ stored_ore_dirty = TRUE
+ for (var/obj/item/ore/ore in from)
+ box.contents += ore
+ ++gathered
+ if (gathered)
+ box.stored_ore_dirty = TRUE
+ if (!silent)
+ to_chat(user, SPAN_ITALIC("You collect all the ore into \the [box]."))
+ return
+ if (length(contents) >= max_storage_space)
+ if (!autoload && !silent)
+ to_chat(user, SPAN_WARNING("\The [src] is full."))
+ return
+ var/gathered = 0
+ var/available_space = max_storage_space - length(contents)
+ for (var/obj/item/ore/ore in from)
+ contents += ore
+ if (--available_space < 1)
+ if (!silent)
+ to_chat(user, SPAN_ITALIC("You completely fill \the [src] with ore."))
+ return
+ ++gathered
+ if (gathered)
+ stored_ore_dirty = TRUE
+ if (!silent)
+ to_chat(user, SPAN_ITALIC("You collect all the ore with \the [src]."))
+ else if (!autoload && !silent)
+ to_chat(user, SPAN_WARNING("There's no ore there to collect."))
+
+
+/obj/item/storage/bag/ore/open(mob/living/user)
+ user.examinate(src)
+
+
+/obj/item/storage/bag/ore/proc/autoload(mob/living/user)
+ gather_all(get_turf(src), user, FALSE, TRUE)
diff --git a/code/modules/mining/ore_box.dm b/code/modules/mining/ore_box.dm
index e1aab40440..cf79c8f671 100644
--- a/code/modules/mining/ore_box.dm
+++ b/code/modules/mining/ore_box.dm
@@ -1,96 +1,102 @@
-/**********************Ore box**************************/
/obj/structure/ore_box
- icon = 'icons/obj/mining.dmi'
- icon_state = "orebox0"
name = "ore box"
desc = "A heavy box used for storing ore."
- density = 1
- var/last_update = 0
- var/list/stored_ore = list()
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "orebox0"
+ density = TRUE
-/obj/structure/ore_box/attackby(obj/item/W as obj, mob/user as mob)
- if (istype(W, /obj/item/ore))
- user.remove_from_mob(W)
- src.contents += W
+ /// Rebuild stored_ore if true. Becomes true when contents changes.
+ var/stored_ore_dirty
- else if (istype(W, /obj/item/storage))
- var/obj/item/storage/S = W
- if(!S.contents.len)
- return
- S.hide_from(usr)
- for(var/obj/item/ore/O in S.contents)
- S.remove_from_storage(O, src) //This will move the item to this item's contents
- to_chat(user, "You empty the satchel into the box.")
+ /// The current ore contents of the bag formatted by english_list.
+ var/stored_ore
- update_ore_count()
-
- return
-
-/obj/structure/ore_box/proc/update_ore_count()
-
- stored_ore = list()
-
- for(var/obj/item/ore/O in contents)
-
- if(stored_ore[O.name])
- stored_ore[O.name]++
- else
- stored_ore[O.name] = 1
/obj/structure/ore_box/examine(mob/user)
. = ..()
+ if (!Adjacent(user) && !isobserver(user))
+ return
+ if (isliving(user))
+ add_fingerprint(user)
+ if (stored_ore_dirty)
+ stored_ore_dirty = FALSE
+ stored_ore = null
+ var/list/ores = list()
+ for (var/obj/item/ore/ore in contents)
+ ++ores[ore.name]
+ var/list/chunks = list()
+ for (var/name in ores)
+ chunks += "[ores[name]] [name]"
+ if (length(chunks))
+ stored_ore = "It contains [english_list(chunks)]"
+ . += SPAN_ITALIC(stored_ore || "It is empty.")
- if(!Adjacent(user)) //Can only check the contents of ore boxes if you can physically reach them.
- return .
- add_fingerprint(user)
+/obj/structure/ore_box/attackby(obj/item/item, mob/living/user)
+ if (istype(item, /obj/item/ore))
+ user.remove_from_mob(item, src)
+ stored_ore_dirty = TRUE
+ return TRUE
+ var/obj/item/storage/storage = item
+ if (istype(storage))
+ . = TRUE
+ var/length = length(storage.contents)
+ if (!length)
+ to_chat(user, SPAN_WARNING("\The [storage] is empty."))
+ return
+ var/gathered
+ var/obj/item/storage/bag/ore/bag = item
+ if (istype(bag))
+ bag.stored_ore_dirty = TRUE
+ contents += bag.contents
+ gathered = TRUE
+ else
+ for (var/obj/item/ore/ore in storage)
+ storage.remove_from_storage(ore, src)
+ ++gathered
+ if (gathered)
+ to_chat(user, SPAN_ITALIC("You empty \the [storage] into \the [src]."))
+ stored_ore_dirty = TRUE
+ else
+ to_chat(user, SPAN_WARNING("\The [storage] contained no ore."))
- if(!contents.len)
- . += "It is empty."
- return .
- if(world.time > last_update + 10)
- update_ore_count()
- last_update = world.time
+/obj/structure/ore_box/ex_act(severity)
+ var/turf/turf = get_turf(src)
+ switch (severity)
+ if (1)
+ if (turf == loc)
+ turf.contents += contents
+ qdel(src)
+ if (2)
+ if (prob(50))
+ return
+ if (turf == loc)
+ turf.contents += contents
+ qdel(src)
- . += "It holds:"
- for(var/ore in stored_ore)
- . += "- [stored_ore[ore]] [ore]"
/obj/structure/ore_box/verb/empty_box()
set name = "Empty Ore Box"
set category = "Object"
set src in view(1)
-
- if(!ishuman(usr) && !isrobot(usr)) //Only living, intelligent creatures with gripping aparatti can empty ore boxes.
- to_chat(usr, "You are physically incapable of emptying the ore box.")
+ var/mob/living/user = usr
+ if (!(ishuman(user) || isrobot(user)))
+ to_chat(user, SPAN_WARNING("You're not dextrous enough to do that."))
return
-
- if(usr.stat || usr.restrained())
+ if (!Adjacent(user))
return
-
- if(!Adjacent(usr)) //You can only empty the box if you can physically reach it
- to_chat(usr, "You cannot reach the ore box.")
+ if (user.stat || user.restrained())
+ to_chat(user, SPAN_WARNING("You're in no condition to do that."))
return
-
- add_fingerprint(usr)
-
- if(contents.len < 1)
- to_chat(usr, "The ore box is empty.")
+ add_fingerprint(user)
+ if (!length(contents))
+ to_chat(user, SPAN_WARNING("\The [src] is empty."))
return
-
- for (var/obj/item/ore/O in contents)
- contents -= O
- O.loc = src.loc
- to_chat(usr, "You empty the ore box.")
-
- return
-
-/obj/structure/ore_box/ex_act(severity)
- if(severity == 1.0 || (severity < 3.0 && prob(50)))
- for (var/obj/item/ore/O in contents)
- O.loc = src.loc
- O.ex_act(severity++)
- qdel(src)
- return
\ No newline at end of file
+ user.visible_message(
+ SPAN_ITALIC("\The [user] empties \a [src]."),
+ SPAN_ITALIC("You empty \the [src]."),
+ range = 5
+ )
+ loc.contents += contents
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/drones/mining_drone.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/drones/mining_drone.dm
index 1c8270f808..f0f5647ce0 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/drones/mining_drone.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/drones/mining_drone.dm
@@ -106,26 +106,48 @@
return has_tool
/mob/living/simple_mob/mechanical/mining_drone/handle_special()
- if(my_storage && (get_AI_stance() in list(STANCE_APPROACH, STANCE_IDLE, STANCE_FOLLOW)) && !is_AI_busy() && isturf(loc) && (world.time > last_search + search_cooldown) && (my_storage.contents.len < my_storage.max_storage_space))
- last_search = world.time
+ if (!isturf(loc))
+ return
+ if (!my_storage)
+ return
+ if (ai_holder?.busy)
+ return
+ if (world.time < last_search + search_cooldown)
+ return
+ switch (get_AI_stance())
+ if (STANCE_APPROACH, STANCE_IDLE, STANCE_FOLLOW)
+ . = . //noop
+ else
+ return
+ var/max_space = my_storage.max_storage_space
+ var/obj/structure/ore_box/box = locate() in view(3, src)
+ if (istype(box))
+ if (length(my_storage.contents))
+ Beam(box, icon_state = "holo_beam", time = 1 SECONDS)
+ box.contents += my_storage.contents
+ my_storage.stored_ore_dirty = TRUE
+ box.stored_ore_dirty = TRUE
+ else if (length(my_storage.contents) >= max_space)
+ audible_message(SPAN_NOTICE("\The [src] emits a shrill beep; it's full!"))
+ return
+ for (var/turf/simulated/floor/floor in view(4, src))
+ if (length(my_storage.contents) >= max_space)
+ audible_message(SPAN_NOTICE("\The [src] emits a shrill beep; it's full!"))
+ return
+ if (prob(50))
+ continue
+ if (!(locate(/obj/item/ore) in floor))
+ continue
+ Beam(floor, icon_state = "holo_beam", time = 0.5 SECONDS)
+ if (box)
+ for (var/obj/item/ore/ore in floor)
+ box.contents += ore
+ box.stored_ore_dirty = TRUE
+ Beam(box, icon_state = "holo_beam", time = 0.5 SECONDS)
+ else
+ my_storage.gather_all(floor, src)
+ my_storage.stored_ore_dirty = TRUE
- for(var/turf/T in view(world.view,src))
- if(my_storage.contents.len >= my_storage.max_storage_space)
- break
-
- if((locate(/obj/item/ore) in T) && prob(40))
- src.Beam(T, icon_state = "holo_beam", time = 0.5 SECONDS)
- my_storage.rangedload(T, src)
-
- if(my_storage.contents.len >= my_storage.max_storage_space)
- visible_message("\The [src] emits a shrill beep, indicating its storage is full.")
-
- var/obj/structure/ore_box/OB = locate() in view(2, src)
-
- if(istype(OB) && my_storage && my_storage.contents.len)
- src.Beam(OB, icon_state = "rped_upgrade", time = 1 SECONDS)
- for(var/obj/item/I in my_storage)
- my_storage.remove_from_storage(I, OB)
/decl/mob_organ_names/miningdrone
hit_zones = list("chassis", "comms array", "sensor suite", "left excavator module", "right excavator module", "maneuvering thruster")
\ No newline at end of file
diff --git a/polaris.dme b/polaris.dme
index 4275a12abb..860ff32c05 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -1162,6 +1162,7 @@
#include "code\game\objects\items\weapons\storage\toolbox.dm"
#include "code\game\objects\items\weapons\storage\uplink_kits.dm"
#include "code\game\objects\items\weapons\storage\wallets.dm"
+#include "code\game\objects\items\weapons\storage\bags\ore.dm"
#include "code\game\objects\items\weapons\tanks\jetpack.dm"
#include "code\game\objects\items\weapons\tanks\tank_types.dm"
#include "code\game\objects\items\weapons\tanks\tanks.dm"