Inexor
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
aiman.hpp
Go to the documentation of this file.
1 //NO INCLUDE GUARD
2 // aiman.h (A.I. manager)
3 // server-side ai manager
4 
5 extern void sendresume(clientinfo *ci);
6 
7 namespace aiman
8 {
9  bool dorefresh = false, botbalance = true;
10 
11  // limit amount of computer controlled players on your server
12  VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS);
13 
14  // use (prefere) bots to balance teams
15  // not accepted my most modded servers
16  VAR(serverbotbalance, 0, 1, 1);
17 
18  // quicksort teams to rank them in scoreboard
20  {
21  static const char * const defaults[2] = { "good", "evil" };
22  loopv(clients)
23  {
24  clientinfo *ci = clients[i];
25  if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue; // skip spectators
26  teamscore *t = nullptr;
27  loopvj(teams)
28  {
29  if(!strcmp(teams[j].team, ci->team))
30  {
31  t = &teams[j];
32  break;
33  }
34  }
35  if(t) t->score++;
36  else teams.add(teamscore(ci->team, 1));
37  }
38  teams.sort(teamscore::compare); // quicksort
39  if(teams.length() < int(sizeof(defaults)/sizeof(defaults[0])))
40  {
41  loopi(sizeof(defaults)/sizeof(defaults[0])) if(teams.htfind(defaults[i]) < 0) teams.add(teamscore(defaults[i], 0));
42  }
43  }
44 
45  // switch team of players to preserve fairness
46  // this algorithm could be improved based on player's statistics!
47  void balanceteams()
48  {
49  vector<teamscore> teams;
50  calcteams(teams);
51  vector<clientinfo *> reassign;
52  loopv(bots) if(bots[i]) reassign.add(bots[i]);
53  while(reassign.length() && teams.length() && teams[0].score > teams.last().score + 1)
54  {
55  teamscore &t = teams.last();
56  clientinfo *bot = nullptr;
57  loopv(reassign) if(reassign[i] && !strcmp(reassign[i]->team, teams[0].team))
58  {
59  bot = reassign.removeunordered(i);
60  teams[0].score--;
61  t.score++;
62  for(int j = teams.length() - 2; j >= 0; j--)
63  {
64  if(teams[j].score >= teams[j+1].score) break;
65  swap(teams[j], teams[j+1]);
66  }
67  break;
68  }
69  if(bot)
70  {
71  if(smode && bot->state.state==CS_ALIVE) smode->changeteam(bot, bot->team, t.team);
72  copystring(bot->team, t.team, MAXTEAMLEN+1);
73  sendf(-1, 1, "riisi", N_SETTEAM, bot->clientnum, bot->team, 0);
74  }
75  else teams.remove(0, 1);
76  }
77  }
78 
79  // return the name of the team with fewest players
80  // used to balance with new connected players
81  const char *chooseteam()
82  {
83  vector<teamscore> teams;
84  calcteams(teams);
85  return teams.length() ? teams.last().team : "";
86  }
87 
89  static inline bool validaiclient(clientinfo *ci)
90  {
91  return ci->clientnum >= 0 && ci->state.aitype == AI_NONE && (ci->state.state!=CS_SPECTATOR || (ci->privilege && !ci->warned));
92  }
93 
94  //
95  clientinfo *findaiclient(clientinfo *exclude = nullptr)
96  {
97  clientinfo *least = nullptr;
98  loopv(clients)
99  {
100  clientinfo *ci = clients[i];
101  if(!validaiclient(ci) || ci==exclude) continue;
102  if(!least || ci->bots.length() < least->bots.length()) least = ci;
103  }
104  return least;
105  }
106 
107  // initialise and add a computer controlled player to the current game
108  bool addai(int skill, int limit)
109  {
110  int numai = 0, cn = -1, maxai = limit >= 0 ? min(limit, MAXBOTS) : MAXBOTS;
111  loopv(bots)
112  {
113  clientinfo *ci = bots[i];
114  if(!ci || ci->ownernum < 0) { if(cn < 0) cn = i; continue; }
115  numai++;
116  }
117  if(numai >= maxai) return false;
118  if(bots.inrange(cn))
119  {
120  clientinfo *ci = bots[cn];
121  if(ci)
122  {
123  // reuse a slot that was going to removed
124  clientinfo *owner = findaiclient();
125  ci->ownernum = owner ? owner->clientnum : -1;
126  if(owner) owner->bots.add(ci);
127  ci->aireinit = 2;
128  dorefresh = true;
129  return true;
130  }
131  }
132  else
133  {
134  cn = bots.length();
135  bots.add(nullptr);
136  }
137  const char *team = m_teammode ? chooseteam() : "";
138  if(!bots[cn]) bots[cn] = new clientinfo;
139  clientinfo *ci = bots[cn];
140 
141  ci->clientnum = MAXCLIENTS + cn;
142  ci->state.aitype = AI_BOT;
143  clientinfo *owner = findaiclient();
144  ci->ownernum = owner ? owner->clientnum : -1;
145  if(owner) owner->bots.add(ci);
146  ci->state.skill = skill <= 0 ? rnd(50) + 51 : clamp(skill, 1, 101);
147  clients.add(ci);
148  ci->state.lasttimeplayed = lastmillis;
149  copystring(ci->name, "bot", MAXNAMELEN+1);
150  ci->state.state = CS_DEAD;
151  copystring(ci->team, team, MAXTEAMLEN+1);
152  copystring(ci->tag, BOTTAG, MAXTAGLEN+1);
153  ci->playermodel = rnd(128);
154  ci->aireinit = 2;
155  ci->connected = true;
156  dorefresh = true;
157  return true;
158  }
159 
160  // delete AI from game
161  void deleteai(clientinfo *ci)
162  {
163  int cn = ci->clientnum - MAXCLIENTS;
164  if(!bots.inrange(cn)) return;
165  if(ci->ownernum >= 0 && !ci->aireinit && smode) smode->leavegame(ci, true);
166  sendf(-1, 1, "ri2", N_CDIS, ci->clientnum);
167  clientinfo *owner = get_client_info(ci->ownernum, false);
168  if(owner) owner->bots.removeobj(ci);
169  clients.removeobj(ci);
170  DELETEP(bots[cn]);
171  dorefresh = true;
172  }
173 
174  // overloaded function which will remove ALL AI from the game
175  bool deleteai()
176  {
177  loopvrev(bots) if(bots[i] && bots[i]->ownernum >= 0)
178  {
179  deleteai(bots[i]);
180  return true;
181  }
182  return false;
183  }
184 
185  // remove a bot and recreate him afterwards
186  void reinitai(clientinfo *ci)
187  {
188  if(ci->ownernum < 0) deleteai(ci);
189  else if(ci->aireinit >= 1)
190  {
191  sendf(-1, 1, "ri6sss", N_INITAI, ci->clientnum, ci->ownernum, ci->state.aitype, ci->state.skill, ci->playermodel, ci->name, ci->team, BOTTAG);
192  if(ci->aireinit == 2)
193  {
194  ci->reassign();
195  if(ci->state.state==CS_ALIVE) sendspawn(ci);
196  else sendresume(ci);
197  }
198  ci->aireinit = 0;
199  }
200  }
201 
202  void shiftai(clientinfo *ci, clientinfo *owner = nullptr)
203  {
204  if(ci->ownernum >= 0 && !ci->aireinit && smode) smode->leavegame(ci, true);
205  clientinfo *prevowner = get_client_info(ci->ownernum, false);
206  if(prevowner) prevowner->bots.removeobj(ci);
207  if(!owner) { ci->aireinit = 0; ci->ownernum = -1; }
208  else if(ci->ownernum != owner->clientnum) { ci->aireinit = 2; ci->ownernum = owner->clientnum; owner->bots.add(ci); }
209  dorefresh = true;
210  }
211 
212  // either schedules a removal, or someone else to assign to
213  void removeai(clientinfo *ci)
214  {
215  loopvrev(ci->bots) shiftai(ci->bots[i], findaiclient(ci));
216  }
217 
218  bool reassignai()
219  {
220  clientinfo *hi = nullptr, *lo = nullptr;
221  loopv(clients)
222  {
223  clientinfo *ci = clients[i];
224  if(!validaiclient(ci)) continue;
225  if(!lo || ci->bots.length() < lo->bots.length()) lo = ci;
226  if(!hi || ci->bots.length() > hi->bots.length()) hi = ci;
227  }
228  if(hi && lo && hi->bots.length() - lo->bots.length() > 1)
229  {
230  loopvrev(hi->bots)
231  {
232  shiftai(hi->bots[i], lo);
233  return true;
234  }
235  }
236  return false;
237  }
238 
239  void checksetup()
240  {
242  loopvrev(bots) if(bots[i]) reinitai(bots[i]);
243  }
244 
245  // clear and remove all ai immediately
246  void clearai()
247  {
248  loopvrev(bots) if(bots[i]) deleteai(bots[i]);
249  }
250 
251  // check if we do even need AI in this game
252  // if the server is empty delete all bots
253  void checkai()
254  {
255  if(!dorefresh) return;
256  dorefresh = false;
257  if(m_botmode && numclients(-1, false, true))
258  {
259  checksetup();
260  while(reassignai());
261  }
262  else clearai();
263  }
264 
265  // master requires to add a bot
266  void reqadd(clientinfo *ci, int skill)
267  {
268  if(!ci->privilege) return;
269  if(!addai(skill, ci->privilege < PRIV_ADMIN ? botlimit : -1)) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to create or assign bot");
270  }
271 
272  // master requires to delete a bot
273  void reqdel(clientinfo *ci)
274  {
275  if(!ci->privilege) return;
276  if(!deleteai()) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to remove any bots");
277  }
278 
279  // set the bot limit and send a message to all clients
280  void setbotlimit(clientinfo *ci, int limit)
281  {
282  if(ci && ci->privilege < PRIV_ADMIN) return;
283  botlimit = clamp(limit, 0, MAXBOTS);
284  dorefresh = true;
285  defformatstring(msg, "bot limit is now %d", *botlimit);
286  sendservmsg(msg);
287  }
288 
289  // enable or disable bot balancing and send a message to all clients
290  void setbotbalance(clientinfo *ci, bool balance)
291  {
292  if(ci && !ci->privilege) return;
293  botbalance = balance ? 1 : 0;
294  dorefresh = true;
295  defformatstring(msg, "bot team balancing is now %s", botbalance ? "enabled" : "disabled");
296  sendservmsg(msg);
297  }
298 
299  // notify bots that map has been changed
300  // force server bot manager to refresh bot balance
301  void changemap()
302  {
303  dorefresh = true;
304  loopv(clients) if(clients[i]->privilege) return;
305  if(botbalance != (serverbotbalance != 0)) setbotbalance(nullptr, serverbotbalance != 0);
306  }
307 
308  // a new human player connected
309  // change/refresh the way computer controlled players think
310  void addclient(clientinfo *ci)
311  {
312  if(ci->state.aitype == AI_NONE) dorefresh = true;
313  }
314 
315  // a human player has changed team
316  // change/refresh the way computer controlled players think
317  void changeteam(clientinfo *ci)
318  {
319  if(ci->state.aitype == AI_NONE) dorefresh = true;
320  }
321 }
void removeai(clientinfo *ci)
Definition: aiman.hpp:213
void reinitai(clientinfo *ci)
Definition: aiman.hpp:186
clientinfo * findaiclient(clientinfo *exclude=nullptr)
Definition: aiman.hpp:95
Vector template.
Definition: cube_vector.hpp:22
#define MAXCLIENTS
Definition: cube_network.hpp:14
Definition: fpsstate.hpp:13
void setbotbalance(clientinfo *ci, bool balance)
Definition: aiman.hpp:290
#define loopvj(v)
Definition: cube_loops.hpp:22
static bool validaiclient(clientinfo *ci)
Definition: aiman.hpp:89
virtual void changeteam(clientinfo *ci, const char *oldteam, const char *newteam)
Definition: gamemode_server.hpp:43
#define BOTTAG
Definition: teaminfo.hpp:17
void checksetup()
Definition: aiman.hpp:239
bool dorefresh
Definition: aiman.hpp:9
int score
Definition: teaminfo.hpp:46
void reqdel(clientinfo *ci)
Definition: aiman.hpp:273
static bool compare(const teamscore &x, const teamscore &y)
Definition: teaminfo.hpp:51
int htfind(const K &key)
similar to find but uses hashtable keys
Definition: cube_vector.hpp:393
C2S send sound signal.
Definition: game_types.hpp:44
T & last()
get the last index
Definition: cube_vector.hpp:131
bool addai(int skill, int limit)
Definition: aiman.hpp:108
VAR(serverbotbalance, 0, 1, 1)
#define m_teammode
Definition: gamemode.hpp:103
void addclient(clientinfo *ci)
Definition: aiman.hpp:310
bool reassignai()
Definition: aiman.hpp:218
void changemap()
Definition: aiman.hpp:301
void clearai()
Definition: aiman.hpp:246
void balanceteams()
Definition: aiman.hpp:47
else loopi(numargs)
Definition: command.cpp:3019
Definition: administration.hpp:8
C2S claim game master.
Definition: game_types.hpp:105
void reqadd(clientinfo *ci, int skill)
Definition: aiman.hpp:266
void sendservmsg(const char *s)
Definition: network_send.cpp:81
clientinfo * get_client_info(int n, bool findbots)
Definition: client_management.cpp:89
int lastmillis
Definition: legacy_time.cpp:14
T & add(const T &x)
Add new index to vector.
Definition: cube_vector.hpp:73
int rnd(int Rmax)
Function alias. Should be replaced inline actually!
Definition: tools.hpp:64
void changeteam(clientinfo *ci)
Definition: aiman.hpp:317
void remove(int i, int n)
remove indices from vector remove ordered?
Definition: cube_vector.hpp:248
void checkai()
Definition: aiman.hpp:253
void deleteai(clientinfo *ci)
Definition: aiman.hpp:161
void sort(F fun, int i=0, int n=-1)
sort the vector using quicksort template and sort criteria F
Definition: cube_vector.hpp:176
T removeunordered(int i)
Definition: cube_vector.hpp:263
const T & min(const inexor::rpc::SharedVar< T > &a, const T &b)
Definition: SharedVar.hpp:210
#define loopvrev(v)
Definition: cube_loops.hpp:24
virtual void leavegame(clientinfo *ci, bool disconnecting=false)
Definition: gamemode_server.hpp:29
Definition: ents.hpp:120
#define MAXBOTS
Definition: ai.hpp:15
int length() const
return size of used memory
Definition: cube_vector.hpp:146
servmode * smode
Definition: server.cpp:292
void t(T x, const char *cmp)
Definition: utilTest.cpp:52
vector< fpsent * > clients
other clients connected to this server
Definition: fps.cpp:581
S2C server ended current game: set game time to 0 ("intermission")
Definition: game_types.hpp:74
#define DELETEP(p)
Delete Pointer, Wrapper around delete, sets pointer to NULL afterwards(!).
Definition: cube_tools.hpp:28
VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS)
const char * chooseteam()
Definition: aiman.hpp:81
void shiftai(clientinfo *ci, clientinfo *owner=nullptr)
Definition: aiman.hpp:202
char * copystring(char *d, const char *s, size_t len)
Definition: cube_tools.hpp:56
#define m_botmode
Definition: gamemode.hpp:118
#define MAXTEAMLEN
Definition: teaminfo.hpp:38
ENetPacket * sendf(int cn, int chan, const char *format,...)
Definition: network_send.cpp:33
bool botbalance
Definition: aiman.hpp:9
void removeobj(const T &o)
remove an element if equal to to given one.
Definition: cube_vector.hpp:286
void sendresume(clientinfo *ci)
Definition: ents.hpp:120
Definition: ents.hpp:120
#define MAXNAMELEN
Definition: teaminfo.hpp:37
void calcteams(vector< teamscore > &teams)
Definition: aiman.hpp:19
some team modes allow more than 2 teams allow sorting multiple teams using team scores ...
Definition: teaminfo.hpp:43
uchar state
Definition: ents.hpp:144
Definition: fpsstate.hpp:13
vector< clientinfo * > bots
Definition: client_management.cpp:52
#define MAXTAGLEN
Definition: teaminfo.hpp:16
const char * team
Definition: teaminfo.hpp:45
int numclients(int exclude, bool nospec, bool noai, bool priv)
List all connected clients (game players)
Definition: client_management.cpp:75
void sendspawn(clientinfo *ci)
Definition: server.cpp:754
#define defformatstring(d,...)
Definition: cube_formatting.hpp:62
void setbotlimit(clientinfo *ci, int limit)
Definition: aiman.hpp:280
#define loopv(v)
Definition: cube_loops.hpp:21
-S- remove a bot from the current game
Definition: game_types.hpp:147