1 // server-side ai manager 2 namespace aiman 3 { 4 int dorefresh = 0, oldbotskillmin = -1, oldbotskillmax = -1, oldcoopskillmin = -1, oldcoopskillmax = -1, oldenemyskillmin = -1, oldenemyskillmax = -1, 5 oldbotbalance = -2, oldnumplayers = -1, oldbotlimit = -1, oldbotoffset = 0, oldenemylimit = -1; 6 float oldbotbalancescale = -1; 7 botrnd(clientinfo * ci,int t,int m)8 int botrnd(clientinfo *ci, int t, int m) 9 { 10 if(G(botcolourseed)&t) return ci->colour%m; 11 return rnd(m); 12 } 13 clientbotscore(clientinfo * ci)14 float clientbotscore(clientinfo *ci) 15 { 16 return (ci->bots.length() * G(aihostnum)) + (ci->ping * G(aihostping)); 17 } 18 19 clientinfo *findaiclient(clientinfo *exclude = NULL) 20 { 21 clientinfo *least = NULL; loopv(clients)22 loopv(clients) 23 { 24 clientinfo *ci = clients[i]; 25 if(ci->actortype > A_PLAYER || !ci->online || !ci->isready() || ci == exclude) continue; 26 if(!least || clientbotscore(ci) < clientbotscore(least)) least = ci; 27 } 28 return least; 29 } 30 31 int getlimit(int type = A_BOT) 32 { 33 if(type >= A_ENEMY) return G(enemylimit); 34 if(m_coop(gamemode, mutators)) 35 { 36 int people = numclients(-1, true, -1), numt = numteams(gamemode, mutators)-1; 37 return min(int(ceilf(people*numt*G(coopbalance))), MAXAI); 38 } 39 return G(botlimit); 40 } 41 getskillrange(clientinfo * ci,int & n,int & m)42 void getskillrange(clientinfo *ci, int &n, int &m) 43 { 44 switch(ci->actortype) 45 { 46 case A_BOT: 47 { 48 #define BOTSKILL(a) \ 49 { \ 50 m = max(G(a##skillmax), G(a##skillmin)); \ 51 n = min(G(a##skillmin), m); \ 52 if(ci->deaths != 0 && G(a##skilldeaths) != 0) \ 53 { \ 54 int amt = int(G(a##skilldeaths)*ci->deaths); \ 55 m += amt; \ 56 n += amt; \ 57 } \ 58 if(ci->frags != 0 && G(a##skillfrags) != 0) \ 59 { \ 60 int amt = int(G(a##skillfrags)*ci->frags); \ 61 m += amt; \ 62 n += amt; \ 63 } \ 64 } 65 if(m_coop(gamemode, mutators)) BOTSKILL(coop) 66 else BOTSKILL(bot) 67 break; 68 } 69 default: 70 m = max(G(enemyskillmax), G(enemyskillmin)); 71 n = min(G(enemyskillmin), m); 72 break; 73 } 74 m = clamp(m, 1, 101); 75 n = clamp(n, 1, m); 76 } 77 setskill(clientinfo * ci,bool init)78 void setskill(clientinfo *ci, bool init) 79 { 80 int n = 1, m = 100; 81 getskillrange(ci, n, m); 82 if(init || ci->skill > m || ci->skill < n) 83 { // needs re-skilling 84 ci->skill = (m != n ? botrnd(ci, 2, m-n) + n + 1 : m); 85 if(!init && !ci->aireinit) ci->aireinit = 1; 86 } 87 } 88 addai(int type,int ent)89 bool addai(int type, int ent) 90 { 91 int count = 0, limit = getlimit(type); 92 if(!limit) return false; 93 loopv(clients) if(clients[i]->actortype == type) 94 { 95 clientinfo *ci = clients[i]; 96 if(ci->ownernum < 0) 97 { // reuse a slot that was going to removed 98 clientinfo *owner = findaiclient(); 99 if(!owner) return false; 100 ci->ownernum = owner->clientnum; 101 owner->bots.add(ci); 102 ci->aireinit = 1; 103 ci->actortype = type; 104 ci->spawnpoint = ent; 105 return true; 106 } 107 if(++count >= limit) return false; 108 } 109 int cn = addclient(ST_REMOTE); 110 if(cn >= 0) 111 { 112 clientinfo *ci = (clientinfo *)getinfo(cn); 113 if(ci) 114 { 115 ci->clientnum = cn; 116 clientinfo *owner = findaiclient(); 117 ci->ownernum = owner ? owner->clientnum : -1; 118 if(owner) owner->bots.add(ci); 119 ci->aireinit = 2; 120 ci->actortype = type; 121 ci->spawnpoint = ent; 122 clients.add(ci); 123 ci->lasttimeplayed = totalmillis; 124 ci->colour = rnd(0xFFFFFF); 125 ci->model = botrnd(ci, 4, PLAYERTYPES); 126 ci->pattern = botrnd(ci, 4, PLAYERPATTERNS); 127 setskill(ci, true); 128 copystring(ci->name, AA(ci->actortype, vname), MAXNAMELEN); 129 ci->loadweap.shrink(0); 130 if(ci->actortype == A_BOT) 131 { 132 const char *list = ci->model ? G(botfemalenames) : G(botmalenames); 133 int len = listlen(list); 134 if(len > 0) 135 { 136 int r = botrnd(ci, 1, len); 137 char *name = indexlist(list, r); 138 if(name) 139 { 140 if(*name) 141 { 142 copystring(ci->name, name, MAXNAMELEN); 143 if(G(botrandomcase) && rnd(G(botrandomcase))) 144 ci->name[0] = iscubeupper(ci->name[0]) ? cubelower(ci->name[0]) : cubeupper(ci->name[0]); 145 } 146 delete[] name; 147 } 148 } 149 ci->setvanity(ci->model ? G(botfemalevanities) : G(botmalevanities)); 150 static vector<int> weaplist; 151 weaplist.shrink(0); 152 loopi(W_LOADOUT) weaplist.add(W_OFFSET+i); 153 while(!weaplist.empty()) 154 { 155 int iter = botrnd(ci, 8, weaplist.length()); 156 ci->loadweap.add(weaplist[iter]); 157 weaplist.remove(iter); 158 } 159 } 160 ci->state = CS_DEAD; 161 ci->team = type == A_BOT ? T_NEUTRAL : T_ENEMY; 162 ci->online = ci->connected = ci->ready = true; 163 return true; 164 } 165 delclient(cn); 166 } 167 return false; 168 } 169 deleteai(clientinfo * ci)170 void deleteai(clientinfo *ci) 171 { 172 if(ci->actortype == A_PLAYER) return; 173 int cn = ci->clientnum; 174 loopv(clients) if(clients[i] != ci) 175 { 176 loopvk(clients[i]->fraglog) if(clients[i]->fraglog[k] == ci->clientnum) 177 clients[i]->fraglog.remove(k--); 178 } 179 if(smode) smode->leavegame(ci, true); 180 mutate(smuts, mut->leavegame(ci, true)); 181 savescore(ci); 182 sendf(-1, 1, "ri3", N_DISCONNECT, cn, DISC_NONE); 183 clientinfo *owner = (clientinfo *)getinfo(ci->ownernum); 184 if(owner) owner->bots.removeobj(ci); 185 clients.removeobj(ci); 186 delclient(cn); 187 dorefresh = max(dorefresh, 1); 188 } 189 delai(int type,bool skip)190 bool delai(int type, bool skip) 191 { 192 bool retry = false; 193 loopvrev(clients) if(clients[i]->actortype == type && clients[i]->ownernum >= 0) 194 { 195 if(!skip || clients[i]->state == CS_DEAD || clients[i]->state == CS_WAITING) 196 { 197 deleteai(clients[i]); 198 return true; 199 } 200 else if(skip && !retry) retry = true; 201 } 202 if(skip && retry) delai(type, false); 203 return false; 204 } 205 reinitai(clientinfo * ci)206 void reinitai(clientinfo *ci) 207 { 208 if(ci->actortype == A_PLAYER) return; 209 if(ci->ownernum < 0) deleteai(ci); 210 else if(ci->aireinit >= 1) 211 { 212 if(ci->aireinit == 2) loopk(W_MAX) loopj(2) ci->weapshots[k][j].reset(); 213 sendf(-1, 1, "ri6si4siv", N_INITAI, ci->clientnum, ci->ownernum, ci->actortype, ci->spawnpoint, ci->skill, ci->name, ci->team, ci->colour, ci->model, ci->pattern, ci->vanity, ci->loadweap.length(), ci->loadweap.length(), ci->loadweap.getbuf()); 214 if(ci->aireinit == 2) 215 { 216 waiting(ci, DROP_RESET); 217 if(smode) smode->entergame(ci); 218 mutate(smuts, mut->entergame(ci)); 219 } 220 ci->aireinit = 0; 221 } 222 } 223 224 void shiftai(clientinfo *ci, clientinfo *owner = NULL) 225 { 226 clientinfo *prevowner = (clientinfo *)getinfo(ci->ownernum); 227 if(prevowner) prevowner->bots.removeobj(ci); 228 if(!owner) { ci->aireinit = 0; ci->ownernum = -1; } 229 else if(ci->ownernum != owner->clientnum) { ci->aireinit = 1; ci->ownernum = owner->clientnum; owner->bots.add(ci); } 230 } 231 removeai(clientinfo * ci,bool complete)232 void removeai(clientinfo *ci, bool complete) 233 { // either schedules a removal, or someone else to assign to 234 loopvrev(ci->bots) shiftai(ci->bots[i], complete ? NULL : findaiclient(ci)); 235 } 236 reassignai(clientinfo * exclude)237 bool reassignai(clientinfo *exclude) 238 { 239 clientinfo *hi = NULL, *lo = NULL; 240 loopv(clients) 241 { 242 clientinfo *ci = clients[i]; 243 if(ci->clientnum < 0 || ci->actortype > A_PLAYER || !ci->isready() || ci == exclude) 244 continue; 245 if(!lo || clientbotscore(ci) < clientbotscore(lo)) lo = ci; 246 if(!hi || clientbotscore(hi) > clientbotscore(hi)) hi = ci; 247 } 248 if(hi && lo && clientbotscore(hi) - clientbotscore(lo) > G(aihostshift)) 249 { 250 loopvrev(hi->bots) 251 { 252 shiftai(hi->bots[i], lo); 253 return true; 254 } 255 } 256 return false; 257 } 258 checksetup()259 void checksetup() 260 { 261 int numbots = 0, numenemies = 0, blimit = getlimit(A_BOT), elimit = getlimit(A_ENEMY); 262 loopv(clients) if(clients[i]->actortype > A_PLAYER && clients[i]->ownernum >= 0) 263 { 264 clientinfo *ci = clients[i]; 265 if(ci->actortype == A_BOT && ++numbots >= blimit) { shiftai(ci, NULL); continue; } 266 if(ci->actortype >= A_ENEMY && ++numenemies >= elimit) { shiftai(ci, NULL); continue; } 267 setskill(ci); 268 } 269 270 int people = numclients(-1, true, -1), balance = people, numt = numteams(gamemode, mutators); 271 if(m_coop(gamemode, mutators)) 272 { 273 numt--; // filter out the human team 274 balance += int(ceilf(people*numt*G(coopbalance))); 275 balance += G(botoffset)*numt; 276 } 277 else if(m_bots(gamemode) && blimit > 0) 278 { 279 int bb = m_botbal(gamemode, mutators); 280 switch(bb) 281 { 282 case -1: balance = max(people, G(numplayers)); break; // use distributed map players 283 case 0: balance = 0; break; // no bots 284 default: balance = max(people, bb); break; // balance to at least this 285 } 286 balance += G(botoffset)*numt; 287 if(!m_duke(gamemode, mutators) && G(botbalancescale) != 1) balance = int(ceilf(balance*G(botbalancescale))); 288 if(balance > 0 && m_team(gamemode, mutators)) 289 { // skew this if teams are unbalanced 290 int plrs[T_NUM] = {0}, highest = -1, bots = 0, offset = balance%numt; // we do this because humans can unbalance in odd ways 291 if(offset) balance += numt-offset; 292 loopv(clients) if(clients[i]->team >= T_FIRST && isteam(gamemode, mutators, clients[i]->team, T_FIRST)) 293 { 294 if(clients[i]->actortype == A_BOT) 295 { 296 bots++; 297 continue; 298 } 299 int team = clients[i]->team-T_FIRST; 300 plrs[team]++; 301 if(highest < 0 || plrs[team] > plrs[highest]) highest = team; 302 } 303 if(highest >= 0) loopi(numt) if(i != highest && plrs[i] < plrs[highest]) loopj(plrs[highest]-plrs[i]) 304 { 305 if(bots > 0) bots--; 306 else balance++; 307 } 308 } 309 } 310 else balance += G(botoffset)*numt; 311 int bots = balance-people; 312 if(bots > blimit) balance -= bots-blimit; 313 if(numt > 1 && (balance%numt) != 0) balance -= balance%numt; 314 if(balance > 0) 315 { 316 while(numclients(-1, true, A_BOT) < balance) if(!addai(A_BOT)) break; 317 while(numclients(-1, true, A_BOT) > balance) if(!delai(A_BOT)) break; 318 if(m_team(gamemode, mutators)) loopvrev(clients) 319 { 320 clientinfo *ci = clients[i]; 321 if(ci->actortype == A_BOT && ci->ownernum >= 0) 322 { 323 int teamb = chooseteam(ci, ci->team); 324 if(ci->team != teamb) setteam(ci, teamb, TT_RESETX); 325 } 326 } 327 } 328 else clearai(1); 329 } 330 checkenemies()331 void checkenemies() 332 { 333 if(m_onslaught(gamemode, mutators)) 334 { 335 loopvj(sents) if(sents[j].type == ACTOR) 336 { 337 if(!checkmapvariant(sents[j].attrs[enttype[sents[j].type].mvattr])) continue; 338 if(sents[j].attrs[0] < 0 || sents[j].attrs[0] >= A_TOTAL || gamemillis < sents[j].millis) continue; 339 if(sents[j].attrs[5] && sents[j].attrs[5] != triggerid) continue; 340 if(!m_check(sents[j].attrs[3], sents[j].attrs[4], gamemode, mutators)) continue; 341 if(sents[j].attrs[0]+A_ENEMY == A_TURRET && m_insta(gamemode, mutators)) continue; 342 int count = 0, numenemies = 0; 343 loopvrev(clients) if(clients[i]->actortype >= A_ENEMY) 344 { 345 if(clients[i]->spawnpoint == j) 346 { 347 count++; 348 if(count > G(enemybalance)) 349 { 350 deleteai(clients[i]); 351 count--; 352 continue; 353 } 354 } 355 numenemies++; 356 } 357 if(numenemies < G(enemylimit) && count < G(enemybalance)) 358 { 359 int amt = min(G(enemybalance)-count, G(enemylimit)-numenemies); 360 loopk(amt) addai(sents[j].attrs[0]+A_ENEMY, j); 361 sents[j].millis = gamemillis+G(enemyspawntime); 362 } 363 } 364 } 365 else clearai(2); 366 } 367 clearai(int type)368 void clearai(int type) 369 { // clear and remove all ai immediately 370 loopvrev(clients) if(!type || (type == 2 ? clients[i]->actortype >= A_ENEMY : clients[i]->actortype == A_BOT)) 371 deleteai(clients[i]); 372 } 373 poke()374 void poke() 375 { 376 dorefresh = max(dorefresh, G(airefreshdelay)); 377 } 378 checkai()379 void checkai() 380 { 381 if(!m_demo(gamemode) && numclients()) 382 { 383 if(canplay()) 384 { 385 if(!dorefresh) 386 { 387 #define checkold(n) if(old##n != G(n)) { dorefresh = -1; old##n = G(n); } 388 if(m_onslaught(gamemode, mutators)) 389 { 390 checkold(enemyskillmin); 391 checkold(enemyskillmax); 392 } 393 if(m_coop(gamemode, mutators)) 394 { 395 checkold(coopskillmin); 396 checkold(coopskillmax); 397 } 398 else 399 { 400 checkold(botskillmin); 401 checkold(botskillmax); 402 checkold(botbalancescale); 403 } 404 checkold(botlimit); 405 checkold(botoffset); 406 checkold(enemylimit); 407 checkold(numplayers); 408 int bb = m_botbal(gamemode, mutators); 409 if(oldbotbalance != bb) { dorefresh = 1; oldbotbalance = bb; } 410 } 411 if(dorefresh) 412 { 413 if(dorefresh > 0) dorefresh -= curtime; 414 if(dorefresh <= 0) 415 { 416 if(canbalancenow()) 417 { 418 dorefresh = 0; 419 checksetup(); 420 } 421 else dorefresh = -1; 422 } 423 } 424 checkenemies(); 425 loopvrev(clients) if(clients[i]->actortype > A_PLAYER) reinitai(clients[i]); 426 while(true) if(!reassignai()) break; 427 } 428 } 429 else clearai(); 430 } 431 } 432