diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
index f66c264fde..0027e6700b 100644
--- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
+++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
@@ -42,6 +42,8 @@
#define COMSIG_XENO_STOP_MOMENTUM "xeno_stop_momentum"
/// Called whenever xeno should resume charge
#define COMSIG_XENO_START_CHARGING "xeno_start_charging"
+/// From /datum/action/xeno_action/onclick/charger_charge/proc/stop_momentum()
+#define COMSIG_XENO_STOPPED_CHARGING "xeno_stopped_charging"
// Used in resin_constructions.dm
// Checks whether the xeno can build a thick structure regardless of hive weeds
diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm
index 935f58f69c..c77dac05fc 100644
--- a/code/__DEFINES/xeno.dm
+++ b/code/__DEFINES/xeno.dm
@@ -662,7 +662,6 @@
#define FIRE_IMMUNITY_NO_DAMAGE (1<<0)
#define FIRE_IMMUNITY_NO_IGNITE (1<<1)
#define FIRE_IMMUNITY_XENO_FRENZY (1<<2)
-#define FIRE_VULNERABILITY (1<<3)
#define FIRE_MULTIPLIER_BASE 1
#define FIRE_MULTIPLIER_LOW 1.25
diff --git a/code/datums/ammo/bullet/shotgun.dm b/code/datums/ammo/bullet/shotgun.dm
index 9504e3c1d1..d8a7248436 100644
--- a/code/datums/ammo/bullet/shotgun.dm
+++ b/code/datums/ammo/bullet/shotgun.dm
@@ -159,9 +159,8 @@
accurate_range = 8
max_range = 8
- damage = 90
- penetration = ARMOR_PENETRATION_TIER_4
- bonus_projectiles_amount = EXTRA_PROJECTILES_TIER_6
+ damage = 60
+ bonus_projectiles_amount = EXTRA_PROJECTILES_TIER_8
firing_freq_offset = SOUND_FREQ_LOW
//buckshot variant only used by the masterkey shotgun attachment.
@@ -196,7 +195,6 @@
accurate_range = 8
max_range = 8
damage = 90
- penetration = ARMOR_PENETRATION_TIER_4
firing_freq_offset = SOUND_FREQ_LOW
/*
diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
index 2a026e9da7..2f2735dc5f 100644
--- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
+++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
@@ -85,6 +85,7 @@
melee_damage_lower = 5
melee_damage_upper = 10
var/melee_vehicle_damage = 10
+ var/melee_sentry_damage_multiplier = 1
var/claw_type = CLAW_TYPE_SHARP
var/burn_damage_lower = 0
var/burn_damage_upper = 0
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_abilities.dm
index 6405428de7..c278b8bbf1 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_abilities.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_abilities.dm
@@ -19,6 +19,9 @@
should_destroy_objects = TRUE
throw_speed = SPEED_FAST
tracks_target = FALSE
+
+ default_ai_action = FALSE
+
var/direct_hit_damage = 60
var/frontal_armor = 15
// Object types that dont reduce cooldown when hit
@@ -44,21 +47,16 @@
var/damage = 65
- var/distance = 2
- var/effect_type_base = /datum/effects/xeno_slow/superslow
- var/effect_duration = 10
+ var/distance = 4
+ var/windup_duration = 1.5 SECONDS
-/datum/action/xeno_action/onclick/crusher_stomp/charger
- name = "Crush"
- action_icon_state = "stomp"
- macro_path = /datum/action/xeno_action/verb/verb_crusher_charger_stomp
- action_type = XENO_ACTION_CLICK
- ability_primacy = XENO_PRIMARY_ACTION_3
- plasma_cost = 25
- damage = 75
- distance = 3
- xeno_cooldown = 12 SECONDS
+ default_ai_action = TRUE
+/datum/action/xeno_action/onclick/crusher_stomp/process_ai(mob/living/carbon/xenomorph/X, delta_time)
+ if(!DT_PROB(ai_prob_chance, delta_time) || get_dist(X, X.current_target) >= distance - 1 || HAS_TRAIT(X, TRAIT_CHARGING) || X.action_busy)
+ return
+
+ use_ability_async()
/datum/action/xeno_action/onclick/crusher_shield
name = "Defensive Shield"
@@ -86,6 +84,14 @@
weaken_power = 0
slowdown = 8
+ default_ai_action = TRUE
+ ai_prob_chance = 60
+
+/datum/action/xeno_action/activable/fling/charger/process_ai(mob/living/carbon/xenomorph/X, delta_time)
+ if(!DT_PROB(ai_prob_chance, delta_time) || get_dist(X, X.current_target) > 1 || HAS_TRAIT(X, TRAIT_CHARGING) || X.action_busy)
+ return
+
+ use_ability_async(X.current_target)
/datum/action/xeno_action/onclick/charger_charge
name = "Toggle Charging"
@@ -95,11 +101,14 @@
action_type = XENO_ACTION_CLICK
ability_primacy = XENO_PRIMARY_ACTION_1
+ default_ai_action = TRUE
+ ai_prob_chance = 40
+
// Config vars
var/max_momentum = 8
- var/steps_to_charge = 4
- var/speed_per_momentum = XENO_SPEED_FASTMOD_TIER_5 + XENO_SPEED_FASTMOD_TIER_1//2
- var/plasma_per_step = 3 // charger has 400 plasma atm, this gives a good 100 tiles of crooshing
+ var/steps_to_charge = 3
+ var/speed_per_momentum = XENO_SPEED_FASTMOD_TIER_5//2.2
+ var/plasma_per_step = 0
// State vars
var/activated = FALSE
@@ -119,29 +128,120 @@
/// Dictates speed and damage dealt via collision, increased with movement
var/momentum = 0
+#define MIN_TARGETS_TO_CHARGE 2
+#define FLOCK_SCAN_RADIUS 3
+#define MINIMUM_CHARGE_DISTANCE 3
+#define MAXIMUM_TARGET_DISTANCE 12
+
+/datum/action/xeno_action/onclick/charger_charge/process_ai(mob/living/carbon/xenomorph/processing_xeno, delta_time)
+ if(!DT_PROB(ai_prob_chance, delta_time) || !isnull(charge_dir) || processing_xeno.action_busy)
+ return
+
+ var/turf/xeno_turf = get_turf(processing_xeno)
+
+ if(!xeno_turf)
+ return
+
+ var/list/possible_charge_dirs = list()
+
+ for(var/mob/living/carbon/human/base_checked_human as anything in GLOB.alive_human_list)
+ var/distance_between_base_human_and_xeno = get_dist(processing_xeno, base_checked_human)
+
+ if(distance_between_base_human_and_xeno > MAXIMUM_TARGET_DISTANCE)
+ continue
+
+ if(distance_between_base_human_and_xeno < MINIMUM_CHARGE_DISTANCE)
+ continue
+
+ if(!processing_xeno.check_mob_target(base_checked_human))
+ continue
+
+ var/secondary_count = 0
+ var/secondary_x_sum = 0
+ var/secondary_y_sum = 0
+
+ for(var/mob/living/carbon/human/secondary_checked_human in range(FLOCK_SCAN_RADIUS, base_checked_human))
+ if(!processing_xeno.check_mob_target(secondary_checked_human))
+ continue
+
+ secondary_count++
+ secondary_x_sum += secondary_checked_human.x
+ secondary_y_sum += secondary_checked_human.y
+ if(secondary_count < MIN_TARGETS_TO_CHARGE)
+ continue
-/datum/action/xeno_action/onclick/charger_charge/proc/handle_movement(mob/living/carbon/xenomorph/Xeno, atom/oldloc, dir, forced)
+ var/x_middle = round(secondary_x_sum / secondary_count)
+ var/y_middle = round(secondary_y_sum / secondary_count)
+
+ if((abs(x_middle - processing_xeno.x) > 1) && (abs(y_middle - processing_xeno.y) > 1))
+ continue
+
+ var/turf/potential_charge_turf = locate(x_middle, y_middle, processing_xeno.z)
+
+ var/distance_between_potential_charge_turf_and_xeno = get_dist(potential_charge_turf, processing_xeno)
+
+ if(distance_between_potential_charge_turf_and_xeno < MINIMUM_CHARGE_DISTANCE)
+ continue
+
+ var/cardinal_dir_to_potential_charge_turf = get_cardinal_dir(processing_xeno, potential_charge_turf)
+
+ var/list/turf/turfs_to_check = getline2(xeno_turf, get_angle_target_turf(xeno_turf, cardinal_dir_to_potential_charge_turf, MINIMUM_CHARGE_DISTANCE), FALSE)
+
+ var/blocked = FALSE
+ var/turf/previous_turf = xeno_turf
+
+ for(var/turf/checked_turf in turfs_to_check)
+ var/list/ignore = list()
+
+ for(var/mob/mob_blocker in checked_turf)
+ ignore += mob_blocker
+
+ if(LinkBlocked(processing_xeno, previous_turf, checked_turf, ignore))
+ blocked = TRUE
+ break
+
+ previous_turf = checked_turf
+
+ if(blocked)
+ continue
+
+ possible_charge_dirs += cardinal_dir_to_potential_charge_turf
+
+ if(!length(possible_charge_dirs))
+ return
+
+ charge_dir = pick(possible_charge_dirs)
+
+ last_charge_move = world.time
+ use_ability_async()
+
+#undef MIN_TARGETS_TO_CHARGE
+#undef FLOCK_SCAN_RADIUS
+#undef MINIMUM_CHARGE_DISTANCE
+#undef MAXIMUM_TARGET_DISTANCE
+
+/datum/action/xeno_action/onclick/charger_charge/proc/handle_movement(mob/living/carbon/xenomorph/xeno, atom/oldloc, dir, forced)
SIGNAL_HANDLER
- if(Xeno.pulling)
+ if(xeno.pulling)
if(!momentum)
steps_taken = 0
return
else
- Xeno.stop_pulling()
+ xeno.stop_pulling()
- if(Xeno.is_mob_incapacitated())
+ if(xeno.is_mob_incapacitated())
if(!momentum)
return
- var/lol = get_ranged_target_turf(Xeno, charge_dir, momentum/2)
- INVOKE_ASYNC(Xeno, TYPE_PROC_REF(/atom/movable, throw_atom), lol, momentum/2, SPEED_FAST, null, TRUE)
+ var/lol = get_ranged_target_turf(xeno, charge_dir, momentum/2)
+ INVOKE_ASYNC(xeno, TYPE_PROC_REF(/atom/movable, throw_atom), lol, momentum/2, SPEED_FAST, null, TRUE)
stop_momentum()
return
- if(!isturf(Xeno.loc))
+ if(!isturf(xeno.loc))
stop_momentum()
return
// Don't build up charge if you move via getting propelled by something
- if(Xeno.throwing)
+ if(xeno.throwing)
stop_momentum()
return
@@ -156,7 +256,7 @@
if(do_stop_momentum)
stop_momentum()
- if(Xeno.plasma_stored <= plasma_per_step)
+ if(xeno.plasma_stored <= plasma_per_step)
stop_momentum()
return
last_charge_move = world.time
@@ -165,34 +265,49 @@
return
if(momentum < max_momentum)
momentum++
- ADD_TRAIT(Xeno, TRAIT_CHARGING, TRAIT_SOURCE_XENO_ACTION_CHARGE)
- Xeno.update_icons()
+ ADD_TRAIT(xeno, TRAIT_CHARGING, TRAIT_SOURCE_XENO_ACTION_CHARGE)
+ xeno.update_icons()
if(momentum == max_momentum)
- Xeno.emote("roar")
+ xeno.emote("roar")
//X.use_plasma(plasma_per_step) // take if you are in toggle charge mode
if(momentum > 0)
- Xeno.use_plasma(plasma_per_step) // take plasma when you have momentum
+ xeno.use_plasma(plasma_per_step) // take plasma when you have momentum
noise_timer = noise_timer ? --noise_timer : 3
if(noise_timer == 3)
- playsound(Xeno, 'sound/effects/alien_footstep_charge1.ogg', 50)
+ playsound(xeno, 'sound/effects/alien_footstep_charge1.ogg', 50)
+
+ for(var/mob/living/carbon/human/Mob in range(10, xeno))
+ shake_camera(Mob, 2, 1)
- for(var/mob/living/carbon/human/Mob in Xeno.loc)
+ for(var/mob/living/carbon/human/Mob in xeno.loc)
if(Mob.lying && Mob.stat != DEAD)
- Xeno.visible_message(SPAN_DANGER("[Xeno] runs [Mob] over!"),
+ xeno.visible_message(SPAN_DANGER("[xeno] runs [Mob] over!"),
SPAN_DANGER("You run [Mob] over!")
)
- var/ram_dir = pick(get_perpen_dir(Xeno.dir))
+ var/ram_dir = pick(get_perpen_dir(xeno.dir))
var/dist = 1
if(momentum == max_momentum)
dist = momentum * 0.25
step(Mob, ram_dir, dist)
- Mob.take_overall_armored_damage(momentum * 6)
+ Mob.take_overall_armored_damage(momentum * 8)
INVOKE_ASYNC(Mob, TYPE_PROC_REF(/mob/living/carbon/human, emote),"pain")
- shake_camera(Mob, 7,3)
+ shake_camera(Mob, 7, 3)
animation_flash_color(Mob)
- Xeno.recalculate_speed()
+ if(momentum >= 5)
+ for(var/mob/living/carbon/human/hit_human in orange(1, xeno))
+ if(hit_human.knocked_down)
+ continue
+
+ shake_camera(hit_human, 4, 2)
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(xeno_throw_human), hit_human, xeno, get_dir(xeno, hit_human), 1, FALSE)
+ to_chat(hit_human, SPAN_XENOHIGHDANGER("You fall backwards as [xeno] gives you a glancing blow!"))
+ hit_human.take_overall_armored_damage(momentum * 4)
+ hit_human.apply_effect(0.5, WEAKEN)
+ animation_flash_color(hit_human)
+
+ xeno.recalculate_speed()
/datum/action/xeno_action/onclick/charger_charge/proc/handle_dir_change(datum/source, old_dir, new_dir)
SIGNAL_HANDLER
@@ -217,6 +332,8 @@
Xeno.visible_message(SPAN_DANGER("[Xeno] skids to a halt!"))
REMOVE_TRAIT(Xeno, TRAIT_CHARGING, TRAIT_SOURCE_XENO_ACTION_CHARGE)
+ SEND_SIGNAL(Xeno, COMSIG_XENO_STOPPED_CHARGING)
+ charge_dir = null
steps_taken = 0
momentum = 0
Xeno.recalculate_speed()
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm
index ce4a631e28..bfa826711b 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/crusher/crusher_powers.dm
@@ -96,92 +96,63 @@
..(T)
/datum/action/xeno_action/onclick/crusher_stomp/use_ability(atom/A)
- var/mob/living/carbon/xenomorph/X = owner
- if (!istype(X))
+ var/mob/living/carbon/xenomorph/xeno_owner = owner
+ if (!istype(xeno_owner))
return
if (!action_cooldown_check())
return
- if (!X.check_state())
+ if (!xeno_owner.check_state())
return
if (!check_and_use_plasma_owner())
return
- playsound(get_turf(X), 'sound/effects/bang.ogg', 25, 0)
- X.visible_message(SPAN_XENODANGER("[X] smashes into the ground!"), SPAN_XENODANGER("You smash into the ground!"))
- X.create_stomp()
+ apply_cooldown()
- for (var/mob/living/carbon/H in get_turf(X))
- if (H.stat == DEAD || X.can_not_harm(H))
- continue
+ xeno_owner.frozen = TRUE
+ xeno_owner.anchored = TRUE
+ xeno_owner.update_canmove()
- new effect_type_base(H, X, , , get_xeno_stun_duration(H, effect_duration))
- to_chat(H, SPAN_XENOHIGHDANGER("You are slowed as [X] knocks you off balance!"))
+ if (!do_after(xeno_owner, windup_duration, INTERRUPT_NO_NEEDHAND, BUSY_ICON_HOSTILE))
+ xeno_owner.frozen = FALSE
+ xeno_owner.anchored = FALSE
+ xeno_owner.update_canmove()
+ return
- if(H.mob_size < MOB_SIZE_BIG)
- H.apply_effect(get_xeno_stun_duration(H, 0.2), WEAKEN)
+ xeno_owner.frozen = FALSE
+ xeno_owner.anchored = FALSE
+ xeno_owner.update_canmove()
- H.apply_armoured_damage(get_xeno_damage_slash(H, damage), ARMOR_MELEE, BRUTE)
- H.last_damage_data = create_cause_data(X.caste_type, X)
+ playsound(get_turf(xeno_owner), 'sound/effects/bang.ogg', 25, 0)
+ xeno_owner.visible_message(SPAN_XENODANGER("[xeno_owner] smashes into the ground!"), SPAN_XENODANGER("You smash into the ground!"))
+ xeno_owner.create_stomp()
- for (var/mob/living/carbon/H in orange(distance, get_turf(X)))
- if (H.stat == DEAD || X.can_not_harm(H))
+ for(var/mob/living/carbon/carbon_in_range in range(distance, get_turf(xeno_owner)))
+ if(carbon_in_range.stat == DEAD || xeno_owner.can_not_harm(carbon_in_range))
continue
- new effect_type_base(H, X, , , get_xeno_stun_duration(H, effect_duration))
- if(H.mob_size < MOB_SIZE_BIG)
- H.apply_effect(get_xeno_stun_duration(H, 0.2), WEAKEN)
- to_chat(H, SPAN_XENOHIGHDANGER("You are slowed as [X] knocks you off balance!"))
-
- apply_cooldown()
- return ..()
-
-/datum/action/xeno_action/onclick/crusher_stomp/charger/use_ability()
- var/mob/living/carbon/xenomorph/Xeno = owner
- var/mob/living/carbon/Targeted
- if (!istype(Xeno))
- return
-
- if (!action_cooldown_check())
- return
+ var/distance_to_target = get_dist(carbon_in_range, xeno_owner)
- if (!Xeno.check_state())
- return
+ to_chat(carbon_in_range, SPAN_XENOHIGHDANGER("You fall as [xeno_owner] knocks you off balance!"))
+ shake_camera(carbon_in_range, (6 - distance_to_target), 2)
- if (!check_and_use_plasma_owner())
- return
+ var/weaken_time = 2.5 - (distance_to_target / 2)
- playsound(get_turf(Xeno), 'sound/effects/bang.ogg', 25, 0)
- Xeno.visible_message(SPAN_XENODANGER("[Xeno] smashes into the ground!"), SPAN_XENODANGER("You smash into the ground!"))
- Xeno.create_stomp()
+ carbon_in_range.apply_effect(weaken_time, WEAKEN)
- for (var/mob/living/carbon/Human in get_turf(Xeno)) // MOBS ONTOP
- if (Human.stat == DEAD || Xeno.can_not_harm(Human))
+ if(distance_to_target == 0)
+ carbon_in_range.apply_armoured_damage(damage, ARMOR_MELEE, BRUTE)
+ carbon_in_range.last_damage_data = create_cause_data(xeno_owner.caste_type, xeno_owner)
+ to_chat(carbon_in_range, SPAN_XENOHIGHDANGER("You are crushed as [xeno_owner] stomps on you!"))
continue
- new effect_type_base(Human, Xeno, , , get_xeno_stun_duration(Human, effect_duration))
- to_chat(Human, SPAN_XENOHIGHDANGER("You are BRUTALLY crushed and stomped on by [Xeno]!!!"))
- shake_camera(Human, 10, 2)
- if(Human.mob_size < MOB_SIZE_BIG)
- Human.apply_effect(get_xeno_stun_duration(Human, 0.2), WEAKEN)
-
- Human.apply_armoured_damage(get_xeno_damage_slash(Human, damage), ARMOR_MELEE, BRUTE,"chest", 3)
- Human.apply_armoured_damage(15, BRUTE) // random
- Human.last_damage_data = create_cause_data(Xeno.caste_type, Xeno)
- Human.emote("pain")
- Targeted = Human
- for (var/mob/living/carbon/Human in orange(distance, get_turf(Xeno))) // MOBS AROUND
- if (Human.stat == DEAD || Xeno.can_not_harm(Human))
+ if(distance_to_target <= 2)
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(xeno_throw_human), carbon_in_range, xeno_owner, get_dir(xeno_owner, carbon_in_range), (6 - (distance_to_target * 2)), FALSE)
+ to_chat(carbon_in_range, SPAN_XENOHIGHDANGER("You are flung by [xeno_owner] from the force of its crashing weight!"))
continue
- if(Human.client)
- shake_camera(Human, 10, 2)
- if(Targeted)
- to_chat(Human, SPAN_XENOHIGHDANGER("You watch as [Targeted] gets crushed by [Xeno]!"))
- to_chat(Human, SPAN_XENOHIGHDANGER("You are shaken as [Xeno] quakes the earth!"))
- apply_cooldown()
return ..()
/datum/action/xeno_action/onclick/crusher_shield/use_ability(atom/Target)
@@ -275,6 +246,14 @@
button.icon_state = "template"
return ..()
+/datum/action/xeno_action/activable/fling/charger/check_and_use_plasma_owner(plasma_to_use)
+ if (!check_plasma_owner(plasma_to_use))
+ return FALSE
+
+ flick("Normal Crusher Charging", owner)
+ use_plasma_owner(plasma_to_use)
+ return TRUE
+
/datum/action/xeno_action/activable/tumble/use_ability(atom/Target)
if(!action_cooldown_check())
return
diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm b/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm
index 781d958771..5f59f1c006 100644
--- a/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm
+++ b/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm
@@ -112,7 +112,7 @@
else
button.color = rgb(255,255,255,255)
-/datum/action/xeno_action/proc/process_ai(mob/living/carbon/xenomorph/X, delta_time)
+/datum/action/xeno_action/proc/process_ai(mob/living/carbon/xenomorph/processing_xeno, delta_time)
SHOULD_NOT_SLEEP(TRUE)
return PROCESS_KILL
diff --git a/code/modules/mob/living/carbon/xenomorph/ai/movement/crusher.dm b/code/modules/mob/living/carbon/xenomorph/ai/movement/crusher.dm
new file mode 100644
index 0000000000..efa541ed74
--- /dev/null
+++ b/code/modules/mob/living/carbon/xenomorph/ai/movement/crusher.dm
@@ -0,0 +1,51 @@
+/datum/xeno_ai_movement/crusher
+
+/datum/xeno_ai_movement/crusher/New(mob/living/carbon/xenomorph/parent)
+ . = ..()
+
+ RegisterSignal(parent, COMSIG_XENO_STOPPED_CHARGING, PROC_REF(stop_charging))
+
+#define MAX_CHARGE_DISTANCE 30
+
+/datum/xeno_ai_movement/crusher/ai_move_target(delta_time)
+ var/mob/living/carbon/xenomorph/moving_xeno = parent
+
+ if(moving_xeno.throwing)
+ return ..()
+
+ var/datum/action/xeno_action/onclick/charger_charge/charge_action = locate() in moving_xeno.actions
+
+ if(!charge_action)
+ return ..()
+
+ if(!charge_action.charge_dir)
+ return ..()
+
+ if(charge_action.steps_taken > MAX_CHARGE_DISTANCE)
+ stop_charging()
+ return ..()
+
+ if(!moving_xeno.can_move_and_apply_move_delay())
+ return TRUE
+
+ var/turf/next_turf = get_step(moving_xeno, charge_action.charge_dir)
+ if(!moving_xeno.Move(next_turf, charge_action.charge_dir))
+ stop_charging()
+
+ return TRUE
+
+#undef MAX_CHARGE_DISTANCE
+
+/datum/xeno_ai_movement/crusher/proc/stop_charging()
+ SIGNAL_HANDLER
+
+ var/datum/action/xeno_action/onclick/charger_charge/charge_action = locate() in parent.actions
+
+ if(!charge_action)
+ return
+
+ if(!charge_action.activated)
+ return
+
+ INVOKE_ASYNC(charge_action, TYPE_PROC_REF(/datum/action/xeno_action/onclick/charger_charge, use_ability_wrapper))
+ COOLDOWN_RESET(parent, forced_retarget_cooldown)
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 9e7cad9c65..3de86d6482 100644
--- a/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
+++ b/code/modules/mob/living/carbon/xenomorph/ai/xeno_ai.dm
@@ -19,6 +19,12 @@
var/datum/xeno_ai_movement/ai_movement_handler
+ /// The time interval before this xeno should forcefully get a new target
+ var/forced_retarget_time = (10 SECONDS)
+
+ /// The actual cooldown declaration for forceful retargeting, reference forced_retarget_time for time in between checks
+ COOLDOWN_DECLARE(forced_retarget_cooldown)
+
/mob/living/carbon/xenomorph/Destroy()
QDEL_NULL(ai_movement_handler)
return ..()
@@ -68,8 +74,9 @@
var/mob/current_target_mob = current_target
stat_check = (current_target_mob.stat != CONSCIOUS)
- if(QDELETED(current_target) || stat_check || get_dist(current_target, src) > ai_range)
+ if(QDELETED(current_target) || stat_check || 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))
return TRUE
@@ -77,7 +84,6 @@
resting = FALSE
if(prob(5))
emote("hiss")
- return TRUE
a_intent = INTENT_HARM
diff --git a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm
index c331324770..aad8d40f3b 100644
--- a/code/modules/mob/living/carbon/xenomorph/attack_alien.dm
+++ b/code/modules/mob/living/carbon/xenomorph/attack_alien.dm
@@ -659,7 +659,7 @@
M.animation_attack_on(src)
playsound(src, 'sound/effects/metalhit.ogg', 25, 1)
- update_health(rand(M.melee_damage_lower, M.melee_damage_upper))
+ update_health(rand(M.melee_damage_lower, M.melee_damage_upper) * M.melee_sentry_damage_multiplier)
if(health <= 0)
M.visible_message(SPAN_DANGER("[M] slices \the [src] apart!"), \
SPAN_DANGER("You slice \the [src] apart!"), null, 5, CHAT_TYPE_XENO_COMBAT)
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm
index 092dae00d6..2651137a80 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Boiler.dm
@@ -22,7 +22,6 @@
acid_level = 3
caste_luminosity = 2
spit_types = list(/datum/ammo/xeno/boiler_gas, /datum/ammo/xeno/boiler_gas/acid)
- fire_immunity = FIRE_VULNERABILITY
// 3x fire damage
fire_vulnerability_mult = FIRE_MULTIPLIER_DEADLY
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm
index bf17025982..7eca90af85 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Crusher.dm
@@ -4,17 +4,17 @@
melee_damage_lower = XENO_DAMAGE_TIER_5
melee_damage_upper = XENO_DAMAGE_TIER_5
- melee_vehicle_damage = XENO_DAMAGE_TIER_5
- max_health = XENO_HEALTH_TIER_10
+ melee_vehicle_damage = XENO_DAMAGE_TIER_5 * 8
+ max_health = XENO_HEALTH_IMMORTAL
plasma_gain = XENO_PLASMA_GAIN_TIER_7
- plasma_max = XENO_PLASMA_TIER_4
+ plasma_max = XENO_PLASMA_TIER_8
xeno_explosion_resistance = XENO_EXPLOSIVE_ARMOR_TIER_10
armor_deflection = XENO_ARMOR_TIER_3
evasion = XENO_EVASION_NONE
- speed = XENO_SPEED_TIER_2
+ speed = XENO_SPEED_TIER_4
heal_standing = 0.66
- behavior_delegate_type = /datum/behavior_delegate/crusher_base
+ behavior_delegate_type = /datum/behavior_delegate/crusher_charger
minimum_evolve_time = 15 MINUTES
@@ -25,6 +25,8 @@
evolution_allowed = FALSE
deevolves_to = list(XENO_CASTE_WARRIOR)
caste_desc = "A huge tanky xenomorph."
+ fire_intensity_resistance = 20
+ fire_vulnerability_mult = 0.25
minimap_icon = "crusher"
@@ -37,6 +39,7 @@
plasma_types = list(PLASMA_CHITIN)
tier = 3
drag_delay = 6 //pulling a big dead xeno is hard
+ melee_sentry_damage_multiplier = 2
small_explosives_stun = FALSE
@@ -56,7 +59,8 @@
/datum/action/xeno_action/onclick/regurgitate,
/datum/action/xeno_action/watch_xeno,
/datum/action/xeno_action/activable/tail_stab,
- /datum/action/xeno_action/activable/pounce/crusher_charge,
+ /datum/action/xeno_action/activable/fling/charger,
+ /datum/action/xeno_action/onclick/charger_charge,
/datum/action/xeno_action/onclick/crusher_stomp,
/datum/action/xeno_action/onclick/crusher_shield,
/datum/action/xeno_action/onclick/tacmap,
@@ -64,11 +68,26 @@
claw_type = CLAW_TYPE_VERY_SHARP
mutation_icon_state = CRUSHER_NORMAL
- mutation_type = CRUSHER_NORMAL
+ mutation_type = CRUSHER_CHARGER
icon_xeno = 'icons/mob/xenos/crusher.dmi'
icon_xenonid = 'icons/mob/xenonids/crusher.dmi'
+ ai_range = 24
+ forced_retarget_time = (3 SECONDS)
+
+/mob/living/carbon/xenomorph/crusher/init_movement_handler()
+ return new /datum/xeno_ai_movement/crusher(src)
+
+/mob/living/carbon/xenomorph/crusher/Initialize(mapload, mob/living/carbon/xenomorph/oldXeno, h_number, ai_hard_off = FALSE)
+ . = ..()
+
+ playsound(src, 'sound/voice/alien_crusher_spawn.ogg', 100, FALSE, 30)
+ for(var/mob/current_mob as anything in get_mobs_in_z_level_range(get_turf(src), 30) - src)
+ var/relative_dir = get_dir(current_mob, src)
+ var/final_dir = dir2text(relative_dir)
+ to_chat(current_mob, SPAN_HIGHDANGER("You hear a terrible roar coming from [final_dir ? "the [final_dir]" : "nearby"] as the ground shakes!"))
+
// Refactored to handle all of crusher's interactions with object during charge.
/mob/living/carbon/xenomorph/proc/handle_collision(atom/target)
if(!target)
@@ -205,7 +224,7 @@
if (!.)
update_icons()
-// Mutator delegate for base ravager
+// Mutator delegate for base crusher
/datum/behavior_delegate/crusher_base
name = "Base Crusher Behavior Delegate"
@@ -276,6 +295,6 @@
. += "Shield: [shield_total]"
/datum/behavior_delegate/crusher_base/on_update_icons()
- if(bound_xeno.throwing || is_charging) //Let it build up a bit so we're not changing icons every single turf
+ if(HAS_TRAIT(bound_xeno, TRAIT_CHARGING) && !bound_xeno.lying)
bound_xeno.icon_state = "[bound_xeno.mutation_icon_state || bound_xeno.mutation_type] Crusher Charging"
return TRUE
diff --git a/code/modules/mob/living/carbon/xenomorph/life.dm b/code/modules/mob/living/carbon/xenomorph/life.dm
index da66c61bfc..6ea3b699dc 100644
--- a/code/modules/mob/living/carbon/xenomorph/life.dm
+++ b/code/modules/mob/living/carbon/xenomorph/life.dm
@@ -83,10 +83,7 @@
G.die()
drop_inv_item_on_ground(G)
if(!caste || !(caste.fire_immunity & FIRE_IMMUNITY_NO_DAMAGE) || fire_reagent.fire_penetrating)
- if(caste.fire_immunity & FIRE_VULNERABILITY && caste.fire_vulnerability_mult >= 1)
- apply_damage(PASSIVE_BURN_DAM_CALC(fire_reagent.intensityfire, fire_reagent.durationfire, fire_stacks) * caste.fire_vulnerability_mult, BURN)
- else
- apply_damage(armor_damage_reduction(GLOB.xeno_fire, PASSIVE_BURN_DAM_CALC(fire_reagent.intensityfire, fire_reagent.durationfire, fire_stacks)), BURN)
+ apply_damage(armor_damage_reduction(GLOB.xeno_fire, PASSIVE_BURN_DAM_CALC(fire_reagent.intensityfire, fire_reagent.durationfire, fire_stacks) * caste.fire_vulnerability_mult), BURN)
INVOKE_ASYNC(src, TYPE_PROC_REF(/mob, emote), pick("roar", "needhelp"))
#undef PASSIVE_BURN_DAM_CALC
diff --git a/code/modules/mob/living/carbon/xenomorph/mutators/strains/crusher/charger.dm b/code/modules/mob/living/carbon/xenomorph/mutators/strains/crusher/charger.dm
index f7daee71df..fc7be12e4e 100644
--- a/code/modules/mob/living/carbon/xenomorph/mutators/strains/crusher/charger.dm
+++ b/code/modules/mob/living/carbon/xenomorph/mutators/strains/crusher/charger.dm
@@ -23,13 +23,11 @@
caste_whitelist = list(XENO_CASTE_CRUSHER)
mutator_actions_to_remove = list (
/datum/action/xeno_action/activable/pounce/crusher_charge,
- /datum/action/xeno_action/onclick/crusher_stomp,
/datum/action/xeno_action/onclick/crusher_shield,
)
mutator_actions_to_add = list(
/datum/action/xeno_action/onclick/charger_charge,
/datum/action/xeno_action/activable/tumble,
- /datum/action/xeno_action/onclick/crusher_stomp/charger,
/datum/action/xeno_action/activable/fling/charger,
)
keystone = TRUE
@@ -59,7 +57,9 @@
name = "Charger Crusher Behavior Delegate"
var/frontal_armor = 30
- var/side_armor = 15
+ var/side_armor = 20
+
+ var/aoe_slash_damage_reduction = 0.40
/datum/behavior_delegate/crusher_charger/add_to_xeno()
RegisterSignal(bound_xeno, COMSIG_MOB_SET_FACE_DIR, PROC_REF(cancel_dir_lock))
@@ -69,6 +69,38 @@
SIGNAL_HANDLER
return COMPONENT_CANCEL_SET_FACE_DIR
+/datum/behavior_delegate/crusher_charger/melee_attack_additional_effects_target(mob/living/carbon/A)
+
+ if (!isxeno_human(A))
+ return
+
+ new /datum/effects/xeno_slow(A, bound_xeno, , , 20)
+
+ var/damage = bound_xeno.melee_damage_upper * aoe_slash_damage_reduction
+
+ var/base_cdr_amount = 15
+ var/cdr_amount = base_cdr_amount
+ for (var/mob/living/carbon/H in orange(1, A))
+ if (H.stat == DEAD)
+ continue
+
+ if(!isxeno_human(H) || bound_xeno.can_not_harm(H))
+ continue
+
+ cdr_amount += 5
+
+ bound_xeno.visible_message(SPAN_DANGER("[bound_xeno] slashes [H]!"), \
+ SPAN_DANGER("You slash [H]!"), null, null, CHAT_TYPE_XENO_COMBAT)
+
+ bound_xeno.flick_attack_overlay(H, "slash")
+
+ H.last_damage_data = create_cause_data(initial(bound_xeno.name), bound_xeno)
+ H.apply_armoured_damage(get_xeno_damage_slash(H, damage), ARMOR_MELEE, BRUTE, bound_xeno.zone_selected)
+
+ var/datum/action/xeno_action/onclick/crusher_stomp/cAction = get_xeno_action_by_type(bound_xeno, /datum/action/xeno_action/onclick/crusher_stomp)
+ if (!cAction.action_cooldown_check())
+ cAction.reduce_cooldown(cdr_amount)
+
/datum/behavior_delegate/crusher_charger/proc/apply_directional_armor(mob/living/carbon/xenomorph/xeno, list/damagedata)
SIGNAL_HANDLER
var/projectile_direction = damagedata["direction"]
@@ -102,7 +134,7 @@
charger_ability.stop_momentum()
return
- health -= CHARGER_DESTROY //Usually knocks it down.
+ health -= CHARGER_DESTROY * 2 //Usually knocks it down.
healthcheck()
if(QDELETED(src))
@@ -340,9 +372,9 @@
attack_log += text("\[[time_stamp()]\] was xeno charged by [xeno] ([xeno.ckey])")
xeno.attack_log += text("\[[time_stamp()]\] xeno charged [src] ([src.ckey])")
log_attack("[xeno] ([xeno.ckey]) xeno charged [src] ([src.ckey])")
- var/momentum_mult = 5
+ var/momentum_mult = 7
if(charger_ability.momentum == charger_ability.max_momentum)
- momentum_mult = 8
+ momentum_mult = 10
take_overall_armored_damage(charger_ability.momentum * momentum_mult, ARMOR_MELEE, BRUTE, 60, 13) // Giving AP because this spreads damage out and then applies armor to them
apply_armoured_damage(charger_ability.momentum * momentum_mult/4, ARMOR_MELEE, BRUTE,"chest")
xeno.visible_message(
@@ -363,7 +395,7 @@
if(LinkBlocked(src, cur_turf, target_turf))
ram_dir = REVERSE_DIR(ram_dir)
step(src, ram_dir, charger_ability.momentum * 0.5)
- charger_ability.lose_momentum(CCA_MOMENTUM_LOSS_MIN)
+// charger_ability.lose_momentum(CCA_MOMENTUM_LOSS_MIN)
return XENO_CHARGE_TRY_MOVE
// Fellow xenos
@@ -401,7 +433,7 @@
apply_effect(1, WEAKEN) // brief flicker stun
src.throw_atom(src.loc,1,3,xeno,TRUE)
step(src, ram_dir, charger_ability.momentum * 0.5)
- charger_ability.lose_momentum(CCA_MOMENTUM_LOSS_MIN)
+// charger_ability.lose_momentum(CCA_MOMENTUM_LOSS_MIN)
return XENO_CHARGE_TRY_MOVE
charger_ability.stop_momentum()
diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
index eabe4a7853..d97fbaa623 100644
--- a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
+++ b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm
@@ -101,7 +101,7 @@
var/caste_luminosity = 0
/// if fire_immunity is set to be vulnerable, how much will fire damage be multiplied. Defines in xeno.dm
- var/fire_vulnerability_mult = 0
+ var/fire_vulnerability_mult = 1
var/burrow_cooldown = 5 SECONDS
var/tunnel_cooldown = 100
diff --git a/colonialmarines.dme b/colonialmarines.dme
index e572416caf..629bdb46c6 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -2006,6 +2006,7 @@
#include "code\modules\mob\living\carbon\xenomorph\abilities\warrior\warrior_powers.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\xeno_ai.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\base_define.dm"
+#include "code\modules\mob\living\carbon\xenomorph\ai\movement\crusher.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\drone.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\linger.dm"
#include "code\modules\mob\living\carbon\xenomorph\ai\movement\lurking.dm"
diff --git a/sound/voice/alien_crusher_spawn.ogg b/sound/voice/alien_crusher_spawn.ogg
new file mode 100644
index 0000000000..b93030e3ed
Binary files /dev/null and b/sound/voice/alien_crusher_spawn.ogg differ