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