Begins work on new AI.

This commit is contained in:
Neerti
2018-02-17 09:40:22 -05:00
parent fdc367a4b5
commit abb3a5f602
14 changed files with 760 additions and 7 deletions

View File

@@ -25,7 +25,7 @@
#define BORGTHERM 0x2
#define BORGXRAY 0x4
#define BORGMATERIAL 8
/*
#define STANCE_IDLE 1 // Looking for targets if hostile. Does idle wandering.
#define STANCE_ALERT 2 // Bears
#define STANCE_ATTACK 3 // Attempting to get into attack position
@@ -33,6 +33,17 @@
#define STANCE_TIRED 5 // Bears
#define STANCE_FOLLOW 6 // Following somone
#define STANCE_BUSY 7 // Do nothing on life ticks (Other code is running)
*/
#define STANCE_SLEEP 0 // Doing (almost) nothing, to save on CPU because nobody is around to notice or the mob died.
#define STANCE_IDLE 1 // The more or less default state. Wanders around, looks for baddies, and spouts one-liners.
#define STANCE_ALERT 2 // A baddie is visible but not too close, and essentially we tell them to go away or die.
#define STANCE_ATTACK 3 // Attempting to get into range to attack them.
#define STANCE_ATTACKING 4 // Actually fighting, with melee or ranged.
#define STANCE_REPOSITION 5 // Relocating to a better position while in combat. Only used for ranged mobs since melee only has one better position, which STANCE_ATTACK already handles.
#define STANCE_MOVE 6 // Similar to above but for out of combat. If a baddie is seen, they'll cancel and fight them.
#define STANCE_FOLLOW 7 // Following somone, without trying to murder them.
#define STANCE_FLEE 8 // Run away from the target because they're too spooky/we're dying/some other reason.
#define STANCE_STUNNED 9 // Do nothing, because the mob is unable to act in some form. Can be applied by other disabling effects besides stuns.
#define LEFT 0x1
#define RIGHT 0x2

View File

@@ -0,0 +1,30 @@
SUBSYSTEM_DEF(ai)
name = "AI"
priority = 9
wait = 5 // This gets run twice a second, however this is technically two loops in one, with the second loop being run every four iterations.
flags = SS_NO_INIT|SS_TICKER
var/list/processing = list()
var/list/currentrun = list()
/datum/controller/subsystem/ai/stat_entry()
..("P:[processing.len]")
/datum/controller/subsystem/ai/fire(resumed = 0)
if (!resumed)
src.currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
// var/mob/living/L = currentrun[currentrun.len]
var/datum/ai_holder/A = currentrun[currentrun.len]
--currentrun.len
if(times_fired % 4 == 0 && A.holder.stat != DEAD)
A.handle_strategicals()
if(A.holder.stat != DEAD) // The /TG/ version checks stat twice, presumably in-case processing somehow got the mob killed in that instant.
A.handle_tactics()
if(MC_TICK_CHECK)
return

View File

@@ -47,6 +47,7 @@
var/check_synth = 0 //if active, will shoot at anything not an AI or cyborg
var/check_all = 0 //If active, will fire on anything, including synthetics.
var/ailock = 0 // AI cannot use this
var/faction = null //if set, will not fire at people in the same faction for any reason.
var/attacked = 0 //if set to 1, the turret gets pissed off and shoots at people nearby (unless they have sec access!)
@@ -80,6 +81,11 @@
lethal = 1
installation = /obj/item/weapon/gun/energy/laser
/obj/machinery/porta_turret/stationary/syndie // Generic turrets for POIs that need to not shoot their buddies.
enabled = TRUE
check_all = TRUE
faction = "syndicate" // Make sure this equals the faction that the mobs in the POI have or they will fight each other.
/obj/machinery/porta_turret/ai_defense
name = "defense turret"
desc = "This variant appears to be much more durable."
@@ -533,6 +539,9 @@ var/list/turret_icons
if(!L)
return TURRET_NOT_TARGET
if(faction && L.faction == faction)
return TURRET_NOT_TARGET
if(!emagged && issilicon(L) && check_all == 0) // Don't target silica, unless told to neutralize everything.
return TURRET_NOT_TARGET

View File

@@ -0,0 +1,3 @@
#define AI_DUMB 1
#define AI_NORMAL 2
#define AI_SMART 3

View File

@@ -0,0 +1,186 @@
// This is a datum-based artificial intelligence for simple mobs (and possibly others) to use.
// The neat thing with having this here instead of on the mob is that it is independant of Life(), and that different mobs
// can use a more or less complex AI by giving it a different datum.
/mob/living
var/datum/ai_holder/ai_holder = null
var/ai_holder_type = null // Which ai_holder datum to give to the mob when initialized. If null, nothing happens.
/mob/living/initialize()
if(ai_holder_type)
ai_holder = new ai_holder_type(src)
return ..()
/mob/living/Destroy()
qdel_null(ai_holder)
return ..()
/datum/ai_holder
var/mob/living/holder = null // The mob this datum is going to control.
var/stance = STANCE_IDLE // Determines if the mob should be doing a specific thing, e.g. attacking, following, standing around, etc.
var/intelligence_level = AI_NORMAL
/datum/ai_holder/hostile
hostile = TRUE
/datum/ai_holder/retaliate
hostile = TRUE
retaliate = TRUE
/datum/ai_holder/test
hostile = TRUE
use_astar = TRUE
/datum/ai_holder/New(var/new_holder)
ASSERT(new_holder)
holder = new_holder
SSai.processing += src
home_turf = get_turf(holder)
..()
/datum/ai_holder/Destroy()
holder = null
SSai.processing -= src // We might've already been asleep and removed, but byond won't care if we do this again and it saves a conditional.
home_turf = null
return ..()
// Now for the actual AI stuff.
/datum/ai_holder/proc/go_sleep()
if(stance == STANCE_SLEEP)
return
stance = STANCE_SLEEP
SSai.processing -= src
/datum/ai_holder/proc/go_wake()
if(stance != STANCE_SLEEP)
return
stance = STANCE_IDLE
SSai.processing += src
// '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()
handle_stance_tactical()
// 'Strategical' processes that are more expensive on the CPU and so don't get run as often as the above proc, such as A* pathfinding or robust targeting.
/datum/ai_holder/proc/handle_strategicals()
world << "[holder.name] Strategicals!"
handle_stance_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)
stance = new_stance
/datum/ai_holder/proc/handle_stance_tactical(var/new_stance)
if(new_stance)
set_stance(new_stance)
if(attack_cooldown_left > 0)
attack_cooldown_left--
switch(stance)
if(STANCE_SLEEP)
go_sleep()
return
if(STANCE_IDLE)
holder.a_intent = I_HELP
// if(hostile)
// find_target()
if(STANCE_ALERT)
threaten_target()
if(STANCE_ATTACK)
if(target)
walk_to_target()
if(STANCE_ATTACKING)
engage_target()
/datum/ai_holder/proc/handle_stance_strategical()
switch(stance)
if(STANCE_IDLE)
if(hostile)
find_target()
if(STANCE_ATTACK)
if(target)
calculate_path(target)
/*
// For proccessing the current stance, or setting and processing a new one
/mob/living/simple_animal/proc/handle_stance(var/new_stance)
if(ai_inactive)
stance = STANCE_IDLE
return
if(new_stance)
set_stance(new_stance)
switch(stance)
if(STANCE_IDLE)
target_mob = null
a_intent = I_HELP
annoyed = max(0,annoyed--)
//Yes I'm breaking this into two if()'s for ease of reading
//If we ARE ALLOWED TO
if(returns_home && home_turf && !astarpathing && (world.time - stance_changed) > 10 SECONDS)
if(get_dist(src,home_turf) > wander_distance)
move_to_delay = initial(move_to_delay)*2 //Walk back.
GoHome()
else
stop_automated_movement = 0
//Search for targets while idle
if(hostile)
FindTarget()
if(STANCE_FOLLOW)
annoyed = 15
FollowTarget()
if(follow_until_time && world.time > follow_until_time)
LoseFollow()
return
if(hostile)
FindTarget()
if(STANCE_ATTACK)
annoyed = 50
a_intent = I_HURT
RequestHelp()
MoveToTarget()
if(STANCE_ATTACKING)
annoyed = 50
AttackTarget()
*/

View File

@@ -0,0 +1,211 @@
/datum/ai_holder
var/hostile = FALSE // Do we try to hurt others? Setting to false disables most combat processing.
var/retaliate = FALSE // Requires hostile to be true to work. If both this and hostile are true, the mob won't attack unless attacked first.
var/cooperative = FALSE // If true, asks allies to help when fighting something.
var/firing_lanes = FALSE // If ture, tries to refrain from shooting allies or the wall.
var/conserve_ammo = FALSE // If true, the mob will avoid shooting anything that does not have a chance to hit a mob. Requires firing_lanes to be true.
var/atom/movable/target // The thing (mob or object) we're trying to kill.
var/attack_cooldown = 2 // If set, the mob will wait for the specified amount of ticks before attempting another attack.
var/attack_cooldown_left = 0 // Actual var for tracking if attacks are off cooldown or not. Note that melee and ranged attacks share this.
var/ranged = FALSE // If true, attempts to shoot at the enemy instead of charging at them wildly.
var/shoot_range = 5 // How close the mob needs to be to attempt to shoot at the enemy.
var/pointblank = FALSE // If ranged is true, and this is true, people adjacent to the mob will suffer the ranged instead of using a melee attack.
var/vision_range = 7 // How far the targeting system will look for things to kill. Note that values higher than 7 are 'offscreen' and might be unsporting.
/**************
* Strategical *
**************/
// Step 1, find out what we can see.
/datum/ai_holder/proc/list_targets()
. = hearers(vision_range, holder) - src // Remove ourselves to prevent suicidal decisions.
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
for(var/HM in typecache_filter_list(range(vision_range, holder), hostile_machines))
if(can_see(holder, HM, vision_range))
. += HM
// 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)
. = list()
if(!has_targets_list)
possible_targets = list_targets()
for(var/possible_target in possible_targets)
var/atom/A = possible_target
// if(Found(A)) // In case people want to override this.
// . = list(A)
// break
if(can_attack(A)) // Can we attack it?
. += A
continue
var/new_target = pick_target(.)
give_target(new_target)
return new_target
// Step 3, pick among the possible, attackable targets.
/datum/ai_holder/proc/pick_target(list/targets)
if(target != null) // If we already have a target, but are told to pick again, calculate the lowest distance between all possible, and pick from the lowest distance targets.
for(var/possible_target in targets)
var/atom/A = possible_target
var/target_dist = get_dist(holder, target)
var/possible_target_distance = get_dist(holder, A)
if(target_dist < possible_target_distance)
targets -= A
if(!targets.len) // We found nothing.
return
var/chosen_target = pick(targets)
return chosen_target
// Step 4, give us our selected target.
/datum/ai_holder/proc/give_target(new_target)
target = new_target
//LosePatience()
if(target != null)
//GainPatience()
//Aggro()
if(should_threaten())
set_stance(STANCE_ALERT)
else
set_stance(STANCE_ATTACK)
return TRUE
/datum/ai_holder/proc/can_attack(atom/movable/the_target)
if(!the_target) // Nothing to attack.
return FALSE
if(holder.see_invisible < the_target.invisibility) // Invisible, can't see it, oh well.
return FALSE
if(isliving(the_target))
var/mob/living/L = the_target
if(L.stat)
return FALSE
if(holder.IIsAlly(L))
return FALSE
return TRUE
if(istype(the_target, /obj/mecha))
var/obj/mecha/M = the_target
if(M.occupant)
return can_attack(M.occupant)
if(istype(the_target, /obj/machinery/porta_turret))
var/obj/machinery/porta_turret/P = the_target
if(P.stat & BROKEN)
return FALSE // Already dead.
if(P.faction == holder.faction)
return FALSE // Don't shoot allied turrets.
if(!P.raised && !P.raising)
return FALSE // Turrets won't get hurt if they're still in their cover.
return TRUE
return FALSE
/***********
* Tactical *
***********/
// This does the actual attacking.
/datum/ai_holder/proc/engage_target()
if(!target || !can_attack(target) || (!(target in list_targets())) )
lose_target()
return
var/distance = get_dist(holder, target)
holder.face_atom(target)
last_conflict_time = world.time
// Stab them.
if(distance <= 1 && !pointblank)
on_engagement(target)
if(attack_cooldown_left <= 0)
pre_melee_attack(target)
if(melee_attack(target))
post_melee_attack(target)
if(attack_cooldown)
attack_cooldown_left = attack_cooldown
// Shoot them.
else if(ranged && (distance <= shoot_range) )
on_engagement(target)
if(attack_cooldown_left <= 0)
if(firing_lanes && !test_projectile_safety(target))
// Nudge them a bit, maybe they can shoot next time.
step_rand(holder)
holder.face_atom(target)
return
pre_ranged_attack(target)
if(ranged_attack(target))
post_ranged_attack(target)
if(attack_cooldown)
attack_cooldown_left = attack_cooldown
// Run after them.
else
set_stance(STANCE_ATTACK)
// We're not entirely sure how holder will do melee attacks since any /mob/living could be holder, but we don't have to care because Interfaces.
/datum/ai_holder/proc/melee_attack(atom/movable/AM)
return holder.IAttack(AM)
// Ditto.
/datum/ai_holder/proc/ranged_attack(atom/movable/AM)
return holder.IRangedAttack(AM)
// Called when within striking distance, however cooldown is not considered.
/datum/ai_holder/proc/on_engagement(atom/movable/AM)
// These two are called before an attack attempt.
/datum/ai_holder/proc/pre_melee_attack(atom/movable/AM)
/datum/ai_holder/proc/pre_ranged_attack(atom/movable/AM)
// These two are called after a successful(ish) attack.
/datum/ai_holder/proc/post_melee_attack(atom/movable/AM)
/datum/ai_holder/proc/post_ranged_attack(atom/movable/AM)
// Used to make sure projectiles will probably hit the target and not the wall or a friend.
/datum/ai_holder/proc/test_projectile_safety(atom/movable/AM)
var/mob/living/L = check_trajectory(AM, holder) // This isn't always reliable but its better than the previous method.
// world << "Checked trajectory, would hit [L]."
if(istype(L)) // Did we hit a mob?
// world << "Hit [L]."
if(holder.IIsAlly(L))
// world << "Would hit ally, canceling."
return FALSE // We would hit a friend!
// world << "Won't threaten ally, firing."
return TRUE // Otherwise we don't care, even if its not the intended target.
else
if(!isliving(AM)) // If the original target was an object, then let it happen if it doesn't threaten an ally.
// world << "Targeting object, ignoring and firing."
return TRUE
// world << "Not sure."
return !conserve_ammo // If we have infinite ammo than shooting the wall isn't so bad, but otherwise lets not.
//We can't see the target
/datum/ai_holder/proc/lose_target()
target = null
set_stance(STANCE_IDLE)
give_up_movement()
//Target is no longer valid (?)
/datum/ai_holder/proc/lost_target()
set_stance(STANCE_IDLE)
give_up_movement()
// Can be used to conditionally do a ranged or melee attack.
/datum/ai_holder/proc/closest_distance()
return ranged ? shoot_range - 1 : 1 // Shoot range -1 just because we don't want to constantly get kited

View File

@@ -0,0 +1,59 @@
/datum/ai_holder
var/threaten = FALSE // If hostile and sees a valid target, gives a 'warning' to the target before beginning the attack.
var/threatened = FALSE // If the mob actually gave the warning, checked so it doesn't constantly yell every tick.
var/threaten_delay = 3 SECONDS // How long a 'threat' lasts, until actual fighting starts. If null, the mob never starts the fight but still does the threat.
var/threaten_timeout = 1 MINUTE // If the mob threatens someone, they leave, and then come back before this timeout period, the mob escalates to fighting immediately.
var/last_conflict_time = null // Last occurance of threatening or fighting being used, in world.time.
var/threaten_sound = null // Sound file played when the mob calls threaten_target() for the first time.
var/stand_down_sound = null // Sound file played when the mob loses sight of the threatened target.
/datum/ai_holder/proc/should_threaten()
if(!threaten)
return FALSE // We don't negotiate.
if(!will_threaten(target))
return FALSE // Pointless to threaten an animal, a mindless drone, or an object.
if(!(stance in list(STANCE_IDLE, STANCE_MOVE, STANCE_FOLLOW)))
return FALSE // We're probably already fighting or recently fought if not in these stances.
if(last_conflict_time && threaten_delay && last_conflict_time + threaten_timeout > world.time)
return FALSE // We threatened someone recently, so lets show them we mean business.
return TRUE // Lets give them a chance to choose wisely and walk away.
/datum/ai_holder/proc/threaten_target()
holder.face_atom(target) // Constantly face the target.
if(!threatened) // First tick.
threatened = TRUE
last_conflict_time = world.time
//TODO: Actual speech.
holder.say("Oi, fuck off mate.")
playsound(holder.loc, threaten_sound, 75, 1) // We do this twice to make the sound -very- noticable to the target.
playsound(target.loc, threaten_sound, 75, 1) // Actual aim-mode also does that so at least it's consistant.
else // Otherwise we are waiting for them to go away or to wait long enough for escalate.
if(target in list_targets()) // Are they still visible?
if(threaten_delay && last_conflict_time + threaten_delay < world.time) // Waited too long.
threatened = FALSE
set_stance(STANCE_ATTACK)
holder.say("Fine, now you die!") //WIP
else
return // Wait a bit.
else // They left, or so we think.
threatened = FALSE
set_stance(STANCE_IDLE)
holder.say("Good riddence.") //WIP
playsound(holder.loc, stand_down_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target.
playsound(target.loc, stand_down_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant.
// Determines what is deserving of a warning when STANCE_ALERT is active.
/datum/ai_holder/proc/will_threaten(mob/living/the_target)
if(!isliving(the_target))
return FALSE // Turrets don't give a fuck so neither will we.
if(istype(the_target, /mob/living/simple_animal) && istype(holder, /mob/living/simple_animal))
var/mob/living/simple_animal/us = holder
var/mob/living/simple_animal/them = target
if(them.intelligence_level < us.intelligence_level) // Todo: Bitflag these.
return FALSE // Humanoids don't care about drones/animals/etc. Drones don't care about animals, and so on.
return TRUE

View File

@@ -0,0 +1,39 @@
/datum/ai_holder
var/path_display = FALSE // Displays a visual path when A* is being used.
var/path_icon = 'icons/misc/debug_group.dmi' // What icon to use for the overlay
var/path_icon_state = "red" // What state to use for the overlay
var/image/path_overlay // A reference to the overlay
/datum/ai_holder/New()
..()
path_overlay = new(path_icon,path_icon_state)
/datum/ai_holder/Destroy()
path_overlay = null
return ..()
// Remove this when finished.
/mob/living/simple_animal/corgi
ai_holder_type = /datum/ai_holder/test
/datum/ai_holder/hostile/ranged
ranged = TRUE
cooperative = TRUE
firing_lanes = TRUE
conserve_ammo = TRUE
threaten = TRUE
threaten_sound = 'sound/weapons/TargetOn.ogg'
stand_down_sound = 'sound/weapons/TargetOff.ogg'
/mob/living/simple_animal/hostile/pirate
hostile = FALSE
ai_inactive = TRUE
ai_holder_type = /datum/ai_holder/hostile/ranged
/datum/ai_holder/hostile/ranged/robust/on_engagement(atom/movable/AM)
step_rand(holder)
holder.face_atom(AM)

View File

@@ -0,0 +1,159 @@
/datum/ai_holder
var/use_astar = FALSE // Do we use the more expensive A* implementation or stick with BYOND's default step_to()?
var/using_astar = FALSE // Are we currently using an A* path?
var/list/path = list() // A list of tiles that A* gave us as a solution to reach the target.
var/list/obstacles = list() // Things A* will try to avoid.
var/astar_adjacent_proc = /turf/proc/CardinalTurfsWithAccess // Proc to use when A* pathfinding. Default makes them bound to cardinals.
var/failed_steps = 0 // If move_once() fails to move the mob onto the correct tile, this increases. When it reaches 3, the path is recalc'd since they're probably stuck.
var/turf/home_turf = null // The mob's 'home' turf. It will try to stay near it if told to do so.
var/return_home = FALSE // If true, makes the mob go to its 'home' if it strays too far.
/**************
* Strategical *
**************/
//Giving up on moving
/datum/ai_holder/proc/give_up_movement()
// ai_log("GiveUpMoving()",1)
forget_path()
// stop_automated_movement = 0
//Forget the path entirely
/datum/ai_holder/proc/forget_path()
// ai_log("ForgetPath()",2)
if(path_display)
for(var/turf/T in path)
T.overlays -= path_overlay
using_astar = FALSE
// walk_list.Cut()
path.Cut()
/datum/ai_holder/proc/calculate_path(atom/A, get_to = 1)
if(!A)
return
if(!use_astar) // If we don't use A* then this is pointless.
return
get_path(get_turf(A), get_to)
//A* now, try to a path to a target
/datum/ai_holder/proc/get_path(var/turf/target,var/get_to = 1, var/max_distance = world.view*6)
// ai_log("GetPath([target],[get_to],[max_distance])",2)
forget_path()
var/list/new_path = AStar(get_turf(holder.loc), target, astar_adjacent_proc, /turf/proc/Distance, min_target_dist = get_to, max_node_depth = max_distance, id = holder.IGetID(), exclude = obstacles)
if(new_path && new_path.len)
path = new_path
if(path_display)
for(var/turf/T in path)
T.overlays |= path_overlay
else
return 0
return path.len
/*
/datum/ai_holder/proc/walk_to_target()
//If we were chasing someone and we can't anymore, give up.
if(!target_mob)
// ai_log("MoveToTarget() Losing target at top",2)
lose_target()
return
//We recompute our path every time we're called if we can still see them
if(target in list_targets(vision_range))
if(using_astar)
forget_path()
// Find out where we're going.
var/get_to = 1 // TODO
var/distance = get_dist(holder, target)
//We're here!
if(distance <= get_to)
// ai_log("MoveToTarget() [src] attack range",2)
set_stance(STANCE_ATTACKING)
return
//We're just setting out, making a new path, or we can't path with A*
if(!path.len)
// ai_log("SA: MoveToTarget() pathing to [target_mob]",2)
//GetPath failed for whatever reason, just smash into things towards them
if(run_at_them || !GetPath(get_turf(target_mob),get_to))
//We try the built-in way to stay close
walk_to(src, target_mob, get_to, move_to_delay)
// ai_log("MoveToTarget() walk_to([src],[target_mob],[get_to],[move_to_delay])",3)
//Break shit in their direction! LEME SMAHSH
var/dir_to_mob = get_dir(src,target_mob)
face_atom(target_mob)
// DestroySurroundings(dir_to_mob)
// ai_log("MoveToTarget() DestroySurroundings([get_dir(src,target_mob)])",3)
*/
/***********
* Tactical *
***********/
// Goes to the target, to attack them.
// Called when in STANCE_ATTACK.
/datum/ai_holder/proc/walk_to_target()
// Make sure we can still chase/attack them.
if(!target || !can_attack(target))
lose_target()
return
// Find out where we're going.
var/get_to = closest_distance()
var/distance = get_dist(holder, target)
// We're here!
if(distance <= get_to)
// ai_log("MoveToTarget() [src] attack range",2)
forget_path()
set_stance(STANCE_ATTACKING)
return
// Otherwise keep walking.
walk_path(target, get_to)
// Walk towards whatever.
/datum/ai_holder/proc/walk_path(atom/A, get_to = 1)
if(use_astar)
if(!path.len) // If we're missing a path, make a new one.
calculate_path(A, get_to)
if(!path.len) // If we still don't have one, then the target's probably somewhere inaccessible to us.
return
if(!move_once()) // Start walking the path.
++failed_steps
if(failed_steps > 3) // We're probably stuck.
forget_path() // So lets try again with a new path.
failed_steps = 0
else
step_to(holder, A)
//Take one step along a path
/datum/ai_holder/proc/move_once()
if(!path.len)
return
if(path_display)
var/turf/T = src.path[1]
T.overlays -= path_overlay
step_towards(holder, src.path[1])
if(holder.loc != src.path[1])
// ai_log("MoveOnce() step_towards returning 0",3)
return FALSE
else
path -= src.path[1]
// ai_log("MoveOnce() step_towards returning 1",3)
return TRUE

View File

@@ -0,0 +1,32 @@
// 'Interfaces' are procs that the ai_holder datum uses to communicate its will to the mob its attached.
// The reason for using this proc in the middle is to ensure the AI has some form of compatibility with most mob types,
// since some actions work very differently between mob types (e.g. executing an attack as a simple animal compared to a human).
// The AI can just call holder.IAttack(target) and the mob is responsible for determining how to actually attack the target.
/mob/living/proc/IAttack(atom/movable/AM)
/mob/living/simple_animal/IAttack(atom/movable/AM)
target_mob = AM
return PunchTarget(AM) // TODO: Clean up on SA side.
/mob/living/proc/IRangedAttack(atom/movable/AM)
/mob/living/simple_animal/IRangedAttack(atom/movable/AM)
target_mob = AM
return ShootTarget(AM, src.loc, src)
/mob/living/proc/ISay(message)
/mob/living/proc/IIsAlly(mob/living/L)
return src.faction == L.faction
/mob/living/simple_animal/IIsAlly(mob/living/L)
. = ..()
if(!.) // Outside the faction, try to see if they're friends.
return L in friends
/mob/living/proc/IGetID()
/mob/living/simple_animal/IGetID()
if(myid)
return myid.GetID()

View File

@@ -470,7 +470,8 @@
return 2
//just assume we can shoot through glass and stuff. No big deal, the player can just choose to not target someone
//on the other side of a window if it makes a difference. Or if they run behind a window, too bad.
return check_trajectory(target, user)
if(check_trajectory(target, user))
return 1 // Magic numbers are fun.
//called if there was no projectile to shoot
/obj/item/weapon/gun/proc/handle_click_empty(mob/user)

View File

@@ -416,6 +416,7 @@
yo = null
xo = null
var/result = 0 //To pass the message back to the gun.
var/atom/movable/result_ref = null // The thing that got hit that made the check return true.
/obj/item/projectile/test/Bump(atom/A as mob|obj|turf|area)
if(A == firer)
@@ -426,6 +427,7 @@
if(istype(A, /obj/structure/foamedmetal)) //Turrets can detect through foamed metal, but will have to blast through it. Similar to windows, if someone runs behind it, a person should probably just not shoot.
return
if(istype(A, /mob/living) || istype(A, /obj/mecha) || istype(A, /obj/vehicle))
result_ref = A
result = 2 //We hit someone, return 1!
return
result = 1
@@ -446,7 +448,8 @@
/obj/item/projectile/test/process(var/turf/targloc)
while(src) //Loop on through!
if(result)
return (result - 1)
return result_ref
// return (result - 1)
if((!( targloc ) || loc == targloc))
targloc = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) //Finding the target turf at map edge
@@ -457,11 +460,13 @@
var/mob/living/M = locate() in get_turf(src)
if(istype(M)) //If there is someting living...
return 1 //Return 1
result_ref = M
return result_ref //Return 1
else
M = locate() in get_step(src,targloc)
if(istype(M))
return 1
result_ref = M
return result_ref
//Helper proc to check if you can hit them or not.
/proc/check_trajectory(atom/target as mob|obj, atom/firer as mob|obj, var/pass_flags=PASSTABLE|PASSGLASS|PASSGRILLE, flags=null)

View File

@@ -171,6 +171,7 @@
#include "code\controllers\Processes\vote.dm"
#include "code\controllers\ProcessScheduler\core\process.dm"
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
#include "code\controllers\subsystems\ai.dm"
#include "code\controllers\subsystems\air.dm"
#include "code\controllers\subsystems\airflow.dm"
#include "code\controllers\subsystems\atoms.dm"
@@ -1230,6 +1231,13 @@
#include "code\modules\admin\view_variables\helpers.dm"
#include "code\modules\admin\view_variables\topic.dm"
#include "code\modules\admin\view_variables\view_variables.dm"
#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_communication.dm"
#include "code\modules\ai\ai_holder_debug.dm"
#include "code\modules\ai\ai_holder_movement.dm"
#include "code\modules\ai\interfaces.dm"
#include "code\modules\alarm\alarm.dm"
#include "code\modules\alarm\alarm_handler.dm"
#include "code\modules\alarm\atmosphere_alarm.dm"
@@ -2404,7 +2412,7 @@
#include "code\ZAS\Zone.dm"
#include "interface\interface.dm"
#include "interface\skin.dmf"
#include "maps\southern_cross\southern_cross.dm"
#include "maps\example\example.dm"
#include "maps\submaps\_readme.dm"
#include "maps\submaps\space_submaps\space.dm"
#include "maps\submaps\surface_submaps\mountains\mountains.dm"