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