Merge remote-tracking branch 'origin/master' into samual/respawn_improvements
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / cl_client.qc
1 void race_send_recordtime(float msg);
2 void race_SendRankings(float pos, float prevpos, float del, float msg);
3
4 void send_CSQC_teamnagger() {
5         WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
6         WriteByte(MSG_BROADCAST, TE_CSQC_TEAMNAGGER);
7 }
8
9 float ClientData_Send(entity to, float sf)
10 {
11         if(to != self.owner)
12         {
13                 error("wtf");
14                 return FALSE;
15         }
16
17         entity e;
18
19         e = to;
20         if(to.classname == "spectator")
21                 e = to.enemy;
22
23         sf = 0;
24
25         if(e.race_completed)
26                 sf |= 1; // forced scoreboard
27         if(to.spectatee_status)
28                 sf |= 2; // spectator ent number follows
29         if(e.zoomstate)
30                 sf |= 4; // zoomed
31         if(e.porto_v_angle_held)
32                 sf |= 8; // angles held
33
34         WriteByte(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
35         WriteByte(MSG_ENTITY, sf);
36
37         if(sf & 2)
38                 WriteByte(MSG_ENTITY, to.spectatee_status);
39
40         if(sf & 8)
41         {
42                 WriteAngle(MSG_ENTITY, e.v_angle_x);
43                 WriteAngle(MSG_ENTITY, e.v_angle_y);
44         }
45
46         return TRUE;
47 }
48
49 void ClientData_Attach()
50 {
51         Net_LinkEntity(self.clientdata = spawn(), FALSE, 0, ClientData_Send);
52         self.clientdata.drawonlytoclient = self;
53         self.clientdata.owner = self;
54 }
55
56 void ClientData_Detach()
57 {
58         remove(self.clientdata);
59         self.clientdata = world;
60 }
61
62 void ClientData_Touch(entity e)
63 {
64         e.clientdata.SendFlags = 1;
65
66         // make it spectatable
67         entity e2;
68         FOR_EACH_REALCLIENT(e2)
69         {
70                 if(e2 != e)
71                         if(e2.classname == "spectator")
72                                 if(e2.enemy == e)
73                                         e2.clientdata.SendFlags = 1;
74         }
75 }
76
77 .string netname_previous;
78
79 /*
80 =============
81 CheckPlayerModel
82
83 Checks if the argument string can be a valid playermodel.
84 Returns a valid one in doubt.
85 =============
86 */
87 string FallbackPlayerModel;
88 string CheckPlayerModel(string plyermodel) {
89         if(FallbackPlayerModel != cvar_defstring("_cl_playermodel"))
90         {
91                 // note: we cannot summon Don Strunzone here, some player may
92                 // still have the model string set. In case anyone manages how
93                 // to change a cvar default, we'll have a small leak here.
94                 FallbackPlayerModel = strzone(cvar_defstring("_cl_playermodel"));
95         }
96         // only in right path
97         if( substring(plyermodel,0,14) != "models/player/")
98                 return FallbackPlayerModel;
99         // only good file extensions
100         if(substring(plyermodel,-4,4) != ".zym")
101         if(substring(plyermodel,-4,4) != ".dpm")
102         if(substring(plyermodel,-4,4) != ".iqm")
103         if(substring(plyermodel,-4,4) != ".md3")
104         if(substring(plyermodel,-4,4) != ".psk")
105                 return FallbackPlayerModel;
106         // forbid the LOD models
107         if(substring(plyermodel, -9,5) == "_lod1")
108                 return FallbackPlayerModel;
109         if(substring(plyermodel, -9,5) == "_lod2")
110                 return FallbackPlayerModel;
111         if(plyermodel != strtolower(plyermodel))
112                 return FallbackPlayerModel;
113         // also, restrict to server models
114         if(autocvar_sv_servermodelsonly)
115         {
116                 if(!fexists(plyermodel))
117                         return FallbackPlayerModel;
118         }
119         return plyermodel;
120 }
121
122 void setplayermodel(entity e, string modelname)
123 {
124         precache_model(modelname);
125         setmodel(e, modelname);
126         player_setupanimsformodel();
127         UpdatePlayerSounds();
128 }
129
130 /*
131 =============
132 PutObserverInServer
133
134 putting a client as observer in the server
135 =============
136 */
137 void FixPlayermodel();
138 void PutObserverInServer (void)
139 {
140         entity  spot;
141     self.hud = HUD_NORMAL;
142         race_PreSpawnObserver();
143
144         spot = SelectSpawnPoint (TRUE);
145         if(!spot)
146                 error("No spawnpoints for observers?!?\n");
147         RemoveGrapplingHook(self); // Wazat's Grappling Hook
148
149         if(clienttype(self) == CLIENTTYPE_REAL)
150         {
151                 msg_entity = self;
152                 WriteByte(MSG_ONE, SVC_SETVIEW);
153                 WriteEntity(MSG_ONE, self);
154         }
155
156         MUTATOR_CALLHOOK(MakePlayerObserver);
157
158         minstagib_stop_countdown(self);
159
160         Portal_ClearAll(self);
161         
162         if(self.alivetime)
163         {
164                 if(!inWarmupStage)
165                         PlayerStats_Event(self, PLAYERSTATS_ALIVETIME, time - self.alivetime);
166                 self.alivetime = 0;
167         }
168
169         if(self.vehicle)
170                 vehicles_exit(VHEF_RELESE);         
171
172         WaypointSprite_PlayerDead();
173
174         if not(g_ca)  // don't reset teams when moving a ca player to the spectators
175                 self.team = -1;  // move this as it is needed to log the player spectating in eventlog
176
177         if(self.killcount != -666) {
178                 if(g_lms) {
179                         if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0 && self.lms_spectate_warning != 2)
180                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_NOLIVES, self.netname);
181                         else
182                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_FORFEIT, self.netname);
183                 } else { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_SPECTATE, self.netname); }
184
185                 if(self.just_joined == FALSE) {
186                         LogTeamchange(self.playerid, -1, 4);
187                 } else
188                         self.just_joined = FALSE;
189         }
190
191         PlayerScore_Clear(self); // clear scores when needed
192
193         accuracy_resend(self);
194
195         self.spectatortime = time;
196         
197         self.classname = "observer";
198         self.iscreature = FALSE;
199         self.teleportable = TELEPORT_SIMPLE;
200         self.damagedbycontents = FALSE;
201         self.health = -666;
202         self.takedamage = DAMAGE_NO;
203         self.solid = SOLID_NOT;
204         self.movetype = MOVETYPE_FLY_WORLDONLY; // user preference is controlled by playerprethink
205         self.flags = FL_CLIENT | FL_NOTARGET;
206         self.armorvalue = 666;
207         self.effects = 0;
208         self.armorvalue = autocvar_g_balance_armor_start;
209         self.pauserotarmor_finished = 0;
210         self.pauserothealth_finished = 0;
211         self.pauseregen_finished = 0;
212         self.damageforcescale = 0;
213         self.death_time = 0;
214         self.respawn_time = 0;
215         self.alpha = 0;
216         self.scale = 0;
217         self.fade_time = 0;
218         self.pain_frame = 0;
219         self.pain_finished = 0;
220         self.strength_finished = 0;
221         self.invincible_finished = 0;
222         self.superweapons_finished = 0;
223         self.pushltime = 0;
224         self.istypefrag = 0;
225         self.think = func_null;
226         self.nextthink = 0;
227         self.hook_time = 0;
228         self.deadflag = DEAD_NO;
229         self.angles = spot.angles;
230         self.angles_z = 0;
231         self.fixangle = TRUE;
232         self.crouch = FALSE;
233
234         setorigin (self, (spot.origin + PL_VIEW_OFS)); // offset it so that the spectator spawns higher off the ground, looks better this way
235         self.prevorigin = self.origin;
236         self.items = 0;
237         WEPSET_CLEAR_E(self);
238         self.model = "";
239         FixPlayermodel();
240         setmodel(self, "null");
241         self.drawonlytoclient = self;
242
243         setsize (self, PL_CROUCH_MIN, PL_CROUCH_MAX); // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY
244         self.view_ofs = '0 0 0'; // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS"
245
246         self.weapon = 0;
247         self.weaponname = "";
248         self.switchingweapon = 0;
249         self.weaponmodel = "";
250         self.weaponentity = world;
251         self.exteriorweaponentity = world;
252         self.killcount = -666;
253         self.velocity = '0 0 0';
254         self.avelocity = '0 0 0';
255         self.punchangle = '0 0 0';
256         self.punchvector = '0 0 0';
257         self.oldvelocity = self.velocity;
258         self.fire_endtime = -1;
259
260         if(g_arena)
261         {
262                 if(self.version_mismatch)
263                 {
264                         self.frags = FRAGS_SPECTATOR;
265                         Spawnqueue_Unmark(self);
266                         Spawnqueue_Remove(self);
267                 }
268                 else
269                 {
270                         self.frags = FRAGS_LMS_LOSER;
271                         Spawnqueue_Insert(self);
272                 }
273         }
274         else if(g_lms)
275         {
276                 // Only if the player cannot play at all
277                 if(PlayerScore_Add(self, SP_LMS_RANK, 0) == 666)
278                         self.frags = FRAGS_SPECTATOR;
279                 else
280                         self.frags = FRAGS_LMS_LOSER;
281         }
282         else if(g_ca)
283         {
284                 if(self.caplayer)
285                         self.frags = FRAGS_LMS_LOSER;
286                 else
287                         self.frags = FRAGS_SPECTATOR;
288         }
289         else if((g_race && g_race_qualifying) || g_cts)
290         {
291                 if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
292                         self.frags = FRAGS_LMS_LOSER;
293                 else
294                         self.frags = FRAGS_SPECTATOR;
295         }
296         else
297                 self.frags = FRAGS_SPECTATOR;
298 }
299
300 .float model_randomizer;
301 void FixPlayermodel()
302 {
303         string defaultmodel;
304         float defaultskin, chmdl, oldskin, n, i;
305         vector m1, m2;
306
307         defaultmodel = "";
308         defaultskin = 0;
309         chmdl = FALSE;
310
311         if(autocvar_sv_defaultcharacter == 1)
312         {
313                 if(teamplay)
314                 {
315                         string s;
316                         s = Team_ColorName_Lower(self.team);
317                         if(s != "neutral")
318                         {
319                                 defaultmodel = cvar_string(strcat("sv_defaultplayermodel_", s));
320                                 defaultskin = cvar(strcat("sv_defaultplayerskin_", s));
321                         }
322                 }
323
324                 if(defaultmodel == "")
325                 {
326                         defaultmodel = autocvar_sv_defaultplayermodel;
327                         defaultskin = autocvar_sv_defaultplayerskin;
328                 }
329
330                 n = tokenize_console(defaultmodel);
331                 if(n > 0)
332                         defaultmodel = argv(floor(n * self.model_randomizer));
333
334                 i = strstrofs(defaultmodel, ":", 0);
335                 if(i >= 0)
336                 {
337                         defaultskin = stof(substring(defaultmodel, i+1, -1));
338                         defaultmodel = substring(defaultmodel, 0, i);
339                 }
340         }
341
342         if(defaultmodel != "")
343         {
344                 if (defaultmodel != self.model)
345                 {
346                         m1 = self.mins;
347                         m2 = self.maxs;
348                         setplayermodel (self, defaultmodel);
349                         setsize (self, m1, m2);
350                         chmdl = TRUE;
351                 }
352
353                 oldskin = self.skin;
354                 self.skin = defaultskin;
355         } else {
356                 if (self.playermodel != self.model || self.playermodel == "")
357                 {
358                         self.playermodel = CheckPlayerModel(self.playermodel); // this is never "", so no endless loop
359                         m1 = self.mins;
360                         m2 = self.maxs;
361                         setplayermodel (self, self.playermodel);
362                         setsize (self, m1, m2);
363                         chmdl = TRUE;
364                 }
365
366                 oldskin = self.skin;
367                 self.skin = stof(self.playerskin);
368         }
369
370         if(chmdl || oldskin != self.skin) // model or skin has changed
371         {
372                 self.species = player_getspecies(); // update species
373                 UpdatePlayerSounds(); // update skin sounds
374         }
375
376         if(!teamplay)
377                 if(strlen(autocvar_sv_defaultplayercolors))
378                         if(self.clientcolors != stof(autocvar_sv_defaultplayercolors))
379                                 setcolor(self, stof(autocvar_sv_defaultplayercolors));
380 }
381
382 /*
383 =============
384 PutClientInServer
385
386 Called when a client spawns in the server
387 =============
388 */
389
390 void PutClientInServer (void)
391 {
392         if(clienttype(self) == CLIENTTYPE_BOT)
393         {
394                 self.classname = "player";
395                 if(g_ca)
396                         self.caplayer = 1;
397         }
398         else if(clienttype(self) == CLIENTTYPE_REAL)
399         {
400                 msg_entity = self;
401                 WriteByte(MSG_ONE, SVC_SETVIEW);
402                 WriteEntity(MSG_ONE, self);
403         }
404
405         // reset player keys
406         self.itemkeys = 0;
407
408         // player is dead and becomes observer
409         // FIXME fix LMS scoring for new system
410         if(g_lms)
411         {
412                 if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0)
413                         self.classname = "observer";
414         }
415
416         if((g_arena && !self.spawned) || (g_ca && !allowed_to_spawn))
417                 self.classname = "observer";
418
419         if(gameover)
420                 self.classname = "observer";
421
422         if(self.classname == "player" && (!g_ca || (g_ca && allowed_to_spawn))) {
423                 entity spot, oldself;
424                 float j;
425
426                 accuracy_resend(self);
427
428                 if(self.team < 0)
429                         JoinBestTeam(self, FALSE, TRUE);
430
431                 race_PreSpawn();
432
433                 spot = SelectSpawnPoint (FALSE);
434                 if(!spot)
435                 {
436                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_JOIN_NOSPAWNS);
437                         return; // spawn failed
438                 }
439
440                 RemoveGrapplingHook(self); // Wazat's Grappling Hook
441
442                 self.classname = "player";
443                 self.wasplayer = TRUE;
444                 self.iscreature = TRUE;
445                 self.teleportable = TELEPORT_NORMAL;
446                 self.damagedbycontents = TRUE;
447                 self.movetype = MOVETYPE_WALK;
448                 self.solid = SOLID_SLIDEBOX;
449                 self.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID;
450                 if(autocvar_g_playerclip_collisions)
451                         self.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
452                 if(clienttype(self) == CLIENTTYPE_BOT && autocvar_g_botclip_collisions)
453                         self.dphitcontentsmask |= DPCONTENTS_BOTCLIP;
454                 self.frags = FRAGS_PLAYER;
455                 if(INDEPENDENT_PLAYERS)
456                         MAKE_INDEPENDENT_PLAYER(self);
457                 self.flags = FL_CLIENT;
458                 if(autocvar__notarget)
459                         self.flags |= FL_NOTARGET;
460                 self.takedamage = DAMAGE_AIM;
461                 if(g_minstagib)
462                         self.effects = EF_FULLBRIGHT;
463                 else
464                         self.effects = 0;
465                 self.effects |= EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
466                 self.air_finished = time + 12;
467                 self.dmg = 2;
468                 if(autocvar_g_balance_nex_charge)
469                 {
470                         if(autocvar_g_balance_nex_secondary_chargepool)
471                                 self.nex_chargepool_ammo = 1;
472                         self.nex_charge = autocvar_g_balance_nex_charge_start;
473                 }
474
475                 if(inWarmupStage)
476                 {
477                         self.ammo_shells = warmup_start_ammo_shells;
478                         self.ammo_nails = warmup_start_ammo_nails;
479                         self.ammo_rockets = warmup_start_ammo_rockets;
480                         self.ammo_cells = warmup_start_ammo_cells;
481                         self.ammo_fuel = warmup_start_ammo_fuel;
482                         self.health = warmup_start_health;
483                         self.armorvalue = warmup_start_armorvalue;
484                         WEPSET_COPY_EA(self, warmup_start_weapons);
485                 }
486                 else
487                 {
488                         self.ammo_shells = start_ammo_shells;
489                         self.ammo_nails = start_ammo_nails;
490                         self.ammo_rockets = start_ammo_rockets;
491                         self.ammo_cells = start_ammo_cells;
492                         self.ammo_fuel = start_ammo_fuel;
493                         self.health = start_health;
494                         self.armorvalue = start_armorvalue;
495                         WEPSET_COPY_EA(self, start_weapons);
496                 }
497
498                 if(WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS)) // exception for minstagib, as minstanex is a superweapon
499                         self.superweapons_finished = time + autocvar_g_balance_superweapons_time;
500                 else
501                         self.superweapons_finished = 0;
502
503                 if(g_weaponarena_random)
504                 {
505                         if(g_weaponarena_random_with_laser)
506                                 WEPSET_ANDNOT_EW(self, WEP_LASER);
507                         W_RandomWeapons(self, g_weaponarena_random);
508                         if(g_weaponarena_random_with_laser)
509                                 WEPSET_OR_EW(self, WEP_LASER);
510                 }
511
512                 self.items = start_items;
513
514                 self.spawnshieldtime = time + autocvar_g_spawnshieldtime;
515                 self.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
516                 self.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn;
517                 self.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn;
518                 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen_spawn;
519                 //extend the pause of rotting if client was reset at the beginning of the countdown
520                 if(!autocvar_sv_ready_restart_after_countdown && time < game_starttime) { // TODO why is this cvar NOTted?
521                         self.spawnshieldtime += game_starttime - time;
522                         self.pauserotarmor_finished += game_starttime - time;
523                         self.pauserothealth_finished += game_starttime - time;
524                         self.pauseregen_finished += game_starttime - time;
525                 }
526                 self.damageforcescale = 2;
527                 self.death_time = 0;
528                 self.respawn_time = 0;
529                 self.scale = 0;
530                 self.fade_time = 0;
531                 self.pain_frame = 0;
532                 self.pain_finished = 0;
533                 self.strength_finished = 0;
534                 self.invincible_finished = 0;
535                 self.pushltime = 0;
536                 // players have no think function
537                 self.think = func_null;
538                 self.nextthink = 0;
539                 self.hook_time = 0;
540                 self.dmg_team = 0;
541                 self.ballistics_density = autocvar_g_ballistics_density_player;
542
543                 self.metertime = 0;
544
545                 self.deadflag = DEAD_NO;
546
547                 self.angles = spot.angles;
548
549                 self.angles_z = 0; // never spawn tilted even if the spot says to
550                 self.fixangle = TRUE; // turn this way immediately
551                 self.velocity = '0 0 0';
552                 self.avelocity = '0 0 0';
553                 self.punchangle = '0 0 0';
554                 self.punchvector = '0 0 0';
555                 self.oldvelocity = self.velocity;
556                 self.fire_endtime = -1;
557
558                 msg_entity = self;
559                 WRITESPECTATABLE_MSG_ONE({
560                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
561                         WriteByte(MSG_ONE, TE_CSQC_SPAWN);
562                 });
563
564                 self.model = "";
565                 FixPlayermodel();
566                 self.drawonlytoclient = world;
567
568                 self.crouch = FALSE;
569                 self.view_ofs = PL_VIEW_OFS;
570                 setsize (self, PL_MIN, PL_MAX);
571                 self.spawnorigin = spot.origin;
572                 setorigin (self, spot.origin + '0 0 1' * (1 - self.mins_z - 24));
573                 // don't reset back to last position, even if new position is stuck in solid
574                 self.oldorigin = self.origin;
575                 self.prevorigin = self.origin;
576                 self.lastrocket = world; // stop rocket guiding, no revenge from the grave!
577                 self.lastteleporttime = time; // prevent insane speeds due to changing origin
578         self.hud = HUD_NORMAL;
579
580                 if(g_arena)
581                 {
582                         Spawnqueue_Remove(self);
583                         Spawnqueue_Mark(self);
584                 }
585                 else if(g_ca)
586                         self.caplayer = 1;
587
588                 self.event_damage = PlayerDamage;
589
590                 self.bot_attack = TRUE;
591
592                 self.statdraintime = time + 5;
593                 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = 0;
594
595                 if(self.killcount == -666) {
596                         PlayerScore_Clear(self);
597                         self.killcount = 0;
598                 }
599
600                 CL_SpawnWeaponentity();
601                 self.alpha = default_player_alpha;
602                 self.colormod = '1 1 1' * autocvar_g_player_brightness;
603                 self.exteriorweaponentity.alpha = default_weapon_alpha;
604
605                 self.lms_nextcheck = time + autocvar_g_lms_campcheck_interval*2;
606                 self.lms_traveled_distance = 0;
607                 self.speedrunning = FALSE;
608
609                 race_PostSpawn(spot);
610
611                 //stuffcmd(self, "chase_active 0");
612                 //stuffcmd(self, "set viewsize $tmpviewsize \n");
613
614                 if(g_assault) {
615                         if(self.team == assault_attacker_team)
616                                 Send_Notification(NOTIF_TEAM, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
617                         else
618                                 Send_Notification(NOTIF_TEAM, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
619                 }
620
621                 target_voicescript_clear(self);
622
623                 // reset fields the weapons may use
624                 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
625                 {
626                         weapon_action(j, WR_RESETPLAYER);
627
628                         // all weapons must be fully loaded when we spawn
629                         entity e;
630                         e = get_weaponinfo(j);
631                         if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars
632                                 self.(weapon_load[j]) = cvar(strcat("g_balance_", e.netname, "_reload_ammo"));
633                 }
634
635                 oldself = self;
636                 self = spot;
637                         activator = oldself;
638                                 string s;
639                                 s = self.target;
640                                 self.target = string_null;
641                                 SUB_UseTargets();
642                                 self.target = s;
643                         activator = world;
644                 self = oldself;
645
646                 spawn_spot = spot;
647                 MUTATOR_CALLHOOK(PlayerSpawn);
648
649                 if(autocvar_spawn_debug)
650                 {
651                         sprint(self, strcat("spawnpoint origin:  ", vtos(spot.origin), "\n"));
652                         remove(spot);   // usefull for checking if there are spawnpoints, that let drop through the floor
653                 }
654
655                 self.switchweapon = w_getbestweapon(self);
656                 self.cnt = -1; // W_LastWeapon will not complain
657                 self.weapon = 0;
658                 self.weaponname = "";
659                 self.switchingweapon = 0;
660
661                 if(!inWarmupStage)
662                         if(!self.alivetime)
663                                 self.alivetime = time;
664
665                 antilag_clear(self);
666
667                 if (autocvar_g_spawnsound)
668                         soundat(world, self.origin, CH_TRIGGER, "misc/spawn.wav", VOL_BASE, ATTN_NORM);
669         } else if(self.classname == "observer") {
670                 PutObserverInServer ();
671         }
672 }
673
674 .float ebouncefactor, ebouncestop; // electro's values
675 // TODO do we need all these fields, or should we stop autodetecting runtime
676 // changes and just have a console command to update this?
677 float ClientInit_SendEntity(entity to, float sf)
678 {
679         WriteByte(MSG_ENTITY, ENT_CLIENT_INIT);
680         WriteByte(MSG_ENTITY, g_nexball_meter_period * 32);
681         WriteInt24_t(MSG_ENTITY, compressShotOrigin(hook_shotorigin[0]));
682         WriteInt24_t(MSG_ENTITY, compressShotOrigin(hook_shotorigin[1]));
683         WriteInt24_t(MSG_ENTITY, compressShotOrigin(hook_shotorigin[2]));
684         WriteInt24_t(MSG_ENTITY, compressShotOrigin(hook_shotorigin[3]));
685         WriteInt24_t(MSG_ENTITY, compressShotOrigin(electro_shotorigin[0]));
686         WriteInt24_t(MSG_ENTITY, compressShotOrigin(electro_shotorigin[1]));
687         WriteInt24_t(MSG_ENTITY, compressShotOrigin(electro_shotorigin[2]));
688         WriteInt24_t(MSG_ENTITY, compressShotOrigin(electro_shotorigin[3]));
689         WriteInt24_t(MSG_ENTITY, compressShotOrigin(gauntlet_shotorigin[0]));
690         WriteInt24_t(MSG_ENTITY, compressShotOrigin(gauntlet_shotorigin[1]));
691         WriteInt24_t(MSG_ENTITY, compressShotOrigin(gauntlet_shotorigin[2]));
692         WriteInt24_t(MSG_ENTITY, compressShotOrigin(gauntlet_shotorigin[3]));
693         if(sv_foginterval && world.fog != "")
694                 WriteString(MSG_ENTITY, world.fog);
695         else
696                 WriteString(MSG_ENTITY, "");
697         WriteByte(MSG_ENTITY, self.count * 255.0); // g_balance_armor_blockpercent
698         WriteByte(MSG_ENTITY, self.cnt * 255.0); // g_balance_weaponswitchdelay
699         WriteCoord(MSG_ENTITY, self.bouncefactor); // g_balance_grenadelauncher_bouncefactor
700         WriteCoord(MSG_ENTITY, self.bouncestop); // g_balance_grenadelauncher_bouncestop
701         WriteCoord(MSG_ENTITY, self.ebouncefactor); // g_balance_grenadelauncher_bouncefactor
702         WriteCoord(MSG_ENTITY, self.ebouncestop); // g_balance_grenadelauncher_bouncestop
703         WriteByte(MSG_ENTITY, autocvar_g_balance_nex_secondary); // client has to know if it should zoom or not
704         WriteByte(MSG_ENTITY, autocvar_g_balance_rifle_secondary); // client has to know if it should zoom or not
705         WriteByte(MSG_ENTITY, serverflags); // client has to know if it should zoom or not
706         WriteByte(MSG_ENTITY, autocvar_g_balance_minelayer_limit); // minelayer max mines
707         WriteByte(MSG_ENTITY, autocvar_g_balance_hagar_secondary_load_max); // hagar max loadable rockets
708         WriteCoord(MSG_ENTITY, autocvar_g_trueaim_minrange);
709         WriteByte(MSG_ENTITY, autocvar_g_balance_porto_secondary);
710         return TRUE;
711 }
712
713 void ClientInit_CheckUpdate()
714 {
715         self.nextthink = time;
716         if(self.count != autocvar_g_balance_armor_blockpercent)
717         {
718                 self.count = autocvar_g_balance_armor_blockpercent;
719                 self.SendFlags |= 1;
720         }
721         if(self.cnt != autocvar_g_balance_weaponswitchdelay)
722         {
723                 self.cnt = autocvar_g_balance_weaponswitchdelay;
724                 self.SendFlags |= 1;
725         }
726         if(self.bouncefactor != autocvar_g_balance_grenadelauncher_bouncefactor)
727         {
728                 self.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor;
729                 self.SendFlags |= 1;
730         }
731         if(self.bouncestop != autocvar_g_balance_grenadelauncher_bouncestop)
732         {
733                 self.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop;
734                 self.SendFlags |= 1;
735         }
736         if(self.ebouncefactor != autocvar_g_balance_electro_secondary_bouncefactor)
737         {
738                 self.ebouncefactor = autocvar_g_balance_electro_secondary_bouncefactor;
739                 self.SendFlags |= 1;
740         }
741         if(self.ebouncestop != autocvar_g_balance_electro_secondary_bouncestop)
742         {
743                 self.ebouncestop = autocvar_g_balance_electro_secondary_bouncestop;
744                 self.SendFlags |= 1;
745         }
746 }
747
748 void ClientInit_Spawn()
749 {
750         entity o;
751         entity e;
752         e = spawn();
753         e.classname = "clientinit";
754         e.think = ClientInit_CheckUpdate;
755         Net_LinkEntity(e, FALSE, 0, ClientInit_SendEntity);
756
757         o = self;
758         self = e;
759         ClientInit_CheckUpdate();
760         self = o;
761 }
762
763 /*
764 =============
765 SetNewParms
766 =============
767 */
768 void SetNewParms (void)
769 {
770         // initialize parms for a new player
771         parm1 = -(86400 * 366);
772 }
773
774 /*
775 =============
776 SetChangeParms
777 =============
778 */
779 void SetChangeParms (void)
780 {
781         // save parms for level change
782         parm1 = self.parm_idlesince - time;
783 }
784
785 /*
786 =============
787 DecodeLevelParms
788 =============
789 */
790 void DecodeLevelParms (void)
791 {
792         // load parms
793         self.parm_idlesince = parm1;
794         if(self.parm_idlesince == -(86400 * 366))
795                 self.parm_idlesince = time;
796
797         // whatever happens, allow 60 seconds of idling directly after connect for map loading
798         self.parm_idlesince = max(self.parm_idlesince, time - sv_maxidle + 60);
799 }
800
801 /*
802 =============
803 ClientKill
804
805 Called when a client types 'kill' in the console
806 =============
807 */
808
809 .float clientkill_nexttime;
810 void ClientKill_Now_TeamChange()
811 {
812         if(self.killindicator_teamchange == -1)
813         {
814                 JoinBestTeam( self, FALSE, TRUE );
815         }
816         else if(self.killindicator_teamchange == -2)
817         {
818                 if(g_ca)
819                         self.caplayer = 0;
820                 if(blockSpectators)
821                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
822                 PutObserverInServer();
823         }
824         else
825                 SV_ChangeTeam(self.killindicator_teamchange - 1);
826 }
827
828 void ClientKill_Now()
829 {
830         if(self.vehicle)
831         {
832             vehicles_exit(VHEF_RELESE);
833             if(!self.killindicator_teamchange)
834             {
835             self.vehicle_health = -1;
836             Damage(self, self, self, 1 , DEATH_KILL, self.origin, '0 0 0');             
837             }
838         }
839
840         if(self.killindicator && !wasfreed(self.killindicator))
841                 remove(self.killindicator);
842
843         self.killindicator = world;
844
845         if(self.killindicator_teamchange)
846                 ClientKill_Now_TeamChange();
847
848         // in any case:
849         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
850
851         // now I am sure the player IS dead
852 }
853 void KillIndicator_Think()
854 {
855         if (gameover)
856         {
857                 self.owner.killindicator = world;
858                 remove(self);
859                 return;
860         }
861
862         if (self.owner.alpha < 0 && !self.owner.vehicle)
863         {
864                 self.owner.killindicator = world;
865                 remove(self);
866                 return;
867         }
868
869         if(self.cnt <= 0)
870         {
871                 self = self.owner;
872                 ClientKill_Now(); // no oldself needed
873                 return;
874         }
875     else if(g_cts && self.health == 1) // health == 1 means that it's silent
876     {
877         self.nextthink = time + 1;
878         self.cnt -= 1;
879     }
880         else
881         {
882                 if(self.cnt <= 10)
883                         setmodel(self, strcat("models/sprites/", ftos(self.cnt), ".spr32"));
884                 if(clienttype(self.owner) == CLIENTTYPE_REAL)
885                 {
886                         if(self.cnt <= 10)
887                                 { Send_Notification(NOTIF_ONE, self.owner, MSG_ANNCE, Announcer_PickNumber(self.cnt)); }
888                 }
889                 self.nextthink = time + 1;
890                 self.cnt -= 1;
891         }
892 }
893
894 float clientkilltime;
895 void ClientKill_TeamChange (float targetteam) // 0 = don't change, -1 = auto, -2 = spec
896 {
897         float killtime;
898         float starttime;
899         entity e;
900
901         if (gameover)
902                 return;
903
904         killtime = autocvar_g_balance_kill_delay;
905
906         if(g_race_qualifying || g_cts)
907                 killtime = 0;
908
909     if(g_cts && self.killindicator && self.killindicator.health == 1) // self.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill
910     {
911                 remove(self.killindicator);
912                 self.killindicator = world;
913
914         ClientKill_Now(); // allow instant kill in this case
915         return;
916     }
917
918         self.killindicator_teamchange = targetteam;
919
920     if(!self.killindicator)
921         {
922                 if(self.deadflag == DEAD_NO)
923                 {
924                         killtime = max(killtime, self.clientkill_nexttime - time);
925                         self.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
926                 }
927
928                 if(killtime <= 0 || self.classname != "player" || self.deadflag != DEAD_NO)
929                 {
930                         ClientKill_Now();
931                 }
932                 else
933                 {
934                         starttime = max(time, clientkilltime);
935
936                         self.killindicator = spawn();
937                         self.killindicator.owner = self;
938                         self.killindicator.scale = 0.5;
939                         setattachment(self.killindicator, self, "");
940                         setorigin(self.killindicator, '0 0 52');
941                         self.killindicator.think = KillIndicator_Think;
942                         self.killindicator.nextthink = starttime + (self.lip) * 0.05;
943                         clientkilltime = max(clientkilltime, self.killindicator.nextthink + 0.05);
944                         self.killindicator.cnt = ceil(killtime);
945                         self.killindicator.count = bound(0, ceil(killtime), 10);
946                         //sprint(self, strcat("^1You'll be dead in ", ftos(self.killindicator.cnt), " seconds\n"));
947
948                         for(e = world; (e = find(e, classname, "body")) != world; )
949                         {
950                                 if(e.enemy != self)
951                                         continue;
952                                 e.killindicator = spawn();
953                                 e.killindicator.owner = e;
954                                 e.killindicator.scale = 0.5;
955                                 setattachment(e.killindicator, e, "");
956                                 setorigin(e.killindicator, '0 0 52');
957                                 e.killindicator.think = KillIndicator_Think;
958                                 e.killindicator.nextthink = starttime + (e.lip) * 0.05;
959                                 clientkilltime = max(clientkilltime, e.killindicator.nextthink + 0.05);
960                                 e.killindicator.cnt = ceil(killtime);
961                         }
962                         self.lip = 0;
963                 }
964         }
965         if(self.killindicator)
966         {
967                 if(targetteam == 0) // just die
968                 {
969                         self.killindicator.colormod = '0 0 0';
970                         if(clienttype(self) == CLIENTTYPE_REAL)
971                         if(self.killindicator.cnt > 0)
972                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, self.killindicator.cnt);
973                 }
974                 else if(targetteam == -1) // auto
975                 {
976                         self.killindicator.colormod = '0 1 0';
977                         if(clienttype(self) == CLIENTTYPE_REAL)
978                         if(self.killindicator.cnt > 0)
979                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, self.killindicator.cnt);
980                 }
981                 else if(targetteam == -2) // spectate
982                 {
983                         self.killindicator.colormod = '0.5 0.5 0.5';
984                         if(clienttype(self) == CLIENTTYPE_REAL)
985                         if(self.killindicator.cnt > 0)
986                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, self.killindicator.cnt);
987                 }
988                 else
989                 {
990                         self.killindicator.colormod = Team_ColorRGB(targetteam);
991                         if(clienttype(self) == CLIENTTYPE_REAL)
992                         if(self.killindicator.cnt > 0)
993                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, APP_TEAM_NUM_4(targetteam, CENTER_TEAMCHANGE_), self.killindicator.cnt);
994                 }
995         }
996
997 }
998
999 void ClientKill (void)
1000 {
1001         if (gameover)
1002                 return;
1003
1004         if((g_arena || g_ca) && ((champion && champion.classname == "player" && player_count > 1) || player_count == 1)) // don't allow a kill in this case either
1005         {
1006                 // do nothing
1007         }
1008     else if(self.freezetag_frozen)
1009     {
1010         // do nothing
1011     }
1012         else
1013                 ClientKill_TeamChange(0);
1014 }
1015
1016 void CTS_ClientKill (entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed
1017 {
1018     e.killindicator = spawn();
1019     e.killindicator.owner = e;
1020     e.killindicator.think = KillIndicator_Think;
1021     e.killindicator.nextthink = time + (e.lip) * 0.05;
1022     e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
1023     e.killindicator.health = 1; // this is used to indicate that it should be silent
1024     e.lip = 0;
1025 }
1026
1027 void FixClientCvars(entity e)
1028 {
1029         // send prediction settings to the client
1030         stuffcmd(e, "\nin_bindmap 0 0\n");
1031         if(g_race || g_cts)
1032                 stuffcmd(e, "cl_cmd settemp cl_movecliptokeyboard 2\n");
1033         if(autocvar_g_antilag == 3) // client side hitscan
1034                 stuffcmd(e, "cl_cmd settemp cl_prydoncursor_notrace 0\n");
1035         if(autocvar_sv_gentle)
1036                 stuffcmd(e, "cl_cmd settemp cl_gentle 1\n");
1037         /*
1038          * we no longer need to stuff this. Remove this comment block if you feel
1039          * 2.3 and higher (or was it 2.2.3?) don't need these any more
1040         stuffcmd(e, strcat("cl_gravity ", ftos(autocvar_sv_gravity), "\n"));
1041         stuffcmd(e, strcat("cl_movement_accelerate ", ftos(autocvar_sv_accelerate), "\n"));
1042         stuffcmd(e, strcat("cl_movement_friction ", ftos(autocvar_sv_friction), "\n"));
1043         stuffcmd(e, strcat("cl_movement_maxspeed ", ftos(autocvar_sv_maxspeed), "\n"));
1044         stuffcmd(e, strcat("cl_movement_airaccelerate ", ftos(autocvar_sv_airaccelerate), "\n"));
1045         stuffcmd(e, strcat("cl_movement_maxairspeed ", ftos(autocvar_sv_maxairspeed), "\n"));
1046         stuffcmd(e, strcat("cl_movement_stopspeed ", ftos(autocvar_sv_stopspeed), "\n"));
1047         stuffcmd(e, strcat("cl_movement_jumpvelocity ", ftos(autocvar_sv_jumpvelocity), "\n"));
1048         stuffcmd(e, strcat("cl_movement_stepheight ", ftos(autocvar_sv_stepheight), "\n"));
1049         stuffcmd(e, strcat("set cl_movement_friction_on_land ", ftos(autocvar_sv_friction_on_land), "\n"));
1050         stuffcmd(e, strcat("set cl_movement_airaccel_qw ", ftos(autocvar_sv_airaccel_qw), "\n"));
1051         stuffcmd(e, strcat("set cl_movement_airaccel_sideways_friction ", ftos(autocvar_sv_airaccel_sideways_friction), "\n"));
1052         stuffcmd(e, "cl_movement_edgefriction 1\n");
1053          */
1054 }
1055
1056 float PlayerInIDList(entity p, string idlist)
1057 {
1058         float n, i;
1059         string s;
1060
1061         // NOTE: we do NOT check crypto_keyfp here, an unsigned ID is fine too for this
1062         if not(p.crypto_idfp)
1063                 return 0;
1064
1065         // this function allows abbreviated player IDs too!
1066         n = tokenize_console(idlist);
1067         for(i = 0; i < n; ++i)
1068         {
1069                 s = argv(i);
1070                 if(s == substring(p.crypto_idfp, 0, strlen(s)))
1071                         return 1;
1072         }
1073
1074         return 0;
1075 }
1076
1077 /*
1078 =============
1079 ClientConnect
1080
1081 Called when a client connects to the server
1082 =============
1083 */
1084 void DecodeLevelParms (void);
1085 //void dom_player_join_team(entity pl);
1086 void set_dom_state(entity e);
1087 void ClientConnect (void)
1088 {
1089         float t;
1090
1091         if(self.flags & FL_CLIENT)
1092         {
1093                 print("Warning: ClientConnect, but already connected!\n");
1094                 return;
1095         }
1096
1097         if(Ban_MaybeEnforceBanOnce(self))
1098                 return;
1099
1100         DecodeLevelParms();
1101
1102 #ifdef WATERMARK
1103         Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_WATERMARK, WATERMARK);
1104 #endif
1105
1106         self.classname = "player_joining";
1107
1108         self.flags = FL_CLIENT;
1109         self.version_nagtime = time + 10 + random() * 10;
1110
1111         if(player_count<0)
1112         {
1113                 dprint("BUG player count is lower than zero, this cannot happen!\n");
1114                 player_count = 0;
1115         }
1116
1117         PlayerScore_Attach(self);
1118         ClientData_Attach();
1119         accuracy_init(self);
1120
1121         bot_clientconnect();
1122
1123         playerdemo_init();
1124
1125         anticheat_init();
1126
1127         race_PreSpawnObserver();
1128
1129         // identify the right forced team
1130         if(autocvar_g_campaign)
1131         {
1132                 if(clienttype(self) == CLIENTTYPE_REAL) // only players, not bots
1133                 {
1134                         switch(autocvar_g_campaign_forceteam)
1135                         {
1136                                 case 1: self.team_forced = NUM_TEAM_1; break;
1137                                 case 2: self.team_forced = NUM_TEAM_2; break;
1138                                 case 3: self.team_forced = NUM_TEAM_3; break;
1139                                 case 4: self.team_forced = NUM_TEAM_4; break;
1140                                 default: self.team_forced = 0;
1141                         }
1142                 }
1143         }
1144         else if(PlayerInIDList(self, autocvar_g_forced_team_red))
1145                 self.team_forced = NUM_TEAM_1;
1146         else if(PlayerInIDList(self, autocvar_g_forced_team_blue))
1147                 self.team_forced = NUM_TEAM_2;
1148         else if(PlayerInIDList(self, autocvar_g_forced_team_yellow))
1149                 self.team_forced = NUM_TEAM_3;
1150         else if(PlayerInIDList(self, autocvar_g_forced_team_pink))
1151                 self.team_forced = NUM_TEAM_4;
1152         else if(autocvar_g_forced_team_otherwise == "red")
1153                 self.team_forced = NUM_TEAM_1;
1154         else if(autocvar_g_forced_team_otherwise == "blue")
1155                 self.team_forced = NUM_TEAM_2;
1156         else if(autocvar_g_forced_team_otherwise == "yellow")
1157                 self.team_forced = NUM_TEAM_3;
1158         else if(autocvar_g_forced_team_otherwise == "pink")
1159                 self.team_forced = NUM_TEAM_4;
1160         else if(autocvar_g_forced_team_otherwise == "spectate")
1161                 self.team_forced = -1;
1162         else if(autocvar_g_forced_team_otherwise == "spectator")
1163                 self.team_forced = -1;
1164         else
1165                 self.team_forced = 0;
1166
1167         if(!teamplay)
1168                 if(self.team_forced > 0)
1169                         self.team_forced = 0;
1170
1171         JoinBestTeam(self, FALSE, FALSE); // if the team number is valid, keep it
1172
1173         if((autocvar_sv_spectate == 1 && !g_lms) || autocvar_g_campaign || self.team_forced < 0) {
1174                 self.classname = "observer";
1175         } else {
1176                 if(teamplay)
1177                 {
1178                         if(autocvar_g_balance_teams)
1179                         {
1180                                 self.classname = "player";
1181                                 campaign_bots_may_start = 1;
1182                         }
1183                         else
1184                         {
1185                                 self.classname = "observer"; // do it anyway
1186                         }
1187                 }
1188                 else
1189                 {
1190                         self.classname = "player";
1191                         campaign_bots_may_start = 1;
1192                 }
1193         }
1194
1195         self.playerid = (playerid_last = playerid_last + 1);
1196
1197         PlayerStats_AddEvent(sprintf("kills-%d", self.playerid));
1198
1199     if(clienttype(self) == CLIENTTYPE_BOT)
1200         PlayerStats_AddPlayer(self);
1201
1202         if(autocvar_sv_eventlog)
1203                 GameLogEcho(strcat(":join:", ftos(self.playerid), ":", ftos(num_for_edict(self)), ":", ((clienttype(self) == CLIENTTYPE_REAL) ? self.netaddress : "bot"), ":", self.netname));
1204
1205         LogTeamchange(self.playerid, self.team, 1);
1206
1207         self.just_joined = TRUE;  // stop spamming the eventlog with additional lines when the client connects
1208
1209         self.netname_previous = strzone(self.netname);
1210
1211         if((self.classname == STR_PLAYER && teamplay))
1212                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(self, INFO_JOIN_CONNECT_TEAM_), self.netname);
1213         else
1214                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JOIN_CONNECT, self.netname);
1215
1216         stuffcmd(self, strcat(clientstuff, "\n"));
1217         stuffcmd(self, "cl_particles_reloadeffects\n"); // TODO do we still need this?
1218
1219         FixClientCvars(self);
1220
1221         // spawnfunc_waypoint sprites
1222         WaypointSprite_InitClient(self);
1223
1224         // Wazat's grappling hook
1225         SetGrappleHookBindings();
1226
1227         // get version info from player
1228         stuffcmd(self, "cmd clientversion $gameversion\n");
1229
1230         // get other cvars from player
1231         GetCvars(0);
1232
1233         // notify about available teams
1234         if(teamplay)
1235         {
1236                 CheckAllowedTeams(self);
1237                 t = 0; if(c1 >= 0) t |= 1; if(c2 >= 0) t |= 2; if(c3 >= 0) t |= 4; if(c4 >= 0) t |= 8;
1238                 stuffcmd(self, strcat("set _teams_available ", ftos(t), "\n"));
1239         }
1240         else
1241                 stuffcmd(self, "set _teams_available 0\n");
1242
1243         if(g_arena || g_ca)
1244         {
1245                 self.classname = "observer";
1246                 if(g_arena)
1247                         Spawnqueue_Insert(self);
1248         }
1249
1250         attach_entcs();
1251
1252         bot_relinkplayerlist();
1253
1254         self.spectatortime = time;
1255         if(blockSpectators)
1256         {
1257                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
1258         }
1259
1260         self.jointime = time;
1261         self.allowed_timeouts = autocvar_sv_timeout_number;
1262
1263         if(clienttype(self) == CLIENTTYPE_REAL)
1264         {
1265                 if(!autocvar_g_campaign)
1266                 {
1267                         self.motd_actived_time = -1;
1268                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_MOTD, getwelcomemessage());
1269                 }
1270
1271                 if(autocvar_g_bugrigs || WEPSET_EQ_AW(g_weaponarena_weapons, WEP_TUBA))
1272                         stuffcmd(self, "cl_cmd settemp chase_active 1\n");
1273         }
1274
1275         if(g_lms)
1276         {
1277                 if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
1278                 {
1279                         PlayerScore_Add(self, SP_LMS_RANK, 666);
1280                         self.frags = FRAGS_SPECTATOR;
1281                 }
1282         }
1283
1284         if(!sv_foginterval && world.fog != "")
1285                 stuffcmd(self, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
1286
1287         if(autocvar_g_hitplots || strstrofs(strcat(" ", autocvar_g_hitplots_individuals, " "), strcat(" ", self.netaddress, " "), 0) >= 0)
1288         {
1289                 self.hitplotfh = fopen(strcat("hits-", matchid, "-", self.netaddress, "-", ftos(self.playerid), ".plot"), FILE_WRITE);
1290                 fputs(self.hitplotfh, strcat("#name ", self.netname, "\n"));
1291         }
1292         else
1293                 self.hitplotfh = -1;
1294
1295         if(g_race || g_cts) {
1296                 string rr;
1297                 if(g_cts)
1298                         rr = CTS_RECORD;
1299                 else
1300                         rr = RACE_RECORD;
1301
1302                 msg_entity = self;
1303                 race_send_recordtime(MSG_ONE);
1304                 race_send_speedaward(MSG_ONE);
1305
1306                 speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
1307                 speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
1308                 race_send_speedaward_alltimebest(MSG_ONE);
1309
1310                 float i;
1311                 for (i = 1; i <= RANKINGS_CNT; ++i) {
1312                         race_SendRankings(i, 0, 0, MSG_ONE);
1313                 }
1314         }
1315         else if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca) // teamnagger is currently bad for ca
1316                 send_CSQC_teamnagger();
1317
1318         CheatInitClient();
1319
1320         CSQCMODEL_AUTOINIT();
1321
1322         self.model_randomizer = random();
1323     
1324     if(clienttype(self) != CLIENTTYPE_REAL)
1325         return;
1326         
1327     sv_notice_join();
1328     
1329     MUTATOR_CALLHOOK(ClientConnect);
1330 }
1331 /*
1332 =============
1333 ClientDisconnect
1334
1335 Called when a client disconnects from the server
1336 =============
1337 */
1338 .entity chatbubbleentity;
1339 void ReadyCount();
1340 void ClientDisconnect (void)
1341 {
1342         if(self.vehicle)
1343             vehicles_exit(VHEF_RELESE);
1344
1345         if not(self.flags & FL_CLIENT)
1346         {
1347                 print("Warning: ClientDisconnect without ClientConnect\n");
1348                 return;
1349         }
1350
1351         PlayerStats_AddGlobalInfo(self);
1352
1353         CheatShutdownClient();
1354
1355         if(self.hitplotfh >= 0)
1356         {
1357                 fclose(self.hitplotfh);
1358                 self.hitplotfh = -1;
1359         }
1360
1361         anticheat_report();
1362         anticheat_shutdown();
1363
1364         playerdemo_shutdown();
1365
1366         bot_clientdisconnect();
1367
1368         if(self.entcs)
1369                 detach_entcs();
1370
1371         if(autocvar_sv_eventlog)
1372                 GameLogEcho(strcat(":part:", ftos(self.playerid)));
1373                 
1374         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_DISCONNECT, self.netname);
1375
1376         MUTATOR_CALLHOOK(ClientDisconnect);
1377
1378         Portal_ClearAll(self);
1379
1380         RemoveGrapplingHook(self);
1381
1382         // Here, everything has been done that requires this player to be a client.
1383
1384         self.flags &~= FL_CLIENT;
1385
1386         if (self.chatbubbleentity)
1387                 remove (self.chatbubbleentity);
1388
1389         if (self.killindicator)
1390                 remove (self.killindicator);
1391
1392         WaypointSprite_PlayerGone();
1393
1394         bot_relinkplayerlist();
1395
1396         if(g_arena)
1397         {
1398                 Spawnqueue_Unmark(self);
1399                 Spawnqueue_Remove(self);
1400         }
1401
1402         accuracy_free(self);
1403         ClientData_Detach();
1404         PlayerScore_Detach(self);
1405
1406         if(self.netname_previous)
1407                 strunzone(self.netname_previous);
1408         if(self.clientstatus)
1409                 strunzone(self.clientstatus);
1410         if(self.weaponorder_byimpulse)
1411                 strunzone(self.weaponorder_byimpulse);
1412
1413         ClearPlayerSounds();
1414
1415         if(self.personal)
1416                 remove(self.personal);
1417
1418         self.playerid = 0;
1419         ReadyCount();
1420
1421         // free cvars
1422         GetCvars(-1);
1423 }
1424
1425 .float BUTTON_CHAT;
1426 void ChatBubbleThink()
1427 {
1428         self.nextthink = time;
1429         if ((self.owner.alpha < 0) || self.owner.chatbubbleentity != self)
1430         {
1431                 if(self.owner) // but why can that ever be world?
1432                         self.owner.chatbubbleentity = world;
1433                 remove(self);
1434                 return;
1435         }
1436         if ((self.owner.BUTTON_CHAT && !self.owner.deadflag)
1437 #ifdef TETRIS
1438                 || self.owner.tetris_on
1439 #endif
1440         )
1441                 self.model = self.mdl;
1442         else
1443                 self.model = "";
1444 }
1445
1446 void UpdateChatBubble()
1447 {
1448         if (self.alpha < 0)
1449                 return;
1450         // spawn a chatbubble entity if needed
1451         if (!self.chatbubbleentity)
1452         {
1453                 self.chatbubbleentity = spawn();
1454                 self.chatbubbleentity.owner = self;
1455                 self.chatbubbleentity.exteriormodeltoclient = self;
1456                 self.chatbubbleentity.think = ChatBubbleThink;
1457                 self.chatbubbleentity.nextthink = time;
1458                 setmodel(self.chatbubbleentity, "models/misc/chatbubble.spr"); // precision set below
1459                 //setorigin(self.chatbubbleentity, self.origin + '0 0 15' + self.maxs_z * '0 0 1');
1460                 setorigin(self.chatbubbleentity, '0 0 15' + self.maxs_z * '0 0 1');
1461                 setattachment(self.chatbubbleentity, self, "");  // sticks to moving player better, also conserves bandwidth
1462                 self.chatbubbleentity.mdl = self.chatbubbleentity.model;
1463                 self.chatbubbleentity.model = "";
1464                 self.chatbubbleentity.effects = EF_LOWPRECISION;
1465         }
1466 }
1467
1468
1469 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
1470 // added to the model skins
1471 /*void UpdateColorModHack()
1472 {
1473         float c;
1474         c = self.clientcolors & 15;
1475         // LordHavoc: only bothering to support white, green, red, yellow, blue
1476              if (!teamplay) self.colormod = '0 0 0';
1477         else if (c ==  0) self.colormod = '1.00 1.00 1.00';
1478         else if (c ==  3) self.colormod = '0.10 1.73 0.10';
1479         else if (c ==  4) self.colormod = '1.73 0.10 0.10';
1480         else if (c == 12) self.colormod = '1.22 1.22 0.10';
1481         else if (c == 13) self.colormod = '0.10 0.10 1.73';
1482         else self.colormod = '1 1 1';
1483 }*/
1484
1485 void respawn(void)
1486 {
1487         if(self.alpha >= 0 && autocvar_g_respawn_ghosts)
1488         {
1489                 self.solid = SOLID_NOT;
1490                 self.takedamage = DAMAGE_NO;
1491                 self.movetype = MOVETYPE_FLY;
1492                 self.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
1493                 self.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
1494                 self.effects |= CSQCMODEL_EF_RESPAWNGHOST;
1495                 pointparticles(particleeffectnum("respawn_ghost"), self.origin, '0 0 0', 1);
1496                 if(autocvar_g_respawn_ghosts_maxtime)
1497                         SUB_SetFade (self, time + autocvar_g_respawn_ghosts_maxtime / 2 + random () * (autocvar_g_respawn_ghosts_maxtime - autocvar_g_respawn_ghosts_maxtime / 2), 1.5);
1498         }
1499
1500         CopyBody(1);
1501
1502         self.effects |= EF_NODRAW; // prevent another CopyBody
1503         PutClientInServer();
1504 }
1505
1506 void play_countdown(float finished, string samp)
1507 {
1508         if(clienttype(self) == CLIENTTYPE_REAL)
1509                 if(floor(finished - time - frametime) != floor(finished - time))
1510                         if(finished - time < 6)
1511                                 sound (self, CH_INFO, samp, VOL_BASE, ATTN_NORM);
1512 }
1513
1514 void player_powerups (void)
1515 {
1516         // add a way to see what the items were BEFORE all of these checks for the mutator hook
1517         olditems = self.items;
1518
1519         if((self.items & IT_USING_JETPACK) && !self.deadflag)
1520                 self.modelflags |= MF_ROCKET;
1521         else
1522                 self.modelflags &~= MF_ROCKET;
1523
1524         self.effects &~= (EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST);
1525
1526         if(self.alpha < 0 || self.deadflag) // don't apply the flags if the player is gibbed
1527                 return;
1528
1529         Fire_ApplyDamage(self);
1530         Fire_ApplyEffect(self);
1531
1532         if (g_minstagib)
1533         {
1534                 self.effects |= EF_FULLBRIGHT;
1535
1536                 if (self.items & IT_STRENGTH)
1537                 {
1538                         play_countdown(self.strength_finished, "misc/poweroff.wav");
1539                         if (time > self.strength_finished)
1540                         {
1541                                 self.alpha = default_player_alpha;
1542                                 self.exteriorweaponentity.alpha = default_weapon_alpha;
1543                                 self.items &~= IT_STRENGTH;
1544                                 //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERDOWN_INVISIBILITY, self.netname);
1545                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_INVISIBILITY);
1546                         }
1547                 }
1548                 else
1549                 {
1550                         if (time < self.strength_finished)
1551                         {
1552                                 self.alpha = g_minstagib_invis_alpha;
1553                                 self.exteriorweaponentity.alpha = g_minstagib_invis_alpha;
1554                                 self.items |= IT_STRENGTH;
1555                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_INVISIBILITY, self.netname);
1556                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_INVISIBILITY);
1557                         }
1558                 }
1559
1560                 if (self.items & IT_INVINCIBLE)
1561                 {
1562                         play_countdown(self.invincible_finished, "misc/poweroff.wav");
1563                         if (time > self.invincible_finished)
1564                         {
1565                                 self.items = self.items - (self.items & IT_INVINCIBLE);
1566                                 //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERDOWN_SPEED, self.netname);
1567                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_SPEED);
1568                         }
1569                 }
1570                 else
1571                 {
1572                         if (time < self.invincible_finished)
1573                         {
1574                                 self.items = self.items | IT_INVINCIBLE;
1575                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_SPEED, self.netname);
1576                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_SPEED);
1577                         }
1578                 }
1579         }
1580         else // if we're not in minstagib, continue. I added this else to replace the "return" which was here that broke the callhook for this function -- This code is nasty.
1581         {
1582                 if (self.items & IT_STRENGTH)
1583                 {
1584                         play_countdown(self.strength_finished, "misc/poweroff.wav");
1585                         self.effects = self.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
1586                         if (time > self.strength_finished)
1587                         {
1588                                 self.items = self.items - (self.items & IT_STRENGTH);
1589                                 //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERDOWN_STRENGTH, self.netname);
1590                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_STRENGTH);
1591                         }
1592                 }
1593                 else
1594                 {
1595                         if (time < self.strength_finished)
1596                         {
1597                                 self.items = self.items | IT_STRENGTH;
1598                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_STRENGTH, self.netname);
1599                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_STRENGTH);
1600                         }
1601                 }
1602                 if (self.items & IT_INVINCIBLE)
1603                 {
1604                         play_countdown(self.invincible_finished, "misc/poweroff.wav");
1605                         self.effects = self.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
1606                         if (time > self.invincible_finished)
1607                         {
1608                                 self.items = self.items - (self.items & IT_INVINCIBLE);
1609                                 //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERDOWN_SHIELD, self.netname);
1610                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERDOWN_SHIELD);
1611                         }
1612                 }
1613                 else
1614                 {
1615                         if (time < self.invincible_finished)
1616                         {
1617                                 self.items = self.items | IT_INVINCIBLE;
1618                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_POWERUP_SHIELD, self.netname);
1619                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_POWERUP_SHIELD);
1620                         }
1621                 }
1622                 if (self.items & IT_SUPERWEAPON)
1623                 {
1624                         if (!WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
1625                         {
1626                                 self.superweapons_finished = 0;
1627                                 self.items = self.items - (self.items & IT_SUPERWEAPON);
1628                                 //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_SUPERWEAPON_LOST, self.netname);
1629                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
1630                         }
1631                         else if (self.items & IT_UNLIMITED_SUPERWEAPONS)
1632                         {
1633                                 // don't let them run out
1634                         }
1635                         else
1636                         {
1637                                 play_countdown(self.superweapons_finished, "misc/poweroff.wav");
1638                                 if (time > self.superweapons_finished)
1639                                 {
1640                                         self.items = self.items - (self.items & IT_SUPERWEAPON);
1641                                         WEPSET_ANDNOT_EA(self, WEPBIT_SUPERWEAPONS);
1642                                         //Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_SUPERWEAPON_BROKEN, self.netname);
1643                                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN);
1644                                 }
1645                         }
1646                 }
1647                 else if(WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
1648                 {
1649                         if (time < self.superweapons_finished || (self.items & IT_UNLIMITED_SUPERWEAPONS))
1650                         {
1651                                 self.items = self.items | IT_SUPERWEAPON;
1652                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_SUPERWEAPON_PICKUP, self.netname);
1653                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
1654                         }
1655                         else
1656                         {
1657                                 self.superweapons_finished = 0;
1658                                 WEPSET_ANDNOT_EA(self, WEPBIT_SUPERWEAPONS);
1659                         }
1660                 }
1661                 else
1662                 {
1663                         self.superweapons_finished = 0;
1664                 }
1665         }
1666         
1667         if(autocvar_g_nodepthtestplayers)
1668                 self.effects = self.effects | EF_NODEPTHTEST;
1669
1670         if(autocvar_g_fullbrightplayers)
1671                 self.effects = self.effects | EF_FULLBRIGHT;
1672
1673         // midair gamemode: damage only while in the air
1674         // if in midair mode, being on ground grants temporary invulnerability
1675         // (this is so that multishot weapon don't clear the ground flag on the
1676         // first damage in the frame, leaving the player vulnerable to the
1677         // remaining hits in the same frame)
1678         if (self.flags & FL_ONGROUND)
1679         if (g_midair)
1680                 self.spawnshieldtime = max(self.spawnshieldtime, time + autocvar_g_midair_shieldtime);
1681
1682         if (time >= game_starttime)
1683         if (time < self.spawnshieldtime)
1684                 self.effects = self.effects | (EF_ADDITIVE | EF_FULLBRIGHT);
1685
1686         MUTATOR_CALLHOOK(PlayerPowerups);
1687 }
1688
1689 float CalcRegen(float current, float stable, float regenfactor, float regenframetime)
1690 {
1691         if(current > stable)
1692                 return current;
1693         else if(current > stable - 0.25) // when close enough, "snap"
1694                 return stable;
1695         else
1696                 return min(stable, current + (stable - current) * regenfactor * regenframetime);
1697 }
1698
1699 float CalcRot(float current, float stable, float rotfactor, float rotframetime)
1700 {
1701         if(current < stable)
1702                 return current;
1703         else if(current < stable + 0.25) // when close enough, "snap"
1704                 return stable;
1705         else
1706                 return max(stable, current + (stable - current) * rotfactor * rotframetime);
1707 }
1708
1709 float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit)
1710 {
1711         if(current > rotstable)
1712         {
1713                 if(rotframetime > 0)
1714                 {
1715                         current = CalcRot(current, rotstable, rotfactor, rotframetime);
1716                         current = max(rotstable, current - rotlinear * rotframetime);
1717                 }
1718         }
1719         else if(current < regenstable)
1720         {
1721                 if(regenframetime > 0)
1722                 {
1723                         current = CalcRegen(current, regenstable, regenfactor, regenframetime);
1724                         current = min(regenstable, current + regenlinear * regenframetime);
1725                 }
1726         }
1727
1728         if(current > limit)
1729                 current = limit;
1730
1731         return current;
1732 }
1733
1734 void player_regen (void)
1735 {
1736         float minh, mina, minf, maxh, maxa, maxf, limith, limita, limitf, max_mod, regen_mod, rot_mod, limit_mod;
1737         maxh = autocvar_g_balance_health_rotstable;
1738         maxa = autocvar_g_balance_armor_rotstable;
1739         maxf = autocvar_g_balance_fuel_rotstable;
1740         minh = autocvar_g_balance_health_regenstable;
1741         mina = autocvar_g_balance_armor_regenstable;
1742         minf = autocvar_g_balance_fuel_regenstable;
1743         limith = autocvar_g_balance_health_limit;
1744         limita = autocvar_g_balance_armor_limit;
1745         limitf = autocvar_g_balance_fuel_limit;
1746
1747         max_mod = regen_mod = rot_mod = limit_mod = 1;
1748
1749         maxh = maxh * max_mod;
1750         //maxa = maxa * max_mod;
1751         //maxf = maxf * max_mod;
1752         minh = minh * max_mod;
1753         //mina = mina * max_mod;
1754         //minf = minf * max_mod;
1755         limith = limith * limit_mod;
1756         limita = limita * limit_mod;
1757         //limitf = limitf * limit_mod;
1758
1759         if(g_lms && g_ca)
1760                 rot_mod = 0;
1761
1762         if (!g_minstagib && !g_ca && (!g_lms || autocvar_g_lms_regenerate))
1763         {
1764                 self.armorvalue = CalcRotRegen(self.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > self.pauserotarmor_finished), limita);
1765                 self.health = CalcRotRegen(self.health, minh, autocvar_g_balance_health_regen, autocvar_g_balance_health_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxh, autocvar_g_balance_health_rot, autocvar_g_balance_health_rotlinear, rot_mod * frametime * (time > self.pauserothealth_finished), limith);
1766
1767                 // if player rotted to death...  die!
1768                 if(self.health < 1)
1769                         self.event_damage(self, self, 1, DEATH_ROT, self.origin, '0 0 0');
1770         }
1771
1772         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
1773                 self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished) * (self.items & IT_FUEL_REGEN != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rot_mod * frametime * (time > self.pauserotfuel_finished), limitf);
1774 }
1775
1776 float zoomstate_set;
1777 void SetZoomState(float z)
1778 {
1779         if(z != self.zoomstate)
1780         {
1781                 self.zoomstate = z;
1782                 ClientData_Touch(self);
1783         }
1784         zoomstate_set = 1;
1785 }
1786
1787 void GetPressedKeys(void) {
1788         MUTATOR_CALLHOOK(GetPressedKeys);
1789         if (self.movement_x > 0) // get if movement keys are pressed
1790         {       // forward key pressed
1791                 self.pressedkeys |= KEY_FORWARD;
1792                 self.pressedkeys &~= KEY_BACKWARD;
1793         }
1794         else if (self.movement_x < 0)
1795         {       // backward key pressed
1796                 self.pressedkeys |= KEY_BACKWARD;
1797                 self.pressedkeys &~= KEY_FORWARD;
1798         }
1799         else
1800         {       // no x input
1801                 self.pressedkeys &~= KEY_FORWARD;
1802                 self.pressedkeys &~= KEY_BACKWARD;
1803         }
1804
1805         if (self.movement_y > 0)
1806         {       // right key pressed
1807                 self.pressedkeys |= KEY_RIGHT;
1808                 self.pressedkeys &~= KEY_LEFT;
1809         }
1810         else if (self.movement_y < 0)
1811         {       // left key pressed
1812                 self.pressedkeys |= KEY_LEFT;
1813                 self.pressedkeys &~= KEY_RIGHT;
1814         }
1815         else
1816         {       // no y input
1817                 self.pressedkeys &~= KEY_RIGHT;
1818                 self.pressedkeys &~= KEY_LEFT;
1819         }
1820
1821         if (self.BUTTON_JUMP) // get if jump and crouch keys are pressed
1822                 self.pressedkeys |= KEY_JUMP;
1823         else
1824                 self.pressedkeys &~= KEY_JUMP;
1825         if (self.BUTTON_CROUCH)
1826                 self.pressedkeys |= KEY_CROUCH;
1827         else
1828                 self.pressedkeys &~= KEY_CROUCH;
1829
1830         if (self.BUTTON_ATCK)
1831                 self.pressedkeys |= KEY_ATCK;
1832         else
1833                 self.pressedkeys &~= KEY_ATCK;
1834         if (self.BUTTON_ATCK2)
1835                 self.pressedkeys |= KEY_ATCK2;
1836         else
1837                 self.pressedkeys &~= KEY_ATCK2;
1838 }
1839
1840 /*
1841 ======================
1842 spectate mode routines
1843 ======================
1844 */
1845
1846 void SpectateCopy(entity spectatee) {
1847         other = spectatee;
1848         MUTATOR_CALLHOOK(SpectateCopy);
1849         self.armortype = spectatee.armortype;
1850         self.armorvalue = spectatee.armorvalue;
1851         self.ammo_cells = spectatee.ammo_cells;
1852         self.ammo_shells = spectatee.ammo_shells;
1853         self.ammo_nails = spectatee.ammo_nails;
1854         self.ammo_rockets = spectatee.ammo_rockets;
1855         self.ammo_fuel = spectatee.ammo_fuel;
1856         self.clip_load = spectatee.clip_load;
1857         self.clip_size = spectatee.clip_size;
1858         self.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
1859         self.health = spectatee.health;
1860         self.impulse = 0;
1861         self.items = spectatee.items;
1862         self.last_pickup = spectatee.last_pickup;
1863         self.hit_time = spectatee.hit_time;
1864         self.metertime = spectatee.metertime;
1865         self.strength_finished = spectatee.strength_finished;
1866         self.invincible_finished = spectatee.invincible_finished;
1867         self.pressedkeys = spectatee.pressedkeys;
1868         WEPSET_COPY_EE(self, spectatee);
1869         self.switchweapon = spectatee.switchweapon;
1870         self.switchingweapon = spectatee.switchingweapon;
1871         self.weapon = spectatee.weapon;
1872         self.nex_charge = spectatee.nex_charge;
1873         self.nex_chargepool_ammo = spectatee.nex_chargepool_ammo;
1874         self.hagar_load = spectatee.hagar_load;
1875         self.minelayer_mines = spectatee.minelayer_mines;
1876         self.punchangle = spectatee.punchangle;
1877         self.view_ofs = spectatee.view_ofs;
1878         self.velocity = spectatee.velocity;
1879         self.dmg_take = spectatee.dmg_take;
1880         self.dmg_save = spectatee.dmg_save;
1881         self.dmg_inflictor = spectatee.dmg_inflictor;
1882         self.v_angle = spectatee.v_angle;
1883         self.angles = spectatee.v_angle;
1884         self.stat_respawn_time = spectatee.stat_respawn_time;
1885         if(!self.BUTTON_USE)
1886                 self.fixangle = TRUE;
1887         setorigin(self, spectatee.origin);
1888         setsize(self, spectatee.mins, spectatee.maxs);
1889         SetZoomState(spectatee.zoomstate);
1890     
1891     anticheat_spectatecopy(spectatee);
1892         self.hud = spectatee.hud;
1893         if(spectatee.vehicle)
1894     {
1895         self.fixangle = FALSE;
1896         //self.velocity = spectatee.vehicle.velocity;
1897         self.vehicle_health = spectatee.vehicle_health;
1898         self.vehicle_shield = spectatee.vehicle_shield;
1899         self.vehicle_energy = spectatee.vehicle_energy;
1900         self.vehicle_ammo1 = spectatee.vehicle_ammo1;
1901         self.vehicle_ammo2 = spectatee.vehicle_ammo2;
1902         self.vehicle_reload1 = spectatee.vehicle_reload1;
1903         self.vehicle_reload2 = spectatee.vehicle_reload2;
1904
1905         msg_entity = self;
1906         
1907         WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1908             WriteAngle(MSG_ONE,  spectatee.v_angle_x);
1909             WriteAngle(MSG_ONE,  spectatee.v_angle_y);
1910             WriteAngle(MSG_ONE,  spectatee.v_angle_z);
1911
1912         //WriteByte (MSG_ONE, SVC_SETVIEW);
1913         //    WriteEntity(MSG_ONE, self);            
1914         //makevectors(spectatee.v_angle);
1915         //setorigin(self, spectatee.origin - v_forward * 400 + v_up * 300);*/    
1916     }
1917 }
1918
1919 float SpectateUpdate() {
1920         if(!self.enemy)
1921             return 0;           
1922
1923         if (self == self.enemy)
1924                 return 0;
1925
1926         if(self.enemy.classname != "player")
1927                 return 0;
1928
1929         SpectateCopy(self.enemy);
1930
1931         return 1;
1932 }
1933
1934
1935 // Returns next available player to spectate if g_ca_spectate_enemies == 0
1936 entity CA_SpectateNext(entity start) {
1937         if (start.team == self.team) {
1938                 return start;
1939         }
1940         
1941         other = start;
1942         // continue from current player
1943         while(other && other.team != self.team) {
1944                 other = find(other, classname, "player");
1945         }
1946         
1947         if (!other) {
1948                 // restart from begining
1949                 other = find(other, classname, "player");
1950                 while(other && other.team != self.team) {
1951                         other = find(other, classname, "player");
1952                 }
1953         }
1954         
1955         return other;
1956 }
1957
1958 float SpectateNext(entity _prefer) {
1959         
1960         if(_prefer)
1961                 other = _prefer;        
1962         else
1963                 other = find(self.enemy, classname, "player");
1964         
1965         if (g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer) {
1966                 // CA and ca players when spectating enemies is forbidden
1967                 other = CA_SpectateNext(other);
1968         } else {
1969                 // other modes and ca spectators or spectating enemies is allowed
1970                 if (!other)
1971                         other = find(other, classname, "player");
1972         }
1973         
1974         if (other)
1975                 self.enemy = other;
1976
1977         if(self.enemy.classname == "player") {
1978             /*if(self.enemy.vehicle)
1979             {      
1980             
1981             msg_entity = self;
1982             WriteByte(MSG_ONE, SVC_SETVIEW);
1983             WriteEntity(MSG_ONE, self.enemy);
1984             //stuffcmd(self, "set viewsize $tmpviewsize \n");
1985             
1986             self.movetype = MOVETYPE_NONE;
1987             accuracy_resend(self);
1988             }
1989             else 
1990             {*/         
1991             msg_entity = self;
1992             WriteByte(MSG_ONE, SVC_SETVIEW);
1993             WriteEntity(MSG_ONE, self.enemy);
1994             //stuffcmd(self, "set viewsize $tmpviewsize \n");
1995             self.movetype = MOVETYPE_NONE;
1996             accuracy_resend(self);
1997
1998             if(!SpectateUpdate())
1999                 PutObserverInServer();
2000         //}
2001         return 1;
2002         } else {
2003                 return 0;
2004         }
2005 }
2006
2007 /*
2008 =============
2009 ShowRespawnCountdown()
2010
2011 Update a respawn countdown display.
2012 =============
2013 */
2014 void ShowRespawnCountdown()
2015 {
2016         float number;
2017         if(self.deadflag == DEAD_NO) // just respawned?
2018                 return;
2019         else
2020         {
2021                 number = ceil(self.respawn_time - time);
2022                 if(number <= 0)
2023                         return;
2024                 if(number <= self.respawn_countdown)
2025                 {
2026                         self.respawn_countdown = number - 1;
2027                         if(ceil(self.respawn_time - (time + 0.5)) == number) // only say it if it is the same number even in 0.5s; to prevent overlapping sounds
2028                                 Send_Notification(NOTIF_ONE, self, MSG_ANNCE, Announcer_PickNumber(number)); 
2029                 }
2030         }
2031 }
2032
2033 void LeaveSpectatorMode()
2034 {
2035         if(nJoinAllowed(self))
2036         {
2037                 if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (self.wasplayer && autocvar_g_changeteam_banned) || self.team_forced > 0)
2038                 {
2039                         self.classname = "player";
2040
2041                         if(autocvar_g_campaign || autocvar_g_balance_teams)
2042                                 { JoinBestTeam(self, FALSE, TRUE); }
2043
2044                         if(autocvar_g_campaign)
2045                                 { campaign_bots_may_start = 1; }
2046
2047                         Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_PREVENT_JOIN);
2048
2049                         PutClientInServer();
2050
2051                         if(IS_PLAYER(self)) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JOIN_PLAY, self.netname); }
2052                 }
2053                 else if not(g_ca && self.caplayer) { stuffcmd(self, "menu_showteamselect\n"); }
2054         }
2055         else
2056         {
2057                 // Player may not join because g_maxplayers is set
2058                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_JOIN_PREVENT);
2059         }
2060 }
2061
2062 /**
2063  * Determines whether the player is allowed to join. This depends on cvar
2064  * g_maxplayers, if it isn't used this function always return TRUE, otherwise
2065  * it checks whether the number of currently playing players exceeds g_maxplayers.
2066  * @return int number of free slots for players, 0 if none
2067  */
2068 float nJoinAllowed(entity ignore) {
2069         if(!ignore)
2070         // this is called that way when checking if anyone may be able to join (to build qcstatus)
2071         // so report 0 free slots if restricted
2072         {
2073                 if(autocvar_g_forced_team_otherwise == "spectate")
2074                         return 0;
2075                 if(autocvar_g_forced_team_otherwise == "spectator")
2076                         return 0;
2077         }
2078
2079         if(self.team_forced < 0)
2080                 return 0; // forced spectators can never join
2081
2082         // TODO simplify this
2083         entity e;
2084         float totalClients = 0;
2085         FOR_EACH_CLIENT(e)
2086                 if(e != ignore)
2087                         totalClients += 1;
2088
2089         if (!autocvar_g_maxplayers)
2090                 return maxclients - totalClients;
2091
2092         float currentlyPlaying = 0;
2093         FOR_EACH_REALPLAYER(e)
2094                 currentlyPlaying += 1;
2095
2096         if(currentlyPlaying < autocvar_g_maxplayers)
2097                 return min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying);
2098
2099         return 0;
2100 }
2101
2102 /**
2103  * Checks whether the client is an observer or spectator, if so, he will get kicked after
2104  * g_maxplayers_spectator_blocktime seconds
2105  */
2106 void checkSpectatorBlock() {
2107         if(self.classname == "spectator" || self.classname == "observer") {
2108                 if( time > (self.spectatortime + autocvar_g_maxplayers_spectator_blocktime) ) {
2109                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
2110                         dropclient(self);
2111                 }
2112         }
2113 }
2114
2115 void PrintWelcomeMessage()
2116 {
2117         if(self.motd_actived_time == 0)
2118         {
2119                 if (autocvar_g_campaign) {
2120                         if ((self.classname == "player" && self.BUTTON_INFO) || (self.classname != "player")) {
2121                                 self.motd_actived_time = time;
2122                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_MOTD, campaign_message);
2123                         }
2124                 } else {
2125                         if (self.BUTTON_INFO) {
2126                                 self.motd_actived_time = time;
2127                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_MOTD, getwelcomemessage());
2128                         }
2129                 }
2130         }
2131         else if(self.motd_actived_time > 0) // showing MOTD or campaign message
2132         {
2133                 if (autocvar_g_campaign) {
2134                         if (self.BUTTON_INFO)
2135                                 self.motd_actived_time = time;
2136                         else if ((time - self.motd_actived_time > 2) && self.classname == "player") { // hide it some seconds after BUTTON_INFO has been released
2137                                 self.motd_actived_time = 0;
2138                                 Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_MOTD);
2139                         }
2140                 } else {
2141                         if (self.BUTTON_INFO)
2142                                 self.motd_actived_time = time;
2143                         else if (time - self.motd_actived_time > 2) { // hide it some seconds after BUTTON_INFO has been released
2144                                 self.motd_actived_time = 0;
2145                                 Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_MOTD);
2146                         }
2147                 }
2148         }
2149         else //if(self.motd_actived_time < 0) // just connected, motd is active
2150         {
2151                 if(self.BUTTON_INFO) // BUTTON_INFO hides initial MOTD
2152                         self.motd_actived_time = -2; // wait until BUTTON_INFO gets released
2153                 else if(self.motd_actived_time == -2 || IS_PLAYER(self) || time - self.jointime > autocvar_welcome_message_time)
2154                 {
2155                         // instanctly hide MOTD
2156                         self.motd_actived_time = 0;
2157                         Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_MOTD);
2158                 }
2159         }
2160 }
2161
2162 void ObserverThink()
2163 {
2164         float prefered_movetype;
2165         if (self.flags & FL_JUMPRELEASED) {
2166                 if (self.BUTTON_JUMP && !self.version_mismatch) {
2167                         self.flags &~= FL_JUMPRELEASED;
2168                         self.flags |= FL_SPAWNING;
2169                 } else if(self.BUTTON_ATCK && !self.version_mismatch) {
2170                         self.flags &~= FL_JUMPRELEASED;
2171                         if(SpectateNext(world) == 1) {
2172                                 self.classname = "spectator";
2173                         }
2174                 } else {
2175                         prefered_movetype = ((!self.BUTTON_USE ? self.cvar_cl_clippedspectating : !self.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
2176                         if (self.movetype != prefered_movetype)
2177                                 self.movetype = prefered_movetype;
2178                 }
2179         } else {
2180                 if (!(self.BUTTON_ATCK || self.BUTTON_JUMP)) {
2181                         self.flags |= FL_JUMPRELEASED;
2182                         if(self.flags & FL_SPAWNING)
2183                         {
2184                                 self.flags &~= FL_SPAWNING;
2185                                 LeaveSpectatorMode();
2186                                 return;
2187                         }
2188                 }
2189         }
2190 }
2191
2192 void SpectatorThink()
2193 {
2194         if (self.flags & FL_JUMPRELEASED) {
2195                 if (self.BUTTON_JUMP && !self.version_mismatch) {
2196                         self.flags &~= FL_JUMPRELEASED;
2197                         self.flags |= FL_SPAWNING;
2198                 } else if(self.BUTTON_ATCK) {
2199                         self.flags &~= FL_JUMPRELEASED;
2200                         if(SpectateNext(world) == 1) {
2201                                 self.classname = "spectator";
2202                         } else {
2203                                 self.classname = "observer";
2204                                 PutClientInServer();
2205                         }
2206                 } else if (self.BUTTON_ATCK2) {
2207                         self.flags &~= FL_JUMPRELEASED;
2208                         self.classname = "observer";
2209                         PutClientInServer();
2210                 } else {
2211                         if(!SpectateUpdate())
2212                                 PutObserverInServer();
2213                 }
2214         } else {
2215                 if (!(self.BUTTON_ATCK || self.BUTTON_ATCK2)) {
2216                         self.flags |= FL_JUMPRELEASED;
2217                         if(self.flags & FL_SPAWNING)
2218                         {
2219                                 self.flags &~= FL_SPAWNING;
2220                                 LeaveSpectatorMode();
2221                                 return;
2222                         }
2223                 }
2224                 if(!SpectateUpdate())
2225                         PutObserverInServer();
2226         }
2227
2228         self.flags |= FL_CLIENT | FL_NOTARGET;
2229 }
2230
2231 void PlayerUseKey()
2232 {
2233         if(self.classname != "player")
2234                 return;
2235
2236         if(self.vehicle)
2237         {
2238         vehicles_exit(VHEF_NORMAL);
2239         return;
2240         }
2241         
2242         // a use key was pressed; call handlers
2243         MUTATOR_CALLHOOK(PlayerUseKey);
2244 }
2245
2246 /*
2247 =============
2248 PlayerPreThink
2249
2250 Called every frame for each client before the physics are run
2251 =============
2252 */
2253 .float usekeypressed;
2254 void() nexball_setstatus;
2255 .float items_added;
2256 void PlayerPreThink (void)
2257 {
2258         WarpZone_PlayerPhysics_FixVAngle();
2259
2260         self.stat_game_starttime = game_starttime;
2261         self.stat_allow_oldnexbeam = autocvar_g_allow_oldnexbeam;
2262         self.stat_leadlimit = autocvar_leadlimit;
2263
2264         if(g_arena || (g_ca && !allowed_to_spawn))
2265                 self.stat_respawn_time = 0;
2266         else
2267                 self.stat_respawn_time = self.respawn_time;
2268
2269         if(frametime)
2270         {
2271                 // physics frames: update anticheat stuff
2272                 anticheat_prethink();
2273         }
2274
2275         if(blockSpectators && frametime)
2276                 // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
2277                 checkSpectatorBlock();
2278
2279         zoomstate_set = 0;
2280
2281         if(self.netname_previous != self.netname)
2282         {
2283                 if(autocvar_sv_eventlog)
2284                         GameLogEcho(strcat(":name:", ftos(self.playerid), ":", self.netname));
2285                 if(self.netname_previous)
2286                         strunzone(self.netname_previous);
2287                 self.netname_previous = strzone(self.netname);
2288         }
2289
2290         // version nagging
2291         if(self.version_nagtime)
2292                 if(self.cvar_g_xonoticversion)
2293                         if(time > self.version_nagtime)
2294                         {
2295                                 // don't notify git users
2296                                 if(strstr(self.cvar_g_xonoticversion, "git", 0) < 0 && strstr(self.cvar_g_xonoticversion, "autobuild", 0) < 0)
2297                                 {
2298                                         if(strstr(autocvar_g_xonoticversion, "git", 0) >= 0 || strstr(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
2299                                         {
2300                                                 // notify release users if connecting to git
2301                                                 dprint("^1NOTE^7 to ", self.netname, "^7 - the server is running ^3Xonotic ", autocvar_g_xonoticversion, " (beta)^7, you have ^3Xonotic ", self.cvar_g_xonoticversion, "^1\n");
2302                                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, self.cvar_g_xonoticversion);
2303                                         }
2304                                         else
2305                                         {
2306                                                 float r;
2307                                                 r = vercmp(self.cvar_g_xonoticversion, autocvar_g_xonoticversion);
2308                                                 if(r < 0)
2309                                                 {
2310                                                         // give users new version
2311                                                         dprint("^1NOTE^7 to ", self.netname, "^7 - ^3Xonotic ", autocvar_g_xonoticversion, "^7 is out, and you still have ^3Xonotic ", self.cvar_g_xonoticversion, "^1 - get the update from ^4http://www.xonotic.org/^1!\n");
2312                                                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, self.cvar_g_xonoticversion);
2313                                                 }
2314                                                 else if(r > 0)
2315                                                 {
2316                                                         // notify users about old server version
2317                                                         print("^1NOTE^7 to ", self.netname, "^7 - the server is running ^3Xonotic ", autocvar_g_xonoticversion, "^7, you have ^3Xonotic ", self.cvar_g_xonoticversion, "^1\n");
2318                                                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, self.cvar_g_xonoticversion);
2319                                                 }
2320                                         }
2321                                 }
2322                                 self.version_nagtime = 0;
2323                         }
2324
2325         // GOD MODE info
2326         if(!(self.flags & FL_GODMODE)) if(self.max_armorvalue)
2327         {
2328                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_GODMODE_OFF, self.max_armorvalue);
2329                 self.max_armorvalue = 0;
2330         }
2331
2332 #ifdef TETRIS
2333         if (TetrisPreFrame())
2334                 return;
2335 #endif
2336
2337         MUTATOR_CALLHOOK(PlayerPreThink);
2338
2339         if(!self.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
2340         {
2341                 if(self.BUTTON_USE && !self.usekeypressed)
2342                         PlayerUseKey();
2343                 self.usekeypressed = self.BUTTON_USE;
2344         }
2345
2346         if(clienttype(self) == CLIENTTYPE_REAL)
2347                 PrintWelcomeMessage();
2348
2349         if(self.classname == "player") {
2350
2351                 CheckRules_Player();
2352
2353                 if (intermission_running)
2354                 {
2355                         IntermissionThink ();   // otherwise a button could be missed between
2356                         return;                                 // the think tics
2357                 }
2358
2359                 //don't allow the player to turn around while game is paused!
2360                 if(timeout_status == TIMEOUT_ACTIVE) {
2361                         // FIXME turn this into CSQC stuff
2362                         self.v_angle = self.lastV_angle;
2363                         self.angles = self.lastV_angle;
2364                         self.fixangle = TRUE;
2365                 }
2366
2367                 if(frametime)
2368                 {
2369                         if(self.weapon == WEP_NEX && autocvar_g_balance_nex_charge)
2370                         {
2371                                 self.weaponentity_glowmod_x = autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_red_half * min(1, self.nex_charge / autocvar_g_balance_nex_charge_animlimit);
2372                                 self.weaponentity_glowmod_y = autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_green_half * min(1, self.nex_charge / autocvar_g_balance_nex_charge_animlimit);
2373                                 self.weaponentity_glowmod_z = autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_blue_half * min(1, self.nex_charge / autocvar_g_balance_nex_charge_animlimit);
2374
2375                                 if(self.nex_charge > autocvar_g_balance_nex_charge_animlimit)
2376                                 {
2377                                         self.weaponentity_glowmod_x = self.weaponentity_glowmod_x + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_red_full * (self.nex_charge - autocvar_g_balance_nex_charge_animlimit) / (1 - autocvar_g_balance_nex_charge_animlimit);
2378                                         self.weaponentity_glowmod_y = self.weaponentity_glowmod_y + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_green_full * (self.nex_charge - autocvar_g_balance_nex_charge_animlimit) / (1 - autocvar_g_balance_nex_charge_animlimit);
2379                                         self.weaponentity_glowmod_z = self.weaponentity_glowmod_z + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_blue_full * (self.nex_charge - autocvar_g_balance_nex_charge_animlimit) / (1 - autocvar_g_balance_nex_charge_animlimit);
2380                                 }
2381                         }
2382                         else
2383                                 self.weaponentity_glowmod = colormapPaletteColor(self.clientcolors & 0x0F, TRUE) * 2;
2384
2385                         player_powerups();
2386                 }
2387
2388                 if (g_minstagib)
2389                         minstagib_ammocheck();
2390
2391                 if (self.deadflag != DEAD_NO)
2392                 {
2393                         float button_pressed, force_respawn;
2394                         if(self.personal && g_race_qualifying)
2395                         {
2396                                 if(time > self.respawn_time)
2397                                 {
2398                                         self.respawn_time = time + 1; // only retry once a second
2399                                         respawn();
2400                                         self.impulse = 141;
2401                                 }
2402                         }
2403                         else
2404                         {
2405                                 if(frametime)
2406                                         player_anim();
2407                                 button_pressed = (self.BUTTON_ATCK || self.BUTTON_JUMP || self.BUTTON_ATCK2 || self.BUTTON_HOOK || self.BUTTON_USE);
2408                                 force_respawn = (g_lms || g_ca || g_cts || autocvar_g_forced_respawn);
2409                                 if (self.deadflag == DEAD_DYING)
2410                                 {
2411                                         if(force_respawn)
2412                                                 self.deadflag = DEAD_RESPAWNING;
2413                                         else if(!button_pressed)
2414                                                 self.deadflag = DEAD_DEAD;
2415                                 }
2416                                 else if (self.deadflag == DEAD_DEAD)
2417                                 {
2418                                         if(button_pressed)
2419                                                 self.deadflag = DEAD_RESPAWNABLE;
2420                                 }
2421                                 else if (self.deadflag == DEAD_RESPAWNABLE)
2422                                 {
2423                                         if(!button_pressed)
2424                                                 self.deadflag = DEAD_RESPAWNING;
2425                                 }
2426                                 else if (self.deadflag == DEAD_RESPAWNING)
2427                                 {
2428                                         if(time > self.respawn_time)
2429                                         {
2430                                                 self.respawn_time = time + 1; // only retry once a second
2431                                                 respawn();
2432                                         }
2433                                 }
2434                                 ShowRespawnCountdown();
2435                         }
2436
2437                         // if respawning, invert stat_respawn_time to indicate this, the client translates it
2438                         if(self.deadflag == DEAD_RESPAWNING && self.stat_respawn_time > 0)
2439                                 self.stat_respawn_time *= -1;
2440
2441                         return;
2442                 }
2443
2444                 if(g_lms && !self.deadflag && autocvar_g_lms_campcheck_interval)
2445                 {
2446                         vector dist;
2447
2448                         // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement)
2449                         dist = self.prevorigin - self.origin;
2450                         dist_z = 0;
2451                         self.lms_traveled_distance += fabs(vlen(dist));
2452
2453                         if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime))
2454                         {
2455                                 self.lms_nextcheck = time + autocvar_g_lms_campcheck_interval*2;
2456                                 self.lms_traveled_distance = 0;
2457                         }
2458
2459                         if(time > self.lms_nextcheck)
2460                         {
2461                                 //sprint(self, "distance: ", ftos(self.lms_traveled_distance), "\n");
2462                                 if(self.lms_traveled_distance < autocvar_g_lms_campcheck_distance)
2463                                 {
2464                                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_LMS_CAMPCHECK);
2465                                         // FIXME KadaverJack: gibbing player here causes playermodel to bounce around, instead of eye.md3
2466                                         // I wasn't able to find out WHY that happens, so I put a workaround in place that shall prevent players from being gibbed :(
2467                                         if(self.vehicle)
2468                                                 Damage(self.vehicle, self, self, autocvar_g_lms_campcheck_damage * 2, DEATH_CAMP, self.vehicle.origin, '0 0 0');
2469                                         else
2470                                                 Damage(self, self, self, bound(0, autocvar_g_lms_campcheck_damage, self.health + self.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP, self.origin, '0 0 0');
2471                                 }
2472                                 self.lms_nextcheck = time + autocvar_g_lms_campcheck_interval;
2473                                 self.lms_traveled_distance = 0;
2474                         }
2475                 }
2476
2477                 self.prevorigin = self.origin;
2478
2479                 float do_crouch = self.BUTTON_CROUCH;
2480                 if(self.hook.state)
2481                         do_crouch = 0;
2482                 if(self.health <= g_bloodloss)
2483                         do_crouch = 1;
2484                 if(self.vehicle)
2485                         do_crouch = 0;
2486                 if(self.freezetag_frozen)
2487                         do_crouch = 0;
2488                 if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
2489                         do_crouch = 0;
2490
2491                 if (do_crouch)
2492                 {
2493                         if (!self.crouch)
2494                         {
2495                                 self.crouch = TRUE;
2496                                 self.view_ofs = PL_CROUCH_VIEW_OFS;
2497                                 setsize (self, PL_CROUCH_MIN, PL_CROUCH_MAX);
2498                                 // setanim(self, self.anim_duck, FALSE, TRUE, TRUE); // this anim is BROKEN anyway
2499                         }
2500                 }
2501                 else
2502                 {
2503                         if (self.crouch)
2504                         {
2505                                 tracebox(self.origin, PL_MIN, PL_MAX, self.origin, FALSE, self);
2506                                 if (!trace_startsolid)
2507                                 {
2508                                         self.crouch = FALSE;
2509                                         self.view_ofs = PL_VIEW_OFS;
2510                                         setsize (self, PL_MIN, PL_MAX);
2511                                 }
2512                         }
2513                 }
2514
2515                 if(self.health <= g_bloodloss && self.deadflag == DEAD_NO)
2516                 {
2517                         if(self.bloodloss_timer < time)
2518                         {
2519                                 self.event_damage(self, self, 1, DEATH_ROT, self.origin, '0 0 0');
2520                                 self.bloodloss_timer = time + 0.5 + random() * 0.5;
2521                         }
2522                 }
2523
2524                 FixPlayermodel();
2525
2526                 GrapplingHookFrame();
2527
2528                 // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
2529                 //if(frametime)
2530                 {
2531                         self.items &~= self.items_added;
2532
2533                         W_WeaponFrame();
2534
2535                         self.items_added = 0;
2536                         if(self.items & IT_JETPACK)
2537                                 if(self.items & IT_FUEL_REGEN || self.ammo_fuel >= 0.01)
2538                                         self.items_added |= IT_FUEL;
2539
2540                         self.items |= self.items_added;
2541                 }
2542
2543                 player_regen();
2544
2545                 // rot nex charge to the charge limit
2546                 if(autocvar_g_balance_nex_charge_rot_rate && self.nex_charge > autocvar_g_balance_nex_charge_limit && self.nex_charge_rottime < time)
2547                         self.nex_charge = bound(autocvar_g_balance_nex_charge_limit, self.nex_charge - autocvar_g_balance_nex_charge_rot_rate * frametime / W_TICSPERFRAME, 1);
2548
2549                 if(frametime)
2550                         player_anim();
2551
2552                 if(g_nexball)
2553                         nexball_setstatus();
2554                 
2555                 // secret status
2556                 secrets_setstatus();
2557                 
2558                 self.dmg_team = max(0, self.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
2559
2560                 //self.angles_y=self.v_angle_y + 90;   // temp
2561         } else if(gameover) {
2562                 if (intermission_running)
2563                         IntermissionThink ();   // otherwise a button could be missed between
2564                 return;
2565         } else if(self.classname == "observer") {
2566                 ObserverThink();
2567         } else if(self.classname == "spectator") {
2568                 SpectatorThink();
2569         }
2570
2571         if(!zoomstate_set)
2572                 SetZoomState(self.BUTTON_ZOOM || self.BUTTON_ZOOMSCRIPT || (self.BUTTON_ATCK2 && self.weapon == WEP_NEX) || (self.BUTTON_ATCK2 && self.weapon == WEP_RIFLE && autocvar_g_balance_rifle_secondary == 0));
2573
2574         float oldspectatee_status;
2575         oldspectatee_status = self.spectatee_status;
2576         if(self.classname == "spectator")
2577                 self.spectatee_status = num_for_edict(self.enemy);
2578         else if(self.classname == "observer")
2579                 self.spectatee_status = num_for_edict(self);
2580         else
2581                 self.spectatee_status = 0;
2582         if(self.spectatee_status != oldspectatee_status)
2583         {
2584                 ClientData_Touch(self);
2585                 if(g_race || g_cts)
2586                         race_InitSpectator();
2587         }
2588
2589         if(self.teamkill_soundtime)
2590         if(time > self.teamkill_soundtime)
2591         {
2592                 self.teamkill_soundtime = 0;
2593
2594                 entity oldpusher, oldself;
2595
2596                 oldself = self; self = self.teamkill_soundsource;
2597                 oldpusher = self.pusher; self.pusher = oldself;
2598
2599                 PlayerSound(playersound_teamshoot, CH_VOICE, VOICETYPE_LASTATTACKER_ONLY);
2600
2601                 self.pusher = oldpusher;
2602                 self = oldself;
2603         }
2604
2605         if(self.taunt_soundtime)
2606         if(time > self.taunt_soundtime)
2607         {
2608                 self.taunt_soundtime = 0;
2609                 PlayerSound(playersound_taunt, CH_VOICE, VOICETYPE_AUTOTAUNT);
2610         }
2611
2612         target_voicescript_next(self);
2613
2614         // if a player goes unarmed after holding a loaded weapon, empty his clip size and remove the crosshair ammo ring
2615         if(!self.weapon)
2616                 self.clip_load = self.clip_size = 0;
2617 }
2618
2619 float isInvisibleString(string s)
2620 {
2621         float i, n, c;
2622         s = strdecolorize(s);
2623         for((i = 0), (n = strlen(s)); i < n; ++i)
2624         {
2625                 c = str2chr(s, i);
2626                 switch(c)
2627                 {
2628                         case 0:
2629                         case 32: // space
2630                                 break;
2631                         case 192: // charmap space
2632                                 if (!autocvar_utf8_enable)
2633                                         break;
2634                                 return FALSE;
2635                         case 160: // space in unicode fonts
2636                         case 0xE000 + 192: // utf8 charmap space
2637                                 if (autocvar_utf8_enable)
2638                                         break;
2639                         default:
2640                                 return FALSE;
2641                 }
2642         }
2643         return TRUE;
2644 }
2645
2646 /*
2647 =============
2648 PlayerPostThink
2649
2650 Called every frame for each client after the physics are run
2651 =============
2652 */
2653 .float idlekick_lasttimeleft;
2654 void PlayerPostThink (void)
2655 {
2656         // Savage: Check for nameless players
2657         if (isInvisibleString(self.netname)) {
2658                 self.netname = "Player";
2659                 stuffcmd(self, strcat("name ", self.netname, substring(ftos(random()), 2, -1), "\n"));
2660         }
2661
2662         if(sv_maxidle && frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
2663         {
2664                 if (time - self.parm_idlesince < 1) // instead of (time == self.parm_idlesince) to support sv_maxidle <= 10
2665                 {
2666                         if(self.idlekick_lasttimeleft) { self.idlekick_lasttimeleft = 0; }
2667                 }
2668                 else
2669                 {
2670                         float timeleft;
2671                         timeleft = ceil(sv_maxidle - (time - self.parm_idlesince));
2672                         if(timeleft == min(10, sv_maxidle - 1)) // - 1 to support sv_maxidle <= 10
2673                         {
2674                                 if(!self.idlekick_lasttimeleft)
2675                                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
2676                         }
2677                         if(timeleft <= 0)
2678                         {
2679                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_KICK_IDLING, self.netname);
2680                                 dropclient(self);
2681                                 return;
2682                         }
2683                         else if(timeleft <= 10)
2684                         {
2685                                 if(timeleft != self.idlekick_lasttimeleft)
2686                                         Send_Notification(NOTIF_ONE, self, MSG_ANNCE, Announcer_PickNumber(timeleft));
2687                                 self.idlekick_lasttimeleft = timeleft;
2688                         }
2689                 }
2690         }
2691
2692 #ifdef TETRIS
2693         if(self.impulse == 100)
2694                 ImpulseCommands();
2695         if (!TetrisPostFrame())
2696         {
2697 #endif
2698
2699         CheatFrame();
2700
2701         //CheckPlayerJump();
2702
2703         if(self.classname == "player") {
2704                 CheckRules_Player();
2705                 UpdateChatBubble();
2706                 if (self.impulse)
2707                         ImpulseCommands();
2708                 if (intermission_running)
2709                         return;         // intermission or finale
2710                 GetPressedKeys();
2711         }
2712         
2713 #ifdef TETRIS
2714         }
2715 #endif
2716
2717         /*
2718         float i;
2719         for(i = 0; i < 1000; ++i)
2720         {
2721                 vector end;
2722                 end = self.origin + '0 0 1024' + 512 * randomvec();
2723                 tracebox(self.origin, self.mins, self.maxs, end, MOVE_NORMAL, self);
2724                 if(trace_fraction < 1)
2725                 if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
2726                 {
2727                         print("I HIT SOLID: ", vtos(self.origin), " -> ", vtos(end), "\n");
2728                         break;
2729                 }
2730         }
2731         */
2732
2733         //pointparticles(particleeffectnum("machinegun_impact"), self.origin + self.view_ofs + '0 0 7', '0 0 0', 1);
2734
2735         if(self.waypointsprite_attachedforcarrier)
2736                 WaypointSprite_UpdateHealth(self.waypointsprite_attachedforcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
2737
2738         playerdemo_write();
2739
2740         if((g_cts || g_race) && self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1)
2741         {
2742                 if not(self.stored_netname)
2743                         self.stored_netname = strzone(uid2name(self.crypto_idfp));
2744                 if(self.stored_netname != self.netname)
2745                 {
2746                         db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname);
2747                         strunzone(self.stored_netname);
2748                         self.stored_netname = strzone(self.netname);
2749                 }
2750         }
2751
2752         /*
2753         if(g_race)
2754                 dprint(sprintf("%f %.6f\n", time, race_GetFractionalLapCount(self)));
2755         */
2756
2757         CSQCMODEL_AUTOUPDATE();
2758 }