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/Walkbot.cpp
2018-05-01 17:09:15 +02:00

1230 lines
34 KiB
C++

/*
* Walkbot.cpp
*
* Created on: Jul 23, 2017
* Author: nullifiedcat
*/
#include "common.hpp"
#include "hack.hpp"
#include <boost/algorithm/string.hpp>
#include <sys/dir.h>
#include <sys/stat.h>
#include <hacks/hacklist.hpp>
namespace hacks
{
namespace shared
{
namespace walkbot
{
using index_t = unsigned;
using connection = uint8_t;
constexpr unsigned VERSION = 2;
constexpr index_t BAD_NODE = unsigned(-1);
constexpr connection MAX_CONNECTIONS = 6;
constexpr connection BAD_CONNECTION = uint8_t(-1);
index_t CreateNode(const Vector &xyz);
void DeleteNode(index_t node);
float distance_2d(Vector &xyz);
void Save(std::string filename);
bool Load(std::string filename);
const char *prevlvlname = "";
enum ENodeFlags
{
NF_GOOD = (1 << 0),
NF_DUCK = (1 << 1),
NF_JUMP = (1 << 2)
};
enum EWalkbotState
{
WB_DISABLED,
WB_RECORDING,
WB_EDITING,
WB_REPLAYING
};
struct walkbot_header_s
{
unsigned version{ VERSION };
size_t node_count{ 0 };
size_t map_length{ 0 };
size_t author_length{ 0 };
};
enum EConnectionFlags
{
CF_GOOD = (1 << 0),
CF_LOW_HEALTH = (1 << 1),
CF_LOW_AMMO = (1 << 2),
CF_RED = (1 << 3),
CF_BLU = (1 << 4),
CF_CAPPED_1 = (1 << 5),
CF_CAPPED_2 = (1 << 6),
CF_CAPPED_3 = (1 << 7),
CF_CAPPED_4 = (1 << 8),
CF_CAPPED_5 = (1 << 9)
};
struct connection_s
{
index_t node{ BAD_NODE };
unsigned flags{ 0 };
void link(index_t a)
{
flags |= CF_GOOD;
node = a;
}
void unlink()
{
flags = 0;
node = BAD_NODE;
}
bool good() const
{
return (flags & CF_GOOD);
}
bool free() const
{
return not good();
}
};
struct walkbot_node_s
{
float x{ 0 };
float y{ 0 };
float z{ 0 };
unsigned flags{ 0 };
connection_s connections[MAX_CONNECTIONS];
Vector &xyz()
{
return *reinterpret_cast<Vector *>(&x);
}
connection free_connection() const
{
for (connection i = 0; i < MAX_CONNECTIONS; i++)
{
if (connections[i].free())
return i;
}
return BAD_CONNECTION;
}
void link(index_t node)
{
connection free = free_connection();
if (free == BAD_CONNECTION)
{
logging::Info("[wb] Too many connections! Node at (%.2f %.2f %.2f)",
x, y, z);
return;
}
connections[free].link(node);
}
void unlink(index_t node)
{
for (connection i = 0; i < MAX_CONNECTIONS; i++)
{
if (connections[i].good() and connections[i].node == node)
{
connections[i].unlink();
}
}
}
}; // 40
float distance_2d(Vector &xyz)
{
float dx = xyz.x - g_pLocalPlayer->v_Origin.x;
float dy = xyz.y - g_pLocalPlayer->v_Origin.y;
return sqrt(dx * dx + dy * dy);
}
namespace state
{
index_t free_node();
// A vector containing all loaded nodes, used in both recording and replaying
std::vector<walkbot_node_s> nodes{};
bool node_good(index_t node)
{
return node != BAD_NODE && node < nodes.size() &&
(nodes[node].flags & NF_GOOD);
}
// Target node when replaying, selected node when editing, last node when
// recording
index_t active_node{ BAD_NODE };
walkbot_node_s *active()
{
if (node_good(active_node))
return &nodes[active_node];
return nullptr;
}
// Last reached node when replaying
index_t last_node{ BAD_NODE };
walkbot_node_s *last()
{
if (node_good(last_node))
return &nodes[last_node];
return nullptr;
}
// Node closest to your crosshair when editing
index_t closest_node{ BAD_NODE };
walkbot_node_s *closest()
{
if (node_good(closest_node))
return &nodes[closest_node];
return nullptr;
}
// Global state
EWalkbotState state{ WB_DISABLED };
// g_pUserCmd->buttons state when last node was recorded
int last_node_buttons{ 0 };
// Set to true when bot is moving to nearest node after dying/losing its active
// node
bool recovery{ true };
// Time when bot started to move towards next point
std::chrono::system_clock::time_point time{};
// A little bit too expensive function, finds next free node or creates one if
// no free slots exist
index_t free_node()
{
for (index_t i = 0; i < nodes.size(); i++)
{
if (not(nodes[i].flags & NF_GOOD))
return i;
}
nodes.emplace_back();
return nodes.size() - 1;
}
}
using state::nodes;
using state::node_good;
bool HasLowAmmo()
{
int *weapon_list =
(int *) ((unsigned) (RAW_ENT(LOCAL_E)) + netvar.hMyWeapons);
for (int i = 0; weapon_list[i]; i++)
{
int handle = weapon_list[i];
int eid = handle & 0xFFF;
if (eid >= 32 && eid <= HIGHEST_ENTITY)
{
IClientEntity *weapon = g_IEntityList->GetClientEntity(eid);
if (weapon and
re::C_BaseCombatWeapon::IsBaseCombatWeapon(weapon) and
re::C_TFWeaponBase::UsesPrimaryAmmo(weapon) and
not re::C_TFWeaponBase::HasPrimaryAmmo(weapon))
{
return true;
}
}
}
return false;
}
bool HasLowHealth()
{
return float(LOCAL_E->m_iHealth) / float(LOCAL_E->m_iMaxHealth) < 0.45;
}
void DeleteNode(index_t node)
{
if (not node_good(node))
return;
logging::Info("[wb] Deleting node %u", node);
auto &n = nodes[node];
for (size_t i = 0; i < MAX_CONNECTIONS; i++)
{
if (n.connections[i].good() and node_good(n.connections[i].node))
{
nodes[n.connections[i].node].unlink(node);
}
}
memset(&n, 0, sizeof(walkbot_node_s));
}
#define BINARY_FILE_WRITE(handle, data) \
handle.write(reinterpret_cast<const char *>(&data), sizeof(data))
#define BINARY_FILE_READ(handle, data) \
handle.read(reinterpret_cast<char *>(&data), sizeof(data))
void Save(std::string filename)
{
if (g_Settings.bInvalid)
{
logging::Info("Not in-game, cannot save!");
return;
}
{
DIR *walkbot_dir = opendir(DATA_PATH "/walkbot");
if (!walkbot_dir)
{
logging::Info("Walkbot directory doesn't exist, creating one!");
mkdir(DATA_PATH "/walkbot", S_IRWXU | S_IRWXG);
}
else
closedir(walkbot_dir);
}
std::string path = format(DATA_PATH "/walkbot/", GetLevelName());
{
DIR *level_dir = opendir(path.c_str());
if (!level_dir)
{
logging::Info(
"Walkbot directory for %s doesn't exist, creating one!",
GetLevelName().c_str());
mkdir(path.c_str(), S_IRWXU | S_IRWXG);
}
else
closedir(level_dir);
}
logging::Info("Saving in %s", format(path, "/", filename).c_str());
try
{
std::ofstream file(format(path, "/", filename),
std::ios::out | std::ios::binary);
if (not file)
{
logging::Info("Could not open file!");
return;
}
walkbot_header_s header;
header.node_count = state::nodes.size();
const char *name = g_ISteamFriends->GetPersonaName();
const char *map = g_IEngine->GetLevelName();
size_t name_s = strlen(name);
size_t map_s = strlen(map);
header.author_length = name_s;
header.map_length = map_s;
BINARY_FILE_WRITE(file, header);
file.write(map, map_s);
file.write(name, name_s);
file.write(reinterpret_cast<const char *>(state::nodes.data()),
sizeof(walkbot_node_s) * header.node_count);
file.close();
logging::Info("Writing successful");
}
catch (std::exception &e)
{
logging::Info("Writing unsuccessful: %s", e.what());
}
}
bool Load(std::string filename)
{
{
DIR *walkbot_dir = opendir(DATA_PATH "/walkbot");
if (!walkbot_dir)
{
logging::Info("Walkbot directory doesn't exist!");
return false;
}
else
closedir(walkbot_dir);
}
std::string path = format(DATA_PATH "/walkbot/", GetLevelName());
{
DIR *level_dir = opendir(path.c_str());
if (!level_dir)
{
logging::Info(
"Walkbot directory for %s doesn't exist, creating one!",
GetLevelName().c_str());
mkdir(path.c_str(), S_IRWXU | S_IRWXG);
}
else
closedir(level_dir);
}
try
{
std::ifstream file(format(path, "/", filename),
std::ios::in | std::ios::binary);
if (!file)
{
return false;
}
walkbot_header_s header;
BINARY_FILE_READ(file, header);
// FIXME magic number: 1
if (header.version != VERSION)
{
logging::Info("Outdated/corrupted walkbot file! Cannot load this.");
file.close();
return false;
}
if (header.author_length > 64 or header.map_length > 512 or
(not header.author_length or not header.map_length))
{
logging::Info("Corrupted author/level data");
}
else
{
char name_buffer[header.author_length + 1];
char map_buffer[header.map_length + 1];
file.read(map_buffer, header.map_length);
file.read(name_buffer, header.author_length);
name_buffer[header.author_length] = 0;
map_buffer[header.map_length] = 0;
logging::Info("Walkbot navigation map for %s\nAuthor: %s",
map_buffer, name_buffer);
}
state::nodes.clear();
logging::Info("Reading %i entries...", header.node_count);
if (header.node_count > 32768)
{
logging::Info("Read %d nodes, max is %d. Aborting.",
header.node_count, 32768);
return false;
}
state::nodes.resize(header.node_count);
file.read(reinterpret_cast<char *>(state::nodes.data()),
sizeof(walkbot_node_s) * header.node_count);
file.close();
logging::Info("Reading successful! Result: %i entries.",
state::nodes.size());
return true;
}
catch (std::exception &e)
{
logging::Info("Reading unsuccessful: %s", e.what());
}
return false;
}
static CatCommand save("wb_save", "Save", [](const CCommand &args) {
logging::Info("Saving");
std::string filename = "default";
if (args.ArgC() > 1)
{
filename = args.Arg(1);
}
Save(filename);
});
static CatCommand load("wb_load", "Load", [](const CCommand &args) {
logging::Info("Loading");
std::string filename = "default";
if (args.ArgC() > 1)
{
filename = args.Arg(1);
}
Load(filename);
});
index_t CreateNode(const Vector &xyz)
{
index_t node = state::free_node();
logging::Info("[wb] Creating node %u at (%.2f %.2f %.2f)", node, xyz.x,
xyz.y, xyz.z);
auto &n = state::nodes[node];
memset(&n, 0, sizeof(n));
n.xyz() = xyz;
n.flags |= NF_GOOD;
return node;
}
static CatVar active_recording(CV_SWITCH, "wb_recording", "0", "Do recording",
"Use BindToggle with this");
static CatVar draw_info(CV_SWITCH, "wb_info", "1", "Walkbot info");
static CatVar draw_path(CV_SWITCH, "wb_path", "1", "Walkbot path");
static CatVar draw_nodes(CV_SWITCH, "wb_nodes", "1", "Walkbot nodes");
static CatVar draw_indices(CV_SWITCH, "wb_indices", "0", "Node indices");
static CatVar free_move(CV_SWITCH, "wb_freemove", "1", "Allow free movement",
"Allow free movement while pressing movement keys");
static CatVar spawn_distance(CV_FLOAT, "wb_node_spawn_distance", "54",
"Node spawn distance");
static CatVar max_distance(CV_FLOAT, "wb_replay_max_distance", "100",
"Max distance to node when replaying");
static CatVar reach_distance(
CV_FLOAT, "wb_replay_reach_distance", "32",
"Distance where bot can be considered 'stepping' on the node");
static CatVar draw_connection_flags(CV_SWITCH, "wb_connection_flags", "1",
"Connection flags");
static CatVar force_slot(CV_INT, "wb_force_slot", "1", "Force slot",
"Walkbot will always select weapon in this slot");
static CatVar leave_if_empty(CV_SWITCH, "wb_leave_if_empty", "0",
"Leave if no walkbot",
"Leave game if there is no walkbot map");
CatCommand c_start_recording("wb_record", "Start recording",
[]() { state::state = WB_RECORDING; });
CatCommand c_start_editing("wb_edit", "Start editing",
[]() { state::state = WB_EDITING; });
CatCommand c_start_replaying("wb_replay", "Start replaying", []() {
state::last_node = state::active_node;
state::active_node = state::closest_node;
state::state = WB_REPLAYING;
});
CatCommand c_exit("wb_exit", "Exit", []() { state::state = WB_DISABLED; });
// Selects closest node, clears selection if node is selected
CatCommand c_select_node("wb_select", "Select node", []() {
if (state::active_node == state::closest_node)
{
state::active_node = BAD_NODE;
}
else
{
state::active_node = state::closest_node;
}
});
// Makes a new node in the middle of connection between 2 nodes
CatCommand c_split_connection("wb_split", "Split connection", []() {
if (not(state::node_good(state::active_node) and
state::node_good(state::closest_node)))
return;
if (state::active_node == state::closest_node)
return;
auto &a = state::nodes[state::active_node];
auto &b = state::nodes[state::closest_node];
a.unlink(state::closest_node);
b.unlink(state::active_node);
index_t node = CreateNode((a.xyz() + b.xyz()) / 2);
auto &n = state::nodes[node];
a.link(node);
n.link(state::active_node);
b.link(node);
n.link(state::closest_node);
});
// Deletes closest node and its connections
CatCommand c_delete_node("wb_delete", "Delete node",
[]() { DeleteNode(state::closest_node); });
// Creates a new node under your feet and connects it to closest node to your
// crosshair
CatCommand c_create_node("wb_create", "Create node", []() {
index_t node = CreateNode(g_pLocalPlayer->v_Origin);
auto &n = state::nodes[node];
if (g_pUserCmd->buttons & IN_DUCK)
n.flags |= NF_DUCK;
if (state::node_good(state::closest_node))
{
auto &c = state::nodes[state::closest_node];
n.link(state::closest_node);
c.link(node);
logging::Info("[wb] Node %u linked to node %u at (%.2f %.2f %.2f)",
node, state::closest_node, c.x, c.y, c.z);
}
});
// Connects selected node to closest one
CatCommand c_connect_node("wb_connect", "Connect nodes", []() {
if (not(state::node_good(state::active_node) and
state::node_good(state::closest_node)))
return;
// Don't link a node to itself, idiot
if (state::active_node == state::closest_node)
return;
auto &a = state::nodes[state::active_node];
auto &b = state::nodes[state::closest_node];
a.link(state::closest_node);
b.link(state::active_node);
});
// Makes a one-way connection
CatCommand
c_connect_single_node("wb_connect_single", "Connect nodes (one-way)", []() {
if (not(state::node_good(state::active_node) and
state::node_good(state::closest_node)))
return;
// Don't link a node to itself, idiot
if (state::active_node == state::closest_node)
return;
auto &a = state::nodes[state::active_node];
a.link(state::closest_node);
});
// Connects selected node to closest one
CatCommand c_disconnect_node("wb_disconnect", "Disconnect nodes", []() {
if (not(state::node_good(state::active_node) and
state::node_good(state::closest_node)))
return;
// Don't link a node to itself, idiot
if (state::active_node == state::closest_node)
return;
auto &a = state::nodes[state::active_node];
auto &b = state::nodes[state::closest_node];
a.unlink(state::closest_node);
b.unlink(state::active_node);
});
// Makes a one-way connection
CatCommand c_disconnect_single_node(
"wb_disconnect_single", "Connect nodes (one-way)", []() {
if (not(state::node_good(state::active_node) and
state::node_good(state::closest_node)))
return;
// Don't link a node to itself, idiot
if (state::active_node == state::closest_node)
return;
auto &a = state::nodes[state::active_node];
a.unlink(state::closest_node);
});
// Toggles jump flag on closest node
CatCommand c_update_duck("wb_duck", "Toggle duck flag", []() {
if (not state::node_good(state::closest_node))
return;
auto &n = state::nodes[state::closest_node];
if (n.flags & NF_DUCK)
n.flags &= ~NF_DUCK;
else
n.flags |= NF_DUCK;
});
// Toggles jump flag on closest node
CatCommand c_update_jump("wb_jump", "Toggle jump flag", []() {
if (not state::node_good(state::closest_node))
return;
auto &n = state::nodes[state::closest_node];
if (n.flags & NF_JUMP)
n.flags &= ~NF_JUMP;
else
n.flags |= NF_JUMP;
});
// Assuming node is good and conn is in range [0; MAX_CONNECTIONS)
std::string DescribeConnection(index_t node, connection conn)
{
const connection_s &c = nodes[node].connections[conn];
std::string extra;
bool broken = not node_good(c.node);
bool oneway = true;
if (not broken)
{
auto &n = state::nodes[c.node];
for (size_t j = 0; j < MAX_CONNECTIONS; j++)
{
if (n.connections[j].good() and n.connections[j].node == node)
{
oneway = false;
break;
}
}
if (c.flags & CF_LOW_AMMO)
{
extra += "A";
}
if (c.flags & CF_LOW_HEALTH)
{
extra += "H";
}
}
std::string result =
format(node, ' ', (broken ? "-x>" : (oneway ? "-->" : "<->")), ' ',
c.node, ' ', extra);
return result;
}
CatCommand c_toggle_cf_ammo(
"wb_conn_ammo",
"Toggle 'ammo' flag on connection from ACTIVE to CLOSEST node", []() {
auto a = state::active();
auto b = state::closest();
if (not(a and b))
return;
for (connection i = 0; i < MAX_CONNECTIONS; i++)
{
auto &c = a->connections[i];
if (c.free())
continue;
if (c.node != state::closest_node)
continue;
// Actually flip the flag
if (c.flags & CF_LOW_AMMO)
c.flags &= ~CF_LOW_AMMO;
else
c.flags |= CF_LOW_AMMO;
}
});
CatCommand c_toggle_cf_health(
"wb_conn_health",
"Toggle 'health' flag on connection from ACTIVE to CLOSEST node", []() {
auto a = state::active();
auto b = state::closest();
if (not(a and b))
return;
for (connection i = 0; i < MAX_CONNECTIONS; i++)
{
auto &c = a->connections[i];
if (c.free())
continue;
if (c.node != state::closest_node)
continue;
// Actually flip the flag
if (c.flags & CF_LOW_HEALTH)
c.flags &= ~CF_LOW_HEALTH;
else
c.flags |= CF_LOW_HEALTH;
}
});
// Displays all info about closest node and its connections
CatCommand c_info("wb_dump", "Show info", []() {
index_t node = state::closest_node;
if (not node_good(node))
return;
auto &n = nodes[node];
logging::Info("[wb] Info about node %u", node);
logging::Info("[wb] Flags: Duck=%d, Jump=%d, Raw=%u", n.flags & NF_DUCK,
n.flags & NF_JUMP, n.flags);
logging::Info("[wb] X: %.2f | Y: %.2f | Z: %.2f", n.x, n.y, n.z);
logging::Info("[wb] Connections:");
for (size_t i = 0; i < MAX_CONNECTIONS; i++)
{
if (n.connections[i].free())
continue;
logging::Info("[wb] %s", DescribeConnection(node, i).c_str());
}
});
// Deletes a whole region of nodes
// Deletes a single closest node if no node is selected
CatCommand c_delete_region("wb_delete_region", "Delete region of nodes", []() {
logging::Info("< DISABLED >");
/*index_t a = state::active_node;
index_t b = state::closest_node;
if (not (state::node_good(a) and state::node_good(b)))
return;
index_t current = state::closest_node;
index_t next = INVALID_NODE;
do {
auto& n = state::nodes[current];
if (n.connection_count > 2) {
logging::Info("[wb] More than 2 connections on a node! Quitting.");
return;
}
bool found_next = false;
for (size_t i = 0; i < 2; i++) {
if (n.connections[i] != current) {
next = n.connections[i];
found_next = true;
}
}
DeleteNode(current);
current = next;
if (not found_next) {
logging::Info("[wb] Dead end? Can't find next node after %u",
current); break;
}
} while (state::node_good(current) and (current != a));*/
});
// Clears the state
CatCommand c_clear("wb_clear", "Removes all nodes",
[]() { state::nodes.clear(); });
void Initialize()
{
}
void UpdateClosestNode()
{
float n_fov = 360.0f;
index_t n_idx = BAD_NODE;
for (index_t i = 0; i < state::nodes.size(); i++)
{
auto &node = state::nodes[i];
if (not node.flags & NF_GOOD)
continue;
// Eclipse shits itself when it sees Vector& beung used as Vector in
// GetFov
float fov = GetFov(g_pLocalPlayer->v_OrigViewangles,
g_pLocalPlayer->v_Eye, node.xyz());
if (fov < n_fov)
{
n_fov = fov;
n_idx = i;
}
}
// Don't select a node if you don't even look at it
if (n_fov < 10)
state::closest_node = n_idx;
else
state::closest_node = BAD_NODE;
}
// Finds nearest node by position, not FOV
// Not to be confused with FindClosestNode
index_t FindNearestNode(bool traceray)
{
index_t r_node{ BAD_NODE };
float r_dist{ 65536.0f };
for (index_t i = 0; i < state::nodes.size(); i++)
{
if (state::node_good(i))
{
auto &n = state::nodes[i];
if (traceray and
not IsVectorVisible(g_pLocalPlayer->v_Eye, n.xyz()))
continue;
float dist = distance_2d(n.xyz());
if (dist < r_dist)
{
r_dist = dist;
r_node = i;
}
}
}
return r_node;
}
index_t SelectNextNode()
{
if (not state::node_good(state::active_node))
{
return FindNearestNode(true);
}
auto &n = state::nodes[state::active_node];
// TODO medkit connections and shit
std::vector<index_t> chance{};
for (index_t i = 0; i < MAX_CONNECTIONS; i++)
{
if (n.connections[i].good() and
n.connections[i].node != state::last_node and
node_good(n.connections[i].node))
{
if (HasLowAmmo() && (n.connections[i].flags & CF_LOW_AMMO))
{
return n.connections[i].node;
}
if (HasLowHealth() && (n.connections[i].flags & CF_LOW_HEALTH))
{
return n.connections[i].node;
}
if (not(n.connections[i].flags & (CF_LOW_AMMO | CF_LOW_HEALTH)))
chance.push_back(n.connections[i].node);
}
}
if (not chance.empty())
{
return chance.at(unsigned(rand()) % chance.size());
}
else
{
return BAD_NODE;
}
return BAD_NODE;
}
bool free_move_used = false;
Timer slot_timer{};
void UpdateSlot()
{
if (!slot_timer.test_and_set(1000))
return;
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W) && !g_pLocalPlayer->life_state)
{
IClientEntity *weapon = RAW_ENT(LOCAL_W);
// IsBaseCombatWeapon()
if (re::C_BaseCombatWeapon::IsBaseCombatWeapon(weapon))
{
int slot = re::C_BaseCombatWeapon::GetSlot(weapon);
if (slot != int(force_slot) - 1)
{
hack::ExecuteCommand(format("slot", int(force_slot)));
}
}
}
}
void UpdateWalker()
{
free_move_used = false;
if (free_move)
{
if (g_pUserCmd->forwardmove != 0.0f or g_pUserCmd->sidemove != 0.0f)
{
free_move_used = true;
return;
}
}
static int jump_ticks = 0;
if (jump_ticks > 0)
{
g_pUserCmd->buttons |= IN_JUMP;
jump_ticks--;
}
bool timeout = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now() - state::time)
.count() > 1;
if (not state::node_good(state::active_node) or timeout)
{
state::active_node = FindNearestNode(true);
state::recovery = true;
}
auto &n = state::nodes[state::active_node];
WalkTo(n.xyz());
if (state::node_good(state::last_node))
{
auto &l = state::nodes[state::last_node];
if (l.flags & NF_DUCK)
g_pUserCmd->buttons |= IN_DUCK;
}
float dist = distance_2d(n.xyz());
if (dist > float(max_distance))
{
state::active_node = FindNearestNode(true);
state::recovery = true;
}
if (dist < float(reach_distance))
{
state::recovery = false;
index_t last = state::active_node;
state::active_node = SelectNextNode();
state::last_node = last;
state::time = std::chrono::system_clock::now();
if (state::node_good(state::active_node))
{
if (state::nodes[state::active_node].flags & NF_JUMP)
{
g_pUserCmd->buttons |= IN_DUCK;
g_pUserCmd->buttons |= IN_JUMP;
jump_ticks = 6;
}
}
else
{
if (not state::recovery)
{
state::recovery = true;
}
}
}
}
bool ShouldSpawnNode()
{
if (not state::node_good(state::active_node))
return true;
bool was_jumping = state::last_node_buttons & IN_JUMP;
bool is_jumping = g_pUserCmd->buttons & IN_JUMP;
if (was_jumping != is_jumping and is_jumping)
return true;
if ((state::last_node_buttons & IN_DUCK) != (g_pUserCmd->buttons & IN_DUCK))
return true;
auto &node = state::nodes[state::active_node];
if (distance_2d(node.xyz()) > float(spawn_distance))
{
return true;
}
return false;
}
void RecordNode()
{
index_t node = CreateNode(g_pLocalPlayer->v_Origin);
auto &n = state::nodes[node];
if (g_pUserCmd->buttons & IN_DUCK)
n.flags |= NF_DUCK;
if (g_pUserCmd->buttons & IN_JUMP)
n.flags |= NF_JUMP;
if (state::node_good(state::active_node))
{
auto &c = state::nodes[state::active_node];
n.link(state::active_node);
c.link(node);
logging::Info("[wb] Node %u auto-linked to node %u at (%.2f %.2f %.2f)",
node, state::active_node, c.x, c.y, c.z);
}
state::last_node_buttons = g_pUserCmd->buttons;
state::active_node = node;
}
#if ENABLE_VISUALS
// Draws a single colored connection between 2 nodes
void DrawConnection(index_t a, connection_s &b)
{
if (b.free())
return;
if (not(node_good(a) and node_good(b.node)))
return;
auto &a_ = state::nodes[a];
auto &b_ = state::nodes[b.node];
Vector center = (a_.xyz() + b_.xyz()) / 2;
Vector center_connection = (a_.xyz() + center) / 2;
Vector wts_a, wts_c, wts_cc;
if (not(draw::WorldToScreen(a_.xyz(), wts_a) and
draw::WorldToScreen(center, wts_c) and
draw::WorldToScreen(center_connection, wts_cc)))
return;
const rgba_t *color = &colors::white;
if ((a_.flags & b_.flags) & NF_JUMP)
color = &colors::yellow;
else if ((a_.flags & b_.flags) & NF_DUCK)
color = &colors::green;
draw_api::draw_line(wts_a.x, wts_a.y, wts_c.x - wts_a.x, wts_c.y - wts_a.y,
*color, 0.5f);
if (draw_connection_flags && b.flags != CF_GOOD)
{
std::string flags;
if (b.flags & CF_LOW_AMMO)
flags += "A";
if (b.flags & CF_LOW_HEALTH)
flags += "H";
// int size_x = 0, size_y = 0;
// FTGL_StringLength(flags, fonts::font_main, &size_x, &size_y);
draw_api::draw_string(wts_cc.x, wts_cc.y - 4, flags.c_str(),
fonts::main_font, colors::white);
}
}
// Draws a node and its connections
void DrawNode(index_t node, bool draw_back)
{
if (not state::node_good(node))
return;
auto &n = state::nodes[node];
for (size_t i = 0; i < MAX_CONNECTIONS; i++)
{
DrawConnection(node, n.connections[i]);
}
if (draw_nodes)
{
const rgba_t *color = &colors::white;
if (n.flags & NF_JUMP)
color = &colors::yellow;
else if (n.flags & NF_DUCK)
color = &colors::green;
Vector wts;
if (not draw::WorldToScreen(n.xyz(), wts))
return;
size_t node_size = 2;
if (state::state != WB_REPLAYING)
{
if (node == state::closest_node)
node_size = 6;
}
if (node == state::active_node)
color = &colors::red;
draw_api::draw_rect(wts.x - node_size, wts.y - node_size, 2 * node_size,
2 * node_size, *color);
}
if (draw_indices)
{
const rgba_t *color = &colors::white;
if (n.flags & NF_JUMP)
color = &colors::yellow;
else if (n.flags & NF_DUCK)
color = &colors::green;
Vector wts;
if (not draw::WorldToScreen(n.xyz(), wts))
return;
draw_api::draw_string_with_outline(
wts.x, wts.y, std::to_string(node).c_str(), fonts::main_font,
*color, colors::black, 1.5f);
}
}
void DrawPath()
{
for (index_t i = 0; i < state::nodes.size(); i++)
{
DrawNode(i, true);
}
}
void Draw()
{
if (state::state == WB_DISABLED)
return;
switch (state::state)
{
case WB_RECORDING:
{
AddSideString("Walkbot: Recording");
}
break;
case WB_EDITING:
{
AddSideString("Walkbot: Editing");
}
break;
case WB_REPLAYING:
{
AddSideString("Walkbot: Replaying");
if (free_move and free_move_used)
{
AddSideString("Walkbot: FREE MOVEMENT (User override)",
colors::green);
}
if (HasLowAmmo())
{
AddSideString("Walkbot: LOW AMMO", colors::yellow);
}
if (HasLowHealth())
{
AddSideString("Walkbot: LOW HEALTH", colors::red);
}
}
break;
}
if (draw_info)
{
AddSideString(format("Active node: ", state::active_node));
AddSideString(format("Highlighted node: ", state::closest_node));
AddSideString(format("Last node: ", state::last_node));
AddSideString(format("Node count: ", state::nodes.size()));
if (state::recovery)
{
AddSideString(format("(Recovery mode)"));
}
}
if (draw_path)
DrawPath();
}
#endif
void OnLevelInit()
{
if (leave_if_empty && state::state == WB_REPLAYING)
{
nodes.clear();
}
}
Timer quit_timer{};
Timer map_check{};
int erasedelay = 0;
void Move()
{
if (state::state == WB_DISABLED)
return;
switch (state::state)
{
case WB_RECORDING:
{
UpdateClosestNode();
if (active_recording and ShouldSpawnNode())
{
RecordNode();
}
}
break;
case WB_EDITING:
{
UpdateClosestNode();
}
break;
case WB_REPLAYING:
{
if (leave_if_empty)
{
if (nodes.size() == 0 || g_IEngine->GetLevelName() != prevlvlname)
{
prevlvlname = g_IEngine->GetLevelName();
if (!boost::contains(prevlvlname, "pl_"))
{
Load("default");
if (leave_if_empty && nodes.size() == 0 &&
quit_timer.test_and_set(5000))
{
logging::Info("No map file, abandon");
tfmm::abandon();
return;
}
}
}
}
prevlvlname = g_IEngine->GetLevelName();
std::string prvlvlname = format(prevlvlname);
logging::Info("%s %s", prevlvlname, prvlvlname.c_str());
if (boost::contains(prvlvlname, "pl_") ||
boost::contains(prvlvlname, "cp_"))
{
logging::Info("1");
bool ret = false;
if (lagexploit::pointarr[0] || lagexploit::pointarr[1] ||
lagexploit::pointarr[2] || lagexploit::pointarr[3] ||
lagexploit::pointarr[4])
{
hacks::shared::followbot::followbot = 1;
hacks::shared::followbot::roambot = 1;
hacks::shared::followbot::followcart = true;
}
else
{
hacks::shared::followbot::followbot = 0;
hacks::shared::followbot::roambot = 0;
hacks::shared::followbot::followcart = false;
}
}
if (nodes.size() == 0)
return;
if (force_slot)
UpdateSlot();
UpdateWalker();
}
break;
}
}
}
}
}