mirror of
https://github.com/ocornut/imgui.git
synced 2026-11-06 06:25:54 +01:00
3418 lines
166 KiB
C++
3418 lines
166 KiB
C++
// 启用 ImVec2 的数学运算符支持
|
||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||
|
||
#include "imgui.h"
|
||
#include "imgui_internal.h"
|
||
#include "imgui_impl_opengl3.h"
|
||
#include <thread>
|
||
#include <cmath>
|
||
#include <fstream>
|
||
#include <string>
|
||
#include <vector>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#include <map>
|
||
#include <chrono>
|
||
#include <GLES3/gl3.h>
|
||
#include <EGL/egl.h>
|
||
#include <android/log.h>
|
||
#include <algorithm>
|
||
#include <unistd.h>
|
||
#include <jni.h>
|
||
#include <mutex>
|
||
#include <atomic>
|
||
#include <memory>
|
||
#include "dobby.h"
|
||
#include <fcntl.h>
|
||
#include <deque>
|
||
#include <stdarg.h>
|
||
#include <ctime>
|
||
#include <dlfcn.h>
|
||
#include <sstream>
|
||
#include <numeric>
|
||
|
||
// 图片加载
|
||
#define STB_IMAGE_IMPLEMENTATION
|
||
#define STBI_NO_SIMD
|
||
#define STBI_NO_THREAD_LOCALS
|
||
#include "stb_image.h"
|
||
#include "HeroImages.h"
|
||
#include "EquipImages.h"
|
||
|
||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "JKInternal", __VA_ARGS__)
|
||
|
||
// =================================================================
|
||
// 全局配置变量与偏移声明
|
||
// =================================================================
|
||
enum ConfigType { C_BOOL, C_INT, C_FLOAT, C_STRING };
|
||
struct ConfigEntry { const char* key; ConfigType type; void* ptr; };
|
||
|
||
int g_gl_width = 0, g_gl_height = 0;
|
||
int g_cached_view_width = -1, g_cached_view_height = -1; // 用于坐标转换映射
|
||
const char* g_configPath = "/data/data/com.tencent.jkchess/jkchess_config.ini";
|
||
std::mutex g_configMutex; // 文件保存专用线程锁
|
||
|
||
// --- 换肤开关与配置 ---
|
||
bool g_enable_skin = false;
|
||
int g_selected_skin_idx = 0;
|
||
bool g_header_skin = true;
|
||
|
||
bool g_enable_map_skin = false;
|
||
int g_selected_map_idx = 0;
|
||
bool g_header_map_skin = true;
|
||
// ----------------------
|
||
|
||
bool g_predict_enemy = false, g_predict_hex = false;
|
||
bool g_esp_board = true, g_esp_level = false;
|
||
bool g_esp_enemy_bench = false; // 已删除自身商店、自身备战、敌方商店
|
||
bool g_show_instant_quit_window = false, g_boardLocked = false;
|
||
bool g_show_card_pool = false, g_card_warning = false;
|
||
|
||
// 卖牌悬浮窗配置
|
||
bool g_show_sell_window = false;
|
||
float g_sell_win_x = 400.0f, g_sell_win_y = 350.0f, g_sell_win_scale = 1.0f;
|
||
|
||
bool g_show_auto_window = false, g_auto_refresh = false;
|
||
bool g_auto_buy = false, g_auto_buy_chosen = false;
|
||
bool g_show_hero_list_window = false;
|
||
bool g_show_clear_bench_btn = true;
|
||
|
||
// 自动穿装备/拆装备相关配置
|
||
bool g_show_equip_window = false;
|
||
bool g_auto_equip_active = false;
|
||
bool g_auto_unequip_active = false;
|
||
float g_equip_win_x = 400.0f, g_equip_win_y = 250.0f, g_equip_win_scale = 1.0f;
|
||
bool g_draw_hero_lines = true;
|
||
float g_esp_line_offset_y = 50.0f;
|
||
|
||
// [深度重构]:每个格子独立的偏移记录 (独立保存,跟随格子而不跟随英雄)
|
||
std::unordered_map<int, ImVec2> g_cell_equip_offset;
|
||
std::string g_cell_equip_offset_str = "";
|
||
std::unordered_map<int, ImVec2> g_cell_sell_offset;
|
||
std::string g_cell_sell_offset_str = "";
|
||
|
||
float g_sell_drop_x = 500.0f; // 卖牌全局落点X
|
||
float g_sell_drop_y = 900.0f; // 卖牌全局落点Y
|
||
|
||
// 模拟滑动的装备栏物理UI坐标配置
|
||
float g_equip_ui_start_x = 100.0f;
|
||
float g_equip_ui_start_y = 300.0f;
|
||
float g_equip_ui_step_x = 100.0f;
|
||
float g_equip_ui_step_y = 100.0f;
|
||
bool g_show_equip_ui_debug = false;
|
||
bool g_show_sell_ui_debug = false;
|
||
int g_unequip_slot_idx = 0;
|
||
|
||
// 自动滑动核心调节参数与视觉反馈结构
|
||
int g_swipe_step_delay_ms = 10;
|
||
int g_swipe_steps = 15;
|
||
bool g_show_swipe_visuals = true;
|
||
int g_swipe_down_delay_ms = 30;
|
||
int g_swipe_up_delay_ms = 20;
|
||
int g_equip_extra_cooldown_ms = 100;
|
||
|
||
struct SwipeVisData { ImVec2 start; ImVec2 end; float lifeTime; float maxLife; };
|
||
std::vector<SwipeVisData> g_active_swipes;
|
||
std::mutex g_swipe_mutex;
|
||
|
||
std::unordered_set<int> g_auto_buy_heroes;
|
||
std::string g_auto_buy_heroes_str = "";
|
||
|
||
std::unordered_map<int, std::vector<int>> g_hero_equip_bindings;
|
||
std::string g_hero_equip_bindings_str = "";
|
||
|
||
std::unordered_map<int, int> g_hero_equip_priority;
|
||
std::string g_hero_equip_priority_str = "";
|
||
std::unordered_set<int> g_expanded_equip_heroes;
|
||
|
||
std::map<int, std::vector<int>> g_local_pokedex;
|
||
std::vector<int> g_local_equip_pokedex;
|
||
std::atomic<bool> g_pokedex_ready{false};
|
||
std::atomic<bool> g_equip_pokedex_ready{false};
|
||
|
||
float g_auto_buy_delay_ms = 0.0f;
|
||
int g_auto_refresh_money_threshold = 2;
|
||
float g_auto_win_x = 400.0f, g_auto_win_y = 150.0f, g_auto_win_scale = 1.0f;
|
||
float g_instant_win_x = 100.0f, g_instant_win_y = 100.0f, g_instant_win_scale = 1.0f;
|
||
float g_global_esp_alpha = 1.0f;
|
||
|
||
float g_enemy_bench_X = 200.0f, g_enemy_bench_Y = 200.0f, g_enemy_bench_Scale = 1.0f;
|
||
|
||
// --- 偏移量配置 ---
|
||
int g_off_shop_base = 0x440, g_off_bench_base = 0x448, g_off_shop_p20 = 0x20;
|
||
int g_off_shop_entries = 0x10, g_off_entries_count = 0x18, g_off_entries_head = 0x20;
|
||
int g_off_entries_step = 0x20, g_off_entry_value = 0x10, g_off_value_slot = 0x1c;
|
||
int g_off_value_p10 = 0x10, g_off_p10_heroId = 0x14;
|
||
|
||
int g_off_pool_heroku = 0x18, g_off_pool_Aheroku = 0x18, g_off_pool_entries1 = 0x10;
|
||
int g_off_pool_e1_count = 0x18, g_off_pool_e1_head = 0x20, g_off_pool_e1_step = 0x20, g_off_pool_e1_val = 0x10;
|
||
int g_off_pool_entries2 = 0x10, g_off_pool_e2_count = 0x18, g_off_pool_e2_head = 0x20, g_off_pool_e2_step = 0x20, g_off_pool_e2_val = 0x10;
|
||
int g_off_pool_v2_id = 0x10, g_off_pool_v2_rem = 0x1c, g_off_pool_v2_tot = 0x20;
|
||
|
||
// 装备与3D寻址偏移配置
|
||
int g_off_equip_base = 0x2e8;
|
||
int g_off_equip_arr = 0x10;
|
||
int g_off_equip_count = 0x18;
|
||
int g_off_equip_head = 0x20;
|
||
int g_off_equip_step = 0x8;
|
||
int g_off_equip_id = 0x14;
|
||
int g_off_hero_dyn = 0x2b0;
|
||
int g_off_hero_arr = 0x20;
|
||
int g_off_hero_count = 0x18;
|
||
int g_off_hero_head = 0x20;
|
||
int g_off_hero_step = 0x8;
|
||
int g_off_hero_id = 0x108;
|
||
int g_off_hero_unit = 0x48;
|
||
int g_off_unit_battle = 0x20;
|
||
int g_off_battle_posx = 0x238;
|
||
|
||
int g_card_pool_rows = 2, g_card_pool_cols = 5;
|
||
float g_cardPoolX = 150.0f, g_cardPoolY = 150.0f;
|
||
float g_cardPoolScaleX = 1.0f, g_cardPoolScaleY = 1.0f, g_cardPoolAlpha = 1.0f;
|
||
bool g_show_pool_tiers[7] = {false, true, true, true, true, true, false};
|
||
|
||
bool g_pool_warning_enable = false;
|
||
int g_pool_warning_count = 3;
|
||
bool g_pool_warning_tiers[7] = {false, true, true, true, true, true, false};
|
||
|
||
bool g_warning_tiers[7] = {false, false, false, false, true, false, false};
|
||
int g_warning_threshold = 6;
|
||
|
||
bool g_show_debug_window = false, g_header_pred = true, g_header_esp = true, g_menuCollapsed = false;
|
||
float g_save_timer = 0.0f, g_scale = 1.0f, g_autoScale = 1.0f, g_current_rendered_size = 0.0f;
|
||
float g_anim[30] = {0.0f};
|
||
float g_boardScale = 2.2f, g_boardManualScale = 1.0f, g_startX = 400.0f, g_startY = 400.0f;
|
||
float g_menuX = 100.0f, g_menuY = 100.0f, g_menuW = 500.0f, g_menuH = 650.0f;
|
||
float g_enemy_X = 100.0f, g_enemy_Y = 100.0f, g_enemy_Scale = 1.0f;
|
||
float g_hex_X = 100.0f, g_hex_Y = 220.0f, g_hex_Scale = 1.0f;
|
||
float g_level_X = 1500.0f, g_level_Y = 200.0f, g_level_Scale = 1.0f, g_level_Spacing = 45.0f;
|
||
float g_warn_X = 1650.0f, g_warn_Y = 200.0f, g_warn_Scale = 1.0f, g_warn_Spacing = 45.0f;
|
||
float g_predictNameX = 500.0f, g_predictNameY = 300.0f, g_predictNameScale = 1.5f;
|
||
|
||
bool g_needUpdateFontSafe = false;
|
||
ImFont* g_mainFont = nullptr, *g_hugeNumFont = nullptr;
|
||
|
||
std::deque<std::string> g_collected_skins;
|
||
std::deque<std::string> g_collected_maps;
|
||
std::mutex g_collect_mutex;
|
||
bool g_collect_skins = false;
|
||
bool g_collect_maps = false;
|
||
|
||
ConfigEntry g_configTable[] = {
|
||
{"enableSkin", C_BOOL, &g_enable_skin}, {"skinIdx", C_INT, &g_selected_skin_idx},
|
||
{"enableMapSkin", C_BOOL, &g_enable_map_skin}, {"mapSkinIdx", C_INT, &g_selected_map_idx},
|
||
{"globalEspAlpha", C_FLOAT, &g_global_esp_alpha},
|
||
{"predictEnemy", C_BOOL, &g_predict_enemy}, {"predictHex", C_BOOL, &g_predict_hex},
|
||
{"predictNameX", C_FLOAT, &g_predictNameX}, {"predictNameY", C_FLOAT, &g_predictNameY}, {"predictNameScale", C_FLOAT, &g_predictNameScale},
|
||
{"collectSkins", C_BOOL, &g_collect_skins}, {"collectMaps", C_BOOL, &g_collect_maps},
|
||
{"espBoard", C_BOOL, &g_esp_board}, {"espLevel", C_BOOL, &g_esp_level},
|
||
{"espEnemyBench", C_BOOL, &g_esp_enemy_bench},
|
||
{"offShopBase", C_INT, &g_off_shop_base}, {"offBenchBase", C_INT, &g_off_bench_base}, {"offShopP20", C_INT, &g_off_shop_p20},
|
||
{"offShopEntries", C_INT, &g_off_shop_entries}, {"offEntriesCount", C_INT, &g_off_entries_count}, {"offEntriesHead", C_INT, &g_off_entries_head},
|
||
{"offEntriesStep", C_INT, &g_off_entries_step}, {"offEntryValue", C_INT, &g_off_entry_value}, {"offValueSlot", C_INT, &g_off_value_slot},
|
||
{"offValueP10", C_INT, &g_off_value_p10}, {"offP10HeroId", C_INT, &g_off_p10_heroId},
|
||
{"offPoolHeroku", C_INT, &g_off_pool_heroku}, {"offPoolAheroku", C_INT, &g_off_pool_Aheroku},
|
||
{"offPoolE1", C_INT, &g_off_pool_entries1}, {"offPoolE1Cnt", C_INT, &g_off_pool_e1_count},
|
||
{"offPoolE1Hd", C_INT, &g_off_pool_e1_head}, {"offPoolE1Stp", C_INT, &g_off_pool_e1_step},
|
||
{"offPoolE1Val", C_INT, &g_off_pool_e1_val}, {"offPoolE2", C_INT, &g_off_pool_entries2},
|
||
{"offPoolE2Cnt", C_INT, &g_off_pool_e2_count}, {"offPoolE2Hd", C_INT, &g_off_pool_e2_head},
|
||
{"offPoolE2Stp", C_INT, &g_off_pool_e2_step}, {"offPoolE2Val", C_INT, &g_off_pool_e2_val},
|
||
{"offPoolV2Id", C_INT, &g_off_pool_v2_id}, {"offPoolV2Rem", C_INT, &g_off_pool_v2_rem},
|
||
{"offPoolV2Tot", C_INT, &g_off_pool_v2_tot},
|
||
{"showCardPool", C_BOOL, &g_show_card_pool}, {"cardPoolRows", C_INT, &g_card_pool_rows}, {"cardPoolCols", C_INT, &g_card_pool_cols},
|
||
{"cardPoolScaleX", C_FLOAT, &g_cardPoolScaleX}, {"cardPoolScaleY", C_FLOAT, &g_cardPoolScaleY}, {"cardPoolAlpha", C_FLOAT, &g_cardPoolAlpha},
|
||
{"cardPoolX", C_FLOAT, &g_cardPoolX}, {"cardPoolY", C_FLOAT, &g_cardPoolY},
|
||
{"poolWarnEn", C_BOOL, &g_pool_warning_enable}, {"poolWarnCnt", C_INT, &g_pool_warning_count},
|
||
{"poolWT1", C_BOOL, &g_pool_warning_tiers[1]}, {"poolWT2", C_BOOL, &g_pool_warning_tiers[2]},
|
||
{"poolWT3", C_BOOL, &g_pool_warning_tiers[3]}, {"poolWT4", C_BOOL, &g_pool_warning_tiers[4]},
|
||
{"poolWT5", C_BOOL, &g_pool_warning_tiers[5]},
|
||
{"autoRefresh", C_BOOL, &g_auto_refresh}, {"autoBuy", C_BOOL, &g_auto_buy}, {"autoBuyDelay", C_FLOAT, &g_auto_buy_delay_ms},
|
||
{"autoRefMoneyThr", C_INT, &g_auto_refresh_money_threshold},
|
||
{"showAutoWin", C_BOOL, &g_show_auto_window}, {"autoBuyChosen", C_BOOL, &g_auto_buy_chosen},
|
||
{"autoWinX", C_FLOAT, &g_auto_win_x}, {"autoWinY", C_FLOAT, &g_auto_win_y}, {"autoWinScale", C_FLOAT, &g_auto_win_scale},
|
||
{"showDebug", C_BOOL, &g_show_debug_window}, {"cardWarning", C_BOOL, &g_card_warning}, {"warningThreshold", C_INT, &g_warning_threshold},
|
||
{"poolTier1", C_BOOL, &g_show_pool_tiers[1]}, {"poolTier2", C_BOOL, &g_show_pool_tiers[2]}, {"poolTier3", C_BOOL, &g_show_pool_tiers[3]},
|
||
{"poolTier4", C_BOOL, &g_show_pool_tiers[4]}, {"poolTier5", C_BOOL, &g_show_pool_tiers[5]},
|
||
{"warningTier1", C_BOOL, &g_warning_tiers[1]}, {"warningTier2", C_BOOL, &g_warning_tiers[2]}, {"warningTier3", C_BOOL, &g_warning_tiers[3]},
|
||
{"warningTier4", C_BOOL, &g_warning_tiers[4]}, {"warningTier5", C_BOOL, &g_warning_tiers[5]}, {"warningTier6", C_BOOL, &g_warning_tiers[6]},
|
||
{"instant", C_BOOL, &g_show_instant_quit_window}, {"boardLocked", C_BOOL, &g_boardLocked},
|
||
{"instantWinX", C_FLOAT, &g_instant_win_x}, {"instantWinY", C_FLOAT, &g_instant_win_y}, {"instantWinScale", C_FLOAT, &g_instant_win_scale},
|
||
{"showSellWin", C_BOOL, &g_show_sell_window}, {"sellWinX", C_FLOAT, &g_sell_win_x}, {"sellWinY", C_FLOAT, &g_sell_win_y}, {"sellWinScale", C_FLOAT, &g_sell_win_scale},
|
||
{"menuX", C_FLOAT, &g_menuX}, {"menuY", C_FLOAT, &g_menuY}, {"menuW", C_FLOAT, &g_menuW}, {"menuH", C_FLOAT, &g_menuH},
|
||
{"menuScale", C_FLOAT, &g_scale}, {"menuCollapsed", C_BOOL, &g_menuCollapsed},
|
||
{"startX", C_FLOAT, &g_startX}, {"startY", C_FLOAT, &g_startY}, {"manualScale", C_FLOAT, &g_boardManualScale},
|
||
{"enemyBenchX", C_FLOAT, &g_enemy_bench_X}, {"enemyBenchY", C_FLOAT, &g_enemy_bench_Y}, {"enemyBenchScale", C_FLOAT, &g_enemy_bench_Scale},
|
||
{"enemyX", C_FLOAT, &g_enemy_X}, {"enemyY", C_FLOAT, &g_enemy_Y}, {"enemyScale", C_FLOAT, &g_enemy_Scale},
|
||
{"hexX", C_FLOAT, &g_hex_X}, {"hexY", C_FLOAT, &g_hex_Y}, {"hexScale", C_FLOAT, &g_hex_Scale},
|
||
{"levelX", C_FLOAT, &g_level_X}, {"levelY", C_FLOAT, &g_level_Y}, {"levelScale", C_FLOAT, &g_level_Scale}, {"levelSpacing", C_FLOAT, &g_level_Spacing},
|
||
{"warnX", C_FLOAT, &g_warn_X}, {"warnY", C_FLOAT, &g_warn_Y}, {"warnScale", C_FLOAT, &g_warn_Scale}, {"warnSpacing", C_FLOAT, &g_warn_Spacing},
|
||
{"headerPred", C_BOOL, &g_header_pred}, {"headerEsp", C_BOOL, &g_header_esp},
|
||
{"showHeroListWin", C_BOOL, &g_show_hero_list_window},
|
||
{"autoBuyHeroes", C_STRING, &g_auto_buy_heroes_str},
|
||
{"heroEquipBindings", C_STRING, &g_hero_equip_bindings_str},
|
||
{"heroEquipPriority", C_STRING, &g_hero_equip_priority_str},
|
||
{"showEquipWin", C_BOOL, &g_show_equip_window},
|
||
{"equipWinX", C_FLOAT, &g_equip_win_x},
|
||
{"equipWinY", C_FLOAT, &g_equip_win_y},
|
||
{"equipWinScale", C_FLOAT, &g_equip_win_scale},
|
||
{"drawHeroLines", C_BOOL, &g_draw_hero_lines},
|
||
{"offEquipBase", C_INT, &g_off_equip_base},
|
||
{"offEquipArr", C_INT, &g_off_equip_arr},
|
||
{"offEquipCount", C_INT, &g_off_equip_count},
|
||
{"offEquipHead", C_INT, &g_off_equip_head},
|
||
{"offEquipStep", C_INT, &g_off_equip_step},
|
||
{"offEquipId", C_INT, &g_off_equip_id},
|
||
{"offHeroDyn", C_INT, &g_off_hero_dyn},
|
||
{"offHeroArr", C_INT, &g_off_hero_arr},
|
||
{"offHeroCount", C_INT, &g_off_hero_count},
|
||
{"offHeroHead", C_INT, &g_off_hero_head},
|
||
{"offHeroStep", C_INT, &g_off_hero_step},
|
||
{"offHeroId", C_INT, &g_off_hero_id},
|
||
{"offHeroUnit", C_INT, &g_off_hero_unit},
|
||
{"offUnitBattle", C_INT, &g_off_unit_battle},
|
||
{"offBattlePosx", C_INT, &g_off_battle_posx},
|
||
{"espLineOffsetY", C_FLOAT, &g_esp_line_offset_y},
|
||
|
||
// 格子独立坐标配置保存
|
||
{"cellEquipOffsets", C_STRING, &g_cell_equip_offset_str},
|
||
{"cellSellOffsets", C_STRING, &g_cell_sell_offset_str},
|
||
{"sellDropX", C_FLOAT, &g_sell_drop_x},
|
||
{"sellDropY", C_FLOAT, &g_sell_drop_y},
|
||
{"showClearBenchBtn", C_BOOL, &g_show_clear_bench_btn},
|
||
|
||
{"equipUiStartX", C_FLOAT, &g_equip_ui_start_x},
|
||
{"equipUiStartY", C_FLOAT, &g_equip_ui_start_y},
|
||
{"equipUiStepX", C_FLOAT, &g_equip_ui_step_x},
|
||
{"equipUiStepY", C_FLOAT, &g_equip_ui_step_y},
|
||
{"swipeDelay", C_INT, &g_swipe_step_delay_ms},
|
||
{"swipeSteps", C_INT, &g_swipe_steps},
|
||
{"showSwipeVis", C_BOOL, &g_show_swipe_visuals},
|
||
{"swipeDownDelay", C_INT, &g_swipe_down_delay_ms},
|
||
{"swipeUpDelay", C_INT, &g_swipe_up_delay_ms},
|
||
{"equipExtraCD", C_INT, &g_equip_extra_cooldown_ms},
|
||
{"unequipSlotIdx", C_INT, &g_unequip_slot_idx}
|
||
};
|
||
|
||
typedef void* (*il2cpp_domain_get_t)();
|
||
typedef void* (*il2cpp_thread_attach_t)(void* domain);
|
||
typedef void (*il2cpp_thread_detach_t)(void* thread);
|
||
typedef void* (*il2cpp_string_new_t)(const char* str);
|
||
|
||
il2cpp_domain_get_t g_il2cpp_domain_get = nullptr;
|
||
il2cpp_thread_attach_t g_il2cpp_thread_attach = nullptr;
|
||
il2cpp_thread_detach_t g_il2cpp_thread_detach = nullptr;
|
||
il2cpp_string_new_t g_il2cpp_string_new = nullptr;
|
||
|
||
struct SkinInfo { const char* zh_name; const char* bundle; const char* asset; };
|
||
SkinInfo g_skinList[] = {
|
||
{"锐雯(至臻)", "art_tft_raw/little_legend_res/model/t_rivenswordgold/t_rivenswordgold/low", "t_rivenswordgold_1_show"},
|
||
{"厄斐琉斯(至臻)", "art_tft_raw/little_legend_res/model/t_apheliosfashiongold/t_apheliosfashiongold/low", "t_apheliosfashiongold_1_show"},
|
||
{"佛耶戈(时装)", "art_tft_raw/little_legend_res/model/t_viegofashion/t_viegofashion/low", "t_viegofashion_1_show"},
|
||
{"泽丽(至臻)", "art_tft_raw/little_legend_res/model/t_zerishadowgold/t_zerishadowgold/low", "t_zerishadowgold_1_show"},
|
||
{"阿狸(福祝)", "art_tft_raw/little_legend_res/model/t_ahriblessed/t_ahriblessed/low", "t_ahriblessed_1_show"},
|
||
{"拉克丝(至臻)", "art_tft_raw/little_legend_res/model/t_luxredgold/t_luxredgold/low", "t_luxredgold_1_show"},
|
||
{"莫甘娜(至臻)", "art_tft_raw/little_legend_res/model/t_morganafashiongold/t_morganafashiongold/low", "t_morganafashiongold_1_show"},
|
||
{"塔姆(至臻)", "art_tft_raw/little_legend_res/model/t_tahmfortune/t_tahmfortune_gold/low_low", "t_tahmfortune_gold_1_show"},
|
||
{"辛德拉(美食)", "art_tft_raw/little_legend_res/model/t_syndrafood/t_syndrafood/low", "t_syndrafood_1_show"},
|
||
{"悠娜拉(美食)", "art_tft_raw/little_legend_res/model/t_yunarafood/t_yunarafood/low", "t_yunarafood_1_show"},
|
||
{"女枪(美食)", "art_tft_raw/little_legend_res/model/t_missfood/t_missfood/low", "t_missfood_1_show"},
|
||
{"俄洛伊(美食)", "art_tft_raw/little_legend_res/model/t_illaoifood/t_illaoifood/low", "t_illaoifood_1_show"},
|
||
{"扎克(天煞)", "art_tft_raw/little_legend_res/model/t_zaahenshadow/t_zaahenshadow/low", "t_zaahenshadow_1_show"},
|
||
{"莉莉娅(天煞)", "art_tft_raw/little_legend_res/model/t_lilliashadow/t_lilliashadow/low", "t_lilliashadow_1_show"},
|
||
{"悟空(天煞)", "art_tft_raw/little_legend_res/model/t_wukongshadow/t_wukongshadow/low", "t_wukongshadow_1_show"},
|
||
{"猪妹(福祝)", "art_tft_raw/little_legend_res/model/t_sejuaniblessed/t_sejuaniblessed/low", "t_sejuaniblessed_1_show"},
|
||
{"娜美(福祝)", "art_tft_raw/little_legend_res/model/t_namiblessed/t_namiblessed/low", "t_namiblessed_1_show"},
|
||
{"瑟提(福祝)", "art_tft_raw/little_legend_res/model/t_settblessed/t_settblessed/low", "t_settblessed_1_show"},
|
||
{"悠米(福祝)", "art_tft_raw/little_legend_res/model/t_yuumiblessed/t_yuumiblessed/low", "t_yuumiblessed_1_show"},
|
||
{"妖姬(红)", "art_tft_raw/little_legend_res/model/t_leblancred/t_leblancred/low", "t_leblancred_1_show"},
|
||
{"风女(红)", "art_tft_raw/little_legend_res/model/t_jannared/t_jannared/low", "t_jannared_1_show"},
|
||
{"EZ(红)", "art_tft_raw/little_legend_res/model/t_ezrealred/t_ezrealred/low", "t_ezrealred_1_show"},
|
||
{"莫甘娜(雪月)", "art_tft_raw/little_legend_res/model/t_morganasnowmoon/t_morganasnowmoon/low", "t_morganasnowmoon_1_show"},
|
||
{"贪吃蛇", "art_tft_raw/little_legend_res/model/tweisnake/tweisnake/low_low", "tweisnake_1_show"},
|
||
{"格温(时装)", "art_tft_raw/little_legend_res/model/t_gwenfashion/t_gwenfashion/low", "t_gwenfashion_1_show"},
|
||
{"寡妇(时装)", "art_tft_raw/little_legend_res/model/t_evelynnfashion/t_evelynnfashion/low", "t_evelynnfashion_1_show"},
|
||
{"吸血鬼(女仆)", "art_tft_raw/little_legend_res/model/t_vladimirmaid/t_vladimirmaid/low", "t_vladimirmaid_1_show"},
|
||
{"EZ(水墨)", "art_tft_raw/little_legend_res/model/t_ezrealink/t_ezrealink/low", "t_ezrealink_1_show"},
|
||
{"卡莎(美食)", "art_tft_raw/little_legend_res/model/t_kaisafood/t_kaisafood/low_low", "t_kaisafood_1_show"},
|
||
{"星妈(美食)", "art_tft_raw/little_legend_res/model/t_sorakafood/t_sorakafood/low_low", "t_sorakafood_1_show"},
|
||
{"泽丽(美食)", "art_tft_raw/little_legend_res/model/t_zerifood/t_zerifood/low_low", "t_zerifood_1_show"},
|
||
{"杰斯(双城2)", "art_tft_raw/little_legend_res/model/t_jaycearcanetwo/t_jaycearcanetwo/low_low", "t_jaycearcanetwo_1_show"},
|
||
{"EZ(青花瓷)", "art_tft_raw/little_legend_res/model/t_ezrealporcelain/t_ezrealporcelain/low", "t_ezrealporcelain_1_show"},
|
||
{"萨勒芬妮(凤凰)", "art_tft_raw/little_legend_res/model/t_seraphinephoenix/t_seraphinephoenix/low_low", "t_seraphinephoenix_1_show"},
|
||
{"猪妹(魄罗)", "art_tft_raw/little_legend_res/model/t_sejuanipororider/t_sejuanipororider/low_low", "t_sejuanipororider_1_show"},
|
||
{"悠米(女仆)", "art_tft_raw/little_legend_res/model/t_yuumimaid/t_yuumimaid/low", "t_yuumimaid_1_show"},
|
||
{"莉莉娅(山海)", "art_tft_raw/little_legend_res/model/t_lilliashanhai/t_lilliashanhai/low_low", "t_lilliashanhai_1_show"},
|
||
{"萨勒芬妮(KDA)", "art_tft_raw/little_legend_res/model/t_seraphinekda/t_seraphinekda/low_low", "t_seraphinekda_1_show"},
|
||
{"提莫(灵魂莲华)", "art_tft_raw/little_legend_res/model/t_teemospirit/t_teemospirit/low_low", "t_teemospirit_1_show"},
|
||
{"琴女(电玩)", "art_tft_raw/little_legend_res/model/t_sonaarcade/t_sonaarcade/low_low", "t_sonaarcade_1_show"},
|
||
{"亚索(神龙尊者)", "art_tft_raw/little_legend_res/model/t_yasuotruedragon/t_yasuotruedragon/low_low", "t_yasuotruedragon_1_show"},
|
||
{"刀妹(福星)", "art_tft_raw/little_legend_res/model/t_ireliafuxc/t_ireliafuxc/low", "t_ireliafuxc_1_show"},
|
||
{"希维尔", "art_tft_raw/little_legend_res/model/t_sivir/t_sivir/low", "t_sivir_1_show"},
|
||
{"佐伊", "art_tft_raw/little_legend_res/model/t_zoetft/t_zoetft/low", "t_zoetft_1_show"},
|
||
{"丽桑卓(基础)", "art_tft_raw/little_legend_res/model/t_lissandra/t_lissandra/low", "t_lissandra_1_show"},
|
||
{"亚索(原皮棕)", "art_tft_raw/little_legend_res/model/t_yasuobase/t_yasuobase_brown/low", "t_yasuobase_brown_1_show"},
|
||
{"蔚(双城)", "art_tft_raw/little_legend_res/model/t_viarcane/t_viarcane/low", "t_viarcane_1_show"},
|
||
{"金克丝(双城)", "art_tft_raw/little_legend_res/model/t_jinxarcane/t_jinxarcane/low", "t_jinxarcane_1_show"},
|
||
{"企鹅骑士(春节)", "art_tft_raw/little_legend_res/model/t_qieqishispring/t_qieqishispringorse/low", "t_qieqishispringorse_1_show"},
|
||
{"深渊(战斗)", "art_tft_raw/little_legend_res/model/t_abyssia/t_abyssia_battle/low", "t_abyssia_battle_1_show"},
|
||
{"天马(白星)", "art_tft_raw/little_legend_res/model/t_pegasusbase/t_pegasusbase_whitestar/low", "t_pegasusbase_whitestar_3_show"},
|
||
{"胖胖龙(美味)", "art_tft_raw/little_legend_res/model/t_dowsiedelicious/t_dowsiedelicious/low", "t_dowsiedelicious_3_show"},
|
||
{"刀汪(美味)", "art_tft_raw/little_legend_res/model/t_knifepupdelicious/t_knifepupdelicious/low", "t_knifepupdelicious_3_show"},
|
||
{"面团猫(美味)", "art_tft_raw/little_legend_res/model/t_doughcatdelicious/t_doughcatdelicious/low", "t_doughcatdelicious_1_show"},
|
||
{"胖胖龙(果冻)", "art_tft_raw/little_legend_res/model/t_dowsie/t_dowsie_jelly/low", "t_dowsie_jelly_3_show"},
|
||
{"团子(美味)", "art_tft_raw/little_legend_res/model/t_dangodelicious/t_dangodelicious/low", "t_dangodelicious_3_show"},
|
||
{"黏黏(美味)", "art_tft_raw/little_legend_res/model/t_gloopdelicious/t_gloopdelicious/low", "t_gloopdelicious_3_show"},
|
||
{"幽影(公会)", "art_tft_raw/little_legend_res/model/t_umbra/t_umbra_guild/low", "t_umbra_guild_3_show"},
|
||
{"塔姆鼠(森林)", "art_tft_raw/little_legend_res/model/tamster/tamster_forest/low", "tamster_forest_3_show"},
|
||
{"熬兴", "art_tft_raw/little_legend_res/model/t_aoshin/t_aoshin/low", "t_aoshin_3_show"},
|
||
{"河灵(波霸)", "art_tft_raw/little_legend_res/model/teliujingling/teliujingling_boba/low", "teliujingling_boba_1_show"},
|
||
{"河灵(维京)", "art_tft_raw/little_legend_res/model/teliujingling/teliujingling_viking/low", "teliujingling_viking_1_show"},
|
||
{"宠物石", "art_tft_raw/little_legend_res/model/t_petchoncc/t_petchoncc_stone/low", "t_petchoncc_stone_1_show"},
|
||
{"鼹鼠", "art_tft_raw/little_legend_res/model/t_mole/t_mole/low", "t_mole_1_show"},
|
||
{"河灵(斗篷)", "art_tft_raw/little_legend_res/model/teliujinglingdoupeng/teliujinglingdoupeng/low", "teliujinglingdoupeng_1_show"},
|
||
{"河灵(基础)", "art_tft_raw/little_legend_res/model/teliujingling/teliujingling/low", "teliujingling_1_show"},
|
||
{"永恩(SKT)", "art_tft_raw/little_legend_res/model/t_yoneskt/t_yoneskt/low", "t_yoneskt_1_show"},
|
||
{"刀妹(至臻)", "art_tft_raw/little_legend_res/model/t_ireliasword/t_ireliasword_gold/low_low", "t_ireliasword_gold_1_show"},
|
||
{"团子(饼干)", "art_tft_raw/little_legend_res/model/t_dangocookie/t_dangocookie/low", "t_dangocookie_1_show"},
|
||
{"亚索(原皮白)", "art_tft_raw/little_legend_res/model/t_yasuobase/t_yasuobase_white/low", "t_yasuobase_white_1_show"},
|
||
{"亚索(原皮黄)", "art_tft_raw/little_legend_res/model/t_yasuobase/t_yasuobase_yellow/low", "t_yasuobase_yellow_1_show"}
|
||
};
|
||
|
||
struct MapInfo { const char* zh_name; const char* bundle; const char* asset; };
|
||
MapInfo g_mapList[] = {
|
||
{"兰亭集序(神话)", "art_tft_raw/scenes/prefab/s11_tft_lantingpoems", "s11_tft_lantingpoems"},
|
||
{"选秀中心(测试用)", "art_tft_raw/scenes/prefab/s4_5_tft_battlefield_center", "s4_5_tft_battlefield_center"},
|
||
{"S16 皮影戏", "art_tft_raw/scenes/prefab/s16_tft_shadowplay", "s16_tft_shadowplay"},
|
||
{"S16 发财", "art_tft_raw/scenes/prefab/s16_tft_facai", "s16_tft_facai"},
|
||
{"S16 龙马", "art_tft_raw/scenes/prefab/s16_tft_longma", "s16_tft_longma"},
|
||
{"S16 大观园", "art_tft_raw/scenes/prefab/s16_tft_dgy", "s16_tft_dgy"},
|
||
{"S6 虎年新春", "art_tft_raw/scenes/prefab/s6_tft_newyear", "s6_tft_newyear"},
|
||
{"S16 时装", "art_tft_raw/scenes/prefab/s16_tft_fashion", "s16_tft_fashion"},
|
||
{"S13 饺子馆", "art_tft_raw/scenes/prefab/s13_tft_dumpling", "s13_tft_dumpling"},
|
||
{"S6 艾克实验室", "art_tft_raw/scenes/prefab/s6_tft_ekkolab", "s6_tft_ekkolab"},
|
||
{"S8 街头", "art_tft_raw/scenes/prefab/s8_tft_street", "s8_tft_street"},
|
||
{"S8 赛博朋克", "art_tft_raw/scenes/prefab/s8_tft_cyberpunk", "s8_tft_cyberpunk"},
|
||
{"S7 星之守护者", "art_tft_raw/scenes/prefab/s7_tft_xingshou", "s7_tft_xingshou"},
|
||
{"S17 富贵马", "art_tft_raw/scenes/prefab/s17_tft_richhorse", "s17_tft_richhorse"},
|
||
{"S16 2026迎新", "art_tft_raw/scenes/prefab/s16_tft_2026yx", "s16_tft_2026yx"},
|
||
{"S16 掌剑", "art_tft_raw/scenes/prefab/s16_tft_zhangjian", "s16_tft_zhangjian"},
|
||
{"S13 广州", "art_tft_raw/scenes/prefab/s13_tft_guangzhou", "s13_tft_guangzhou"},
|
||
{"S9 德玛西亚", "art_tft_raw/scenes/prefab/s9_tft_demacia", "s9_tft_demacia"},
|
||
{"S9 艾欧尼亚亚索", "art_tft_raw/scenes/prefab/s9_tft_ioniayasuo", "s9_tft_ioniayasuo"},
|
||
{"S4 凌云", "art_tft_raw/scenes/prefab/s4_tft_lingyun_001", "s4_tft_lingyun_001"}
|
||
};
|
||
|
||
inline bool IsValidPtr(uintptr_t addr) {
|
||
return addr > 0x10000000 && addr < 0x00007FFFFFFFFFFF && (addr % 4 == 0);
|
||
}
|
||
|
||
thread_local int g_safe_pipe[2] = {-1, -1};
|
||
|
||
bool SafeReadMemory(uintptr_t addr, void* buffer, size_t size) {
|
||
if (addr < 0x10000000 || addr > 0x00007FFFFFFFFFFF) return false;
|
||
|
||
if (g_safe_pipe[0] == -1) {
|
||
if (pipe2(g_safe_pipe, O_NONBLOCK) != 0) {
|
||
if (IsValidPtr(addr)) { memcpy(buffer, (void*)addr, size); return true; }
|
||
return false;
|
||
}
|
||
}
|
||
ssize_t res = write(g_safe_pipe[1], (void*)addr, size);
|
||
if (res == (ssize_t)size) {
|
||
read(g_safe_pipe[0], buffer, size); return true;
|
||
} else if (res > 0) {
|
||
char temp[256]; read(g_safe_pipe[0], temp, res);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
std::deque<std::string> g_appLogs;
|
||
std::mutex g_logMutex;
|
||
|
||
void AddLog(const char* fmt, ...) {
|
||
char buf[1024];
|
||
va_list args;
|
||
va_start(args, fmt);
|
||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||
va_end(args);
|
||
std::string logStr(buf);
|
||
|
||
std::lock_guard<std::mutex> lock(g_logMutex);
|
||
static std::unordered_set<std::string> seenLogs;
|
||
if (seenLogs.find(logStr) != seenLogs.end()) return;
|
||
seenLogs.insert(logStr);
|
||
|
||
time_t now = time(nullptr);
|
||
struct tm tstruct = *localtime(&now);
|
||
char timebuf[80];
|
||
strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tstruct);
|
||
|
||
g_appLogs.push_back(std::string("[") + timebuf + "] " + logStr);
|
||
if (g_appLogs.size() > 100) g_appLogs.pop_front();
|
||
}
|
||
|
||
#define SAFE_READ(type, base, offset, defaultVal) \
|
||
([&]() -> type { \
|
||
type val = defaultVal; \
|
||
uintptr_t targetAddr = (uintptr_t)(base) + (offset); \
|
||
if (!SafeReadMemory(targetAddr, &val, sizeof(type))) val = defaultVal; \
|
||
return val; \
|
||
}())
|
||
#define SAFE_READ_PTR(base, offset) SAFE_READ(uintptr_t, base, offset, 0)
|
||
|
||
std::string Utf16ToUtf8(const char16_t* u16str, size_t len) {
|
||
std::string u8str;
|
||
u8str.reserve(len);
|
||
for (size_t i = 0; i < len; ++i) {
|
||
char16_t wc = u16str[i];
|
||
if (wc < 0x80) u8str += (char)wc;
|
||
else if (wc < 0x800) { u8str += (char)(0xC0 | (wc >> 6)); u8str += (char)(0x80 | (wc & 0x3F)); }
|
||
else { u8str += (char)(0xE0 | (wc >> 12)); u8str += (char)(0x80 | ((wc >> 6) & 0x3F)); u8str += (char)(0x80 | (wc & 0x3F)); }
|
||
}
|
||
return u8str;
|
||
}
|
||
|
||
std::string ReadIl2cppStr(void* strObj) {
|
||
if (!strObj || !IsValidPtr((uintptr_t)strObj)) return "";
|
||
int len = SAFE_READ(int, strObj, 0x10, 0);
|
||
if (len > 0 && len < 500) {
|
||
char16_t buffer[500] = {0};
|
||
if (SafeReadMemory((uintptr_t)strObj + 0x14, buffer, len * sizeof(char16_t))) {
|
||
return Utf16ToUtf8(buffer, len);
|
||
}
|
||
}
|
||
return "";
|
||
}
|
||
|
||
typedef void* (*func_LoadAsset_t)(void* x0, void* x1, void* x2, void* x3, int x4);
|
||
func_LoadAsset_t orig_LoadAsset_718 = nullptr;
|
||
void* hook_LoadAsset_718(void* x0, void* x1, void* x2, void* x3, int x4) {
|
||
if (x1 && x2) {
|
||
std::string bundleStr = ReadIl2cppStr(x1);
|
||
std::string assetStr = ReadIl2cppStr(x2);
|
||
|
||
if (g_collect_skins && !bundleStr.empty()) {
|
||
std::lock_guard<std::mutex> lock(g_collect_mutex);
|
||
std::string logStr = bundleStr + " | " + assetStr;
|
||
if (std::find(g_collected_skins.begin(), g_collected_skins.end(), logStr) == g_collected_skins.end()) {
|
||
g_collected_skins.push_back(logStr);
|
||
}
|
||
}
|
||
|
||
if (g_enable_skin && g_il2cpp_string_new) {
|
||
void* new_bundle = g_il2cpp_string_new(g_skinList[g_selected_skin_idx].bundle);
|
||
void* new_asset = g_il2cpp_string_new(g_skinList[g_selected_skin_idx].asset);
|
||
|
||
if (new_bundle && new_asset) {
|
||
return orig_LoadAsset_718(x0, new_bundle, new_asset, x3, x4);
|
||
}
|
||
}
|
||
}
|
||
return orig_LoadAsset_718(x0, x1, x2, x3, x4);
|
||
}
|
||
|
||
typedef void* (*func_LoadMapImpl_t)(void* x0, void* bundlePath, void* assetName, void* container, uint64_t isBackground, uint64_t isMineMap);
|
||
func_LoadMapImpl_t orig_LoadMapImpl = nullptr;
|
||
void* hook_LoadMapImpl(void* x0, void* bundlePath, void* assetName, void* container, uint64_t isBackground, uint64_t isMineMap) {
|
||
if (bundlePath && assetName) {
|
||
std::string bundleStr = ReadIl2cppStr(bundlePath);
|
||
std::string assetStr = ReadIl2cppStr(assetName);
|
||
|
||
if (g_collect_maps && !bundleStr.empty()) {
|
||
std::lock_guard<std::mutex> lock(g_collect_mutex);
|
||
std::string logStr = bundleStr + " | " + assetStr;
|
||
if (std::find(g_collected_maps.begin(), g_collected_maps.end(), logStr) == g_collected_maps.end()) {
|
||
g_collected_maps.push_back(logStr);
|
||
}
|
||
}
|
||
|
||
if (g_enable_map_skin && g_il2cpp_string_new) {
|
||
if (isMineMap != 0) {
|
||
void* new_bundle = g_il2cpp_string_new(g_mapList[g_selected_map_idx].bundle);
|
||
void* new_asset = g_il2cpp_string_new(g_mapList[g_selected_map_idx].asset);
|
||
|
||
if (new_bundle && new_asset) {
|
||
return orig_LoadMapImpl(x0, new_bundle, new_asset, container, isBackground, isMineMap);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return orig_LoadMapImpl(x0, bundlePath, assetName, container, isBackground, isMineMap);
|
||
}
|
||
|
||
template <typename T, size_t Size>
|
||
class RingBuffer {
|
||
T buffer[Size];
|
||
std::atomic<size_t> head{0};
|
||
std::atomic<size_t> tail{0};
|
||
public:
|
||
bool push(const T& item) {
|
||
size_t current_tail = tail.load(std::memory_order_relaxed);
|
||
size_t next_tail = (current_tail + 1) % Size;
|
||
if (next_tail != head.load(std::memory_order_acquire)) {
|
||
buffer[current_tail] = item;
|
||
tail.store(next_tail, std::memory_order_release);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
bool pop(T& item) {
|
||
size_t current_head = head.load(std::memory_order_relaxed);
|
||
if (current_head == tail.load(std::memory_order_acquire)) return false;
|
||
item = buffer[current_head];
|
||
head.store((current_head + 1) % Size, std::memory_order_release);
|
||
return true;
|
||
}
|
||
void clear() {
|
||
head.store(0, std::memory_order_relaxed);
|
||
tail.store(0, std::memory_order_relaxed);
|
||
}
|
||
};
|
||
|
||
struct PlayerRowInfo { uintptr_t obj; int rowIdx; };
|
||
struct PlayerStatInfo { uintptr_t obj; int val; };
|
||
|
||
struct MainThreadTasks {
|
||
std::atomic<bool> trigger_surrender{false};
|
||
std::atomic<bool> trigger_game_end{false};
|
||
std::atomic<uintptr_t> pending_hex_cfg_obj{0};
|
||
uintptr_t processed_hex_cfg_obj = 0;
|
||
std::atomic<uintptr_t> pending_turn_start_obj{0};
|
||
|
||
RingBuffer<uintptr_t, 128> update_turn_start_queue;
|
||
RingBuffer<PlayerRowInfo, 128> player_row_queue;
|
||
RingBuffer<PlayerStatInfo, 256> money_queue;
|
||
RingBuffer<PlayerStatInfo, 256> level_queue;
|
||
} g_Tasks;
|
||
|
||
uintptr_t g_shop_slot_instances[5] = {0};
|
||
int g_shop_slot_index = 0;
|
||
typedef void (*func_reqbuyhero_t)(void* x0, void* x1, void* x2, void* x3);
|
||
func_reqbuyhero_t g_reqbuyhero = nullptr;
|
||
|
||
uintptr_t g_refresh_instance = 0;
|
||
typedef void (*func_req_refresh_t)(void* x0);
|
||
func_req_refresh_t g_req_refresh = nullptr;
|
||
|
||
struct Vector3 { float x, y, z; };
|
||
|
||
// 增加 cellIndex 来记录是哪个格子(0-27场上, 28-36备战席)
|
||
struct LiveHeroData { int heroId; Vector3 worldPos; Vector3 screenPos; int equipCount; int cellIndex; };
|
||
|
||
struct DebugHeroTrace {
|
||
int index;
|
||
uintptr_t heroObj;
|
||
int heroId;
|
||
uintptr_t unitData;
|
||
uintptr_t battleUnit;
|
||
Vector3 worldPos;
|
||
Vector3 screenPos;
|
||
};
|
||
|
||
struct PlayerInfo {
|
||
uintptr_t objAddr = 0, midAddr = 0, strPtr = 0, dynamicAddr = 0, logicObjAddr = 0, heroArrayAddr = 0, csoGameAddr = 0;
|
||
int playerId = 0, rowIdx = 0, nameLen = 0, money = 0, level = 0;
|
||
int winStreak = 0, loseStreak = 0;
|
||
std::string name = "";
|
||
bool isAi = false;
|
||
};
|
||
struct EnemyHeroPos { int heroId; int x; int y; int equipCount; };
|
||
struct HeroSlotInfo { int heroId; int baseHeroId; int slotIndex; int cost; int starLevel; };
|
||
struct CardInfo { int heroId; int remaining; int total; int cost; };
|
||
struct PlayerWarningInfo { int rowIdx; std::vector<std::pair<int, int>> heroCounts; };
|
||
|
||
struct GameSnapshot {
|
||
std::unordered_map<int, PlayerInfo> players;
|
||
std::unordered_map<uintptr_t, int> objToPlayerId;
|
||
std::vector<EnemyHeroPos> currentEnemyHeroes;
|
||
std::vector<HeroSlotInfo> myShopHeroes, myBenchHeroes, enemyShopHeroes, enemyBenchHeroes;
|
||
std::unordered_map<int, CardInfo> cardPoolData;
|
||
std::vector<PlayerWarningInfo> playerWarnings;
|
||
|
||
uintptr_t chessBattleModel = 0;
|
||
uintptr_t csoGame = 0;
|
||
int season = 0;
|
||
int myPlayerId = -1;
|
||
int nextEnemyPlayerId = -1;
|
||
|
||
int cachedHexes[4] = {0, 0, 0, 0};
|
||
int validHexCount = 0;
|
||
bool hex_fetched = false;
|
||
|
||
uintptr_t mainCamera = 0;
|
||
std::vector<int> myItems;
|
||
std::vector<LiveHeroData> myLiveHeroes;
|
||
std::vector<LiveHeroData> myBenchLiveHeroes;
|
||
|
||
std::vector<DebugHeroTrace> debugHeroTraces;
|
||
};
|
||
|
||
GameSnapshot g_BackendData;
|
||
GameSnapshot g_Snapshots[2];
|
||
std::atomic<int> g_WriteIdx{0};
|
||
std::atomic<int> g_ReadIdx{0};
|
||
|
||
std::atomic<bool> g_is_in_match{false};
|
||
|
||
void PushSnapshot() {
|
||
int write_idx = g_WriteIdx.load(std::memory_order_relaxed);
|
||
int next_write = 1 - write_idx;
|
||
g_Snapshots[write_idx] = g_BackendData;
|
||
g_ReadIdx.store(write_idx, std::memory_order_release);
|
||
g_WriteIdx.store(next_write, std::memory_order_relaxed);
|
||
}
|
||
|
||
const GameSnapshot* GetSnapshot() {
|
||
int read_idx = g_ReadIdx.load(std::memory_order_acquire);
|
||
return &g_Snapshots[read_idx];
|
||
}
|
||
|
||
uintptr_t g_il2cppBase = 0, g_debug_mgrObj = 0;
|
||
void* g_turnStartThisObj = nullptr;
|
||
void* g_hexCfgObj = nullptr;
|
||
|
||
int g_debug_TurnStartCalled = 0, g_debug_hook_3Cd6058_count = 0, g_debug_hook_set_Money_count = 0, g_debug_hook_set_Level_count = 0;
|
||
int g_debug_UpdateTurnStart_count = 0, g_debug_set_IsGameEnd_count = 0, g_debug_SendWillRenderCanvases_count = 0;
|
||
int g_debug_collected_players_count = 0, g_debug_hook_InitTurnDropCfg_count = 0, g_debug_bg_thread_count = 0;
|
||
int g_debug_hook_6A67e48_count = 0;
|
||
|
||
std::atomic<bool> g_collected_all_players{false};
|
||
std::atomic<bool> g_matchFetchRequested{false}, g_matchFetchReady{false};
|
||
std::chrono::steady_clock::time_point g_matchFetchTime;
|
||
std::atomic<uintptr_t> g_pool_entries1{0};
|
||
|
||
// =================================================================
|
||
// 极速数学解析引擎
|
||
// =================================================================
|
||
inline void FastParseHeroId(int rawHeroId, int& baseHeroId, int& cost, int& starLevel) {
|
||
if (rawHeroId < 10) { baseHeroId = rawHeroId; cost = 1; starLevel = 1; return; }
|
||
if (rawHeroId >= 10000) { starLevel = rawHeroId / 10000; cost = (rawHeroId / 1000) % 10; baseHeroId = rawHeroId - (starLevel * 10000) + 10000; }
|
||
else if (rawHeroId >= 1000) { starLevel = rawHeroId / 1000; cost = (rawHeroId / 100) % 10; baseHeroId = rawHeroId - (starLevel * 1000) + 1000; }
|
||
else if (rawHeroId >= 100) { starLevel = rawHeroId / 100; cost = (rawHeroId / 10) % 10; baseHeroId = rawHeroId - (starLevel * 100) + 100; }
|
||
else { starLevel = rawHeroId / 10; cost = rawHeroId % 10; baseHeroId = rawHeroId - (starLevel * 10) + 10; }
|
||
}
|
||
|
||
inline int GetCostFromHeroId(int heroId) {
|
||
if (heroId < 10) return 1;
|
||
int cost = 1;
|
||
if (heroId >= 10000) cost = (heroId / 1000) % 10;
|
||
else if (heroId >= 1000) cost = (heroId / 100) % 10;
|
||
else if (heroId >= 100) cost = (heroId / 10) % 10;
|
||
else cost = heroId % 10;
|
||
return (cost >= 1 && cost <= 5) ? cost : 1;
|
||
}
|
||
|
||
void BuildLocalPokedex() {
|
||
if (g_pokedex_ready.load()) return;
|
||
|
||
std::thread([]() {
|
||
std::map<int, std::vector<int>> temp_pokedex;
|
||
for (int i = 1; i <= 60000; i++) {
|
||
int len = 0;
|
||
const unsigned char* ptr = GetHeroImageBytes(i, &len);
|
||
if (ptr != nullptr && len > 0) {
|
||
int cost = GetCostFromHeroId(i);
|
||
if (cost >= 1 && cost <= 5) { temp_pokedex[cost].push_back(i); }
|
||
}
|
||
}
|
||
g_local_pokedex = std::move(temp_pokedex);
|
||
|
||
std::vector<int> temp_equip_pokedex;
|
||
temp_equip_pokedex.reserve(256);
|
||
for (int i = 1; i <= 20000; i++) {
|
||
int len = 0;
|
||
const unsigned char* ptr = GetEquipImageBytes(i, &len);
|
||
if (ptr != nullptr && len > 0) {
|
||
temp_equip_pokedex.push_back(i);
|
||
}
|
||
}
|
||
g_local_equip_pokedex = std::move(temp_equip_pokedex);
|
||
g_equip_pokedex_ready.store(true);
|
||
|
||
g_pokedex_ready.store(true);
|
||
}).detach();
|
||
}
|
||
|
||
// =================================================================
|
||
// 异步纹理加载队列
|
||
// =================================================================
|
||
struct TexDecodedData { int w, h; unsigned char* pixels; };
|
||
std::mutex g_TexMutex;
|
||
|
||
std::unordered_map<int, GLuint> g_heroTextureCache;
|
||
std::vector<std::pair<int, TexDecodedData>> g_HeroTexDecodedQueue;
|
||
|
||
std::unordered_map<int, GLuint> g_equipTextureCache;
|
||
std::vector<std::pair<int, TexDecodedData>> g_EquipTexDecodedQueue;
|
||
|
||
struct DecodeRequest { int id; bool isEquip; };
|
||
std::deque<DecodeRequest> g_DecodeRequestQueue;
|
||
std::mutex g_DecodeRequestMutex;
|
||
|
||
void ClearAllTextureCaches() {
|
||
std::lock_guard<std::mutex> lock(g_TexMutex);
|
||
for (auto& pair : g_heroTextureCache) { if (pair.second > 0) glDeleteTextures(1, &pair.second); }
|
||
g_heroTextureCache.clear();
|
||
for (auto& item : g_HeroTexDecodedQueue) { if (item.second.pixels) stbi_image_free(item.second.pixels); }
|
||
g_HeroTexDecodedQueue.clear();
|
||
|
||
for (auto& pair : g_equipTextureCache) { if (pair.second > 0) glDeleteTextures(1, &pair.second); }
|
||
g_equipTextureCache.clear();
|
||
for (auto& item : g_EquipTexDecodedQueue) { if (item.second.pixels) stbi_image_free(item.second.pixels); }
|
||
g_EquipTexDecodedQueue.clear();
|
||
}
|
||
|
||
void TextureDecodingWorkerThread() {
|
||
while (true) {
|
||
DecodeRequest req;
|
||
bool hasReq = false;
|
||
|
||
{
|
||
std::lock_guard<std::mutex> lock(g_DecodeRequestMutex);
|
||
if (!g_DecodeRequestQueue.empty()) {
|
||
req = g_DecodeRequestQueue.front();
|
||
g_DecodeRequestQueue.pop_front();
|
||
hasReq = true;
|
||
}
|
||
}
|
||
|
||
if (hasReq) {
|
||
int imgLen = 0;
|
||
const unsigned char* imgData = req.isEquip ? GetEquipImageBytes(req.id, &imgLen) : GetHeroImageBytes(req.id, &imgLen);
|
||
|
||
if (imgData != nullptr && imgLen > 0) {
|
||
int w, h, channels;
|
||
unsigned char* data = stbi_load_from_memory(imgData, imgLen, &w, &h, &channels, 4);
|
||
if (data && w > 0 && h > 0 && w <= 2048 && h <= 2048) {
|
||
std::lock_guard<std::mutex> lock(g_TexMutex);
|
||
if (req.isEquip) g_EquipTexDecodedQueue.push_back({req.id, {w, h, data}});
|
||
else g_HeroTexDecodedQueue.push_back({req.id, {w, h, data}});
|
||
} else if (data) {
|
||
stbi_image_free(data);
|
||
}
|
||
}
|
||
} else {
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||
}
|
||
}
|
||
}
|
||
|
||
GLuint GetHeroTexture(int heroId) {
|
||
auto it = g_heroTextureCache.find(heroId);
|
||
if (it != g_heroTextureCache.end()) return it->second;
|
||
|
||
g_heroTextureCache[heroId] = 0;
|
||
std::lock_guard<std::mutex> lock(g_DecodeRequestMutex);
|
||
g_DecodeRequestQueue.push_back({heroId, false});
|
||
|
||
return 0;
|
||
}
|
||
|
||
GLuint GetEquipTexture(int equipId) {
|
||
auto it = g_equipTextureCache.find(equipId);
|
||
if (it != g_equipTextureCache.end()) return it->second;
|
||
|
||
g_equipTextureCache[equipId] = 0;
|
||
std::lock_guard<std::mutex> lock(g_DecodeRequestMutex);
|
||
g_DecodeRequestQueue.push_back({equipId, true});
|
||
|
||
return 0;
|
||
}
|
||
|
||
void ProcessTextureQueue() {
|
||
std::lock_guard<std::mutex> lock(g_TexMutex);
|
||
if (g_HeroTexDecodedQueue.empty() && g_EquipTexDecodedQueue.empty()) return;
|
||
|
||
GLint last_unpack; glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack);
|
||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||
|
||
for (auto& item : g_HeroTexDecodedQueue) {
|
||
GLuint tex = 0;
|
||
glGenTextures(1, &tex);
|
||
if (tex != 0) {
|
||
glBindTexture(GL_TEXTURE_2D, tex);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item.second.w, item.second.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.second.pixels);
|
||
}
|
||
stbi_image_free(item.second.pixels);
|
||
g_heroTextureCache[item.first] = tex;
|
||
}
|
||
g_HeroTexDecodedQueue.clear();
|
||
|
||
for (auto& item : g_EquipTexDecodedQueue) {
|
||
GLuint tex = 0;
|
||
glGenTextures(1, &tex);
|
||
if (tex != 0) {
|
||
glBindTexture(GL_TEXTURE_2D, tex);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item.second.w, item.second.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.second.pixels);
|
||
}
|
||
stbi_image_free(item.second.pixels);
|
||
g_equipTextureCache[item.first] = tex;
|
||
}
|
||
g_EquipTexDecodedQueue.clear();
|
||
|
||
glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack);
|
||
}
|
||
|
||
inline void DrawTextWithStroke(ImDrawList* d, ImFont* font, float fontSize, ImVec2 pos, ImU32 textColor, ImU32 strokeColor, const char* text) {
|
||
d->AddText(font, fontSize, pos + ImVec2(1.5f, 1.5f), strokeColor, text);
|
||
d->AddText(font, fontSize, pos, textColor, text);
|
||
}
|
||
|
||
// =================================================================
|
||
// 牌池全自动极速采集引擎
|
||
// =================================================================
|
||
typedef void* (*func_6A67e48_t)(void* x0, void* x1, void* x2, void* x3);
|
||
func_6A67e48_t orig_6A67e48 = nullptr;
|
||
|
||
void* hook_6A67e48(void* x0, void* x1, void* x2, void* x3) {
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_6A67e48(x0, x1, x2, x3);
|
||
if (g_pool_entries1.load(std::memory_order_relaxed) != 0) {
|
||
return orig_6A67e48(x0, x1, x2, x3);
|
||
}
|
||
g_debug_hook_6A67e48_count++;
|
||
|
||
if (x0 != nullptr) {
|
||
uintptr_t base = (uintptr_t)x0;
|
||
uintptr_t heroku = SAFE_READ_PTR(base, g_off_pool_heroku);
|
||
if (IsValidPtr(heroku)) {
|
||
uintptr_t aheroku = SAFE_READ_PTR(heroku, g_off_pool_Aheroku);
|
||
if (IsValidPtr(aheroku)) {
|
||
uintptr_t entries1 = SAFE_READ_PTR(aheroku, g_off_pool_entries1);
|
||
if (IsValidPtr(entries1)) g_pool_entries1.store(entries1, std::memory_order_release);
|
||
}
|
||
}
|
||
}
|
||
return orig_6A67e48(x0, x1, x2, x3);
|
||
}
|
||
|
||
void UpdateCardPoolData() {
|
||
uintptr_t pEntries1 = g_pool_entries1.load(std::memory_order_acquire);
|
||
if (pEntries1 == 0 || !IsValidPtr(pEntries1)) return;
|
||
|
||
static auto last_pool_update = std::chrono::steady_clock::now();
|
||
auto now = std::chrono::steady_clock::now();
|
||
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_pool_update).count() < 100) return;
|
||
last_pool_update = now;
|
||
|
||
int count1 = SAFE_READ(int, pEntries1, g_off_pool_e1_count, 0);
|
||
int maxIter1 = std::clamp(count1, 0, 150);
|
||
|
||
for (int i = 0; i < maxIter1; i++) {
|
||
uintptr_t entry1_ptr = pEntries1 + g_off_pool_e1_head + i * g_off_pool_e1_step;
|
||
uintptr_t value1 = SAFE_READ_PTR(entry1_ptr, g_off_pool_e1_val);
|
||
if (!IsValidPtr(value1)) continue;
|
||
|
||
uintptr_t entries2 = SAFE_READ_PTR(value1, g_off_pool_entries2);
|
||
if (!IsValidPtr(entries2)) continue;
|
||
|
||
int count2 = SAFE_READ(int, entries2, g_off_pool_e2_count, 0);
|
||
int maxIter2 = std::clamp(count2, 0, 100);
|
||
|
||
for (int j = 0; j < maxIter2; j++) {
|
||
uintptr_t entry2_ptr = entries2 + g_off_pool_e2_head + j * g_off_pool_e2_step;
|
||
uintptr_t value2 = SAFE_READ_PTR(entry2_ptr, g_off_pool_e2_val);
|
||
if (!IsValidPtr(value2)) continue;
|
||
|
||
int heroId = SAFE_READ(int, value2, g_off_pool_v2_id, 0);
|
||
if (heroId > 0) {
|
||
int rem = SAFE_READ(int, value2, g_off_pool_v2_rem, 0);
|
||
int tot = SAFE_READ(int, value2, g_off_pool_v2_tot, 0);
|
||
int cost = GetCostFromHeroId(heroId);
|
||
g_BackendData.cardPoolData[heroId] = { heroId, rem, tot, cost };
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// =================================================================
|
||
// 解析引擎核心 (后台线程无锁调用)
|
||
// =================================================================
|
||
auto getEntriesDynamically = [](uintptr_t objAddr, int baseOff) -> uintptr_t {
|
||
if (!objAddr || !IsValidPtr(objAddr)) return 0;
|
||
uintptr_t pBase = SAFE_READ_PTR(objAddr, baseOff);
|
||
if (!pBase) return 0;
|
||
uintptr_t p20 = SAFE_READ_PTR(pBase, g_off_shop_p20);
|
||
if (!p20) return 0;
|
||
return SAFE_READ_PTR(p20, g_off_shop_entries);
|
||
};
|
||
|
||
void EnsureChessBattleModel() {
|
||
if (g_il2cppBase == 0) return;
|
||
typedef void* (*func_58543a8_t)();
|
||
func_58543a8_t getMgr = (func_58543a8_t)(g_il2cppBase + 0x58543a8);
|
||
void* mgrObj = getMgr();
|
||
|
||
if (mgrObj && IsValidPtr((uintptr_t)mgrObj)) {
|
||
g_debug_mgrObj = (uintptr_t)mgrObj;
|
||
uintptr_t current_model = SAFE_READ_PTR(mgrObj, 0x10);
|
||
if (IsValidPtr(current_model)) {
|
||
typedef int (*func_3fac734_t)(void* battleModel);
|
||
func_3fac734_t getMyId = (func_3fac734_t)(g_il2cppBase + 0x3fac734);
|
||
int myId = getMyId ? getMyId((void*)current_model) : -1;
|
||
|
||
bool isNewModel = (g_BackendData.chessBattleModel != current_model) || (g_BackendData.myPlayerId != myId);
|
||
|
||
if (isNewModel && myId != -1) {
|
||
int season = SAFE_READ(int, current_model, 0x140, 0);
|
||
|
||
g_BackendData = GameSnapshot();
|
||
g_BackendData.chessBattleModel = current_model;
|
||
g_BackendData.myPlayerId = myId;
|
||
g_BackendData.season = season;
|
||
g_BackendData.nextEnemyPlayerId = -1;
|
||
|
||
g_collected_all_players.store(false, std::memory_order_relaxed);
|
||
g_debug_collected_players_count = 0;
|
||
|
||
g_hexCfgObj = nullptr;
|
||
g_Tasks.processed_hex_cfg_obj = 0;
|
||
g_Tasks.pending_hex_cfg_obj.store(0);
|
||
g_pool_entries1.store(0, std::memory_order_release);
|
||
|
||
g_Tasks.player_row_queue.clear();
|
||
g_Tasks.money_queue.clear();
|
||
g_Tasks.level_queue.clear();
|
||
g_Tasks.update_turn_start_queue.clear();
|
||
g_shop_slot_index = 0;
|
||
for (int i = 0; i < 5; i++) g_shop_slot_instances[i] = 0;
|
||
|
||
{
|
||
std::lock_guard<std::mutex> lock(g_swipe_mutex);
|
||
g_active_swipes.clear();
|
||
}
|
||
|
||
g_is_in_match.store(true, std::memory_order_release);
|
||
|
||
PushSnapshot();
|
||
PushSnapshot();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void CheckReadyMatchFetch() {
|
||
uintptr_t modelToFetch = g_BackendData.chessBattleModel;
|
||
int myIdToFetch = g_BackendData.myPlayerId;
|
||
|
||
if (modelToFetch != 0 && myIdToFetch != -1 && IsValidPtr(modelToFetch)) {
|
||
typedef int (*func_GetMatchPlayerId_t)(void* battleModel, int myPlayerId);
|
||
func_GetMatchPlayerId_t getMatch = (func_GetMatchPlayerId_t)(g_il2cppBase + 0x3fd7a80);
|
||
if (getMatch) {
|
||
try {
|
||
int nextEnemy = getMatch((void*)modelToFetch, myIdToFetch);
|
||
if (nextEnemy != -1) {
|
||
g_BackendData.nextEnemyPlayerId = nextEnemy;
|
||
}
|
||
} catch (...) {}
|
||
}
|
||
}
|
||
}
|
||
|
||
void UpdateShopAndBenchData() {
|
||
uintptr_t myShop = 0, myBench = 0, enemyShop = 0, enemyBench = 0;
|
||
uintptr_t myLogicObj = 0, myHeroArray = 0;
|
||
|
||
auto myIt = g_BackendData.players.find(g_BackendData.myPlayerId);
|
||
if (g_BackendData.myPlayerId != -1 && myIt != g_BackendData.players.end()) {
|
||
uintptr_t myObj = myIt->second.objAddr;
|
||
myShop = getEntriesDynamically(myObj, g_off_shop_base);
|
||
myBench = getEntriesDynamically(myObj, g_off_bench_base);
|
||
|
||
myLogicObj = myIt->second.logicObjAddr;
|
||
myHeroArray = myIt->second.heroArrayAddr;
|
||
}
|
||
|
||
int enemyId = g_BackendData.nextEnemyPlayerId;
|
||
auto enemyIt = g_BackendData.players.find(enemyId);
|
||
if (enemyId != -1 && enemyIt != g_BackendData.players.end()) {
|
||
uintptr_t enemyObj = enemyIt->second.objAddr;
|
||
enemyShop = getEntriesDynamically(enemyObj, g_off_shop_base);
|
||
enemyBench = getEntriesDynamically(enemyObj, g_off_bench_base);
|
||
}
|
||
|
||
if (!IsValidPtr(myHeroArray) && IsValidPtr(myLogicObj)) {
|
||
uintptr_t dyn2b0 = SAFE_READ_PTR(myLogicObj, 0x2b0);
|
||
if (IsValidPtr(dyn2b0)) {
|
||
myHeroArray = SAFE_READ_PTR(dyn2b0, 0x20);
|
||
if (IsValidPtr(myHeroArray)) g_BackendData.players[g_BackendData.myPlayerId].heroArrayAddr = myHeroArray;
|
||
}
|
||
}
|
||
|
||
auto parseEntries = [](uintptr_t entriesAddr, int maxSlots, std::vector<HeroSlotInfo>& outVec) {
|
||
outVec.clear();
|
||
if (!IsValidPtr(entriesAddr)) return;
|
||
|
||
int count = SAFE_READ(int, entriesAddr, g_off_entries_count, 0);
|
||
int maxIter = std::clamp(count, 0, 50);
|
||
outVec.reserve(maxIter);
|
||
|
||
for (int i = 0; i < maxIter; i++) {
|
||
uintptr_t entryStart = entriesAddr + g_off_entries_head + i * g_off_entries_step;
|
||
uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value);
|
||
if (!value) continue;
|
||
|
||
int slotIndex = SAFE_READ(int, value, g_off_value_slot, -1);
|
||
if (slotIndex >= 0 && slotIndex < maxSlots) {
|
||
uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10);
|
||
if (p10) {
|
||
int heroId = SAFE_READ(int, p10, g_off_p10_heroId, 0);
|
||
if (heroId > 0) {
|
||
int starLevel, cost, baseHeroId;
|
||
FastParseHeroId(heroId, baseHeroId, cost, starLevel);
|
||
outVec.push_back({heroId, baseHeroId, slotIndex, cost, starLevel});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
parseEntries(myShop, 5, g_BackendData.myShopHeroes);
|
||
parseEntries(myBench, 9, g_BackendData.myBenchHeroes);
|
||
parseEntries(enemyShop, 5, g_BackendData.enemyShopHeroes);
|
||
parseEntries(enemyBench, 9, g_BackendData.enemyBenchHeroes);
|
||
}
|
||
|
||
void UpdateEnemyBoardState() {
|
||
uintptr_t heroArray = 0;
|
||
uintptr_t logicObj = 0;
|
||
|
||
auto it = g_BackendData.players.find(g_BackendData.nextEnemyPlayerId);
|
||
if (g_BackendData.nextEnemyPlayerId != -1 && it != g_BackendData.players.end()) {
|
||
logicObj = it->second.logicObjAddr;
|
||
}
|
||
|
||
if (IsValidPtr(logicObj)) {
|
||
uintptr_t dynAddr2b0 = SAFE_READ_PTR(logicObj, 0x2b0);
|
||
if (IsValidPtr(dynAddr2b0)) {
|
||
uintptr_t newArray = SAFE_READ_PTR(dynAddr2b0, 0x20);
|
||
if (IsValidPtr(newArray)) {
|
||
heroArray = newArray;
|
||
if (it != g_BackendData.players.end()) {
|
||
it->second.heroArrayAddr = heroArray;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!IsValidPtr(heroArray) && it != g_BackendData.players.end()) {
|
||
heroArray = it->second.heroArrayAddr;
|
||
}
|
||
|
||
if (!IsValidPtr(heroArray)) return;
|
||
|
||
int heroCount = SAFE_READ(int, heroArray, g_off_hero_count, 0);
|
||
if (heroCount <= 0 || heroCount > 200) {
|
||
auto iter = g_BackendData.players.find(g_BackendData.nextEnemyPlayerId);
|
||
if (iter != g_BackendData.players.end()) iter->second.heroArrayAddr = 0;
|
||
return;
|
||
}
|
||
|
||
g_BackendData.currentEnemyHeroes.clear();
|
||
g_BackendData.currentEnemyHeroes.reserve(28);
|
||
|
||
for (int x = 0; x < 7; ++x) {
|
||
for (int y = 0; y < 4; ++y) {
|
||
uintptr_t heroObj = SAFE_READ_PTR(heroArray, 0x20 + (x * 8 + y) * 8);
|
||
if (IsValidPtr(heroObj)) {
|
||
int heroId = SAFE_READ(int, heroObj, 0x108, 0);
|
||
if (heroId > 0) {
|
||
int equipCount = 0;
|
||
uintptr_t equipAddr = SAFE_READ_PTR(heroObj, 0x218);
|
||
if (IsValidPtr(equipAddr)) equipCount = SAFE_READ(int, equipAddr, 0x18, 0);
|
||
g_BackendData.currentEnemyHeroes.push_back({heroId, x, y, equipCount});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
typedef Vector3 (*func_WorldToScreenPoint_t)(void* camera, Vector3 position);
|
||
|
||
void UpdateEquipAndLiveHeroes() {
|
||
auto it = g_BackendData.players.find(g_BackendData.myPlayerId);
|
||
if (it == g_BackendData.players.end()) return;
|
||
uintptr_t logicObj = it->second.logicObjAddr;
|
||
if (!IsValidPtr(logicObj)) return;
|
||
|
||
if (g_il2cppBase != 0) {
|
||
typedef void* (*func_get_main_t)();
|
||
func_get_main_t get_main = (func_get_main_t)(g_il2cppBase + 0x9898204);
|
||
if (get_main) {
|
||
try {
|
||
void* cam = get_main();
|
||
if (cam && IsValidPtr((uintptr_t)cam)) {
|
||
g_BackendData.mainCamera = (uintptr_t)cam;
|
||
} else {
|
||
g_BackendData.mainCamera = 0;
|
||
}
|
||
} catch(...) {}
|
||
}
|
||
}
|
||
|
||
g_BackendData.myItems.clear();
|
||
uintptr_t pEquipBase = SAFE_READ_PTR(logicObj, g_off_equip_base);
|
||
if (IsValidPtr(pEquipBase)) {
|
||
uintptr_t itemArr = SAFE_READ_PTR(pEquipBase, g_off_equip_arr);
|
||
if (IsValidPtr(itemArr)) {
|
||
int equipCount = SAFE_READ(int, itemArr, g_off_equip_count, 0);
|
||
int maxEquipIter = std::clamp(equipCount, 0, 20);
|
||
g_BackendData.myItems.reserve(maxEquipIter);
|
||
for (int i = 0; i < maxEquipIter; i++) {
|
||
uintptr_t itemObj = SAFE_READ_PTR(itemArr, g_off_equip_head + i * g_off_equip_step);
|
||
int equipId = 0;
|
||
if (IsValidPtr(itemObj)) {
|
||
equipId = SAFE_READ(int, itemObj, g_off_equip_id, 0);
|
||
}
|
||
g_BackendData.myItems.push_back(equipId);
|
||
}
|
||
}
|
||
}
|
||
|
||
g_BackendData.myLiveHeroes.clear();
|
||
g_BackendData.myBenchLiveHeroes.clear();
|
||
g_BackendData.debugHeroTraces.clear();
|
||
|
||
g_BackendData.myLiveHeroes.reserve(28);
|
||
g_BackendData.myBenchLiveHeroes.reserve(9);
|
||
g_BackendData.debugHeroTraces.reserve(37);
|
||
|
||
uintptr_t pDyn = SAFE_READ_PTR(logicObj, g_off_hero_dyn);
|
||
uintptr_t cachedHeroArr = 0;
|
||
if (IsValidPtr(pDyn)) {
|
||
cachedHeroArr = SAFE_READ_PTR(pDyn, g_off_hero_arr);
|
||
if (IsValidPtr(cachedHeroArr)) {
|
||
it->second.heroArrayAddr = cachedHeroArr;
|
||
}
|
||
}
|
||
if (!IsValidPtr(cachedHeroArr)) {
|
||
cachedHeroArr = it->second.heroArrayAddr;
|
||
}
|
||
|
||
if (IsValidPtr(cachedHeroArr)) {
|
||
int heroCount = SAFE_READ(int, cachedHeroArr, g_off_hero_count, 0);
|
||
|
||
if (heroCount <= 0 || heroCount > 200) {
|
||
it->second.heroArrayAddr = 0;
|
||
return;
|
||
}
|
||
|
||
int maxHeroIter = std::min(heroCount, 128);
|
||
|
||
for (int i = 0; i < maxHeroIter; i++) {
|
||
uintptr_t heroObj = SAFE_READ_PTR(cachedHeroArr, g_off_hero_head + i * g_off_hero_step);
|
||
if (IsValidPtr(heroObj)) {
|
||
int hId = SAFE_READ(int, heroObj, g_off_hero_id, 0);
|
||
if (hId > 0 && hId < 60000) {
|
||
uintptr_t unitData = SAFE_READ_PTR(heroObj, g_off_hero_unit);
|
||
uintptr_t battleUnit = 0;
|
||
Vector3 pos = {0,0,0};
|
||
Vector3 screenPos = {0,0,0};
|
||
|
||
if (IsValidPtr(unitData)) {
|
||
battleUnit = SAFE_READ_PTR(unitData, g_off_unit_battle);
|
||
if (IsValidPtr(battleUnit)) {
|
||
pos.x = SAFE_READ(float, battleUnit, g_off_battle_posx, 0.0f);
|
||
pos.y = SAFE_READ(float, battleUnit, g_off_battle_posx + 4, 0.0f);
|
||
pos.z = SAFE_READ(float, battleUnit, g_off_battle_posx + 8, 0.0f);
|
||
|
||
// 【彻底修复追踪线乱跳】:加上严格的野指针检测与边界过滤!
|
||
if (std::isnan(pos.x) || std::isnan(pos.y) || std::isnan(pos.z) ||
|
||
std::isinf(pos.x) || std::isinf(pos.y) || std::isinf(pos.z) ||
|
||
pos.y < -50.0f || pos.y > 200.0f ||
|
||
pos.x < -300.0f || pos.x > 300.0f ||
|
||
pos.z < -300.0f || pos.z > 300.0f) {
|
||
continue;
|
||
}
|
||
|
||
if (g_BackendData.mainCamera != 0 && g_il2cppBase != 0) {
|
||
func_WorldToScreenPoint_t w2s = (func_WorldToScreenPoint_t)(g_il2cppBase + 0x9897df8);
|
||
try { screenPos = w2s((void*)g_BackendData.mainCamera, pos); } catch(...) {}
|
||
}
|
||
|
||
// 屏幕坐标过滤:摄像机背面(z<=0.01)或者转换失败的,直接丢弃
|
||
if (screenPos.z <= 0.01f || std::isnan(screenPos.x) || std::isnan(screenPos.y) || std::isinf(screenPos.x) || std::isinf(screenPos.y)) {
|
||
continue;
|
||
}
|
||
|
||
int equipCount = 0;
|
||
uintptr_t equipAddr = SAFE_READ_PTR(heroObj, 0x218);
|
||
if (IsValidPtr(equipAddr)) equipCount = SAFE_READ(int, equipAddr, 0x18, 0);
|
||
|
||
if (i < 28) {
|
||
g_BackendData.myLiveHeroes.push_back({hId, pos, screenPos, equipCount, i});
|
||
} else {
|
||
g_BackendData.myBenchLiveHeroes.push_back({hId, pos, screenPos, equipCount, i});
|
||
}
|
||
}
|
||
}
|
||
|
||
DebugHeroTrace trace;
|
||
trace.index = i;
|
||
trace.heroObj = heroObj;
|
||
trace.heroId = hId;
|
||
trace.unitData = unitData;
|
||
trace.battleUnit = battleUnit;
|
||
trace.worldPos = pos;
|
||
trace.screenPos = screenPos;
|
||
g_BackendData.debugHeroTraces.push_back(trace);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void UpdateWarningData() {
|
||
if (!g_card_warning) return;
|
||
|
||
g_BackendData.playerWarnings.clear();
|
||
for (const auto& pair : g_BackendData.players) {
|
||
const PlayerInfo& p = pair.second;
|
||
if (p.rowIdx < 1 || p.rowIdx > 8) continue;
|
||
|
||
std::unordered_map<int, int> counts;
|
||
auto addHeroToCount = [&](int heroId) {
|
||
if (heroId <= 0) return;
|
||
int star = 1, baseId = heroId, dummyCost;
|
||
FastParseHeroId(heroId, baseId, dummyCost, star);
|
||
counts[baseId] += (star == 3 ? 9 : (star == 2 ? 3 : 1));
|
||
};
|
||
|
||
uintptr_t shopEntries = getEntriesDynamically(p.objAddr, g_off_shop_base);
|
||
if (IsValidPtr(shopEntries)) {
|
||
int count = SAFE_READ(int, shopEntries, g_off_entries_count, 0);
|
||
for (int i = 0; i < std::clamp(count, 0, 5); i++) {
|
||
uintptr_t entryStart = shopEntries + g_off_entries_head + i * g_off_entries_step;
|
||
uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value);
|
||
if (value) {
|
||
uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10);
|
||
if (p10) addHeroToCount(SAFE_READ(int, p10, g_off_p10_heroId, 0));
|
||
}
|
||
}
|
||
}
|
||
|
||
uintptr_t benchEntries = getEntriesDynamically(p.objAddr, g_off_bench_base);
|
||
if (IsValidPtr(benchEntries)) {
|
||
int count = SAFE_READ(int, benchEntries, g_off_entries_count, 0);
|
||
for (int i = 0; i < std::clamp(count, 0, 9); i++) {
|
||
uintptr_t entryStart = benchEntries + g_off_entries_head + i * g_off_entries_step;
|
||
uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value);
|
||
if (value) {
|
||
uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10);
|
||
if (p10) addHeroToCount(SAFE_READ(int, p10, g_off_p10_heroId, 0));
|
||
}
|
||
}
|
||
}
|
||
|
||
uintptr_t heroArr = 0;
|
||
if (IsValidPtr(p.logicObjAddr)) {
|
||
uintptr_t dyn2b0 = SAFE_READ_PTR(p.logicObjAddr, 0x2b0);
|
||
if (IsValidPtr(dyn2b0)) heroArr = SAFE_READ_PTR(dyn2b0, 0x20);
|
||
}
|
||
if (!IsValidPtr(heroArr)) {
|
||
heroArr = p.heroArrayAddr;
|
||
}
|
||
if (IsValidPtr(heroArr)) {
|
||
for (int i = 0; i < 28; i++) {
|
||
uintptr_t heroObj = SAFE_READ_PTR(heroArr, 0x20 + i * 8);
|
||
if (IsValidPtr(heroObj)) addHeroToCount(SAFE_READ(int, heroObj, 0x108, 0));
|
||
}
|
||
}
|
||
|
||
PlayerWarningInfo info;
|
||
info.rowIdx = p.rowIdx;
|
||
info.heroCounts.reserve(counts.size());
|
||
for (const auto& kv : counts) info.heroCounts.push_back({kv.first, kv.second});
|
||
g_BackendData.playerWarnings.push_back(std::move(info));
|
||
}
|
||
}
|
||
|
||
void UpdatePlayerStreaks() {
|
||
for (auto& pair : g_BackendData.players) {
|
||
uintptr_t logicObj = pair.second.logicObjAddr;
|
||
if (IsValidPtr(logicObj)) {
|
||
pair.second.winStreak = SAFE_READ(int, logicObj, 0xdc, 0);
|
||
pair.second.loseStreak = SAFE_READ(int, logicObj, 0xe0, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
// =================================================================
|
||
// JNI Hook 与 剪贴板支持
|
||
// =================================================================
|
||
JavaVM* g_jvm = nullptr;
|
||
jobject g_context = nullptr;
|
||
jobject g_view_obj = nullptr;
|
||
|
||
void CopyToAndroidClipboard(const char* text) {
|
||
if (!g_jvm || !g_context) return;
|
||
JNIEnv* env = nullptr;
|
||
int status = g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
|
||
bool attached = false;
|
||
if (status == JNI_EDETACHED) {
|
||
if (g_jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) return;
|
||
attached = true;
|
||
}
|
||
if (!env) return;
|
||
|
||
jstring jtext = env->NewStringUTF(text);
|
||
jclass contextClass = env->GetObjectClass(g_context);
|
||
jmethodID getSystemServiceMid = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
|
||
jstring serviceName = env->NewStringUTF("clipboard");
|
||
jobject clipboard = env->CallObjectMethod(g_context, getSystemServiceMid, serviceName);
|
||
|
||
if (clipboard) {
|
||
jclass clipboardClass = env->GetObjectClass(clipboard);
|
||
jclass clipDataClass = env->FindClass("android/content/ClipData");
|
||
if (clipDataClass) {
|
||
jmethodID newPlainTextMid = env->GetStaticMethodID(clipDataClass, "newPlainText", "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;");
|
||
jstring label = env->NewStringUTF("CopiedPath");
|
||
jobject clipData = env->CallStaticObjectMethod(clipDataClass, newPlainTextMid, label, jtext);
|
||
|
||
jmethodID setPrimaryClipMid = env->GetMethodID(clipboardClass, "setPrimaryClip", "(Landroid/content/ClipData;)V");
|
||
if (setPrimaryClipMid) env->CallVoidMethod(clipboard, setPrimaryClipMid, clipData);
|
||
|
||
env->DeleteLocalRef(clipData);
|
||
env->DeleteLocalRef(label);
|
||
} else {
|
||
env->ExceptionClear();
|
||
jmethodID setTextMid = env->GetMethodID(clipboardClass, "setText", "(Ljava/lang/CharSequence;)V");
|
||
if (setTextMid) env->CallVoidMethod(clipboard, setTextMid, jtext);
|
||
}
|
||
env->DeleteLocalRef(clipboardClass);
|
||
env->DeleteLocalRef(clipboard);
|
||
}
|
||
|
||
env->DeleteLocalRef(serviceName);
|
||
env->DeleteLocalRef(contextClass);
|
||
env->DeleteLocalRef(jtext);
|
||
|
||
if (attached) g_jvm->DetachCurrentThread();
|
||
}
|
||
|
||
typedef void (*nativeInjectEvent_t)(JNIEnv*, jobject, jobject);
|
||
nativeInjectEvent_t old_nativeInjectEvent = nullptr;
|
||
|
||
void SimulateSwipe(float startX, float startY, float endX, float endY, bool instant = false) {
|
||
if (g_show_swipe_visuals) {
|
||
std::lock_guard<std::mutex> lock(g_swipe_mutex);
|
||
g_active_swipes.push_back({{startX, startY}, {endX, endY}, instant ? 0.5f : 2.0f, instant ? 0.5f : 2.0f});
|
||
}
|
||
|
||
if (!g_jvm || !g_view_obj || !old_nativeInjectEvent) return;
|
||
|
||
std::thread([startX, startY, endX, endY, instant]() {
|
||
JNIEnv* env = nullptr;
|
||
bool attached = false;
|
||
if (g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) {
|
||
if (g_jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) return;
|
||
attached = true;
|
||
}
|
||
|
||
jclass sysClockClass = env->FindClass("android/os/SystemClock");
|
||
jmethodID uptimeMid = env->GetStaticMethodID(sysClockClass, "uptimeMillis", "()J");
|
||
jclass meClass = env->FindClass("android/view/MotionEvent");
|
||
jmethodID obtainMid = env->GetStaticMethodID(meClass, "obtain", "(JJIFFI)Landroid/view/MotionEvent;");
|
||
jmethodID recycleMid = env->GetMethodID(meClass, "recycle", "()V");
|
||
jmethodID setSourceMid = env->GetMethodID(meClass, "setSource", "(I)V");
|
||
|
||
if (sysClockClass && uptimeMid && meClass && obtainMid) {
|
||
long downTime = env->CallStaticLongMethod(sysClockClass, uptimeMid);
|
||
|
||
float v_startX = startX, v_startY = startY, v_endX = endX, v_endY = endY;
|
||
if (g_cached_view_width > 0 && g_gl_width > 0) {
|
||
v_startX = startX * ((float)g_cached_view_width / g_gl_width);
|
||
v_endX = endX * ((float)g_cached_view_width / g_gl_width);
|
||
}
|
||
if (g_cached_view_height > 0 && g_gl_height > 0) {
|
||
v_startY = startY * ((float)g_cached_view_height / g_gl_height);
|
||
v_endY = endY * ((float)g_cached_view_height / g_gl_height);
|
||
}
|
||
|
||
AddLog("[底层滑动] 渲染坐标(%.1f, %.1f)->(%.1f, %.1f)", startX, startY, endX, endY);
|
||
AddLog("[底层滑动] 物理映射(%.1f, %.1f)->(%.1f, %.1f)", v_startX, v_startY, v_endX, v_endY);
|
||
|
||
int steps = instant ? 0 : g_swipe_steps;
|
||
int step_delay = instant ? 0 : g_swipe_step_delay_ms;
|
||
int down_delay = instant ? 20 : g_swipe_down_delay_ms;
|
||
int up_delay = instant ? 20 : g_swipe_up_delay_ms;
|
||
|
||
auto injectTouchEvent = [&](int action, float x, float y) {
|
||
long eventTime = env->CallStaticLongMethod(sysClockClass, uptimeMid);
|
||
jobject meObj = env->CallStaticObjectMethod(meClass, obtainMid, downTime, eventTime, action, x, y, 0);
|
||
if (meObj) {
|
||
if (setSourceMid) env->CallVoidMethod(meObj, setSourceMid, 4098);
|
||
old_nativeInjectEvent(env, g_view_obj, meObj);
|
||
if (recycleMid) env->CallVoidMethod(meObj, recycleMid);
|
||
env->DeleteLocalRef(meObj);
|
||
}
|
||
};
|
||
|
||
injectTouchEvent(0, v_startX, v_startY);
|
||
if (down_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(down_delay));
|
||
|
||
if (steps > 0) {
|
||
for (int i = 1; i <= steps; i++) {
|
||
float t = (float)i / steps;
|
||
float curX = v_startX + (v_endX - v_startX) * t;
|
||
float curY = v_startY + (v_endY - v_startY) * t;
|
||
injectTouchEvent(2, curX, curY);
|
||
if (step_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(step_delay));
|
||
}
|
||
} else {
|
||
injectTouchEvent(2, v_endX, v_endY);
|
||
if (step_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(step_delay));
|
||
}
|
||
|
||
if (up_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(up_delay));
|
||
injectTouchEvent(1, v_endX, v_endY);
|
||
}
|
||
|
||
if (sysClockClass) env->DeleteLocalRef(sysClockClass);
|
||
if (meClass) env->DeleteLocalRef(meClass);
|
||
|
||
if (attached) g_jvm->DetachCurrentThread();
|
||
}).detach();
|
||
}
|
||
|
||
// =================================================================
|
||
// 序列化
|
||
// =================================================================
|
||
void SaveConfig() {
|
||
std::lock_guard<std::mutex> lock(g_configMutex);
|
||
|
||
std::ostringstream ss_heroes;
|
||
for (int id : g_auto_buy_heroes) { ss_heroes << id << ","; }
|
||
g_auto_buy_heroes_str = ss_heroes.str();
|
||
|
||
std::ostringstream ss_binds;
|
||
for(const auto& pair : g_hero_equip_bindings) {
|
||
if(pair.second.empty()) continue;
|
||
ss_binds << pair.first << ":";
|
||
for(size_t i=0; i<pair.second.size(); i++) {
|
||
ss_binds << pair.second[i] << (i < pair.second.size()-1 ? "," : "");
|
||
}
|
||
ss_binds << ";";
|
||
}
|
||
g_hero_equip_bindings_str = ss_binds.str();
|
||
|
||
std::ostringstream ss_prio;
|
||
for(const auto& pair : g_hero_equip_priority) {
|
||
if (pair.second > 0) ss_prio << pair.first << ":" << pair.second << ";";
|
||
}
|
||
g_hero_equip_priority_str = ss_prio.str();
|
||
|
||
std::ostringstream ss_eq;
|
||
for (const auto& kv : g_cell_equip_offset) {
|
||
ss_eq << kv.first << ":" << kv.second.x << "," << kv.second.y << ";";
|
||
}
|
||
g_cell_equip_offset_str = ss_eq.str();
|
||
|
||
std::ostringstream ss_sell;
|
||
for (const auto& kv : g_cell_sell_offset) {
|
||
ss_sell << kv.first << ":" << kv.second.x << "," << kv.second.y << ";";
|
||
}
|
||
g_cell_sell_offset_str = ss_sell.str();
|
||
|
||
std::ofstream out(g_configPath);
|
||
if (!out.is_open()) return;
|
||
for (const auto& entry : g_configTable) {
|
||
out << entry.key << "=";
|
||
if (entry.type == C_BOOL) out << (*(bool*)entry.ptr);
|
||
else if (entry.type == C_INT) out << (*(int*)entry.ptr);
|
||
else if (entry.type == C_FLOAT) out << (*(float*)entry.ptr);
|
||
else if (entry.type == C_STRING) out << (*(std::string*)entry.ptr);
|
||
out << "\n";
|
||
}
|
||
out.close();
|
||
}
|
||
|
||
// =================================================================
|
||
// 自适应分辨率:自动穿/拆装备主干逻辑 (多维排序)
|
||
// =================================================================
|
||
void ExecuteMainThreadAutoEquip() {
|
||
auto snap = GetSnapshot();
|
||
if (!snap) return;
|
||
|
||
int cooldown_ms = g_swipe_down_delay_ms + (g_swipe_steps * g_swipe_step_delay_ms) + g_swipe_up_delay_ms + g_equip_extra_cooldown_ms;
|
||
|
||
static auto last_swipe_time = std::chrono::steady_clock::now();
|
||
auto now = std::chrono::steady_clock::now();
|
||
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_swipe_time).count() < cooldown_ms) {
|
||
return;
|
||
}
|
||
|
||
static auto equip_idle_start = std::chrono::steady_clock::now();
|
||
|
||
static bool was_equip_active = false;
|
||
if (g_auto_equip_active && !was_equip_active) {
|
||
equip_idle_start = std::chrono::steady_clock::now();
|
||
}
|
||
was_equip_active = g_auto_equip_active;
|
||
|
||
// --- 自动穿装备 ---
|
||
if (g_auto_equip_active) {
|
||
bool equipped_this_frame = false;
|
||
int num_heroes = snap->myLiveHeroes.size();
|
||
|
||
struct HeroEquipTarget {
|
||
const LiveHeroData* hero;
|
||
int priority;
|
||
int cost;
|
||
int star;
|
||
};
|
||
std::vector<HeroEquipTarget> sorted_targets;
|
||
sorted_targets.reserve(num_heroes);
|
||
|
||
for (int i = 0; i < num_heroes; i++) {
|
||
const auto& liveHero = snap->myLiveHeroes[i];
|
||
|
||
if (liveHero.equipCount >= 9) continue;
|
||
|
||
int baseId, cost, star;
|
||
FastParseHeroId(liveHero.heroId, baseId, cost, star);
|
||
|
||
int prio = std::max(g_hero_equip_priority[liveHero.heroId], g_hero_equip_priority[baseId]);
|
||
sorted_targets.push_back({&liveHero, prio, cost, star});
|
||
}
|
||
|
||
std::stable_sort(sorted_targets.begin(), sorted_targets.end(), [](const HeroEquipTarget& a, const HeroEquipTarget& b) {
|
||
if (a.priority != b.priority) return a.priority > b.priority;
|
||
if (a.cost != b.cost) return a.cost > b.cost;
|
||
return a.star > b.star;
|
||
});
|
||
|
||
std::unordered_set<int> used_slots_this_frame;
|
||
|
||
for (const auto& target : sorted_targets) {
|
||
const auto& liveHero = *(target.hero);
|
||
int heroId = liveHero.heroId;
|
||
int baseId, dummyCost, dummyStar;
|
||
FastParseHeroId(heroId, baseId, dummyCost, dummyStar);
|
||
|
||
auto binds_it = g_hero_equip_bindings.find(heroId);
|
||
if (binds_it == g_hero_equip_bindings.end()) {
|
||
binds_it = g_hero_equip_bindings.find(baseId);
|
||
}
|
||
|
||
if (binds_it != g_hero_equip_bindings.end()) {
|
||
for (int targetEquip : binds_it->second) {
|
||
|
||
int found_slot_index = -1;
|
||
for (size_t item_idx = 0; item_idx < snap->myItems.size(); ++item_idx) {
|
||
if (snap->myItems[item_idx] == targetEquip && used_slots_this_frame.find(item_idx) == used_slots_this_frame.end()) {
|
||
found_slot_index = item_idx;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (found_slot_index != -1 && liveHero.screenPos.z > 0.01f) {
|
||
used_slots_this_frame.insert(found_slot_index);
|
||
|
||
int col = found_slot_index / 10;
|
||
int row = found_slot_index % 10;
|
||
|
||
float startX = (g_equip_ui_start_x + col * g_equip_ui_step_x) * g_autoScale;
|
||
float startY = (g_equip_ui_start_y + row * g_equip_ui_step_y) * g_autoScale;
|
||
|
||
if (g_cell_equip_offset.find(liveHero.cellIndex) == g_cell_equip_offset.end()) {
|
||
g_cell_equip_offset[liveHero.cellIndex] = ImVec2(0.0f, -80.0f);
|
||
}
|
||
ImVec2 off = g_cell_equip_offset[liveHero.cellIndex];
|
||
float endX = liveHero.screenPos.x + off.x * g_autoScale;
|
||
float endY = g_gl_height - liveHero.screenPos.y + off.y * g_autoScale;
|
||
|
||
if (endX > 0 && endY > 0 && endX < g_gl_width && endY < g_gl_height) {
|
||
AddLog("[智能穿装] 目标:%d Prio:%d 装备:%d 槽位:%d", baseId, target.priority, targetEquip, found_slot_index);
|
||
SimulateSwipe(startX, startY, endX, endY);
|
||
last_swipe_time = std::chrono::steady_clock::now();
|
||
equipped_this_frame = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (equipped_this_frame) break;
|
||
}
|
||
|
||
if (equipped_this_frame) {
|
||
equip_idle_start = std::chrono::steady_clock::now();
|
||
} else {
|
||
auto idle_duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - equip_idle_start).count();
|
||
if (idle_duration > 1500) {
|
||
g_auto_equip_active = false;
|
||
equip_idle_start = std::chrono::steady_clock::now();
|
||
AddLog("[穿装结束] 所有优先级目标装备已处理完毕");
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 自动拆装备 ---
|
||
if (g_auto_unequip_active) {
|
||
bool swiped_this_frame = false;
|
||
bool all_cleared = true;
|
||
|
||
int col = g_unequip_slot_idx / 10;
|
||
int row = g_unequip_slot_idx % 10;
|
||
float startX = (g_equip_ui_start_x + col * g_equip_ui_step_x) * g_autoScale;
|
||
float startY = (g_equip_ui_start_y + row * g_equip_ui_step_y) * g_autoScale;
|
||
|
||
auto tryUnequipList = [&](const std::vector<LiveHeroData>& heroes) -> bool {
|
||
for (const auto& hero : heroes) {
|
||
if (hero.equipCount > 0 && hero.screenPos.z > 0.01f) {
|
||
all_cleared = false;
|
||
|
||
if (g_cell_equip_offset.find(hero.cellIndex) == g_cell_equip_offset.end()) {
|
||
g_cell_equip_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f);
|
||
}
|
||
ImVec2 off = g_cell_equip_offset[hero.cellIndex];
|
||
float endX = hero.screenPos.x + off.x * g_autoScale;
|
||
float endY = g_gl_height - hero.screenPos.y + off.y * g_autoScale;
|
||
|
||
if (endX > 0 && endY > 0 && endX < g_gl_width && endY < g_gl_height) {
|
||
AddLog("[智能拆装] 目标英雄ID:%d 剩余装备:%d", hero.heroId, hero.equipCount);
|
||
SimulateSwipe(startX, startY, endX, endY);
|
||
last_swipe_time = std::chrono::steady_clock::now();
|
||
swiped_this_frame = true;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
if (!tryUnequipList(snap->myLiveHeroes)) {
|
||
tryUnequipList(snap->myBenchLiveHeroes);
|
||
}
|
||
|
||
if (!swiped_this_frame && all_cleared) {
|
||
g_auto_unequip_active = false;
|
||
AddLog("[拆装完成] 全场英雄装备已清空");
|
||
}
|
||
}
|
||
}
|
||
|
||
// =================================================================
|
||
// 胶囊窗口一键卖英雄核心逻辑
|
||
// =================================================================
|
||
void TriggerSellAllHeroes() {
|
||
static std::atomic<bool> is_selling{false};
|
||
if (is_selling.load()) return;
|
||
is_selling.store(true);
|
||
|
||
std::thread([]() {
|
||
int retries = 0;
|
||
const int MAX_RETRIES = 8;
|
||
|
||
while (retries < MAX_RETRIES) {
|
||
auto snap = GetSnapshot();
|
||
if (!snap) break;
|
||
|
||
std::vector<ImVec2> sell_positions;
|
||
|
||
auto add_sell_pos = [&](const std::vector<LiveHeroData>& heroes) {
|
||
for (const auto& hero : heroes) {
|
||
if (hero.screenPos.z > 0.01f) {
|
||
ImVec2 off(0.0f, -80.0f);
|
||
auto it = g_cell_sell_offset.find(hero.cellIndex);
|
||
if (it != g_cell_sell_offset.end()) off = it->second;
|
||
|
||
float sx = hero.screenPos.x + off.x * g_autoScale;
|
||
float sy = g_gl_height - hero.screenPos.y + off.y * g_autoScale;
|
||
sell_positions.push_back(ImVec2(sx, sy));
|
||
}
|
||
}
|
||
};
|
||
|
||
add_sell_pos(snap->myBenchLiveHeroes);
|
||
add_sell_pos(snap->myLiveHeroes);
|
||
|
||
if (sell_positions.empty()) {
|
||
if (retries > 0) AddLog("[一键卖牌] 场上已空,所有英雄成功售出");
|
||
break;
|
||
}
|
||
|
||
if (retries == 0) AddLog("[一键卖牌] 开始极速出售所有 %d 个英雄", (int)sell_positions.size());
|
||
else AddLog("[一键卖牌] 检测到漏卖,第 %d 次重试,剩余 %d 个", retries, (int)sell_positions.size());
|
||
|
||
for (const auto& pos : sell_positions) {
|
||
float endX = g_sell_drop_x * g_autoScale;
|
||
float endY = g_sell_drop_y * g_autoScale;
|
||
SimulateSwipe(pos.x, pos.y, endX, endY, true);
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(70));
|
||
}
|
||
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(600));
|
||
retries++;
|
||
}
|
||
|
||
if (retries >= MAX_RETRIES) {
|
||
AddLog("[一键卖牌] 已达到最大重试次数,已停止。可能存在无法出售的单位(如假人)");
|
||
}
|
||
|
||
is_selling.store(false);
|
||
}).detach();
|
||
}
|
||
|
||
extern "C" void hook_nativeInjectEvent(JNIEnv* env, jobject obj, jobject event) {
|
||
if (!g_jvm) env->GetJavaVM(&g_jvm);
|
||
|
||
if (obj) {
|
||
if (!g_view_obj || !env->IsSameObject(g_view_obj, obj)) {
|
||
if (g_view_obj) env->DeleteGlobalRef(g_view_obj);
|
||
g_view_obj = env->NewGlobalRef(obj);
|
||
}
|
||
if (!g_context) {
|
||
jclass viewClass = env->GetObjectClass(obj);
|
||
jmethodID getContextMid = env->GetMethodID(viewClass, "getContext", "()Landroid/content/Context;");
|
||
if (getContextMid) {
|
||
jobject ctx = env->CallObjectMethod(obj, getContextMid);
|
||
if (ctx) g_context = env->NewGlobalRef(ctx);
|
||
}
|
||
env->DeleteLocalRef(viewClass);
|
||
}
|
||
}
|
||
|
||
if (event) {
|
||
static jclass motionEventClassGlobal = nullptr;
|
||
if (!motionEventClassGlobal) {
|
||
jclass meClass = env->FindClass("android/view/MotionEvent");
|
||
if (meClass) { motionEventClassGlobal = (jclass)env->NewGlobalRef(meClass); env->DeleteLocalRef(meClass); }
|
||
}
|
||
|
||
if (motionEventClassGlobal && env->IsInstanceOf(event, motionEventClassGlobal)) {
|
||
static jmethodID getWidthMid = nullptr, getHeightMid = nullptr;
|
||
static jmethodID getActionMid = nullptr, getXMid = nullptr, getYMid = nullptr;
|
||
|
||
if (getWidthMid == nullptr) {
|
||
jclass viewClass = env->GetObjectClass(obj);
|
||
getWidthMid = env->GetMethodID(viewClass, "getWidth", "()I"); getHeightMid = env->GetMethodID(viewClass, "getHeight", "()I"); env->DeleteLocalRef(viewClass);
|
||
getActionMid = env->GetMethodID(motionEventClassGlobal, "getAction", "()I"); getXMid = env->GetMethodID(motionEventClassGlobal, "getX", "()F"); getYMid = env->GetMethodID(motionEventClassGlobal, "getY", "()F");
|
||
}
|
||
|
||
if (getActionMid && getXMid && getYMid) {
|
||
int action = env->CallIntMethod(event, getActionMid) & 255;
|
||
if ((action == 0 && g_cached_view_width <= 0) || g_cached_view_width <= 0) {
|
||
g_cached_view_width = env->CallIntMethod(obj, getWidthMid);
|
||
g_cached_view_height = env->CallIntMethod(obj, getHeightMid);
|
||
}
|
||
float raw_x = env->CallFloatMethod(event, getXMid), raw_y = env->CallFloatMethod(event, getYMid);
|
||
float scale_x = 1.0f, scale_y = 1.0f;
|
||
if (g_cached_view_width > 0 && g_gl_width > 0) scale_x = (float)g_gl_width / g_cached_view_width;
|
||
if (g_cached_view_height > 0 && g_gl_height > 0) scale_y = (float)g_gl_height / g_cached_view_height;
|
||
|
||
ImGuiIO& io = ImGui::GetIO(); io.AddMousePosEvent(raw_x * scale_x, raw_y * scale_y);
|
||
if (action == 0) io.AddMouseButtonEvent(0, true); else if (action == 1) io.AddMouseButtonEvent(0, false);
|
||
if (io.WantCaptureMouse) return;
|
||
}
|
||
}
|
||
}
|
||
if (old_nativeInjectEvent) old_nativeInjectEvent(env, obj, event);
|
||
}
|
||
|
||
void FindAndHookHiddenJNI() {
|
||
FILE* fp = fopen("/proc/self/maps", "r"); if (!fp) return;
|
||
char line[1024]; struct MemRegion { uintptr_t start, end; bool is_rw; }; std::vector<MemRegion> regions;
|
||
while (fgets(line, sizeof(line), fp)) {
|
||
if (strstr(line, "libunity.so")) {
|
||
bool is_r = strstr(line, "r-") != nullptr; bool is_rw = strstr(line, "rw") != nullptr;
|
||
if (is_r || is_rw) { uintptr_t start, end; sscanf(line, "%lx-%lx", &start, &end); regions.push_back({start, end, is_rw}); }
|
||
}
|
||
}
|
||
fclose(fp);
|
||
|
||
const char* target_string = "nativeInjectEvent"; std::vector<uintptr_t> string_addrs;
|
||
for (const auto& reg : regions) {
|
||
if (!reg.is_rw) {
|
||
for (uintptr_t p = reg.start; p < reg.end - strlen(target_string); p++) {
|
||
if (memcmp((void*)p, target_string, strlen(target_string)) == 0) string_addrs.push_back(p);
|
||
}
|
||
}
|
||
}
|
||
if (string_addrs.empty()) return;
|
||
|
||
bool found_func = false;
|
||
for (const auto& reg : regions) {
|
||
uintptr_t align_start = (reg.start + 7) & ~7;
|
||
for (uintptr_t p = align_start; p < reg.end - sizeof(void*)*3; p += sizeof(void*)) {
|
||
uintptr_t ptr_val = *(uintptr_t*)p;
|
||
for (uintptr_t str_addr : string_addrs) {
|
||
if (ptr_val == str_addr) {
|
||
void** fnPtr_addr = (void**)(p + 16); void* real_function_addr = *fnPtr_addr;
|
||
if (real_function_addr != nullptr && (uintptr_t)real_function_addr > 0x100000) {
|
||
DobbyHook(real_function_addr, (void*)hook_nativeInjectEvent, (void**)&old_nativeInjectEvent);
|
||
found_func = true; break;
|
||
}
|
||
}
|
||
}
|
||
if (found_func) break;
|
||
}
|
||
if (found_func) break;
|
||
}
|
||
}
|
||
|
||
typedef int (*func_3Cd6058_t)(void* playerObj); func_3Cd6058_t orig_3Cd6058 = nullptr;
|
||
typedef void (*func_set_Money_t)(void* playerObj, int money); func_set_Money_t orig_set_Money = nullptr;
|
||
typedef void (*func_set_Level_t)(void* playerObj, int level); func_set_Level_t orig_set_Level = nullptr;
|
||
typedef void (*func_TurnStart_t)(void* thisObj); func_TurnStart_t orig_TurnStart = nullptr;
|
||
typedef void (*func_set_IsGameEnd_t)(void* thisObj, uint8_t isEnd); func_set_IsGameEnd_t orig_set_IsGameEnd = nullptr;
|
||
typedef void (*func_UpdateTurnStart_t)(void* arg0); func_UpdateTurnStart_t orig_UpdateTurnStart = nullptr;
|
||
typedef int (*func_InitTurnDropCfg_t)(void* obj, int index); func_InitTurnDropCfg_t orig_InitTurnDropCfg = nullptr;
|
||
|
||
// === 大量使用 g_is_in_match 保护机制,防止大厅/加载界面卡死 ===
|
||
int hook_3Cd6058(void* playerObj) {
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_3Cd6058(playerObj);
|
||
g_debug_hook_3Cd6058_count++;
|
||
int rowIdx = orig_3Cd6058(playerObj);
|
||
if (playerObj && IsValidPtr((uintptr_t)playerObj)) {
|
||
g_Tasks.player_row_queue.push({(uintptr_t)playerObj, rowIdx});
|
||
}
|
||
return rowIdx;
|
||
}
|
||
|
||
void hook_set_Money(void* playerObj, int money) {
|
||
orig_set_Money(playerObj, money);
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return;
|
||
g_debug_hook_set_Money_count++;
|
||
if (playerObj && IsValidPtr((uintptr_t)playerObj)) {
|
||
g_Tasks.money_queue.push({(uintptr_t)playerObj, money});
|
||
}
|
||
}
|
||
|
||
void hook_set_Level(void* playerObj, int level) {
|
||
orig_set_Level(playerObj, level);
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return;
|
||
g_debug_hook_set_Level_count++;
|
||
if (playerObj && IsValidPtr((uintptr_t)playerObj)) {
|
||
g_Tasks.level_queue.push({(uintptr_t)playerObj, level});
|
||
}
|
||
}
|
||
|
||
void hook_TurnStart(void* thisObj) {
|
||
orig_TurnStart(thisObj);
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return;
|
||
g_debug_TurnStartCalled++;
|
||
if (thisObj) g_Tasks.pending_turn_start_obj.store((uintptr_t)thisObj, std::memory_order_release);
|
||
}
|
||
|
||
// 【修复 1】:权威状态接管
|
||
void hook_set_IsGameEnd(void* thisObj, uint8_t isEnd) {
|
||
orig_set_IsGameEnd(thisObj, isEnd);
|
||
g_debug_set_IsGameEnd_count++;
|
||
|
||
if (isEnd == 0) {
|
||
if (!g_is_in_match.load(std::memory_order_acquire)) {
|
||
AddLog("[状态监控] 引擎触发 isGameEnd = 0, 正式进入对局!");
|
||
g_is_in_match.store(true, std::memory_order_release);
|
||
}
|
||
}
|
||
else {
|
||
if (g_is_in_match.load(std::memory_order_acquire)) {
|
||
AddLog("[状态监控] 引擎触发 isGameEnd = %d, 退出对局,清理残余数据!", isEnd);
|
||
g_is_in_match.store(false, std::memory_order_release);
|
||
g_Tasks.trigger_game_end.store(true, std::memory_order_release);
|
||
}
|
||
}
|
||
}
|
||
|
||
void hook_UpdateTurnStart(void* arg0) {
|
||
orig_UpdateTurnStart(arg0);
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return;
|
||
if (g_collected_all_players.load(std::memory_order_relaxed)) return;
|
||
g_debug_UpdateTurnStart_count++;
|
||
if (arg0 && IsValidPtr((uintptr_t)arg0)) {
|
||
g_Tasks.update_turn_start_queue.push((uintptr_t)arg0);
|
||
}
|
||
}
|
||
|
||
int hook_InitTurnDropCfg(void* obj, int index) {
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_InitTurnDropCfg(obj, index);
|
||
g_debug_hook_InitTurnDropCfg_count++;
|
||
int ret = orig_InitTurnDropCfg(obj, index);
|
||
if (obj && IsValidPtr((uintptr_t)obj)) g_Tasks.pending_hex_cfg_obj.store((uintptr_t)obj, std::memory_order_release);
|
||
return ret;
|
||
}
|
||
|
||
typedef void* (*func_46cce08_t)(void* x0, void* x1, void* x2, void* x3);
|
||
func_46cce08_t orig_46cce08 = nullptr;
|
||
|
||
void* hook_46cce08(void* x0, void* arg1, void* arg2, void* arg3) {
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_46cce08(x0, arg1, arg2, arg3);
|
||
if (g_shop_slot_index < 5 && x0 != nullptr) {
|
||
uintptr_t ptr_x0 = (uintptr_t)x0;
|
||
bool already_exists = false;
|
||
for (int i = 0; i < g_shop_slot_index; i++) {
|
||
if (g_shop_slot_instances[i] == ptr_x0) { already_exists = true; break; }
|
||
}
|
||
if (!already_exists) { g_shop_slot_instances[g_shop_slot_index++] = ptr_x0; }
|
||
}
|
||
return orig_46cce08(x0, arg1, arg2, arg3);
|
||
}
|
||
|
||
static auto g_last_buy_request_time = std::chrono::steady_clock::now() - std::chrono::hours(1);
|
||
|
||
void ExecuteMainThreadAutoRefresh() {
|
||
if (!g_auto_refresh || g_refresh_instance == 0 || g_req_refresh == nullptr) return;
|
||
static auto last_refresh_time = std::chrono::steady_clock::now();
|
||
auto now = std::chrono::steady_clock::now();
|
||
|
||
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - g_last_buy_request_time).count() < 300) return;
|
||
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_refresh_time).count() < g_auto_buy_delay_ms) return;
|
||
|
||
auto snap = GetSnapshot();
|
||
int myMoney = 0;
|
||
bool has_target_in_shop = false;
|
||
|
||
auto it = snap->players.find(snap->myPlayerId);
|
||
if (it != snap->players.end()) myMoney = it->second.money;
|
||
|
||
for (const auto& shopHero : snap->myShopHeroes) {
|
||
if (g_auto_buy_heroes.count(shopHero.baseHeroId) || g_auto_buy_heroes.count(shopHero.heroId)) {
|
||
if (shopHero.starLevel == 2 && !g_auto_buy_chosen) continue;
|
||
has_target_in_shop = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (has_target_in_shop || myMoney < g_auto_refresh_money_threshold) {
|
||
if (myMoney < g_auto_refresh_money_threshold) g_auto_refresh = false;
|
||
return;
|
||
}
|
||
|
||
try {
|
||
g_req_refresh((void*)g_refresh_instance);
|
||
last_refresh_time = now;
|
||
} catch (...) {}
|
||
}
|
||
|
||
void ExecuteMainThreadAutoBuy() {
|
||
if (!g_auto_buy || g_reqbuyhero == nullptr) return;
|
||
|
||
auto snap = GetSnapshot();
|
||
if (g_auto_buy_heroes.empty()) return;
|
||
|
||
uintptr_t myObj = 0;
|
||
auto it = snap->players.find(snap->myPlayerId);
|
||
if (it != snap->players.end()) myObj = it->second.objAddr;
|
||
if (!myObj || !IsValidPtr(myObj)) return;
|
||
|
||
uintptr_t shopEntries = getEntriesDynamically(myObj, g_off_shop_base);
|
||
if (!IsValidPtr(shopEntries)) return;
|
||
|
||
int count = SAFE_READ(int, shopEntries, g_off_entries_count, 0);
|
||
int maxIter = std::clamp(count, 0, 5);
|
||
|
||
for (int i = 0; i < maxIter; i++) {
|
||
uintptr_t entryStart = shopEntries + g_off_entries_head + i * g_off_entries_step;
|
||
uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value);
|
||
if (!value) continue;
|
||
|
||
int slot = SAFE_READ(int, value, g_off_value_slot, -1);
|
||
if (slot >= 0 && slot < 5) {
|
||
uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10);
|
||
if (p10) {
|
||
int heroId = SAFE_READ(int, p10, g_off_p10_heroId, 0);
|
||
if (heroId > 0) {
|
||
int baseId, cost, star;
|
||
FastParseHeroId(heroId, baseId, cost, star);
|
||
|
||
if (star == 2 && !g_auto_buy_chosen) continue;
|
||
|
||
bool is_recommended = g_auto_buy_heroes.count(baseId) || g_auto_buy_heroes.count(heroId);
|
||
|
||
if (is_recommended) {
|
||
uintptr_t target_x0 = g_shop_slot_instances[slot];
|
||
if (target_x0 != 0) {
|
||
try {
|
||
g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr);
|
||
g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr);
|
||
g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr);
|
||
g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr);
|
||
g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr);
|
||
g_last_buy_request_time = std::chrono::steady_clock::now();
|
||
} catch (...) {}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void ProcessMainThreadTasks() {
|
||
if (g_Tasks.trigger_game_end.exchange(false, std::memory_order_acquire)) {
|
||
g_BackendData = GameSnapshot();
|
||
PushSnapshot();
|
||
PushSnapshot();
|
||
|
||
g_refresh_instance = 0; g_turnStartThisObj = nullptr; g_hexCfgObj = nullptr;
|
||
g_pool_entries1.store(0, std::memory_order_release);
|
||
g_debug_hook_6A67e48_count = 0;
|
||
g_matchFetchRequested.store(false, std::memory_order_relaxed);
|
||
g_matchFetchReady.store(false, std::memory_order_relaxed);
|
||
g_collected_all_players.store(false, std::memory_order_relaxed);
|
||
g_debug_collected_players_count = 0; g_debug_UpdateTurnStart_count = 0;
|
||
|
||
g_shop_slot_index = 0;
|
||
for (int i = 0; i < 5; i++) g_shop_slot_instances[i] = 0;
|
||
g_Tasks.pending_hex_cfg_obj.store(0); g_Tasks.processed_hex_cfg_obj = 0;
|
||
g_Tasks.pending_turn_start_obj.store(0);
|
||
|
||
g_Tasks.player_row_queue.clear();
|
||
g_Tasks.money_queue.clear();
|
||
g_Tasks.level_queue.clear();
|
||
g_Tasks.update_turn_start_queue.clear();
|
||
|
||
std::lock_guard<std::mutex> lock(g_swipe_mutex);
|
||
g_active_swipes.clear();
|
||
}
|
||
|
||
uintptr_t tObj = g_Tasks.pending_turn_start_obj.exchange(0, std::memory_order_acquire);
|
||
if (tObj != 0) {
|
||
g_turnStartThisObj = (void*)tObj;
|
||
if (!g_matchFetchRequested.load(std::memory_order_acquire)) {
|
||
g_matchFetchTime = std::chrono::steady_clock::now() + std::chrono::seconds(1);
|
||
g_matchFetchRequested.store(true, std::memory_order_release);
|
||
}
|
||
}
|
||
|
||
uintptr_t hexObj = g_Tasks.pending_hex_cfg_obj.load(std::memory_order_acquire);
|
||
if (hexObj != 0 && hexObj != g_Tasks.processed_hex_cfg_obj) {
|
||
g_Tasks.processed_hex_cfg_obj = hexObj;
|
||
g_hexCfgObj = (void*)hexObj;
|
||
|
||
g_BackendData.hex_fetched = false;
|
||
for(int i=0; i<4; i++) g_BackendData.cachedHexes[i] = 0;
|
||
g_BackendData.validHexCount = 0;
|
||
}
|
||
|
||
uintptr_t pObj;
|
||
while (g_Tasks.update_turn_start_queue.pop(pObj)) {
|
||
bool isAi = SAFE_READ(bool, pObj, 0x58, false);
|
||
uintptr_t dynAddrs = SAFE_READ_PTR(pObj, 0x50);
|
||
if (IsValidPtr(dynAddrs)) {
|
||
int pid = SAFE_READ(int, dynAddrs, 0x20, -1);
|
||
if (pid != -1) {
|
||
if (g_BackendData.players[pid].logicObjAddr != pObj) {
|
||
g_BackendData.players[pid].logicObjAddr = pObj;
|
||
g_BackendData.players[pid].dynamicAddr = dynAddrs;
|
||
g_BackendData.players[pid].isAi = isAi;
|
||
g_BackendData.players[pid].heroArrayAddr = 0;
|
||
}
|
||
}
|
||
}
|
||
int valid_count = 0;
|
||
for (const auto& pair : g_BackendData.players) if (pair.second.logicObjAddr != 0) valid_count++;
|
||
g_debug_collected_players_count = valid_count;
|
||
if (valid_count >= 8) g_collected_all_players.store(true, std::memory_order_relaxed);
|
||
}
|
||
|
||
PlayerRowInfo rowItem;
|
||
while (g_Tasks.player_row_queue.pop(rowItem)) {
|
||
uintptr_t objAddr = rowItem.obj;
|
||
int rowIdx = rowItem.rowIdx;
|
||
uintptr_t tempCsoGame = SAFE_READ_PTR(objAddr, 0x30);
|
||
int playerId = SAFE_READ(int, objAddr, 0x258, -1);
|
||
if (playerId == -1) continue;
|
||
|
||
// 【修复 3:跨局野指针终极清空保护】
|
||
if (g_BackendData.csoGame != 0 && tempCsoGame != 0 && IsValidPtr(tempCsoGame) && g_BackendData.csoGame != tempCsoGame) {
|
||
AddLog("[系统] 检测到新的一局游戏,强行清空上一局残留数据!");
|
||
g_Tasks.trigger_game_end.store(true, std::memory_order_release);
|
||
return;
|
||
}
|
||
|
||
bool needFetchName = false;
|
||
if (g_BackendData.csoGame == 0 && IsValidPtr(tempCsoGame)) g_BackendData.csoGame = tempCsoGame;
|
||
auto it = g_BackendData.players.find(playerId);
|
||
|
||
if (it == g_BackendData.players.end() || it->second.objAddr != objAddr) {
|
||
PlayerInfo& info = g_BackendData.players[playerId];
|
||
info.objAddr = objAddr; info.csoGameAddr = tempCsoGame; info.playerId = playerId; info.rowIdx = rowIdx;
|
||
if(it == g_BackendData.players.end()) info.name = "Unknown";
|
||
g_BackendData.objToPlayerId[objAddr] = playerId; needFetchName = true;
|
||
} else {
|
||
it->second.rowIdx = rowIdx; it->second.csoGameAddr = tempCsoGame;
|
||
if (it->second.name == "Unknown" || it->second.name.empty()) needFetchName = true;
|
||
}
|
||
|
||
if (needFetchName) {
|
||
std::string name = "未知";
|
||
uintptr_t midAddr = SAFE_READ_PTR(objAddr, 0x268);
|
||
uintptr_t strPtr = 0; int nameLen = 0;
|
||
if (IsValidPtr(midAddr)) {
|
||
strPtr = SAFE_READ_PTR(midAddr, 0x28);
|
||
if (IsValidPtr(strPtr)) {
|
||
nameLen = SAFE_READ(int, strPtr, 0x10, 0);
|
||
if (nameLen > 0 && nameLen < 64) {
|
||
char16_t chars[64] = {0};
|
||
if (SafeReadMemory(strPtr + 0x14, chars, nameLen * sizeof(char16_t))) name = Utf16ToUtf8(chars, nameLen);
|
||
}
|
||
}
|
||
}
|
||
auto it2 = g_BackendData.players.find(playerId);
|
||
if (it2 != g_BackendData.players.end()) {
|
||
it2->second.name = name; it2->second.midAddr = midAddr;
|
||
it2->second.strPtr = strPtr; it2->second.nameLen = nameLen;
|
||
}
|
||
}
|
||
}
|
||
|
||
PlayerStatInfo statItem;
|
||
while (g_Tasks.money_queue.pop(statItem)) {
|
||
auto it = g_BackendData.objToPlayerId.find(statItem.obj);
|
||
if (it != g_BackendData.objToPlayerId.end()) g_BackendData.players[it->second].money = statItem.val;
|
||
}
|
||
while (g_Tasks.level_queue.pop(statItem)) {
|
||
auto it = g_BackendData.objToPlayerId.find(statItem.obj);
|
||
if (it != g_BackendData.objToPlayerId.end()) g_BackendData.players[it->second].level = statItem.val;
|
||
}
|
||
}
|
||
|
||
void BackgroundDataUpdateThread() {
|
||
while (!g_il2cpp_domain_get || !g_il2cpp_thread_attach || !g_il2cpp_thread_detach) {
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||
}
|
||
|
||
void* domain = g_il2cpp_domain_get();
|
||
|
||
while (true) {
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||
g_debug_bg_thread_count++;
|
||
|
||
if (g_il2cppBase == 0) continue;
|
||
|
||
// 【修复 2】:完全信任 hook_set_IsGameEnd 的判断,不再主动试探引发闪退!
|
||
if (!g_is_in_match.load(std::memory_order_acquire)) {
|
||
ProcessMainThreadTasks();
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||
continue;
|
||
}
|
||
|
||
void* current_thread_handle = g_il2cpp_thread_attach(domain);
|
||
|
||
EnsureChessBattleModel();
|
||
ProcessMainThreadTasks();
|
||
|
||
if (g_matchFetchRequested.load(std::memory_order_acquire)) {
|
||
if (std::chrono::steady_clock::now() >= g_matchFetchTime) {
|
||
g_matchFetchRequested.store(false, std::memory_order_relaxed);
|
||
g_matchFetchReady.store(true, std::memory_order_release);
|
||
}
|
||
}
|
||
if (g_matchFetchReady.load(std::memory_order_acquire)) {
|
||
g_matchFetchReady.store(false, std::memory_order_relaxed);
|
||
CheckReadyMatchFetch();
|
||
}
|
||
|
||
if (g_predict_hex && g_hexCfgObj != nullptr && !g_BackendData.hex_fetched) {
|
||
typedef int (*func_GetNextHexLevel_t)(void* obj, int index);
|
||
func_GetNextHexLevel_t GetNextHexLevel = (func_GetNextHexLevel_t)(g_il2cppBase + 0x4446EDc);
|
||
|
||
if (GetNextHexLevel) {
|
||
int tempHexes[5] = {0}, verifyHexes[5] = {0};
|
||
int checkCount = 5;
|
||
|
||
for (int i = 0; i < checkCount; i++) tempHexes[i] = GetNextHexLevel(g_hexCfgObj, i);
|
||
for (int i = 0; i < checkCount; i++) verifyHexes[i] = GetNextHexLevel(g_hexCfgObj, i);
|
||
|
||
bool match = true;
|
||
int validCount = 0;
|
||
|
||
for (int i = 0; i < checkCount; i++) {
|
||
if (tempHexes[i] != verifyHexes[i]) {
|
||
match = false;
|
||
break;
|
||
}
|
||
if (tempHexes[i] >= 1 && tempHexes[i] <= 3) {
|
||
validCount++;
|
||
}
|
||
}
|
||
|
||
if (!match) {
|
||
g_BackendData.validHexCount = 0;
|
||
}
|
||
else if (validCount > 0) {
|
||
int saveCount = (validCount > 4) ? 4 : validCount;
|
||
for (int i = 0; i < saveCount; i++) {
|
||
g_BackendData.cachedHexes[i] = tempHexes[i];
|
||
}
|
||
g_BackendData.validHexCount = saveCount;
|
||
g_BackendData.hex_fetched = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
UpdateShopAndBenchData();
|
||
UpdateCardPoolData();
|
||
if (g_predict_enemy) UpdateEnemyBoardState();
|
||
UpdateWarningData();
|
||
UpdatePlayerStreaks();
|
||
|
||
UpdateEquipAndLiveHeroes();
|
||
|
||
PushSnapshot();
|
||
|
||
ExecuteMainThreadAutoEquip();
|
||
|
||
if (g_Tasks.trigger_surrender.load(std::memory_order_acquire)) {
|
||
void* turnObj = g_turnStartThisObj;
|
||
int myId = g_BackendData.myPlayerId;
|
||
if (g_il2cppBase != 0 && turnObj != nullptr && myId != -1) {
|
||
if (IsValidPtr((uintptr_t)turnObj)) {
|
||
typedef void (*func_UserExit_t)(void* thisObj, int playerId, int surrenderFlag);
|
||
func_UserExit_t doUserExit = (func_UserExit_t)(g_il2cppBase + 0x3da60c0);
|
||
try { doUserExit(turnObj, myId, 1); } catch (...) {}
|
||
}
|
||
}
|
||
g_Tasks.trigger_surrender.store(false, std::memory_order_release);
|
||
g_Tasks.trigger_game_end.store(true, std::memory_order_release);
|
||
}
|
||
|
||
g_il2cpp_thread_detach(current_thread_handle);
|
||
}
|
||
}
|
||
|
||
typedef void (*f_SendWillRenderCanvases)();
|
||
f_SendWillRenderCanvases orig_SendWillRenderCanvases = nullptr;
|
||
|
||
void hook_SendWillRenderCanvases() {
|
||
if (orig_SendWillRenderCanvases) orig_SendWillRenderCanvases();
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return;
|
||
|
||
g_debug_SendWillRenderCanvases_count++;
|
||
ExecuteMainThreadAutoBuy();
|
||
ExecuteMainThreadAutoRefresh();
|
||
}
|
||
|
||
typedef void* (*func_4848cec_t)(void* x0, void* x1, void* x2, void* x3);
|
||
func_4848cec_t orig_4848cec = nullptr;
|
||
|
||
void* hook_4848cec(void* x0, void* arg1, void* arg2, void* arg3) {
|
||
if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_4848cec(x0, arg1, arg2, arg3);
|
||
if (x0 != nullptr) g_refresh_instance = (uintptr_t)x0;
|
||
return orig_4848cec(x0, arg1, arg2, arg3);
|
||
}
|
||
|
||
void InitBusinessHooks() {
|
||
while (g_il2cppBase == 0) {
|
||
FILE *fp = fopen("/proc/self/maps", "r");
|
||
if (fp) {
|
||
char line[512];
|
||
while (fgets(line, sizeof(line), fp)) {
|
||
if (strstr(line, "libil2cpp.so") && strstr(line, "r-xp")) {
|
||
ssscanf(line, "%lx", &g_il2cppBase); break;
|
||
}
|
||
}
|
||
fclose(fp);
|
||
}
|
||
if (g_il2cppBase == 0) sleep(1);
|
||
}
|
||
|
||
void* handle = dlopen("libil2cpp.so", RTLD_LAZY);
|
||
if (handle) {
|
||
g_il2cpp_domain_get = (il2cpp_domain_get_t)dlsym(handle, "il2cpp_domain_get");
|
||
g_il2cpp_thread_attach = (il2cpp_thread_attach_t)dlsym(handle, "il2cpp_thread_attach");
|
||
g_il2cpp_thread_detach = (il2cpp_thread_detach_t)dlsym(handle, "il2cpp_thread_detach");
|
||
g_il2cpp_string_new = (il2cpp_string_new_t)dlsym(handle, "il2cpp_string_new");
|
||
}
|
||
|
||
DobbyHook((void*)(g_il2cppBase + 0x5277718), (void*)hook_LoadAsset_718, (void**)&orig_LoadAsset_718);
|
||
DobbyHook((void*)(g_il2cppBase + 0x470f4b0), (void*)hook_LoadMapImpl, (void**)&orig_LoadMapImpl);
|
||
|
||
DobbyHook((void*)(g_il2cppBase + 0x46cce08), (void*)hook_46cce08, (void**)&orig_46cce08);
|
||
g_reqbuyhero = (func_reqbuyhero_t)(g_il2cppBase + 0x46d6e94);
|
||
DobbyHook((void*)(g_il2cppBase + 0x4848cec), (void*)hook_4848cec, (void**)&orig_4848cec);
|
||
g_req_refresh = (func_req_refresh_t)(g_il2cppBase + 0x483fce4);
|
||
DobbyHook((void*)(g_il2cppBase + 0xAE25E80), (void*)hook_SendWillRenderCanvases, (void**)&orig_SendWillRenderCanvases);
|
||
DobbyHook((void*)(g_il2cppBase + 0x3Cd6058), (void*)hook_3Cd6058, (void**)&orig_3Cd6058);
|
||
DobbyHook((void*)(g_il2cppBase + 0x3cd3f80), (void*)hook_set_Money, (void**)&orig_set_Money);
|
||
DobbyHook((void*)(g_il2cppBase + 0x3cd2d18), (void*)hook_set_Level, (void**)&orig_set_Level);
|
||
DobbyHook((void*)(g_il2cppBase + 0x3d7c338), (void*)hook_TurnStart, (void**)&orig_TurnStart);
|
||
DobbyHook((void*)(g_il2cppBase + 0x3fc3c70), (void*)hook_set_IsGameEnd, (void**)&orig_set_IsGameEnd);
|
||
DobbyHook((void*)(g_il2cppBase + 0x3f7f704), (void*)hook_UpdateTurnStart, (void**)&orig_UpdateTurnStart);
|
||
DobbyHook((void*)(g_il2cppBase + 0x4440520), (void*)hook_InitTurnDropCfg, (void**)&orig_InitTurnDropCfg);
|
||
DobbyHook((void*)(g_il2cppBase + 0x6A67e48), (void*)hook_6A67e48, (void**)&orig_6A67e48);
|
||
|
||
std::thread(BackgroundDataUpdateThread).detach();
|
||
}
|
||
|
||
void LoadConfig() {
|
||
std::ifstream in(g_configPath);
|
||
if (!in.is_open()) return;
|
||
std::string line;
|
||
while (std::getline(in, line)) {
|
||
size_t pos = line.find('=');
|
||
if (pos == std::string::npos) continue;
|
||
std::string k = line.substr(0, pos), v = line.substr(pos + 1);
|
||
if (k == "playersX") { g_level_X = std::stof(v); g_warn_X = std::stof(v) + 150.0f * g_autoScale; continue; }
|
||
if (k == "playersY") { g_level_Y = std::stof(v); g_warn_Y = std::stof(v); continue; }
|
||
for (const auto& entry : g_configTable) {
|
||
if (k == entry.key) {
|
||
try {
|
||
if (entry.type == C_BOOL) *(bool*)entry.ptr = (v == "1");
|
||
else if (entry.type == C_INT) *(int*)entry.ptr = std::stoi(v);
|
||
else if (entry.type == C_FLOAT) *(float*)entry.ptr = std::stof(v);
|
||
else if (entry.type == C_STRING) *(std::string*)entry.ptr = v;
|
||
} catch (...) {}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
in.close();
|
||
|
||
g_auto_buy_heroes.clear();
|
||
std::stringstream ss(g_auto_buy_heroes_str);
|
||
std::string item;
|
||
while (std::getline(ss, item, ',')) {
|
||
if (!item.empty()) { try { g_auto_buy_heroes.insert(std::stoi(item)); } catch (...) {} }
|
||
}
|
||
|
||
g_hero_equip_bindings.clear();
|
||
std::stringstream ss_binds(g_hero_equip_bindings_str);
|
||
std::string item_bind;
|
||
while(std::getline(ss_binds, item_bind, ';')) {
|
||
if(item_bind.empty()) continue;
|
||
size_t colon_pos = item_bind.find(':');
|
||
if(colon_pos != std::string::npos) {
|
||
try {
|
||
int hId = std::stoi(item_bind.substr(0, colon_pos));
|
||
std::string eqs = item_bind.substr(colon_pos + 1);
|
||
std::stringstream ss_eqs(eqs);
|
||
std::string eq;
|
||
while(std::getline(ss_eqs, eq, ',')) {
|
||
if(!eq.empty()) g_hero_equip_bindings[hId].push_back(std::stoi(eq));
|
||
}
|
||
} catch (...) {}
|
||
}
|
||
}
|
||
|
||
g_hero_equip_priority.clear();
|
||
std::stringstream ss_prio(g_hero_equip_priority_str);
|
||
std::string item_prio;
|
||
while(std::getline(ss_prio, item_prio, ';')) {
|
||
if(item_prio.empty()) continue;
|
||
size_t colon_pos = item_prio.find(':');
|
||
if(colon_pos != std::string::npos) {
|
||
try {
|
||
int hId = std::stoi(item_prio.substr(0, colon_pos));
|
||
int prio = std::stoi(item_prio.substr(colon_pos + 1));
|
||
g_hero_equip_priority[hId] = prio;
|
||
} catch (...) {}
|
||
}
|
||
}
|
||
|
||
// [格子独立记录] 坐标读取
|
||
g_cell_equip_offset.clear();
|
||
std::stringstream ss_eqOff(g_cell_equip_offset_str);
|
||
std::string item_eqOff;
|
||
while(std::getline(ss_eqOff, item_eqOff, ';')) {
|
||
if(item_eqOff.empty()) continue;
|
||
size_t colon = item_eqOff.find(':');
|
||
size_t comma = item_eqOff.find(',');
|
||
if(colon != std::string::npos && comma != std::string::npos) {
|
||
try {
|
||
int cIdx = std::stoi(item_eqOff.substr(0, colon));
|
||
float x = std::stof(item_eqOff.substr(colon + 1, comma - colon - 1));
|
||
float y = std::stof(item_eqOff.substr(comma + 1));
|
||
g_cell_equip_offset[cIdx] = ImVec2(x, y);
|
||
} catch(...) {}
|
||
}
|
||
}
|
||
|
||
g_cell_sell_offset.clear();
|
||
std::stringstream ss_sellOff(g_cell_sell_offset_str);
|
||
std::string item_sellOff;
|
||
while(std::getline(ss_sellOff, item_sellOff, ';')) {
|
||
if(item_sellOff.empty()) continue;
|
||
size_t colon = item_sellOff.find(':');
|
||
size_t comma = item_sellOff.find(',');
|
||
if(colon != std::string::npos && comma != std::string::npos) {
|
||
try {
|
||
int cIdx = std::stoi(item_sellOff.substr(0, colon));
|
||
float x = std::stof(item_sellOff.substr(colon + 1, comma - colon - 1));
|
||
float y = std::stof(item_sellOff.substr(comma + 1));
|
||
g_cell_sell_offset[cIdx] = ImVec2(x, y);
|
||
} catch(...) {}
|
||
}
|
||
}
|
||
|
||
g_anim[1] = g_predict_enemy ? 1.0f : 0.0f; g_anim[2] = g_predict_hex ? 1.0f : 0.0f;
|
||
g_anim[3] = g_esp_board ? 1.0f : 0.0f;
|
||
g_anim[5] = g_esp_enemy_bench ? 1.0f : 0.0f;
|
||
g_anim[7] = g_show_instant_quit_window ? 1.0f : 0.0f;
|
||
g_anim[8] = g_boardLocked ? 1.0f : 0.0f; g_anim[9] = g_esp_level ? 1.0f : 0.0f;
|
||
g_anim[10] = g_show_card_pool ? 1.0f : 0.0f; g_anim[11] = g_card_warning ? 1.0f : 0.0f;
|
||
g_anim[12] = g_show_debug_window ? 1.0f : 0.0f; g_anim[17] = g_pool_warning_enable ? 1.0f : 0.0f;
|
||
g_anim[18] = g_enable_skin ? 1.0f : 0.0f;
|
||
g_anim[19] = g_enable_map_skin ? 1.0f : 0.0f;
|
||
g_anim[20] = g_show_hero_list_window ? 1.0f : 0.0f;
|
||
g_anim[23] = g_show_sell_window ? 1.0f : 0.0f;
|
||
g_anim[24] = g_show_clear_bench_btn ? 1.0f : 0.0f;
|
||
g_anim[25] = g_show_equip_ui_debug ? 1.0f : 0.0f;
|
||
g_anim[26] = g_show_sell_ui_debug ? 1.0f : 0.0f;
|
||
g_needUpdateFontSafe = true;
|
||
}
|
||
|
||
// =================================================================
|
||
// 绘制基础 UI 模块
|
||
// =================================================================
|
||
void UpdateFontHD(bool force = false) {
|
||
ImGuiIO& io = ImGui::GetIO();
|
||
float screenH = (io.DisplaySize.y > 100.0f) ? io.DisplaySize.y : 2400.0f;
|
||
g_autoScale = screenH / 1080.0f;
|
||
float targetSize = std::clamp(20.0f * g_autoScale, 16.0f, 45.0f);
|
||
if (!force && std::abs(targetSize - g_current_rendered_size) < 2.0f) return;
|
||
|
||
ImGui_ImplOpenGL3_DestroyDeviceObjects();
|
||
io.Fonts->Clear();
|
||
g_mainFont = nullptr;
|
||
g_hugeNumFont = nullptr;
|
||
|
||
ImFontConfig configMain; configMain.OversampleH = 2; configMain.OversampleV = 2; configMain.PixelSnapH = false;
|
||
ImFontConfig configNum; configNum.OversampleH = 2; configNum.OversampleV = 2; configNum.PixelSnapH = false;
|
||
|
||
float mainResFactor = 1.5f;
|
||
float numResFactor = (targetSize * 4.0f > 200.0f) ? (200.0f / targetSize) : 4.0f;
|
||
static const ImWchar numRanges[] = { 0x0020, 0x00FF, 0 };
|
||
const char* fonts[] = { "/system/fonts/Miui-Regular.ttf", "/system/fonts/SysSans-Hans-Regular.ttf", "/system/fonts/NotoSansCJK-Regular.ttc", "/system/fonts/DroidSansFallback.ttf" };
|
||
|
||
bool loaded = false;
|
||
for(const char* path : fonts) {
|
||
if (access(path, R_OK) == 0) {
|
||
g_mainFont = io.Fonts->AddFontFromFileTTF(path, targetSize * mainResFactor, &configMain, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
|
||
if (g_mainFont) g_mainFont->Scale = 1.0f / mainResFactor;
|
||
g_hugeNumFont = io.Fonts->AddFontFromFileTTF(path, targetSize * numResFactor, &configNum, numRanges);
|
||
if (g_hugeNumFont) g_hugeNumFont->Scale = 1.0f / numResFactor;
|
||
loaded = true;
|
||
break;
|
||
}
|
||
}
|
||
if(!loaded || !g_mainFont) {
|
||
g_mainFont = io.Fonts->AddFontDefault();
|
||
if (g_mainFont) g_mainFont->Scale = 1.0f / mainResFactor;
|
||
}
|
||
io.Fonts->Build();
|
||
ImGui_ImplOpenGL3_CreateDeviceObjects();
|
||
g_current_rendered_size = targetSize;
|
||
}
|
||
|
||
void HandleGridInteractionCore(
|
||
float& out_x, float& out_y, float& out_scaleX, float& out_scaleY, float& t_x, float& t_y, float& t_scaleX, float& t_scaleY,
|
||
bool& isDragging, bool& isScaling, ImVec2& dragOffset, ImVec2& scaleDragOffset,
|
||
float h_dx, float h_dy, float c_dx, float c_dy, float hitMinX, float hitMinY, float hitMaxX, float hitMaxY,
|
||
bool locked, bool* isOpen, bool enableScale, bool uniformScale)
|
||
{
|
||
ImGuiIO& io = ImGui::GetIO();
|
||
if (!locked) {
|
||
ImVec2 p_scale(out_x + h_dx * out_scaleX, out_y + h_dy * out_scaleY);
|
||
ImVec2 p_close(out_x + c_dx * out_scaleX, out_y + c_dy * out_scaleY);
|
||
if (ImGui::IsMouseClicked(0)) {
|
||
if (isOpen && ImLengthSqr(io.MousePos - p_close) < (1600.0f * g_autoScale * g_autoScale)) { *isOpen = false; return; }
|
||
else if (enableScale && ImLengthSqr(io.MousePos - p_scale) < (400.0f * g_autoScale * g_autoScale)) {
|
||
isScaling = true; scaleDragOffset = io.MousePos - ImVec2(t_x + h_dx * t_scaleX, t_y + h_dy * t_scaleY);
|
||
}
|
||
else if (ImRect(ImVec2(out_x + hitMinX * out_scaleX, out_y + hitMinY * out_scaleY), ImVec2(out_x + hitMaxX * out_scaleX, out_y + hitMaxY * out_scaleY)).Contains(io.MousePos)) {
|
||
isDragging = true; dragOffset = ImVec2(t_x - io.MousePos.x, t_y - io.MousePos.y);
|
||
}
|
||
}
|
||
if (isScaling && enableScale) {
|
||
if (ImGui::IsMouseDown(0)) {
|
||
ImVec2 tCenter = io.MousePos - scaleDragOffset;
|
||
if (uniformScale) {
|
||
float dx = tCenter.x - t_x;
|
||
float dy = tCenter.y - t_y;
|
||
float dist = sqrtf(dx * dx + dy * dy);
|
||
float baseDist = sqrtf(h_dx * h_dx + h_dy * h_dy);
|
||
t_scaleX = t_scaleY = std::clamp(dist / (baseDist > 0.01f ? baseDist : 0.01f), 0.05f, 10.0f);
|
||
} else {
|
||
t_scaleX = std::clamp((tCenter.x - t_x) / (h_dx > 0.01f ? h_dx : 0.01f), 0.05f, 10.0f);
|
||
t_scaleY = std::clamp((tCenter.y - t_y) / (h_dy > 0.01f ? h_dy : 0.01f), 0.05f, 10.0f);
|
||
}
|
||
} else isScaling = false;
|
||
}
|
||
if (isDragging && !isScaling) {
|
||
if (ImGui::IsMouseDown(0)) { t_x = io.MousePos.x + dragOffset.x; t_y = io.MousePos.y + dragOffset.y; }
|
||
else isDragging = false;
|
||
}
|
||
}
|
||
float smoothness = 1.0f - expf(-20.0f * io.DeltaTime);
|
||
out_x = ImLerp(out_x, t_x, smoothness); out_y = ImLerp(out_y, t_y, smoothness);
|
||
if (enableScale) { out_scaleX = ImLerp(out_scaleX, t_scaleX, smoothness); out_scaleY = uniformScale ? out_scaleX : ImLerp(out_scaleY, t_scaleY, smoothness); }
|
||
}
|
||
|
||
void HandleGridInteraction(float& ox, float& oy, float& os, float& tx, float& ty, float& ts, bool& d, bool& s, ImVec2& doff, ImVec2& soff, float hdx, float hdy, float cdx, float cdy, float mx, float my, float mX, float mY, bool l, bool* o, bool e) {
|
||
HandleGridInteractionCore(ox, oy, os, os, tx, ty, ts, ts, d, s, doff, soff, hdx, hdy, cdx, cdy, mx, my, mX, mY, l, o, e, true);
|
||
}
|
||
|
||
void HandleGridInteractionXY(float& ox, float& oy, float& osX, float& osY, float& tx, float& ty, float& tsX, float& tsY, bool& d, bool& s, ImVec2& doff, ImVec2& soff, float hdx, float hdy, float cdx, float cdy, float mx, float my, float mX, float mY, bool l, bool* o) {
|
||
HandleGridInteractionCore(ox, oy, osX, osY, tx, ty, tsX, tsY, d, s, doff, soff, hdx, hdy, cdx, cdy, mx, my, mX, mY, l, o, true, false);
|
||
}
|
||
|
||
void DrawScaleHandle(ImDrawList* d, ImVec2 p, bool isScaling) {
|
||
d->AddCircleFilled(p, 10.0f * g_autoScale, IM_COL32(255, 215, 0, 240));
|
||
d->AddCircleFilled(p, 4.0f * g_autoScale, isScaling ? IM_COL32(0, 255, 180, 255) : IM_COL32(255, 255, 255, 255));
|
||
d->AddCircle(p, 14.0f * g_autoScale, IM_COL32(255, 215, 0, 150), 32, 2.0f * g_autoScale);
|
||
}
|
||
|
||
void DrawCloseHandle(ImDrawList* d, ImVec2 p, bool* isOpen) {
|
||
if (!isOpen) return;
|
||
ImGuiIO& io = ImGui::GetIO(); float cr = 13.0f * g_autoScale;
|
||
static std::unordered_map<void*, float> hover_map;
|
||
float cha = (hover_map[isOpen] = ImLerp(hover_map[isOpen], ImLengthSqr(io.MousePos - p) < (cr*cr * 2.5f) ? 1.0f : 0.0f, 1.0f - expf(-15.0f * io.DeltaTime)));
|
||
d->AddCircleFilled(p, cr, IM_COL32(200 + 55*cha, 50, 50, 200 + 55*cha));
|
||
d->AddLine(p - ImVec2(cr*0.35f, cr*0.35f), p + ImVec2(cr*0.35f, cr*0.35f), IM_COL32_WHITE, 2.5f * g_autoScale);
|
||
d->AddLine(p + ImVec2(cr*0.35f, -cr*0.35f), p - ImVec2(cr*0.35f, -cr*0.35f), IM_COL32_WHITE, 2.5f * g_autoScale);
|
||
}
|
||
|
||
void ColoredSeparator() {
|
||
ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(0.0f, 0.85f, 0.55f, 0.6f));
|
||
ImGui::Separator();
|
||
ImGui::PopStyleColor();
|
||
}
|
||
|
||
template <typename T> void ModernAdjusterInternal(const char* label, T* v, T v_min, T v_max, T step, const char* format) {
|
||
ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushID(label);
|
||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() * 2.2f + style.ItemInnerSpacing.x); ImGui::Text("%s", label); ImGui::SameLine();
|
||
|
||
float btn_sz = ImGui::GetFrameHeight();
|
||
float val_w = 40.0f * g_autoScale * g_scale;
|
||
|
||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 15.0f * g_autoScale * g_scale);
|
||
|
||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.25f, 0.3f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * g_autoScale);
|
||
ImGui::PushButtonRepeat(true);
|
||
if (ImGui::Button("-", ImVec2(btn_sz, btn_sz))) if (*v > v_min) *v -= step; ImGui::SameLine();
|
||
char buf[32]; std::is_same<T, int>::value ? snprintf(buf, sizeof(buf), format, *v) : snprintf(buf, sizeof(buf), format, (float)(*v));
|
||
ImVec2 t_sz = ImGui::CalcTextSize(buf); ImVec2 val_pos = win->DC.CursorPos;
|
||
win->DrawList->AddRectFilled(val_pos, val_pos + ImVec2(val_w, btn_sz), IM_COL32(20, 25, 30, 255), 4.0f * g_autoScale * g_scale);
|
||
win->DrawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), val_pos + ImVec2((val_w - t_sz.x)*0.5f, (btn_sz - t_sz.y)*0.5f), IM_COL32(0, 255, 180, 255), buf); ImGui::Dummy(ImVec2(val_w, btn_sz)); ImGui::SameLine();
|
||
if (ImGui::Button("+", ImVec2(btn_sz, btn_sz))) if (*v < v_max) *v += step;
|
||
ImGui::PopButtonRepeat();
|
||
ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::PopID();
|
||
}
|
||
|
||
void ModernNumberAdjuster(const char* label, int* v, int v_min, int v_max) { ModernAdjusterInternal<int>(label, v, v_min, v_max, 1, "%d"); }
|
||
void ModernFloatAdjuster(const char* label, float* v, float v_min, float v_max, float step) { ModernAdjusterInternal<float>(label, v, v_min, v_max, step, "%.1f"); }
|
||
|
||
void ModernSlider(const char* label, float* v, float v_min, float v_max) {
|
||
ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushID(label);
|
||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() * 2.2f + style.ItemInnerSpacing.x); ImGui::Text("%s", label); ImGui::SameLine();
|
||
|
||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 15.0f * g_autoScale * g_scale);
|
||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.18f, 0.22f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.20f, 0.25f, 0.30f, 1.0f));
|
||
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.0f, 0.85f, 0.55f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.0f, 0.95f, 0.65f, 1.0f));
|
||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * g_autoScale);
|
||
|
||
ImGui::PushItemWidth(180.0f * g_autoScale * g_scale);
|
||
ImGui::SliderFloat("##sl", v, v_min, v_max, "%.2f");
|
||
ImGui::PopItemWidth(); ImGui::PopStyleVar(); ImGui::PopStyleColor(4); ImGui::PopID();
|
||
}
|
||
|
||
bool ModernToggle(const char* label, bool* v, int idx) {
|
||
ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle(); ImGuiID id = win->GetID(label);
|
||
float h = ImGui::GetFrameHeight(); float w = h * 2.2f; const ImRect bb(win->DC.CursorPos, win->DC.CursorPos + ImVec2(w + style.ItemInnerSpacing.x + ImGui::CalcTextSize(label).x, h));
|
||
ImGui::ItemSize(bb, style.FramePadding.y); bool pressed = false;
|
||
if (ImGui::ItemAdd(bb, id)) { bool hovered, held; if ((pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held))) *v = !(*v); }
|
||
g_anim[idx] = ImLerp(g_anim[idx], *v ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime));
|
||
win->DrawList->AddRectFilled(bb.Min, bb.Min + ImVec2(w, h), ImGui::GetColorU32(ImLerp(ImVec4(0.2f, 0.22f, 0.27f, 1.0f), ImVec4(0.0f, 0.85f, 0.55f, 1.0f), g_anim[idx])), h*0.5f);
|
||
win->DrawList->AddRect(bb.Min, bb.Min + ImVec2(w, h), IM_COL32(0, 0, 0, 80), h*0.5f, 0, 1.0f);
|
||
ImVec2 hc = bb.Min + ImVec2(h*0.5f + g_anim[idx]*(w-h), h*0.5f);
|
||
win->DrawList->AddCircleFilled(hc + ImVec2(0, 1.5f), h*0.5f - 2.5f, IM_COL32(0, 0, 0, 90)); win->DrawList->AddCircleFilled(hc, h*0.5f - 2.5f, IM_COL32_WHITE);
|
||
ImGui::RenderText(ImVec2(bb.Min.x + w + style.ItemInnerSpacing.x, bb.Min.y + (h - ImGui::GetFontSize()) * 0.5f), label);
|
||
return pressed;
|
||
}
|
||
|
||
bool ModernAnimatedFolder(const char* label, bool* state) {
|
||
ImGuiWindow* win = ImGui::GetCurrentWindow(); ImGuiID id = win->GetID(label); ImVec2 pos = win->DC.CursorPos; ImVec2 size(ImGui::GetContentRegionAvail().x, ImGui::GetFrameHeight() * 1.3f);
|
||
const ImRect bb(pos, pos + size); ImGui::ItemSize(bb); bool hovered = false, held = false;
|
||
if (ImGui::ItemAdd(bb, id)) if (ImGui::ButtonBehavior(bb, id, &hovered, &held)) *state = !(*state);
|
||
float* p_anim = win->StateStorage.GetFloatRef(id, *state ? 1.0f : 0.0f); float anim = (*p_anim = ImLerp(*p_anim, *state ? 1.0f : 0.0f, 1.0f - expf(-18.0f * ImGui::GetIO().DeltaTime)));
|
||
win->DrawList->AddRectFilled(bb.Min, bb.Max, hovered ? IM_COL32(50, 60, 75, 200) : IM_COL32(40, 48, 60, 150), 8.0f * g_autoScale * g_scale);
|
||
float cx = bb.Min.x + 15.0f * g_autoScale * g_scale; float cy = bb.Min.y + size.y * 0.5f; float ang = anim * 1.5708f; float arr = 5.0f * g_autoScale * g_scale;
|
||
win->DrawList->AddTriangleFilled(ImVec2(cx+cosf(ang)*arr, cy+sinf(ang)*arr), ImVec2(cx+cosf(ang+2.094f)*arr, cy+sinf(ang+2.094f)*arr), ImVec2(cx+cosf(ang-2.094f)*arr, cy+sinf(ang-2.094f)*arr), IM_COL32(200, 200, 200, 255));
|
||
ImFont* font = ImGui::GetFont(); float fSz = ImGui::GetFontSize();
|
||
win->DrawList->AddText(font, fSz, ImVec2(cx + 25.0f * g_autoScale * g_scale, bb.Min.y + (size.y - fSz)*0.5f), IM_COL32_WHITE, label);
|
||
if (*state) { ImGui::Indent(15.0f * g_autoScale * g_scale); return true; } return false;
|
||
}
|
||
|
||
void EndModernAnimatedFolder() { ImGui::Unindent(15.0f * g_autoScale * g_scale); }
|
||
|
||
void ModernTierSelector(const char* label, bool* tierArray) {
|
||
ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle();
|
||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() * 2.2f + style.ItemInnerSpacing.x); ImGui::Text("%s", label); ImGui::SameLine();
|
||
for (int i = 1; i <= 5; i++) {
|
||
if (i > 1) ImGui::SameLine(0, 5.0f * g_autoScale);
|
||
ImVec2 pos = win->DC.CursorPos; ImRect bb(pos, pos + ImVec2(ImGui::GetFrameHeight()*1.2f, ImGui::GetFrameHeight())); ImGui::ItemSize(bb);
|
||
if (ImGui::ItemAdd(bb, win->GetID(tierArray + i))) { bool h, he; if (ImGui::ButtonBehavior(bb, win->GetID(tierArray+i), &h, &he)) tierArray[i] = !tierArray[i]; }
|
||
static std::unordered_map<void*, float> anims_map; float& a = (anims_map[tierArray + i] = ImLerp(anims_map[tierArray + i], tierArray[i] ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)));
|
||
win->DrawList->AddRectFilled(bb.Min, bb.Max, IM_COL32((int)(30+70*a), (int)(35+100*a), (int)(45+50*a), 255), 4.0f * g_autoScale);
|
||
if (a > 0.01f) win->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255, 200, 50, (int)(200*a)), 4.0f*g_autoScale, 0, 1.5f*g_autoScale);
|
||
char buf[4]; snprintf(buf, sizeof(buf), "%d", i); ImVec2 t_sz = ImGui::CalcTextSize(buf);
|
||
win->DrawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), pos + ImVec2((bb.GetWidth() - t_sz.x)*0.5f, (bb.GetHeight() - t_sz.y)*0.5f), tierArray[i] ? IM_COL32(0,0,0,255) : IM_COL32_WHITE, buf);
|
||
}
|
||
}
|
||
|
||
// =================================================================
|
||
// 绘制 ESP / 功能模块
|
||
// =================================================================
|
||
void DrawSwipePaths() {
|
||
if (!g_show_swipe_visuals) return;
|
||
std::lock_guard<std::mutex> lock(g_swipe_mutex);
|
||
if (g_active_swipes.empty()) return;
|
||
|
||
ImDrawList* d = ImGui::GetForegroundDrawList();
|
||
float dt = ImGui::GetIO().DeltaTime;
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
|
||
for (auto it = g_active_swipes.begin(); it != g_active_swipes.end(); ) {
|
||
it->lifeTime -= dt;
|
||
if (it->lifeTime <= 0.0f) {
|
||
it = g_active_swipes.erase(it);
|
||
continue;
|
||
}
|
||
|
||
float alpha = it->lifeTime / it->maxLife;
|
||
ImU32 lineColor = IM_COL32(255, 200, 0, (int)(255 * alpha));
|
||
|
||
d->AddLine(it->start, it->end, lineColor, 3.0f * g_autoScale);
|
||
|
||
d->AddCircleFilled(it->start, 12.0f * g_autoScale, IM_COL32(0, 255, 100, (int)(200 * alpha)));
|
||
d->AddCircle(it->start, 16.0f * g_autoScale, IM_COL32(255, 255, 255, (int)(255 * alpha)), 0, 2.0f * g_autoScale);
|
||
|
||
d->AddCircleFilled(it->end, 12.0f * g_autoScale, IM_COL32(255, 50, 50, (int)(200 * alpha)));
|
||
d->AddCircle(it->end, 16.0f * g_autoScale, IM_COL32(255, 255, 255, (int)(255 * alpha)), 0, 2.0f * g_autoScale);
|
||
|
||
float progress = 1.0f - (it->lifeTime / it->maxLife);
|
||
ImVec2 currentPos = ImVec2(it->start.x + (it->end.x - it->start.x) * progress,
|
||
it->start.y + (it->end.y - it->start.y) * progress);
|
||
d->AddCircleFilled(currentPos, 8.0f * g_autoScale, IM_COL32(255, 255, 255, (int)(255 * alpha)));
|
||
|
||
DrawTextWithStroke(d, font, 18.0f * g_autoScale, it->end + ImVec2(15, 15), IM_COL32(255, 255, 255, (int)(255*alpha)), IM_COL32(0,0,0,(int)(255*alpha)), "滑动释放");
|
||
|
||
++it;
|
||
}
|
||
}
|
||
|
||
void DrawEquipUIDebug() {
|
||
bool show_equip = g_show_equip_ui_debug;
|
||
bool show_sell = g_show_sell_ui_debug;
|
||
|
||
if (!show_equip && !show_sell) return;
|
||
|
||
ImDrawList* d = ImGui::GetForegroundDrawList();
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
ImVec2 mouse = ImGui::GetIO().MousePos;
|
||
bool mouseDown = ImGui::IsMouseDown(0);
|
||
bool mouseClicked = ImGui::IsMouseClicked(0);
|
||
|
||
static int dragging_idx = -1; // 0: 穿装绿圈, 1: 卖牌粉圈(起点), 2: 卖牌红圈(落点)
|
||
static int dragging_cell_idx = -1; // 正在拖拽的独立格子索引
|
||
static ImVec2 drag_start_mouse;
|
||
static float drag_start_off_x = 0;
|
||
static float drag_start_off_y = 0;
|
||
|
||
// 松手时自动保存配置
|
||
if (!mouseDown && dragging_idx != -1) {
|
||
dragging_idx = -1;
|
||
dragging_cell_idx = -1;
|
||
g_save_timer = 1.5f;
|
||
std::thread([]() { SaveConfig(); }).detach();
|
||
}
|
||
|
||
auto snap = GetSnapshot();
|
||
float hitRadius = 45.0f * g_autoScale; // 触摸判定范围(大,防断触)
|
||
float drawRadius = 10.0f * g_autoScale; // 实际画的圆圈尺寸
|
||
float dummyX = g_gl_width * 0.5f;
|
||
float dummyY = g_gl_height * 0.5f;
|
||
|
||
std::vector<LiveHeroData> allHeroData;
|
||
if (snap) {
|
||
for (const auto& hero : snap->myLiveHeroes) {
|
||
if (hero.screenPos.z > 0.01f) allHeroData.push_back(hero);
|
||
}
|
||
for (const auto& hero : snap->myBenchLiveHeroes) {
|
||
if (hero.screenPos.z > 0.01f) allHeroData.push_back(hero);
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 【核心修复 3:新增装备席物理坐标透视】
|
||
// ==========================================
|
||
if (show_equip) {
|
||
for (int i = 0; i < 20; i++) {
|
||
int col = i / 10;
|
||
int row = i % 10;
|
||
float startX = (g_equip_ui_start_x + col * g_equip_ui_step_x) * g_autoScale;
|
||
float startY = (g_equip_ui_start_y + row * g_equip_ui_step_y) * g_autoScale;
|
||
|
||
ImVec2 pos(startX, startY);
|
||
d->AddCircleFilled(pos, 15.0f * g_autoScale, IM_COL32(0, 200, 255, 80));
|
||
d->AddCircle(pos, 15.0f * g_autoScale, IM_COL32(0, 220, 255, 255), 0, 2.0f * g_autoScale);
|
||
|
||
char buf[16]; snprintf(buf, sizeof(buf), "槽 %d", i);
|
||
d->AddText(font, 12.0f * g_autoScale, pos - ImVec2(10 * g_autoScale, 8 * g_autoScale), IM_COL32(255, 255, 255, 255), buf);
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 1. 穿拆装备可视化 (各个格子独立偏移,深绿色)
|
||
// ==========================================
|
||
if (show_equip) {
|
||
if (allHeroData.empty()) {
|
||
if (g_cell_equip_offset.find(0) == g_cell_equip_offset.end()) g_cell_equip_offset[0] = ImVec2(0.0f, -80.0f);
|
||
ImVec2 targetPos(dummyX + g_cell_equip_offset[0].x * g_autoScale, dummyY + g_cell_equip_offset[0].y * g_autoScale);
|
||
|
||
if (!g_boardLocked && mouseClicked && dragging_idx == -1 && ImLengthSqr(mouse - targetPos) < hitRadius * hitRadius) {
|
||
dragging_idx = 0; dragging_cell_idx = 0; drag_start_mouse = mouse;
|
||
drag_start_off_x = g_cell_equip_offset[0].x; drag_start_off_y = g_cell_equip_offset[0].y;
|
||
}
|
||
if (mouseDown && dragging_idx == 0 && dragging_cell_idx == 0) {
|
||
g_cell_equip_offset[0].x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale;
|
||
g_cell_equip_offset[0].y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale;
|
||
targetPos = ImVec2(dummyX + g_cell_equip_offset[0].x * g_autoScale, dummyY + g_cell_equip_offset[0].y * g_autoScale);
|
||
}
|
||
d->AddCircleFilled(ImVec2(dummyX, dummyY), 4.0f * g_autoScale, IM_COL32(255,255,255,150));
|
||
d->AddText(font, 14.0f*g_autoScale, ImVec2(dummyX-30, dummyY-20), IM_COL32(255,255,255,150), "假人");
|
||
d->AddLine(ImVec2(dummyX, dummyY), targetPos, IM_COL32(0, 160, 0, 100), 2.0f * g_autoScale);
|
||
d->AddCircleFilled(targetPos, drawRadius, IM_COL32(0, 160, 0, 230));
|
||
d->AddCircle(targetPos, drawRadius + 1.5f, IM_COL32(0, 220, 0, 255), 0, 1.5f * g_autoScale);
|
||
d->AddText(font, 14.0f*g_autoScale, targetPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(0, 220, 0, 255), "格子0 穿装落点");
|
||
} else {
|
||
// 点击判定
|
||
if (!g_boardLocked && mouseClicked && dragging_idx == -1) {
|
||
for (const auto& hero : allHeroData) {
|
||
if (g_cell_equip_offset.find(hero.cellIndex) == g_cell_equip_offset.end()) g_cell_equip_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f);
|
||
ImVec2 targetPos(hero.screenPos.x + g_cell_equip_offset[hero.cellIndex].x * g_autoScale, g_gl_height - hero.screenPos.y + g_cell_equip_offset[hero.cellIndex].y * g_autoScale);
|
||
if (ImLengthSqr(mouse - targetPos) < hitRadius * hitRadius) {
|
||
dragging_idx = 0; dragging_cell_idx = hero.cellIndex; drag_start_mouse = mouse;
|
||
drag_start_off_x = g_cell_equip_offset[hero.cellIndex].x; drag_start_off_y = g_cell_equip_offset[hero.cellIndex].y;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
// 拖拽应用
|
||
if (mouseDown && dragging_idx == 0 && dragging_cell_idx != -1) {
|
||
g_cell_equip_offset[dragging_cell_idx].x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale;
|
||
g_cell_equip_offset[dragging_cell_idx].y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale;
|
||
}
|
||
// 渲染
|
||
std::unordered_set<int> drawn_cells;
|
||
for (const auto& hero : allHeroData) {
|
||
if (drawn_cells.count(hero.cellIndex)) continue;
|
||
drawn_cells.insert(hero.cellIndex);
|
||
if (g_cell_equip_offset.find(hero.cellIndex) == g_cell_equip_offset.end()) g_cell_equip_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f);
|
||
|
||
ImVec2 basePos(hero.screenPos.x, g_gl_height - hero.screenPos.y);
|
||
ImVec2 targetPos(basePos.x + g_cell_equip_offset[hero.cellIndex].x * g_autoScale, basePos.y + g_cell_equip_offset[hero.cellIndex].y * g_autoScale);
|
||
|
||
d->AddLine(basePos, targetPos, IM_COL32(0, 160, 0, 100), 2.0f * g_autoScale);
|
||
d->AddCircleFilled(targetPos, drawRadius, IM_COL32(0, 160, 0, 230));
|
||
d->AddCircle(targetPos, drawRadius + 1.5f, IM_COL32(0, 220, 0, 255), 0, 1.5f * g_autoScale);
|
||
char buf[32]; snprintf(buf, sizeof(buf), "格子%d穿装落点", hero.cellIndex);
|
||
d->AddText(font, 14.0f*g_autoScale, targetPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(0, 220, 0, 255), buf);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 2. 卖出英雄可视化 (各个格子独立起点 -> 统一全局落点)
|
||
// ==========================================
|
||
if (show_sell) {
|
||
ImVec2 dropPos(g_sell_drop_x * g_autoScale, g_sell_drop_y * g_autoScale);
|
||
|
||
// 点击判定
|
||
if (!g_boardLocked && mouseClicked && dragging_idx == -1) {
|
||
if (ImLengthSqr(mouse - dropPos) < hitRadius * hitRadius) { // 优先点红圈
|
||
dragging_idx = 2; drag_start_mouse = mouse;
|
||
drag_start_off_x = g_sell_drop_x; drag_start_off_y = g_sell_drop_y;
|
||
} else {
|
||
for (const auto& hero : allHeroData) {
|
||
if (g_cell_sell_offset.find(hero.cellIndex) == g_cell_sell_offset.end()) g_cell_sell_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f);
|
||
ImVec2 grabPos(hero.screenPos.x + g_cell_sell_offset[hero.cellIndex].x * g_autoScale, g_gl_height - hero.screenPos.y + g_cell_sell_offset[hero.cellIndex].y * g_autoScale);
|
||
if (ImLengthSqr(mouse - grabPos) < hitRadius * hitRadius) {
|
||
dragging_idx = 1; dragging_cell_idx = hero.cellIndex; drag_start_mouse = mouse;
|
||
drag_start_off_x = g_cell_sell_offset[hero.cellIndex].x; drag_start_off_y = g_cell_sell_offset[hero.cellIndex].y;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
// 假人补充逻辑
|
||
if (dragging_idx == -1 && allHeroData.empty()) {
|
||
if (g_cell_sell_offset.find(0) == g_cell_sell_offset.end()) g_cell_sell_offset[0] = ImVec2(0.0f, -80.0f);
|
||
ImVec2 grabPos(dummyX + g_cell_sell_offset[0].x * g_autoScale, dummyY + g_cell_sell_offset[0].y * g_autoScale);
|
||
if (ImLengthSqr(mouse - grabPos) < hitRadius * hitRadius) {
|
||
dragging_idx = 1; dragging_cell_idx = 0; drag_start_mouse = mouse; drag_start_off_x = g_cell_sell_offset[0].x; drag_start_off_y = g_cell_sell_offset[0].y;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 拖动计算
|
||
if (mouseDown) {
|
||
if (dragging_idx == 1 && dragging_cell_idx != -1) { // 拖动独立格子起滑点
|
||
g_cell_sell_offset[dragging_cell_idx].x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale;
|
||
g_cell_sell_offset[dragging_cell_idx].y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale;
|
||
} else if (dragging_idx == 2) { // 拖动全局落点
|
||
g_sell_drop_x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale;
|
||
g_sell_drop_y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale;
|
||
dropPos = ImVec2(g_sell_drop_x * g_autoScale, g_sell_drop_y * g_autoScale);
|
||
}
|
||
}
|
||
|
||
// 渲染假人
|
||
if (allHeroData.empty()) {
|
||
ImVec2 grabPos(dummyX + g_cell_sell_offset[0].x * g_autoScale, dummyY + g_cell_sell_offset[0].y * g_autoScale);
|
||
d->AddCircleFilled(ImVec2(dummyX, dummyY), 4.0f * g_autoScale, IM_COL32(255,255,255,150));
|
||
d->AddLine(grabPos, dropPos, IM_COL32(160, 0, 160, 150), 1.5f * g_autoScale);
|
||
d->AddCircleFilled(grabPos, drawRadius, IM_COL32(160, 0, 160, 230));
|
||
d->AddCircle(grabPos, drawRadius + 1.5f, IM_COL32(220, 50, 220, 255), 0, 1.5f * g_autoScale);
|
||
d->AddText(font, 14.0f*g_autoScale, grabPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(220, 50, 220, 255), "格子0卖出起滑");
|
||
}
|
||
|
||
// 画单一落点 (深红圈)
|
||
d->AddCircleFilled(dropPos, drawRadius * 1.5f, IM_COL32(180, 0, 0, 230));
|
||
d->AddCircle(dropPos, drawRadius * 1.5f + 1.5f, IM_COL32(255, 50, 50, 255), 0, 2.0f * g_autoScale);
|
||
d->AddText(font, 14.0f*g_autoScale, dropPos + ImVec2(drawRadius*1.5f+2, -drawRadius*1.5f-2), IM_COL32(255, 50, 50, 255), "全局卖出落点");
|
||
|
||
// 渲染所有真实英雄独立格子起滑点 (深紫圈) 及连线
|
||
std::unordered_set<int> drawn_cells;
|
||
for (const auto& hero : allHeroData) {
|
||
if (drawn_cells.count(hero.cellIndex)) continue;
|
||
drawn_cells.insert(hero.cellIndex);
|
||
if (g_cell_sell_offset.find(hero.cellIndex) == g_cell_sell_offset.end()) g_cell_sell_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f);
|
||
|
||
ImVec2 basePos(hero.screenPos.x, g_gl_height - hero.screenPos.y);
|
||
ImVec2 grabPos(basePos.x + g_cell_sell_offset[hero.cellIndex].x * g_autoScale, basePos.y + g_cell_sell_offset[hero.cellIndex].y * g_autoScale);
|
||
|
||
d->AddLine(grabPos, dropPos, IM_COL32(160, 0, 160, 100), 1.5f * g_autoScale);
|
||
d->AddLine(basePos, grabPos, IM_COL32(160, 0, 160, 60), 1.0f * g_autoScale);
|
||
|
||
d->AddCircleFilled(grabPos, drawRadius, IM_COL32(160, 0, 160, 230));
|
||
d->AddCircle(grabPos, drawRadius + 1.5f, IM_COL32(220, 50, 220, 255), 0, 1.5f * g_autoScale);
|
||
char buf[32]; snprintf(buf, sizeof(buf), "格子%d卖出起滑", hero.cellIndex);
|
||
d->AddText(font, 14.0f*g_autoScale, grabPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(220, 50, 220, 255), buf);
|
||
}
|
||
}
|
||
}
|
||
|
||
void DrawSingleSlotOverlay(const char* name, const char* rowLabel, bool& isOpen, float& posX, float& posY, float& scale, int totalSlots,
|
||
const std::vector<HeroSlotInfo>& data, ImU32 baseLabelColor) {
|
||
static std::unordered_map<const char*, float> alphas_raw;
|
||
float deltaTime = ImGui::GetIO().DeltaTime;
|
||
alphas_raw[name] = ImLerp(alphas_raw[name], isOpen ? 1.0f : 0.0f, 1.0f - expf(-20.0f * deltaTime));
|
||
float alpha = alphas_raw[name] * g_global_esp_alpha;
|
||
if (alpha < 0.01f) return;
|
||
|
||
ImDrawList* d = ImGui::GetForegroundDrawList();
|
||
static std::unordered_map<const char*, float> t_xs, t_ys, t_scales;
|
||
if (t_scales.find(name) == t_scales.end()) { t_xs[name] = posX; t_ys[name] = posY; t_scales[name] = scale; }
|
||
static std::unordered_map<const char*, bool> draggings, scalings;
|
||
static std::unordered_map<const char*, ImVec2> dragOffsets, scaleDragOffsets;
|
||
|
||
float baseSz = 60.0f * g_autoScale, labelW = 40.0f * g_autoScale;
|
||
float totalW = labelW + totalSlots * baseSz, totalH = baseSz;
|
||
|
||
HandleGridInteraction(posX, posY, scale, t_xs[name], t_ys[name], t_scales[name], draggings[name], scalings[name], dragOffsets[name], scaleDragOffsets[name],
|
||
totalW + 20.0f * g_autoScale, totalH * 0.5f, -10.0f * g_autoScale, totalH * 0.5f,
|
||
0, 0, totalW, totalH, g_boardLocked, &isOpen, true);
|
||
|
||
if (!g_boardLocked && alpha > 0.9f * g_global_esp_alpha) {
|
||
DrawScaleHandle(d, ImVec2(posX + (totalW + 20.0f * g_autoScale) * scale, posY + totalH * 0.5f * scale), scalings[name]);
|
||
DrawCloseHandle(d, ImVec2(posX - 10.0f * g_autoScale * scale, posY + totalH * 0.5f * scale), &isOpen);
|
||
}
|
||
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
ImU32 labelColor = (baseLabelColor & 0x00FFFFFF) | ((int)(255 * alpha) << 24);
|
||
|
||
float lFSz = 26.0f * g_autoScale * scale;
|
||
ImVec2 lSz = font->CalcTextSizeA(lFSz, FLT_MAX, 0.0f, rowLabel);
|
||
ImVec2 lPos = ImVec2(posX + (labelW * scale - lSz.x) * 0.5f, posY + (baseSz * scale - lSz.y) * 0.5f);
|
||
|
||
DrawTextWithStroke(d, font, lFSz, lPos, labelColor, IM_COL32(0,0,0,(int)(255*alpha)), rowLabel);
|
||
|
||
for (int i = 0; i < totalSlots; i++) {
|
||
float cx = posX + labelW * scale + i * baseSz * scale + (baseSz * 0.5f) * scale;
|
||
float cy = posY + (baseSz * 0.5f) * scale;
|
||
float drawRadius = baseSz * 0.5f * scale;
|
||
|
||
ImU32 borderColor = IM_COL32(100, 100, 100, (int)(255 * alpha));
|
||
int matchedBaseId = -1, matchedCost = -1, matchedStarLevel = -1;
|
||
|
||
for (const auto& hero : data) {
|
||
if (hero.slotIndex == i) {
|
||
matchedBaseId = hero.baseHeroId; matchedCost = hero.cost; matchedStarLevel = hero.starLevel;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (matchedCost != -1) {
|
||
switch (matchedCost) {
|
||
case 1: borderColor = IM_COL32(200, 200, 200, (int)(255 * alpha)); break;
|
||
case 2: borderColor = IM_COL32(0, 255, 0, (int)(255 * alpha)); break;
|
||
case 3: borderColor = IM_COL32(0, 200, 255, (int)(255 * alpha)); break;
|
||
case 4: borderColor = IM_COL32(210, 50, 255, (int)(255 * alpha)); break;
|
||
case 5: borderColor = IM_COL32(255, 215, 0, (int)(255 * alpha)); break;
|
||
}
|
||
}
|
||
|
||
if (matchedBaseId != -1) {
|
||
GLuint tex = GetHeroTexture(matchedBaseId);
|
||
ImVec2 pMin = ImVec2(cx - drawRadius, cy - drawRadius);
|
||
ImVec2 pMax = ImVec2(cx + drawRadius, cy + drawRadius);
|
||
|
||
if (tex != 0) {
|
||
d->AddImageRounded((ImTextureID)(intptr_t)tex, pMin, pMax, ImVec2(0,0), ImVec2(1,1), IM_COL32(255, 255, 255, (int)(255 * alpha)), 8.0f * g_autoScale * scale);
|
||
} else {
|
||
char idBuf[16]; snprintf(idBuf, sizeof(idBuf), "%d", matchedBaseId);
|
||
float idFSz = 20.0f * g_autoScale * scale;
|
||
ImVec2 idSz = font->CalcTextSizeA(idFSz, FLT_MAX, 0.0f, idBuf);
|
||
d->AddText(font, idFSz, ImVec2(cx - idSz.x * 0.5f, cy - idSz.y * 0.5f), borderColor, idBuf);
|
||
}
|
||
|
||
char starBuf[16]; snprintf(starBuf, sizeof(starBuf), "%d星", matchedStarLevel);
|
||
float fSzStar = 20.0f * g_autoScale * scale;
|
||
ImVec2 tPos = pMin + ImVec2(4.0f * scale, 4.0f * scale);
|
||
DrawTextWithStroke(d, font, fSzStar, tPos, borderColor, IM_COL32(0,0,0, (int)(255 * alpha)), starBuf);
|
||
}
|
||
d->AddRect(ImVec2(cx - drawRadius, cy - drawRadius), ImVec2(cx + drawRadius, cy + drawRadius), borderColor, 8.0f * g_autoScale * scale, 0, 2.5f * g_autoScale * scale);
|
||
}
|
||
}
|
||
|
||
void DrawAutoEquipWindow() {
|
||
if (!g_show_equip_window) return;
|
||
static float alpha = 0.0f;
|
||
alpha = ImLerp(alpha, g_show_equip_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime));
|
||
if (alpha < 0.01f) return;
|
||
|
||
ImDrawList* d = ImGui::GetForegroundDrawList();
|
||
ImGuiIO& io = ImGui::GetIO();
|
||
|
||
static float t_x = g_equip_win_x, t_y = g_equip_win_y, t_scale = g_equip_win_scale;
|
||
static bool first = true; if (first) { t_x = g_equip_win_x; t_y = g_equip_win_y; t_scale = g_equip_win_scale; first = false; }
|
||
static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset;
|
||
|
||
float baseW = 260.0f * g_autoScale, baseH = 60.0f * g_autoScale;
|
||
HandleGridInteraction(g_equip_win_x, g_equip_win_y, g_equip_win_scale, t_x, t_y, t_scale,
|
||
isDragging, isScaling, dragOffset, scaleDragOffset,
|
||
baseW + 20.0f * g_autoScale, baseH * 0.5f,
|
||
-20.0f * g_autoScale, baseH * 0.5f,
|
||
0, 0, baseW, baseH, g_boardLocked, &g_show_equip_window, true);
|
||
|
||
float curW = baseW * g_equip_win_scale, curH = baseH * g_equip_win_scale;
|
||
ImRect bgRect(ImVec2(g_equip_win_x, g_equip_win_y), ImVec2(g_equip_win_x + curW, g_equip_win_y + curH));
|
||
|
||
if (!g_boardLocked && alpha > 0.9f) {
|
||
DrawScaleHandle(d, ImVec2(g_equip_win_x + (baseW + 20.0f * g_autoScale) * g_equip_win_scale, g_equip_win_y + baseH * 0.5f * g_equip_win_scale), isScaling);
|
||
DrawCloseHandle(d, ImVec2(g_equip_win_x - 20.0f * g_autoScale * g_equip_win_scale, g_equip_win_y + baseH * 0.5f * g_equip_win_scale), &g_show_equip_window);
|
||
}
|
||
|
||
float rounding = curH * 0.5f;
|
||
d->AddRectFilled(bgRect.Min, bgRect.Max, IM_COL32(20, 25, 30, (int)(220 * alpha)), rounding);
|
||
d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(80, 85, 95, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_equip_win_scale);
|
||
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
|
||
float curBtnW = 110.0f * g_autoScale * g_equip_win_scale, curBtnH = 44.0f * g_autoScale * g_equip_win_scale;
|
||
float gap = (curW - curBtnW * 2.0f) / 3.0f;
|
||
ImVec2 btn1Pos = ImVec2(g_equip_win_x + gap, g_equip_win_y + (curH - curBtnH) * 0.5f);
|
||
ImVec2 btn2Pos = ImVec2(g_equip_win_x + gap * 2.0f + curBtnW, g_equip_win_y + (curH - curBtnH) * 0.5f);
|
||
|
||
auto drawBtn = [&](ImVec2 pos, const char* text, bool& active, int id) {
|
||
ImRect bb(pos, pos + ImVec2(curBtnW, curBtnH));
|
||
bool hovered = bb.Contains(io.MousePos);
|
||
if (hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling) active = !active;
|
||
|
||
static std::unordered_map<int, float> anims;
|
||
float target = active ? 1.0f : (hovered ? 0.6f : 0.0f);
|
||
float a = (anims[id] = ImLerp(anims[id], target, 1.0f - expf(-20.0f * io.DeltaTime)));
|
||
float rR = curBtnH * 0.5f;
|
||
|
||
float hue = fmodf((float)ImGui::GetTime() * 0.5f + id * 0.3f, 1.0f);
|
||
float r, g, b; ImGui::ColorConvertHSVtoRGB(hue, 0.7f, 1.0f, r, g, b);
|
||
ImU32 neonColor = IM_COL32((int)(r * 255), (int)(g * 255), (int)(b * 255), (int)(200 * alpha));
|
||
|
||
if (active) {
|
||
d->AddRectFilled(bb.Min, bb.Max, IM_COL32(0, 200, 150, (int)(255 * alpha)), rR);
|
||
d->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 180, (int)(255 * alpha)), rR, 0, 2.5f * g_autoScale * g_equip_win_scale);
|
||
} else {
|
||
d->AddRectFilled(bb.Min, bb.Max, IM_COL32(40, 45, 55, (int)(255 * alpha)), rR);
|
||
d->AddRect(bb.Min, bb.Max, neonColor, rR, 0, 2.0f * g_autoScale * g_equip_win_scale);
|
||
}
|
||
|
||
float fSz = 20.0f * g_autoScale * g_equip_win_scale;
|
||
ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text);
|
||
d->AddText(font, fSz, pos + ImVec2((curBtnW - tSz.x)*0.5f, (curBtnH - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text);
|
||
};
|
||
|
||
drawBtn(btn1Pos, (const char*)u8"穿装备", g_auto_equip_active, 201);
|
||
drawBtn(btn2Pos, (const char*)u8"拆装备", g_auto_unequip_active, 202);
|
||
}
|
||
|
||
void DrawHeroLinesESP(const GameSnapshot* snap) {
|
||
if (!snap || !g_draw_hero_lines || snap->mainCamera == 0) return;
|
||
|
||
ImDrawList* d = ImGui::GetBackgroundDrawList();
|
||
ImGuiIO& io = ImGui::GetIO();
|
||
|
||
ImVec2 screenCenter(io.DisplaySize.x * 0.5f, 0.0f);
|
||
|
||
auto drawLine = [&](const LiveHeroData& hero) {
|
||
if (hero.screenPos.z > 0.01f) {
|
||
float imguiX = hero.screenPos.x;
|
||
float imguiY = io.DisplaySize.y - hero.screenPos.y + g_esp_line_offset_y * g_autoScale;
|
||
ImVec2 targetPos(imguiX, imguiY);
|
||
|
||
d->AddLine(screenCenter, targetPos, IM_COL32(0, 255, 200, 150), 2.0f);
|
||
d->AddCircleFilled(targetPos, 5.0f, IM_COL32(0, 255, 200, 255));
|
||
|
||
char buf[32]; snprintf(buf, sizeof(buf), "Hero: %d", hero.heroId);
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
DrawTextWithStroke(d, font, 18.0f * g_autoScale, targetPos + ImVec2(10.0f, -10.0f), IM_COL32(255,255,255,255), IM_COL32(0,0,0,255), buf);
|
||
}
|
||
};
|
||
|
||
for (const auto& hero : snap->myLiveHeroes) drawLine(hero);
|
||
for (const auto& hero : snap->myBenchLiveHeroes) drawLine(hero);
|
||
}
|
||
|
||
void DrawAutoActionWindow() {
|
||
if (!g_show_auto_window) return;
|
||
static float alpha = 0.0f;
|
||
alpha = ImLerp(alpha, g_show_auto_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime));
|
||
if (alpha < 0.01f) return;
|
||
|
||
ImDrawList* d = ImGui::GetForegroundDrawList();
|
||
ImGuiIO& io = ImGui::GetIO();
|
||
|
||
static float t_x = g_auto_win_x, t_y = g_auto_win_y, t_scale = g_auto_win_scale;
|
||
static bool first = true; if (first) { t_x = g_auto_win_x; t_y = g_auto_win_y; t_scale = g_auto_win_scale; first = false; }
|
||
static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset;
|
||
|
||
float baseW = 260.0f * g_autoScale;
|
||
float baseH = g_show_clear_bench_btn ? (130.0f * g_autoScale) : (60.0f * g_autoScale);
|
||
|
||
HandleGridInteraction(g_auto_win_x, g_auto_win_y, g_auto_win_scale, t_x, t_y, t_scale,
|
||
isDragging, isScaling, dragOffset, scaleDragOffset,
|
||
baseW + 20.0f * g_autoScale, baseH * 0.5f,
|
||
-20.0f * g_autoScale, baseH * 0.5f,
|
||
0, 0, baseW, baseH, g_boardLocked, &g_show_auto_window, true);
|
||
|
||
float curW = baseW * g_auto_win_scale, curH = baseH * g_auto_win_scale;
|
||
ImRect bgRect(ImVec2(g_auto_win_x, g_auto_win_y), ImVec2(g_auto_win_x + curW, g_auto_win_y + curH));
|
||
|
||
if (!g_boardLocked && alpha > 0.9f) {
|
||
DrawScaleHandle(d, ImVec2(g_auto_win_x + (baseW + 20.0f * g_autoScale) * g_auto_win_scale, g_auto_win_y + baseH * 0.5f * g_auto_win_scale), isScaling);
|
||
DrawCloseHandle(d, ImVec2(g_auto_win_x - 20.0f * g_autoScale * g_auto_win_scale, g_auto_win_y + baseH * 0.5f * g_auto_win_scale), &g_show_auto_window);
|
||
}
|
||
|
||
float rounding = 60.0f * g_autoScale * g_auto_win_scale * 0.3f;
|
||
d->AddRectFilled(bgRect.Min, bgRect.Max, IM_COL32(20, 25, 30, (int)(220 * alpha)), rounding);
|
||
d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(80, 85, 95, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_auto_win_scale);
|
||
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
|
||
float curBtnW = 110.0f * g_autoScale * g_auto_win_scale, curBtnH = 44.0f * g_autoScale * g_auto_win_scale;
|
||
float gap = (curW - curBtnW * 2.0f) / 3.0f;
|
||
|
||
ImVec2 btn1Pos = ImVec2(g_auto_win_x + gap, g_auto_win_y + gap);
|
||
ImVec2 btn2Pos = ImVec2(g_auto_win_x + gap * 2.0f + curBtnW, g_auto_win_y + gap);
|
||
|
||
auto drawBtn = [&](ImVec2 pos, const char* text, bool& active, int id) {
|
||
ImRect bb(pos, pos + ImVec2(curBtnW, curBtnH));
|
||
bool hovered = bb.Contains(io.MousePos);
|
||
if (hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling) active = !active;
|
||
|
||
static std::unordered_map<int, float> anims;
|
||
float target = active ? 1.0f : (hovered ? 0.6f : 0.0f);
|
||
float a = (anims[id] = ImLerp(anims[id], target, 1.0f - expf(-20.0f * io.DeltaTime)));
|
||
float rR = curBtnH * 0.5f;
|
||
|
||
float hue = fmodf((float)ImGui::GetTime() * 0.5f + id * 0.3f, 1.0f);
|
||
float r, g, b; ImGui::ColorConvertHSVtoRGB(hue, 0.7f, 1.0f, r, g, b);
|
||
ImU32 neonColor = IM_COL32((int)(r * 255), (int)(g * 255), (int)(b * 255), (int)(200 * alpha));
|
||
|
||
if (active) {
|
||
d->AddRectFilled(bb.Min, bb.Max, IM_COL32(0, 200, 150, (int)(255 * alpha)), rR);
|
||
d->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 180, (int)(255 * alpha)), rR, 0, 2.5f * g_autoScale * g_auto_win_scale);
|
||
} else {
|
||
d->AddRectFilled(bb.Min, bb.Max, IM_COL32(40, 45, 55, (int)(255 * alpha)), rR);
|
||
d->AddRect(bb.Min, bb.Max, neonColor, rR, 0, 2.0f * g_autoScale * g_auto_win_scale);
|
||
}
|
||
|
||
float fSz = 20.0f * g_autoScale * g_auto_win_scale;
|
||
ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text);
|
||
d->AddText(font, fSz, pos + ImVec2((curBtnW - tSz.x)*0.5f, (curBtnH - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text);
|
||
};
|
||
|
||
auto drawClickBtn = [&](ImVec2 pos, float w, float h, const char* text) -> bool {
|
||
ImRect bb(pos, pos + ImVec2(w, h));
|
||
bool hovered = bb.Contains(io.MousePos);
|
||
bool clicked = hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling;
|
||
bool held = hovered && ImGui::IsMouseDown(0);
|
||
|
||
float rR = h * 0.5f;
|
||
float alpha_factor = held ? 0.8f : (hovered ? 1.0f : 0.7f);
|
||
ImU32 bgColor = IM_COL32(220, 60, 60, (int)(255 * alpha * alpha_factor));
|
||
|
||
d->AddRectFilled(bb.Min, bb.Max, bgColor, rR);
|
||
d->AddRect(bb.Min, bb.Max, IM_COL32(255, 100, 100, (int)(255 * alpha)), rR, 0, 2.0f * g_autoScale * g_auto_win_scale);
|
||
|
||
float fSz = 20.0f * g_autoScale * g_auto_win_scale;
|
||
ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text);
|
||
d->AddText(font, fSz, pos + ImVec2((w - tSz.x)*0.5f, (h - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text);
|
||
|
||
return clicked;
|
||
};
|
||
|
||
drawBtn(btn1Pos, (const char*)u8"自动刷新", g_auto_refresh, 101);
|
||
drawBtn(btn2Pos, (const char*)u8"自动拿牌", g_auto_buy, 102);
|
||
|
||
if (g_show_clear_bench_btn) {
|
||
ImVec2 btn3Pos = ImVec2(g_auto_win_x + gap, g_auto_win_y + gap * 2.0f + curBtnH);
|
||
if (drawClickBtn(btn3Pos, curW - gap * 2.0f, curBtnH, (const char*)u8"一键清空备战席")) {
|
||
TriggerSellAllHeroes();
|
||
}
|
||
}
|
||
}
|
||
|
||
void DrawInstantQuitWindow() {
|
||
if (!g_show_instant_quit_window) return;
|
||
static float alpha = 0.0f;
|
||
alpha = ImLerp(alpha, g_show_instant_quit_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime));
|
||
if (alpha < 0.01f) return;
|
||
|
||
ImDrawList* d = ImGui::GetForegroundDrawList();
|
||
ImGuiIO& io = ImGui::GetIO();
|
||
|
||
static float t_x = g_instant_win_x, t_y = g_instant_win_y, t_scale = g_instant_win_scale;
|
||
static bool first = true; if (first) { t_x = g_instant_win_x; t_y = g_instant_win_y; t_scale = g_instant_win_scale; first = false; }
|
||
static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset;
|
||
|
||
float baseW = 160.0f * g_autoScale, baseH = 60.0f * g_autoScale;
|
||
HandleGridInteraction(g_instant_win_x, g_instant_win_y, g_instant_win_scale, t_x, t_y, t_scale,
|
||
isDragging, isScaling, dragOffset, scaleDragOffset,
|
||
baseW + 20.0f * g_autoScale, baseH * 0.5f,
|
||
-20.0f * g_autoScale, baseH * 0.5f,
|
||
0, 0, baseW, baseH, g_boardLocked, &g_show_instant_quit_window, true);
|
||
|
||
float curW = baseW * g_instant_win_scale, curH = baseH * g_instant_win_scale;
|
||
ImRect bgRect(ImVec2(g_instant_win_x, g_instant_win_y), ImVec2(g_instant_win_x + curW, g_instant_win_y + curH));
|
||
|
||
if (!g_boardLocked && alpha > 0.9f) {
|
||
DrawScaleHandle(d, ImVec2(g_instant_win_x + (baseW + 20.0f * g_autoScale) * g_instant_win_scale, g_instant_win_y + baseH * 0.5f * g_instant_win_scale), isScaling);
|
||
DrawCloseHandle(d, ImVec2(g_instant_win_x - 20.0f * g_autoScale * g_instant_win_scale, g_instant_win_y + baseH * 0.5f * g_instant_win_scale), &g_show_instant_quit_window);
|
||
}
|
||
|
||
float rounding = curH * 0.5f;
|
||
d->AddRectFilled(bgRect.Min, bgRect.Max, IM_COL32(20, 25, 30, (int)(220 * alpha)), rounding);
|
||
d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(80, 85, 95, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_instant_win_scale);
|
||
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
|
||
float curBtnW = 130.0f * g_autoScale * g_instant_win_scale, curBtnH = 44.0f * g_autoScale * g_instant_win_scale;
|
||
ImVec2 btnPos = ImVec2(g_instant_win_x + (curW - curBtnW) * 0.5f, g_instant_win_y + (curH - curBtnH) * 0.5f);
|
||
ImRect bb(btnPos, btnPos + ImVec2(curBtnW, curBtnH));
|
||
|
||
static int confirmState = 0;
|
||
static float confirmTimer = 0.0f;
|
||
|
||
if (confirmState == 1) {
|
||
confirmTimer -= io.DeltaTime;
|
||
if (confirmTimer <= 0.0f) confirmState = 0;
|
||
}
|
||
|
||
bool hovered = bb.Contains(io.MousePos);
|
||
if (hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling) {
|
||
if (confirmState == 0) {
|
||
confirmState = 1;
|
||
confirmTimer = 3.0f;
|
||
} else {
|
||
g_Tasks.trigger_surrender.store(true);
|
||
confirmState = 0;
|
||
}
|
||
}
|
||
|
||
static float animA = 0.0f;
|
||
animA = ImLerp(animA, confirmState == 1 ? 1.0f : 0.0f, 1.0f - expf(-15.0f * io.DeltaTime));
|
||
float rR = curBtnH * 0.5f;
|
||
|
||
ImU32 normalBg = IM_COL32(40, 45, 55, (int)(255 * alpha));
|
||
ImU32 confirmBg = IM_COL32(200, 50, 50, (int)(255 * alpha));
|
||
|
||
float pulse = (sinf((float)ImGui::GetTime() * 8.0f) + 1.0f) * 0.5f;
|
||
ImU32 normalBorder = hovered ? IM_COL32(150, 150, 150, (int)(255 * alpha)) : IM_COL32(100, 100, 100, (int)(200 * alpha));
|
||
ImU32 confirmBorder = IM_COL32(255, 100 + (int)(100*pulse), 100 + (int)(100*pulse), (int)(255 * alpha));
|
||
|
||
auto lerpColor = [](ImU32 c1, ImU32 c2, float t) -> ImU32 {
|
||
int r = (int)( ((c1 >> 0) & 0xFF) * (1-t) + ((c2 >> 0) & 0xFF) * t );
|
||
int g = (int)( ((c1 >> 8) & 0xFF) * (1-t) + ((c2 >> 8) & 0xFF) * t );
|
||
int b = (int)( ((c1 >> 16) & 0xFF) * (1-t) + ((c2 >> 16) & 0xFF) * t );
|
||
int a = (int)( ((c1 >> 24) & 0xFF) * (1-t) + ((c2 >> 24) & 0xFF) * t );
|
||
return IM_COL32(r, g, b, a);
|
||
};
|
||
|
||
d->AddRectFilled(bb.Min, bb.Max, lerpColor(normalBg, confirmBg, animA), rR);
|
||
d->AddRect(bb.Min, bb.Max, lerpColor(normalBorder, confirmBorder, animA), rR, 0, 2.0f * g_autoScale * g_instant_win_scale);
|
||
|
||
float fSz = 20.0f * g_autoScale * g_instant_win_scale;
|
||
const char* textNormal = (const char*)u8"极速退游";
|
||
const char* textConfirm = (const char*)u8"确认退游?";
|
||
|
||
if (animA < 0.99f) {
|
||
ImVec2 tSzNormal = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, textNormal);
|
||
d->AddText(font, fSz, btnPos + ImVec2((curBtnW - tSzNormal.x)*0.5f, (curBtnH - tSzNormal.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha * (1.0f - animA))), textNormal);
|
||
}
|
||
if (animA > 0.01f) {
|
||
ImVec2 tSzConfirm = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, textConfirm);
|
||
d->AddText(font, fSz, btnPos + ImVec2((curBtnW - tSzConfirm.x)*0.5f, (curBtnH - tSzConfirm.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha * animA)), textConfirm);
|
||
}
|
||
}
|
||
|
||
void DrawSellHeroWindow() {
|
||
if (!g_show_sell_window) return;
|
||
static float alpha = 0.0f;
|
||
alpha = ImLerp(alpha, g_show_sell_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime));
|
||
if (alpha < 0.01f) return;
|
||
|
||
ImDrawList* d = ImGui::GetForegroundDrawList();
|
||
ImGuiIO& io = ImGui::GetIO();
|
||
|
||
static float t_x = g_sell_win_x, t_y = g_sell_win_y, t_scale = g_sell_win_scale;
|
||
static bool first = true; if (first) { t_x = g_sell_win_x; t_y = g_sell_win_y; t_scale = g_sell_win_scale; first = false; }
|
||
static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset;
|
||
|
||
float baseW = 120.0f * g_autoScale, baseH = 44.0f * g_autoScale;
|
||
HandleGridInteraction(g_sell_win_x, g_sell_win_y, g_sell_win_scale, t_x, t_y, t_scale,
|
||
isDragging, isScaling, dragOffset, scaleDragOffset,
|
||
baseW + 20.0f * g_autoScale, baseH * 0.5f,
|
||
-20.0f * g_autoScale, baseH * 0.5f,
|
||
0, 0, baseW, baseH, g_boardLocked, &g_show_sell_window, true);
|
||
|
||
float curW = baseW * g_sell_win_scale, curH = baseH * g_sell_win_scale;
|
||
ImRect bgRect(ImVec2(g_sell_win_x, g_sell_win_y), ImVec2(g_sell_win_x + curW, g_sell_win_y + curH));
|
||
|
||
if (!g_boardLocked && alpha > 0.9f) {
|
||
DrawScaleHandle(d, ImVec2(g_sell_win_x + (baseW + 20.0f * g_autoScale) * g_sell_win_scale, g_sell_win_y + baseH * 0.5f * g_sell_win_scale), isScaling);
|
||
DrawCloseHandle(d, ImVec2(g_sell_win_x - 20.0f * g_autoScale * g_sell_win_scale, g_sell_win_y + baseH * 0.5f * g_sell_win_scale), &g_show_sell_window);
|
||
}
|
||
|
||
float rounding = curH * 0.5f;
|
||
|
||
bool hovered = bgRect.Contains(io.MousePos);
|
||
bool clicked = hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling;
|
||
bool held = hovered && ImGui::IsMouseDown(0);
|
||
|
||
if (clicked) {
|
||
TriggerSellAllHeroes();
|
||
}
|
||
|
||
float alpha_factor = held ? 0.8f : (hovered ? 1.0f : 0.7f);
|
||
ImU32 bgColor = IM_COL32(220, 50, 50, (int)(255 * alpha * alpha_factor));
|
||
|
||
d->AddRectFilled(bgRect.Min, bgRect.Max, bgColor, rounding);
|
||
d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(255, 100, 100, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_sell_win_scale);
|
||
|
||
ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont();
|
||
float fSz = 20.0f * g_autoScale * g_sell_win_scale;
|
||
const char* text = (const char*)u8"卖英雄";
|
||
ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text);
|
||
d->AddText(font, fSz, ImVec2(g_sell_win_x + (curW - tSz.x)*0.5f, g_sell_win_y + (curH - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text);
|
||
}
|
||
|
||
void DrawHeroSelectionWindow() {
|
||
if (!g_show_hero_list_window) return;
|
||
|
||
ImGui::SetNextWindowPos(ImVec2(g_menuX + g_menuW + 10.0f * g_autoScale, g_menuY), ImGuiCond_FirstUseEver);
|
||
ImGui::SetNextWindowSize(ImVec2(480.0f * g_autoScale, 650.0f * g_autoScale), ImGuiCond_FirstUseEver);
|
||
|
||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoSavedSettings;
|
||
if (ImGui::Begin((const char*)u8"自动拿牌图鉴 (随时配置,无需开局)", &g_show_hero_list_window, flags)) {
|
||
|
||
static float h_scale = 1.0f;
|
||
if (!ImGui::IsWindowCollapsed()) {
|
||
float curW = ImGui::GetWindowSize().x;
|
||
h_scale = std::clamp(curW / (480.0f * g_autoScale), 0.5f, 3.0f);
|
||
}
|
||
ImGui::SetWindowFontScale(h_scale);
|
||
|
||
if (!g_pokedex_ready.load()) {
|
||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), (const char*)u8"正在极速扫描本地图片数据,请稍候...");
|
||
} else if (g_local_pokedex.empty()) {
|
||
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), (const char*)u8"未扫描到有效的图片数据,请确认 HeroImages.h 已正确包含!");
|
||
} else {
|
||
ImGuiStyle& style = ImGui::GetStyle();
|
||
float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x;
|
||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||
|
||
static int active_hero_id = -1;
|
||
static double press_time = 0.0;
|
||
static bool just_opened_popup = false;
|
||
|
||
auto renderHeroBlock = [&](int heroId, ImU32 costColor, bool bottomList) {
|
||
ImGui::PushID(heroId + (bottomList ? 50000 : 0));
|
||
|
||
bool is_selected = g_auto_buy_heroes.count(heroId) > 0;
|
||
auto& binds = g_hero_equip_bindings[heroId];
|
||
|
||
float smallEqBoxSz = 24.0f * g_autoScale * h_scale;
|
||
float smEqSpaceX = 4.0f * g_autoScale * h_scale;
|
||
float smEqSpaceY = 4.0f * g_autoScale * h_scale;
|
||
float smallIconSz = 3.0f * smallEqBoxSz + 2.0f * smEqSpaceX;
|
||
|
||
float prioH = 20.0f * g_autoScale * h_scale;
|
||
float triHeight = 28.0f * g_autoScale * h_scale;
|
||
|
||
bool is_expanded = g_expanded_equip_heroes.count(heroId) > 0;
|
||
int cols_in_fold = 3;
|
||
int eq_rows = is_expanded ? std::ceil((float)binds.size() / cols_in_fold) : 0;
|
||
if (eq_rows == 0 && is_expanded) eq_rows = 1;
|
||
|
||
ImVec2 startPos = ImGui::GetCursorScreenPos();
|
||
ImGui::BeginGroup();
|
||
|
||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||
|
||
int prio = g_hero_equip_priority[heroId];
|
||
ImGui::PushID("prio_minus");
|
||
if (ImGui::InvisibleButton("-", ImVec2(smallIconSz * 0.3f, prioH))) {
|
||
prio = std::max(0, prio - 1);
|
||
g_hero_equip_priority[heroId] = prio;
|
||
g_save_timer = 1.0f; SaveConfig();
|
||
}
|
||
ImGui::PopID();
|
||
|
||
ImGui::SameLine(0, 0);
|
||
ImGui::Dummy(ImVec2(smallIconSz * 0.4f, prioH));
|
||
|
||
ImGui::SameLine(0, 0);
|
||
ImGui::PushID("prio_plus");
|
||
if (ImGui::InvisibleButton("+", ImVec2(smallIconSz * 0.3f, prioH))) {
|
||
prio = std::min(5, prio + 1);
|
||
g_hero_equip_priority[heroId] = prio;
|
||
g_save_timer = 1.0f; SaveConfig();
|
||
}
|
||
ImGui::PopID();
|
||
|
||
ImGui::PushID("hero");
|
||
ImGui::InvisibleButton("img", ImVec2(smallIconSz, smallIconSz));
|
||
bool isHovered = ImGui::IsItemHovered();
|
||
bool isActive = ImGui::IsItemActive();
|
||
bool isDeactivated = ImGui::IsItemDeactivated();
|
||
ImGui::PopID();
|
||
|
||
if (isActive) {
|
||
if (active_hero_id != heroId) {
|
||
active_hero_id = heroId;
|
||
press_time = ImGui::GetTime();
|
||
just_opened_popup = false;
|
||
} else if (!just_opened_popup && (ImGui::GetTime() - press_time) > 0.45) {
|
||
ImGui::OpenPopup("EquipPopup");
|
||
just_opened_popup = true;
|
||
}
|