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