]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/client.qc
Add an option (off by default) to allow the vortex to charge even while not in hand
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / client.qc
index 9171444d8762ae3833a7fda6efc4e2dec07dcbe5..5029da7df9613f216a5ba89cedd44b70b647e801 100644 (file)
@@ -40,6 +40,7 @@
 #include "../common/mapobjects/teleporters.qh"
 #include "../common/mapobjects/target/spawnpoint.qh"
 #include <common/mapobjects/trigger/counter.qh>
+#include <common/mapobjects/trigger/swamp.qh>
 
 #include "../common/vehicles/all.qh"
 
@@ -232,7 +233,7 @@ void PutObserverInServer(entity this)
 
        if (IS_PLAYER(this))
        {
-               if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1)
+               if(GetResource(this, RES_HEALTH) >= 1)
                {
                        // despawn effect
                        Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
@@ -274,7 +275,7 @@ void PutObserverInServer(entity this)
 
     RemoveGrapplingHooks(this);
        Portal_ClearAll(this);
-       Unfreeze(this);
+       Unfreeze(this, false);
        SetSpectatee(this, NULL);
 
        if (this.alivetime)
@@ -311,14 +312,14 @@ void PutObserverInServer(entity this)
        if(this.damagedbycontents)
                IL_REMOVE(g_damagedbycontents, this);
        this.damagedbycontents = false;
-       SetResourceAmountExplicit(this, RESOURCE_HEALTH, FRAGS_SPECTATOR);
+       SetResourceExplicit(this, RES_HEALTH, FRAGS_SPECTATOR);
        SetSpectatee_status(this, etof(this));
        this.takedamage = DAMAGE_NO;
        this.solid = SOLID_NOT;
        set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink
        this.flags = FL_CLIENT | FL_NOTARGET;
        this.effects = 0;
-       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_balance_armor_start); // was 666?!
+       SetResourceExplicit(this, RES_ARMOR, autocvar_g_balance_armor_start); // was 666?!
        this.pauserotarmor_finished = 0;
        this.pauserothealth_finished = 0;
        this.pauseregen_finished = 0;
@@ -347,6 +348,7 @@ void PutObserverInServer(entity this)
        this.crouch = false;
        STAT(REVIVE_PROGRESS, this) = 0;
        this.revival_time = 0;
+       this.draggable = drag_undraggable;
 
        this.items = 0;
        STAT(WEAPONS, this) = '0 0 0';
@@ -380,7 +382,7 @@ void PutObserverInServer(entity this)
                if(axh.owner == this && axh != NULL && !wasfreed(axh))
                        delete(axh);
        }
-       
+
        if (mutator_returnvalue)
        {
                // mutator prevents resetting teams+score
@@ -389,7 +391,9 @@ void PutObserverInServer(entity this)
        {
                SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
                this.frags = FRAGS_SPECTATOR;
-    }
+       }
+       if (CS(this).just_joined)
+               CS(this).just_joined = false;
 }
 
 int player_getspecies(entity this)
@@ -554,24 +558,24 @@ void PutPlayerInServer(entity this)
        this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
 
        if (warmup_stage) {
-               SetResourceAmount(this, RESOURCE_SHELLS, warmup_start_ammo_shells);
-               SetResourceAmount(this, RESOURCE_BULLETS, warmup_start_ammo_nails);
-               SetResourceAmount(this, RESOURCE_ROCKETS, warmup_start_ammo_rockets);
-               SetResourceAmount(this, RESOURCE_CELLS, warmup_start_ammo_cells);
-               SetResourceAmount(this, RESOURCE_PLASMA, warmup_start_ammo_plasma);
-               SetResourceAmount(this, RESOURCE_FUEL, warmup_start_ammo_fuel);
-               SetResourceAmount(this, RESOURCE_HEALTH, warmup_start_health);
-               SetResourceAmount(this, RESOURCE_ARMOR, warmup_start_armorvalue);
+               SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
+               SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
+               SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
+               SetResource(this, RES_CELLS, warmup_start_ammo_cells);
+               SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
+               SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
+               SetResource(this, RES_HEALTH, warmup_start_health);
+               SetResource(this, RES_ARMOR, warmup_start_armorvalue);
                STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
        } else {
-               SetResourceAmount(this, RESOURCE_SHELLS, start_ammo_shells);
-               SetResourceAmount(this, RESOURCE_BULLETS, start_ammo_nails);
-               SetResourceAmount(this, RESOURCE_ROCKETS, start_ammo_rockets);
-               SetResourceAmount(this, RESOURCE_CELLS, start_ammo_cells);
-               SetResourceAmount(this, RESOURCE_PLASMA, start_ammo_plasma);
-               SetResourceAmount(this, RESOURCE_FUEL, start_ammo_fuel);
-               SetResourceAmount(this, RESOURCE_HEALTH, start_health);
-               SetResourceAmount(this, RESOURCE_ARMOR, start_armorvalue);
+               SetResource(this, RES_SHELLS, start_ammo_shells);
+               SetResource(this, RES_BULLETS, start_ammo_nails);
+               SetResource(this, RES_ROCKETS, start_ammo_rockets);
+               SetResource(this, RES_CELLS, start_ammo_cells);
+               SetResource(this, RES_PLASMA, start_ammo_plasma);
+               SetResource(this, RES_FUEL, start_ammo_fuel);
+               SetResource(this, RES_HEALTH, start_health);
+               SetResource(this, RES_ARMOR, start_armorvalue);
                STAT(WEAPONS, this) = start_weapons;
                if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
                {
@@ -601,7 +605,7 @@ void PutPlayerInServer(entity this)
                this.pauseregen_finished += f;
        }
 
-       this.damageforcescale = 2;
+       this.damageforcescale = autocvar_g_player_damageforcescale;
        this.death_time = 0;
        this.respawn_flags = 0;
        this.respawn_time = 0;
@@ -621,7 +625,10 @@ void PutPlayerInServer(entity this)
        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;
+               bot_aim_reset(this);
+       }
        this.fixangle = true; // turn this way immediately
        this.oldvelocity = this.velocity = '0 0 0';
        this.avelocity = '0 0 0';
@@ -634,6 +641,10 @@ void PutPlayerInServer(entity this)
        STAT(REVIVE_PROGRESS, this) = 0;
        this.revival_time = 0;
 
+       // TODO: we can't set these in the PlayerSpawn hook since the target code is called before it!
+       STAT(BUFFS, this) = 0;
+       STAT(BUFF_TIME, this) = 0;
+
        this.air_finished = time + 12;
        this.waterlevel = WATERLEVEL_NONE;
        this.watertype = CONTENT_EMPTY;
@@ -672,11 +683,16 @@ void PutPlayerInServer(entity this)
        if(this.conveyor)
                IL_REMOVE(g_conveyed, this);
        this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player
+       if(this.swampslug)
+               delete(this.swampslug);
+       this.in_swamp = false;
        STAT(HUD, this) = HUD_NORMAL;
 
        this.event_damage = PlayerDamage;
        this.event_heal = PlayerHeal;
 
+       this.draggable = func_null;
+
        if(!this.bot_attack)
                IL_PUSH(g_bot_targets, this);
        this.bot_attack = true;
@@ -727,13 +743,15 @@ void PutPlayerInServer(entity this)
        });
 
        {
-               //string s = spot.target;
-               //spot.target = string_null;
+               string s = spot.target;
+               if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
+                       spot.target = string_null;
                SUB_UseTargets(spot, this, NULL);
-               //spot.target = s;
+               if(g_assault || g_race)
+                       spot.target = s;
        }
 
-       Unfreeze(this);
+       Unfreeze(this, false);
 
        MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
 
@@ -1051,6 +1069,8 @@ string getwelcomemessage(entity this)
        return s;
 }
 
+bool autocvar_sv_qcphysics = false; // TODO this is for testing - remove when qcphysics work
+
 /**
 =============
 ClientConnect
@@ -1143,6 +1163,8 @@ void ClientConnect(entity this)
        if (IS_REAL_CLIENT(this))
                sv_notice_join(this);
 
+       this.move_qcphysics = autocvar_sv_qcphysics;
+
        // update physics stats (players can spawn before physics runs)
        Physics_UpdateStats(this);
 
@@ -1196,7 +1218,7 @@ void ClientDisconnect(entity this)
 
        Portal_ClearAll(this);
 
-       Unfreeze(this);
+       Unfreeze(this, false);
 
        RemoveGrapplingHooks(this);
 
@@ -1307,12 +1329,14 @@ void respawn(entity this)
        PutClientInServer(this);
 }
 
+ERASEABLE
 void PrintToChat(entity client, string text)
 {
        text = strcat("\{1}^7", text, "\n");
        sprint(client, text);
 }
 
+ERASEABLE
 void DebugPrintToChat(entity client, string text)
 {
        if (autocvar_developer)
@@ -1321,12 +1345,14 @@ void DebugPrintToChat(entity client, string text)
        }
 }
 
+ERASEABLE
 void PrintToChatAll(string text)
 {
        text = strcat("\{1}^7", text, "\n");
        bprint(text);
 }
 
+ERASEABLE
 void DebugPrintToChatAll(string text)
 {
        if (autocvar_developer)
@@ -1335,6 +1361,7 @@ void DebugPrintToChatAll(string text)
        }
 }
 
+ERASEABLE
 void PrintToChatTeam(int team_num, string text)
 {
        text = strcat("\{1}^7", text, "\n");
@@ -1347,6 +1374,7 @@ void PrintToChatTeam(int team_num, string text)
        });
 }
 
+ERASEABLE
 void DebugPrintToChatTeam(int team_num, string text)
 {
        if (autocvar_developer)
@@ -1563,25 +1591,25 @@ void player_regen(entity this)
                float mina, maxa, limith, limita;
                maxa = autocvar_g_balance_armor_rotstable;
                mina = autocvar_g_balance_armor_regenstable;
-               limith = GetResourceLimit(this, RESOURCE_HEALTH);
-               limita = GetResourceLimit(this, RESOURCE_ARMOR);
+               limith = GetResourceLimit(this, RES_HEALTH);
+               limita = GetResourceLimit(this, RES_ARMOR);
 
                regen_health_rotstable = regen_health_rotstable * max_mod;
                regen_health_stable = regen_health_stable * max_mod;
                limith = limith * limit_mod;
                limita = limita * limit_mod;
 
-               SetResourceAmount(this, RESOURCE_ARMOR, CalcRotRegen(GetResourceAmount(this, RESOURCE_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 
+               SetResource(this, RES_ARMOR, CalcRotRegen(GetResource(this, RES_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 
                                                                        regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear,
                                                                        rot_mod * frametime * (time > this.pauserotarmor_finished), limita));
-               SetResourceAmount(this, RESOURCE_HEALTH, CalcRotRegen(GetResourceAmount(this, RESOURCE_HEALTH), regen_health_stable, regen_health, regen_health_linear,
+               SetResource(this, RES_HEALTH, CalcRotRegen(GetResource(this, RES_HEALTH), regen_health_stable, regen_health, regen_health_linear,
                                                                        regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear,
                                                                        rot_mod * frametime * (time > this.pauserothealth_finished), limith));
        }
 
        // if player rotted to death...  die!
        // check this outside above checks, as player may still be able to rot to death
-       if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
+       if(GetResource(this, RES_HEALTH) < 1)
        {
                if(this.vehicle)
                        vehicles_exit(this.vehicle, VHEF_RELEASE);
@@ -1595,9 +1623,9 @@ void player_regen(entity this)
 
                maxf = autocvar_g_balance_fuel_rotstable;
                minf = autocvar_g_balance_fuel_regenstable;
-               limitf = GetResourceLimit(this, RESOURCE_FUEL);
+               limitf = GetResourceLimit(this, RES_FUEL);
 
-               SetResourceAmount(this, RESOURCE_FUEL, CalcRotRegen(GetResourceAmount(this, RESOURCE_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, 
+               SetResource(this, RES_FUEL, CalcRotRegen(GetResource(this, RES_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, 
                                                                                frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0),
                                                                                maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf));
        }
@@ -1645,15 +1673,15 @@ void SpectateCopy(entity this, entity spectatee)
        MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
        PS(this) = PS(spectatee);
        this.armortype = spectatee.armortype;
-       SetResourceAmountExplicit(this, RESOURCE_ARMOR, GetResourceAmount(spectatee, RESOURCE_ARMOR));
-       SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(spectatee, RESOURCE_CELLS));
-       SetResourceAmountExplicit(this, RESOURCE_PLASMA, GetResourceAmount(spectatee, RESOURCE_PLASMA));
-       SetResourceAmountExplicit(this, RESOURCE_SHELLS, GetResourceAmount(spectatee, RESOURCE_SHELLS));
-       SetResourceAmountExplicit(this, RESOURCE_BULLETS, GetResourceAmount(spectatee, RESOURCE_BULLETS));
-       SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(spectatee, RESOURCE_ROCKETS));
-       SetResourceAmountExplicit(this, RESOURCE_FUEL, GetResourceAmount(spectatee, RESOURCE_FUEL));
+       SetResourceExplicit(this, RES_ARMOR, GetResource(spectatee, RES_ARMOR));
+       SetResourceExplicit(this, RES_CELLS, GetResource(spectatee, RES_CELLS));
+       SetResourceExplicit(this, RES_PLASMA, GetResource(spectatee, RES_PLASMA));
+       SetResourceExplicit(this, RES_SHELLS, GetResource(spectatee, RES_SHELLS));
+       SetResourceExplicit(this, RES_BULLETS, GetResource(spectatee, RES_BULLETS));
+       SetResourceExplicit(this, RES_ROCKETS, GetResource(spectatee, RES_ROCKETS));
+       SetResourceExplicit(this, RES_FUEL, GetResource(spectatee, RES_FUEL));
        this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
-       SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(spectatee, RESOURCE_HEALTH));
+       SetResourceExplicit(this, RES_HEALTH, GetResource(spectatee, RES_HEALTH));
        CS(this).impulse = 0;
        this.items = spectatee.items;
        STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
@@ -1911,6 +1939,14 @@ void Join(entity this)
        this.team_selected = false;
 }
 
+int GetPlayerLimit()
+{
+       int player_limit = autocvar_g_maxplayers;
+       MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
+       player_limit = M_ARGV(0, int);
+       return player_limit;
+}
+
 /**
  * Determines whether the player is allowed to join. This depends on cvar
  * g_maxplayers, if it isn't used this function always return true, otherwise
@@ -1943,11 +1979,13 @@ int nJoinAllowed(entity this, entity ignore)
                        ++currentlyPlaying;
        });
 
+       int player_limit = GetPlayerLimit();
+
        float free_slots = 0;
-       if (!autocvar_g_maxplayers)
+       if (!player_limit)
                free_slots = maxclients - totalClients;
-       else if(currentlyPlaying < autocvar_g_maxplayers)
-               free_slots = min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying);
+       else if(currentlyPlaying < player_limit)
+               free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
 
        static float join_prevent_msg_time = 0;
        if(this && ignore && !free_slots && time > join_prevent_msg_time)
@@ -2014,12 +2052,21 @@ void PrintWelcomeMessage(entity this)
        {
                if(PHYS_INPUT_BUTTON_INFO(this)) // BUTTON_INFO hides initial MOTD
                        CS(this).motd_actived_time = -2; // wait until BUTTON_INFO gets released
-               else if(CS(this).motd_actived_time == -2 || IS_PLAYER(this) || IS_SPEC(this))
+               else if (CS(this).motd_actived_time == -2)
                {
-                       // instanctly hide MOTD
+                       // instantly hide MOTD
                        CS(this).motd_actived_time = 0;
                        Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
                }
+               else if (IS_PLAYER(this) || IS_SPEC(this))
+               {
+                       // FIXME occasionally for some reason MOTD never goes away
+                       // delay MOTD removal a little bit in the hope it fixes this bug
+                       if (CS(this).motd_actived_time == -1) // MOTD marked to fade away as soon as client becomes player or spectator
+                               CS(this).motd_actived_time = -(5 + floor(random() * 10)); // add small delay
+                       else //if (CS(this).motd_actived_time < -2)
+                               CS(this).motd_actived_time++;
+               }
        }
 }
 
@@ -2147,11 +2194,13 @@ bool PlayerThink(entity this)
                for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
                        .entity weaponentity = weaponentities[slot];
+                       if(WEP_CVAR(vortex, charge_always))
+                               W_Vortex_Charge(this, weaponentity, frametime);
                        W_WeaponFrame(this, weaponentity);
                }
 
                this.items_added = 0;
-               if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || GetResourceAmount(this, RESOURCE_FUEL) >= 0.01))
+               if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || GetResource(this, RES_FUEL) >= 0.01))
             this.items_added |= IT_FUEL;
 
                this.items |= this.items_added;
@@ -2300,19 +2349,15 @@ void PlayerUseKey(entity this)
        }
        else if(autocvar_g_vehicles_enter)
        {
-               if(!STAT(FROZEN, this))
-               if(!IS_DEAD(this))
-               if(!game_stopped)
+               if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
                {
                        entity head, closest_target = NULL;
                        head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
 
                        while(head) // find the closest acceptable target to enter
                        {
-                               if(IS_VEHICLE(head))
-                               if(!IS_DEAD(head))
+                               if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO)
                                if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this)))
-                               if(head.takedamage != DAMAGE_NO)
                                {
                                        if(closest_target)
                                        {
@@ -2366,9 +2411,18 @@ void PlayerPreThink (entity this)
        if (this.netname == "" || this.netname != CS(this).netname_previous)
        {
                bool assume_unchanged = (CS(this).netname_previous == "");
+               if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
+               {
+                       int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
+                       this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
+                       sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
+                       assume_unchanged = false;
+                       // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
+               }
                if (isInvisibleString(this.netname))
                {
                        this.netname = strzone(sprintf("Player#%d", this.playerid));
+                       sprint(this, "Warning: invisible names are not allowed.\n");
                        assume_unchanged = false;
                        // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
                }
@@ -2404,21 +2458,21 @@ void PlayerPreThink (entity this)
 
        if(IS_PLAYER(this))
        {
-               if (STAT(FROZEN, this) == 2)
+               if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
-                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
+                       SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
                        this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
 
                        if (STAT(REVIVE_PROGRESS, this) >= 1)
-                               Unfreeze(this);
+                               Unfreeze(this, false);
                }
-               else if (STAT(FROZEN, this) == 3)
+               else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
-                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
+                       SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
 
-                       if (GetResourceAmount(this, RESOURCE_HEALTH) < 1)
+                       if (GetResource(this, RES_HEALTH) < 1)
                        {
                                if (this.vehicle)
                                        vehicles_exit(this.vehicle, VHEF_RELEASE);
@@ -2426,29 +2480,28 @@ void PlayerPreThink (entity this)
                                        this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
                        }
                        else if (STAT(REVIVE_PROGRESS, this) <= 0)
-                               Unfreeze(this);
+                               Unfreeze(this, false);
                }
        }
 
        MUTATOR_CALLHOOK(PlayerPreThink, this);
 
        if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
-       if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this))
+       if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
        {
-               FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it),
+               FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
                {
-                       if(!IS_DEAD(it) && it.takedamage != DAMAGE_NO)
-                       if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
-                       {
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
-                       }
-                       else if(!it.owner)
+                       if(!it.owner)
                        {
                                if(!it.team || SAME_TEAM(this, it))
                                        Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
                                else if(autocvar_g_vehicles_steal)
                                        Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
                        }
+                       else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
+                       {
+                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
+                       }
                });
 
                this.last_vehiclecheck = time + 1;
@@ -2562,7 +2615,7 @@ void DrownPlayer(entity this)
 
 void Player_Physics(entity this)
 {
-       set_movetype(this, this.move_movetype);
+       this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
 
        if(!this.move_qcphysics)
                return;
@@ -2660,13 +2713,344 @@ void PlayerPostThink (entity this)
        }
 
        if (this.waypointsprite_attachedforcarrier) {
-           vector v = healtharmor_maxdamage(GetResourceAmount(this, RESOURCE_HEALTH), GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id);
-               WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, '1 0 0' * v);
-    }
+               float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
+               WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
+       }
 
        CSQCMODEL_AUTOUPDATE(this);
 }
 
+/**
+ * message "": do not say, just test flood control
+ * return value:
+ *   1 = accept
+ *   0 = reject
+ *  -1 = fake accept
+ */
+int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
+{
+       if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
+               msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
+
+       if (source)
+               msgin = formatmessage(source, msgin);
+
+       string colorstr;
+       if (!(IS_PLAYER(source) || source.caplayer))
+               colorstr = "^0"; // black for spectators
+       else if(teamplay)
+               colorstr = Team_ColorCode(source.team);
+       else
+       {
+               colorstr = "";
+               teamsay = false;
+       }
+
+       if (!source) {
+               colorstr = "";
+               teamsay = false;
+       }
+
+       if(msgin != "")
+               msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
+
+       /*
+        * using bprint solves this... me stupid
+       // how can we prevent the message from appearing in a listen server?
+       // for now, just give "say" back and only handle say_team
+       if(!teamsay)
+       {
+               clientcommand(source, strcat("say ", msgin));
+               return;
+       }
+       */
+
+       string namestr = "";
+       if (source)
+               namestr = playername(source, autocvar_g_chat_teamcolors);
+
+       string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
+
+       string msgstr = "", cmsgstr = "";
+       string privatemsgprefix = string_null;
+       int privatemsgprefixlen = 0;
+       if (msgin != "")
+       {
+               bool found_me = false;
+               if(strstrofs(msgin, "/me", 0) >= 0)
+               {
+                       string newmsgin = "";
+                       string newnamestr = ((teamsay) ? strcat(colorstr, "(", colorprefix, namestr, colorstr, ")", "^7") : strcat(colorprefix, namestr, "^7"));
+                       FOREACH_WORD(msgin, true,
+                       {
+                               if(strdecolorize(it) == "/me")
+                               {
+                                       found_me = true;
+                                       newmsgin = cons(newmsgin, newnamestr);
+                               }
+                               else
+                                       newmsgin = cons(newmsgin, it);
+                       });
+                       msgin = newmsgin;
+               }
+
+               if(privatesay)
+               {
+                       msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
+                       privatemsgprefixlen = strlen(msgstr);
+                       msgstr = strcat(msgstr, msgin);
+                       cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
+                       privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7");
+               }
+               else if(teamsay)
+               {
+                       if(found_me)
+                       {
+                               //msgin = strreplace("/me", "", msgin);
+                               //msgin = substring(msgin, 3, strlen(msgin));
+                               //msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
+                               msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
+                       }
+                       else
+                               msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+                       cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
+               }
+               else
+               {
+                       if(found_me)
+                       {
+                               //msgin = strreplace("/me", "", msgin);
+                               //msgin = substring(msgin, 3, strlen(msgin));
+                               //msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
+                               msgstr = strcat("\{1}^4* ^7", msgin);
+                       }
+                       else {
+                               msgstr = "\{1}";
+                               msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
+                               msgstr = strcat(msgstr, msgin);
+                       }
+                       cmsgstr = "";
+               }
+               msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
+       }
+
+       string fullmsgstr = msgstr;
+       string fullcmsgstr = cmsgstr;
+
+       // FLOOD CONTROL
+       int flood = 0;
+       var .float flood_field = floodcontrol_chat;
+       if(floodcontrol && source)
+       {
+               float flood_spl;
+               float flood_burst;
+               float flood_lmax;
+               float lines;
+               if(privatesay)
+               {
+                       flood_spl = autocvar_g_chat_flood_spl_tell;
+                       flood_burst = autocvar_g_chat_flood_burst_tell;
+                       flood_lmax = autocvar_g_chat_flood_lmax_tell;
+                       flood_field = floodcontrol_chattell;
+               }
+               else if(teamsay)
+               {
+                       flood_spl = autocvar_g_chat_flood_spl_team;
+                       flood_burst = autocvar_g_chat_flood_burst_team;
+                       flood_lmax = autocvar_g_chat_flood_lmax_team;
+                       flood_field = floodcontrol_chatteam;
+               }
+               else
+               {
+                       flood_spl = autocvar_g_chat_flood_spl;
+                       flood_burst = autocvar_g_chat_flood_burst;
+                       flood_lmax = autocvar_g_chat_flood_lmax;
+                       flood_field = floodcontrol_chat;
+               }
+               flood_burst = max(0, flood_burst - 1);
+               // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
+
+               // do flood control for the default line size
+               if(msgstr != "")
+               {
+                       getWrappedLine_remaining = msgstr;
+                       msgstr = "";
+                       lines = 0;
+                       while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
+                       {
+                               msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
+                               ++lines;
+                       }
+                       msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
+
+                       if(getWrappedLine_remaining != "")
+                       {
+                               msgstr = strcat(msgstr, "\n");
+                               flood = 2;
+                       }
+
+                       if (time >= source.(flood_field))
+                       {
+                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
+                       }
+                       else
+                       {
+                               flood = 1;
+                               msgstr = fullmsgstr;
+                       }
+               }
+               else
+               {
+                       if (time >= source.(flood_field))
+                               source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
+                       else
+                               flood = 1;
+               }
+
+               if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
+                       source.(flood_field) = flood = 0;
+       }
+
+       string sourcemsgstr, sourcecmsgstr;
+       if(flood == 2) // cannot happen for empty msgstr
+       {
+               if(autocvar_g_chat_flood_notify_flooder)
+               {
+                       sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
+                       sourcecmsgstr = "";
+               }
+               else
+               {
+                       sourcemsgstr = fullmsgstr;
+                       sourcecmsgstr = fullcmsgstr;
+               }
+               cmsgstr = "";
+       }
+       else
+       {
+               sourcemsgstr = msgstr;
+               sourcecmsgstr = cmsgstr;
+       }
+
+       if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+       {
+               if (!game_stopped)
+               if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
+                       teamsay = -1; // spectators
+       }
+
+       if(flood)
+               LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.");
+
+       // build sourcemsgstr by cutting off a prefix and replacing it by the other one
+       if(privatesay)
+               sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
+
+       int ret;
+       if(source && CS(source).muted)
+       {
+               // always fake the message
+               ret = -1;
+       }
+       else if(flood == 1)
+       {
+               if (autocvar_g_chat_flood_notify_flooder)
+               {
+                       sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
+                       ret = 0;
+               }
+               else
+                       ret = -1;
+       }
+       else
+       {
+               ret = 1;
+       }
+
+       if (privatesay && source && !(IS_PLAYER(source) || source.caplayer))
+       {
+               if (!game_stopped)
+               if ((privatesay && (IS_PLAYER(privatesay) || privatesay.caplayer)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)))
+                       ret = -1; // just hide the message completely
+       }
+
+       MUTATOR_CALLHOOK(ChatMessage, source, ret);
+       ret = M_ARGV(1, int);
+
+       string event_log_msg = "";
+
+       if(sourcemsgstr != "" && ret != 0)
+       {
+               if(ret < 0) // faked message, because the player is muted
+               {
+                       sprint(source, sourcemsgstr);
+                       if(sourcecmsgstr != "" && !privatesay)
+                               centerprint(source, sourcecmsgstr);
+               }
+               else if(privatesay) // private message, between 2 people only
+               {
+                       sprint(source, sourcemsgstr);
+                       if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
+                       if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
+                       {
+                               sprint(privatesay, msgstr);
+                               if(cmsgstr != "")
+                                       centerprint(privatesay, cmsgstr);
+                       }
+               }
+               else if ( teamsay && CS(source).active_minigame )
+               {
+                       sprint(source, sourcemsgstr);
+                       dedicated_print(msgstr); // send to server console too
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
+                       event_log_msg = sprintf(":chat_minigame:%d:%s:%s", source.playerid, CS(source).active_minigame.netname, msgin);
+
+               }
+               else if(teamsay > 0) // team message, only sent to team mates
+               {
+                       sprint(source, sourcemsgstr);
+                       dedicated_print(msgstr); // send to server console too
+                       if(sourcecmsgstr != "")
+                               centerprint(source, sourcecmsgstr);
+                       FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                               if(cmsgstr != "")
+                                       centerprint(it, cmsgstr);
+                       });
+                       event_log_msg = sprintf(":chat_team:%d:%d:%s", source.playerid, source.team, strreplace("\n", " ", msgin));
+               }
+               else if(teamsay < 0) // spectator message, only sent to spectators
+               {
+                       sprint(source, sourcemsgstr);
+                       dedicated_print(msgstr); // send to server console too
+                       FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
+                       event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin));
+               }
+               else
+               {
+                       if (source) {
+                               sprint(source, sourcemsgstr);
+                               dedicated_print(msgstr); // send to server console too
+                               MX_Say(strcat(playername(source, true), "^7: ", msgin));
+                       }
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
+                               sprint(it, msgstr);
+                       });
+                       event_log_msg = sprintf(":chat:%d:%s", source.playerid, strreplace("\n", " ", msgin));
+               }
+       }
+
+       if (autocvar_sv_eventlog && (event_log_msg != "")) {
+               GameLogEcho(event_log_msg);
+       }
+
+       return ret;
+}
+
 // hack to copy the button fields from the client entity to the Client State
 void PM_UpdateButtons(entity this, entity store)
 {
@@ -2674,7 +3058,7 @@ void PM_UpdateButtons(entity this, entity store)
                store.impulse = this.impulse;
        this.impulse = 0;
 
-       bool typing = this.buttonchat || this.button14;
+       bool typing = this.buttonchat || this.button12;
 
        store.button0 = (typing) ? 0 : this.button0;
        //button1?!