Files
BatrachophrenoandGitHub 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

331 lines
11 KiB
Plaintext

/* Teleportation devices.
* Contains:
* * Locator
* * Hand-tele
* * Closet Teleporter
* * Inhibitor handling proc for above
*/
/*
* Special inhibitor handling. Different from the one used by teleport datums.
*/
/proc/check_inhibitors(var/turf/T)
for(var/found_inhibitor in GLOB.bluespace_inhibitors)
var/obj/machinery/anti_bluespace/AB = found_inhibitor
if(T.z != AB.z || get_dist(T, AB) > 8 || (AB.stat & (NOPOWER | BROKEN)))
continue
else
return FALSE
return TRUE
/*
* Locator
*/
/obj/item/locator
name = "locator"
desc = "A device that can be used to track those with locator implants."
icon = 'icons/obj/item/pinpointer.dmi'
icon_state = "pinoff"
var/temp = null
var/frequency = 1451
var/broadcasting = null
var/listening = TRUE
obj_flags = OBJ_FLAG_CONDUCTABLE
w_class = WEIGHT_CLASS_SMALL
item_state = "electronic"
throw_speed = 4
throw_range = 20
origin_tech = list(TECH_MAGNET = 1)
matter = list(DEFAULT_WALL_MATERIAL = 400)
/obj/item/locator/attack_self(mob/user as mob)
user.set_machine(src)
var/dat
if (src.temp)
dat = "[src.temp]<BR><BR><A href='byond://?src=[REF(src)];temp=1'>Clear</A>"
else
dat = {"
<B>Persistent Signal Locator</B><HR>
Frequency:
<A href='byond://?src=[REF(src)];freq=-10'>-</A>
<A href='byond://?src=[REF(src)];freq=-2'>-</A> [format_frequency(src.frequency)]
<A href='byond://?src=[REF(src)];freq=2'>+</A>
<A href='byond://?src=[REF(src)];freq=10'>+</A><BR>
<A href='byond://?src=[REF(src)];refresh=1'>Refresh</A>"}
user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
return
/obj/item/locator/Topic(href, href_list)
..()
if (usr.stat || usr.restrained())
return
var/turf/current_location = get_turf(usr)//What turf is the user on?
if(!current_location||current_location.z==1)//If turf was not found or they're on z level 1.
to_chat(usr, "The [src] is malfunctioning.")
return
if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))))
usr.set_machine(src)
if (href_list["refresh"])
src.temp = "<B>Persistent Signal Locator</B><HR>"
var/turf/sr = get_turf(src)
if (sr)
src.temp += "<B>Located Beacons:</B><BR>"
for(var/obj/item/radio/beacon/W in GLOB.teleportbeacons)
if (W.get_frequency() == src.frequency)
var/turf/tr = get_turf(W)
if (tr.z == sr.z && tr)
var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
if (direct < 5)
direct = "very strong"
else
if (direct < 10)
direct = "strong"
else
if (direct < 20)
direct = "weak"
else
direct = "very weak"
src.temp += "[W.code]-[dir2text(get_dir(sr, tr))]-[direct]<BR>"
src.temp += "<B>Extraneous Signals:</B><BR>"
for (var/obj/item/implant/tracking/W in GLOB.implants)
if (!W.implanted || !(istype(W.loc,/obj/item/organ/external) || ismob(W.loc)))
continue
else
var/mob/M = W.loc
if (M.stat == DEAD)
if (M.timeofdeath + W.lifespan_postmortem < world.time)
continue
var/turf/tr = get_turf(W)
if (tr.z == sr.z && tr)
var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
if (direct < 20)
if (direct < 5)
direct = "very strong"
else
if (direct < 10)
direct = "strong"
else
direct = "weak"
src.temp += "[W.id]-[dir2text(get_dir(sr, tr))]-[direct]<BR>"
src.temp += "<B>You are at \[[sr.x],[sr.y],[sr.z]\]</B> in orbital coordinates.<BR><BR><A href='byond://?src=[REF(src)];refresh=1'>Refresh</A><BR>"
else
src.temp += "<B><span class='warning'>Processing Error:</span></B> Unable to locate orbital position.<BR>"
else
if (href_list["freq"])
src.frequency += text2num(href_list["freq"])
src.frequency = sanitize_frequency(src.frequency)
else
if (href_list["temp"])
src.temp = null
if (istype(src.loc, /mob))
attack_self(src.loc)
else
for(var/mob/M in viewers(1, src))
if (M.client)
src.attack_self(M)
return
/*
* Hand-tele
*/
/obj/item/hand_tele
name = "hand tele"
desc = "A hand-held bluespace teleporter that can rip open portals to a random nearby location, or lock onto a teleporter with a selected teleportation beacon."
icon = 'icons/obj/item/hand_tele.dmi'
icon_state = "hand_tele"
item_state = "electronic"
throwforce = 5
item_flags = ITEM_FLAG_HELD_MAP_TEXT
w_class = WEIGHT_CLASS_SMALL
throw_speed = 3
throw_range = 5
origin_tech = list(TECH_MAGNET = 1, TECH_BLUESPACE = 3)
matter = list(DEFAULT_WALL_MATERIAL = 10000)
var/obj/machinery/teleport/pad/linked_pad
var/list/active_teleporters
var/max_portals = 2
/obj/item/hand_tele/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Ctrl-click to choose which teleportation pad to link to."
. += "Use in-hand or alt-click to deploy a portal. "
. += "When not linked to a pad, or the pad isn't pointing at a beacon, it will choose a completely random teleportation destination."
/obj/item/hand_tele/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(linked_pad)
var/area/A = get_area(linked_pad)
var/display_name = get_area_display_name(A)
. += SPAN_NOTICE("\The [src] is linked to a teleportation pad in [display_name]")
else
. += SPAN_WARNING("\The [src] isn't linked to any teleportation pads!")
/obj/item/hand_tele/set_initial_maptext()
held_maptext = SMALL_FONTS(7, "Ready")
/obj/item/hand_tele/attack_self(mob/user)
var/turf/current_location = get_turf(user)//What turf is the user on?
if(!current_location || isAdminLevel(current_location.z))
to_chat(user, SPAN_WARNING("\The [src] can't get a bearing on anything right now."))
return
//Cannot make one if too close to an inhibitor
if(!check_inhibitors(current_location))
to_chat(user, SPAN_DANGER("\The [src] can't seem to find a lock. Something in the area must be preventing the portal from opening..."))
return
if(LAZYLEN(active_teleporters) >= max_portals)
user.show_message(SPAN_WARNING("\The [src] is recharging!"))
return
var/turf/teleport_turf
if(linked_pad)
if(linked_pad.stat & (NOPOWER|BROKEN))
to_chat(user, SPAN_WARNING("The pad \the [src] is connected doesn't seem to be responding!"))
return
if(!AreConnectedZLevels(current_location.z, linked_pad.z))
to_chat(user, SPAN_WARNING("The pad \the [src] is connected to isn't close enough to lock onto now!"))
return
if(linked_pad.locked_obj)
teleport_turf = get_turf(linked_pad.locked_obj.resolve())
else
var/list/potential_turfs = list()
for(var/turf/T in orange(10))
if(T.x > world.maxx-8 || T.x < 8)
continue //putting them at the edge is dumb
if(T.y > world.maxy-8 || T.y < 8)
continue
if(T.density || turf_contains_dense_objects(T))
continue
if(!check_inhibitors(T))
continue
potential_turfs += T
teleport_turf = pick(potential_turfs)
if(!teleport_turf)
to_chat(user, SPAN_WARNING("\The [src] was unable to get a lock onto anything!"))
return
if(isAdminLevel(teleport_turf.z))
to_chat(user, SPAN_WARNING("The signal to the beacon seems to be scrambled!"))
return
var/obj/effect/portal/P = new /obj/effect/portal(get_turf(src), teleport_turf, src)
LAZYADD(active_teleporters, P)
if(LAZYLEN(active_teleporters) >= max_portals)
check_maptext(SMALL_FONTS(6, "Charge"))
add_fingerprint(user)
/obj/item/hand_tele/AltClick(mob/user)
if(user == loc)
attack_self(user)
return
return ..()
/obj/item/hand_tele/CtrlClick(mob/user)
if(user == loc)
var/turf/current_location = get_turf(src)
var/list/teleport_options = list()
for(var/obj/machinery/teleport/pad/P in SSmachinery.machinery)
if(AreConnectedZLevels(current_location.z, P.z))
var/area/A = get_area(P)
var/display_name = get_area_display_name(A)
if(P.engaged)
teleport_options["[display_name] (Active)"] = P
else
teleport_options["[display_name] (Inactive)"] = P
teleport_options["None (Dangerous)"] = null
var/teleport_choice = tgui_input_list(user, "Please select a teleporter to lock in on.", "Hand Teleporter", teleport_options)
if(!teleport_choice)
return
var/old_pad = linked_pad
linked_pad = teleport_options[teleport_choice]
if(linked_pad)
RegisterSignal(linked_pad, COMSIG_QDELETING, PROC_REF(pad_destroyed))
if(old_pad && linked_pad != old_pad)
UnregisterSignal(old_pad, COMSIG_QDELETING)
return
return ..()
/obj/item/hand_tele/proc/pad_destroyed()
linked_pad = null
audible_message("\The [src] beeps, \"Connected pad destroyed, resetting to no-pad.\"", null, 3)
/obj/item/hand_tele/proc/remove_portal(var/obj/effect/portal/P)
LAZYREMOVE(active_teleporters, P)
if(LAZYLEN(active_teleporters) < max_portals)
check_maptext(SMALL_FONTS(7, "Ready"))
icon_state = "hand_tele"
else
icon_state = "hand_tele_recharging"
/obj/item/closet_teleporter
name = "closet teleporter"
desc = "A device that allows a user to connect two closets into a bluespace network."
icon = 'icons/obj/modular_computers/modular_components.dmi'
icon_state = "cpu_normal_photonic"
obj_flags = OBJ_FLAG_CONDUCTABLE
w_class = WEIGHT_CLASS_SMALL
origin_tech = list(TECH_MAGNET = 2, TECH_BLUESPACE = 3)
matter = list(DEFAULT_WALL_MATERIAL = 400)
var/obj/structure/closet/attached_closet
var/obj/item/closet_teleporter/linked_teleporter
var/last_use = 0
/obj/item/closet_teleporter/antagonist_hints(mob/user, distance, is_adjacent)
. += ..()
. += "Left-click a closet with this to install."
. += "Once two closets have been 'upgraded', step into one closet and close the door to teleport to the linked closet."
. += "It has a one minute cooldown after a batch teleport."
/obj/item/closet_teleporter/proc/do_teleport(var/mob/user)
if(!attached_closet)
to_chat(user, SPAN_WARNING("\The [src] doesn't have an attached closet!"))
return FALSE
if(!linked_teleporter)
to_chat(user, SPAN_WARNING("\The [src] doesn't have a linked teleporter!"))
return FALSE
if(!linked_teleporter.attached_closet)
to_chat(user, SPAN_WARNING("The linked teleporter doesn't have an attached closet!"))
return FALSE
if(last_use + 600 > world.time)
return FALSE
if(!check_inhibitors(get_turf(attached_closet)) || !check_inhibitors(get_turf(linked_teleporter.attached_closet)))
to_chat(user, SPAN_WARNING("Something near you or your destination is destabilizing the bluespace network between the closets. \The [src] can't get a clear link to the other side!"))
return FALSE
var/obj/structure/closet/target_closet = linked_teleporter.attached_closet
user.forceMove(target_closet.opened ? get_turf(target_closet) : target_closet)
if(target_closet.opened)
if(user.client)
user.client.eye = user.client.mob
user.client.perspective = MOB_PERSPECTIVE
user.visible_message(SPAN_NOTICE("\The [user] steps out of the back of \the [target_closet]."), SPAN_NOTICE("You teleport into the linked closet, stepping out of it."))
user.set_fullscreen(FALSE, "closet_impaired", /atom/movable/screen/fullscreen/closet_impaired)
else
if(user.client)
user.client.eye = target_closet
target_closet.visible_message(SPAN_WARNING("\The [target_closet] rattles."))
to_chat(user, SPAN_NOTICE("You teleport into the target closet, bumping into the closed door."))
target_closet.shake_animation()
playsound(get_turf(src), 'sound/effects/grillehit.ogg', 100, TRUE)
return TRUE
/obj/item/closet_teleporter/Destroy()
attached_closet = null
if(linked_teleporter?.linked_teleporter == src)
linked_teleporter.linked_teleporter = null
linked_teleporter = null
return ..()