]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Hacks to spawn bots as non-clients Melanosuchus/single_player-bot_fakeclient
authorMattia Basaglia <mattia.basaglia@gmail.com>
Sun, 9 Apr 2017 08:31:41 +0000 (09:31 +0100)
committerMattia Basaglia <mattia.basaglia@gmail.com>
Sun, 9 Apr 2017 08:31:41 +0000 (09:31 +0100)
qcsrc/server/_all.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/client.qc
qcsrc/server/mutators/mutator/gamemode_singleplayer.qc

index 1fabe4d0f965aa9abd6b564b180ad4e83637a96d..32617a68804838decd4553d3376d8a070a0b6f4c 100644 (file)
@@ -12,7 +12,7 @@ const string STR_OBSERVER = "observer";
 
 #define IS_CLIENT(v) (v.flags & FL_CLIENT)
 /** want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v)) */
-#define IS_BOT_CLIENT(v) (clienttype(v) == CLIENTTYPE_BOT)
+#define IS_BOT_CLIENT(v) (clienttype(v) == CLIENTTYPE_BOT || IS_FAKE_CLIENT(v))
 #define IS_FAKE_CLIENT(v) (clienttype(v) == CLIENTTYPE_NOTACLIENT)
 #define IS_REAL_CLIENT(v) (clienttype(v) == CLIENTTYPE_REAL)
 /** was: (clienttype(v) == CLIENTTYPE_NOTACLIENT) */
index 5514c7c5d5c572abf8be4f1d3d07ca1fd891f57c..1d8c4f3a4490481bb48176cc5489e5bca7f43b23 100644 (file)
@@ -63,7 +63,10 @@ void bot_spawn_setup(entity bot)
 void bot_remove(entity bot)
 {
        currentbots = currentbots - 1;
-       dropclient(bot);
+    if ( IS_FAKE_CLIENT(bot) )
+        delete(bot);
+    else
+        dropclient(bot);
 }
 
 void bot_think(entity this)
@@ -110,16 +113,19 @@ void bot_think(entity this)
        // skill 0 = ping 0.7 (slightly drunk)
 
        // clear buttons
-       PHYS_INPUT_BUTTON_ATCK(this) = false;
-       PHYS_INPUT_BUTTON_JUMP(this) = false;
-       PHYS_INPUT_BUTTON_ATCK2(this) = false;
-       PHYS_INPUT_BUTTON_ZOOM(this) = false;
-       PHYS_INPUT_BUTTON_CROUCH(this) = false;
-       PHYS_INPUT_BUTTON_HOOK(this) = false;
-       PHYS_INPUT_BUTTON_INFO(this) = false;
-       PHYS_INPUT_BUTTON_DRAG(this) = false;
-       PHYS_INPUT_BUTTON_CHAT(this) = false;
-       PHYS_INPUT_BUTTON_USE(this) = false;
+    if ( !IS_FAKE_CLIENT(this) )
+    {
+        PHYS_INPUT_BUTTON_ATCK(this) = false;
+        PHYS_INPUT_BUTTON_JUMP(this) = false;
+        PHYS_INPUT_BUTTON_ATCK2(this) = false;
+        PHYS_INPUT_BUTTON_ZOOM(this) = false;
+        PHYS_INPUT_BUTTON_CROUCH(this) = false;
+        PHYS_INPUT_BUTTON_HOOK(this) = false;
+        PHYS_INPUT_BUTTON_INFO(this) = false;
+        PHYS_INPUT_BUTTON_DRAG(this) = false;
+        PHYS_INPUT_BUTTON_CHAT(this) = false;
+        PHYS_INPUT_BUTTON_USE(this) = false;
+    }
 
        if (time < game_starttime)
        {
@@ -133,7 +139,7 @@ void bot_think(entity this)
        if (IS_DEAD(this))
        {
                this.movement = '0 0 0';
-               if (this.deadflag == DEAD_DEAD)
+               if (this.deadflag == DEAD_DEAD && !IS_FAKE_CLIENT(this))
                {
                        PHYS_INPUT_BUTTON_JUMP(this) = true; // press jump to respawn
                        this.bot_strategytime = 0;
@@ -399,7 +405,7 @@ void bot_relinkplayerlist()
 
 void bot_clientdisconnect(entity this)
 {
-       if (!IS_BOT_CLIENT(this))
+       if (IS_REAL_CLIENT(this))
                return;
        bot_clearqueue(this);
        if(this.cleanname)
@@ -422,7 +428,7 @@ void bot_clientdisconnect(entity this)
 
 void bot_clientconnect(entity this)
 {
-       if (!IS_BOT_CLIENT(this)) return;
+       if (IS_REAL_CLIENT(this)) return;
        this.bot_preferredcolors = this.clientcolors;
        this.bot_nextthink = time - random();
        this.lag_func = bot_lagfunc;
@@ -500,16 +506,8 @@ void bot_removenewest()
        best = M_ARGV(0, entity);
        if ( best )
        {
-               if ( !IS_BOT_CLIENT(best) )
-               {
-                       LOG_WARN("Mutator selected a non-bot as a bot to remove\n");
-                       best = NULL;
-               }
-               else
-               {
-                       bot_remove(best);
-                       return;
-               }
+               bot_remove(best);
+               return;
        }
 
        if(teamplay)
@@ -605,7 +603,7 @@ float bot_fixcount()
 {
        int activerealplayers = 0;
        int realplayers = 0;
-       int bots = 0;
+       int bots = -1;
 
        if (MUTATOR_CALLHOOK(Bot_FixCount, activerealplayers, realplayers, bots)) {
                activerealplayers = M_ARGV(0, int);
@@ -624,7 +622,7 @@ float bot_fixcount()
        // But don't remove bots immediately on level change, as the real players
        // usually haven't rejoined yet
        bots_would_leave = false;
-       if (bots)
+       if (bots >= 0)
        {
                // Nothing to do, number of bots set by the hook
        }
index 9d5be4c80f3cb432d32fd3939aedd59327bbb6e8..89ea7f3eba8b66624ca5720407e8a0ee185f738a 100644 (file)
@@ -491,12 +491,12 @@ void FixPlayermodel(entity player)
 /** Called when a client spawns in the server */
 void PutClientInServer(entity this)
 {
-       if (IS_BOT_CLIENT(this)) {
-               TRANSMUTE(Player, this);
-       } else if (IS_REAL_CLIENT(this)) {
+    if (IS_REAL_CLIENT(this)) {
                msg_entity = this;
                WriteByte(MSG_ONE, SVC_SETVIEW);
                WriteEntity(MSG_ONE, this);
+       } else {
+               TRANSMUTE(Player, this);
        }
        if (game_stopped)
                TRANSMUTE(Observer, this);
index 371f906af01c976d75411e936a608e32355858fe..a91692d4485f72849e7a5652f05797ce8504993f 100644 (file)
@@ -43,17 +43,22 @@ spawnfunc(info_player_singleplayer)
     spawnfunc_info_player_deathmatch(this);
 }
 
+.bool sp_hack;
 /*
  * Creates a new bot and assigns it to the given spawn point
  */
 void sp_spawn_bot(entity spawn_point)
 {
     sp_bot_number++;
-    entity bot = spawnclient();
+    entity bot = spawn();
     if (bot)
     {
         bot.sp_spawn_spot = spawn_point;
+//         bot.flags |= FL_CLIENT;
         bot_spawn_setup(bot);
+        //bot.origin = spawn_point.origin;
+        bot.team = SP_TEAM_ENEMY;
+        bot.sp_hack = true;
     }
     else
     {
@@ -109,6 +114,7 @@ void sp_delayed_init(entity this)
 // Ensures the given bot will be removed
 void sp_remove_bot(entity bot)
 {
+    bot.netname = "killed";
     sp_bot_number--;
     bot.sp_spawn_spot = NULL;
     bot_clear(bot);
@@ -123,11 +129,11 @@ MUTATOR_HOOKFUNCTION(sp, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
 {
        M_ARGV(1, string) = "sp_team";
     entity ent = M_ARGV(2, entity);
-    if ( IS_BOT_CLIENT(ent) )
+    if ( !IS_REAL_CLIENT(ent) )
     {
         ent.team_forced = NUM_TEAM_2;
     }
-    else if( IS_REAL_CLIENT(ent) )
+    else
     {
         ent.team_forced = NUM_TEAM_1;
         c1 = 1;
@@ -152,12 +158,20 @@ MUTATOR_HOOKFUNCTION(sp, PlayerSpawn)
 {
     entity player = M_ARGV(0, entity);
     entity spawn_spot = M_ARGV(1, entity);
-    if ( IS_BOT_CLIENT(player) )
+    if ( !IS_REAL_CLIENT(player) )
     {
+        if ( player.sp_spawn_spot != spawn_spot )
+        {
+            player.netname = "derp";
+            return;
+        }
+
         player.can_drop_weapon = spawn_spot.can_drop_weapon;
         player.items |= IT_UNLIMITED_WEAPON_AMMO;
         if ( spawn_spot.health )
             player.health = spawn_spot.health;
+        else
+            player.health = 100;
         player.armorvalue = spawn_spot.armorvalue;
         player.weapons = WepSet_FromWeapon(Weapons_fromstr(spawn_spot.weapon_name));
         if ( spawn_spot.netname )
@@ -195,7 +209,7 @@ MUTATOR_HOOKFUNCTION(sp, Spawn_Score)
     entity player = M_ARGV(0, entity);
     entity spawn_spot = M_ARGV(1, entity);
     vector spawn_score = M_ARGV(2, vector);
-    if ( IS_BOT_CLIENT(player) )
+    if ( !IS_REAL_CLIENT(player) )
     {
         if ( spawn_spot.sp_spawn_team != SP_TEAM_ENEMY ||
             (player.sp_spawn_spot && player.sp_spawn_spot != spawn_spot) )
@@ -221,7 +235,7 @@ MUTATOR_HOOKFUNCTION(sp, HideTeamNagger)
  */
 MUTATOR_HOOKFUNCTION(sp, Bot_FixCount)
 {
-    M_ARGV(2, int) = sp_bot_number;
+    M_ARGV(2, int) = 0;
     return true;
 }
 
@@ -229,7 +243,7 @@ MUTATOR_HOOKFUNCTION(sp, Bot_FixCount)
 MUTATOR_HOOKFUNCTION(sp, PlayerDies)
 {
     entity target = M_ARGV(2, entity);
-    if ( IS_BOT_CLIENT(target) )
+    if ( !IS_REAL_CLIENT(target) )
     {
         if ( target.sp_spawn_spot && !target.sp_spawn_spot.can_respawn )
         {
@@ -255,7 +269,7 @@ MUTATOR_HOOKFUNCTION(sp, Bot_SelectRemove)
 MUTATOR_HOOKFUNCTION(sp, PlayerPreThink)
 {
     entity player = M_ARGV(0, entity);
-    if ( IS_BOT_CLIENT(player) && !player.sp_spawn_spot )
+    if ( !IS_REAL_CLIENT(player) && !player.sp_spawn_spot )
     {
         bot_remove(player);
     }
@@ -265,7 +279,7 @@ MUTATOR_HOOKFUNCTION(sp, PlayerPreThink)
 MUTATOR_HOOKFUNCTION(sp, ItemTouch)
 {
     entity toucher = M_ARGV(1, entity);
-    if ( IS_BOT_CLIENT(toucher) && !autocvar_g_sp_allow_bot_pickup )
+    if ( !IS_REAL_CLIENT(toucher) && !autocvar_g_sp_allow_bot_pickup )
         return MUT_ITEMTOUCH_RETURN;
     return MUT_ITEMTOUCH_CONTINUE;
 }
@@ -280,3 +294,364 @@ MUTATOR_HOOKFUNCTION(sp, Item_Spawn)
     }
     return false;
 }
+
+void PlayerPreThinkHack (entity this)
+{
+       WarpZone_PlayerPhysics_FixVAngle(this);
+
+    STAT(GAMESTARTTIME, this) = game_starttime;
+       STAT(ROUNDSTARTTIME, this) = round_starttime;
+       STAT(ALLOW_OLDVORTEXBEAM, this) = autocvar_g_allow_oldvortexbeam;
+       STAT(LEADLIMIT, this) = autocvar_leadlimit;
+
+       STAT(WEAPONSINMAP, this) = weaponsInMap;
+
+       if (frametime) {
+               // physics frames: update anticheat stuff
+               anticheat_prethink(this);
+       }
+
+       if (blockSpectators && frametime) {
+               // WORKAROUND: only use dropclient in server frames (frametime set).
+               // Never use it in cl_movement frames (frametime zero).
+               checkSpectatorBlock(this);
+    }
+
+       zoomstate_set = false;
+
+       // Check for nameless players
+       /*if (isInvisibleString(this.netname)) {
+               this.netname = strzone(sprintf("Player#%d", this.playerid));
+               // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
+       }
+       if (this.netname != this.netname_previous) {
+               if (autocvar_sv_eventlog) {
+                       GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this, false)));
+        }
+               if (this.netname_previous) strunzone(this.netname_previous);
+               this.netname_previous = strzone(this.netname);
+       }*/
+
+       // version nagging
+       /*if (this.version_nagtime && this.cvar_g_xonoticversion && time > this.version_nagtime) {
+        this.version_nagtime = 0;
+        if (strstrofs(this.cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(this.cvar_g_xonoticversion, "autobuild", 0) >= 0) {
+            // git client
+        } else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0) {
+            // git server
+            Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, this.cvar_g_xonoticversion);
+        } else {
+            int r = vercmp(this.cvar_g_xonoticversion, autocvar_g_xonoticversion);
+            if (r < 0) { // old client
+                Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, this.cvar_g_xonoticversion);
+            } else if (r > 0) { // old server
+                Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, this.cvar_g_xonoticversion);
+            }
+        }
+    }*/
+
+       // GOD MODE info
+       if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
+       {
+               Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
+               this.max_armorvalue = 0;
+       }
+
+       if (STAT(FROZEN, this) == 2)
+       {
+               this.revive_progress = bound(0, this.revive_progress + frametime * this.revive_speed, 1);
+               this.health = max(1, this.revive_progress * start_health);
+               this.iceblock.alpha = bound(0.2, 1 - this.revive_progress, 1);
+
+               if (this.revive_progress >= 1)
+                       Unfreeze(this);
+       }
+       else if (STAT(FROZEN, this) == 3)
+       {
+               this.revive_progress = bound(0, this.revive_progress - frametime * this.revive_speed, 1);
+               this.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * this.revive_progress );
+
+               if (this.health < 1)
+               {
+                       if (this.vehicle)
+                               vehicles_exit(this.vehicle, VHEF_RELEASE);
+                       if(this.event_damage)
+                               this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, this.origin, '0 0 0');
+               }
+               else if (this.revive_progress <= 0)
+                       Unfreeze(this);
+       }
+
+       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))
+       {
+               FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it),
+               {
+                       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.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);
+                       }
+               });
+
+               this.last_vehiclecheck = time + 1;
+       }*/
+
+       if(!this.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
+       {
+               if(PHYS_INPUT_BUTTON_USE(this) && !this.usekeypressed)
+                       PlayerUseKey(this);
+               this.usekeypressed = PHYS_INPUT_BUTTON_USE(this);
+       }
+
+       /*if (IS_REAL_CLIENT(this))
+               PrintWelcomeMessage(this);*/
+
+       if (IS_PLAYER(this)) {
+               CheckRules_Player(this);
+
+               if (game_stopped || intermission_running) {
+                       this.modelflags &= ~MF_ROCKET;
+                       if(intermission_running)
+                               IntermissionThink(this);
+                       return;
+               }
+
+               if (timeout_status == TIMEOUT_ACTIVE) {
+            // don't allow the player to turn around while game is paused
+                       // FIXME turn this into CSQC stuff
+                       this.v_angle = this.lastV_angle;
+                       this.angles = this.lastV_angle;
+                       this.fixangle = true;
+               }
+
+               if (frametime) player_powerups(this);
+
+               if (IS_DEAD(this)) {
+                       if (this.personal && g_race_qualifying) {
+                               if (time > this.respawn_time) {
+                                       STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second
+                                       respawn(this);
+                                       this.impulse = CHIMPULSE_SPEEDRUN.impulse;
+                               }
+                       } else {
+                               if (frametime) player_anim(this);
+                               bool button_pressed = (PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this));
+
+                               switch(this.deadflag)
+                               {
+                                       case DEAD_DYING:
+                                       {
+                                               if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max))
+                                                       this.deadflag = DEAD_RESPAWNING;
+                                               else if (!button_pressed || (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)))
+                                                       this.deadflag = DEAD_DEAD;
+                                               break;
+                                       }
+                                       case DEAD_DEAD:
+                                       {
+                                               if (button_pressed)
+                                                       this.deadflag = DEAD_RESPAWNABLE;
+                                               else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE))
+                                                       this.deadflag = DEAD_RESPAWNING;
+                                               break;
+                                       }
+                                       case DEAD_RESPAWNABLE:
+                                       {
+                                               if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE))
+                                                       this.deadflag = DEAD_RESPAWNING;
+                                               break;
+                                       }
+                                       case DEAD_RESPAWNING:
+                                       {
+                                               if (time > this.respawn_time)
+                                               {
+                                                       this.respawn_time = time + 1; // only retry once a second
+                                                       this.respawn_time_max = this.respawn_time;
+                                                       respawn(this);
+                                               }
+                                               break;
+                                       }
+                               }
+
+                               ShowRespawnCountdown(this);
+
+                               if (this.respawn_flags & RESPAWN_SILENT)
+                                       STAT(RESPAWN_TIME, this) = 0;
+                               else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max)
+                               {
+                                       if (time < this.respawn_time)
+                                               STAT(RESPAWN_TIME, this) = this.respawn_time;
+                                       else if (this.deadflag != DEAD_RESPAWNING)
+                                               STAT(RESPAWN_TIME, this) = -this.respawn_time_max;
+                               }
+                               else
+                                       STAT(RESPAWN_TIME, this) = this.respawn_time;
+                       }
+
+                       // if respawning, invert stat_respawn_time to indicate this, the client translates it
+                       if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0)
+                               STAT(RESPAWN_TIME, this) *= -1;
+
+                       return;
+               }
+
+               this.prevorigin = this.origin;
+
+               bool have_hook = false;
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if(this.(weaponentity).hook.state)
+                       {
+                               have_hook = true;
+                               break;
+                       }
+               }
+               bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
+               if (have_hook) {
+                       do_crouch = false;
+               } else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
+                       do_crouch = false;
+               } else if (this.vehicle) {
+                       do_crouch = false;
+               } else if (STAT(FROZEN, this)) {
+                       do_crouch = false;
+        }
+
+               if (do_crouch) {
+                       if (!this.crouch) {
+                               this.crouch = true;
+                               this.view_ofs = STAT(PL_CROUCH_VIEW_OFS, this);
+                               setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this));
+                               // setanim(this, this.anim_duck, false, true, true); // this anim is BROKEN anyway
+                       }
+               } else if (this.crouch) {
+            tracebox(this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), this.origin, false, this);
+            if (!trace_startsolid) {
+                this.crouch = false;
+                this.view_ofs = STAT(PL_VIEW_OFS, this);
+                setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this));
+            }
+               }
+
+               FixPlayermodel(this);
+
+               // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
+               //if(frametime)
+               {
+                       this.items &= ~this.items_added;
+
+                       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+                       {
+                               .entity weaponentity = weaponentities[slot];
+                               W_WeaponFrame(this, weaponentity);
+
+                               if(slot == 0)
+                               {
+                                       this.clip_load = this.(weaponentity).clip_load;
+                                       this.clip_size = this.(weaponentity).clip_size;
+                               }
+                       }
+
+                       this.items_added = 0;
+                       if (this.items & ITEM_Jetpack.m_itemid && (this.items & ITEM_JetpackRegen.m_itemid || this.ammo_fuel >= 0.01))
+                this.items_added |= IT_FUEL;
+
+                       this.items |= this.items_added;
+               }
+
+               player_regen(this);
+
+               // WEAPONTODO: Add a weapon request for this
+               // rot vortex charge to the charge limit
+               /*for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
+                               this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
+               }*/
+
+               if (frametime) player_anim(this);
+
+               // secret status
+               //secrets_setstatus(this);
+
+               // monsters status
+               //monsters_setstatus(this);
+
+               this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
+       }
+       else if (game_stopped || intermission_running) {
+               if(intermission_running)
+                       IntermissionThink(this);
+               return;
+       }
+       /*else if (IS_OBSERVER(this)) {
+               ObserverThink(this);
+       }
+       else if (IS_SPEC(this)) {
+               SpectatorThink(this);
+       }*/
+
+       // WEAPONTODO: Add weapon request for this
+       /*if (!zoomstate_set) {
+               bool wep_zoomed = false;
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       Weapon thiswep = this.(weaponentity).m_weapon;
+                       if(thiswep != WEP_Null && thiswep.wr_zoom)
+                               wep_zoomed += thiswep.wr_zoom(thiswep, this);
+               }
+               SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
+    }*/
+
+       /*if (this.teamkill_soundtime && time > this.teamkill_soundtime)
+       {
+               this.teamkill_soundtime = 0;
+
+               entity e = this.teamkill_soundsource;
+               entity oldpusher = e.pusher;
+               e.pusher = this;
+               PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
+               e.pusher = oldpusher;
+       }
+
+       if (this.taunt_soundtime && time > this.taunt_soundtime) {
+               this.taunt_soundtime = 0;
+               PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
+       }
+
+       target_voicescript_next(this);
+
+       // WEAPONTODO: Move into weaponsystem somehow
+       // if a player goes unarmed after holding a loaded weapon, empty his clip size and remove the crosshair ammo ring
+       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+       {
+               .entity weaponentity = weaponentities[slot];
+               if(this.(weaponentity).m_weapon == WEP_Null)
+                       this.(weaponentity).clip_load = this.(weaponentity).clip_size = 0;
+       }*/
+}
+
+
+MUTATOR_HOOKFUNCTION(sp, SV_StartFrame)
+{
+    entity e = NULL;
+    while( (e = findfloat(e, sp_hack, true)) )
+    {
+//         PlayerPreThink(e);
+               PlayerPreThinkHack(e);
+
+    }
+}