diff --git a/TODO b/TODO index 620a5ae1..4c0cd318 100644 --- a/TODO +++ b/TODO @@ -1,67 +1,141 @@ -MULTIPLATFORM: - -Signature Scanner -Whole IPC thing | actually, no. cathook for windows WON'T have followbots/ipc. -Signatures and offsets -File handling (playerlist, spam, etc...) -CMake? - -------------====----------- +//==================================================================================================// +//Big TODO list for cathook // +//Organized by Julianacat // +//==================================================================================================// -TF2C teams -TF2C merc -HL2DM teams -fullbright toggle -Hunter Rifle -TTS -namesteal -autovote -voice command spam (not like 1 voice command every 1 ms but like just spamming it on the menu) +//----------------------------------------------------------------------------------------------------------------------// +//Aimbot/Triggerbot // +//------------------------------------------------------------------------------------------------------------------// // + // // +Hook + Aimbot FIX // // + // // +no aim sapper // // + // // +Make hacks respect Mannpower powerups and other conditions when calcuating damage, hitbox and prioritizing targets // // +Proper AutoHitbox, Isnt this already a thing?? // // + // // +Flare aim on fire // // +Improve Projectile Aimbot. A lot. // // +ProjPredOrigin // // +MAX -> MIN priority // // + // // +Hunter Rifle? // // + // // +AutoDetonator // // + // // +No AutoShoot when disguised // // +Ambassador bodyshotting // // +No Trigger Mediguns // // +More projectile weapons aimbot (wrap assassin, wrangler, stickybomb, airstrike) // // +Auto trigger DR before rockets // // + // // +Make make sentrys slightly lower priority (for getting those pesky turtle engies) // // + // // +Make building aimbot compensate for gravity on projectile weapons // // + // // +add Spectator Silent for projectile weapons // // + // // +Improve aimbot accuracy // // + // // +Add Multipoint (Take hitbox, shrink it, then use corners as points to check) // // + // // +Make ambasador check rely on wait for charge user setting // // + // // +//------------------------------------------------------------------------------------------------------------------// // +// // +//----------------------------------------------------------------------------------------------------------------------// + + -dominatesay assistsay worldsay -AutoDetonator +//----------------------------------------------------------------------------------------------------------------------// +//Visuals/GUI // +//------------------------------------------------------------------------------------------------------------------// // + // // +Display on the left // // + // // +make gui color change // // + // // +Cheat status menu (A gui to display enabled/disabled states of user settings for when the menu is off) // // + // // +add teleporter count down // // +add spy cloak esp // // + // // +Make ubercharge esp color RGB when fully charged // // + // // +fullbright toggle // // + // // +FLAG ESP? // // + // // +ESP Icons // // +ESP Distance sort // // +Show sapped buildings in ESP // // +halloween medkits // // +carrying esp // // +Tracers/Spy Cam // // + // // +//------------------------------------------------------------------------------------------------------------------// // +// // +//----------------------------------------------------------------------------------------------------------------------// -ProjPredOrigin -MAX -> MIN priority -Deflected by enemy player - Aim Stickies -FLAG ESP -no aim sapper -Improve Projectile Aimbot. A lot. -Proper AutoHitbox -Display on the left -Flare aim on fire -Aim Sticky -Proj Pred visuals -Hook + Aimbot FIX -ESP Icons -ESP Distance sort -Show sapped buildings in ESP -Make hacks respect Mannpower powerups and other conditions when calcuating damage, hitbox and prioritizing targets -Fake Lag -Engine Prediction -AutoSticky improve -No AutoShoot when disguised -Ambassador bodyshotting -No Trigger Mediguns -More projectile weapons aimbot (wrap assassin, wrangler, stickybomb, airstrike) -Auto trigger DR before rockets -Sticky vischeck -InsultSpam -Critical rifles bodyshotting -FLAG ESP -Spy alert uses angles -halloween medkits -carrying esp -Tracers/Spy Cam -Bodyshot if not zoomed -Proper medic followbot AI -Bot option for aiming at owner's prey (just for fun) -Bot pathfinding & navigation files system -Proper entity classes (actually I might just use a lot of helper functions taking IClientEntity* as first arg) -Priority system optimization and testing -General optimization and refactoring -Bone ESP -XorString or something to make it harder to detect. \ No newline at end of file + + +//----------------------------------------------------------------------------------------------------------------------// +//Chat and Related // +//------------------------------------------------------------------------------------------------------------------// // + // // +TTS // // +namesteal // // +autovote // // + // // +dominatesay assistsay worldsay // // +InsultSpam // // + // // +//------------------------------------------------------------------------------------------------------------------// // +// // +//----------------------------------------------------------------------------------------------------------------------// + + + +//----------------------------------------------------------------------------------------------------------------------// +//Followbots // +//------------------------------------------------------------------------------------------------------------------// // + // // +Proper medic followbot AI, breadcrumb followbot is good but a nav system would be better // // + // // +Bot option for aiming at owner's prey (just for fun) // // +Bot pathfinding & navigation files system // // +Proper entity classes (actually I might just use a lot of helper functions taking IClientEntity* as first arg) // // + // // +//------------------------------------------------------------------------------------------------------------------// // +// // +//----------------------------------------------------------------------------------------------------------------------// + + + +//----------------------------------------------------------------------------------------------------------------------// +//Misc // +//------------------------------------------------------------------------------------------------------------------// // + // // +Proper Fake Lag // // +Add auto-strafe // // + // // +Fix achievement hack getting blocked with sv_cheats // // + // // +Spy alert uses angles // // + // // +Add ignore gunslinger to melee crit hack // // + // // +Priority system optimization and testing // // +General optimization and refactoring // // + // // +XorString or something to make it harder to detect. // // + // // +TF2C teams // // +TF2C merc // // +HL2DM teams // // + // // +//------------------------------------------------------------------------------------------------------------------// // +// // +//----------------------------------------------------------------------------------------------------------------------// diff --git a/simple-ipc b/simple-ipc index 94a287d6..e532876f 160000 --- a/simple-ipc +++ b/simple-ipc @@ -1 +1 @@ -Subproject commit 94a287d6faa00d44e1084b04e602842849858443 +Subproject commit e532876ffd707a48389d54ff904dcc40a84f2839 diff --git a/src/drawmgr.cpp b/src/drawmgr.cpp index 3c81a05d..c8f20ddf 100644 --- a/src/drawmgr.cpp +++ b/src/drawmgr.cpp @@ -79,6 +79,9 @@ void DrawCheatVisuals() { PROF_SECTION(PT_spyalert); SAFE_CALL(hacks::tf::spyalert::Draw()); } +#ifdef IPC_ENABLED + IF_GAME(IsTF()) SAFE_CALL(hacks::shared::followbot::Draw()); +#endif { PROF_SECTION(DRAW_esp); hacks::shared::esp::Draw(); diff --git a/src/hacks/Aimbot.cpp b/src/hacks/Aimbot.cpp index 1c3c8977..0d4d672c 100644 --- a/src/hacks/Aimbot.cpp +++ b/src/hacks/Aimbot.cpp @@ -55,6 +55,7 @@ static CatVar wait_for_charge(CV_SWITCH, "aimbot_charge", "0", "Wait for sniper static CatVar ignore_vaccinator(CV_SWITCH, "aimbot_ignore_vaccinator", "1", "Ignore Vaccinator", "Hitscan weapons won't fire if enemy is vaccinated against bullets"); static CatVar ignore_hoovy(CV_SWITCH, "aimbot_ignore_hoovy", "0", "Ignore Hoovies", "Aimbot won't attack hoovies"); static CatVar ignore_cloak(CV_SWITCH, "aimbot_ignore_cloak", "1", "Ignore cloaked", "Don't aim at invisible enemies"); +static CatVar ignore_deadringer(CV_SWITCH, "aimbot_ignore_deadringer", "1", "Ignore deadringer", "Don't aim at deadringed enemies"); static CatVar buildings_sentry(CV_SWITCH, "aimbot_buildings_sentry", "1", "Aim Sentry", "Should aimbot aim at sentryguns?"); static CatVar buildings_other(CV_SWITCH, "aimbot_buildings_other", "1", "Aim Other building", "Should aimbot aim at other buildings"); static CatVar stickybot(CV_SWITCH, "aimbot_stickys", "0", "Aim Sticky", "Should aimbot aim at stickys"); @@ -92,6 +93,8 @@ static CatVar huntsman_full_auto(CV_SWITCH, "aimbot_full_auto_huntsman", "1", "A // Debug vars static CatVar aimbot_debug(CV_SWITCH, "aimbot_debug", "0", "Aimbot Debug", "Display simple debug info for aimbot"); static CatVar engine_projpred(CV_SWITCH, "debug_aimbot_engine_pp", "0", "Engine ProjPred"); +// Followbot vars +static CatVar auto_spin_up(CV_SWITCH, "aimbot_spin_up", "0", "Auto Spin Up", "Spin up minigun if you can see target, useful for followbots"); /* TODO IMPLEMENT static CatVar auto_spin_up(CV_SWITCH, "aimbot_spin_up", "0", "Auto Spin Up", "Spin up minigun if you can see target, useful for followbots"); static CatVar auto_zoom(CV_SWITCH, "aimbot_auto_zoom", "0", "Auto Zoom", "Automatically zoom in if you can see target, useful for followbots"); @@ -120,10 +123,6 @@ void CreateMove() { // Check if aimbot is enabled if (!enabled) return; - // Save should aim info - bool shouldAim = ShouldAim(); - bool aimkeyStatus = UpdateAimkey(); - // Refresh projectile info int huntsman_ticks = 0; projectile_mode = (GetProjectileData(g_pLocalPlayer->weapon(), cur_proj_speed, cur_proj_grav)); @@ -140,23 +139,27 @@ void CreateMove() { huntsman_ticks = max(0, huntsman_ticks - 1); } + // Save should aim info + // We do this as we need to pass whether the aimkey allows aiming to both the find target and aiming system. If we just call the func than toggle aimkey would break so we save it to a var to use it twice + bool aimkey_status = UpdateAimkey(); + // Refresh our best target - CachedEntity* target = RetrieveBestTarget(aimkeyStatus); + CachedEntity* target = RetrieveBestTarget(aimkey_status); // Check target for dormancy and if there even is a target at all if (CE_GOOD(target) && foundTarget) { + // Set target esp color to pink hacks::shared::esp::SetEntityColor(target, colors::pink); // Check if player can aim and if aimkey allows aiming // We also preform a CanShoot check here per the old canshoot method - if (shouldAim && aimkeyStatus && GetCanAim(1)) { + if (ShouldAim() && aimkey_status && GetCanAim(1)) { // Check if player isnt using a huntsman if (g_pLocalPlayer->weapon()->m_iClassID != CL_CLASS(CTFCompoundBow)) { - // If settings allow, limit aiming to only when can shoot - // We do this here only if wip is true as we do the check elsewhere for legacy + // We check if we need to do a canshoot check as we might want to shoot but not aim, so do that check here if (GetCanAim(2)) { // Check the flNextPrimaryAttack netvar to tell when to aim if (CanShoot()) Aim(target); @@ -250,6 +253,7 @@ bool ShouldAim() { return false; }; } + IF_GAME (IsTF()) { // Check if player is zooming if (g_pLocalPlayer->bZoomed) { @@ -257,8 +261,19 @@ bool ShouldAim() { if (!CanHeadshot()) return false; } } - } - IF_GAME (IsTF()) { + + // Minigun spun up handler + if (g_pLocalPlayer->weapon()->m_iClassID == CL_CLASS(CTFMinigun)) { + int weapon_state = CE_INT(g_pLocalPlayer->weapon(), netvar.iWeaponState); + // If user setting for autospin isnt true, then we check if minigun is already zoomed + if ((weapon_state == MinigunState_t::AC_STATE_IDLE || weapon_state == MinigunState_t::AC_STATE_STARTFIRING) && !auto_spin_up) { + return false; + } + if (!(g_pUserCmd->buttons & (IN_ATTACK2 | IN_ATTACK))) { + return false; + } + } + // Check if crithack allows attacking if (!AllowAttacking()) return false; @@ -357,8 +372,18 @@ bool IsTargetStateGood(CachedEntity* entity) { if (ignore_taunting && HasCondition(entity)) return false; // Dont target invulnerable players, ex: uber, bonk if (IsPlayerInvulnerable(entity)) return false; - // If settings allow, dont target cloaked players - if (ignore_cloak && IsPlayerInvisible(entity)) return false; + // Checks for cloaked/deadringed players + if (ignore_cloak || ignore_deadringer) { + if (IsPlayerInvisible(entity)) { + // Determine whether cloaked player is using deadringer and checks user settings accordingly + // Item id for deadringer is 59 as of time of creation + if (HasWeapon(entity, 59)) { + if (ignore_deadringer) return false; + } else { + if (ignore_cloak) return false; + } + } + } // If settings allow, dont target vaccinated players if (g_pLocalPlayer->weapon_mode == weaponmode::weapon_hitscan || LOCAL_W->m_iClassID == CL_CLASS(CTFCompoundBow)) if (ignore_vaccinator && HasCondition(entity)) return false; @@ -892,7 +917,7 @@ void Reset() { } // Function called when we need to draw to screen -static CatVar fov_draw(CV_SWITCH, "aimbot_fov_draw", "0", "Draw Fov Ring", "Draws a ring to represent your current aimbot fov\nDoesnt change according to zoom fov\nWIP"); +static CatVar fov_draw(CV_SWITCH, "aimbot_fov_draw", "0", "Draw Fov Ring", "Draws a ring to represent your current aimbot fov"); void DrawText() { // Dont draw to screen when aimbot is disabled if (!enabled) return; @@ -908,13 +933,13 @@ void DrawText() { // Grab the screen resolution and save to some vars int width, height; g_IEngine->GetScreenSize(width, height); + // Some math to find radius of the fov circle - // float radius = tanf(DEG2RAD((float)fov) / 2) / tanf(DEG2RAD(draw::fov) / 2) * width; - float mon_fov = (float(width) / float(height) / (4.0f / 3.0f)); float fov_real = RAD2DEG(2 * atanf(mon_fov * tanf(DEG2RAD(draw::fov / 2)))); float radius = tan(DEG2RAD(float(fov)) / 2) / tan(DEG2RAD(fov_real) / 2) * (width); + // Draw a circle with our newfound circle float px = 0; float py = 0; diff --git a/src/hacks/AutoReflect.cpp b/src/hacks/AutoReflect.cpp index 1a6a88eb..62c412d6 100644 --- a/src/hacks/AutoReflect.cpp +++ b/src/hacks/AutoReflect.cpp @@ -33,6 +33,10 @@ void CreateMove() { // Check if player is using a flame thrower if (g_pLocalPlayer->weapon()->m_iClassID != CL_CLASS(CTFFlameThrower)) return; + + // Check for phlogistinator, which is item 594 + if (HasWeapon(LOCAL_E, 594)) return; + // If user settings allow, return if local player is in attack if (idle_only && (g_pUserCmd->buttons & IN_ATTACK)) return; diff --git a/src/hacks/AutoSticky.cpp b/src/hacks/AutoSticky.cpp index ab7ca58c..e0a66006 100644 --- a/src/hacks/AutoSticky.cpp +++ b/src/hacks/AutoSticky.cpp @@ -77,11 +77,14 @@ void CreateMove() { // Check user settings if auto-sticky is enabled if (!enabled) return; + // Check if game is a tf game + //IF_GAME (!IsTF()) return; + // Check if player is demoman if (g_pLocalPlayer->clazz != tf_demoman) return; // Check for sticky jumper, which is item 265, if true, return - if (HasWeapon(LOCAL_E, 265)); + if (HasWeapon(LOCAL_E, 265)) return; // Clear the arrays bombs.clear(); diff --git a/src/hacks/FollowBot.cpp b/src/hacks/FollowBot.cpp index 460ea1fe..d2459b70 100644 --- a/src/hacks/FollowBot.cpp +++ b/src/hacks/FollowBot.cpp @@ -12,123 +12,490 @@ #ifdef IPC_ENABLED namespace hacks { namespace shared { namespace followbot { + + + + + +/* Big Followbot TODO list +1. Fix crash when setting followbot_idx var and remove the fix var in its place +2. Test with followbots to ensure that vector followbot and crumb followbot work as intended +3. Clean the finished code and push to main from fork +*/ + + + + +// User settings +CatVar bot(CV_SWITCH, "fb_bot", "0", "Master Followbot Switch", "Set to 1 in followbots' configs"); +CatVar follow_distance(CV_FLOAT, "fb_distance", "175", "Follow Distance", "How close the bots should stay to the target"); +CatVar follow_activation(CV_FLOAT, "fb_activation", "175", "Activation Distance", "How close a player should be until the followbot will pick them as a target"); +CatVar mimic_slot(CV_SWITCH, "fb_mimic_slot", "0", "Mimic selected weapon", "If enabled, this bot will select same weapon slot as the owner"); +CatVar always_medigun(CV_SWITCH, "fb_always_medigun", "0", "Always use Medigun", "Medics will always use Medigun"); +CatVar crumb_draw(CV_SWITCH, "fb_crumb_draw", "1", "Draw Crumbs", "Draws the path made for the followbot"); +CatVar roaming(CV_SWITCH, "fb_roaming", "0", "Roaming", "Allows the bot to find a different target if it cant find one using the steam id"); +CatVar sync_taunt(CV_SWITCH, "fb_sync_taunt", "0", "Mimic taunts", "Bots will taunt if target is taunting"); -unsigned follow_steamid { 0 }; -Vector last_direction; -float lost_time { 0 }; -float idle_time { 0 }; -int following_idx { 0 }; +// Var to store the current steamid to follow +unsigned follow_steamid { 1 }; +// Vars that decides how the followbot will act and move +EFollowType current_follow_type = EFollowType::ENTITY; +CachedEntity* target_last = 0; +int following_idx = 0; +bool allow_moving = true; + +// Arrays to store selected ents std::set selection {}; std::set selection_secondary {}; -//Cats vars for old followbot +// Vars for vector followbot float destination_point_time { 0.0f }; Vector destination_point {}; -bool destination_reached { false }; -bool allow_moving { true }; - -//Initalize vars for BreadCrumb followbot -//An array for storing the breadcrumbs -static CatVar crumbFollowDistance(CV_FLOAT, "fb_follow_distance", "150", "Followbot Distance", "How close the bots should stay to you"); -Vector breadcrumbs [55]; -float crumbWaitPrune = 0; -float crumbWaitVis = 0; + +// Var that control jumping and the followbot +float idle_time = 0; + +// Vars for breadcrumb followbot + +// An array for storing the breadcrumbs +static Vector breadcrumbs [55]; +// Array Bookkeeping vars int crumbBottom = 0; int crumbTop = 0; int crumbArrayLength = 0; -bool crumbAbleToMove = false; +// Used for states for the followbot +float crumb_prune_timeout = 0; bool crumbStopped = true; bool crumbFindNew = false; bool crumbForceMove = false; -//A function to start the crumb followbot up -void CrumbStart() { - //A check to make sure using the fb tool repeatedly doesnt clear the cache of crumbs - if (crumbStopped || !crumbAbleToMove) { - crumbTop = 0; - crumbBottom = 0; - crumbArrayLength = 0; - crumbWaitPrune = g_GlobalVars->curtime; - crumbWaitVis = g_GlobalVars->curtime; - crumbFindNew = true; - crumbStopped = false; - crumbAbleToMove = true; - logging::Info("Crumb Start"); - } -} - -//A function to place a crumb into the array -void CrumbTopAdd(Vector crumbToAdd) { - //Once the crumbs have hit the limit of the array, loop around and over write unused spots - if (crumbTop == 55) { - crumbTop = 0; - } else { - //Else, bump the top number market of the array - crumbTop = crumbTop + 1; - } - - //Put the newly determined crumb into the array and add to the length - crumbArrayLength = crumbArrayLength + 1; - breadcrumbs[crumbTop] = crumbToAdd; - logging::Info("Crumb Top add"); - - //The array can only hold so many crumbs, once it goes over its cap, stop the bot to prevent un-needed movement - if (crumbArrayLength > 55) { - crumbStopped = true; - logging::Info("Crumb Overload!\nDumping array"); - } -} - -//A function to free a crumb from the array -void CrumbBottomAdd() { - //Once the crumbs have hit the limit of the array, loop around and over write unused spots - if (crumbBottom == 55) { - crumbBottom = 0; - } else { - //Else, bump the top number market of the array - crumbBottom = crumbBottom + 1; - } - - //Subtract from the length to make room for more crumbs - crumbArrayLength = crumbArrayLength - 1; - logging::Info("Crumb Bottom add"); - - //A check to detect if too many crumbs have been removed. Without crumbs the bot will just use random variables in the array. - //To prevent un-nessasary movement, just clear the array and wait for player - if (crumbArrayLength < 0) { - crumbStopped = true; - logging::Info("Crumb Over-Prune!\nDumping array"); - } -} - -//A function to calculate distance from a entity, to the floor -float crumbDistanceToFloor(CachedEntity* ent) { - if (ent->m_Type == ENTITY_PLAYER) { - if (CE_INT(ent, netvar.iFlags) & FL_ONGROUND) return 0; +// Function called after create move +void AfterCreateMove() { + + // Primary Selection handling and selection clearing + auto it = selection.begin(); + while (it != selection.end()) { + int idx = *it; + CachedEntity* entity = ENTITY(idx); + if (CE_BAD(entity)) { + selection.erase(it++); + } else { + hacks::shared::esp::AddEntityString(entity, "[SELECTED]", colors::orange); + if (fmod(g_GlobalVars->curtime, 2.0f) < 1.0f) { + hacks::shared::esp::SetEntityColor(entity, colors::yellow); + } + ++it; + } } - Vector& origin = ent->m_vecOrigin; - float v1 = DistanceToGround(origin + Vector(10.0f, 10.0f, 0.0f)); - float v2 = DistanceToGround(origin + Vector(-10.0f, 10.0f, 0.0f)); - float v3 = DistanceToGround(origin + Vector(10.0f, -10.0f, 0.0f)); - float v4 = DistanceToGround(origin + Vector(-10.0f, -10.0f, 0.0f)); - return MIN(v1, MIN(v2, MIN(v3, v4))); + + // Secondary Selection handling + it = selection_secondary.begin(); + while (it != selection_secondary.end()) { + int idx = *it; + CachedEntity* entity = ENTITY(idx); + if (CE_BAD(entity)) { + selection_secondary.erase(it++); + } else { + hacks::shared::esp::AddEntityString(entity, "[SELECTED (SECONDARY)]", colors::orange); + if (fmod(g_GlobalVars->curtime, 2.0f) < 1.0f) { + hacks::shared::esp::SetEntityColor(entity, colors::yellow); + } + ++it; + } + } + + // Processing Selection is done, attemt to walk + DoWalking(); } -bool IsBot(CachedEntity* entity) { - if (!ipc::peer) return false; - if (entity->m_Type == ENTITY_PLAYER) { - if (ipc::peer) { - for (unsigned i = 1; i < cat_ipc::max_peers; i++) { - if (!ipc::peer->memory->peer_data[i].free && ipc::peer->memory->peer_user_data[i].friendid == entity->player_info.friendsID) { - return true; +// Function for followbots to use for following, weapon selection, etc... +void DoWalking() { + + if (!bot) return; + + + + // Get our best target, preferably from a steamid + + //following_idx = 0; + CachedEntity* best_target = nullptr; + CachedEntity* target_priority = nullptr; + + // Get ent from steamid + for (int i = 1; i < HIGHEST_ENTITY; i++) { + + CachedEntity* ent = ENTITY(i); + + if (CE_BAD(ent)) continue; + if (ent == LOCAL_E) continue; + if (ent->m_Type != ENTITY_PLAYER) continue; + if (!ent->m_bAlivePlayer) continue; + if (ent->m_bEnemy) continue; + if (g_pLocalPlayer->v_Origin.DistTo(ent->m_vecOrigin) > 3400.0F) continue; + + if (ent->player_info.friendsID == follow_steamid) { + target_priority = ent; + break; + } + } + + // Check Steam id target + if (CE_GOOD(target_priority)) { + // If last target isnt the priority, vis check it + if (target_last != target_priority) { + if (target_priority->IsVisible()) { + target_last = target_priority; + best_target = target_priority; + crumbStopped = true; + } + } else { + best_target = target_priority; + } + + + + } + // Doesnt work for some reason, FIX!!! + // If we cant use steam id target, try someone else + if (roaming && CE_BAD(best_target)) { + + // Check last target if good + if (CE_GOOD(target_last)) { + if (g_pLocalPlayer->v_Origin.DistTo(target_last->m_vecOrigin) < 3400.0F && target_last->m_bAlivePlayer && !crumbStopped) { + best_target = target_last; + } + } + + // Without good target we must find a new one + if (CE_BAD(best_target)) { + + float target_highest_score = -256; + CachedEntity* ent; + target_last = nullptr; + crumbStopped = true; + + for (int i = 0; i < HIGHEST_ENTITY; i++) { + ent = ENTITY(i); + + if (CE_BAD(ent)) continue; + if (ent == LOCAL_E) continue; + if (!ent->m_bAlivePlayer) continue; + + if (ent->m_bEnemy) continue; + if (g_pLocalPlayer->v_Origin.DistTo(ent->m_vecOrigin) > 3400.0F) continue; + // Check activation distance + if (g_pLocalPlayer->v_Origin.DistTo(ent->m_vecOrigin) > (float)follow_activation) continue; + if (!ent->IsVisible()) continue; + + // Distance Priority works in our case + float scr = 4096.0f - g_pLocalPlayer->v_Origin.DistTo(ent->m_vecOrigin); + if (scr > target_highest_score) { + target_highest_score = scr; + target_last = ent; + best_target = ent; + } + } + } + } + + CachedEntity* found_entity = best_target; + // TODO, setting following_idx causes a crash for an unknown reason, probs to do with autoheal. + // I created a different var to take its place and prevent the crash but i need to fix the crash with the externed var. + // For now this works and it will stay like this untill I find a way to fix it + int following_idx2 = 0; + if (CE_GOOD(found_entity)) { + following_idx2 = found_entity->m_IDX; + hacks::shared::esp::AddEntityString(found_entity, "[FOLLOWING]", colors::green); + hacks::shared::esp::SetEntityColor(found_entity, colors::green); + } else { + crumbStopped = true; + return; + } + + + + + + // Slot Changer/Mimicer + + // Set a static var for last slot check + static float last_slot_check = 0.0f; + + // If curtime is less than the last time we checked the slot, then reset our check timer + if (g_GlobalVars->curtime < last_slot_check) last_slot_check = 0.0f; + + // If we have a follow target, user settings tell us that we should mess with the slot, the local player is alive, and our target is alive + if (following_idx2 && (always_medigun || mimic_slot) && (g_GlobalVars->curtime - last_slot_check > 1.0f) && !g_pLocalPlayer->life_state && !CE_BYTE(found_entity, 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(found_entity, 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 (vfunc(RAW_ENT(g_pLocalPlayer->weapon()), 190, 0)(RAW_ENT(g_pLocalPlayer->weapon())) && + vfunc(owner_weapon, 190, 0)(owner_weapon)) { + + // Get the players slot numbers and store in some vars + int my_slot = vfunc(RAW_ENT(g_pLocalPlayer->weapon()), 395, 0)(RAW_ENT(g_pLocalPlayer->weapon())); + int owner_slot = vfunc(owner_weapon, 395, 0)(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()); + } + } + } + } + } + + + + + + // Main followbot code + + + // Switch to different types of following mechanisms depending on the type we need to go to + switch (current_follow_type) { + case EFollowType::VECTOR: // If were using a vector to follow, we just go directly to it + + // If destination_point_time is more than curtime than we reset it to zero + if (destination_point_time > g_GlobalVars->curtime) destination_point_time = 0.0f; + + // If we havent reached our destination and the destination point timeout isnt more than 5 seconds, then we continue to walk to the destination point + if (g_GlobalVars->curtime - destination_point_time < 5.0f) { + + // Walk to the point + WalkTo(destination_point); + + // If we have reached the destination point then we want to disable the vector followbot + if (g_pLocalPlayer->v_Origin.DistTo(destination_point) < 50.0f) { + current_follow_type = EFollowType::ENTITY; + } + } + // Break from the switch + break; + + case EFollowType::ENTITY: // If were using a player to follow, we use the breadcrumb followbot + + if (CE_GOOD(found_entity)) { + // If the bot is lost but it finds the player again, start the followbot again. + if (crumbStopped) { + crumbForceMove = true; + CrumbReset(); + } + } else { + // If the entity we have isnt good, we stop the crumb followbot + crumbStopped = true; + } + + // Breadcrumb followbot + if (!crumbStopped) { + + // Generate new breadcrumbs made by the player only if they are close to the ground. If the bot is told to generate a starting point, it does that as well. + if ((found_entity->m_vecOrigin.DistTo(breadcrumbs[crumbTop]) > 40.0F || crumbFindNew) && DistanceToGround(found_entity) < 40) { + + // Add to the crumb. + CrumbTopAdd(found_entity->m_vecOrigin); + + // If the bot was forced to select a point, we tell it that we no longer need a new one and clear the bottom crumb to use the newest one + if (crumbFindNew) { + crumbFindNew = false; + CrumbBottomAdd(); + } + + } + + // Prune used crumbs from the stack to make way for new ones when you get close to them. + if (g_pLocalPlayer->v_Origin.DistTo(breadcrumbs[crumbBottom]) < 40.0F ) { + + // Debug Logging + logging::Info("Pruning"); + + // When the bot is forced to move to the player, since they have reached their destination we reset the var + crumbForceMove = false; + + // Check 15 times for close crumbs to prune, this allows simple miss steps to be smoothed out as well as make room for new crumbs + for (int i = 0; i < 15; i++) { + + // When one is close or too high, just bump the array and reset the stuck timer + if (g_pLocalPlayer->v_Origin.DistTo(breadcrumbs[crumbBottom]) < 60.0F && crumbArrayLength > 1) { + CrumbBottomAdd(); + + // When pruning is finished. Break the loop + } else { + crumb_prune_timeout = g_GlobalVars->curtime; + logging::Info("Finish Prune"); + break; + } + } + // Reset stuck timer + crumb_prune_timeout = g_GlobalVars->curtime; + } + + // When player to follow is too far away. the bot cant see the player or the bot is forced to the player, then follow breadcrumbs if movement is allowed + if ((g_pLocalPlayer->v_Origin.DistTo(found_entity->m_vecOrigin) > (float)follow_distance || crumbForceMove) && crumbArrayLength >= 1 && allow_moving) { + WalkTo(breadcrumbs[crumbBottom]); + + // If a crumb hasnt been pruned in a while, it probably cant travel to it so reset and wait for the player to collect it. + if (g_GlobalVars->curtime - 2.5F > crumb_prune_timeout) { + crumbStopped = true; + logging::Info("Cannot goto next crumb!\nCrumb Lost!"); + } + + // If the bot is next to the player then we clear our crumbs as theres no need to follow previously generated ones. + } else if (g_pLocalPlayer->v_Origin.DistTo(found_entity->m_vecOrigin) < 100.0F && found_entity->IsVisible()) { + CrumbReset(); + crumbForceMove = false; + } + } + } + + + + + + // If we have a follow target, then we attempt to mimic their conditions + if (following_idx2) { + + // If follow target is too far away and the local player is zoomed, then unzoom + if (found_entity->m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin) > 325.0f) { + if (g_pLocalPlayer->bZoomed) g_pUserCmd->buttons |= IN_ATTACK2; + } + + // If found target is heavy and the local player is too, + if (CE_INT(found_entity, netvar.iClass) == tf_heavy && g_pLocalPlayer->clazz == tf_heavy) { + + // If found target is spun up, then spin up too + if (HasCondition(found_entity)) { + g_pUserCmd->buttons |= IN_ATTACK2; + } + } + + // If found target is zoomed + if (HasCondition(found_entity)) { + + // If the local player isnt zoomed and its class is sniper, then zoom in to mimic the followed target + if (!g_pLocalPlayer->bZoomed && g_pLocalPlayer->clazz == tf_sniper) { + g_pUserCmd->buttons |= IN_ATTACK2; + } + } + + // If user settings allow, we attemt to mimic taunting player + if (sync_taunt) { + + // Check if target is taunting + if (HasCondition(found_entity)) { + + // Check if local player isnt taunting + if (!HasCondition(LOCAL_E)) { + g_IEngine->ExecuteClientCmd("taunt"); } } } } - return false; } +// Used on a bot to select a target to follow +CatCommand follow("fb_follow", "Follows you (or player with SteamID specified)", [](const CCommand& args) { + + // Set our target steam id to the argument put into the command + follow_steamid = strtol(args.Arg(1), nullptr, 10); + + // Tell the followbot to follow entities + current_follow_type = EFollowType::ENTITY; + // Allow the followbot to move + allow_moving = true; +}); + +// Used by the main player to send an ipc request to the bots to follow +CatCommand follow_me("fb_follow_me", "Makes all bots follow you", []() { + + // Check if the ipc server is connected + if (ipc::peer) { + // Get our steam id + unsigned id = g_ISteamUser->GetSteamID().GetAccountID(); + // Send our id through the ipc server + ipc::peer->SendMessage((const char*)&id, 0, ipc::commands::set_follow_steamid, 0, 0); + } +}); + +// User command for cat_fb_tool +CatCommand tool("fb_tool", "Followbot multitool", [](const CCommand& args) { + + // Check if ipc server is connected + if (!ipc::peer) return; + + // If argument is "select" + if (!strcmp(args.Arg(1), "select")) { + logging::Info("FB TOOL -> SELECT"); + + // Check if the shift key is depressed + if (g_IInputSystem->IsButtonDown(ButtonCode_t::KEY_LSHIFT)) { + + // Shift cleans selection.. + selection.clear(); + selection_secondary.clear(); + logging::Info("Selection cleared!"); + + // If the shift key isnt depressed + } else { + + // Make a var and try to get an ent of what we are looking at + int eindex = 0; + WhatIAmLookingAt(&eindex, nullptr); + logging::Info("Selecting entity..."); + + // If the entity isnt null, Add the entity to the selection + if (eindex) { + SelectEntity(eindex); + } + } + + // If argument is "move" + } else if (!strcmp(args.Arg(1), "move")) { + logging::Info("FB TOOL -> MOVE"); + + // Create a vector and attemt to get an end vector from it + Vector destination; + WhatIAmLookingAt(nullptr, &destination); + + // Send the vector through the ipc server and tell the bots to move to it + float array[3] = { destination.x, destination.y, destination.z }; + ipc::peer->SendMessage((const char*)array, MakeMask(), ipc::commands::move_to_vector, nullptr, 0); + + // If argument is "stay" + } else if (!strcmp(args.Arg(1), "stay")) { + logging::Info("FB TOOL -> STAY"); + + // Send a message through the ipc server to notify the bots to stop moving + ipc::peer->SendMessage(nullptr, MakeMask(), ipc::commands::stop_moving, nullptr, 0); + + // If argument is "follow" + } else if (!strcmp(args.Arg(1), "follow")) { + logging::Info("FB TOOL -> FOLLOW"); + + // Send a message through the ipc server to notify the bots to start moving + ipc::peer->SendMessage(nullptr, MakeMask(), ipc::commands::start_moving, nullptr, 0); + } +}); + +// Helper Functions for bot selection + void SelectEntity(int idx) { logging::Info("Selecting entity %i", idx); CachedEntity* entity = ENTITY(idx); @@ -142,43 +509,23 @@ void SelectEntity(int idx) { logging::Info("Selected!"); } } - -void AddMessageHandlers(ipc::peer_t* peer) { - peer->SetCommandHandler(ipc::commands::set_follow_steamid, [](cat_ipc::command_s& command, void* payload) { - logging::Info("IPC Message: now following %ld", *(unsigned*)&command.cmd_data); - hacks::shared::followbot::follow_steamid = *(unsigned*)&command.cmd_data; - //Tell followbot to follow - crumbForceMove = true; - CrumbStart(); - }); - peer->SetCommandHandler(ipc::commands::move_to_vector, [](cat_ipc::command_s& command, void* payload) { - float* data = (float*)&command.cmd_data; - logging::Info("IPC Message: moving to %.2f %.2f %.2f", data[0], data[1], data[2]); - destination_point = Vector(data[0], data[1], data[2]); - destination_point_time = g_GlobalVars->curtime; - destination_reached = false; - //prevent followbot from following - crumbAbleToMove = false; - crumbStopped = true; - }); - peer->SetCommandHandler(ipc::commands::start_moving, [](cat_ipc::command_s& command, void* payload) { - allow_moving = true; - //Tell followbot to follow - crumbAbleToMove = true; - }); - peer->SetCommandHandler(ipc::commands::stop_moving, [](cat_ipc::command_s& command, void* payload) { - allow_moving = false; - crumbStopped = true; - }); -} - -CatCommand follow_me("fb_follow_me", "Makes all bots follow you", []() { - if (ipc::peer) { - unsigned id = g_ISteamUser->GetSteamID().GetAccountID(); - ipc::peer->SendMessage((const char*)&id, 0, ipc::commands::set_follow_steamid, 0, 0); + +// A Function to tell whether an entity input into it is a bot +bool IsBot(CachedEntity* entity) { + if (!ipc::peer) return false; + if (entity->m_Type == ENTITY_PLAYER) { + if (ipc::peer) { + for (unsigned i = 1; i < cat_ipc::max_peers; i++) { + if (!ipc::peer->memory->peer_data[i].free && ipc::peer->memory->peer_user_data[i].friendid == entity->player_info.friendsID) { + return true; + } + } + } } -}); - + return false; +} + +// :thinking: unsigned MakeMask() { unsigned result = 0; if (!ipc::peer) return 0; @@ -194,54 +541,75 @@ unsigned MakeMask() { } return result; } + +// Ipc message handlers. When an ipc event comes from an ipc server, its called below +void AddMessageHandlers(ipc::peer_t* peer) { + + // When the ipc server gives us a steam id to follow, we run the following + peer->SetCommandHandler(ipc::commands::set_follow_steamid, [](cat_ipc::command_s& command, void* payload) { + // Log in console the steam id that were following + logging::Info("IPC Message: now following %ld", *(unsigned*)&command.cmd_data); + // Set our steam id that we want to follow + hacks::shared::followbot::follow_steamid = *(unsigned*)&command.cmd_data; + // Tell the followbot to follow entities + current_follow_type = EFollowType::ENTITY; + }); + + // When the ipc server gives us a vector to follow, we run the following + peer->SetCommandHandler(ipc::commands::move_to_vector, [](cat_ipc::command_s& command, void* payload) { + // Create a var to recieve the payload with + float* data = (float*)&command.cmd_data; + // Log in console the vector that we will attemt to goto + logging::Info("IPC Message: moving to %.2f %.2f %.2f", data[0], data[1], data[2]); + // Set our dest info with the payload data + destination_point = Vector(data[0], data[1], data[2]); + destination_point_time = g_GlobalVars->curtime; + // Notify the followbot to follow vectors + current_follow_type = EFollowType::VECTOR; + }); + + // When the ipc server tells us to start moving, we run the following + peer->SetCommandHandler(ipc::commands::start_moving, [](cat_ipc::command_s& command, void* payload) { + allow_moving = true; + }); + + // When the ipc server tells us to stop moving, we run the following + peer->SetCommandHandler(ipc::commands::stop_moving, [](cat_ipc::command_s& command, void* payload) { + allow_moving = false; + }); +} -CatCommand tool("fb_tool", "Followbot multitool", [](const CCommand& args) { - if (!ipc::peer) return; - if (args.ArgC() == 1) { - // TODO open a gui or something... - } else { - if (!strcmp(args.Arg(1), "select")) { - logging::Info("FB TOOL -> SELECT"); - if (g_IInputSystem->IsButtonDown(ButtonCode_t::KEY_LSHIFT)) { - // Shift cleans selection.. - selection.clear(); - selection_secondary.clear(); - logging::Info("Selection cleared!"); - } else { - logging::Info("Selecting entity..."); - int eindex = 0; - WhatIAmLookingAt(&eindex, nullptr); - if (eindex) { - SelectEntity(eindex); - } - } - } else if (!strcmp(args.Arg(1), "move")) { - logging::Info("FB TOOL -> MOVE"); - Vector destination; - WhatIAmLookingAt(nullptr, &destination); - float array[3] = { destination.x, destination.y, destination.z }; - ipc::peer->SendMessage((const char*)array, MakeMask(), ipc::commands::move_to_vector, nullptr, 0); - } else if (!strcmp(args.Arg(1), "stay")) { - logging::Info("FB TOOL -> STAY"); - ipc::peer->SendMessage(nullptr, MakeMask(), ipc::commands::stop_moving, nullptr, 0); - } else if (!strcmp(args.Arg(1), "follow")) { - logging::Info("FB TOOL -> FOLLOW"); - ipc::peer->SendMessage(nullptr, MakeMask(), ipc::commands::start_moving, nullptr, 0); +// Function for when you want to goto a vector +void WalkTo(const Vector& vector) { + + // Check if the local player is not moving + if (CE_VECTOR(LOCAL_E, netvar.vVelocity).IsZero(1.0f)) { + // Set idle time if we havent already + if (!idle_time) idle_time = g_GlobalVars->curtime; + + // If the time idle is over 2 seconds + if (g_GlobalVars->curtime - idle_time > 2.0f) { + // If the player isnt zoomed, then jump + if (!g_pLocalPlayer->bZoomed) + g_pUserCmd->buttons |= IN_JUMP; + + // Since the vector is close enough we reset our idle timer + } else { + idle_time = 0; } } -}); - -CatCommand follow("fb_follow", "Follows you (or player with SteamID specified)", [](const CCommand& args) { - follow_steamid = strtol(args.Arg(1), nullptr, 10); -}); -CatVar bot(CV_SWITCH, "fb_bot", "0", "This player is a bot", "Set to 1 in followbots' configs"); -CatVar mimic_slot(CV_SWITCH, "fb_mimic_slot", "1", "Mimic selected weapon", "If enabled, this bot will select same weapon slot as the owner"); -CatVar always_medigun(CV_SWITCH, "fb_always_medigun", "1", "Always use Medigun", "Medics will always use Medigun"); -//CatVar sync_taunt(CV_SWITCH, "fb_sync_taunt", "1", "Synchronize taunts", "Bots will taunt if owner is taunting"); + + // 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; +} // I've spent 2 days on writing this method. // I couldn't succeed. // Just removed everything and put movement fix code from createmove here. +// Helper function for WalkTo std::pair ComputeMove(const Vector& a, const Vector& b) { Vector diff = (b - a); if (diff.Length() == 0) return { 0, 0 }; @@ -254,247 +622,204 @@ std::pair ComputeMove(const Vector& a, const Vector& b) { float yaw = DEG2RAD(ang.y - g_pUserCmd->viewangles.y); return { cos(yaw) * 450, -sin(yaw) * 450 }; } + + +// Crumb Followbot Helper functions + +// A function to reset the crumb followbot +void CrumbReset() { + + // We just reset the bookkeeping vars for the array, no need to clear the array as everything will be re-written anyways + crumbTop = 0; + crumbBottom = 0; + crumbArrayLength = 0; + crumb_prune_timeout = g_GlobalVars->curtime; + crumbFindNew = true; + crumbStopped = false; + logging::Info("Crumb Reset"); -// I've removed that too early. -void PrintDebug() { -/* - const Vector& a = LOCAL_E->m_vecOrigin; - const Vector& b = last_direction; - - Vector diff = (b - a); - if (diff.Length() == 0) return; - AddSideString(format("dx: ", diff.x)); - AddSideString(format("dy: ", diff.y)); - //float v_cos = diff.x / diff.Length(); - //float rad = acos(v_cos); - //if (diff.y < 0) rad = 2 * PI - rad; - //AddSideString(format("angle: ", rad / PI, " PI")); - float yan = g_Settings.last_angles.y; - float yaw = DEG2RAD(yan); - AddSideString(format("yaw: ", yaw / PI, " PI")); - //float rad_diff = yaw - rad; - //AddSideString(format("diff: ", rad_diff / PI, " PI")); - - auto move = ComputeMove(a, b); - AddSideString(format("forward: ", move.first)); - AddSideString(format("side: ", move.second));*/ } -void WalkTo(const Vector& vector) { - if (CE_VECTOR(LOCAL_E, netvar.vVelocity).IsZero(1.0f)) { - if (!idle_time) idle_time = g_GlobalVars->curtime; - if (LOCAL_E->m_vecOrigin.DistTo(vector) > 150.0f) { - if (g_GlobalVars->curtime - idle_time > 2.0f) { - if (!g_pLocalPlayer->bZoomed) - g_pUserCmd->buttons |= IN_JUMP; - } - } else { - idle_time = 0; - } - } - auto result = ComputeMove(LOCAL_E->m_vecOrigin, vector); - - g_pUserCmd->forwardmove = result.first; - g_pUserCmd->sidemove = result.second; +// A function to place a crumb into the array +void CrumbTopAdd(Vector crumbToAdd) { + + // Once the crumbs have hit the limit of the array, loop around and over write unused spots + if (crumbTop == 55) { + crumbTop = 0; + } else { + // Else, bump the top number market of the array + crumbTop++; + } + + // Put the newly determined crumb into the array and add to the length + crumbArrayLength++; + breadcrumbs[crumbTop] = crumbToAdd; + logging::Info("Crumb Top add"); + + // The array can only hold so many crumbs, once it goes over its cap, stop the bot to prevent un-needed movement + if (crumbArrayLength > 55) { + CrumbReset(); + crumbStopped = true; + logging::Info("Crumb Overload!\nDumping array"); + } } -void AfterCreateMove() { - auto it = selection.begin(); - while (it != selection.end()) { - int idx = *it; - CachedEntity* entity = ENTITY(idx); - if (CE_BAD(entity)) { - selection.erase(it++); - } else { - hacks::shared::esp::AddEntityString(entity, "[SELECTED]", colors::orange); - if (fmod(g_GlobalVars->curtime, 2.0f) < 1.0f) { - hacks::shared::esp::SetEntityColor(entity, colors::yellow); - } - ++it; - } - } - it = selection_secondary.begin(); - while (it != selection_secondary.end()) { - int idx = *it; - CachedEntity* entity = ENTITY(idx); - if (CE_BAD(entity)) { - selection_secondary.erase(it++); - } else { - hacks::shared::esp::AddEntityString(entity, "[SELECTED (SECONDARY)]", colors::orange); - if (fmod(g_GlobalVars->curtime, 2.0f) < 1.0f) { - hacks::shared::esp::SetEntityColor(entity, colors::yellow); - } - ++it; - } - } - DoWalking(); +// A function to free a crumb from the array +void CrumbBottomAdd() { + + // Once the crumbs have hit the limit of the array, loop around and over write unused spots + if (crumbBottom == 55) { + crumbBottom = 0; + } else { + // Else, bump the top number market of the array + crumbBottom++; + } + + // Subtract from the length to make room for more crumbs + crumbArrayLength--; + logging::Info("Crumb Bottom add"); + + // A check to detect if too many crumbs have been removed. Without crumbs the bot will just use random variables in the array. + // To prevent un-nessasary movement, just clear the array and wait for player + if (crumbArrayLength < 0) { + CrumbReset(); + crumbStopped = true; + logging::Info("Crumb Over-Prune!\nDumping array"); + } } -void DoWalking() { +// Function called when we need to draw onto the screen +void Draw() { + + // Dont draw if bot isnt true if (!bot) return; - following_idx = 0; - for (int i = 1; i < 32 && i < HIGHEST_ENTITY; i++) { - CachedEntity* ent = ENTITY(i); - if (CE_BAD(ent)) continue; - if (ent->player_info.friendsID == follow_steamid) { - following_idx = i; + + // If user settings allow, draw our breadcrumbs + if (crumb_draw) + DrawFollowbot(); +} + +// A Function for when we want to draw out the crumbs in the array onto the screen +void DrawFollowbot() { + + // Usefull debug info to know + AddSideString(format("Array Length: ", crumbArrayLength)); + AddSideString(format("Top Crumb: ", crumbTop)); + AddSideString(format("Bottom Crumb: ", crumbBottom)); + AddSideString(format("Crumb Stopped: ", crumbStopped)); + AddSideString(format("Curtime: ", g_GlobalVars->curtime)); + AddSideString(format("Timeout: ", crumb_prune_timeout)); + + + // Disabled as the enum was misbehaving for an unknown reason + + /*switch (current_follow_type) { + case EFollowType::VECTOR: // If our follow type is a vector, then we just draw a rect on the vector + + // Create a vector for the screen, run world to screen to bring the world vector into the screen vector, then draw a rect at the point + Vector scn; + draw::WorldToScreen(destination_point, scn); + drawgl::FilledRect(scn.x - 3, scn.y - 3, 6, 6); + // Return as we are finished + break; + + case EFollowType::ENTITY: // If our follow type is entity, then we draw out the crumbs here + logging::Info("Drawcrumb1"); + // Check if we have enough crumbs to draw a line between + if (crumbArrayLength < 2) { + + // If not, we check if we have 1 point and draw on it + if (crumbArrayLength == 1) { + + // Get the entity vector to screen and draw a rect on it + Vector scn; + draw::WorldToScreen(breadcrumbs[crumbBottom], scn); + drawgl::FilledRect(scn.x - 3, scn.y - 3, 6, 6); + } + + // Return as we have nothing else to do break; } - } - CachedEntity* found_entity = ENTITY(following_idx); - static float last_slot_check = 0.0f; - if (g_GlobalVars->curtime < last_slot_check) last_slot_check = 0.0f; + // Create book keeping vars to draw with + Vector scnSrt, scnEnd; + int tmpCrumb1, tmpCrumb2; - if (following_idx && (always_medigun || mimic_slot) && (g_GlobalVars->curtime - last_slot_check > 1.0f) && !g_pLocalPlayer->life_state && !CE_BYTE(found_entity, netvar.iLifeState)) { - int owner_weapon_eid = (CE_INT(found_entity, netvar.hActiveWeapon) & 0xFFF); - IClientEntity* owner_weapon = g_IEntityList->GetClientEntity(owner_weapon_eid); - if (owner_weapon && CE_GOOD(g_pLocalPlayer->weapon())) { - // IsBaseCombatWeapon() - if (vfunc(RAW_ENT(g_pLocalPlayer->weapon()), 190, 0)(RAW_ENT(g_pLocalPlayer->weapon())) && - vfunc(owner_weapon, 190, 0)(owner_weapon)) { - int my_slot = vfunc(RAW_ENT(g_pLocalPlayer->weapon()), 395, 0)(RAW_ENT(g_pLocalPlayer->weapon())); - int owner_slot = vfunc(owner_weapon, 395, 0)(owner_weapon); - if (g_pLocalPlayer->clazz == tf_medic && always_medigun) { - if (my_slot != 1) { - g_IEngine->ExecuteClientCmd("slot2"); - } - } else { - if (my_slot != owner_slot) { - g_IEngine->ExecuteClientCmd(format("slot", owner_slot + 1).c_str()); - } - } - } - // FIXME proper classes - // FIXME IsBaseCombatWeapon + // For loop to draw through the crumbs + for (int i = 0; i < crumbArrayLength; i++) { + + // We need 2 crumbs to draw a line, so we get the number in the array for 2 crumbs + tmpCrumb1 = crumbBottom + i; + tmpCrumb2 = crumbBottom + i + 1; + + // Correction for array numbers when one goes over our limit + if (tmpCrumb1 >= 55) + tmpCrumb1 - 55; + if (tmpCrumb2 >= 55) + tmpCrumb1 - 55; + + // Take our 2 crumbs and get a position on the screen + draw::WorldToScreen(breadcrumbs[tmpCrumb1], scnSrt); + draw::WorldToScreen(breadcrumbs[tmpCrumb2], scnEnd); + + // Draw a line from one crumb to the other + drawgl::Line(scnSrt.x, scnSrt.y, scnEnd.x - scnSrt.x, scnEnd.y - scnSrt.y, colors::white); + + // If this is our first iteration, draw a box on 1 + if (i == 1) + drawgl::FilledRect(scnSrt.x - 3, scnSrt.y - 3, 6, 6); } - last_slot_check = g_GlobalVars->curtime; - } + break; + }*/ - //Nullified Cats Followbot code used for followbot tool - //Keeping this for the followbot move and stay commands - //TODO remove player follow from this or change it to be a backup for the crumb follow bot - if (!crumbAbleToMove) { - if (destination_point_time > g_GlobalVars->curtime) destination_point_time = 0.0f; - - if (!destination_reached && (g_GlobalVars->curtime - destination_point_time < 5.0f)) { - WalkTo(destination_point); - last_direction = destination_point; - if (g_pLocalPlayer->v_Origin.DistTo(destination_point) < 50.0f) destination_reached = true; - } else if (following_idx) { - if (allow_moving) { - if (!found_entity->IsVisible()) { - if (!lost_time) { - lost_time = g_GlobalVars->curtime; - } - if (g_GlobalVars->curtime - lost_time < 2.0f) { - WalkTo(last_direction); - } - } else { - lost_time = 0; - if (found_entity->m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin) > 150.0f) { - WalkTo(found_entity->m_vecOrigin); - } - last_direction = found_entity->m_vecOrigin; - } - } - } - } - - //Breadcrumb followbot - if (allow_moving && crumbAbleToMove && !crumbStopped) { - //Generate new breadcrumbs made by the player only if they are close to the ground. If the bot is told to generate a starting point, it does that as well. - if ((found_entity->m_vecOrigin.DistTo(breadcrumbs[crumbTop]) > 40.0F || crumbFindNew) && crumbDistanceToFloor(found_entity) < 25) { - //Add to the crumb. - CrumbTopAdd(found_entity->m_vecOrigin); - - //If the bot was forced to select a point, add a buffer crumb and bump to the newly selected point and use it - if (crumbFindNew) { - crumbFindNew = false; - CrumbTopAdd(found_entity->m_vecOrigin); - CrumbBottomAdd(); - } - } - - //Prune used crumbs from the stack to make way for new ones when you get close to them. - if (g_pLocalPlayer->v_Origin.DistTo(breadcrumbs[crumbBottom]) < 40.0F) { - //Debug Logging - logging::Info("Pruning"); - - //When the bot is forced to move to the player, since they have reached their destination we reset the var - crumbForceMove = false; - - //Check 15 times for close crumbs to prune, this allows simple miss steps to be smoothed out as well as make room for new crumbs - for (int i = 0; i < 15; i++) { - //When one is close or too high, just bump the array and reset the stuck timer - if (g_pLocalPlayer->v_Origin.DistTo(breadcrumbs[crumbBottom]) < 60.0F) { - CrumbBottomAdd(); - - //When pruning is finished. Break the loop - } else { - crumbWaitPrune = g_GlobalVars->curtime; - logging::Info("Finish Prune"); - break; - } - } - //Reset stuck timer - crumbWaitPrune = g_GlobalVars->curtime; - } - - //When the player is not visible to the bot, within 5 seconds of it not being visible we stop the bot and wait for the player to collect it - if (!found_entity->IsVisible()) { - if (g_GlobalVars->curtime - 5.0F > crumbWaitVis && g_GlobalVars->curtime - 2.5F > crumbWaitPrune) { - //crumbStopped = true; - logging::Info("Crumb Lost!"); - } - - //If the player is visible, then reset our lost timer. - } else crumbWaitVis = g_GlobalVars->curtime; - - - //When player to follow is too far away. the bot cant see the player or the bot is forced to the player, then follow breadcrumbs - if ((g_pLocalPlayer->v_Origin.DistTo(found_entity->m_vecOrigin) > (float)crumbFollowDistance || crumbForceMove) && crumbArrayLength >= 1 ) { - WalkTo(breadcrumbs[crumbBottom]); - - //If a crumb hasnt been pruned in a while, it probably cant travel to it so reset and wait for the player to collect it. - if (g_GlobalVars->curtime - 2.5F > crumbWaitPrune) { - crumbStopped = true; - logging::Info("Cannot goto next crumb!\nCrumb Lost!"); - } - - //If the bot is next to the player then we clear our crumbs as theres no need to follow previously generated ones. - } else if (g_pLocalPlayer->v_Origin.DistTo(found_entity->m_vecOrigin) < 100.0F && found_entity->IsVisible()) { - crumbForceMove = false; - crumbStopped = true; - CrumbStart(); - } - - //If the bot is lost but it finds the player again, start the followbot again. - } else if (crumbStopped && allow_moving && crumbAbleToMove && found_entity->IsVisible() && (g_pLocalPlayer->v_Origin.DistTo(found_entity->m_vecOrigin) < 3000.0F)) { - crumbForceMove = true; - CrumbStart(); - logging::Info("Crumb found player!"); - } - - if (following_idx) { - if (found_entity->m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin) > 150.0f) { - if (LOCAL_E->m_vecOrigin.DistTo(found_entity->m_vecOrigin) > 350.0f) { - if (g_pLocalPlayer->bZoomed) g_pUserCmd->buttons |= IN_ATTACK2; - } - } - if (CE_INT(found_entity, netvar.iClass) == tf_heavy && g_pLocalPlayer->clazz == tf_heavy) { - if (HasCondition(found_entity)) { - g_pUserCmd->buttons |= IN_ATTACK2; - } - } - if (HasCondition(found_entity)) { - if (!g_pLocalPlayer->bZoomed && g_pLocalPlayer->clazz == tf_sniper) { - g_pUserCmd->buttons |= IN_ATTACK2; - } + + // Not using switch due to switch not working correctly + if (crumbArrayLength < 2) { + + // If not, we check if we have 1 point and draw on it + if (crumbArrayLength == 1) { + + // Get the entity vector to screen and draw a rect on it + Vector scn; + draw::WorldToScreen(breadcrumbs[crumbBottom], scn); + drawgl::FilledRect(scn.x - 3, scn.y - 3, 6, 6); } + + // Return as we have nothing else to do + return; } -} + // Create book keeping vars to draw with + Vector scnSrt, scnEnd; + int tmpCrumb1, tmpCrumb2; + + // For loop to draw through the crumbs + for (int i = 0; i < crumbArrayLength; i++) { + + // We need 2 crumbs to draw a line, so we get the number in the array for 2 crumbs + tmpCrumb1 = crumbBottom + i; + tmpCrumb2 = crumbBottom + i + 1; + + // Correction for array numbers when one goes over our limit + if (tmpCrumb1 >= 55) + tmpCrumb1 - 55; + if (tmpCrumb2 >= 55) + tmpCrumb2 - 55; + + // Take our 2 crumbs and get a position on the screen + draw::WorldToScreen(breadcrumbs[tmpCrumb1], scnSrt); + draw::WorldToScreen(breadcrumbs[tmpCrumb2], scnEnd); + + // Draw a line from one crumb to the other + drawgl::Line(scnSrt.x, scnSrt.y, scnEnd.x - scnSrt.x, scnEnd.y - scnSrt.y, colors::white); + + // If this is our first iteration, draw a box on 1 + if (i == 0) + drawgl::FilledRect(scnSrt.x - 3, scnSrt.y - 3, 6, 6); + } + return; +} + }}} #endif diff --git a/src/hacks/FollowBot.h b/src/hacks/FollowBot.h index a97cdfd8..c38c069d 100644 --- a/src/hacks/FollowBot.h +++ b/src/hacks/FollowBot.h @@ -18,19 +18,32 @@ class CachedEntity; namespace hacks { namespace shared { namespace followbot { -extern CatCommand move_to_crosshair; -extern CatCommand follow; -extern CatCommand follow_entity; +enum class EFollowType { + VECTOR, // When we need to goto a vector + ENTITY // when we dont have a specific steamid, but we still want to follow an entity +}; + +//extern CatCommand move_to_crosshair; +//extern CatCommand follow; +//extern CatCommand follow_entity; extern CatVar bot; extern unsigned follow_steamid; extern int following_idx; bool IsBot(CachedEntity* entity); -void DoWalking(); -void PrintDebug(); void AddMessageHandlers(ipc::peer_t* peer); void AfterCreateMove(); +void Draw(); +void CrumbReset(); +void CrumbTopAdd(Vector crumbToAdd); +void CrumbBottomAdd(); +void DoWalking(); +void DrawFollowbot(); +std::pair ComputeMove(const Vector& a, const Vector& b); +void WalkTo(const Vector& vector); +unsigned MakeMask(); +void SelectEntity(int idx); }}} diff --git a/src/hacks/Spam.cpp b/src/hacks/Spam.cpp index b56798d2..131efc7d 100644 --- a/src/hacks/Spam.cpp +++ b/src/hacks/Spam.cpp @@ -14,8 +14,13 @@ static CatEnum spam_enum({"DISABLED", "CUSTOM", "DEFAULT", "LENNYFACES", "BLANKS CatVar spam_source(spam_enum, "spam", "0", "Chat Spam", "Defines source of spam lines. CUSTOM spam file must be set in cat_spam_file and loaded with cat_spam_reload (Use console!)"); CatVar random_order(CV_SWITCH, "spam_random", "0", "Random Order"); CatVar filename(CV_STRING, "spam_file", "spam.txt", "Spam file", "Spam file name. Each line should be no longer than 100 characters, file must be located in cathook data folder"); -CatVar teamname_spam(CV_SWITCH, "spam_teamname", "0", "Teamname Spam", "Spam changes the tournament name"); CatCommand reload("spam_reload", "Reload spam file", Reload); + +static CatEnum voicecommand_enum({"DISABLED", "RANDOM", "MEDIC", "THANKS", "NICE SHOT", "CHEERS", "JEERS"}); +CatVar voicecommand_spam(voicecommand_enum, "spam_voicecommand", "0", "Voice Command Spam", "Spams tf voice commands"); + +CatVar teamname_spam(CV_SWITCH, "spam_teamname", "0", "Teamname Spam", "Spam changes the tournament name"); + bool teamname_swap = false; int current_index { 0 }; @@ -24,6 +29,8 @@ TextFile file {}; const std::string teams[] = { "RED", "BLU" }; + + // FUCK enum class. // It doesn't have bitwise operators by default!! WTF!! static_cast(REEE)! @@ -172,15 +179,46 @@ bool FormatSpamMessage(std::string& message) { } void CreateMove() { - if (teamname_spam) { - if (teamname_swap) { - teamname_swap = false; - g_IEngine->ServerCmd("tournament_teamname Cat"); - } else { - teamname_swap = true; - g_IEngine->ServerCmd("tournament_teamname Hook"); - } + + IF_GAME (IsTF2()) { + // Spam changes the tournament name in casual and compeditive gamemodes + if (teamname_spam) { + if (teamname_swap) { + teamname_swap = false; + g_IEngine->ServerCmd("tournament_teamname Cat"); + } else { + teamname_swap = true; + g_IEngine->ServerCmd("tournament_teamname Hook"); + } + } + + if (voicecommand_spam) { + static float last_voice_spam = 0.0f; + if (g_GlobalVars->curtime - 4.0F > last_voice_spam) { + switch ((int)voicecommand_spam) { + case 1: // RANDOM + g_IEngine->ServerCmd(format("voicemenu ", floor(RandFloatRange(0, 2.9)), " ", floor(RandFloatRange(0, 8.9))).c_str()); + break; + case 2: // MEDIC + g_IEngine->ServerCmd("voicemenu 0 0"); + break; + case 3: // THANKS + g_IEngine->ServerCmd("voicemenu 0 1"); + break; + case 4: // NICE SHOT + g_IEngine->ServerCmd("voicemenu 2 6"); + break; + case 5: // CHEERS + g_IEngine->ServerCmd("voicemenu 2 2"); + break; + case 6: // JEERS + g_IEngine->ServerCmd("voicemenu 2 3"); + } + last_voice_spam = g_GlobalVars->curtime; + } + } } + if (!spam_source) return; static int safety_ticks = 0; static int last_spam = 0; diff --git a/src/hacks/Trigger.cpp b/src/hacks/Trigger.cpp index 892f7974..ab0af204 100644 --- a/src/hacks/Trigger.cpp +++ b/src/hacks/Trigger.cpp @@ -26,6 +26,8 @@ static CatVar trigger_key_mode(trigger_key_modes_enum, "trigger_key_mode", "1", static CatEnum hitbox_mode_enum({ "AUTO-HEAD", "AUTO-CLOSEST", "Head only" }); static CatVar hitbox_mode(hitbox_mode_enum, "trigger_hitboxmode", "0", "Hitbox Mode", "Defines hitbox selection mode"); + +static CatVar accuracy(CV_INT, "trigger_accuracy", "0", "Improve accuracy", "Improves triggerbot accuracy when aiming for specific hitbox"); static CatVar ignore_vaccinator(CV_SWITCH, "trigger_ignore_vaccinator", "1", "Ignore Vaccinator", "Hitscan weapons won't fire if enemy is vaccinated against bullets"); static CatVar ignore_hoovy(CV_SWITCH, "trigger_ignore_hoovy", "1", "Ignore Hoovies", "Triggerbot won't attack hoovies"); @@ -42,6 +44,7 @@ static CatVar max_range(CV_INT, "trigger_maxrange", "0", "Max distance", "900-1100 range is efficient for scout/widowmaker engineer", 4096.0f); int last_hb_traced = 0; +Vector forward; // The main "loop" of the triggerbot void CreateMove() { @@ -170,6 +173,35 @@ bool IsTargetStateGood(CachedEntity* entity) { if (last_hb_traced != hitbox_t::head) return false; } + // If usersettings tell us to use accuracy improvements and the cached hitbox isnt null, then we check if it hits here + if (accuracy) { + + // Get a cached hitbox for the one traced + hitbox_cache::CachedHitbox* hb = entity->hitboxes.GetHitbox(last_hb_traced); + + // Check for null + if (hb) { + + // Get the min and max for the hitbox + Vector minz(min(hb->min.x, hb->max.x), min(hb->min.y, hb->max.y), min(hb->min.z, hb->max.z)); + Vector maxz(max(hb->min.x, hb->max.x), max(hb->min.y, hb->max.y), max(hb->min.z, hb->max.z)); + + // Shrink the hitbox here + Vector size = maxz - minz; + Vector smod = size * 0.05f * (int)accuracy; + + // Save the changes to the vectors + minz += smod; + maxz -= smod; + + // Trace and test if it hits the smaller hitbox, if it fails we return false + Vector hit; + if (!CheckLineBox(minz, maxz, g_pLocalPlayer->v_Eye, forward, hit)) { + return false; + } + } + } + // Target passed the tests so return true return true; @@ -219,6 +251,7 @@ bool IsTargetStateGood(CachedEntity* entity) { // A function to return a potential entity in front of the player CachedEntity* FindEntInSight(float range) { + // We dont want to hit ourself so we set an ignore trace_t trace; trace::filter_default.SetSelf(RAW_ENT(g_pLocalPlayer->entity)); @@ -226,7 +259,6 @@ CachedEntity* FindEntInSight(float range) { // Use math to get a vector in front of the player float sp, sy, cp, cy; QAngle angle; - Vector forward; g_IEngine->GetViewAngles(angle); sy = sinf(DEG2RAD(angle[1])); cy = cosf(DEG2RAD(angle[1])); @@ -246,6 +278,7 @@ CachedEntity* FindEntInSight(float range) { // Return an ent if that is what we hit if (trace.m_pEnt) { + last_hb_traced = trace.hitbox; return ENTITY(((IClientEntity*)trace.m_pEnt)->entindex()); } @@ -384,6 +417,49 @@ float EffectiveTargetingRange() { } } +// Helper functions to trace for hitboxes + +// TEMPORARY CODE. +// TODO +bool GetIntersection(float fDst1, float fDst2, Vector P1, Vector P2, Vector& Hit) { + if ((fDst1 * fDst2) >= 0.0f) return false; + if (fDst1 == fDst2) return false; + Hit = P1 + (P2 - P1) * (-fDst1 / (fDst2 - fDst1)); + return true; +} + +bool InBox(Vector Hit, Vector B1, Vector B2, int Axis) { + if (Axis == 1 && Hit.z > B1.z && Hit.z < B2.z && Hit.y > B1.y && Hit.y < B2.y) return true; + if (Axis == 2 && Hit.z > B1.z && Hit.z < B2.z && Hit.x > B1.x && Hit.x < B2.x) return true; + if (Axis == 3 && Hit.x > B1.x && Hit.x < B2.x && Hit.y > B1.y && Hit.y < B2.y) return true; + return false; +} + +bool CheckLineBox(Vector B1, Vector B2, Vector L1, Vector L2, Vector& Hit) { + if (L2.x < B1.x && L1.x < B1.x) return false; + if (L2.x > B2.x && L1.x > B2.x) return false; + if (L2.y < B1.y && L1.y < B1.y) return false; + if (L2.y > B2.y && L1.y > B2.y) return false; + if (L2.z < B1.z && L1.z < B1.z) return false; + if (L2.z > B2.z && L1.z > B2.z) return false; + if (L1.x > B1.x && L1.x < B2.x && + L1.y > B1.y && L1.y < B2.y && + L1.z > B1.z && L1.z < B2.z) + { + Hit = L1; + return true; + } + if ((GetIntersection(L1.x - B1.x, L2.x - B1.x, L1, L2, Hit) && InBox(Hit, B1, B2, 1)) + || (GetIntersection(L1.y - B1.y, L2.y - B1.y, L1, L2, Hit) && InBox(Hit, B1, B2, 2)) + || (GetIntersection(L1.z - B1.z, L2.z - B1.z, L1, L2, Hit) && InBox(Hit, B1, B2, 3)) + || (GetIntersection(L1.x - B2.x, L2.x - B2.x, L1, L2, Hit) && InBox(Hit, B1, B2, 1)) + || (GetIntersection(L1.y - B2.y, L2.y - B2.y, L1, L2, Hit) && InBox(Hit, B1, B2, 2)) + || (GetIntersection(L1.z - B2.z, L2.z - B2.z, L1, L2, Hit) && InBox(Hit, B1, B2, 3))) + return true; + + return false; +} + void Draw() { } diff --git a/src/hacks/Trigger.h b/src/hacks/Trigger.h index f963678d..317a6b8b 100644 --- a/src/hacks/Trigger.h +++ b/src/hacks/Trigger.h @@ -22,6 +22,7 @@ bool HeadPreferable(CachedEntity* target); bool UpdateAimkey(); float EffectiveTargetingRange(); void Draw(); +bool CheckLineBox(Vector B1, Vector B2, Vector L1, Vector L2, Vector& Hit); }}} diff --git a/tf-settings/menu.json b/tf-settings/menu.json index 06bf6625..91ee2899 100644 --- a/tf-settings/menu.json +++ b/tf-settings/menu.json @@ -16,14 +16,15 @@ "type": "list", "name": "Ignore", "list": [ + "aimbot_ignore_cloak", + "aimbot_ignore_deadringer", + "aimbot_ignore_vaccinator", "ignore_taunting", "aimbot_ignore_hoovy", + "aimbot_teammates", "aimbot_buildings_sentry", "aimbot_buildings_other", - "aimbot_stickys", - "aimbot_teammates", - "aimbot_ignore_cloak", - "aimbot_ignore_vaccinator" + "aimbot_stickys" ] }, { @@ -110,11 +111,11 @@ "name": "Auto Reflect", "list": [ "reflect_enabled", + "reflect_key", "reflect_legit", "reflect_only_idle", "reflect_stickybombs", - "reflect_dodgeball", - "reflect_key" + "reflect_dodgeball" ] }, { @@ -138,6 +139,7 @@ "trigger_maxrange", "trigger_charge", "trigger_hitboxmode", + "trigger_accuracy", "trigger_key_mode", "trigger_key" ] @@ -345,36 +347,23 @@ "type": "list", "name": "Chat Settings", "list": [ + "spam", + "killsay", + "uberspam", + "spam_file", + "killsay_file", + "spam_random", "chat_newlines", "clean_chat", - "killsay", - "killsay_file", - "spam", - "spam_random", - "spam_file", - "spam_teamname", - "uberspam", - "uberspam_build", + "chat_crypto", "uberspam_ready", "uberspam_used", "uberspam_ended", "uberspam_team", - "uberspam_file" - ] - }, - { - "type": "list", - "name": "Follow Bot Settings", - "data": "ipc", - "list": [ - "fb_bot", - "fb_auto_playerlist", - "fb_mimic_slot", - "fb_always_medigun", - "fb_autoclass", - "fb_follow_distance", - "ipc_server", - "fb_autoteam" + "uberspam_build", + "uberspam_file", + "spam_voicecommand", + "spam_teamname" ] }, { @@ -392,6 +381,20 @@ "ac_bhop_count" ] }, + { + "type": "list", + "name": "Followbot", + "list": [ + "fb_bot", + "fb_distance", + "fb_activation", + "fb_roaming", + "fb_mimic_slot", + "fb_always_medigun", + "fb_sync_taunt", + "fb_crumb_draw" + ] + }, { "type": "list", "name": "Miscellaneous", @@ -492,6 +495,7 @@ "debug_info", "log", "setupbones_time", + "nolerp", "skinchanger_debug", "software_cursor_mode", "experimental_cursor_fix",