From b3ac4e1c7d926e76a2a54ee4f433abe85201ee38 Mon Sep 17 00:00:00 2001 From: Kubisopplay <38842052+Kubisopplay@users.noreply.github.com> Date: Wed, 26 Jul 2023 23:09:19 +0200 Subject: [PATCH] A small pile of borg combat changes (#75819) ## About The Pull Request This PR rebalances borg combat. It is first part of broader set of changes that I talked about with armhullen, which was then discussed in discord player project channel. Ultimately arm decided to rewrite the complete rebalance and grab combat, but to help with testing, and to measure reaction to that changes, I wrote this much smaller change, that should be usable as a TMd test. Main points that changed: - Flashing cyborgs for the first time blinds them, and scrambles their movement. Flashing them while they are blinded immobilizes them as it used to, but still allows them to talk, and use radio. - Emping cyborgs still turns them completely off, but the code behind is _slightly_ changed, because of how utterly incomprehensible cyborg code is - Borg stunarm deals 60 stamina damage, costs 200 energy (SUBJECT TO CHANGE AS SOON AS I DECIDE ON HOW TO), and applies confusion, stuttering, and jittering. - Robot consoles used by humans can now lock down only one borg at a time, they exhibit increased power draw while doing so. The borg is also informed of the location of the console. If left alone, the borg will unlock itself in 10 minutes, to avoid leaving it permanently locked down in some faraway place. If the console gets destroyed (or depowered), the borg will get unlocked - laser pointer only blinds borgs, without the paralyze component. If you flash a borg blinded this way it will paralyze it, so comboing it is possible. - Throwing things at borgs slows them down Not planned, but possible if testing shows its required: - Changing borg health to malfunctions - Whatever people convince me to add - Portable lockdown solution for sec, or a way for them to do basic check on borg. - Usage of bucket/something else to restrict radio usage of cyborg If you have ideas what would make this change better please use [discord channel](https://discord.com/channels/326822144233439242/1113145741788065924), or comments. Almost everything that I wrote here is subject to change, as requested or suggested. ### DISCLAIMER I had to change few functions in background of how borgs work. This WILL have changes I couldn't predict, since some of them aren't linked in any obvious way in code. I am aware of that, and will try fixing what needs fixing when pointed out. PS: Borg code is a nightmare ## Why It's Good For The Game Borg combat sucks. It is absolutely binary, there is 0 capacity for talking, because both sides fear losing in one click. Sec can't really feasibly ask the borg to state laws while flashed, so their only solution is to kill it and revive it. - Turning most instastun solutions into less lethal versions should lessen the pressure, since it reduces the chance that person that reacts first survives. - It causes minor reduction in traitor's capacity to emag borgs with only a flash, but EMPs still hardstun, and they are plentiful both in uplink, and in ghetto form, so that shouldn't be a problem. - Since it allows borgs to scream for help while being flashed, it also increases the potential of AI helping it, or at least noticing its death. - Lockdown console changes reduce its capability in completely turning off malf ai, and leave it still highly capable of being an useful tool in stopping emagged cyborgs. ## Changelog :cl: balance: Flashing borgs requires two consecutive flashes to fully immobilize balance: Flashed borgs can speak balance: Remote lockdown on cyborgs lasts 180 seconds balance: Cyborg stun arm works like normal baton, and costs less energy /:cl: --------- Co-authored-by: ATH1909 <42606352+ATH1909@users.noreply.github.com> Co-authored-by: Jacquerel --- code/game/machinery/computer/robot.dm | 82 ++++++++++++++++--- .../objects/items/devices/laserpointer.dm | 2 +- .../game/objects/items/robot/items/generic.dm | 25 ++++-- code/modules/assembly/flash.dm | 16 ++-- .../modules/mob/living/silicon/robot/robot.dm | 18 +++- .../mob/living/silicon/robot/robot_defense.dm | 21 ++++- .../mob/living/silicon/robot/robot_defines.dm | 2 + .../mob/living/silicon/robot/robot_model.dm | 2 + .../mob/living/silicon/silicon_defense.dm | 2 +- code/modules/movespeed/modifiers/mobs.dm | 3 + 10 files changed, 143 insertions(+), 30 deletions(-) diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index e3d3a0e2acb..00b446c2b27 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -6,6 +6,9 @@ req_access = list(ACCESS_ROBOTICS) circuit = /obj/item/circuitboard/computer/robotics light_color = LIGHT_COLOR_PINK + var/mob/living/silicon/robot/locked_down_borg = null + active_power_usage = 10000 //no idea how much is it + /obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) . = FALSE @@ -88,21 +91,37 @@ if(allowed(usr)) var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs if(can_control(usr, R) && !..()) - if(isAI(usr) && (R.ai_lockdown && R.lockcharge || !R.lockcharge) || !isAI(usr)) - R.ai_lockdown = FALSE - if(isAI(usr) && !R.lockcharge) + if(isAI(usr)) + if(R.lockcharge) + if(R.ai_lockdown) + R.ai_lockdown = FALSE + lock_unlock_borg(R) + else + to_chat(usr, span_danger("Cyborg locked by an user with superior permissions.")) + else R.ai_lockdown = TRUE - message_admins(span_notice("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!")) - log_silicon("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") - log_combat(usr, R, "[!R.lockcharge ? "locked down" : "released"] cyborg") - R.SetLockdown(!R.lockcharge) - to_chat(R, !R.lockcharge ? span_notice("Your lockdown has been lifted!") : span_alert("You have been locked down!")) - if(R.connected_ai) - to_chat(R.connected_ai, "[!R.lockcharge ? span_notice("NOTICE - Cyborg lockdown lifted") : span_alert("ALERT - Cyborg lockdown detected")]: [R.name]
") + lock_unlock_borg(R) else - to_chat(usr, span_danger("Cyborg locked by an user with superior permissions.")) + if(isnull(locked_down_borg) && !R.lockcharge) //If there is no borg locked down by the console yet + lock_unlock_borg(R, src.loc.loc.name) + R.ai_lockdown = FALSE //Just in case I'm stupid + locked_down_borg = R + RegisterSignal(R, COMSIG_QDELETING, PROC_REF(borg_destroyed)) + else if(locked_down_borg == R) //If the borg locked down by the console is the same as the one we're trying to unlock + lock_unlock_borg(R) + else if(R.lockcharge&&R.ai_lockdown) + R.ai_lockdown = FALSE + lock_unlock_borg(R) + else if(R.lockcharge&&locked_down_borg!=R) + to_chat(usr, span_danger("The cyborg was locked by a different console.")) + else + to_chat(usr, span_danger("You can lock down only one cyborg at a time.")) else to_chat(usr, span_danger("Access Denied.")) + if(!isnull(locked_down_borg)) + use_power = ACTIVE_POWER_USE + else + use_power = IDLE_POWER_USE if("killbot") //Malf AIs, and AIs with a combat upgrade, can detonate their cyborgs remotely. if(!isAI(usr)) @@ -142,3 +161,44 @@ drone.visible_message(span_danger("\the [drone] self-destructs!")) drone.investigate_log("has been gibbed by a robotics console.", INVESTIGATE_DEATHS) drone.gib() + + +// I feel like this should be changed, but I have no idea in what way exactly, so I just extracted it to make the code less of a mess +/obj/machinery/computer/robotics/proc/lock_unlock_borg(mob/living/silicon/robot/R, console_location = null) + if(R.lockcharge && locked_down_borg == R) + UnregisterSignal(locked_down_borg, COMSIG_QDELETING) + locked_down_borg = null + message_admins(span_notice("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!")) + log_silicon("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") + log_combat(usr, R, "[!R.lockcharge ? "locked down" : "released"] cyborg") + R.SetLockdown(!R.lockcharge) + to_chat(R, !R.lockcharge ? span_notice("Your lockdown has been lifted!") : span_alert("You have been locked down!")) + if(!isnull(console_location)) + to_chat(R, span_alert("The approximate location of the console that is keeping you locked down is [console_location]")) + if(R.connected_ai) + to_chat(R.connected_ai, "[!R.lockcharge ? span_notice("NOTICE - Cyborg lockdown lifted") : span_alert("ALERT - Cyborg lockdown detected")]: [R.name]
") + +/obj/machinery/computer/robotics/proc/borg_destroyed() + SIGNAL_HANDLER + locked_down_borg = null + +/obj/machinery/computer/robotics/on_set_machine_stat(old_value) //depowering the console unlocks the borg + if(!isnull(locked_down_borg)) + if(machine_stat & (NOPOWER|BROKEN|MAINT) && locked_down_borg.lockcharge) + src.lock_unlock_borg(locked_down_borg) + return ..() + +/obj/machinery/computer/robotics/atom_break() // This shouldnt be needed, but hitting console doesnt trigger destroy apparently + if(!isnull(locked_down_borg)) + lock_unlock_borg(locked_down_borg) + return ..() + +/obj/machinery/computer/robotics/Destroy() + if(!isnull(locked_down_borg)) + lock_unlock_borg(locked_down_borg) + return ..() + + + + + diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 45000bf4353..f56cc83e116 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -121,7 +121,7 @@ log_combat(user, S, "shone in the sensors", src) //chance to actually hit the eyes depends on internal component if(prob(effectchance * diode.rating) && S.flash_act(affect_silicon = TRUE)) - S.Paralyze(rand(100,200)) + S.set_temp_blindness_if_lower(5 SECONDS) to_chat(S, span_danger("Your sensors were overloaded by a laser!")) outmsg = span_notice("You overload [S] by shining [src] at [S.p_their()] sensors.") else diff --git a/code/game/objects/items/robot/items/generic.dm b/code/game/objects/items/robot/items/generic.dm index a7477357779..65436e5c8f4 100644 --- a/code/game/objects/items/robot/items/generic.dm +++ b/code/game/objects/items/robot/items/generic.dm @@ -15,10 +15,17 @@ /obj/item/borg/stun name = "electrically-charged arm" icon_state = "elecarm" + var/stamina_damage = 60 //Same as normal batong /// Cost to use the stun arm - var/charge_cost = 1000 + var/charge_cost = 200 + var/cooldown_check = 0 + /// cooldown between attacks + var/cooldown = 4 SECONDS // same as baton /obj/item/borg/stun/attack(mob/living/attacked_mob, mob/living/user) + if(cooldown_check > world.time) + user.balloon_alert(user, "still recharging!") + return if(ishuman(attacked_mob)) var/mob/living/carbon/human/human = attacked_mob if(human.check_shields(src, 0, "[attacked_mob]'s [name]", MELEE_ATTACK)) @@ -30,14 +37,20 @@ return user.do_attack_animation(attacked_mob) - attacked_mob.Paralyze(100) - attacked_mob.adjust_stutter(10 SECONDS) - - attacked_mob.visible_message(span_danger("[user] prods [attacked_mob] with [src]!"), \ + attacked_mob.adjustStaminaLoss(stamina_damage) + attacked_mob.set_confusion_if_lower(5 SECONDS) + attacked_mob.adjust_stutter(20 SECONDS) + attacked_mob.set_jitter_if_lower(5 SECONDS) + if(issilicon(attacked_mob)) + attacked_mob.emp_act(EMP_HEAVY) + attacked_mob.visible_message(span_danger("[user] shocks [attacked_mob] with [src]!"), \ + span_userdanger("[user] shocks you with [src]!")) + else + attacked_mob.visible_message(span_danger("[user] prods [attacked_mob] with [src]!"), \ span_userdanger("[user] prods you with [src]!")) playsound(loc, 'sound/weapons/egloves.ogg', 50, TRUE, -1) - + cooldown_check = world.time + cooldown log_combat(user, attacked_mob, "stunned", src, "(Combat mode: [user.combat_mode ? "On" : "Off"])") /obj/item/borg/cyborghug diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index b170ba204c4..553118aae24 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -243,12 +243,18 @@ var/mob/living/silicon/robot/flashed_borgo = M log_combat(user, flashed_borgo, "flashed", src) update_icon(ALL, TRUE) - if(!flashed_borgo.flash_act(affect_silicon = TRUE)) + if(flashed_borgo.flash_act(affect_silicon = TRUE)) + if(flashed_borgo.is_blind()) + var/flash_duration = rand(8,12) SECONDS + flashed_borgo.Paralyze(flash_duration) + flashed_borgo.set_temp_blindness_if_lower(flash_duration) + user.visible_message(span_warning("[user] overloads [flashed_borgo]'s sensors and computing with the flash!"), span_danger("You overload [flashed_borgo]'s sensors and computing with the flash!")) + else + user.visible_message(span_warning("[user] blinds [flashed_borgo] with the flash!"), span_danger("You blind [flashed_borgo] with the flash!")) + flashed_borgo.set_temp_blindness_if_lower( (rand(5,15) SECONDS)) + flashed_borgo.set_confusion_if_lower(5 SECONDS * CONFUSION_STACK_MAX_MULTIPLIER) + else user.visible_message(span_warning("[user] fails to blind [flashed_borgo] with the flash!"), span_warning("You fail to blind [flashed_borgo] with the flash!")) - return - flashed_borgo.Paralyze(rand(80,120)) - flashed_borgo.set_confusion_if_lower(5 SECONDS * CONFUSION_STACK_MAX_MULTIPLIER) - user.visible_message(span_warning("[user] overloads [flashed_borgo]'s sensors with the flash!"), span_danger("You overload [flashed_borgo]'s sensors with the flash!")) return user.visible_message(span_warning("[user] fails to blind [M] with the flash!"), span_warning("You fail to blind [M] with the flash!")) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 8e928a9189e..d773e49a003 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -411,12 +411,25 @@ // They stay locked down if their wire is cut. if(wires?.is_cut(WIRE_LOCKDOWN)) state = TRUE + else if(!ai_lockdown) + lockdown_timer = addtimer(CALLBACK(src,PROC_REF(lockdown_override), FALSE), 10 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_DELETE_ME | TIMER_STOPPABLE) + to_chat(src, "

[span_alert("ALERT - Remote system lockdown engaged. Trying to hack the lockdown subsystem...")]
") if(state) throw_alert(ALERT_HACKED, /atom/movable/screen/alert/locked) else + deltimer(lockdown_timer) clear_alert(ALERT_HACKED) set_lockcharge(state) +/// Allows the borg to unlock themselves after a lenghty period of time. +/mob/living/silicon/robot/proc/lockdown_override() + if(ai_lockdown) + to_chat(src, "

[span_alert("ALERT - Remote system lockdown override failed.")]
") + return + set_lockcharge(FALSE) + to_chat(src, "

[span_notice("ALERT - Remote system lockdown override successful.")]
") + if(connected_ai) + to_chat(connected_ai, "

[span_notice("ALERT - Cyborg [name] succesfully overriden the lockdown system")]
") ///Reports the event of the change in value of the lockcharge variable. /mob/living/silicon/robot/proc/set_lockcharge(new_lockcharge) @@ -658,10 +671,6 @@ 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() @@ -677,6 +686,7 @@ builtInCamera.toggle_cam(src, 0) if(full_heal_flags & HEAL_ADMIN) locked = TRUE + src.set_stat(CONSCIOUS) notify_ai(AI_NOTIFICATION_NEW_BORG) toggle_headlamp(FALSE, TRUE) //This will reenable borg headlamps if doomsday is currently going on still. return TRUE diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 61cccd35f9b..277b36c274b 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -320,9 +320,17 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real return switch(severity) if(1) - Stun(160) + emp_knockout(16 SECONDS) if(2) - Stun(60) + emp_knockout(6 SECONDS) + +/mob/living/silicon/robot/proc/emp_knockout(deciseconds) + set_stat(UNCONSCIOUS) + addtimer(CALLBACK(src, PROC_REF(wake_from_emp)), deciseconds, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_DELETE_ME) + +/mob/living/silicon/robot/proc/wake_from_emp() + set_stat(CONSCIOUS) + update_stat() /mob/living/silicon/robot/emag_act(mob/user, obj/item/card/emag/emag_card) if(user == src)//To prevent syndieborgs from emagging themselves @@ -430,3 +438,12 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real updatehealth() if(prob(75) && Proj.damage > 0) spark_system.start() + +/mob/living/silicon/on_hit(obj/projectile/P) + if(!has_movespeed_modifier(/datum/movespeed_modifier/borg_throw)) + add_movespeed_modifier(/datum/movespeed_modifier/borg_throw) + addtimer(CALLBACK(src, TYPE_PROC_REF(/mob/living/silicon, clear_throw_slowdown)), (P.throwforce / 10) SECONDS) + return ..() + +/mob/living/silicon/proc/clear_throw_slowdown() + src.remove_movespeed_modifier(/datum/movespeed_modifier/borg_throw) diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index 308a7a838c6..a39bfb4b623 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -100,6 +100,8 @@ var/lockcharge = FALSE ///Boolean of whether the borg was locked by its AI or nothing var/ai_lockdown = FALSE + ///Timer that allows the borg to self-unlock after a set amount of time + var/lockdown_timer = null ///Random serial number generated for each cyborg upon its initialization var/ident = 0 var/locked = TRUE diff --git a/code/modules/mob/living/silicon/robot/robot_model.dm b/code/modules/mob/living/silicon/robot/robot_model.dm index ad70a6fa7ec..033d52a9622 100644 --- a/code/modules/mob/living/silicon/robot/robot_model.dm +++ b/code/modules/mob/living/silicon/robot/robot_model.dm @@ -276,6 +276,7 @@ flick("[cyborg_base_icon]_transform", cyborg) cyborg.notransform = TRUE if(locked_transform) + cyborg.ai_lockdown = TRUE cyborg.SetLockdown(TRUE) cyborg.set_anchored(TRUE) cyborg.logevent("Chassis model has been set to [name].") @@ -284,6 +285,7 @@ playsound(cyborg, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1) sleep(0.7 SECONDS) cyborg.SetLockdown(FALSE) + cyborg.ai_lockdown = FALSE cyborg.setDir(SOUTH) cyborg.set_anchored(FALSE) cyborg.notransform = FALSE diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index 0106202e32d..847e6867521 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -100,7 +100,7 @@ /mob/living/silicon/emp_act(severity) . = ..() to_chat(src, span_danger("Warning: Electromagnetic pulse detected.")) - if(. & EMP_PROTECT_SELF) + if(. & EMP_PROTECT_SELF || QDELETED(src)) return switch(severity) if(1) diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index bc879368b64..1b27af0eb9f 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -84,6 +84,9 @@ /datum/movespeed_modifier/shove multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH +/datum/movespeed_modifier/borg_throw + multiplicative_slowdown = 0.9 + /datum/movespeed_modifier/human_carry multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN blacklisted_movetypes = FLOATING