]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/bots_scripting.qc
Added resetqueue bot command
[divverent/nexuiz.git] / data / qcsrc / server / bots_scripting.qc
1 // NOTE: New commands should be added here. Do not forget to update BOT_CMD_COUNTER
2 #define BOT_CMD_NULL                    0
3 #define BOT_CMD_PAUSE                   1
4 #define BOT_CMD_CONTINUE                2
5 #define BOT_CMD_WAIT                    3
6 #define BOT_CMD_TURN                    4
7 #define BOT_CMD_MOVETO                  5       // Not implemented yet
8 #define BOT_CMD_RESETGOAL               6       // Not implemented yet
9 #define BOT_CMD_CC                      7
10 #define BOT_CMD_IF                      8
11 #define BOT_CMD_ELSE                    9
12 #define BOT_CMD_FI                      10
13 #define BOT_CMD_RESETAIM                11
14 #define BOT_CMD_AIM                     12
15 #define BOT_CMD_PRESSKEY                13
16 #define BOT_CMD_RELEASEKEY              14
17 #define BOT_CMD_SELECTWEAPON            15
18 #define BOT_CMD_IMPULSE                 16
19 #define BOT_CMD_WAIT_UNTIL              17
20 #define BOT_CMD_RESETQUEUE              18
21 #define BOT_CMD_WHILE                   19      // TODO: Not implemented yet
22 #define BOT_CMD_WEND                    20      // TODO: Not implemented yet
23 #define BOT_CMD_CHASE                   20      // TODO: Not implemented yet
24
25 #define BOT_CMD_COUNTER                 19      // Update this value if you add/remove a command
26
27 // NOTE: Following commands should be implemented on the bot ai
28 //               If a new command should be handled by the target ai(s) please declare it here
29 .float(vector) cmd_moveto;
30 .float() cmd_resetgoal;
31
32 //
33 #define BOT_CMD_PARAMETER_NONE          0
34 #define BOT_CMD_PARAMETER_FLOAT         1
35 #define BOT_CMD_PARAMETER_STRING        2
36 #define BOT_CMD_PARAMETER_VECTOR        3
37 #define BOT_CMD_PARAMETER_FLOAT_RELATIVE_TO_TIME                4
38
39 float bot_cmds_initialized;
40 float bot_cmd_parm_type[BOT_CMD_COUNTER];
41 string bot_cmd_string[BOT_CMD_COUNTER];
42
43 // Bots command queue
44 entity bot_cmd;                         // Current command
45
46 .float is_bot_cmd;                      // Tells if the entity is a bot command
47 .float bot_cmd_index;                   // Position of the command in the queue
48 .float bot_cmd_type;                    // If of command (see the BOT_CMD_* defines)
49 .float bot_cmd_parm_float;              // Field to store a float parameter
50 .string bot_cmd_parm_string;            // Field to store a string parameter
51 .vector bot_cmd_parm_vector;            // Field to store a vector parameter
52 .float bot_cmd_execution_counter;       // How many times this command on the queue was executed
53
54 .float bot_cmd_execution_index;         // Position in the queue of the command to be executed
55 .float bot_cmd_queue_index;             // Position of the last command in the queue
56
57 // Initialize global commands list
58 // NOTE: New commands should be initialized here
59 void bot_commands_init()
60 {
61         bot_cmd_string[BOT_CMD_NULL]                    = "";
62         bot_cmd_parm_type[BOT_CMD_NULL]                 = BOT_CMD_PARAMETER_NONE;
63
64         bot_cmd_string[BOT_CMD_PAUSE]                   = "pause";
65         bot_cmd_parm_type[BOT_CMD_PAUSE]                = BOT_CMD_PARAMETER_NONE;
66
67         bot_cmd_string[BOT_CMD_CONTINUE]                = "continue";
68         bot_cmd_parm_type[BOT_CMD_CONTINUE]             = BOT_CMD_PARAMETER_NONE;
69
70         bot_cmd_string[BOT_CMD_WAIT]                    = "wait";
71         bot_cmd_parm_type[BOT_CMD_WAIT]                 = BOT_CMD_PARAMETER_FLOAT;
72
73         bot_cmd_string[BOT_CMD_TURN]                    = "turn";
74         bot_cmd_parm_type[BOT_CMD_TURN]                 = BOT_CMD_PARAMETER_FLOAT;
75
76         bot_cmd_string[BOT_CMD_MOVETO]                  = "moveto";
77         bot_cmd_parm_type[BOT_CMD_MOVETO]               = BOT_CMD_PARAMETER_VECTOR;
78
79         bot_cmd_string[BOT_CMD_RESETGOAL]               = "resetgoal";
80         bot_cmd_parm_type[BOT_CMD_RESETGOAL]            = BOT_CMD_PARAMETER_NONE;
81
82         bot_cmd_string[BOT_CMD_CC]                      = "cc";
83         bot_cmd_parm_type[BOT_CMD_CC]                   = BOT_CMD_PARAMETER_STRING;
84
85         bot_cmd_string[BOT_CMD_IF]                      = "if";
86         bot_cmd_parm_type[BOT_CMD_IF]                   = BOT_CMD_PARAMETER_STRING;
87
88         bot_cmd_string[BOT_CMD_ELSE]                    = "else";
89         bot_cmd_parm_type[BOT_CMD_ELSE]                 = BOT_CMD_PARAMETER_NONE;
90
91         bot_cmd_string[BOT_CMD_FI]                      = "fi";
92         bot_cmd_parm_type[BOT_CMD_FI]                   = BOT_CMD_PARAMETER_NONE;
93
94         bot_cmd_string[BOT_CMD_RESETAIM]                = "resetaim";
95         bot_cmd_parm_type[BOT_CMD_RESETAIM]             = BOT_CMD_PARAMETER_NONE;
96
97         bot_cmd_string[BOT_CMD_AIM]                     = "aim";
98         bot_cmd_parm_type[BOT_CMD_AIM]                  = BOT_CMD_PARAMETER_STRING;
99
100         bot_cmd_string[BOT_CMD_PRESSKEY]                = "presskey";
101         bot_cmd_parm_type[BOT_CMD_PRESSKEY]             = BOT_CMD_PARAMETER_STRING;
102
103         bot_cmd_string[BOT_CMD_RELEASEKEY]              = "releasekey";
104         bot_cmd_parm_type[BOT_CMD_RELEASEKEY]           = BOT_CMD_PARAMETER_STRING;
105
106         bot_cmd_string[BOT_CMD_SELECTWEAPON]            = "selectweapon";
107         bot_cmd_parm_type[BOT_CMD_SELECTWEAPON]         = BOT_CMD_PARAMETER_FLOAT;
108
109         bot_cmd_string[BOT_CMD_IMPULSE]                 = "impulse";
110         bot_cmd_parm_type[BOT_CMD_IMPULSE]              = BOT_CMD_PARAMETER_FLOAT;
111
112         bot_cmd_string[BOT_CMD_WAIT_UNTIL]              = "wait_until";
113         bot_cmd_parm_type[BOT_CMD_WAIT_UNTIL]   = BOT_CMD_PARAMETER_FLOAT_RELATIVE_TO_TIME;
114
115         bot_cmd_string[BOT_CMD_RESETQUEUE]              = "resetqueue";
116         bot_cmd_parm_type[BOT_CMD_RESETQUEUE]   = BOT_CMD_PARAMETER_NONE;
117
118         bot_cmds_initialized = TRUE;
119 }
120
121 // Returns first bot with matching name
122 entity find_bot_by_name(string name)
123 {
124         local entity bot;
125
126         bot = findchainflags(flags, FL_CLIENT);
127         while (bot)
128         {
129                 if(clienttype(bot) == CLIENTTYPE_BOT)
130                 if(bot.netname==name)
131                         return bot;
132
133                 bot = bot.chain;
134         }
135
136         return world;
137 }
138
139 // Returns a bot by number on list
140 entity find_bot_by_number(float number)
141 {
142         local entity bot;
143         local float c;
144
145         bot = findchainflags(flags, FL_CLIENT);
146         while (bot)
147         {
148                 if(clienttype(bot) == CLIENTTYPE_BOT)
149                 {
150                         if(++c==number)
151                                 return bot;
152                 }
153                 bot = bot.chain;
154         }
155
156         return world;
157 }
158
159 void bot_clearqueue()
160 {
161         entity head, newhead;
162
163         head = findchainfloat(is_bot_cmd, TRUE);
164         newhead = head;
165
166         while(newhead)
167         {
168                 newhead = head.chain;
169
170                 if(head.owner==self)
171                         remove(head);
172
173                 head = newhead;
174         }
175
176         self.bot_cmd_queue_index = 0;
177         self.bot_cmd_execution_index = 0;
178 }
179
180 entity bot_spawncmd(entity bot, float type)
181 {
182         entity cmd;
183
184         bot.bot_cmd_queue_index++;
185
186         cmd = spawn();
187         cmd.owner = bot;
188         cmd.is_bot_cmd = TRUE;
189         cmd.bot_cmd_type = type;
190         cmd.bot_cmd_index = bot.bot_cmd_queue_index;
191
192         return cmd;
193 }
194
195 void bot_debugcmd(entity cmd)
196 {
197         print(strcat("Owner: ",cmd.owner.netname, "\n"));
198         print(strcat("Cmd Type: ",ftos(cmd.bot_cmd_type), "\n"));
199         print(strcat("Cmd Index: ",ftos(cmd.bot_cmd_index), "\n"));
200
201         print(strcat("Param Float: ",ftos(cmd.bot_cmd_parm_float), "\n"));
202         print(strcat("Param String: ",cmd.bot_cmd_parm_string, "\n"));
203         print(strcat("Param Vector: ",vtos(cmd.bot_cmd_parm_vector), "\n"));
204
205         print(strcat("Bot queue index: ",       ftos(cmd.owner.bot_cmd_queue_index), "\n"));
206         print(strcat("Bot execution index: ",   ftos(cmd.owner.bot_cmd_execution_index), "\n\n"));
207 }
208
209 void bot_queuecommand(entity bot, string cmdstring, string parm)
210 {
211         entity cmd;
212         local float cmd_parm_type, i;
213
214         if not(bot.isbot)
215                 return;
216
217         if(!bot_cmds_initialized)
218                 bot_commands_init();
219
220         for(i=1;i<BOT_CMD_COUNTER;++i)
221         {
222                 if(bot_cmd_string[i]!=cmdstring)
223                         continue;
224
225                 cmd_parm_type = bot_cmd_parm_type[i];
226
227                 if(cmd_parm_type!=BOT_CMD_PARAMETER_NONE&&parm=="")
228                 {
229                         print("ERROR: A parameter is required for this command\n");
230                         return;
231                 }
232
233                 // Load command into queue
234                 cmd = bot_spawncmd(bot, i);
235
236                 // Attach parameter
237                 switch(cmd_parm_type)
238                 {
239                         case BOT_CMD_PARAMETER_FLOAT:
240                                 cmd.bot_cmd_parm_float = stof(parm);
241                                 break;
242                         case BOT_CMD_PARAMETER_FLOAT_RELATIVE_TO_TIME:
243                                 cmd.bot_cmd_parm_float = stof(parm) + time;
244                                 break;
245                         case BOT_CMD_PARAMETER_STRING:
246                                 cmd.bot_cmd_parm_string = strzone(parm);
247                                 break;
248                         case BOT_CMD_PARAMETER_VECTOR:
249                                 cmd.bot_cmd_parm_vector = stov(parm);
250                                 break;
251                         default:
252                                 break;
253                 }
254                 return;
255         }
256         print("ERROR: No such command\n");
257 }
258
259 void bot_cmdhelp(string scmd)
260 {
261         local float i, ntype;
262         local string stype;
263
264         if(!bot_cmds_initialized)
265                 bot_commands_init();
266
267         for(i=1;i<BOT_CMD_COUNTER;++i)
268         {
269                 if(bot_cmd_string[i]!=scmd)
270                         continue;
271
272                 ntype = bot_cmd_parm_type[i];
273
274                 switch(ntype)
275                 {
276                         case BOT_CMD_PARAMETER_FLOAT:
277                         case BOT_CMD_PARAMETER_FLOAT_RELATIVE_TO_TIME:
278                                 stype = "float number";
279                                 break;
280                         case BOT_CMD_PARAMETER_STRING:
281                                 stype = "string";
282                                 break;
283                         case BOT_CMD_PARAMETER_VECTOR:
284                                 stype = "vector";
285                                 break;
286                         default:
287                                 stype = "none";
288                                 break;
289                 }
290
291                 print(strcat("Command: ",bot_cmd_string[i],"\nParameter: <",stype,"> \n"));
292
293                 print("Description: ");
294                 switch(i)
295                 {
296                         case BOT_CMD_PAUSE:
297                                 print("Stops the bot completely. Any command other than 'continue' will be ignored.");
298                                 break;
299                         case BOT_CMD_CONTINUE:
300                                 print("Disable paused status");
301                                 break;
302                         case BOT_CMD_WAIT:
303                                 print("Pause command parsing and bot ai for N seconds. Pressed key will remain pressed");
304                                 break;
305                         case BOT_CMD_WAIT_UNTIL:
306                                 print("Pause command parsing and bot ai until time is N from NOW (when the command is added). Pressed key will remain pressed");
307                                 break;
308                         case BOT_CMD_TURN:
309                                 print("Look to the right or left N degrees. For turning to the left use positive numbers.");
310                                 break;
311                         case BOT_CMD_MOVETO:
312                                 print("Walk to an specific coordinate on the map. Usage: moveto \"x y z\"");
313                                 break;
314                         case BOT_CMD_RESETGOAL:
315                                 print("Resets the goal stack");
316                                 break;
317                         case BOT_CMD_CC:
318                                 print("Execute client command. Examples: cc \"say something\"; cc god; cc \"name newnickname\"; cc kill;");
319                                 break;
320                         case BOT_CMD_IF:
321                                 print("Perform simple conditional execution.\n");
322                                 print("Syntax: \n");
323                                 print("        sv_cmd .. if \"condition\"\n");
324                                 print("        sv_cmd ..        <instruction if true>\n");
325                                 print("        sv_cmd ..        <instruction if true>\n");
326                                 print("        sv_cmd .. else\n");
327                                 print("        sv_cmd ..        <instruction if false>\n");
328                                 print("        sv_cmd ..        <instruction if false>\n");
329                                 print("        sv_cmd .. fi\n");
330                                 print("Conditions: a=b, a>b, a<b, a\t\t(spaces not allowed)\n");
331                                 print("            Values in conditions can be numbers, cvars in the form cvar.cvar_string or special fields\n");
332                                 print("Fields: health, speed, flagcarrier\n");
333                                 print("Examples: if health>50; if health>cvar.g_balance_laser_primary_damage; if flagcarrier;");
334                                 break;
335                         case BOT_CMD_RESETAIM:
336                                 print("Points the aim to the coordinates x,y 0,0");
337                                 break;
338                         case BOT_CMD_AIM:
339                                 print("Move the aim x/y (horizontal/vertical) degrees relatives to the bot\n");
340                                 print("There is a 3rd optional parameter telling in how many seconds the aim has to reach the new position\n");
341                                 print("Examples: aim \"90 0\"   // Turn 90 degrees inmediately (positive numbers move to the left/up)\n");
342                                 print("          aim \"0 90 2\" // Will gradually look to the sky in the next two seconds");
343                                 break;
344                         case BOT_CMD_PRESSKEY:
345                                 print("Press one of the following keys: forward, backward, left, right, jump, crouch, attack1, attack2, use\n");
346                                 print("Multiple keys can be pressed at time (with many presskey calls) and it will remain pressed until the command \"releasekey\" is called");
347                                 print("Note: The script will not return the control to the bot ai until all keys are released");
348                                 break;
349                         case BOT_CMD_RELEASEKEY:
350                                 print("Release previoulsy used keys. Use the parameter \"all\" to release all keys");
351                                 break;
352                         default:
353                                 print("This command has no description yet.");
354                                 break;
355                 }
356                 print("\n");
357         }
358 }
359
360 void bot_list_commands()
361 {
362         local float i;
363         local string ptype;
364
365         if(!bot_cmds_initialized)
366                 bot_commands_init();
367
368         print("List of all available commands:\n");
369         print(" Command\t\t\t\tParameter Type\n");
370
371         for(i=1;i<BOT_CMD_COUNTER;++i)
372         {
373                 switch(bot_cmd_parm_type[i])
374                 {
375                         case BOT_CMD_PARAMETER_FLOAT:
376                         case BOT_CMD_PARAMETER_FLOAT_RELATIVE_TO_TIME:
377                                 ptype = "float number";
378                                 break;
379                         case BOT_CMD_PARAMETER_STRING:
380                                 ptype = "string";
381                                 break;
382                         case BOT_CMD_PARAMETER_VECTOR:
383                                 ptype = "vector";
384                                 break;
385                         default:
386                                 ptype = "none";
387                                 break;
388                 }
389                 print(strcat(" ",bot_cmd_string[i],"\t\t\t\t<",ptype,"> \n"));
390         }
391 }
392
393 // Commands code
394 .float bot_exec_status;
395
396 #define BOT_EXEC_STATUS_IDLE    0
397 #define BOT_EXEC_STATUS_PAUSED  1
398 #define BOT_EXEC_STATUS_WAITING 2
399
400 #define CMD_STATUS_EXECUTING    0
401 #define CMD_STATUS_FINISHED     1
402 #define CMD_STATUS_ERROR        2
403
404 float bot_cmd_cc()
405 {
406         clientcommand(self,bot_cmd.bot_cmd_parm_string);
407         return CMD_STATUS_FINISHED;
408 }
409
410 float bot_cmd_impulse()
411 {
412         self.impulse = bot_cmd.bot_cmd_parm_float;
413         return CMD_STATUS_FINISHED;
414 }
415
416 float bot_cmd_continue()
417 {
418         self.bot_exec_status &~= BOT_EXEC_STATUS_PAUSED;
419         return CMD_STATUS_FINISHED;
420 }
421
422 .float bot_cmd_wait_time;
423 float bot_cmd_wait()
424 {
425         if(self.bot_exec_status & BOT_EXEC_STATUS_WAITING)
426         {
427                 if(time>=self.bot_cmd_wait_time)
428                 {
429                         self.bot_exec_status &~= BOT_EXEC_STATUS_WAITING;
430                         return CMD_STATUS_FINISHED;
431                 }
432                 else
433                         return CMD_STATUS_EXECUTING;
434         }
435
436         self.bot_cmd_wait_time = time + bot_cmd.bot_cmd_parm_float;
437         self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
438         return CMD_STATUS_EXECUTING;
439 }
440
441 float bot_cmd_wait_until()
442 {
443         if(time < bot_cmd.bot_cmd_parm_float)
444         {
445                 self.bot_exec_status |= BOT_EXEC_STATUS_WAITING;
446                 return CMD_STATUS_EXECUTING;
447         }
448         self.bot_exec_status &~= BOT_EXEC_STATUS_WAITING;
449         return CMD_STATUS_FINISHED;
450 }
451
452 float bot_cmd_turn()
453 {
454         self.v_angle_y = self.v_angle_y + bot_cmd.bot_cmd_parm_float;
455         self.v_angle_y = self.v_angle_y - floor(self.v_angle_y / 360) * 360;
456         return CMD_STATUS_FINISHED;
457 }
458
459 float bot_cmd_select_weapon()
460 {
461         local float id;
462
463         id = bot_cmd.bot_cmd_parm_float;
464
465         if(id < WEP_FIRST || id > WEP_LAST)
466                 return CMD_STATUS_ERROR;
467
468         if(client_hasweapon(self, id, TRUE, FALSE))
469                 self.switchweapon = id;
470
471         return CMD_STATUS_FINISHED;
472 }
473
474 .float bot_cmd_condition_status;
475
476 #define CMD_CONDITION_NONE              0
477 #define CMD_CONDITION_TRUE              1
478 #define CMD_CONDITION_FALSE             2
479 #define CMD_CONDITION_TRUE_BLOCK        4
480 #define CMD_CONDITION_FALSE_BLOCK       8
481
482 float bot_cmd_eval(string expr)
483 {
484         // Search for numbers
485         if(strstrofs("0123456789", substring(expr, 0, 1), 0) >= 0)
486         {
487                 return stof(expr);
488         }
489
490         // Search for cvars
491         if(substring(expr, 0, 5)=="cvar.")
492         {
493                 return cvar(substring(expr, 5, strlen(expr)));
494         }
495
496         // Search for fields
497         switch(expr)
498         {
499                 case "health":
500                         return self.health;
501                 case "speed":
502                         return vlen(self.velocity);
503                 case "flagcarrier":
504                         return ((self.flagcarried!=world));
505         }
506
507         print(strcat("ERROR: Unable to convert the expression '",expr,"' into a numeric value\n"));
508         return 0;
509 }
510
511 float bot_cmd_if()
512 {
513         local string expr, val_a, val_b;
514         local float cmpofs;
515
516         if(self.bot_cmd_condition_status != CMD_CONDITION_NONE)
517         {
518                 // Only one "if" block is allowed at time
519                 print("ERROR: Only one conditional block can be processed at time");
520                 bot_clearqueue();
521                 return CMD_STATUS_ERROR;
522         }
523
524         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE_BLOCK;
525
526         // search for operators
527         expr = bot_cmd.bot_cmd_parm_string;
528
529         cmpofs = strstrofs(expr,"=",0);
530
531         if(cmpofs>0)
532         {
533                 val_a = substring(expr,0,cmpofs);
534                 val_b = substring(expr,cmpofs+1,strlen(expr));
535
536                 if(bot_cmd_eval(val_a)==bot_cmd_eval(val_b))
537                         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
538                 else
539                         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
540
541                 return CMD_STATUS_FINISHED;
542         }
543
544         cmpofs = strstrofs(expr,">",0);
545
546         if(cmpofs>0)
547         {
548                 val_a = substring(expr,0,cmpofs);
549                 val_b = substring(expr,cmpofs+1,strlen(expr));
550
551                 if(bot_cmd_eval(val_a)>bot_cmd_eval(val_b))
552                         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
553                 else
554                         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
555
556                 return CMD_STATUS_FINISHED;
557         }
558
559         cmpofs = strstrofs(expr,"<",0);
560
561         if(cmpofs>0)
562         {
563                 val_a = substring(expr,0,cmpofs);
564                 val_b = substring(expr,cmpofs+1,strlen(expr));
565
566                 if(bot_cmd_eval(val_a)<bot_cmd_eval(val_b))
567                         self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
568                 else
569                         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
570
571                 return CMD_STATUS_FINISHED;
572         }
573
574         if(bot_cmd_eval(expr))
575                 self.bot_cmd_condition_status |= CMD_CONDITION_TRUE;
576         else
577                 self.bot_cmd_condition_status |= CMD_CONDITION_FALSE;
578
579         return CMD_STATUS_FINISHED;
580 }
581
582 float bot_cmd_else()
583 {
584         self.bot_cmd_condition_status &~= CMD_CONDITION_TRUE_BLOCK;
585         self.bot_cmd_condition_status |= CMD_CONDITION_FALSE_BLOCK;
586         return CMD_STATUS_FINISHED;
587 }
588
589 float bot_cmd_fi()
590 {
591         self.bot_cmd_condition_status = CMD_CONDITION_NONE;
592         return CMD_STATUS_FINISHED;
593 }
594
595 float bot_cmd_resetaim()
596 {
597         self.v_angle = '0 0 0';
598         return CMD_STATUS_FINISHED;
599 }
600
601 .float bot_cmd_aim_begintime;
602 .float bot_cmd_aim_endtime;
603 .vector bot_cmd_aim_begin;
604 .vector bot_cmd_aim_end;
605
606 float bot_cmd_aim()
607 {
608         // Current direction
609         if(self.bot_cmd_aim_endtime)
610         {
611                 local float progress;
612
613                 progress = min(1 - (self.bot_cmd_aim_endtime - time) / (self.bot_cmd_aim_endtime - self.bot_cmd_aim_begintime),1);
614                 self.v_angle = self.bot_cmd_aim_begin + ((self.bot_cmd_aim_end - self.bot_cmd_aim_begin) * progress);
615
616                 if(time>=self.bot_cmd_aim_endtime)
617                 {
618                         self.bot_cmd_aim_endtime = 0;
619                         return CMD_STATUS_FINISHED;
620                 }
621                 else
622                         return CMD_STATUS_EXECUTING;
623         }
624
625         // New aiming direction
626         local string parms;
627         local float tokens, step;
628
629         parms = bot_cmd.bot_cmd_parm_string;
630
631         tokens = tokenizebyseparator(parms, " ");
632
633         if(tokens==2)
634         {
635                 self.v_angle_x -= stof(argv(1));
636                 self.v_angle_y += stof(argv(0));
637                 return CMD_STATUS_FINISHED;
638         }
639
640         if(tokens<1||tokens>3)
641                 return CMD_STATUS_ERROR;
642
643         step = stof(argv(2));
644
645         self.bot_cmd_aim_begin = self.v_angle;
646
647         self.bot_cmd_aim_end_x = self.v_angle_x - stof(argv(1));
648         self.bot_cmd_aim_end_y = self.v_angle_y + stof(argv(0));
649         self.bot_cmd_aim_end_z = 0;
650
651         self.bot_cmd_aim_begintime = time;
652         self.bot_cmd_aim_endtime = time + step;
653
654         return CMD_STATUS_EXECUTING;
655 }
656
657 .float bot_cmd_keys;
658
659 #define BOT_CMD_KEY_NONE        0
660 #define BOT_CMD_KEY_FORWARD     1
661 #define BOT_CMD_KEY_BACKWARD    2
662 #define BOT_CMD_KEY_RIGHT       4
663 #define BOT_CMD_KEY_LEFT        8
664 #define BOT_CMD_KEY_JUMP        16
665 #define BOT_CMD_KEY_ATTACK1     32
666 #define BOT_CMD_KEY_ATTACK2     64
667 #define BOT_CMD_KEY_USE         128
668 #define BOT_CMD_KEY_HOOK        256
669 #define BOT_CMD_KEY_CROUCH      512
670
671 float bot_presskeys()
672 {
673         self.movement = '0 0 0';
674
675         if(self.bot_cmd_keys == BOT_CMD_KEY_NONE)
676                 return FALSE;
677
678         if(self.bot_cmd_keys & BOT_CMD_KEY_FORWARD)
679                 self.movement_x = cvar("sv_maxspeed");
680         else if(self.bot_cmd_keys & BOT_CMD_KEY_BACKWARD)
681                 self.movement_x = -cvar("sv_maxspeed");
682
683         if(self.bot_cmd_keys & BOT_CMD_KEY_RIGHT)
684                 self.movement_y = cvar("sv_maxspeed");
685         else if(self.bot_cmd_keys & BOT_CMD_KEY_LEFT)
686                 self.movement_y = -cvar("sv_maxspeed");
687
688         if(self.bot_cmd_keys & BOT_CMD_KEY_JUMP)
689                 self.BUTTON_JUMP = TRUE;
690
691         if(self.bot_cmd_keys & BOT_CMD_KEY_CROUCH)
692                 self.BUTTON_CROUCH = TRUE;
693
694         if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK1)
695                 self.BUTTON_ATCK = TRUE;
696
697         if(self.bot_cmd_keys & BOT_CMD_KEY_ATTACK2)
698                 self.BUTTON_ATCK2 = TRUE;
699
700         if(self.bot_cmd_keys & BOT_CMD_KEY_USE)
701                 self.BUTTON_USE = TRUE;
702
703         return TRUE;
704 }
705
706
707 float bot_cmd_keypress_handler(string key, float enabled)
708 {
709         switch(key)
710         {
711                 case "all":
712                         if(enabled)
713                                 self.bot_cmd_keys = power2of(20) - 1; // >:)
714                         else
715                                 self.bot_cmd_keys = BOT_CMD_KEY_NONE;
716                 case "forward":
717                         if(enabled)
718                         {
719                                 self.bot_cmd_keys |= BOT_CMD_KEY_FORWARD;
720                                 self.bot_cmd_keys &~= BOT_CMD_KEY_BACKWARD;
721                         }
722                         else
723                                 self.bot_cmd_keys &~= BOT_CMD_KEY_FORWARD;
724                         break;
725                 case "backward":
726                         if(enabled)
727                         {
728                                 self.bot_cmd_keys |= BOT_CMD_KEY_BACKWARD;
729                                 self.bot_cmd_keys &~= BOT_CMD_KEY_FORWARD;
730                         }
731                         else
732                                 self.bot_cmd_keys &~= BOT_CMD_KEY_BACKWARD;
733                         break;
734                 case "left":
735                         if(enabled)
736                         {
737                                 self.bot_cmd_keys |= BOT_CMD_KEY_LEFT;
738                                 self.bot_cmd_keys &~= BOT_CMD_KEY_RIGHT;
739                         }
740                         else
741                                 self.bot_cmd_keys &~= BOT_CMD_KEY_LEFT;
742                         break;
743                 case "right":
744                         if(enabled)
745                         {
746                                 self.bot_cmd_keys |= BOT_CMD_KEY_RIGHT;
747                                 self.bot_cmd_keys &~= BOT_CMD_KEY_LEFT;
748                         }
749                         else
750                                 self.bot_cmd_keys &~= BOT_CMD_KEY_RIGHT;
751                         break;
752                 case "jump":
753                         if(enabled)
754                                 self.bot_cmd_keys |= BOT_CMD_KEY_JUMP;
755                         else
756                                 self.bot_cmd_keys &~= BOT_CMD_KEY_JUMP;
757                         break;
758                 case "crouch":
759                         if(enabled)
760                                 self.bot_cmd_keys |= BOT_CMD_KEY_CROUCH;
761                         else
762                                 self.bot_cmd_keys &~= BOT_CMD_KEY_CROUCH;
763                         break;
764                 case "attack1":
765                         if(enabled)
766                                 self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK1;
767                         else
768                                 self.bot_cmd_keys &~= BOT_CMD_KEY_ATTACK1;
769                         break;
770                 case "attack2":
771                         if(enabled)
772                                 self.bot_cmd_keys |= BOT_CMD_KEY_ATTACK2;
773                         else
774                                 self.bot_cmd_keys &~= BOT_CMD_KEY_ATTACK2;
775                         break;
776                 case "use":
777                         if(enabled)
778                                 self.bot_cmd_keys |= BOT_CMD_KEY_USE;
779                         else
780                                 self.bot_cmd_keys &~= BOT_CMD_KEY_USE;
781                         break;
782                 default:
783                         break;
784         }
785
786         return CMD_STATUS_FINISHED;
787 }
788
789 float bot_cmd_presskey()
790 {
791         local string key;
792
793         key = bot_cmd.bot_cmd_parm_string;
794
795         bot_cmd_keypress_handler(key,TRUE);
796
797         return CMD_STATUS_FINISHED;
798 }
799
800 float bot_cmd_releasekey()
801 {
802         local string key;
803
804         key = bot_cmd.bot_cmd_parm_string;
805
806         return bot_cmd_keypress_handler(key,FALSE);
807 }
808
809 float bot_cmd_pause()
810 {
811         self.button1        = 0;
812         self.button8        = 0;
813         self.BUTTON_USE     = 0;
814         self.BUTTON_ATCK    = 0;
815         self.BUTTON_JUMP    = 0;
816         self.BUTTON_HOOK    = 0;
817         self.BUTTON_CHAT    = 0;
818         self.BUTTON_ATCK2   = 0;
819         self.BUTTON_CROUCH  = 0;
820
821         self.movement = '0 0 0';
822         self.bot_cmd_keys = BOT_CMD_KEY_NONE;
823
824         self.bot_exec_status = self.bot_exec_status | BOT_EXEC_STATUS_PAUSED;
825         return CMD_STATUS_FINISHED;
826 }
827
828 float bot_cmd_moveto()
829 {
830         return self.cmd_moveto(bot_cmd.bot_cmd_parm_vector);
831 }
832
833 float bot_cmd_resetgoal()
834 {
835         return self.cmd_resetgoal();
836 }
837
838 void bot_command_executed(float rm)
839 {
840         entity cmd;
841
842         cmd = bot_cmd;
843
844         self.bot_cmd_execution_index++;
845
846         if(rm)
847         {
848                 if(bot_cmd_parm_type[cmd.bot_cmd_type]==BOT_CMD_PARAMETER_STRING)
849                 {
850                         strunzone(cmd.bot_cmd_parm_string);
851                 }
852                 remove(cmd);
853                 return;
854         }
855
856         cmd.bot_cmd_execution_counter++;
857 }
858
859 void bot_setcurrentcommand()
860 {
861         entity cmd;
862
863         bot_cmd = world;
864
865         if(self.bot_cmd_execution_index==0)
866                 self.bot_cmd_execution_index=1;
867
868         for (cmd = findchainfloat(bot_cmd_index, self.bot_cmd_execution_index); cmd; cmd = cmd.chain)
869         {
870                 if(cmd.owner==self)
871                 {
872                         bot_cmd = cmd;
873                         return;
874                 }
875         }
876 }
877
878 // This function should be (the only) called directly from the bot ai loop
879 // It maps commands to functions and deal with complex interactions between commands and execution states
880 // NOTE: Of course you need to include your commands here too :)
881 float bot_execute_commands()
882 {
883         local float status, ispressingkey;
884
885 :next
886         // Find command
887         bot_setcurrentcommand();
888
889         // Keep pressing keys raised by the "presskey" command
890         ispressingkey = bot_presskeys();
891
892         if(bot_cmd==world)
893                 return ispressingkey;
894
895         // Ignore all commands except continue when the bot is paused
896         if(self.bot_exec_status & BOT_EXEC_STATUS_PAUSED)
897         if(bot_cmd.bot_cmd_type!=BOT_CMD_CONTINUE)
898         {
899                 if(bot_cmd.bot_cmd_type!=BOT_CMD_NULL)
900                 {
901                         bot_command_executed(TRUE);
902                         print( "WARNING: Commands are ignored while the bot is paused. Use the command 'continue' instead.\n");
903                 }
904                 return TRUE;
905         }
906
907         // Handle conditions
908         if not(bot_cmd.bot_cmd_type==BOT_CMD_FI||bot_cmd.bot_cmd_type==BOT_CMD_ELSE)
909         if(self.bot_cmd_condition_status & CMD_CONDITION_TRUE && self.bot_cmd_condition_status & CMD_CONDITION_FALSE_BLOCK)
910         {
911                 bot_command_executed(TRUE);
912                 goto next;
913         }
914         else if(self.bot_cmd_condition_status & CMD_CONDITION_FALSE && self.bot_cmd_condition_status & CMD_CONDITION_TRUE_BLOCK)
915         {
916                 bot_command_executed(TRUE);
917                 goto next;
918         }
919
920         // Map commands to functions
921         switch(bot_cmd.bot_cmd_type)
922         {
923                 case BOT_CMD_NULL:
924                         return ispressingkey;
925                         break;
926                 case BOT_CMD_PAUSE:
927                         status = bot_cmd_pause();
928                         break;
929                 case BOT_CMD_CONTINUE:
930                         status = bot_cmd_continue();
931                         break;
932                 case BOT_CMD_WAIT:
933                         status = bot_cmd_wait();
934                         break;
935                 case BOT_CMD_WAIT_UNTIL:
936                         status = bot_cmd_wait_until();
937                         break;
938                 case BOT_CMD_TURN:
939                         status = bot_cmd_turn();
940                         break;
941                 case BOT_CMD_MOVETO:
942                         status = bot_cmd_moveto();
943                         break;
944                 case BOT_CMD_RESETGOAL:
945                         status = bot_cmd_resetgoal();
946                         break;
947                 case BOT_CMD_CC:
948                         status = bot_cmd_cc();
949                         break;
950                 case BOT_CMD_IF:
951                         status = bot_cmd_if();
952                         break;
953                 case BOT_CMD_ELSE:
954                         status = bot_cmd_else();
955                         break;
956                 case BOT_CMD_FI:
957                         status = bot_cmd_fi();
958                         break;
959                 case BOT_CMD_RESETAIM:
960                         status = bot_cmd_resetaim();
961                         break;
962                 case BOT_CMD_AIM:
963                         status = bot_cmd_aim();
964                         break;
965                 case BOT_CMD_PRESSKEY:
966                         status = bot_cmd_presskey();
967                         break;
968                 case BOT_CMD_RELEASEKEY:
969                         status = bot_cmd_releasekey();
970                         break;
971                 case BOT_CMD_SELECTWEAPON:
972                         status = bot_cmd_select_weapon();
973                         break;
974                 case BOT_CMD_IMPULSE:
975                         status = bot_cmd_impulse();
976                         break;
977                 case BOT_CMD_RESETQUEUE:
978                         bot_clearqueue();
979                         status = CMD_STATUS_FINISHED;
980                         break;
981                 default:
982                         print(strcat("ERROR: Invalid command on queue with id '",ftos(bot_cmd.bot_cmd_type),"'\n"));
983                         return FALSE;
984         }
985
986         if (status==CMD_STATUS_ERROR)
987                 print(strcat("ERROR: The command '",bot_cmd_string[bot_cmd.bot_cmd_type],"' returned an error status\n"));
988
989         // Move execution pointer
990         if(status==CMD_STATUS_EXECUTING)
991         {
992                 return TRUE;
993         }
994         else
995         {
996                 if(cvar("g_debug_bot_commands"))
997                 {
998                         local string parms;
999
1000                         switch(bot_cmd_parm_type[bot_cmd.bot_cmd_type])
1001                         {
1002                                 case BOT_CMD_PARAMETER_FLOAT:
1003                                 case BOT_CMD_PARAMETER_FLOAT_RELATIVE_TO_TIME:
1004                                         parms = ftos(bot_cmd.bot_cmd_parm_float);
1005                                         break;
1006                                 case BOT_CMD_PARAMETER_STRING:
1007                                         parms = bot_cmd.bot_cmd_parm_string;
1008                                         break;
1009                                 case BOT_CMD_PARAMETER_VECTOR:
1010                                         parms = vtos(bot_cmd.bot_cmd_parm_vector);
1011                                         break;
1012                                 default:
1013                                         parms = "";
1014                                         break;
1015                         }
1016                         clientcommand(self,strcat("say ^7", bot_cmd_string[bot_cmd.bot_cmd_type]," ",parms,"\n"));
1017                 }
1018
1019                 bot_command_executed(TRUE);
1020         }
1021
1022         if(status>-1) // Always true, just to fix a compiler warning
1023                 goto next;
1024         else
1025                 return status;
1026 }