diff --git a/code/__DEFINES/mode.dm b/code/__DEFINES/mode.dm
index 04c7993bd9f2..1933f3da25d9 100644
--- a/code/__DEFINES/mode.dm
+++ b/code/__DEFINES/mode.dm
@@ -295,6 +295,7 @@ DEFINE_BITFIELD(whitelist_status, list(
#define FACTION_LIST_MARINE_WY list(FACTION_MARINE, FACTION_PMC, FACTION_WY_DEATHSQUAD, FACTION_WY)
#define FACTION_LIST_MARINE_UPP list(FACTION_MARINE, FACTION_UPP)
#define FACTION_LIST_MARINE_TWE list(FACTION_MARINE, FACTION_TWE)
+#define FACTION_LIST_YAUTJA list(FACTION_YAUTJA)
// Xenomorphs
#define FACTION_PREDALIEN "Predalien"
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 5d7955d85013..c790c822a61a 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -135,3 +135,6 @@ GLOBAL_VAR(xeno_queue_candidate_count)
GLOBAL_VAR(obfs_x)
/// A number between -500 and 500.
GLOBAL_VAR(obfs_y)
+
+/// The current amount of giant lizards that are alive.
+GLOBAL_VAR_INIT(giant_lizards_alive, 0)
diff --git a/code/game/gamemodes/cm_initialize.dm b/code/game/gamemodes/cm_initialize.dm
index 137ec91ced31..4cfc02eecad9 100644
--- a/code/game/gamemodes/cm_initialize.dm
+++ b/code/game/gamemodes/cm_initialize.dm
@@ -165,6 +165,7 @@ Additional game mode variables.
player.mind_initialize() //Will work on ghosts too, but won't add them to active minds.
player.mind.setup_human_stats()
player.faction = FACTION_YAUTJA
+ player.faction_group = FACTION_LIST_YAUTJA
players += player.mind
return players
diff --git a/code/game/objects/effects/landmarks/landmarks.dm b/code/game/objects/effects/landmarks/landmarks.dm
index 1ed45a75e9cd..87c820054475 100644
--- a/code/game/objects/effects/landmarks/landmarks.dm
+++ b/code/game/objects/effects/landmarks/landmarks.dm
@@ -121,6 +121,8 @@
GLOB.monkey_spawns -= src
return ..()
+#define MAXIMUM_LIZARD_AMOUNT 4
+
/obj/effect/landmark/lizard_spawn
name = "lizard spawn"
icon_state = "lizard_spawn"
@@ -129,6 +131,22 @@
. = ..()
if(prob(66))
new /mob/living/simple_animal/hostile/retaliate/giant_lizard(loc)
+ addtimer(CALLBACK(src, PROC_REF(latespawn_lizard)), rand(35 MINUTES, 50 MINUTES))
+
+/obj/effect/landmark/lizard_spawn/proc/latespawn_lizard()
+ //if there's already a ton of lizards alive, try again later
+ if(GLOB.giant_lizards_alive > MAXIMUM_LIZARD_AMOUNT)
+ addtimer(CALLBACK(src, PROC_REF(latespawn_lizard)), rand(15 MINUTES, 25 MINUTES))
+ return
+ //if there's a living mob that can witness the spawn then try again later
+ for(var/mob/living/living_mob in range(7, src))
+ if(living_mob.stat != DEAD || living_mob.client)
+ continue
+ addtimer(CALLBACK(src, PROC_REF(latespawn_lizard)), 1 MINUTES)
+ return
+ new /mob/living/simple_animal/hostile/retaliate/giant_lizard(loc)
+
+#undef MAXIMUM_LIZARD_AMOUNT
/obj/effect/landmark/latewhiskey
name = "Whiskey Outpost Late join"
diff --git a/code/modules/gear_presets/yautja.dm b/code/modules/gear_presets/yautja.dm
index 8ffd8664a977..9b8e64948c62 100644
--- a/code/modules/gear_presets/yautja.dm
+++ b/code/modules/gear_presets/yautja.dm
@@ -4,6 +4,7 @@
languages = list(LANGUAGE_YAUTJA)
rank = "Predator"
faction = FACTION_YAUTJA
+ faction_group = FACTION_LIST_YAUTJA
uses_special_name = TRUE
skills = /datum/skills/yautja/warrior
@@ -23,6 +24,7 @@
/datum/equipment_preset/yautja/load_id(mob/living/carbon/human/new_human)
new_human.job = rank
new_human.faction = faction
+ new_human.faction_group = faction_group
/datum/equipment_preset/yautja/load_vanity(mob/living/carbon/human/new_human)
return //No vanity items for Yautja!
diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm
index 8aea59b96a81..ba74c06fcaf1 100644
--- a/code/modules/mob/death.dm
+++ b/code/modules/mob/death.dm
@@ -49,7 +49,7 @@
return 0
if(!gibbed)
- visible_message("\The [src.name] [deathmessage]")
+ visible_message("[src.name] [deathmessage]")
if(cause_data && !istype(cause_data))
stack_trace("death called with string cause ([cause_data]) instead of datum")
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/giant_lizard.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/giant_lizard.dm
index 91cf46d1491c..0c58ca612514 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/giant_lizard.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/giant_lizard.dm
@@ -34,18 +34,27 @@
speak_chance = 2
speak_emote = "hisses"
emote_hear = list("hisses.", "growls.", "roars.", "bellows.")
- emote_see = list("shakes its head.", "wags its tail.", "flicks its tongue.", "yawns.")
+ emote_see = list("shakes its head.", "wags its tail.", "yawns.")
melee_damage_lower = 20
melee_damage_upper = 25
+ break_stuff_probability = 100
attack_same = FALSE
langchat_color = LIGHT_COLOR_GREEN
///Reference to the ZZzzz sleep overlay when resting.
var/sleep_overlay
+ ///Reference to the tongue flick overlay.
+ var/atom/movable/vis_obj/giant_lizard_icon_holder/tongue_icon_holder
+ ///Reference to the wound icon.
+ var/atom/movable/vis_obj/giant_lizard_icon_holder/wound_icon_holder
+ ///How far the mob is willing to chase before losing its target.
+ var/max_attack_distance = 16
///If 0, moves the mob out of attacking into idle state. Used to prevent the mob from chasing down targets that did not mean to hurt it.
var/aggression_value = 0
+ ///Every type of structure that can get attacked in the DestroySurroundings() proc.
+ var/list/destruction_targets = list(/obj/structure/mineral_door/resin, /obj/structure/window, /obj/structure/closet, /obj/structure/surface/table, /obj/structure/grille, /obj/structure/barricade, /obj/structure/machinery/door, /obj/structure/largecrate)
///Emotes to play when being pet by a friend.
var/list/pet_emotes = list("closes its eyes.", "growls happily.", "wags its tail.", "rolls on the ground.")
@@ -71,11 +80,12 @@
var/retreat_attempts = 0
///Tied directly to retreat_attempts. If our retreat fail, then we will completely stop trying to retreat for the length of this cooldown.
COOLDOWN_DECLARE(retreat_cooldown)
-
+ ///Can this mob be tamed?
+ var/tameable = TRUE
///The food object that the mob is trying to eat.
var/food_target
- ///A list of foods the mob is interested in eating.
- var/list/acceptable_foods = list(/obj/item/reagent_container/food/snacks/meat, /obj/item/reagent_container/food/snacks/packaged_meal, /obj/item/reagent_container/food/snacks/resin_fruit)
+ ///A list of foods the mob is interested in eating. The mob will eat anything that has meat protein in it even if it's not in this list.
+ var/list/acceptable_foods = list(/obj/item/reagent_container/food/snacks/packaged_meal, /obj/item/reagent_container/food/snacks/resin_fruit)
///Is the mob currently eating the food_target?
var/is_eating = FALSE
///Cooldown dictating how long the mob will wait between eating food.
@@ -86,13 +96,75 @@
///Cooldown for when the mob calms down, so the mob doesn't start attacking immediately after calming down.
COOLDOWN_DECLARE(calm_cooldown)
+//For displaying wound states, and the tongue flicker.
+/atom/movable/vis_obj/giant_lizard_icon_holder
+ icon = 'icons/mob/mob_64.dmi'
+
+//Due to being constrained to 64x64, we need to change the icon's offsets manually whenever the mob's dir is changed.
+//apparantly movement sets both olddir and newdir to the same value, in comparison to manually switching facing which returns the right values.
+//i don't know if there's any worthwhile performance gain in having a second signal exclusively for movement so we can check if the olddir and newdir are same.
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/change_tongue_offset(datum/source, olddir, newdir)
+ SIGNAL_HANDLER
+
+ if(!newdir)
+ newdir = dir
+
+ switch(newdir)
+ if(WEST)
+ if(resting)
+ tongue_icon_holder.pixel_x = 7
+ tongue_icon_holder.pixel_y = -4
+ return
+
+ tongue_icon_holder.pixel_x = -2
+ tongue_icon_holder.pixel_y = 0
+
+ if(EAST)
+ if(resting)
+ tongue_icon_holder.pixel_x = -7
+ tongue_icon_holder.pixel_y = -4
+ return
+
+ tongue_icon_holder.pixel_x = 2
+ tongue_icon_holder.pixel_y = 0
+ if(SOUTH)
+ tongue_icon_holder.pixel_x = 0
+ tongue_icon_holder.pixel_y = 0
+ //there is no north facing tongue animation
+
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/face_dir(ndir, specific_dir)
+ //there is no north or south facing rest sprite, so updating the direction would mess up tongue flicking.
+ if(resting && (ndir == NORTH || ndir == SOUTH))
+ return
+ return ..()
+
+//we also have to check for the keybind apparantly...
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/keybind_face_direction(direction)
+ if(resting && (direction == NORTH || direction == SOUTH))
+ return
+ return ..()
+
/mob/living/simple_animal/hostile/retaliate/giant_lizard/Initialize()
. = ..()
+ wound_icon_holder = new(null, src)
+ tongue_icon_holder = new(null, src)
+ tongue_icon_holder.pixel_x = 2
+ vis_contents += wound_icon_holder
+ vis_contents += tongue_icon_holder
+
+ RegisterSignal(src, COMSIG_ATOM_DIR_CHANGE, PROC_REF(change_tongue_offset))
+
+ GLOB.giant_lizards_alive++
change_real_name(src, "[name] ([rand(1, 999)])")
pounce_callbacks[/mob] = DYNAMIC(/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/pounced_mob_wrapper)
pounce_callbacks[/turf] = DYNAMIC(/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/pounced_turf_wrapper)
pounce_callbacks[/obj] = DYNAMIC(/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/pounced_obj_wrapper)
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/initialize_pass_flags(datum/pass_flags_container/pass_flags_container)
+ ..()
+ if(pass_flags_container)
+ pass_flags_container.flags_pass |= PASS_FLAGS_CRAWLER
+
//regular pain datum will make the mob die when trying to pounce after taking enough damage.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/initialize_pain()
pain = new /datum/pain/xeno(src)
@@ -128,11 +200,16 @@
. = ..()
find_target_on_trait_loss()
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/on_floored_trait_loss(datum/source)
+ . = ..()
+ find_target_on_trait_loss()
+
///Proc for handling the AI post-status effect.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/find_target_on_trait_loss()
+ update_transform()
is_retreating = FALSE
if(stance > HOSTILE_STANCE_ALERT)
- FindTarget()
+ target_mob = FindTarget()
MoveToTarget()
//procs for handling sleeping icons when resting
@@ -154,6 +231,7 @@
walk_to(src, 0)
/mob/living/simple_animal/hostile/retaliate/giant_lizard/update_transform(instant_update = FALSE)
+ tongue_icon_holder.alpha = alpha
if(stat == DEAD)
icon_state = icon_dead
else if(body_position == LYING_DOWN)
@@ -161,10 +239,47 @@
icon_state = "Giant Lizard Sleeping"
else
icon_state = "Giant Lizard Knocked Down"
+ tongue_icon_holder.alpha = 0
+ //we can't stop an animation that's called via flick(). best we can do is hide it.
else
icon_state = icon_living
+ update_wounds()
+ change_tongue_offset()
return ..()
+
+#define NO_WOUNDS 4
+#define SMALL_WOUNDS 3
+#define BIG_WOUNDS 2
+
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/update_wounds()
+ if(!wound_icon_holder)
+ return
+
+ var/health_threshold = max(ceil((health * 4) / (maxHealth)), 0) //From 0 to 4, in 25% chunks
+ switch(health_threshold)
+ if(NO_WOUNDS)
+ health_threshold = "none"
+ if(SMALL_WOUNDS)
+ health_threshold = "Wounds Small"
+ if(0 to BIG_WOUNDS)
+ health_threshold = "Wounds Big"
+
+ if(health > 0)
+ if(health_threshold == "none")
+ wound_icon_holder.icon_state = "none"
+ else if(body_position == LYING_DOWN)
+ if(!HAS_TRAIT(src, TRAIT_INCAPACITATED) && !HAS_TRAIT(src, TRAIT_FLOORED))
+ wound_icon_holder.icon_state = "Giant Lizard [health_threshold] Rest"
+ else
+ wound_icon_holder.icon_state = "Giant Lizard [health_threshold] Stun"
+ else
+ wound_icon_holder.icon_state = "Giant Lizard [health_threshold]"
+
+#undef NO_WOUNDS
+#undef SMALL_WOUNDS
+#undef BIG_WOUNDS
+
/mob/living/simple_animal/hostile/retaliate/giant_lizard/get_examine_text(mob/user)
. = ..()
if(stat == DEAD || user == src)
@@ -183,13 +298,22 @@
/mob/living/simple_animal/hostile/retaliate/giant_lizard/set_resting(new_resting, silent, instant)
. = ..()
+ //north and south rest sprites don't exist, we need to set it to WEST for tongue flicks to work properly
+ if(resting && dir == NORTH || dir == SOUTH)
+ setDir(WEST)
if(!resting)
RemoveSleepingIcon()
update_transform()
-/mob/living/simple_animal/hostile/retaliate/giant_lizard/death()
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/rejuvenate()
+ //if the mob was dead beforehand, it's now alive and therefore it's an extra lizard to the count
+ if(stat == DEAD)
+ GLOB.giant_lizards_alive++
+ return ..()
+
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/death(datum/cause_data/cause_data, gibbed = FALSE, deathmessage = "lets out a waning growl....")
playsound(loc, 'sound/effects/giant_lizard_death.ogg', 70)
- manual_emote("lets out a waning growl.")
+ GLOB.giant_lizards_alive--
return ..()
/mob/living/simple_animal/hostile/retaliate/giant_lizard/attack_hand(mob/living/carbon/human/attacking_mob)
@@ -224,11 +348,13 @@
chance_to_rest = 0
if(COOLDOWN_FINISHED(src, emote_cooldown))
COOLDOWN_START(src, emote_cooldown, rand(5, 8) SECONDS)
- manual_emote(pick(pick(pet_emotes), "stares at [attacking_mob].", "nuzzles [attacking_mob].", "licks [attacking_mob]'s hand."), "nibbles [attacking_mob]'s arm.", "flicks its tongue at [attacking_mob].")
+ manual_emote(pick(pick(pet_emotes), "stares at [attacking_mob].", "nuzzles [attacking_mob].", "licks [attacking_mob]'s hand."), "nibbles [attacking_mob]'s arm.")
if(prob(50))
playsound(loc, "giant_lizard_hiss", 25)
- if(attacking_mob.a_intent == INTENT_DISARM && prob(75))
- step_to(src, get_step(loc, attacking_mob.dir), 0, LIZARD_SPEED_NORMAL)
+ flick("Giant Lizard Tongue", tongue_icon_holder)
+ if(attacking_mob.a_intent == INTENT_DISARM && prob(25))
+ playsound(loc, 'sound/weapons/alien_knockdown.ogg', 25, 1)
+ KnockDown(0.4)
//apply blood splatter when attacked by a sufficently damaging sharp weapon
/mob/living/simple_animal/hostile/retaliate/giant_lizard/attackby(obj/item/weapon, mob/living/carbon/human/attacker)
@@ -249,14 +375,15 @@
addtimer(VARSET_CALLBACK(src, speed, LIZARD_SPEED_NORMAL_CLIENT), 8 SECONDS)
addtimer(VARSET_CALLBACK(src, is_retreating, FALSE), 8 SECONDS)
else
- MoveTo(target_mob, 12, TRUE, 8 SECONDS)
+ MoveTo(target_mob, 12, TRUE, 4.5 SECONDS)
if(damage >= 10 && damagetype == BRUTE)
add_splatter_floor(loc, TRUE)
bleed_ticks = clamp(bleed_ticks + ceil(damage / 10), 0, 30)
+ update_wounds()
///Proc that forces the mob to disengage and try to extinguish itself. Will not be called if the mob is already retreating.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/try_to_extinguish()
- if(is_retreating || !on_fire || client)
+ if(is_retreating || !on_fire || client || stat == DEAD || body_position == LYING_DOWN)
return
//forget EVERYTHING. we need to stop the flames!!!
@@ -280,17 +407,28 @@
try_to_extinguish()
/mob/living/simple_animal/hostile/retaliate/giant_lizard/Life(delta_time)
- //simplemobs don't have innate knockdown reduction so we'll manually lower it here.
- AdjustKnockDown(-0.5)
- AdjustStun(-0.5)
+ //simplemobs don't have innate knockdown reduction so we'll just remove the status effects manually. works alright due to the 2 second delay on life()
+ SetKnockOut(0)
+ SetStun(0)
+ SetKnockDown(0)
if(aggression_value > 0)
aggression_value--
+ //it is possible for the mob to keep a target_mob saved, yet be stuck in alert stance and never lose said target.
+ //this will cause it to be paralyzed and do nothing for the rest of its life, so this specific check is here to remedy that (hopefully)
+ if(!client && stance == HOSTILE_STANCE_ALERT && target_mob && !is_retreating && !on_fire)
+ target_mob = FindTarget()
+ MoveToTarget()
+
if(resting && stat != DEAD)
health += maxHealth * 0.05
+ update_wounds()
if(prob(33) && !HAS_TRAIT(src, TRAIT_INCAPACITATED) && !HAS_TRAIT(src, TRAIT_FLOORED))
AddSleepingIcon()
+ if(stat != DEAD && !HAS_TRAIT(src, TRAIT_INCAPACITATED) && !HAS_TRAIT(src, TRAIT_FLOORED) && prob(25))
+ flick("Giant Lizard Tongue", tongue_icon_holder)
+
if(bleed_ticks)
var/is_small_pool = FALSE
if(bleed_ticks < 10)
@@ -298,6 +436,20 @@
bleed_ticks--
add_splatter_floor(loc, is_small_pool)
+ //done before the parent proccall because it would result in it moving before resting
+ if(stance == HOSTILE_STANCE_IDLE && !client)
+ stop_automated_movement = FALSE
+ //if there's a friend on the same tile as us, don't bother getting up (cute!)
+ var/mob/living/carbon/friend = locate(/mob/living/carbon) in get_turf(src)
+ if((friend?.faction in faction_group) && resting)
+ chance_to_rest = 0
+
+ if(prob(chance_to_rest))
+ set_resting(!resting)
+ chance_to_rest = 0
+
+ chance_to_rest += rand(1, 2)
+
. = ..()
if(client)
@@ -316,19 +468,6 @@
if(stance > HOSTILE_STANCE_ALERT)
is_eating = FALSE
- if(stance == HOSTILE_STANCE_IDLE)
- stop_automated_movement = FALSE
- //if there's a friend on the same tile as us, don't bother getting up (cute!)
- var/mob/living/carbon/friend = locate(/mob/living/carbon) in get_turf(src)
- if((friend?.faction in faction_group) && resting)
- chance_to_rest = 0
-
- if(prob(chance_to_rest))
- set_resting(!resting)
- chance_to_rest = 0
-
- chance_to_rest += rand(1, 2)
-
//if we're resting and something catches our interest, get up
if(stance != HOSTILE_STANCE_IDLE && resting)
set_resting(FALSE)
@@ -336,6 +475,10 @@
if(target_mob && !is_retreating && target_mob.stat == CONSCIOUS && stance == HOSTILE_STANCE_ATTACKING && COOLDOWN_FINISHED(src, pounce_cooldown) && (prob(75) || get_dist(src, target_mob) <= 5) && (target_mob in view(5, src)))
pounce(target_mob)
+ //if we're trying to get away and there's obstacles around us, try to break them
+ if(target_mob && is_retreating && stance >= HOSTILE_STANCE_ALERT && destroy_surroundings)
+ INVOKE_ASYNC(src, PROC_REF(DestroySurroundings))
+
if(target_mob || on_fire)
return
@@ -345,15 +488,16 @@
stance = HOSTILE_STANCE_IDLE
//if we're hungry and we don't have already have our eyes on a snack, try eating food if possible
- if(!food_target && COOLDOWN_FINISHED(src, food_cooldown))
+ if(tameable && !food_target && COOLDOWN_FINISHED(src, food_cooldown))
for(var/obj/item/reagent_container/food/snacks/food in view(6, src))
- if(!is_type_in_list(food, acceptable_foods))
- continue
- food_target = food
- stance = HOSTILE_STANCE_ALERT
- stop_automated_movement = TRUE
- MoveTo(food_target)
- break
+ var/is_meat = locate(/datum/reagent/nutriment/meat) in food.reagents.reagent_list
+
+ if(is_type_in_list(food, acceptable_foods) || is_meat)
+ food_target = food
+ stance = HOSTILE_STANCE_ALERT
+ stop_automated_movement = TRUE
+ MoveTo(food_target)
+ break
//handling mobs that are invading our personal space
if(stance <= HOSTILE_STANCE_ALERT && !food_target && COOLDOWN_FINISHED(src, calm_cooldown))
@@ -385,7 +529,6 @@
else if(!check_food_loc(food_target) && Adjacent(food_target))
INVOKE_ASYNC(src, PROC_REF(handle_food), food_target)
-
/mob/living/simple_animal/hostile/retaliate/giant_lizard/bullet_act(obj/projectile/projectile)
. = ..()
if(projectile.damage)
@@ -418,6 +561,11 @@
target.attack_animal(src)
animation_attack_on(target)
+ //xenos take extra damage
+ if(isxeno(target))
+ var/extra_damage = rand(melee_damage_lower, melee_damage_upper) * 0.33
+ target.apply_damage(extra_damage, BRUTE)
+
if(prob(33))
if(client && !is_retreating)
is_retreating = TRUE
@@ -429,6 +577,33 @@
MoveTo(target_mob, 8, TRUE, 2 SECONDS, TRUE) //skirmish around our target
return target
+ if(isStructure(inherited_target))
+ var/obj/structure/structure = inherited_target
+ if(structure.unslashable)
+ return
+
+ var/is_xeno_structure = FALSE
+ if(structure.flags_obj & OBJ_ORGANIC)
+ is_xeno_structure = TRUE
+
+ animation_attack_on(structure)
+ playsound(loc, is_xeno_structure ? "alien_resin_break" : 'sound/effects/metalhit.ogg', 25)
+ visible_message(SPAN_DANGER("[src] slashes [structure]!"), SPAN_DANGER("You slash [structure]!"), null, 5, CHAT_TYPE_COMBAT_ACTION)
+ var/damage_multiplier = 2
+ if(is_xeno_structure)
+ damage_multiplier = !client ? 15 : 5 //ai mobs need that extra oomph else they won't be able to break anything before they die
+ structure.update_health(rand(melee_damage_lower, melee_damage_upper) * damage_multiplier)
+ return
+
+ if(client && !is_eating && istype(inherited_target, /obj/item/reagent_container/food/snacks))
+ handle_food_client(inherited_target)
+ return
+
+ //if it's not an object or a structure, just swipe at it
+ animation_attack_on(inherited_target)
+ visible_message(SPAN_DANGER("[src] swipes at [inherited_target]!"), SPAN_DANGER("You swipe at [inherited_target]!"), null, 5, CHAT_TYPE_COMBAT_ACTION)
+ playsound(loc, 'sound/weapons/alien_claw_swipe.ogg', 10, 1)
+
//Used to handle attacks when a client is in the mob. Otherwise we'd default to a generic animal attack.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/UnarmedAttack(atom/target)
var/tile_attack = FALSE
@@ -444,9 +619,32 @@
target = targets
break
- if(isliving(target))
- AttackingTarget(target)
- next_move = world.time + 8
+ AttackingTarget(target)
+ //turf attacks are missed attacks so it should just be a minor penalty to attack delay
+ next_move = isturf(target) ? world.time + 4 : world.time + 8
+
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/DestroySurroundings()
+ if(!prob(break_stuff_probability))
+ return
+
+ for(var/obj/structure/obstacle in view(1, src))
+ if(is_type_in_list(obstacle, destruction_targets))
+ AttackingTarget(obstacle)
+ return
+
+//no longer checks for distance with ListTargets(). thershold for losing targets is increased, due to needing range for skirmishing
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/AttackTarget()
+ stop_automated_movement = TRUE
+ if(!target_mob || SA_attackable(target_mob))
+ LoseTarget()
+ return
+ if(get_dist(src, target_mob) > max_attack_distance)
+ LoseTarget()
+ return
+ if(in_range(src, target_mob)) //Attacking
+ AttackingTarget()
+ return TRUE
+
///Proc for when the mob finds food and starts DEVOURING it.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/handle_food(obj/item/reagent_container/food/snacks/food)
@@ -460,19 +658,38 @@
playsound(loc,'sound/items/eatfood.ogg', 25, 1)
for(var/mob/living/carbon/nearest_mob in view(7, src))
- if(nearest_mob != food.last_dropped_by || (nearest_mob in faction_group))
+ if(nearest_mob != food.last_dropped_by || (nearest_mob.faction in faction_group))
continue
face_atom(nearest_mob)
manual_emote("stares curiously at [nearest_mob].")
faction_group += nearest_mob.faction_group
break
+ health += maxHealth * 0.15
+ update_wounds()
qdel(food)
food_target = null
is_eating = FALSE
stance = HOSTILE_STANCE_IDLE
COOLDOWN_START(src, food_cooldown, 30 SECONDS)
+/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/handle_food_client(obj/item/reagent_container/food/snacks/food)
+ manual_emote("starts gnawing [food].")
+ playsound(loc,'sound/items/eatfood.ogg', 25, 1)
+ is_eating = TRUE
+ if(!do_after(src, 4 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_FRIENDLY))
+ is_eating = FALSE
+ return
+
+ is_eating = FALSE
+ if(!Adjacent(food))
+ return
+
+ playsound(loc,'sound/items/eatfood.ogg', 25, 1)
+ health += maxHealth * 0.10
+ update_wounds()
+ qdel(food)
+
///Proc for checking if someone picked our food target.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/check_food_loc(obj/food)
if(!ismob(food.loc))
@@ -525,7 +742,7 @@
//Immediately retaliate after being attacked.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/Retaliate()
- if(stat == DEAD || target_mob || on_fire)
+ if(stat == DEAD || get_dist(src, target_mob) < 6 || on_fire)
return
aggression_value = clamp(aggression_value + 5, 0, 15)
@@ -537,7 +754,7 @@
MoveToTarget()
//basic pack behaviour
- for(var/mob/living/simple_animal/hostile/retaliate/giant_lizard/pack_member in view(7, src))
+ for(var/mob/living/simple_animal/hostile/retaliate/giant_lizard/pack_member in view(12, src))
if(pack_member == src || pack_member.target_mob)
continue
pack_member.Retaliate()
@@ -545,18 +762,19 @@
///Proc for moving to targets. walk_to() doesn't check for resting and status effects so we will do it ourselves.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/MoveTo(target, distance = 1, retreat = FALSE, time = 6 SECONDS, return_to_combat = FALSE)
if(stat == DEAD || HAS_TRAIT(src, TRAIT_INCAPACITATED) || HAS_TRAIT(src, TRAIT_FLOORED))
- return
+ return FALSE
if(resting)
set_resting(FALSE)
if(!retreat)
walk_to(src, target ? target : get_turf(src), distance, move_to_delay)
- return
+ return TRUE
if(!is_retreating)
is_retreating = TRUE
stop_automated_movement = TRUE
stance = HOSTILE_STANCE_ALERT
walk_away(src, target ? target : get_turf(src), distance, LIZARD_SPEED_RETREAT)
addtimer(CALLBACK(src, PROC_REF(stop_retreat), return_to_combat), time)
+ return TRUE
//Proc that's called after the retreat has run its course.
/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/stop_retreat(return_to_combat = FALSE)
@@ -568,7 +786,7 @@
if(!return_to_combat)
//can't retreat? go back to fighting
if(retreat_attempts >= 2)
- FindTarget()
+ target_mob = FindTarget()
MoveToTarget()
retreat_attempts = 0
//seems like it's a life or death situation. we will stop trying to run away.
@@ -578,13 +796,13 @@
for(var/mob/living/carbon/hostile_mob in view(7, src))
if(hostile_mob.faction in faction_group)
continue
- MoveTo(hostile_mob, 10, TRUE, 3 SECONDS, FALSE)
+ MoveTo(hostile_mob, 10, TRUE, 2 SECONDS, FALSE)
retreat_attempts++
return
retreat_attempts = 0
LoseTarget()
else
- FindTarget()
+ target_mob = FindTarget()
MoveToTarget()
//Replaced walk_to() with MoveTo().
@@ -592,9 +810,9 @@
stop_automated_movement = TRUE
if(!target_mob || SA_attackable(target_mob))
stance = HOSTILE_STANCE_IDLE
- if(target_mob in ListTargets(10))
- stance = HOSTILE_STANCE_ATTACKING
- MoveTo(target_mob)
+ if(get_dist(src, target_mob) <= max_attack_distance)
+ if(MoveTo(target_mob))
+ stance = HOSTILE_STANCE_ATTACKING
/mob/living/simple_animal/hostile/retaliate/giant_lizard/resist_fire()
visible_message(SPAN_NOTICE("[src] rolls frantically on the ground to extinguish itself!"))
@@ -611,8 +829,14 @@
var/successful_attacks = 0
for(var/times_to_attack = 3, times_to_attack > 0, times_to_attack--)
+ if(body_position == LYING_DOWN)
+ return
+
if(Adjacent(target))
var/damage = rand(melee_damage_lower, melee_damage_upper) * 0.4
+ //xenos take extra damage
+ if(isxeno(target))
+ damage *= 1.33
var/attack_type = pick(ATTACK_SLASH, ATTACK_BITE)
attacktext = attack_type ? "claws" : "bites"
flick_attack_overlay(target, attack_type ? "slash" : "animalbite")
@@ -628,6 +852,7 @@
face_atom(target)
sleep(0.5 SECONDS)
successful_attacks++
+
if(successful_attacks == 3 && !COOLDOWN_FINISHED(src, pounce_cooldown))
to_chat(src, SPAN_BOLDWARNING("The bloodlust invigorates you! You will be ready to pounce much sooner."))
COOLDOWN_START(src, pounce_cooldown, COOLDOWN_TIMELEFT(src, pounce_cooldown) * 0.5)
@@ -690,7 +915,7 @@
playsound(loc, "giant_lizard_hiss", 25)
pounced_mob.KnockDown(0.5)
step_to(src, pounced_mob)
- if(!client)
+ if(!client && !(pounced_mob.faction in faction_group))
ravagingattack(pounced_mob)
/mob/living/simple_animal/hostile/retaliate/giant_lizard/proc/pounced_turf(turf/turf_target)
@@ -755,6 +980,18 @@
sound = "giant_lizard_hiss"
emote_type = EMOTE_AUDIBLE|EMOTE_VISIBLE
+/datum/emote/living/giant_lizard/flicktongue
+ key = "flicktongue"
+ message = null
+ emote_type = EMOTE_VISIBLE
+
+/datum/emote/living/giant_lizard/flicktongue/run_emote(mob/living/simple_animal/hostile/retaliate/giant_lizard/user, params, type_override, intentional)
+ . = ..()
+ if(!.)
+ return
+
+ flick("Giant Lizard Tongue", user.tongue_icon_holder)
+
#undef ATTACK_SLASH
#undef ATTACK_BITE
#undef LIZARD_SPEED_NORMAL
diff --git a/icons/mob/mob_64.dmi b/icons/mob/mob_64.dmi
index 8ac2194b3eb2..ece08490fa5b 100644
Binary files a/icons/mob/mob_64.dmi and b/icons/mob/mob_64.dmi differ