diff --git a/baystation12.dme b/baystation12.dme
index 03a30853ba3..93f012236b4 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -196,7 +196,11 @@
#include "code\datums\diseases\advance\symptoms\vomit.dm"
#include "code\datums\diseases\advance\symptoms\weight.dm"
#include "code\datums\expansions\expansion.dm"
+#include "code\datums\expansions\multitool\_multitool.dm"
+#include "code\datums\expansions\multitool\cloning.dm"
#include "code\datums\expansions\multitool\multitool.dm"
+#include "code\datums\expansions\multitool\store.dm"
+#include "code\datums\expansions\multitool\~multitool.dm"
#include "code\datums\helper_datums\construction_datum.dm"
#include "code\datums\helper_datums\events.dm"
#include "code\datums\helper_datums\getrev.dm"
diff --git a/code/_macros.dm b/code/_macros.dm
index b6ddf4f12ca..69376b6e5e1 100644
--- a/code/_macros.dm
+++ b/code/_macros.dm
@@ -23,6 +23,8 @@
#define isnewplayer(A) istype(A, /mob/new_player)
+#define isobj(A) istype(A, /obj)
+
#define isobserver(A) istype(A, /mob/dead/observer)
#define isorgan(A) istype(A, /obj/item/organ/external)
diff --git a/code/datums/expansions/expansion.dm b/code/datums/expansions/expansion.dm
index 1235467b37d..81c53b76dd1 100644
--- a/code/datums/expansions/expansion.dm
+++ b/code/datums/expansions/expansion.dm
@@ -12,3 +12,13 @@
/datum/expansion/Destroy()
holder = null
return ..()
+
+/datum/expansion/CanUseTopic(var/mob/user)
+ return holder && user && !user.incapacitated() ? STATUS_INTERACTIVE : STATUS_CLOSE
+
+/datum/expansion/Topic()
+ if(..())
+ return 1
+ if(CanUseTopic(usr) != STATUS_INTERACTIVE)
+ return 1
+ return 0
diff --git a/code/datums/expansions/multitool/_multitool.dm b/code/datums/expansions/multitool/_multitool.dm
new file mode 100644
index 00000000000..6ec2a8657e7
--- /dev/null
+++ b/code/datums/expansions/multitool/_multitool.dm
@@ -0,0 +1,3 @@
+#define MT_NOACTION 0
+#define MT_REFRESH 1
+#define MT_CLOSE 2
diff --git a/code/datums/expansions/multitool/cloning.dm b/code/datums/expansions/multitool/cloning.dm
new file mode 100644
index 00000000000..fcda47aac45
--- /dev/null
+++ b/code/datums/expansions/multitool/cloning.dm
@@ -0,0 +1,24 @@
+/datum/expansion/multitool/cryo/get_interact_window(var/obj/item/device/multitool/M, var/mob/user)
+ return buffer(M)
+
+/datum/expansion/multitool/cryo/receive_buffer(var/obj/item/device/multitool/M, var/atom/buffer, var/mob/user)
+ var/obj/machinery/clonepod/P = M.get_buffer(/obj/machinery/clonepod)
+ var/obj/machinery/computer/cloning/C = holder
+
+ if(!P)
+ user << "No valid connection data in \the [M] buffer."
+ return MT_NOACTION
+
+ var/is_connected = (P in C.pods)
+ if(!is_connected)
+ if(C.connect_pod(P))
+ user << "You connect \the [P] to \the [C]."
+ else
+ user << "You failed to connect \the [P] to \the [C]."
+ return MT_REFRESH
+
+ if(C.release_pod(P))
+ user << "You disconnect \the [P] from \the [C]."
+ else
+ user << "You failed to disconnect \the [P] from \the [C]."
+ return MT_REFRESH
diff --git a/code/datums/expansions/multitool/multitool.dm b/code/datums/expansions/multitool/multitool.dm
index e5e1b51599c..b95bb9f1659 100644
--- a/code/datums/expansions/multitool/multitool.dm
+++ b/code/datums/expansions/multitool/multitool.dm
@@ -1,2 +1,69 @@
-/datum/expansion/multitool/proc/interact()
+/datum/expansion/multitool
+ var/window_x = 370
+ var/window_y = 470
+
+/datum/expansion/multitool/proc/interact(var/obj/item/device/multitool/M, var/mob/user)
+ if(!CanUseTopic(user))
+ return
+
+ var/html = get_interact_window(M, user)
+ if(html)
+ var/datum/browser/popup = new(usr, "multitool", holder.name, window_x, window_y)
+ popup.set_content(html)
+ popup.set_title_image(user.browse_rsc_icon(holder.icon, holder.icon_state))
+ popup.open()
+ else
+ close_window(usr)
+
+/datum/expansion/multitool/proc/get_interact_window(var/obj/item/device/multitool/M, var/mob/user)
+ return
+
+/datum/expansion/multitool/proc/close_window(var/mob/user)
+ user << browse(null, "window=multitool")
+
+/datum/expansion/multitool/proc/buffer(var/obj/item/device/multitool/multitool)
+ . += "Buffer Memory:
"
+ if(multitool.buffer_name)
+ . += "[multitool.buffer_name] Send Purge
"
+ else
+ . += "No connection stored in the buffer."
+
+/datum/expansion/multitool/CanUseTopic(var/mob/user)
+ . = ..()
+ if(. == STATUS_CLOSE)
+ return
+
+ return user.get_multitool() && user.Adjacent(holder) ? STATUS_INTERACTIVE : STATUS_CLOSE
+
+/datum/expansion/multitool/Topic(href, href_list)
+ if(..())
+ close_window(usr)
+ return 1
+
+ var/mob/user = usr
+ var/obj/item/device/multitool/M = user.get_multitool()
+ if(href_list["send"])
+ var/atom/buffer = locate(href_list["send"])
+ . = send_buffer(M, buffer, user)
+ else if(href_list["purge"])
+ M.set_buffer(null)
+ . = MT_REFRESH
+ else
+ . = OnTopic(href, href_list, user)
+
+ switch(.)
+ if(MT_REFRESH)
+ interact(M, user)
+ if(MT_CLOSE)
+ close_window(user)
+ return 1
+
+/datum/expansion/multitool/proc/OnTopic(href, href_list, usr)
+ return
+
+/datum/expansion/multitool/proc/send_buffer(var/obj/item/device/multitool/M, var/atom/buffer, var/mob/user)
+ if(M.buffer_object == buffer)
+ receive_buffer(M, buffer, user)
+
+/datum/expansion/multitool/proc/receive_buffer(var/obj/item/device/multitool/M, var/atom/buffer, var/mob/user)
return
diff --git a/code/datums/expansions/multitool/store.dm b/code/datums/expansions/multitool/store.dm
new file mode 100644
index 00000000000..f41a8d9bf2f
--- /dev/null
+++ b/code/datums/expansions/multitool/store.dm
@@ -0,0 +1,22 @@
+/datum/expansion/multitool/store
+ var/can_load_connection
+
+/datum/expansion/multitool/store/New(var/atom/holder, var/can_load_connection)
+ ..()
+ src.can_load_connection = can_load_connection
+
+/datum/expansion/multitool/store/CanUseTopic()
+ if(can_load_connection && !call(src, can_load_connection)())
+ return STATUS_CLOSE
+ return ..()
+
+/datum/expansion/multitool/store/interact(var/obj/item/device/multitool/M, var/mob/user)
+ if(!CanUseTopic(user))
+ return
+
+ if(M.get_buffer() == holder)
+ M.set_buffer(null)
+ user << "You purge the connection data of \the [holder] from \the [M]."
+ else
+ M.set_buffer(holder)
+ user << "You load connection data from \the [holder] to \the [M]."
diff --git a/code/datums/expansions/multitool/~multitool.dm b/code/datums/expansions/multitool/~multitool.dm
new file mode 100644
index 00000000000..875f0e47cc0
--- /dev/null
+++ b/code/datums/expansions/multitool/~multitool.dm
@@ -0,0 +1,3 @@
+#undef MT_NOACTION
+#undef MT_REFRESH
+#undef MT_CLOSE
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 3c5f44ea6e3..1a14c9e59f9 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -57,6 +57,12 @@
RefreshParts()
update_icon()
+ expansions[/datum/expansion/multitool] = new/datum/expansion/multitool/store(src)
+
+/obj/machinery/clonepod/Destroy()
+ if(connected)
+ connected.release_pod(src)
+ return ..()
/obj/machinery/clonepod/attack_ai(mob/user as mob)
@@ -239,11 +245,6 @@
user.visible_message("[user] secures [src] to the floor.", "You secure [src] to the floor.")
else
user.visible_message("[user] unsecures [src] from the floor.", "You unsecure [src] from the floor.")
- else if(istype(W, /obj/item/device/multitool))
- var/obj/item/device/multitool/M = W
- M.set_buffer(src)
- user << "You load connection data from [src] to [M]."
- return
else
..()
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index 872a5ce8a54..97b196df3ad 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -18,11 +18,12 @@
/obj/machinery/computer/cloning/initialize()
..()
+ expansions[/datum/expansion/multitool] = new/datum/expansion/multitool/cryo(src)
updatemodules()
/obj/machinery/computer/cloning/Destroy()
releasecloner()
- ..()
+ return ..()
/obj/machinery/computer/cloning/proc/updatemodules()
src.scanner = findscanner()
@@ -52,6 +53,33 @@
P.name = initial(P.name)
pods.Cut()
+/obj/machinery/computer/cloning/proc/connect_pod(var/obj/machinery/clonepod/P)
+ if(P in pods)
+ return 0
+
+ if(P.connected)
+ P.connected.release_pod(P)
+ P.connected = src
+ pods += P
+ rename_pods()
+
+ return 1
+
+/obj/machinery/computer/cloning/proc/release_pod(var/obj/machinery/clonepod/P)
+ if(!(P in pods))
+ return
+
+ P.connected = null
+ P.name = initial(P.name)
+ pods -= P
+ rename_pods()
+ return 1
+
+/obj/machinery/computer/cloning/proc/rename_pods()
+ for(var/i = 1 to pods.len)
+ var/atom/P = pods[i]
+ P.name = "[initial(P.name)] #[i]"
+
/obj/machinery/computer/cloning/proc/findcloner()
var/num = 1
var/area/A = get_area(src)
@@ -70,14 +98,6 @@
user << "You insert [W]."
src.updateUsrDialog()
return
- else if(istype(W, /obj/item/device/multitool))
- var/obj/item/device/multitool/M = W
- var/obj/machinery/clonepod/P = M.get_buffer()
- if(istype(P) && !(P in pods))
- pods += P
- P.connected = src
- P.name = "[initial(P.name)] #[pods.len]"
- user << "You connect [P] to [src]."
else
..()
return
diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm
index d53ab1f5bbd..e383bd0d044 100644
--- a/code/game/objects/items/devices/multitool.dm
+++ b/code/game/objects/items/devices/multitool.dm
@@ -23,18 +23,20 @@
var/buffer_name
var/atom/buffer_object
-/obj/item/device/multitool/proc/get_buffer()
- if(buffer_object)
+/obj/item/device/multitool/proc/get_buffer(var/typepath)
+ // Update the buffer name only when someone fetches the buffer.
+ // Means you cannot be sure the source hasn't been destroyed until the very moment it's needed.
+ buffer_name = buffer_object ? buffer_object.name : null
+ if(buffer_object && (!typepath || istype(buffer_object, typepath)))
return buffer_object
- buffer_name = null
/obj/item/device/multitool/proc/set_buffer(var/atom/buffer)
if(!buffer || istype(buffer))
buffer_object = buffer
- buffer_name = buffer ? buffer_name : null
+ buffer_name = buffer ? buffer.name : null
/obj/item/device/multitool/resolve_attackby(atom/A, mob/user)
- if(!isobject(A))
+ if(!isobj(A))
return ..(A, user)
var/obj/O = A
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index b40972a11ac..dc9d37a93d5 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -21,7 +21,8 @@
processing_objects -= src
for(var/expansion in expansions)
qdel(expansions[expansion])
- expansions.Cut()
+ if(expansions)
+ expansions.Cut()
return ..()
/obj/Topic(href, href_list, var/datum/topic_state/state = default_state)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 3a73bffb700..d1b01b6c21e 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -29,6 +29,8 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
var/image/ghostimage = null //this mobs ghost image, for deleting and stuff
var/ghostvision = 1 //is the ghost able to see things humans can't?
var/seedarkness = 1
+
+ var/obj/item/device/multitool/ghost_multitool
incorporeal_move = 1
/mob/dead/observer/New(mob/body)
@@ -79,9 +81,14 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
if(!name) //To prevent nameless ghosts
name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
real_name = name
+
+ ghost_multitool = new(src)
..()
/mob/dead/observer/Destroy()
+ qdel(ghost_multitool)
+ ghost_multitool = null
+
if (ghostimage)
ghost_darkness_images -= ghostimage
qdel(ghostimage)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 5d3cf1a74c7..6bbdc0aae1a 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -83,6 +83,8 @@ var/list/ai_verbs_default = list(
var/custom_sprite = 0 // Whether the selected icon is custom
var/carded
+ var/obj/item/device/multitool/ai_multitool
+
/mob/living/silicon/ai/proc/add_ai_verbs()
src.verbs |= ai_verbs_default
src.verbs |= silicon_subsystems
@@ -173,7 +175,6 @@ var/list/ai_verbs_default = list(
ai_list += src
..()
- return
/mob/living/silicon/ai/proc/on_mob_init()
src << "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras)."
@@ -201,9 +202,17 @@ var/list/ai_verbs_default = list(
setup_icon()
/mob/living/silicon/ai/Destroy()
+ qdel(aiPDA)
+ qdel(aiMulti)
+ qdel(aiRadio)
+ aiPDA = null
+ aiMulti = null
+ aiRadio = null
+
ai_list -= src
qdel(eyeobj)
- ..()
+ eyeobj = null
+ return ..()
/mob/living/silicon/ai/proc/setup_icon()
var/file = file2text("config/custom_sprites.txt")
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index fe4d6ed56de..4d2fc208cc0 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -570,3 +570,19 @@ proc/is_blind(A)
return threatcount
#undef SAFE_PERP
+
+/mob/proc/get_multitool(var/obj/item/device/multitool/P)
+ if(istype(P))
+ return P
+
+mob/dead/observer/get_multitool()
+ return can_admin_interact() && ..(ghost_multitool)
+
+/mob/living/carbon/human/get_multitool()
+ return ..(get_active_hand())
+
+/mob/living/silicon/robot/get_multitool()
+ return ..(get_active_hand())
+
+/mob/living/silicon/ai/get_multitool()
+ return ..(aiMulti)