This repository has been archived on 2024-06-01. You can view files and clone it, but cannot push or open issues or pull requests.
cathook/src/hacks/FollowBot.cpp
2018-07-22 12:45:17 +02:00

543 lines
18 KiB
C++

/*
* CREDITS TO ONEE-CHAN FOR IMPROVED FOLLOWBOT
*
*
*
*/
#include "common.hpp"
#include <hacks/FollowBot.hpp>
#include <hacks/LagExploit.hpp>
#include <glez/draw.hpp>
namespace hacks::shared::followbot
{
CatVar followbot(CV_SWITCH, "fb", "0", "Followbot Switch",
"Set to 1 in followbots' configs");
bool followcart = false;
CatVar roambot(CV_SWITCH, "fb_roaming", "1", "Roambot",
"Followbot will roam free, finding targets it can");
static CatVar draw_crumb(CV_SWITCH, "fb_draw", "1", "Draw crumbs",
"Self explanitory");
static CatVar follow_distance(CV_INT, "fb_distance", "175", "Follow Distance",
"How close the bots should stay to the target");
static CatVar follow_activation(CV_INT, "fb_activation", "1000",
"Activation Distance",
"How close a player should be until the "
"followbot will pick them as a target");
unsigned steamid = 0x0;
CatCommand follow_steam("fb_steam", "Follow Steam Id",
[](const CCommand &args) {
if (args.ArgC() < 1)
{
steamid = 0x0;
return;
}
steamid = atol(args.Arg(1));
});
static CatVar mimic_slot(CV_SWITCH, "fb_mimic_slot", "0", "Mimic weapon slot",
"Mimic follow target's weapon slot");
static CatVar always_medigun(CV_SWITCH, "fb_always_medigun", "0",
"Always Medigun", "Always use medigun");
static CatVar sync_taunt(CV_SWITCH, "fb_sync_taunt", "0", "Synced taunt",
"Taunt when follow target does");
static CatVar change(CV_SWITCH, "fb_switch", "0", "Change followbot target",
"Always change roaming target when possible");
static CatVar autojump(CV_SWITCH, "fb_autojump", "1", "Autojump",
"Automatically jump if stuck");
static CatVar afk(CV_SWITCH, "fb_afk", "1", "Switch target if AFK",
"Automatically switch target if the target is afk");
static CatVar afktime(
CV_INT, "fb_afk_time", "15000", "Max AFK Time",
"Max time in ms spent standing still before player gets declared afk");
static CatVar corneractivate(
CV_SWITCH, "fb_activation_corners", "1", "Activate around corners",
"Try to find an activation path to an entity behind a corner.");
// Something to store breadcrumbs created by followed players
static std::vector<Vector> breadcrumbs;
static const int crumb_limit = 64; // limit
// Followed entity, externed for highlight color
int follow_target = 0;
static bool inited;
Timer lastTaunt{}; // time since taunt was last executed, used to avoid kicks
Timer lastJump{};
std::array<Timer, 32> afkTicks; // for how many ms the player hasn't been moving
void checkAFK()
{
for (int i = 0; i < g_GlobalVars->maxClients; i++)
{
auto entity = ENTITY(i);
if (CE_BAD(entity))
continue;
if (!CE_VECTOR(entity, netvar.vVelocity).IsZero(60.0f))
{
afkTicks[i].update();
}
}
}
void init()
{
for (int i = 0; i < afkTicks.size(); i++)
{
afkTicks[i].update();
}
inited = true;
return;
}
// auto add checked crumbs for the walkbot to follow
void addCrumbs(CachedEntity *target, Vector corner = g_pLocalPlayer->v_Origin)
{
breadcrumbs.clear();
if (g_pLocalPlayer->v_Origin != corner)
{
Vector dist = corner - g_pLocalPlayer->v_Origin;
int maxiterations = floor(corner.DistTo(g_pLocalPlayer->v_Origin)) / 40;
for (int i = 0; i < maxiterations; i++)
{
breadcrumbs.push_back(g_pLocalPlayer->v_Origin +
dist / vectorMax(vectorAbs(dist)) * 40.0f *
(i + 1));
}
}
Vector dist = target->m_vecOrigin() - corner;
int maxiterations = floor(corner.DistTo(target->m_vecOrigin())) / 40;
for (int i = 0; i < maxiterations; i++)
{
breadcrumbs.push_back(
corner + dist / vectorMax(vectorAbs(dist)) * 40.0f * (i + 1));
}
}
int ClassPriority(CachedEntity *ent)
{
switch (g_pPlayerResource->GetClass(ent))
{
if (g_pPlayerResource->GetClass(ent) == tf_spy)
return 0;
case tf_engineer:
return 1;
case tf_medic:
return 2;
case tf_pyro:
return 3;
case tf_scout:
return 4;
case tf_sniper:
return 5;
case tf_demoman:
return 6;
case tf_soldier:
return 7;
case tf_heavy:
return 8;
default:
return 0;
}
}
Timer waittime{};
int lastent = 0;
void WorldTick()
{
if (!followbot)
{
follow_target = 0;
return;
}
if (!inited)
init();
// We need a local player to control
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer())
{
follow_target = 0;
return;
}
if (afk)
checkAFK();
// Still good check
if (follow_target)
{
// Overflow protection
if (breadcrumbs.size() > crumb_limit)
follow_target = 0;
// Still good check
else if (CE_BAD(ENTITY(follow_target)))
follow_target = 0;
}
if (!follow_target)
breadcrumbs.clear(); // no target == no path
// Target Selection
if (steamid)
{
// Find a target with the steam id, as it is prioritized
auto ent_count = HIGHEST_ENTITY;
for (int i = 0; i < ent_count; i++)
{
auto entity = ENTITY(i);
if (CE_BAD(entity)) // Exist + dormant
continue;
if (i == follow_target)
break;
if (entity->m_Type() != ENTITY_PLAYER)
continue;
if (steamid != entity->player_info.friendsID) // steamid check
continue;
if (!entity->m_bAlivePlayer()) // Dont follow dead players
continue;
if (corneractivate)
{
Vector indirectOrigin =
VischeckCorner(LOCAL_E, entity, float(follow_activation) / 2,
true); // get the corner location that the
// future target is visible from
std::pair<Vector, Vector> corners;
if (!indirectOrigin.z && entity->m_IDX == lastent) // if we couldn't find it, run wallcheck instead
{
corners = VischeckWall(LOCAL_E, entity, float(follow_activation) / 2, true);
if (!corners.first.z || !corners.second.z)
continue;
addCrumbs(LOCAL_E, corners.first);
addCrumbs(entity, corners.second);
}
if (indirectOrigin.z)
addCrumbs(entity, indirectOrigin);
else if (!indirectOrigin.z && !corners.first.z)
continue;
}
else
{
if (!VisCheckEntFromEnt(LOCAL_E, entity))
continue;
}
follow_target = entity->m_IDX;
break;
}
}
// If we dont have a follow target from that, we look again for someone
// else who is suitable
if ((!follow_target || change ||
(ClassPriority(ENTITY(follow_target)) < 6 &&
ENTITY(follow_target)->player_info.friendsID != steamid)) &&
roambot)
{
// Try to get a new target
auto ent_count =
followcart ? HIGHEST_ENTITY : g_IEngine->GetMaxClients();
for (int i = 0; i < ent_count; i++)
{
auto entity = ENTITY(i);
if (CE_BAD(entity)) // Exist + dormant
continue;
if (!followcart)
if (entity->m_Type() != ENTITY_PLAYER)
continue;
if (entity == LOCAL_E) // Follow self lol
continue;
if (entity->m_bEnemy())
continue;
if (afk &&
afkTicks[i].check(int(
afktime))) // don't follow target that was determined afk
continue;
if (IsPlayerDisguised(entity) || IsPlayerInvisible(entity))
continue;
if (!entity->m_bAlivePlayer()) // Dont follow dead players
continue;
if (follow_activation &&
entity->m_flDistance() > (float) follow_activation)
continue;
const model_t *model =
ENTITY(follow_target)->InternalEntity()->GetModel();
if (followcart && model &&
(lagexploit::pointarr[0] || lagexploit::pointarr[1] ||
lagexploit::pointarr[2] || lagexploit::pointarr[3] ||
lagexploit::pointarr[4]) &&
(model == lagexploit::pointarr[0] ||
model == lagexploit::pointarr[1] ||
model == lagexploit::pointarr[2] ||
model == lagexploit::pointarr[3] ||
model == lagexploit::pointarr[4]))
follow_target = entity->m_IDX;
if (entity->m_Type() != ENTITY_PLAYER)
continue;
// favor closer entitys
if (follow_target &&
ENTITY(follow_target)->m_flDistance() <
entity->m_flDistance()) // favor closer entitys
continue;
// check if new target has a higher priority than current target
if (ClassPriority(ENTITY(follow_target)) >=
ClassPriority(ENTITY(i)))
continue;
if (corneractivate)
{
Vector indirectOrigin =
VischeckCorner(LOCAL_E, entity, 250,
true); // get the corner location that the
// future target is visible from
std::pair<Vector, Vector> corners;
corners.first.z = 0;
corners.second.z = 0;
if (!indirectOrigin.z && entity->m_IDX == lastent) // if we couldn't find it, run wallcheck instead
{
corners = VischeckWall(LOCAL_E, entity, 250, true);
if (!corners.first.z || !corners.second.z)
continue;
addCrumbs(LOCAL_E, corners.first);
addCrumbs(entity, corners.second);
}
if (indirectOrigin.z)
addCrumbs(entity, indirectOrigin);
else if (!indirectOrigin.z && !corners.first.z)
continue;
}
else
{
if (!VisCheckEntFromEnt(LOCAL_E, entity))
continue;
}
// ooooo, a target
follow_target = i;
afkTicks[i].update(); // set afk time to 0
}
}
lastent++;
if (lastent > g_IEngine->GetMaxClients())
lastent = 0;
// last check for entity before we continue
if (!follow_target)
return;
CachedEntity *followtar = ENTITY(follow_target);
// wtf is this needed
if (CE_BAD(followtar) || !followtar->m_bAlivePlayer())
{
follow_target = 0;
return;
}
// Check if we are following a disguised/spy
if (IsPlayerDisguised(followtar) || IsPlayerInvisible(followtar))
{
follow_target = 0;
return;
}
// check if target is afk
if (afk)
{
if (afkTicks[follow_target].check(int(afktime)))
{
follow_target = 0;
return;
}
}
// Update timer on new target
static Timer idle_time{};
if (breadcrumbs.empty())
idle_time.update();
// If the player is close enough, we dont need to follow the path
auto tar_orig = followtar->m_vecOrigin();
auto loc_orig = LOCAL_E->m_vecOrigin();
auto dist_to_target = loc_orig.DistTo(tar_orig);
if ((dist_to_target < (float) follow_distance) &&
VisCheckEntFromEnt(LOCAL_E, followtar))
{
idle_time.update();
}
// New crumbs, we add one if its empty so we have something to follow
if ((breadcrumbs.empty() ||
tar_orig.DistTo(breadcrumbs.at(breadcrumbs.size() - 1)) > 40.0F) &&
DistanceToGround(ENTITY(follow_target)) < 45)
breadcrumbs.push_back(tar_orig);
// Prune old and close crumbs that we wont need anymore, update idle timer
// too
for (int i = 0; i < breadcrumbs.size(); i++)
{
if (loc_orig.DistTo(breadcrumbs.at(i)) < 60.f)
{
idle_time.update();
for (int j = 0; j <= i; j++)
breadcrumbs.erase(breadcrumbs.begin());
}
}
// Tauntsync
if (sync_taunt && HasCondition<TFCond_Taunting>(followtar) &&
lastTaunt.test_and_set(1000))
{
g_IEngine->ClientCmd("taunt");
}
// Follow the crumbs when too far away, or just starting to follow
if (dist_to_target > (float) follow_distance)
{
// Check for jump
if (autojump && lastJump.check(1000) && (idle_time.check(2000) || DistanceToGround({breadcrumbs[0].x,breadcrumbs[0].y,breadcrumbs[0].z + 5}) > 47))
{
g_pUserCmd->buttons |= IN_JUMP;
lastJump.update();
}
// Check if still moving. 70 HU = Sniper Zoomed Speed
if (idle_time.check(3000) && CE_VECTOR(g_pLocalPlayer->entity, netvar.vVelocity).IsZero(60.0f))
{
follow_target = 0;
return;
}
// Basic idle check
if (idle_time.test_and_set(5000))
{
follow_target = 0;
return;
}
static float last_slot_check = 0.0f;
if (g_GlobalVars->curtime < last_slot_check)
last_slot_check = 0.0f;
if (follow_target && (always_medigun || mimic_slot) &&
(g_GlobalVars->curtime - last_slot_check > 1.0f) &&
!g_pLocalPlayer->life_state &&
!CE_BYTE(ENTITY(follow_target), netvar.iLifeState))
{
// We are checking our slot so reset the timer
last_slot_check = g_GlobalVars->curtime;
// Get the follow targets active weapon
int owner_weapon_eid =
(CE_INT(ENTITY(follow_target), netvar.hActiveWeapon) & 0xFFF);
IClientEntity *owner_weapon =
g_IEntityList->GetClientEntity(owner_weapon_eid);
// If both the follow targets and the local players weapons arnt
// null or
// dormant
if (owner_weapon && CE_GOOD(g_pLocalPlayer->weapon()))
{
// IsBaseCombatWeapon()
if (re::C_BaseCombatWeapon::IsBaseCombatWeapon(
RAW_ENT(g_pLocalPlayer->weapon())) &&
re::C_BaseCombatWeapon::IsBaseCombatWeapon(owner_weapon))
{
// Get the players slot numbers and store in some vars
int my_slot = re::C_BaseCombatWeapon::GetSlot(
RAW_ENT(g_pLocalPlayer->weapon()));
int owner_slot =
re::C_BaseCombatWeapon::GetSlot(owner_weapon);
// If the local player is a medic and user settings allow,
// then
// keep the medigun out
if (g_pLocalPlayer->clazz == tf_medic && always_medigun)
{
if (my_slot != 1)
{
g_IEngine->ExecuteClientCmd("slot2");
}
// Else we attemt to keep our weapon mimiced with our
// follow
// target
}
else
{
if (my_slot != owner_slot)
{
g_IEngine->ExecuteClientCmd(
format("slot", owner_slot + 1).c_str());
}
}
}
}
}
WalkTo(breadcrumbs[0]);
}
else
idle_time.update();
}
void DrawTick()
{
#if ENABLE_VISUALS
if (!followbot || !draw_crumb)
return;
if (breadcrumbs.size() < 2)
return;
for (size_t i = 0; i < breadcrumbs.size() - 1; i++)
{
Vector wts1, wts2;
if (draw::WorldToScreen(breadcrumbs[i], wts1) &&
draw::WorldToScreen(breadcrumbs[i + 1], wts2))
{
glez::draw::line(wts1.x, wts1.y, wts2.x - wts1.x, wts2.y - wts1.y,
colors::white, 0.1f);
}
}
Vector wts;
if (!draw::WorldToScreen(breadcrumbs[0], wts))
return;
glez::draw::rect(wts.x - 4, wts.y - 4, 8, 8, colors::white);
glez::draw::rect_outline(wts.x - 4, wts.y - 4, 7, 7, colors::white, 1.0f);
#endif
}
#if ENABLE_IPC
static CatCommand
follow_me("fb_follow_me", "IPC connected bots will follow you", []() {
if (!ipc::peer)
{
logging::Info("IPC isnt connected");
return;
}
auto local_ent = LOCAL_E;
if (!local_ent)
{
logging::Info("Cant get a local player");
return;
}
player_info_s info;
g_IEngine->GetPlayerInfo(local_ent->m_IDX, &info);
auto steam_id = info.friendsID;
if (!steam_id)
{
logging::Info("Cant get steam-id, the game module probably doesnt "
"support it.");
return;
}
// Construct the command
std::string tmp =
CON_PREFIX + follow_steam.name + " " + std::to_string(steam_id);
if (tmp.length() >= 63)
{
ipc::peer->SendMessage(0, 0, ipc::commands::execute_client_cmd_long,
tmp.c_str(), tmp.length() + 1);
}
else
{
ipc::peer->SendMessage(tmp.c_str(), 0,
ipc::commands::execute_client_cmd, 0, 0);
}
});
#endif
}