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