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
🆑
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
/🆑

---------

Co-authored-by: ATH1909 <42606352+ATH1909@users.noreply.github.com>
Co-authored-by: Jacquerel <hnevard@gmail.com>
This commit is contained in:
Kubisopplay
2023-07-26 23:09:19 +02:00
committed by GitHub
parent d6b176cd78
commit b3ac4e1c7d
10 changed files with 143 additions and 30 deletions
+71 -11
View File
@@ -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")]: <a href='?src=[REF(R.connected_ai)];track=[html_encode(R.name)]'>[R.name]</a><br>")
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")]: <a href='?src=[REF(R.connected_ai)];track=[html_encode(R.name)]'>[R.name]</a><br>")
/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 ..()
@@ -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
+19 -6
View File
@@ -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
+11 -5
View File
@@ -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!"))
+14 -4
View File
@@ -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, "<br><br>[span_alert("ALERT - Remote system lockdown engaged. Trying to hack the lockdown subsystem...")]<br>")
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, "<br><br>[span_alert("ALERT - Remote system lockdown override failed.")]<br>")
return
set_lockcharge(FALSE)
to_chat(src, "<br><br>[span_notice("ALERT - Remote system lockdown override successful.")]<br>")
if(connected_ai)
to_chat(connected_ai, "<br><br>[span_notice("ALERT - Cyborg [name] succesfully overriden the lockdown system")]<br>")
///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
@@ -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)
@@ -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
@@ -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
@@ -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)
+3
View File
@@ -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