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