Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
scripting.qc
Go to the documentation of this file.
1#include "scripting.qh"
2
5#include <common/state.qh>
6#include <common/stats.qh>
8#include <common/wepent.qh>
13
14.int state;
15
20
22{
23 if(!bot.bot_cmdqueuebuf_allocated)
24 return;
25 buf_del(bot.bot_cmdqueuebuf);
26 bot.bot_cmdqueuebuf_allocated = false;
27 LOG_TRACE("bot ", bot.netname, " queue cleared");
28}
29
30void bot_queuecommand(entity bot, string cmdstring)
31{
32 if(!bot.bot_cmdqueuebuf_allocated)
33 {
34 bot.bot_cmdqueuebuf = buf_create();
35 bot.bot_cmdqueuebuf_allocated = true;
36 bot.bot_cmdqueuebuf_start = 0;
37 bot.bot_cmdqueuebuf_end = 0;
38 }
39
40 bufstr_set(bot.bot_cmdqueuebuf, bot.bot_cmdqueuebuf_end, cmdstring);
41
42 // if the command was a "sound" command, precache the sound NOW
43 // this prevents lagging!
44 {
45 float sp;
46 string parm;
47 string cmdstr;
48
49 sp = strstrofs(cmdstring, " ", 0);
50 if(sp >= 0)
51 {
52 parm = substring(cmdstring, sp + 1, -1);
53 cmdstr = substring(cmdstring, 0, sp);
54 if(cmdstr == "sound")
55 {
56 // find the LAST word
57 for (;;)
58 {
59 sp = strstrofs(parm, " ", 0);
60 if(sp < 0)
61 break;
62 parm = substring(parm, sp + 1, -1);
63 }
64 precache_sound(parm);
65 }
66 }
67 }
68
69 bot.bot_cmdqueuebuf_end += 1;
70}
71
72void bot_dequeuecommand(entity bot, float idx)
73{
74 if(!bot.bot_cmdqueuebuf_allocated)
75 error("dequeuecommand but no queue allocated");
76 if(idx < bot.bot_cmdqueuebuf_start)
77 error("dequeueing a command in the past");
78 if(idx >= bot.bot_cmdqueuebuf_end)
79 error("dequeueing a command in the future");
80 bufstr_set(bot.bot_cmdqueuebuf, idx, "");
81 if(idx == bot.bot_cmdqueuebuf_start)
82 bot.bot_cmdqueuebuf_start += 1;
83 if(bot.bot_cmdqueuebuf_start >= bot.bot_cmdqueuebuf_end)
84 bot_clearqueue(bot);
85}
86
87string bot_readcommand(entity bot, float idx)
88{
89 if(!bot.bot_cmdqueuebuf_allocated)
90 error("readcommand but no queue allocated");
91 if(idx < bot.bot_cmdqueuebuf_start)
92 error("reading a command in the past");
93 if(idx >= bot.bot_cmdqueuebuf_end)
94 error("reading a command in the future");
95 return bufstr_get(bot.bot_cmdqueuebuf, idx);
96}
97
98bool bot_havecommand(entity this, int idx)
99{
101 return false;
102 if(idx < this.bot_cmdqueuebuf_start)
103 return false;
104 if(idx >= this.bot_cmdqueuebuf_end)
105 return false;
106 return true;
107}
108
109const int MAX_BOT_PLACES = 4;
113entity bot_getplace(entity this, string placename)
114{
115 entity e;
116 if(substring(placename, 0, 1) == "@")
117 {
118 int i, p;
119 placename = substring(placename, 1, -1);
120 string s, s2;
121 for(i = 0; i < this.bot_places_count; ++i)
122 if(this.(bot_placenames[i]) == placename)
123 return this.(bot_places[i]);
124 // now: i == this.bot_places_count
125 s = s2 = cvar_string(placename);
126 p = strstrofs(s2, " ", 0);
127 if(p >= 0)
128 {
129 s = substring(s2, 0, p);
130 //print("places: ", placename, " -> ", cvar_string(placename), "\n");
131 cvar_set(placename, strcat(substring(s2, p+1, -1), " ", s));
132 //print("places: ", placename, " := ", cvar_string(placename), "\n");
133 }
134 e = find(NULL, targetname, s);
135 if(!e)
136 LOG_INFO("invalid place ", s);
137 if(i < MAX_BOT_PLACES)
138 {
139 this.(bot_placenames[i]) = strzone(placename);
140 this.(bot_places[i]) = e;
141 this.bot_places_count += 1;
142 }
143 return e;
144 }
145 else
146 {
147 e = find(NULL, targetname, placename);
148 if(!e)
149 LOG_INFO("invalid place ", placename);
150 return e;
151 }
152}
153
154
155// Initialize global commands list
156// NOTE: New commands should be initialized here
158{
161
162 bot_cmd_string[BOT_CMD_PAUSE] = "pause";
164
165 bot_cmd_string[BOT_CMD_CONTINUE] = "continue";
167
170
173
174 bot_cmd_string[BOT_CMD_MOVETO] = "moveto";
176
177 bot_cmd_string[BOT_CMD_MOVETOTARGET] = "movetotarget";
179
180 bot_cmd_string[BOT_CMD_RESETGOAL] = "resetgoal";
182
185
188
191
194
195 bot_cmd_string[BOT_CMD_RESETAIM] = "resetaim";
197
200
201 bot_cmd_string[BOT_CMD_AIMTARGET] = "aimtarget";
203
204 bot_cmd_string[BOT_CMD_PRESSKEY] = "presskey";
206
207 bot_cmd_string[BOT_CMD_RELEASEKEY] = "releasekey";
209
210 bot_cmd_string[BOT_CMD_SELECTWEAPON] = "selectweapon";
212
213 bot_cmd_string[BOT_CMD_IMPULSE] = "impulse";
215
216 bot_cmd_string[BOT_CMD_WAIT_UNTIL] = "wait_until";
218
219 bot_cmd_string[BOT_CMD_BARRIER] = "barrier";
221
222 bot_cmd_string[BOT_CMD_CONSOLE] = "console";
224
225 bot_cmd_string[BOT_CMD_SOUND] = "sound";
227
228 bot_cmd_string[BOT_CMD_DEBUG_ASSERT_CANFIRE] = "debug_assert_canfire";
230
232}
233
234// Returns first bot with matching name
236{
237 FOREACH_CLIENT(IS_BOT_CLIENT(it) && it.netname == name,
238 {
239 return it;
240 });
241
242 return NULL;
243}
244
245// Returns a bot by number on list
247{
248 entity bot;
249 float c = 0;
250
251 if(!number)
252 return NULL;
253
254 bot = findchainflags(flags, FL_CLIENT); // TODO: doesn't findchainflags loop backwards through entities?
255 while (bot)
256 {
257 if(IS_BOT_CLIENT(bot))
258 {
259 if(++c==number)
260 return bot;
261 }
262 bot = bot.chain;
263 }
264
265 return NULL;
266}
267
268float bot_decodecommand(string cmdstring)
269{
270 float cmd_parm_type;
271 float sp;
272 string parm;
273
274 sp = strstrofs(cmdstring, " ", 0);
275 if(sp < 0)
276 {
277 parm = "";
278 }
279 else
280 {
281 parm = substring(cmdstring, sp + 1, -1);
282 cmdstring = substring(cmdstring, 0, sp);
283 }
284
287
288 int i;
289 for(i=1;i<BOT_CMD_COUNTER;++i)
290 {
291 if(bot_cmd_string[i]!=cmdstring)
292 continue;
293
294 cmd_parm_type = bot_cmd_parm_type[i];
295
296 if(cmd_parm_type!=BOT_CMD_PARAMETER_NONE&&parm=="")
297 {
298 LOG_INFO("ERROR: A parameter is required for this command");
299 return 0;
300 }
301
302 // Load command into queue
303 bot_cmd.bot_cmd_type = i;
304
305 // Attach parameter
306 switch(cmd_parm_type)
307 {
309 bot_cmd.bot_cmd_parm_float = stof(parm);
310 break;
312 strcpy(bot_cmd.bot_cmd_parm_string, parm);
313 break;
315 if(substring(parm, 0, 1) != "\'")
316 {
317 LOG_INFOF("ERROR: expected vector type \'x y z\', got %s", parm);
318 return 0;
319 }
320 bot_cmd.bot_cmd_parm_vector = stov(parm);
321 break;
322 default:
323 break;
324 }
325 return 1;
326 }
327 LOG_INFO("ERROR: No such command '", cmdstring, "'");
328 return 0;
329}
330
331void bot_cmdhelp(string scmd)
332{
333 int i, ntype;
334 string stype;
335
338
339 for(i=1;i<BOT_CMD_COUNTER;++i)
340 {
341 if(bot_cmd_string[i]!=scmd)
342 continue;
343
344 ntype = bot_cmd_parm_type[i];
345
346 switch(ntype)
347 {
349 stype = "float";
350 break;
352 stype = "string";
353 break;
355 stype = "vector";
356 break;
357 default:
358 stype = "none";
359 break;
360 }
361
362 string desc = "";
363 switch(i)
364 {
365 case BOT_CMD_PAUSE:
366 desc = "Stops the bot completely. Any command other than 'continue' will be ignored.";
367 break;
368 case BOT_CMD_CONTINUE:
369 desc = "Disable paused status";
370 break;
371 case BOT_CMD_WAIT:
372 desc = "Pause command parsing and bot ai for N seconds. Pressed key will remain pressed";
373 break;
375 desc = "Pause command parsing and bot ai until time is N from the last barrier. Pressed key will remain pressed";
376 break;
377 case BOT_CMD_BARRIER:
378 desc = "Waits till all bots that have a command queue reach this command. Pressed key will remain pressed";
379 break;
380 case BOT_CMD_TURN:
381 desc = "Look to the right or left N degrees. For turning to the left use positive numbers.";
382 break;
383 case BOT_CMD_MOVETO:
384 desc = "Walk to an specific coordinate on the map. Usage: moveto \'x y z\'";
385 break;
387 desc = "Walk to the specific target on the map";
388 break;
390 desc = "Resets the goal stack";
391 break;
392 case BOT_CMD_CC:
393 desc = "Execute client command. Examples: cc say something; cc god; cc name newnickname; cc kill;";
394 break;
395 case BOT_CMD_IF:
396 desc = "Perform simple conditional execution.\n"
397 "Syntax: \n"
398 " sv_cmd .. if \"condition\"\n"
399 " sv_cmd .. <instruction if true>\n"
400 " sv_cmd .. <instruction if true>\n"
401 " sv_cmd .. else\n"
402 " sv_cmd .. <instruction if false>\n"
403 " sv_cmd .. <instruction if false>\n"
404 " sv_cmd .. fi\n"
405 "Conditions: a=b, a>b, a<b, a\t\t(spaces not allowed)\n"
406 " Values in conditions can be numbers, cvars in the form cvar.cvar_string or special fields\n"
407 "Fields: health, speed, flagcarrier\n"
408 "Examples: if health>50; if health>cvar.g_balance_laser_primary_damage; if flagcarrier;";
409 break;
410 case BOT_CMD_RESETAIM:
411 desc = "Points the aim to the coordinates x,y 0,0";
412 break;
413 case BOT_CMD_AIM:
414 desc = "Move the aim x/y (horizontal/vertical) degrees relatives to the bot\n"
415 "There is a 3rd optional parameter telling in how many seconds the aim has to reach the new position\n"
416 "Examples: aim \"90 0\" // Turn 90 degrees inmediately (positive numbers move to the left/up)\n"
417 " aim \"0 90 2\" // Will gradually look to the sky in the next two seconds";
418 break;
420 desc = "Points the aim to given target";
421 break;
422 case BOT_CMD_PRESSKEY:
423 desc = "Press one of the following keys: forward, backward, left, right, jump, crouch, attack1, attack2, use"
424 "Multiple keys can be pressed at time (with many presskey calls) and it will remain pressed until the command \"releasekey\" is called"
425 "Note: The script will not return the control to the bot ai until all keys are released";
426 break;
428 desc = "Release previoulsy used keys. Use the parameter \"all\" to release all keys";
429 break;
430 case BOT_CMD_SOUND:
431 desc = "play sound file at bot location";
432 break;
434 desc = "verify the state of the weapon entity";
435 break;
436 default:
437 desc = "This command has no description yet.";
438 break;
439 }
440 LOG_HELP("Command: ", bot_cmd_string[i], "\nParameter: <", stype, ">", "\nDescription: ", desc);
441 }
442}
443
445{
446 int i;
447 string ptype;
448
451
452 LOG_HELP("Bot commands:");
453
454 for(i=1;i<BOT_CMD_COUNTER;++i)
455 {
456 switch(bot_cmd_parm_type[i])
457 {
459 ptype = "float";
460 break;
462 ptype = "string";
463 break;
465 ptype = "vector";
466 break;
467 default:
468 ptype = "none";
469 break;
470 }
471 if (ptype != "none")
472 LOG_HELP(" ^2", bot_cmd_string[i]," ^7<", ptype, ">");
473 else
474 LOG_HELP(" ^2", bot_cmd_string[i]);
475 }
476 LOG_HELP("For help about a specific command, type bot_cmd help <command>");
477}
478
479// Commands code
481
483{
484 SV_ParseClientCommand(this, bot_cmd.bot_cmd_parm_string);
485 return CMD_STATUS_FINISHED;
486}
487
489{
490 CS(this).impulse = bot_cmd.bot_cmd_parm_float;
491 return CMD_STATUS_FINISHED;
492}
493
500
503{
505 {
506 if(time>=this.bot_cmd_wait_time)
507 {
509 return CMD_STATUS_FINISHED;
510 }
511 else
513 }
514
515 this.bot_cmd_wait_time = time + bot_cmd.bot_cmd_parm_float;
518}
519
521{
522 if(time < bot_cmd.bot_cmd_parm_float + bot_barriertime)
523 {
526 }
528 return CMD_STATUS_FINISHED;
529}
530
532{
533 // 0 = no barrier, 1 = waiting, 2 = waiting finished
534
535 if(this.bot_barrier == 0) // initialization
536 {
537 this.bot_barrier = 1;
538
539 //this.colormod = '4 4 0';
540 }
541
542 if(this.bot_barrier == 1) // find other bots
543 {
544 FOREACH_CLIENT(it.isbot, {
545 if(it.bot_cmdqueuebuf_allocated)
546 if(it.bot_barrier != 1)
547 return CMD_STATUS_EXECUTING; // not all are at the barrier yet
548 });
549
550 // all bots hit the barrier!
551
552 // acknowledge barrier
553 FOREACH_CLIENT(it.isbot, { it.bot_barrier = 2; });
554
556 }
557
558 // if we get here, the barrier is finished
559 // so end it...
560 this.bot_barrier = 0;
561 //this.colormod = '0 0 0';
562
563 return CMD_STATUS_FINISHED;
564}
565
567{
568 this.v_angle_y = this.v_angle.y + bot_cmd.bot_cmd_parm_float;
569 this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
570 return CMD_STATUS_FINISHED;
571}
572
574{
575 float id = bot_cmd.bot_cmd_parm_float;
576
577 if(id < WEP_FIRST || id > WEP_LAST)
578 return CMD_STATUS_ERROR;
579
580 bool success = false;
581
582 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
583 {
584 .entity weaponentity = weaponentities[slot];
585 if(this.(weaponentity).m_weapon == WEP_Null && slot != 0)
586 continue;
587
588 if(client_hasweapon(this, REGISTRY_GET(Weapons, id), weaponentity, true, false))
589 {
590 success = true;
591 this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, id);
592 }
593 }
594
595 if(!success)
596 return CMD_STATUS_ERROR;
597
598 return CMD_STATUS_FINISHED;
599}
600
602
603const int CMD_CONDITION_NONE = 0;
604const int CMD_CONDITION_true = 1;
608
609float bot_cmd_eval(entity this, string expr)
610{
611 // Search for numbers
612 if(IS_DIGIT(substring(expr, 0, 1)))
613 return stof(expr);
614
615 // Search for cvars
616 if(substring(expr, 0, 5)=="cvar.")
617 return cvar(substring(expr, 5, strlen(expr)));
618
619 // Search for fields
620 // TODO: expand with support for more fields (key carrier, ball carrier, armor etc)
621 switch(expr)
622 {
623 case "health":
624 return GetResource(this, RES_HEALTH);
625 case "speed":
626 return vlen(this.velocity);
627 case "flagcarrier":
628 return ((this.flagcarried!=NULL));
629 }
630
631 LOG_INFO("ERROR: Unable to convert the expression '", expr, "' into a numeric value");
632 return 0;
633}
634
636{
637 string expr, val_a, val_b;
638 float cmpofs;
639
641 {
642 // Only one "if" block is allowed at time
643 LOG_INFO("ERROR: Only one conditional block can be processed at time");
644 bot_clearqueue(this);
645 return CMD_STATUS_ERROR;
646 }
647
649
650 // search for operators
651 expr = bot_cmd.bot_cmd_parm_string;
652
653 cmpofs = strstrofs(expr,"=",0);
654
655 if(cmpofs>0)
656 {
657 val_a = substring(expr,0,cmpofs);
658 val_b = substring(expr,cmpofs+1,strlen(expr));
659
660 if(bot_cmd_eval(this, val_a)==bot_cmd_eval(this, val_b))
662 else
664
665 return CMD_STATUS_FINISHED;
666 }
667
668 cmpofs = strstrofs(expr,">",0);
669
670 if(cmpofs>0)
671 {
672 val_a = substring(expr,0,cmpofs);
673 val_b = substring(expr,cmpofs+1,strlen(expr));
674
675 if(bot_cmd_eval(this, val_a)>bot_cmd_eval(this, val_b))
677 else
679
680 return CMD_STATUS_FINISHED;
681 }
682
683 cmpofs = strstrofs(expr,"<",0);
684
685 if(cmpofs>0)
686 {
687 val_a = substring(expr,0,cmpofs);
688 val_b = substring(expr,cmpofs+1,strlen(expr));
689
690 if(bot_cmd_eval(this, val_a)<bot_cmd_eval(this, val_b))
692 else
694
695 return CMD_STATUS_FINISHED;
696 }
697
698 if(bot_cmd_eval(this, expr))
700 else
702
703 return CMD_STATUS_FINISHED;
704}
705
712
718
720{
721 this.v_angle = '0 0 0';
722 return CMD_STATUS_FINISHED;
723}
724
729
731{
732 // Current direction
733 if(this.bot_cmd_aim_endtime)
734 {
735 float progress;
736
737 progress = min(1 - (this.bot_cmd_aim_endtime - time) / (this.bot_cmd_aim_endtime - this.bot_cmd_aim_begintime),1);
738 this.v_angle = this.bot_cmd_aim_begin + ((this.bot_cmd_aim_end - this.bot_cmd_aim_begin) * progress);
739
740 if(time>=this.bot_cmd_aim_endtime)
741 {
742 this.bot_cmd_aim_endtime = 0;
743 return CMD_STATUS_FINISHED;
744 }
745 else
747 }
748
749 // New aiming direction
750 string parms;
751 float tokens, step;
752
753 parms = bot_cmd.bot_cmd_parm_string;
754
755 tokens = tokenizebyseparator(parms, " ");
756
757 if(tokens<2||tokens>3)
758 return CMD_STATUS_ERROR;
759
760 step = (tokens == 3) ? stof(argv(2)) : 0;
761
762 if(step == 0)
763 {
764 this.v_angle_x -= stof(argv(1));
765 this.v_angle_y += stof(argv(0));
766 return CMD_STATUS_FINISHED;
767 }
768
769 this.bot_cmd_aim_begin = this.v_angle;
770
771 this.bot_cmd_aim_end_x = this.v_angle.x - stof(argv(1));
772 this.bot_cmd_aim_end_y = this.v_angle.y + stof(argv(0));
773 this.bot_cmd_aim_end_z = 0;
774
776 this.bot_cmd_aim_endtime = time + step;
777
779}
780
782{
783 if(this.bot_cmd_aim_endtime)
784 {
785 return bot_cmd_aim(this);
786 }
787
788 entity e;
789 string parms;
790 vector v;
791 float tokens, step;
792
793 parms = bot_cmd.bot_cmd_parm_string;
794
795 tokens = tokenizebyseparator(parms, " ");
796
797 e = bot_getplace(this, argv(0));
798 if(!e)
799 return CMD_STATUS_ERROR;
800
801 v = e.origin + (e.mins + e.maxs) * 0.5;
802
803 if(tokens==1)
804 {
805 this.v_angle = vectoangles(v - (this.origin + this.view_ofs));
806 this.v_angle_x = -this.v_angle.x;
807 return CMD_STATUS_FINISHED;
808 }
809
810 if(tokens<1||tokens>2)
811 return CMD_STATUS_ERROR;
812
813 step = stof(argv(1));
814
815 this.bot_cmd_aim_begin = this.v_angle;
816 this.bot_cmd_aim_end = vectoangles(v - (this.origin + this.view_ofs));
817 this.bot_cmd_aim_end_x = -this.bot_cmd_aim_end.x;
818
820 this.bot_cmd_aim_endtime = time + step;
821
823}
824
826
827const int BOT_CMD_KEY_NONE = 0;
830const int BOT_CMD_KEY_RIGHT = BIT(2);
831const int BOT_CMD_KEY_LEFT = BIT(3);
832const int BOT_CMD_KEY_JUMP = BIT(4);
835const int BOT_CMD_KEY_USE = BIT(7);
836const int BOT_CMD_KEY_HOOK = BIT(8);
837const int BOT_CMD_KEY_CROUCH = BIT(9);
838const int BOT_CMD_KEY_CHAT = BIT(10);
839
841{
842 CS(this).movement = '0 0 0';
843 PHYS_INPUT_BUTTON_JUMP(this) = false;
844 PHYS_INPUT_BUTTON_CROUCH(this) = false;
845 PHYS_INPUT_BUTTON_ATCK(this) = false;
846 PHYS_INPUT_BUTTON_ATCK2(this) = false;
847 PHYS_INPUT_BUTTON_USE(this) = false;
848 PHYS_INPUT_BUTTON_HOOK(this) = false;
849 PHYS_INPUT_BUTTON_CHAT(this) = false;
850
852 return false;
853
855 CS(this).movement_x = autocvar_sv_maxspeed;
856 else if(this.bot_cmd_keys & BOT_CMD_KEY_BACKWARD)
857 CS(this).movement_x = -autocvar_sv_maxspeed;
858
860 CS(this).movement_y = autocvar_sv_maxspeed;
861 else if(this.bot_cmd_keys & BOT_CMD_KEY_LEFT)
862 CS(this).movement_y = -autocvar_sv_maxspeed;
863
865 PHYS_INPUT_BUTTON_JUMP(this) = true;
866
868 PHYS_INPUT_BUTTON_CROUCH(this) = true;
869
871 PHYS_INPUT_BUTTON_ATCK(this) = true;
872
874 PHYS_INPUT_BUTTON_ATCK2(this) = true;
875
877 PHYS_INPUT_BUTTON_USE(this) = true;
878
880 PHYS_INPUT_BUTTON_HOOK(this) = true;
881
883 PHYS_INPUT_BUTTON_CHAT(this) = true;
884
885 return true;
886}
887
888
889float bot_cmd_keypress_handler(entity this, string key, float enabled)
890{
891 switch(key)
892 {
893 case "all":
894 if(enabled)
895 this.bot_cmd_keys = (2 ** 20) - 1; // >:)
896 else
898 case "forward":
899 if(enabled)
900 {
903 }
904 else
906 break;
907 case "backward":
908 if(enabled)
909 {
912 }
913 else
915 break;
916 case "left":
917 if(enabled)
918 {
921 }
922 else
924 break;
925 case "right":
926 if(enabled)
927 {
930 }
931 else
933 break;
934 case "jump":
935 if(enabled)
937 else
939 break;
940 case "crouch":
941 if(enabled)
943 else
945 break;
946 case "attack1":
947 if(enabled)
949 else
951 break;
952 case "attack2":
953 if(enabled)
955 else
957 break;
958 case "use":
959 if(enabled)
961 else
963 break;
964 case "hook":
965 if(enabled)
967 else
969 break;
970 case "chat":
971 if(enabled)
973 else
975 break;
976 default:
977 break;
978 }
979
980 return CMD_STATUS_FINISHED;
981}
982
984{
985 string key;
986
987 key = bot_cmd.bot_cmd_parm_string;
988
989 bot_cmd_keypress_handler(this, key,true);
990
991 return CMD_STATUS_FINISHED;
992}
993
995{
996 string key;
997
998 key = bot_cmd.bot_cmd_parm_string;
999
1000 return bot_cmd_keypress_handler(this, key,false);
1001}
1002
1004{
1006}
1007
1009{
1010 PHYS_INPUT_BUTTON_DRAG(this) = false;
1011 PHYS_INPUT_BUTTON_USE(this) = false;
1012 PHYS_INPUT_BUTTON_ATCK(this) = false;
1013 PHYS_INPUT_BUTTON_JUMP(this) = false;
1014 PHYS_INPUT_BUTTON_HOOK(this) = false;
1015 PHYS_INPUT_BUTTON_CHAT(this) = false;
1016 PHYS_INPUT_BUTTON_ATCK2(this) = false;
1017 PHYS_INPUT_BUTTON_CROUCH(this) = false;
1018
1019 CS(this).movement = '0 0 0';
1021
1024 return CMD_STATUS_FINISHED;
1025}
1026
1028{
1029 return this.cmd_moveto(this, bot_cmd.bot_cmd_parm_vector);
1030}
1031
1033{
1034 entity e;
1035 e = bot_getplace(this, bot_cmd.bot_cmd_parm_string);
1036 if(!e)
1037 return CMD_STATUS_ERROR;
1038 return this.cmd_moveto(this, e.origin + (e.mins + e.maxs) * 0.5);
1039}
1040
1042{
1043 return this.cmd_resetgoal(this);
1044}
1045
1046
1048{
1049 string f;
1050 f = bot_cmd.bot_cmd_parm_string;
1051
1052 float n = tokenizebyseparator(f, " ");
1053
1054 string sample = f;
1055 float chan = CH_WEAPON_B;
1056 float vol = VOL_BASE;
1057 float atten = ATTEN_MIN;
1058
1059 if(n >= 1)
1060 sample = argv(n - 1);
1061 if(n >= 2)
1062 chan = stof(argv(0));
1063 if(n >= 3)
1064 vol = stof(argv(1));
1065 if(n >= 4)
1066 atten = stof(argv(2));
1067
1068 precache_sound(f);
1069 _sound(this, chan, sample, vol, atten);
1070
1071 return CMD_STATUS_FINISHED;
1072}
1073
1076{
1077 float f = bot_cmd.bot_cmd_parm_float;
1078
1079 int slot = 0; // TODO: unhardcode?
1080 .entity weaponentity = weaponentities[slot];
1081 if(this.(weaponentity).state != WS_READY)
1082 {
1083 if(f)
1084 {
1085 this.colormod = '0 8 8';
1086 LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by weaponentity state");
1087 }
1088 }
1089 else if(ATTACK_FINISHED(this, weaponentity) > time)
1090 {
1091 if(f)
1092 {
1093 this.colormod = '8 0 8';
1094 LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(this, weaponentity) - time), " seconds left)");
1095 }
1096 }
1097 else if(this.(weaponentity).tuba_note)
1098 {
1099 if(f)
1100 {
1101 this.colormod = '8 0 0';
1102 LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, bot still has an active tuba note");
1103 }
1104 }
1105 else
1106 {
1107 if(!f)
1108 {
1109 this.colormod = '8 8 0';
1110 LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(this, weaponentity) - time), " seconds left");
1111 }
1112 }
1113
1114 return CMD_STATUS_FINISHED;
1115}
1116
1117//
1118
1119void bot_command_executed(entity this, bool rm)
1120{
1121 entity cmd;
1122
1123 cmd = bot_cmd;
1124
1125 if(rm)
1127
1129}
1130
1132{
1133 bot_cmd = NULL;
1134
1135 if(!this.bot_cmd_current)
1136 {
1138 }
1139
1140 bot_cmd = this.bot_cmd_current;
1141 if(bot_cmd.bot_cmd_index != this.bot_cmd_execution_index || this.bot_cmd_execution_index == 0)
1142 {
1144 {
1145 string cmdstring;
1146 cmdstring = bot_readcommand(this, this.bot_cmd_execution_index);
1147 if(bot_decodecommand(cmdstring))
1148 {
1149 bot_cmd.owner = this;
1150 bot_cmd.bot_cmd_index = this.bot_cmd_execution_index;
1151 }
1152 else
1153 {
1154 // Invalid command, remove from queue
1155 bot_cmd = NULL;
1158 }
1159 }
1160 else
1161 bot_cmd = NULL;
1162 }
1163}
1164
1166{
1167 FOREACH_CLIENT(it.isbot, {
1168 it.bot_cmd_execution_index = 0;
1169 bot_clearqueue(it);
1170 // also, cancel all barriers
1171 it.bot_barrier = 0;
1172 for(int i = 0; i < it.bot_places_count; ++i)
1173 {
1174 strfree(it.(bot_placenames[i]));
1175 }
1176 it.bot_places_count = 0;
1177 });
1178
1180}
1181
1182// Here we map commands to functions and deal with complex interactions between commands and execution states
1183// NOTE: Of course you need to include your commands here too :)
1185{
1186 float status, ispressingkey;
1187
1188 // Find command
1190
1191 // Ignore all commands except continue when the bot is paused
1193 {
1194 // if we have no bot command, better return
1195 // old logic kept pressing previously pressed keys, but that has problems
1196 // (namely, it means you cannot make a bot "normal" ever again)
1197 // to keep a bot walking for a while, use the "wait" bot command
1198 if(bot_cmd == world)
1199 return 0;
1200 }
1201 else if(bot_cmd.bot_cmd_type != BOT_CMD_CONTINUE)
1202 {
1203 if(bot_cmd.bot_cmd_type!=BOT_CMD_NULL)
1204 {
1205 bot_command_executed(this, true);
1206 LOG_INFO("WARNING: Commands are ignored while the bot is paused. Use the command 'continue' instead.");
1207 }
1208 return 1;
1209 }
1210
1211 // Keep pressing keys raised by the "presskey" command
1212 ispressingkey = boolean(bot_presskeys(this));
1213
1214 // Handle conditions
1215 if (!(bot_cmd.bot_cmd_type == BOT_CMD_FI || bot_cmd.bot_cmd_type == BOT_CMD_ELSE))
1217 {
1218 bot_command_executed(this, true);
1219 return -1;
1220 }
1222 {
1223 bot_command_executed(this, true);
1224 return -1;
1225 }
1226
1227 // Map commands to functions
1228 switch(bot_cmd.bot_cmd_type)
1229 {
1230 case BOT_CMD_NULL:
1231 return ispressingkey;
1232 //break;
1233 case BOT_CMD_PAUSE:
1234 status = bot_cmd_pause(this);
1235 break;
1236 case BOT_CMD_CONTINUE:
1237 status = bot_cmd_continue(this);
1238 break;
1239 case BOT_CMD_WAIT:
1240 status = bot_cmd_wait(this);
1241 break;
1242 case BOT_CMD_WAIT_UNTIL:
1243 status = bot_cmd_wait_until(this);
1244 break;
1245 case BOT_CMD_TURN:
1246 status = bot_cmd_turn(this);
1247 break;
1248 case BOT_CMD_MOVETO:
1249 status = bot_cmd_moveto(this);
1250 break;
1252 status = bot_cmd_movetotarget(this);
1253 break;
1254 case BOT_CMD_RESETGOAL:
1255 status = bot_cmd_resetgoal(this);
1256 break;
1257 case BOT_CMD_CC:
1258 status = bot_cmd_cc(this);
1259 break;
1260 case BOT_CMD_IF:
1261 status = bot_cmd_if(this);
1262 break;
1263 case BOT_CMD_ELSE:
1264 status = bot_cmd_else(this);
1265 break;
1266 case BOT_CMD_FI:
1267 status = bot_cmd_fi(this);
1268 break;
1269 case BOT_CMD_RESETAIM:
1270 status = bot_cmd_resetaim(this);
1271 break;
1272 case BOT_CMD_AIM:
1273 status = bot_cmd_aim(this);
1274 break;
1275 case BOT_CMD_AIMTARGET:
1276 status = bot_cmd_aimtarget(this);
1277 break;
1278 case BOT_CMD_PRESSKEY:
1279 status = bot_cmd_presskey(this);
1280 break;
1281 case BOT_CMD_RELEASEKEY:
1282 status = bot_cmd_releasekey(this);
1283 break;
1285 status = bot_cmd_select_weapon(this);
1286 break;
1287 case BOT_CMD_IMPULSE:
1288 status = bot_cmd_impulse(this);
1289 break;
1290 case BOT_CMD_BARRIER:
1291 status = bot_cmd_barrier(this);
1292 break;
1293 case BOT_CMD_CONSOLE:
1294 localcmd(strcat(bot_cmd.bot_cmd_parm_string, "\n"));
1295 status = CMD_STATUS_FINISHED;
1296 break;
1297 case BOT_CMD_SOUND:
1298 status = bot_cmd_sound(this);
1299 break;
1301 status = bot_cmd_debug_assert_canfire(this);
1302 break;
1303 default:
1304 LOG_INFOF("ERROR: Invalid command on queue with id '%s'", ftos(bot_cmd.bot_cmd_type));
1305 return 0;
1306 }
1307
1308 if (status==CMD_STATUS_ERROR)
1309 LOG_INFOF("ERROR: The command '%s' returned an error status", bot_cmd_string[bot_cmd.bot_cmd_type]);
1310
1311 // Move execution pointer
1312 if(status==CMD_STATUS_EXECUTING)
1313 {
1314 return 1;
1315 }
1316 else
1317 {
1319 {
1320 string parms;
1321
1322 switch(bot_cmd_parm_type[bot_cmd.bot_cmd_type])
1323 {
1325 parms = ftos(bot_cmd.bot_cmd_parm_float);
1326 break;
1328 parms = bot_cmd.bot_cmd_parm_string;
1329 break;
1331 parms = vtos(bot_cmd.bot_cmd_parm_vector);
1332 break;
1333 default:
1334 parms = "";
1335 break;
1336 }
1337 clientcommand(this,strcat("say ^7", bot_cmd_string[bot_cmd.bot_cmd_type]," ",parms,"\n"));
1338 }
1339
1340 bot_command_executed(this, true);
1341 }
1342
1343 if(status == CMD_STATUS_FINISHED)
1344 return -1;
1345
1346 return CMD_STATUS_ERROR;
1347}
1348
1349// This function should be (the only) called directly from the bot ai loop
1351{
1352 int f;
1353 do
1354 {
1355 f = bot_execute_commands_once(this);
1356 }
1357 while(f < 0);
1358 return f;
1359}
void bot_relinkplayerlist()
Definition bot.qc:424
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
#define boolean(value)
Definition bool.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
string netname
Definition powerups.qc:20
vector colormod
Definition powerups.qc:21
#define PHYS_INPUT_BUTTON_CROUCH(s)
Definition player.qh:154
#define PHYS_INPUT_BUTTON_JUMP(s)
Definition player.qh:151
#define PHYS_INPUT_BUTTON_HOOK(s)
Definition player.qh:155
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition player.qh:159
vector v_angle
Definition player.qh:237
float autocvar_sv_maxspeed
Definition player.qh:53
#define PHYS_INPUT_BUTTON_USE(s)
Definition player.qh:158
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition player.qh:150
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition player.qh:152
#define PHYS_INPUT_BUTTON_DRAG(s)
Definition player.qh:157
const int FL_CLIENT
Definition constants.qh:72
float flags
vector velocity
float time
vector origin
bool autocvar_g_debug_bot_commands
Definition cvars.qh:59
int state
#define strstrofs
#define strlen
#define tokenizebyseparator
#define buf_create
Weapons
Definition guide.qh:113
#define SV_ParseClientCommand
Definition _all.inc:284
#define LOG_HELP(...)
Definition log.qh:85
#define LOG_INFO(...)
Definition log.qh:65
#define LOG_TRACE(...)
Definition log.qh:76
#define LOG_INFOF(...)
Definition log.qh:66
string name
Definition menu.qh:30
void localcmd(string command,...)
void cvar_set(string name, string value)
float stof(string val,...)
string substring(string s, float start, float length)
float cvar(string name)
entity find(entity start,.string field, string match)
vector stov(string s)
void clientcommand(float client, string s)
const string cvar_string(string name)
float vlen(vector v)
vector vectoangles(vector v)
void cmd(string command,...)
string precache_sound(string sample)
string vtos(vector v)
float min(float f,...)
string ftos(float f)
entity findchainflags(.float field, float match)
float floor(float f)
string strzone(string s)
string argv(float n)
spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2 f1points s1 s2
Definition all.inc:469
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
#define NULL
Definition post.qh:14
#define world
Definition post.qh:15
#define error
Definition pre.qh:6
vector view_ofs
Definition progsdefs.qc:151
#define REGISTRY_GET(id, i)
Definition registry.qh:43
float bot_cmd_eval(entity this, string expr)
Definition scripting.qc:609
const int BOT_CMD_KEY_ATTACK2
Definition scripting.qc:834
void bot_resetqueues()
float bot_cmd_keypress_handler(entity this, string key, float enabled)
Definition scripting.qc:889
float bot_cmd_sound(entity this)
entity find_bot_by_number(float number)
Definition scripting.qc:246
void bot_setcurrentcommand(entity this)
float bot_cmd_fi(entity this)
Definition scripting.qc:713
float bot_cmd_resetaim(entity this)
Definition scripting.qc:719
float bot_cmd_barrier(entity this)
Definition scripting.qc:531
const int BOT_CMD_KEY_RIGHT
Definition scripting.qc:830
float bot_cmd_turn(entity this)
Definition scripting.qc:566
entity find_bot_by_name(string name)
Definition scripting.qc:235
float bot_cmdqueuebuf_start
Definition scripting.qc:18
float bot_cmd_wait(entity this)
Definition scripting.qc:502
const int BOT_CMD_KEY_ATTACK1
Definition scripting.qc:833
const int BOT_CMD_KEY_BACKWARD
Definition scripting.qc:829
void bot_queuecommand(entity bot, string cmdstring)
Definition scripting.qc:30
bool bot_havecommand(entity this, int idx)
Definition scripting.qc:98
float bot_cmd_aim(entity this)
Definition scripting.qc:730
float bot_cmd_movetotarget(entity this)
float bot_places_count
Definition scripting.qc:110
float bot_cmd_aimtarget(entity this)
Definition scripting.qc:781
const int CMD_CONDITION_true_BLOCK
Definition scripting.qc:606
void bot_cmdhelp(string scmd)
Definition scripting.qc:331
const int CMD_CONDITION_true
Definition scripting.qc:604
const int MAX_BOT_PLACES
Definition scripting.qc:109
void bot_clearqueue(entity bot)
Definition scripting.qc:21
const int CMD_CONDITION_false_BLOCK
Definition scripting.qc:607
const int CMD_CONDITION_false
Definition scripting.qc:605
void bot_commands_init()
Definition scripting.qc:157
float bot_cmd_presskey(entity this)
Definition scripting.qc:983
entity bot_getplace(entity this, string placename)
Definition scripting.qc:113
float bot_cmd_resetgoal(entity this)
float bot_cmdqueuebuf
Definition scripting.qc:17
entity bot_places[MAX_BOT_PLACES]
Definition scripting.qc:111
float bot_cmd_wait_until(entity this)
Definition scripting.qc:520
const int BOT_CMD_KEY_CROUCH
Definition scripting.qc:837
float bot_cmd_continue(entity this)
Definition scripting.qc:494
vector bot_cmd_aim_end
Definition scripting.qc:728
float bot_cmdqueuebuf_end
Definition scripting.qc:19
int bot_cmd_keys
Definition scripting.qc:825
const int BOT_CMD_KEY_FORWARD
Definition scripting.qc:828
float bot_cmd_moveto(entity this)
float bot_cmd_select_weapon(entity this)
Definition scripting.qc:573
float bot_cmd_debug_assert_canfire(entity this)
string bot_placenames[MAX_BOT_PLACES]
Definition scripting.qc:112
float bot_cmd_cc(entity this)
Definition scripting.qc:482
string bot_readcommand(entity bot, float idx)
Definition scripting.qc:87
float bot_cmd_if(entity this)
Definition scripting.qc:635
float bot_execute_commands_once(entity this)
const int BOT_CMD_KEY_HOOK
Definition scripting.qc:836
const int CMD_CONDITION_NONE
Definition scripting.qc:603
void bot_list_commands()
Definition scripting.qc:444
const int BOT_CMD_KEY_CHAT
Definition scripting.qc:838
bool bot_presskeys(entity this)
Definition scripting.qc:840
float bot_cmd_releasekey(entity this)
Definition scripting.qc:994
bool bot_ispaused(entity this)
const int BOT_CMD_KEY_JUMP
Definition scripting.qc:832
int bot_exec_status
Definition scripting.qc:480
const int BOT_CMD_KEY_NONE
Definition scripting.qc:827
float bot_cmd_else(entity this)
Definition scripting.qc:706
int bot_execute_commands(entity this)
float bot_cmd_pause(entity this)
vector bot_cmd_aim_begin
Definition scripting.qc:727
int bot_cmd_condition_status
Definition scripting.qc:601
const int BOT_CMD_KEY_LEFT
Definition scripting.qc:831
const int BOT_CMD_KEY_USE
Definition scripting.qc:835
float bot_decodecommand(string cmdstring)
Definition scripting.qc:268
void bot_command_executed(entity this, bool rm)
float bot_cmd_aim_endtime
Definition scripting.qc:726
float bot_cmd_impulse(entity this)
Definition scripting.qc:488
float bot_cmd_aim_begintime
Definition scripting.qc:725
float bot_cmd_wait_time
Definition scripting.qc:501
void bot_dequeuecommand(entity bot, float idx)
Definition scripting.qc:72
float bot_cmdqueuebuf_allocated
Definition scripting.qc:16
const int BOT_CMD_AIMTARGET
Definition scripting.qh:32
const int BOT_CMD_AIM
Definition scripting.qh:25
entity bot_cmd_current
Definition scripting.qh:60
const int BOT_CMD_PARAMETER_FLOAT
Definition scripting.qh:50
const int BOT_CMD_PARAMETER_NONE
Definition scripting.qh:49
const int BOT_CMD_CC
Definition scripting.qh:20
const int BOT_CMD_PRESSKEY
Definition scripting.qh:26
#define BOT_EXEC_STATUS_PAUSED
Definition scripting.qh:4
const int BOT_CMD_WAIT_UNTIL
Definition scripting.qh:30
#define BOT_EXEC_STATUS_WAITING
Definition scripting.qh:5
const int BOT_CMD_CONSOLE
Definition scripting.qh:34
const int BOT_CMD_ELSE
Definition scripting.qh:22
#define CMD_STATUS_ERROR
Definition scripting.qh:9
const int BOT_CMD_IF
Definition scripting.qh:21
const int BOT_CMD_DEBUG_ASSERT_CANFIRE
Definition scripting.qh:36
#define CMD_STATUS_FINISHED
Definition scripting.qh:8
string bot_cmd_string[BOT_CMD_COUNTER]
Definition scripting.qh:56
const int BOT_CMD_FI
Definition scripting.qh:23
const int BOT_CMD_SOUND
Definition scripting.qh:35
const int BOT_CMD_NULL
Definition scripting.qh:13
#define CMD_STATUS_EXECUTING
Definition scripting.qh:7
const int BOT_CMD_RELEASEKEY
Definition scripting.qh:27
float bot_barrier
Definition scripting.qh:69
const int BOT_CMD_MOVETOTARGET
Definition scripting.qh:31
const int BOT_CMD_PAUSE
Definition scripting.qh:14
const int BOT_CMD_CONTINUE
Definition scripting.qh:15
const int BOT_CMD_MOVETO
Definition scripting.qh:18
const int BOT_CMD_SELECTWEAPON
Definition scripting.qh:28
float bot_barriertime
Definition scripting.qh:68
const int BOT_CMD_PARAMETER_VECTOR
Definition scripting.qh:52
const int BOT_CMD_COUNTER
Definition scripting.qh:41
const int BOT_CMD_PARAMETER_STRING
Definition scripting.qh:51
const int BOT_CMD_IMPULSE
Definition scripting.qh:29
const int BOT_CMD_BARRIER
Definition scripting.qh:33
const int BOT_CMD_WAIT
Definition scripting.qh:16
entity bot_cmd
Definition scripting.qh:59
const int BOT_CMD_TURN
Definition scripting.qh:17
int bot_cmd_parm_type[BOT_CMD_COUNTER]
Definition scripting.qh:55
const int BOT_CMD_RESETGOAL
Definition scripting.qh:19
const int BOT_CMD_RESETAIM
Definition scripting.qh:24
float bot_cmds_initialized
Definition scripting.qh:54
float bot_cmd_execution_index
Definition scripting.qh:71
bool client_hasweapon(entity this, Weapon wpn,.entity weaponentity, float andammo, bool complain)
Definition selection.qc:48
vector
Definition self.qh:92
int int number
Definition impulse.qc:89
const float VOL_BASE
Definition sound.qh:36
const float ATTEN_MIN
Definition sound.qh:28
#define _sound(e, c, s, v, a)
Definition sound.qh:43
const int CH_WEAPON_B
Definition sound.qh:8
ClientState CS(Client this)
Definition state.qh:47
#define IS_DIGIT(d)
Definition string.qh:576
#define strcpy(this, s)
Definition string.qh:52
entity flagcarried
Definition sv_ctf.qh:93
float atten
Definition triggers.qh:47
string targetname
Definition triggers.qh:56
entity tuba_note
Definition tuba.qc:5
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
#define WEP_LAST
Definition all.qh:327
string weaponname
Definition all.qh:388
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
const int WS_READY
idle frame
Definition weapon.qh:41
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17
#define ATTACK_FINISHED(ent, w)
Weapon m_weapon
Definition wepent.qh:26
Weapon m_switchweapon
Definition wepent.qh:25