Files
Batrachophreno 8111a8489b Restore many missing inhand sprites for items, add/port others (#21831)
Many, many, many items have inhand sprites in their .dmis but for
whatever reasons do not display them in-game. This PR:

1. Updates many item definitions to point to their already-existing
inhands correctly. This consists largely of held tools, but also gas
tanks and jetpacks mounted in the suit storage slot.

2. Adds a few codersprites made by me for objects with either missing
inhands or poorly matching mishands (IE, the tape recorder, which has a
black case, reused the white inhand sprites of the health analyzer). The
new sprites are modified or recolored variations of other inhand sprites
from our repo, except for circuitboards which are new.
<img width="444" height="400" alt="image"
src="https://github.com/user-attachments/assets/7f107b9a-fe24-4e31-8f16-4d34768ee117"
/>


3. Adds inhand sprites for Inflatables and Inflatable Boxes made by
Tomixcomics.
<img width="424" height="101" alt="image"
src="https://github.com/user-attachments/assets/434107c4-8577-49a2-a58e-d6b014c03933"
/>

4. Ports inhand sprites for the Hydraulic Rescue Tool from tg's Jaws of
Life.
<img width="224" height="94" alt="Screenshot 2026-02-07 172931"
src="https://github.com/user-attachments/assets/070c7956-f6a8-4fb5-870f-10c64afcc8b3"
/>

5. Some additional cleanup while in the area. The 'analyzer' has been
renamed the 'gas analyzer' to be consistent with the other analyzer
objects, standardized icon_state naming conventions where I saw
oddballs, updated code docs to use DMDocs when in the area, etc.

### Asset Licenses
The following assets that **have not** been created by myself are
included in this PR:

| Path | Original Author | License |
| --- | --- | --- |
| icons/obj/item/hydraulic_rescue_tool.dmi | [SomeAngryMiner (bee
station)](https://github.com/BeeStation/BeeStation-Hornet/pull/2487),
[maxymax
(/tg/station)](https://github.com/tgstation/tgstation/pull/58616) |
CC-BY-SA |
| icons/obj/item/inflatables.dmi |
[Tomixcomics](https://github.com/tomixcomics) | CC-BY-SA |
2026-02-17 17:51:22 +00:00

228 lines
7.2 KiB
Plaintext

/**
* Multitool -- A multitool is used for hacking electronic devices.
*
*/
/obj/item/multitool
name = "multitool"
desc = "This small, handheld device is made of durable, insulated plastic. It has a electrode jack, perfect for interfacing with numerous machines, as well as an in-built NT-SmartTrack! system."
icon = 'icons/obj/item/multitool.dmi'
icon_state = "multitool"
item_state = "multitool"
contained_sprite = TRUE
obj_flags = OBJ_FLAG_CONDUCTABLE
force = 11
w_class = WEIGHT_CLASS_SMALL
throwforce = 5.0
throw_range = 15
throw_speed = 3
drop_sound = 'sound/items/drop/multitool.ogg'
pickup_sound = 'sound/items/pickup/multitool.ogg'
matter = list(MATERIAL_PLASTIC = 50, MATERIAL_GLASS = 20, DEFAULT_WALL_MATERIAL = 5)
origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
var/obj/machinery/buffer // simple machine buffer for device linkage
var/obj/machinery/clonepod/connecting //same for cryopod linkage
var/buffer_name
var/atom/buffer_object
var/tracking_apc = FALSE
var/mutable_appearance/apc_indicator
var/datum/integrated_io/selected_io = null
var/mode = 0
tool_behaviour = TOOL_MULTITOOL
/obj/item/multitool/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "You can use this on a variety of objects (including APCs, airlocks and more) to try to hack them without cutting wires."
. += "Using it in-hand will toggle tracking of nearby APCs."
. += "Using it on a length of laid cable will return how much current it is carrying."
. += "It is used for interacting with a variety of machines."
. += "It is a necessary tool for integrated electronics wiring."
/obj/item/multitool/Destroy()
unregister_buffer(buffer_object)
QDEL_NULL(apc_indicator)
return ..()
/obj/item/multitool/proc/get_buffer(var/typepath)
// Only allow clearing the buffer name when someone fetches the buffer.
// Means you cannot be sure the source hasn't been destroyed until the very moment it's needed.
get_buffer_name(TRUE)
if(buffer_object && (!typepath || istype(buffer_object, typepath)))
return buffer_object
/obj/item/multitool/proc/get_buffer_name(var/null_name_if_missing = FALSE)
if(buffer_object)
buffer_name = buffer_object.name
else if(null_name_if_missing)
buffer_name = null
return buffer_name
/obj/item/multitool/proc/set_buffer(var/atom/buffer)
if(!buffer || istype(buffer))
buffer_name = buffer ? buffer.name : null
if(buffer != buffer_object)
unregister_buffer(buffer_object)
buffer_object = buffer
if(buffer_object)
RegisterSignal(buffer_object, COMSIG_QDELETING, PROC_REF(on_buffer_object_deletion))
update_icon()
/obj/item/multitool/proc/on_buffer_object_deletion(datum/source)
SIGNAL_HANDLER
unregister_buffer(source)
/obj/item/multitool/proc/unregister_buffer(atom/buffer_to_unregister)
// Only remove the buffered object, don't reset the name
// This means one cannot know if the buffer has been destroyed until one attempts to use it.
if(buffer_to_unregister == buffer_object && buffer_object)
UnregisterSignal(buffer_object, COMSIG_QDELETING)
buffer_object = null
update_icon()
/obj/item/multitool/resolve_attackby(atom/A, mob/user, var/click_parameters)
if(!isobj(A))
return ..(A, user, click_parameters)
var/obj/O = A
var/datum/component/multitool/MT = O.GetComponent(/datum/component/multitool)
if(!MT)
return ..(A, user, click_parameters)
user.AddTopicPrint(src)
MT.interact(src, user)
return 1
/obj/item/multitool/attack_self(mob/user)
interact(user)
/obj/item/multitool/interact(mob/user)
ui_interact(user)
/obj/item/multitool/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Multitool", "Multitool", 300, 250)
ui.open()
/obj/item/multitool/ui_data(mob/user)
var/list/data = list()
data["tracking_apc"] = tracking_apc
if(selected_io)
data["selected_io"] = list("name" = selected_io.name, "type" = selected_io.io_type)
return data
/obj/item/multitool/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return TRUE
switch(action)
if("track_apc")
tracking_apc = !tracking_apc
if(tracking_apc)
START_PROCESSING(SSprocessing, src)
apc_indicator = mutable_appearance(icon, "lost")
AddOverlays(apc_indicator)
else
STOP_PROCESSING(SSprocessing, src)
QDEL_NULL(apc_indicator)
. = TRUE
if("clear_io")
selected_io = null
. = TRUE
update_icon()
/obj/item/multitool/update_icon()
if(tracking_apc)
icon_state = "multitool_clear"
else if(selected_io)
if(buffer || connecting || buffer_object)
icon_state = "multitool_tracking"
else
icon_state = "multitool_red"
else
if(buffer || connecting || buffer_object)
icon_state = "multitool_tracking_fail"
else
icon_state = "multitool"
/obj/item/multitool/process()
if(!apc_indicator)
return PROCESS_KILL
var/turf/T = get_turf(src)
var/area/A = T.loc
var/obj/machinery/power/apc/APC = A.apc
if(!APC || (APC.z != T.z))
apc_indicator.icon_state = "lost"
else
// shamelessly stolen from technomancer
dir = get_dir(T, APC.loc) // overlays can't have different dirs than their atoms, but the multitool only has one dir sprite, so this works
switch(get_dist(T, APC.loc))
if(-1 to 0)
apc_indicator.icon_state = "direct"
if(1 to 8)
apc_indicator.icon_state = "close"
if(9 to 16)
apc_indicator.icon_state = "medium"
if(16 to INFINITY)
apc_indicator.icon_state = "far"
SetOverlays(apc_indicator)
/obj/item/multitool/proc/wire(datum/integrated_io/io, mob/user)
if(!io.holder.assembly)
to_chat(user, SPAN_WARNING("\The [io.holder] needs to be secured inside an assembly first."))
return
if(selected_io)
if(io == selected_io)
to_chat(user, SPAN_WARNING("Wiring \the [selected_io.holder]'s '[selected_io.name]' pin into itself is rather pointless."))
return
if(io.io_type != selected_io.io_type)
to_chat(user, SPAN_WARNING("Those two types of channels are incompatable. The first is a [selected_io.io_type], \
while the second is a [io.io_type]."))
return
if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly)
to_chat(user, SPAN_WARNING("Both \the [io.holder] and \the [selected_io.holder] need to be inside the same assembly."))
return
selected_io.linked |= io
io.linked |= selected_io
to_chat(user, SPAN_NOTICE("You connect \the [selected_io.holder]'s '[selected_io.name]' pin to \the [io.holder]'s '[io.name]' pin."))
selected_io.holder.interact(user) // This is to update the UI.
selected_io = null
else
selected_io = io
to_chat(user, SPAN_NOTICE("You link \the multitool to \the [selected_io.holder]'s [selected_io.name] data channel."))
update_icon()
/obj/item/multitool/proc/unwire(datum/integrated_io/io1, datum/integrated_io/io2, mob/user)
if(!io1.linked.len || !io2.linked.len)
to_chat(user, SPAN_WARNING("There is nothing connected to the data channel."))
return
if(!(io1 in io2.linked) || !(io2 in io1.linked) )
to_chat(user, SPAN_WARNING("These data pins aren't connected!"))
return
else
io1.linked.Remove(io2)
io2.linked.Remove(io1)
to_chat(user, "<span class='notice'>You clip the data connection between the [io1.holder.displayed_name]'s \
'[io1.name]' pin and the [io2.holder.displayed_name]'s '[io2.name]' pin.</span>")
io1.holder.interact(user) // This is to update the UI.
update_icon()