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