Files
Bubberstation/code/modules/mob/living/silicon/robot/robot.dm
SkyratBot d8da1153b7 [MIRROR] Biddle Verbs: Queues the Most Expensive Verbs for the Next Tick if the Server Is Overloaded [MDB IGNORE] (#15329)
* Biddle Verbs: Queues the Most Expensive Verbs for the Next Tick if the Server Is Overloaded (#65589)

This pr goes through: /client/Click(), /client/Topic(), /mob/living/verb/resist(), /mob/verb/quick_equip(), /mob/verb/examinate(), and /mob/verb/mode() and makes them queue their functionality to a subsystem to execute in the next tick if the server is overloaded. To do this a new subsystem is made to handle most verbs called SSverb_manager, if the server is overloaded the verb queues itself in the subsystem and returns, then near the start of the next tick that verb is resumed with the provided callback. The verbs are called directly after SSinput, and the subsystem does not yield until its queue is completely finished.

The exception are clicks from player input since they are extremely important for the feeling of responsiveness. I considered not queuing them but theyre too expensive not to, suffering from a death of a thousand cuts performance wise from many many things in the process adding up. Instead clicks are executed at the very start of the next tick, as the first action that SSinput completes, before player movement is processed even.

A few months ago, before I died I was trying to figure out why games at midpop (40-50 people) had non zero and consistent time dilation without maptick being consistently above 28% (which is when the MC stops yielding for maptick if its overloaded). I found it out, started working on this pr, then promptly died. luckily im a bit less dead now

the current MC has a problem: the cost of verbs is completely and totally invisible to it, it cannot account for them. Why is this bad? because verbs are the last thing to execute in the tick, after the MC and SendMaps have finished executing.
tick diagram2
If the MC is overloaded and uses 100% of the time it allots itself this means that if SendMaps uses the amount its expected to take, verbs have at most 2% of the tick to execute in before they are overtiming and thus delaying the start of the next tick. This is bad, and im 99% sure this is the majority of our overtime.

Take Click() for example. Click isnt listed as a verb but since its called as a result of client commands its executed at the end of the tick like other verbs. in this random 80 pop sybil round profile i had saved on my computer sybil 80 pop (2).txt /client/Click() has an overtime of only 1.8 seconds, which isnt that bad. however it has a self cpu of 2.5 seconds meaning 1.8/2.5 = 72% of its time is overtiming, and it also is calling 80.2 seconds worth of total cpu, which means that more than 57.7 seconds of overtime is attributed to just /client/Click() executing at the very end of a tick. the reason why this isnt obvious is just because the verbs themselves typically dont have high enough self cpu to get high enough on the rankings of overtiming procs to be noticed, all of their overtime is distributed among a ton of procs they call in the chain.

Since i cant guarantee the MC resumes at the very start of the next tick due to other sleeping procs almost always resuming first: I time the duration between clicks being queued up for the next tick and when theyre actually executed. if it exceeds 20 milliseconds of added latency (less than one tenth the average human reaction time) clicks will execute immediately instead of queuing, this should make instances where a player can notice the added latency a vanishingly small minority of cases. still, this should be tm'd

* Biddle Verbs: Queues the Most Expensive Verbs for the Next Tick if the Server Is Overloaded

Co-authored-by: Kylerace <kylerlumpkin1@gmail.com>
2022-07-31 22:03:59 +00:00

1034 lines
34 KiB
Plaintext

/mob/living/silicon/robot/Initialize(mapload)
spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src)
spark_system.attach(src)
ADD_TRAIT(src, TRAIT_CAN_STRIP, INNATE_TRAIT)
AddComponent(/datum/component/tippable, \
tip_time = 3 SECONDS, \
untip_time = 2 SECONDS, \
self_right_time = 60 SECONDS, \
post_tipped_callback = CALLBACK(src, .proc/after_tip_over), \
post_untipped_callback = CALLBACK(src, .proc/after_righted), \
roleplay_friendly = TRUE, \
roleplay_emotes = list(/datum/emote/living/human/buzz, /datum/emote/living/human/buzz2, /datum/emote/living/human/beep, /datum/emote/living/human/beep2), \
roleplay_callback = CALLBACK(src, .proc/untip_roleplay)) // SKYRAT EDIT CHANGE
wires = new /datum/wires/robot(src)
AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES)
AddElement(/datum/element/ridable, /datum/component/riding/creature/cyborg)
RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater)
robot_modules_background = new()
robot_modules_background.icon_state = "block"
robot_modules_background.plane = HUD_PLANE
inv1 = new /atom/movable/screen/robot/module1()
inv2 = new /atom/movable/screen/robot/module2()
inv3 = new /atom/movable/screen/robot/module3()
ident = rand(1, 999)
previous_health = health
if(ispath(cell))
cell = new cell(src)
create_modularInterface()
model = new /obj/item/robot_model(src)
model.rebuild_modules()
if(lawupdate)
make_laws()
for (var/law in laws.inherent)
lawcheck += law
if(!TryConnectToAI())
lawupdate = FALSE
if(!scrambledcodes && !builtInCamera)
builtInCamera = new (src)
builtInCamera.c_tag = real_name
builtInCamera.network = list("ss13")
builtInCamera.internal_light = FALSE
if(wires.is_cut(WIRE_CAMERA))
builtInCamera.status = 0
update_icons()
. = ..()
//If this body is meant to be a borg controlled by the AI player
if(shell)
make_shell()
else
//MMI stuff. Held togheter by magic. ~Miauw
if(!mmi?.brainmob)
mmi = new (src)
mmi.brain = new /obj/item/organ/internal/brain(mmi)
mmi.brain.organ_flags |= ORGAN_FROZEN
mmi.brain.name = "[real_name]'s brain"
mmi.name = "[initial(mmi.name)]: [real_name]"
mmi.set_brainmob(new /mob/living/brain(mmi))
mmi.brainmob.name = src.real_name
mmi.brainmob.real_name = src.real_name
mmi.brainmob.container = mmi
mmi.update_appearance()
setup_default_name()
aicamera = new/obj/item/camera/siliconcam/robot_camera(src)
toner = tonermax
diag_hud_set_borgcell()
logevent("System brought online.")
alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z))
RegisterSignal(alert_control.listener, COMSIG_ALARM_LISTENER_TRIGGERED, .proc/alarm_triggered)
RegisterSignal(alert_control.listener, COMSIG_ALARM_LISTENER_CLEARED, .proc/alarm_cleared)
alert_control.listener.RegisterSignal(src, COMSIG_LIVING_DEATH, /datum/alarm_listener/proc/prevent_alarm_changes)
alert_control.listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, /datum/alarm_listener/proc/allow_alarm_changes)
/mob/living/silicon/robot/model/syndicate/Initialize(mapload)
. = ..()
laws = new /datum/ai_laws/syndicate_override()
addtimer(CALLBACK(src, .proc/show_playstyle), 5)
/mob/living/silicon/robot/model/syndicate/create_modularInterface()
if(!modularInterface)
modularInterface = new /obj/item/modular_computer/tablet/integrated/syndicate(src)
modularInterface.saved_identification = real_name
modularInterface.saved_job = "Cyborg"
return ..()
/**
* Sets the tablet theme and icon
*
* These variables are based on if the borg is a syndicate type or is emagged. This gets used in model change code
* and also borg emag code.
*/
/mob/living/silicon/robot/proc/set_modularInterface_theme()
if(istype(model, /obj/item/robot_model/syndicate) || emagged)
modularInterface.device_theme = "syndicate"
modularInterface.icon_state = "tablet-silicon-syndicate"
modularInterface.icon_state_powered = "tablet-silicon-syndicate"
modularInterface.icon_state_unpowered = "tablet-silicon-syndicate"
else
modularInterface.device_theme = "ntos"
modularInterface.icon_state = "tablet-silicon"
modularInterface.icon_state_powered = "tablet-silicon"
modularInterface.icon_state_unpowered = "tablet-silicon"
modularInterface.update_icon()
//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
/mob/living/silicon/robot/Destroy()
var/atom/T = drop_location()//To hopefully prevent run time errors.
if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside.
if(T)
mmi.forceMove(T)
if(mmi.brainmob)
if(mmi.brainmob.stat == DEAD)
mmi.brainmob.set_stat(CONSCIOUS)
mind.transfer_to(mmi.brainmob)
mmi.update_appearance()
else
to_chat(src, span_boldannounce("Oops! Something went very wrong, your MMI was unable to receive your mind. You have been ghosted. Please make a bug report so we can fix this bug."))
ghostize()
stack_trace("Borg MMI lacked a brainmob")
mmi = null
if(modularInterface)
QDEL_NULL(modularInterface)
if(connected_ai)
set_connected_ai(null)
if(shell)
GLOB.available_ai_shells -= src
else
if(T && istype(radio) && istype(radio.keyslot))
radio.keyslot.forceMove(T)
radio.keyslot = null
QDEL_NULL(wires)
QDEL_NULL(model)
QDEL_NULL(eye_lights)
QDEL_NULL(inv1)
QDEL_NULL(inv2)
QDEL_NULL(inv3)
QDEL_NULL(hands)
QDEL_NULL(spark_system)
QDEL_NULL(alert_control)
cell = null
return ..()
/mob/living/silicon/robot/Topic(href, href_list)
. = ..()
//Show alerts window if user clicked on "Show alerts" in chat
if(href_list["showalerts"])
alert_control.ui_interact(src)
// SKYRAT EDIT ADDITION -- Customization
if(href_list["lookup_info"])
tgui.holder = src
tgui.ui_interact(usr) //datum has a tgui component, here we open the window
// SKYRAT EDIT END
/mob/living/silicon/robot/get_cell()
return cell
/mob/living/silicon/robot/proc/pick_model()
if(model.type != /obj/item/robot_model)
return
if(wires.is_cut(WIRE_RESET_MODEL))
to_chat(src,span_userdanger("ERROR: Model installer reply timeout. Please check internal connections."))
return
if(lockcharge == TRUE)
to_chat(src,span_userdanger("ERROR: Lockdown is engaged. Please disengage lockdown to pick module."))
return
// SKYRAT EDIT START - Making the cyborg model list static to reduce how many times it's generated.
if(!length(GLOB.cyborg_model_list))
GLOB.cyborg_model_list = list(
"Engineering" = /obj/item/robot_model/engineering,
"Medical" = /obj/item/robot_model/medical,
"Cargo" = /obj/item/robot_model/cargo,
"Miner" = /obj/item/robot_model/miner,
"Janitor" = /obj/item/robot_model/janitor,
"Service" = /obj/item/robot_model/service,
)
if(!CONFIG_GET(flag/disable_peaceborg))
GLOB.cyborg_model_list["Peacekeeper"] = /obj/item/robot_model/peacekeeper
if(!CONFIG_GET(flag/disable_secborg))
GLOB.cyborg_model_list["Security"] = /obj/item/robot_model/security
for(var/model in GLOB.cyborg_model_list)
// Creating the lists here since we know all the model icons will need them right after.
GLOB.cyborg_all_models_icon_list[model] = list()
// Create radial menu for choosing borg model
if(!length(GLOB.cyborg_base_models_icon_list))
for(var/option in GLOB.cyborg_model_list)
var/obj/item/robot_model/model = GLOB.cyborg_model_list[option]
var/model_icon = initial(model.cyborg_base_icon)
GLOB.cyborg_base_models_icon_list[option] = image(icon = 'modular_skyrat/master_files/icons/mob/robots.dmi', icon_state = model_icon) // SKYRAT EDIT - CARGO BORGS - ORIGINAL: model_icons[option] = image(icon = 'icons/mob/robots.dmi', icon_state = model_icon)
// SKYRAT EDIT END
var/input_model = show_radial_menu(src, src, GLOB.cyborg_base_models_icon_list, radius = 42)
if(!input_model || model.type != /obj/item/robot_model)
return
model.transform_to(GLOB.cyborg_model_list[input_model])
/// Used to setup the a basic and (somewhat) unique name for the robot.
/mob/living/silicon/robot/proc/setup_default_name()
var/new_name
if(GLOB.current_anonymous_theme) //only robotic renames will allow for anything other than the anonymous one
new_name = GLOB.current_anonymous_theme.anonymous_ai_name(FALSE)
else if(custom_name)
new_name = custom_name
else
new_name = get_standard_name()
if(new_name != real_name)
fully_replace_character_name(real_name, new_name)
/// Updates the borg name taking the client preferences into account.
/mob/living/silicon/robot/proc/updatename(client/pref_source)
if(shell)
return
if(!pref_source)
pref_source = client
var/changed_name = ""
if(GLOB.current_anonymous_theme) //only robotic renames will allow for anything other than the anonymous one
changed_name = GLOB.current_anonymous_theme.anonymous_ai_name(FALSE)
else if(custom_name)
changed_name = custom_name
else if(pref_source && pref_source.prefs.read_preference(/datum/preference/name/cyborg) != DEFAULT_CYBORG_NAME)
apply_pref_name(/datum/preference/name/cyborg, pref_source)
return //built in camera handled in proc
else
changed_name = get_standard_name()
fully_replace_character_name(real_name, changed_name)
/mob/living/silicon/robot/proc/get_standard_name()
return "[(designation ? "[designation] " : "")][mmi.braintype]-[ident]"
/mob/living/silicon/robot/proc/ionpulse()
if(!ionpulse_on)
return
if(cell.charge <= 10)
toggle_ionpulse()
return
cell.charge -= 10
return TRUE
/mob/living/silicon/robot/proc/toggle_ionpulse()
if(!ionpulse)
to_chat(src, span_notice("No thrusters are installed!"))
return
if(!ion_trail)
ion_trail = new
ion_trail.set_up(src)
ionpulse_on = !ionpulse_on
to_chat(src, span_notice("You [ionpulse_on ? null :"de"]activate your ion thrusters."))
if(ionpulse_on)
ion_trail.start()
else
ion_trail.stop()
/mob/living/silicon/robot/get_status_tab_items()
. = ..()
. += ""
if(cell)
. += "Charge Left: [cell.charge]/[cell.maxcharge]"
else
. += "No Cell Inserted!"
if(model)
for(var/datum/robot_energy_storage/st in model.storages)
. += "[st.name]: [st.energy]/[st.max_energy]"
if(connected_ai)
. += "Master AI: [connected_ai.name]"
/mob/living/silicon/robot/proc/alarm_triggered(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
queueAlarm("--- [alarm_type] alarm detected in [source_area.name]!", alarm_type)
/mob/living/silicon/robot/proc/alarm_cleared(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
queueAlarm("--- [alarm_type] alarm in [source_area.name] has been cleared.", alarm_type, FALSE)
/mob/living/silicon/robot/can_interact_with(atom/A)
if (A == modularInterface)
return TRUE //bypass for borg tablets
if (low_power_mode)
return FALSE
return ..()
/mob/living/silicon/robot/proc/after_tip_over(mob/user)
if(hat)
hat.forceMove(drop_location())
unbuckle_all_mobs()
///For any special cases for robots after being righted.
/mob/living/silicon/robot/proc/after_righted(mob/user)
return
/mob/living/silicon/robot/proc/allowed(mob/M)
//check if it doesn't require any access at all
if(check_access(null))
return TRUE
if(ishuman(M))
var/mob/living/carbon/human/H = M
//if they are holding or wearing a card that has access, that works
if(check_access(H.get_active_held_item()) || check_access(H.wear_id))
return TRUE
else if(isalien(M))
var/mob/living/carbon/george = M
//they can only hold things :(
if(isitem(george.get_active_held_item()))
return check_access(george.get_active_held_item())
return FALSE
/mob/living/silicon/robot/proc/check_access(obj/item/card/id/I)
if(!istype(req_access, /list)) //something's very wrong
return TRUE
var/list/L = req_access
if(!L.len) //no requirements
return TRUE
if(!istype(I, /obj/item/card/id) && isitem(I))
I = I.GetID()
if(!I || !I.access) //not ID or no access
return FALSE
for(var/req in req_access)
if(!(req in I.access)) //doesn't have this access
return FALSE
return TRUE
/mob/living/silicon/robot/regenerate_icons()
return update_icons()
/mob/living/silicon/robot/update_icons()
cut_overlays()
SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
icon_state = model.cyborg_base_icon
if(stat != DEAD && !(HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || IsStun() || IsParalyzed() || low_power_mode)) //Not dead, not stunned.
if(!eye_lights)
eye_lights = new()
if(lamp_enabled || lamp_doom)
eye_lights.icon_state = "[model.special_light_key ? "[model.special_light_key]":"[model.cyborg_base_icon]"]_l"
eye_lights.color = lamp_doom? COLOR_RED : lamp_color
eye_lights.plane = ABOVE_LIGHTING_PLANE //glowy eyes
else
eye_lights.icon_state = "[model.special_light_key ? "[model.special_light_key]":"[model.cyborg_base_icon]"]_e"
eye_lights.color = COLOR_WHITE
eye_lights.plane = ABOVE_GAME_PLANE
eye_lights.icon = icon
add_overlay(eye_lights)
if(opened)
if(wiresexposed)
add_overlay("ov-opencover +w")
else if(cell)
add_overlay("ov-opencover +c")
else
add_overlay("ov-opencover -c")
if(hat)
var/mutable_appearance/head_overlay = hat.build_worn_icon(default_layer = 20, default_icon_file = 'icons/mob/clothing/head.dmi')
head_overlay.pixel_y += hat_offset
add_overlay(head_overlay)
update_fire()
/mob/living/silicon/robot/proc/self_destruct(mob/usr)
var/turf/groundzero = get_turf(src)
message_admins(span_notice("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(src, client)] at [ADMIN_VERBOSEJMP(groundzero)]!"))
log_game("[key_name(usr)] detonated [key_name(src)]!")
log_combat(usr, src, "detonated cyborg")
log_silicon("CYBORG: [key_name(src)] has been detonated by [key_name(usr)].")
if(connected_ai)
to_chat(connected_ai, "<br><br>[span_alert("ALERT - Cyborg detonation detected: [name]")]<br>")
if(emagged)
QDEL_NULL(mmi)
explosion(src, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 4, flame_range = 2)
else
explosion(src, devastation_range = -1, light_impact_range = 2)
gib()
/mob/living/silicon/robot/proc/UnlinkSelf()
set_connected_ai(null)
lawupdate = FALSE
set_lockcharge(FALSE)
scrambledcodes = TRUE
log_silicon("CYBORG: [key_name(src)] has been unlinked from an AI.")
//Disconnect it's camera so it's not so easily tracked.
if(!QDELETED(builtInCamera))
QDEL_NULL(builtInCamera)
// I'm trying to get the Cyborg to not be listed in the camera list
// Instead of being listed as "deactivated". The downside is that I'm going
// to have to check if every camera is null or not before doing anything, to prevent runtime errors.
// I could change the network to null but I don't know what would happen, and it seems too hacky for me.
/mob/living/silicon/robot/mode()
set name = "Activate Held Object"
set category = "IC"
set src = usr
return ..()
/mob/living/silicon/robot/execute_mode()
if(incapacitated())
return
var/obj/item/W = get_active_held_item()
if(W)
W.attack_self(src)
/mob/living/silicon/robot/proc/SetLockdown(state = TRUE)
// They stay locked down if their wire is cut.
if(wires?.is_cut(WIRE_LOCKDOWN))
state = TRUE
if(state)
throw_alert(ALERT_HACKED, /atom/movable/screen/alert/locked)
else
clear_alert(ALERT_HACKED)
set_lockcharge(state)
///Reports the event of the change in value of the lockcharge variable.
/mob/living/silicon/robot/proc/set_lockcharge(new_lockcharge)
if(new_lockcharge == lockcharge)
return
. = lockcharge
lockcharge = new_lockcharge
if(lockcharge)
if(!.)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, LOCKED_BORG_TRAIT)
else if(.)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LOCKED_BORG_TRAIT)
logevent("System lockdown [lockcharge?"triggered":"released"].")
/mob/living/silicon/robot/proc/SetEmagged(new_state)
emagged = new_state
model.rebuild_modules()
update_icons()
if(emagged)
throw_alert(ALERT_HACKED, /atom/movable/screen/alert/hacked)
else
clear_alert(ALERT_HACKED)
set_modularInterface_theme()
/// Special handling for getting hit with a light eater
/mob/living/silicon/robot/proc/on_light_eater(mob/living/silicon/robot/source, datum/light_eater)
SIGNAL_HANDLER
if(lamp_enabled)
smash_headlamp()
return COMPONENT_BLOCK_LIGHT_EATER
/**
* 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, span_danger("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))
set_light_on(lamp_functional && stat != DEAD && lamp_doom) //If the lamp isn't broken and borg isn't dead, doomsday borgs cannot disable their light fully.
set_light_color(COLOR_RED) //This should only matter for doomsday borgs, as any other time the lamp will be off and the color not seen
set_light_range(1) //Again, like above, this only takes effect when the light is forced on by doomsday mode.
lamp_enabled = FALSE
lampButton?.update_appearance()
update_icons()
return
set_light_range(lamp_intensity)
set_light_color(lamp_doom? COLOR_RED : lamp_color) //Red for doomsday killborgs, borg's choice otherwise
set_light_on(TRUE)
lamp_enabled = TRUE
lampButton?.update_appearance()
update_icons()
/mob/living/silicon/robot/proc/deconstruct()
SEND_SIGNAL(src, COMSIG_BORG_SAFE_DECONSTRUCT)
if(shell)
undeploy()
var/turf/T = get_turf(src)
if (robot_suit)
robot_suit.forceMove(T)
robot_suit.l_leg.forceMove(T)
robot_suit.l_leg = null
robot_suit.r_leg.forceMove(T)
robot_suit.r_leg = null
new /obj/item/stack/cable_coil(T, robot_suit.chest.wired)
robot_suit.chest.forceMove(T)
robot_suit.chest.wired = FALSE
robot_suit.chest = null
robot_suit.l_arm.forceMove(T)
robot_suit.l_arm = null
robot_suit.r_arm.forceMove(T)
robot_suit.r_arm = null
robot_suit.head.forceMove(T)
robot_suit.head.flash1.forceMove(T)
robot_suit.head.flash1.burn_out()
robot_suit.head.flash1 = null
robot_suit.head.flash2.forceMove(T)
robot_suit.head.flash2.burn_out()
robot_suit.head.flash2 = null
robot_suit.head = null
robot_suit.update_appearance()
else
new /obj/item/robot_suit(T)
new /obj/item/bodypart/l_leg/robot(T)
new /obj/item/bodypart/r_leg/robot(T)
new /obj/item/stack/cable_coil(T, 1)
new /obj/item/bodypart/chest/robot(T)
new /obj/item/bodypart/l_arm/robot(T)
new /obj/item/bodypart/r_arm/robot(T)
new /obj/item/bodypart/head/robot(T)
var/b
for(b=0, b!=2, b++)
var/obj/item/assembly/flash/handheld/F = new /obj/item/assembly/flash/handheld(T)
F.burn_out()
if (cell) //Sanity check.
cell.forceMove(T)
cell = null
qdel(src)
/mob/living/silicon/robot/proc/notify_ai(notifytype, oldname, newname)
if(!connected_ai)
return
switch(notifytype)
if(AI_NOTIFICATION_NEW_BORG) //New Cyborg
to_chat(connected_ai, "<br><br>[span_notice("NOTICE - New cyborg connection detected: <a href='?src=[REF(connected_ai)];track=[html_encode(name)]'>[name]</a>")]<br>")
if(AI_NOTIFICATION_NEW_MODEL) //New Model
to_chat(connected_ai, "<br><br>[span_notice("NOTICE - Cyborg model change detected: [name] has loaded the [designation] model.")]<br>")
if(AI_NOTIFICATION_CYBORG_RENAMED) //New Name
to_chat(connected_ai, "<br><br>[span_notice("NOTICE - Cyborg reclassification detected: [oldname] is now designated as [newname].")]<br>")
if(AI_NOTIFICATION_AI_SHELL) //New Shell
to_chat(connected_ai, "<br><br>[span_notice("NOTICE - New cyborg shell detected: <a href='?src=[REF(connected_ai)];track=[html_encode(name)]'>[name]</a>")]<br>")
if(AI_NOTIFICATION_CYBORG_DISCONNECTED) //Tampering with the wires
to_chat(connected_ai, "<br><br>[span_notice("NOTICE - Remote telemetry lost with [name].")]<br>")
/mob/living/silicon/robot/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE, need_hands = FALSE, floor_okay=FALSE)
if(lockcharge || low_power_mode)
to_chat(src, span_warning("You can't do that right now!"))
return FALSE
return ..()
/mob/living/silicon/robot/updatehealth()
..()
if(!model.breakable_modules)
return
/// the current percent health of the robot (-1 to 1)
var/percent_hp = health/maxHealth
if(health <= previous_health) //if change in health is negative (we're losing hp)
if(percent_hp <= 0.5)
break_cyborg_slot(3)
if(percent_hp <= 0)
break_cyborg_slot(2)
if(percent_hp <= -0.5)
break_cyborg_slot(1)
else //if change in health is positive (we're gaining hp)
if(percent_hp >= 0.5)
repair_cyborg_slot(3)
if(percent_hp >= 0)
repair_cyborg_slot(2)
if(percent_hp >= -0.5)
repair_cyborg_slot(1)
previous_health = health
/mob/living/silicon/robot/update_sight()
if(!client)
return
if(stat == DEAD)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
sight = null
else if(is_secret_level(z))
sight = initial(sight)
else
sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
see_in_dark = 8
see_invisible = SEE_INVISIBLE_OBSERVER
return
see_invisible = initial(see_invisible)
see_in_dark = initial(see_in_dark)
sight = initial(sight)
lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
if(client.eye != src)
var/atom/A = client.eye
if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates.
return
if(sight_mode & BORGMESON)
sight |= SEE_TURFS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
see_in_dark = 1
if(sight_mode & BORGMATERIAL)
sight |= SEE_OBJS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
see_in_dark = 1
if(sight_mode & BORGXRAY)
sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
see_invisible = SEE_INVISIBLE_LIVING
see_in_dark = 8
if(sight_mode & BORGTHERM)
sight |= SEE_MOBS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
see_invisible = min(see_invisible, SEE_INVISIBLE_LIVING)
see_in_dark = 8
if(see_override)
see_invisible = see_override
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
sight = null
sync_lighting_plane_alpha()
/mob/living/silicon/robot/update_stat()
if(status_flags & GODMODE)
return
if(stat != DEAD)
if(health <= -maxHealth) //die only once
death()
toggle_headlamp(1)
return
if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || IsStun() || IsKnockdown() || IsParalyzed())
set_stat(UNCONSCIOUS)
else
set_stat(CONSCIOUS)
diag_hud_set_status()
diag_hud_set_health()
diag_hud_set_aishell()
update_health_hud()
update_icons() //Updates eye_light overlay
/mob/living/silicon/robot/revive(full_heal = FALSE, admin_revive = FALSE)
if(..()) //successfully ressuscitated from death
if(!QDELETED(builtInCamera) && !wires.is_cut(WIRE_CAMERA))
builtInCamera.toggle_cam(src,0)
if(admin_revive)
locked = TRUE
notify_ai(AI_NOTIFICATION_NEW_BORG)
. = TRUE
toggle_headlamp(FALSE, TRUE) //This will reenable borg headlamps if doomsday is currently going on still.
/mob/living/silicon/robot/fully_replace_character_name(oldname, newname)
. = ..()
if(!.)
return
notify_ai(AI_NOTIFICATION_CYBORG_RENAMED, oldname, newname)
if(!QDELETED(builtInCamera))
builtInCamera.c_tag = real_name
modularInterface.saved_identification = real_name
custom_name = newname
/mob/living/silicon/robot/proc/ResetModel()
SEND_SIGNAL(src, COMSIG_BORG_SAFE_DECONSTRUCT)
uneq_all()
shown_robot_modules = FALSE
for(var/obj/item/storage/bag in model.contents) // drop all of the items that may be stored by the cyborg
for(var/obj/item in bag)
item.forceMove(drop_location())
if(hud_used)
hud_used.update_robot_modules_display()
if (hasExpanded)
//resize = 0.5 //ORIGINAL
resize = 0.8 //SKYRAT EDIT CHANGE - CYBORG
hasExpanded = FALSE
update_transform()
//SKYRAT EDIT ADDITION BEGIN - CYBORG
if (hasShrunk)
hasShrunk = FALSE
resize = (4/3)
update_transform()
hasAffection = FALSE //Just so they can get the affection modules back if they want them.
//SKYRAT EDIT ADDITION END
logevent("Chassis model has been reset.")
log_silicon("CYBORG: [key_name(src)] has reset their cyborg model.")
model.transform_to(/obj/item/robot_model)
// Remove upgrades.
for(var/obj/item/borg/upgrade/I in upgrades)
I.forceMove(get_turf(src))
ionpulse = FALSE
revert_shell()
return TRUE
/mob/living/silicon/robot/model/syndicate/ResetModel()
return
/mob/living/silicon/robot/proc/has_model()
if(!model || model.type == /obj/item/robot_model)
. = FALSE
else
. = TRUE
/mob/living/silicon/robot/proc/update_module_innate()
designation = model.name
if(hands)
hands.icon_state = model.model_select_icon
REMOVE_TRAITS_IN(src, MODEL_TRAIT)
if(model.model_traits)
for(var/trait in model.model_traits)
ADD_TRAIT(src, trait, MODEL_TRAIT)
hat_offset = model.hat_offset
INVOKE_ASYNC(src, .proc/updatename)
/mob/living/silicon/robot/proc/place_on_head(obj/item/new_hat)
if(hat)
hat.forceMove(get_turf(src))
hat = new_hat
new_hat.forceMove(src)
update_icons()
/**
*Checking Exited() to detect if a hat gets up and walks off.
*Drones and pAIs might do this, after all.
*/
/mob/living/silicon/robot/Exited(atom/movable/gone, direction)
if(hat && hat == gone)
hat = null
if(!QDELETED(src)) //Don't update icons if we are deleted.
update_icons()
return ..()
///Use this to add upgrades to robots. It'll register signals for when the upgrade is moved or deleted, if not single use.
/mob/living/silicon/robot/proc/add_to_upgrades(obj/item/borg/upgrade/new_upgrade, mob/user)
if(new_upgrade in upgrades)
return FALSE
if(!user.temporarilyRemoveItemFromInventory(new_upgrade)) //calling the upgrade's dropped() proc /before/ we add action buttons
return FALSE
if(!new_upgrade.action(src, user))
to_chat(user, span_danger("Upgrade error."))
new_upgrade.forceMove(loc) //gets lost otherwise
return FALSE
to_chat(user, span_notice("You apply the upgrade to [src]."))
to_chat(src, "----------------\nNew hardware detected...Identified as \"<b>[new_upgrade]</b>\"...Setup complete.\n----------------")
if(new_upgrade.one_use)
logevent("Firmware [new_upgrade] run successfully.")
qdel(new_upgrade)
return FALSE
upgrades += new_upgrade
new_upgrade.forceMove(src)
RegisterSignal(new_upgrade, COMSIG_MOVABLE_MOVED, .proc/remove_from_upgrades)
RegisterSignal(new_upgrade, COMSIG_PARENT_QDELETING, .proc/on_upgrade_deleted)
logevent("Hardware [new_upgrade] installed successfully.")
///Called when an upgrade is moved outside the robot. So don't call this directly, use forceMove etc.
/mob/living/silicon/robot/proc/remove_from_upgrades(obj/item/borg/upgrade/old_upgrade)
SIGNAL_HANDLER
if(loc == src)
return
old_upgrade.deactivate(src)
upgrades -= old_upgrade
UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
///Called when an applied upgrade is deleted.
/mob/living/silicon/robot/proc/on_upgrade_deleted(obj/item/borg/upgrade/old_upgrade)
SIGNAL_HANDLER
if(!QDELETED(src))
old_upgrade.deactivate(src)
upgrades -= old_upgrade
UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
/**
* make_shell: Makes an AI shell out of a cyborg unit
*
* Arguments:
* * board - B.O.R.I.S. module board used for transforming the cyborg into AI shell
*/
/mob/living/silicon/robot/proc/make_shell(obj/item/borg/upgrade/ai/board)
if(!board)
upgrades |= new /obj/item/borg/upgrade/ai(src)
shell = TRUE
braintype = "AI Shell"
name = "Empty AI Shell-[ident]"
real_name = name
GLOB.available_ai_shells |= src
if(!QDELETED(builtInCamera))
builtInCamera.c_tag = real_name //update the camera name too
diag_hud_set_aishell()
/**
* revert_shell: Reverts AI shell back into a normal cyborg unit
*/
/mob/living/silicon/robot/proc/revert_shell()
if(!shell)
return
undeploy()
for(var/obj/item/borg/upgrade/ai/boris in src)
//A player forced reset of a borg would drop the module before this is called, so this is for catching edge cases
qdel(boris)
shell = FALSE
GLOB.available_ai_shells -= src
name = "Unformatted Cyborg-[ident]"
real_name = name
if(!QDELETED(builtInCamera))
builtInCamera.c_tag = real_name
diag_hud_set_aishell()
/**
* deploy_init: Deploys AI unit into AI shell
*
* Arguments:
* * AI - AI unit that initiated the deployment into the AI shell
*/
/mob/living/silicon/robot/proc/deploy_init(mob/living/silicon/ai/AI)
real_name = "[AI.real_name] [designation] Shell-[ident]"
name = real_name
if(!QDELETED(builtInCamera))
builtInCamera.c_tag = real_name //update the camera name too
mainframe = AI
deployed = TRUE
set_connected_ai(mainframe)
mainframe.connected_robots |= src
lawupdate = TRUE
lawsync()
if(radio && AI.radio) //AI keeps all channels, including Syndie if it is a Traitor
if(AI.radio.syndie)
radio.make_syndie()
radio.subspace_transmission = TRUE
radio.channels = AI.radio.channels
for(var/chan in radio.channels)
radio.secure_radio_connections[chan] = add_radio(radio, GLOB.radiochannels[chan])
diag_hud_set_aishell()
undeployment_action.Grant(src)
/datum/action/innate/undeployment
name = "Disconnect from shell"
desc = "Stop controlling your shell and resume normal core operations."
icon_icon = 'icons/mob/actions/actions_AI.dmi'
button_icon_state = "ai_core"
/datum/action/innate/undeployment/Trigger(trigger_flags)
if(!..())
return FALSE
var/mob/living/silicon/robot/R = owner
R.undeploy()
return TRUE
/mob/living/silicon/robot/proc/undeploy()
if(!deployed || !mind || !mainframe)
return
mainframe.redeploy_action.Grant(mainframe)
mainframe.redeploy_action.last_used_shell = src
mind.transfer_to(mainframe)
deployed = FALSE
mainframe.deployed_shell = null
undeployment_action.Remove(src)
if(radio) //Return radio to normal
radio.recalculateChannels()
if(!QDELETED(builtInCamera))
builtInCamera.c_tag = real_name //update the camera name too
diag_hud_set_aishell()
mainframe.diag_hud_set_deployed()
if(mainframe.laws)
mainframe.laws.show_laws(mainframe) //Always remind the AI when switching
if(mainframe.eyeobj)
mainframe.eyeobj.setLoc(loc)
mainframe = null
/mob/living/silicon/robot/attack_ai(mob/user)
if(shell && (!connected_ai || connected_ai == user))
var/mob/living/silicon/ai/AI = user
AI.deploy_to_shell(src)
/mob/living/silicon/robot/mouse_buckle_handling(mob/living/M, mob/living/user)
//Don't try buckling on INTENT_HARM so that silicons can search people's inventories without loading them
if(can_buckle && isliving(user) && isliving(M) && !(M in buckled_mobs) && ((user != src) || (!combat_mode)))
return user_buckle_mob(M, user, check_loc = FALSE)
/mob/living/silicon/robot/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE, buckle_mob_flags= RIDER_NEEDS_ARM)
if(!is_type_in_typecache(M, can_ride_typecache))
M.visible_message(span_warning("[M] really can't seem to mount [src]..."))
return
if(stat || incapacitated())
return
if(model && !model.allow_riding)
M.visible_message(span_boldwarning("Unfortunately, [M] just can't seem to hold onto [src]!"))
return
buckle_mob_flags= RIDER_NEEDS_ARM // just in case
return ..()
/mob/living/silicon/robot/execute_resist()
. = ..()
if(!has_buckled_mobs())
return
for(var/mob/unbuckle_me_now as anything in buckled_mobs)
unbuckle_mob(unbuckle_me_now, FALSE)
/mob/living/silicon/robot/proc/TryConnectToAI()
set_connected_ai(select_active_ai_with_fewest_borgs(z))
if(connected_ai)
lawsync()
lawupdate = TRUE
return TRUE
picturesync()
return FALSE
/mob/living/silicon/robot/proc/picturesync()
if(connected_ai?.aicamera && aicamera)
for(var/i in aicamera.stored)
connected_ai.aicamera.stored[i] = TRUE
for(var/i in connected_ai.aicamera.stored)
aicamera.stored[i] = TRUE
/mob/living/silicon/robot/proc/charge(datum/source, amount, repairs)
SIGNAL_HANDLER
if(model)
model.respawn_consumable(src, amount * 0.005)
if(cell)
cell.charge = min(cell.charge + amount, cell.maxcharge)
if(repairs)
heal_bodypart_damage(repairs, repairs - 1)
/mob/living/silicon/robot/proc/set_connected_ai(new_ai)
if(connected_ai == new_ai)
return
. = connected_ai
connected_ai = new_ai
if(.)
var/mob/living/silicon/ai/old_ai = .
old_ai.connected_robots -= src
lamp_doom = FALSE
if(connected_ai)
connected_ai.connected_robots |= src
lamp_doom = connected_ai.doomsday_device ? TRUE : FALSE
toggle_headlamp(FALSE, TRUE)
/mob/living/silicon/robot/get_exp_list(minutes)
. = ..()
var/datum/job/cyborg/cyborg_job_ref = SSjob.GetJobType(/datum/job/cyborg)
.[cyborg_job_ref.title] = minutes
/mob/living/silicon/robot/proc/untip_roleplay()
to_chat(src, span_notice("Your frustration has empowered you! You can now right yourself faster!"))
/mob/living/silicon/robot/update_fire_overlay(stacks, on_fire, last_icon_state, suffix = "")
var/fire_icon = "generic_fire[suffix]"
if(!GLOB.fire_appearances[fire_icon])
var/mutable_appearance/new_fire_overlay = mutable_appearance('icons/mob/onfire.dmi', fire_icon, -FIRE_LAYER)
new_fire_overlay.appearance_flags = RESET_COLOR
GLOB.fire_appearances[fire_icon] = new_fire_overlay
if(stacks && on_fire)
if(last_icon_state == fire_icon)
return last_icon_state
add_overlay(GLOB.fire_appearances[fire_icon])
return fire_icon
if(!last_icon_state)
return last_icon_state
cut_overlay(GLOB.fire_appearances[fire_icon])
return null
/// Draw power from the robot
/mob/living/silicon/robot/proc/draw_power(power_to_draw)
cell?.use(power_to_draw)