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

254 lines
8.0 KiB
Plaintext

/obj/machinery/computer/robotics
name = "robotics control console"
desc = "Used to remotely lockdown or detonate linked cyborgs."
icon = 'icons/obj/modular_computers/modular_console.dmi'
icon_screen = "robot"
icon_keyboard = "purple_key"
icon_keyboard_emis = "purple_key_mask"
light_color = LIGHT_COLOR_PURPLE
req_one_access = list(ACCESS_RD, ACCESS_ROBOTICS)
circuit = /obj/item/circuitboard/robotics
var/safety = 1
/obj/machinery/computer/robotics/attack_ai(var/mob/user as mob)
if(!ai_can_interact(user))
return
ui_interact(user)
/obj/machinery/computer/robotics/attack_hand(var/mob/user as mob)
ui_interact(user)
/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
var/data[0]
data["robots"] = get_cyborgs(user)
data["safety"] = safety
// Also applies for cyborgs. Hides the manual self-destruct button.
data["is_ai"] = issilicon(user)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if(!ui)
ui = new(user, src, ui_key, "robot_control.tmpl", "Robotic Control Console", 400, 500)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/obj/machinery/computer/robotics/Topic(href, href_list)
if(..())
return
var/mob/user = usr
if(!src.allowed(user))
to_chat(user, "Access denied.")
return
// Destroys the cyborg
if(href_list["detonate"])
var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["detonate"])
if(!target || !istype(target))
return
if(isAI(user) && (target.connected_ai != user))
to_chat(user, "Access denied. This robot is not linked to you.")
return
// Cyborgs may blow up themselves via the console
if(isrobot(user) && user != target)
to_chat(user, "Access denied.")
return
var/choice = tgui_alert(usr, "Really detonate [target.name]?", "Robotics Control", list("Yes", "No"))
if(choice != "Yes")
return
if(!target || !istype(target))
return
// Antagonistic cyborgs? Left here for downstream
if(target.mind && target.mind.special_role && target.emagged)
to_chat(target, "Extreme danger. Termination codes detected. Scrambling security codes and automatic AI unlink triggered.")
target.ResetSecurityCodes()
return
if(target.emagged)
to_chat(user, "Access denied. Safety protocols are disabled.")
return
else
message_admins("[key_name_admin(usr)] detonated [target.name]!")
log_game("[key_name(usr)] detonated [target.name]!")
to_chat(target, SPAN_DANGER("Self-destruct command received."))
addtimer(CALLBACK(target, TYPE_PROC_REF(/mob/living/silicon/robot, self_destruct)), 1 SECONDS)
// Locks or unlocks the cyborg
else if(href_list["lockdown"])
var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["lockdown"])
if(!target || !istype(target))
return
if(isAI(user) && (target.connected_ai != user))
to_chat(user, "Access denied. This robot is not linked to you.")
return
if(isrobot(user))
to_chat(user, "Access denied.")
return
if(target.emagged)
return
var/choice = tgui_alert(usr, "Really [target.lock_charge ? "unlock" : "lockdown"] [target.name] ?", "Robotics Control", list("Yes", "No"))
if(choice != "Yes")
return
if(!target || !istype(target))
return
target.SetLockdown(!target.lock_charge) // Toggle.
message_admins("[key_name_admin(usr)] [target.lock_charge ? "locked down" : "released"] [target.name]!")
log_game("[key_name(usr)] [target.lock_charge ? "locked down" : "released"] [target.name]!")
to_chat(target, (target.lock_charge ? "You have been locked down!" : "Your lockdown has been lifted!"))
// Changes borg's access
else if(href_list["access"])
var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["access"])
if(!istype(target))
return
if(isAI(user) && (target.connected_ai != user))
to_chat(user, "Access denied. This robot is not linked to you.")
return
if(isrobot(user) || target.emagged)
to_chat(user, "Access denied.")
return
if(!istype(target))
return
if(!target.module)
to_chat(user, "\The [src]\s access protocols are immutable.")
return
target.module.all_access = !target.module.all_access
target.update_access()
var/log_message = "[key_name_admin(usr)] changed [target.name] access to [target.module.all_access ? "all access" : "role specific"]."
message_admins(log_message)
log_game(log_message)
to_chat(target, ("Your access was changed to: [target.module.all_access ? "all access" : "role specific"]."))
// Remotely hacks the cyborg. Only antag AIs can do this and only to linked cyborgs.
else if(href_list["hack"])
var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["hack"])
if(!target || !istype(target))
return
// Antag AI checks
if(!istype(user, /mob/living/silicon/ai) || !(user.mind.special_role && user.mind.original == user))
to_chat(user, "Access denied.")
return
if(target.emagged)
to_chat(user, "Robot is already hacked.")
return
var/choice = tgui_alert(usr, "Really hack [target.name]? This cannot be undone.", list("Yes", "No"))
if(choice != "Yes")
return
if(!target || !istype(target))
return
message_admins("[key_name_admin(usr)] emagged [target.name] using robotic console!")
log_game("[key_name(usr)] emagged [target.name] using robotic console!")
target.emagged = 1
to_chat(target, SPAN_NOTICE("Failsafe protocols overriden. New tools available."))
// Arms the emergency self-destruct system
else if(href_list["arm"])
if(istype(user, /mob/living/silicon))
to_chat(user, "Access denied.")
return
safety = !safety
to_chat(user, "You [safety ? "disarm" : "arm"] the emergency self destruct")
// Destroys all accessible cyborgs if safety is disabled
else if(href_list["nuke"])
if(istype(user, /mob/living/silicon))
to_chat(user, "Access denied.")
return
if(safety)
to_chat(user, "Self-destruct aborted - safety active")
return
message_admins("[key_name_admin(usr)] detonated all cyborgs!")
log_game("[key_name(usr)] detonated all cyborgs!")
for(var/mob/living/silicon/robot/R in GLOB.mob_list)
if(istype(R, /mob/living/silicon/robot/drone))
continue
// Ignore antagonistic cyborgs
if(R.scrambled_codes)
continue
if(R.emagged)
continue
to_chat(R, SPAN_DANGER("Self-destruct command received."))
spawn(10)
R.self_destruct()
// Proc: get_cyborgs()
// Parameters: 1 (operator - mob which is operating the console.)
// Description: Returns NanoUI-friendly list of accessible cyborgs.
/obj/machinery/computer/robotics/proc/get_cyborgs(var/mob/operator)
var/list/robots = list()
for(var/mob/living/silicon/robot/R in GLOB.mob_list)
// Ignore drones
if(istype(R, /mob/living/silicon/robot/drone))
continue
// Ignore antagonistic cyborgs
if(R.scrambled_codes)
continue
var/list/robot = list()
robot["name"] = R.name
if(R.stat)
robot["status"] = "Not Responding"
else if(R.lock_charge) // changed this from !R.canmove to R.lock_charge because of issues with lockdown and chairs
robot["status"] = "Lockdown"
else
robot["status"] = "Operational"
if(R.cell)
robot["cell"] = 1
robot["cell_capacity"] = R.cell.maxcharge
robot["cell_current"] = R.cell.charge
robot["cell_percentage"] = round(R.cell.percent())
else
robot["cell"] = 0
robot["module"] = R.module ? R.module.name : "None"
robot["master_ai"] = R.connected_ai ? R.connected_ai.name : "None"
robot["hackable"] = 0
robot["access"] = R.module ? R.module.all_access : FALSE
// Antag AIs know whether linked cyborgs are hacked or not.
if(operator && istype(operator, /mob/living/silicon/ai) && (R.connected_ai == operator) && (operator.mind.special_role && operator.mind.original == operator))
robot["hacked"] = R.emagged ? 1 : 0
robot["hackable"] = R.emagged? 0 : 1
robots.Add(list(robot))
return robots
// Proc: get_cyborg_by_name()
// Parameters: 1 (name - Cyborg we are trying to find)
// Description: Helper proc for finding cyborg by name
/obj/machinery/computer/robotics/proc/get_cyborg_by_name(var/name)
if(!name)
return
for(var/mob/living/silicon/robot/R in GLOB.mob_list)
if(R.name == name)
return R