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