diff --git a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm index 9b4fb3044f..5a25fa7de0 100644 --- a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm +++ b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm @@ -149,3 +149,39 @@ // Simple mobs that retaliate and support others in their faction who get attacked. /datum/ai_holder/simple_mob/retaliate/cooperative cooperative = TRUE + +// With all the bells and whistles +/datum/ai_holder/simple_mob/humanoid + intelligence_level = AI_SMART //Purportedly + retaliate = TRUE //If attacked, attack back + threaten = TRUE //Verbal threats + firing_lanes = TRUE //Avoid shooting allies + conserve_ammo = TRUE //Don't shoot when it can't hit target + can_breakthrough = TRUE //Can break through doors + violent_breakthrough = FALSE //Won't try to break through walls (humans can, but usually don't) + speak_chance = 2 //Babble chance + cooperative = TRUE //Assist each other + wander = TRUE //Wander around + returns_home = TRUE //But not too far + use_astar = TRUE //Path smartly + +// The hostile subtype is implied to be trained combatants who use ""tactics"" +/datum/ai_holder/simple_mob/humanoid/hostile + var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away. + hostile = TRUE //Attack! + +// Juke +/datum/ai_holder/simple_mob/humanoid/hostile/post_melee_attack(atom/A) + holder.IMove(get_step(holder, pick(alldirs))) + holder.face_atom(A) + +/datum/ai_holder/simple_mob/humanoid/hostile/post_ranged_attack(atom/A) + //Pick a random turf to step into + var/turf/T = get_step(holder, pick(alldirs)) + if(check_trajectory(A, T)) // Can we even hit them from there? + holder.IMove(T) + holder.face_atom(A) + + if(get_dist(holder, A) < run_if_this_close) + holder.IMove(get_step_away(holder, A)) + holder.face_atom(A) diff --git a/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm index b723c98695..94fae4f9b3 100644 --- a/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm +++ b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm @@ -77,7 +77,7 @@ if(rabid) return var/justified = my_slime.is_justified_to_discipline() // This will also consider the AI-side of that proc. - lost_target() // Stop attacking. + remove_target() // Stop attacking. if(justified) obedience++ @@ -137,7 +137,7 @@ // Called when using a pacification agent (or it's Kendrick being initalized). /datum/ai_holder/simple_mob/xenobio_slime/proc/pacify() - lost_target() // So it stops trying to kill them. + remove_target() // So it stops trying to kill them. rabid = FALSE hostile = FALSE retaliate = FALSE @@ -247,7 +247,7 @@ else delayed_say("Fine...", speaker) adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified. - lost_target() + remove_target() if(leader) // We're being asked to stop following someone. diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index c61bfe6cbf..cdff43d31f 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -93,9 +93,7 @@ /datum/ai_holder/proc/forget_everything() // Some of these might be redundant, but hopefully this prevents future bugs if that changes. lose_follow() - lose_target() - lose_target_position() - give_up_movement() + remove_target() // 'Tactical' processes such as moving a step, meleeing an enemy, firing a projectile, and other fairly cheap actions that need to happen quickly. /datum/ai_holder/proc/handle_tactics() @@ -116,33 +114,6 @@ /datum/ai_holder/proc/handle_special_strategical() -/* - //AI Actions - if(!ai_inactive) - //Stanceyness - handle_stance() - - //Movement - if(!stop_automated_movement && wander && !anchored) //Allowed to move? - handle_wander_movement() - - //Speaking - if(speak_chance && stance == STANCE_IDLE) // Allowed to chatter? - handle_idle_speaking() - - //Resisting out buckles - if(stance != STANCE_IDLE && incapacitated(INCAPACITATION_BUCKLED_PARTIALLY)) - handle_resist() - - //Resisting out of closets - if(istype(loc,/obj/structure/closet)) - var/obj/structure/closet/C = loc - if(C.welded) - resist() - else - C.open() -*/ - // For setting the stance WITHOUT processing it /datum/ai_holder/proc/set_stance(var/new_stance) ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO) @@ -221,7 +192,8 @@ if(STANCE_REPOSITION) // This is the same as above but doesn't stop if an enemy is visible since its an 'in-combat' move order. ai_log("handle_stance_tactical() : STANCE_REPOSITION, going to walk_to_destination().", AI_LOG_TRACE) - walk_to_destination() + if(!target && !find_target()) + walk_to_destination() if(STANCE_FOLLOW) ai_log("handle_stance_tactical() : STANCE_FOLLOW, going to walk_to_leader().", AI_LOG_TRACE) @@ -247,12 +219,18 @@ ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE) ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE) + ai_log("handle_stance_strategical() : LTT=[lose_target_time]", AI_LOG_TRACE) + if(lose_target_time && (lose_target_time + lose_target_timeout < world.time)) // We were tracking an enemy but they are gone. + ai_log("handle_stance_strategical() : Giving up a chase.", AI_LOG_DEBUG) + remove_target() + + if(stance in STANCES_COMBAT) + request_help() // Call our allies. + switch(stance) if(STANCE_IDLE) - if(speak_chance) // In the long loop since otherwise it wont shut up. handle_idle_speaking() - if(hostile) ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE) find_target() @@ -260,6 +238,7 @@ if(target) ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE) calculate_path(target) + walk_to_target() if(STANCE_MOVE) if(hostile && find_target()) // This will switch its stance. ai_log("handle_stance_strategical() : STANCE_MOVE, found target and was inturrupted.", AI_LOG_TRACE) @@ -269,6 +248,7 @@ else if(leader) ai_log("handle_stance_strategical() : STANCE_FOLLOW, going to calculate_path([leader]).", AI_LOG_TRACE) calculate_path(leader) + walk_to_leader() ai_log("handle_stance_strategical() : Exiting.", AI_LOG_TRACE) ai_log("++++++++++ Slow Process Ending ++++++++++", AI_LOG_TRACE) diff --git a/code/modules/ai/ai_holder_combat.dm b/code/modules/ai/ai_holder_combat.dm index 63154e4fe4..65fd1785b1 100644 --- a/code/modules/ai/ai_holder_combat.dm +++ b/code/modules/ai/ai_holder_combat.dm @@ -9,47 +9,23 @@ var/violent_breakthrough = TRUE // If false, the AI is not allowed to destroy things like windows or other structures in the way. Requires above var to be true. var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range. - - + // This does the actual attacking. /datum/ai_holder/proc/engage_target() ai_log("engage_target() : Entering.", AI_LOG_DEBUG) // Can we still see them? -// if(!target || !can_attack(target) || (!(target in list_targets())) ) if(!target || !can_attack(target)) ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE) - lose_target() // We lost them. - - if(!find_target()) // If we can't get a new one, then wait for a bit and then time out. - set_stance(STANCE_IDLE) - lost_target() - ai_log("engage_target() : No more targets. Exiting.", AI_LOG_DEBUG) + if(lose_target()) // We lost them (returns TRUE if we found something else to do) + ai_log("engage_target() : Pursuing other options (last seen, or a new target).", AI_LOG_TRACE) return - // if(lose_target_time + lose_target_timeout < world.time) - // ai_log("engage_target() : Unseen enemy timed out.", AI_LOG_TRACE) - // set_stance(STANCE_IDLE) // It must've been the wind. - // lost_target() - // ai_log("engage_target() : Exiting.", AI_LOG_DEBUG) - // return - - // // But maybe we do one last ditch effort. - // if(!target_last_seen_turf || intelligence_level < AI_SMART) - // ai_log("engage_target() : No last known position or is too dumb to fight unseen enemies.", AI_LOG_TRACE) - // set_stance(STANCE_IDLE) - // else - // ai_log("engage_target() : Fighting unseen enemy.", AI_LOG_TRACE) - // engage_unseen_enemy() - else - ai_log("engage_target() : Got new target ([target]).", AI_LOG_TRACE) var/distance = get_dist(holder, target) ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE) holder.face_atom(target) last_conflict_time = world.time - request_help() // Call our allies. - // Do a 'special' attack, if one is allowed. // if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range)) if(holder.ICheckSpecialAttack(target)) @@ -198,12 +174,8 @@ // Make sure we can still chase/attack them. if(!target || !can_attack(target)) ai_log("walk_to_target() : Lost target.", AI_LOG_INFO) - if(!find_target()) - lost_target() - ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG) - return - else - ai_log("walk_to_target() : Found new target ([target]).", AI_LOG_INFO) + lose_target() + return // Find out where we're going. var/get_to = closest_distance(target) @@ -220,7 +192,6 @@ ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG) return - // Otherwise keep walking. if(!stand_ground) walk_path(target, get_to) diff --git a/code/modules/ai/ai_holder_combat_unseen.dm b/code/modules/ai/ai_holder_combat_unseen.dm index 0cb518f08c..c854377c89 100644 --- a/code/modules/ai/ai_holder_combat_unseen.dm +++ b/code/modules/ai/ai_holder_combat_unseen.dm @@ -2,21 +2,21 @@ // Used when a target is out of sight or invisible. /datum/ai_holder/proc/engage_unseen_enemy() + ai_log("engage_unseen_enemy() : Entering.", AI_LOG_TRACE) // Lets do some last things before giving up. - if(!ranged) - if(get_dist(holder, target_last_seen_turf > 1)) // We last saw them over there. + if(conserve_ammo || !holder.ICheckRangedAttack(target_last_seen_turf)) + if(get_dist(holder, target_last_seen_turf) > 1) // We last saw them over there. // Go to where you last saw the enemy. - give_destination(target_last_seen_turf, 1, TRUE) // This will set it to STANCE_REPOSITION. - else // We last saw them next to us, so do a blind attack on that tile. + give_destination(target_last_seen_turf, 1, TRUE) // Sets stance as well + else if(lose_target_time == world.time) // We last saw them next to us, so do a blind attack on that tile. melee_on_tile(target_last_seen_turf) - - else if(!conserve_ammo) + else + find_target() + else shoot_near_turf(target_last_seen_turf) // This shoots semi-randomly near a specific turf. /datum/ai_holder/proc/shoot_near_turf(turf/targeted_turf) - if(!ranged) - return // Can't shoot. if(get_dist(holder, targeted_turf) > max_range(targeted_turf)) return // Too far to shoot. @@ -32,6 +32,7 @@ // Attempts to attack something on a specific tile. // TODO: Put on mob/living? /datum/ai_holder/proc/melee_on_tile(turf/T) + ai_log("melee_on_tile() : Entering.", AI_LOG_TRACE) var/mob/living/L = locate() in T if(!L) T.visible_message("\The [holder] attacks nothing around \the [T].") diff --git a/code/modules/ai/ai_holder_communication.dm b/code/modules/ai/ai_holder_communication.dm index 4cfec6f7af..ef8fcf253d 100644 --- a/code/modules/ai/ai_holder_communication.dm +++ b/code/modules/ai/ai_holder_communication.dm @@ -15,7 +15,7 @@ /datum/ai_holder/proc/should_threaten() if(!threaten) return FALSE // We don't negotiate. - if(target in attackers) + if(check_attacker(target)) return FALSE // They (or someone like them) attacked us before, escalate immediately. if(!will_threaten(target)) return FALSE // Pointless to threaten an animal, a mindless drone, or an object. diff --git a/code/modules/ai/ai_holder_cooperation.dm b/code/modules/ai/ai_holder_cooperation.dm index 0f6b0bcfa2..dd6228f460 100644 --- a/code/modules/ai/ai_holder_cooperation.dm +++ b/code/modules/ai/ai_holder_cooperation.dm @@ -74,7 +74,7 @@ ai_log("request_help() : Exiting.", AI_LOG_DEBUG) -// What allies receive when someone else is calling for help. +// What allies receive when someone else is calling for help.1 /datum/ai_holder/proc/help_requested(mob/living/friend) ai_log("help_requested() : Entering.", AI_LOG_DEBUG) if(stance == STANCE_SLEEP) @@ -92,24 +92,26 @@ if(!holder.IIsAlly(friend)) // Extra sanity. ai_log("help_requested() : Help requested by [friend] but we hate them.", AI_LOG_INFO) return - if(friend.ai_holder && friend.ai_holder.target && !can_attack(friend.ai_holder.target)) - ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO) - return - if(get_dist(holder, friend) <= follow_distance) - ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO) - return - if(get_dist(holder, friend) <= vision_range) // Within our sight. - ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO) - if(friend.ai_holder) // AI calling for help. - if(friend.ai_holder.target && can_attack(friend.ai_holder.target)) // Friend wants us to attack their target. - last_conflict_time = world.time // So we attack immediately and not threaten. - give_target(friend.ai_holder.target) // This will set us to the appropiate stance. - ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG) - return + var/their_target = friend?.ai_holder?.target + if(their_target) // They have a target and aren't just shouting for no reason + if(!can_attack(their_target, vision_required = FALSE)) + ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO) + return + if(get_dist(holder, friend) <= follow_distance) + ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO) + return + if(get_dist(holder, friend) <= vision_range) // Within our sight. + ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO) + last_conflict_time = world.time // So we attack immediately and not threaten. + give_target(their_target, urgent = TRUE) // This will set us to the appropiate stance. + ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG) + return // Otherwise they're outside our sight, lack a target, or aren't AI controlled, but within call range. // So assuming we're AI controlled, we'll go to them and see whats wrong. ai_log("help_requested() : Help requested by [friend], going to go to friend.", AI_LOG_INFO) + if(their_target) + add_attacker(their_target) // We won't wait and 'warn' them while they're stabbing our ally set_follow(friend, 10 SECONDS) ai_log("help_requested() : Exiting.", AI_LOG_DEBUG) diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm index e70e991d6e..40f2c5752f 100644 --- a/code/modules/ai/ai_holder_targeting.dm +++ b/code/modules/ai/ai_holder_targeting.dm @@ -26,6 +26,7 @@ // Step 1, find out what we can see. /datum/ai_holder/proc/list_targets() . = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder. + . -= dview_mob // Not the dview mob either, nerd. var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha)) @@ -35,6 +36,7 @@ // Step 2, filter down possible targets to things we actually care about. /datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE) + ai_log("find_target() : Entered.", AI_LOG_TRACE) if(!hostile) // So retaliating mobs only attack the thing that hit it. return null . = list() @@ -70,13 +72,17 @@ return chosen_target // Step 4, give us our selected target. -/datum/ai_holder/proc/give_target(new_target) +/datum/ai_holder/proc/give_target(new_target, urgent = FALSE) + ai_log("give_target() : Given '[new_target]', urgent=[urgent].", AI_LOG_TRACE) target = new_target + if(target != null) - if(should_threaten()) + lose_target_time = 0 + track_target_position() + if(should_threaten() && !urgent) set_stance(STANCE_ALERT) else - set_stance(STANCE_APPROACH) + set_stance(STANCE_FIGHT) last_target_time = world.time return TRUE @@ -109,8 +115,8 @@ sorted_targets += A return sorted_targets -/datum/ai_holder/proc/can_attack(atom/movable/the_target) - if(!can_see_target(the_target)) +/datum/ai_holder/proc/can_attack(atom/movable/the_target, vision_required = TRUE) + if(!can_see_target(the_target) && vision_required) return FALSE if(istype(the_target, /mob/zshadow)) @@ -157,21 +163,38 @@ /datum/ai_holder/proc/found(atom/movable/the_target) return FALSE -//We can't see the target, go look or attack where they were last seen. +// 'Soft' loss of target. They may still exist, we still have some info about them maybe. /datum/ai_holder/proc/lose_target() + ai_log("lose_target() : Entering.", AI_LOG_TRACE) if(target) + ai_log("lose_target() : Had a target, setting to null and LTT.", AI_LOG_DEBUG) target = null lose_target_time = world.time give_up_movement() + if(target_last_seen_turf && intelligence_level >= AI_SMART) + ai_log("lose_target() : Going into 'engage unseen enemy' mode.", AI_LOG_INFO) + engage_unseen_enemy() + return TRUE //We're still working on it + else + ai_log("lose_target() : Can't chase target, so giving up.", AI_LOG_INFO) + remove_target() + return find_target() //Returns if we found anything else to do -//Target is no longer valid (?) -/datum/ai_holder/proc/lost_target() - set_stance(STANCE_IDLE) + return FALSE //Nothing new to do + +// 'Hard' loss of target. Clean things up and return to idle. +/datum/ai_holder/proc/remove_target() + ai_log("remove_target() : Entering.", AI_LOG_TRACE) + if(target) + target = null + + lose_target_time = 0 + give_up_movement() lose_target_position() - lose_target() - + set_stance(STANCE_IDLE) + // Check if target is visible to us. /datum/ai_holder/proc/can_see_target(atom/movable/the_target, view_range = vision_range) ai_log("can_see_target() : Entering.", AI_LOG_TRACE) @@ -235,7 +258,7 @@ ai_log("react_to_attack() : Was attacked by [attacker], but we already have a target.", AI_LOG_TRACE) on_attacked(attacker) // So we attack immediately and not threaten. return FALSE - else if(attacker in attackers && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip + else if(check_attacker(attacker) && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip ai_log("react_to_attack() : Was attacked by [attacker]. Can retaliate, waited 3 seconds.", AI_LOG_INFO) on_attacked(attacker) // So we attack immediately and not threaten. return give_target(attacker) // Also handles setting the appropiate stance. @@ -246,16 +269,25 @@ ai_log("react_to_attack() : Was attacked by [attacker].", AI_LOG_INFO) on_attacked(attacker) // So we attack immediately and not threaten. - return give_target(attacker) // Also handles setting the appropiate stance. + return give_target(attacker, urgent = TRUE) // Also handles setting the appropiate stance. // Sets a few vars so mobs that threaten will react faster to an attacker or someone who attacked them before. /datum/ai_holder/proc/on_attacked(atom/movable/AM) - if(isliving(AM)) - var/mob/living/L = AM - if(!(L.name in attackers)) - attackers |= L.name - last_conflict_time = world.time + last_conflict_time = world.time + add_attacker(AM) +// Checks to see if an atom attacked us lately +/datum/ai_holder/proc/check_attacker(var/atom/movable/A) + return (A in attackers) + +// We were attacked by this thing recently +/datum/ai_holder/proc/add_attacker(var/atom/movable/A) + attackers |= A.name + +// Forgive this attacker +/datum/ai_holder/proc/remove_attacker(var/atom/movable/A) + attackers -= A.name + // Causes targeting to prefer targeting the taunter if possible. // This generally occurs if more than one option is within striking distance, including the taunter. // Otherwise the default filter will prefer the closest target. diff --git a/code/modules/xenobio/items/slimepotions.dm b/code/modules/xenobio/items/slimepotions.dm index 944853bec7..2a600fb801 100644 --- a/code/modules/xenobio/items/slimepotions.dm +++ b/code/modules/xenobio/items/slimepotions.dm @@ -109,7 +109,7 @@ to_chat(user, "You feed \the [SM] the agent, calming it.") playsound(src, 'sound/effects/bubbles.ogg', 50, 1) - AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. + AI.remove_target() // So hostile things stop attacking people even if not hostile anymore. var/newname = copytext(sanitize(input(user, "Would you like to give \the [M] a name?", "Name your new pet", M.name) as null|text),1,MAX_NAME_LEN) if(newname) @@ -202,7 +202,7 @@ to_chat(user, "You feed \the [M] the agent. It will now try to murder things that want to murder you instead.") to_chat(M, "\The [user] feeds you \the [src], and feel that the others will regard you as an outsider now.") M.faction = user.faction - AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. + AI.remove_target() // So hostile things stop attacking people even if not hostile anymore. playsound(src, 'sound/effects/bubbles.ogg', 50, 1) qdel(src) @@ -237,7 +237,7 @@ to_chat(user, "You feed \the [M] the agent. It will now be your best friend.") to_chat(M, "\The [user] feeds you \the [src], and feel that \the [user] wants to be best friends with you.") M.friends.Add(user) - AI.lost_target() // So hostile things stop attacking people even if not hostile anymore. + AI.remove_target() // So hostile things stop attacking people even if not hostile anymore. playsound(src, 'sound/effects/bubbles.ogg', 50, 1) qdel(src) diff --git a/polaris.dme b/polaris.dme index 3effb8a361..cc6030b775 100644 --- a/polaris.dme +++ b/polaris.dme @@ -1359,6 +1359,7 @@ #include "code\modules\ai\_defines.dm" #include "code\modules\ai\ai_holder.dm" #include "code\modules\ai\ai_holder_combat.dm" +#include "code\modules\ai\ai_holder_combat_unseen.dm" #include "code\modules\ai\ai_holder_communication.dm" #include "code\modules\ai\ai_holder_cooperation.dm" #include "code\modules\ai\ai_holder_debug.dm"