先贴代码:
#include <avz.h> #include <dsl/shorthand.h> #include <filesystem> #include "custom/lib.h" #include "mod/mod.h" #include "DanceCheat/DanceCheat.h" /* 挂机基础设定 */ constexpr auto INIT_DAT_PATH = "C:\\ProgramData\\PopCap Games\\PlantsVsZombies\\game1_13.dat"; constexpr auto GAME_DAT_PATH = "C:\\ProgramData\\PopCap Games\\PlantsVsZombies\\userdata\\game1_13.dat"; constexpr auto TEMP_DAT_PATH = "C:\\Users\\cresc\\Desktop\\temp\\tmp.dat"; const std::vector<Grid> COB_LIST = {{1, 7}, {2, 7}, {5, 7}, {6, 7}}; const std::vector<Grid> RESTORE_HP_COB_LIST = {{1, 1}, {2, 1}, {5, 1}, {6, 1}}; constexpr AutoplayMode MODE = AutoplayMode::DEMO; constexpr int FLAG_GOAL = MODE == AutoplayMode::DEMO ? 50 : 4500; /* 收集数据 */ Logger<Console> logger; auto start = std::chrono::high_resolution_clock::now(); int sl_count = 0; int flag_count = 0; Level level; Stat total_level_stat("总"), cob_hp_stat("炮HP"), cob_finish_hp_stat("轮末炮HP"), special_fodder_stat("特殊垫材"), fail_cause_stat("SL原因"); Record fodder_record("垫材", 6); bool stop_skip_tick = false; bool reload = false; void pause(); void sl(); const std::vector<PlantType> FODDER_LIST = {PUFF, SUN, SCAREDY, POT, SUNFLOWER, GARLIC}; int fodder_idx = 0; bool plant_exist(int row, int col) { for (auto& p : alivePlantFilter) { if (p.Row() + 1 == row && p.Col() + 1 == col) return true; } return false; } std::unordered_set<int> get_giga_threatening_rows() { std::unordered_set<int> res; for (auto& z : aliveZombieFilter) { if (z.Type() == GIGA && z.FreezeCountdown() == 0 && z.State() == 0 && static_cast<int>(z.Abscissa()) <= 671 && !plant_exist(z.Row() + 1, 9)) { res.insert(z.Row() + 1); } } return res; } std::unordered_set<int> get_col9_empty_rows() { std::unordered_set<int> res; for (int row : {1, 2, 5, 6}) { if (!plant_exist(row, 9)) res.insert(row); } return res; } void fodder(int row) { if (fodder_idx >= FODDER_LIST.size()) { logger.Error("试图使用垫材#, 失败.", fodder_idx); return; } if (!IsSeedUsable(FODDER_LIST[fodder_idx])) { logger.Error("垫材不可用."); pause(); return; } ACard(FODDER_LIST[fodder_idx], row, 9); fodder_record.add(std::format("[{}, {}]垫{}至{}-9", GetMainObject()->Wave(), NowTime(GetMainObject()->Wave()), fodder_idx, row)); if (fodder_idx >= 4) { special_fodder_stat[std::to_string(fodder_idx)]++; } fodder_idx++; } constexpr int SMART_FODDER_START_TIME = 478; constexpr int SMART_FODDER_START_TIME_W11 = 553; constexpr int FIXED_FODDER_TIME = 714; constexpr int ADDITIONAL_FODDER_END_TIME = 900; Coroutine place_fodder() { fodder_idx = 0; int wave = GetMainObject()->Wave(); int zero_time = NowTime(wave); int smart_fodder_start_time = wave == 11 ? SMART_FODDER_START_TIME_W11 : SMART_FODDER_START_TIME; co_await [=] { return NowTime(wave) >= zero_time + smart_fodder_start_time; }; while (NowTime(wave) < zero_time + FIXED_FODDER_TIME) { co_await [=] { return !get_giga_threatening_rows().empty() || NowTime(wave) >= zero_time + FIXED_FODDER_TIME; }; for (int row : get_giga_threatening_rows()) fodder(row); } for (int row : get_col9_empty_rows()) fodder(row); co_await [=] { return NowTime(wave) > zero_time + FIXED_FODDER_TIME; }; while (fodder_idx < FODDER_LIST.size() && NowTime(wave) < zero_time + ADDITIONAL_FODDER_END_TIME) { co_await [=] { return !get_col9_empty_rows().empty() || NowTime(wave) >= zero_time + ADDITIONAL_FODDER_END_TIME; }; for (int row : get_col9_empty_rows()) fodder(row); } } void Script() { if (flag_count >= FLAG_GOAL) return; EnableModsScoped(DisableItemDrop, RemoveFog); logger.SetLevel({LogLevel::DEBUG, LogLevel::ERROR, LogLevel::WARNING}); SetInternalLogger(logger); SetReloadMode(ReloadMode::MAIN_UI_OR_FIGHT_UI); SelectCards({ICE, M_ICE, SQUASH, CHERRY, PUFF, SUN, SCAREDY, POT, SUNFLOWER, GARLIC}, MODE == AutoplayMode::DEMO ? 8 : 0); DanceCheat(DanceCheatMode::FAST); for (auto acc : {2, 3, 5, 6, 8, 10, 12, 13, 15, 16, 18}) SetWavelength(acc, 601); for (auto iced : {1, 4, 7, 11, 14, 17}) SetWavelength(iced, 1299); auto zombie_type_list = GetZombieTypeList(); std::vector<int> zombies; for (int i = 0; i < 33; i++) { if (i == ADR_ZOMBOSS || i == FLAG_ZOMBIE) continue; if (zombie_type_list[i] || i == GIGA || i == ZOMBONI) zombies.push_back(i); } SetZombies(zombies, SetZombieMode::AVERAGE); if (MODE != AutoplayMode::SKIPTICK) SetGameSpeed(5); else SkipTick([=] { if (stop_skip_tick) return false; for (auto p : GetPlantPtrs(COB_LIST, COB)) { if (p == nullptr) { int smashing_giga_wave = 999; for (auto& z : aliveZombieFilter) { if (z.Type() == GIGA && z.Abscissa() < 671 && z.State() == 70) smashing_giga_wave = std::min(smashing_giga_wave, z.AtWave() + 1); } if (smashing_giga_wave != GetMainObject()->Wave() - 2) { logger.Error("推测砸炮巨人波数时发生错误."); pause(); } else { logger.Warning("w#红砸#-#炮", smashing_giga_wave, p->Row() + 1, p->Col() + 1); fail_cause_stat[std::format("w{}红砸炮", smashing_giga_wave)]++; sl(); } return false; } else if (p->Hp() < p->HpMax()) { logger.Error("#-#炮损伤", p->Row() + 1, p->Col() + 1); pause(); return false; } } if (zombie_type_list[POGO_ZOMBIE]) { for (auto& z : aliveZombieFilter) { if (z.Type() == POGO_ZOMBIE && z.Abscissa() < 0) { logger.Error("w#跳进家", z.AtWave() + 1); fail_cause_stat[std::format("w{}跳进家", z.AtWave() + 1)]++; pause(); return false; } } } return true; }); Connect('F', [] { SetGameSpeed(5); }); Connect('G', [] { SetGameSpeed(1); }); C.SetCards({PUFF, SUN, SCAREDY, POT, SUNFLOWER, GARLIC}); auto PPCC1 = { At(359) PP(), At(166) C(100, {1256, 9})}; auto PPCC2 = { At(288) PP(), At(327) C(196, {1256, 9})}; auto PPA = { At(401) PP(), At(208) C(60, {56, 9}), At(402) C(65, {56, 9}), At(275) A(2, 9)}; auto IPPcc_PP = { At(1) I(1, 4), At(304) DD(9), At(0) Do { CoLaunch(place_fodder); }, At(918) Shovel({{1, 9}, {2, 9}, {5, 9}, {6, 9}}), At(1099) PP() }; OnWave(1_9 % 3, 11_19 % 3) PPCC1 | PPCC2 | IPPcc_PP; OnWave(9, 19) { At(601) IPPcc_PP, At(1879) PP() }; OnWave(10) PPA; OnWave(20) { At(0) Do { for (auto p : GetPlantPtrs(RESTORE_HP_COB_LIST, COB)) { if (p->Hp() < 300) { logger.Debug("恢复#-#炮HP #→300", p->Row() + 1, p->Col() + 1, p->Hp()); cob_finish_hp_stat[std::to_string(p->Hp())]++; p->Hp() = 300; } } GetMainObject()->Sun() = 8000; }, At(300) Do { kill_all_zombies(); } }; } OnAfterInject({ if (INIT_DAT_PATH[0] != '\0') std::filesystem::copy(INIT_DAT_PATH, GAME_DAT_PATH, std::filesystem::copy_options::overwrite_existing); EnterGame(); }); OnBeforeScript({ if (flag_count > 0 && flag_count % 100 == 0) { for (auto p : GetPlantPtrs(COB_LIST, COB)) cob_hp_stat[std::format("{}-{}", p->Row() + 1, p->Col() + 1)] = p->Hp(); logger.Debug("#\n#\n#\n#, #", cob_finish_hp_stat, special_fodder_stat, fail_cause_stat, total_level_stat, get_time_estimate(flag_count, FLAG_GOAL, start)); } if (flag_count % 100 == 0) logger.Debug(set_random_seed()); level = get_level(); logger.Debug("阳光: #, #, #f, SL次数: # #", GetMainObject()->Sun(), to_string(level), flag_count, sl_count, get_timestamp()); if (MODE != AutoplayMode::DEMO) std::filesystem::copy(GAME_DAT_PATH, TEMP_DAT_PATH + std::to_string(flag_count % 10), std::filesystem::copy_options::overwrite_existing); flag_count += 2; total_level_stat[to_string(level)]++; }); void print_fail_stats() { logger.Debug(get_prev_wave_stat(3)); logger.Debug("#", fodder_record); } void pause() { stop_skip_tick = true; SetAdvancedPause(true); } void sl() { reload = true; BackToMain(false); EnterGame(); } OnExitFight({ if (GetPvzBase()->GameUi() == AAsm::ZOMBIES_WON) { Zombie* zombie = nullptr; for(auto& z : aliveZombieFilter) { if (zombie == nullptr || z.Abscissa() < zombie->Abscissa()) { zombie = &z; } } logger.Error("#进家(x=#), 僵尸获胜", ZOMBIE_NAME[zombie->Type()], zombie->Abscissa()); sl(); } if (GetPvzBase()->GameUi() != AAsm::LEVEL_INTRO && reload) { sl_count++; std::filesystem::copy(TEMP_DAT_PATH + std::to_string(flag_count % 10), GAME_DAT_PATH, std::filesystem::copy_options::overwrite_existing); flag_count = std::clamp(flag_count - 10, 0, INT_MAX); } reload = false; });
依赖 https://github.com/Rottenham/pvz-autoplay/tree/main/inc/custom。