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