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