diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index be662ecf7cd..de4a5cb6b9f 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -6,6 +6,8 @@
faction += "\ref[src]"
determine_move_and_pull_forces()
GLOB.mob_living_list += src
+ if(advanced_bullet_dodge_chance)
+ RegisterSignal(src, COMSIG_ATOM_PREHIT, PROC_REF(advanced_bullet_dodge))
// Used to determine the forces dependend on the mob size
// Will only change the force if the force was not set in the mob type itself
@@ -51,6 +53,7 @@
QDEL_NULL(middleClickOverride)
if(mind?.current == src)
mind.current = null
+ UnregisterSignal(src, COMSIG_ATOM_PREHIT)
return ..()
/mob/living/ghostize(can_reenter_corpse = 1)
@@ -1071,6 +1074,9 @@
update_transform()
if("lighting_alpha")
sync_lighting_plane_alpha()
+ if("advanced_bullet_dodge_chance")
+ UnregisterSignal(src, COMSIG_ATOM_PREHIT)
+ RegisterSignal(src, COMSIG_ATOM_PREHIT, PROC_REF(advanced_bullet_dodge))
/mob/living/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback, force, dodgeable, block_movement)
stop_pulling()
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index aef08f931e7..1715f148a85 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -51,6 +51,33 @@
check_projectile_dismemberment(P, def_zone)
return P.on_hit(src, armor, def_zone)
+/// Tries to dodge incoming bullets if we aren't disabled for any reasons. Advised to overide with advanced effects, this is as basic example admins can apply.
+/mob/living/proc/advanced_bullet_dodge(mob/living/source, obj/item/projectile/hitting_projectile)
+ SIGNAL_HANDLER
+
+ if(HAS_TRAIT(source, TRAIT_IMMOBILIZED))
+ return NONE
+ if(source.stat != CONSCIOUS)
+ return NONE
+ if(!prob(advanced_bullet_dodge_chance))
+ return NONE
+
+ source.visible_message(
+ "[source] [pick("dodges","jumps out of the way of","evades","dives out of the way of")] [hitting_projectile]!",
+ "You evade [hitting_projectile]!",
+ )
+ playsound(source, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE)
+ var/dir_to_avoid = angle2dir_cardinal(hitting_projectile.Angle)
+ var/list/potential_first_directions = list(NORTH, SOUTH, EAST, WEST)
+ potential_first_directions -= dir_to_avoid
+ step(source, pick(potential_first_directions))
+ // Chance to dodge multiple shotgun spreads, but not likely. Mainly: Infinite loop prevention from admins setting it to 100 and doing something stupid.
+ // If you want to set your dodge chance to 100 on a subtype, no issue: Just make sure the subtype does not step in a direction, otherwise you'll have the mob move a large distance to dodge rubbershot.
+ if(prob(50))
+ addtimer(VARSET_CALLBACK(src, advanced_bullet_dodge_chance, advanced_bullet_dodge_chance), 0.25 SECONDS) // Returns fast enough for multiple laser shots.
+ advanced_bullet_dodge_chance = 0
+ return ATOM_PREHIT_FAILURE
+
/mob/living/proc/check_projectile_dismemberment(obj/item/projectile/P, def_zone)
return 0
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 8928ad4c6c0..09c0f5375bd 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -100,6 +100,9 @@
/// Famous last words -- if succumbing, what the user's last words were
var/last_words
+ ///This variable is the chance for a mob to automatically dodge a bullet. Useful for admins, and applied to some mobs by default, such as the malfunctioning drone mobs.
+ var/advanced_bullet_dodge_chance = 0
+
/*
Taste Vars
*/
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/combat_drone.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/combat_drone.dm
index 38e012c6fc8..f66f763b252 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/combat_drone.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/combat_drone.dm
@@ -30,6 +30,7 @@
faction = list("malf_drone")
deathmessage = "suddenly breaks apart."
del_on_death = TRUE
+ advanced_bullet_dodge_chance = 25 // This will be adjusted when active, vs deactivated. Randomises on hit if it is zero.
var/passive_mode = TRUE // if true, don't target anything.
/mob/living/simple_animal/hostile/malf_drone/Initialize(mapload)
@@ -69,6 +70,8 @@
do_sparks(3, 1, src)
passive_mode = FALSE
update_icons()
+ if(!advanced_bullet_dodge_chance)
+ advanced_bullet_dodge_chance = 25
. = ..() // this will handle finding a target if there is a valid one nearby
/mob/living/simple_animal/hostile/malf_drone/Life(seconds, times_fired)
@@ -83,12 +86,38 @@
passive_mode = !passive_mode
if(passive_mode)
visible_message("[src] retracts several targetting vanes.")
+ advanced_bullet_dodge_chance = 0
if(target)
LoseTarget()
else
visible_message("[src] suddenly lights up, and additional targetting vanes slide into place.")
+ advanced_bullet_dodge_chance = 25
update_icons()
+/// We overide the basic effect, as malfunctioning drones are in space, and use jets to dodge. Also lets us do cool effects.
+/mob/living/simple_animal/hostile/malf_drone/advanced_bullet_dodge(mob/living/source, obj/item/projectile/hitting_projectile)
+ if(HAS_TRAIT(source, TRAIT_IMMOBILIZED))
+ return NONE
+ if(source.stat != CONSCIOUS)
+ return NONE
+ if(!prob(advanced_bullet_dodge_chance))
+ return NONE
+
+ source.visible_message(
+ "[source]'s jets [pick("boosts", "propels", "pulses", "flares up and moves", "shudders and pushes")] it out of '[hitting_projectile]'s way!",
+ "You evade [hitting_projectile]!",
+ )
+ playsound(source, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg', 'sound/effects/refill.ogg'), 75, TRUE)
+ var/dir_to_avoid = angle2dir_cardinal(hitting_projectile.Angle)
+ var/list/potential_first_directions = list(NORTH, SOUTH, EAST, WEST)
+ potential_first_directions -= dir_to_avoid
+ new /obj/effect/temp_visual/decoy/fading(source.loc, source)
+ step(source, pick(potential_first_directions))
+ if(prob(50))
+ addtimer(VARSET_CALLBACK(source, advanced_bullet_dodge_chance, advanced_bullet_dodge_chance), 0.25 SECONDS)
+ advanced_bullet_dodge_chance = 0
+ return ATOM_PREHIT_FAILURE
+
/mob/living/simple_animal/hostile/malf_drone/emp_act(severity)
adjustHealth(100 / severity) // takes the same damage as a mining drone from emp