1 #include "game.h"
2 
3 extern int fog;
4 
5 namespace ai
6 {
7     using namespace game;
8 
9     avoidset obstacles;
10     int updatemillis = 0, iteration = 0, itermillis = 0, forcegun = -1;
11     vec aitarget(0, 0, 0);
12 
13     VAR(aidebug, 0, 0, 6);
14     VAR(aiforcegun, -1, -1, NUMGUNS-1);
15 
16     ICOMMAND(addbot, "s", (char *s), addmsg(N_ADDBOT, "ri", *s ? clamp(parseint(s), 1, 101) : -1));
17     ICOMMAND(delbot, "", (), addmsg(N_DELBOT, "r"));
18     ICOMMAND(botlimit, "i", (int *n), addmsg(N_BOTLIMIT, "ri", *n));
19     ICOMMAND(botbalance, "i", (int *n), addmsg(N_BOTBALANCE, "ri", *n));
20 
viewdist(int x)21     float viewdist(int x)
22     {
23         return x <= 100 ? clamp((SIGHTMIN+(SIGHTMAX-SIGHTMIN))/100.f*float(x), float(SIGHTMIN), float(fog)) : float(fog);
24     }
25 
viewfieldx(int x)26     float viewfieldx(int x)
27     {
28         return x <= 100 ? clamp((VIEWMIN+(VIEWMAX-VIEWMIN))/100.f*float(x), float(VIEWMIN), float(VIEWMAX)) : float(VIEWMAX);
29     }
30 
viewfieldy(int x)31     float viewfieldy(int x)
32     {
33         return viewfieldx(x)*3.f/4.f;
34     }
35 
canmove(gameent * d)36     bool canmove(gameent *d)
37     {
38         return d->state != CS_DEAD && !intermission;
39     }
40 
attackmindist(int atk)41     float attackmindist(int atk)
42     {
43         return max(int(attacks[atk].exprad), 2);
44     }
45 
attackmaxdist(int atk)46     float attackmaxdist(int atk)
47     {
48         return attacks[atk].range + 4;
49     }
50 
attackrange(gameent * d,int atk,float dist)51     bool attackrange(gameent *d, int atk, float dist)
52     {
53         float mindist = attackmindist(atk), maxdist = attackmaxdist(atk);
54         return dist >= mindist*mindist && dist <= maxdist*maxdist;
55     }
56 
targetable(gameent * d,gameent * e)57     bool targetable(gameent *d, gameent *e)
58     {
59         if(d == e || !canmove(d)) return false;
60         return e->state == CS_ALIVE && !isteam(d->team, e->team);
61     }
62 
getsight(vec & o,float yaw,float pitch,vec & q,vec & v,float mdist,float fovx,float fovy)63     bool getsight(vec &o, float yaw, float pitch, vec &q, vec &v, float mdist, float fovx, float fovy)
64     {
65         float dist = o.dist(q);
66 
67         if(dist <= mdist)
68         {
69             float x = fmod(fabs(asin((q.z-o.z)/dist)/RAD-pitch), 360);
70             float y = fmod(fabs(-atan2(q.x-o.x, q.y-o.y)/RAD-yaw), 360);
71             if(min(x, 360-x) <= fovx && min(y, 360-y) <= fovy) return raycubelos(o, q, v);
72         }
73         return false;
74     }
75 
cansee(gameent * d,vec & x,vec & y,vec & targ)76     bool cansee(gameent *d, vec &x, vec &y, vec &targ)
77     {
78         aistate &b = d->ai->getstate();
79         if(canmove(d) && b.type != AI_S_WAIT)
80             return getsight(x, d->yaw, d->pitch, y, targ, d->ai->views[2], d->ai->views[0], d->ai->views[1]);
81         return false;
82     }
83 
canshoot(gameent * d,int atk,gameent * e)84     bool canshoot(gameent *d, int atk, gameent *e)
85     {
86         if(attackrange(d, atk, e->o.squaredist(d->o)) && targetable(d, e))
87             return d->ammo[attacks[atk].gun] > 0 && lastmillis - d->lastaction >= d->gunwait;
88         return false;
89     }
90 
canshoot(gameent * d,int atk)91     bool canshoot(gameent *d, int atk)
92     {
93         return !d->ai->becareful && d->ammo[attacks[atk].gun] > 0 && lastmillis - d->lastaction >= d->gunwait;
94     }
95 
hastarget(gameent * d,int atk,aistate & b,gameent * e,float yaw,float pitch,float dist)96     bool hastarget(gameent *d, int atk, aistate &b, gameent *e, float yaw, float pitch, float dist)
97     { // add margins of error
98         if(attackrange(d, atk, dist) || (d->skill <= 100 && !rnd(d->skill)))
99         {
100             float skew = clamp(float(lastmillis-d->ai->enemymillis)/float((d->skill*attacks[atk].attackdelay/200.f)), 0.f, attacks[atk].projspeed ? 0.25f : 1e16f),
101                 offy = yaw-d->yaw, offp = pitch-d->pitch;
102             if(offy > 180) offy -= 360;
103             else if(offy < -180) offy += 360;
104             if(fabs(offy) <= d->ai->views[0]*skew && fabs(offp) <= d->ai->views[1]*skew) return true;
105         }
106         return false;
107     }
108 
getaimpos(gameent * d,int atk,gameent * e)109     vec getaimpos(gameent *d, int atk, gameent *e)
110     {
111         vec o = e->o;
112         if(atk == ATK_PULSE_SHOOT) o.z += (e->aboveeye*0.2f)-(0.8f*d->eyeheight);
113         else o.z += (e->aboveeye-e->eyeheight)*0.5f;
114         if(d->skill <= 100)
115         {
116             if(lastmillis >= d->ai->lastaimrnd)
117             {
118                 int aiskew = 1;
119                 switch(atk)
120                 {
121                     case ATK_RAIL_SHOOT: aiskew = 5; break;
122                     case ATK_PULSE_SHOOT: aiskew = 20; break;
123                     default: break;
124                 }
125                 #define rndaioffset(r) ((rnd(int(r*aiskew*2)+1)-(r*aiskew))*(1.f/float(max(d->skill, 1))))
126                 loopk(3) d->ai->aimrnd[k] = rndaioffset(e->radius);
127                 int dur = (d->skill+10)*10;
128                 d->ai->lastaimrnd = lastmillis+dur+rnd(dur);
129             }
130             loopk(3) o[k] += d->ai->aimrnd[k];
131         }
132         return o;
133     }
134 
create(gameent * d)135     void create(gameent *d)
136     {
137         if(!d->ai) d->ai = new aiinfo;
138     }
139 
destroy(gameent * d)140     void destroy(gameent *d)
141     {
142         if(d->ai) DELETEP(d->ai);
143     }
144 
init(gameent * d,int at,int ocn,int sk,int bn,int pm,int col,const char * name,int team)145     void init(gameent *d, int at, int ocn, int sk, int bn, int pm, int col, const char *name, int team)
146     {
147         loadwaypoints();
148 
149         gameent *o = newclient(ocn);
150 
151         d->aitype = at;
152 
153         bool resetthisguy = false;
154         if(!d->name[0])
155         {
156             if(aidebug) conoutf("%s assigned to %s at skill %d", colorname(d, name), o ? colorname(o) : "?", sk);
157             else conoutf("\f0join:\f7 %s", colorname(d, name));
158             resetthisguy = true;
159         }
160         else
161         {
162             if(d->ownernum != ocn)
163             {
164                 if(aidebug) conoutf("%s reassigned to %s", colorname(d, name), o ? colorname(o) : "?");
165                 resetthisguy = true;
166             }
167             if(d->skill != sk && aidebug) conoutf("%s changed skill to %d", colorname(d, name), sk);
168         }
169 
170         copystring(d->name, name, MAXNAMELEN+1);
171         d->team = validteam(team) ? team : 0;
172         d->ownernum = ocn;
173         d->plag = 0;
174         d->skill = sk;
175         d->playermodel = chooserandomplayermodel(pm);
176         d->playercolor = col;
177 
178         if(resetthisguy) removeweapons(d);
179         if(d->ownernum >= 0 && player1->clientnum == d->ownernum)
180         {
181             create(d);
182             if(d->ai)
183             {
184                 d->ai->views[0] = viewfieldx(d->skill);
185                 d->ai->views[1] = viewfieldy(d->skill);
186                 d->ai->views[2] = viewdist(d->skill);
187             }
188         }
189         else if(d->ai) destroy(d);
190     }
191 
update()192     void update()
193     {
194         if(intermission) { loopv(players) if(players[i]->ai) players[i]->stopmoving(); }
195         else // fixed rate logic done out-of-sequence at 1 frame per second for each ai
196         {
197             if(totalmillis-updatemillis > 1000)
198             {
199                 avoid();
200                 forcegun = multiplayer(false) ? -1 : aiforcegun;
201                 updatemillis = totalmillis;
202             }
203             if(!iteration && totalmillis-itermillis > 1000)
204             {
205                 iteration = 1;
206                 itermillis = totalmillis;
207             }
208             int count = 0;
209             loopv(players) if(players[i]->ai) think(players[i], ++count == iteration ? true : false);
210             if(++iteration > count) iteration = 0;
211         }
212     }
213 
checkothers(vector<int> & targets,gameent * d,int state,int targtype,int target,bool teams,int * members)214     bool checkothers(vector<int> &targets, gameent *d, int state, int targtype, int target, bool teams, int *members)
215     { // checks the states of other ai for a match
216         targets.setsize(0);
217         loopv(players)
218         {
219             gameent *e = players[i];
220             if(targets.find(e->clientnum) >= 0) continue;
221             if(teams && d && !isteam(d->team, e->team)) continue;
222             if(members) (*members)++;
223             if(e == d || !e->ai || e->state != CS_ALIVE) continue;
224             aistate &b = e->ai->getstate();
225             if(state >= 0 && b.type != state) continue;
226             if(target >= 0 && b.target != target) continue;
227             if(targtype >=0 && b.targtype != targtype) continue;
228             targets.add(e->clientnum);
229         }
230         return !targets.empty();
231     }
232 
makeroute(gameent * d,aistate & b,int node,bool changed,int retries)233     bool makeroute(gameent *d, aistate &b, int node, bool changed, int retries)
234     {
235         if(!iswaypoint(d->lastnode)) return false;
236         if(changed && d->ai->route.length() > 1 && d->ai->route[0] == node) return true;
237         if(route(d, d->lastnode, node, d->ai->route, obstacles, retries))
238         {
239             b.override = false;
240             return true;
241         }
242         // retry fails: 0 = first attempt, 1 = try ignoring obstacles, 2 = try ignoring prevnodes too
243         if(retries <= 1) return makeroute(d, b, node, false, retries+1);
244         return false;
245     }
246 
makeroute(gameent * d,aistate & b,const vec & pos,bool changed,int retries)247     bool makeroute(gameent *d, aistate &b, const vec &pos, bool changed, int retries)
248     {
249         int node = closestwaypoint(pos, SIGHTMIN, true);
250         return makeroute(d, b, node, changed, retries);
251     }
252 
randomnode(gameent * d,aistate & b,const vec & pos,float guard,float wander)253     bool randomnode(gameent *d, aistate &b, const vec &pos, float guard, float wander)
254     {
255         static vector<int> candidates;
256         candidates.setsize(0);
257         findwaypointswithin(pos, guard, wander, candidates);
258 
259         while(!candidates.empty())
260         {
261             int w = rnd(candidates.length()), n = candidates.removeunordered(w);
262             if(n != d->lastnode && !d->ai->hasprevnode(n) && !obstacles.find(n, d) && makeroute(d, b, n)) return true;
263         }
264         return false;
265     }
266 
randomnode(gameent * d,aistate & b,float guard,float wander)267     bool randomnode(gameent *d, aistate &b, float guard, float wander)
268     {
269         return randomnode(d, b, d->feetpos(), guard, wander);
270     }
271 
badhealth(gameent * d)272     bool badhealth(gameent *d)
273     {
274         //if(d->skill <= 100) return d->health <= (111-d->skill)/4;
275         return false;
276     }
277 
enemy(gameent * d,aistate & b,const vec & pos,float guard=SIGHTMIN,int pursue=0)278     bool enemy(gameent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, int pursue = 0)
279     {
280         gameent *t = NULL;
281         vec dp = d->headpos();
282         float mindist = guard*guard, bestdist = 1e16f;
283         int atk = guns[d->gunselect].attacks[ACT_SHOOT];
284         loopv(players)
285         {
286             gameent *e = players[i];
287             if(e == d || !targetable(d, e)) continue;
288             vec ep = getaimpos(d, atk, e);
289             float dist = ep.squaredist(dp);
290             if(dist < bestdist && (cansee(d, dp, ep) || dist <= mindist))
291             {
292                 t = e;
293                 bestdist = dist;
294             }
295         }
296         if(t && violence(d, b, t, pursue)) return true;
297         return false;
298     }
299 
patrol(gameent * d,aistate & b,const vec & pos,float guard,float wander,int walk,bool retry)300     bool patrol(gameent *d, aistate &b, const vec &pos, float guard, float wander, int walk, bool retry)
301     {
302         vec feet = d->feetpos();
303         if(walk == 2 || b.override || (walk && feet.squaredist(pos) <= guard*guard) || !makeroute(d, b, pos))
304         { // run away and back to keep ourselves busy
305             if(!b.override && randomnode(d, b, pos, guard, wander))
306             {
307                 b.override = true;
308                 return true;
309             }
310             else if(d->ai->route.empty())
311             {
312                 if(!retry)
313                 {
314                     b.override = false;
315                     return patrol(d, b, pos, guard, wander, walk, true);
316                 }
317                 b.override = false;
318                 return false;
319             }
320         }
321         b.override = false;
322         return true;
323     }
324 
defend(gameent * d,aistate & b,const vec & pos,float guard,float wander,int walk)325     bool defend(gameent *d, aistate &b, const vec &pos, float guard, float wander, int walk)
326     {
327         bool hasenemy = enemy(d, b, pos, wander);
328         if(!walk)
329         {
330             if(d->feetpos().squaredist(pos) <= guard*guard)
331             {
332                 b.idle = hasenemy ? 2 : 1;
333                 return true;
334             }
335             walk++;
336         }
337         return patrol(d, b, pos, guard, wander, walk);
338     }
339 
violence(gameent * d,aistate & b,gameent * e,int pursue)340     bool violence(gameent *d, aistate &b, gameent *e, int pursue)
341     {
342         if(e && targetable(d, e))
343         {
344             if(pursue)
345             {
346                 if((b.targtype != AI_T_AFFINITY || !(pursue%2)) && makeroute(d, b, e->lastnode))
347                     d->ai->switchstate(b, AI_S_PURSUE, AI_T_PLAYER, e->clientnum);
348                 else if(pursue >= 3) return false; // can't pursue
349             }
350             if(d->ai->enemy != e->clientnum)
351             {
352                 d->ai->enemyseen = d->ai->enemymillis = lastmillis;
353                 d->ai->enemy = e->clientnum;
354             }
355             return true;
356         }
357         return false;
358     }
359 
target(gameent * d,aistate & b,int pursue=0,bool force=false,float mindist=0.f)360     bool target(gameent *d, aistate &b, int pursue = 0, bool force = false, float mindist = 0.f)
361     {
362         static vector<gameent *> hastried; hastried.setsize(0);
363         vec dp = d->headpos();
364         while(true)
365         {
366             float dist = 1e16f;
367             gameent *t = NULL;
368             int atk = guns[d->gunselect].attacks[ACT_SHOOT];
369             loopv(players)
370             {
371                 gameent *e = players[i];
372                 if(e == d || hastried.find(e) >= 0 || !targetable(d, e)) continue;
373                 vec ep = getaimpos(d, atk, e);
374                 float v = ep.squaredist(dp);
375                 if((!t || v < dist) && (mindist <= 0 || v <= mindist) && (force || cansee(d, dp, ep)))
376                 {
377                     t = e;
378                     dist = v;
379                 }
380             }
381             if(t)
382             {
383                 if(violence(d, b, t, pursue)) return true;
384                 hastried.add(t);
385             }
386             else break;
387         }
388         return false;
389     }
390 
isgoodammo(int gun)391     int isgoodammo(int gun) { return gun == GUN_PULSE || gun == GUN_RAIL; }
392 
hasgoodammo(gameent * d)393     bool hasgoodammo(gameent *d)
394     {
395         static const int goodguns[] = { GUN_PULSE, GUN_RAIL };
396         loopi(sizeof(goodguns)/sizeof(goodguns[0])) if(d->hasammo(goodguns[0])) return true;
397         return false;
398     }
399 
assist(gameent * d,aistate & b,vector<interest> & interests,bool all,bool force)400     void assist(gameent *d, aistate &b, vector<interest> &interests, bool all, bool force)
401     {
402         loopv(players)
403         {
404             gameent *e = players[i];
405             if(e == d || (!all && e->aitype != AI_NONE) || !isteam(d->team, e->team)) continue;
406             interest &n = interests.add();
407             n.state = AI_S_DEFEND;
408             n.node = e->lastnode;
409             n.target = e->clientnum;
410             n.targtype = AI_T_PLAYER;
411             n.score = e->o.squaredist(d->o)/(hasgoodammo(d) ? 1e8f : (force ? 1e4f : 1e2f));
412         }
413     }
414 
tryitem(gameent * d,extentity & e,int id,aistate & b,vector<interest> & interests,bool force=false)415     static void tryitem(gameent *d, extentity &e, int id, aistate &b, vector<interest> &interests, bool force = false)
416     {
417         float score = 0;
418         switch(e.type)
419         {
420         }
421         if(score != 0)
422         {
423             interest &n = interests.add();
424             n.state = AI_S_INTEREST;
425             n.node = closestwaypoint(e.o, SIGHTMIN, true);
426             n.target = id;
427             n.targtype = AI_T_ENTITY;
428             n.score = d->feetpos().squaredist(e.o)/(force ? -1 : score);
429         }
430     }
431 
items(gameent * d,aistate & b,vector<interest> & interests,bool force=false)432     void items(gameent *d, aistate &b, vector<interest> &interests, bool force = false)
433     {
434         loopv(entities::ents)
435         {
436             extentity &e = *(extentity *)entities::ents[i];
437             if(!e.spawned() || !d->canpickup(e.type)) continue;
438             tryitem(d, e, i, b, interests, force);
439         }
440     }
441 
442     static vector<int> targets;
443 
parseinterests(gameent * d,aistate & b,vector<interest> & interests,bool override,bool ignore)444     bool parseinterests(gameent *d, aistate &b, vector<interest> &interests, bool override, bool ignore)
445     {
446         while(!interests.empty())
447         {
448             int q = interests.length()-1;
449             loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i;
450             interest n = interests.removeunordered(q);
451             bool proceed = true;
452             if(!ignore) switch(n.state)
453             {
454                 case AI_S_DEFEND: // don't get into herds
455                 {
456                     int members = 0;
457                     proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true, &members) && members > 1;
458                     break;
459                 }
460                 default: break;
461             }
462             if(proceed && makeroute(d, b, n.node))
463             {
464                 d->ai->switchstate(b, n.state, n.targtype, n.target);
465                 return true;
466             }
467         }
468         return false;
469     }
470 
find(gameent * d,aistate & b,bool override=false)471     bool find(gameent *d, aistate &b, bool override = false)
472     {
473         static vector<interest> interests;
474         interests.setsize(0);
475 #if 0
476         if(!hasgoodammo(d) || d->health < min(d->skill - 15, 75))
477             items(d, b, interests);
478         else
479         {
480             static vector<int> nearby;
481             nearby.setsize(0);
482             findents(I_FIRST, I_LAST, false, d->feetpos(), vec(32, 32, 24), nearby);
483             loopv(nearby)
484             {
485                 int id = nearby[i];
486                 extentity &e = *(extentity *)entities::ents[id];
487                 if(d->canpickup(e.type)) tryitem(d, e, id, b, interests);
488             }
489         }
490 #endif
491         if(cmode) cmode->aifind(d, b, interests);
492         if(m_teammode) assist(d, b, interests);
493         return parseinterests(d, b, interests, override);
494     }
495 
findassist(gameent * d,aistate & b,bool override=false)496     bool findassist(gameent *d, aistate &b, bool override = false)
497     {
498         static vector<interest> interests;
499         interests.setsize(0);
500         assist(d, b, interests);
501         while(!interests.empty())
502         {
503             int q = interests.length()-1;
504             loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i;
505             interest n = interests.removeunordered(q);
506             bool proceed = true;
507             switch(n.state)
508             {
509                 case AI_S_DEFEND: // don't get into herds
510                 {
511                     int members = 0;
512                     proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true, &members) && members > 1;
513                     break;
514                 }
515                 default: break;
516             }
517             if(proceed && makeroute(d, b, n.node))
518             {
519                 d->ai->switchstate(b, n.state, n.targtype, n.target);
520                 return true;
521             }
522         }
523         return false;
524     }
525 
damaged(gameent * d,gameent * e)526     void damaged(gameent *d, gameent *e)
527     {
528         if(d->ai && canmove(d) && targetable(d, e)) // see if this ai is interested in a grudge
529         {
530             aistate &b = d->ai->getstate();
531             if(violence(d, b, e)) return;
532         }
533         if(checkothers(targets, d, AI_S_DEFEND, AI_T_PLAYER, d->clientnum, true))
534         {
535             loopv(targets)
536             {
537                 gameent *t = getclient(targets[i]);
538                 if(!t->ai || !canmove(t) || !targetable(t, e)) continue;
539                 aistate &c = t->ai->getstate();
540                 if(violence(t, c, e)) return;
541             }
542         }
543     }
544 
findorientation(vec & o,float yaw,float pitch,vec & pos)545     void findorientation(vec &o, float yaw, float pitch, vec &pos)
546     {
547         vec dir;
548         vecfromyawpitch(yaw, pitch, 1, 0, dir);
549         if(raycubepos(o, dir, pos, 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1)
550             pos = dir.mul(2*getworldsize()).add(o); //otherwise 3dgui won't work when outside of map
551     }
552 
setup(gameent * d)553     void setup(gameent *d)
554     {
555         d->ai->clearsetup();
556         d->ai->reset(true);
557         d->ai->lastrun = lastmillis;
558         if(forcegun >= 0 && forcegun < NUMGUNS) d->ai->weappref = forcegun;
559         else d->ai->weappref = rnd(NUMGUNS);
560         vec dp = d->headpos();
561         findorientation(dp, d->yaw, d->pitch, d->ai->target);
562     }
563 
spawned(gameent * d)564     void spawned(gameent *d)
565     {
566         if(d->ai) setup(d);
567     }
568 
killed(gameent * d,gameent * e)569     void killed(gameent *d, gameent *e)
570     {
571         if(d->ai) d->ai->reset();
572     }
573 
itemspawned(int ent)574     void itemspawned(int ent)
575     {
576         if(!entities::ents.inrange(ent)) return;
577         extentity &e = *entities::ents[ent];
578         if(validitem(e.type))
579         {
580             loopv(players) if(players[i] && players[i]->ai && players[i]->aitype == AI_BOT && players[i]->canpickup(e.type))
581             {
582                 gameent *d = players[i];
583                 bool wantsitem = false;
584                 switch(e.type)
585                 {
586                 }
587                 if(wantsitem)
588                 {
589                     aistate &b = d->ai->getstate();
590                     if(b.targtype == AI_T_AFFINITY) continue;
591                     if(b.type == AI_S_INTEREST && b.targtype == AI_T_ENTITY)
592                     {
593                         if(entities::ents.inrange(b.target))
594                         {
595                             if(d->o.squaredist(entities::ents[ent]->o) < d->o.squaredist(entities::ents[b.target]->o))
596                                 d->ai->switchstate(b, AI_S_INTEREST, AI_T_ENTITY, ent);
597                         }
598                         continue;
599                     }
600                     d->ai->switchstate(b, AI_S_INTEREST, AI_T_ENTITY, ent);
601                 }
602             }
603         }
604     }
605 
check(gameent * d,aistate & b)606     bool check(gameent *d, aistate &b)
607     {
608         if(cmode && cmode->aicheck(d, b)) return true;
609         return false;
610     }
611 
dowait(gameent * d,aistate & b)612     int dowait(gameent *d, aistate &b)
613     {
614         d->ai->clear(true); // ensure they're clean
615         if(check(d, b) || find(d, b)) return 1;
616         if(target(d, b, 4, false)) return 1;
617         if(target(d, b, 4, true)) return 1;
618         if(randomnode(d, b, SIGHTMIN, 1e16f))
619         {
620             d->ai->switchstate(b, AI_S_INTEREST, AI_T_NODE, d->ai->route[0]);
621             return 1;
622         }
623         return 0; // but don't pop the state
624     }
625 
dodefend(gameent * d,aistate & b)626     int dodefend(gameent *d, aistate &b)
627     {
628         if(d->state == CS_ALIVE)
629         {
630             switch(b.targtype)
631             {
632                 case AI_T_NODE:
633                     if(check(d, b)) return 1;
634                     if(iswaypoint(b.target)) return defend(d, b, waypoints[b.target].o) ? 1 : 0;
635                     break;
636                 case AI_T_ENTITY:
637                     if(check(d, b)) return 1;
638                     if(entities::ents.inrange(b.target)) return defend(d, b, entities::ents[b.target]->o) ? 1 : 0;
639                     break;
640                 case AI_T_AFFINITY:
641                     if(cmode) return cmode->aidefend(d, b) ? 1 : 0;
642                     break;
643                 case AI_T_PLAYER:
644                 {
645                     if(check(d, b)) return 1;
646                     gameent *e = getclient(b.target);
647                     if(e && e->state == CS_ALIVE) return defend(d, b, e->feetpos()) ? 1 : 0;
648                     break;
649                 }
650                 default: break;
651             }
652         }
653         return 0;
654     }
655 
dointerest(gameent * d,aistate & b)656     int dointerest(gameent *d, aistate &b)
657     {
658         if(d->state != CS_ALIVE) return 0;
659         switch(b.targtype)
660         {
661             case AI_T_NODE: // this is like a wait state without sitting still..
662                 if(check(d, b) || find(d, b)) return 1;
663                 if(target(d, b, 4, true)) return 1;
664                 if(iswaypoint(b.target) && vec(waypoints[b.target].o).sub(d->feetpos()).magnitude() > CLOSEDIST)
665                     return makeroute(d, b, waypoints[b.target].o) ? 1 : 0;
666                 break;
667             case AI_T_ENTITY:
668                 if(entities::ents.inrange(b.target))
669                 {
670                     extentity &e = *(extentity *)entities::ents[b.target];
671                     if(!e.spawned() || !validitem(e.type)) return 0;
672                     //if(d->feetpos().squaredist(e.o) <= CLOSEDIST*CLOSEDIST)
673                     //{
674                     //    b.idle = 1;
675                     //    return true;
676                     //}
677                     return makeroute(d, b, e.o) ? 1 : 0;
678                 }
679                 break;
680         }
681         return 0;
682     }
683 
dopursue(gameent * d,aistate & b)684     int dopursue(gameent *d, aistate &b)
685     {
686         if(d->state == CS_ALIVE)
687         {
688             switch(b.targtype)
689             {
690                 case AI_T_NODE:
691                 {
692                     if(check(d, b)) return 1;
693                     if(iswaypoint(b.target))
694                         return defend(d, b, waypoints[b.target].o) ? 1 : 0;
695                     break;
696                 }
697 
698                 case AI_T_AFFINITY:
699                 {
700                     if(cmode) return cmode->aipursue(d, b) ? 1 : 0;
701                     break;
702                 }
703 
704                 case AI_T_PLAYER:
705                 {
706                     //if(check(d, b)) return 1;
707                     gameent *e = getclient(b.target);
708                     if(e && e->state == CS_ALIVE)
709                     {
710                         int atk = guns[d->gunselect].attacks[ACT_SHOOT];
711                         float guard = SIGHTMIN, wander = attacks[atk].range;
712                         return patrol(d, b, e->feetpos(), guard, wander) ? 1 : 0;
713                     }
714                     break;
715                 }
716                 default: break;
717             }
718         }
719         return 0;
720     }
721 
closenode(gameent * d)722     int closenode(gameent *d)
723     {
724         vec pos = d->feetpos();
725         int node1 = -1, node2 = -1;
726         float mindist1 = CLOSEDIST*CLOSEDIST, mindist2 = CLOSEDIST*CLOSEDIST;
727         loopv(d->ai->route) if(iswaypoint(d->ai->route[i]))
728         {
729             vec epos = waypoints[d->ai->route[i]].o;
730             float dist = epos.squaredist(pos);
731             if(dist > FARDIST*FARDIST) continue;
732             int entid = obstacles.remap(d, d->ai->route[i], epos);
733             if(entid >= 0)
734             {
735                 if(entid != i) dist = epos.squaredist(pos);
736                 if(dist < mindist1) { node1 = i; mindist1 = dist; }
737             }
738             else if(dist < mindist2) { node2 = i; mindist2 = dist; }
739         }
740         return node1 >= 0 ? node1 : node2;
741     }
742 
wpspot(gameent * d,int n,bool check=false)743     int wpspot(gameent *d, int n, bool check = false)
744     {
745         if(iswaypoint(n)) loopk(2)
746         {
747             vec epos = waypoints[n].o;
748             int entid = obstacles.remap(d, n, epos, k!=0);
749             if(iswaypoint(entid))
750             {
751                 d->ai->spot = epos;
752                 d->ai->targnode = entid;
753                 return !check || d->feetpos().squaredist(epos) > MINWPDIST*MINWPDIST ? 1 : 2;
754             }
755         }
756         return 0;
757     }
758 
randomlink(gameent * d,int n)759     int randomlink(gameent *d, int n)
760     {
761         if(iswaypoint(n) && waypoints[n].haslinks())
762         {
763             waypoint &w = waypoints[n];
764             static vector<int> linkmap; linkmap.setsize(0);
765             loopi(MAXWAYPOINTLINKS)
766             {
767                 if(!w.links[i]) break;
768                 if(iswaypoint(w.links[i]) && !d->ai->hasprevnode(w.links[i]) && d->ai->route.find(w.links[i]) < 0)
769                     linkmap.add(w.links[i]);
770             }
771             if(!linkmap.empty()) return linkmap[rnd(linkmap.length())];
772         }
773         return -1;
774     }
775 
anynode(gameent * d,aistate & b,int len=NUMPREVNODES)776     bool anynode(gameent *d, aistate &b, int len = NUMPREVNODES)
777     {
778         if(iswaypoint(d->lastnode)) loopk(2)
779         {
780             d->ai->clear(k ? true : false);
781             int n = randomlink(d, d->lastnode);
782             if(wpspot(d, n))
783             {
784                 d->ai->route.add(n);
785                 d->ai->route.add(d->lastnode);
786                 loopi(len)
787                 {
788                     n = randomlink(d, n);
789                     if(iswaypoint(n)) d->ai->route.insert(0, n);
790                     else break;
791                 }
792                 return true;
793             }
794         }
795         return false;
796     }
797 
checkroute(gameent * d,int n)798     bool checkroute(gameent *d, int n)
799     {
800         if(d->ai->route.empty() || !d->ai->route.inrange(n)) return false;
801         int last = d->ai->lastcheck ? lastmillis-d->ai->lastcheck : 0;
802         if(last < 500 || n < 3) return false; // route length is too short
803         d->ai->lastcheck = lastmillis;
804         int w = iswaypoint(d->lastnode) ? d->lastnode : d->ai->route[n], c = min(n-1, NUMPREVNODES);
805         loopj(c) // check ahead to see if we need to go around something
806         {
807             int p = n-j-1, v = d->ai->route[p];
808             if(d->ai->hasprevnode(v) || obstacles.find(v, d)) // something is in the way, try to remap around it
809             {
810                 int m = p-1;
811                 if(m < 3) return false; // route length is too short from this point
812                 loopirev(m)
813                 {
814                     int t = d->ai->route[i];
815                     if(!d->ai->hasprevnode(t) && !obstacles.find(t, d))
816                     {
817                         static vector<int> remap; remap.setsize(0);
818                         if(route(d, w, t, remap, obstacles))
819                         { // kill what we don't want and put the remap in
820                             while(d->ai->route.length() > i) d->ai->route.pop();
821                             loopvk(remap) d->ai->route.add(remap[k]);
822                             return true;
823                         }
824                         return false; // we failed
825                     }
826                 }
827                 return false;
828             }
829         }
830         return false;
831     }
832 
hunt(gameent * d,aistate & b)833     bool hunt(gameent *d, aistate &b)
834     {
835         if(!d->ai->route.empty())
836         {
837             int n = closenode(d);
838             if(d->ai->route.inrange(n) && checkroute(d, n)) n = closenode(d);
839             if(d->ai->route.inrange(n))
840             {
841                 if(!n)
842                 {
843                     switch(wpspot(d, d->ai->route[n], true))
844                     {
845                         case 2: d->ai->clear(false);
846                         case 1: return true; // not close enough to pop it yet
847                         case 0: default: break;
848                     }
849                 }
850                 else
851                 {
852                     while(d->ai->route.length() > n+1) d->ai->route.pop(); // waka-waka-waka-waka
853                     int m = n-1; // next, please!
854                     if(d->ai->route.inrange(m) && wpspot(d, d->ai->route[m])) return true;
855                 }
856             }
857         }
858         b.override = false;
859         return anynode(d, b);
860     }
861 
jumpto(gameent * d,aistate & b,const vec & pos)862     void jumpto(gameent *d, aistate &b, const vec &pos)
863     {
864         vec off = vec(pos).sub(d->feetpos()), dir(off.x, off.y, 0);
865         bool sequenced = d->ai->blockseq || d->ai->targseq, offground = d->timeinair && !d->inwater,
866             jump = !offground && lastmillis >= d->ai->jumpseed && (sequenced || off.z >= JUMPMIN || lastmillis >= d->ai->jumprand);
867         if(jump)
868         {
869             vec old = d->o;
870             d->o = vec(pos).addz(d->eyeheight);
871             if(collide(d, vec(0, 0, 1))) jump = false;
872             d->o = old;
873             if(jump)
874             {
875                 float radius = 18*18;
876                 loopv(entities::ents) if(entities::ents[i]->type == JUMPPAD)
877                 {
878                     gameentity &e = *(gameentity *)entities::ents[i];
879                     if(e.o.squaredist(pos) <= radius) { jump = false; break; }
880                 }
881             }
882         }
883         if(jump)
884         {
885             d->jumping = true;
886             int seed = (111-d->skill)*(d->inwater ? 3 : 5);
887             d->ai->jumpseed = lastmillis+seed+rnd(seed);
888             seed *= b.idle ? 50 : 25;
889             d->ai->jumprand = lastmillis+seed+rnd(seed);
890         }
891     }
892 
fixfullrange(float & yaw,float & pitch,float & roll,bool full)893     void fixfullrange(float &yaw, float &pitch, float &roll, bool full)
894     {
895         if(full)
896         {
897             while(pitch < -180.0f) pitch += 360.0f;
898             while(pitch >= 180.0f) pitch -= 360.0f;
899             while(roll < -180.0f) roll += 360.0f;
900             while(roll >= 180.0f) roll -= 360.0f;
901         }
902         else
903         {
904             if(pitch > 89.9f) pitch = 89.9f;
905             if(pitch < -89.9f) pitch = -89.9f;
906             if(roll > 89.9f) roll = 89.9f;
907             if(roll < -89.9f) roll = -89.9f;
908         }
909         while(yaw < 0.0f) yaw += 360.0f;
910         while(yaw >= 360.0f) yaw -= 360.0f;
911     }
912 
fixrange(float & yaw,float & pitch)913     void fixrange(float &yaw, float &pitch)
914     {
915         float r = 0.f;
916         fixfullrange(yaw, pitch, r, false);
917     }
918 
getyawpitch(const vec & from,const vec & pos,float & yaw,float & pitch)919     void getyawpitch(const vec &from, const vec &pos, float &yaw, float &pitch)
920     {
921         float dist = from.dist(pos);
922         yaw = -atan2(pos.x-from.x, pos.y-from.y)/RAD;
923         pitch = asin((pos.z-from.z)/dist)/RAD;
924     }
925 
scaleyawpitch(float & yaw,float & pitch,float targyaw,float targpitch,float frame,float scale)926     void scaleyawpitch(float &yaw, float &pitch, float targyaw, float targpitch, float frame, float scale)
927     {
928         if(yaw < targyaw-180.0f) yaw += 360.0f;
929         if(yaw > targyaw+180.0f) yaw -= 360.0f;
930         float offyaw = fabs(targyaw-yaw)*frame, offpitch = fabs(targpitch-pitch)*frame*scale;
931         if(targyaw > yaw)
932         {
933             yaw += offyaw;
934             if(targyaw < yaw) yaw = targyaw;
935         }
936         else if(targyaw < yaw)
937         {
938             yaw -= offyaw;
939             if(targyaw > yaw) yaw = targyaw;
940         }
941         if(targpitch > pitch)
942         {
943             pitch += offpitch;
944             if(targpitch < pitch) pitch = targpitch;
945         }
946         else if(targpitch < pitch)
947         {
948             pitch -= offpitch;
949             if(targpitch > pitch) pitch = targpitch;
950         }
951         fixrange(yaw, pitch);
952     }
953 
lockon(gameent * d,int atk,gameent * e,float maxdist)954     bool lockon(gameent *d, int atk, gameent *e, float maxdist)
955     {
956         if(attacks[atk].action == ACT_MELEE && !d->blocked && !d->timeinair)
957         {
958             vec dir = vec(e->o).sub(d->o);
959             float xydist = dir.x*dir.x+dir.y*dir.y, zdist = dir.z*dir.z, mdist = maxdist*maxdist, ddist = d->radius*d->radius+e->radius*e->radius;
960             if(zdist <= ddist && xydist >= ddist+4 && xydist <= mdist+ddist) return true;
961         }
962         return false;
963     }
964 
process(gameent * d,aistate & b)965     int process(gameent *d, aistate &b)
966     {
967         int result = 0, stupify = d->skill <= 10+rnd(15) ? rnd(d->skill*1000) : 0, skmod = 101-d->skill;
968         float frame = d->skill <= 100 ? float(lastmillis-d->ai->lastrun)/float(max(skmod,1)*10) : 1;
969         vec dp = d->headpos();
970 
971         bool idle = b.idle == 1 || (stupify && stupify <= skmod);
972         d->ai->dontmove = false;
973         if(idle)
974         {
975             d->ai->lastaction = d->ai->lasthunt = lastmillis;
976             d->ai->dontmove = true;
977             d->ai->spot = vec(0, 0, 0);
978         }
979         else if(hunt(d, b))
980         {
981             getyawpitch(dp, vec(d->ai->spot).addz(d->eyeheight), d->ai->targyaw, d->ai->targpitch);
982             d->ai->lasthunt = lastmillis;
983         }
984         else
985         {
986             idle = d->ai->dontmove = true;
987             d->ai->spot = vec(0, 0, 0);
988         }
989 
990         if(!d->ai->dontmove) jumpto(d, b, d->ai->spot);
991 
992         gameent *e = getclient(d->ai->enemy);
993         bool enemyok = e && targetable(d, e);
994         if(!enemyok || d->skill >= 50)
995         {
996             gameent *f = (gameent *)intersectclosest(dp, d->ai->target, d, 1);
997             if(f)
998             {
999                 if(targetable(d, f))
1000                 {
1001                     if(!enemyok) violence(d, b, f);
1002                     enemyok = true;
1003                     e = f;
1004                 }
1005                 else enemyok = false;
1006             }
1007             else if(!enemyok && target(d, b, 0, false, SIGHTMIN))
1008                 enemyok = (e = getclient(d->ai->enemy)) != NULL;
1009         }
1010         if(enemyok)
1011         {
1012             int atk = guns[d->gunselect].attacks[ACT_SHOOT];
1013             vec ep = getaimpos(d, atk, e);
1014             float yaw, pitch;
1015             getyawpitch(dp, ep, yaw, pitch);
1016             fixrange(yaw, pitch);
1017             bool insight = cansee(d, dp, ep), hasseen = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->skill*10)+3000,
1018                 quick = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= skmod+30;
1019             if(insight) d->ai->enemyseen = lastmillis;
1020             if(idle || insight || hasseen || quick)
1021             {
1022                 float sskew = insight || d->skill > 100 ? 1.5f : (hasseen ? 1.f : 0.5f);
1023                 if(insight && lockon(d, atk, e, 16))
1024                 {
1025                     d->ai->targyaw = yaw;
1026                     d->ai->targpitch = pitch;
1027                     if(!idle) frame *= 2;
1028                     d->ai->becareful = false;
1029                 }
1030                 scaleyawpitch(d->yaw, d->pitch, yaw, pitch, frame, sskew);
1031                 if(insight || quick)
1032                 {
1033                     if(canshoot(d, atk, e) && hastarget(d, atk, b, e, yaw, pitch, dp.squaredist(ep)))
1034                     {
1035                         d->attacking = attacks[atk].action;
1036                         d->ai->lastaction = lastmillis;
1037                         result = 3;
1038                     }
1039                     else result = 2;
1040                 }
1041                 else result = 1;
1042             }
1043             else
1044             {
1045                 if(!d->ai->enemyseen || lastmillis-d->ai->enemyseen > (d->skill*50)+3000)
1046                 {
1047                     d->ai->enemy = -1;
1048                     d->ai->enemyseen = d->ai->enemymillis = 0;
1049                 }
1050                 enemyok = false;
1051                 result = 0;
1052             }
1053         }
1054         else
1055         {
1056             if(!enemyok)
1057             {
1058                 d->ai->enemy = -1;
1059                 d->ai->enemyseen = d->ai->enemymillis = 0;
1060             }
1061             enemyok = false;
1062             result = 0;
1063         }
1064 
1065         fixrange(d->ai->targyaw, d->ai->targpitch);
1066         if(!result) scaleyawpitch(d->yaw, d->pitch, d->ai->targyaw, d->ai->targpitch, frame*0.25f, 1.f);
1067 
1068         if(d->ai->becareful && d->physstate == PHYS_FALL)
1069         {
1070             float offyaw, offpitch;
1071             vec v = vec(d->vel).normalize();
1072             vectoyawpitch(v, offyaw, offpitch);
1073             offyaw -= d->yaw; offpitch -= d->pitch;
1074             if(fabs(offyaw)+fabs(offpitch) >= 135) d->ai->becareful = false;
1075             else if(d->ai->becareful) d->ai->dontmove = true;
1076         }
1077         else d->ai->becareful = false;
1078 
1079         if(d->ai->dontmove) d->move = d->strafe = 0;
1080         else
1081         { // our guys move one way.. but turn another?! :)
1082             const struct aimdir { int move, strafe, offset; } aimdirs[8] =
1083             {
1084                 {  1,  0,   0 },
1085                 {  1,  -1,  45 },
1086                 {  0,  -1,  90 },
1087                 { -1,  -1, 135 },
1088                 { -1,  0, 180 },
1089                 { -1, 1, 225 },
1090                 {  0, 1, 270 },
1091                 {  1, 1, 315 }
1092             };
1093             float yaw = d->ai->targyaw-d->yaw;
1094             while(yaw < 0.0f) yaw += 360.0f;
1095             while(yaw >= 360.0f) yaw -= 360.0f;
1096             int r = clamp(((int)floor((yaw+22.5f)/45.0f))&7, 0, 7);
1097             const aimdir &ad = aimdirs[r];
1098             d->move = ad.move;
1099             d->strafe = ad.strafe;
1100         }
1101         findorientation(dp, d->yaw, d->pitch, d->ai->target);
1102         return result;
1103     }
1104 
hasrange(gameent * d,gameent * e,int weap)1105     bool hasrange(gameent *d, gameent *e, int weap)
1106     {
1107         if(!e) return true;
1108         if(targetable(d, e))
1109         {
1110             int atk = guns[weap].attacks[ACT_SHOOT];
1111             vec ep = getaimpos(d, atk, e);
1112             float dist = ep.squaredist(d->headpos());
1113             if(attackrange(d, atk, dist)) return true;
1114         }
1115         return false;
1116     }
1117 
request(gameent * d,aistate & b)1118     bool request(gameent *d, aistate &b)
1119     {
1120         gameent *e = getclient(d->ai->enemy);
1121         if(!d->hasammo(d->gunselect) || !hasrange(d, e, d->gunselect) || (d->gunselect != d->ai->weappref && (!isgoodammo(d->gunselect) || d->hasammo(d->ai->weappref))))
1122         {
1123             static const int gunprefs[] = { GUN_PULSE, GUN_RAIL };
1124             int gun = -1;
1125             if(d->hasammo(d->ai->weappref) && hasrange(d, e, d->ai->weappref)) gun = d->ai->weappref;
1126             else
1127             {
1128                 loopi(sizeof(gunprefs)/sizeof(gunprefs[0])) if(d->hasammo(gunprefs[i]) && hasrange(d, e, gunprefs[i]))
1129                 {
1130                     gun = gunprefs[i];
1131                     break;
1132                 }
1133             }
1134             if(gun >= 0 && gun != d->gunselect) gunselect(gun, d);
1135         }
1136         return process(d, b) >= 2;
1137     }
1138 
timeouts(gameent * d,aistate & b)1139     void timeouts(gameent *d, aistate &b)
1140     {
1141         if(d->blocked)
1142         {
1143             d->ai->blocktime += lastmillis-d->ai->lastrun;
1144             if(d->ai->blocktime > (d->ai->blockseq+1)*1000)
1145             {
1146                 d->ai->blockseq++;
1147                 switch(d->ai->blockseq)
1148                 {
1149                     case 1: case 2: case 3:
1150                         if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode);
1151                         d->ai->clear(false);
1152                         break;
1153                     case 4: d->ai->reset(true); break;
1154                     case 5: d->ai->reset(false); break;
1155                     case 6: default: suicide(d); return; break; // this is our last resort..
1156                 }
1157             }
1158         }
1159         else d->ai->blocktime = d->ai->blockseq = 0;
1160 
1161         if(d->ai->targnode == d->ai->targlast)
1162         {
1163             d->ai->targtime += lastmillis-d->ai->lastrun;
1164             if(d->ai->targtime > (d->ai->targseq+1)*1000)
1165             {
1166                 d->ai->targseq++;
1167                 switch(d->ai->targseq)
1168                 {
1169                     case 1: case 2: case 3:
1170                         if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode);
1171                         d->ai->clear(false);
1172                         break;
1173                     case 4: d->ai->reset(true); break;
1174                     case 5: d->ai->reset(false); break;
1175                     case 6: default: suicide(d); return; break; // this is our last resort..
1176                 }
1177             }
1178         }
1179         else
1180         {
1181             d->ai->targtime = d->ai->targseq = 0;
1182             d->ai->targlast = d->ai->targnode;
1183         }
1184 
1185         if(d->ai->lasthunt)
1186         {
1187             int millis = lastmillis-d->ai->lasthunt;
1188             if(millis <= 1000) { d->ai->tryreset = false; d->ai->huntseq = 0; }
1189             else if(millis > (d->ai->huntseq+1)*1000)
1190             {
1191                 d->ai->huntseq++;
1192                 switch(d->ai->huntseq)
1193                 {
1194                     case 1: d->ai->reset(true); break;
1195                     case 2: d->ai->reset(false); break;
1196                     case 3: default: suicide(d); return; break; // this is our last resort..
1197                 }
1198             }
1199         }
1200     }
1201 
logic(gameent * d,aistate & b,bool run)1202     void logic(gameent *d, aistate &b, bool run)
1203     {
1204         bool allowmove = canmove(d) && b.type != AI_S_WAIT;
1205         if(d->state != CS_ALIVE || !allowmove) d->stopmoving();
1206         if(d->state == CS_ALIVE)
1207         {
1208             if(allowmove)
1209             {
1210                 if(!request(d, b)) target(d, b, 0, b.idle ? true : false);
1211                 shoot(d, d->ai->target);
1212             }
1213             if(!intermission)
1214             {
1215                 if(d->ragdoll) cleanragdoll(d);
1216                 moveplayer(d, 10, true);
1217                 if(allowmove && !b.idle) timeouts(d, b);
1218                 entities::checkitems(d);
1219                 if(cmode) cmode->checkitems(d);
1220             }
1221         }
1222         else if(d->state == CS_DEAD)
1223         {
1224             if(d->ragdoll) moveragdoll(d);
1225             else if(lastmillis-d->lastpain<2000)
1226             {
1227                 d->move = d->strafe = 0;
1228                 moveplayer(d, 10, false);
1229             }
1230         }
1231         d->attacking = ACT_IDLE;
1232         d->jumping = false;
1233     }
1234 
avoid()1235     void avoid()
1236     {
1237         // guess as to the radius of ai and other critters relying on the avoid set for now
1238         float guessradius = player1->radius;
1239         obstacles.clear();
1240         loopv(players)
1241         {
1242             dynent *d = players[i];
1243             if(d->state != CS_ALIVE) continue;
1244             obstacles.avoidnear(d, d->o.z + d->aboveeye + 1, d->feetpos(), guessradius + d->radius);
1245         }
1246         extern avoidset wpavoid;
1247         obstacles.add(wpavoid);
1248         avoidweapons(obstacles, guessradius);
1249     }
1250 
think(gameent * d,bool run)1251     void think(gameent *d, bool run)
1252     {
1253         // the state stack works like a chain of commands, certain commands simply replace each other
1254         // others spawn new commands to the stack the ai reads the top command from the stack and executes
1255         // it or pops the stack and goes back along the history until it finds a suitable command to execute
1256         bool cleannext = false;
1257         if(d->ai->state.empty()) d->ai->addstate(AI_S_WAIT);
1258         loopvrev(d->ai->state)
1259         {
1260             aistate &c = d->ai->state[i];
1261             if(cleannext)
1262             {
1263                 c.millis = lastmillis;
1264                 c.override = false;
1265                 cleannext = false;
1266             }
1267             if(d->state == CS_DEAD && d->respawned!=d->lifesequence && (!cmode || cmode->respawnwait(d) <= 0) && lastmillis - d->lastpain >= 500)
1268             {
1269                 addmsg(N_TRYSPAWN, "rc", d);
1270                 d->respawned = d->lifesequence;
1271             }
1272             else if(d->state == CS_ALIVE && run)
1273             {
1274                 int result = 0;
1275                 c.idle = 0;
1276                 switch(c.type)
1277                 {
1278                     case AI_S_WAIT: result = dowait(d, c); break;
1279                     case AI_S_DEFEND: result = dodefend(d, c); break;
1280                     case AI_S_PURSUE: result = dopursue(d, c); break;
1281                     case AI_S_INTEREST: result = dointerest(d, c); break;
1282                     default: result = 0; break;
1283                 }
1284                 if(result <= 0)
1285                 {
1286                     if(c.type != AI_S_WAIT)
1287                     {
1288                         switch(result)
1289                         {
1290                             case 0: default: d->ai->removestate(i); cleannext = true; break;
1291                             case -1: i = d->ai->state.length()-1; break;
1292                         }
1293                         continue; // shouldn't interfere
1294                     }
1295                 }
1296             }
1297             logic(d, c, run);
1298             break;
1299         }
1300         if(d->ai->trywipe) d->ai->wipe();
1301         d->ai->lastrun = lastmillis;
1302     }
1303 
drawroute(gameent * d,float amt=1.f)1304     void drawroute(gameent *d, float amt = 1.f)
1305     {
1306         int last = -1;
1307         loopvrev(d->ai->route)
1308         {
1309             if(d->ai->route.inrange(last))
1310             {
1311                 int index = d->ai->route[i], prev = d->ai->route[last];
1312                 if(iswaypoint(index) && iswaypoint(prev))
1313                 {
1314                     waypoint &e = waypoints[index], &f = waypoints[prev];
1315                     vec fr = f.o, dr = e.o;
1316                     fr.z += amt; dr.z += amt;
1317                     particle_flare(fr, dr, 1, PART_STREAK, 0xFFFFFF);
1318                 }
1319             }
1320             last = i;
1321         }
1322         if(aidebug >= 5)
1323         {
1324             vec pos = d->feetpos();
1325             if(d->ai->spot != vec(0, 0, 0)) particle_flare(pos, d->ai->spot, 1, PART_LIGHTNING, 0x00FFFF);
1326             if(iswaypoint(d->ai->targnode))
1327                 particle_flare(pos, waypoints[d->ai->targnode].o, 1, PART_LIGHTNING, 0xFF00FF);
1328             if(iswaypoint(d->lastnode))
1329                 particle_flare(pos, waypoints[d->lastnode].o, 1, PART_LIGHTNING, 0xFFFF00);
1330             loopi(NUMPREVNODES) if(iswaypoint(d->ai->prevnodes[i]))
1331             {
1332                 particle_flare(pos, waypoints[d->ai->prevnodes[i]].o, 1, PART_LIGHTNING, 0x884400);
1333                 pos = waypoints[d->ai->prevnodes[i]].o;
1334             }
1335         }
1336     }
1337 
1338     VAR(showwaypoints, 0, 0, 1);
1339     VAR(showwaypointsradius, 0, 200, 10000);
1340 
1341     const char *stnames[AI_S_MAX] = {
1342         "wait", "defend", "pursue", "interest"
1343     }, *sttypes[AI_T_MAX+1] = {
1344         "none", "node", "player", "affinity", "entity"
1345     };
render()1346     void render()
1347     {
1348         if(aidebug > 1)
1349         {
1350             int total = 0, alive = 0;
1351             loopv(players) if(players[i]->ai) total++;
1352             loopv(players) if(players[i]->state == CS_ALIVE && players[i]->ai)
1353             {
1354                 gameent *d = players[i];
1355                 vec pos = d->abovehead();
1356                 pos.z += 3;
1357                 alive++;
1358                 if(aidebug >= 4) drawroute(d, 4.f*(float(alive)/float(total)));
1359                 if(aidebug >= 3)
1360                 {
1361                     defformatstring(q, "node: %d route: %d (%d)",
1362                         d->lastnode,
1363                         !d->ai->route.empty() ? d->ai->route[0] : -1,
1364                         d->ai->route.length()
1365                     );
1366                     particle_textcopy(pos, q, PART_TEXT, 1);
1367                     pos.z += 2;
1368                 }
1369                 bool top = true;
1370                 loopvrev(d->ai->state)
1371                 {
1372                     aistate &b = d->ai->state[i];
1373                     defformatstring(s, "%s%s (%d ms) %s:%d",
1374                         top ? "\fg" : "\fy",
1375                         stnames[b.type],
1376                         lastmillis-b.millis,
1377                         sttypes[b.targtype+1], b.target
1378                     );
1379                     particle_textcopy(pos, s, PART_TEXT, 1);
1380                     pos.z += 2;
1381                     if(top)
1382                     {
1383                         if(aidebug >= 3) top = false;
1384                         else break;
1385                     }
1386                 }
1387                 if(aidebug >= 3)
1388                 {
1389                     if(d->ai->weappref >= 0 && d->ai->weappref < NUMGUNS)
1390                     {
1391                         particle_textcopy(pos, guns[d->ai->weappref].name, PART_TEXT, 1);
1392                         pos.z += 2;
1393                     }
1394                     gameent *e = getclient(d->ai->enemy);
1395                     if(e)
1396                     {
1397                         particle_textcopy(pos, colorname(e), PART_TEXT, 1);
1398                         pos.z += 2;
1399                     }
1400                 }
1401             }
1402             if(aidebug >= 4)
1403             {
1404                 int cur = 0;
1405                 loopv(obstacles.obstacles)
1406                 {
1407                     const avoidset::obstacle &ob = obstacles.obstacles[i];
1408                     int next = cur + ob.numwaypoints;
1409                     for(; cur < next; cur++)
1410                     {
1411                         int ent = obstacles.waypoints[cur];
1412                         if(iswaypoint(ent))
1413                             regular_particle_splash(PART_EDIT, 2, 40, waypoints[ent].o, 0xFF6600, 1.5f);
1414                     }
1415                     cur = next;
1416                 }
1417             }
1418         }
1419         if(showwaypoints || aidebug >= 6)
1420         {
1421             vector<int> close;
1422             int len = waypoints.length();
1423             if(showwaypointsradius)
1424             {
1425                 findwaypointswithin(camera1->o, 0, showwaypointsradius, close);
1426                 len = close.length();
1427             }
1428             loopi(len)
1429             {
1430                 waypoint &w = waypoints[showwaypointsradius ? close[i] : i];
1431                 loopj(MAXWAYPOINTLINKS)
1432                 {
1433                      int link = w.links[j];
1434                      if(!link) break;
1435                      particle_flare(w.o, waypoints[link].o, 1, PART_STREAK, 0x0000FF);
1436                 }
1437             }
1438 
1439         }
1440     }
1441 }
1442 
1443