]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/cl_client.qc
Fix kill/teamchange/spectate not working in vehicles
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / cl_client.qc
index 2386c3ca9b407a04ac21c6e7c7c6448d8b726b39..7c3308041e670a8270eca36b9a660a640746a6e0 100644 (file)
@@ -123,7 +123,7 @@ void spawnpoint_use()
 // Returns:
 //   _x: prio (-1 if unusable)
 //   _y: weight
-vector Spawn_Score(entity spot, entity playerlist, float teamcheck, float anypoint)
+vector Spawn_Score(entity spot, float mindist, float teamcheck)
 {
        float shortest, thisdist;
        float prio;
@@ -151,55 +151,37 @@ vector Spawn_Score(entity spot, entity playerlist, float teamcheck, float anypoi
                        return '-1 0 0';
        }
 
+       shortest = vlen(world.maxs - world.mins);
+       FOR_EACH_PLAYER(player) if (player != self)
+       {
+               thisdist = vlen(player.origin - spot.origin);
+               if (thisdist < shortest)
+                       shortest = thisdist;
+       }
+       if(shortest > mindist)
+               prio += SPAWN_PRIO_GOOD_DISTANCE;
+
+       spawn_score = prio * '1 0 0' + shortest * '0 1 0';
+       spawn_spot = spot;
+
        // filter out spots for assault
        if(spot.target != "") {
                entity ent;
-               float good, found;
+               float found;
 
                found = 0;
-               good = 0;
                for(ent = world; (ent = find(ent, targetname, spot.target)); )
                {
                        ++found;
-                       if(ent.classname == "target_objective")
-                       {
-                               if(ent.health < 0 || ent.health >= ASSAULT_VALUE_INACTIVE)
-                                       continue;
-                       }
-                       else if(ent.classname == "trigger_race_checkpoint")
+                       if(ent.spawn_evalfunc)
                        {
-                               if(!anypoint) // spectators may spawn everywhere
-                               {
-                                       if(g_race_qualifying)
-                                       {
-                                               // spawn at first
-                                               if(ent.race_checkpoint != 0)
-                                                       continue;
-                                               if(spot.race_place != race_lowest_place_spawn)
-                                                       continue;
-                                       }
-                                       else
-                                       {
-                                               if(ent.race_checkpoint != self.race_respawn_checkpoint)
-                                                       continue;
-                                               // try reusing the previous spawn
-                                               if(ent == self.race_respawn_spotref || spot == self.race_respawn_spotref)
-                                                       prio += 1;
-                                               if(ent.race_checkpoint == 0)
-                                               {
-                                                       float pl;
-                                                       pl = self.race_place;
-                                                       if(pl > race_highest_place_spawn)
-                                                               pl = 0;
-                                                       if(pl == 0 && !self.race_started)
-                                                               pl = race_highest_place_spawn; // use last place if he has not even touched finish yet
-                                                       if(spot.race_place != pl)
-                                                               continue;
-                                               }
-                                       }
-                               }
+                               entity oldself = self;
+                               self = ent;
+                               spawn_score = ent.spawn_evalfunc(oldself, spot, spawn_score);
+                               self = oldself;
+                               if(spawn_score_x < 0)
+                                       return spawn_score;
                        }
-                       ++good;
                }
 
                if(!found)
@@ -207,95 +189,42 @@ vector Spawn_Score(entity spot, entity playerlist, float teamcheck, float anypoi
                        dprint("WARNING: spawnpoint at ", vtos(spot.origin), " could not find its target ", spot.target, "\n");
                        return '-1 0 0';
                }
-
-               if(good < found) // at least one was bad
-                       return '-1 0 0';
        }
 
-       player = playerlist;
-       shortest = vlen(world.maxs - world.mins);
-       for(player = playerlist; player; player = player.chain)
-               if (player != self)
-               {
-                       thisdist = vlen(player.origin - spot.origin);
-                       if (thisdist < shortest)
-                               shortest = thisdist;
-               }
-       return prio * '1 0 0' + shortest * '0 1 0';
+       MUTATOR_CALLHOOK(Spawn_Score);
+       return spawn_score;
+}
+
+void Spawn_ScoreAll(entity firstspot, float mindist, float teamcheck)
+{
+       entity spot;
+       for(spot = firstspot; spot; spot = spot.chain)
+               spot.spawnpoint_score = Spawn_Score(spot, mindist, teamcheck);
 }
 
-float spawn_allbad;
-float spawn_allgood;
-entity Spawn_FilterOutBadSpots(entity firstspot, entity playerlist, float mindist, float teamcheck, float anypoint)
+entity Spawn_FilterOutBadSpots(entity firstspot, float mindist, float teamcheck)
 {
        entity spot, spotlist, spotlistend;
-       spawn_allgood = TRUE;
-       spawn_allbad = TRUE;
 
        spotlist = world;
        spotlistend = world;
 
+       Spawn_ScoreAll(firstspot, mindist, teamcheck);
+
        for(spot = firstspot; spot; spot = spot.chain)
        {
-               spot.spawnpoint_score = Spawn_Score(spot, playerlist, teamcheck, anypoint);
-
-               if(autocvar_spawn_debugview)
-               {
-                       setmodel(spot, "models/runematch/rune.mdl");
-                       if(spot.spawnpoint_score_y < mindist)
-                       {
-                               spot.colormod = '1 0 0';
-                               spot.scale = 1;
-                       }
-                       else
-                       {
-                               spot.colormod = '0 1 0';
-                               spot.scale = spot.spawnpoint_score_y / mindist;
-                       }
-               }
-
                if(spot.spawnpoint_score_x >= 0) // spawning allowed here
                {
-                       if(spot.spawnpoint_score_y < mindist)
-                       {
-                               // too short distance
-                               spawn_allgood = FALSE;
-                       }
-                       else
-                       {
-                               // perfect
-                               spawn_allbad = FALSE;
-
-                               if(spotlistend)
-                                       spotlistend.chain = spot;
-                               spotlistend = spot;
-                               if(!spotlist)
-                                       spotlist = spot;
-
-                               /*
-                               if(teamcheck >= 0)
-                               if(spot.team != teamcheck)
-                                       error("invalid spawn added");
-
-                               print("added ", etos(spot), "\n");
-                               */
-                       }
+                       if(spotlistend)
+                               spotlistend.chain = spot;
+                       spotlistend = spot;
+                       if(!spotlist)
+                               spotlist = spot;
                }
        }
        if(spotlistend)
                spotlistend.chain = world;
 
-       /*
-               entity e;
-               if(teamcheck >= 0)
-                       for(e = spotlist; e; e = e.chain)
-                       {
-                               print("seen ", etos(e), "\n");
-                               if(e.team != teamcheck)
-                                       error("invalid spawn found");
-                       }
-       */
-
        return spotlist;
 }
 
@@ -322,8 +251,7 @@ Finds a point to respawn
 entity SelectSpawnPoint (float anypoint)
 {
        float teamcheck;
-       entity firstspot_new;
-       entity spot, firstspot, playerlist;
+       entity spot, firstspot;
 
        spot = find (world, classname, "testplayerstart");
        if (spot)
@@ -354,8 +282,6 @@ entity SelectSpawnPoint (float anypoint)
                // if we get here, we either require team spawns but have none, or we require non-team spawns and have none; use any spawn then
 
 
-       // get the list of players
-       playerlist = findchain(classname, "player");
        // get the entire list of spots
        firstspot = findchain(classname, "info_player_deathmatch");
        // filter out the bad ones
@@ -366,42 +292,26 @@ entity SelectSpawnPoint (float anypoint)
        }
        else
        {
-               firstspot_new = Spawn_FilterOutBadSpots(firstspot, playerlist, 100, teamcheck, anypoint);
-               if(!firstspot_new)
-                       firstspot_new = Spawn_FilterOutBadSpots(firstspot, playerlist, -1, teamcheck, anypoint);
-               firstspot = firstspot_new;
+               float mindist;
+               if (arena_roundbased && !g_ca)
+                       mindist = 800;
+               else
+                       mindist = 100;
+               firstspot = Spawn_FilterOutBadSpots(firstspot, mindist, teamcheck);
 
                // there is 50/50 chance of choosing a random spot or the furthest spot
                // (this means that roughly every other spawn will be furthest, so you
                // usually won't get fragged at spawn twice in a row)
-               if (arena_roundbased && !g_ca)
-               {
-                       firstspot_new = Spawn_FilterOutBadSpots(firstspot, playerlist, 800, teamcheck, anypoint);
-                       if(firstspot_new)
-                               firstspot = firstspot_new;
-                       spot = Spawn_WeightedPoint(firstspot, 1, 1, 1);
-               }
-               else if (random() > autocvar_g_spawn_furthest)
+               if (random() > autocvar_g_spawn_furthest)
                        spot = Spawn_WeightedPoint(firstspot, 1, 1, 1);
                else
                        spot = Spawn_WeightedPoint(firstspot, 1, 5000, 5); // chooses a far far away spawnpoint
        }
 
-       if(autocvar_spawn_debugview)
-       {
-               print("spot mindistance: ", vtos(spot.spawnpoint_score), "\n");
-
-               entity e;
-               if(teamcheck >= 0)
-                       for(e = firstspot; e; e = e.chain)
-                               if(e.team != teamcheck)
-                                       error("invalid spawn found");
-       }
-
        if (!spot)
        {
                if(autocvar_spawn_debug)
-                       GotoNextMap();
+                       GotoNextMap(0);
                else
                {
                        if(some_spawn_has_been_used)
@@ -431,25 +341,26 @@ string CheckPlayerModel(string plyermodel) {
                // to change a cvar default, we'll have a small leak here.
                FallbackPlayerModel = strzone(cvar_defstring("_cl_playermodel"));
        }
-       if(strlen(plyermodel) < 4)
-               return FallbackPlayerModel;
+       // only in right path
        if( substring(plyermodel,0,14) != "models/player/")
                return FallbackPlayerModel;
-       else if(autocvar_sv_servermodelsonly)
+       // only good file extensions
+       if(substring(plyermodel,-4,4) != ".zym")
+       if(substring(plyermodel,-4,4) != ".dpm")
+       if(substring(plyermodel,-4,4) != ".iqm")
+       if(substring(plyermodel,-4,4) != ".md3")
+       if(substring(plyermodel,-4,4) != ".psk")
+               return FallbackPlayerModel;
+       // forbid the LOD models
+       if(substring(plyermodel, -9,5) == "_lod1")
+               return FallbackPlayerModel;
+       if(substring(plyermodel, -9,5) == "_lod2")
+               return FallbackPlayerModel;
+       if(plyermodel != strtolower(plyermodel))
+               return FallbackPlayerModel;
+       // also, restrict to server models
+       if(autocvar_sv_servermodelsonly)
        {
-               if(substring(plyermodel,-4,4) != ".zym")
-               if(substring(plyermodel,-4,4) != ".dpm")
-               if(substring(plyermodel,-4,4) != ".iqm")
-               if(substring(plyermodel,-4,4) != ".md3")
-               if(substring(plyermodel,-4,4) != ".psk")
-                       return FallbackPlayerModel;
-               // forbid the LOD models
-               if(substring(plyermodel, -9,5) == "_lod1")
-                       return FallbackPlayerModel;
-               if(substring(plyermodel, -9,5) == "_lod2")
-                       return FallbackPlayerModel;
-               if(plyermodel != strtolower(plyermodel))
-                       return FallbackPlayerModel;
                if(!fexists(plyermodel))
                        return FallbackPlayerModel;
        }
@@ -493,8 +404,7 @@ void PutObserverInServer (void)
        DropAllRunes(self);
        MUTATOR_CALLHOOK(MakePlayerObserver);
 
-       if (g_minstagib)
-               minstagib_stop_countdown();
+       minstagib_stop_countdown(self);
 
        Portal_ClearAll(self);
 
@@ -505,14 +415,11 @@ void PutObserverInServer (void)
        }
 
        if(self.vehicle)
-           vehicles_exit(VHEF_RELESE);
+               vehicles_exit(VHEF_RELESE);         
 
        if(self.flagcarried)
                DropFlag(self.flagcarried, world, world);
 
-       if(self.ballcarried && g_nexball)
-               DropBall(self.ballcarried, self.origin + self.ballcarried.origin, self.velocity);
-
        WaypointSprite_PlayerDead();
 
        if not(g_ca)  // don't reset teams when moving a ca player to the spectators
@@ -545,7 +452,7 @@ void PutObserverInServer (void)
        self.health = -666;
        self.takedamage = DAMAGE_NO;
        self.solid = SOLID_NOT;
-       self.movetype = MOVETYPE_FLY_WORLDONLY; //(self.cvar_cl_clippedspectating ? MOVETYPE_NOCLIP : MOVETYPE_FLY); // it's too early for this anyway, lets just set it in playerprethink
+       self.movetype = MOVETYPE_FLY_WORLDONLY; // user preference is controlled by playerprethink
        self.flags = FL_CLIENT | FL_NOTARGET;
        self.armorvalue = 666;
        self.effects = 0;
@@ -555,7 +462,7 @@ void PutObserverInServer (void)
        self.pauseregen_finished = 0;
        self.damageforcescale = 0;
        self.death_time = 0;
-       self.dead_frame = 0;
+       self.respawn_time = 0;
        self.alpha = 0;
        self.scale = 0;
        self.fade_time = 0;
@@ -563,7 +470,9 @@ void PutObserverInServer (void)
        self.pain_finished = 0;
        self.strength_finished = 0;
        self.invincible_finished = 0;
+       self.superweapons_finished = 0;
        self.pushltime = 0;
+       self.istypefrag = 0;
        self.think = SUB_Null;
        self.nextthink = 0;
        self.hook_time = 0;
@@ -574,10 +483,10 @@ void PutObserverInServer (void)
        self.fixangle = TRUE;
        self.crouch = FALSE;
 
-       setorigin (self, spot.origin);
+       setorigin (self, (spot.origin + PL_VIEW_OFS)); // offset it so that the spectator spawns higher off the ground, looks better this way
        self.prevorigin = self.origin;
        self.items = 0;
-       self.weapons = 0;
+       WEPSET_CLEAR_E(self);
        self.model = "";
        FixPlayermodel();
        setmodel(self, "null");
@@ -639,11 +548,11 @@ void FixPlayermodel()
        vector m1, m2;
 
        defaultmodel = "";
+       defaultskin = 0;
+       chmdl = FALSE;
 
        if(autocvar_sv_defaultcharacter == 1)
        {
-               defaultskin = 0;
-
                if(teamplay)
                {
                        string s;
@@ -673,13 +582,6 @@ void FixPlayermodel()
                }
        }
 
-       if(self.modelindex == 0 && self.deadflag == DEAD_NO)
-       {
-               if(self.model != "")
-                       bprint("\{1}^1Player ", self.netname, "^1 has a zero modelindex, trying to fix...\n");
-               self.model = ""; // force the != checks to return true
-       }
-
        if(defaultmodel != "")
        {
                if (defaultmodel != self.model)
@@ -740,11 +642,14 @@ Called when a client spawns in the server
 =============
 */
 //void() ctf_playerchanged;
+
 void PutClientInServer (void)
 {
        if(clienttype(self) == CLIENTTYPE_BOT)
        {
                self.classname = "player";
+               if(g_ca)
+                       self.caplayer = 1;
        }
        else if(clienttype(self) == CLIENTTYPE_REAL)
        {
@@ -752,7 +657,7 @@ void PutClientInServer (void)
                WriteByte(MSG_ONE, SVC_SETVIEW);
                WriteEntity(MSG_ONE, self);
        }
-       
+
        // reset player keys
        self.itemkeys = 0;
 
@@ -764,8 +669,7 @@ void PutClientInServer (void)
                        self.classname = "observer";
        }
 
-       if(g_arena || (g_ca && !allowed_to_spawn))
-       if(!self.spawned)
+       if((g_arena && !self.spawned) || (g_ca && !allowed_to_spawn))
                self.classname = "observer";
 
        if(gameover)
@@ -832,7 +736,7 @@ void PutClientInServer (void)
                        self.ammo_fuel = warmup_start_ammo_fuel;
                        self.health = warmup_start_health;
                        self.armorvalue = warmup_start_armorvalue;
-                       self.weapons = warmup_start_weapons;
+                       WEPSET_COPY_EA(self, warmup_start_weapons);
                }
                else
                {
@@ -843,20 +747,24 @@ void PutClientInServer (void)
                        self.ammo_fuel = start_ammo_fuel;
                        self.health = start_health;
                        self.armorvalue = start_armorvalue;
-                       self.weapons = start_weapons;
+                       WEPSET_COPY_EA(self, start_weapons);
                }
 
+               if(WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS)) // exception for minstagib, as minstanex is a superweapon
+                       self.superweapons_finished = time + autocvar_g_balance_superweapons_time;
+               else
+                       self.superweapons_finished = 0;
+
                if(g_weaponarena_random)
                {
                        if(g_weaponarena_random_with_laser)
-                               self.weapons &~= WEPBIT_LASER;
-                       self.weapons = randombits(self.weapons, g_weaponarena_random, FALSE);
+                               WEPSET_ANDNOT_EW(self, WEP_LASER);
+                       W_RandomWeapons(self, g_weaponarena_random);
                        if(g_weaponarena_random_with_laser)
-                               self.weapons |= WEPBIT_LASER;
+                               WEPSET_OR_EW(self, WEP_LASER);
                }
 
                self.items = start_items;
-               self.jump_interval = time;
 
                self.spawnshieldtime = time + autocvar_g_spawnshieldtime;
                self.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
@@ -872,8 +780,7 @@ void PutClientInServer (void)
                }
                self.damageforcescale = 2;
                self.death_time = 0;
-               self.dead_frame = 0;
-               self.alpha = 0;
+               self.respawn_time = 0;
                self.scale = 0;
                self.fade_time = 0;
                self.pain_frame = 0;
@@ -926,13 +833,12 @@ void PutClientInServer (void)
                self.lastrocket = world; // stop rocket guiding, no revenge from the grave!
                self.lastteleporttime = time; // prevent insane speeds due to changing origin
         self.hud = HUD_NORMAL;
-        
+
                if(g_arena)
                {
                        Spawnqueue_Remove(self);
                        Spawnqueue_Mark(self);
                }
-
                else if(g_ca)
                        self.caplayer = 1;
 
@@ -959,18 +865,9 @@ void PutClientInServer (void)
 
                race_PostSpawn(spot);
 
-               if(autocvar_spawn_debug)
-               {
-                       sprint(self, strcat("spawnpoint origin:  ", vtos(spot.origin), "\n"));
-                       remove(spot);   // usefull for checking if there are spawnpoints, that let drop through the floor
-               }
-
                //stuffcmd(self, "chase_active 0");
                //stuffcmd(self, "set viewsize $tmpviewsize \n");
 
-               if (autocvar_g_spawnsound)
-                       sound (self, CH_TRIGGER, "misc/spawn.wav", VOL_BASE, ATTN_NORM);
-
                if(g_assault) {
                        if(self.team == assault_attacker_team)
                                centerprint(self, "You are attacking!");
@@ -1003,8 +900,15 @@ void PutClientInServer (void)
                        activator = world;
                self = oldself;
 
+               spawn_spot = spot;
                MUTATOR_CALLHOOK(PlayerSpawn);
 
+               if(autocvar_spawn_debug)
+               {
+                       sprint(self, strcat("spawnpoint origin:  ", vtos(spot.origin), "\n"));
+                       remove(spot);   // usefull for checking if there are spawnpoints, that let drop through the floor
+               }
+
                self.switchweapon = w_getbestweapon(self);
                self.cnt = -1; // W_LastWeapon will not complain
                self.weapon = 0;
@@ -1013,7 +917,12 @@ void PutClientInServer (void)
 
                if(!self.alivetime)
                        self.alivetime = time;
-       } else if(self.classname == "observer" || (g_ca && !allowed_to_spawn)) {
+
+               antilag_clear(self);
+
+               if (autocvar_g_spawnsound)
+                       soundat(world, self.origin, CH_TRIGGER, "misc/spawn.wav", VOL_BASE, ATTN_NORM);
+       } else if(self.classname == "observer") {
                PutObserverInServer ();
        }
 
@@ -1056,6 +965,7 @@ float ClientInit_SendEntity(entity to, float sf)
        WriteByte(MSG_ENTITY, autocvar_g_balance_minelayer_limit); // minelayer max mines
        WriteByte(MSG_ENTITY, autocvar_g_balance_hagar_secondary_load_max); // hagar max loadable rockets
        WriteCoord(MSG_ENTITY, autocvar_g_trueaim_minrange);
+       WriteByte(MSG_ENTITY, autocvar_g_balance_porto_secondary);
        return TRUE;
 }
 
@@ -1160,8 +1070,7 @@ void ClientKill_Now_TeamChange()
 {
        if(self.killindicator_teamchange == -1)
        {
-               self.team = -1;
-               JoinBestTeam( self, FALSE, FALSE );
+               JoinBestTeam( self, FALSE, TRUE );
        }
        else if(self.killindicator_teamchange == -2)
        {
@@ -1209,7 +1118,7 @@ void KillIndicator_Think()
                return;
        }
 
-       if (!self.owner.modelindex)
+       if (self.owner.alpha < 0 && !self.owner.vehicle)
        {
                self.owner.killindicator = world;
                remove(self);
@@ -1269,7 +1178,7 @@ void ClientKill_TeamChange (float targetteam) // 0 = don't change, -1 = auto, -2
 
     if(!self.killindicator)
        {
-               if(self.modelindex && self.deadflag == DEAD_NO)
+               if(self.deadflag == DEAD_NO)
                {
                        killtime = max(killtime, self.clientkill_nexttime - time);
                        self.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
@@ -1424,10 +1333,6 @@ float PlayerInIDList(entity p, string idlist)
        return 0;
 }
 
-#ifndef NO_LEGACY_NETWORKING
-.float iscsqcmodel;
-#endif
-
 /*
 =============
 ClientConnect
@@ -1450,7 +1355,7 @@ void ClientConnect (void)
                return;
        }
 
-       if(Ban_MaybeEnforceBan(self))
+       if(Ban_MaybeEnforceBanOnce(self))
                return;
 
        DecodeLevelParms();
@@ -1534,7 +1439,7 @@ void ClientConnect (void)
        } else {
                if(teamplay)
                {
-                       if(autocvar_g_balance_teams || autocvar_g_balance_teams_force)
+                       if(autocvar_g_balance_teams)
                        {
                                self.classname = "player";
                                campaign_bots_may_start = 1;
@@ -1601,8 +1506,6 @@ void ClientConnect (void)
        else
                stuffcmd(self, "set _teams_available 0\n");
 
-       stuffcmd(self, strcat("set gametype ", ftos(game), "\n"));
-
        if(g_arena || g_ca)
        {
                self.classname = "observer";
@@ -1625,11 +1528,11 @@ void ClientConnect (void)
        }
 
        self.jointime = time;
-       self.allowedTimeouts = autocvar_sv_timeout_number;
+       self.allowed_timeouts = autocvar_sv_timeout_number;
 
        if(clienttype(self) == CLIENTTYPE_REAL)
        {
-               if(autocvar_g_bugrigs || g_weaponarena == WEPBIT_TUBA)
+               if(autocvar_g_bugrigs || WEPSET_EQ_AW(g_weaponarena_weapons, WEP_TUBA))
                        stuffcmd(self, "cl_cmd settemp chase_active 1\n");
        }
 
@@ -1661,7 +1564,6 @@ void ClientConnect (void)
                        rr = CTS_RECORD;
                else
                        rr = RACE_RECORD;
-               t = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "time")));
 
                msg_entity = self;
                race_send_recordtime(MSG_ONE);
@@ -1687,19 +1589,15 @@ void ClientConnect (void)
        if(!autocvar_g_campaign)
                Send_CSQC_Centerprint_Generic(self, CPID_MOTD, getwelcomemessage(), autocvar_welcome_message_time, 0);
 
-#ifndef NO_LEGACY_NETWORKING
-       if(autocvar_sv_use_csqc_players)
-       {
-               CSQCModel_LinkEntity();
-               self.iscsqcmodel = 1;
-       }
-#else
-       CSQCModel_LinkEntity();
-#endif
+       CSQCMODEL_AUTOINIT();
 
        self.model_randomizer = random();
+    
+    if(clienttype(self) != CLIENTTYPE_REAL)
+        return;
+        
+    sv_notice_join();
 }
-
 /*
 =============
 ClientDisconnect
@@ -1755,8 +1653,6 @@ void ClientDisconnect (void)
        RemoveGrapplingHook(self);
        if(self.flagcarried)
                DropFlag(self.flagcarried, world, world);
-       if(self.ballcarried && g_nexball)
-               DropBall(self.ballcarried, self.origin + self.ballcarried.origin, self.velocity);
 
        // Here, everything has been done that requires this player to be a client.
 
@@ -1805,7 +1701,7 @@ void ClientDisconnect (void)
 void ChatBubbleThink()
 {
        self.nextthink = time;
-       if (!self.owner.modelindex || self.owner.chatbubbleentity != self)
+       if ((self.owner.alpha < 0) || self.owner.chatbubbleentity != self)
        {
                if(self.owner) // but why can that ever be world?
                        self.owner.chatbubbleentity = world;
@@ -1824,7 +1720,7 @@ void ChatBubbleThink()
 
 void UpdateChatBubble()
 {
-       if (!self.modelindex)
+       if (self.alpha < 0)
                return;
        // spawn a chatbubble entity if needed
        if (!self.chatbubbleentity)
@@ -1861,31 +1757,24 @@ void UpdateChatBubble()
        else self.colormod = '1 1 1';
 }*/
 
-.float oldcolormap;
 void respawn(void)
 {
-       if(self.modelindex != 0 && autocvar_g_respawn_ghosts)
+       if(self.alpha >= 0 && autocvar_g_respawn_ghosts)
        {
                self.solid = SOLID_NOT;
                self.takedamage = DAMAGE_NO;
                self.movetype = MOVETYPE_FLY;
                self.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
                self.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
-               self.effects |= EF_ADDITIVE;
-               self.oldcolormap = self.colormap;
-               self.colormap = 0; // this originally was 512, but raises a warning in the engine, so get rid of it
+               self.effects |= CSQCMODEL_EF_RESPAWNGHOST;
                pointparticles(particleeffectnum("respawn_ghost"), self.origin, '0 0 0', 1);
                if(autocvar_g_respawn_ghosts_maxtime)
                        SUB_SetFade (self, time + autocvar_g_respawn_ghosts_maxtime / 2 + random () * (autocvar_g_respawn_ghosts_maxtime - autocvar_g_respawn_ghosts_maxtime / 2), 1.5);
        }
 
        CopyBody(1);
+
        self.effects |= EF_NODRAW; // prevent another CopyBody
-       if(self.oldcolormap)
-       {
-               self.colormap = self.oldcolormap;
-               self.oldcolormap = 0;
-       }
        PutClientInServer();
 }
 
@@ -1913,9 +1802,9 @@ void player_powerups (void)
                self.modelflags &~= MF_ROCKET;
        }
 
-       self.effects &~= (EF_DIMLIGHT | EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST);
+       self.effects &~= (EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST);
 
-       if(!self.modelindex || self.deadflag) // don't apply the flags if the player is gibbed
+       if(self.alpha < 0 || self.deadflag) // don't apply the flags if the player is gibbed
                return;
 
        Fire_ApplyDamage(self);
@@ -1950,7 +1839,7 @@ void player_powerups (void)
                if (self.items & IT_INVINCIBLE)
                {
                        play_countdown(self.invincible_finished, "misc/poweroff.wav");
-                       if (time > self.invincible_finished && autocvar_g_balance_powerup_timer)
+                       if (time > self.invincible_finished)
                        {
                                self.items = self.items - (self.items & IT_INVINCIBLE);
                                sprint(self, "^3Speed has worn off\n");
@@ -1971,7 +1860,7 @@ void player_powerups (void)
                {
                        play_countdown(self.strength_finished, "misc/poweroff.wav");
                        self.effects = self.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > self.strength_finished && autocvar_g_balance_powerup_timer)
+                       if (time > self.strength_finished)
                        {
                                self.items = self.items - (self.items & IT_STRENGTH);
                                sprint(self, "^3Strength has worn off\n");
@@ -1989,7 +1878,7 @@ void player_powerups (void)
                {
                        play_countdown(self.invincible_finished, "misc/poweroff.wav");
                        self.effects = self.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > self.invincible_finished && autocvar_g_balance_powerup_timer)
+                       if (time > self.invincible_finished)
                        {
                                self.items = self.items - (self.items & IT_INVINCIBLE);
                                sprint(self, "^3Shield has worn off\n");
@@ -2003,26 +1892,66 @@ void player_powerups (void)
                                sprint(self, "^3Shield surrounds you\n");
                        }
                }
+               if (self.items & IT_SUPERWEAPON)
+               {
+                       if (!WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
+                       {
+                               self.superweapons_finished = 0;
+                               self.items = self.items - (self.items & IT_SUPERWEAPON);
+                               sprint(self, "^3Superweapons have been lost\n");
+                       }
+                       else if (self.items & IT_UNLIMITED_SUPERWEAPONS)
+                       {
+                               // don't let them run out
+                       }
+                       else
+                       {
+                               play_countdown(self.superweapons_finished, "misc/poweroff.wav");
+                               if (time > self.superweapons_finished)
+                               {
+                                       self.items = self.items - (self.items & IT_SUPERWEAPON);
+                                       WEPSET_ANDNOT_EA(self, WEPBIT_SUPERWEAPONS);
+                                       sprint(self, "^3Superweapons have broken down\n");
+                               }
+                       }
+               }
+               else if(WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
+               {
+                       if (time < self.superweapons_finished || (self.items & IT_UNLIMITED_SUPERWEAPONS))
+                       {
+                               self.items = self.items | IT_SUPERWEAPON;
+                               sprint(self, "^3You now have a superweapon\n");
+                       }
+                       else
+                       {
+                               self.superweapons_finished = 0;
+                               WEPSET_ANDNOT_EA(self, WEPBIT_SUPERWEAPONS);
+                       }
+               }
+               else
+               {
+                       self.superweapons_finished = 0;
+               }
+       }
+       
+       if(autocvar_g_nodepthtestplayers)
+               self.effects = self.effects | EF_NODEPTHTEST;
 
-               if(autocvar_g_nodepthtestplayers)
-                       self.effects = self.effects | EF_NODEPTHTEST;
-
-               if(autocvar_g_fullbrightplayers)
-                       self.effects = self.effects | EF_FULLBRIGHT;
+       if(autocvar_g_fullbrightplayers)
+               self.effects = self.effects | EF_FULLBRIGHT;
 
-               // midair gamemode: damage only while in the air
-               // if in midair mode, being on ground grants temporary invulnerability
-               // (this is so that multishot weapon don't clear the ground flag on the
-               // first damage in the frame, leaving the player vulnerable to the
-               // remaining hits in the same frame)
-               if (self.flags & FL_ONGROUND)
-               if (g_midair)
-                       self.spawnshieldtime = max(self.spawnshieldtime, time + autocvar_g_midair_shieldtime);
+       // midair gamemode: damage only while in the air
+       // if in midair mode, being on ground grants temporary invulnerability
+       // (this is so that multishot weapon don't clear the ground flag on the
+       // first damage in the frame, leaving the player vulnerable to the
+       // remaining hits in the same frame)
+       if (self.flags & FL_ONGROUND)
+       if (g_midair)
+               self.spawnshieldtime = max(self.spawnshieldtime, time + autocvar_g_midair_shieldtime);
 
-               if (time >= game_starttime)
-               if (time < self.spawnshieldtime)
-                       self.effects = self.effects | (EF_ADDITIVE | EF_FULLBRIGHT);
-       }
+       if (time >= game_starttime)
+       if (time < self.spawnshieldtime)
+               self.effects = self.effects | (EF_ADDITIVE | EF_FULLBRIGHT);
 
        MUTATOR_CALLHOOK(PlayerPowerups);
 }
@@ -2193,6 +2122,15 @@ void GetPressedKeys(void) {
                self.pressedkeys |= KEY_CROUCH;
        else
                self.pressedkeys &~= KEY_CROUCH;
+
+       if (self.BUTTON_ATCK)
+               self.pressedkeys |= KEY_ATCK;
+       else
+               self.pressedkeys &~= KEY_ATCK;
+       if (self.BUTTON_ATCK2)
+               self.pressedkeys |= KEY_ATCK2;
+       else
+               self.pressedkeys &~= KEY_ATCK2;
 }
 
 /*
@@ -2223,7 +2161,7 @@ void SpectateCopy(entity spectatee) {
        self.strength_finished = spectatee.strength_finished;
        self.invincible_finished = spectatee.invincible_finished;
        self.pressedkeys = spectatee.pressedkeys;
-       self.weapons = spectatee.weapons;
+       WEPSET_COPY_EE(self, spectatee);
        self.switchweapon = spectatee.switchweapon;
        self.switchingweapon = spectatee.switchingweapon;
        self.weapon = spectatee.weapon;
@@ -2233,29 +2171,24 @@ void SpectateCopy(entity spectatee) {
        self.minelayer_mines = spectatee.minelayer_mines;
        self.punchangle = spectatee.punchangle;
        self.view_ofs = spectatee.view_ofs;
-       self.v_angle = spectatee.v_angle;
        self.velocity = spectatee.velocity;
        self.dmg_take = spectatee.dmg_take;
        self.dmg_save = spectatee.dmg_save;
        self.dmg_inflictor = spectatee.dmg_inflictor;
+       self.v_angle = spectatee.v_angle;
        self.angles = spectatee.v_angle;
        if(!self.BUTTON_USE)
                self.fixangle = TRUE;
        setorigin(self, spectatee.origin);
        setsize(self, spectatee.mins, spectatee.maxs);
        SetZoomState(spectatee.zoomstate);
-
-       anticheat_spectatecopy(spectatee);
-
-       //self.vehicle = spectatee.vehicle;
-
+    
+    anticheat_spectatecopy(spectatee);
        self.hud = spectatee.hud;
        if(spectatee.vehicle)
     {
-        setorigin(self, spectatee.origin);
-        self.velocity = spectatee.vehicle.velocity;
-        self.v_angle += spectatee.vehicle.angles;
-        //self.v_angle_x *= -1;
+        self.fixangle = FALSE;
+        //self.velocity = spectatee.vehicle.velocity;
         self.vehicle_health = spectatee.vehicle_health;
         self.vehicle_shield = spectatee.vehicle_shield;
         self.vehicle_energy = spectatee.vehicle_energy;
@@ -2263,11 +2196,18 @@ void SpectateCopy(entity spectatee) {
         self.vehicle_ammo2 = spectatee.vehicle_ammo2;
         self.vehicle_reload1 = spectatee.vehicle_reload1;
         self.vehicle_reload2 = spectatee.vehicle_reload2;
-        
+
         msg_entity = self;
-        WriteByte (MSG_ONE, SVC_SETVIEWPORT);
-        WriteEntity(MSG_ONE, spectatee);
-        //self.tur_head = spectatee.vehicle.vehicle_viewport;
+        
+        WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
+            WriteAngle(MSG_ONE,  spectatee.v_angle_x);
+            WriteAngle(MSG_ONE,  spectatee.v_angle_y);
+            WriteAngle(MSG_ONE,  spectatee.v_angle_z);
+
+        //WriteByte (MSG_ONE, SVC_SETVIEW);
+        //    WriteEntity(MSG_ONE, self);            
+        //makevectors(spectatee.v_angle);
+        //setorigin(self, spectatee.origin - v_forward * 400 + v_up * 300);*/    
     }
 }
 
@@ -2310,8 +2250,13 @@ entity CA_SpectateNext(entity start) {
        return other;
 }
 
-float SpectateNext() {
-       other = find(self.enemy, classname, "player");
+float SpectateNext(entity _prefer) {
+       
+       if(_prefer)
+               other = _prefer;        
+       else
+               other = find(self.enemy, classname, "player");
+       
        if (g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer) {
                // CA and ca players when spectating enemies is forbidden
                other = CA_SpectateNext(other);
@@ -2325,17 +2270,19 @@ float SpectateNext() {
                self.enemy = other;
 
        if(self.enemy.classname == "player") {
-           if(self.enemy.vehicle)
+           /*if(self.enemy.vehicle)
            {      
+            
             msg_entity = self;
-            WriteByte(MSG_ONE, SVC_SETVIEWPORT);
+            WriteByte(MSG_ONE, SVC_SETVIEW);
             WriteEntity(MSG_ONE, self.enemy);
             //stuffcmd(self, "set viewsize $tmpviewsize \n");
+            
             self.movetype = MOVETYPE_NONE;
             accuracy_resend(self);
            }
            else 
-           {           
+           {*/         
             msg_entity = self;
             WriteByte(MSG_ONE, SVC_SETVIEW);
             WriteEntity(MSG_ONE, self.enemy);
@@ -2345,7 +2292,7 @@ float SpectateNext() {
 
             if(!SpectateUpdate())
                 PutObserverInServer();
-        }
+        //}
         return 1;
        } else {
                return 0;
@@ -2366,13 +2313,13 @@ void ShowRespawnCountdown()
                return;
        else
        {
-               number = ceil(self.death_time - time);
+               number = ceil(self.respawn_time - time);
                if(number <= 0)
                        return;
                if(number <= self.respawn_countdown)
                {
                        self.respawn_countdown = number - 1;
-                       if(ceil(self.death_time - (time + 0.5)) == number) // only say it if it is the same number even in 0.5s; to prevent overlapping sounds
+                       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
                                AnnounceTo(self, strcat(ftos(number), ""));
                }
        }
@@ -2381,11 +2328,11 @@ void ShowRespawnCountdown()
 .float prevent_join_msgtime;
 void LeaveSpectatorMode()
 {
-       if(nJoinAllowed(1)) {
+       if(nJoinAllowed(self)) {
                if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (self.wasplayer && autocvar_g_changeteam_banned) || self.team_forced > 0) {
                        self.classname = "player";
 
-                       if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force)
+                       if(autocvar_g_campaign || autocvar_g_balance_teams)
                                JoinBestTeam(self, FALSE, TRUE);
 
                        if(autocvar_g_campaign)
@@ -2431,26 +2378,36 @@ void LeaveSpectatorMode()
  * it checks whether the number of currently playing players exceeds g_maxplayers.
  * @return int number of free slots for players, 0 if none
  */
-float nJoinAllowed(float includeMe) {
+float nJoinAllowed(entity ignore) {
+       if(!ignore)
+       // this is called that way when checking if anyone may be able to join (to build qcstatus)
+       // so report 0 free slots if restricted
+       {
+               if(autocvar_g_forced_team_otherwise == "spectate")
+                       return 0;
+               if(autocvar_g_forced_team_otherwise == "spectator")
+                       return 0;
+       }
+
        if(self.team_forced < 0)
-               return FALSE; // forced spectators can never join
+               return 0; // forced spectators can never join
 
        // TODO simplify this
        entity e;
-
-       float totalClients;
+       float totalClients = 0;
        FOR_EACH_CLIENT(e)
-               totalClients += 1;
+               if(e != ignore)
+                       totalClients += 1;
 
        if (!autocvar_g_maxplayers)
-               return maxclients - totalClients + includeMe;
+               return maxclients - totalClients;
 
-       float currentlyPlaying;
+       float currentlyPlaying = 0;
        FOR_EACH_REALPLAYER(e)
                currentlyPlaying += 1;
 
        if(currentlyPlaying < autocvar_g_maxplayers)
-               return min(maxclients - totalClients + includeMe, autocvar_g_maxplayers - currentlyPlaying);
+               return min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying);
 
        return 0;
 }
@@ -2513,7 +2470,7 @@ void ObserverThink()
                        self.flags |= FL_SPAWNING;
                } else if(self.BUTTON_ATCK && !self.version_mismatch) {
                        self.flags &~= FL_JUMPRELEASED;
-                       if(SpectateNext() == 1) {
+                       if(SpectateNext(world) == 1) {
                                self.classname = "spectator";
                        }
                } else {
@@ -2544,7 +2501,7 @@ void SpectatorThink()
                        self.flags |= FL_SPAWNING;
                } else if(self.BUTTON_ATCK) {
                        self.flags &~= FL_JUMPRELEASED;
-                       if(SpectateNext() == 1) {
+                       if(SpectateNext(world) == 1) {
                                self.classname = "spectator";
                        } else {
                                self.classname = "observer";
@@ -2708,7 +2665,7 @@ void PlayerPreThink (void)
                }
 
                //don't allow the player to turn around while game is paused!
-               if(timeoutStatus == 2) {
+               if(timeout_status == TIMEOUT_ACTIVE) {
                        // FIXME turn this into CSQC stuff
                        self.v_angle = self.lastV_angle;
                        self.angles = self.lastV_angle;
@@ -2748,9 +2705,9 @@ void PlayerPreThink (void)
                        float button_pressed, force_respawn;
                        if(self.personal && g_race_qualifying)
                        {
-                               if(time > self.death_time)
+                               if(time > self.respawn_time)
                                {
-                                       self.death_time = time + 1; // only retry once a second
+                                       self.respawn_time = time + 1; // only retry once a second
                                        respawn();
                                        self.impulse = 141;
                                }
@@ -2780,9 +2737,9 @@ void PlayerPreThink (void)
                                }
                                else if (self.deadflag == DEAD_RESPAWNING)
                                {
-                                       if(time > self.death_time)
+                                       if(time > self.respawn_time)
                                        {
-                                               self.death_time = time + 1; // only retry once a second
+                                               self.respawn_time = time + 1; // only retry once a second
                                                respawn();
                                        }
                                }
@@ -2842,6 +2799,7 @@ void PlayerPreThink (void)
 
                self.prevorigin = self.origin;
 
+               if (!self.vehicle)
                if (((self.BUTTON_CROUCH && !self.hook.state) || self.health <= g_bloodloss) && self.animstate_startframe != self.anim_melee_x) // prevent crouching if using melee attack
                {
                        if (!self.crouch)
@@ -3028,10 +2986,8 @@ void PlayerPostThink (void)
                stuffcmd(self, strcat("name ", self.netname, substring(ftos(random()), 2, -1), "\n"));
        }
 
-       if(sv_maxidle && frametime)
+       if(sv_maxidle && frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
        {
-               // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
-               float timeleft;
                if (time - self.parm_idlesince < 1) // instead of (time == self.parm_idlesince) to support sv_maxidle <= 10
                {
                        if(self.idlekick_lasttimeleft)
@@ -3039,34 +2995,37 @@ void PlayerPostThink (void)
                                Send_CSQC_Centerprint_Generic_Expire(self, CPID_DISCONNECT_IDLING);
                                self.idlekick_lasttimeleft = 0;
                        }
-                       return;
-               }
-               timeleft = ceil(sv_maxidle - (time - self.parm_idlesince));
-               if(timeleft == min(10, sv_maxidle - 1)) // - 1 to support sv_maxidle <= 10
-               {
-                       if(!self.idlekick_lasttimeleft)
-                               Send_CSQC_Centerprint_Generic(self, CPID_DISCONNECT_IDLING, "^3Stop idling!\n^3Disconnecting in %d seconds...", 1, timeleft);
-               }
-               if(timeleft <= 0)
-               {
-                       bprint("^3", self.netname, "^3 was kicked for idling.\n");
-                       AnnounceTo(self, "terminated");
-                       dropclient(self);
-                       return;
                }
-               else if(timeleft <= 10)
+               else
                {
-                       if(timeleft != self.idlekick_lasttimeleft)
-                               AnnounceTo(self, ftos(timeleft));
-                       self.idlekick_lasttimeleft = timeleft;
+                       float timeleft;
+                       timeleft = ceil(sv_maxidle - (time - self.parm_idlesince));
+                       if(timeleft == min(10, sv_maxidle - 1)) // - 1 to support sv_maxidle <= 10
+                       {
+                               if(!self.idlekick_lasttimeleft)
+                                       Send_CSQC_Centerprint_Generic(self, CPID_DISCONNECT_IDLING, "^3Stop idling!\n^3Disconnecting in %d seconds...", 1, timeleft);
+                       }
+                       if(timeleft <= 0)
+                       {
+                               bprint("^3", self.netname, "^3 was kicked for idling.\n");
+                               AnnounceTo(self, "terminated");
+                               dropclient(self);
+                               return;
+                       }
+                       else if(timeleft <= 10)
+                       {
+                               if(timeleft != self.idlekick_lasttimeleft)
+                                       AnnounceTo(self, ftos(timeleft));
+                               self.idlekick_lasttimeleft = timeleft;
+                       }
                }
        }
 
 #ifdef TETRIS
        if(self.impulse == 100)
                ImpulseCommands();
-       if (TetrisPostFrame())
-               return;
+       if (!TetrisPostFrame())
+       {
 #endif
 
        CheatFrame();
@@ -3087,6 +3046,10 @@ void PlayerPostThink (void)
                //do nothing
        }
        
+#ifdef TETRIS
+       }
+#endif
+
        /*
        float i;
        for(i = 0; i < 1000; ++i)
@@ -3103,8 +3066,6 @@ void PlayerPostThink (void)
        }
        */
 
-       Arena_Warmup();
-
        //pointparticles(particleeffectnum("machinegun_impact"), self.origin + self.view_ofs + '0 0 7', '0 0 0', 1);
 
        if(self.waypointsprite_attachedforcarrier)
@@ -3150,22 +3111,5 @@ void PlayerPostThink (void)
                dprint(sprintf("%f %.6f\n", time, race_GetFractionalLapCount(self)));
        */
 
-#ifndef NO_LEGACY_NETWORKING
-       if(autocvar_sv_use_csqc_players && !self.iscsqcmodel)
-       {
-               CSQCModel_LinkEntity();
-               self.iscsqcmodel = 1;
-       }
-
-       if(!autocvar_sv_use_csqc_players && self.iscsqcmodel)
-       {
-               CSQCModel_UnlinkEntity();
-               self.iscsqcmodel = 0;
-       }
-
-       if(self.iscsqcmodel)
-               CSQCModel_CheckUpdate();
-#else
-       CSQCModel_CheckUpdate();
-#endif
+       CSQCMODEL_AUTOUPDATE();
 }