#include <common/physics/player.qh>
#include <common/constants.qh>
+#include <common/net_linked.qh>
#include <common/mapinfo.qh>
#include <common/teams.qh>
#include <common/util.qh>
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)
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;
if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
{
+ this.movement = '0 0 0';
this.bot_nextthink = time + 0.5;
return;
}
// 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)
{
// 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;
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
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);
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;
void bot_clientdisconnect(entity this)
{
- if (!IS_BOT_CLIENT(this))
+ if (IS_REAL_CLIENT(this))
return;
bot_clearqueue(this);
if(this.cleanname)
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;
});
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();
}
float besttime = 0;
- entity best = NULL;
int bcount = 0;
FOREACH_CLIENT(it.isbot,
if(!bcount)
return; // no bots to remove
- currentbots = currentbots - 1;
- dropclient(best);
+ bot_remove(best);
}
void autoskill(float factor)
{
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))
));
}
- 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;
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)