]> icculus.org git repositories - divverent/nexuiz.git/blob - qcsrc/bot/bot.qc
bots should be able to pick up weapons and powerups (not ammo/health/other misc items...
[divverent/nexuiz.git] / qcsrc / bot / bot.qc
1
2 /*
3 ======================================
4 FrikBot X (Version 0.10.1)
5 ======================================
6
7 This program is in the Public Domain. My crack legal
8 team would like to add:
9
10 RYAN "FRIKAC" SMITH IS PROVIDING THIS SOFTWARE "AS IS"
11 AND MAKES NO WARRANTY, EXPRESS OR IMPLIED, AS TO THE
12 ACCURACY, CAPABILITY, EFFICIENCY, MERCHANTABILITY, OR
13 FUNCTIONING OF THIS SOFTWARE AND/OR DOCUMENTATION. IN
14 NO EVENT WILL RYAN "FRIKAC" SMITH BE LIABLE FOR ANY
15 GENERAL, CONSEQUENTIAL, INDIRECT, INCIDENTAL,
16 EXEMPLARY, OR SPECIAL DAMAGES, EVEN IF RYAN "FRIKAC"
17 SMITH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
18 DAMAGES, IRRESPECTIVE OF THE CAUSE OF SUCH DAMAGES. 
19
20 You accept this software on the condition that you
21 indemnify and hold harmless Ryan "FrikaC" Smith from
22 any and all liability or damages to third parties,
23 including attorney fees, court costs, and other
24 related costs and expenses, arising out of your use
25 of this software irrespective of the cause of said
26 liability. 
27
28 The export from the United States or the subsequent
29 reexport of this software is subject to compliance
30 with United States export control and munitions
31 control restrictions. You agree that in the event you
32 seek to export this software, you assume full
33 responsibility for obtaining all necessary export
34 licenses and approvals and for assuring compliance
35 with applicable reexport restrictions. 
36
37 Any reproduction of this software must contain
38 this notice in its entirety. 
39
40 ======================================
41 These installation instructions only apply to Normal Quake (as does this
42 entire file). For QuakeWorld, please refer to bot_qw.qc
43
44 --------------------------------------
45 To install on a new mod, do all this:
46 --------------------------------------
47 Place all included bot*.qc files in the subdirectory "frikbot"
48 in your source folder, then...
49
50 * Add the following lines to progs.src right after the defs.qc line
51 frikbot/bot.qc
52 frikbot/bot_way.qc
53 frikbot/bot_fight.qc 
54 frikbot/bot_ai.qc
55 frikbot/bot_misc.qc
56 frikbot/bot_phys.qc
57 frikbot/bot_move.qc
58 frikbot/bot_ed.qc
59
60 --------------------------------------
61 * Comment out the following functions in defs.qc
62 sound, stuffcmd, sprint, aim, centerprint, setspawnparms
63 WriteByte, WriteChar, WriteShort, WriteLong, WriteCoord
64 WriteAngle, WriteString, WriteEntity
65 --------------------------------------
66 * Add this to worldspawn() in world.qc, right at the very top, before InitBodyQue();
67 BotInit();  // FrikBot
68 --------------------------------------
69 * add this line to StartFrame() in world.qc, at the very top
70 BotFrame(); // FrikBot
71 --------------------------------------
72 * Add these two lines to PlayerPreThink in client.qc at the very top
73 if (BotPreFrame()) // FrikBot
74         return;
75 --------------------------------------
76 * Add this line to PlayerPostThink in client.qc at the very top
77 if (BotPostFrame()) // FrikBot
78         return;
79 --------------------------------------
80 * Add the following line to the very top of Client Connect in client.qc
81 ClientInRankings(); // FrikBot
82 --------------------------------------
83 * Add these lines to the very top of ClientDisconnect in client.qc
84 ClientDisconnected(); // FrikBot
85 --------------------------------------
86 */
87
88 void() bot_map_load =
89 {
90         // place your qc loaded waypoints here
91         map_mattrye2();
92 };
93
94 /*
95 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
96
97 Variables and shtuff
98
99 bot.qc has become pretty much a header file
100 for all variable in the bot...
101
102 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
103 */
104
105 // ----- entity fields ---
106 .float  wallhug, keys, oldkeys, ishuman;
107 .float  b_frags, b_clientno, b_shirt, b_pants; 
108 .float  priority, ai_time, b_sound, missile_speed;
109 .float  portal_time, b_skill, switch_wallhug;
110 .float  b_aiflags, b_num, b_chattime;
111 .float  b_menu, b_menu_time, b_menu_value;
112 .float route_failed, dyn_flags, dyn_time;
113 .float dyn_plat;
114 .entity temp_way, last_way, phys_obj;
115 .entity target1, target2, target3, target4;
116 .entity _next, _last;
117 .entity current_way;
118 .vector b_angle, b_dest, mouse_emu, obs_dir;
119 .vector movevect, b_dir;
120 .vector dyn_dest;
121
122 .float search_time;
123 .vector dest1, dest2;
124
125 // --------defines-----
126 //float SVC_UPDATENAME  = 13;
127 //float SVC_UPDATEFRAGS = 14;
128 //float SVC_UPDATECOLORS        = 17;
129
130 // used for the physics & movement AI
131 float KEY_MOVEUP                = 1;
132 float KEY_MOVEDOWN      = 2;
133 float KEY_MOVELEFT      = 4;
134 float KEY_MOVERIGHT     = 8;
135 float KEY_MOVEFORWARD   = 16;
136 float KEY_MOVEBACK      = 32;
137 float KEY_LOOKUP                = 64;
138 float KEY_LOOKDOWN      = 128;
139 float KEY_LOOKLEFT      = 256;
140 float KEY_LOOKRIGHT     = 512;
141
142 // these are aiflags for waypoints
143 // some overlap to the bot
144 float AI_TELELINK_1     = 1; // link type
145 float AI_TELELINK_2     = 2; // link type
146 float AI_TELELINK_3     = 4; // link type
147 float AI_TELELINK_4     = 8; // link type
148 float AI_DOORFLAG               = 16; // read ahead
149 float AI_PRECISION      = 32; // read ahead + point
150 float AI_SURFACE                = 64; // point 
151 float AI_BLIND          = 128; // read ahead + point
152 float AI_JUMP           = 256; // point + ignore
153 float AI_DIRECTIONAL    = 512; // read ahead + ignore
154 float AI_PLAT_BOTTOM    = 1024; // read ahead 
155 float AI_RIDE_TRAIN     = 2048; // read ahead 
156 float AI_SUPER_JUMP     = 4096; // point + ignore + route test
157 float AI_SNIPER         = 8192; // point type 
158 float AI_AMBUSH         = 16384; // point type
159 float AI_DOOR_NO_OPEN   = 32768; // read ahead
160 float AI_DIFFICULT      = 65536; // route test
161 float AI_TRACE_TEST     = 131072; // route test
162
163 // these are flags for bots/players (dynamic/editor flags)
164 float AI_OBSTRUCTED     = 1;
165 float AI_HOLD_SELECT    = 2;
166 float AI_ROUTE_FAILED   = 2;
167 float AI_WAIT           = 4;
168 float AI_DANGER         = 8;
169
170
171 // addition masks
172 float AI_POINT_TYPES    = 29152;
173 float AI_READAHEAD_TYPES        = 36528;
174 float AI_IGNORE_TYPES   = 4864;
175
176 float WM_UNINIT         = 0;
177 float WM_DYNAMIC                = 1;
178 float WM_LOADING                = 2;
179 float WM_LOADED         = 3;
180 float WM_EDITOR         = 4;
181 float WM_EDITOR_DYNAMIC = 5;
182 float WM_EDITOR_DYNLINK = 6;
183
184
185 float OPT_SAVEBOTS      = 1;
186 float OPT_NOCHAT        = 2;
187
188 // -------globals-----
189 float   active_clients;
190 float           max_clients, real_frametime;
191 float           bot_count, b_options;
192 float           waypoint_mode, dump_mode; 
193 float           waypoints, direct_route;
194 float           sv_friction, sv_gravity;
195 float           sv_accelerate, sv_maxspeed, sv_stopspeed;
196 entity  fixer;
197 entity  route_table;
198 entity  b_temp1, b_temp2, b_temp3;
199 entity  player_head, phys_head, way_head;
200 float           busy_waypoints;
201 float           saved_bots, saved_skills1, saved_skills2, current_bots;
202
203 // -------ProtoTypes------
204 // external
205 void()                          ClientConnect;
206 void()                          ClientDisconnect;
207 void()                          SetNewParms;
208
209 // rankings
210 float(float clientno)           ClientBitFlag;
211 float()                         ClientNextAvailable;
212 void(float whichteam, float whatbot, float whatskill) BotConnect;
213 void(entity bot)                        BotDisconnect;
214 void(float clientno)            BotInvalidClientNo;
215 void(entity who)                        UpdateClient;
216
217 // waypointing
218 void()                          DynamicWaypoint;
219 entity(vector org)              make_waypoint;
220 void()                          ClearAllWays;
221 void()                          FixWaypoints;
222 float()                         begin_route;
223 void(entity this, float direct)                 bot_get_path;
224 void()                          WaypointThink;
225 entity(entity start)                            FindWayPoint;
226
227 // physics & movement
228 float(entity e)                 bot_can_rj;
229 void()                          bot_jump;
230 void()                          frik_bot_roam;
231 float(vector weird)             frik_walkmove;
232 void()                          frik_movetogoal;
233 void()                          frik_obstacles;
234 float(float flag)                       frik_recognize_plat;
235 float(vector sdir)              frik_KeysForDir;
236 void(vector whichway, float danger) frik_obstructed;
237 void()                          SV_Physics_Client;
238 void()                          SV_ClientThink;
239 void()                          CL_KeyMove;
240
241 // ai & misc
242 string()                                PickARandomName;
243 float(entity targ)              fov;
244 float(float y1, float y2)       angcomp;
245 float(entity targ1, entity targ2)               wisible;
246 float(entity targ)              sisible;
247 float(entity targ)              fisible;
248 vector(entity ent)              realorigin;
249 void(entity ent)                        target_drop;
250 void(entity ent)                        target_add;
251 void()                          KickABot;
252 void()                          BotImpulses;
253 void(entity targ, float success) bot_lost;
254 string(float r)                 BotName;
255 float(float v)                  frik_anglemod;
256 void() bot_chat;
257 void(float tpic) bot_start_topic;
258
259
260 // editor stuffs
261
262 void()                          bot_way_edit;
263 void()                          bot_menu_display;
264
265
266 // ----------Commands---------
267 void(entity e, float chan, string samp, float vol, float atten) frik_sound = #8;
268 void(entity client, string s)   frik_stuffcmd = #21;
269 void(entity client, string s)   frik_sprint = #24;
270 vector(entity e, float sped)    frik_aim = #44; 
271 void(entity client, string s)   frik_centerprint = #73;
272 void(entity e)                  frik_setspawnparms = #78;
273 void(float to, float f)         frik_WriteByte = #52;
274 void(float to, float f)         frik_WriteChar = #53;
275 void(float to, float f)         frik_WriteShort = #54;
276 void(float to, float f)         frik_WriteLong = #55;
277 void(float to, float f)         frik_WriteCoord = #56;
278 void(float to, float f)         frik_WriteAngle = #57;
279 void(float to, string s)        frik_WriteString        = #58;
280 void(float to, entity s)        frik_WriteEntity        = #59;
281
282 void(entity client, string s1, string s2, string s3, string s4, string s5, string s6, string s7)
283 frik_big_centerprint = #73;
284
285 //----------------------------------------------------------------------------
286
287 /*
288 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
289
290 Function redclarations. These allow function 
291 designed to work for clients (sprint, so forth)
292 to mainly not complain when working with a bot
293
294 Although these shouldn't be needed anymore,
295 as the bots truly are clients now, if you don't
296 stop the SZ_ buffer from filling up by disabling
297 direct messages to the bots, it crashes quake :-(
298
299 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
300 */
301 void(entity     client, string s) stuffcmd =
302 {
303         if (client.ishuman == 1)
304                 frik_stuffcmd(client, s);
305         b_temp1 = player_head;
306
307         while(b_temp1)
308         {
309                 if (b_temp1.classname == "botcam")
310                 {
311                         if ((b_temp1.enemy == client) && b_temp1.ishuman)
312                                 frik_stuffcmd(b_temp1, s);
313                 }
314                 b_temp1 = b_temp1._next;
315         }
316 };
317
318 void(entity     e) setspawnparms =
319 {
320         if (e.ishuman == 1)
321                 frik_setspawnparms(e);
322         else
323         {
324                 b_temp1 = player_head;
325                 while(b_temp1)
326                 {
327                         if (b_temp1.ishuman)
328                         {
329                                 frik_setspawnparms(b_temp1);
330                                 return;
331                         }
332                         b_temp1 = b_temp1._next;
333                 }
334                 SetNewParms();
335         }
336 };
337 void(entity     client, string s) sprint =
338 {
339         if (client.ishuman == 1)
340                 frik_sprint(client, s);
341         b_temp1 = player_head;
342
343         while(b_temp1)
344         {
345                 if (b_temp1.classname == "botcam")
346                 {
347                         if ((b_temp1.enemy == client) && b_temp1.ishuman)
348                                 frik_sprint(b_temp1, s);
349                 }
350                 b_temp1 = b_temp1._next;
351         }
352
353 };
354 void(entity     client, string s) centerprint =
355 {
356         if (client.ishuman == 1)
357                 frik_centerprint(client, s);
358         b_temp1 = player_head;
359
360         while(b_temp1)
361         {
362                 if (b_temp1.classname == "botcam")
363                 {
364                         if ((b_temp1.enemy == client) && b_temp1.ishuman)
365                                 frik_centerprint(b_temp1, s);
366                 }
367                 b_temp1 = b_temp1._next;
368         }
369 };
370
371 vector(entity e, float sped) aim =
372 {
373         e.missile_speed = sped;
374         return frik_aim(e, sped);
375 };
376
377 void(entity e, float chan, string samp, float vol, float atten) sound = 
378 {
379
380         frik_sound(e, chan, samp, vol, atten);
381         if (samp == "items/inv3.wav")
382                 return;
383         else if (e.classname == "player")
384                 e.b_sound = time + 1;
385         else if (other.classname == "player")
386                 other.b_sound = time + 1;
387
388 };
389 void(float to, float f) WriteByte =
390 {
391         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
392                 return;
393         frik_WriteByte(to, f);
394 };
395 void(float to, float f) WriteChar =             
396 {
397         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
398                 return;
399         frik_WriteChar(to, f);
400 };
401 void(float to, float f) WriteShort =    
402 {
403         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
404                 return;
405         frik_WriteShort(to, f);
406 };
407 void(float to, float f) WriteLong = 
408 {
409         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
410                 return;
411         frik_WriteLong(to, f);
412 };
413 void(float to, float f) WriteCoord = 
414 {
415         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
416                 return;
417         frik_WriteCoord(to, f);
418 };
419 void(float to, float f) WriteAngle = 
420 {
421         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
422                 return;
423         frik_WriteAngle(to, f);
424 };
425 void(float to, string s) WriteString = 
426 {
427         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
428                 return;
429         frik_WriteString(to, s);
430 };
431 void(float to, entity s) WriteEntity = 
432 {
433         if ((to == MSG_ONE) && (msg_entity.ishuman != TRUE))
434                 return;
435         frik_WriteEntity(to, s);
436 };
437 /*
438 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
439
440 Bot Cam, see what the bot sees (or any other player)
441
442 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
443 */
444
445 float() botcam =
446 {
447         if (self.classname != "botcam")
448                 return FALSE;
449         setorigin(self, self.enemy.origin);
450         self.items = self.enemy.items;
451         self.weapon = self.enemy.weapon;
452         self.weaponmodel = self.enemy.weaponmodel;
453         self.currentammo = self.enemy.currentammo;
454         self.weaponframe = self.enemy.weaponframe;
455         self.ammo_shells = self.enemy.ammo_shells;
456         self.ammo_nails = self.enemy.ammo_nails;
457         self.ammo_rockets= self.enemy.ammo_rockets;
458         self.ammo_cells = self.enemy.ammo_cells;
459         self.view_ofs = self.enemy.view_ofs;
460         self.health = self.enemy.health;
461         self.armorvalue = self.enemy.armorvalue;
462         self.dmg_take = self.enemy.dmg_take;
463         self.dmg_save = self.enemy.dmg_save;
464         self.dmg_inflictor = self.enemy.dmg_inflictor;
465         self.punchangle = self.enemy.punchangle;
466         self.deadflag = self.enemy.deadflag;
467         msg_entity = self;
468         WriteByte (MSG_ONE,5);
469         WriteEntity (MSG_ONE, self.enemy); 
470         WriteByte (MSG_ONE, 10);
471         WriteAngle (MSG_ONE,self.enemy.v_angle_x);
472         WriteAngle (MSG_ONE,self.enemy.v_angle_y);
473         WriteAngle (MSG_ONE,self.enemy.v_angle_z);
474         self.modelindex = 0;
475         
476         self.impulse = 0;       
477         return TRUE;
478         
479 };
480
481 void() botcam_u = 
482 {
483
484         // sloppy cycling code
485         if (self.classname != "botcam")
486         {
487                 self.enemy = player_head;
488         }
489         else
490         {
491                 do
492                         self.enemy = self.enemy._next;
493                 while (self.enemy.classname == "botcam");
494         }
495         if (self.enemy == self)
496         {
497                 do
498                         self.enemy = self.enemy._next;
499                 while (self.enemy.classname == "botcam");
500         }
501
502         self.classname = "botcam";
503         self.solid = SOLID_NOT;
504         self.movetype = MOVETYPE_NONE;
505         self.takedamage = DAMAGE_NO;
506
507
508         if (!self.enemy)
509         {
510                 sprint(self, "No one left to track!\n");
511                 msg_entity = self;
512                 WriteByte (MSG_ONE,5);
513                 WriteEntity (MSG_ONE, self);
514                 PutClientInServer();
515                 return;
516         }
517         if (!self.enemy.ishuman)
518         {
519                 self.enemy.dmg_take = 0;
520                 self.enemy.dmg_save = 0;
521         }
522         sprint(self, "Now tracking ");
523         sprint(self, self.enemy.netname);
524         sprint(self, "\n");
525 };
526
527
528
529 /*
530 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
531
532 Stuff mentioned up top
533 it just links the bot into the mod
534
535 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
536 */
537
538 void() ClientFixRankings =
539 {
540         local float cno;
541         if (self.switch_wallhug > time)
542                 return;
543         self.switch_wallhug = 0;
544
545         b_temp2 = nextent(world);
546         cno = 0;
547
548         while (cno < max_clients)
549         {
550                 if ((!b_temp2.ishuman) && (active_clients & ClientBitFlag(cno)))
551                         UpdateClient(b_temp2);
552                 cno = cno + 1;
553                 b_temp2 = nextent(b_temp2);
554         }
555 };
556                 
557 void() ClientInRankings =
558 {
559         local float cno;
560         if (player_head)
561                 player_head._last = self;
562
563         self._next = player_head;
564         self._last = world;
565         player_head = self;
566
567         if (!self.phys_obj)
568         {
569                 b_temp2 = phys_head;
570                 while (b_temp2 != world && b_temp2.owner != self)
571                         b_temp2 = b_temp2._next;
572                 self.phys_obj = b_temp2;
573         }
574
575         if (self.ishuman == 2)
576         {
577                 self.ishuman = FALSE;
578                 return;
579         }
580         cno = self.colormap - 1;
581         BotInvalidClientNo (cno);
582         active_clients = active_clients | ClientBitFlag(cno);
583         
584         self.b_clientno = cno;
585         self.ishuman = TRUE;
586         self.switch_wallhug = time + 1;
587 };
588
589
590 void() ClientDisconnected =
591 {
592         if (player_head == self)
593                 player_head = self._next;
594         if (self._next)
595                 self._next._last = self._last;
596         if (self._last)
597                 self._last._next = self._next;
598
599         active_clients = active_clients - active_clients & ClientBitFlag(self.b_clientno);
600 };
601 /*
602 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
603
604 BotPreFrame & BotPostFrame, used to make the
605 bot easier to install
606
607 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
608 */
609 float () BotPreFrame =
610 {
611         if (self.b_clientno == -1)
612                 return TRUE;
613         if (self.ishuman)
614         {
615                 if (self.switch_wallhug)
616                         ClientFixRankings();
617                 if (self.classname == "botcam")
618                         return TRUE;
619         }
620         if (self.b_frags != self.frags)
621         {
622                 
623                 if (self.b_frags > self.frags)
624                 {
625                         if (pointcontents(self.origin) == CONTENT_LAVA)
626                                 bot_start_topic(10);
627                         else
628                                 bot_start_topic(9);
629                 }
630                 else
631                         bot_start_topic(2);
632                 self.b_frags = self.frags;
633         }
634         DynamicWaypoint();
635         return FALSE;
636 };
637 float () BotPostFrame =
638 {
639         if (self.b_clientno == -1)
640                 return TRUE;
641         if (self.ishuman)
642         {
643
644                 if (waypoint_mode > WM_LOADED)
645                         bot_menu_display();
646
647                 BotImpulses();
648
649                 if (botcam())
650                         return TRUE;
651         }
652         return FALSE;
653 };
654
655 /*
656 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
657
658 Bot Chat code
659
660 The rest of this code is in bot_misc.qc
661
662 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
663 */
664 void(string h) BotSay = // simulate talking by composing a 'chat' message
665 {
666         WriteByte(MSG_ALL, 8);
667         WriteByte(MSG_ALL, 1);
668         WriteString(MSG_ALL, self.netname);
669         WriteByte(MSG_ALL, 8);
670         WriteByte(MSG_ALL, 2);
671         WriteString(MSG_ALL, h);
672 };
673 void() BotSayInit =
674 {
675         WriteByte(MSG_ALL, 8);
676         WriteByte(MSG_ALL, 1);
677         WriteString(MSG_ALL, self.netname);
678 };
679 void(string h) BotSay2 =
680 {
681         WriteByte(MSG_ALL, 8);
682         WriteByte(MSG_ALL, 2);
683         WriteString(MSG_ALL, h);
684 };
685 void(string h) BotSayTeam = 
686 {
687         local entity t;
688         if (!teamplay)
689                 return;
690         t = player_head;
691         while(t)
692         {
693                 if (t.team == self.team)
694                 {
695                         msg_entity = t;
696                         WriteByte(MSG_ONE, 8);
697                         WriteByte(MSG_ONE, 1);
698                         WriteByte(MSG_ONE, 40);
699                         WriteString(MSG_ONE, self.netname);
700                         WriteByte(MSG_ONE, 8);
701                         WriteByte(MSG_ONE, 2);
702                         WriteByte(MSG_ONE, 41);
703                         WriteString(MSG_ONE, h);
704                 }
705                 t = t._next;
706         }
707 };
708 /*
709 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
710
711 BotInit
712
713 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
714 */
715
716
717 void() BotInit =
718 {
719         local entity ent, fisent;
720         local float numents;
721         
722         // spawn entities for the physics
723         ent = nextent(world);
724         max_clients = 0;
725
726         while(ent != world)
727         {
728                 max_clients = max_clients + 1;
729                 ent = nextent(ent);
730         }
731         if (max_clients > 16)
732                 max_clients = 16;
733
734         ent = nextent(world);
735         fisent = world;
736         while (numents < max_clients)
737         {
738
739                 phys_head = spawn();
740                 if (fisent)
741                         fisent._next = phys_head;
742                 phys_head._last = fisent;
743                 fisent = phys_head;
744                 ent.phys_obj = phys_head;
745                 phys_head.classname = "phys_obj";
746                 phys_head.owner = ent;
747                 numents = numents + 1;
748                 ent = nextent(ent);
749         }
750         precache_model("progs/s_light.spr");
751         precache_model("progs/s_bubble.spr");
752         // the bots return!
753         b_options = cvar("saved1");
754         if (coop || (b_options & OPT_SAVEBOTS))
755         {
756                 saved_bots = cvar("scratch1");
757                 saved_skills1 = cvar("scratch2");
758                 saved_skills2 = cvar("scratch3");
759         }
760         cvar_set ("saved4", "0");
761         if (max_clients > 1)
762         {
763                 localcmd("exec maps/");
764                 localcmd(mapname);
765                 localcmd(".way\n");
766                 waypoint_mode = WM_DYNAMIC;
767                 bot_map_load();
768         }
769         else
770                 waypoint_mode = WM_LOADED;
771
772 };
773
774 /*
775 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
776
777 Rankings 'utilities'. Written by Alan Kivlin,
778 this code just fools clients by sending precisely
779 the same network messages as when a real player
780 signs on to the server.
781
782 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
783 */
784
785
786 void(entity who) UpdateClient =
787 {
788         WriteByte (MSG_ALL, SVC_UPDATENAME);
789         WriteByte (MSG_ALL, who.b_clientno);
790         WriteString (MSG_ALL, who.netname);
791         WriteByte (MSG_ALL, SVC_UPDATECOLORS);
792         WriteByte (MSG_ALL, who.b_clientno);
793         WriteByte (MSG_ALL, who.b_shirt * 16 + who.b_pants);
794         WriteByte (MSG_ALL, SVC_UPDATEFRAGS);
795         WriteByte (MSG_ALL, who.b_clientno);
796         WriteShort (MSG_ALL, who.frags);
797 };
798
799 float(float clientno) ClientBitFlag =
800 {
801         // bigger, but faster
802         if (clientno == 0)
803                 return 1;
804         else if (clientno == 1)
805                 return 2;
806         else if (clientno == 2)
807                 return 4;
808         else if (clientno == 3)
809                 return 8;
810         else if (clientno == 4)
811                 return 16;
812         else if (clientno == 5)
813                 return 32;
814         else if (clientno == 6)
815                 return 64;
816         else if (clientno == 7)
817                 return 128;
818         else if (clientno == 8)
819                 return 256;
820         else if (clientno == 9)
821                 return 512;
822         else if (clientno == 10)
823                 return 1024;
824         else if (clientno == 11)
825                 return 2048;
826         else if (clientno == 12)
827                 return 4096;
828         else if (clientno == 13)
829                 return 8192;
830         else if (clientno == 14)
831                 return 16384;
832         else if (clientno == 15)
833                 return 32768;
834         return 0;
835 };
836
837 float() ClientNextAvailable =
838 {
839         local float clientno;
840
841         clientno = max_clients;
842         while(clientno > 0)
843         {
844                 clientno = clientno - 1;
845
846                 if(!(active_clients & ClientBitFlag(clientno)))
847                         return clientno;
848         }
849
850         return -1;
851 };
852
853
854 void(entity e1, entity e2, float flag) DeveloperLightning =
855 {
856         // used to show waypoint links for debugging
857         WriteByte (MSG_BROADCAST, 23);
858         if (flag)
859                 WriteByte (MSG_BROADCAST, 6);
860         else
861                 WriteByte (MSG_BROADCAST, 13);
862         WriteEntity (MSG_BROADCAST, e2);
863         WriteCoord (MSG_BROADCAST, e1.origin_x);
864         WriteCoord (MSG_BROADCAST, e1.origin_y);
865         WriteCoord (MSG_BROADCAST, e1.origin_z);
866         WriteCoord (MSG_BROADCAST, e2.origin_x);
867         WriteCoord (MSG_BROADCAST, e2.origin_y);
868         WriteCoord (MSG_BROADCAST, e2.origin_z); 
869 };
870
871 /*
872 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
873
874 Find Another Color
875
876 Team finding code
877
878 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
879 */
880
881 float(float tcolor) FindAnotherColor =
882 {
883         local float bestbet, scolor, pcount, bestp;
884         bestbet = -1;
885         bestp = 16;
886         while(scolor < 14)
887         {
888                 if (scolor != tcolor)
889                 {
890                         b_temp2 = player_head;
891                         pcount = 0;
892                         while(b_temp2 != world)
893                         {
894                                 if (b_temp2.team == scolor + 1)
895                                         pcount = pcount + 1;
896                                 b_temp2 = b_temp2._next;
897                         }
898                         if ((pcount < bestp) && pcount)
899                         {
900                                 bestbet = scolor;
901                                 bestp = pcount;
902                         }
903                 }
904                 scolor = scolor + 1;
905         }
906         if (bestbet < 0)
907         {
908                 bestbet = tcolor;
909                 while (bestbet == tcolor)
910                 {
911                         bestbet = floor(random() * 13);
912                 }
913         }
914         return bestbet;
915 };
916
917 /*
918 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
919
920 BotConnect and related functions.
921
922 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
923 */                              
924 entity(float num) GetClientEntity =
925 {
926         local entity upsy;
927         upsy = world;
928         num = num + 1;
929         while (num > 0)
930         {
931                 num = num - 1;
932                 upsy = nextent(upsy);
933         } 
934         return upsy;
935 };
936
937 void(float whichteam, float whatbot, float whatskill) BotConnect =
938 {
939         local float f;
940         local string h;
941         local entity uself;
942         
943         f = ClientNextAvailable();
944         uself = self;
945         if(f == -1)
946         {
947                 bprint("Unable to connect a bot, server is full.\n");
948                 return;
949         }
950         
951         // chat thing
952
953         active_clients = active_clients | ClientBitFlag(f);
954         bot_count = bot_count + 1;
955         self = GetClientEntity(f);
956         if (!saved_bots)
957                 bot_start_topic(1);
958         self.b_clientno = f;
959         self.colormap = f + 1;
960         if (whatbot)
961                 self.netname = BotName(whatbot);
962         else
963                 self.netname = PickARandomName();
964
965
966         // players can set skill all weird, so leave these checks in
967         whatskill = rint(whatskill);
968         if (whatskill > 3)
969                 whatskill = 3;
970         else if (whatskill < 0)
971                 whatskill = 0;
972         self.b_skill = whatskill;
973
974         if (teamplay && !coop)
975         {
976                 if (whichteam)
977                         self.b_pants = FindAnotherColor(uself.team - 1);
978                 else
979                         self.b_pants = uself.team - 1;
980                 self.b_shirt = self.b_pants;
981         }
982
983         self.team = self.b_pants + 1;
984         UpdateClient(self);
985         SetNewParms();
986         self.ishuman = 2;
987         ClientConnect();
988         PutClientInServer();
989
990         // this is risky... could corrupt .way files if done wrong
991         // If you're not the gambling type, comment this out
992
993         f = ClientBitFlag(self.b_num - 1);
994         current_bots = current_bots | f;
995
996         if (self.b_num <= 8)
997                 saved_skills1 = (saved_skills1 & (65536 - (3 * f)) | (self.b_skill * f));
998         else
999         {
1000                 f = ClientBitFlag(self.b_num - 9);
1001                 saved_skills2 = (saved_skills2 & (65536 - (3 * f)) | (self.b_skill * f));
1002         }
1003
1004         h = ftos(current_bots);
1005         cvar_set("scratch1", h);
1006         h = ftos(saved_skills1);
1007         cvar_set("scratch2", h);
1008         h = ftos(saved_skills2);
1009         cvar_set("scratch3", h);
1010         self = uself;
1011
1012 };
1013
1014 void(entity bot) BotDisconnect =
1015 {
1016         local string h;
1017         local entity uself;
1018         uself = self;
1019         self = bot;
1020
1021         bot_count = bot_count - 1;
1022         current_bots = current_bots - (current_bots & ClientBitFlag(self.b_num - 1));
1023         h = ftos(current_bots);
1024         cvar_set("scratch1", h);
1025
1026
1027         ClientDisconnect();
1028
1029         if (self.b_clientno != -1)
1030         {
1031         // the bot's client number is not in use by a real player so we
1032                 // must remove it's entry in the rankings
1033                 // Quake engine sets all fields to 0, can only do the most important here
1034                 self.b_frags = self.frags = 0;
1035                 self.netname = "";
1036                 self.classname = "";
1037                 self.health = 0;
1038                 self.items = 0;
1039                 self.armorvalue = 0;
1040                 self.weaponmodel = "";
1041                 self.b_pants = 0;
1042                 self.b_shirt = 0;
1043                 self.ammo_shells = self.ammo_nails = self.ammo_rockets = self.ammo_cells = 0;
1044                 UpdateClient(self);
1045                 active_clients = active_clients - (active_clients & ClientBitFlag(self.b_clientno));
1046               self.b_clientno = -1;
1047         }
1048         self = uself;
1049 };
1050 /*
1051 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1052
1053 BotInvalidClientNo
1054 kicks a bot if a player connects and takes the bot's space
1055
1056 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1057 */
1058
1059 void(float clientno) BotInvalidClientNo =
1060 {
1061         local entity bot;
1062
1063         bot = GetClientEntity(clientno);
1064         if(bot.b_clientno > 0)
1065         {
1066                 if (!bot.ishuman)
1067                 {
1068                         bot.b_clientno = -1;
1069                         BotDisconnect(bot);
1070                         active_clients = active_clients | ClientBitFlag(self.b_clientno);
1071                         BotConnect(0, bot.b_num, bot.b_skill); 
1072                         return;
1073                 }
1074         }
1075 };
1076
1077 /*
1078 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1079
1080 Waypoint Loading from file
1081
1082 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1083 */
1084 void() LoadWaypoint = 
1085 {
1086         local vector org;
1087         local entity tep;
1088         local float r;
1089         org_x = cvar("saved1");
1090         org_y = cvar("saved2");
1091         org_z = cvar("saved3");
1092         
1093         tep = make_waypoint(org);
1094
1095         r = cvar("saved4");
1096
1097         tep.b_aiflags = floor(r / 4);
1098         tep.b_pants = cvar("scratch1");
1099         tep.b_skill = cvar("scratch2");
1100         tep.b_shirt = cvar("scratch3");
1101         tep.b_frags = cvar("scratch4");
1102 };
1103
1104 void() bot_return =
1105 {
1106         if (time > 2)
1107         {
1108                 if ((waypoint_mode == WM_DYNAMIC) || (waypoint_mode == WM_LOADED))
1109                 {
1110                         // minor precaution
1111
1112                         if (saved_bots & 1) BotConnect(0, 1, saved_skills1 & 3);
1113                         if (saved_bots & 2) BotConnect(0, 2, (saved_skills1 & 12) / 4);
1114                         if (saved_bots & 4) BotConnect(0, 3, (saved_skills1 & 48) / 16);
1115                         if (saved_bots & 8) BotConnect(0, 4, (saved_skills1 & 192) / 64);
1116                         if (saved_bots & 16) BotConnect(0, 5, (saved_skills1 & 768) / 256);
1117                         if (saved_bots & 32) BotConnect(0, 6, (saved_skills1 & 3072) / 1024);
1118                         if (saved_bots & 64) BotConnect(0, 7, (saved_skills1 & 12288) / 4096);
1119                         if (saved_bots & 128) BotConnect(0, 8, (saved_skills1 & 49152) / 16384);
1120                         if (saved_bots & 256) BotConnect(0, 9, saved_skills2 & 3);
1121                         if (saved_bots & 512) BotConnect(0, 10, (saved_skills2 & 12) / 4);
1122                         if (saved_bots & 1024) BotConnect(0, 11, (saved_skills2& 48) / 16);
1123                         if (saved_bots & 2048) BotConnect(0, 12, (saved_skills2 & 192) / 64);
1124                         if (saved_bots & 4096) BotConnect(0, 13, (saved_skills2 & 768) / 256);
1125                         if (saved_bots & 8192) BotConnect(0, 14, (saved_skills2 & 3072) / 1024);
1126                         if (saved_bots & 16384) BotConnect(0, 15, (saved_skills2 & 12288) / 4096);
1127                         if (saved_bots & 32768) BotConnect(0, 16, (saved_skills2 & 49152) / 16384);
1128                         saved_bots = 0;
1129                 }
1130         }
1131 };
1132
1133
1134 void() WaypointWatch =
1135 {
1136         // Waypoint Baywatch
1137         local float bigboobs;
1138         local string h;
1139
1140         if (max_clients < 2)
1141                 return;
1142         if (waypoint_mode != WM_UNINIT)
1143         {
1144                 bigboobs = cvar("saved4");
1145                 if (bigboobs != 0)
1146                 {
1147                         if ((bigboobs & 3) == 1)
1148                                 ClearAllWays();
1149                         else if ((bigboobs & 3) == 3)
1150                         {
1151                                 FixWaypoints();
1152                                 h = ftos(b_options);
1153                                 cvar_set("saved1", h);
1154                                 cvar_set("saved4", "0");
1155                                 cvar_set("scratch1", "0");
1156                                 waypoint_mode = WM_LOADED;
1157                                 return;
1158                         }
1159                         LoadWaypoint();
1160                         waypoint_mode = WM_LOADING;
1161                         cvar_set("saved4", "0");
1162                 }
1163         }
1164 };
1165 void() BotFrame =
1166 {
1167         local float num;
1168
1169         // for the sake of speed
1170         sv_maxspeed = cvar("sv_maxspeed");
1171         sv_gravity = cvar("sv_gravity");
1172         sv_friction = cvar("sv_friction");
1173         sv_accelerate = cvar("sv_accelerate");
1174         sv_stopspeed = cvar("sv_stopspeed");
1175         real_frametime = frametime; // in NQ this is alright
1176         
1177         self = nextent(world);
1178         num = 0;
1179         while (num < max_clients)
1180         {
1181                 if (self.ishuman == FALSE)
1182                 {
1183                         if (active_clients & ClientBitFlag(num))
1184                         {
1185                                 frik_obstacles();
1186                                 CL_KeyMove();
1187                                 SV_ClientThink();
1188                                 SV_Physics_Client();
1189                         }
1190                 }
1191                 self = nextent(self);
1192                 num = num + 1;
1193         }
1194         WaypointWatch();
1195
1196         if (saved_bots)
1197                 bot_return();
1198 };
1199
1200 /*
1201 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1202
1203 Bot Impulses. Allows the player to perform bot
1204 related functions.
1205
1206 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1207 */
1208
1209 void() BotImpulses =
1210 {
1211         local float f;
1212
1213         if (self.impulse == 100)
1214         {
1215                 f = cvar("skill");
1216                 BotConnect(0, 0, f);
1217         }
1218         else if (self.impulse == 101)
1219         {
1220                 f = cvar("skill");
1221                 BotConnect(1, 0, f);
1222         }
1223         else if (self.impulse == 102)
1224                 KickABot();
1225         else if (self.impulse == 103)
1226                 botcam_u();
1227         else if (self.impulse == 104)
1228                 bot_way_edit();
1229         else
1230                 return;
1231
1232         self.impulse = 0;
1233 };
1234
1235
1236