]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/bot/default/bot.qc
Hacks to spawn bots as non-clients
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / default / bot.qc
index 2a7478dee2d7688322dfa3006b4534957159bcd3..1d8c4f3a4490481bb48176cc5489e5bca7f43b23 100644 (file)
@@ -46,14 +46,29 @@ entity bot_spawn()
        entity bot = spawnclient();
        if (bot)
        {
-               currentbots = currentbots + 1;
-               bot_setnameandstuff(bot);
-               ClientConnect(bot);
-               PutClientInServer(bot);
+               bot_spawn_setup(bot);
        }
        return bot;
 }
 
+void bot_spawn_setup(entity bot)
+{
+       setItemGroupCount();
+       currentbots = currentbots + 1;
+       bot_setnameandstuff(bot);
+       ClientConnect(bot);
+       PutClientInServer(bot);
+}
+
+void bot_remove(entity bot)
+{
+       currentbots = currentbots - 1;
+    if ( IS_FAKE_CLIENT(bot) )
+        delete(bot);
+    else
+        dropclient(bot);
+}
+
 void bot_think(entity this)
 {
        if (this.bot_nextthink > time)
@@ -63,7 +78,8 @@ void bot_think(entity this)
        if(autocvar_bot_god)
                this.flags |= FL_GODMODE;
 
-       this.bot_nextthink = this.bot_nextthink + autocvar_bot_ai_thinkinterval * pow(0.5, this.bot_aiskill);
+       this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * (0.5 ** this.bot_aiskill) * min(14 / (skill + 14), 1));
+
        //if (this.bot_painintensity > 0)
        //      this.bot_painintensity = this.bot_painintensity - (skill + 1) * 40 * frametime;
 
@@ -72,6 +88,7 @@ void bot_think(entity this)
 
        if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
        {
+               this.movement = '0 0 0';
                this.bot_nextthink = time + 0.5;
                return;
        }
@@ -96,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)
        {
@@ -118,7 +138,8 @@ void bot_think(entity this)
        // if dead, just wait until we can respawn
        if (IS_DEAD(this))
        {
-               if (this.deadflag == DEAD_DEAD)
+               this.movement = '0 0 0';
+               if (this.deadflag == DEAD_DEAD && !IS_FAKE_CLIENT(this))
                {
                        PHYS_INPUT_BUTTON_JUMP(this) = true; // press jump to respawn
                        this.bot_strategytime = 0;
@@ -204,7 +225,13 @@ void bot_setnameandstuff(entity this)
 
        prio = 6;
 
-       #define READSKILL(f,w,r) if(argv(prio) != "") this.f = stof(argv(prio)) * (w); else this.f = (!autocvar_g_campaign) * (2 * random() - 1) * (r) * (w); ++prio
+       #define READSKILL(f, w, r) MACRO_BEGIN { \
+               if(argv(prio) != "") \
+                       this.f = stof(argv(prio)) * w; \
+               else \
+                       this.f = (!autocvar_g_campaign) * (2 * random() - 1) * r * w; \
+               prio++; \
+       } MACRO_END
        //print(bot_name, ": ping=", argv(9), "\n");
 
        READSKILL(havocbot_keyboardskill, 0.5, 0.5); // keyboard skill
@@ -338,9 +365,8 @@ void bot_custom_weapon_priority_setup()
 
 void bot_endgame()
 {
-       entity e;
-       //dprint("bot_endgame\n");
-       e = bot_list;
+       bot_relinkplayerlist();
+       entity e = bot_list;
        while (e)
        {
                setcolor(e, e.bot_preferredcolors);
@@ -365,14 +391,13 @@ void bot_relinkplayerlist()
                        if(prevbot)
                                prevbot.nextbot = it;
                        else
-                       {
                                bot_list = it;
-                               bot_list.nextbot = NULL;
-                       }
                        prevbot = it;
                        ++currentbots;
                }
        });
+       if(prevbot)
+               prevbot.nextbot = NULL;
        LOG_TRACE("relink: ", ftos(currentbots), " bots seen.");
        bot_strategytoken = bot_list;
        bot_strategytoken_taken = true;
@@ -380,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)
@@ -397,13 +422,13 @@ void bot_clientdisconnect(entity this)
        this.playerskin_freeme = string_null;
        if(this.bot_cmd_current)
                delete(this.bot_cmd_current);
-       if(bot_waypoint_queue_owner==this)
+       if(bot_waypoint_queue_owner == this)
                bot_waypoint_queue_owner = NULL;
 }
 
 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;
@@ -471,12 +496,20 @@ void bot_removefromlargestteam()
        });
        if(!bcount)
                return; // no bots to remove
-       currentbots = currentbots - 1;
-       dropclient(best);
+       bot_remove(best);
 }
 
 void bot_removenewest()
 {
+       entity best = NULL;
+       MUTATOR_CALLHOOK(Bot_SelectRemove, best);
+       best = M_ARGV(0, entity);
+       if ( best )
+       {
+               bot_remove(best);
+               return;
+       }
+
        if(teamplay)
        {
                bot_removefromlargestteam();
@@ -484,7 +517,6 @@ void bot_removenewest()
        }
 
        float besttime = 0;
-       entity best = NULL;
        int bcount = 0;
 
        FOREACH_CLIENT(it.isbot,
@@ -507,8 +539,7 @@ void bot_removenewest()
        if(!bcount)
                return; // no bots to remove
 
-       currentbots = currentbots - 1;
-       dropclient(best);
+       bot_remove(best);
 }
 
 void autoskill(float factor)
@@ -572,9 +603,12 @@ float bot_fixcount()
 {
        int activerealplayers = 0;
        int realplayers = 0;
-       if (MUTATOR_CALLHOOK(Bot_FixCount, activerealplayers, realplayers)) {
+       int bots = -1;
+
+       if (MUTATOR_CALLHOOK(Bot_FixCount, activerealplayers, realplayers, bots)) {
                activerealplayers = M_ARGV(0, int);
                realplayers = M_ARGV(1, int);
+        bots = M_ARGV(2, int);
        } else {
                FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(
                        if(IS_PLAYER(it))
@@ -583,14 +617,19 @@ float bot_fixcount()
                ));
        }
 
-       int bots;
        // add/remove bots if needed to make sure there are at least
        // minplayers+bot_number, or remove all bots if no one is playing
        // But don't remove bots immediately on level change, as the real players
        // usually haven't rejoined yet
        bots_would_leave = false;
-       if (teamplay && autocvar_bot_vs_human && AvailableTeams() == 2)
+       if (bots >= 0)
+       {
+               // Nothing to do, number of bots set by the hook
+       }
+       else if (teamplay && autocvar_bot_vs_human && AvailableTeams() == 2)
+       {
                bots = min(ceil(fabs(autocvar_bot_vs_human) * activerealplayers), maxclients - realplayers);
+       }
        else if ((realplayers || autocvar_bot_join_empty || (currentbots > 0 && time < 5)))
        {
                float realminplayers, minplayers;
@@ -630,9 +669,42 @@ float bot_fixcount()
        return true;
 }
 
+void bot_remove_from_bot_list(entity this)
+{
+       entity e = bot_list;
+       entity prev_bot = NULL;
+       while (e)
+       {
+               if(e == this)
+               {
+                       if(!prev_bot)
+                               bot_list = this.nextbot;
+                       else
+                               prev_bot.nextbot = this.nextbot;
+                       if(bot_strategytoken == this)
+                       {
+                               bot_strategytoken = this.nextbot;
+                               bot_strategytoken_taken = true;
+                       }
+                       this.nextbot = NULL;
+                       break;
+               }
+               prev_bot = e;
+               e = e.nextbot;
+       }
+}
+
+void bot_clear(entity this)
+{
+       bot_remove_from_bot_list(this);
+       if(bot_waypoint_queue_owner == this)
+               bot_waypoint_queue_owner = NULL;
+       this.aistatus &= ~AI_STATUS_STUCK; // otherwise bot_waypoint_queue_owner will be set again to this by navigation_unstuck
+}
+
 void bot_serverframe()
 {
-       if (intermission_running)
+       if (game_stopped)
                return;
 
        if (time < 2)