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