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