ID:1895800
 
Not another NPC AI post... Geeze, when will we get this right?
Okay, in all seriousness this is my current method:

area
npc_area
icon = 'area.dmi'
layer = MOB_LAYER + 1
var
tmp
list
active_players = list()
New()
src.icon = null
// This is to help with map-making. What I'm doing is having it so you can physically make the /areas on the map editor
// and it will be visible on the map editor, but in run-time the /areas are going to be blank.

// When placing down NPC(s), create a new enemy_bounds/guard_bounds area for it, example:area_one, area_two, etc.
// This will be the "bounding area" the NPC can be activated at. If a player steps in this area, they will active the NPC's located
// in the area. To prevent NPC's from escaping the area, place the /area/npc_area/stop_npc around the "bounding area", otherwise
// the NPC's won't be deactivated and they'll roam around the whole place with no stop.
..()
stop_npc
icon = 'area.dmi'
icon_state = "stop"
layer = MOB_LAYER + 1
Enter(object)
if(istype(object, /mob))
var/mob/possible_npc = object
if(!possible_npc.client)
return FALSE
else
return TRUE
Entered(object)
var/mob/player
if(istype(object, /mob))
player = object
src.active_players += player
if(player.client)
for(var/mob/possible_npc in src)
if(!possible_npc.client)
possible_npc.ai_active = 1
possible_npc.ai_area = src
Exited(object)
var/mob/player
if(istype(object, /mob))
player = object
src.active_players -= player
if(src.active_players.len < 1)
for(var/mob/possible_npc in src)
if(!possible_npc.client)
possible_npc.ai_active = 0
possible_npc.target = null

enemy_bounds
area_one
// This is where X will be.
area_two
// This is where X will be.
area_three
// This is where X will be.
area_four
// This is where X will be.
area_five
// This is where X will be.

guard_bounds
area_one
// This is where X will be.
area_two
// This is where X will be.
area_three
// This is where X will be.
area_four
// This is where X will be.
area_five
// This is where X will be.
mob
proc
npc_AI()
while(src)
if(src.ai_active)
if(src.target)
src.npc_goToTarget()
else
src.npc_randomMove()
sleep(world.tick_lag)
npc_randomMove()
if(src.target)
src.npc_goToTarget()
else
var/direction = list(1, 2, 4, 5, 6, 8, 9, 10)
var/direction_to_walk = pick(direction)
for(var/loop = 1, loop < rand(25, 50), loop++)
src.movement(direction_to_walk)
sleep(world.tick_lag)
src.npc_findTarget()

npc_goToTarget()
if(src.target)
if(!src.target in src.ai_area)
src.target = null
src.movement(pick(list(1, 2, 4, 5, 6, 8, 9, 10)))
else
src.movement(get_dir(src, src.target))
if(bounds_dist(src, src.target) <= src.bound_width)
flick(pick(list("punch", "punch2", "kick", "kick2")), src)
sleep(src.attack_delay + world.tick_lag)
// Replace last 2 lines of code with damage(src, src.target) proc

npc_findTarget()
src.target = null
for(var/mob/possible_target in src.ai_area)
if(possible_target.client)
src.target = possible_target

walk_npc
step_size = 2
icon = 'human.dmi'
New()
generateShadow(src, "SOUTHEAST", 75)
spawn() src.npc_AI()
..()

/mob/proc/movement() is just
    step(src, direction, src.step_size)


Network wise when hosting it's great. The movement is smooth and the walk_npc function as they should. However, I notice world.cpu reaches ridiculous heights of almost 1-2 CPU per walk_npc placed on the map. So, about 40 walk_npc generates about 50-60 world.cpu.
Player's step_size is changeable from 2, 6, 8.
world.fps @ 40, tick_lag @ 0.25, icon_size @ 32.

The Profiler shows this (Average):
npc_randomMove Self(0.000) Total(0.002) Real(0.727) Calls(18,754)
npc_goToTarget Self(0.000) Total(0.002) Real(0.004) Calls(4,869,971)
stop_npc Enter Self(0.000) Total(0.002) Real(0.004) Calls(77,748)
stop_npc Entered Self(0.000) Total(0.002) Real(0.004) Calls(108)
stop_npc Exited Self(0.000) Total(0.002) Real(0.004) Calls(108)
movement Self(0.000) Total(0.002) Real(0.004) Calls(5,420,838)

Is there something I'm missing as far as making this more efficient?
Also, I noticed that when hosting the other player's movements are choppy, but /mob/walk_npc movement is perfect. Is this a networking issue (I don't have the best internet so I'm fairly certain it's my connection.)

Is world.cpu dependent on the hosting machine's performance? Will hosting with a more powerful machine actually reduce the world.cpu figures, or is it plain and simply the same figures for one machine compared to a machine that runs 10x better?
This is one problem right here:
for(var/loop = 1, loop < rand(25, 50), loop++)

Remember that calls rand() every time the condition is checked. The odds of this loop making it to 50 are astronomical. I think you want to pre-calculate that.
In response to Lummox JR
That's what I originally thought. But testing it shows having a rand(), regardless of the amounts, versus a constant number has shown no difference in world.cpu performance. Also, having the higher amounts is just used to have the walk_npc have more believable walking, compared to taking a step North and then just taking a step West for example. This allows the walk_npc to walk North for a random amount and then switch direction.

Basically Step x Amount, Switch, Repeat.
Instead of Step, Switch, Repeat.
I doubt it impacts your performance seriously, just saying that the logic is wrong.
Never average your profiler results. Self CPU is what we care about, not realtime or total. Unaveraged results allow us to know the average, but averaged results don't allow us to know the total because the display is clamped at 3 significant figures below the decimal point.
Also, give this a read for implementing a state machine.

http://www.byond.com/forum/?post=1447399#comment7868507

This is a really basic example, but it should give you some ideas on how to make some lean, mean AI.