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