commit
209f1eed7f
@ -71,6 +71,10 @@ void DrawCheatVisuals() {
|
|||||||
PROF_SECTION(DRAW_healarrows);
|
PROF_SECTION(DRAW_healarrows);
|
||||||
hacks::tf2::healarrow::Draw();
|
hacks::tf2::healarrow::Draw();
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
PROF_SECTION(DRAW_walkbot);
|
||||||
|
hacks::shared::walkbot::Draw();
|
||||||
|
}
|
||||||
IF_GAME(IsTF()) {
|
IF_GAME(IsTF()) {
|
||||||
PROF_SECTION(PT_antidisguise);
|
PROF_SECTION(PT_antidisguise);
|
||||||
SAFE_CALL(hacks::tf2::antidisguise::Draw());
|
SAFE_CALL(hacks::tf2::antidisguise::Draw());
|
||||||
|
@ -276,7 +276,7 @@ void hack::Initialize() {
|
|||||||
hacks::shared::spam::Init();
|
hacks::shared::spam::Init();
|
||||||
backpacktf::init();
|
backpacktf::init();
|
||||||
logging::Info("Initialized Backpack.TF integration");
|
logging::Info("Initialized Backpack.TF integration");
|
||||||
|
hacks::shared::walkbot::Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void hack::Think() {
|
void hack::Think() {
|
||||||
|
699
src/hacks/Walkbot.cpp
Normal file
699
src/hacks/Walkbot.cpp
Normal file
@ -0,0 +1,699 @@
|
|||||||
|
/*
|
||||||
|
* Walkbot.cpp
|
||||||
|
*
|
||||||
|
* Created on: Jul 23, 2017
|
||||||
|
* Author: nullifiedcat
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <sys/dir.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
namespace hacks { namespace shared { namespace walkbot {
|
||||||
|
|
||||||
|
using index_t = unsigned;
|
||||||
|
|
||||||
|
constexpr index_t INVALID_NODE = unsigned(-1);
|
||||||
|
constexpr size_t MAX_CONNECTIONS = 4;
|
||||||
|
|
||||||
|
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 { 1 };
|
||||||
|
unsigned node_count { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct walkbot_node_s {
|
||||||
|
struct {
|
||||||
|
float x { 0 }; // 4
|
||||||
|
float y { 0 }; // 8
|
||||||
|
float z { 0 }; // 12
|
||||||
|
};
|
||||||
|
unsigned flags { 0 }; // 16
|
||||||
|
size_t connection_count { 0 }; // 20
|
||||||
|
index_t connections[MAX_CONNECTIONS]; // 36
|
||||||
|
index_t preferred { INVALID_NODE }; // 40
|
||||||
|
|
||||||
|
Vector& xyz() {
|
||||||
|
return *reinterpret_cast<Vector*>(&x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void link(index_t node) {
|
||||||
|
if (connection_count == MAX_CONNECTIONS) {
|
||||||
|
logging::Info("[wb] Too many connections! Node at (%.2f %.2f %.2f)", x, y, z);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connections[connection_count++] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlink(index_t node) {
|
||||||
|
for (size_t i = 0; i < connection_count; i++) {
|
||||||
|
if (connections[i] == node) {
|
||||||
|
connections[i] = connections[connection_count];
|
||||||
|
connections[connection_count--] = INVALID_NODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}; // 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 {
|
||||||
|
|
||||||
|
// A vector containing all loaded nodes, used in both recording and replaying
|
||||||
|
std::vector<walkbot_node_s> nodes {};
|
||||||
|
|
||||||
|
// Target node when replaying, selected node when editing, last node when recording
|
||||||
|
index_t active_node { INVALID_NODE };
|
||||||
|
|
||||||
|
// Last reached node when replaying
|
||||||
|
index_t last_node { INVALID_NODE };
|
||||||
|
|
||||||
|
// Node closest to your crosshair when editing
|
||||||
|
index_t closest_node { INVALID_NODE };
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool node_good(index_t node) {
|
||||||
|
return node != INVALID_NODE && node < nodes.size() && (nodes[node].flags & NF_GOOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteNode(index_t node) {
|
||||||
|
if (not state::node_good(node))
|
||||||
|
return;
|
||||||
|
logging::Info("[wb] Deleting node %u", node);
|
||||||
|
auto& n = state::nodes[node];
|
||||||
|
for (size_t i = 0; i < n.connection_count && i < MAX_CONNECTIONS; i++) {
|
||||||
|
if (state::node_good(n.connections[i])) {
|
||||||
|
state::nodes[n.connections[i]].unlink(state::closest_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) {
|
||||||
|
{
|
||||||
|
DIR* walkbot_dir = opendir("cathook/walkbot");
|
||||||
|
if (!walkbot_dir) {
|
||||||
|
logging::Info("Walkbot directory doesn't exist, creating one!");
|
||||||
|
mkdir("cathook/walkbot", S_IRWXU | S_IRWXG);
|
||||||
|
} else closedir(walkbot_dir);
|
||||||
|
}
|
||||||
|
std::string path = format("cathook/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();
|
||||||
|
BINARY_FILE_WRITE(file, header);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load(std::string filename) {
|
||||||
|
{
|
||||||
|
DIR* walkbot_dir = opendir("cathook/walkbot");
|
||||||
|
if (!walkbot_dir) {
|
||||||
|
logging::Info("Walkbot directory doesn't exist, creating one!");
|
||||||
|
mkdir("cathook/walkbot", S_IRWXU | S_IRWXG);
|
||||||
|
} else closedir(walkbot_dir);
|
||||||
|
}
|
||||||
|
std::string path = format("cathook/walkbot/", GetLevelName());
|
||||||
|
{
|
||||||
|
DIR* level_dir = opendir(path.c_str());
|
||||||
|
if (!level_dir) {
|
||||||
|
logging::Info("Walkbot directory for %s doesn't exist, creating one!", GetLevelName());
|
||||||
|
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);
|
||||||
|
walkbot_header_s header;
|
||||||
|
BINARY_FILE_READ(file, header);
|
||||||
|
// FIXME magic number: 1
|
||||||
|
if (header.version != 1) {
|
||||||
|
logging::Info("Outdated/corrupted walkbot file! Cannot load this.");
|
||||||
|
file.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state::nodes.clear();
|
||||||
|
logging::Info("Reading %i entries...", header.node_count);
|
||||||
|
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());
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
logging::Info("Reading unsuccessful: %s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
n.xyz() = xyz;
|
||||||
|
n.preferred = INVALID_NODE;
|
||||||
|
n.connection_count = 0;
|
||||||
|
n.flags |= NF_GOOD;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
CatVar active_recording(CV_SWITCH, "wb_recording", "0", "Do recording", "Use BindToggle with this");
|
||||||
|
CatVar draw_info(CV_SWITCH, "wb_info", "1", "Walkbot info");
|
||||||
|
CatVar draw_path(CV_SWITCH, "wb_path", "1", "Walkbot path");
|
||||||
|
CatVar draw_nodes(CV_SWITCH, "wb_nodes", "1", "Walkbot nodes");
|
||||||
|
CatVar draw_indices(CV_SWITCH, "wb_indices", "1", "Node indices");
|
||||||
|
CatVar spawn_distance(CV_FLOAT, "wb_node_spawn_distance", "48", "Node spawn distance");
|
||||||
|
CatVar max_distance(CV_FLOAT, "wb_replay_max_distance", "100", "Max distance to node when replaying");
|
||||||
|
CatVar reach_distance(CV_FLOAT, "wb_replay_reach_distance", "16", "Distance where bot can be considered 'stepping' on the node");
|
||||||
|
|
||||||
|
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 = INVALID_NODE;
|
||||||
|
} else {
|
||||||
|
state::active_node = 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 node", []() {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
// Updates duck flag on region of nodes (selected to closest)
|
||||||
|
// Updates a single closest node if no node is selected
|
||||||
|
CatCommand c_update_duck("wb_duck", "Update duck flags", []() {
|
||||||
|
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;
|
||||||
|
|
||||||
|
do {
|
||||||
|
auto& n = state::nodes[current];
|
||||||
|
if (g_pUserCmd->buttons & IN_DUCK)
|
||||||
|
n.flags |= NF_DUCK;
|
||||||
|
else
|
||||||
|
n.flags &= ~NF_DUCK;
|
||||||
|
if (n.connection_count > 2) {
|
||||||
|
logging::Info("[wb] More than 2 connections on a node - instructions unclear, got my duck stuck in 'if' block");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool found_next = false;
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
if (n.connections[i] != current) {
|
||||||
|
current = n.connections[i];
|
||||||
|
found_next = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
// 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;
|
||||||
|
});
|
||||||
|
// Sets the closest node as preferred path for the selected node (or disable it)
|
||||||
|
CatCommand c_set_preferred("wb_prefer", "Set preferred node", []() {
|
||||||
|
index_t a = state::active_node;
|
||||||
|
index_t b = state::closest_node;
|
||||||
|
|
||||||
|
if (not (state::node_good(a) and state::node_good(b)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& n = state::nodes[a];
|
||||||
|
if (n.preferred == b) {
|
||||||
|
n.preferred = INVALID_NODE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (size_t i = 0; i < n.connection_count; i++) {
|
||||||
|
if (n.connections[i] == b) {
|
||||||
|
if (found) {
|
||||||
|
logging::Info("[wb] WARNING!!! Duplicate connection to %u on node %u!!!", a, b);
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.preferred = b;
|
||||||
|
});
|
||||||
|
// Displays all info about closest node
|
||||||
|
CatCommand c_info("wb_dump", "Show info", []() {
|
||||||
|
if (not state::node_good(state::closest_node))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& n = state::nodes[state::closest_node];
|
||||||
|
|
||||||
|
logging::Info("[wb] Info about node %u", state::closest_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: %d/%d", n.connection_count, MAX_CONNECTIONS);
|
||||||
|
for (size_t i = 0; i < n.connection_count; i++) {
|
||||||
|
logging::Info("[wb] %u <-> %u", state::closest_node, n.connections[i]);
|
||||||
|
auto& c = state::nodes[n.connections[i]];
|
||||||
|
bool found = false;
|
||||||
|
for (size_t j = 0; j < c.connection_count; j++) {
|
||||||
|
if (c.connections[j] == state::closest_node) {
|
||||||
|
if (found) {
|
||||||
|
logging::Info("[wb] DUPLICATE CONNECTION: %u <-> %u", i, state::closest_node);
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (not found) {
|
||||||
|
logging::Info("[wb] One-directional connection! (%u)", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 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", []() {
|
||||||
|
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 = INVALID_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 = INVALID_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds nearest node by position, not FOV
|
||||||
|
// Not to be confused with FindClosestNode
|
||||||
|
index_t FindNearestNode() {
|
||||||
|
index_t r_node { INVALID_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];
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
auto& n = state::nodes[state::active_node];
|
||||||
|
if (n.connection_count) {
|
||||||
|
if (state::node_good(n.preferred)) {
|
||||||
|
return n.preferred;
|
||||||
|
} else {
|
||||||
|
std::vector<index_t> chance {};
|
||||||
|
for (index_t i = 0; i < n.connection_count && i < MAX_CONNECTIONS; i++) {
|
||||||
|
if (n.connections[i] != state::active_node && n.connections[i] != state::last_node && state::node_good(n.connections[i])) {
|
||||||
|
chance.push_back(n.connections[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (not chance.empty()) {
|
||||||
|
return chance.at(rand() % chance.size());
|
||||||
|
} else {
|
||||||
|
return INVALID_NODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INVALID_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateWalker() {
|
||||||
|
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() > 2;
|
||||||
|
if (not state::node_good(state::active_node) or timeout) {
|
||||||
|
state::active_node = FindNearestNode();
|
||||||
|
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();
|
||||||
|
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();
|
||||||
|
logging::Info("[wb] Reached node %u, moving to %u", state::last_node, state::active_node);
|
||||||
|
if (state::node_good(state::active_node)) {
|
||||||
|
if (state::nodes[state::active_node].flags & NF_JUMP) {
|
||||||
|
g_pUserCmd->buttons |= IN_JUMP;
|
||||||
|
jump_ticks = 6;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (not state::recovery) {
|
||||||
|
logging::Info("[wb] FATAL: Next node is bad");
|
||||||
|
state::recovery = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws a single colored connection between 2 nodes
|
||||||
|
void DrawConnection(index_t a, index_t b) {
|
||||||
|
if (not (state::node_good(a) and state::node_good(b)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& a_ = state::nodes[a];
|
||||||
|
auto& b_ = state::nodes[b];
|
||||||
|
|
||||||
|
Vector center = (a_.xyz() + b_.xyz()) / 2;
|
||||||
|
|
||||||
|
Vector wts_a, wts_c;
|
||||||
|
if (not (draw::WorldToScreen(a_.xyz(), wts_a) and draw::WorldToScreen(center, wts_c)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (a_.preferred == b)
|
||||||
|
color = &colors::pink;
|
||||||
|
|
||||||
|
drawgl::Line(wts_a.x, wts_a.y, wts_c.x - wts_a.x, wts_c.y - wts_a.y, color->rgba);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 < n.connection_count && i < MAX_CONNECTIONS; i++) {
|
||||||
|
index_t connection = n.connections[i];
|
||||||
|
// To prevent drawing connections twice in a for loop, we only draw connections to nodes with higher index
|
||||||
|
if (not draw_back) {
|
||||||
|
if (connection < node)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DrawConnection(node, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw_nodes) {
|
||||||
|
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 (node == state::closest_node)
|
||||||
|
node_size = 6;
|
||||||
|
if (node == state::active_node)
|
||||||
|
color = &colors::red;
|
||||||
|
|
||||||
|
drawgl::FilledRect(wts.x - node_size, wts.y - node_size, 2 * node_size, 2 * node_size, color->rgba);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw_indices) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
FTGL_Draw(std::to_string(node), wts.x, wts.y, fonts::ftgl_ESP, *color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
if (draw_path)
|
||||||
|
DrawPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Move() {
|
||||||
|
if (state::state == WB_DISABLED) return;
|
||||||
|
switch (state::state) {
|
||||||
|
case WB_RECORDING: {
|
||||||
|
if (active_recording and ShouldSpawnNode()) {
|
||||||
|
RecordNode();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case WB_EDITING: {
|
||||||
|
UpdateClosestNode();
|
||||||
|
} break;
|
||||||
|
case WB_REPLAYING: {
|
||||||
|
UpdateWalker();
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}}}
|
16
src/hacks/Walkbot.hpp
Normal file
16
src/hacks/Walkbot.hpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Walkbot.hpp
|
||||||
|
*
|
||||||
|
* Created on: Jul 23, 2017
|
||||||
|
* Author: nullifiedcat
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace hacks { namespace shared { namespace walkbot {
|
||||||
|
|
||||||
|
void Initialize();
|
||||||
|
void Draw();
|
||||||
|
void Move();
|
||||||
|
|
||||||
|
}}}
|
@ -18,6 +18,7 @@
|
|||||||
#include "Bunnyhop.h"
|
#include "Bunnyhop.h"
|
||||||
#include "ESP.h"
|
#include "ESP.h"
|
||||||
#include "LagExploit.hpp"
|
#include "LagExploit.hpp"
|
||||||
|
#include "Walkbot.hpp"
|
||||||
#include "AntiBackstab.hpp"
|
#include "AntiBackstab.hpp"
|
||||||
#include "AutoBackstab.hpp"
|
#include "AutoBackstab.hpp"
|
||||||
#include "FollowBot.h"
|
#include "FollowBot.h"
|
||||||
|
@ -73,6 +73,38 @@ ConVar* CreateConVar(std::string name, std::string value, std::string help) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function for when you want to goto a vector
|
||||||
|
void WalkTo(const Vector& vector) {
|
||||||
|
// Calculate how to get to a vector
|
||||||
|
auto result = ComputeMove(LOCAL_E->m_vecOrigin, vector);
|
||||||
|
// Push our move to usercmd
|
||||||
|
g_pUserCmd->forwardmove = result.first;
|
||||||
|
g_pUserCmd->sidemove = result.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string GetLevelName() {
|
||||||
|
|
||||||
|
std::string name(g_IEngine->GetLevelName());
|
||||||
|
size_t slash = name.find('/');
|
||||||
|
if (slash == std::string::npos) slash = 0;
|
||||||
|
else slash++;
|
||||||
|
return name.substr(slash, name.length() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<float, float> ComputeMove(const Vector& a, const Vector& b) {
|
||||||
|
Vector diff = (b - a);
|
||||||
|
if (diff.Length() == 0) return { 0, 0 };
|
||||||
|
const float x = diff.x;
|
||||||
|
const float y = diff.y;
|
||||||
|
Vector vsilent(x, y, 0);
|
||||||
|
float speed = sqrt(vsilent.x * vsilent.x + vsilent.y * vsilent.y);
|
||||||
|
Vector ang;
|
||||||
|
VectorAngles(vsilent, ang);
|
||||||
|
float yaw = DEG2RAD(ang.y - g_pUserCmd->viewangles.y);
|
||||||
|
return { cos(yaw) * 450, -sin(yaw) * 450 };
|
||||||
|
}
|
||||||
|
|
||||||
ConCommand* CreateConCommand(const char* name, FnCommandCallback_t callback, const char* help) {
|
ConCommand* CreateConCommand(const char* name, FnCommandCallback_t callback, const char* help) {
|
||||||
ConCommand* ret = new ConCommand(name, callback, help);
|
ConCommand* ret = new ConCommand(name, callback, help);
|
||||||
g_ICvar->RegisterConCommand(ret);
|
g_ICvar->RegisterConCommand(ret);
|
||||||
|
@ -139,6 +139,11 @@ float GetFov(Vector ang, Vector src, Vector dst);
|
|||||||
|
|
||||||
void ReplaceString(std::string& input, const std::string& what, const std::string& with_what);
|
void ReplaceString(std::string& input, const std::string& what, const std::string& with_what);
|
||||||
|
|
||||||
|
std::pair<float, float> ComputeMove(const Vector& a, const Vector& b);
|
||||||
|
void WalkTo(const Vector& vector);
|
||||||
|
|
||||||
|
std::string GetLevelName();
|
||||||
|
|
||||||
void format_internal(std::stringstream& stream);
|
void format_internal(std::stringstream& stream);
|
||||||
template<typename T, typename... Targs>
|
template<typename T, typename... Targs>
|
||||||
void format_internal(std::stringstream& stream, T value, Targs... args) {
|
void format_internal(std::stringstream& stream, T value, Targs... args) {
|
||||||
|
@ -267,6 +267,10 @@ bool CreateMove_hook(void* thisptr, float inputSample, CUserCmd* cmd) {
|
|||||||
SAFE_CALL(hacks::shared::esp::CreateMove());
|
SAFE_CALL(hacks::shared::esp::CreateMove());
|
||||||
}
|
}
|
||||||
if (!g_pLocalPlayer->life_state && CE_GOOD(g_pLocalPlayer->weapon())) {
|
if (!g_pLocalPlayer->life_state && CE_GOOD(g_pLocalPlayer->weapon())) {
|
||||||
|
{
|
||||||
|
PROF_SECTION(CM_walkbot);
|
||||||
|
SAFE_CALL(hacks::shared::walkbot::Move());
|
||||||
|
}
|
||||||
IF_GAME (IsTF()) {
|
IF_GAME (IsTF()) {
|
||||||
PROF_SECTION(CM_uberspam);
|
PROF_SECTION(CM_uberspam);
|
||||||
SAFE_CALL(hacks::tf::uberspam::CreateMove());
|
SAFE_CALL(hacks::tf::uberspam::CreateMove());
|
||||||
|
Reference in New Issue
Block a user