How would I view and edit a Bot's current difficulty level without removing the bot in Team Fortress 2?
I know you can use something like Dr. McKay's Bot Manager Random Difficulty (link: https://forums.alliedmods.net/showthread.php?p=1983621 )to create random difficulty bots. However, I want to be able to view a Bots difficulty level and change it if desired AFTER a bot is already spawned.
You can read a TFBot's difficulty with the m_nBotSkill netprop.
Setting the difficulty is a bit more... difficult, as that property actually doesn't control the skill level internally. For that you'll have to write to the CTFBot::m_skill property. Offset for that can be found after the CTFPlayer::HandleCommand_JoinTeam() call in the tf_bot_add command or in a few other functions. You'll probably want to modify both that value and the previously mentioned netprop.
This is the gamedata I use:
Code:
"Addresses"
{
"CTFBot::m_iSkill"
{
"linux"
{
"signature" "CTFBotVision::GetMinRecognizeTime()"
"offset" "43" // 0x2B
}
"windows"
{
// untested since I only run this on linux
"signature" "tf_bot_add(CCommand)"
"offset" "943" // 0x3AF
}
}
}
And corresponding code:
Code:
Address pSkillOffset = GameConfGetAddress(hGameConf, "CTFBot::m_iSkill");
int offs_CTFBot_iSkill = LoadFromAddress(pSkillOffset, NumberType_Int32);
// make sure the function hasn't changed drastically
if (offs_CTFBot_iSkill & 0xFFFF != offs_CTFBot_iSkill) {
LogError("Offset read for CTFBot::m_iSkill outside expected range (was %d). "
... "Bots will be stuck at normal difficulty.", offs_CTFBot_iSkill);
offs_CTFBot_iSkill = 0;
}
// ...
SetEntData(bot, offs_CTFBot_iSkill, difficulty);
At the time of writing the Linux / Windows member offsets are 0x25B0 and 0x25B4 respectively.
Unfortunately, the newly set skill level is only temporary. When the round ends or the Bot respawns the skill level will revert back to the orginal unset skill.
I then added the gamedata but that didn't seem to work. I either coded it wrong or the address is bad.
Code:
Spoiler
PHP Code:
#include <tf2_stocks> #pragma semicolon 1
#define PLUGIN_VERSION "1.0"
Handle g_hBotSkillConfig; Handle g_hBotSkill;
public Plugin myinfo = { name = "Bot Skills", author = "PC Gamer", description = "View and Set Bot Skill Level", version = PLUGIN_VERSION, url = "https://forums.alliedmods.net" }
public void OnPluginStart() { RegAdminCmd("sm_viewbotskill", Command_Viewbot, ADMFLAG_SLAY, "View a Bot Skill Level"); RegAdminCmd("sm_setbotskill", Command_Setbot, ADMFLAG_SLAY, "Set a Bot Skill Level"); LoadTranslations("core.phrases");
for (new i = 0; i < target_count; i++) { if (!IsFakeClient(target_list[i])) { ReplyToCommand(client, "Target is not a Bot"); return Plugin_Handled; }
Address pSkillOffset = GameConfGetAddress(g_hBotSkill, "CTFBot::m_iSkill"); int offs_CTFBot_iSkill = LoadFromAddress(pSkillOffset, NumberType_Int32);
// make sure the function hasn't changed drastically if (offs_CTFBot_iSkill & 0xFFFF != offs_CTFBot_iSkill) { LogError("Offset read for CTFBot::m_iSkill outside expected range (was %d). " ... "Bots will be stuck at normal difficulty.", offs_CTFBot_iSkill); offs_CTFBot_iSkill = 0; }
// Note: This next part works without gamedata but is only temporary. // I'll comment it out to try the gamedata code above. // SetEntProp(target_list[i], Prop_Send, "m_nBotSkill", ibotlvl); // ReplyToCommand(client, "Bot %N skill level is now: %i", target_list[i], ibotlvl); }
Unfortunately, the newly set skill level is only temporary. When the round ends or the Bot respawns the skill level will revert back to the orginal unset skill.
I then added the gamedata but that didn't seem to work. I either coded it wrong or the address is bad.
Haven't tested it at runtime myself. No reason why it shouldn't work, though -- I use it in an internal bot manager replacement.
Of course, you'll need the signature corresponding to the given function(s). I omitted them as an exercise for you to find out; I'm not planning on maintaining it across platforms in the event that they do change, but here they are.