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