diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 467c67e0c3..7dcfad01b5 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -49,16 +49,12 @@
#define ui_storage1 "CENTER+1:18,SOUTH:5"
#define ui_storage2 "CENTER+2:20,SOUTH:5"
-#define ui_borg_sensor "CENTER-3:15, SOUTH:5" //borgs
-#define ui_borg_lamp "CENTER-4:15, SOUTH:5" //borgs
-#define ui_borg_thrusters "CENTER-5:15, SOUTH:5" //borgs
#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs
#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs
#define ui_inv3 "CENTER :16,SOUTH:5" //borgs
#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs
#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs
#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs
-#define ui_borg_album "CENTER+4:21,SOUTH:5" //borgs
#define ui_borg_language_menu "EAST-1:27,SOUTH+2:8" //borgs
#define ui_monkey_head "CENTER-5:13,SOUTH:5" //monkey
@@ -91,7 +87,9 @@
#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
#define ui_borg_intents "EAST-2:26,SOUTH:5"
-
+#define ui_borg_lamp "CENTER-3:16, SOUTH:5"
+#define ui_borg_tablet "CENTER-4:16, SOUTH:5"
+#define ui_borg_alerts "CENTER+4:21,SOUTH:5"
//Upper-middle right (alerts)
#define ui_alert1 "EAST-1:28,CENTER+5:27"
diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm
index 93c3b63d1e..f54d229c5e 100644
--- a/code/_onclick/hud/ai.dm
+++ b/code/_onclick/hud/ai.dm
@@ -150,9 +150,6 @@
if(isAI(usr))
var/mob/living/silicon/ai/AI = usr
AI.aicamera.viewpictures(usr)
- else if(iscyborg(usr))
- var/mob/living/silicon/robot/R = usr
- R.aicamera.viewpictures(usr)
/obj/screen/ai/sensors
name = "Sensor Augmentation"
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index 9859e7acdf..6a9a670214 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -70,23 +70,46 @@
/obj/screen/robot/lamp
name = "headlamp"
- icon_state = "lamp0"
+ icon = 'icons/mob/screen_cyborg.dmi'
+ icon_state = "lamp_off"
+ var/mob/living/silicon/robot/robot
/obj/screen/robot/lamp/Click()
- if(..())
+ . = ..()
+ if(.)
return
- var/mob/living/silicon/robot/R = usr
- R.control_headlamp()
+ robot?.toggle_headlamp()
+ update_icon()
-/obj/screen/robot/thrusters
- name = "ion thrusters"
- icon_state = "ionpulse0"
+/obj/screen/robot/lamp/update_icon()
+ if(robot?.lamp_enabled)
+ icon_state = "lamp_on"
+ else
+ icon_state = "lamp_off"
-/obj/screen/robot/thrusters/Click()
- if(..())
+/obj/screen/robot/modPC
+ name = "Modular Interface"
+ icon = 'icons/mob/screen_cyborg.dmi'
+ icon_state = "template"
+ var/mob/living/silicon/robot/robot
+
+/obj/screen/robot/modPC/Click()
+ . = ..()
+ if(.)
return
- var/mob/living/silicon/robot/R = usr
- R.toggle_ionpulse()
+ robot.modularInterface?.interact(robot)
+
+/obj/screen/robot/alerts
+ name = "Alert Panel"
+ icon = 'icons/mob/screen_ai.dmi'
+ icon_state = "alerts"
+
+/obj/screen/robot/alerts/Click()
+ . = ..()
+ if(.)
+ return
+ var/mob/living/silicon/robot/borgo = usr
+ borgo.robot_alerts()
/obj/screen/robot/sensors
name = "Sensor Augmentation"
@@ -148,37 +171,37 @@
//End of module select
+ using = new /obj/screen/robot/lamp()
+ using.screen_loc = ui_borg_lamp
+ using.hud = src
+ static_inventory += using
+ mymobR.lampButton = using
+ var/obj/screen/robot/lamp/lampscreen = using
+ lampscreen.robot = mymobR
+
//Photography stuff
using = new /obj/screen/ai/image_take()
using.screen_loc = ui_borg_camera
using.hud = src
static_inventory += using
- using = new /obj/screen/ai/image_view()
- using.screen_loc = ui_borg_album
+//Borg Integrated Tablet
+ using = new /obj/screen/robot/modPC()
+ using.screen_loc = ui_borg_tablet
using.hud = src
static_inventory += using
+ mymobR.interfaceButton = using
+ if(mymobR.modularInterface)
+ using.vis_contents += mymobR.modularInterface
+ var/obj/screen/robot/modPC/tabletbutton = using
+ tabletbutton.robot = mymobR
-//Sec/Med HUDs
- using = new /obj/screen/robot/sensors()
- using.screen_loc = ui_borg_sensor
+//Alerts
+ using = new /obj/screen/robot/alerts()
+ using.screen_loc = ui_borg_alerts
using.hud = src
static_inventory += using
-//Headlamp control
- using = new /obj/screen/robot/lamp()
- using.screen_loc = ui_borg_lamp
- using.hud = src
- static_inventory += using
- mymobR.lamp_button = using
-
-//Thrusters
- using = new /obj/screen/robot/thrusters()
- using.screen_loc = ui_borg_thrusters
- using.hud = src
- static_inventory += using
- mymobR.thruster_button = using
-
//Intent
action_intent = new /obj/screen/act_intent/robot()
action_intent.icon_state = mymob.a_intent
diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm
index 593579c469..8d5847d811 100644
--- a/code/datums/wires/robot.dm
+++ b/code/datums/wires/robot.dm
@@ -70,19 +70,23 @@
if(R.shell)
R.undeploy()
R.set_connected_ai(null)
+ R.logevent("AI connection fault [mend?"cleared":"detected"]")
if(WIRE_LAWSYNC) // Cut the law wire, and the borg will no longer receive law updates from its AI. Repair and it will re-sync.
if(mend)
if(!R.emagged)
R.lawupdate = TRUE
else if(!R.deployed) //AI shells must always have the same laws as the AI
R.lawupdate = FALSE
+ R.logevent("Lawsync Module fault [mend?"cleared":"detected"]")
if (WIRE_CAMERA) // Disable the camera.
if(!QDELETED(R.builtInCamera) && !R.scrambledcodes)
R.builtInCamera.status = mend
R.builtInCamera.toggle_cam(usr, 0)
- R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.")
+ R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.")
+ R.logevent("Camera Module fault [mend?"cleared":"detected"]")
if(WIRE_LOCKDOWN) // Simple lockdown.
R.SetLockdown(!mend)
+ R.logevent("Motor Controller fault [mend?"cleared":"detected"]")
if(WIRE_RESET_MODULE)
if(R.has_module() && !mend)
R.ResetModule()
diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm
index 9b911e9c15..932a2fe455 100644
--- a/code/game/machinery/computer/robot.dm
+++ b/code/game/machinery/computer/robot.dm
@@ -118,6 +118,7 @@
log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!")
message_admins("[ADMIN_LOOKUPFLW(usr)] emagged cyborg [key_name_admin(R)] using robotic console!")
R.SetEmagged(TRUE)
+ R.logevent("WARN: root privleges granted to PID [num2hex(rand(1,65535), -1)][num2hex(rand(1,65535), -1)].") //random eight digit hex value. Two are used because rand(1,4294967295) throws an error
if("convert")
if(isAI(usr) && is_servant_of_ratvar(usr))
var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 291fc30aa2..bbdf80c308 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -74,6 +74,8 @@ as performing this in action() will cause the upgrade to end up in the borg inst
playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
R.revive()
+ R.logevent("WARN -- System recovered from unexpected shutdown.")
+ R.logevent("System brought online.")
/obj/item/borg/upgrade/vtec
name = "cyborg VTEC module"
@@ -149,6 +151,7 @@ as performing this in action() will cause the upgrade to end up in the borg inst
return FALSE
R.ionpulse = TRUE
+ R.toggle_ionpulse() //Enabled by default
/obj/item/borg/upgrade/thrusters/deactivate(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -285,6 +288,8 @@ as performing this in action() will cause the upgrade to end up in the borg inst
return FALSE
R.SetEmagged(1)
+ R.logevent("WARN: hardware installed with missing security certificate!") //A bit of fluff to hint it was an illegal tech item
+ R.logevent("WARN: root privleges granted to PID [num2hex(rand(1,65535), -1)][num2hex(rand(1,65535), -1)].") //random eight digit hex value. Two are used because rand(1,4294967295) throws an error
return TRUE
diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
index 0659655da5..5722276159 100644
--- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
+++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
@@ -354,9 +354,14 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
var/obj/effect/countdown/doomsday/countdown
var/detonation_timer
var/next_announce
+ var/mob/living/silicon/ai/owner
/obj/machinery/doomsday_device/Initialize()
. = ..()
+ owner = loc
+ if(!istype(owner))
+ stack_trace("Doomsday created outside an AI somehow, shit's fucking broke. Anyway, we're just gonna qdel now. Go make a github issue report.")
+ qdel(src)
countdown = new(src)
/obj/machinery/doomsday_device/Destroy()
@@ -364,6 +369,9 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
STOP_PROCESSING(SSfastprocess, src)
SSshuttle.clearHostileEnvironment(src)
SSmapping.remove_nuke_threat(src)
+ for(var/mob/living/silicon/robot/borg in owner.connected_robots)
+ borg.lamp_doom = FALSE
+ borg.toggle_headlamp(FALSE, TRUE) //forces borg lamp to update
for(var/A in GLOB.ai_list)
var/mob/living/silicon/ai/AI = A
if(AI.doomsday_device == src)
@@ -378,6 +386,9 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
START_PROCESSING(SSfastprocess, src)
SSshuttle.registerHostileEnvironment(src)
SSmapping.add_nuke_threat(src) //This causes all blue "circuit" tiles on the map to change to animated red icon state.
+ for(var/mob/living/silicon/robot/borg in owner.connected_robots)
+ borg.lamp_doom = TRUE
+ borg.toggle_headlamp(FALSE, TRUE) //forces borg lamp to update
/obj/machinery/doomsday_device/proc/seconds_remaining()
. = max(0, (round((detonation_timer - world.time) / 10)))
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index 22fb266033..a5290a5752 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -48,8 +48,8 @@
"smmon_4.gif" = 'icons/program_icons/smmon_4.gif',
"smmon_5.gif" = 'icons/program_icons/smmon_5.gif',
"smmon_6.gif" = 'icons/program_icons/smmon_6.gif'
- // "borg_mon.gif" = 'icons/program_icons/borg_mon.gif',
- // "robotact.gif" = 'icons/program_icons/robotact.gif'
+ "borg_mon.gif" = 'icons/program_icons/borg_mon.gif',
+ "robotact.gif" = 'icons/program_icons/robotact.gif'
)
/datum/asset/simple/radar_assets
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index 33a2a09b70..329fa5ee65 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -187,12 +187,8 @@
AM.emp_act(50)
if(iscyborg(AM))
var/mob/living/silicon/robot/borg = AM
- if(borg.lamp_intensity)
- borg.update_headlamp(TRUE, INFINITY)
- to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.")
- for(var/obj/item/assembly/flash/cyborg/F in borg.held_items)
- if(!F.crit_fail)
- F.burn_out()
+ if(borg.lamp_enabled)
+ borg.smash_headlamp()
else
for(var/obj/item/O in AM)
if(O.light_range && O.light_power)
diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm
index 6a5338c1f5..b5fa78a3fd 100644
--- a/code/modules/mob/living/silicon/robot/death.dm
+++ b/code/modules/mob/living/silicon/robot/death.dm
@@ -17,6 +17,9 @@
if(stat == DEAD)
return
+ if(!gibbed)
+ logevent("FATAL -- SYSTEM HALT")
+ modularInterface.shutdown_computer()
. = ..()
locked = FALSE //unlock cover
@@ -24,7 +27,7 @@
update_mobility()
if(!QDELETED(builtInCamera) && builtInCamera.status)
builtInCamera.toggle_cam(src,0)
- update_headlamp(1) //So borg lights are disabled when killed.
+ toggle_headlamp(TRUE) //So borg lights are disabled when killed.
uneq_all() // particularly to ensure sight modes are cleared
diff --git a/code/modules/mob/living/silicon/robot/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm
index d499c2abae..2ef6f6490e 100644
--- a/code/modules/mob/living/silicon/robot/emote.dm
+++ b/code/modules/mob/living/silicon/robot/emote.dm
@@ -64,15 +64,3 @@
key = "warn"
message = "blares an alarm!"
sound = 'sound/machines/warning-buzzer.ogg'
-
-/mob/living/silicon/robot/verb/powerwarn()
- set category = "Robot Commands"
- set name = "Power Warning"
-
- if(stat == CONSCIOUS)
- if(!cell || !cell.charge)
- visible_message("The power warning light on [src] flashes urgently.",\
- "You announce you are operating in low power mode.")
- playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0)
- else
- to_chat(src, "You can only use this emote when you're out of charge.")
diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm
index 995ad5a5ee..e8d3b76927 100644
--- a/code/modules/mob/living/silicon/robot/laws.dm
+++ b/code/modules/mob/living/silicon/robot/laws.dm
@@ -1,11 +1,3 @@
-/mob/living/silicon/robot/verb/cmd_show_laws()
- set category = "Robot Commands"
- set name = "Show Laws"
-
- if(usr.stat == DEAD)
- return //won't work if dead
- show_laws()
-
/mob/living/silicon/robot/show_laws(everyone = 0)
laws_sanity_check()
var/who
@@ -79,4 +71,12 @@
if (length(temp) > 0)
laws.supplied[index] = temp
+ var/datum/computer_file/program/robotact/program = modularInterface.get_robotact()
+ if(program)
+ program.force_full_update()
+
picturesync()
+
+/mob/living/silicon/robot/post_lawchange(announce = TRUE)
+ . = ..()
+ addtimer(CALLBACK(src, .proc/logevent,"Law update processed."), 0, TIMER_UNIQUE | TIMER_OVERRIDE) //Post_Lawchange gets spammed by some law boards, so let's wait it out
diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm
index 0feb8b98b7..1b3b1a8d94 100644
--- a/code/modules/mob/living/silicon/robot/life.dm
+++ b/code/modules/mob/living/silicon/robot/life.dm
@@ -9,8 +9,7 @@
if(stat != DEAD)
if(low_power_mode)
if(cell && cell.charge)
- low_power_mode = 0
- update_headlamp()
+ low_power_mode = FALSE
else if(stat == CONSCIOUS)
use_power()
@@ -18,12 +17,12 @@
if(cell && cell.charge)
if(cell.charge <= 100)
uneq_all()
- var/amt = clamp((lamp_intensity - 2) * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell.
+ var/amt = clamp(lamp_enabled ? (lamp_intensity - 2) : 0 * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell.
cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick
else
uneq_all()
- low_power_mode = 1
- update_headlamp()
+ low_power_mode = TRUE
+ toggle_headlamp(TRUE)
diag_hud_set_borgcell()
/mob/living/silicon/robot/proc/handle_robot_hud_updates()
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 7db417b8fa..03e59408c6 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -33,6 +33,8 @@
if(!TryConnectToAI())
lawupdate = FALSE
+ create_modularInterface()
+
radio = new /obj/item/radio/borg(src)
if(!scrambledcodes && !builtInCamera)
builtInCamera = new (src)
@@ -71,6 +73,7 @@
aicamera = new/obj/item/camera/siliconcam/robot_camera(src)
toner = tonermax
diag_hud_set_borgcell()
+ logevent("System brought online.")
add_verb(src, /mob/living/proc/lay_down) //CITADEL EDIT gimmie rest verb kthx
add_verb(src, /mob/living/silicon/robot/proc/rest_style)
@@ -93,6 +96,8 @@
ghostize()
stack_trace("Borg MMI lacked a brainmob")
mmi = null
+ if(modularInterface)
+ QDEL_NULL(modularInterface)
//CITADEL EDIT: Cyborgs drop encryption keys on destroy
if(istype(radio) && istype(radio.keyslot))
radio.keyslot.forceMove(T)
@@ -165,14 +170,6 @@
/mob/living/silicon/robot/proc/get_standard_name()
return "[(designation ? "[designation] " : "")][mmi.braintype]-[ident]"
-/mob/living/silicon/robot/verb/cmd_robot_alerts()
- set category = "Robot Commands"
- set name = "Show Alerts"
- if(usr.stat == DEAD)
- to_chat(src, "Alert: You are dead.")
- return //won't work if dead
- robot_alerts()
-
/mob/living/silicon/robot/proc/robot_alerts()
var/dat = ""
for (var/cat in alarms)
@@ -219,8 +216,6 @@
ion_trail.start()
else
ion_trail.stop()
- if(thruster_button)
- thruster_button.icon_state = "ionpulse[ionpulse_on]"
/mob/living/silicon/robot/get_status_tab_items()
. = ..()
@@ -281,6 +276,8 @@
return !cleared
/mob/living/silicon/robot/can_interact_with(atom/A)
+ if (A == modularInterface)
+ return TRUE //bypass for borg tablets
if (low_power_mode)
return FALSE
var/turf/T0 = get_turf(src)
@@ -428,8 +425,6 @@
to_chat(user, "Unable to locate a radio!")
else if (istype(W, /obj/item/card/id)||istype(W, /obj/item/pda)) // trying to unlock the interface with an ID card
- if(emagged)//still allow them to open the cover
- to_chat(user, "The interface seems slightly damaged.")
if(opened)
to_chat(user, "You must close the cover to swipe an ID card!")
else
@@ -437,6 +432,11 @@
locked = !locked
to_chat(user, "You [ locked ? "lock" : "unlock"] [src]'s cover.")
update_icons()
+ if(emagged)
+ to_chat(user, "The cover interface glitches out for a split second.")
+ logevent("ChÃ¥vÃis cover lock has been [locked ? "engaged" : "released"]") //ChÃ¥vÃis: see above line
+ else
+ logevent("Chassis cover lock has been [locked ? "engaged" : "released"]")
else
to_chat(user, "Access denied.")
@@ -454,11 +454,13 @@
if(U.action(src))
to_chat(user, "You apply the upgrade to [src].")
if(U.one_use)
+ logevent("Firmware [U] run successfully.")
U.afterInstall(src)
qdel(U)
else
U.forceMove(src)
upgrades += U
+ logevent("Hardware [U] installed successfully.")
U.afterInstall(src)
else
to_chat(user, "Upgrade error.")
@@ -477,15 +479,22 @@
else if(istype(W, /obj/item/flashlight))
if(!opened)
to_chat(user, "You need to open the panel to repair the headlamp!")
- else if(lamp_cooldown <= world.time)
+ else if(lamp_functional)
to_chat(user, "The headlamp is already functional!")
else
if(!user.temporarilyRemoveItemFromInventory(W))
to_chat(user, "[W] seems to be stuck to your hand. You'll have to find a different light.")
return
- lamp_cooldown = 0
+ lamp_functional = TRUE
qdel(W)
to_chat(user, "You replace the headlamp bulbs.")
+ else if(istype(W, /obj/item/computer_hardware/hard_drive/portable)) //Allows borgs to install new programs with human help
+ if(!modularInterface)
+ stack_trace("Cyborg [src] ( [type] ) was somehow missing their integrated tablet. Please make a bug report.")
+ create_modularInterface()
+ var/obj/item/computer_hardware/hard_drive/portable/floppy = W
+ if(modularInterface.install_component(floppy, user))
+ return
else
return ..()
@@ -502,19 +511,6 @@
return TRUE
return ..()
-/mob/living/silicon/robot/verb/unlock_own_cover()
- set category = "Robot Commands"
- set name = "Unlock Cover"
- set desc = "Unlocks your own cover if it is locked. You can not lock it again. A human will have to lock it for you."
- if(stat == DEAD)
- return //won't work if dead
- if(locked)
- switch(alert("You cannot lock your cover again, are you sure?\n (You can still ask for a human to lock it)", "Unlock Own Cover", "Yes", "No"))
- if("Yes")
- locked = FALSE
- update_icons()
- to_chat(usr, "You unlock your cover.")
-
/mob/living/silicon/robot/proc/allowed(mob/M)
//check if it doesn't require any access at all
if(check_access(null))
@@ -596,6 +592,7 @@
else
clear_alert("locked")
locked_down = state
+ logevent("System lockdown [locked_down?"triggered":"released"].")
update_mobility()
/mob/living/silicon/robot/proc/SetEmagged(new_state)
@@ -607,48 +604,6 @@
else
clear_alert("hacked")
-/mob/living/silicon/robot/verb/outputlaws()
- set category = "Robot Commands"
- set name = "State Laws"
-
- if(usr.stat == DEAD)
- return //won't work if dead
- checklaws()
-
-/mob/living/silicon/robot/verb/set_automatic_say_channel() //Borg version of setting the radio for autosay messages.
- set name = "Set Auto Announce Mode"
- set desc = "Modify the default radio setting for stating your laws."
- set category = "Robot Commands"
-
- if(usr.stat == DEAD)
- return //won't work if dead
- set_autosay()
-
-/mob/living/silicon/robot/proc/control_headlamp()
- if(stat || lamp_cooldown > world.time || low_power_mode)
- to_chat(src, "This function is currently offline.")
- return
-
-//Some sort of magical "modulo" thing which somehow increments lamp power by 2, until it hits the max and resets to 0.
- lamp_intensity = (lamp_intensity+2) % (lamp_max+2)
- to_chat(src, "[lamp_intensity ? "Headlamp power set to Level [lamp_intensity/2]" : "Headlamp disabled."]")
- update_headlamp()
-
-/mob/living/silicon/robot/proc/update_headlamp(var/turn_off = 0, var/cooldown = 100)
- set_light(0)
-
- if(lamp_intensity && (turn_off || stat || low_power_mode))
- to_chat(src, "Your headlamp has been deactivated.")
- lamp_intensity = 0
- lamp_cooldown = world.time + cooldown
- else
- set_light(lamp_intensity)
-
- if(lamp_button)
- lamp_button.icon_state = "lamp[lamp_intensity]"
-
- update_icons()
-
/mob/living/silicon/robot/proc/deconstruct()
var/turf/T = get_turf(src)
if (robot_suit)
@@ -867,13 +822,11 @@
if(!eye_blind)
blind_eyes(1)
update_mobility()
- update_headlamp()
else
if(stat == UNCONSCIOUS)
stat = CONSCIOUS
adjust_blindness(-1)
update_mobility()
- update_headlamp()
diag_hud_set_status()
diag_hud_set_health()
diag_hud_set_aishell()
@@ -883,7 +836,6 @@
if(..()) //successfully ressuscitated from death
if(!QDELETED(builtInCamera) && !wires.is_cut(WIRE_CAMERA))
builtInCamera.toggle_cam(src,0)
- update_headlamp()
if(admin_revive)
locked = TRUE
notify_ai(NEW_BORG)
@@ -908,6 +860,7 @@
resize = 0.5
hasExpanded = FALSE
update_transform()
+ logevent("Chassis configuration has been reset.")
module.transform_to(/obj/item/robot_module)
// Remove upgrades.
@@ -1173,8 +1126,91 @@
return
. = connected_ai
connected_ai = new_ai
+ lamp_doom = FALSE
if(.)
var/mob/living/silicon/ai/old_ai = .
old_ai.connected_robots -= src
if(connected_ai)
connected_ai.connected_robots |= src
+ toggle_headlamp(FALSE, TRUE)
+
+/**
+ * Handles headlamp smashing
+ *
+ * When called (such as by the shadowperson lighteater's attack), this proc will break the borg's headlamp
+ * and then call toggle_headlamp to disable the light. It also plays a sound effect of glass breaking, and
+ * tells the borg what happened to its chat. Broken lights can be repaired by using a flashlight on the borg.
+ */
+/mob/living/silicon/robot/proc/smash_headlamp()
+ if(!lamp_functional)
+ return
+ lamp_functional = FALSE
+ playsound(src, 'sound/effects/glass_step.ogg', 50)
+ toggle_headlamp(TRUE)
+ to_chat(src, "Your headlamp is broken! You'll need a human to help replace it.")
+
+/**
+ * Handles headlamp toggling, disabling, and color setting.
+ *
+ * The initial if statment is a bit long, but the gist of it is that should the lamp be on AND the update_color
+ * arg be true, we should simply change the color of the lamp but not disable it. Otherwise, should the turn_off
+ * arg be true, the lamp already be enabled, any of the normal reasons the lamp would turn off happen, or the
+ * update_color arg be passed with the lamp not on, we should set the lamp off. The update_color arg is only
+ * ever true when this proc is called from the borg tablet, when the color selection feature is used.
+ *
+ * Arguments:
+ * * arg1 - turn_off, if enabled will force the lamp into an off state (rather than toggling it if possible)
+ * * arg2 - update_color, if enabled, will adjust the behavior of the proc to change the color of the light if it is already on.
+ */
+/mob/living/silicon/robot/proc/toggle_headlamp(turn_off = FALSE, update_color = FALSE)
+ //if both lamp is enabled AND the update_color flag is on, keep the lamp on. Otherwise, if anything listed is true, disable the lamp.
+ if(!(update_color && lamp_enabled) && (turn_off || lamp_enabled || update_color || !lamp_functional || stat || low_power_mode))
+ if(lamp_functional && stat != DEAD && lamp_doom)
+ set_light(l_power = TRUE) //If the lamp isn't broken and borg isn't dead, doomsday borgs cannot disable their light fully.
+ set_light(l_color = "#FF0000") //This should only matter for doomsday borgs, as any other time the lamp will be off and the color not seen
+ set_light(l_range = 1) //Again, like above, this only takes effect when the light is forced on by doomsday mode.
+ set_light(l_power = FALSE)
+ lamp_enabled = FALSE
+ lampButton?.update_icon()
+ update_icons()
+ return
+ set_light(l_range = lamp_intensity)
+ set_light(l_color = (lamp_doom? "#FF0000" : lamp_color)) //Red for doomsday killborgs, borg's choice otherwise
+ set_light(l_power = TRUE)
+ lamp_enabled = TRUE
+ lampButton?.update_icon()
+ update_icons()
+
+/mob/living/silicon/robot/proc/create_modularInterface()
+ if(!modularInterface)
+ modularInterface = new /obj/item/modular_computer/tablet/integrated(src)
+ modularInterface.layer = ABOVE_HUD_PLANE
+ modularInterface.plane = ABOVE_HUD_PLANE
+
+/mob/living/silicon/robot/modules/syndicate/create_modularInterface()
+ if(!modularInterface)
+ modularInterface = new /obj/item/modular_computer/tablet/integrated/syndicate(src)
+ return ..()
+
+/**
+ * Records an IC event log entry in the cyborg's internal tablet.
+ *
+ * Creates an entry in the borglog list of the cyborg's internal tablet, listing the current
+ * in-game time followed by the message given. These logs can be seen by the cyborg in their
+ * BorgUI tablet app. By design, logging fails if the cyborg is dead.
+ *
+ * Arguments:
+ * arg1: a string containing the message to log.
+ */
+/mob/living/silicon/robot/proc/logevent(var/string = "")
+ if(!string)
+ return
+ if(stat == DEAD) //Dead borgs log no longer
+ return
+ if(!modularInterface)
+ stack_trace("Cyborg [src] ( [type] ) was somehow missing their integrated tablet. Please make a bug report.")
+ create_modularInterface()
+ modularInterface.borglog += "[STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)] - [string]"
+ var/datum/computer_file/program/robotact/program = modularInterface.get_robotact()
+ if(program)
+ program.force_full_update()
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index 0ed380040b..69497b1f8b 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -118,6 +118,7 @@
if(connected_ai && connected_ai.mind && connected_ai.mind.has_antag_datum(/datum/antagonist/traitor))
to_chat(src, "ALERT: Foreign software execution prevented.")
to_chat(connected_ai, "ALERT: Cyborg unit \[[src]] successfully defended against subversion.")
+ logevent("ALERT: Foreign software execution prevented.")
log_game("[key_name(user)] attempted to emag cyborg [key_name(src)], but they were slaved to traitor AI [connected_ai].")
return TRUE
@@ -140,10 +141,12 @@
var/time = time2text(world.realtime,"hh:mm:ss")
GLOB.lawchanges.Add("[time] : [user.name]([user.key]) emagged [name]([key])")
to_chat(src, "ALERT: Foreign software detected.")
+ logevent("ALERT: Foreign software detected.")
sleep(5)
to_chat(src, "Initiating diagnostics...")
sleep(20)
to_chat(src, "SynBorg v1.7 loaded.")
+ logevent("WARN: root privleges granted to PID [num2hex(rand(1,65535), -1)][num2hex(rand(1,65535), -1)].") //random eight digit hex value. Two are used because rand(1,4294967295) throws an error
sleep(5)
to_chat(src, "LAW SYNCHRONISATION ERROR")
sleep(5)
diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm
index 4f7ae41969..f9d56e9caa 100644
--- a/code/modules/mob/living/silicon/robot/robot_defines.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defines.dm
@@ -26,8 +26,6 @@
var/obj/screen/inv1 = null
var/obj/screen/inv2 = null
var/obj/screen/inv3 = null
- var/obj/screen/lamp_button = null
- var/obj/screen/thruster_button = null
var/obj/screen/hands = null
var/shown_robot_modules = 0 //Used to determine whether they have the module menu shown or not
@@ -70,11 +68,24 @@
var/toner = 0
var/tonermax = 40
- var/lamp_max = 10 //Maximum brightness of a borg lamp. Set as a var for easy adjusting.
- var/lamp_intensity = 0 //Luminosity of the headlamp. 0 is off. Higher settings than the minimum require power.
- light_color = "#FFCC66"
- light_power = 0.8
- var/lamp_cooldown = 0 //Flag for if the lamp is on cooldown after being forcibly disabled.
+ ///If the lamp isn't broken.
+ var/lamp_functional = TRUE
+ ///If the lamp is turned on
+ var/lamp_enabled = FALSE
+ ///Set lamp color
+ var/lamp_color = "#FFFFFF"
+ ///Lamp brightness. Starts at 3, but can be 1 - 5.
+ var/lamp_intensity = 3
+ ///Lamp button reference
+ var/obj/screen/robot/lamp/lampButton
+ ///Set to true if a doomsday event is locking our lamp to on and RED
+ var/lamp_doom = FALSE
+ //Maximum brightness of a borg lamp. Set as a var for easy adjusting.
+ var/lamp_max = 10
+
+ ///The reference to the built-in tablet that borgs carry.
+ var/obj/item/modular_computer/tablet/integrated/modularInterface
+ var/obj/screen/robot/modPC/interfaceButton
var/sight_mode = 0
hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_TRACK_HUD)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 4c83841809..241efcca6c 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -262,6 +262,7 @@
R.mob_transforming = TRUE
R.SetLockdown(1)
R.anchored = TRUE
+ R.logevent("Chassis configuration has been set to [name].")
sleep(1)
for(var/i in 1 to 4)
playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, 1, -1)
@@ -271,7 +272,7 @@
R.setDir(SOUTH)
R.anchored = FALSE
R.mob_transforming = FALSE
- R.update_headlamp()
+ R.update_icons()
R.notify_ai(NEW_MODULE)
if(R.hud_used)
R.hud_used.update_robot_modules_display()
diff --git a/code/modules/mob/living/silicon/robot/update_icons.dm b/code/modules/mob/living/silicon/robot/update_icons.dm
index a567446e9e..cc54ea7af2 100644
--- a/code/modules/mob/living/silicon/robot/update_icons.dm
+++ b/code/modules/mob/living/silicon/robot/update_icons.dm
@@ -1,6 +1,7 @@
/// this is bad code
/mob/living/silicon/robot/update_icons()
cut_overlays()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
icon_state = module.cyborg_base_icon
//Citadel changes start here - Allows modules to use different icon files, and allows modules to specify a pixel offset
icon = (module.cyborg_icon_override ? module.cyborg_icon_override : initial(icon))
@@ -26,10 +27,14 @@
if(stat != DEAD && !(IsUnconscious() ||IsStun() || IsKnockdown() || IsParalyzed() || low_power_mode)) //Not dead, not stunned.
if(!eye_lights)
eye_lights = new()
- if(lamp_intensity > 2)
+ if(lamp_enabled)
eye_lights.icon_state = "[module.special_light_key ? "[module.special_light_key]":"[module.cyborg_base_icon]"]_l"
+ eye_lights.color = lamp_doom? COLOR_RED : lamp_color
+ eye_lights.plane = 19 //glowy eyes
else
eye_lights.icon_state = "[module.special_light_key ? "[module.special_light_key]":"[module.cyborg_base_icon]"]_e[is_servant_of_ratvar(src) ? "_r" : ""]"
+ eye_lights.color = "#FFFFFF"
+ eye_lights.plane = -1
eye_lights.icon = icon
add_overlay(eye_lights)
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index 63cb1cc5fa..a1fa8c7e1a 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -386,32 +386,32 @@
to_chat(user, "You repair \the [src].")
return
- if(W.tool_behaviour == TOOL_SCREWDRIVER)
- if(!all_components.len)
- to_chat(user, "This device doesn't have any components installed.")
- return
- var/list/component_names = list()
- for(var/h in all_components)
- var/obj/item/computer_hardware/H = all_components[h]
- component_names.Add(H.name)
+ ..()
- var/choice = input(user, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in sortList(component_names)
+/obj/item/modular_computer/screwdriver_act(mob/user, obj/item/tool)
+ if(!all_components.len)
+ to_chat(user, "This device doesn't have any components installed.")
+ return
+ var/list/component_names = list()
+ for(var/h in all_components)
+ var/obj/item/computer_hardware/H = all_components[h]
+ component_names.Add(H.name)
- if(!choice)
- return
+ var/choice = input(user, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in sortList(component_names)
- if(!Adjacent(user))
- return
-
- var/obj/item/computer_hardware/H = find_hardware_by_name(choice)
-
- if(!H)
- return
-
- uninstall_component(H, user)
+ if(!choice)
return
- ..()
+ if(!Adjacent(user))
+ return
+
+ var/obj/item/computer_hardware/H = find_hardware_by_name(choice)
+
+ if(!H)
+ return
+
+ uninstall_component(H, user)
+ return
// Used by processor to relay qdel() to machinery type.
/obj/item/modular_computer/proc/relay_qdel()
diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm
index 4a985b93c1..88fab58859 100644
--- a/code/modules/modular_computers/computers/item/computer_ui.dm
+++ b/code/modules/modular_computers/computers/item/computer_ui.dm
@@ -47,7 +47,9 @@
data["login"] = list()
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
+ data["cardholder"] = FALSE
if(cardholder)
+ data["cardholder"] = TRUE
var/obj/item/card/id/stored_card = cardholder.GetID()
if(stored_card)
var/stored_name = stored_card.registered_name
diff --git a/code/modules/modular_computers/computers/item/tablet-silicon.dm b/code/modules/modular_computers/computers/item/tablet-silicon.dm
new file mode 100644
index 0000000000..df224fd71c
--- /dev/null
+++ b/code/modules/modular_computers/computers/item/tablet-silicon.dm
@@ -0,0 +1,98 @@
+/// Borg Built-in tablet interface
+/obj/item/modular_computer/tablet/integrated
+ name = "modular interface"
+ icon_state = "tablet-silicon"
+ has_light = FALSE //tablet light button actually enables/disables the borg lamp
+ comp_light_luminosity = 0
+ has_variants = FALSE
+ ///Ref to the borg we're installed in. Set by the borg during our creation.
+ var/mob/living/silicon/robot/borgo
+ ///Ref to the RoboTact app. Important enough to borgs to deserve a ref.
+ var/datum/computer_file/program/robotact/robotact
+ ///IC log that borgs can view in their personal management app
+ var/list/borglog = list()
+
+/obj/item/modular_computer/tablet/integrated/Initialize(mapload)
+ . = ..()
+ vis_flags |= VIS_INHERIT_ID
+ borgo = loc
+ if(!istype(borgo))
+ borgo = null
+ stack_trace("[type] initialized outside of a borg, deleting.")
+ return INITIALIZE_HINT_QDEL
+
+/obj/item/modular_computer/tablet/integrated/Destroy()
+ borgo = null
+ return ..()
+
+/obj/item/modular_computer/tablet/integrated/turn_on(mob/user)
+ if(borgo?.stat != DEAD)
+ return ..()
+ return FALSE
+
+/**
+ * Returns a ref to the RoboTact app, creating the app if need be.
+ *
+ * The RoboTact app is important for borgs, and so should always be available.
+ * This proc will look for it in the tablet's robotact var, then check the
+ * hard drive if the robotact var is unset, and finally attempt to create a new
+ * copy if the hard drive does not contain the app. If the hard drive rejects
+ * the new copy (such as due to lack of space), the proc will crash with an error.
+ * RoboTact is supposed to be undeletable, so these will create runtime messages.
+ */
+/obj/item/modular_computer/tablet/integrated/proc/get_robotact()
+ if(!borgo)
+ return null
+ if(!robotact)
+ var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
+ robotact = hard_drive.find_file_by_name("robotact")
+ if(!robotact)
+ stack_trace("Cyborg [borgo] ( [borgo.type] ) was somehow missing their self-manage app in their tablet. A new copy has been created.")
+ robotact = new(hard_drive)
+ if(!hard_drive.store_file(robotact))
+ qdel(robotact)
+ robotact = null
+ CRASH("Cyborg [borgo]'s tablet hard drive rejected recieving a new copy of the self-manage app. To fix, check the hard drive's space remaining. Please make a bug report about this.")
+ return robotact
+
+//Makes the light settings reflect the borg's headlamp settings
+/obj/item/modular_computer/tablet/integrated/ui_data(mob/user)
+ . = ..()
+ .["has_light"] = TRUE
+ .["light_on"] = borgo?.lamp_enabled
+ .["comp_light_color"] = borgo?.lamp_color
+
+//Overrides the ui_act to make the flashlight controls link to the borg instead
+/obj/item/modular_computer/tablet/integrated/ui_act(action, params)
+ switch(action)
+ if("PC_toggle_light")
+ if(!borgo)
+ return FALSE
+ borgo.toggle_headlamp()
+ return TRUE
+
+ if("PC_light_color")
+ if(!borgo)
+ return FALSE
+ var/mob/user = usr
+ var/new_color
+ while(!new_color)
+ new_color = input(user, "Choose a new color for [src]'s flashlight.", "Light Color",light_color) as color|null
+ if(!new_color || QDELETED(borgo))
+ return
+ if(color_hex2num(new_color) < 200) //Colors too dark are rejected
+ to_chat(user, "That color is too dark! Choose a lighter one.")
+ new_color = null
+ borgo.lamp_color = new_color
+ borgo.toggle_headlamp(FALSE, TRUE)
+ return TRUE
+ return ..()
+
+/obj/item/modular_computer/tablet/integrated/syndicate
+ icon_state = "tablet-silicon-syndicate"
+ device_theme = "syndicate"
+
+
+/obj/item/modular_computer/tablet/integrated/syndicate/Initialize()
+ . = ..()
+ borgo.lamp_color = COLOR_RED //Syndicate likes it red
diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm
index 67e8118c7a..ffbd5c9936 100644
--- a/code/modules/modular_computers/computers/item/tablet.dm
+++ b/code/modules/modular_computers/computers/item/tablet.dm
@@ -41,6 +41,7 @@
comp_light_luminosity = 6.3
has_variants = FALSE
device_theme = "syndicate"
+ light_color = COLOR_RED
/obj/item/modular_computer/tablet/nukeops/emag_act(mob/user)
if(!enabled)
diff --git a/code/modules/modular_computers/computers/item/tablet_presets.dm b/code/modules/modular_computers/computers/item/tablet_presets.dm
index 8ac669d2bf..fac6d8fef7 100644
--- a/code/modules/modular_computers/computers/item/tablet_presets.dm
+++ b/code/modules/modular_computers/computers/item/tablet_presets.dm
@@ -67,3 +67,11 @@
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
install_component(new /obj/item/computer_hardware/hard_drive/small/nukeops)
install_component(new /obj/item/computer_hardware/network_card)
+
+//Borg Built-in tablet
+/obj/item/modular_computer/tablet/integrated/Initialize()
+ . = ..()
+ install_component(new /obj/item/computer_hardware/processor_unit/small)
+ install_component(new /obj/item/computer_hardware/hard_drive/small/integrated)
+ install_component(new /obj/item/computer_hardware/recharger/cyborg)
+ install_component(new /obj/item/computer_hardware/network_card/integrated)
diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm
index 0e6f4d161a..91e51b8066 100644
--- a/code/modules/modular_computers/computers/machinery/modular_computer.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm
@@ -146,3 +146,7 @@
/obj/machinery/modular_computer/bullet_act(obj/item/projectile/Proj)
if(cpu)
cpu.bullet_act(Proj)
+
+/obj/machinery/modular_computer/screwdriver_act(mob/user, obj/item/tool)
+ if(cpu)
+ return cpu.screwdriver_act(user, tool)
diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm
index 4f1c488b9e..a4408346e5 100644
--- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm
+++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm
@@ -17,6 +17,12 @@
/datum/computer_file/program/revelation/proc/activate()
if(computer)
+ if(istype(computer, /obj/item/modular_computer/tablet/integrated)) //If this is a borg's integrated tablet
+ var/obj/item/modular_computer/tablet/integrated/modularInterface = computer
+ to_chat(modularInterface.borgo,"SYSTEM PURGE DETECTED/")
+ addtimer(CALLBACK(modularInterface.borgo, /mob/living/silicon/robot/.proc/death), 2 SECONDS, TIMER_UNIQUE)
+ return
+
computer.visible_message("\The [computer]'s screen brightly flashes and loud electrical buzzing is heard.")
computer.enabled = FALSE
computer.update_icon()
diff --git a/code/modules/modular_computers/file_system/programs/borg_monitor.dm b/code/modules/modular_computers/file_system/programs/borg_monitor.dm
index 13caab27ef..938d8a17de 100644
--- a/code/modules/modular_computers/file_system/programs/borg_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm
@@ -54,10 +54,14 @@
var/ID = checkID()
if(!ID)
return
+ if(R.stat == DEAD) //Dead borgs will listen to you no longer
+ to_chat(usr, "Error -- Could not open a connection to unit:[R]")
var/message = stripped_input(usr, message = "Enter message to be sent to remote cyborg.", title = "Send Message")
if(!message)
return
to_chat(R, "
Message from [ID] -- \"[message]\"
")
+ to_chat(usr, "Message sent to [R]: [message]")
+ R.logevent("Message from [ID] -- \"[message]\"")
SEND_SOUND(R, 'sound/machines/twobeep_high.ogg')
if(R.connected_ai)
to_chat(R.connected_ai, "
Message from [ID] to [R] -- \"[message]\"
")
diff --git a/code/modules/modular_computers/file_system/programs/robotact.dm b/code/modules/modular_computers/file_system/programs/robotact.dm
new file mode 100644
index 0000000000..066c9a6744
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/robotact.dm
@@ -0,0 +1,147 @@
+/datum/computer_file/program/robotact
+ filename = "robotact"
+ filedesc = "RoboTact"
+ extended_desc = "A built-in app for cyborg self-management and diagnostics."
+ ui_header = "robotact.gif" //DEBUG -- new icon before PR
+ program_icon_state = "command"
+ requires_ntnet = FALSE
+ transfer_access = null
+ available_on_ntnet = FALSE
+ unsendable = TRUE
+ undeletable = TRUE
+ usage_flags = PROGRAM_TABLET
+ size = 5
+ tgui_id = "NtosRobotact"
+ ///A typed reference to the computer, specifying the borg tablet type
+ var/obj/item/modular_computer/tablet/integrated/tablet
+
+/datum/computer_file/program/robotact/Destroy()
+ tablet = null
+ return ..()
+
+/datum/computer_file/program/robotact/run_program(mob/living/user)
+ if(!istype(computer, /obj/item/modular_computer/tablet/integrated))
+ to_chat(user, "A warning flashes across \the [computer]: Device Incompatible.")
+ return FALSE
+ . = ..()
+ if(.)
+ tablet = computer
+ if(tablet.device_theme == "syndicate")
+ program_icon_state = "command-syndicate"
+ return TRUE
+ return FALSE
+
+/datum/computer_file/program/robotact/ui_data(mob/user)
+ var/list/data = get_header_data()
+ if(!iscyborg(user))
+ return data
+ var/mob/living/silicon/robot/borgo = tablet.borgo
+
+ data["name"] = borgo.name
+ data["designation"] = borgo.designation //Borgo module type
+ data["masterAI"] = borgo.connected_ai //Master AI
+
+ var/charge = 0
+ var/maxcharge = 1
+ if(borgo.cell)
+ charge = borgo.cell.charge
+ maxcharge = borgo.cell.maxcharge
+ data["charge"] = charge //Current cell charge
+ data["maxcharge"] = maxcharge //Cell max charge
+ data["integrity"] = ((borgo.health + 100) / 2) //Borgo health, as percentage
+ data["lampIntensity"] = borgo.lamp_intensity //Borgo lamp power setting
+ data["sensors"] = "[borgo.sensors_on?"ACTIVE":"DISABLED"]"
+ data["printerPictures"] = borgo.connected_ai? borgo.connected_ai.aicamera.stored.len : borgo.aicamera.stored.len //Number of pictures taken, synced to AI if available
+ data["printerToner"] = borgo.toner //amount of toner
+ data["printerTonerMax"] = borgo.tonermax //It's a variable, might as well use it
+ data["thrustersInstalled"] = borgo.ionpulse //If we have a thruster uprade
+ data["thrustersStatus"] = "[borgo.ionpulse_on?"ACTIVE":"DISABLED"]" //Feedback for thruster status
+ data["lampPowerUse"] = clamp(borgo.lamp_enabled ? (borgo.lamp_intensity - 2) : 0 * 2,1,borgo.cell.charge)
+
+ //DEBUG -- Cover, TRUE for locked
+ data["cover"] = "[borgo.locked? "LOCKED":"UNLOCKED"]"
+ //Ability to move. FAULT if lockdown wire is cut, DISABLED if borg locked, ENABLED otherwise
+ data["locomotion"] = "[borgo.wires.is_cut(WIRE_LOCKDOWN)?"FAULT":"[borgo.locked_down?"DISABLED":"ENABLED"]"]"
+ //Module wire. FAULT if cut, NOMINAL otherwise
+ data["wireModule"] = "[borgo.wires.is_cut(WIRE_RESET_MODULE)?"FAULT":"NOMINAL"]"
+ //DEBUG -- Camera(net) wire. FAULT if cut (or no cameranet camera), DISABLED if pulse-disabled, NOMINAL otherwise
+ data["wireCamera"] = "[!borgo.builtInCamera || borgo.wires.is_cut(WIRE_CAMERA)?"FAULT":"[borgo.builtInCamera.can_use()?"NOMINAL":"DISABLED"]"]"
+ //AI wire. FAULT if wire is cut, CONNECTED if connected to AI, READY otherwise
+ data["wireAI"] = "[borgo.wires.is_cut(WIRE_AI)?"FAULT":"[borgo.connected_ai?"CONNECTED":"READY"]"]"
+ //Law sync wire. FAULT if cut, NOMINAL otherwise
+ data["wireLaw"] = "[borgo.wires.is_cut(WIRE_LAWSYNC)?"FAULT":"NOMINAL"]"
+
+ return data
+
+/datum/computer_file/program/robotact/ui_static_data(mob/user)
+ var/list/data = list()
+ if(!iscyborg(user))
+ return data
+ var/mob/living/silicon/robot/borgo = user
+
+ data["Laws"] = borgo.laws.get_law_list(TRUE, TRUE, FALSE)
+ data["borgLog"] = tablet.borglog
+ data["borgUpgrades"] = borgo.upgrades
+ return data
+
+/datum/computer_file/program/robotact/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/living/silicon/robot/borgo = tablet.borgo
+
+ switch(action)
+ if("coverunlock")
+ if(borgo.locked)
+ borgo.locked = FALSE
+ borgo.update_icons()
+ if(borgo.emagged)
+ borgo.logevent("ChÃ¥vÃis cover lock has been [borgo.locked ? "engaged" : "released"]") //"The cover interface glitches out for a split second"
+ else
+ borgo.logevent("Chassis cover lock has been [borgo.locked ? "engaged" : "released"]")
+
+ if("lawchannel")
+ borgo.set_autosay()
+
+ if("lawstate")
+ borgo.checklaws()
+
+ if("alertPower")
+ if(borgo.stat == CONSCIOUS)
+ if(!borgo.cell || !borgo.cell.charge)
+ borgo.visible_message("The power warning light on [borgo] flashes urgently.", \
+ "You announce you are operating in low power mode.")
+ playsound(borgo, 'sound/machines/buzz-two.ogg', 50, FALSE)
+
+ if("toggleSensors")
+ borgo.toggle_sensors()
+
+ if("viewImage")
+ if(borgo.connected_ai)
+ borgo.connected_ai.aicamera?.viewpictures(usr)
+ else
+ borgo.aicamera?.viewpictures(usr)
+
+ if("printImage")
+ var/obj/item/camera/siliconcam/robot_camera/borgcam = borgo.aicamera
+ borgcam?.borgprint(usr)
+
+ if("toggleThrusters")
+ borgo.toggle_ionpulse()
+
+ if("lampIntensity")
+ borgo.lamp_intensity = params["ref"]
+ borgo.toggle_headlamp(FALSE, TRUE)
+
+/**
+ * Forces a full update of the UI, if currently open.
+ *
+ * Forces an update that includes refreshing ui_static_data. Called by
+ * law changes and borg log additions.
+ */
+/datum/computer_file/program/robotact/proc/force_full_update()
+ if(tablet)
+ var/datum/tgui/active_ui = SStgui.get_open_ui(tablet.borgo, src)
+ if(active_ui)
+ active_ui.send_full_update()
diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm
index e5c133de20..63d3162579 100644
--- a/code/modules/modular_computers/hardware/hard_drive.dm
+++ b/code/modules/modular_computers/hardware/hard_drive.dm
@@ -184,3 +184,9 @@
max_capacity = 32
icon_state = "ssd_micro"
w_class = WEIGHT_CLASS_TINY
+
+// For borg integrated tablets. No downloader.
+/obj/item/computer_hardware/hard_drive/small/integrated/install_default_programs()
+ store_file(new /datum/computer_file/program/computerconfig(src)) // Computer configuration utility, allows hardware control and displays more info than status bar
+ store_file(new /datum/computer_file/program/filemanager(src)) // File manager, allows text editor functions and basic file manipulation.
+ store_file(new /datum/computer_file/program/robotact(src))
diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm
index 04bf494fe4..625ead6ed7 100644
--- a/code/modules/modular_computers/hardware/network_card.dm
+++ b/code/modules/modular_computers/hardware/network_card.dm
@@ -77,3 +77,23 @@
power_usage = 100 // Better range but higher power usage.
icon_state = "net_wired"
w_class = WEIGHT_CLASS_NORMAL
+
+/obj/item/computer_hardware/network_card/integrated //Borg tablet version, only works while the borg has power and is not locked
+ name = "cyborg data link"
+
+/obj/item/computer_hardware/network_card/integrated/get_signal(specific_action = 0)
+ var/obj/item/modular_computer/tablet/integrated/modularInterface = holder
+
+ if(!modularInterface || !istype(modularInterface))
+ return FALSE //wrong type of tablet
+
+ if(!modularInterface.borgo)
+ return FALSE //No borg found
+
+ if(modularInterface.borgo.locked_down)
+ return FALSE //lockdown restricts borg networking
+
+ if(!modularInterface.borgo.cell || modularInterface.borgo.cell.charge == 0)
+ return FALSE //borg cell dying restricts borg networking
+
+ return ..()
diff --git a/code/modules/modular_computers/hardware/recharger.dm b/code/modules/modular_computers/hardware/recharger.dm
index 13ae6c1f39..2fee94c5f3 100644
--- a/code/modules/modular_computers/hardware/recharger.dm
+++ b/code/modules/modular_computers/hardware/recharger.dm
@@ -89,3 +89,12 @@
/obj/item/computer_hardware/recharger/lambda/use_power(amount, charging=0)
return 1
+
+/// This recharger exists only in borg built-in tablets. I would have tied it to the borg's cell but
+/// the program that displays laws should always be usable, and the exceptions were starting to pile.
+/obj/item/computer_hardware/recharger/cyborg
+ name = "modular interface power harness"
+ desc = "A standard connection to power a small computer device from a cyborg's chassis."
+
+/obj/item/computer_hardware/recharger/cyborg/use_power(amount, charging=0)
+ return TRUE
diff --git a/code/modules/photography/camera/silicon_camera.dm b/code/modules/photography/camera/silicon_camera.dm
index 28a080d5f1..a6b8093f27 100644
--- a/code/modules/photography/camera/silicon_camera.dm
+++ b/code/modules/photography/camera/silicon_camera.dm
@@ -74,14 +74,6 @@
else
return ..()
-/obj/item/camera/siliconcam/robot_camera/verb/borgprinting()
- set category ="Robot Commands"
- set name = "Print Image"
- set src in usr
- if(usr.stat == DEAD)
- return
- borgprint(usr)
-
/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/user)
var/mob/living/silicon/robot/C = loc
if(!istype(C) || C.toner < 20)
diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm
index fa88cc1338..1410dc6221 100644
--- a/code/modules/tgui/states.dm
+++ b/code/modules/tgui/states.dm
@@ -83,8 +83,9 @@
return ..()
/mob/living/silicon/robot/shared_ui_interaction(src_object)
- // Disable UIs if the Borg is unpowered or locked.
- if(!cell || cell.charge <= 0 || locked_down)
+ // Disable UIs if the object isn't installed in the borg AND the borg is either locked, has a dead cell, or no cell.
+ var/atom/device = src_object
+ if((istype(device) && device.loc != src) && (!cell || cell.charge <= 0 || locked_down))
return UI_DISABLED
return ..()
diff --git a/icons/mob/screen_cyborg.dmi b/icons/mob/screen_cyborg.dmi
index 632443036e..3d69a52d32 100644
Binary files a/icons/mob/screen_cyborg.dmi and b/icons/mob/screen_cyborg.dmi differ
diff --git a/icons/obj/modular_tablet.dmi b/icons/obj/modular_tablet.dmi
index 32edb57475..b5de2701e9 100644
Binary files a/icons/obj/modular_tablet.dmi and b/icons/obj/modular_tablet.dmi differ
diff --git a/icons/program_icons/robotact.gif b/icons/program_icons/robotact.gif
new file mode 100644
index 0000000000..cf11eb184b
Binary files /dev/null and b/icons/program_icons/robotact.gif differ
diff --git a/tgstation.dme b/tgstation.dme
index f733aee27c..eadf0983b0 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2821,6 +2821,7 @@
#include "code\modules\modular_computers\computers\item\laptop.dm"
#include "code\modules\modular_computers\computers\item\laptop_presets.dm"
#include "code\modules\modular_computers\computers\item\processor.dm"
+#include "code\modules\modular_computers\computers\item\tablet-silicon.dm"
#include "code\modules\modular_computers\computers\item\tablet.dm"
#include "code\modules\modular_computers\computers\item\tablet_presets.dm"
#include "code\modules\modular_computers\computers\machinery\console_presets.dm"
@@ -2847,6 +2848,7 @@
#include "code\modules\modular_computers\file_system\programs\powermonitor.dm"
#include "code\modules\modular_computers\file_system\programs\radar.dm"
#include "code\modules\modular_computers\file_system\programs\robocontrol.dm"
+#include "code\modules\modular_computers\file_system\programs\robotact.dm"
#include "code\modules\modular_computers\file_system\programs\sm_monitor.dm"
#include "code\modules\modular_computers\file_system\programs\antagonist\contract_uplink.dm"
#include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm"