]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/client/Main.qc
cl_showspeed_z to show z-axis speed as a separate number instead
[divverent/nexuiz.git] / data / qcsrc / client / Main.qc
1 // --------------------------------------------------------------------------
2 // BEGIN REQUIRED CSQC FUNCTIONS
3 //include "main.qh"
4
5 #define DP_CSQC_ENTITY_REMOVE_IS_B0RKED
6
7 void cvar_clientsettemp(string cv, string val)
8 {
9         entity e;
10         for(e = world; (e = find(e, classname, "saved_cvar_value")); )
11                 if(e.netname == cv)
12                         goto saved;
13         e = spawn();
14         e.classname = "saved_cvar_value";
15         e.netname = strzone(cv);
16         e.message = strzone(cvar_string(cv));
17 :saved
18         cvar_set(cv, val);
19 }
20
21 void cvar_clientsettemp_restore()
22 {
23         entity e;
24         for(e = world; (e = find(e, classname, "saved_cvar_value")); )
25                         cvar_set(e.netname, e.message);
26 }
27
28 void() menu_show_error =
29 {
30         drawstring('0 200 0', "ERROR - MENU IS VISIBLE BUT NO MENU WAS DEFINED!", '8 8 0', '1 0 0', 1, 0);
31 };
32
33 // CSQC_Init : Called every time the CSQC code is initialized (essentially at map load)
34 // Useful for precaching things
35
36 void() menu_sub_null =
37 {
38 };
39
40 #ifdef USE_FTE
41 float __engine_check;
42 #endif
43
44 string forcefog;
45 void WaypointSprite_Load();
46 void CSQC_Init(void)
47 {
48 #ifdef USE_FTE
49 #pragma target ID
50         __engine_check = checkextension("DP_SV_WRITEPICTURE");
51         if(!__engine_check)
52         {
53                 print("^3Your engine build is outdated\n^3This Server uses a newer QC VM. Please update!\n");
54                 localcmd("\ndisconnect\n");
55                 return;
56         }
57 #pragma target FTE
58 #endif
59         
60         check_unacceptable_compiler_bugs();
61
62         float i;
63         CSQC_CheckEngine();
64
65         binddb = db_create();
66         tempdb = db_create();
67         ClientProgsDB = db_load("client.db");
68         compressShortVector_init();
69
70         drawfont = 0;
71         menu_visible = FALSE;
72         menu_show = menu_show_error;
73         menu_action = menu_sub_null;
74
75         for(i = 0; i < 255; ++i)
76                 if(getplayerkey(i, "viewentity") == "")
77                         break;
78         maxclients = i;
79
80         //ctf_temp_1 = "";
81         // localcmd("alias order \"cmd order $*\""); enable if ctf-command thingy is used
82         //registercmd("ctf_menu");
83         registercmd("ons_map");
84         //registercmd("menu_action");
85
86         registercmd("+button3");
87         registercmd("-button3");
88         registercmd("+button4");
89         registercmd("-button4");
90         registercmd("+showaccuracy");registercmd("-showaccuracy");
91
92 #ifndef CAMERATEST
93         if(isdemo())
94         {
95 #endif
96                 registercmd("+forward");registercmd("-forward");
97                 registercmd("+back");registercmd("-back");
98                 registercmd("+moveup");registercmd("-moveup");
99                 registercmd("+movedown");registercmd("-movedown");
100                 registercmd("+moveright");registercmd("-moveright");
101                 registercmd("+moveleft");registercmd("-moveleft");
102                 registercmd("+roll_right");registercmd("-roll_right");
103                 registercmd("+roll_left");registercmd("-roll_left");
104 #ifndef CAMERATEST
105         }
106 #endif
107         registercvar("sbar_usecsqc", "1");
108         registercvar("sbar_columns", "default", CVAR_SAVE);
109
110         gametype = 0;
111
112         // sbar_fields uses strunzone on the titles!
113         for(i = 0; i < MAX_SBAR_FIELDS; ++i)
114                 sbar_title[i] = strzone("(null)");
115
116         postinit = false;
117
118         calledhooks = 0;
119
120         teams = Sort_Spawn();
121         players = Sort_Spawn();
122
123         GetTeam(COLOR_SPECTATOR, true); // add specs first
124
125         cvar_clientsettemp("_supports_weaponpriority", "1");
126
127         RegisterWeapons();
128
129         WaypointSprite_Load();
130
131         Projectile_Precache();
132         GibSplash_Precache();
133         Casings_Precache();
134         DamageInfo_Precache();
135         Announcer_Precache();
136         Tuba_Precache();
137
138         get_mi_min_max_texcoords(1); // try the CLEVER way first
139         minimapname = strcat("gfx/", mi_shortname, "_radar.tga");
140         shortmapname = mi_shortname;
141
142         if(precache_pic(minimapname) == "")
143         {
144                 // but maybe we have a non-clever minimap
145                 minimapname = strcat("gfx/", mi_shortname, "_mini.tga");
146                 if(precache_pic(minimapname) == "")
147                         minimapname = ""; // FAIL
148                 else
149                         get_mi_min_max_texcoords(0); // load new texcoords
150         }
151
152         mi_center = (mi_min + mi_max) * 0.5;
153         mi_scale = mi_max - mi_min;
154         minimapname = strzone(minimapname);
155 }
156
157 // CSQC_Shutdown : Called every time the CSQC code is shutdown (changing maps, quitting, etc)
158 void CSQC_Shutdown(void)
159 {
160 #ifdef USE_FTE
161 #pragma TARGET id
162         if(!__engine_check)
163                 return 0;
164 #pragma TARGET fte
165 #endif
166
167         remove(teams);
168         remove(players);
169         db_close(binddb);
170         db_close(tempdb);
171         db_save(ClientProgsDB, "client.db");
172         db_close(ClientProgsDB);
173
174         cvar_clientsettemp_restore();
175
176         if(camera_active)
177                 cvar_set("chase_active",ftos(chase_active_backup));
178
179         if not(isdemo())
180         {
181                 if not(calledhooks & HOOK_START)
182                         localcmd("\n_cl_hook_gamestart nop;");
183                 if not(calledhooks & HOOK_END)
184                         localcmd("\ncl_hook_gameend;");
185         }
186 }
187
188 .float has_team;
189 float SetTeam(entity o, float Team)
190 {
191         entity tm;
192         if(teamplay)
193         {
194                 switch(Team)
195                 {
196                         case -1:
197                         case COLOR_TEAM1:
198                         case COLOR_TEAM2:
199                         case COLOR_TEAM3:
200                         case COLOR_TEAM4:
201                                 break;
202                         default:
203                                 if(GetTeam(Team, false) == NULL)
204                                 {
205                                         print("trying to switch to unsupported team ", ftos(Team), "\n");
206                                         Team = COLOR_SPECTATOR;
207                                 }
208                                 break;
209                 }
210         }
211         else
212         {
213                 switch(Team)
214                 {
215                         case -1:
216                         case 0:
217                                 break;
218                         default:
219                                 if(GetTeam(Team, false) == NULL)
220                                 {
221                                         print("trying to switch to unsupported team ", ftos(Team), "\n");
222                                         Team = COLOR_SPECTATOR;
223                                 }
224                                 break;
225                 }
226         }
227         if(Team == -1) // leave
228         {
229                 if(o.has_team)
230                 {
231                         //print("(DISCONNECT) leave team ", ftos(o.team), "\n");
232                         tm = GetTeam(o.team, false);
233                         tm.team_size -= 1;
234                         o.has_team = 0;
235                         return TRUE;
236                 }
237         }
238         else
239         {
240                 if not(o.has_team)
241                 {
242                         //print("(CONNECT) enter team ", ftos(o.team), "\n");
243                         o.team = Team;
244                         tm = GetTeam(Team, true);
245                         tm.team_size += 1;
246                         o.has_team = 1;
247                         return TRUE;
248                 }
249                 else if(Team != o.team)
250                 {
251                         //print("(CHANGE) leave team ", ftos(o.team), "\n");
252                         tm = GetTeam(o.team, false);
253                         tm.team_size -= 1;
254                         o.team = Team;
255                         //print("(CHANGE) enter team ", ftos(o.team), "\n");
256                         tm = GetTeam(Team, true);
257                         tm.team_size += 1;
258                         return TRUE;
259                 }
260         }
261         return FALSE;
262 }
263
264 void Playerchecker_Think()
265 {
266         float i;
267         entity e;
268         for(i = 0; i < maxclients; ++i)
269         {
270                 e = playerslots[i];
271                 if(GetPlayerName(i) == "")
272                 {
273                         if(e.sort_prev)
274                         {
275                                 //print("playerchecker: KILL KILL KILL\n");
276                                 // player disconnected
277                                 SetTeam(e, -1);
278                                 RemovePlayer(e);
279                                 e.sort_prev = world;
280                                 //e.gotscores = 0;
281                         }
282                 }
283                 else
284                 {
285                         if not(e.sort_prev)
286                         {
287                                 //print("playerchecker: SPAWN SPAWN SPAWN\n");
288                                 // player connected
289                                 if not(e)
290                                         playerslots[i] = e = spawn();
291                                 e.sv_entnum = i;
292                                 e.ping = 0;
293                                 e.ping_packetloss = 0;
294                                 e.ping_movementloss = 0;
295                                 //e.gotscores = 0; // we might already have the scores...
296                                 SetTeam(e, GetPlayerColor(i)); // will not hurt; later updates come with Sbar_UpdatePlayerTeams
297                                 RegisterPlayer(e);
298                                 Sbar_UpdatePlayerPos(e);
299                         }
300                 }
301         }
302         self.nextthink = time + 0.2;
303 }
304
305 void Porto_Init();
306 void TrueAim_Init();
307 void PostInit(void)
308 {
309         print(strcat("PostInit\n    maxclients = ", ftos(maxclients), "\n"));
310         localcmd(strcat("\nsbar_columns_set ", cvar_string("sbar_columns"), ";\n"));
311
312         entity playerchecker;
313         playerchecker = spawn();
314         playerchecker.think = Playerchecker_Think;
315         playerchecker.nextthink = time + 0.2;
316
317         Porto_Init();
318         TrueAim_Init();
319
320         postinit = true;
321 }
322
323 // CSQC_ConsoleCommand : Used to parse commands in the console that have been registered with the "registercmd" function
324 // Return value should be 1 if CSQC handled the command, otherwise return 0 to have the engine handle it.
325 float button_zoom;
326 void Cmd_Sbar_SetFields(float);
327 void Cmd_Sbar_Help(float);
328 float CSQC_ConsoleCommand(string strMessage)
329 {
330         float argc;
331         // Tokenize String
332         //argc = tokenize(strMessage);
333         argc = tokenize_console(strMessage);
334
335         // Acquire Command
336         local string strCmd;
337         strCmd = argv(0);
338
339         if(strCmd == "+button4") { // zoom
340                 // return false, because the message shall be sent to the server anyway (for demos/speccing)
341                 if(ignore_plus_zoom)
342                 {
343                         --ignore_plus_zoom;
344                         return false;
345                 }
346                 button_zoom = 1;
347                 return true;
348         } else if(strCmd == "-button4") { // zoom
349                 if(ignore_minus_zoom)
350                 {
351                         --ignore_minus_zoom;
352                         return false;
353                 }
354                 button_zoom = 0;
355                 return true;
356         } else if(strCmd == "+button3") { // secondary
357                 button_attack2 = 1;
358                 return false;
359         } else if(strCmd == "-button3") { // secondary
360                 button_attack2 = 0;
361                 return false;
362         } else if(strCmd == "+showscores") {
363                 sb_showscores = true;
364                 return true;
365         } else if(strCmd == "-showscores") {
366                 sb_showscores = false;
367                 return true;
368         } else if(strCmd == "+showaccuracy") {
369                 sb_showaccuracy = true;
370                 return true;
371         } else if(strCmd == "-showaccuracy") {
372                 sb_showaccuracy = false;
373                 return true;
374         }
375
376         if(camera_active)
377         if(strCmd == "+forward" || strCmd == "-back") {
378                 ++camera_direction_x;
379                 return true;
380         } else if(strCmd == "-forward" || strCmd == "+back") {
381                 --camera_direction_x;
382                 return true;
383         } else if(strCmd == "+moveright" || strCmd == "-moveleft") {
384                 --camera_direction_y;
385                 return true;
386         } else if(strCmd == "-moveright" || strCmd == "+moveleft") {
387                 ++camera_direction_y;
388                 return true;
389         } else if(strCmd == "+moveup" || strCmd == "-movedown") {
390                 ++camera_direction_z;
391                 return true;
392         } else if(strCmd == "-moveup" || strCmd == "+movedown") {
393                 --camera_direction_z;
394                 return true;
395         } else if(strCmd == "+roll_right" || strCmd == "-roll_left") {
396                 ++camera_roll;
397                 return true;
398         } else if(strCmd == "+roll_left" || strCmd == "-roll_right") {
399                 --camera_roll;
400                 return true;
401         }
402
403         return false;
404 }
405
406 .vector view_ofs;
407 entity debug_shotorg;
408 void ShotOrg_Draw()
409 {
410         self.origin = view_origin + view_forward * self.view_ofs_x + view_right * self.view_ofs_y + view_up * self.view_ofs_z;
411         self.angles = view_angles;
412         self.angles_x = -self.angles_x;
413         if not(self.cnt)
414                 R_AddEntity(self);
415 }
416 void ShotOrg_Draw2D()
417 {
418         vector coord2d_topleft, coord2d_topright, coord2d;
419         string s;
420         vector fs;
421
422         s = vtos(self.view_ofs);
423         s = substring(s, 1, strlen(s) - 2);
424         if(tokenize_console(s) == 3)
425                 s = strcat(argv(0), " ", argv(1), " ", argv(2));
426
427         coord2d_topleft = project_3d_to_2d(self.origin + view_up * 4 - view_right * 4);
428         coord2d_topright = project_3d_to_2d(self.origin + view_up * 4 + view_right * 4);
429
430         fs = '1 1 0' * ((coord2d_topright_x - coord2d_topleft_x) / stringwidth(s, FALSE));
431
432         coord2d = coord2d_topleft;
433         if(fs_x < 8)
434         {
435                 coord2d_x += (coord2d_topright_x - coord2d_topleft_x) * (1 - 8 / fs_x) * 0.5;
436                 fs = '8 8 0';
437         }
438         coord2d_y -= fs_y;
439         coord2d_z = 0;
440         drawstring(coord2d, s, fs, '1 1 1', 1, 0);
441 }
442
443 void ShotOrg_Spawn()
444 {
445         debug_shotorg = spawn();
446         debug_shotorg.draw = ShotOrg_Draw;
447         debug_shotorg.draw2d = ShotOrg_Draw2D;
448         debug_shotorg.renderflags = RF_VIEWMODEL;
449         debug_shotorg.effects = EF_FULLBRIGHT;
450         precache_model("models/shotorg_adjuster.md3");
451         setmodel(debug_shotorg, "models/shotorg_adjuster.md3");
452         debug_shotorg.scale = 2;
453         debug_shotorg.view_ofs = '25 8 -8';
454 }
455
456 void GameCommand(string msg)
457 {
458         float argc;
459         argc = tokenize_console(msg);
460
461         if(argv(0) == "help" || argc == 0)
462         {
463                 print("Usage: cl_cmd COMMAND..., where possible commands are:\n");
464                 print("  settemp cvar value\n");
465                 print("  radar\n");
466                 print("  sbar_columns_set ...\n");
467                 print("  sbar_columns_help\n");
468                 GameCommand_Generic("help");
469                 return;
470         }
471
472         if(GameCommand_Generic(msg))
473                 return;
474
475         string cmd;
476         cmd = argv(0);
477         if(cmd == "mv_download") {
478                 Cmd_MapVote_MapDownload(argc);
479         }
480         else if(cmd == "settemp") {
481                 cvar_clientsettemp(argv(1), argv(2));
482         }
483         else if(cmd == "radar") {
484                 ons_showmap = !ons_showmap;
485         }
486         else if(cmd == "sbar_columns_set") {
487                 Cmd_Sbar_SetFields(argc);
488         }
489         else if(cmd == "sbar_columns_help") {
490                 Cmd_Sbar_Help(argc);
491         }
492 #ifdef BLURTEST
493         else if(cmd == "blurtest") {
494                 blurtest_time0 = time;
495                 blurtest_time1 = time + stof(argv(1));
496                 blurtest_radius = stof(argv(2));
497                 blurtest_power = stof(argv(3));
498         }
499 #endif
500         else if(cmd == "shotorg_move") {
501                 if(!debug_shotorg)
502                         ShotOrg_Spawn();
503                 else
504                         debug_shotorg.view_ofs = debug_shotorg.view_ofs + stov(argv(1));
505                 localcmd("sv_cmd debug_shotorg \"", vtos(debug_shotorg.view_ofs), "\"\n");
506         }
507         else if(cmd == "shotorg_movez") {
508                 if(!debug_shotorg)
509                         ShotOrg_Spawn();
510                 else
511                         debug_shotorg.view_ofs = debug_shotorg.view_ofs + stof(argv(1)) * (debug_shotorg.view_ofs * (1 / debug_shotorg.view_ofs_x)); // closer/farther, same xy pos
512                 localcmd("sv_cmd debug_shotorg \"", vtos(debug_shotorg.view_ofs), "\"\n");
513         }
514         else if(cmd == "shotorg_set") {
515                 if(!debug_shotorg)
516                         ShotOrg_Spawn();
517                 else
518                         debug_shotorg.view_ofs = stov(argv(1));
519                 localcmd("sv_cmd debug_shotorg \"", vtos(debug_shotorg.view_ofs), "\"\n");
520         }
521         else if(cmd == "shotorg_setz") {
522                 if(!debug_shotorg)
523                         ShotOrg_Spawn();
524                 else
525                         debug_shotorg.view_ofs = debug_shotorg.view_ofs * (stof(argv(1)) / debug_shotorg.view_ofs_x); // closer/farther, same xy pos
526                 localcmd("sv_cmd debug_shotorg \"", vtos(debug_shotorg.view_ofs), "\"\n");
527         }
528         else if(cmd == "shotorg_toggle_hide") {
529                 if(debug_shotorg)
530                 {
531                         debug_shotorg.cnt = !debug_shotorg.cnt;
532                 }
533         }
534         else if(cmd == "shotorg_end") {
535                 if(debug_shotorg)
536                 {
537                         print(vtos(debug_shotorg.view_ofs), "\n");
538                         remove(debug_shotorg);
539                         debug_shotorg = world;
540                 }
541                 localcmd("sv_cmd debug_shotorg\n");
542         }
543         else
544         {
545                 print("Invalid command. For a list of supported commands, try cl_cmd help.\n");
546         }
547
548         return;
549 }
550
551 // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client.
552 // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine.
553 // All keys are in ascii.
554 // bInputType = 0 is key pressed, 1 is key released, 2 is mouse input.
555 // In the case of keyboard input, nPrimary is the ascii code, and nSecondary is 0.
556 // In the case of mouse input, nPrimary is xdelta, nSecondary is ydelta.
557 float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary)
558 {
559         local float bSkipKey;
560         bSkipKey = false;
561
562         if(menu_visible)
563                 if(menu_action(bInputType, nPrimary, nSecondary))
564                         return TRUE;
565         return bSkipKey;
566 }
567
568 // END REQUIRED CSQC FUNCTIONS
569 // --------------------------------------------------------------------------
570
571 // --------------------------------------------------------------------------
572 // BEGIN OPTIONAL CSQC FUNCTIONS
573 void Ent_ReadEntCS()
574 {
575         InterpolateOrigin_Undo();
576
577         self.classname = "entcs_receiver";
578         self.sv_entnum = ReadByte() - 1;
579         self.origin_x = ReadShort();
580         self.origin_y = ReadShort();
581         self.origin_z = ReadShort();
582         self.angles_y = ReadByte() * 360.0 / 256;
583         self.origin_z = self.angles_x = self.angles_z = 0;
584
585         InterpolateOrigin_Note();
586 }
587
588 void Ent_Remove();
589
590 void Ent_RemovePlayerScore()
591 {
592         float i;
593
594         if(self.owner)
595         {
596                 SetTeam(self.owner, -1);
597                 self.owner.gotscores = 0;
598                 for(i = 0; i < MAX_SCORE; ++i)
599                         self.owner.(scores[i]) = 0; // clear all scores
600         }
601 }
602
603 void Ent_ReadPlayerScore()
604 {
605         float i, n;
606         float isNew;
607         entity o;
608
609         // damnit -.- don't want to go change every single .sv_entnum in sbar.qc AGAIN
610         // (no I've never heard of M-x replace-string, sed, or anything like that)
611         isNew = !self.owner; // workaround for DP bug
612         n = ReadByte()-1;
613
614 #ifdef DP_CSQC_ENTITY_REMOVE_IS_B0RKED
615         if(!isNew && n != self.sv_entnum)
616         {
617                 print("A CSQC entity changed its owner!\n");
618                 isNew = true;
619                 Ent_Remove();
620                 self.enttype = ENT_CLIENT_SCORES;
621         }
622 #endif
623
624         self.sv_entnum = n;
625
626         if not(playerslots[self.sv_entnum])
627                 playerslots[self.sv_entnum] = spawn();
628         o = self.owner = playerslots[self.sv_entnum];
629         o.sv_entnum = self.sv_entnum;
630         o.gotscores = 1;
631
632         //if not(o.sort_prev)
633         //      RegisterPlayer(o);
634         //playerchecker will do this for us later, if it has not already done so
635
636         float sf, lf;
637 #if MAX_SCORE <= 8
638         sf = ReadByte();
639         lf = ReadByte();
640 #else
641         sf = ReadShort();
642         lf = ReadShort();
643 #endif
644         float p;
645         for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2)
646                 if(sf & p)
647                 {
648                         if(lf & p)
649                                 o.(scores[i]) = ReadInt24_t();
650                         else
651                                 o.(scores[i]) = ReadChar();
652                 }
653
654         if(o.sort_prev)
655                 Sbar_UpdatePlayerPos(o); // if not registered, we cannot do this yet!
656
657         self.entremove = Ent_RemovePlayerScore;
658 }
659
660 void Ent_ReadTeamScore()
661 {
662         float i;
663         entity o;
664
665         self.team = ReadByte();
666         o = self.owner = GetTeam(self.team, true); // these team numbers can always be trusted
667
668         float sf, lf;
669 #if MAX_TEAMSCORE <= 8
670         sf = ReadByte();
671         lf = ReadByte();
672 #else
673         sf = ReadShort();
674         lf = ReadShort();
675 #endif
676         float p;
677         for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
678                 if(sf & p)
679                 {
680                         if(lf & p)
681                                 o.(teamscores[i]) = ReadInt24_t();
682                         else
683                                 o.(teamscores[i]) = ReadChar();
684                 }
685
686         Sbar_UpdateTeamPos(o);
687 }
688
689 void Net_Reset()
690 {
691 }
692
693 void Ent_ClientData()
694 {
695         float f;
696         float newspectatee_status;
697
698         f = ReadByte();
699
700         sb_showscores_force = (f & 1);
701
702         if(f & 2)
703         {
704                 newspectatee_status = ReadByte();
705                 if(newspectatee_status == player_localentnum)
706                         newspectatee_status = -1; // observing
707         }
708         else
709                 newspectatee_status = 0;
710
711         spectatorbutton_zoom = (f & 4);
712
713         if(f & 8)
714         {
715                 angles_held_status = 1;
716                 angles_held_x = ReadAngle();
717                 angles_held_y = ReadAngle();
718                 angles_held_z = 0;
719         }
720         else
721                 angles_held_status = 0;
722
723         if(newspectatee_status != spectatee_status)
724         {
725                 float i;
726                 // clear the weapon accuracy stats
727                 for(i = WEP_FIRST; i <= WEP_LAST; ++i) {
728                         weapon_hits[i] = 0;
729                         weapon_fired[i] = 0;
730                 }
731
732                 // clear race stuff
733                 race_laptime = 0;
734                 race_checkpointtime = 0;
735         }
736         spectatee_status = newspectatee_status;
737 }
738
739 void Ent_Nagger()
740 {
741         float nags, i, j, b, f;
742
743         nags = ReadByte();
744
745         if(nags & 128)
746         {
747                 if(vote_called_vote)
748                         strunzone(vote_called_vote);
749                 vote_called_vote = strzone(ColorTranslateRGB(ReadString()));
750         }
751
752         if(nags & 1)
753         {
754                 for(j = 0; j < maxclients; ++j)
755                         if(playerslots[j])
756                                 playerslots[j].ready = 1;
757                 for(i = 1; i <= maxclients; i += 8)
758                 {
759                         f = ReadByte();
760                         for(j = i-1, b = 1; b < 256; b *= 2, ++j)
761                                 if not(f & b)
762                                         if(playerslots[j])
763                                                 playerslots[j].ready = 0;
764                 }
765         }
766
767         ready_waiting = (nags & 1);
768         ready_waiting_for_me = (nags & 2);
769         vote_waiting = (nags & 4);
770         vote_waiting_for_me = (nags & 8);
771         warmup_stage = (nags & 16);
772 }
773
774 void Ent_RandomSeed()
775 {
776         float s;
777         prandom_debug();
778         s = ReadShort();
779         psrandom(s);
780 }
781
782 // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured.
783 // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS.
784 void Ent_RadarLink();
785 void Ent_Init();
786 void Ent_ScoresInfo();
787 void(float bIsNewEntity) CSQC_Ent_Update =
788 {
789         float t;
790         float savetime;
791         t = ReadByte();
792
793         // set up the "time" global for received entities to be correct for interpolation purposes
794         savetime = time;
795         if(servertime)
796         {
797                 time = servertime;
798         }
799         else
800         {
801                 serverprevtime = time;
802                 serverdeltatime = getstatf(STAT_MOVEVARS_TICRATE) * getstatf(STAT_MOVEVARS_TIMESCALE);
803                 time = serverprevtime + serverdeltatime;
804         }
805
806 #ifdef DP_CSQC_ENTITY_REMOVE_IS_B0RKED
807         if(self.enttype)
808                 if(t != self.enttype)
809                 {
810                         print("A CSQC entity changed its type!\n");
811                         Ent_Remove();
812                         bIsNewEntity = 1;
813                 }
814 #endif
815         self.enttype = t;
816         switch(t)
817         {
818                 case ENT_CLIENT_ENTCS: Ent_ReadEntCS(); break;
819                 case ENT_CLIENT_SCORES: Ent_ReadPlayerScore(); break;
820                 case ENT_CLIENT_TEAMSCORES: Ent_ReadTeamScore(); break;
821                 case ENT_CLIENT_POINTPARTICLES: Ent_PointParticles(); break;
822                 case ENT_CLIENT_RAINSNOW: Ent_RainOrSnow(); break;
823                 case ENT_CLIENT_LASER: Ent_Laser(); break;
824                 case ENT_CLIENT_NAGGER: Ent_Nagger(); break;
825                 case ENT_CLIENT_WAYPOINT: Ent_WaypointSprite(); break;
826                 case ENT_CLIENT_RADARLINK: Ent_RadarLink(); break;
827                 case ENT_CLIENT_PROJECTILE: Ent_Projectile(); break;
828                 case ENT_CLIENT_GIBSPLASH: Ent_GibSplash(bIsNewEntity); break;
829                 case ENT_CLIENT_DAMAGEINFO: Ent_DamageInfo(bIsNewEntity); break;
830                 case ENT_CLIENT_CASING: Ent_Casing(bIsNewEntity); break;
831                 case ENT_CLIENT_INIT: Ent_Init(); break;
832                 case ENT_CLIENT_SCORES_INFO: Ent_ScoresInfo(); break;
833                 case ENT_CLIENT_MAPVOTE: Ent_MapVote(); break;
834                 case ENT_CLIENT_CLIENTDATA: Ent_ClientData(); break;
835                 case ENT_CLIENT_RANDOMSEED: Ent_RandomSeed(); break;
836                 case ENT_CLIENT_WALL: Ent_Wall(); break;
837                 case ENT_CLIENT_MODELEFFECT: Ent_ModelEffect(bIsNewEntity); break;
838                 case ENT_CLIENT_TUBANOTE: Ent_TubaNote(bIsNewEntity); break;
839                 default:
840                         error(strcat("unknown entity type in CSQC_Ent_Update: ", ftos(self.enttype), "\n"));
841                         break;
842         }
843
844         time = savetime;
845 };
846 // Destructor, but does NOT deallocate the entity by calling remove(). Also
847 // used when an entity changes its type. For an entity that someone interacts
848 // with others, make sure it can no longer do so.
849 void Ent_Remove()
850 {
851         if(self.entremove)
852                 self.entremove();
853
854         self.enttype = 0;
855         self.classname = "";
856         self.draw = menu_sub_null;
857         self.entremove = menu_sub_null;
858         // TODO possibly set more stuff to defaults
859 }
860 // CSQC_Ent_Remove : Called when the server requests a SSQC / CSQC entity to be removed.  Essentially call remove(self) as well.
861 void CSQC_Ent_Remove()
862 {
863         if(self.enttype)
864                 Ent_Remove();
865         remove(self);
866 }
867
868 void Gamemode_Init()
869 {
870         if(gametype == GAME_ONSLAUGHT) {
871                 print(strcat("Using ", minimapname, " as minimap.\n"));
872                 precache_pic("gfx/ons-cp-neutral.tga");
873                 precache_pic("gfx/ons-cp-red.tga");
874                 precache_pic("gfx/ons-cp-blue.tga");
875                 precache_pic("gfx/ons-frame.tga");
876                 precache_pic("gfx/ons-frame-team.tga");
877         } else if(gametype == GAME_KEYHUNT) {
878                 precache_pic("gfx/sb_key_carrying");
879                 precache_pic("gfx/sb_key_carrying_outline");
880         }
881
882         if not(isdemo())
883         {
884                 localcmd("\n_cl_hook_gamestart ", GametypeNameFromType(gametype), ";");
885                 calledhooks |= HOOK_START;
886         }
887 }
888 // CSQC_Parse_StuffCmd : Provides the stuffcmd string in the first parameter that the server provided.  To execute standard behavior, simply execute localcmd with the string.
889 void CSQC_Parse_StuffCmd(string strMessage)
890 {
891         localcmd(strMessage);
892 }
893 // CSQC_Parse_Print : Provides the print string in the first parameter that the server provided.  To execute standard behavior, simply execute print with the string.
894 void CSQC_Parse_Print(string strMessage)
895 {
896         print(ColorTranslateRGB(strMessage));
897 }
898
899 // CSQC_Parse_CenterPrint : Provides the centerprint string in the first parameter that the server provided.
900 void CSQC_Parse_CenterPrint(string strMessage)
901 {
902         centerprint(strMessage);
903 }
904
905 string notranslate_fogcmd1 = "\nfog ";
906 string notranslate_fogcmd2 = "\nr_fog_exp2 0\nr_drawfog 1\n";
907 void Fog_Force()
908 {
909         // TODO somehow thwart prvm_globalset client ...
910
911         if(forcefog != "")
912                 localcmd(strcat(notranslate_fogcmd1, forcefog, notranslate_fogcmd2));
913 }
914
915 void Gamemode_Init();
916 void Ent_ScoresInfo()
917 {
918         float i;
919         self.classname = "ent_client_scores_info";
920         gametype = ReadByte();
921         for(i = 0; i < MAX_SCORE; ++i)
922         {
923                 scores_label[i] = strzone(ReadString());
924                 scores_flags[i] = ReadByte();
925         }
926         for(i = 0; i < MAX_TEAMSCORE; ++i)
927         {
928                 teamscores_label[i] = strzone(ReadString());
929                 teamscores_flags[i] = ReadByte();
930         }
931         Sbar_InitScores();
932         Gamemode_Init();
933 }
934
935 void Ent_Init()
936 {
937         float i;
938         self.classname = "ent_client_init";
939
940         nb_pb_period = ReadByte() / 32; //Accuracy of 1/32th
941
942         for(i = 0; i < 24; ++i)
943                 weaponimpulse[i] = ReadByte() - 1;
944         hook_shotorigin_x = ReadCoord();
945         hook_shotorigin_y = ReadCoord();
946         hook_shotorigin_z = ReadCoord();
947
948         if(forcefog)
949                 strunzone(forcefog);
950         forcefog = strzone(ReadString());
951
952         armorblockpercent = ReadByte() / 255.0;
953
954         g_weaponswitchdelay = ReadByte() / 255.0;
955
956         if(!postinit)
957                 PostInit();
958 }
959
960 void Net_ReadRace()
961 {
962         float b;
963
964         b = ReadByte();
965
966         switch(b)
967         {
968                 case RACE_NET_CHECKPOINT_HIT_QUALIFYING:
969                         race_checkpoint = ReadByte();
970                         race_time = ReadInt24_t();
971                         race_previousbesttime = ReadInt24_t();
972                         if(race_previousbestname)
973                                 strunzone(race_previousbestname);
974                         race_previousbestname = strzone(ColorTranslateRGB(ReadString()));
975
976                         race_checkpointtime = time;
977
978                         if(race_checkpoint == 0 || race_checkpoint == 254)
979                         {
980                                 race_penaltyaccumulator = 0;
981                                 race_laptime = time; // valid
982                         }
983
984                         break;
985
986                 case RACE_NET_CHECKPOINT_CLEAR:
987                         race_laptime = 0;
988                         race_checkpointtime = 0;
989                         break;
990
991                 case RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING:
992                         race_laptime = ReadCoord();
993                         race_checkpointtime = -99999;
994                         // fall through
995                 case RACE_NET_CHECKPOINT_NEXT_QUALIFYING:
996                         race_nextcheckpoint = ReadByte();
997
998                         race_nextbesttime = ReadInt24_t();
999                         if(race_nextbestname)
1000                                 strunzone(race_nextbestname);
1001                         race_nextbestname = strzone(ColorTranslateRGB(ReadString()));
1002                         break;
1003
1004                 case RACE_NET_CHECKPOINT_HIT_RACE:
1005                         race_mycheckpoint = ReadByte();
1006                         race_mycheckpointtime = time;
1007                         race_mycheckpointdelta = ReadInt24_t();
1008                         race_mycheckpointlapsdelta = ReadByte();
1009                         if(race_mycheckpointlapsdelta >= 128)
1010                                 race_mycheckpointlapsdelta -= 256;
1011                         if(race_mycheckpointenemy)
1012                                 strunzone(race_mycheckpointenemy);
1013                         race_mycheckpointenemy = strzone(ColorTranslateRGB(ReadString()));
1014                         break;
1015
1016                 case RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT:
1017                         race_othercheckpoint = ReadByte();
1018                         race_othercheckpointtime = time;
1019                         race_othercheckpointdelta = ReadInt24_t();
1020                         race_othercheckpointlapsdelta = ReadByte();
1021                         if(race_othercheckpointlapsdelta >= 128)
1022                                 race_othercheckpointlapsdelta -= 256;
1023                         if(race_othercheckpointenemy)
1024                                 strunzone(race_othercheckpointenemy);
1025                         race_othercheckpointenemy = strzone(ColorTranslateRGB(ReadString()));
1026                         break;
1027
1028                 case RACE_NET_PENALTY_RACE:
1029                         race_penaltyeventtime = time;
1030                         race_penaltytime = ReadShort();
1031                         //race_penaltyaccumulator += race_penaltytime;
1032                         if(race_penaltyreason)
1033                                 strunzone(race_penaltyreason);
1034                         race_penaltyreason = strzone(ReadString());
1035                         break;
1036
1037                 case RACE_NET_PENALTY_QUALIFYING:
1038                         race_penaltyeventtime = time;
1039                         race_penaltytime = ReadShort();
1040                         race_penaltyaccumulator += race_penaltytime;
1041                         if(race_penaltyreason)
1042                                 strunzone(race_penaltyreason);
1043                         race_penaltyreason = strzone(ReadString());
1044                         break;
1045
1046                 case RACE_NET_SERVER_RECORD:
1047                         race_server_record = ReadInt24_t();
1048                         break;
1049                 case RACE_NET_SPEED_AWARD:
1050                         race_speedaward = ReadShort();
1051                         race_speedaward_holder = strzone(ReadString());
1052                         break;
1053                 case RACE_NET_SPEED_AWARD_BEST:
1054                         race_speedaward_alltimebest = ReadShort();
1055                         race_speedaward_alltimebest_holder = strzone(ReadString());
1056                         break;
1057         }
1058 }
1059
1060 void Net_ReadSpawn()
1061 {
1062         zoomin_effect = 1;
1063         current_viewzoom = 0.6;
1064 }
1065
1066 void Net_TeamNagger()
1067 {
1068         teamnagger = 1;
1069 }
1070
1071 void Net_ReadPingPLReport()
1072 {
1073         float e, pi, pl, ml;
1074         e = ReadByte();
1075         pi = ReadShort();
1076         pl = ReadByte();
1077         ml = ReadByte();
1078         if not(playerslots[e])
1079                 return;
1080         playerslots[e].ping = pi;
1081         playerslots[e].ping_packetloss = pl / 255.0;
1082         playerslots[e].ping_movementloss = ml / 255.0;
1083 }
1084
1085 // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer.
1086 // You must ALWAYS first acquire the temporary ID, which is sent as a byte.
1087 // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event.
1088 float CSQC_Parse_TempEntity()
1089 {
1090         local float bHandled;
1091                 bHandled  = true;
1092         // Acquire TE ID
1093         local float nTEID;
1094                 nTEID = ReadByte();
1095
1096                 // NOTE: Could just do return instead of break...
1097         switch(nTEID)
1098         {
1099                 case TE_CSQC_PICTURE:
1100                         Net_MapVote_Picture();
1101                         bHandled = true;
1102                         break;
1103                 case TE_CSQC_RACE:
1104                         Net_ReadRace();
1105                         bHandled = true;
1106                         break;
1107                 case 13: // TE_BEAM
1108                         Net_GrapplingHook();
1109                         bHandled = true;
1110                         break;
1111                 case TE_CSQC_SPAWN:
1112                         Net_ReadSpawn();
1113                         bHandled = true;
1114                         break;
1115                 case TE_CSQC_ZCURVEPARTICLES:
1116                         Net_ReadZCurveParticles();
1117                         bHandled = true;
1118                         break;
1119                 case TE_CSQC_NEXGUNBEAMPARTICLE:
1120                         Net_ReadNexgunBeamParticle();
1121                         bHandled = true;
1122                         break;
1123                 case TE_CSQC_TEAMNAGGER:
1124                         Net_TeamNagger();
1125                         bHandled = true;
1126                         break;
1127         case TE_CSQC_LIGHTNINGARC:
1128             Net_ReadLightningarc();
1129             bHandled = true;
1130             break;
1131         case TE_CSQC_PINGPLREPORT:
1132             Net_ReadPingPLReport();
1133             bHandled = true;
1134             break;
1135                 default:
1136                         // No special logic for this temporary entity; return 0 so the engine can handle it
1137                         bHandled = false;
1138                         break;
1139         }
1140
1141         return bHandled;
1142 }
1143
1144 string getcommandkey(string text, string command)
1145 {
1146         string keys;
1147         float n, j, k, l;
1148
1149         if (!sbar_showbinds)
1150                 return text;
1151
1152         keys = db_get(binddb, command);
1153         if (!keys)
1154         {
1155                 n = tokenize(findkeysforcommand(command)); // uses '...' strings
1156                 for(j = 0; j < n; ++j)
1157                 {
1158                         k = stof(argv(j));
1159                         if(k != -1)
1160                         {
1161                                 if ("" == keys)
1162                                         keys = keynumtostring(k);
1163                                 else
1164                                         keys = strcat(keys, ", ", keynumtostring(k));
1165
1166                                 ++l;
1167                                 if (sbar_showbinds_limit > 0 && sbar_showbinds_limit >= l) break;
1168                         }
1169
1170                 }
1171                 db_put(binddb, command, keys);
1172         }
1173
1174         if ("" == keys) {
1175                 if (sbar_showbinds > 1)
1176                         return strcat(text, " (not bound)");
1177                 else
1178                         return text;
1179         }
1180         else if (sbar_showbinds > 1)
1181                 return strcat(text, " (", keys, ")");
1182         else
1183                 return keys;
1184 }