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

398 lines
12 KiB
Plaintext

/mob/living/bot/medbot
name = "Medibot"
desc = "A little medical robot. He looks somewhat underwhelmed."
icon_state = "medibot0"
req_one_access = list(ACCESS_MEDICAL, ACCESS_ROBOTICS)
botcard_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_PHARMACY, ACCESS_VIROLOGY, ACCESS_GENETICS)
var/obj/item/storage/firstaid/firstaid_item
//AI vars
var/frustration = 0
var/list/path = list()
var/mob/living/carbon/human/patient = null
var/mob/ignored = list() // Used by emag
var/last_newpatient_speak = 0
var/message = null
var/speech = 0
//Healing vars
var/obj/item/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents.
var/currently_healing = 0
var/injection_amount = 10 //How much reagent do we inject at a time?
var/heal_threshold = 10 //Start healing when they have this much damage in a category
var/use_beaker = 0 //Use reagents in beaker instead of default treatment agents.
var/treatment_brute = /singleton/reagent/tricordrazine
var/treatment_oxy = /singleton/reagent/tricordrazine
var/treatment_fire = /singleton/reagent/tricordrazine
var/treatment_tox = /singleton/reagent/tricordrazine
var/treatment_emag = /singleton/reagent/toxin
var/declare_treatment = 0 //When attempting to treat a patient, should it notify everyone wearing medhuds?
/mob/living/bot/medbot/Initialize()
var/list/firstaid_types = typesof(/obj/item/storage/firstaid)
var/firstaid_to_use = pick(firstaid_types)
firstaid_item = new firstaid_to_use(src)
. = ..()
/mob/living/bot/medbot/Destroy()
QDEL_NULL(reagent_glass)
QDEL_NULL(firstaid_item)
return ..()
/mob/living/bot/medbot/think()
..()
if(!on)
return
if(speech && prob(1))
say(message)
if(patient)
if(Adjacent(patient))
if(!currently_healing)
INVOKE_ASYNC(src, PROC_REF(UnarmedAttack), patient)
else
if(path.len && (get_dist(patient, path[path.len]) > 2)) // We have a path, but it's off
path = list()
if(!path.len && (get_dist(src, patient) > 1))
path = get_path_to(src, patient, 30, 0, botcard.GetAccess())
if(!length(path))
path = list()
if(path.len)
icon_state = "medibots"
step_to(src, path[1])
path -= path[1]
++frustration
if(get_dist(src, patient) > 7 || frustration > 8)
patient = null
icon_state = "medibot[on]"
else
for(var/mob/living/carbon/human/H in view(7, src)) // Time to find a patient!
if(valid_healing_target(H))
patient = H
frustration = 0
if(last_newpatient_speak + 300 < world.time)
var/message = pick("Hey, [H.name]! Hold on, I'm coming.", "Wait [H.name]! I want to help!", "[H.name], you appear to be injured!")
say(message)
custom_emote(VISIBLE_MESSAGE, "points at [H.name].")
last_newpatient_speak = world.time
break
/mob/living/bot/medbot/UnarmedAttack(var/mob/living/carbon/human/H, var/proximity)
. = ..()
if(!.)
return
if(!on)
return
if(!istype(H))
return
if(H.stat == DEAD)
if(pAI)
to_chat(pAI.pai, SPAN_WARNING("\The [H] is dead, you cannot help them now."))
return
var/death_message = pick("No! NO!", "Live, damnit! LIVE!", "I... I've never lost a patient before. Not today, I mean.")
say(death_message)
patient = null
return
var/t = valid_healing_target(H)
if(!t)
var/message = pick("All patched up!", "An apple a day keeps me away.", "Feel better soon!")
say(message)
patient = null
return
visible_message(SPAN_WARNING("[src] is trying to inject [H]!"))
if(declare_treatment)
var/area/location = get_area(src)
broadcast_medical_hud_message("[src] is treating <b>[H]</b> in <b>[location]</b>", src)
currently_healing = 1
update_icon()
if(do_mob(src, H, 30))
if(t == 1)
reagent_glass.reagents.trans_to_mob(H, injection_amount, CHEM_BLOOD)
else
H.reagents.add_reagent(t, injection_amount)
visible_message(SPAN_WARNING("[src] injects [H] with the syringe!"))
currently_healing = 0
update_icon()
/mob/living/bot/medbot/update_icon()
if(!underlays.len)
underlays += image(firstaid_item.icon, firstaid_item.icon_state)
var/matrix/M = matrix()
var/image/ha_image = image('icons/obj/item/scanner.dmi', "healthanalyzer")
M.Translate(5, 0)
ha_image.transform = M
underlays += ha_image
if(currently_healing)
icon_state = "mediboth"
else
icon_state = "medibot[on]"
/mob/living/bot/medbot/attack_hand(var/mob/user)
if (!has_ui_access(user))
to_chat(user, SPAN_WARNING("The unit's interface refuses to unlock!"))
return
var/dat = ""
dat += "Status: <A href='byond://?src=[REF(src)];power=1'>[on ? "On" : "Off"]</A><BR>"
dat += "Maintenance panel is [open ? "opened" : "closed"]<BR>"
dat += "Beaker: "
if (reagent_glass)
dat += "<A href='byond://?src=[REF(src)];eject=1'>Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]</a>"
else
dat += "None Loaded"
dat += "<br>Behaviour controls are [locked ? "locked" : "unlocked"]<hr>"
if(!locked || issilicon(user))
dat += "<TT>Healing Threshold: "
dat += "<a href='byond://?src=[REF(src)];adj_threshold=-10'>--</a> "
dat += "<a href='byond://?src=[REF(src)];adj_threshold=-5'>-</a> "
dat += "[heal_threshold] "
dat += "<a href='byond://?src=[REF(src)];adj_threshold=5'>+</a> "
dat += "<a href='byond://?src=[REF(src)];adj_threshold=10'>++</a>"
dat += "</TT><br>"
dat += "<TT>Injection Level: "
dat += "<a href='byond://?src=[REF(src)];adj_inject=-5'>-</a> "
dat += "[injection_amount] "
dat += "<a href='byond://?src=[REF(src)];adj_inject=5'>+</a> "
dat += "</TT><br>"
dat += "Reagent Source: "
dat += "<a href='byond://?src=[REF(src)];use_beaker=1'>[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]</a><br>"
dat += "Treatment report is [declare_treatment ? "on" : "off"]. <a href='byond://?src=[REF(src)];declaretreatment=[1]'>Toggle</a><br>"
dat += "The speaker switch is [speech ? "on" : "off"]. <a href='byond://?src=[REF(src)];speaker=[1]'>Toggle</a><br>"
dat += "Message is [message ? message : "unset"]. <a href='byond://?src=[REF(src)];msg=[1]'>Set</a><br>"
var/datum/browser/bot_win = new(user, "automed", "Automatic Medibot v1.2 Controls")
bot_win.set_content(dat)
bot_win.open()
/mob/living/bot/medbot/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/reagent_containers/glass))
if(locked)
to_chat(user, SPAN_NOTICE("You cannot insert a beaker because the panel is locked."))
return
if(!isnull(reagent_glass))
to_chat(user, SPAN_NOTICE("There is already a beaker loaded."))
return
user.drop_from_inventory(attacking_item, src)
reagent_glass = attacking_item
to_chat(user, SPAN_NOTICE("You insert [attacking_item]."))
return 1
else
..()
/mob/living/bot/medbot/Topic(href, href_list)
if(..())
return
usr.set_machine(src)
add_fingerprint(usr)
if (!has_ui_access(usr))
to_chat(usr, SPAN_WARNING("Insufficient permissions."))
return
if (href_list["power"])
if (on)
turn_off()
else
turn_on()
else if(href_list["adj_threshold"] && (!locked || issilicon(usr)))
var/adjust_num = text2num(href_list["adj_threshold"])
heal_threshold += adjust_num
if(heal_threshold < 5)
heal_threshold = 5
if(heal_threshold > 75)
heal_threshold = 75
else if(href_list["adj_inject"] && (!locked || issilicon(usr)))
var/adjust_num = text2num(href_list["adj_inject"])
injection_amount += adjust_num
if(injection_amount < 5)
injection_amount = 5
if(injection_amount > 15)
injection_amount = 15
else if(href_list["use_beaker"] && (!locked || issilicon(usr)))
use_beaker = !use_beaker
else if (href_list["eject"] && (!isnull(reagent_glass)))
if(!locked)
reagent_glass.forceMove(get_turf(src))
reagent_glass = null
else
to_chat(usr, SPAN_NOTICE("You cannot eject the beaker because the panel is locked."))
else if (href_list["declaretreatment"] && (!locked || issilicon(usr)))
declare_treatment = !declare_treatment
else if (href_list["msg"] && (!locked || issilicon(usr)))
var/I = sanitize(input(usr,"What will this medbot say?", "Set Message") as text|null)
if(!I)
return
message = I
speech = 1
else if (href_list["speaker"] && (!locked || issilicon(usr)))
speech = !speech
attack_hand(usr)
return
/mob/living/bot/medbot/emag_act(var/remaining_uses, var/mob/user)
. = ..()
if(!emagged)
if(user)
to_chat(user, SPAN_WARNING("You short out [src]'s reagent synthesis circuits."))
visible_message(SPAN_WARNING("[src] buzzes oddly!"))
flick_overlay("medibot_spark", src)
patient = null
currently_healing = 0
emagged = 1
on = 1
update_icon()
. = 1
ignored |= user
/mob/living/bot/medbot/explode()
on = FALSE
visible_message(SPAN_DANGER("\The [src] blows apart!"))
var/turf/Tsec = get_turf(src)
if(firstaid_item)
firstaid_item.forceMove(Tsec)
firstaid_item.contents = null
firstaid_item = null
new /obj/item/assembly/prox_sensor(Tsec)
new /obj/item/healthanalyzer(Tsec)
if (prob(50))
new /obj/item/robot_parts/l_arm(Tsec)
if(reagent_glass)
reagent_glass.forceMove(Tsec)
reagent_glass = null
spark(src, 3, GLOB.alldirs)
qdel(src)
return
/mob/living/bot/medbot/turn_off()
patient = null
frustration = 0
currently_healing = 0
..()
/mob/living/bot/medbot/proc/valid_healing_target(var/mob/living/carbon/human/H)
if(H.stat == DEAD) // He's dead, Jim
return null
if(isipc(H))
return null
if(H in ignored)
return null
if(emagged)
return treatment_emag
// If they're injured, we're using a beaker, and they don't have on of the chems in the beaker
if(reagent_glass && use_beaker && ((H.getBruteLoss() >= heal_threshold) || (H.getToxLoss() >= heal_threshold) || (H.getToxLoss() >= heal_threshold) || (H.getOxyLoss() >= (heal_threshold + 15))))
for(var/_R in reagent_glass.reagents.reagent_volumes)
var/singleton/reagent/R = GET_SINGLETON(_R)
if(!H.reagents.has_reagent(R))
return TRUE
continue
if((H.getBruteLoss() >= heal_threshold) && (!H.reagents.has_reagent(treatment_brute)))
return treatment_brute //If they're already medicated don't bother!
if((H.getOxyLoss() >= (15 + heal_threshold)) && (!H.reagents.has_reagent(treatment_oxy)))
return treatment_oxy
if((H.getFireLoss() >= heal_threshold) && (!H.reagents.has_reagent(treatment_fire)))
return treatment_fire
if((H.getToxLoss() >= heal_threshold) && (!H.reagents.has_reagent(treatment_tox)))
return treatment_tox
/* Construction */
/obj/item/storage/firstaid/attackby(obj/item/attacking_item, mob/user)
var/obj/item/robot_parts/S = attacking_item
if ((!istype(S, /obj/item/robot_parts/l_arm)) && (!istype(S, /obj/item/robot_parts/r_arm)))
..()
return
if(contents.len >= 1)
to_chat(user, SPAN_NOTICE("You need to empty [src] out first."))
return
var/obj/item/firstaid_arm_assembly/A = new /obj/item/firstaid_arm_assembly
A.underlays += image(icon, icon_state)
A.firstaid_item = src
qdel(S)
user.put_in_hands(A)
to_chat(user, SPAN_NOTICE("You add the robot arm to the first aid kit."))
qdel(src)
/obj/item/firstaid_arm_assembly
name = "first aid/robot arm assembly"
desc = "A first aid kit with a robot arm permanently grafted to it."
icon = 'icons/mob/npc/aibots.dmi'
icon_state = "firstaid_arm"
w_class = WEIGHT_CLASS_NORMAL
var/build_step = 0
var/created_name = "Medibot" //To preserve the name if it's a unique medbot I guess
var/obj/item/storage/firstaid/firstaid_item // store the firstaid type if it blows up
/obj/item/firstaid_arm_assembly/attackby(obj/item/attacking_item, mob/user)
..()
if(attacking_item.tool_behaviour == TOOL_PEN)
var/t = sanitizeSafe( tgui_input_text(user, "Enter new robot name", name, created_name, MAX_NAME_LEN), MAX_NAME_LEN )
if(!t)
return
if(!in_range(src, user) && loc != user)
return
created_name = t
else
switch(build_step)
if(0)
if(istype(attacking_item, /obj/item/healthanalyzer))
user.drop_from_inventory(attacking_item,get_turf(src))
qdel(attacking_item)
build_step++
to_chat(user, SPAN_NOTICE("You add the health sensor to [src]."))
name = "first-aid/robot arm/health analyzer assembly"
var/matrix/M = matrix()
var/image/ha_image = image('icons/obj/item/scanner.dmi', "health")
M.Translate(5, 0)
ha_image.transform = M
underlays += ha_image
return 1
if(1)
if(isprox(attacking_item))
user.drop_from_inventory(attacking_item, get_turf(src))
qdel(attacking_item)
to_chat(user, SPAN_NOTICE("You complete the Medibot! Beep boop."))
var/turf/T = get_turf(src)
var/mob/living/bot/medbot/S = new /mob/living/bot/medbot(T)
S.name = created_name
S.underlays = src.underlays
S.update_icon()
S.firstaid_item = firstaid_item
qdel(src)
return 1