diff --git a/code/__DEFINES/combat_defines.dm b/code/__DEFINES/combat_defines.dm
index 368e1cb2df4f..88e5cf342111 100644
--- a/code/__DEFINES/combat_defines.dm
+++ b/code/__DEFINES/combat_defines.dm
@@ -20,6 +20,7 @@
#define MAGIC "magic"
#define STUN "stun"
+#define STAM_CRIT "stam_crit"
#define WEAKEN "weaken"
#define KNOCKDOWN "knockdown"
#define PARALYZE "paralize"
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 269585c0239e..e00e6f47f1dd 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -495,6 +495,10 @@
#define COMPONENT_NO_ATTACH (1<<0)
///sent from borg recharge stations: (amount, repairs)
#define COMSIG_PROCESS_BORGCHARGER_OCCUPANT "living_charge"
+///sent when a mob enters a borg charger
+#define COMSIG_ENTERED_BORGCHARGER "enter_charger"
+///sent when a mob exits a borg charger
+#define COMSIG_EXITED_BORGCHARGER "exit_charger"
///sent when a mob/login() finishes: (client)
#define COMSIG_MOB_CLIENT_LOGIN "comsig_mob_client_login"
///sent from borg mobs to itself, for tools to catch an upcoming destroy() due to safe decon (rather than detonation)
diff --git a/code/_onclick/hud/robot_hud.dm b/code/_onclick/hud/robot_hud.dm
index 60310611b71f..95c2861049e4 100644
--- a/code/_onclick/hud/robot_hud.dm
+++ b/code/_onclick/hud/robot_hud.dm
@@ -176,6 +176,8 @@
//Health
mymob.healths = new /atom/movable/screen/healths/robot()
infodisplay += mymob.healths
+ mymob.staminas = new /atom/movable/screen/healths/stamina()
+ infodisplay += mymob.staminas
//Installed Module
mymobR.hands = new /atom/movable/screen/robot/module()
diff --git a/code/controllers/configuration/sections/movement_configuration.dm b/code/controllers/configuration/sections/movement_configuration.dm
index 169259512466..4c5ddd9c08bf 100644
--- a/code/controllers/configuration/sections/movement_configuration.dm
+++ b/code/controllers/configuration/sections/movement_configuration.dm
@@ -9,7 +9,7 @@
/// Move delay for humanoids
var/human_delay = 1.5
/// Move delay for cyborgs
- var/robot_delay = 2.5
+ var/robot_delay = 1.5
/// Move delay for xenomorphs
var/alien_delay = 1.5
/// Move delay for slimes (xenobio, not slimepeople)
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 8285baf82e74..c8373ec00c06 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -239,11 +239,10 @@
/datum/status_effect/cult_stun_mark/on_apply()
. = ..()
- if(!ishuman(owner))
+ if(!isliving(owner))
return
overlay = mutable_appearance('icons/effects/cult_effects.dmi', "cult-mark", ABOVE_MOB_LAYER)
- var/mob/living/carbon/human/H = owner
- H.add_overlay(overlay)
+ owner.add_overlay(overlay)
/datum/status_effect/cult_stun_mark/on_remove()
owner.cut_overlay(overlay)
diff --git a/code/game/gamemodes/cult/blood_magic.dm b/code/game/gamemodes/cult/blood_magic.dm
index a00e4b8a0648..8d0dc1e03d5f 100644
--- a/code/game/gamemodes/cult/blood_magic.dm
+++ b/code/game/gamemodes/cult/blood_magic.dm
@@ -468,20 +468,20 @@
else
to_chat(user, "In a brilliant flash of red, [L] falls to the ground!")
- L.KnockDown(10 SECONDS)
- L.apply_damage(60, STAMINA)
L.apply_status_effect(STATUS_EFFECT_CULT_STUN)
- L.flash_eyes(1, TRUE)
+ L.Silence(6 SECONDS)
if(issilicon(target))
var/mob/living/silicon/S = L
S.emp_act(EMP_HEAVY)
else if(iscarbon(target))
var/mob/living/carbon/C = L
- C.Silence(6 SECONDS)
+ C.KnockDown(10 SECONDS)
+ C.apply_damage(60, STAMINA)
+ C.flash_eyes(1, TRUE)
C.Stuttering(16 SECONDS)
C.CultSlur(20 SECONDS)
C.Jitter(16 SECONDS)
- to_chat(user, "Stun mark applied! Stab them with a dagger, sword or blood spear to stun them fully!")
+ to_chat(user, "Stun mark applied! Stab them with a dagger, sword or blood spear to stun them fully!")
user.do_attack_animation(target)
uses--
..()
diff --git a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm b/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
index ec13c9c829b2..90e9a9f2f6cc 100644
--- a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
+++ b/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
@@ -288,9 +288,6 @@ CONTENTS:
if(!isabductor(user))
return
- if(isrobot(target))
- ..()
- return
if(!isliving(target))
return
@@ -299,6 +296,10 @@ CONTENTS:
user.do_attack_animation(L)
+ if(isrobot(L))
+ L.apply_damage(120, STAMINA) //Force a reboot instantly
+ return
+
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index 614c260162da..b5e3fab26dbc 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -165,6 +165,7 @@
/obj/machinery/recharge_station/proc/go_out()
if(!occupant)
return
+ SEND_SIGNAL(occupant, COMSIG_EXITED_BORGCHARGER)
UnregisterSignal(occupant, COMSIG_MOVABLE_MOVED)
occupant.forceMove(loc)
occupant = null
@@ -200,18 +201,11 @@
var/can_accept_user
if(isrobot(target))
var/mob/living/silicon/robot/R = target
-
- if(R.stat == DEAD)
- //Whoever had it so that a borg with a dead cell can't enter this thing should be shot. --NEO
- return
+
if(occupant)
to_chat(R, "The cell is already occupied!")
return
- if(!R.cell)
- to_chat(R, "Without a power cell, you can't be recharged.")
- //Make sure they actually HAVE a cell, now that they can get in while powerless. --NEO
- return
- can_accept_user = 1
+ can_accept_user = TRUE
else if(ishuman(target))
var/mob/living/carbon/human/H = target
@@ -224,7 +218,7 @@
if(!ismodcontrol(H.back))
if(!H.get_int_organ(/obj/item/organ/internal/cell))
return
- can_accept_user = 1
+ can_accept_user = TRUE
if(!can_accept_user)
to_chat(user, "Only non-organics may enter the recharger!")
@@ -232,6 +226,7 @@
target.stop_pulling()
QDEL_LIST_CONTENTS(target.grabbed_by)
+ SEND_SIGNAL(target, COMSIG_ENTERED_BORGCHARGER)
target.forceMove(src)
occupant = target
RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(go_out))
diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm
index 5faa9203f8ca..6a96e40bc1b7 100644
--- a/code/game/objects/items/devices/flash.dm
+++ b/code/game/objects/items/devices/flash.dm
@@ -28,8 +28,8 @@
/obj/item/flash/proc/clown_check(mob/user)
if(user && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
flash_carbon(user, user, 30 SECONDS, 0)
- return 0
- return 1
+ return FALSE
+ return TRUE
/obj/item/flash/attackby(obj/item/I, mob/user, params)
if(can_overcharge)
@@ -66,7 +66,7 @@
/obj/item/flash/proc/flash_recharge(mob/user)
if(prob(times_used * 2)) //if you use it 5 times in a minute it has a 10% chance to break!
burn_out()
- return 0
+ return FALSE
var/deciseconds_passed = world.time - last_used
for(var/seconds = deciseconds_passed/10, seconds>=10, seconds-=10) //get 1 charge every 10 seconds
@@ -122,20 +122,19 @@
/obj/item/flash/attack(mob/living/M, mob/user)
if(!try_use_flash(user))
- return 0
+ return FALSE
if(iscarbon(M))
flash_carbon(M, user, 10 SECONDS, 1)
if(overcharged)
M.adjust_fire_stacks(6)
M.IgniteMob()
burn_out()
- return 1
+ return TRUE
else if(issilicon(M))
add_attack_logs(user, M, "Flashed with [src]")
- if(M.flash_eyes(affect_silicon = 1))
- M.Weaken(rand(8 SECONDS, 12 SECONDS))
+ if(M.flash_eyes(intensity = 1.25, affect_silicon = TRUE)) // 40 * 1.25 = 50 stamina damage
user.visible_message("[user] overloads [M]'s sensors with [src]!", "You overload [M]'s sensors with [src]!")
- return 1
+ return TRUE
user.visible_message("[user] fails to blind [M] with [src]!", "You fail to blind [M] with [src]!")
/obj/item/flash/afterattack(atom/target, mob/living/user, proximity, params)
@@ -155,7 +154,7 @@
/obj/item/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0)
if(!try_use_flash(user))
- return 0
+ return FALSE
user.visible_message("[user]'s [name] emits a blinding light!", "Your [name] emits a blinding light!")
for(var/mob/living/carbon/M in oviewers(3, null))
flash_carbon(M, user, 6 SECONDS, 0)
@@ -168,7 +167,7 @@
/obj/item/flash/emp_act(severity)
if(!try_use_flash())
- return 0
+ return FALSE
for(var/mob/living/carbon/M in viewers(3, null))
flash_carbon(M, null, 20 SECONDS, 0)
burn_out()
diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm
index 51107e2eba9c..bc8f1a0cc05a 100644
--- a/code/game/objects/items/devices/laserpointer.dm
+++ b/code/game/objects/items/devices/laserpointer.dm
@@ -117,8 +117,7 @@
var/mob/living/silicon/S = target
//20% chance to actually hit the sensors
if(prob(effectchance * diode.rating))
- S.flash_eyes(affect_silicon = 1)
- S.Weaken(rand(10 SECONDS, 20 SECONDS))
+ S.flash_eyes(affect_silicon = TRUE)
to_chat(S, "Your sensors were overloaded by a laser!")
outmsg = "You overload [S] by shining [src] at [S.p_their()] sensors."
diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm
index 1d6bca8cf14a..0db33374ecf1 100644
--- a/code/game/objects/items/devices/traitordevices.dm
+++ b/code/game/objects/items/devices/traitordevices.dm
@@ -380,7 +380,8 @@
if(!M.client)
continue
if(issilicon(M))
- M.Weaken(10 SECONDS)
+ var/mob/living/silicon/robot/R = M
+ R.flash_eyes(3, affect_silicon = TRUE) //Enough stamina damage to instantly force a reboot
else
M.Confused(45 SECONDS)
M.adjustBrainLoss(10)
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index c40fb8d86c3d..e84bdd20be16 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -5,6 +5,10 @@
icon = 'icons/mob/robot_items.dmi'
var/powerneeded // Percentage of power remaining required to run item
+/*
+The old, instant-stun borg arm.
+Keeping it in for adminabuse but the malf one is /obj/item/melee/baton/borg_stun_arm
+*/
/obj/item/borg/stun
name = "electrically-charged arm"
icon_state = "elecarm"
@@ -136,8 +140,3 @@
#undef CYBORG_HUG
#undef CYBORG_SHOCK
#undef CYBORG_CRUSH
-
-/obj/item/borg/overdrive
- name = "Overdrive"
- icon = 'icons/obj/decals.dmi'
- icon_state = "shock"
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index 82282c1f96cd..6cb40e0e3262 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -296,7 +296,6 @@
var/datum/robot_component/cell_component = O.components["power cell"]
cell_component.install(chest.cell)
- chest.cell.forceMove(O)
chest.cell = null
M.forceMove(O) //Should fix cybros run time erroring when blown up. It got deleted before, along with the frame.
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 245656352e44..59a94e277d50 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -253,7 +253,7 @@
/obj/item/borg/upgrade/vtec
name = "robotic VTEC Module"
- desc = "Used to activate a cyborg's VTEC systems, increasing their speed."
+ desc = "Used to activate a cyborg's VTEC systems, allowing them to retain more speed when damaged. Alternatively speeds up slow vehicles."
icon_state = "cyborg_upgrade2"
require_module = TRUE
origin_tech = "engineering=4;materials=5;programming=4"
@@ -264,7 +264,7 @@
to_chat(usr, "There's no room for another VTEC unit!")
return
- R.speed -= 1 // Gotta go fast.
+ R.slowdown_cap = 3.5
return TRUE
/***********************/
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index 88e24cf65540..ca4e38529b5d 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -316,3 +316,8 @@
/obj/item/melee/baton/cattleprod/baton_stun(mob/living/L, mob/user, skip_cooldown = FALSE, ignore_shield_check = FALSE)
if(sparkler.activate())
return ..()
+
+/obj/item/melee/baton/loaded/borg_stun_arm
+ name = "electrically-charged arm"
+ desc = "A piece of scrap metal wired directly to your power cell."
+ hitcost = 100
diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm
index 21dc282a5348..27b1303c5349 100644
--- a/code/modules/antagonists/changeling/powers/shriek.dm
+++ b/code/modules/antagonists/changeling/powers/shriek.dm
@@ -27,8 +27,10 @@
SEND_SOUND(M, sound('sound/effects/clingscream.ogg'))
if(issilicon(M))
+ var/mob/living/silicon/robot/R = M
+ R.disable_component("actuator", 7 SECONDS)
SEND_SOUND(M, sound('sound/weapons/flash.ogg'))
- M.Weaken(rand(10 SECONDS, 20 SECONDS))
+ R.flash_eyes(2, affect_silicon = TRUE) //80 Stamina damage
for(var/obj/machinery/light/L in range(4, user))
L.on = TRUE
diff --git a/code/modules/mob/language.dm b/code/modules/mob/language.dm
index 322f994df1e9..9c8ac77ccc32 100644
--- a/code/modules/mob/language.dm
+++ b/code/modules/mob/language.dm
@@ -605,6 +605,9 @@
if(!message)
return
+ if(HAS_TRAIT(speaker, TRAIT_MUTE))
+ return
+
var/log_message = "(ROBOT) [message]"
log_say(log_message, speaker)
speaker.create_log(SAY_LOG, log_message)
diff --git a/code/modules/mob/living/carbon/carbon_status_procs.dm b/code/modules/mob/living/carbon/carbon_status_procs.dm
index b96a55084a3a..7448ec8262ab 100644
--- a/code/modules/mob/living/carbon/carbon_status_procs.dm
+++ b/code/modules/mob/living/carbon/carbon_status_procs.dm
@@ -12,8 +12,8 @@
stam_regen_start_time = world.time + (STAMINA_REGEN_BLOCK_TIME * stamina_regen_block_modifier)
var/prev = stam_paralyzed
stam_paralyzed = TRUE
- ADD_TRAIT(src, TRAIT_IMMOBILIZED, "stam_crit") // make defines later
- ADD_TRAIT(src, TRAIT_FLOORED, "stam_crit")
- ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, "stam_crit")
+ ADD_TRAIT(src, TRAIT_IMMOBILIZED, STAM_CRIT)
+ ADD_TRAIT(src, TRAIT_FLOORED, STAM_CRIT)
+ ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, STAM_CRIT)
if(!prev && getStaminaLoss() < 120) // Puts you a little further into the initial stamcrit, makes stamcrit harder to outright counter with chems.
adjustStaminaLoss(30, FALSE)
diff --git a/code/modules/mob/living/carbon/carbon_update_status.dm b/code/modules/mob/living/carbon/carbon_update_status.dm
index f98d41b6863d..58b0c6a3a778 100644
--- a/code/modules/mob/living/carbon/carbon_update_status.dm
+++ b/code/modules/mob/living/carbon/carbon_update_status.dm
@@ -29,9 +29,9 @@
else if(stam_paralyzed)
SEND_SIGNAL(src, COMSIG_CARBON_EXIT_STAMINACRIT)
stam_paralyzed = FALSE
- REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, "stam_crit") // make defines later
- REMOVE_TRAIT(src, TRAIT_FLOORED, "stam_crit")
- REMOVE_TRAIT(src, TRAIT_HANDS_BLOCKED, "stam_crit")
+ REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, STAM_CRIT)
+ REMOVE_TRAIT(src, TRAIT_FLOORED, STAM_CRIT)
+ REMOVE_TRAIT(src, TRAIT_HANDS_BLOCKED, STAM_CRIT)
/mob/living/carbon/can_hear()
. = FALSE
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index 6bb553548cc1..18ad834c87e4 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -86,7 +86,7 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
S.message = cultslur(S.message)
verb = "slurs"
- if(!IsVocal())
+ if(!IsVocal() || HAS_TRAIT(src, TRAIT_MUTE))
S.message = ""
return list("verb" = verb)
diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm
index 21473ee908ce..5999eb34eda2 100644
--- a/code/modules/mob/living/silicon/ai/ai_mob.dm
+++ b/code/modules/mob/living/silicon/ai/ai_mob.dm
@@ -680,12 +680,8 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/emp_act(severity)
..()
- if(prob(30))
- switch(pick(1,2))
- if(1)
- view_core()
- if(2)
- ai_call_shuttle()
+ Stun((12 SECONDS) / severity)
+ view_core()
/mob/living/silicon/ai/ex_act(severity)
..()
diff --git a/code/modules/mob/living/silicon/robot/component.dm b/code/modules/mob/living/silicon/robot/component.dm
index f3f11facad02..643d344c9f5f 100644
--- a/code/modules/mob/living/silicon/robot/component.dm
+++ b/code/modules/mob/living/silicon/robot/component.dm
@@ -8,13 +8,21 @@
var/brute_damage = 0
var/electronics_damage = 0
var/max_damage = 30
- var/component_disabled = 0
+ var/component_disabled = FALSE
var/mob/living/silicon/robot/owner
var/external_type = null // The actual device object that has to be installed for this.
var/obj/item/wrapped = null // The wrapped device(e.g. radio), only set if external_type isn't null
+ ///How much a component is contributing to slowdown, set on New to be equal to min_slowdown_factor
+ var/current_slowdown_factor
+ ///The max amount of slowness a component can contribute, set on New to be 1% of the max damage
+ var/max_slowdown_factor
+ ///The minimum amount of speed modification a component can contribute. Currently just 0 for all.
+ var/min_slowdown_factor = 0
/datum/robot_component/New(mob/living/silicon/robot/R)
owner = R
+ max_slowdown_factor = (max_damage / 100)
+ current_slowdown_factor = min_slowdown_factor
// Should only ever be destroyed when a borg gets destroyed
/datum/robot_component/Destroy(force, ...)
@@ -25,6 +33,8 @@
/datum/robot_component/proc/install(obj/item/I, update_health = TRUE)
wrapped = I
installed = TRUE
+ I.forceMove(owner)
+ current_slowdown_factor = (brute_damage + electronics_damage) / 100
go_online()
if(update_health)
owner.updatehealth("component '[src]' installed")
@@ -35,7 +45,7 @@
go_offline()
owner.updatehealth("component '[src]' removed")
-/datum/robot_component/proc/destroy()
+/datum/robot_component/proc/break_component()
if(wrapped)
qdel(wrapped)
uninstall()
@@ -50,9 +60,9 @@
brute_damage += brute
electronics_damage += electronics
-
+ current_slowdown_factor = clamp((current_slowdown_factor + ((brute + electronics) / 100)), min_slowdown_factor, max_slowdown_factor)
if(brute_damage + electronics_damage >= max_damage)
- destroy()
+ break_component()
SStgui.update_uis(owner.self_diagnosis)
@@ -63,12 +73,19 @@
if(owner && updating_health)
owner.updatehealth("component '[src]' heal damage")
-
- brute_damage = max(0, brute_damage - brute)
- electronics_damage = max(0, electronics_damage - electronics)
-
+ var/burn_damage_healed = clamp(electronics, 0, electronics_damage)
+ var/brute_damage_healed = clamp(brute, 0, brute_damage)
+ current_slowdown_factor = clamp((current_slowdown_factor - ((burn_damage_healed + brute_damage_healed) / 100)), min_slowdown_factor, max_slowdown_factor)
+ rounding_error_check()
+ brute_damage -= brute_damage_healed
+ electronics_damage -= burn_damage_healed
SStgui.update_uis(owner.self_diagnosis)
+///There tends to be some desync between slowdown and damage when being healed up slowly like through self-repair or upgraded rechargers.
+/datum/robot_component/proc/rounding_error_check()
+ if(current_slowdown_factor < (min_slowdown_factor) + 0.0001) //Within .0001 of the minimum, just set it to the minimum
+ current_slowdown_factor = min_slowdown_factor
+
/datum/robot_component/proc/is_powered()
return installed && (brute_damage + electronics_damage < max_damage) && (powered)
@@ -90,12 +107,12 @@
/datum/robot_component/proc/disable()
if(!component_disabled)
go_offline()
- component_disabled++
+ component_disabled = TRUE
/datum/robot_component/proc/enable()
- component_disabled--
- if(!component_disabled)
+ if(component_disabled)
go_online()
+ component_disabled = FALSE
/datum/robot_component/proc/toggle()
toggled = !toggled
@@ -112,6 +129,13 @@
/datum/robot_component/proc/go_offline()
return
+/datum/robot_component/proc/get_movement_delay()
+ if(is_missing())
+ return 0
+ if(is_destroyed())
+ return max_slowdown_factor
+ return current_slowdown_factor
+
/datum/robot_component/armour
name = "armour plating"
external_type = /obj/item/robot_parts/robot_component/armour
@@ -136,16 +160,19 @@
owner.cell = null
/datum/robot_component/cell/is_powered()
- return ..() && owner.cell
+ return ..() && owner.cell?.charge
/datum/robot_component/cell/Destroy(force, ...)
owner.cell = null
return ..()
-/datum/robot_component/cell/destroy()
+/datum/robot_component/cell/break_component()
..()
owner.cell = null
+/datum/robot_component/cell/disable() //This can't be manually disabled, and shouldn't be accidentally disabled
+ return
+
/datum/robot_component/radio
name = "radio"
external_type = /obj/item/robot_parts/robot_component/radio
@@ -189,11 +216,19 @@
var/datum/robot_component/C = components[module_name]
return C && C.installed && C.toggled && C.is_powered() && !C.component_disabled
+///Disables a random component for the duration, or until manually turned back on.
+/mob/living/silicon/robot/proc/disable_random_component(number_disabled, duration)
+ var/list/random_components = pick_multiple_unique(components, number_disabled)
+ for(var/component in random_components)
+ disable_component(component, duration)
+
/mob/living/silicon/robot/proc/disable_component(module_name, duration)
var/datum/robot_component/D = get_component(module_name)
D.disable()
- spawn(duration)
- D.enable()
+ addtimer(CALLBACK(src, PROC_REF(reenable_component), D), duration)
+
+/mob/living/silicon/robot/proc/reenable_component(datum/robot_component/to_enable)
+ to_enable.enable()
// Returns component by it's string name
/mob/living/silicon/robot/proc/get_component(component_name)
diff --git a/code/modules/mob/living/silicon/robot/robot_damage.dm b/code/modules/mob/living/silicon/robot/robot_damage.dm
index 60dbb3982185..c618bb5f67a3 100644
--- a/code/modules/mob/living/silicon/robot/robot_damage.dm
+++ b/code/modules/mob/living/silicon/robot/robot_damage.dm
@@ -32,6 +32,21 @@
heal_overall_damage(0, -amount, updating_health)
return STATUS_UPDATE_HEALTH
+/mob/living/silicon/robot/update_stamina()
+ if(rebooting)
+ return
+ var/current_stam_damage = getStaminaLoss()
+ if(current_stam_damage > DAMAGE_PRECISION && (maxHealth - current_stam_damage) <= HEALTH_THRESHOLD_CRIT && stat == CONSCIOUS)
+ start_emergency_reboot()
+
+/mob/living/silicon/robot/handle_status_effects()
+ ..()
+ if(stam_regen_start_time <= world.time)
+ update_stamina()
+ if(staminaloss && !rebooting)
+ setStaminaLoss(0, FALSE)
+ update_stamina_hud()
+
/mob/living/silicon/robot/proc/get_damaged_components(get_brute, get_burn, get_borked = FALSE, get_missing = FALSE)
var/list/datum/robot_component/parts = list()
for(var/V in components)
@@ -55,14 +70,32 @@
if(C.installed)
rval += C
return rval
-
+///Returns the armour component for a borg, or false if its missing or broken
/mob/living/silicon/robot/proc/get_armour()
if(!LAZYLEN(components))
- return 0
+ return FALSE
var/datum/robot_component/C = components["armour"]
if(C && C.installed)
return C
- return 0
+ return FALSE
+///Returns the power cell component for a borg, or false if its missing or broken
+/mob/living/silicon/robot/proc/get_cell_component()
+ if(!LAZYLEN(components))
+ return FALSE
+ var/datum/robot_component/C = components["power cell"]
+ if(C?.installed)
+ return C
+ return FALSE
+
+/mob/living/silicon/robot/proc/get_total_component_slowdown()
+ var/total_slowdown = 0
+ for(var/V in components)
+ var/datum/robot_component/C = components[V]
+ total_slowdown += C.get_movement_delay()
+ return total_slowdown
+
+/mob/living/silicon/robot/proc/get_stamina_slowdown()
+ return round((staminaloss / 40), 0.125)
/mob/living/silicon/robot/heal_organ_damage(brute, burn, updating_health = TRUE)
var/list/datum/robot_component/parts = get_damaged_components(brute, burn)
@@ -131,3 +164,45 @@
parts -= picked
updatehealth()
+
+/*
+Begins the stamcrit reboot process for borgs. Stuns them, and warns people if the borg has no power source.
+*/
+/mob/living/silicon/robot/proc/start_emergency_reboot()
+ rebooting = TRUE
+ playsound(src, 'sound/machines/shut_down.ogg', 100, FALSE, SOUND_RANGE_SET(10))
+ if(!has_power_source())
+ visible_message(
+ "[src]'s system sounds an alarm, \"ERROR: NO POWER SOURCE DETECTED. SYSTEM SHUTDOWN IMMINENT.\"",
+ "EMERGENCY: FULL SYSTEM SHUTDOWN IMMINENT.")
+ playsound(src, 'sound/machines/buzz-two.ogg' , 50, FALSE, SOUND_RANGE_SET(10))
+ else
+ visible_message(
+ "[src]'s lights suddenly go dark and [p_they()] seem to shut down.",
+ "A critical neural connection error has occurred. Beginning emergency reboot..."
+ )
+ var/stun_time = rand(13 SECONDS, 18 SECONDS) //Slightly longer than old flash timer
+ setStaminaLoss(0) //Have you tried turning it off and on again?
+ Weaken(stun_time)
+ addtimer(CALLBACK(src, PROC_REF(end_emergency_reboot)), stun_time)
+/*
+Finishes the stamcrit process. If the borg doesn't have a power source for the reboot, they die.
+*/
+/mob/living/silicon/robot/proc/end_emergency_reboot()
+ if(!has_power_source()) //Can't turn itself back on
+ rebooting = FALSE
+ death()
+ return
+ if(getStaminaLoss()) //If someone has been chain-flashing a borg then the ride never ends
+ var/restun_time = rand(7 SECONDS, 10 SECONDS)
+ to_chat(src, "Error: Continual sensor overstimulation resulted in faulty reboot. Retrying in [restun_time / 10] seconds.")
+ setStaminaLoss(0) //Just keep trying to turn it off and on again, surely it'll work eventually
+ Weaken(restun_time)
+ addtimer(CALLBACK(src, PROC_REF(end_emergency_reboot)), restun_time)
+ return
+ rebooting = FALSE
+ if(stat != CONSCIOUS)
+ return
+ playsound(src, 'sound/machines/reboot_chime.ogg' , 100, FALSE, SOUND_RANGE_SET(10))
+ update_stamina_hud()
+ to_chat(src, "Reboot complete, neural interface operational.")
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index b397601193be..9129dd97b27d 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -8,7 +8,7 @@
visible_message("[M] disarmed [src]!", "[M] has disabled [src]'s active module!")
add_attack_logs(M, src, "alien disarmed")
else
- Stun(4 SECONDS)
+ adjustStaminaLoss(30) //Same as carbons, I guess?
step(src, get_dir(M,src))
add_attack_logs(M, src, "Alien pushed over")
visible_message("[M] forces back [src]!", "[M] forces back [src]!")
@@ -55,3 +55,12 @@
step_away(src, user, 15)
sleep(3)
step_away(src, user, 15)
+
+/mob/living/silicon/robot/flash_eyes(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/stretch/flash/noise)
+ if(!affect_silicon)
+ return
+ Confused(intensity * 4 SECONDS)
+ var/software_damage = (intensity * 40)
+ adjustStaminaLoss(software_damage)
+ to_chat(src, "Error: Optical sensors overstimulated.")
+ ..()
diff --git a/code/modules/mob/living/silicon/robot/robot_life.dm b/code/modules/mob/living/silicon/robot/robot_life.dm
index 7f104251361f..c25b202e3f20 100644
--- a/code/modules/mob/living/silicon/robot/robot_life.dm
+++ b/code/modules/mob/living/silicon/robot/robot_life.dm
@@ -15,33 +15,35 @@
/mob/living/silicon/robot/proc/handle_robot_cell()
- if(stat != DEAD)
- if(!is_component_functioning("power cell"))
- uneq_all()
- low_power_mode = TRUE
- update_headlamp()
- diag_hud_set_borgcell()
- return
- if(low_power_mode)
- if(is_component_functioning("power cell") && cell.charge)
- low_power_mode = FALSE
- update_headlamp()
- else if(stat == CONSCIOUS)
- use_power()
+ if(stat == DEAD)
+ return
+ if(externally_powered)
+ return
+ if(low_power_mode)
+ handle_no_power()
+ else if(!is_component_functioning("power cell")) //This makes it so you'll only get the warnings once per running out of charge
+ enter_low_power_mode()
+ else if(stat == CONSCIOUS)
+ use_power()
/mob/living/silicon/robot/proc/use_power()
- // this check is safe because `cell` is guaranteed to be set when the power cell is functioning
- if(is_component_functioning("power 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.
- 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 = TRUE
- update_headlamp()
+ 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.
+ cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick
diag_hud_set_borgcell()
+/mob/living/silicon/robot/proc/handle_no_power()
+ diag_hud_set_borgcell()
+ if(is_component_functioning("power cell"))
+ low_power_mode = FALSE
+ return
+ adjustStaminaLoss(3)
+
+/mob/living/silicon/robot/proc/enter_low_power_mode()
+ low_power_mode = TRUE
+ playsound(src, "sound/mecha/lowpower.ogg", 50, FALSE, SOUND_RANGE_SET(10))
+ to_chat(src, "Alert: Power cell requires immediate charging.")
+ handle_no_power()
+
/mob/living/silicon/robot/proc/handle_equipment()
if(camera && !scrambledcodes)
if(stat == DEAD || wires.is_cut(WIRE_BORG_CAMERA))
diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm
index bb700a8d976f..b6d5163c105a 100644
--- a/code/modules/mob/living/silicon/robot/robot_mob.dm
+++ b/code/modules/mob/living/silicon/robot/robot_mob.dm
@@ -75,7 +75,12 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/brute_mod = 1
/// Incoming burn damage is multiplied by this number.
var/burn_mod = 1
-
+ ///If the cyborg is rebooting from stamcrit
+ var/rebooting = FALSE
+ ///If the cyborg is in a charger, or otherwise receiving power from an outside source.
+ var/externally_powered = FALSE
+ ///What the cyborg's maximum slowdown penalty is, if it has one.
+ var/slowdown_cap = INFINITY
var/list/force_modules
/// Can a robot rename itself with the Namepick verb?
var/allow_rename = TRUE
@@ -212,7 +217,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
scanner = new(src)
scanner.Grant(src)
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(create_trail))
-
+ RegisterSignal(src, COMSIG_ENTERED_BORGCHARGER, PROC_REF(gain_external_power))
+ RegisterSignal(src, COMSIG_EXITED_BORGCHARGER, PROC_REF(lose_external_power))
robot_module_hat_offset(icon_state)
/mob/living/silicon/robot/get_radio()
@@ -684,7 +690,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if("lava" in weather_immunities) // Remove the lava-immunity effect given by a printable upgrade
weather_immunities -= "lava"
armor = getArmor(arglist(initial(armor)))
-
+ slowdown_cap = INFINITY
status_flags |= CANPUSH
hud_used.update_robot_modules_display()
@@ -881,21 +887,17 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(istype(W, /obj/item/robot_parts/robot_component) && opened)
for(var/V in components)
var/datum/robot_component/C = components[V]
- if(C.is_missing() && istype(W, C.external_type))
- if(!user.drop_item())
- to_chat(user, "[W] seems to be stuck in your hand!")
- return
- C.install(W)
- W.loc = null
-
- var/obj/item/robot_parts/robot_component/WC = W
- if(istype(WC))
- C.brute_damage = WC.brute
- C.electronics_damage = WC.burn
-
- to_chat(usr, "You install [W].")
-
+ if(!C.is_missing() || !istype(W, C.external_type))
+ continue
+ if(!user.drop_item())
+ to_chat(user, "[W] seems to be stuck in your hand!")
return
+ var/obj/item/robot_parts/robot_component/WC = W
+ C.brute_damage = WC.brute
+ C.electronics_damage = WC.burn
+ C.install(WC)
+ to_chat(usr, "You install [W].")
+ return
if(istype(W, /obj/item/stack/cable_coil) && user.a_intent == INTENT_HELP && (wiresexposed || isdrone(src)))
user.changeNext_move(CLICK_CD_MELEE)
@@ -920,12 +922,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
to_chat(user, "There is a power cell already installed.")
else
user.drop_item()
- W.loc = src
to_chat(user, "You insert the power cell.")
C.install(W)
- //This will mean that removing and replacing a power cell will repair the mount, but I don't care at this point. ~Z
- C.brute_damage = 0
- C.electronics_damage = 0
var/been_hijacked = FALSE
for(var/mob/living/simple_animal/demon/pulse_demon/demon in cell)
@@ -1092,7 +1090,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
thing.burn = C.electronics_damage
C.uninstall()
- thing.loc = loc
+ thing.forceMove(loc)
@@ -1485,11 +1483,12 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(emp_protection)
return
..()
+ adjustStaminaLoss((30 / severity)) //They also get flashed for an additional 30
switch(severity)
- if(1)
- disable_component("comms", 160)
- if(2)
- disable_component("comms", 60)
+ if(EMP_HEAVY)
+ disable_random_component(2, 20 SECONDS)
+ if(EMP_LIGHT)
+ disable_random_component(1, 10 SECONDS)
/mob/living/silicon/robot/deathsquad
base_icon = "nano_bloodhound"
@@ -1750,3 +1749,17 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
old_ai.connected_robots -= src
if(connected_ai)
connected_ai.connected_robots |= src
+
+/mob/living/silicon/robot/proc/gain_external_power()
+ SIGNAL_HANDLER //COMSIG_ENTERED_BORGCHARGER
+ externally_powered = TRUE
+
+/mob/living/silicon/robot/proc/lose_external_power()
+ SIGNAL_HANDLER //COMSIG_EXITED_BORGCHARGER
+ externally_powered = FALSE
+
+/mob/living/silicon/robot/proc/has_power_source()
+ var/datum/robot_component/cell/cell = get_cell_component()
+ if(!cell)
+ return externally_powered
+ return cell.is_powered() || externally_powered
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 00f93669a175..7ecc75569d78 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -472,7 +472,7 @@
/obj/item/inflatable/cyborg,
/obj/item/inflatable/cyborg/door
)
- emag_modules = list(/obj/item/borg/stun, /obj/item/restraints/handcuffs/cable/zipties/cyborg, /obj/item/rcd/borg)
+ emag_modules = list(/obj/item/melee/baton/loaded/borg_stun_arm, /obj/item/restraints/handcuffs/cable/zipties/cyborg, /obj/item/rcd/borg)
override_modules = list(/obj/item/gun/energy/emitter/cyborg/proto)
malf_modules = list(/obj/item/gun/energy/emitter/cyborg)
special_rechargables = list(/obj/item/extinguisher, /obj/item/weldingtool/largetank/cyborg, /obj/item/gun/energy/emitter/cyborg)
diff --git a/code/modules/mob/living/silicon/robot/robot_movement.dm b/code/modules/mob/living/silicon/robot/robot_movement.dm
index 2c4eccf2b787..89ff5cc82ea6 100644
--- a/code/modules/mob/living/silicon/robot/robot_movement.dm
+++ b/code/modules/mob/living/silicon/robot/robot_movement.dm
@@ -5,16 +5,18 @@
return TRUE
return FALSE
- //No longer needed, but I'll leave it here incase we plan to re-use it.
/mob/living/silicon/robot/movement_delay()
. = ..()
+ . += GLOB.configuration.movement.robot_delay
. += speed
+ . += get_total_component_slowdown()
+ . += get_stamina_slowdown()
// Counteract magboot slow in 0G.
if(!has_gravity(src) && HAS_TRAIT(src, TRAIT_MAGPULSE))
. -= 2 // The slowdown value on the borg magpulse.
if(module_active && istype(module_active,/obj/item/borg/destroyer/mobility))
. -= 3
- . += GLOB.configuration.movement.robot_delay
+ . = min(., slowdown_cap)
/mob/living/silicon/robot/mob_negates_gravity()
return HAS_TRAIT(src, TRAIT_MAGPULSE)
diff --git a/code/modules/mob/living/silicon/robot/robot_update_status.dm b/code/modules/mob/living/silicon/robot/robot_update_status.dm
index ee0efa21cb2b..a152d9262353 100644
--- a/code/modules/mob/living/silicon/robot/robot_update_status.dm
+++ b/code/modules/mob/living/silicon/robot/robot_update_status.dm
@@ -14,7 +14,7 @@
death()
create_debug_log("died of damage, trigger reason: [reason]")
return
- if(!is_component_functioning("actuator") || !is_component_functioning("power cell") || HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || IsStunned() || IsWeakened() || getOxyLoss() > maxHealth * 0.5) // Borgs need to be able to sleep for adminfreeze
+ if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT) || IsStunned() || IsWeakened() || getOxyLoss() > maxHealth * 0.5) // Borgs need to be able to sleep for adminfreeze
if(stat == CONSCIOUS)
KnockOut()
create_debug_log("fell unconscious, trigger reason: [reason]")
@@ -23,11 +23,11 @@
WakeUp()
create_debug_log("woke up, trigger reason: [reason]")
else
- if(health > 0 && !suiciding)
+ if(health > 0 && !suiciding && has_power_source())
update_revive()
var/mob/dead/observer/ghost = get_ghost()
if(ghost)
- to_chat(ghost, "Your cyborg shell has been repaired, re-enter if you want to continue! (Verbs -> Ghost -> Re-enter corpse)")
+ to_chat(ghost, "Your cyborg shell has been repaired and repowered, re-enter if you want to continue! (Verbs -> Ghost -> Re-enter corpse)")
SEND_SOUND(ghost, sound('sound/effects/genetics.ogg'))
create_attack_log("revived, trigger reason: [reason]")
create_log(MISC_LOG, "revived, trigger reason: [reason]")
diff --git a/code/modules/mob/living/silicon/silicon_mob.dm b/code/modules/mob/living/silicon/silicon_mob.dm
index 42d8bf19b98e..8c5026b2ec3a 100644
--- a/code/modules/mob/living/silicon/silicon_mob.dm
+++ b/code/modules/mob/living/silicon/silicon_mob.dm
@@ -230,10 +230,8 @@
switch(severity)
if(EMP_HEAVY)
take_organ_damage(20)
- Stun(16 SECONDS)
if(EMP_LIGHT)
take_organ_damage(10)
- Stun(6 SECONDS)
flash_eyes(affect_silicon = 1)
to_chat(src, "*BZZZT*")
to_chat(src, "Warning: Electromagnetic pulse detected.")
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm
index 6ead2543757e..5fd8f2003215 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm
@@ -43,6 +43,12 @@
flag = ENERGY
temperature = 50
+/obj/item/projectile/temp/basilisk/on_hit(atom/target, blocked)
+ ..()
+ if(isrobot(target))
+ var/mob/living/silicon/robot/cyborg = target
+ cyborg.apply_damage(35, STAMINA)
+
/mob/living/simple_animal/hostile/asteroid/basilisk/GiveTarget(new_target)
if(..()) //we have a target
if(isliving(target) && !target.Adjacent(targets_from) && ranged_cooldown <= world.time)//No more being shot at point blank or spammed with RNG beams
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index d509a37b8f94..28d21843b858 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -294,8 +294,6 @@
if(isalien(target))
knockdown = 0
nodamage = TRUE
- if(isrobot(target))
- stun = 10 SECONDS
. = ..() // Execute the rest of the code.
/obj/item/projectile/bullet/anti_alien_toxin
diff --git a/config/example/config.toml b/config/example/config.toml
index 846ba5bc8165..28050252d5a9 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -594,7 +594,7 @@ crawling_speed_reduction = 4
# Move delay for humanoids
human_delay = 1.5
# Move delay for cyborgs
-robot_delay = 2.5
+robot_delay = 1.5
# Move delay for xenomorphs
alien_delay = 1.5
# Move delay for slimes (xenobio, not slimepeople)
diff --git a/sound/machines/reboot_chime.ogg b/sound/machines/reboot_chime.ogg
new file mode 100644
index 000000000000..ff90a50dd053
Binary files /dev/null and b/sound/machines/reboot_chime.ogg differ
diff --git a/sound/machines/shut_down.ogg b/sound/machines/shut_down.ogg
new file mode 100644
index 000000000000..66be028b0df8
Binary files /dev/null and b/sound/machines/shut_down.ogg differ