]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/cl_client.qc
Merge branch 'master' into Mario/user_movetypes
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / cl_client.qc
index a47e14e162b1d39042fe4efc0f70fa85e2a2a80f..0a608e979bff5da3e4b535fed7019bc0a9fb2301 100644 (file)
@@ -19,8 +19,7 @@
 #include "campaign.qh"
 #include "command/common.qh"
 
-#include "bot/bot.qh"
-#include "bot/navigation.qh"
+#include "bot/api.qh"
 
 #include "../common/ent_cs.qh"
 #include <common/state.qh>
@@ -75,6 +74,30 @@ void send_CSQC_teamnagger() {
        WriteHeader(MSG_BROADCAST, TE_CSQC_TEAMNAGGER);
 }
 
+int CountSpectators(entity player, entity to)
+{
+       if(!player) { return 0; } // not sure how, but best to be safe
+
+       int spec_count = 0;
+
+       FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player,
+       {
+               spec_count++;
+       });
+
+       return spec_count;
+}
+
+void WriteSpectators(entity player, entity to)
+{
+       if(!player) { return; } // not sure how, but best to be safe
+
+       FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player,
+       {
+               WriteByte(MSG_ENTITY, num_for_edict(it));
+       });
+}
+
 bool ClientData_Send(entity this, entity to, int sf)
 {
        assert(to == this.owner, return false);
@@ -87,6 +110,7 @@ bool ClientData_Send(entity this, entity to, int sf)
        if (to.spectatee_status)    sf |= 2; // spectator ent number follows
        if (e.zoomstate)            sf |= 4; // zoomed
        if (e.porto_v_angle_held)   sf |= 8; // angles held
+       if (autocvar_sv_showspectators) sf |= 16; // show spectators
 
        WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
        WriteByte(MSG_ENTITY, sf);
@@ -100,6 +124,14 @@ bool ClientData_Send(entity this, entity to, int sf)
                WriteAngle(MSG_ENTITY, e.v_angle.x);
                WriteAngle(MSG_ENTITY, e.v_angle.y);
        }
+
+       if(sf & 16)
+       {
+               float specs = CountSpectators(e, to);
+               WriteByte(MSG_ENTITY, specs);
+               WriteSpectators(e, to);
+       }
+
        return true;
 }
 
@@ -112,7 +144,7 @@ void ClientData_Attach(entity this)
 
 void ClientData_Detach(entity this)
 {
-       remove(this.clientdata);
+       delete(this.clientdata);
        this.clientdata = NULL;
 }
 
@@ -215,7 +247,7 @@ void PutObserverInServer(entity this)
                // needed for player sounds
                this.model = "";
                FixPlayermodel(this);
-        } 
+        }
         setmodel(this, MDL_Null);
         setsize(this, STAT(PL_CROUCH_MIN, NULL), STAT(PL_CROUCH_MAX, NULL));
         this.view_ofs = '0 0 0';
@@ -224,6 +256,7 @@ void PutObserverInServer(entity this)
     RemoveGrapplingHook(this);
        Portal_ClearAll(this);
        Unfreeze(this);
+       SetSpectatee(this, NULL);
 
        if (this.alivetime)
        {
@@ -269,7 +302,7 @@ void PutObserverInServer(entity this)
        this.health = FRAGS_SPECTATOR;
        this.takedamage = DAMAGE_NO;
        this.solid = SOLID_NOT;
-       this.movetype = MOVETYPE_FLY_WORLDONLY; // user preference is controlled by playerprethink
+       set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink
        this.flags = FL_CLIENT | FL_NOTARGET;
        this.armorvalue = 666;
        this.effects = 0;
@@ -318,6 +351,10 @@ void PutObserverInServer(entity this)
        this.oldvelocity = this.velocity;
        this.fire_endtime = -1;
        this.event_damage = func_null;
+
+       STAT(ACTIVEWEAPON, this) = WEP_Null.m_id;
+       STAT(SWITCHINGWEAPON, this) = WEP_Null.m_id;
+       STAT(SWITCHWEAPON, this) = WEP_Null.m_id;
 }
 
 int player_getspecies(entity this)
@@ -463,7 +500,7 @@ void PutClientInServer(entity this)
                PutObserverInServer(this);
        } else if (IS_PLAYER(this)) {
                if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
-               
+
                PlayerState_attach(this);
                accuracy_resend(this);
 
@@ -481,7 +518,7 @@ void PutClientInServer(entity this)
                this.iscreature = true;
                this.teleportable = TELEPORT_NORMAL;
                this.damagedbycontents = true;
-               this.movetype = MOVETYPE_WALK;
+               set_movetype(this, MOVETYPE_WALK);
                this.solid = SOLID_SLIDEBOX;
                this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID;
                if (autocvar_g_playerclip_collisions)
@@ -639,7 +676,7 @@ void PutClientInServer(entity this)
                if (autocvar_spawn_debug)
                {
                        sprint(this, strcat("spawnpoint origin:  ", vtos(spot.origin), "\n"));
-                       remove(spot); // usefull for checking if there are spawnpoints, that let drop through the floor
+                       delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor
                }
 
                PS(this).m_switchweapon = w_getbestweapon(this);
@@ -797,7 +834,7 @@ void ClientKill_Now(entity this)
        }
 
        if(this.killindicator && !wasfreed(this.killindicator))
-               remove(this.killindicator);
+               delete(this.killindicator);
 
        this.killindicator = NULL;
 
@@ -814,14 +851,14 @@ void KillIndicator_Think(entity this)
        if (gameover)
        {
                this.owner.killindicator = NULL;
-               remove(this);
+               delete(this);
                return;
        }
 
        if (this.owner.alpha < 0 && !this.owner.vehicle)
        {
                this.owner.killindicator = NULL;
-               remove(this);
+               delete(this);
                return;
        }
 
@@ -1147,7 +1184,7 @@ void ClientConnect(entity this)
        if (!sv_foginterval && world.fog != "")
                stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
 
-       if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)))
+       if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2))
                if (!g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts
                        send_CSQC_teamnagger();
 
@@ -1187,6 +1224,8 @@ void ClientDisconnect(entity this)
 
        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname);
 
+       SetSpectatee(this, NULL);
+
     MUTATOR_CALLHOOK(ClientDisconnect, this);
 
        ClientState_detach(this);
@@ -1201,8 +1240,8 @@ void ClientDisconnect(entity this)
 
        this.flags &= ~FL_CLIENT;
 
-       if (this.chatbubbleentity) remove(this.chatbubbleentity);
-       if (this.killindicator) remove(this.killindicator);
+       if (this.chatbubbleentity) delete(this.chatbubbleentity);
+       if (this.killindicator) delete(this.killindicator);
 
        WaypointSprite_PlayerGone(this);
 
@@ -1211,7 +1250,7 @@ void ClientDisconnect(entity this)
        if (this.netname_previous) strunzone(this.netname_previous);
        if (this.clientstatus) strunzone(this.clientstatus);
        if (this.weaponorder_byimpulse) strunzone(this.weaponorder_byimpulse);
-       if (this.personal) remove(this.personal);
+       if (this.personal) delete(this.personal);
 
        this.playerid = 0;
        ReadyCount();
@@ -1225,7 +1264,7 @@ void ChatBubbleThink(entity this)
        {
                if(this.owner) // but why can that ever be NULL?
                        this.owner.chatbubbleentity = NULL;
-               remove(this);
+               delete(this);
                return;
        }
 
@@ -1289,7 +1328,7 @@ void respawn(entity this)
        {
                this.solid = SOLID_NOT;
                this.takedamage = DAMAGE_NO;
-               this.movetype = MOVETYPE_FLY;
+               set_movetype(this, MOVETYPE_FLY);
                this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
                this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
                this.effects |= CSQCMODEL_EF_RESPAWNGHOST;
@@ -1615,7 +1654,7 @@ void SpectateCopy(entity this, entity spectatee)
        this.angles = spectatee.v_angle;
        STAT(FROZEN, this) = STAT(FROZEN, spectatee);
        this.revive_progress = spectatee.revive_progress;
-       if(!PHYS_INPUT_BUTTON_USE(this))
+       if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2)
                this.fixangle = true;
        setorigin(this, spectatee.origin);
        setsize(this, spectatee.mins, spectatee.maxs);
@@ -1672,10 +1711,12 @@ bool SpectateSet(entity this)
        if(!IS_PLAYER(this.enemy))
                return false;
 
+       ClientData_Touch(this.enemy);
+
        msg_entity = this;
        WriteByte(MSG_ONE, SVC_SETVIEW);
        WriteEntity(MSG_ONE, this.enemy);
-       this.movetype = MOVETYPE_NONE;
+       set_movetype(this, MOVETYPE_NONE);
        accuracy_resend(this);
 
        if(!SpectateUpdate(this))
@@ -1692,8 +1733,27 @@ void SetSpectatee(entity this, entity spectatee)
 
        // WEAPONTODO
        // these are required to fix the spectator bug with arc
-       if(old_spectatee && old_spectatee.arc_beam) { old_spectatee.arc_beam.SendFlags |= ARC_SF_SETTINGS; }
-       if(this.enemy && this.enemy.arc_beam) { this.enemy.arc_beam.SendFlags |= ARC_SF_SETTINGS; }
+       if(old_spectatee)
+       {
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if(old_spectatee.(weaponentity).arc_beam)
+                               old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
+               }
+       }
+       if(this.enemy)
+       {
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if(this.enemy.(weaponentity).arc_beam)
+                               this.enemy.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
+               }
+       }
+
+       // needed to update spectator list
+       if(old_spectatee) { ClientData_Touch(old_spectatee); }
 }
 
 bool Spectate(entity this, entity pl)
@@ -1791,6 +1851,8 @@ void LeaveSpectatorMode(entity this)
                {
                        TRANSMUTE(Player, this);
 
+                       SetSpectatee(this, NULL);
+
                        if(autocvar_g_campaign || autocvar_g_balance_teams)
                                { JoinBestTeam(this, false, true); }
 
@@ -1925,7 +1987,6 @@ void ObserverThink(entity this)
                MinigameImpulse(this, this.impulse);
                this.impulse = 0;
        }
-       float prefered_movetype;
        if (this.flags & FL_JUMPRELEASED) {
                if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) {
                        this.flags &= ~FL_JUMPRELEASED;
@@ -1936,9 +1997,8 @@ void ObserverThink(entity this)
                                TRANSMUTE(Spectator, this);
                        }
                } else {
-                       prefered_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? this.cvar_cl_clippedspectating : !this.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
-                       if (this.movetype != prefered_movetype)
-                               this.movetype = prefered_movetype;
+                       int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? this.cvar_cl_clippedspectating : !this.cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
+                       set_movetype(this, preferred_movetype);
                }
        } else {
                if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this))) {
@@ -1959,6 +2019,13 @@ void SpectatorThink(entity this)
        {
                if(MinigameImpulse(this, this.impulse))
                        this.impulse = 0;
+
+               if (this.impulse == IMP_weapon_drop.impulse)
+               {
+                       STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
+                       this.impulse = 0;
+                       return;
+               }
        }
        if (this.flags & FL_JUMPRELEASED) {
                if (PHYS_INPUT_BUTTON_JUMP(this) && !this.version_mismatch) {
@@ -2278,7 +2345,6 @@ void PlayerPreThink (entity this)
                this.prevorigin = this.origin;
 
                bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
-        .entity weaponentity = weaponentities[0]; // TODO: unhardcode
                if (this.hook.state) {
                        do_crouch = false;
                } else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
@@ -2287,9 +2353,6 @@ void PlayerPreThink (entity this)
                        do_crouch = false;
                } else if (STAT(FROZEN, this)) {
                        do_crouch = false;
-        } else if ((PS(this).m_weapon == WEP_SHOTGUN || PS(this).m_weapon == WEP_SHOCKWAVE) && this.(weaponentity).wframe == WFRAME_FIRE2 && time < this.(weaponentity).weapon_nextthink) {
-                   // WEAPONTODO: predict
-                       do_crouch = false;
         }
 
                if (do_crouch) {
@@ -2315,7 +2378,13 @@ void PlayerPreThink (entity this)
                {
                        this.items &= ~this.items_added;
 
-                       W_WeaponFrame(this);
+                       //for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+                       //{
+                               //.entity weaponentity = weaponentities[slot];
+                               //W_WeaponFrame(this, weaponentity);
+                       //}
+                       .entity weaponentity = weaponentities[0]; // TODO
+                       W_WeaponFrame(this, weaponentity);
 
                        this.items_added = 0;
                        if (this.items & ITEM_Jetpack.m_itemid && (this.items & ITEM_JetpackRegen.m_itemid || this.ammo_fuel >= 0.01))
@@ -2420,6 +2489,30 @@ void DrownPlayer(entity this)
        }
 }
 
+void Player_Physics(entity this)
+{
+       set_movetype(this, ((this.move_qcphysics) ? MOVETYPE_NONE : this.move_movetype));
+
+       if(!this.move_qcphysics)
+               return;
+
+       int mt = this.move_movetype;
+
+       if(mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH || mt == MOVETYPE_PHYSICS)
+       {
+               this.move_qcphysics = false;
+               set_movetype(this, mt);
+               return;
+       }
+
+       if(!frametime && !this.pm_frametime)
+               return;
+
+       Movetype_Physics_NoMatchTicrate(this, this.pm_frametime, true);
+
+       this.pm_frametime = 0;
+}
+
 /*
 =============
 PlayerPostThink
@@ -2430,6 +2523,8 @@ Called every frame for each client after the physics are run
 .float idlekick_lasttimeleft;
 void PlayerPostThink (entity this)
 {
+       Player_Physics(this);
+
        if (sv_maxidle > 0)
        if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero).
        if (IS_REAL_CLIENT(this))