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