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