]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/main.qc
Onslaught: move controlpoint + generator to mutator module
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / main.qc
1 #include "main.qh"
2
3 #include "damage.qh"
4 #include "effects.qh"
5 #include "gibs.qh"
6 #include "hook.qh"
7 #include "hud/all.qh"
8 #include "laser.qh"
9 #include "mapvoting.qh"
10 #include "modeleffects.qh"
11 #include "mutators/events.qh"
12 #include "particles.qh"
13 #include "quickmenu.qh"
14 #include "scoreboard.qh"
15 #include "shownames.qh"
16 #include "tuba.qh"
17 #include "t_items.qh"
18 #include "wall.qh"
19 #include "weapons/projectile.qh"
20 #include "../common/deathtypes/all.qh"
21 #include "../common/items/all.qh"
22 #include "../common/mapinfo.qh"
23 #include "../common/minigames/cl_minigames.qh"
24 #include "../common/minigames/cl_minigames_hud.qh"
25 #include "../common/net_notice.qh"
26 #include "../common/triggers/include.qh"
27 #include "../common/turrets/cl_turrets.qh"
28 #include "../common/vehicles/all.qh"
29 #include "../lib/csqcmodel/cl_model.qh"
30 #include "../lib/csqcmodel/interpolate.qh"
31 #include "../lib/warpzone/client.qh"
32
33 // --------------------------------------------------------------------------
34 // BEGIN REQUIRED CSQC FUNCTIONS
35 //include "main.qh"
36
37 entity clearentity_ent;
38 void clearentity(entity e)
39 {
40         if (!clearentity_ent)
41         {
42                 clearentity_ent = new(clearentity);
43         }
44         int n = e.entnum;
45         copyentity(clearentity_ent, e);
46         e.entnum = n;
47 }
48
49 #define DP_CSQC_ENTITY_REMOVE_IS_B0RKED
50 void menu_show_error()
51 {
52         drawstring('0 200 0', _("ERROR - MENU IS VISIBLE BUT NO MENU WAS DEFINED!"), '8 8 0', '1 0 0', 1, 0);
53 }
54
55 // CSQC_Init : Called every time the CSQC code is initialized (essentially at map load)
56 // Useful for precaching things
57
58 void menu_sub_null()
59 {
60 }
61
62 void draw_null(entity this) { }
63
64 string forcefog;
65 void ConsoleCommand_macro_init();
66 void CSQC_Init()
67 {
68         prvm_language = strzone(cvar_string("prvm_language"));
69
70 #ifdef WATERMARK
71         LOG_TRACEF("^4CSQC Build information: ^1%s\n", WATERMARK);
72 #endif
73
74         int i;
75
76         binddb = db_create();
77         tempdb = db_create();
78         ClientProgsDB = db_load("client.db");
79         compressShortVector_init();
80
81         draw_endBoldFont();
82         menu_visible = false;
83         menu_show = menu_show_error;
84         menu_action = func_null;
85
86         for(i = 0; i < 255; ++i)
87                 if(getplayerkeyvalue(i, "viewentity") == "")
88                         break;
89         maxclients = i;
90
91         //registercommand("hud_configure");
92         //registercommand("hud_save");
93         //registercommand("menu_action");
94
95         ConsoleCommand_macro_init();
96
97         registercvar("hud_usecsqc", "1");
98         registercvar("scoreboard_columns", "default");
99
100         registercvar("cl_nade_type", "3");
101         registercvar("cl_pokenade_type", "zombie");
102
103         registercvar("cl_jumpspeedcap_min", "");
104         registercvar("cl_jumpspeedcap_max", "");
105
106         registercvar("cl_multijump", "0");
107
108         registercvar("cl_spawn_near_teammate", "1");
109
110         gametype = 0;
111
112         // hud_fields uses strunzone on the titles!
113         for(i = 0; i < MAX_HUD_FIELDS; ++i)
114                 hud_title[i] = strzone("(null)");
115
116         Cmd_HUD_SetFields(0);
117
118         postinit = false;
119
120         calledhooks = 0;
121
122         teams = Sort_Spawn();
123         players = Sort_Spawn();
124
125         GetTeam(NUM_SPECTATOR, true); // add specs first
126
127         // needs to be done so early because of the constants they create
128         static_init();
129         static_init_late();
130         static_init_precache();
131
132         // precaches
133
134         Projectile_Precache();
135         Tuba_Precache();
136
137         if(autocvar_cl_reticle)
138         {
139                 precache_pic("gfx/reticle_normal");
140                 // weapon reticles are precached in weapon files
141         }
142
143         get_mi_min_max_texcoords(1); // try the CLEVER way first
144         minimapname = strcat("gfx/", mi_shortname, "_radar.tga");
145         shortmapname = mi_shortname;
146
147         if(precache_pic(minimapname) == "")
148         {
149                 // but maybe we have a non-clever minimap
150                 minimapname = strcat("gfx/", mi_shortname, "_mini.tga");
151                 if(precache_pic(minimapname) == "")
152                         minimapname = ""; // FAIL
153                 else
154                         get_mi_min_max_texcoords(0); // load new texcoords
155         }
156
157         mi_center = (mi_min + mi_max) * 0.5;
158         mi_scale = mi_max - mi_min;
159         minimapname = strzone(minimapname);
160
161         WarpZone_Init();
162
163         hud_skin_path = strzone(strcat("gfx/hud/", autocvar_hud_skin));
164         draw_currentSkin = strzone(strcat("gfx/menu/", cvar_string("menu_skin")));
165 }
166
167 // CSQC_Shutdown : Called every time the CSQC code is shutdown (changing maps, quitting, etc)
168 void Shutdown()
169 {
170         WarpZone_Shutdown();
171
172         remove(teams);
173         remove(players);
174         db_close(binddb);
175         db_close(tempdb);
176         if(autocvar_cl_db_saveasdump)
177                 db_dump(ClientProgsDB, "client.db");
178         else
179                 db_save(ClientProgsDB, "client.db");
180         db_close(ClientProgsDB);
181
182         if(camera_active)
183                 cvar_set("chase_active",ftos(chase_active_backup));
184
185         // unset the event chasecam's chase_active
186         if(autocvar_chase_active < 0)
187                 cvar_set("chase_active", "0");
188
189         if (!isdemo())
190         {
191                 if (!(calledhooks & HOOK_START))
192                         localcmd("\n_cl_hook_gamestart nop\n");
193                 if (!(calledhooks & HOOK_END))
194                         localcmd("\ncl_hook_gameend\n");
195         }
196
197         deactivate_minigame();
198         HUD_MinigameMenu_Close();
199 }
200
201 .float has_team;
202 float SetTeam(entity o, int Team)
203 {
204         entity tm;
205         if(teamplay)
206         {
207                 switch(Team)
208                 {
209                         case -1:
210                         case NUM_TEAM_1:
211                         case NUM_TEAM_2:
212                         case NUM_TEAM_3:
213                         case NUM_TEAM_4:
214                                 break;
215                         default:
216                                 if(GetTeam(Team, false) == world)
217                                 {
218                                         LOG_TRACEF("trying to switch to unsupported team %d\n", Team);
219                                         Team = NUM_SPECTATOR;
220                                 }
221                                 break;
222                 }
223         }
224         else
225         {
226                 switch(Team)
227                 {
228                         case -1:
229                         case 0:
230                                 break;
231                         default:
232                                 if(GetTeam(Team, false) == world)
233                                 {
234                                         LOG_TRACEF("trying to switch to unsupported team %d\n", Team);
235                                         Team = NUM_SPECTATOR;
236                                 }
237                                 break;
238                 }
239         }
240         if(Team == -1) // leave
241         {
242                 if(o.has_team)
243                 {
244                         tm = GetTeam(o.team, false);
245                         tm.team_size -= 1;
246                         o.has_team = 0;
247                         return true;
248                 }
249         }
250         else
251         {
252                 if (!o.has_team)
253                 {
254                         o.team = Team;
255                         tm = GetTeam(Team, true);
256                         tm.team_size += 1;
257                         o.has_team = 1;
258                         return true;
259                 }
260                 else if(Team != o.team)
261                 {
262                         tm = GetTeam(o.team, false);
263                         tm.team_size -= 1;
264                         o.team = Team;
265                         tm = GetTeam(Team, true);
266                         tm.team_size += 1;
267                         return true;
268                 }
269         }
270         return false;
271 }
272
273 void Playerchecker_Think()
274 {SELFPARAM();
275     int i;
276         entity e;
277         for(i = 0; i < maxclients; ++i)
278         {
279                 e = playerslots[i];
280                 if(GetPlayerName(i) == "")
281                 {
282                         if(e.sort_prev)
283                         {
284                                 // player disconnected
285                                 SetTeam(e, -1);
286                                 RemovePlayer(e);
287                                 e.sort_prev = world;
288                                 //e.gotscores = 0;
289                         }
290                 }
291                 else
292                 {
293                         if (!e.sort_prev)
294                         {
295                                 // player connected
296                                 if (!e)
297                                 {
298                                         playerslots[i] = e = new(playerslot);
299                                         make_pure(e);
300                                 }
301                                 e.sv_entnum = i;
302                                 e.ping = 0;
303                                 e.ping_packetloss = 0;
304                                 e.ping_movementloss = 0;
305                                 //e.gotscores = 0; // we might already have the scores...
306                                 SetTeam(e, GetPlayerColor(i)); // will not hurt; later updates come with HUD_UpdatePlayerTeams
307                                 RegisterPlayer(e);
308                                 HUD_UpdatePlayerPos(e);
309                         }
310                 }
311         }
312         self.nextthink = time + 0.2;
313 }
314
315 void Porto_Init();
316 void TrueAim_Init();
317 void PostInit()
318 {
319         entity playerchecker = new(playerchecker);
320         make_pure(playerchecker);
321         playerchecker.think = Playerchecker_Think;
322         playerchecker.nextthink = time + 0.2;
323
324         Porto_Init();
325         TrueAim_Init();
326
327         postinit = true;
328 }
329
330 // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client.
331 // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine.
332 // All keys are in ascii.
333 // bInputType = 0 is key pressed, 1 is key released, 2 and 3 are mouse input.
334 // In the case of keyboard input, nPrimary is the ascii code, and nSecondary is 0.
335 // In the case of mouse input, nPrimary is xdelta, nSecondary is ydelta.
336 // In the case of mouse input after a setcursormode(1) call, nPrimary is xpos, nSecondary is ypos.
337 float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary)
338 {
339         float bSkipKey;
340         bSkipKey = false;
341
342         if (HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary))
343                 return true;
344
345         if (QuickMenu_InputEvent(bInputType, nPrimary, nSecondary))
346                 return true;
347
348         if ( HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary) )
349                 return true;
350
351         if (MapVote_InputEvent(bInputType, nPrimary, nSecondary))
352                 return true;
353
354         if (HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary))
355                 return true;
356
357         if(menu_visible && menu_action)
358                 if(menu_action(bInputType, nPrimary, nSecondary))
359                         return true;
360
361         return bSkipKey;
362 }
363
364 // END REQUIRED CSQC FUNCTIONS
365 // --------------------------------------------------------------------------
366
367 // --------------------------------------------------------------------------
368 // BEGIN OPTIONAL CSQC FUNCTIONS
369
370 void Ent_RemoveEntCS()
371 {SELFPARAM();
372         entcs_receiver[self.sv_entnum] = NULL;
373 }
374 void Ent_ReadEntCS()
375 {SELFPARAM();
376         make_pure(this);
377         this.classname = "entcs_receiver";
378         InterpolateOrigin_Undo();
379         int sf = ReadByte();
380
381         if(sf & BIT(0))
382                 self.sv_entnum = ReadByte();
383         if (sf & BIT(1))
384         {
385                 self.origin_x = ReadShort();
386                 self.origin_y = ReadShort();
387                 self.origin_z = ReadShort();
388                 setorigin(self, self.origin);
389         }
390         if (sf & BIT(2))
391         {
392                 self.angles_y = ReadByte() * 360.0 / 256;
393                 self.angles_x = self.angles_z = 0;
394         }
395         if (sf & BIT(3))
396                 self.healthvalue = ReadByte() * 10;
397         if (sf & BIT(4))
398                 self.armorvalue = ReadByte() * 10;
399
400         entcs_receiver[self.sv_entnum] = self;
401         self.entremove = Ent_RemoveEntCS;
402         self.iflags |= IFLAG_ORIGIN;
403
404         InterpolateOrigin_Note();
405 }
406
407 void Ent_Remove();
408
409 void Ent_RemovePlayerScore()
410 {SELFPARAM();
411         if(self.owner) {
412                 SetTeam(self.owner, -1);
413                 self.owner.gotscores = 0;
414                 for(int i = 0; i < MAX_SCORE; ++i) {
415                         self.owner.(scores[i]) = 0; // clear all scores
416                 }
417         }
418 }
419
420 void Ent_ReadPlayerScore()
421 {SELFPARAM();
422         make_pure(this);
423         int i, n;
424         bool isNew;
425         entity o;
426
427         // damnit -.- don't want to go change every single .sv_entnum in hud.qc AGAIN
428         // (no I've never heard of M-x replace-string, sed, or anything like that)
429         isNew = !self.owner; // workaround for DP bug
430         n = ReadByte()-1;
431
432 #ifdef DP_CSQC_ENTITY_REMOVE_IS_B0RKED
433         if(!isNew && n != self.sv_entnum)
434         {
435                 //print("A CSQC entity changed its owner!\n");
436                 LOG_INFOF("A CSQC entity changed its owner! (edict: %d, classname: %s)\n", num_for_edict(self), self.classname);
437                 isNew = true;
438                 Ent_Remove();
439         }
440 #endif
441
442         self.sv_entnum = n;
443
444         o = playerslots[self.sv_entnum];
445         if (!o)
446         {
447                 o = playerslots[self.sv_entnum] = new(playerslot);
448                 make_pure(o);
449         }
450         self.owner = o;
451         o.sv_entnum = self.sv_entnum;
452         o.gotscores = 1;
453
454         //if (!o.sort_prev)
455         //      RegisterPlayer(o);
456         //playerchecker will do this for us later, if it has not already done so
457
458     int sf, lf;
459 #if MAX_SCORE <= 8
460         sf = ReadByte();
461         lf = ReadByte();
462 #else
463         sf = ReadShort();
464         lf = ReadShort();
465 #endif
466     int p;
467         for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2)
468                 if(sf & p)
469                 {
470                         if(lf & p)
471                                 o.(scores[i]) = ReadInt24_t();
472                         else
473                                 o.(scores[i]) = ReadChar();
474                 }
475
476         if(o.sort_prev)
477                 HUD_UpdatePlayerPos(o); // if not registered, we cannot do this yet!
478
479         self.entremove = Ent_RemovePlayerScore;
480 }
481
482 void Ent_ReadTeamScore()
483 {SELFPARAM();
484         make_pure(this);
485         int i;
486         entity o;
487
488         self.team = ReadByte();
489         o = self.owner = GetTeam(self.team, true); // these team numbers can always be trusted
490
491     int sf, lf;
492 #if MAX_TEAMSCORE <= 8
493         sf = ReadByte();
494         lf = ReadByte();
495 #else
496         sf = ReadShort();
497         lf = ReadShort();
498 #endif
499         int p;
500         for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2)
501                 if(sf & p)
502                 {
503                         if(lf & p)
504                                 o.(teamscores[i]) = ReadInt24_t();
505                         else
506                                 o.(teamscores[i]) = ReadChar();
507                 }
508
509         HUD_UpdateTeamPos(o);
510 }
511
512 void Ent_ClientData()
513 {
514         make_pure(self);
515         float newspectatee_status;
516
517     int f = ReadByte();
518
519         scoreboard_showscores_force = (f & 1);
520
521         if(f & 2)
522         {
523                 newspectatee_status = ReadByte();
524                 if(newspectatee_status == player_localnum + 1)
525                         newspectatee_status = -1; // observing
526         }
527         else
528                 newspectatee_status = 0;
529
530         spectatorbutton_zoom = (f & 4);
531
532         if(f & 8)
533         {
534                 angles_held_status = 1;
535                 angles_held.x = ReadAngle();
536                 angles_held.y = ReadAngle();
537                 angles_held.z = 0;
538         }
539         else
540                 angles_held_status = 0;
541
542         if(newspectatee_status != spectatee_status)
543         {
544                 // clear race stuff
545                 race_laptime = 0;
546                 race_checkpointtime = 0;
547         }
548         if (autocvar_hud_panel_healtharmor_progressbar_gfx)
549         {
550                 if ( (spectatee_status == -1 && newspectatee_status > 0) //before observing, now spectating
551                   || (spectatee_status > 0 && newspectatee_status > 0 && spectatee_status != newspectatee_status) //changed spectated player
552                 )
553                         prev_p_health = -1;
554                 else if(spectatee_status && !newspectatee_status) //before observing/spectating, now playing
555                         prev_health = -1;
556         }
557         spectatee_status = newspectatee_status;
558
559         // we could get rid of spectatee_status, and derive it from player_localentnum and player_localnum
560 }
561
562 void Ent_Nagger()
563 {
564         make_pure(self);
565     int i, j, b, f;
566
567     int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS
568
569         if(!(nags & BIT(2)))
570         {
571                 if(vote_called_vote)
572                         strunzone(vote_called_vote);
573                 vote_called_vote = string_null;
574                 vote_active = 0;
575         }
576         else
577         {
578                 vote_active = 1;
579         }
580
581         if(nags & BIT(6))
582         {
583                 vote_yescount = ReadByte();
584                 vote_nocount = ReadByte();
585                 vote_needed = ReadByte();
586                 vote_highlighted = ReadChar();
587         }
588
589         if(nags & BIT(7))
590         {
591                 if(vote_called_vote)
592                         strunzone(vote_called_vote);
593                 vote_called_vote = strzone(ColorTranslateRGB(ReadString()));
594         }
595
596         if(nags & 1)
597         {
598                 for(j = 0; j < maxclients; ++j)
599                         if(playerslots[j])
600                                 playerslots[j].ready = 1;
601                 for(i = 1; i <= maxclients; i += 8)
602                 {
603                         f = ReadByte();
604                         for(j = i-1, b = 1; b < 256; b *= 2, ++j)
605                                 if (!(f & b))
606                                         if(playerslots[j])
607                                                 playerslots[j].ready = 0;
608                 }
609         }
610
611         ready_waiting = (nags & BIT(0));
612         ready_waiting_for_me = (nags & BIT(1));
613         vote_waiting = (nags & BIT(2));
614         vote_waiting_for_me = (nags & BIT(3));
615         warmup_stage = (nags & BIT(4));
616 }
617
618 void Ent_EliminatedPlayers()
619 {
620         make_pure(self);
621     int i, j, b, f;
622
623     int sf = ReadByte();
624         if(sf & 1)
625         {
626                 for(j = 0; j < maxclients; ++j)
627                         if(playerslots[j])
628                                 playerslots[j].eliminated = 1;
629                 for(i = 1; i <= maxclients; i += 8)
630                 {
631                         f = ReadByte();
632                         for(j = i-1, b = 1; b < 256; b *= 2, ++j)
633                                 if (!(f & b))
634                                         if(playerslots[j])
635                                                 playerslots[j].eliminated = 0;
636                 }
637         }
638 }
639
640 void Ent_RandomSeed()
641 {
642         make_pure(self);
643         prandom_debug();
644         float s = ReadShort();
645         psrandom(s);
646 }
647
648 void Ent_ReadAccuracy()
649 {
650         make_pure(self);
651     int sf = ReadInt24_t();
652         if (sf == 0) {
653                 for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w)
654                         weapon_accuracy[w] = -1;
655                 return;
656         }
657
658         int f = 1;
659         for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w) {
660                 if (sf & f) {
661             int b = ReadByte();
662                         if (b == 0)
663                                 weapon_accuracy[w] = -1;
664                         else if (b == 255)
665                                 weapon_accuracy[w] = 1.0; // no better error handling yet, sorry
666                         else
667                                 weapon_accuracy[w] = (b - 1.0) / 100.0;
668                 }
669                 f = (f == 0x800000) ? 1 : f * 2;
670         }
671 }
672
673 void Spawn_Draw(entity this)
674 {
675         __pointparticles(this.cnt, this.origin + '0 0 28', '0 0 2', bound(0, frametime, 0.1));
676 }
677
678 void Ent_ReadSpawnPoint(float is_new) // entity for spawnpoint
679 {SELFPARAM();
680         float teamnum = (ReadByte() - 1);
681         vector spn_origin;
682         spn_origin.x = ReadShort();
683         spn_origin.y = ReadShort();
684         spn_origin.z = ReadShort();
685
686         //if(is_new)
687         //{
688                 self.origin = spn_origin;
689                 setsize(self, PL_MIN_CONST, PL_MAX_CONST);
690                 //droptofloor();
691
692                 /*if(autocvar_cl_spawn_point_model) // needs a model first
693                 {
694                         self.mdl = "models/spawnpoint.md3";
695                         self.colormod = Team_ColorRGB(teamnum);
696                         precache_model(self.mdl);
697                         setmodel(self, self.mdl);
698                         self.drawmask = MASK_NORMAL;
699                         //self.movetype = MOVETYPE_NOCLIP;
700                         //self.draw = Spawn_Draw;
701                 }*/
702                 if(autocvar_cl_spawn_point_particles)
703                 {
704                         if((serverflags & SERVERFLAG_TEAMPLAY))
705                         {
706                                 switch(teamnum)
707                                 {
708                                         case NUM_TEAM_1: self.cnt = particleeffectnum(EFFECT_SPAWNPOINT_RED); break;
709                                         case NUM_TEAM_2: self.cnt = particleeffectnum(EFFECT_SPAWNPOINT_BLUE); break;
710                                         case NUM_TEAM_3: self.cnt = particleeffectnum(EFFECT_SPAWNPOINT_YELLOW); break;
711                                         case NUM_TEAM_4: self.cnt = particleeffectnum(EFFECT_SPAWNPOINT_PINK); break;
712                                         default: self.cnt = particleeffectnum(EFFECT_SPAWNPOINT_NEUTRAL); break;
713                                 }
714                         }
715                         else { self.cnt = particleeffectnum(EFFECT_SPAWNPOINT_NEUTRAL); }
716
717                         self.draw = Spawn_Draw;
718                 }
719         //}
720
721         //printf("Ent_ReadSpawnPoint(is_new = %d); origin = %s, team = %d, effect = %d\n", is_new, vtos(self.origin), teamnum, self.cnt);
722 }
723
724 void Ent_ReadSpawnEvent(float is_new)
725 {SELFPARAM();
726         // If entnum is 0, ONLY do the local spawn actions
727         // this way the server can disable the sending of
728         // spawn origin or such to clients if wanted.
729         float entnum = ReadByte();
730
731         if(entnum)
732         {
733                 self.origin_x = ReadShort();
734                 self.origin_y = ReadShort();
735                 self.origin_z = ReadShort();
736
737                 if(is_new)
738                 {
739                         float teamnum = GetPlayerColor(entnum - 1);
740
741                         if(autocvar_cl_spawn_event_particles)
742                         {
743                                 switch(teamnum)
744                                 {
745                                         case NUM_TEAM_1: pointparticles(EFFECT_SPAWN_RED, self.origin, '0 0 0', 1); break;
746                                         case NUM_TEAM_2: pointparticles(EFFECT_SPAWN_BLUE, self.origin, '0 0 0', 1); break;
747                                         case NUM_TEAM_3: pointparticles(EFFECT_SPAWN_YELLOW, self.origin, '0 0 0', 1); break;
748                                         case NUM_TEAM_4: pointparticles(EFFECT_SPAWN_PINK, self.origin, '0 0 0', 1); break;
749                                         default: pointparticles(EFFECT_SPAWN_NEUTRAL, self.origin, '0 0 0', 1); break;
750                                 }
751                         }
752                         if(autocvar_cl_spawn_event_sound)
753                         {
754                                 sound(self, CH_TRIGGER, SND_SPAWN, VOL_BASE, ATTEN_NORM);
755                         }
756                 }
757         }
758
759         // local spawn actions
760         if(is_new && (!entnum || (entnum == player_localentnum)))
761         {
762                 zoomin_effect = 1;
763                 current_viewzoom = (1 / bound(1, autocvar_cl_spawnzoom_factor, 16));
764
765                 if(autocvar_cl_unpress_zoom_on_spawn)
766                 {
767                         localcmd("-zoom\n");
768                         button_zoom = false;
769                 }
770         }
771         HUD_Radar_Hide_Maximized();
772         //printf("Ent_ReadSpawnEvent(is_new = %d); origin = %s, entnum = %d, localentnum = %d\n", is_new, vtos(self.origin), entnum, player_localentnum);
773 }
774
775 // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured.
776 // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS.
777 void Ent_RadarLink();
778 void Ent_Init();
779 void Ent_ScoresInfo();
780 void CSQC_Ent_Update(float bIsNewEntity)
781 {
782         SELFPARAM();
783         this.sourceLocLine = __LINE__;
784         this.sourceLocFile = __FILE__;
785         int t = ReadByte();
786
787         // set up the "time" global for received entities to be correct for interpolation purposes
788         float savetime = time;
789         if(servertime)
790         {
791                 time = servertime;
792         }
793         else
794         {
795                 serverprevtime = time;
796                 serverdeltatime = getstatf(STAT_MOVEVARS_TICRATE) * getstatf(STAT_MOVEVARS_TIMESCALE);
797                 time = serverprevtime + serverdeltatime;
798         }
799
800 #ifdef DP_CSQC_ENTITY_REMOVE_IS_B0RKED
801         if(this.enttype)
802         {
803                 if(t != this.enttype || bIsNewEntity)
804                 {
805                         LOG_INFOF("A CSQC entity changed its type! (edict: %d, server: %d, type: %d -> %d)\n", num_for_edict(this), this.entnum, this.enttype, t);
806                         Ent_Remove();
807                         clearentity(this);
808                         bIsNewEntity = 1;
809                 }
810         }
811         else
812         {
813                 if(!bIsNewEntity)
814                 {
815                         LOG_INFOF("A CSQC entity appeared out of nowhere! (edict: %d, server: %d, type: %d)\n", num_for_edict(this), this.entnum, t);
816                         bIsNewEntity = 1;
817                 }
818         }
819 #endif
820         this.enttype = t;
821         bool done = false;
822         FOREACH(LinkedEntities, it.m_id == t, LAMBDA(
823                 this.classname = it.netname;
824                 if (autocvar_developer_csqcentities)
825             LOG_INFOF("CSQC_Ent_Update(%d) with self=%i {.entnum=%d, .enttype=%d} t=%s (%d)\n", bIsNewEntity, this, this.entnum, this.enttype, it.netname, t);
826                 done = it.m_read(this, bIsNewEntity);
827                 break;
828         ));
829         time = savetime;
830         if (!done)
831         {
832                 //error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), this.enttype));
833                 error(sprintf("Unknown entity type in CSQC_Ent_Update (enttype: %d, edict: %d, classname: %s)\n", this.enttype, num_for_edict(this), this.classname));
834         }
835 }
836 NET_HANDLE(ENT_CLIENT_ENTCS, bool isnew)
837 {
838         Ent_ReadEntCS();
839         return true;
840 }
841 NET_HANDLE(ENT_CLIENT_SCORES, bool isnew)
842 {
843         Ent_ReadPlayerScore();
844         return true;
845 }
846 NET_HANDLE(ENT_CLIENT_TEAMSCORES, bool isnew)
847 {
848         Ent_ReadTeamScore();
849         return true;
850 }
851 NET_HANDLE(ENT_CLIENT_POINTPARTICLES, bool isnew)
852 {
853         Ent_PointParticles();
854         return true;
855 }
856 NET_HANDLE(ENT_CLIENT_RAINSNOW, bool isnew)
857 {
858         Ent_RainOrSnow();
859         return true;
860 }
861 NET_HANDLE(ENT_CLIENT_LASER, bool isnew)
862 {
863         Ent_Laser();
864         return true;
865 }
866 NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
867 {
868         Ent_Nagger();
869         return true;
870 }
871 NET_HANDLE(ENT_CLIENT_ELIMINATEDPLAYERS, bool isnew)
872 {
873         Ent_EliminatedPlayers();
874         return true;
875 }
876 NET_HANDLE(ENT_CLIENT_RADARLINK, bool isnew)
877 {
878         Ent_RadarLink();
879         return true;
880 }
881 NET_HANDLE(ENT_CLIENT_PROJECTILE, bool isnew)
882 {
883         Ent_Projectile();
884         return true;
885 }
886 NET_HANDLE(ENT_CLIENT_DAMAGEINFO, bool isnew)
887 {
888         Ent_DamageInfo(isnew);
889         return true;
890 }
891 NET_HANDLE(ENT_CLIENT_INIT, bool isnew)
892 {
893         Ent_Init();
894         return true;
895 }
896 NET_HANDLE(ENT_CLIENT_SCORES_INFO, bool isnew)
897 {
898         Ent_ScoresInfo();
899         return true;
900 }
901 NET_HANDLE(ENT_CLIENT_MAPVOTE, bool isnew)
902 {
903         Ent_MapVote();
904         return true;
905 }
906 NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
907 {
908         Ent_ClientData();
909         return true;
910 }
911 NET_HANDLE(ENT_CLIENT_RANDOMSEED, bool isnew)
912 {
913         Ent_RandomSeed();
914         return true;
915 }
916 NET_HANDLE(ENT_CLIENT_WALL, bool isnew)
917 {
918         Ent_Wall();
919         return true;
920 }
921 NET_HANDLE(ENT_CLIENT_MODELEFFECT, bool isnew)
922 {
923         Ent_ModelEffect(isnew);
924         return true;
925 }
926 NET_HANDLE(ENT_CLIENT_TUBANOTE, bool isnew)
927 {
928         Ent_TubaNote(isnew);
929         return true;
930 }
931 NET_HANDLE(ENT_CLIENT_WARPZONE, bool isnew)
932 {
933         WarpZone_Read(isnew);
934         return true;
935 }
936 NET_HANDLE(ENT_CLIENT_WARPZONE_CAMERA, bool isnew)
937 {
938         WarpZone_Camera_Read(isnew);
939         return true;
940 }
941 NET_HANDLE(ENT_CLIENT_WARPZONE_TELEPORTED, bool isnew)
942 {
943         WarpZone_Teleported_Read(isnew);
944         return true;
945 }
946 NET_HANDLE(ENT_CLIENT_TRIGGER_MUSIC, bool isnew)
947 {
948         Ent_ReadTriggerMusic();
949         return true;
950 }
951 NET_HANDLE(ENT_CLIENT_HOOK, bool isnew)
952 {
953         Ent_ReadHook(isnew, NET_ENT_CLIENT_HOOK);
954         return true;
955 }
956 NET_HANDLE(ENT_CLIENT_INVENTORY, bool isnew)
957 {
958         Inventory_Read(this);
959         return true;
960 }
961 NET_HANDLE(ENT_CLIENT_ARC_BEAM, bool isnew)
962 {
963         Ent_ReadArcBeam(isnew);
964         return true;
965 }
966 NET_HANDLE(ENT_CLIENT_ACCURACY, bool isnew)
967 {
968         Ent_ReadAccuracy();
969         return true;
970 }
971 NET_HANDLE(ENT_CLIENT_AUXILIARYXHAIR, bool isnew)
972 {
973         Net_AuXair2(isnew);
974         return true;
975 }
976 NET_HANDLE(ENT_CLIENT_TURRET, bool isnew)
977 {
978         ent_turret();
979         return true;
980 }
981 NET_HANDLE(ENT_CLIENT_MODEL, bool isnew)
982 {
983         CSQCModel_Read(isnew);
984         return true;
985 }
986 NET_HANDLE(ENT_CLIENT_ITEM, bool isnew)
987 {
988         ItemRead(isnew);
989         return true;
990 }
991 NET_HANDLE(ENT_CLIENT_BUMBLE_RAYGUN, bool isnew)
992 {
993         bumble_raygun_read(isnew);
994         return true;
995 }
996 NET_HANDLE(ENT_CLIENT_SPAWNPOINT, bool isnew)
997 {
998         Ent_ReadSpawnPoint(isnew);
999         return true;
1000 }
1001 NET_HANDLE(ENT_CLIENT_SPAWNEVENT, bool isnew)
1002 {
1003         Ent_ReadSpawnEvent(isnew);
1004         return true;
1005 }
1006 NET_HANDLE(ENT_CLIENT_NOTIFICATION, bool isnew)
1007 {
1008         Read_Notification(isnew);
1009         return true;
1010 }
1011 NET_HANDLE(ENT_CLIENT_MINIGAME, bool isnew)
1012 {
1013         ent_read_minigame();
1014         return true;
1015 }
1016 NET_HANDLE(ENT_CLIENT_VIEWLOC, bool isnew)
1017 {
1018         ent_viewloc();
1019         return true;
1020 }
1021 NET_HANDLE(ENT_CLIENT_VIEWLOC_TRIGGER, bool isnew)
1022 {
1023         ent_viewloc_trigger();
1024         return true;
1025 }
1026 NET_HANDLE(ENT_CLIENT_LADDER, bool isnew)
1027 {
1028         ent_func_ladder();
1029         return true;
1030 }
1031 NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, bool isnew)
1032 {
1033         ent_trigger_push();
1034         return true;
1035 }
1036 NET_HANDLE(ENT_CLIENT_TARGET_PUSH, bool isnew)
1037 {
1038         ent_target_push();
1039         return true;
1040 }
1041 NET_HANDLE(ENT_CLIENT_CONVEYOR, bool isnew)
1042 {
1043         ent_conveyor();
1044         return true;
1045 }
1046 NET_HANDLE(ENT_CLIENT_DOOR, bool isnew)
1047 {
1048         ent_door();
1049         return true;
1050 }
1051 NET_HANDLE(ENT_CLIENT_PLAT, bool isnew)
1052 {
1053         ent_plat();
1054         return true;
1055 }
1056 NET_HANDLE(ENT_CLIENT_SWAMP, bool isnew)
1057 {
1058         ent_swamp();
1059         return true;
1060 }
1061 NET_HANDLE(ENT_CLIENT_CORNER, bool isnew)
1062 {
1063         ent_corner();
1064         return true;
1065 }
1066 NET_HANDLE(ENT_CLIENT_KEYLOCK, bool isnew)
1067 {
1068         ent_keylock();
1069         return true;
1070 }
1071 NET_HANDLE(ENT_CLIENT_TRAIN, bool isnew)
1072 {
1073         ent_train();
1074         return true;
1075 }
1076 NET_HANDLE(ENT_CLIENT_TRIGGER_IMPULSE, bool isnew)
1077 {
1078         ent_trigger_impulse();
1079         return true;
1080 }
1081
1082 // Destructor, but does NOT deallocate the entity by calling remove(). Also
1083 // used when an entity changes its type. For an entity that someone interacts
1084 // with others, make sure it can no longer do so.
1085 void Ent_Remove()
1086 {SELFPARAM();
1087         if(self.entremove)
1088                 self.entremove();
1089
1090         if(self.skeletonindex)
1091         {
1092                 skel_delete(self.skeletonindex);
1093                 self.skeletonindex = 0;
1094         }
1095
1096         if(self.snd_looping > 0)
1097         {
1098                 sound(self, self.snd_looping, SND_Null, VOL_BASE, autocvar_g_jetpack_attenuation);
1099                 self.snd_looping = 0;
1100         }
1101
1102         self.enttype = 0;
1103         self.classname = "";
1104         self.draw = draw_null;
1105         self.entremove = menu_sub_null;
1106         // TODO possibly set more stuff to defaults
1107 }
1108 // CSQC_Ent_Remove : Called when the server requests a SSQC / CSQC entity to be removed.  Essentially call remove(self) as well.
1109 void CSQC_Ent_Remove()
1110 {SELFPARAM();
1111         if(autocvar_developer_csqcentities)
1112                 LOG_INFOF("CSQC_Ent_Remove() with self=%i self.entnum=%d self.enttype=%d\n", self, self.entnum, self.enttype);
1113
1114         if(wasfreed(self))
1115         {
1116                 LOG_INFO("WARNING: CSQC_Ent_Remove called for already removed entity. Packet loss?\n");
1117                 return;
1118         }
1119         if(self.enttype)
1120                 Ent_Remove();
1121         remove(self);
1122 }
1123
1124 void Gamemode_Init()
1125 {
1126         if (!isdemo())
1127         {
1128                 if(!(calledhooks & HOOK_START))
1129                         localcmd("\n_cl_hook_gamestart ", MapInfo_Type_ToString(gametype), "\n");
1130                 calledhooks |= HOOK_START;
1131         }
1132 }
1133 // 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.
1134 void CSQC_Parse_StuffCmd(string strMessage)
1135 {
1136         if(autocvar_developer_csqcentities)
1137                 LOG_INFOF("CSQC_Parse_StuffCmd(\"%s\")\n", strMessage);
1138
1139         localcmd(strMessage);
1140 }
1141 // 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.
1142 void CSQC_Parse_Print(string strMessage)
1143 {
1144         if(autocvar_developer_csqcentities)
1145                 LOG_INFOF("CSQC_Parse_Print(\"%s\")\n", strMessage);
1146
1147         print(ColorTranslateRGB(strMessage));
1148 }
1149
1150 // CSQC_Parse_CenterPrint : Provides the centerprint_hud string in the first parameter that the server provided.
1151 void CSQC_Parse_CenterPrint(string strMessage)
1152 {
1153         if(autocvar_developer_csqcentities)
1154                 LOG_INFOF("CSQC_Parse_CenterPrint(\"%s\")\n", strMessage);
1155
1156         centerprint_hud(strMessage);
1157 }
1158
1159 string notranslate_fogcmd1 = "\nfog ";
1160 string notranslate_fogcmd2 = "\nr_fog_exp2 0\nr_drawfog 1\n";
1161 void Fog_Force()
1162 {
1163         // TODO somehow thwart prvm_globalset client ...
1164
1165         if(autocvar_cl_orthoview && autocvar_cl_orthoview_nofog)
1166                 { localcmd("\nr_drawfog 0\n"); }
1167         else if(forcefog != "")
1168                 { localcmd(strcat(notranslate_fogcmd1, forcefog, notranslate_fogcmd2)); }
1169 }
1170
1171 void Gamemode_Init();
1172 void Ent_ScoresInfo()
1173 {SELFPARAM();
1174     int i;
1175         make_pure(this);
1176         gametype = ReadInt24_t();
1177         HUD_ModIcons_SetFunc();
1178         for(i = 0; i < MAX_SCORE; ++i)
1179         {
1180                 if(scores_label[i])
1181                         strunzone(scores_label[i]);
1182                 scores_label[i] = strzone(ReadString());
1183                 scores_flags[i] = ReadByte();
1184         }
1185         for(i = 0; i < MAX_TEAMSCORE; ++i)
1186         {
1187                 if(teamscores_label[i])
1188                         strunzone(teamscores_label[i]);
1189                 teamscores_label[i] = strzone(ReadString());
1190                 teamscores_flags[i] = ReadByte();
1191         }
1192         HUD_InitScores();
1193         Gamemode_Init();
1194 }
1195
1196 void Ent_Init()
1197 {SELFPARAM();
1198         make_pure(this);
1199
1200         nb_pb_period = ReadByte() / 32; //Accuracy of 1/32th
1201
1202         hook_shotorigin[0] = decompressShotOrigin(ReadInt24_t());
1203         hook_shotorigin[1] = decompressShotOrigin(ReadInt24_t());
1204         hook_shotorigin[2] = decompressShotOrigin(ReadInt24_t());
1205         hook_shotorigin[3] = decompressShotOrigin(ReadInt24_t());
1206         arc_shotorigin[0] = decompressShotOrigin(ReadInt24_t());
1207         arc_shotorigin[1] = decompressShotOrigin(ReadInt24_t());
1208         arc_shotorigin[2] = decompressShotOrigin(ReadInt24_t());
1209         arc_shotorigin[3] = decompressShotOrigin(ReadInt24_t());
1210
1211         if(forcefog)
1212                 strunzone(forcefog);
1213         forcefog = strzone(ReadString());
1214
1215         armorblockpercent = ReadByte() / 255.0;
1216
1217         g_balance_mortar_bouncefactor = ReadCoord();
1218         g_balance_mortar_bouncestop = ReadCoord();
1219         g_balance_electro_secondary_bouncefactor = ReadCoord();
1220         g_balance_electro_secondary_bouncestop = ReadCoord();
1221
1222         vortex_scope = !ReadByte();
1223         rifle_scope = !ReadByte();
1224
1225         serverflags = ReadByte();
1226
1227         minelayer_maxmines = ReadByte();
1228
1229         hagar_maxrockets = ReadByte();
1230
1231         g_trueaim_minrange = ReadCoord();
1232         g_balance_porto_secondary = ReadByte();
1233
1234         MUTATOR_CALLHOOK(Ent_Init);
1235
1236         if(!postinit)
1237                 PostInit();
1238 }
1239
1240 void Net_ReadRace()
1241 {
1242         float b;
1243
1244         b = ReadByte();
1245
1246         switch(b)
1247         {
1248                 case RACE_NET_CHECKPOINT_HIT_QUALIFYING:
1249                         race_checkpoint = ReadByte();
1250                         race_time = ReadInt24_t();
1251                         race_previousbesttime = ReadInt24_t();
1252                         if(race_previousbestname)
1253                                 strunzone(race_previousbestname);
1254                         race_previousbestname = strzone(ColorTranslateRGB(ReadString()));
1255
1256                         race_checkpointtime = time;
1257
1258                         if(race_checkpoint == 0 || race_checkpoint == 254)
1259                         {
1260                                 race_penaltyaccumulator = 0;
1261                                 race_laptime = time; // valid
1262                         }
1263
1264                         break;
1265
1266                 case RACE_NET_CHECKPOINT_CLEAR:
1267                         race_laptime = 0;
1268                         race_checkpointtime = 0;
1269                         break;
1270
1271                 case RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING:
1272                         race_laptime = ReadCoord();
1273                         race_checkpointtime = -99999;
1274                         // fall through
1275                 case RACE_NET_CHECKPOINT_NEXT_QUALIFYING:
1276                         race_nextcheckpoint = ReadByte();
1277
1278                         race_nextbesttime = ReadInt24_t();
1279                         if(race_nextbestname)
1280                                 strunzone(race_nextbestname);
1281                         race_nextbestname = strzone(ColorTranslateRGB(ReadString()));
1282                         break;
1283
1284                 case RACE_NET_CHECKPOINT_HIT_RACE:
1285                         race_mycheckpoint = ReadByte();
1286                         race_mycheckpointtime = time;
1287                         race_mycheckpointdelta = ReadInt24_t();
1288                         race_mycheckpointlapsdelta = ReadByte();
1289                         if(race_mycheckpointlapsdelta >= 128)
1290                                 race_mycheckpointlapsdelta -= 256;
1291                         if(race_mycheckpointenemy)
1292                                 strunzone(race_mycheckpointenemy);
1293                         race_mycheckpointenemy = strzone(ColorTranslateRGB(ReadString()));
1294                         break;
1295
1296                 case RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT:
1297                         race_othercheckpoint = ReadByte();
1298                         race_othercheckpointtime = time;
1299                         race_othercheckpointdelta = ReadInt24_t();
1300                         race_othercheckpointlapsdelta = ReadByte();
1301                         if(race_othercheckpointlapsdelta >= 128)
1302                                 race_othercheckpointlapsdelta -= 256;
1303                         if(race_othercheckpointenemy)
1304                                 strunzone(race_othercheckpointenemy);
1305                         race_othercheckpointenemy = strzone(ColorTranslateRGB(ReadString()));
1306                         break;
1307
1308                 case RACE_NET_PENALTY_RACE:
1309                         race_penaltyeventtime = time;
1310                         race_penaltytime = ReadShort();
1311                         //race_penaltyaccumulator += race_penaltytime;
1312                         if(race_penaltyreason)
1313                                 strunzone(race_penaltyreason);
1314                         race_penaltyreason = strzone(ReadString());
1315                         break;
1316
1317                 case RACE_NET_PENALTY_QUALIFYING:
1318                         race_penaltyeventtime = time;
1319                         race_penaltytime = ReadShort();
1320                         race_penaltyaccumulator += race_penaltytime;
1321                         if(race_penaltyreason)
1322                                 strunzone(race_penaltyreason);
1323                         race_penaltyreason = strzone(ReadString());
1324                         break;
1325
1326                 case RACE_NET_SERVER_RECORD:
1327                         race_server_record = ReadInt24_t();
1328                         break;
1329                 case RACE_NET_SPEED_AWARD:
1330                         race_speedaward = ReadInt24_t();
1331                         if(race_speedaward_holder)
1332                                 strunzone(race_speedaward_holder);
1333                         race_speedaward_holder = strzone(ReadString());
1334                         break;
1335                 case RACE_NET_SPEED_AWARD_BEST:
1336                         race_speedaward_alltimebest = ReadInt24_t();
1337                         if(race_speedaward_alltimebest_holder)
1338                                 strunzone(race_speedaward_alltimebest_holder);
1339                         race_speedaward_alltimebest_holder = strzone(ReadString());
1340                         break;
1341                 case RACE_NET_SERVER_RANKINGS:
1342                         float prevpos, del;
1343             int pos = ReadShort();
1344                         prevpos = ReadShort();
1345                         del = ReadShort();
1346
1347                         // move other rankings out of the way
1348             int i;
1349                         if (prevpos) {
1350                                 for (i=prevpos-1;i>pos-1;--i) {
1351                                         grecordtime[i] = grecordtime[i-1];
1352                                         if(grecordholder[i])
1353                                                 strunzone(grecordholder[i]);
1354                                         grecordholder[i] = strzone(grecordholder[i-1]);
1355                                 }
1356                         } else if (del) { // a record has been deleted by the admin
1357                                 for (i=pos-1; i<= RANKINGS_CNT-1; ++i) {
1358                                         if (i == RANKINGS_CNT-1) { // clear out last record
1359                                                 grecordtime[i] = 0;
1360                                                 if (grecordholder[i])
1361                                                         strunzone(grecordholder[i]);
1362                                                 grecordholder[i] = string_null;
1363                                         }
1364                                         else {
1365                                                 grecordtime[i] = grecordtime[i+1];
1366                                                 if (grecordholder[i])
1367                                                         strunzone(grecordholder[i]);
1368                                                 grecordholder[i] = strzone(grecordholder[i+1]);
1369                                         }
1370                                 }
1371                         } else { // player has no ranked record yet
1372                                 for (i=RANKINGS_CNT-1;i>pos-1;--i) {
1373                                         grecordtime[i] = grecordtime[i-1];
1374                                         if(grecordholder[i])
1375                                                 strunzone(grecordholder[i]);
1376                                         grecordholder[i] = strzone(grecordholder[i-1]);
1377                                 }
1378                         }
1379
1380                         // store new ranking
1381                         if(grecordholder[pos-1] != "")
1382                                 strunzone(grecordholder[pos-1]);
1383                         grecordholder[pos-1] = strzone(ReadString());
1384                         grecordtime[pos-1] = ReadInt24_t();
1385                         if(grecordholder[pos-1] == GetPlayerName(player_localnum))
1386                                 race_myrank = pos;
1387                         break;
1388                 case RACE_NET_SERVER_STATUS:
1389                         race_status = ReadShort();
1390                         if(race_status_name)
1391                                 strunzone(race_status_name);
1392                         race_status_name = strzone(ReadString());
1393         }
1394 }
1395
1396 NET_HANDLE(TE_CSQC_RACE, bool isNew)
1397 {
1398         Net_ReadRace();
1399         return true;
1400 }
1401
1402 NET_HANDLE(TE_CSQC_TEAMNAGGER, bool isNew)
1403 {
1404         teamnagger = 1;
1405         return true;
1406 }
1407
1408 void Net_ReadPingPLReport()
1409 {
1410         int e, pi, pl, ml;
1411         e = ReadByte();
1412         pi = ReadShort();
1413         pl = ReadByte();
1414         ml = ReadByte();
1415         if (!(playerslots[e]))
1416                 return;
1417         playerslots[e].ping = pi;
1418         playerslots[e].ping_packetloss = pl / 255.0;
1419         playerslots[e].ping_movementloss = ml / 255.0;
1420 }
1421 NET_HANDLE(TE_CSQC_PINGPLREPORT, bool isNew)
1422 {
1423         Net_ReadPingPLReport();
1424         return true;
1425 }
1426
1427 void Net_WeaponComplain()
1428 {
1429         complain_weapon = ReadByte();
1430
1431         if(complain_weapon_name)
1432                 strunzone(complain_weapon_name);
1433         complain_weapon_name = strzone(WEP_NAME(complain_weapon));
1434
1435         complain_weapon_type = ReadByte();
1436
1437         complain_weapon_time = time;
1438         weapontime = time; // ping the weapon panel
1439
1440         switch(complain_weapon_type)
1441         {
1442                 case 0: Local_Notification(MSG_MULTI, ITEM_WEAPON_NOAMMO, complain_weapon); break;
1443                 case 1: Local_Notification(MSG_MULTI, ITEM_WEAPON_DONTHAVE, complain_weapon); break;
1444                 default: Local_Notification(MSG_MULTI, ITEM_WEAPON_UNAVAILABLE, complain_weapon); break;
1445         }
1446 }
1447 NET_HANDLE(TE_CSQC_WEAPONCOMPLAIN, bool isNew)
1448 {
1449         Net_WeaponComplain();
1450         return true;
1451 }
1452
1453 // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer.
1454 // You must ALWAYS first acquire the temporary ID, which is sent as a byte.
1455 // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event.
1456 bool CSQC_Parse_TempEntity()
1457 {
1458         // Acquire TE ID
1459         int nTEID = ReadByte();
1460
1461         FOREACH(TempEntities, it.m_id == nTEID, LAMBDA(
1462                 if (autocvar_developer_csqcentities)
1463                         LOG_INFOF("CSQC_Parse_TempEntity() nTEID=%s (%d)\n", it.netname, nTEID);
1464                 return it.m_read(NULL, true);
1465         ));
1466
1467         if (autocvar_developer_csqcentities)
1468                 LOG_INFOF("CSQC_Parse_TempEntity() with nTEID=%d\n", nTEID);
1469
1470         // No special logic for this temporary entity; return 0 so the engine can handle it
1471         return false;
1472 }
1473
1474 string getcommandkey(string text, string command)
1475 {
1476         string keys;
1477         float n, j, k, l = 0;
1478
1479         if (!autocvar_hud_showbinds)
1480                 return text;
1481
1482         keys = db_get(binddb, command);
1483         if (keys == "")
1484         {
1485                 n = tokenize(findkeysforcommand(command, 0)); // uses '...' strings
1486                 for(j = 0; j < n; ++j)
1487                 {
1488                         k = stof(argv(j));
1489                         if(k != -1)
1490                         {
1491                                 if ("" == keys)
1492                                         keys = keynumtostring(k);
1493                                 else
1494                                         keys = strcat(keys, ", ", keynumtostring(k));
1495
1496                                 ++l;
1497                                 if (autocvar_hud_showbinds_limit > 0 && autocvar_hud_showbinds_limit <= l)
1498                                         break;
1499                         }
1500
1501                 }
1502                 if (keys == "")
1503                         keys = "NO_KEY";
1504                 db_put(binddb, command, keys);
1505         }
1506
1507         if (keys == "NO_KEY") {
1508                 if (autocvar_hud_showbinds > 1)
1509                         return sprintf(_("%s (not bound)"), text);
1510                 else
1511                         return text;
1512         }
1513         else if (autocvar_hud_showbinds > 1)
1514                 return sprintf("%s (%s)", text, keys);
1515         else
1516                 return keys;
1517 }