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