diff --git a/code/__DEFINES/conflict.dm b/code/__DEFINES/conflict.dm
index 17fd8b32c2..24d86e5d49 100644
--- a/code/__DEFINES/conflict.dm
+++ b/code/__DEFINES/conflict.dm
@@ -3,6 +3,8 @@
#define GRAB_AGGRESSIVE 1
#define GRAB_CARRY 2
#define GRAB_CHOKE 3
+/// Alien-specific grab, somewhat between an aggressive and choke. Limited to AI only for the moment.
+#define GRAB_XENO 4
//Ammo defines for gun/projectile related things.
diff --git a/code/__DEFINES/pain.dm b/code/__DEFINES/pain.dm
index 8c84dd1070..5867eb52cb 100644
--- a/code/__DEFINES/pain.dm
+++ b/code/__DEFINES/pain.dm
@@ -15,6 +15,11 @@
#define PAIN_REDUCTION_FULL 80 //oxycodone, neuraline
// Pain amount supplied by the action
+
+/// How much pain is dealt continually to the mob being dragged by the AI as well as when initially grabbing.
+#define PAIN_XENO_DRAG 10
+/// How much additional pain is dealt when lunging/throat grabbing.
+#define PAIN_XENO_GRAB 15
#define PAIN_BONE_BREAK 25
#define PAIN_BONE_BREAK_SPLINTED 15
#define PAIN_DELIMB 40
diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm
index 7e30291260..fd43b47990 100644
--- a/code/__DEFINES/xeno.dm
+++ b/code/__DEFINES/xeno.dm
@@ -689,8 +689,9 @@
#define XENO_CASTE_HIVELORD "Hivelord"
#define XENO_CASTE_LURKER "Lurker"
#define XENO_CASTE_WARRIOR "Warrior"
+#define XENO_CASTE_SOLDIER "Soldier"
#define XENO_CASTE_SPITTER "Spitter"
-#define XENO_T2_CASTES list(XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER)
+#define XENO_T2_CASTES list(XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER, XENO_CASTE_SOLDIER)
//t3
#define XENO_CASTE_BOILER "Boiler"
#define XENO_CASTE_PRAETORIAN "Praetorian"
@@ -704,7 +705,7 @@
#define XENO_CASTE_HELLHOUND "Hellhound"
#define XENO_SPECIAL_CASTES list(XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND)
-#define ALL_XENO_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER, XENO_CASTE_LESSER_DRONE, XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_SENTINEL, XENO_CASTE_DEFENDER, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER, XENO_CASTE_BOILER, XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER, XENO_CASTE_RAVAGER, XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND, XENO_CASTE_KING)
+#define ALL_XENO_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER, XENO_CASTE_LESSER_DRONE, XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_SENTINEL, XENO_CASTE_DEFENDER, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER, XENO_CASTE_BOILER, XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER, XENO_CASTE_RAVAGER, XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND, XENO_CASTE_SOLDIER, XENO_CASTE_KING)
// Checks if two hives are allied to each other.
// PARAMETERS:
diff --git a/code/game/jobs/role_authority.dm b/code/game/jobs/role_authority.dm
index 859ec0647a..49123d3db9 100644
--- a/code/game/jobs/role_authority.dm
+++ b/code/game/jobs/role_authority.dm
@@ -744,6 +744,8 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou
M = /mob/living/carbon/xenomorph/predalien
if(XENO_CASTE_HELLHOUND)
M = /mob/living/carbon/xenomorph/hellhound
+ if(XENO_CASTE_SOLDIER)
+ M = /mob/living/carbon/xenomorph/soldieR
if(XENO_CASTE_KING)
M = /mob/living/carbon/xenomorph/king
return M
diff --git a/code/game/objects/effects/aliens.dm b/code/game/objects/effects/aliens.dm
index 45fbd5d4ba..8753fa7a35 100644
--- a/code/game/objects/effects/aliens.dm
+++ b/code/game/objects/effects/aliens.dm
@@ -322,6 +322,12 @@
flare_damage = 1875
icon_state = "acid_strong"
+//Similar to strong acid, just not quite as strong other than barricade damage.
+/obj/effect/xenomorph/acid/spatter
+ acid_delay = 0.6
+ barricade_damage = 100
+ flare_damage = 1000
+
/obj/effect/xenomorph/acid/Initialize(mapload, atom/target)
. = ..()
acid_t = target
@@ -334,6 +340,21 @@
RegisterSignal(acid_t, COMSIG_PARENT_QDELETING, PROC_REF(cleanup))
START_PROCESSING(SSoldeffects, src)
+/obj/effect/xenomorph/acid/spatter/Initialize(mapload, atom/target)
+ . = ..()
+ if(!acid_t)
+ var/obj/structure/barricade/B = locate() in loc
+ if(B && !B.unacidable) acid_t = B
+ else
+ for(var/obj/O in loc) //Find the first thing.
+ if(O.unacidable || istype(O, /obj/effect)) continue //Not unacidable things or effects. Don't want to melt xenogibs.
+ acid_t = O
+ break
+ if(acid_t) layer = acid_t.layer
+ else
+ animate(src, alpha = 0, 1 SECONDS)
+ QDEL_IN(src, 1 SECONDS) //No point in keeping it if it's not melting anything.
+
/obj/effect/xenomorph/acid/Destroy()
acid_t = null
STOP_PROCESSING(SSoldeffects, src)
@@ -428,8 +449,11 @@
else
for(var/mob/mob in acid_t)
mob.forceMove(loc)
- qdel(acid_t)
- qdel(src)
+ animate(acid_t, alpha = 0, 1 SECONDS)
+ QDEL_IN(acid_t, 1 SECONDS)
+
+ animate(src, alpha = 0, 1 SECONDS)
+ QDEL_IN(src, 1 SECONDS)
/obj/effect/xenomorph/boiler_bombard
name = "???"
diff --git a/code/modules/admin/game_master/game_master.dm b/code/modules/admin/game_master/game_master.dm
index 5370701893..330c7ef855 100644
--- a/code/modules/admin/game_master/game_master.dm
+++ b/code/modules/admin/game_master/game_master.dm
@@ -29,7 +29,7 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100)
// Spawn stuff
#define DEFAULT_SPAWN_XENO_STRING XENO_CASTE_DRONE
-#define GAME_MASTER_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_LURKER, XENO_CASTE_CRUSHER, XENO_CASTE_FACEHUGGER)
+#define GAME_MASTER_AI_XENOS list(XENO_CASTE_DRONE, XENO_CASTE_SOLDIER, XENO_CASTE_RUNNER, XENO_CASTE_LURKER, XENO_CASTE_CRUSHER, XENO_CASTE_FACEHUGGER)
#define DEFAULT_SPAWN_HIVE_STRING XENO_HIVE_NORMAL
#define DEFAULT_XENO_AMOUNT_TO_SPAWN 1
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index f202e0a6ec..2becfa0066 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -98,7 +98,7 @@ you will have to do something like if(client.admin_holder.rights & R_ADMIN) your
if(!other)
return FALSE
if(rights_required && other.admin_holder?.rank)
- if(check_client_rights(usr.client, rights_required, show_msg))
+ if(check_client_rights(other, rights_required, show_msg))
return TRUE
else if(show_msg)
to_chat(usr, SPAN_WARNING("You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")]."))
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index cfb81066d2..31e5a7f270 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -201,7 +201,7 @@
if(istype(brain))
return TRUE
return FALSE
-
+f
/mob/living/carbon/human/has_eyes()
var/datum/internal_organ/eyes = LAZYACCESS(internal_organs_by_name, "eyes")
if(istype(eyes) && !eyes.cut_away)
@@ -210,7 +210,7 @@
/mob/living/carbon/human/is_mob_restrained(check_grab = TRUE)
- if(check_grab && pulledby && pulledby.grab_level >= GRAB_AGGRESSIVE)
+ if(check_grab && pulledby && pulledby.grab_level > GRAB_PASSIVE && pulledby.grab_level != GRAB_XENO)
return TRUE
if (handcuffed)
return TRUE
diff --git a/code/modules/mob/living/carbon/human/life/handle_breath.dm b/code/modules/mob/living/carbon/human/life/handle_breath.dm
index 7dafdb6e17..6e5f99badd 100644
--- a/code/modules/mob/living/carbon/human/life/handle_breath.dm
+++ b/code/modules/mob/living/carbon/human/life/handle_breath.dm
@@ -8,7 +8,7 @@
return
if(istype(loc, /obj/structure/machinery/cryo_cell))
return
- if(species && (species.flags & NO_BREATHE || species.flags & IS_SYNTHETIC))
+ if(species && (species.flags & (NO_BREATHE|IS_SYNTHETIC)))
return
var/list/air_info
diff --git a/code/modules/mob/living/carbon/human/life/handle_grabbed.dm b/code/modules/mob/living/carbon/human/life/handle_grabbed.dm
index 0c588da7ca..6f8d2526a4 100644
--- a/code/modules/mob/living/carbon/human/life/handle_grabbed.dm
+++ b/code/modules/mob/living/carbon/human/life/handle_grabbed.dm
@@ -1,15 +1,19 @@
-
+// Why is this proc in its own .dm file?
/mob/living/carbon/proc/handle_grabbed()
if(!pulledby)
return
- if(pulledby.grab_level >= GRAB_AGGRESSIVE)
- drop_held_items()
- if(pulledby.grab_level >= GRAB_CHOKE)
- apply_damage(3, OXY)
- apply_stamina_damage(5)
+ /// Rewrote this to be a little different, so that the normal effects don't apply to xeno grabs.
+ switch(pulledby.grab_level)
+ if(GRAB_AGGRESSIVE to GRAB_CARRY)
+ drop_held_items()
+ if(GRAB_CHOKE)
+ drop_held_items()
+ apply_damage(3, OXY)
+ apply_stamina_damage(5)
+ if(GRAB_XENO) /// Alien grabs inflict pain when the human is not incapacitated, but not the other effects.
+ /// Ignoring restrained with (TRUE). The grab does *not* restrain by itself. See /mob/living/carbon/human/is_mob_restrained(check_grab = TRUE)
+ /// This is too allow the human a chance to fight back, with guns or melee.
+ return is_mob_incapacitated(TRUE) || pain.apply_pain(PAIN_XENO_DRAG)
- log_attack("[key_name(pulledby)] choked [key_name(src)] at [get_area_name(src)]")
- attack_log += text("\[[time_stamp()]\] was choked by [key_name(pulledby)]")
- pulledby.attack_log += text("\[[time_stamp()]\] choked [key_name(src)]")
diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
index 10df5ae197..b9e4dca999 100644
--- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
@@ -243,6 +243,8 @@
var/evolving = FALSE // Whether the xeno is in the process of evolving
/// The damage dealt by a xeno whenever they take damage near someone
var/acid_blood_damage = 15
+ /// Does their blood also create an acid object to melt the environment?
+ var/acid_blood_spatter = FALSE
var/nocrit = FALSE
var/deselect_timer = 0 // Much like Carbon.last_special is a short tick record to prevent accidental deselects of abilities
var/got_evolution_message = FALSE
diff --git a/code/modules/mob/living/carbon/xenomorph/ai/movement/base_define.dm b/code/modules/mob/living/carbon/xenomorph/ai/movement/base_define.dm
index ef7cae6e23..affb2ab5a6 100644
--- a/code/modules/mob/living/carbon/xenomorph/ai/movement/base_define.dm
+++ b/code/modules/mob/living/carbon/xenomorph/ai/movement/base_define.dm
@@ -8,6 +8,9 @@
var/home_locate_range = 15
var/turf/home_turf
+ /// Should the alien try climbing barricades and other structures, if able?
+ var/do_climb_structures = FALSE
+
/datum/xeno_ai_movement/New(mob/living/carbon/xenomorph/parent)
. = ..()
if(!parent)
diff --git a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
index e87de58155..4724ba9dc8 100644
--- a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
+++ b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
@@ -30,6 +30,9 @@
/// Cooldown declaration for delaying finding a new path if no path was found
COOLDOWN_DECLARE(no_path_found_cooldown)
+ var/ai_active_intent = INTENT_HARM
+ var/target_unconscious = FALSE
+
/mob/living/carbon/xenomorph/proc/init_movement_handler()
return new /datum/xeno_ai_movement(src)
@@ -63,14 +66,14 @@
var/datum/component/ai_behavior_override/behavior_override = check_overrides()
- if(behavior_override?.process_override_behavior(src, delta_time))
+ if(is_mob_incapacitated(TRUE)) ///If they are incapacitated, the rest doesn't matter.
+ current_path = null
return TRUE
- if(is_mob_incapacitated(TRUE))
- current_path = null
+ if(behavior_override?.process_override_behavior(src, delta_time))
return TRUE
- if(QDELETED(current_target) || !current_target.ai_check_stat() || get_dist(current_target, src) > ai_range || COOLDOWN_FINISHED(src, forced_retarget_cooldown))
+ if(QDELETED(current_target) || !current_target.ai_check_stat(src) || get_dist(current_target, src) > ai_range || COOLDOWN_FINISHED(src, forced_retarget_cooldown))
current_target = get_target(ai_range)
COOLDOWN_START(src, forced_retarget_cooldown, forced_retarget_time)
if(QDELETED(src))
@@ -81,7 +84,7 @@
if(prob(5))
emote("hiss")
- a_intent = INTENT_HARM
+ a_intent = ai_active_intent
if(!current_target)
ai_move_idle(delta_time)
@@ -133,7 +136,7 @@
return 0
return INFINITY
-/atom/proc/ai_check_stat()
+/atom/proc/ai_check_stat(mob/living/carbon/xenomorph/X)
return TRUE // So we aren't trying to find a new target on attack override
// Called whenever an obstacle is encountered but xeno_ai_obstacle returned something else than infinite
@@ -267,28 +270,6 @@
#undef EXTRA_CHECK_DISTANCE_MULTIPLIER
-/mob/living/carbon/proc/ai_can_target(mob/living/carbon/xenomorph/X)
- if(!ai_check_stat(X))
- return FALSE
-
- if(X.can_not_harm(src))
- return FALSE
-
- if(alpha <= 45 && get_dist(X, src) > 2)
- return FALSE
-
- if(isfacehugger(X))
- if(status_flags & XENO_HOST)
- return FALSE
-
- if(istype(wear_mask, /obj/item/clothing/mask/facehugger))
- return FALSE
-
- else if(HAS_TRAIT(src, TRAIT_NESTED))
- return FALSE
-
- return TRUE
-
/mob/living/carbon/xenomorph/proc/make_ai()
SHOULD_CALL_PARENT(TRUE)
create_hud()
@@ -343,3 +324,7 @@
if(cycled_turf.x == min_x_value)
min_x_turfs += cycled_turf
return min_x_turfs
+
+/// Override as necessary to check for more specific triggers for an ability activation.
+/mob/living/carbon/xenomorph/proc/check_additional_ai_activation()
+ return TRUE
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm
index 2404823fad..0c31e59d5d 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm
@@ -106,7 +106,7 @@
add_temp_negative_pass_flags(PASS_FLAGS_CRAWLER)
-/mob/living/carbon/xenomorph/runner/stop_pulling()
+/mob/living/carbon/xenomorph/runner/stop_pulling(bumped_movement = FALSE)
. = ..()
remove_temp_negative_pass_flags(PASS_FLAGS_CRAWLER)
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Soldier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Soldier.dm
new file mode 100644
index 0000000000..2b8b856ae7
--- /dev/null
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Soldier.dm
@@ -0,0 +1,281 @@
+/datum/caste_datum/soldier
+ caste_type = XENO_CASTE_SOLDIER
+ tier = 2
+ melee_damage_lower = XENO_DAMAGE_TIER_3
+ melee_damage_upper = XENO_DAMAGE_TIER_4
+ melee_vehicle_damage = XENO_DAMAGE_TIER_3
+ max_health = XENO_HEALTH_TIER_2
+ plasma_gain = XENO_PLASMA_GAIN_TIER_8
+ plasma_max = XENO_PLASMA_TIER_10
+ crystal_max = XENO_CRYSTAL_LOW
+ xeno_explosion_resistance = XENO_EXPLOSIVE_ARMOR_TIER_1
+ armor_deflection = XENO_ARMOR_TIER_1
+ evasion = XENO_EVASION_MEDIUM
+ speed = XENO_SPEED_HELLHOUND //Faster than drones, slower than lurkers.
+
+ caste_desc = "A warrior of the hive."
+ evolves_to = list(XENO_CASTE_QUEEN, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD) //Add more here separated by commas
+ deevolves_to = list("Drone")
+
+ tackle_min = 2
+ tackle_max = 4
+
+ minimap_icon = "warrior"
+
+/mob/living/carbon/xenomorph/soldier
+ caste_type = XENO_CASTE_SOLDIER
+ name = XENO_CASTE_SOLDIER
+ desc = "An alien warrior."
+ icon = 'icons/mob/xenos/soldier.dmi'
+ icon_size = 48
+ icon_state = "Soldier Walking"
+ plasma_types = list(PLASMA_PURPLE)
+ tier = 2
+ pixel_x = -12
+ old_x = -12
+
+ gib_chance = 100
+ claw_type = CLAW_TYPE_SHARP
+ pull_multiplier = 0.2 /// Pretty much no pull delay, for those quick drags.
+
+ acid_blood_damage = 35 /// Strong acid blood. Should be a define in the future.
+ acid_blood_spatter = TRUE /// Testing variable, means that their blood can melt objects in the environment. Primarily barricades.
+
+ mutation_type = SOLDIER_NORMAL
+ icon_xeno = 'icons/mob/xenos/soldier.dmi'
+ icon_xenonid = 'icons/mob/xenonids/drone.dmi'
+
+ target_unconscious = TRUE
+
+/*
+ * ==========================================================================|
+ * BASE DEFINES AND PROCS
+ * --------------------------------------------------------------------------|
+ * ==========================================================================|
+*/
+
+/// The aggression the alien starts with by default.
+#define AGGRESSION_MINIMUM 0
+/// The maximum aggression it is possible to accumulate.
+#define AGGRESSION_MAXIMUM 100
+/// Aggression required to go full-on lethals instead of capture.
+#define AGGRESSION_ENABLE_LETHAL 50
+/// If there is no hive to drag people to, this is the aggression the alien starts with.
+#define AGGRESSION_NO_HIVE AGGRESSION_ENABLE_LETHAL
+/// The cap on aggression increase when something significant happens, like taking damage.
+#define AGGRESSION_INCREMENT_CAP 10
+
+/// Threshold for lunging at a target. Low as the alien wants to do this early.
+#define AGGRESSION_LUNGE 10
+/// Threshold for throwing humans around.
+#define AGGRESSION_FLING 35
+/// Threshold for stabbing them with the tail.
+#define AGGRESSION_TAIL_STAB 70
+/// Threshold for headbiting/instakilling a downed human.
+#define AGGRESSION_HEADBITE 90
+
+ /// AI behavior variables should probably go into their own datum. I thought about placing this into movement handler, but these govern general behavior.
+ var/aggression_cur = AGGRESSION_MINIMUM ///How angry this alien is. Higher aggression means more lethal options are open.
+ var/aggression_min = AGGRESSION_MINIMUM ///Aggression cannot fall below this value.
+
+/mob/living/carbon/xenomorph/soldier/Initialize()
+ base_actions = list(
+ /datum/action/xeno_action/onclick/xeno_resting,
+ /datum/action/xeno_action/onclick/regurgitate,
+ /datum/action/xeno_action/watch_xeno,
+ /datum/action/xeno_action/activable/tail_stab/soldier,
+ /datum/action/xeno_action/activable/fling/soldier,
+ /datum/action/xeno_action/activable/lunge/soldier,
+ /datum/action/xeno_action/activable/headbite/soldier,
+ /datum/action/xeno_action/onclick/tacmap,
+ )
+ inherent_verbs = list(
+ /mob/living/carbon/xenomorph/proc/vent_crawl,
+ )
+
+ . = ..()
+
+ if(!length(GLOB.ai_hives)) ///We check for this first. If there is a hive, we want to make sure the aliens drag people there.
+ aggression_cur = AGGRESSION_NO_HIVE ///If we do not have a hive, bump up aggression to go lethal instead.
+ aggression_min = AGGRESSION_NO_HIVE
+
+ /// This is so stupid. Mutators are set inline with new, and are not created on Initialize(). However, pull_multiplier is then overriden by the personal mutator pull_multiplier on Initialize() with recalculate_actions().
+ mutators.pull_multiplier = initial(pull_multiplier)
+ pull_multiplier = mutators.pull_multiplier
+
+/mob/living/carbon/xenomorph/soldier/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, used_weapon = null, sharp = 0, edge = 0, force = FALSE)
+ . = ..() //It will take the previous arguments.
+
+ if(.)
+ /// Fire gives the largest aggression increase.
+ aggression_cur = min(AGGRESSION_MAXIMUM, aggression_cur + min(AGGRESSION_INCREMENT_CAP, damage * (damagetype == BURN ? 0.2 : 0.13) ) ) /// Multiplying is faster than dividing. Go figure.
+
+/*
+ * ==========================================================================|
+ * MOVEMENT LOOPS AND BEHAVIOR LOGIC
+ * --------------------------------------------------------------------------|
+ * ==========================================================================|
+*/
+
+/mob/living/carbon/xenomorph/soldier/process_ai(delta_time)
+ var/mob/living/pulling_target = pulling /// Let's see if the alien is pulling anyone.
+ var/mob/living/potential_target = current_target
+
+ if(istype(pulling_target)) /// Our soldier is pulling someone.
+ if(get_active_hand()) swap_hand() /// Swap hand to either tackle or harm.
+ ai_active_intent = INTENT_DISARM /// If we are pulling someone and are not too aggressive, switch to disarm.
+ if(prob(5)) emote("tail")
+
+ else if(istype(potential_target)) /// We have a target. We'll do more thorough checking in the main loop, for now we only need to know if they are being pulled by a hostile or friendly xeno.
+ var/mob/living/carbon/xenomorph/other_xenomorph = potential_target.pulledby /// Are they being pulled by an alien?
+ /// Need to make sure the alien dragging is friendly to us. If it is not friendly, or not a xeno, our alien will try to grab back.
+ ai_active_intent = (istype(other_xenomorph) && IS_SAME_HIVENUMBER(src, other_xenomorph)) ? INTENT_DISARM : INTENT_GRAB
+
+ /// I had it set up for slightly faster assignment, but this is easier to read.
+ ai_active_intent = (aggression_cur >= AGGRESSION_ENABLE_LETHAL) ? INTENT_HARM : ai_active_intent /// Override harm or continue with the previous intent.
+
+ return ..()
+
+/datum/xeno_ai_movement/assault
+ do_climb_structures = TRUE /// The alien will climb structures, if able.
+
+/mob/living/carbon/xenomorph/soldier/init_movement_handler()
+ return new /datum/xeno_ai_movement/assault(src)
+
+/mob/living/carbon/xenomorph/soldier/ai_move_idle(delta_time)
+ if(!ai_movement_handler)
+ CRASH("No valid movement handler for [src]!")
+
+ var/mob/living/pulling_target = pulling
+ return (istype(pulling_target) && length(GLOB.ai_hives)) ? ai_movement_handler.ai_move_hive(delta_time) : ai_movement_handler.ai_move_idle(delta_time)
+
+/mob/living/carbon/xenomorph/soldier/ai_move_target(delta_time)
+ if(!ai_movement_handler)
+ CRASH("No valid movement handler for [src]!")
+
+ var/mob/living/pulling_target = pulling
+ return (istype(pulling_target) && length(GLOB.ai_hives)) ? ai_movement_handler.ai_move_hive(delta_time) : ai_movement_handler.ai_move_target(delta_time)
+
+/datum/xeno_ai_movement/assault/ai_move_target(delta_time)
+ var/mob/living/carbon/xenomorph/soldier/current_parent = parent
+
+ /// Moving toward target grows aggression.
+ current_parent.aggression_cur = min(AGGRESSION_MAXIMUM, current_parent.aggression_cur + 0.1) ///Fractions for a more gradual aggression gain/loss.
+ return ..()
+
+/datum/xeno_ai_movement/assault/ai_move_idle(delta_time)
+ var/mob/living/carbon/xenomorph/soldier/current_parent = parent
+
+ /// Being idle drains aggression.
+ current_parent.aggression_cur = max(current_parent.aggression_min, current_parent.aggression_cur - 0.1)
+ return ..()
+
+/datum/xeno_ai_movement/assault/ai_move_hive(delta_time)
+ . = ..()
+
+ /// This will mean if the alien is pulling someone to the hive, they will keep tackling them with their free claw.
+ if(. && parent.pulling && DT_PROB(XENO_SLASH, delta_time)) INVOKE_ASYNC(parent, TYPE_PROC_REF(/mob, do_click), parent.pulling, "", list())
+
+/datum/xeno_ai_movement/assault/ai_strap_host(turf/closest_hive, hive_radius, delta_time)
+ /// Want to make sure when nesting, they actually have the grab active in their main claw.
+ /// This can lead to some funny behavior of the alien standing around with the victim next to them, but it should be fine for the moment.
+ /// Something to address later perhaps.
+ if(parent.pulling && parent.get_active_hand()) parent.swap_hand()
+ return ..()
+
+/*
+ * ==========================================================================|
+ * SPECIAL ACTIONS
+ * --------------------------------------------------------------------------|
+ * ==========================================================================|
+*/
+
+/mob/living/carbon/xenomorph/soldier/check_additional_ai_activation(activation_threshold)
+ return (aggression_cur >= activation_threshold)
+
+/datum/action/xeno_action/activable/tail_stab/soldier /// Specific to soldiers, so that not all xenos get it.
+ default_ai_action = TRUE
+ ai_prob_chance = 65 //So they are not spamming it quite as often.
+ charge_time = null /// AI soldiers can just use this whenever instead of having to charge it up.
+ xeno_cooldown = 11 SECONDS
+
+/datum/action/xeno_action/activable/tail_stab/process_ai(mob/living/carbon/xenomorph/parent, delta_time)
+ /// Short-circuit. Will return the last thing checked or FALSE if it fails at any step.
+ /// We do not need to check for distance here as the tailstab itself will do that; that distance being 2.
+ return parent.check_additional_ai_activation(AGGRESSION_TAIL_STAB) && DT_PROB(ai_prob_chance, delta_time) && use_ability_async(parent.current_target)
+
+/datum/action/xeno_action/activable/fling/soldier
+ default_ai_action = TRUE
+ ai_prob_chance = 70
+ xeno_cooldown = 8 SECONDS
+
+/datum/action/xeno_action/activable/fling/process_ai(mob/living/carbon/xenomorph/parent, delta_time)
+ /// We have a home turf to fling to.
+ if(parent.check_additional_ai_activation(AGGRESSION_FLING) && DT_PROB(ai_prob_chance, delta_time))
+ parent.dir = parent.ai_movement_handler.home_turf ? get_dir(parent, parent.ai_movement_handler.home_turf) : pick(NORTH, SOUTH, EAST, WEST) /// Pick at random if there is no valid direction.
+ use_ability_async(parent.current_target)
+
+/datum/action/xeno_action/activable/lunge/soldier
+ default_ai_action = TRUE
+ grab_range = 2
+ ai_prob_chance = 90 // Want to do this often, as it's their way of saying hello.
+ xeno_cooldown = 10 SECONDS
+
+/datum/action/xeno_action/activable/lunge/process_ai(mob/living/carbon/xenomorph/parent, delta_time)
+ /// Want to make sure no obstacles are in the way so that the alien is not lunging for no reason, or bonking into barricades like an idiot.
+ /// Maybe in the future the actual lunge can be stripped down for the AI only?
+ if( parent.check_additional_ai_activation(AGGRESSION_LUNGE) && DT_PROB(ai_prob_chance, delta_time) && get_dist(parent, parent.current_target) == grab_range )
+ /// get_step_to() should return the turf nearest the target if successful, with no obstacles to block movement there with the lunge.
+ var/turf/T = get_step_to(parent, parent.current_target)
+ return T?.AdjacentQuick(parent.current_target.loc) && use_ability_async(parent.current_target)
+
+/// Override for soldier lunges. Similar to the woyer grab, but this one only inflicts pain without stuns and has a shorter range.
+/mob/living/carbon/xenomorph/soldier/start_pulling(atom/movable/AM, lunge)
+ if (!check_state() || agility || !isliving(AM)) return FALSE
+
+ var/mob/living/L = AM
+ var/should_neckgrab = !can_not_harm(L) && lunge
+
+ if(!QDELETED(L) && !QDELETED(L.pulledby) && L != src ) //override pull of other mobs
+ visible_message(SPAN_WARNING("[src] has broken [L.pulledby]'s grip on [L]!"), null, null, 5)
+ L.pulledby.stop_pulling()
+
+ . = ..(L, lunge, should_neckgrab)
+
+ if(.)
+ var/pain_to_cause = PAIN_XENO_DRAG /// Basic amount of pain caused with each grab.
+ if(should_neckgrab && L.mob_size < MOB_SIZE_BIG)
+ pain_to_cause += PAIN_XENO_GRAB /// Neck grabs cause even more pain.
+ L.pulledby = src
+ visible_message(SPAN_XENOWARNING("\The [src] grabs [L] by the throat!"), \
+ SPAN_XENOWARNING("You grab [L] by the throat!"))
+
+ L.pain.apply_pain(pain_to_cause)
+ grab_level = GRAB_XENO /// Alien-specific grab level, with its own logic for escaping. AI only for the moment. See /mob/living/resist_grab()
+ if(prob(10)) emote("growl")
+ /// The actual pain processing for humans is handled in: /mob/living/carbon/proc/handle_grabbed() Other mobs don't process the effects of the grab, like other xenomorphs.
+
+/mob/living/carbon/xenomorph/soldier/stop_pulling(bumped_movement = FALSE)
+ //Let's see if we can ignore this. If our direction is the same as where the mob went, we likely bumped into them. So we lasso them back.
+ if(bumped_movement && grab_level == GRAB_XENO && get_dir(src, pulling) == dir) /// Only on xeno grabs, so if they are on GRAB_PASSIVE, this doesn't trigger.
+ pulling.loc = get_step_towards(src, pulling) // GET OVER HERE!
+ else return ..()
+
+/datum/action/xeno_action/activable/headbite/soldier
+ default_ai_action = TRUE
+ ai_prob_chance = 95 // Absolutely DEAD if the alien is angry enough.
+ xeno_cooldown = 30 SECONDS // Don't want to chain these, as unlikely as that could be.
+
+/datum/action/xeno_action/activable/headbite/soldier/process_ai(mob/living/carbon/xenomorph/parent, delta_time)
+ return parent.check_additional_ai_activation(AGGRESSION_HEADBITE) && DT_PROB(ai_prob_chance, delta_time) && use_ability_async(parent.current_target)
+
+#undef AGGRESSION_MINIMUM
+#undef AGGRESSION_MAXIMUM
+#undef AGGRESSION_INCREMENT_CAP
+#undef AGGRESSION_ENABLE_LETHAL
+#undef AGGRESSION_NO_HIVE
+
+#undef AGGRESSION_LUNGE
+#undef AGGRESSION_FLING
+#undef AGGRESSION_TAIL_STAB
+#undef AGGRESSION_HEADBITE
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
index 2885dd6ac5..7db17cf54e 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Warrior.dm
@@ -69,7 +69,7 @@
/mob/living/carbon/xenomorph/warrior/throw_item(atom/target)
toggle_throw_mode(THROW_MODE_OFF)
-/mob/living/carbon/xenomorph/warrior/stop_pulling()
+/mob/living/carbon/xenomorph/warrior/stop_pulling(bumped_movement = FALSE)
if(isliving(pulling) && lunging)
lunging = FALSE // To avoid extreme cases of stopping a lunge then quickly pulling and stopping to pull someone else
var/mob/living/lunged = pulling
diff --git a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm
index 9d4de90072..ad01de1fb6 100644
--- a/code/modules/mob/living/carbon/xenomorph/damage_procs.dm
+++ b/code/modules/mob/living/carbon/xenomorph/damage_procs.dm
@@ -292,6 +292,11 @@
playsound(victim, "acid_sizzle", 25, TRUE)
animation_flash_color(victim, "#FF0000") //pain hit flicker
+ if(damtype == BRUTE && acid_blood_spatter && prob(splash_chance)) /// Only brute damage makes sense for the spatter.
+ var/turf/acid_loc = pick(get_step(src,rand(0,8)))
+ if(acid_loc && !(locate(/obj/effect/xenomorph/acid) in acid_loc))
+ new /obj/effect/xenomorph/acid/spatter(acid_loc) ///Don't want to double up. It will target barricades first.
+
/mob/living/carbon/xenomorph/get_target_lock(access_to_check)
if(isnull(access_to_check))
return
diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm b/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm
index 9627ac60c0..599faa5c27 100644
--- a/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm
+++ b/code/modules/mob/living/carbon/xenomorph/xeno_ai_interaction.dm
@@ -140,6 +140,30 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
/mob/living/ai_check_stat(mob/living/carbon/xenomorph/X)
return stat == CONSCIOUS && !(locate(/datum/effects/crit) in effects_list)
+/////////////////////////////
+// CARBON //
+/////////////////////////////
+/mob/living/carbon/proc/ai_can_target(mob/living/carbon/xenomorph/X)
+ if(!ai_check_stat(X))
+ return FALSE
+
+ if(X.can_not_harm(src))
+ return FALSE
+
+ if(alpha <= 45 && get_dist(X, src) > 2)
+ return FALSE
+
+ if(isfacehugger(X))
+ if(status_flags & XENO_HOST)
+ return FALSE
+
+ if(istype(wear_mask, /obj/item/clothing/mask/facehugger))
+ return FALSE
+
+ else if(HAS_TRAIT(src, TRAIT_NESTED))
+ return FALSE
+
+ return TRUE
/////////////////////////////
// HUMANS //
@@ -161,14 +185,10 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
/mob/living/carbon/human/ai_can_target(mob/living/carbon/xenomorph/X)
. = ..()
- if(!.)
- return FALSE
if(species.flags & IS_SYNTHETIC)
return FALSE
- return TRUE
-
/mob/living/carbon/human/ai_check_stat(mob/living/carbon/xenomorph/X)
. = ..()
if(isfacehugger(X))
@@ -232,6 +252,16 @@ At bare minimum, make sure the relevant checks from parent types gets copied in
return SENTRY_PENALTY
+/////////////////////////////
+// STRUCTURE //
+/////////////////////////////
+/// Allows this xenomorph to climb most structures that can be climbed, if they are capable of it.
+/obj/structure/xeno_ai_act(mob/living/carbon/xenomorph/X)
+ if(X.ai_movement_handler.do_climb_structures && can_climb(X))
+ do_climb(X)
+ else
+ X.do_click(src, "", list())
+ return TRUE
/////////////////////////////
// WINDOW FRAME //
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 27697a8938..5842f614b8 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -177,34 +177,34 @@
var/pull_dir = get_dir(src, pulling)
- if(grab_level >= GRAB_CARRY)
- switch(grab_level)
- if(GRAB_CARRY)
- var/direction_to_face = EAST
-
- if(direct & WEST)
- direction_to_face = WEST
-
- pulling.Move(NewLoc, direction_to_face)
- var/mob/living/pmob = pulling
- if(istype(pmob))
- SEND_SIGNAL(pmob, COMSIG_MOB_MOVE_OR_LOOK, TRUE, direction_to_face, direction_to_face)
- else
- pulling.Move(NewLoc, direct)
- else if(get_dist(src, pulling) > 1 || ((pull_dir - 1) & pull_dir)) //puller and pullee more than one tile away or in diagonal position
- var/pulling_dir = get_dir(pulling, T)
- pulling.Move(T, pulling_dir) //the pullee tries to reach our previous position
- if(pulling && get_dist(src, pulling) > 1) //the pullee couldn't keep up
- stop_pulling()
- else
+ switch(grab_level)
+ if(GRAB_CARRY)
+ var/direction_to_face = EAST
+
+ if(direct & WEST)
+ direction_to_face = WEST
+
+ pulling.Move(NewLoc, direction_to_face)
var/mob/living/pmob = pulling
if(istype(pmob))
- SEND_SIGNAL(pmob, COMSIG_MOB_MOVE_OR_LOOK, TRUE, pulling_dir, pulling_dir)
- if(!(flags_atom & DIRLOCK))
- setDir(turn(direct, 180)) //face the pullee
+ SEND_SIGNAL(pmob, COMSIG_MOB_MOVE_OR_LOOK, TRUE, direction_to_face, direction_to_face)
+ if(GRAB_CHOKE)
+ pulling.Move(NewLoc, direct)
+ else
+ if(get_dist(src, pulling) > 1 || ((pull_dir - 1) & pull_dir)) //puller and pullee more than one tile away or in diagonal position
+ var/pulling_dir = get_dir(pulling, T)
+ pulling.Move(T, pulling_dir) //the pullee tries to reach our previous position
+ if(pulling && get_dist(src, pulling) > 1) //the pullee couldn't keep up
+ stop_pulling()
+ else
+ var/mob/living/pmob = pulling
+ if(istype(pmob))
+ SEND_SIGNAL(pmob, COMSIG_MOB_MOVE_OR_LOOK, TRUE, pulling_dir, pulling_dir)
+ if(!(flags_atom & DIRLOCK))
+ setDir(turn(direct, 180)) //face the pullee
if(pulledby && get_dist(src, pulledby) > 1)//separated from our puller and not in the middle of a diagonal move.
- pulledby.stop_pulling()
+ pulledby.stop_pulling(TRUE) /// Mob was likely bumped by the pulledby. Can lead to additional checking, if we don't want the victim to get out of jail for free.
if (s_active && !( s_active in contents ) && get_turf(s_active) != get_turf(src)) //check !( s_active in contents ) first so we hopefully don't have to call get_turf() so much.
s_active.storage_close(src)
@@ -216,42 +216,48 @@
if(back && (back.flags_item & ITEM_OVERRIDE_NORTHFACE))
update_inv_back()
-
-
/mob/proc/resist_grab(moving_resist)
return //returning 1 means we successfully broke free
/mob/living/resist_grab(moving_resist)
if(!pulledby)
return
- // vars for checks of strengh
- var/pulledby_is_strong = HAS_TRAIT(pulledby, TRAIT_SUPER_STRONG)
- var/src_is_strong = HAS_TRAIT(src, TRAIT_SUPER_STRONG)
- if(!pulledby.grab_level && (!pulledby_is_strong || src_is_strong)) // if passive grab, check if puller is stronger than src, and if not, break free
- pulledby.stop_pulling()
- return TRUE
-
- // Chance for person to break free of grip, defaults to 50.
- var/chance = 50
- if(src_is_strong && !isxeno(pulledby)) // no extra chance to resist warrior grabs
- chance += 30 // you are strong, you can overpower them easier
- if(pulledby_is_strong)
- chance -= 30 // stronger grip
- // above code means that if you are super strong, 80% chance to resist, otherwise, 20 percent. if both are super strong, standard 50.
-
- if(prob(chance))
- playsound(loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7)
- visible_message(SPAN_DANGER("[src] has broken free of [pulledby]'s grip!"), max_distance = 5)
- pulledby.stop_pulling()
- return TRUE
- if(moving_resist && client) //we resisted by trying to move
- visible_message(SPAN_DANGER("[src] struggles to break free of [pulledby]'s grip!"), max_distance = 5)
- // +1 delay if super strong, also done as passive grabs would have a modifier of 0 otherwise, causing spam
- if(pulledby_is_strong && !src_is_strong)
- client.next_movement = world.time + (10*(pulledby.grab_level + 1)) + client.move_delay
+ switch(pulledby.grab_level)
+ if(GRAB_AGGRESSIVE to GRAB_CHOKE)
+ /// Probably could use some refactoring for the CQC skill, traits, and the like.
+ if(prob(50))
+ playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7)
+ visible_message(SPAN_DANGER("[src] has broken free of [pulledby]'s grip!"), null, null, 5)
+ pulledby.stop_pulling()
+ return TRUE
+ if(moving_resist && client) //we resisted by trying to move
+ visible_message(SPAN_DANGER("[src] struggles to break free of [pulledby]'s grip!"), null, null, 5)
+ client.next_movement = world.time + (10*pulledby.grab_level) + client.move_delay
+ if(GRAB_XENO) /// Specific to some xenomorphs.
+ if(HAS_TRAIT(src, TRAIT_SUPER_STRONG)) /// Superstrength means you get out of jail for free.
+ . = TRUE
+ else
+ /// If the living mob is skilled in CQC, they will get a bonus to escape the grab, 7% per skill level, or 35% if maxed out.
+ var/skill_bonus = (skills?.get_skill_level(SKILL_CQC)) * 7
+ /// Then we need to determine if they loosen the grip or escape outright, or struggle in vain.
+ if(prob((65 - SKILL_CQC_MAX * 7) + skill_bonus)) /// At most a 65% chance to escape outright.
+ . = TRUE
+ else
+ if(prob((100 - SKILL_CQC_MAX * 7) + skill_bonus)) /// Maximum 100% to loosen the grip.
+ visible_message(SPAN_DANGER("[src] loosens [pulledby]'s grip!"), null, null, 5)
+ pulledby.grab_level = GRAB_PASSIVE /// Loosens the grab into a passive grab, being able to escape on the next go.
+ else
+ visible_message(SPAN_DANGER("[src] struggles to break free of [pulledby]'s grip!"), null, null, 5)
+ client.next_movement = world.time + (10 * GRAB_XENO) + client.move_delay /// Grab level may get reset, so we want to keep it at a good delay.
+
+ if(.) /// Our living mob successfully broke away.
+ playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7)
+ visible_message(SPAN_DANGER("[src] has broken free of [pulledby]'s grip!"), null, null, 5)
+ pulledby.stop_pulling()
else
- client.next_movement = world.time + (10*pulledby.grab_level) + client.move_delay
+ pulledby.stop_pulling()
+ return TRUE
/mob/living/movement_delay()
. = ..()
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 0c9b34bbb8..9a86b4855e 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -485,7 +485,7 @@
return do_pull(AM, lunge, no_msg)
-/mob/proc/stop_pulling()
+/mob/proc/stop_pulling(bumped_movement = FALSE)
if(!pulling)
return
diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm
index c326cc81f6..e3d56c4751 100644
--- a/code/modules/mob/mob_grab.dm
+++ b/code/modules/mob/mob_grab.dm
@@ -24,8 +24,11 @@
/obj/item/grab/Destroy()
grabbed_thing = null
- if(ismob(loc))
- var/mob/M = loc
+ var/mob/M = loc
+ if(istype(M))
+ /// If a mob is qdeleted while grabbing, the stack will call for stop_pulling(), which will call for this item to be deleted on drop.
+ /// But it is already being deleted through previous procs in the stack; so we don't want to call the function again when it is dropped.
+ if(M.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) flags_item &= ~DELONDROP
M.grab_level = 0
M.stop_pulling()
. = ..()
diff --git a/colonialmarines.dme b/colonialmarines.dme
index 2acf1dec00..af1730fa65 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -2127,6 +2127,7 @@
#include "code\modules\mob\living\carbon\xenomorph\castes\Ravager.dm"
#include "code\modules\mob\living\carbon\xenomorph\castes\Runner.dm"
#include "code\modules\mob\living\carbon\xenomorph\castes\Sentinel.dm"
+#include "code\modules\mob\living\carbon\xenomorph\castes\Soldier.dm"
#include "code\modules\mob\living\carbon\xenomorph\castes\Spitter.dm"
#include "code\modules\mob\living\carbon\xenomorph\castes\Warrior.dm"
#include "code\modules\mob\living\carbon\xenomorph\items\iff_tag.dm"
diff --git a/icons/mob/xenos/soldier.dmi b/icons/mob/xenos/soldier.dmi
new file mode 100644
index 0000000000..b61fab7b72
Binary files /dev/null and b/icons/mob/xenos/soldier.dmi differ
diff --git a/icons/mob/xenos/wounds.dmi b/icons/mob/xenos/wounds.dmi
index 2501196f75..7b47a9fbfa 100644
Binary files a/icons/mob/xenos/wounds.dmi and b/icons/mob/xenos/wounds.dmi differ