diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 34e1f4279b8..ffbbc134bba 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -50,8 +50,14 @@
#define GRAB_UPGRADING 4
#define GRAB_KILL 5
+//Hostile Mob Stances
#define HOSTILE_STANCE_IDLE 1
#define HOSTILE_STANCE_ALERT 2
#define HOSTILE_STANCE_ATTACK 3
#define HOSTILE_STANCE_ATTACKING 4
-#define HOSTILE_STANCE_TIRED 5
\ No newline at end of file
+#define HOSTILE_STANCE_TIRED 5
+
+//Hostile Mob AI Status
+#define AI_ON 1
+#define AI_SLEEP 2
+#define AI_OFF 3
diff --git a/code/modules/mining/equipment_locker.dm b/code/modules/mining/equipment_locker.dm
index 65e63b387de..702a87ab336 100644
--- a/code/modules/mining/equipment_locker.dm
+++ b/code/modules/mining/equipment_locker.dm
@@ -603,6 +603,7 @@
var/mob/living/simple_animal/hostile/H = M
if(malfunctioning)
H.faction |= list("lazarus", "\ref[user]")
+ H.robust_searching = 1
H.friends += user
H.attack_same = 1
log_game("[user] has revived hostile mob [target] with a malfunctioning lazarus injector")
diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm
index b7181c6ad5a..25113ae500c 100644
--- a/code/modules/mob/living/simple_animal/hostile/bear.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bear.dm
@@ -23,6 +23,8 @@
health = 60
melee_damage_lower = 20
melee_damage_upper = 30
+ attacktext = "mauls"
+ attack_sound = 'sound/weapons/genhit3.ogg'
//Space bears aren't affected by atmos.
min_oxy = 0
@@ -75,7 +77,7 @@
stop_automated_movement = 1
var/found_mob = 0
if(target && target in ListTargets())
- if(!(SA_attackable(target)))
+ if(CanAttack(target))
stance_step = max(0, stance_step) //If we have not seen a mob in a while, the stance_step will be negative, we need to reset it to 0 as soon as we see a mob again.
stance_step++
found_mob = 1
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 52aebdbfd17..28ba410a928 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -5,7 +5,6 @@
environment_smash = 1 //Set to 1 to break closets,tables,racks, etc; 2 for walls; 3 for rwalls
var/stance = HOSTILE_STANCE_IDLE //Used to determine behavior
var/atom/target
- var/attack_same = 0 //Set us to 1 to allow us to attack our own faction, or 2, to only ever attack our own faction
var/ranged = 0
var/rapid = 0
var/projectiletype
@@ -13,20 +12,24 @@
var/casingtype
var/move_to_delay = 3 //delay for the automated movement.
var/list/friends = list()
- var/vision_range = 9 //How big of an area to search for targets in, a vision of 9 attempts to find targets as soon as they walk into screen view
-
- var/aggro_vision_range = 9 //If a mob is aggro, we search in this radius. Defaults to 9 to keep in line with original simple mob aggro radius
- var/idle_vision_range = 9 //If a mob is just idling around, it's vision range is limited to this. Defaults to 9 to keep in line with original simple mob aggro radius
var/ranged_message = "fires" //Fluff text for ranged mobs
var/ranged_cooldown = 0 //What the starting cooldown is on ranged attacks
var/ranged_cooldown_cap = 3 //What ranged attacks, after being used are set to, to go back on cooldown, defaults to 3 life() ticks
var/retreat_distance = null //If our mob runs from players when they're too close, set in tile distance. By default, mobs do not retreat.
var/minimum_distance = 1 //Minimum approach distance, so ranged mobs chase targets down, but still keep their distance set in tiles to the target, set higher to make mobs keep distance
+
+//These vars are related to how mobs locate and target
+ var/robust_searching = 0 //By default, mobs have a simple searching method, set this to 1 for the more scrutinous searching (stat_attack, stat_exclusive, etc), should be disabled on most mobs
+ var/vision_range = 9 //How big of an area to search for targets in, a vision of 9 attempts to find targets as soon as they walk into screen view
+ var/aggro_vision_range = 9 //If a mob is aggro, we search in this radius. Defaults to 9 to keep in line with original simple mob aggro radius
+ var/idle_vision_range = 9 //If a mob is just idling around, it's vision range is limited to this. Defaults to 9 to keep in line with original simple mob aggro radius
var/search_objects = 0 //If we want to consider objects when searching around, set this to 1. If you want to search for objects while also ignoring mobs until hurt, set it to 2. To completely ignore mobs, even when attacked, set it to 3
var/list/wanted_objects = list() //A list of objects that will be checked against to attack, should we have search_objects enabled
var/stat_attack = 0 //Mobs with stat_attack to 1 will attempt to attack things that are unconscious, Mobs with stat_attack set to 2 will attempt to attack the dead.
var/stat_exclusive = 0 //Mobs with this set to 1 will exclusively attack things defined by stat_attack, stat_attack 2 means they will only attack corpses
- var/AIenabled = 1 //Badminnery and general AI toggle.
+ var/attack_same = 0 //Set us to 1 to allow us to attack our own faction, or 2, to only ever attack our own faction
+
+ var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_SLEEP (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever)
/mob/living/simple_animal/hostile/Life()
@@ -34,7 +37,11 @@
if(!.)
walk(src, 0)
return 0
- if(client || !AIenabled)
+ if(ranged)
+ ranged_cooldown--
+ if(client)
+ return 0
+ if(!AICanContinue())
return 0
if(!stat)
switch(stance)
@@ -50,30 +57,24 @@
AttackTarget()
DestroySurroundings()
- if(ranged)
- ranged_cooldown--
+ if(AIShouldSleep())
+ AIStatus = AI_SLEEP
+
//////////////HOSTILE MOB TARGETTING AND AGGRESSION////////////
/mob/living/simple_animal/hostile/proc/ListTargets()//Step 1, find out what we can see
var/list/L = list()
- if(search_objects < 2)
- var/list/Mobs = hearers(src, vision_range)
- for(var/mob/living/G in Mobs)
- L.Add(G)
- L.Remove(src)//So we don't suicide because we listed ourselves as a target!
- if(search_objects)
- var/list/Objects = oview(vision_range, src)
- for(var/obj/O in Objects)
- L.Add(O)
- else
+ if(!search_objects)
+ var/list/Mobs = hearers(vision_range, src) - src //Remove self, so we don't suicide
+ L += Mobs
for(var/obj/mecha/M in mechas_list)
if(get_dist(M, src) <= vision_range && can_see(src, M, vision_range))
L += M
- for(var/obj/spacepod/S in spacepods_list)
- if(get_dist(S, src) <= vision_range && can_see(src, S, vision_range))
- L += S
+ else
+ var/list/Objects = oview(vision_range, src)
+ L += Objects
return L
/mob/living/simple_animal/hostile/proc/FindTarget()//Step 2, filter down possible targets to things we actually care about
@@ -82,16 +83,14 @@
for(var/atom/A in ListTargets())
if(Found(A))//Just in case people want to override targetting
var/list/FoundTarget = list()
- FoundTarget.Add(A)
+ FoundTarget += A
Targets = FoundTarget
break
if(CanAttack(A))//Can we attack it?
- if(istype(src, /mob/living/simple_animal/hostile/scarybat))
- if(A == src:owner)
- continue
Targets += A
continue
Target = PickTarget(Targets)
+ GiveTarget(Target)
return Target //We now have a target
/mob/living/simple_animal/hostile/proc/Found(var/atom/A)//This is here as a potential override to pick a specific target if available
@@ -112,32 +111,35 @@
/mob/living/simple_animal/hostile/CanAttack(var/atom/the_target)//Can we actually attack a possible target?
if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it
return 0
- if(isobj(the_target) && search_objects)
+ if(search_objects < 2)
+ if(istype(the_target, /obj/mecha))
+ var/obj/mecha/M = the_target
+ if(M.occupant)//Just so we don't attack empty mechs
+ if(CanAttack(M.occupant))
+ return 1
+ if(isliving(the_target))
+ var/mob/living/L = the_target
+ var/faction_check = 0
+ for(var/F in faction)
+ if(F in L.faction)
+ faction_check = 1
+ break
+ if(robust_searching)
+ if(L.stat > stat_attack || L.stat != stat_attack && stat_exclusive == 1)
+ return 0
+ if(faction_check && !attack_same || !faction_check && attack_same == 2)
+ return 0
+ if(L in friends)
+ return 0
+ else
+ if(L.stat)
+ return 0
+ if(faction_check && !attack_same)
+ return 0
+ return 1
+ if(isobj(the_target))
if(the_target.type in wanted_objects)
return 1
- if(isliving(the_target) && search_objects < 2)
- var/mob/living/L = the_target
- if(L.stat > stat_attack || L.stat != stat_attack && stat_exclusive == 1)
- return 0
- var/faction_check = 0
- for(var/F in faction)
- if(F in L.faction)
- faction_check = 1
- break
- if(faction_check && !attack_same || !faction_check && attack_same == 2)
- return 0
- return 0
- if(L in friends)
- return 0
- return 1
- if(istype(the_target, /obj/mecha))
- var/obj/mecha/M = the_target
- if(M.occupant)//Just so we don't attack empty mechs
- return 1
- if(istype(the_target, /obj/spacepod))
- var/obj/spacepod/S = the_target
- if(S.occupant || S.occupant2)//Just so we don't attack empty mechs
- return 1
return 0
/mob/living/simple_animal/hostile/proc/GiveTarget(var/new_target)//Step 4, give us our selected target
@@ -149,15 +151,16 @@
/mob/living/simple_animal/hostile/proc/MoveToTarget()//Step 5, handle movement between us and our target
stop_automated_movement = 1
- if(!target || SA_attackable(target))
+ if(!target || !CanAttack(target))
LoseTarget()
+ return
if(target in ListTargets())
- var/TargetDistance = get_dist(src,target)
+ var/target_distance = get_dist(src,target)
if(ranged)//We ranged? Shoot at em
- if(TargetDistance >= 2 && ranged_cooldown <= 0)//But make sure they're a tile away at least, and our range attack is off cooldown
+ if(target_distance >= 2 && ranged_cooldown <= 0)//But make sure they're a tile away at least, and our range attack is off cooldown
OpenFire(target)
if(retreat_distance != null)//If we have a retreat distance, check if we need to run from our target
- if(TargetDistance <= retreat_distance)//If target's closer than our retreat distance, run
+ if(target_distance <= retreat_distance)//If target's closer than our retreat distance, run
walk_away(src,target,retreat_distance,move_to_delay)
else
Goto(target,move_to_delay,minimum_distance)//Otherwise, get to our minimum distance so we chase them
@@ -166,6 +169,15 @@
if(isturf(loc) && target.Adjacent(src)) //If they're next to us, attack
AttackingTarget()
return
+ if(environment_smash)
+ if(target.loc != null && get_dist(src, target.loc) <= vision_range)//We can't see our target, but he's in our vision range still
+ if(environment_smash >= 2)//If we're capable of smashing through walls, forget about vision completely after finding our target
+ Goto(target,move_to_delay,minimum_distance)
+ FindHidden()
+ return
+ else
+ if(FindHidden())
+ return
LostTarget()
/mob/living/simple_animal/hostile/proc/Goto(var/target, var/delay, var/minimum_distance)
@@ -189,7 +201,7 @@
/mob/living/simple_animal/hostile/proc/AttackTarget()
stop_automated_movement = 1
- if(!target || SA_attackable(target))
+ if(!target || !CanAttack(target))
LoseTarget()
return 0
if(!(target in ListTargets()))
@@ -229,8 +241,9 @@
walk(src, 0)
/mob/living/simple_animal/hostile/proc/OpenFire(var/the_target)
+
var/target = the_target
- visible_message("\red [src] [ranged_message] at [target]!", 1)
+ visible_message("[src] [ranged_message] at [target]!")
var/tturf = get_turf(target)
if(rapid)
@@ -250,9 +263,6 @@
Shoot(tturf, src.loc, src)
if(casingtype)
new casingtype
-
- stance = HOSTILE_STANCE_IDLE
- target = null
ranged_cooldown = ranged_cooldown_cap
return
@@ -265,9 +275,10 @@
if(!A) return
if (!istype(target, /turf))
- del(A)
+ qdel(A)
return
A.current = target
+ A.firer = src
A.yo = target:y - start:y
A.xo = target:x - start:x
spawn( 0 )
@@ -276,18 +287,66 @@
/mob/living/simple_animal/hostile/proc/DestroySurroundings()
if(environment_smash)
- if(buckled)//Beds and chairs are no longer hostile mob kryptonite
- buckled.attack_animal(src)
- var/list/directions = cardinal.Copy()
- for(var/dir in directions)
+ EscapeConfinement()
+ for(var/dir in cardinal)
var/turf/T = get_step(src, dir)
- if(istype(T, /turf/simulated/wall) && T.Adjacent(src))
- T.attack_animal(src)
+ if(istype(T, /turf/simulated/wall) || istype(T, /turf/simulated/mineral))
+ if(T.Adjacent(src))
+ T.attack_animal(src)
for(var/atom/A in T)
if(!A.Adjacent(src))
continue
- if(istype(A, /obj/structure/window) || istype(A, /obj/structure/closet) || \
- istype(A, /obj/structure/table) || istype(A, /obj/structure/grille) || \
- istype(A, /obj/structure/rack) || istype(A, /obj/machinery/door/window))
+ if(istype(A, /obj/structure/window) || istype(A, /obj/structure/closet) || istype(A, /obj/structure/table) || istype(A, /obj/structure/grille) || istype(A, /obj/structure/rack))
A.attack_animal(src)
return
+
+/mob/living/simple_animal/hostile/proc/EscapeConfinement()
+ if(buckled)
+ buckled.attack_animal(src)
+ if(!isturf(src.loc) && src.loc != null)//Did someone put us in something?
+ var/atom/A = src.loc
+ A.attack_animal(src)//Bang on it till we get out
+ return
+
+/mob/living/simple_animal/hostile/proc/FindHidden()
+ if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper))
+ var/atom/A = target.loc
+ Goto(A,move_to_delay,minimum_distance)
+ if(A.Adjacent(src))
+ A.attack_animal(src)
+ return 1
+
+/mob/living/simple_animal/hostile/RangedAttack(var/atom/A, var/params) //Player firing
+ if(ranged && ranged_cooldown <= 0)
+ target = A
+ OpenFire(A)
+ ..()
+
+
+
+////// AI Status ///////
+/mob/living/simple_animal/hostile/proc/AICanContinue()
+ switch(AIStatus)
+ if(AI_ON)
+ . = 1
+ if(AI_SLEEP)
+ if(AIShouldWake())
+ . = 1
+ AIStatus = AI_ON //Wake up for more than one Life() cycle.
+ else
+ . = 0
+ if(AI_OFF)
+ . = 0
+
+
+//Returns 1 if the AI should wake up
+//Returns 0 if the AI should remain asleep
+/mob/living/simple_animal/hostile/proc/AIShouldWake()
+ . = 0
+ if(FindTarget())
+ . = 1
+
+
+//Convenience
+/mob/living/simple_animal/hostile/proc/AIShouldSleep()
+ . = !(AIShouldWake())
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm
index 61d8467cd94..b2d8c1c37e1 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm
@@ -20,6 +20,8 @@
a_intent = "harm"
var/throw_message = "bounces off of"
var/icon_aggro = null // for swapping to when we get aggressive
+ see_in_dark = 8
+ see_invisible = SEE_INVISIBLE_MINIMUM
/mob/living/simple_animal/hostile/asteroid/Aggro()
..()
@@ -138,6 +140,7 @@
melee_damage_lower = 0
melee_damage_upper = 0
attacktext = "barrels into"
+ attack_sound = 'sound/weapons/punch1.ogg'
a_intent = "help"
throw_message = "sinks in slowly, before being pushed out of "
status_flags = CANPUSH
@@ -238,6 +241,7 @@
melee_damage_lower = 0
melee_damage_upper = 0
attacktext = "lashes out at"
+ speak_emote = list("telepathically cries")
throw_message = "falls right through the strange body of the"
ranged_cooldown = 0
ranged_cooldown_cap = 0
@@ -288,7 +292,7 @@
user << "You chomp into [src], barely managing to hold it down, but feel amazingly refreshed in mere moments."
playsound(src.loc,'sound/items/eatfood.ogg', rand(10,50), 1)
H.revive()
- del(src)
+ qdel(src)
..()
/mob/living/simple_animal/hostile/asteroid/hivelordbrood
@@ -311,6 +315,7 @@
melee_damage_lower = 2
melee_damage_upper = 2
attacktext = "slashes"
+ speak_emote = list("telepathically cries")
throw_message = "falls right through the strange body of the"
environment_smash = 0
pass_flags = PASSTABLE
@@ -318,10 +323,10 @@
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/New()
..()
spawn(100)
- del(src)
+ qdel(src)
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/Die()
- del(src)
+ qdel(src)
/mob/living/simple_animal/hostile/asteroid/goliath
name = "goliath"
@@ -339,6 +344,7 @@
ranged_cooldown = 2 //By default, start the Goliath with his cooldown off so that people can run away quickly on first sight
ranged_cooldown_cap = 8
friendly = "wails at"
+ speak_emote = list("bellows")
vision_range = 4
speed = 3
maxHealth = 300
@@ -347,6 +353,7 @@
melee_damage_lower = 25
melee_damage_upper = 25
attacktext = "pulverizes"
+ attack_sound = 'sound/weapons/punch1.ogg'
throw_message = "does nothing to the rocky hide of the"
aggro_vision_range = 9
idle_vision_range = 5
@@ -398,6 +405,7 @@
name = "Goliath tentacle"
icon = 'icons/mob/animal.dmi'
icon_state = "Goliath_tentacle"
+ var/latched = 0
/obj/effect/goliath_tentacle/New()
var/turftype = get_turf(src)
@@ -425,15 +433,15 @@
/obj/effect/goliath_tentacle/proc/Trip()
for(var/mob/living/M in src.loc)
- M.Weaken(5)
- visible_message("The [src.name] knocks [M.name] down!")
- del(src)
-
-/obj/effect/goliath_tentacle/Crossed(AM as mob|obj)
- if(isliving(AM))
- Trip()
- return
- ..()
+ M.Stun(5)
+ M.adjustBruteLoss(rand(10,15))
+ latched = 1
+ visible_message("The [src.name] grabs hold of [M.name]!")
+ if(!latched)
+ qdel(src)
+ else
+ spawn(50)
+ qdel(src)
/mob/living/simple_animal/hostile/asteroid/goliath/Die()
var/obj/item/asteroid/goliath_hide/G = new /obj/item/asteroid/goliath_hide(src.loc)
@@ -457,15 +465,18 @@
if(current_armor.["melee"] < 80)
current_armor.["melee"] = min(current_armor.["melee"] + 10, 80)
user << "You strengthen [target], improving its resistance against melee attacks."
- del(src)
+ qdel(src)
else
user << "You can't improve [C] any further."
return
if(istype(target, /obj/mecha/working/ripley))
var/obj/mecha/D = target
var/list/damage_absorption = D.damage_absorption
- if(damage_absorption.["brute"] > 0.3)
- damage_absorption.["brute"] = max(damage_absorption.["brute"] - 0.1, 0.3)
+ if(damage_absorption["brute"] > 0.3)
+ damage_absorption["brute"] = max(damage_absorption["brute"] - 0.1, 0.3)
+ damage_absorption["bullet"] = damage_absorption["bullet"] - 0.05
+ damage_absorption["fire"] = damage_absorption["fire"] - 0.05
+ damage_absorption["laser"] = damage_absorption["laser"] - 0.025
user << "You strengthen [target], improving its resistance against melee attacks."
qdel(src)
if(D.icon_state == "ripley-open")
@@ -478,7 +489,7 @@
D.overlays += image("icon"="mecha.dmi", "icon_state"="ripley-g-full-open")
D.desc = "Autonomous Power Loader Unit. It's wearing a fearsome carapace entirely composed of goliath hide plates - the pilot must be an experienced monster hunter."
else
- user << "You can't add armour onto the mech while someone is inside!"
+ user << "You can't add armour onto the mech while someone is inside!"
else
- user << "You can't improve [D] any further."
+ user << "You can't improve [D] any further!"
return
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/spaceworms.dm b/code/modules/mob/living/simple_animal/hostile/spaceworms.dm
index 3662485ddcc..951c71bc27d 100644
--- a/code/modules/mob/living/simple_animal/hostile/spaceworms.dm
+++ b/code/modules/mob/living/simple_animal/hostile/spaceworms.dm
@@ -36,7 +36,7 @@
speed = -1
- AIenabled = 0 //The body isn't conscious
+ AIStatus = AI_OFF
anchored = 1 //otherwise people can literally fucking pull spaceworms apart
@@ -74,7 +74,7 @@
animate_movement = SLIDE_STEPS
- AIenabled = 1//The head is conscious
+ AIStatus = AI_ON//The head is conscious
stop_automated_movement = 0 //Ditto ^
faction = list("spaceworms") //head and body both have this faction JIC
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 56d4ffb9f61..861df0c1c38 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -474,19 +474,17 @@
if(health < 1)
Die()
-/mob/living/simple_animal/proc/SA_attackable(target)
- if (isliving(target))
- var/mob/living/L = target
- if(!L.stat)
+/mob/living/simple_animal/proc/CanAttack(var/atom/the_target)
+ if(see_invisible < the_target.invisibility)
+ return 0
+ if (isliving(the_target))
+ var/mob/living/L = the_target
+ if(L.stat != CONSCIOUS)
return 0
- if (istype(target,/obj/mecha))
- var/obj/mecha/M = target
+ if (istype(the_target, /obj/mecha))
+ var/obj/mecha/M = the_target
if (M.occupant)
return 0
- if (istype(target,/obj/spacepod))
- var/obj/spacepod/S = target
- if (S.occupant || S.occupant2)
- return 0
return 1
/mob/living/simple_animal/update_fire()
@@ -529,24 +527,6 @@
gib()
return
-
-/mob/living/simple_animal/proc/CanAttack(var/atom/the_target)
- if(see_invisible < the_target.invisibility)
- return 0
- if (isliving(the_target))
- var/mob/living/L = the_target
- if(L.stat != CONSCIOUS)
- return 0
- if (istype(the_target, /obj/mecha))
- var/obj/mecha/M = the_target
- if (M.occupant)
- return 0
- if (istype(the_target, /obj/spacepod))
- var/obj/spacepod/S = the_target
- if (S.occupant || S.occupant2)
- return 0
- return 1
-
/mob/living/simple_animal/say(var/message)
if(stat)
return