X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fserver%2Fbot%2Fdefault%2Fbot.qc;h=20af7e71984dfdac24a5895ec91b7bf128f03b8b;hp=318422153bb3423478e9f8dc980a0fea24daf5e4;hb=9ff975c1d5a91d3e27ea7f77c30375b50f2c15a6;hpb=3ee342eac91e8a0cc50f5e7517bb4c4d640a4774 diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc index 318422153..9ebcffae3 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -21,7 +21,7 @@ #include "../../race.qh" #include -#include "../../mutators/_mod.qh" +#include #include "../../weapons/accuracy.qh" @@ -41,6 +41,15 @@ #include #include +STATIC_INIT(bot) { bot_calculate_stepheightvec(); } + +// TODO: remove this function! its only purpose is to update these fields since bot_setnameandstuff is called before ClientState +void bot_setclientfields(entity this) +{ + CS(this).cvar_cl_accuracy_data_share = 1; // share the bots weapon accuracy data with the world + CS(this).cvar_cl_accuracy_data_receive = 0; // don't receive any weapon accuracy data +} + entity bot_spawn() { entity bot = spawnclient(); @@ -50,6 +59,7 @@ entity bot_spawn() currentbots = currentbots + 1; bot_setnameandstuff(bot); ClientConnect(bot); + bot_setclientfields(bot); PutClientInServer(bot); } return bot; @@ -64,17 +74,11 @@ void bot_think(entity this) if(autocvar_bot_god) this.flags |= FL_GODMODE; - this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * pow(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; - - //this.bot_painintensity = this.bot_painintensity + this.bot_oldhealth - this.health; - //this.bot_painintensity = bound(0, this.bot_painintensity, 100); + 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 (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start)) { - this.movement = '0 0 0'; + CS(this).movement = '0 0 0'; this.bot_nextthink = time + 0.5; return; } @@ -94,7 +98,7 @@ void bot_think(entity this) // (simulated network latency + naturally delayed reflexes) //this.ping = 0.7 - bound(0, 0.05 * skill, 0.5); // moved the reflexes to bot_aimdir (under the name 'think') // minimum ping 20+10 random - this.ping = bound(0,0.07 - bound(0, (skill + this.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server + CS(this).ping = bound(0,0.07 - bound(0, (skill + this.bot_pingskill) * 0.005,0.05)+random()*0.01,0.65); // Now holds real lag to server, and higer skill players take a less laggy server // skill 10 = ping 0.2 (adrenaline) // skill 0 = ping 0.7 (slightly drunk) @@ -113,7 +117,7 @@ void bot_think(entity this) if (time < game_starttime) { // block the bot during the countdown to game start - this.movement = '0 0 0'; + CS(this).movement = '0 0 0'; this.bot_nextthink = game_starttime; return; } @@ -121,11 +125,14 @@ void bot_think(entity this) // if dead, just wait until we can respawn if (IS_DEAD(this)) { - this.movement = '0 0 0'; + if (bot_waypoint_queue_owner == this) + bot_waypoint_queue_owner = NULL; + this.aistatus = 0; + CS(this).movement = '0 0 0'; if (this.deadflag == DEAD_DEAD) { PHYS_INPUT_BUTTON_JUMP(this) = true; // press jump to respawn - this.bot_strategytime = 0; + navigation_goalrating_timeout_force(this); } } else if(this.aistatus & AI_STATUS_STUCK) @@ -158,7 +165,7 @@ void bot_setnameandstuff(entity this) if(file < 0) { - LOG_INFO(strcat("Error: Can not open the bot configuration file '",autocvar_bot_config_file,"'\n")); + LOG_INFOF("Error: Can not open the bot configuration file '%s'", autocvar_bot_config_file); readfile = ""; } else @@ -175,13 +182,13 @@ void bot_setnameandstuff(entity this) continue; s = argv(0); prio = 1; - FOREACH_CLIENT(IS_BOT_CLIENT(it), LAMBDA( + FOREACH_CLIENT(IS_BOT_CLIENT(it), { if(s == it.cleanname) { prio = 0; break; } - )); + }); RandomSelection_AddString(readfile, 1, prio); } readfile = RandomSelection_chosen_string; @@ -236,7 +243,7 @@ void bot_setnameandstuff(entity this) this.bot_config_loaded = true; - // this is really only a default, JoinBestTeam is called later + // this is really only a default, TeamBalance_JoinBestTeam is called later setcolor(this, stof(bot_shirt) * 16 + stof(bot_pants)); this.bot_preferredcolors = this.clientcolors; @@ -247,16 +254,23 @@ void bot_setnameandstuff(entity this) name = bot_name; // number bots with identical names - int j = 0; - FOREACH_CLIENT(IS_BOT_CLIENT(it), LAMBDA( - if(it.cleanname == name) - ++j; - )); - if (j) - this.netname = this.netname_freeme = strzone(strcat(prefix, name, "(", ftos(j), ")", suffix)); - else + if (name == "") + { + name = ftos(etof(this)); this.netname = this.netname_freeme = strzone(strcat(prefix, name, suffix)); - + } + else + { + int j = 0; + FOREACH_CLIENT(IS_BOT_CLIENT(it), { + if(it.cleanname == name) + ++j; + }); + if (j) + this.netname = this.netname_freeme = strzone(strcat(prefix, name, "(", ftos(j), ")", suffix)); + else + this.netname = this.netname_freeme = strzone(strcat(prefix, name, suffix)); + } this.cleanname = strzone(name); // pick the model and skin @@ -264,9 +278,6 @@ void bot_setnameandstuff(entity this) bot_model = strcat(bot_model, ".iqm"); this.playermodel = this.playermodel_freeme = strzone(strcat("models/player/", bot_model)); this.playerskin = this.playerskin_freeme = strzone(bot_skin); - - this.cvar_cl_accuracy_data_share = 1; // share the bots weapon accuracy data with the NULL - this.cvar_cl_accuracy_data_receive = 0; // don't receive any weapon accuracy data } void bot_custom_weapon_priority_setup() @@ -374,14 +385,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; @@ -392,18 +402,10 @@ void bot_clientdisconnect(entity this) if (!IS_BOT_CLIENT(this)) return; bot_clearqueue(this); - if(this.cleanname) - strunzone(this.cleanname); - if(this.netname_freeme) - strunzone(this.netname_freeme); - if(this.playermodel_freeme) - strunzone(this.playermodel_freeme); - if(this.playerskin_freeme) - strunzone(this.playerskin_freeme); - this.cleanname = string_null; - this.netname_freeme = string_null; - this.playermodel_freeme = string_null; - this.playerskin_freeme = string_null; + strfree(this.cleanname); + strfree(this.netname_freeme); + strfree(this.playermodel_freeme); + strfree(this.playerskin_freeme); if(this.bot_cmd_current) delete(this.bot_cmd_current); if(bot_waypoint_queue_owner == this) @@ -420,7 +422,10 @@ void bot_clientconnect(entity this) this.createdtime = this.bot_nextthink; if(!this.bot_config_loaded) // This is needed so team overrider doesn't break between matches + { bot_setnameandstuff(this); + bot_setclientfields(this); + } if(this.bot_forced_team==1) this.team = NUM_TEAM_1; @@ -431,15 +436,15 @@ void bot_clientconnect(entity this) else if(this.bot_forced_team==4) this.team = NUM_TEAM_4; else - JoinBestTeam(this, false, true); + TeamBalance_JoinBestTeam(this); havocbot_setupbot(this); } void bot_removefromlargestteam() { - CheckAllowedTeams(NULL); - GetTeamCounts(NULL); + entity balance = TeamBalance_CheckAllowedTeams(NULL); + TeamBalance_GetTeamCounts(balance, NULL); entity best = NULL; float besttime = 0; @@ -458,12 +463,10 @@ void bot_removefromlargestteam() int thiscount = 0; - switch(it.team) + if (Team_IsValidTeam(it.team)) { - case NUM_TEAM_1: thiscount = c1; break; - case NUM_TEAM_2: thiscount = c2; break; - case NUM_TEAM_3: thiscount = c3; break; - case NUM_TEAM_4: thiscount = c4; break; + thiscount = TeamBalance_GetNumberOfPlayers(balance, + Team_TeamToIndex(it.team)); } if(thiscount > bestcount) @@ -478,6 +481,7 @@ void bot_removefromlargestteam() best = it; } }); + TeamBalance_Destroy(balance); if(!bcount) return; // no bots to remove currentbots = currentbots - 1; @@ -527,25 +531,25 @@ void autoskill(float factor) bestbot = -1; bestplayer = -1; - FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( + FOREACH_CLIENT(IS_PLAYER(it), { if(IS_REAL_CLIENT(it)) bestplayer = max(bestplayer, it.totalfrags - it.totalfrags_lastcheck); else bestbot = max(bestbot, it.totalfrags - it.totalfrags_lastcheck); - )); + }); - LOG_TRACE("autoskill: best player got ", ftos(bestplayer), ", "); - LOG_TRACE("best bot got ", ftos(bestbot), "; "); + LOG_DEBUG("autoskill: best player got ", ftos(bestplayer), ", "); + LOG_DEBUG("best bot got ", ftos(bestbot), "; "); if(bestbot < 0 || bestplayer < 0) { - LOG_TRACE("not doing anything"); + LOG_DEBUG("not doing anything"); // don't return, let it reset all counters below } else if(bestbot <= bestplayer * factor - 2) { if(autocvar_skill < 17) { - LOG_TRACE("2 frags difference, increasing skill"); + LOG_DEBUG("2 frags difference, increasing skill"); cvar_set("skill", ftos(autocvar_skill + 1)); bprint("^2SKILL UP!^7 Now at level ", ftos(autocvar_skill), "\n"); } @@ -554,30 +558,29 @@ void autoskill(float factor) { if(autocvar_skill > 0) { - LOG_TRACE("2 frags difference, decreasing skill"); + LOG_DEBUG("2 frags difference, decreasing skill"); cvar_set("skill", ftos(autocvar_skill - 1)); bprint("^1SKILL DOWN!^7 Now at level ", ftos(autocvar_skill), "\n"); } } else { - LOG_TRACE("not doing anything"); + LOG_DEBUG("not doing anything"); return; // don't reset counters, wait for them to accumulate } - FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.totalfrags_lastcheck = it.totalfrags)); + FOREACH_CLIENT(IS_PLAYER(it), { it.totalfrags_lastcheck = it.totalfrags; }); } void bot_calculate_stepheightvec() { stepheightvec = autocvar_sv_stepheight * '0 0 1'; - jumpstepheightvec = stepheightvec + - ((autocvar_sv_jumpvelocity * autocvar_sv_jumpvelocity) / (2 * autocvar_sv_gravity)) * '0 0 0.85'; - // 0.75 factor is for safety to make the jumps easy + jumpheight_vec = (autocvar_sv_jumpvelocity ** 2) / (2 * autocvar_sv_gravity) * '0 0 1'; + jumpstepheightvec = stepheightvec + jumpheight_vec * 0.85; // reduce it a bit to make the jumps easy } -float bot_fixcount() +bool bot_fixcount() { int activerealplayers = 0; int realplayers = 0; @@ -585,16 +588,14 @@ float bot_fixcount() activerealplayers = M_ARGV(0, int); realplayers = M_ARGV(1, int); } else { - FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA( + FOREACH_CLIENT(IS_REAL_CLIENT(it), { if(IS_PLAYER(it)) ++activerealplayers; ++realplayers; - )); + }); } 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; @@ -602,15 +603,17 @@ float bot_fixcount() 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; - realminplayers = autocvar_minplayers; - minplayers = max(0, floor(realminplayers)); + int minplayers = max(0, floor(autocvar_minplayers)); + int minbots = max(0, floor(autocvar_bot_number)); - float realminbots, minbots; - realminbots = autocvar_bot_number; - minbots = max(0, floor(realminbots)); + // add bots to reach minplayers if needed + bots = max(minbots, minplayers - activerealplayers); + // cap bots to the max players allowed by the server + int player_limit = GetPlayerLimit(); + if(player_limit) + bots = min(bots, player_limit - activerealplayers); + bots = min(bots, maxclients - realplayers); - bots = min(max(minbots, minplayers - activerealplayers), maxclients - realplayers); if(bots > minbots) bots_would_leave = true; } @@ -623,8 +626,7 @@ float bot_fixcount() // only add one bot per frame to avoid utter chaos if(time > botframe_nextthink) { - //dprint(ftos(bots), " ? ", ftos(currentbots), "\n"); - while (currentbots < bots) + if (currentbots < bots) { if (bot_spawn() == NULL) { @@ -674,11 +676,51 @@ void bot_clear(entity this) void bot_serverframe() { + if (intermission_running && currentbots > 0) + { + // after the end of the match all bots stay unless all human players disconnect + int realplayers = 0; + FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++realplayers; }); + if (!realplayers) + { + FOREACH_CLIENT(IS_BOT_CLIENT(it), { dropclient(it); }); + currentbots = 0; + } + return; + } + if (game_stopped) return; - if (time < 2) + // Added 0.5 to avoid possible addition + immediate removal of bots that would make them appear as + // spectators in the scoreboard and never go away. This issue happens at time 2 if map is changed + // with the gotomap command, minplayers is > 1 and human clients join as players very soon + // either intentionally or automatically (sv_spectate 0) + if (time < 2.5) + { + currentbots = -1; return; + } + + if (currentbots == -1) + { + // count bots already in the server from the previous match + currentbots = 0; + FOREACH_CLIENT(IS_BOT_CLIENT(it), { ++currentbots; }); + } + + if(autocvar_skill != skill) + { + float wpcost_update = false; + if(skill >= autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill < autocvar_bot_ai_bunnyhop_skilloffset) + wpcost_update = true; + if(skill < autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill >= autocvar_bot_ai_bunnyhop_skilloffset) + wpcost_update = true; + + skill = autocvar_skill; + if (wpcost_update) + waypoint_updatecost_foralllinks(); + } bot_calculate_stepheightvec(); bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL); @@ -698,8 +740,6 @@ void bot_serverframe() botframe_nextthink = time + 10; } - bot_ignore_bots = autocvar_bot_ignore_bots; - if(botframe_spawnedwaypoints) { if(autocvar_waypoint_benchmark) @@ -728,8 +768,7 @@ void bot_serverframe() { botframe_spawnedwaypoints = true; waypoint_loadall(); - if(!waypoint_load_links()) - waypoint_schedulerelinkall(); + waypoint_load_links(); } if (bot_list) @@ -739,11 +778,26 @@ void bot_serverframe() // frame, which causes choppy framerates) if (bot_strategytoken_taken) { + // give goal token to the first bot without goals; if all bots don't have + // any goal (or are dead/frozen) simply give it to the next one bot_strategytoken_taken = false; - if (bot_strategytoken) - bot_strategytoken = bot_strategytoken.nextbot; - if (!bot_strategytoken) - bot_strategytoken = bot_list; + entity bot_strategytoken_save = bot_strategytoken; + while (true) + { + if (bot_strategytoken) + bot_strategytoken = bot_strategytoken.nextbot; + if (!bot_strategytoken) + bot_strategytoken = bot_list; + + if (!(IS_DEAD(bot_strategytoken) || STAT(FROZEN, bot_strategytoken)) + && !bot_strategytoken.goalcurrent) + break; + + if (!bot_strategytoken_save) // break loop if all the bots are dead or frozen + break; + if (bot_strategytoken == bot_strategytoken_save) + bot_strategytoken_save = NULL; // looped through all the bots + } } if (botframe_nextdangertime < time)