]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/client.qc
Port idlekick_lasttimeleft to ClientState
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / client.qc
index 58e512a38255aa825cd207ba8b013df1583d492f..58bf67762fe2f852de1b335aaab134dff74170b7 100644 (file)
@@ -231,7 +231,7 @@ void PutObserverInServer(entity this)
         this.angles_z = 0;
         this.fixangle = true;
         // offset it so that the spectator spawns higher off the ground, looks better this way
-        setorigin(this, spot.origin + STAT(PL_VIEW_OFS, NULL));
+        setorigin(this, spot.origin + STAT(PL_VIEW_OFS, this));
         this.prevorigin = this.origin;
         if (IS_REAL_CLIENT(this))
         {
@@ -248,7 +248,7 @@ void PutObserverInServer(entity this)
                FixPlayermodel(this);
         }
         setmodel(this, MDL_Null);
-        setsize(this, STAT(PL_CROUCH_MIN, NULL), STAT(PL_CROUCH_MAX, NULL));
+        setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this));
         this.view_ofs = '0 0 0';
     }
 
@@ -279,8 +279,8 @@ void PutObserverInServer(entity this)
        if (this.killcount != FRAGS_SPECTATOR)
        {
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname);
-               if(!intermission_running)
-               if(autocvar_g_chat_nospectators == 1 || (!(warmup_stage || gameover) && autocvar_g_chat_nospectators == 2))
+               if(!game_stopped)
+               if(autocvar_g_chat_nospectators == 1 || (!warmup_stage && autocvar_g_chat_nospectators == 2))
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
 
                if(this.just_joined == false) {
@@ -333,19 +333,17 @@ void PutObserverInServer(entity this)
        this.nextthink = 0;
        this.deadflag = DEAD_NO;
        this.crouch = false;
+       this.revive_progress = 0;
        this.revival_time = 0;
 
        this.items = 0;
        this.weapons = '0 0 0';
+       this.dual_weapons = '0 0 0';
        this.drawonlytoclient = this;
 
        this.weaponmodel = "";
        for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
        {
-               if(!this.weaponentities[slot])
-                       continue; // first load
-               this.weaponentities[slot].hook_time = 0;
-               this.weaponentities[slot].weaponname = "";
                this.weaponentities[slot] = NULL;
        }
        this.exteriorweaponentity = NULL;
@@ -357,6 +355,15 @@ void PutObserverInServer(entity this)
        this.oldvelocity = this.velocity;
        this.fire_endtime = -1;
        this.event_damage = func_null;
+
+       for(int slot = 0; slot < MAX_AXH; ++slot)
+       {
+               entity axh = this.(AuxiliaryXhair[slot]);
+               this.(AuxiliaryXhair[slot]) = NULL;
+
+               if(axh.owner == this && axh != NULL && !wasfreed(axh))
+                       delete(axh);
+       }
 }
 
 int player_getspecies(entity this)
@@ -377,11 +384,12 @@ void FixPlayermodel(entity player)
        {
                if(teamplay)
                {
-                       string s = Static_Team_ColorName_Lower(player.team);
-                       if (s != "neutral")
+                       switch(player.team)
                        {
-                               defaultmodel = cvar_string(strcat("sv_defaultplayermodel_", s));
-                               defaultskin = cvar(strcat("sv_defaultplayerskin_", s));
+                               case NUM_TEAM_1: defaultmodel = autocvar_sv_defaultplayermodel_red; defaultskin = autocvar_sv_defaultplayerskin_red; break;
+                               case NUM_TEAM_2: defaultmodel = autocvar_sv_defaultplayermodel_blue; defaultskin = autocvar_sv_defaultplayerskin_blue; break;
+                               case NUM_TEAM_3: defaultmodel = autocvar_sv_defaultplayermodel_yellow; defaultskin = autocvar_sv_defaultplayerskin_yellow; break;
+                               case NUM_TEAM_4: defaultmodel = autocvar_sv_defaultplayermodel_pink; defaultskin = autocvar_sv_defaultplayerskin_pink; break;
                        }
                }
 
@@ -412,9 +420,13 @@ void FixPlayermodel(entity player)
        {
                if(teamplay)
                {
-                       string s = Static_Team_ColorName_Lower(player.team);
-                       if (s != "neutral")
-                               defaultskin = cvar(strcat("sv_defaultplayerskin_", s));
+                       switch(player.team)
+                       {
+                               case NUM_TEAM_1: defaultskin = autocvar_sv_defaultplayerskin_red; break;
+                               case NUM_TEAM_2: defaultskin = autocvar_sv_defaultplayerskin_blue; break;
+                               case NUM_TEAM_3: defaultskin = autocvar_sv_defaultplayerskin_yellow; break;
+                               case NUM_TEAM_4: defaultskin = autocvar_sv_defaultplayerskin_pink; break;
+                       }
                }
 
                if(!defaultskin)
@@ -476,243 +488,257 @@ void FixPlayermodel(entity player)
                                setcolor(player, stof(autocvar_sv_defaultplayercolors));
 }
 
-
-/** Called when a client spawns in the server */
-void PutClientInServer(entity this)
+void PutPlayerInServer(entity this)
 {
-       if (IS_BOT_CLIENT(this)) {
-               TRANSMUTE(Player, this);
-       } else if (IS_REAL_CLIENT(this)) {
-               msg_entity = this;
-               WriteByte(MSG_ONE, SVC_SETVIEW);
-               WriteEntity(MSG_ONE, this);
-       }
-       if (gameover) {
-               TRANSMUTE(Observer, this);
-       }
+       if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
 
-       SetSpectatee(this, NULL);
+       PlayerState_attach(this);
+       accuracy_resend(this);
 
-       // reset player keys
-       this.itemkeys = 0;
+       if (this.team < 0)
+               JoinBestTeam(this, false, true);
 
-       MUTATOR_CALLHOOK(PutClientInServer, this);
+       entity spot = SelectSpawnPoint(this, false);
+       if (!spot) {
+               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_NOSPAWNS);
+               return; // spawn failed
+       }
 
-       if (IS_OBSERVER(this)) {
-               PutObserverInServer(this);
-       } else if (IS_PLAYER(this)) {
-               if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
+       TRANSMUTE(Player, this);
 
-               PlayerState_attach(this);
-               accuracy_resend(this);
+       this.wasplayer = true;
+       this.iscreature = true;
+       this.teleportable = TELEPORT_NORMAL;
+       if(!this.damagedbycontents)
+               IL_PUSH(g_damagedbycontents, this);
+       this.damagedbycontents = true;
+       set_movetype(this, MOVETYPE_WALK);
+       this.solid = SOLID_SLIDEBOX;
+       this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID;
+       if (autocvar_g_playerclip_collisions)
+               this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
+       if (IS_BOT_CLIENT(this) && autocvar_g_botclip_collisions)
+               this.dphitcontentsmask |= DPCONTENTS_BOTCLIP;
+       this.frags = FRAGS_PLAYER;
+       if (INDEPENDENT_PLAYERS) MAKE_INDEPENDENT_PLAYER(this);
+       this.flags = FL_CLIENT | FL_PICKUPITEMS;
+       if (autocvar__notarget)
+               this.flags |= FL_NOTARGET;
+       this.takedamage = DAMAGE_AIM;
+       this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
+       this.dmg = 2; // WTF
+
+       if (warmup_stage) {
+               this.ammo_shells = warmup_start_ammo_shells;
+               this.ammo_nails = warmup_start_ammo_nails;
+               this.ammo_rockets = warmup_start_ammo_rockets;
+               this.ammo_cells = warmup_start_ammo_cells;
+               this.ammo_plasma = warmup_start_ammo_plasma;
+               this.ammo_fuel = warmup_start_ammo_fuel;
+               this.health = warmup_start_health;
+               this.armorvalue = warmup_start_armorvalue;
+               this.weapons = WARMUP_START_WEAPONS;
+       } else {
+               this.ammo_shells = start_ammo_shells;
+               this.ammo_nails = start_ammo_nails;
+               this.ammo_rockets = start_ammo_rockets;
+               this.ammo_cells = start_ammo_cells;
+               this.ammo_plasma = start_ammo_plasma;
+               this.ammo_fuel = start_ammo_fuel;
+               this.health = start_health;
+               this.armorvalue = start_armorvalue;
+               this.weapons = start_weapons;
+       }
+       SetSpectatee_status(this, 0);
+
+       this.dual_weapons = '0 0 0';
+
+       this.superweapons_finished = (this.weapons & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0;
+
+       this.items = start_items;
+
+       this.spawnshieldtime = time + autocvar_g_spawnshieldtime;
+       this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
+       this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn;
+       this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn;
+       this.pauseregen_finished = time + autocvar_g_balance_pause_health_regen_spawn;
+       // extend the pause of rotting if client was reset at the beginning of the countdown
+       if (!autocvar_sv_ready_restart_after_countdown && time < game_starttime) { // TODO why is this cvar NOTted?
+               float f = game_starttime - time;
+               this.spawnshieldtime += f;
+               this.pauserotarmor_finished += f;
+               this.pauserothealth_finished += f;
+               this.pauseregen_finished += f;
+       }
+       this.damageforcescale = 2;
+       this.death_time = 0;
+       this.respawn_flags = 0;
+       this.respawn_time = 0;
+       this.stat_respawn_time = 0;
+       this.scale = autocvar_sv_player_scale;
+       this.fade_time = 0;
+       this.pain_frame = 0;
+       this.pain_finished = 0;
+       this.pushltime = 0;
+       setthink(this, func_null); // players have no think function
+       this.nextthink = 0;
+       this.dmg_team = 0;
+       this.ballistics_density = autocvar_g_ballistics_density_player;
 
-               if (this.team < 0)
-                       JoinBestTeam(this, false, true);
+       this.deadflag = DEAD_NO;
 
-               entity spot = SelectSpawnPoint(this, false);
-               if (!spot) {
-                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_NOSPAWNS);
-                       return; // spawn failed
-               }
+       this.angles = spot.angles;
+       this.angles_z = 0; // never spawn tilted even if the spot says to
+       if (IS_BOT_CLIENT(this))
+               this.v_angle = this.angles;
+       this.fixangle = true; // turn this way immediately
+       this.oldvelocity = this.velocity = '0 0 0';
+       this.avelocity = '0 0 0';
+       this.punchangle = '0 0 0';
+       this.punchvector = '0 0 0';
 
-               TRANSMUTE(Player, this);
+       this.strength_finished = 0;
+       this.invincible_finished = 0;
+       this.fire_endtime = -1;
+       this.revive_progress = 0;
+       this.revival_time = 0;
+       this.air_finished = time + 12;
 
-               this.wasplayer = true;
-               this.iscreature = true;
-               this.teleportable = TELEPORT_NORMAL;
-               if(!this.damagedbycontents)
-                       IL_PUSH(g_damagedbycontents, this);
-               this.damagedbycontents = true;
-               set_movetype(this, MOVETYPE_WALK);
-               this.solid = SOLID_SLIDEBOX;
-               this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID;
-               if (autocvar_g_playerclip_collisions)
-                       this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
-               if (IS_BOT_CLIENT(this) && autocvar_g_botclip_collisions)
-                       this.dphitcontentsmask |= DPCONTENTS_BOTCLIP;
-               this.frags = FRAGS_PLAYER;
-               if (INDEPENDENT_PLAYERS) MAKE_INDEPENDENT_PLAYER(this);
-               this.flags = FL_CLIENT | FL_PICKUPITEMS;
-               if (autocvar__notarget)
-                       this.flags |= FL_NOTARGET;
-               this.takedamage = DAMAGE_AIM;
-               this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
-               this.dmg = 2; // WTF
-
-               if (warmup_stage) {
-                       this.ammo_shells = warmup_start_ammo_shells;
-                       this.ammo_nails = warmup_start_ammo_nails;
-                       this.ammo_rockets = warmup_start_ammo_rockets;
-                       this.ammo_cells = warmup_start_ammo_cells;
-                       this.ammo_plasma = warmup_start_ammo_plasma;
-                       this.ammo_fuel = warmup_start_ammo_fuel;
-                       this.health = warmup_start_health;
-                       this.armorvalue = warmup_start_armorvalue;
-                       this.weapons = WARMUP_START_WEAPONS;
-               } else {
-                       this.ammo_shells = start_ammo_shells;
-                       this.ammo_nails = start_ammo_nails;
-                       this.ammo_rockets = start_ammo_rockets;
-                       this.ammo_cells = start_ammo_cells;
-                       this.ammo_plasma = start_ammo_plasma;
-                       this.ammo_fuel = start_ammo_fuel;
-                       this.health = start_health;
-                       this.armorvalue = start_armorvalue;
-                       this.weapons = start_weapons;
-               }
-               SetSpectatee_status(this, 0);
-
-               this.superweapons_finished = (this.weapons & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0;
-
-               this.items = start_items;
-
-               this.spawnshieldtime = time + autocvar_g_spawnshieldtime;
-               this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
-               this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn;
-               this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn;
-               this.pauseregen_finished = time + autocvar_g_balance_pause_health_regen_spawn;
-               // extend the pause of rotting if client was reset at the beginning of the countdown
-               if (!autocvar_sv_ready_restart_after_countdown && time < game_starttime) { // TODO why is this cvar NOTted?
-                       float f = game_starttime - time;
-                       this.spawnshieldtime += f;
-                       this.pauserotarmor_finished += f;
-                       this.pauserothealth_finished += f;
-                       this.pauseregen_finished += f;
-               }
-               this.damageforcescale = 2;
-               this.death_time = 0;
-               this.respawn_flags = 0;
-               this.respawn_time = 0;
-               this.stat_respawn_time = 0;
-               this.scale = autocvar_sv_player_scale;
-               this.fade_time = 0;
-               this.pain_frame = 0;
-               this.pain_finished = 0;
-               this.pushltime = 0;
-               setthink(this, func_null); // players have no think function
-               this.nextthink = 0;
-               this.dmg_team = 0;
-               this.ballistics_density = autocvar_g_ballistics_density_player;
-
-               this.deadflag = DEAD_NO;
-
-               this.angles = spot.angles;
-               this.angles_z = 0; // never spawn tilted even if the spot says to
-               if (IS_BOT_CLIENT(this))
-                       this.v_angle = this.angles;
-               this.fixangle = true; // turn this way immediately
-               this.oldvelocity = this.velocity = '0 0 0';
-               this.avelocity = '0 0 0';
-               this.punchangle = '0 0 0';
-               this.punchvector = '0 0 0';
-
-               this.strength_finished = 0;
-               this.invincible_finished = 0;
-               this.fire_endtime = -1;
-               this.revival_time = 0;
-               this.air_finished = time + 12;
-
-               entity spawnevent = new_pure(spawnevent);
-               spawnevent.owner = this;
-               Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send);
-
-               // Cut off any still running player sounds.
-               stopsound(this, CH_PLAYER_SINGLE);
-
-               this.model = "";
-               FixPlayermodel(this);
-               this.drawonlytoclient = NULL;
+       entity spawnevent = new_pure(spawnevent);
+       spawnevent.owner = this;
+       Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send);
 
-               this.viewloc = NULL;
+       // Cut off any still running player sounds.
+       stopsound(this, CH_PLAYER_SINGLE);
 
-               this.crouch = false;
-               this.view_ofs = STAT(PL_VIEW_OFS, this);
-               setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this));
-               this.spawnorigin = spot.origin;
-               setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24));
-               // don't reset back to last position, even if new position is stuck in solid
-               this.oldorigin = this.origin;
-               this.prevorigin = this.origin;
-               this.lastteleporttime = time; // prevent insane speeds due to changing origin
-               if(this.conveyor)
-                       IL_REMOVE(g_conveyed, this);
-               this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player
-               this.hud = HUD_NORMAL;
+       this.model = "";
+       FixPlayermodel(this);
+       this.drawonlytoclient = NULL;
 
-               this.event_damage = PlayerDamage;
+       this.viewloc = NULL;
 
-               if(!this.bot_attack)
-                       IL_PUSH(g_bot_targets, this);
-               this.bot_attack = true;
-               this.monster_attack = true;
+       this.crouch = false;
+       this.view_ofs = STAT(PL_VIEW_OFS, this);
+       setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this));
+       this.spawnorigin = spot.origin;
+       setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24));
+       // don't reset back to last position, even if new position is stuck in solid
+       this.oldorigin = this.origin;
+       this.prevorigin = this.origin;
+       this.lastteleporttime = time; // prevent insane speeds due to changing origin
+       if(this.conveyor)
+               IL_REMOVE(g_conveyed, this);
+       this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player
+       this.hud = HUD_NORMAL;
+
+       this.event_damage = PlayerDamage;
+
+       if(!this.bot_attack)
+               IL_PUSH(g_bot_targets, this);
+       this.bot_attack = true;
+       if(!this.monster_attack)
+               IL_PUSH(g_monster_targets, this);
+       this.monster_attack = true;
+       navigation_dynamicgoal_init(this, false);
+
+       PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
+
+       if (this.killcount == FRAGS_SPECTATOR) {
+               PlayerScore_Clear(this);
+               this.killcount = 0;
+       }
 
-               PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
+       for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+       {
+               .entity weaponentity = weaponentities[slot];
+               entity oldwep = this.(weaponentity);
+               CL_SpawnWeaponentity(this, weaponentity);
+               if(oldwep && oldwep.owner == this)
+                       this.(weaponentity).m_gunalign = oldwep.m_gunalign;
+       }
+       this.alpha = default_player_alpha;
+       this.colormod = '1 1 1' * autocvar_g_player_brightness;
+       this.exteriorweaponentity.alpha = default_weapon_alpha;
 
-               if (this.killcount == FRAGS_SPECTATOR) {
-                       PlayerScore_Clear(this);
-                       this.killcount = 0;
-               }
+       this.speedrunning = false;
 
-               for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-               {
-                       .entity weaponentity = weaponentities[slot];
-                       CL_SpawnWeaponentity(this, weaponentity);
+       target_voicescript_clear(this);
+
+       // reset fields the weapons may use
+       FOREACH(Weapons, true, LAMBDA(
+               it.wr_resetplayer(it, this);
+                       // reload all reloadable weapons
+               if (it.spawnflags & WEP_FLAG_RELOADABLE) {
+                       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+                       {
+                               .entity weaponentity = weaponentities[slot];
+                               this.(weaponentity).weapon_load[it.m_id] = it.reloading_ammo;
+                       }
                }
-               this.alpha = default_player_alpha;
-               this.colormod = '1 1 1' * autocvar_g_player_brightness;
-               this.exteriorweaponentity.alpha = default_weapon_alpha;
+       ));
 
-               this.speedrunning = false;
+       {
+               string s = spot.target;
+               spot.target = string_null;
+               SUB_UseTargets(spot, this, NULL);
+               spot.target = s;
+       }
 
-               target_voicescript_clear(this);
+       Unfreeze(this);
 
-               // reset fields the weapons may use
-               FOREACH(Weapons, true, LAMBDA(
-                       it.wr_resetplayer(it, this);
-                       // reload all reloadable weapons
-                       if (it.spawnflags & WEP_FLAG_RELOADABLE) {
-                               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-                               {
-                                       .entity weaponentity = weaponentities[slot];
-                                       this.(weaponentity).weapon_load[it.m_id] = it.reloading_ammo;
-                               }
-                       }
-               ));
+       MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
 
-               {
-                       string s = spot.target;
-                       spot.target = string_null;
-                       SUB_UseTargets(spot, this, NULL);
-                       spot.target = s;
-               }
+       if (autocvar_spawn_debug)
+       {
+               sprint(this, strcat("spawnpoint origin:  ", vtos(spot.origin), "\n"));
+               delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor
+       }
 
-               Unfreeze(this);
+       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+       {
+               .entity weaponentity = weaponentities[slot];
+               if(slot == 0 || autocvar_g_weaponswitch_debug == 1)
+                       this.(weaponentity).m_switchweapon = w_getbestweapon(this, weaponentity);
+               else
+                       this.(weaponentity).m_switchweapon = WEP_Null;
+               this.(weaponentity).m_weapon = WEP_Null;
+               this.(weaponentity).weaponname = "";
+               this.(weaponentity).m_switchingweapon = WEP_Null;
+               this.(weaponentity).cnt = -1;
+       }
 
-               MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
+       MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
 
-               if (autocvar_spawn_debug)
-               {
-                       sprint(this, strcat("spawnpoint origin:  ", vtos(spot.origin), "\n"));
-                       delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor
-               }
+       if (!warmup_stage && !this.alivetime)
+               this.alivetime = time;
 
-               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-               {
-                       .entity weaponentity = weaponentities[slot];
-                       if(slot == 0)
-                               this.(weaponentity).m_switchweapon = w_getbestweapon(this, weaponentity);
-                       else
-                               this.(weaponentity).m_switchweapon = WEP_Null;
-                       this.(weaponentity).m_weapon = WEP_Null;
-                       this.(weaponentity).weaponname = "";
-                       this.(weaponentity).m_switchingweapon = WEP_Null;
-                       this.(weaponentity).cnt = -1;
-               }
+       antilag_clear(this, CS(this));
+}
+
+/** Called when a client spawns in the server */
+void PutClientInServer(entity this)
+{
+       if (IS_BOT_CLIENT(this)) {
+               TRANSMUTE(Player, this);
+       } else if (IS_REAL_CLIENT(this)) {
+               msg_entity = this;
+               WriteByte(MSG_ONE, SVC_SETVIEW);
+               WriteEntity(MSG_ONE, this);
+       }
+       if (game_stopped)
+               TRANSMUTE(Observer, this);
 
-               if (!warmup_stage && !this.alivetime)
-                       this.alivetime = time;
+       SetSpectatee(this, NULL);
 
-               antilag_clear(this, CS(this));
+       // reset player keys
+       this.itemkeys = 0;
+
+       MUTATOR_CALLHOOK(PutClientInServer, this);
+
+       if (IS_OBSERVER(this)) {
+               PutObserverInServer(this);
+       } else if (IS_PLAYER(this)) {
+               PutPlayerInServer(this);
        }
 }
 
@@ -795,7 +821,7 @@ SetChangeParms
 void SetChangeParms (entity this)
 {
        // save parms for level change
-       parm1 = this.parm_idlesince - time;
+       parm1 = CS(this).parm_idlesince - time;
 
        MUTATOR_CALLHOOK(SetChangeParms);
 }
@@ -808,12 +834,12 @@ DecodeLevelParms
 void DecodeLevelParms(entity this)
 {
        // load parms
-       this.parm_idlesince = parm1;
-       if (this.parm_idlesince == -(86400 * 366))
-               this.parm_idlesince = time;
+       CS(this).parm_idlesince = parm1;
+       if (CS(this).parm_idlesince == -(86400 * 366))
+               CS(this).parm_idlesince = time;
 
        // whatever happens, allow 60 seconds of idling directly after connect for map loading
-       this.parm_idlesince = max(this.parm_idlesince, time - sv_maxidle + 60);
+       CS(this).parm_idlesince = max(CS(this).parm_idlesince, time - sv_maxidle + 60);
 
        MUTATOR_CALLHOOK(DecodeLevelParms);
 }
@@ -829,19 +855,19 @@ Called when a client types 'kill' in the console
 .float clientkill_nexttime;
 void ClientKill_Now_TeamChange(entity this)
 {
-       if(this.killindicator_teamchange == -1)
+       if(CS(this).killindicator_teamchange == -1)
        {
                JoinBestTeam( this, false, true );
        }
-       else if(this.killindicator_teamchange == -2)
+       else if(CS(this).killindicator_teamchange == -2)
        {
                if(blockSpectators)
                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
                PutObserverInServer(this);
        }
        else
-               SV_ChangeTeam(this, this.killindicator_teamchange - 1);
-       this.killindicator_teamchange = 0;
+               SV_ChangeTeam(this, CS(this).killindicator_teamchange - 1);
+       CS(this).killindicator_teamchange = 0;
 }
 
 void ClientKill_Now(entity this)
@@ -849,7 +875,7 @@ void ClientKill_Now(entity this)
        if(this.vehicle)
        {
            vehicles_exit(this.vehicle, VHEF_RELEASE);
-           if(!this.killindicator_teamchange)
+           if(!CS(this).killindicator_teamchange)
            {
             this.vehicle_health = -1;
             Damage(this, this, this, 1 , DEATH_KILL.m_id, this.origin, '0 0 0');
@@ -861,7 +887,7 @@ void ClientKill_Now(entity this)
 
        this.killindicator = NULL;
 
-       if(this.killindicator_teamchange)
+       if(CS(this).killindicator_teamchange)
                ClientKill_Now_TeamChange(this);
 
        if(!IS_SPEC(this) && !IS_OBSERVER(this))
@@ -871,7 +897,7 @@ void ClientKill_Now(entity this)
 }
 void KillIndicator_Think(entity this)
 {
-       if (gameover)
+       if (game_stopped)
        {
                this.owner.killindicator = NULL;
                delete(this);
@@ -890,7 +916,7 @@ void KillIndicator_Think(entity this)
                ClientKill_Now(this.owner);
                return;
        }
-    else if(g_cts && this.health == 1) // health == 1 means that it's silent
+    else if(this.health == 1) // health == 1 means that it's silent
     {
         this.nextthink = time + 1;
         this.cnt -= 1;
@@ -915,18 +941,16 @@ void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change,
        float killtime;
        float starttime;
 
-       if (gameover)
+       if (game_stopped)
                return;
 
        killtime = autocvar_g_balance_kill_delay;
 
-       if(g_race_qualifying || g_cts)
-               killtime = 0;
-
     if(MUTATOR_CALLHOOK(ClientKill, this, killtime))
        return;
+    killtime = M_ARGV(1, float);
 
-       this.killindicator_teamchange = targetteam;
+       CS(this).killindicator_teamchange = targetteam;
 
     if(!this.killindicator)
        {
@@ -1007,7 +1031,7 @@ void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change,
 
 void ClientKill (entity this)
 {
-       if(gameover) return;
+       if(game_stopped) return;
        if(this.player_blocked) return;
        if(STAT(FROZEN, this)) return;
 
@@ -1023,28 +1047,44 @@ void FixClientCvars(entity e)
        if(autocvar_sv_gentle)
                stuffcmd(e, "cl_cmd settemp cl_gentle 1\n");
 
+       stuffcmd(e, sprintf("\ncl_jumpspeedcap_min \"%s\"\n", autocvar_sv_jumpspeedcap_min));
+       stuffcmd(e, sprintf("\ncl_jumpspeedcap_max \"%s\"\n", autocvar_sv_jumpspeedcap_max));
+
        MUTATOR_CALLHOOK(FixClientCvars, e);
 }
 
-float PlayerInIDList(entity p, string idlist)
+bool findinlist_abbrev(string tofind, string list)
 {
-       float n, i;
-       string s;
+       // this function allows abbreviated strings!
+       FOREACH_WORD(list, it == substring(tofind, 0, strlen(it)),
+       {
+               return true;
+       });
+
+       return false;
+}
+
+bool PlayerInIPList(entity p, string iplist)
+{
+       // some safety checks (never allow local?)
+       if(p.netaddress == "local" || p.netaddress == "" || !IS_REAL_CLIENT(p))
+               return false;
+
+       return findinlist_abbrev(p.netaddress, iplist);
+}
 
+bool PlayerInIDList(entity p, string idlist)
+{
        // NOTE: we do NOT check crypto_idfp_signed here, an unsigned ID is fine too for this
-       if (!p.crypto_idfp)
-               return 0;
+       if(!p.crypto_idfp)
+               return false;
 
-       // this function allows abbreviated player IDs too!
-       n = tokenize_console(idlist);
-       for(i = 0; i < n; ++i)
-       {
-               s = argv(i);
-               if(s == substring(p.crypto_idfp, 0, strlen(s)))
-                       return 1;
-       }
+       return findinlist_abbrev(p.crypto_idfp, idlist);
+}
 
-       return 0;
+bool PlayerInList(entity player, string list)
+{
+       return PlayerInIDList(player, list) || PlayerInIPList(player, list);
 }
 
 #ifdef DP_EXT_PRECONNECT
@@ -1103,10 +1143,10 @@ void ClientConnect(entity this)
                        }
                }
        }
-       else if (PlayerInIDList(this, autocvar_g_forced_team_red))    this.team_forced = NUM_TEAM_1;
-       else if (PlayerInIDList(this, autocvar_g_forced_team_blue))   this.team_forced = NUM_TEAM_2;
-       else if (PlayerInIDList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3;
-       else if (PlayerInIDList(this, autocvar_g_forced_team_pink))   this.team_forced = NUM_TEAM_4;
+       else if (PlayerInList(this, autocvar_g_forced_team_red))    this.team_forced = NUM_TEAM_1;
+       else if (PlayerInList(this, autocvar_g_forced_team_blue))   this.team_forced = NUM_TEAM_2;
+       else if (PlayerInList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3;
+       else if (PlayerInList(this, autocvar_g_forced_team_pink))   this.team_forced = NUM_TEAM_4;
        else switch (autocvar_g_forced_team_otherwise)
        {
                default: this.team_forced = 0; break;
@@ -1145,7 +1185,7 @@ void ClientConnect(entity this)
     if (IS_BOT_CLIENT(this)) PlayerStats_GameReport_AddPlayer(this);
 
        if (autocvar_sv_eventlog)
-               GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", this.netname));
+               GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", playername(this, false)));
 
        LogTeamchange(this.playerid, this.team, 1);
 
@@ -1195,12 +1235,6 @@ void ClientConnect(entity this)
 
        if (IS_REAL_CLIENT(this))
        {
-               if (!autocvar_g_campaign)
-               {
-                       this.motd_actived_time = -1;
-                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this));
-               }
-
                if (g_weaponarena_weapons == WEPSET(TUBA))
                        stuffcmd(this, "cl_cmd settemp chase_active 1\n");
        }
@@ -1209,7 +1243,7 @@ void ClientConnect(entity this)
                stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
 
        if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2))
-               if (!g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts
+               if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
                        send_CSQC_teamnagger();
 
        CSQCMODEL_AUTOINIT(this);
@@ -1220,6 +1254,8 @@ void ClientConnect(entity this)
                sv_notice_join(this);
 
        // update physics stats (players can spawn before physics runs)
+       STAT(MOVEVARS_HIGHSPEED, this) = autocvar_g_movement_highspeed;
+       MUTATOR_CALLHOOK(PlayerPhysics_UpdateStats, this); // do it BEFORE the function so we can modify highspeed!
        Physics_UpdateStats(this, PHYS_HIGHSPEED(this));
 
        IL_EACH(g_initforplayer, it.init_for_player, {
@@ -1227,6 +1263,15 @@ void ClientConnect(entity this)
        });
 
        MUTATOR_CALLHOOK(ClientConnect, this);
+
+       if (IS_REAL_CLIENT(this))
+       {
+               if (!autocvar_g_campaign && !IS_PLAYER(this))
+               {
+                       this.motd_actived_time = -1;
+                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this));
+               }
+       }
 }
 /*
 =============
@@ -1251,7 +1296,8 @@ void ClientDisconnect(entity this)
 
        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname);
 
-       SetSpectatee(this, NULL);
+       if(IS_SPEC(this))
+               SetSpectatee(this, NULL);
 
     MUTATOR_CALLHOOK(ClientDisconnect, this);
 
@@ -1386,7 +1432,7 @@ void player_powerups(entity this)
        // add a way to see what the items were BEFORE all of these checks for the mutator hook
        int items_prev = this.items;
 
-       if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !gameover)
+       if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped)
                this.modelflags |= MF_ROCKET;
        else
                this.modelflags &= ~MF_ROCKET;
@@ -1417,7 +1463,8 @@ void player_powerups(entity this)
                        if (time < this.strength_finished)
                        {
                                this.items = this.items | ITEM_Strength.m_itemid;
-                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_STRENGTH, this.netname);
+                               if(!g_cts)
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_STRENGTH, this.netname);
                                Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_STRENGTH);
                        }
                }
@@ -1437,7 +1484,8 @@ void player_powerups(entity this)
                        if (time < this.invincible_finished)
                        {
                                this.items = this.items | ITEM_Shield.m_itemid;
-                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SHIELD, this.netname);
+                               if(!g_cts)
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SHIELD, this.netname);
                                Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_SHIELD);
                        }
                }
@@ -1471,7 +1519,8 @@ void player_powerups(entity this)
                        if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS))
                        {
                                this.items = this.items | IT_SUPERWEAPON;
-                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
+                               if(!g_cts)
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
                                Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
                        }
                        else
@@ -1668,8 +1717,10 @@ void SpectateCopy(entity this, entity spectatee)
        this.hit_time = spectatee.hit_time;
        this.strength_finished = spectatee.strength_finished;
        this.invincible_finished = spectatee.invincible_finished;
+       this.superweapons_finished = spectatee.superweapons_finished;
        STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
        this.weapons = spectatee.weapons;
+       this.dual_weapons = spectatee.dual_weapons;
        this.vortex_charge = spectatee.vortex_charge;
        this.vortex_chargepool_ammo = spectatee.vortex_chargepool_ammo;
        this.hagar_load = spectatee.hagar_load;
@@ -1698,6 +1749,11 @@ void SpectateCopy(entity this, entity spectatee)
                this.(weaponentity) = spectatee.(weaponentity);
        }
 
+       for(int slot = 0; slot < MAX_AXH; ++slot)
+       {
+               this.(AuxiliaryXhair[slot]) = spectatee.(AuxiliaryXhair[slot]);
+       }
+
     anticheat_spectatecopy(this, spectatee);
        this.hud = spectatee.hud;
        if(spectatee.vehicle)
@@ -1777,6 +1833,9 @@ void SetSpectatee_status(entity this, int spectatee_num)
 
 void SetSpectatee(entity this, entity spectatee)
 {
+       if(IS_BOT_CLIENT(this))
+               return; // bots abuse .enemy, this code is useless to them
+
        entity old_spectatee = this.enemy;
 
        this.enemy = spectatee;
@@ -1917,6 +1976,7 @@ void Join(entity this)
 
        PutClientInServer(this);
 
+       if(IS_PLAYER(this))
        if(teamplay && this.team != -1)
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
        else
@@ -2150,7 +2210,7 @@ void PlayerUseKey(entity this)
 
        if(this.vehicle)
        {
-               if(!gameover)
+               if(!game_stopped)
                {
                        vehicles_exit(this.vehicle, VHEF_NORMAL);
                        return;
@@ -2160,7 +2220,7 @@ void PlayerUseKey(entity this)
        {
                if(!STAT(FROZEN, this))
                if(!IS_DEAD(this))
-               if(!gameover)
+               if(!game_stopped)
                {
                        entity head, closest_target = NULL;
                        head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
@@ -2233,7 +2293,7 @@ void PlayerPreThink (entity this)
        }
        if (this.netname != this.netname_previous) {
                if (autocvar_sv_eventlog) {
-                       GameLogEcho(strcat(":name:", ftos(this.playerid), ":", this.netname));
+                       GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this, false)));
         }
                if (this.netname_previous) strunzone(this.netname_previous);
                this.netname_previous = strzone(this.netname);
@@ -2291,7 +2351,7 @@ void PlayerPreThink (entity this)
 
        MUTATOR_CALLHOOK(PlayerPreThink, this);
 
-       if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !gameover && !this.vehicle)
+       if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
        if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this))
        {
                FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it),
@@ -2326,8 +2386,10 @@ void PlayerPreThink (entity this)
        if (IS_PLAYER(this)) {
                CheckRules_Player(this);
 
-               if (intermission_running) {
-                       IntermissionThink(this);
+               if (game_stopped || intermission_running) {
+                       this.modelflags &= ~MF_ROCKET;
+                       if(intermission_running)
+                               IntermissionThink(this);
                        return;
                }
 
@@ -2350,6 +2412,13 @@ void PlayerPreThink (entity this)
                                }
                        } else {
                                if (frametime) player_anim(this);
+
+                               if (this.respawn_flags & RESPAWN_DENY)
+                               {
+                                       STAT(RESPAWN_TIME, this) = 0;
+                                       return;
+                               }
+
                                bool button_pressed = (PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this));
 
                                switch(this.deadflag)
@@ -2412,8 +2481,18 @@ void PlayerPreThink (entity this)
 
                this.prevorigin = this.origin;
 
+               bool have_hook = false;
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if(this.(weaponentity).hook.state)
+                       {
+                               have_hook = true;
+                               break;
+                       }
+               }
                bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
-               if (this.hook.state) {
+               if (have_hook) {
                        do_crouch = false;
                } else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
                        do_crouch = false;
@@ -2486,8 +2565,9 @@ void PlayerPreThink (entity this)
 
                this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
        }
-       else if (gameover) {
-               if (intermission_running) IntermissionThink(this);
+       else if (game_stopped || intermission_running) {
+               if(intermission_running)
+                       IntermissionThink(this);
                return;
        }
        else if (IS_OBSERVER(this)) {
@@ -2584,7 +2664,6 @@ PlayerPostThink
 Called every frame for each client after the physics are run
 =============
 */
-.float idlekick_lasttimeleft;
 void PlayerPostThink (entity this)
 {
        Player_Physics(this);
@@ -2605,19 +2684,19 @@ void PlayerPostThink (entity this)
 
                if (sv_maxidle_slots > 0 && (maxclients - totalClients) > sv_maxidle_slots)
                { /* do nothing */ }
-               else if (time - this.parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
+               else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
                {
-                       if (this.idlekick_lasttimeleft)
+                       if (CS(this).idlekick_lasttimeleft)
                        {
-                               this.idlekick_lasttimeleft = 0;
+                               CS(this).idlekick_lasttimeleft = 0;
                                Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING);
                        }
                }
                else
                {
-                       float timeleft = ceil(sv_maxidle - (time - this.parm_idlesince));
+                       float timeleft = ceil(sv_maxidle - (time - CS(this).parm_idlesince));
                        if (timeleft == min(10, sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10
-                               if (!this.idlekick_lasttimeleft)
+                               if (!CS(this).idlekick_lasttimeleft)
                                        Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
                        }
                        if (timeleft <= 0) {
@@ -2626,10 +2705,10 @@ void PlayerPostThink (entity this)
                                return;
                        }
                        else if (timeleft <= 10) {
-                               if (timeleft != this.idlekick_lasttimeleft) {
+                               if (timeleft != CS(this).idlekick_lasttimeleft) {
                                    Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft));
                 }
-                               this.idlekick_lasttimeleft = timeleft;
+                               CS(this).idlekick_lasttimeleft = timeleft;
                        }
                }
        }
@@ -2637,13 +2716,23 @@ void PlayerPostThink (entity this)
        CheatFrame(this);
 
        //CheckPlayerJump();
+       if (game_stopped)
+       {
+               this.solid = SOLID_NOT;
+               this.takedamage = DAMAGE_NO;
+               set_movetype(this, MOVETYPE_NONE);
+       }
 
        if (IS_PLAYER(this)) {
                DrownPlayer(this);
                CheckRules_Player(this);
                UpdateChatBubble(this);
                if (this.impulse) ImpulseCommands(this);
-               if (intermission_running) return; // intermission or finale
+               if (game_stopped)
+               {
+                       CSQCMODEL_AUTOUPDATE(this);
+                       return;
+               }
                GetPressedKeys(this);
        }