From 93419f7fd4293d659d1fadddc4ab680a72fd3d37 Mon Sep 17 00:00:00 2001 From: DZD Date: Tue, 26 May 2015 20:30:32 -0400 Subject: [PATCH 1/2] simple_animal and hostile mob refactor Ports the following: tgstation/-tg-station#1780 Part of tgstation/-tg-station#6278 (Not including faction transferring) tgstation/-tg-station#8206 tgstation/-tg-station#8543 --- code/__DEFINES/combat.dm | 8 +- code/modules/mining/equipment_locker.dm | 1 + .../mob/living/simple_animal/hostile/bear.dm | 2 +- .../living/simple_animal/hostile/hostile.dm | 197 ++++++++++++------ .../simple_animal/hostile/mining_mobs.dm | 45 ++-- .../simple_animal/hostile/spaceworms.dm | 4 +- .../mob/living/simple_animal/simple_animal.dm | 36 +--- 7 files changed, 175 insertions(+), 118 deletions(-) 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..0e4cc479394 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -75,7 +75,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 3e1fd60f404..5071c3f4b48 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -497,19 +497,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() @@ -552,24 +550,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 From db863b258d231339a6825cc25fa41248165e3844 Mon Sep 17 00:00:00 2001 From: DZD Date: Wed, 27 May 2015 18:22:03 -0400 Subject: [PATCH 2/2] Minor bear fixes Fixes bears not having attack text or an attack sound. --- code/modules/mob/living/simple_animal/hostile/bear.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index 0e4cc479394..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