ID:1447399
 
(See the best response by Ter13.)
Code:
        /*******************/
/**SPAWN COMBAT AI**/
/*******************/
New(/**/)
..(/**/)
while(src)
sleep(rand(15,18))
var/mob/target
src.stable=1
target=locate(/mob/player) in oview(7,src)
while(target && !src.aggressive && !src.blastable)
sleep(rand(15,18))
step(src,pick(NORTH,SOUTH,EAST,WEST))

while(target && src.aggressive && !src.blastable)
sleep(rand(10,12))
if(get_dist(src,target)<=1)
var/next_dir = get_dir(src,target)
switch(next_dir)
if(NORTHEAST){step(src,NORTH);sleep(2);step(src,EAST)}
if(NORTHWEST){step(src,NORTH);sleep(2);step(src,WEST)}
if(SOUTHEAST){step(src,SOUTH);sleep(2);step(src,EAST)}
if(SOUTHWEST){step(src,SOUTH);sleep(2);step(src,WEST)}
src.dir=get_dir(src,target)
if(prob(src.throwchanse))
src.AIThrow(target)
else if(src.dir==get_dir(src,target))
src.AIAttack(/**/)
else
step_towards(src,target)

while(target && get_dist(src,target)<=7 && !src.aggressive && src.blastable)
sleep(rand(10,18))
if(get_dist(src,target)>1)
var/next_dir = get_dir(src,target)
switch(next_dir)
if(NORTHEAST){step(src,NORTH);sleep(2);step(src,EAST)}
if(NORTHWEST){step(src,NORTH);sleep(2);step(src,WEST)}
if(SOUTHEAST){step(src,SOUTH);sleep(2);step(src,EAST)}
if(SOUTHWEST){step(src,SOUTH);sleep(2);step(src,WEST)}
if(prob(src.newspecialmovechanse2))
src.MonsterZanzoke(/**/)
src.x = target.x+1
src.y = target.y
src.z = target.z

if(src.dir==get_dir(src,target))
if(prob(src.newspecialmovechanse1))
src.SpecialMove(/**/)
else
src.AIBlast(/**/)
else
step_towards(src,target)
else
if(get_dist(src,target)<=1)
var/next_dir = get_dir(src,target)
switch(next_dir)
if(NORTHEAST){step(src,NORTH);sleep(2);step(src,EAST)}
if(NORTHWEST){step(src,NORTH);sleep(2);step(src,WEST)}
if(SOUTHEAST){step(src,SOUTH);sleep(2);step(src,EAST)}
if(SOUTHWEST){step(src,SOUTH);sleep(2);step(src,WEST)}
src.dir=get_dir(src,target)
if(prob(src.throwchanse))
src.AIThrow(target)
else if(src.dir==get_dir(src,target))
src.AIAttack(/**/)
else
step_towards(src,target)


Problem description:

Hello there... I`ve been trying to create ai system for my game and I could really need some advice:

- How to reduce this game
- Is it even ok to make it like that
- How to detect while no target so they would go back to spawn ( allready have spawn system)

Best response
I wrote an entire AI solution for you, but you deleted your post and it got eaten...

Okay, here's how I would handle AI if I weren't using my own insanely complicated techniques to keep CPU occupancy down to a minimum:

In this example, we actually define a number of states that the AI can enter based upon the position of its target.

In our case, we are using the following states:

Chase
Attack
Reset

In the Chase state, the mob attempts to get within range of the player.

In the Attack state, the mob ensures it is within the correct range of the player, and attempts to attack until that mob is dead.

In the reset state, it tries to make it home, and after a certain period of time, if it's not back home, it gives up on trying and just teleports back to home.

The reason I've split out the AI like this, is so that you can change AI behavior later easily. Your approach will not allow you to make changes to how things operate at all, so your AI will all act the same. Plus, my approach doesn't make any conditional checks that aren't necessary.

On top of that, players trigger AI aggro, rather than all monsters in the world looking for players all the time. It might be in your interests to turn it off in safezones, for instance, so players in towns don't trigger AI aggro, and it saves you some CPU.

#define MAX_AGGRO_RANGE 7

mob
combatant
player
Move(turf/NewLoc,Dir=0,step_x=0,step_y=0)
var
turf/OldLoc = src.loc
oDir = src.dir
osx = step_x
osy = step_y
. = ..(NewLoc,Dir,step_x,step_y)
if(.)
//if the move was successful, call Moved()
Moved(OldLoc,oDir,osx,osy)

proc
Moved(turf/OldLoc,oDir=0,osx,osy)
for(var/mob/combatant/ai/ai in range(MAX_AGGRO_RANGE,src))
ai.foundTarget(src)
ai
var
aggro_dist = 7
mob/combatant/target
keep_dist = 1
chase_speed = 1

turf/aggro_loc
turf/home_loc

next_attack = 0

//helper functions
proc
foundTarget(var/mob/combatant/c)
if(!src.target)
src.target = c
aggro_loc = src.loc
src.chaseState()

lostTarget()
var
rng = range(aggro_loc,aggro_dist)
mob/combatant/trg
mdist = aggro_dist+1
d

//search for combatants within range
for(var/mob/combatant/c in rng)
d = get_dist(src,c)
if(d<mdist||(d==mdist&&rand(1)))
mdist = d
trg = c

//if we found anything, chase, if not, reset
if(trg)
src.target = trg
chaseState()
else
resetState()

attack()
//You can put whatever you want in here.
//just make sure you set next_attack when you have made an attack.

//state functions
proc
chaseState()
set waitfor=0
var/d = get_dist(src,target)
while(d>keep_dist)
//if the target is out of range of dead, bail out.
if(get_dist(aggro_loc,src)>aggro_dist||src.target.hp<=0)
src.lostTarget()
return 0
//if the path is blocked, take a random step
. = step(src,get_dir(src,target))
if(!.)
step_rand(src)
sleep(chase_speed)
d = get_dist(src,target)
attackState()
return 1

attackState()
set waitfor=0
var/d
while(src.target.hp>0)
d = get_dist(src,target)

//if the target is too far away, chase
if(d>src.keep_dist)
chaseState()
return

//if the target is too close, avoid
if(d<src.keep_dist)
//if the path is blocked, take a random step
. = step_away(src,target)
if(!.)
step_rand(src)

//if we are eligible to attack, do it.
if(world.time>=next_attack)
attack()
sleep(chase_speed)

//when the loop is done, we've lost the target
src.lostTarget()

resetState()
set waitfor=0
var
//allow us longer than it should take to get home via distance
returntime = world.time + get_dist(src,home_loc) * (3 + chase_speed)
while(world.time<returntime&&src.loc!=home_loc)
//if the path is blocked, take a random step
. = step(src,get_dir(src,home_loc))
if(!.)
step_rand(src)
sleep(chase_speed)

//teleport if we can't get home
if(src.loc!=home_loc)
src.loc = home_loc
src.target = null
src.aggro_loc = null

Move(turf/NewLoc,Dir=0,step_x=0,step_y=0)
//prevent diagonal movement
var/d = Dir & Dir-1
if(d)
Dir = rand(1) ? d : Dir^d
NewLoc = get_step(src,Dir)
. = ..(NewLoc,Dir,step_x,step_y)

New()
. = ..()
src.home_loc = src.loc
In case you read my last post prior to my edit, I've updated this (pager goes bing!) with a rudimentary AI approach so you can see how an AI state machine SHOULD be written for a decent game.

There is a LOT more you can do. This example doesn't include pathfinding, because BYOND's built-in pathfinding is inadequate and will eat up your CPU like nothing else. Writing your own highly efficient pathfinding solution is beyond the scope of what I want to cover here in this forum. I've done it, and it's brutal.

Anyway, I'll be gone for six or so hours, so if any of the other regular helpers want to take a crack at whatever problems/questions you are inevitably going to have with this snippet, I'll let them. (I'm looking at you, Kaio/Lord Andrew)

I've got enrollment to do for college (starting a bit late... Being pretty close to my 30s and a freshman in college, right?), so I'll be back later tonight to check up on your progress.
Moved() was undefined proc :

mob
player
proc/Moved(turf/OldLoc,oDir=0,osx,osy)
for(var/mob/monsters/ai in range(MAX_AGGRO_RANGE,src))
ai.foundTarget(src)
Move(turf/NewLoc,Dir=0,step_x=0,step_y=0)
var
turf/OldLoc = src.loc
oDir = src.dir
osx = step_x
osy = step_y
. = ..(NewLoc,Dir,step_x,step_y)
if(.)
//if the move was successful, call Moved()
Moved(OldLoc,oDir,osx,osy)

No other problems that I found in it. And my CPU is now 0. Ty
#define MAX_AGGRO_RANGE 15


mob
monsters
var
aggro_dist = 7
mob/player/target
keep_dist = 1
chase_speed = 7


turf/aggro_loc
turf/home_loc

next_attack = 0

proc
/******************/
/** FOUND TARGET **/
/******************/
foundTarget(var/mob/player/c)
if(!src.target)
src.target = c
aggro_loc = src.loc
src.lostTarget(/**/)
/*****************/
/** LOST TARGET **/
/*****************/
lostTarget(/**/)
var
rng = range(aggro_loc,aggro_dist)
mob/player/trg
mdist = aggro_dist+1
d

//search for combatants within range
for(var/mob/player/c in rng)
d = get_dist(src,c)
if(d<mdist||(d==mdist&&rand(1)))
mdist = d
trg = c

//if we found anything, chase, if not, reset
if(trg)
// seek for type
if(!src.aggressive && !src.blastable)
src.target = trg
src.wanderState(/**/)
if(src.aggressive && !src.blastable)
src.target = trg
src.chaseState(/**/)
if(src.blastable)
src.target = trg
src.chaseblastState(/**/)

if(!trg)
src.target=null
src.loc = home_loc
proc
/******************/
/** WANDER STATE **/
/******************/
wanderState(/**/)
set waitfor=0
while(target in oview(src))
//if the target become aggressive
if(src.aggressive)
src.lostTarget(/**/)
return 0
// else walk random
step_rand(src)
sleep(rand(15,18))
src.lostTarget(/**/)
return 1
/*****************/
/** CHASE STATE **/
/*****************/
chaseState(/**/)
set waitfor=0
var/d = get_dist(src,target)
while(d>keep_dist)
//if the target is out of range of dead, bail out.
if(get_dist(aggro_loc,src)>aggro_dist||src.target.powerlevel<=0)
src.lostTarget(/**/)
return 0
//if the path is blocked, take a random step
. = step(src,get_dir(src,target))
if(!.)
step_rand(src)
sleep(chase_speed)
d = get_dist(src,target)
src.attackState(/**/)
return 1
/******************/
/** ATTACK STATE **/
/******************/
attackState(/**/)
set waitfor=0
var/d
while(src.target.powerlevel>0)
d = get_dist(src,target)

//if the target is too far away, chase
if(d>src.keep_dist)
if(!src.blastable)
src.chaseState(/**/)
else
src.chaseblastState(/**/)
return


//if the target is too close, avoid
if(d<src.keep_dist)
//if the path is blocked, take a random step
. = step_away(src,target)
if(!.)
step_rand(src)

//if we are eligible to attack, do it.
// if ai face monster
if(src.dir==get_dir(src,target))
AIAttack(/**/)
else
step_towards(src,target)
sleep(rand(10,13))

//when the loop is done, we've lost the target
src.lostTarget(/**/)
/***********************/
/** CHASE BLAST STATE **/
/***********************/
chaseblastState(/**/)
set waitfor=0
var/d = get_dist(src,target)
while(d>keep_dist)
//if the target is out of range of dead, bail out.
if(get_dist(aggro_loc,src)>aggro_dist||src.target.powerlevel<=0)
src.lostTarget(/**/)
return 0
//if the path is blocked, take a random step
. = step(src,get_dir(src,target))
if(!.)
step_rand(src)

// If monster is facing at target
while(src.dir==get_dir(src,target))
src.AIBlast(/**/)
sleep(rand(10,13))
sleep(rand(10,13))
d = get_dist(src,target)
src.attackState(/**/)

proc
/******************/
/**ATTACK AI PROC**/
/******************/
AIAttack(/**/)
if(prob(target.dodge))
view(src)<<"[target] dodged [src]`s attack"
return
else
target<<"dmg +1"
/*****************/
/**BLAST AI PROC**/
/*****************/
AIBlast(/**/)
for(var/atom/M in oview(src,1))
if(ismob(M) && M.density||isturf(M) && M.density)
if(src.dir == NORTH && M.y == src.y+1 && M.x == src.x)
src.AIAttack(/**/)
return
if(src.dir == SOUTH && M.y == src.y-1 && M.x == src.x)
src.AIAttack(/**/)
return
if(src.dir == WEST && M.x == src.x-1 && M.y == src.y)
src.AIAttack(/**/)
return
if(src.dir == EAST && M.x == src.x+1 && M.y == src.y)
src.AIAttack(/**/)
return

var/obj/Battle/Monster/Acid/A = new(null,src)
walk(A,A.dir,A.delay)


/************/
/**MOVEMENT**/
/************/

Move(turf/NewLoc,Dir=0,step_x=0,step_y=0)
//prevent diagonal movement
var/d = Dir & Dir-1
if(d)
Dir = rand(1) ? d : Dir^d
NewLoc = get_step(src,Dir)
. = ..(NewLoc,Dir,step_x,step_y)


New(/**/)
. = ..(/**/)
src.home_loc = src.loc


Hope everything is how it should be. And it works just fine


Oh wow, took you no time at all to fall back into old habits.

This is wrong:
            wanderState(/**/)
set waitfor=0
while(target in oview(src))
//if the target become aggressive
if(src.aggressive)
src.lostTarget(/**/)
return 0
// else walk random
step_rand(src)
sleep(rand(15,18))
src.lostTarget(/**/)
return 1


The mob doesn't need to check to see if a mob is in view. We just check to make sure that target is null:

            wanderState(/**/)
set waitfor=0
while(!target)
step_rand(src)
sleep(rand(15,18))
return 1


Second, your entire aggressive/blastable approach is wrong. You should be using polymorphism for that.

mob
proc
Attacked(var/mob/attacker)
player
Moved()
for(var/mob/monster/aggressive/a in range(src,MAX_AGGRO_RANGE))
a.foundTarget(src)
//this should be called any time you use any attack.
Attack(var/mob/target)
target.Attacked(src)
mob
monster
Attacked(var/mob/attacker)
foundTarget(attacker)
aggressive/blaster
var
blast_dist
chaseState(/**/)
set waitfor=0
var/d = get_dist(src,target)
while(d>keep_dist)
//if the target is out of range of dead, bail out.
if(get_dist(aggro_loc,src)>aggro_dist||src.target.powerlevel<=0)
src.lostTarget(/**/)
return 0
//if the path is blocked, take a random step
. = step(src,get_dir(src,target))
if(!.)
step_rand(src)
//allow it to attack while approaching
if(world.time>next_attack)
attack()
sleep(chase_speed)
d = get_dist(src,target)
src.attackState(/**/)
return 1
attack()
var/d = get_dist(src,target)
//ensure only one tile distance.
if(abs(target.x-src.x)+abs(target.y-src.y)==1)
src.melee()
else if(d<=blast_dist)
src.blast()
proc
blast()
var/obj/Battle/Monster/Acid/A = new(null,src)
walk(A,src.dir,A.delay)
//apply the attack delay
next_attack = world.time+rand(10,15)
melee()
//handle your melee attacking here
blaster
var
blast_dist
chaseState(/**/)
set waitfor=0
var/d = get_dist(src,target)
while(d>keep_dist)
//if the target is out of range of dead, bail out.
if(get_dist(aggro_loc,src)>aggro_dist||src.target.powerlevel<=0)
src.lostTarget(/**/)
return 0
//if the path is blocked, take a random step
. = step(src,get_dir(src,target))
if(!.)
step_rand(src)
//allow it to attack while approaching
if(world.time>next_attack)
attack()
sleep(chase_speed)
d = get_dist(src,target)
src.attackState(/**/)
return 1
attack()
var/d = get_dist(src,target)
//ensure only one tile distance.
if(abs(target.x-src.x)+abs(target.y-src.y)==1)
src.melee()
else if(d<=blast_dist)
src.blast()
proc
blast()
var/obj/Battle/Monster/Acid/A = new(null,src)
walk(A,src.dir,A.delay)
//apply the attack delay
next_attack = world.time+rand(10,15)
melee()
//handle your melee attacking here


Like I said, your attack functions are where you are going to want to do your logic, and I wrote this AI system with the intention of you overriding behavior in subtypes, not using variables. That's going to slow you down and make your code a nightmare to maintain.

Also, using my keep_dist variable properly will make your game's combat a lot more interesting. If you give the mob a "power" variable, and only allow it to shoot blasts while it has the necessary power, you can keep your keep_dist variable set to the maximum distance the blast should be. This will make it so that it will attempt to stay out of the player's reach while it can still fire blasts. Once the creature has less than the amount of power it needs to fire a blast, you can change keep_dist to 1, and force the mob to charge at the enemy and start smacking.

Every time it attacks, check to see if keep_dist == 1, and power is about 50% (if it regens), then reset keep_dist to blast_dist. The mob will run away and attempt to fire at the player again.

The system I set up for you is really quite dynamic if you are clever enough to use it properly.
Well im still learning it and thank you for taking time and helped me :)
In response to Ter13
Would this also work with BYONDs native pixel movement?
The main difference between tile and pixel movement regarding movement speed is:

In tile movement, movement speed is based on a delay between steps. Move once every few frames, for example.

In pixel movement, movement speed is based on pixels per step. This happens every single frame.

Of course, although pixel movement must be smooth and happen every frame, enemy detection does not.
In response to Kaiochao
So I take it, you're answering my question with a "no."
In response to Flysbad
Flysbad wrote:
So I take it, you're answering my question with a "no."

Yes and no.

Instead of using range(), do bounds() on a successful move. also sub out get_dist with bound_dist(). There's nothing here that's inherently incompatible, though.