]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/bot/scripting.qc
Merge remote branch 'origin/terencehill/newpanelhud-dom_points' into terencehill...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / scripting.qc
1 .float bot_cmdqueuebuf_allocated;
2 .float bot_cmdqueuebuf;
3 .float bot_cmdqueuebuf_start;
4 .float bot_cmdqueuebuf_end;
5
6 void bot_clearqueue(entity bot)
7 {
8         if(!bot.bot_cmdqueuebuf_allocated)
9                 error("clearqueue but no queue allocated");
10         buf_del(bot.bot_cmdqueuebuf);
11         bot.bot_cmdqueuebuf_allocated = FALSE;
12         dprint("bot ", bot.netname, " queue cleared\n");
13 }
14
15 void bot_queuecommand(entity bot, string cmdstring)
16 {
17         if(!bot.bot_cmdqueuebuf_allocated)
18         {
19                 bot.bot_cmdqueuebuf = buf_create();
20                 bot.bot_cmdqueuebuf_allocated = TRUE;
21         }
22
23         bufstr_set(bot.bot_cmdqueuebuf, bot.bot_cmdqueuebuf_end, cmdstring);
24         bot.bot_cmdqueuebuf_end += 1;
25 }
26
27 void bot_dequeuecommand(entity bot, float idx)
28 {
29         if(!bot.bot_cmdqueuebuf_allocated)
30                 error("dequeuecommand but no queue allocated");
31         if(idx < bot.bot_cmdqueuebuf_start)
32                 error("dequeueing a command in the past");
33         if(idx >= bot.bot_cmdqueuebuf_end)
34                 error("dequeueing a command in the future");
35         bufstr_set(bot.bot_cmdqueuebuf, idx, "");
36         if(idx == bot.bot_cmdqueuebuf_start)
37                 bot.bot_cmdqueuebuf_start += 1;
38         if(bot.bot_cmdqueuebuf_start >= bot.bot_cmdqueuebuf_end)
39                 bot_clearqueue(bot);
40 }
41
42 string bot_readcommand(entity bot, float idx)
43 {
44         if(!bot.bot_cmdqueuebuf_allocated)
45                 error("readcommand but no queue allocated");
46         if(idx < bot.bot_cmdqueuebuf_start)
47                 error("reading a command in the past");
48         if(idx >= bot.bot_cmdqueuebuf_end)
49                 error("reading a command in the future");
50         return bufstr_get(bot.bot_cmdqueuebuf, idx);
51 }
52
53 float bot_havecommand(entity bot, float idx)
54 {
55         if(!bot.bot_cmdqueuebuf_allocated)
56                 return 0;
57         if(idx < bot.bot_cmdqueuebuf_start)
58                 return 0;
59         if(idx >= bot.bot_cmdqueuebuf_end)
60                 return 0;
61         return 1;
62 }
63
64 #define MAX_BOT_PLACES 4
65 .float bot_places_count;
66 .entity bot_places[MAX_BOT_PLACES]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bot_places);
67 .string bot_placenames[MAX_BOT_PLACES]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(bot_placenames);
68 entity bot_getplace(string placename)
69 {
70         entity e;
71         if(substring(placename, 0, 1) == "@")
72         {
73                 float i, p;
74                 placename = substring(placename, 1, -1);
75                 string s, s2;
76                 for(i = 0; i < self.bot_places_count; ++i)
77                         if(self.(bot_placenames[i]) == placename)
78                                 return self.(bot_places[i]);
79                 // now: i == self.bot_places_count
80                 s = s2 = cvar_string(placename);
81                 p = strstrofs(s2, " ", 0);
82                 if(p >= 0)
83                 {
84                         s = substring(s2, 0, p);
85                         //print("places: ", placename, " -> ", cvar_string(placename), "\n");
86                         cvar_set(placename, strcat(substring(s2, p+1, -1), " ", s));
87                         //print("places: ", placename, " := ", cvar_string(placename), "\n");
88                 }
89                 e = find(world, targetname, s);
90                 if(!e)
91                         print("invalid place ", s, "\n");
92                 if(i < MAX_BOT_PLACES)
93                 {
94                         self.(bot_placenames[i]) = strzone(placename);
95                         self.(bot_places[i]) = e;
96                         self.bot_places_count += 1;
97                 }
98                 return e;
99         }
100         else
101         {
102                 e = find(world, targetname, placename);
103                 if(!e)
104                         print("invalid place ", placename, "\n");
105                 return e;
106         }
107 }
108
109
110 // NOTE: New commands should be added here. Do not forget to update BOT_CMD_COUNTER
111 #define BOT_CMD_NULL                    0
112 #define BOT_CMD_PAUSE                   1
113 #define BOT_CMD_CONTINUE                2
114 #define BOT_CMD_WAIT                    3
115 #define BOT_CMD_TURN                    4
116 #define BOT_CMD_MOVETO                  5
117 #define BOT_CMD_RESETGOAL               6       // Not implemented yet
118 #define BOT_CMD_CC                      7
119 #define BOT_CMD_IF                      8
120 #define BOT_CMD_ELSE                    9
121 #define BOT_CMD_FI                      10
122 #define BOT_CMD_RESETAIM                11
123 #define BOT_CMD_AIM                     12
124 #define BOT_CMD_PRESSKEY                13
125 #define BOT_CMD_RELEASEKEY              14
126 #define BOT_CMD_SELECTWEAPON            15
127 #define BOT_CMD_IMPULSE                 16
128 #define BOT_CMD_WAIT_UNTIL              17
129 #define BOT_CMD_MOVETOTARGET    18
130 #define BOT_CMD_AIMTARGET       19
131 #define BOT_CMD_BARRIER         20
132 #define BOT_CMD_CONSOLE                 21
133 #define BOT_CMD_SOUND                   22
134 #define BOT_CMD_WHILE                   23      // TODO: Not implemented yet
135 #define BOT_CMD_WEND                    24      // TODO: Not implemented yet
136 #define BOT_CMD_CHASE                   25      // TODO: Not implemented yet
137
138 #define BOT_CMD_COUNTER                 23      // Update this value if you add/remove a command
139
140 // NOTE: Following commands should be implemented on the bot ai
141 //               If a new command should be handled by the target ai(s) please declare it here
142 .float(vector) cmd_moveto;
143 .float() cmd_resetgoal;
144
145 //
146 #define BOT_CMD_PARAMETER_NONE          0
147 #define BOT_CMD_PARAMETER_FLOAT         1
148 #define BOT_CMD_PARAMETER_STRING        2
149 #define BOT_CMD_PARAMETER_VECTOR        3
150
151 float bot_cmds_initialized;
152 float bot_cmd_parm_type[BOT_CMD_COUNTER];
153 string bot_cmd_string[BOT_CMD_COUNTER];
154
155 // Bots command queue
156 entity bot_cmd; // global current command
157 .entity bot_cmd_current; // current command of this bot
158
159 .float is_bot_cmd;                      // Tells if the entity is a bot command
160 .float bot_cmd_index;                   // Position of the command in the queue
161 .float bot_cmd_type;                    // If of command (see the BOT_CMD_* defines)
162 .float bot_cmd_parm_float;              // Field to store a float parameter
163 .string bot_cmd_parm_string;            // Field to store a string parameter
164 .vector bot_cmd_parm_vector;            // Field to store a vector parameter
165
166 float bot_barriertime;
167 .float bot_barrier;
168
169 .float bot_cmd_execution_index;         // Position in the queue of the command to be executed
170
171 // Initialize global commands list
172 // NOTE: New commands should be initialized here
173 void bot_commands_init()
174 {
175         bot_cmd_string[BOT_CMD_NULL] = "";
176         bot_cmd_parm_type[BOT_CMD_NULL] = BOT_CMD_PARAMETER_NONE;
177
178         bot_cmd_string[BOT_CMD_PAUSE] = "pause";
179         bot_cmd_parm_type[BOT_CMD_PAUSE] = BOT_CMD_PARAMETER_NONE;
180
181         bot_cmd_string[BOT_CMD_CONTINUE] = "continue";
182         bot_cmd_parm_type[BOT_CMD_CONTINUE] = BOT_CMD_PARAMETER_NONE;
183
184         bot_cmd_string[BOT_CMD_WAIT] = "wait";
185         bot_cmd_parm_type[BOT_CMD_WAIT] = BOT_CMD_PARAMETER_FLOAT;
186
187         bot_cmd_string[BOT_CMD_TURN] = "turn";
188         bot_cmd_parm_type[BOT_CMD_TURN] = BOT_CMD_PARAMETER_FLOAT;
189
190         bot_cmd_string[BOT_CMD_MOVETO] = "moveto";
191         bot_cmd_parm_type[BOT_CMD_MOVETO] = BOT_CMD_PARAMETER_VECTOR;
192
193         bot_cmd_string[BOT_CMD_MOVETOTARGET] = "movetotarget";
194         bot_cmd_parm_type[BOT_CMD_MOVETOTARGET] = BOT_CMD_PARAMETER_STRING;
195
196         bot_cmd_string[BOT_CMD_RESETGOAL] = "resetgoal";
197         bot_cmd_parm_type[BOT_CMD_RESETGOAL] = BOT_CMD_PARAMETER_NONE;
198
199         bot_cmd_string[BOT_CMD_CC] = "cc";
200         bot_cmd_parm_type[BOT_CMD_CC] = BOT_CMD_PARAMETER_STRING;
201
202         bot_cmd_string[BOT_CMD_IF] = "if";
203         bot_cmd_parm_type[BOT_CMD_IF] = BOT_CMD_PARAMETER_STRING;
204
205         bot_cmd_string[BOT_CMD_ELSE] = "else";
206         bot_cmd_parm_type[BOT_CMD_ELSE] = BOT_CMD_PARAMETER_NONE;
207
208         bot_cmd_string[BOT_CMD_FI] = "fi";
209         bot_cmd_parm_type[BOT_CMD_FI] = BOT_CMD_PARAMETER_NONE;
210
211         bot_cmd_string[BOT_CMD_RESETAIM] = "resetaim";
212         bot_cmd_parm_type[BOT_CMD_RESETAIM] = BOT_CMD_PARAMETER_NONE;
213
214         bot_cmd_string[BOT_CMD_AIM] = "aim";
215         bot_cmd_parm_type[BOT_CMD_AIM] = BOT_CMD_PARAMETER_STRING;
216
217         bot_cmd_string[BOT_CMD_AIMTARGET] = "aimtarget";
218         bot_cmd_parm_type[BOT_CMD_AIMTARGET] = BOT_CMD_PARAMETER_STRING;
219
220         bot_cmd_string[BOT_CMD_PRESSKEY] = "presskey";
221         bot_cmd_parm_type[BOT_CMD_PRESSKEY] = BOT_CMD_PARAMETER_STRING;
222
223         bot_cmd_string[BOT_CMD_RELEASEKEY] = "releasekey";
224         bot_cmd_parm_type[BOT_CMD_RELEASEKEY] = BOT_CMD_PARAMETER_STRING;
225
226         bot_cmd_string[BOT_CMD_SELECTWEAPON] = "selectweapon";
227         bot_cmd_parm_type[BOT_CMD_SELECTWEAPON] = BOT_CMD_PARAMETER_FLOAT;
228
229         bot_cmd_string[BOT_CMD_IMPULSE] = "impulse";
230         bot_cmd_parm_type[BOT_CMD_IMPULSE] = BOT_CMD_PARAMETER_FLOAT;
231
232         bot_cmd_string[BOT_CMD_WAIT_UNTIL] = "wait_until";
233         bot_cmd_parm_type[BOT_CMD_WAIT_UNTIL] = BOT_CMD_PARAMETER_FLOAT;
234
235         bot_cmd_string[BOT_CMD_BARRIER] = "barrier";
236         bot_cmd_parm_type[BOT_CMD_BARRIER] = BOT_CMD_PARAMETER_NONE;
237
238         bot_cmd_string[BOT_CMD_CONSOLE] = "console";
239         bot_cmd_parm_type[BOT_CMD_CONSOLE] = BOT_CMD_PARAMETER_STRING;
240
241         bot_cmd_string[BOT_CMD_SOUND] = "sound";
242         bot_cmd_parm_type[BOT_CMD_SOUND] = BOT_CMD_PARAMETER_STRING;
243
244         bot_cmds_initialized = TRUE;
245 }
246
247 // Returns first bot with matching name
248 entity find_bot_by_name(string name)
249 {
250         local entity bot;
251
252         bot = findchainflags(flags, FL_CLIENT);
253         while (bot)
254         {
255                 if(clienttype(bot) == CLIENTTYPE_BOT)
256                 if(bot.netname==name)
257                         return bot;
258
259                 bot = bot.chain;
260         }
261
262         return world;
263 }
264
265 // Returns a bot by number on list
266 entity find_bot_by_number(float number)
267 {
268         local entity bot;
269         local float c;
270
271         if(!number)
272                 return world;
273
274         bot = findchainflags(flags, FL_CLIENT);
275         while (bot)
276         {
277                 if(clienttype(bot) == CLIENTTYPE_BOT)
278                 {
279                         if(++c==number)
280                                 return bot;
281                 }
282                 bot = bot.chain;
283         }
284
285         return world;
286 }
287
288 float bot_decodecommand(string cmdstring)
289 {
290         local float cmd_parm_type, i;
291         float sp;
292         string parm;
293
294         sp = strstrofs(cmdstring, " ", 0);
295         if(sp < 0)
296         {
297                 parm = "";
298         }
299         else
300         {
301                 parm = substring(cmdstring, sp + 1, -1);
302                 cmdstring = substring(cmdstring, 0, sp);
303         }
304
305         if(!bot_cmds_initialized)
306                 bot_commands_init();
307
308         for(i=1;i<BOT_CMD_COUNTER;++i)
309         {
310                 if(bot_cmd_string[i]!=cmdstring)
311                         continue;
312
313                 cmd_parm_type = bot_cmd_parm_type[i];
314
315                 if(cmd_parm_type!=BOT_CMD_PARAMETER_NONE&&parm=="")
316                 {
317                         print("ERROR: A parameter is required for this command\n");
318                         return 0;
319                 }
320
321                 // Load command into queue
322                 bot_cmd.bot_cmd_type = i;
323
324                 // Attach parameter
325                 switch(cmd_parm_type)
326                 {
327                         case BOT_CMD_PARAMETER_FLOAT:
328                                 bot_cmd.bot_cmd_parm_float = stof(parm);
329                                 break;
330                         case BOT_CMD_PARAMETER_STRING:
331                                 if(bot_cmd.bot_cmd_parm_string)
332                                         strunzone(bot_cmd.bot_cmd_parm_string);
333                                 bot_cmd.bot_cmd_parm_string = strzone(parm);
334                                 break;
335                         case BOT_CMD_PARAMETER_VECTOR:
336                                 bot_cmd.bot_cmd_parm_vector = stov(parm);
337                                 break;
338                         default:
339                                 break;
340                 }
341                 return 1;
342         }
343         print("ERROR: No such command '", cmdstring, "'\n");
344         return 0;
345 }
346
347 void bot_cmdhelp(string scmd)
348 {
349         local float i, ntype;
350         local string stype;
351
352         if(!bot_cmds_initialized)
353                 bot_commands_init();
354
355         for(i=1;i<BOT_CMD_COUNTER;++i)
356         {
357                 if(bot_cmd_string[i]!=scmd)
358                         continue;
359
360                 ntype = bot_cmd_parm_type[i];
361
362                 switch(ntype)
363                 {
364                         case BOT_CMD_PARAMETER_FLOAT:
365                                 stype = "float number";
366                                 break;
367                         case BOT_CMD_PARAMETER_STRING:
368                                 stype = "string";
369                                 break;
370                         case BOT_CMD_PARAMETER_VECTOR:
371                                 stype = "vector";
372                                 break;
373                         default:
374                                 stype = "none";
375                                 break;
376                 }
377
378                 print(strcat("Command: ",bot_cmd_string[i],"\nParameter: <",stype,"> \n"));
379
380                 print("Description: ");
381                 switch(i)
382                 {
383                         case BOT_CMD_PAUSE:
384                                 print("Stops the bot completely. Any command other than 'continue' will be ignored.");
385                                 break;
386                         case BOT_CMD_CONTINUE:
387                                 print("Disable paused status");
388                                 break;
389                         case BOT_CMD_WAIT:
390                                 print("Pause command parsing and bot ai for N seconds. Pressed key will remain pressed");
391                                 break;
392                         case BOT_CMD_WAIT_UNTIL:
393                                 print("Pause command parsing and bot ai until time is N from the last barrier. Pressed key will remain pressed");
394                                 break;
395                         case BOT_CMD_BARRIER:
396                                 print("Waits till all bots that have a command queue reach this command. Pressed key will remain pressed");
397                                 break;
398                         case BOT_CMD_TURN:
399                                 print("Look to the right or left N degrees. For turning to the left use positive numbers.");
400                                 break;
401                         case BOT_CMD_MOVETO:
402                                 print("Walk to an specific coordinate on the map. Usage: moveto \"x y z\"");
403                                 break;
404                         case BOT_CMD_MOVETOTARGET:
405                                 print("Walk to the specific target on the map");
406                                 break;
407                         case BOT_CMD_RESETGOAL:
408                                 print("Resets the goal stack");
409                                 break;
410                         case BOT_CMD_CC:
411                                 print("Execute client command. Examples: cc \"say something\"; cc god; cc \"name newnickname\"; cc kill;");
412                                 break;
413                         case BOT_CMD_IF:
414                                 print("Perform simple conditional execution.\n");
415                                 print("Syntax: \n");
416                                 print("        sv_cmd .. if \"condition\"\n");
417                                 print("        sv_cmd ..        <instruction if true>\n");
418                                 print("        sv_cmd ..        <instruction if true>\n");
419                                 print("        sv_cmd .. else\n");
420                                 print("        sv_cmd ..        <instruction if false>\n");
421                                 print("        sv_cmd ..        <instruction if false>\n");
422                                 print("        sv_cmd .. fi\n");
423                                 print("Conditions: a=b, a>b, a<b, a\t\t(spaces not allowed)\n");
424                                 print("            Values in conditions can be numbers, cvars in the form cvar.cvar_string or special fields\n");
425                                 print("Fields: health, speed, flagcarrier\n");
426                                 print("Examples: if health>50; if health>cvar.g_balance_laser_primary_damage; if flagcarrier;");
427                                 break;
428                         case BOT_CMD_RESETAIM:
429                                 print("Points the aim to the coordinates x,y 0,0");
430                                 break;
431                         case BOT_CMD_AIM:
432                                 print("Move the aim x/y (horizontal/vertical) degrees relatives to the bot\n");
433                                 print("There is a 3rd optional parameter telling in how many seconds the aim has to reach the new position\n");
434                                 print("Examples: aim \"90 0\"   // Turn 90 degrees inmediately (positive numbers move to the left/up)\n");
435                                 print("          aim \"0 90 2\" // Will gradually look to the sky in the next two seconds");
436                                 break;
437                         case BOT_CMD_AIMTARGET:
438                                 print("Points the aim to given target");
439                                 break;
440                         case BOT_CMD_PRESSKEY:
441                                 print("Press one of the following keys: forward, backward, left, right, jump, crouch, attack1, attack2, use\n");
442                                 print("Multiple keys can be pressed at time (with many presskey calls) and it will remain pressed until the command \"releasekey\" is called");
443                                 print("Note: The script will not return the control to the bot ai until all keys are released");
444                                 break;
445                         case BOT_CMD_RELEASEKEY:
446                                 print("Release previoulsy used keys. Use the parameter \"all\" to release all keys");
447                                 break;
448                         case BOT_CMD_SOUND:
449                                 print("play sound file at bot location");
450                                 break;
451                         default:
452                                 print("This command has no description yet.");
453                                 break;
454                 }
455                 print("\n");
456         }
457 }
458
459 void bot_list_commands()
460 {
461         local float i;
462         local string ptype;
463
464         if(!bot_cmds_initialized)
465                 bot_commands_init();
466
467         print("List of all available commands:\n");
468         print(" Command\t\t\t\tParameter Type\n");
469
470         for(i=1;i<BOT_CMD_COUNTER;++i)
471         {
472                 switch(bot_cmd_parm_type[i])
473                 {
474                         case BOT_CMD_PARAMETER_FLOAT:
475                                 ptype = "float number";
476                                 break;
477                         case BOT_CMD_PARAMETER_STRING:
478                                 ptype = "string";
479                                 break;
480                         case BOT_CMD_PARAMETER_VECTOR:
481                                 ptype = "vector";
482                                 break;
483                         default:
484                                 ptype = "none";
485                                 break;
486                 }
487                 print(strcat(" ",bot_cmd_string[i],"\t\t\t\t<",ptype,"> \n"));
488         }
489 }
490
491 // Commands code
492 .float bot_exec_status;
493
494 #define BOT_EXEC_STATUS_IDLE    0
495 #define BOT_EXEC_STATUS_PAUSED  1
496 #define BOT_EXEC_STATUS_WAITING 2
497
498 #define CMD_STATUS_EXECUTING    0
499 #define CMD_STATUS_FINISHED     1
500 #define CMD_STATUS_ERROR        2
501
502 void SV_ParseClientCommand(string s);
503 float bot_cmd_cc()
504 {
505         SV_ParseClientCommand(bot_cmd.bot_cmd_parm_string);
506         return CMD_STATUS_FINISHED;
507 }
508
509 float bot_cmd_impulse()
510 {
511         self.impulse = bot_cmd.bot_cmd_parm_float;
512         return CMD_STATUS_FINISHED;
513 }
514
515 float bot_cmd_continue()
516 {
517         self.bot_exec_status &~= BOT_EXEC_STATUS_PAUSED;
518         return CMD_STATUS_FINISHED;
519 }
520
521 .float bot_cmd_wait_time;
522 float bot_cmd_wait()
523 {
524         if(self.bot_exec_status & BOT_EXEC_STATUS_WAITING)
525         {
526                 if(time>=self.bot_cmd_wait_time)
527                 {
528                         self.bot_exec_status &~= BOT_EXEC_STATUS_WAITING;
529                         return CMD_STATUS_FINISHED;
530                 }
531                 else
532                         return CMD_STATUS_EXECUTING;
533         }
534
535         self.bot_cmd_wait_time = time + bot_cmd.bot_cmd_parm_float;
536         self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
537         return CMD_STATUS_EXECUTING;
538 }
539
540 float bot_cmd_wait_until()
541 {
542         if(time < bot_cmd.bot_cmd_parm_float + bot_barriertime)
543         {
544                 self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
545                 return CMD_STATUS_EXECUTING;
546         }
547         self.bot_exec_status &~= BOT_EXEC_STATUS_WAITING;
548         return CMD_STATUS_FINISHED;
549 }
550
551 float bot_cmd_barrier()
552 {
553         entity cl;
554
555         // 0 = no barrier, 1 = waiting, 2 = waiting finished
556
557         if(self.bot_barrier == 0) // initialization
558         {
559                 self.bot_barrier = 1;
560
561                 //self.colormod = '4 4 0';
562         }
563
564         if(self.bot_barrier == 1) // find other bots
565         {
566                 FOR_EACH_CLIENT(cl) if(cl.isbot)
567                 {
568                         if(cl.bot_cmdqueuebuf_allocated)
569                                 if(cl.bot_barrier != 1)
570                                         return CMD_STATUS_EXECUTING; // not all are at the barrier yet
571                 }
572
573                 // all bots hit the barrier!
574                 FOR_EACH_CLIENT(cl) if(cl.isbot)
575                 {
576                         cl.bot_barrier = 2; // acknowledge barrier
577                 }
578
579                 bot_barriertime = time;
580         }
581
582         // if we get here, the barrier is finished
583         // so end it...
584         self.bot_barrier = 0;
585         //self.colormod = '0 0 0';
586
587         return CMD_STATUS_FINISHED;
588 }
589
590 float bot_cmd_turn()
591 {
592         self.v_angle_y = self.v_angle_y + bot_cmd.bot_cmd_parm_float;
593         self.v_angle_y = self.v_angle_y - floor(self.v_angle_y / 360) * 360;
594         return CMD_STATUS_FINISHED;
595 }
596
597 float bot_cmd_select_weapon()
598 {
599         local float id;
600
601         id = bot_cmd.bot_cmd_parm_float;
602
603         if(id < WEP_FIRST || id > WEP_LAST)
604                 return CMD_STATUS_ERROR;
605
606         if(client_hasweapon(self, id, TRUE, FALSE))
607                 self.switchweapon = id;
608         else
609                 return CMD_STATUS_ERROR;
610
611         return CMD_STATUS_FINISHED;
612 }
613
614 .float bot_cmd_condition_status;
615
616 #define CMD_CONDITION_NONE              0
617 #define CMD_CONDITION_TRUE              1
618 #define CMD_CONDITION_FALSE             2
619 #define CMD_CONDITION_TRUE_BLOCK        4
620 #define CMD_CONDITION_FALSE_BLOCK       8
621
622 float bot_cmd_eval(string expr)
623 {
624         // Search for numbers
625         if(strstrofs("0123456789", substring(expr, 0, 1), 0) >= 0)
626         {
627                 return stof(expr);
628         }
629
630         // Search for cvars
631         if(substring(expr, 0, 5)=="cvar.")
632         {
633                 return cvar(substring(expr, 5, strlen(expr)));
634         }
635
636         // Search for fields
637         switch(expr)
638         {
639                 case "health":
640                         return self.health;
641                 case "speed":
642                         return vlen(self.velocity);
643                 case "flagcarrier":
644                         return ((self.flagcarried!=world));
645         }
646
647         print(strcat("ERROR: Unable to convert the expression '",expr,"' into a numeric value\n"));
648         return 0;
649 }
650
651 float bot_cmd_if()
652 {
653         local string expr, val_a, val_b;
654         local float cmpofs;
655
656         if(self.bot_cmd_condition_status != CMD_CONDITION_NONE)
657         {
658                 // Only one "if" block is allowed at time
659                 print("ERROR: Only one conditional block can be processed at time");
660                 bot_clearqueue(self);
661                 return CMD_STATUS_ERROR;
662         }
663
664         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE_BLOCK;
665
666         // search for operators
667         expr = bot_cmd.bot_cmd_parm_string;
668
669         cmpofs = strstrofs(expr,"=",0);
670
671         if(cmpofs>0)
672         {
673                 val_a = substring(expr,0,cmpofs);
674                 val_b = substring(expr,cmpofs+1,strlen(expr));
675
676                 if(bot_cmd_eval(val_a)==bot_cmd_eval(val_b))
677                         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
678                 else
679                         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
680
681                 return CMD_STATUS_FINISHED;
682         }
683
684         cmpofs = strstrofs(expr,">",0);
685
686         if(cmpofs>0)
687         {
688                 val_a = substring(expr,0,cmpofs);
689                 val_b = substring(expr,cmpofs+1,strlen(expr));
690
691                 if(bot_cmd_eval(val_a)>bot_cmd_eval(val_b))
692                         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
693                 else
694                         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
695
696                 return CMD_STATUS_FINISHED;
697         }
698
699         cmpofs = strstrofs(expr,"<",0);
700
701         if(cmpofs>0)
702         {
703                 val_a = substring(expr,0,cmpofs);
704                 val_b = substring(expr,cmpofs+1,strlen(expr));
705
706                 if(bot_cmd_eval(val_a)<bot_cmd_eval(val_b))
707                         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
708                 else
709                         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
710
711                 return CMD_STATUS_FINISHED;
712         }
713
714         if(bot_cmd_eval(expr))
715                 self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
716         else
717                 self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
718
719         return CMD_STATUS_FINISHED;
720 }
721
722 float bot_cmd_else()
723 {
724         self.bot_cmd_condition_status &~= CMD_CONDITION_TRUE_BLOCK;
725         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE_BLOCK;
726         return CMD_STATUS_FINISHED;
727 }
728
729 float bot_cmd_fi()
730 {
731         self.bot_cmd_condition_status = CMD_CONDITION_NONE;
732         return CMD_STATUS_FINISHED;
733 }
734
735 float bot_cmd_resetaim()
736 {
737         self.v_angle = '0 0 0';
738         return CMD_STATUS_FINISHED;
739 }
740
741 .float bot_cmd_aim_begintime;
742 .float bot_cmd_aim_endtime;
743 .vector bot_cmd_aim_begin;
744 .vector bot_cmd_aim_end;
745
746 float bot_cmd_aim()
747 {
748         // Current direction
749         if(self.bot_cmd_aim_endtime)
750         {
751                 local float progress;
752
753                 progress = min(1 - (self.bot_cmd_aim_endtime - time) / (self.bot_cmd_aim_endtime - self.bot_cmd_aim_begintime),1);
754                 self.v_angle = self.bot_cmd_aim_begin + ((self.bot_cmd_aim_end - self.bot_cmd_aim_begin) * progress);
755
756                 if(time>=self.bot_cmd_aim_endtime)
757                 {
758                         self.bot_cmd_aim_endtime = 0;
759                         return CMD_STATUS_FINISHED;
760                 }
761                 else
762                         return CMD_STATUS_EXECUTING;
763         }
764
765         // New aiming direction
766         local string parms;
767         local float tokens, step;
768
769         parms = bot_cmd.bot_cmd_parm_string;
770
771         tokens = tokenizebyseparator(parms, " ");
772
773         if(tokens==2)
774         {
775                 self.v_angle_x -= stof(argv(1));
776                 self.v_angle_y += stof(argv(0));
777                 return CMD_STATUS_FINISHED;
778         }
779
780         if(tokens<2||tokens>3)
781                 return CMD_STATUS_ERROR;
782
783         step = stof(argv(2));
784
785         self.bot_cmd_aim_begin = self.v_angle;
786
787         self.bot_cmd_aim_end_x = self.v_angle_x - stof(argv(1));
788         self.bot_cmd_aim_end_y = self.v_angle_y + stof(argv(0));
789         self.bot_cmd_aim_end_z = 0;
790
791         self.bot_cmd_aim_begintime = time;
792         self.bot_cmd_aim_endtime = time + step;
793
794         return CMD_STATUS_EXECUTING;
795 }
796
797 float bot_cmd_aimtarget()
798 {
799         if(self.bot_cmd_aim_endtime)
800         {
801                 return bot_cmd_aim();
802         }
803
804         local entity e;
805         local string parms;
806         local vector v;
807         local float tokens, step;
808
809         parms = bot_cmd.bot_cmd_parm_string;
810
811         tokens = tokenizebyseparator(parms, " ");
812
813         e = bot_getplace(argv(0));
814         if(!e)
815                 return CMD_STATUS_ERROR;
816
817         v = e.origin + (e.mins + e.maxs) * 0.5;
818
819         if(tokens==1)
820         {
821                 self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
822                 self.v_angle_x = -self.v_angle_x;
823                 return CMD_STATUS_FINISHED;
824         }
825
826         if(tokens<1||tokens>2)
827                 return CMD_STATUS_ERROR;
828
829         step = stof(argv(1));
830
831         self.bot_cmd_aim_begin = self.v_angle;
832         self.bot_cmd_aim_end = vectoangles(v - (self.origin + self.view_ofs));
833         self.bot_cmd_aim_end_x = -self.bot_cmd_aim_end_x;
834
835         self.bot_cmd_aim_begintime = time;
836         self.bot_cmd_aim_endtime = time + step;
837
838         return CMD_STATUS_EXECUTING;
839 }
840
841 .float bot_cmd_keys;
842
843 #define BOT_CMD_KEY_NONE        0
844 #define BOT_CMD_KEY_FORWARD     1
845 #define BOT_CMD_KEY_BACKWARD    2
846 #define BOT_CMD_KEY_RIGHT       4
847 #define BOT_CMD_KEY_LEFT        8
848 #define BOT_CMD_KEY_JUMP        16
849 #define BOT_CMD_KEY_ATTACK1     32
850 #define BOT_CMD_KEY_ATTACK2     64
851 #define BOT_CMD_KEY_USE         128
852 #define BOT_CMD_KEY_HOOK        256
853 #define BOT_CMD_KEY_CROUCH      512
854 #define BOT_CMD_KEY_CHAT        1024
855
856 float bot_presskeys()
857 {
858         self.movement = '0 0 0';
859         self.BUTTON_JUMP = FALSE;
860         self.BUTTON_CROUCH = FALSE;
861         self.BUTTON_ATCK = FALSE;
862         self.BUTTON_ATCK2 = FALSE;
863         self.BUTTON_USE = FALSE;
864         self.BUTTON_HOOK = FALSE;
865         self.BUTTON_CHAT = FALSE;
866
867         if(self.bot_cmd_keys == BOT_CMD_KEY_NONE)
868                 return FALSE;
869
870         if(self.bot_cmd_keys & BOT_CMD_KEY_FORWARD)
871                 self.movement_x = autocvar_sv_maxspeed;
872         else if(self.bot_cmd_keys & BOT_CMD_KEY_BACKWARD)
873                 self.movement_x = -autocvar_sv_maxspeed;
874
875         if(self.bot_cmd_keys & BOT_CMD_KEY_RIGHT)
876                 self.movement_y = autocvar_sv_maxspeed;
877         else if(self.bot_cmd_keys & BOT_CMD_KEY_LEFT)
878                 self.movement_y = -autocvar_sv_maxspeed;
879
880         if(self.bot_cmd_keys & BOT_CMD_KEY_JUMP)
881                 self.BUTTON_JUMP = TRUE;
882
883         if(self.bot_cmd_keys & BOT_CMD_KEY_CROUCH)
884                 self.BUTTON_CROUCH = TRUE;
885
886         if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK1)
887                 self.BUTTON_ATCK = TRUE;
888
889         if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK2)
890                 self.BUTTON_ATCK2 = TRUE;
891
892         if(self.bot_cmd_keys & BOT_CMD_KEY_USE)
893                 self.BUTTON_USE = TRUE;
894
895         if(self.bot_cmd_keys & BOT_CMD_KEY_HOOK)
896                 self.BUTTON_HOOK = TRUE;
897
898         if(self.bot_cmd_keys & BOT_CMD_KEY_CHAT)
899                 self.BUTTON_CHAT = TRUE;
900
901         return TRUE;
902 }
903
904
905 float bot_cmd_keypress_handler(string key, float enabled)
906 {
907         switch(key)
908         {
909                 case "all":
910                         if(enabled)
911                                 self.bot_cmd_keys = power2of(20) - 1; // >:)
912                         else
913                                 self.bot_cmd_keys = BOT_CMD_KEY_NONE;
914                 case "forward":
915                         if(enabled)
916                         {
917                                 self.bot_cmd_keys |= BOT_CMD_KEY_FORWARD;
918                                 self.bot_cmd_keys &~= BOT_CMD_KEY_BACKWARD;
919                         }
920                         else
921                                 self.bot_cmd_keys &~= BOT_CMD_KEY_FORWARD;
922                         break;
923                 case "backward":
924                         if(enabled)
925                         {
926                                 self.bot_cmd_keys |= BOT_CMD_KEY_BACKWARD;
927                                 self.bot_cmd_keys &~= BOT_CMD_KEY_FORWARD;
928                         }
929                         else
930                                 self.bot_cmd_keys &~= BOT_CMD_KEY_BACKWARD;
931                         break;
932                 case "left":
933                         if(enabled)
934                         {
935                                 self.bot_cmd_keys |= BOT_CMD_KEY_LEFT;
936                                 self.bot_cmd_keys &~= BOT_CMD_KEY_RIGHT;
937                         }
938                         else
939                                 self.bot_cmd_keys &~= BOT_CMD_KEY_LEFT;
940                         break;
941                 case "right":
942                         if(enabled)
943                         {
944                                 self.bot_cmd_keys |= BOT_CMD_KEY_RIGHT;
945                                 self.bot_cmd_keys &~= BOT_CMD_KEY_LEFT;
946                         }
947                         else
948                                 self.bot_cmd_keys &~= BOT_CMD_KEY_RIGHT;
949                         break;
950                 case "jump":
951                         if(enabled)
952                                 self.bot_cmd_keys |= BOT_CMD_KEY_JUMP;
953                         else
954                                 self.bot_cmd_keys &~= BOT_CMD_KEY_JUMP;
955                         break;
956                 case "crouch":
957                         if(enabled)
958                                 self.bot_cmd_keys |= BOT_CMD_KEY_CROUCH;
959                         else
960                                 self.bot_cmd_keys &~= BOT_CMD_KEY_CROUCH;
961                         break;
962                 case "attack1":
963                         if(enabled)
964                                 self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK1;
965                         else
966                                 self.bot_cmd_keys &~= BOT_CMD_KEY_ATTACK1;
967                         break;
968                 case "attack2":
969                         if(enabled)
970                                 self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK2;
971                         else
972                                 self.bot_cmd_keys &~= BOT_CMD_KEY_ATTACK2;
973                         break;
974                 case "use":
975                         if(enabled)
976                                 self.bot_cmd_keys |= BOT_CMD_KEY_USE;
977                         else
978                                 self.bot_cmd_keys &~= BOT_CMD_KEY_USE;
979                         break;
980                 case "hook":
981                         if(enabled)
982                                 self.bot_cmd_keys |= BOT_CMD_KEY_HOOK;
983                         else
984                                 self.bot_cmd_keys &~= BOT_CMD_KEY_HOOK;
985                         break;
986                 case "chat":
987                         if(enabled)
988                                 self.bot_cmd_keys |= BOT_CMD_KEY_CHAT;
989                         else
990                                 self.bot_cmd_keys &~= BOT_CMD_KEY_CHAT;
991                         break;
992                 default:
993                         break;
994         }
995
996         return CMD_STATUS_FINISHED;
997 }
998
999 float bot_cmd_presskey()
1000 {
1001         local string key;
1002
1003         key = bot_cmd.bot_cmd_parm_string;
1004
1005         bot_cmd_keypress_handler(key,TRUE);
1006
1007         return CMD_STATUS_FINISHED;
1008 }
1009
1010 float bot_cmd_releasekey()
1011 {
1012         local string key;
1013
1014         key = bot_cmd.bot_cmd_parm_string;
1015
1016         return bot_cmd_keypress_handler(key,FALSE);
1017 }
1018
1019 float bot_cmd_pause()
1020 {
1021         self.button1        = 0;
1022         self.button8        = 0;
1023         self.BUTTON_USE     = 0;
1024         self.BUTTON_ATCK    = 0;
1025         self.BUTTON_JUMP    = 0;
1026         self.BUTTON_HOOK    = 0;
1027         self.BUTTON_CHAT    = 0;
1028         self.BUTTON_ATCK2   = 0;
1029         self.BUTTON_CROUCH  = 0;
1030
1031         self.movement = '0 0 0';
1032         self.bot_cmd_keys = BOT_CMD_KEY_NONE;
1033
1034         self.bot_exec_status |= BOT_EXEC_STATUS_PAUSED;
1035         return CMD_STATUS_FINISHED;
1036 }
1037
1038 float bot_cmd_moveto()
1039 {
1040         return self.cmd_moveto(bot_cmd.bot_cmd_parm_vector);
1041 }
1042
1043 float bot_cmd_movetotarget()
1044 {
1045         entity e;
1046         e = bot_getplace(bot_cmd.bot_cmd_parm_string);
1047         if(!e)
1048                 return CMD_STATUS_ERROR;
1049         return self.cmd_moveto(e.origin + (e.mins + e.maxs) * 0.5);
1050 }
1051
1052 float bot_cmd_resetgoal()
1053 {
1054         return self.cmd_resetgoal();
1055 }
1056
1057
1058 float bot_cmd_sound()
1059 {
1060         string f;
1061         f = bot_cmd.bot_cmd_parm_string;
1062
1063         precache_sound(f);
1064         sound(self, CHAN_WEAPON2, f, VOL_BASE, ATTN_MIN);
1065
1066         return CMD_STATUS_FINISHED;
1067 }
1068
1069 //
1070
1071 void bot_command_executed(float rm)
1072 {
1073         entity cmd;
1074
1075         cmd = bot_cmd;
1076
1077         if(rm)
1078                 bot_dequeuecommand(self, self.bot_cmd_execution_index);
1079
1080         self.bot_cmd_execution_index++;
1081 }
1082
1083 void bot_setcurrentcommand()
1084 {
1085         bot_cmd = world;
1086
1087         if(!self.bot_cmd_current)
1088         {
1089                 self.bot_cmd_current = spawn();
1090                 self.bot_cmd_current.classname = "bot_cmd";
1091                 self.bot_cmd_current.is_bot_cmd = 1;
1092         }
1093
1094         bot_cmd = self.bot_cmd_current;
1095         if(bot_cmd.bot_cmd_index != self.bot_cmd_execution_index || self.bot_cmd_execution_index == 0)
1096         {
1097                 if(bot_havecommand(self, self.bot_cmd_execution_index))
1098                 {
1099                         string cmdstring;
1100                         cmdstring = bot_readcommand(self, self.bot_cmd_execution_index);
1101                         if(bot_decodecommand(cmdstring))
1102                         {
1103                                 bot_cmd.owner = self;
1104                                 bot_cmd.bot_cmd_index = self.bot_cmd_execution_index;
1105                         }
1106                         else
1107                         {
1108                                 // Invalid command, remove from queue
1109                                 bot_cmd = world;
1110                                 bot_dequeuecommand(self, self.bot_cmd_execution_index);
1111                                 self.bot_cmd_execution_index++;
1112                         }
1113                 }
1114                 else
1115                         bot_cmd = world;
1116         }
1117 }
1118
1119 void bot_resetqueues()
1120 {
1121         entity cl;
1122         float i;
1123
1124         FOR_EACH_CLIENT(cl) if(cl.isbot)
1125         {
1126                 if(cl.bot_cmdqueuebuf_allocated)
1127                         bot_clearqueue(cl);
1128                 // also, cancel all barriers
1129                 cl.bot_barrier = 0;
1130                 for(i = 0; i < cl.bot_places_count; ++i)
1131                 {
1132                         strunzone(cl.(bot_placenames[i]));
1133                         cl.(bot_placenames[i]) = string_null;
1134                 }
1135                 cl.bot_places_count = 0;
1136         }
1137
1138         bot_barriertime = time;
1139 }
1140
1141 // Here we map commands to functions and deal with complex interactions between commands and execution states
1142 // NOTE: Of course you need to include your commands here too :)
1143 float bot_execute_commands_once()
1144 {
1145         local float status, ispressingkey;
1146
1147         if(self.deadflag!=DEAD_NO)
1148                 return 0;
1149
1150         // Find command
1151         bot_setcurrentcommand();
1152
1153         // Ignore all commands except continue when the bot is paused
1154         if(self.bot_exec_status & BOT_EXEC_STATUS_PAUSED)
1155         if(bot_cmd.bot_cmd_type!=BOT_CMD_CONTINUE)
1156         {
1157                 if(bot_cmd.bot_cmd_type!=BOT_CMD_NULL)
1158                 {
1159                         bot_command_executed(TRUE);
1160                         print( "WARNING: Commands are ignored while the bot is paused. Use the command 'continue' instead.\n");
1161                 }
1162                 return 1;
1163         }
1164
1165         // Keep pressing keys raised by the "presskey" command
1166         ispressingkey = !!bot_presskeys();
1167
1168         if(bot_cmd==world)
1169                 return ispressingkey;
1170
1171         // Handle conditions
1172         if not(bot_cmd.bot_cmd_type==BOT_CMD_FI||bot_cmd.bot_cmd_type==BOT_CMD_ELSE)
1173         if(self.bot_cmd_condition_status & CMD_CONDITION_TRUE && self.bot_cmd_condition_status & CMD_CONDITION_FALSE_BLOCK)
1174         {
1175                 bot_command_executed(TRUE);
1176                 return -1;
1177         }
1178         else if(self.bot_cmd_condition_status & CMD_CONDITION_FALSE && self.bot_cmd_condition_status & CMD_CONDITION_TRUE_BLOCK)
1179         {
1180                 bot_command_executed(TRUE);
1181                 return -1;
1182         }
1183
1184         // Map commands to functions
1185         switch(bot_cmd.bot_cmd_type)
1186         {
1187                 case BOT_CMD_NULL:
1188                         return ispressingkey;
1189                         //break;
1190                 case BOT_CMD_PAUSE:
1191                         status = bot_cmd_pause();
1192                         break;
1193                 case BOT_CMD_CONTINUE:
1194                         status = bot_cmd_continue();
1195                         break;
1196                 case BOT_CMD_WAIT:
1197                         status = bot_cmd_wait();
1198                         break;
1199                 case BOT_CMD_WAIT_UNTIL:
1200                         status = bot_cmd_wait_until();
1201                         break;
1202                 case BOT_CMD_TURN:
1203                         status = bot_cmd_turn();
1204                         break;
1205                 case BOT_CMD_MOVETO:
1206                         status = bot_cmd_moveto();
1207                         break;
1208                 case BOT_CMD_MOVETOTARGET:
1209                         status = bot_cmd_movetotarget();
1210                         break;
1211                 case BOT_CMD_RESETGOAL:
1212                         status = bot_cmd_resetgoal();
1213                         break;
1214                 case BOT_CMD_CC:
1215                         status = bot_cmd_cc();
1216                         break;
1217                 case BOT_CMD_IF:
1218                         status = bot_cmd_if();
1219                         break;
1220                 case BOT_CMD_ELSE:
1221                         status = bot_cmd_else();
1222                         break;
1223                 case BOT_CMD_FI:
1224                         status = bot_cmd_fi();
1225                         break;
1226                 case BOT_CMD_RESETAIM:
1227                         status = bot_cmd_resetaim();
1228                         break;
1229                 case BOT_CMD_AIM:
1230                         status = bot_cmd_aim();
1231                         break;
1232                 case BOT_CMD_AIMTARGET:
1233                         status = bot_cmd_aimtarget();
1234                         break;
1235                 case BOT_CMD_PRESSKEY:
1236                         status = bot_cmd_presskey();
1237                         break;
1238                 case BOT_CMD_RELEASEKEY:
1239                         status = bot_cmd_releasekey();
1240                         break;
1241                 case BOT_CMD_SELECTWEAPON:
1242                         status = bot_cmd_select_weapon();
1243                         break;
1244                 case BOT_CMD_IMPULSE:
1245                         status = bot_cmd_impulse();
1246                         break;
1247                 case BOT_CMD_BARRIER:
1248                         status = bot_cmd_barrier();
1249                         break;
1250                 case BOT_CMD_CONSOLE:
1251                         localcmd(strcat(bot_cmd.bot_cmd_parm_string, "\n"));
1252                         status = CMD_STATUS_FINISHED;
1253                         break;
1254                 case BOT_CMD_SOUND:
1255                         status = bot_cmd_sound();
1256                         break;
1257                 default:
1258                         print(strcat("ERROR: Invalid command on queue with id '",ftos(bot_cmd.bot_cmd_type),"'\n"));
1259                         return 0;
1260         }
1261
1262         if (status==CMD_STATUS_ERROR)
1263                 print(strcat("ERROR: The command '",bot_cmd_string[bot_cmd.bot_cmd_type],"' returned an error status\n"));
1264
1265         // Move execution pointer
1266         if(status==CMD_STATUS_EXECUTING)
1267         {
1268                 return 1;
1269         }
1270         else
1271         {
1272                 if(autocvar_g_debug_bot_commands)
1273                 {
1274                         local string parms;
1275
1276                         switch(bot_cmd_parm_type[bot_cmd.bot_cmd_type])
1277                         {
1278                                 case BOT_CMD_PARAMETER_FLOAT:
1279                                         parms = ftos(bot_cmd.bot_cmd_parm_float);
1280                                         break;
1281                                 case BOT_CMD_PARAMETER_STRING:
1282                                         parms = bot_cmd.bot_cmd_parm_string;
1283                                         break;
1284                                 case BOT_CMD_PARAMETER_VECTOR:
1285                                         parms = vtos(bot_cmd.bot_cmd_parm_vector);
1286                                         break;
1287                                 default:
1288                                         parms = "";
1289                                         break;
1290                         }
1291                         clientcommand(self,strcat("say ^7", bot_cmd_string[bot_cmd.bot_cmd_type]," ",parms,"\n"));
1292                 }
1293
1294                 bot_command_executed(TRUE);
1295         }
1296
1297         if(status == CMD_STATUS_FINISHED)
1298                 return -1;
1299
1300         return CMD_STATUS_ERROR;
1301 }
1302
1303 // This function should be (the only) called directly from the bot ai loop
1304 float bot_execute_commands()
1305 {
1306         float f;
1307         do
1308         {
1309                 f = bot_execute_commands_once();
1310         }
1311         while(f < 0);
1312         return f;
1313 }