diff --git a/blcrack/cracker/cracker.cpp b/blcrack/cracker/cracker.cpp index 457ff5596..036853f19 100644 --- a/blcrack/cracker/cracker.cpp +++ b/blcrack/cracker/cracker.cpp @@ -1,5 +1,6 @@ #include "cracker.hpp" +#include #include #include #include @@ -326,6 +327,14 @@ void Cracker::disable_all() { disable_auto_once_again(); } +void Cracker::enable_auto_retire() { + ENABLE(AUTO_RETIRE); +} + +void Cracker::disable_auto_retire() { + DISABLE(AUTO_RETIRE); +} + void Cracker::enable_auto_once_again() { ENABLE(AUTO_ONCE_AGAIN); } @@ -742,6 +751,28 @@ void Cracker::load_lua_resources() { m_lua_res.Ship_ENERGY_LOW = m_state["Ship"]["ENERGY_LOW"]; m_lua_res.BaseUI_closeView = m_state["BaseUI"]["closeView"]; m_lua_res.LevelMediator2_ON_RETRACKING = m_state["LevelMediator2"]["ON_RETRACKING"]; + m_lua_res.PlayerProxy = m_state["PlayerProxy"]; + m_lua_res.BayProxy = m_state["BayProxy"]; + m_lua_res.GAME_DESTROY_SHIPS = m_state["GAME"]["DESTROY_SHIPS"]; + m_lua_res.Proxy_getData = m_state["pm"]["Proxy"]["getData"]; + m_lua_res.Ship_calReturnRes = m_state["Ship"]["calReturnRes"]; + m_lua_res.Player_GoldMax = m_state["Player"]["GoldMax"]; + m_lua_res.Player_OilMax = m_state["Player"]["OilMax"]; + m_lua_res.pg_m02 = m_state["pg"]["m02"]; + m_lua_res.pg_m02_sendNotification = m_state["pg"]["m02"]["sendNotification"]; + m_lua_res.BayProxy_getShips = m_state["BayProxy"]["getShips"]; + m_lua_res.Ship_isMaxStar = m_state["Ship"]["isMaxStar"]; + m_lua_res.Ship_getGroupId = m_state["Ship"]["getGroupId"]; + m_lua_res.Ship_getMaxStar = m_state["Ship"]["getMaxStar"]; + m_lua_res.Ship_getStar = m_state["Ship"]["getStar"]; + m_lua_res.Ship_LOCK_STATE_UNLOCK = m_state["Ship"]["LOCK_STATE_UNLOCK"]; + m_lua_res.Ship_LOCK_STATE_LOCK = m_state["Ship"]["LOCK_STATE_LOCK"]; + m_lua_res.Ship_GetLockState = m_state["Ship"]["GetLockState"]; + m_lua_res.Ship_getRarity = m_state["Ship"]["getRarity"]; + m_lua_res.Ship_getFlag = m_state["Ship"]["getFlag"]; + m_lua_res.BayProxy_findShipsByGroup = m_state["BayProxy"]["findShipsByGroup"]; + m_lua_res._map = m_state["_"]["map"]; + m_lua_res._select = m_state["_"]["select"]; SPDLOG_INFO("Load lua functions"); m_original.GetBattleCheckResult = m_state["GetBattleCheckResult"]; @@ -963,13 +994,31 @@ void Cracker::hook_all_lua_functions() { // skip_ship_gain_show m_state["NewBattleResultDisplayAwardPage"]["ShowShips"] = [this](sol::this_state L, Lua::VariadicArgs args) { CALLED(NewBattleResultDisplayAwardPage.ShowShips); - if (!IS_ENABLED(SKIP_SHIP_GAIN_SHOW)) { + + auto enabled_SKIP_SHIP_GAIN_SHOW = IS_ENABLED(SKIP_SHIP_GAIN_SHOW); + auto enabled_AUTO_RETIRE = IS_ENABLED(AUTO_RETIRE); + + if (!enabled_SKIP_SHIP_GAIN_SHOW && !enabled_AUTO_RETIRE) { m_original.NewBattleResultDisplayAwardPage_ShowShips(L, args); return; } - Lua::Function callback = args[2]; - callback(L); + if (enabled_SKIP_SHIP_GAIN_SHOW) { + Lua::Function callback = args[2]; + callback(L); + } + if (enabled_AUTO_RETIRE) { + Lua::Object retire_timer = m_lua_res.Timer_New(L, [this](sol::this_state l, Lua::VariadicArgs ags) { + std::optional playerProxy = m_lua_res.getProxy(l, m_lua_res.PlayerProxy); + std::optional bayProcy = m_lua_res.getProxy(l, m_lua_res.BayProxy); + + if (playerProxy.has_value() && playerProxy->get_type() != sol::type::nil && bayProcy.has_value() && bayProcy->get_type() != sol::type::nil) { + execute_retire_ship(l, ags); + } + + }, 1.5, 1); + m_lua_res.Timer_Start(L, retire_timer); + } }; m_state["BaseUI"]["emit"] = [this](sol::this_state L, Lua::VariadicArgs args) { @@ -1576,6 +1625,171 @@ void Cracker::better_global_speedup_set_rate(double rate) { } } +bool Cracker::is_valid_ship(const Lua::Object& obj) { + if (obj.get_type() != sol::type::table) { + return false; + } + if (!obj.as().get>("configId").has_value()) { + return false; + } + return true; +} + +Lua::Table Cracker::filter_ship(sol::this_state& l, Lua::VariadicArgs& args) { + Lua::Object bayProxy = m_lua_res.getProxy(l, m_lua_res.BayProxy); + auto maxStarGroup = Lua::Table(l); + auto upgradeRequirements = std::map(); + + Lua::Table ships = m_lua_res.BayProxy_getShips(l, bayProxy); + ships.for_each([&](const Lua::Object& key, const Lua::Object& value) { + bool isMaxStar = m_lua_res.Ship_isMaxStar(l, value); + int groupId = m_lua_res.Ship_getGroupId(l, value); + if (isMaxStar) { + maxStarGroup[groupId] = true; + } else { + int maxStar = m_lua_res.Ship_getMaxStar(l, value); + int star = m_lua_res.Ship_getStar(l, value); + int lockState = m_lua_res.Ship_GetLockState(l, value); + + auto require = maxStar - star + 1; + if (lockState == m_lua_res.Ship_LOCK_STATE_UNLOCK) { + require++; + } + + upgradeRequirements[groupId] = std::min(require, upgradeRequirements.contains(groupId) ? upgradeRequirements[groupId] : 999); + } + }); + + auto filter = [&](sol::this_state, Lua::Object value) { + Lua::Table ship = value; + + int configId = ship["configId"]; + if (std::ranges::find(s_excludedShips, configId) != s_excludedShips.end()) { + return false; + } + if (configId == 100001 || configId == 100011 || configId == 100021) { + return false; + } + + int lockState = m_lua_res.Ship_GetLockState(l, ship); + if (lockState == m_lua_res.Ship_LOCK_STATE_LOCK) { + return false; + } + + int rarity = m_lua_res.Ship_getRarity(l, ship); + bool rarityMatched = false; + auto rarityIt = std::ranges::find(s_shipRarityId, rarity); + if (rarityIt != s_shipRarityId.end()) { + rarityMatched = true; + } + if (!rarityMatched) { + return false; + } + + // Don't retire SSR and RN ships + if (rarity >= 5) { + return false; + } + + for (const auto& status: s_shipStatus) { + bool forbidden = m_lua_res.Ship_getFlag(l, ship, status); + if (forbidden) { + return false; + } + } + return true; + }; + + Lua::Table candidate = m_lua_res._select(l, ships, filter); + + /* + * According to the original Lua cheating code, it should work properly. + * But in reality it will cause duplicate addition of ship IDs. + * Don't know why. + */ + // auto final = Lua::Table(l); + // auto groupRetireNumber = std::map(); + // candidate.for_each([&](const Lua::Object& key, const Lua::Object& value) { + // if (!is_valid_ship(value)) { + // return; + // } + // int groupId = m_lua_res.Ship_getGroupId(l, value); + // if (!groupRetireNumber.contains(groupId)) { + // groupRetireNumber[groupId] = 1; + // } else { + // groupRetireNumber[groupId]++; + // } + // }); + // + // for (const auto& groupId: groupRetireNumber | std::views::keys) { + // Lua::Table bayGroup = m_lua_res.BayProxy_findShipsByGroup(l, bayProxy, groupId); + // int bayGroupTotal = bayGroup.size(); + // int maxToRetire = std::max(0, bayGroupTotal - 1); + // + // int hasAdded = 0; + // candidate.for_each([&](const Lua::Object& key, const Lua::Object& value) { + // if (!is_valid_ship(value)) { + // return; + // } + // Lua::Table ship = value; + // int shipGroupId = m_lua_res.Ship_getGroupId(l, ship); + // if (shipGroupId == groupId) { + // if (hasAdded < maxToRetire) { + // final.add(ship); + // hasAdded++; + // } + // } + // }); + // } + // return final; + + return candidate; +} + +void Cracker::execute_retire_ship(sol::this_state& l, Lua::VariadicArgs& args) { + Lua::Object playerProxy = m_lua_res.getProxy(l, m_lua_res.PlayerProxy); + Lua::Object bayProxy = m_lua_res.getProxy(l, m_lua_res.BayProxy); + + auto ships = filter_ship(l, args); + if (ships.empty()) { + return; + } + + Lua::Object player = m_lua_res.Proxy_getData(l, playerProxy); + auto totalCoin = 0; + auto totalOil = 0; + + ships.for_each([&](const Lua::Object& key, const Lua::Object& value) { + if (!is_valid_ship(value)) { + return; + } + std::tuple returnRes = m_lua_res.Ship_calReturnRes(l, value); + totalCoin += std::get<0>(returnRes); + totalOil += std::get<1>(returnRes); + }); + + std::optional triggeredCoinMax = m_lua_res.Player_GoldMax(l, player, totalCoin); + if (triggeredCoinMax.has_value() && triggeredCoinMax.value()) { + return; + } + std::optional triggeredOilMax = m_lua_res.Player_OilMax(l, player, totalOil); + if (triggeredOilMax.has_value() && triggeredOilMax.value()) { + return; + } + + Lua::Table shipId = m_lua_res._map(l, ships, [](sol::this_state, Lua::Object value) { + int id = value.as()["id"]; + return id; + }); + + auto params = Lua::Table(l); + params["destroyEquipment"] = true; + params["shipIds"] = shipId; + + SPDLOG_INFO("Retire {} ships", shipId.size()); + m_lua_res.pg_m02_sendNotification(l, m_lua_res.pg_m02, m_lua_res.GAME_DESTROY_SHIPS, params); +} + void Cracker::better_global_speedup_set_rate_with_pause(double rate) { m_lua_state_pauser.do_when_paused([&]() { m_state["ys"]["Battle"]["BattleConfig"]["BASIC_TIME_SCALE"] = rate; @@ -1623,6 +1837,7 @@ Cracker::Config Cracker::get_config() { SET_CONFIG_FLAG(INFINITE_BATTLE), SET_CONFIG_FLAG(SKIP_AIR_STRIKE_ANIMATION), SET_CONFIG_FLAG(AUTO_ONCE_AGAIN), + SET_CONFIG_FLAG(AUTO_RETIRE), }, .globle_ship_properties = m_globle_ship_properties, .global_speedup_rate = static_cast(m_global_speedup_rate), @@ -1637,6 +1852,12 @@ Cracker::Config Cracker::get_config() { #define IS_CONFIG_ENABLED(n) config.flag.n void Cracker::apply_config(Config& config) { + if(IS_CONFIG_ENABLED(AUTO_RETIRE)) { + enable_auto_retire(); + } else { + disable_auto_retire(); + } + if(IS_CONFIG_ENABLED(AUTO_ONCE_AGAIN)) { enable_auto_once_again(); } else { @@ -2272,3 +2493,47 @@ void Cracker::clear_hard_mode_ship_properties_limit(Lua::Table& t) { } } } + +std::vector Cracker::s_shipStatus = { + "inFleet", + "inChapter", + "inWorld", + "inEvent", + "inBackyard", + "inClass", + "inTactics", + "inExercise", + "inAdmiral", + "inElite", + "inActivity", + "inGuildEvent", + "inGuildBossEvent", +}; + +std::vector Cracker::s_excludedShips = { + 900493, + 900494, + 900495, + 9702091, + 9702092, + 9702093, + 9702094, +}; + +std::vector Cracker::s_shipRarityId = { + 0, + 2, + 3, + 4, + 5, + 6, +}; + +std::map Cracker::s_shipRarityNameToId = { + {"无", 0}, + {"N", 2}, + {"R", 3}, + {"SR", 4}, + {"SSR", 5}, + {"RN", 6}, +}; diff --git a/blcrack/cracker/cracker.hpp b/blcrack/cracker/cracker.hpp index a8c5fbec1..63e19dfad 100644 --- a/blcrack/cracker/cracker.hpp +++ b/blcrack/cracker/cracker.hpp @@ -119,6 +119,7 @@ public: bool INFINITE_BATTLE = false; bool SKIP_AIR_STRIKE_ANIMATION = false; bool AUTO_ONCE_AGAIN = false; + bool AUTO_RETIRE = false; } flag; ShipProperties globle_ship_properties; int global_speedup_rate = 1; @@ -155,6 +156,9 @@ public: void disable_all(); + void enable_auto_retire(); + void disable_auto_retire(); + void enable_auto_once_again(); void disable_auto_once_again(); @@ -306,6 +310,10 @@ private: void better_global_speedup_set_rate(double rate); + static bool is_valid_ship(const Lua::Object& obj); + Lua::Table filter_ship(sol::this_state& l, Lua::VariadicArgs& args); + void execute_retire_ship(sol::this_state& l, Lua::VariadicArgs& args); + static void modify_ship_properties(Lua::Table& properties, const ShipProperties& new_properties); static void clear_hard_mode_ship_properties_limit(Lua::Table& t); @@ -341,6 +349,7 @@ private: std::atomic INFINITE_BATTLE = false; std::atomic SKIP_AIR_STRIKE_ANIMATION = false; std::atomic AUTO_ONCE_AGAIN = false; + std::atomic AUTO_RETIRE = false; } m_flag; struct { @@ -434,6 +443,28 @@ private: int Ship_ENERGY_LOW; Lua::Function BaseUI_closeView; std::string LevelMediator2_ON_RETRACKING; + Lua::Object PlayerProxy; + Lua::Object BayProxy; + std::string GAME_DESTROY_SHIPS; + Lua::Function Proxy_getData; + Lua::Function Ship_calReturnRes; + Lua::Function Player_GoldMax; + Lua::Function Player_OilMax; + Lua::Object pg_m02; + Lua::Function pg_m02_sendNotification; + Lua::Function BayProxy_getShips; + Lua::Function Ship_isMaxStar; + Lua::Function Ship_getGroupId; + Lua::Function Ship_getMaxStar; + Lua::Function Ship_getStar; + int Ship_LOCK_STATE_UNLOCK; + int Ship_LOCK_STATE_LOCK; + Lua::Function Ship_GetLockState; + Lua::Function Ship_getRarity; + Lua::Function Ship_getFlag; + Lua::Function BayProxy_findShipsByGroup; + Lua::Function _map; + Lua::Function _select; } m_lua_res; struct { @@ -464,6 +495,11 @@ private: std::atomic m_better_global_speedup_need_pause = false; std::atomic m_skip_battle_celebrate_need_pause = false; std::atomic m_fast_stage_move_need_pause = false; + + static std::vector s_shipStatus; + static std::vector s_excludedShips; + static std::vector s_shipRarityId; + static std::map s_shipRarityNameToId; }; #endif //CRACKER_HPP diff --git a/blcrack/cracker/server.cpp b/blcrack/cracker/server.cpp index 447791faa..d5325fb7c 100644 --- a/blcrack/cracker/server.cpp +++ b/blcrack/cracker/server.cpp @@ -1256,6 +1256,28 @@ CrackerServer::CrackerServer() { CRACK_OK(); }); + Post("/enable_auto_retire", [](const httplib::Request& req, httplib::Response& res) { + try { + Cracker::Instance().enable_auto_retire(); + } catch (std::exception& e) { + SPDLOG_ERROR("Enable auto retire failed: {}", e.what()); + res.status = 500; + return; + } + CRACK_OK(); + }); + + Post("/disable_auto_retire", [](const httplib::Request& req, httplib::Response& res) { + try { + Cracker::Instance().disable_auto_retire(); + } catch (std::exception& e) { + SPDLOG_ERROR("Disable auto retire failed: {}", e.what()); + res.status = 500; + return; + } + CRACK_OK(); + }); + Post("/init", [](const httplib::Request& req, httplib::Response& res) { try { Cracker::Instance(); diff --git a/blcrack/cracker/ui/ui.cpp b/blcrack/cracker/ui/ui.cpp index bd9dac1e2..92a31e875 100644 --- a/blcrack/cracker/ui/ui.cpp +++ b/blcrack/cracker/ui/ui.cpp @@ -162,6 +162,7 @@ void CrackerUI::draw_menu() { ImGui::Checkbox("章节图自动开荒", &CONFIG_FLAG(CHAPTER_AUTO_CLEAR)); ImGui::Checkbox("无限连战", &CONFIG_FLAG(INFINITE_BATTLE)); ImGui::Checkbox("自动再次前往", &CONFIG_FLAG(AUTO_ONCE_AGAIN)); + ImGui::Checkbox("自动退役", &CONFIG_FLAG(AUTO_RETIRE)); ImGui::EndTable(); } diff --git a/config/template.json b/config/template.json index b1a9f4751..014f61840 100644 --- a/config/template.json +++ b/config/template.json @@ -233,7 +233,8 @@ "SkipStory": false, "InfiniteBattle": false, "SkipAirStrikeAnimation": false, - "AutoOnceAgain": false + "AutoOnceAgain": false, + "AutoRetire": false }, "ShipProperty": { "Method": "disable", diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 1d14c9912..04ef2606b 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -929,6 +929,10 @@ "AutoOnceAgain": { "type": "checkbox", "value": false + }, + "AutoRetire": { + "type": "checkbox", + "value": false } }, "ShipProperty": { diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 5b87fcc13..923f5c37d 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -899,6 +899,7 @@ Misc: InfiniteBattle: false SkipAirStrikeAnimation: false AutoOnceAgain: false + AutoRetire: false # ==================== Cheat ==================== PowerLimit: diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 16fc93c07..e88433d95 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -554,6 +554,7 @@ class GeneratedConfig: Misc_InfiniteBattle = False Misc_SkipAirStrikeAnimation = False Misc_AutoOnceAgain = False + Misc_AutoRetire = False # Group `PowerLimit` PowerLimit_Enable = True diff --git a/module/config/full_config_generated.py b/module/config/full_config_generated.py index df6ac7d70..445acee36 100644 --- a/module/config/full_config_generated.py +++ b/module/config/full_config_generated.py @@ -167,6 +167,7 @@ class FullGeneratedConfig: Hook_Misc_InfiniteBattle = None Hook_Misc_SkipAirStrikeAnimation = None Hook_Misc_AutoOnceAgain = None + Hook_Misc_AutoRetire = None Hook_ShipProperty_Method = None Hook_ShipProperty_Factor = None Hook_ShipProperty_Armor = None diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index fa114a4bf..4cf780e32 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -3185,6 +3185,10 @@ "AutoOnceAgain": { "name": "Misc.AutoOnceAgain.name", "help": "Misc.AutoOnceAgain.help" + }, + "AutoRetire": { + "name": "Misc.AutoRetire.name", + "help": "Misc.AutoRetire.help" } }, "PowerLimit": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 5e824bd88..b60fa53fc 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -3185,6 +3185,10 @@ "AutoOnceAgain": { "name": "Misc.AutoOnceAgain.name", "help": "Misc.AutoOnceAgain.help" + }, + "AutoRetire": { + "name": "Misc.AutoRetire.name", + "help": "Misc.AutoRetire.help" } }, "PowerLimit": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index a3ec0c484..0679befde 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -3185,6 +3185,10 @@ "AutoOnceAgain": { "name": "自动再次前往", "help": "" + }, + "AutoRetire": { + "name": "自动退役", + "help": "注意:该功能存在风险,开启前请确保船是锁上的!!!" } }, "PowerLimit": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 409fe62ed..425b33de5 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -3185,6 +3185,10 @@ "AutoOnceAgain": { "name": "Misc.AutoOnceAgain.name", "help": "Misc.AutoOnceAgain.help" + }, + "AutoRetire": { + "name": "Misc.AutoRetire.name", + "help": "Misc.AutoRetire.help" } }, "PowerLimit": { diff --git a/module/luahook/api.py b/module/luahook/api.py index 2f3c9ed05..8055f3042 100644 --- a/module/luahook/api.py +++ b/module/luahook/api.py @@ -416,6 +416,12 @@ class CrackApi: def disable_auto_once_again(self): self.post("disable_auto_once_again") + def enable_auto_retire(self): + self.post("enable_auto_retire") + + def disable_auto_retire(self): + self.post("disable_auto_retire") + def init(self): self.post("init") diff --git a/module/luahook/crack.py b/module/luahook/crack.py index 3b0723e87..1188baaba 100644 --- a/module/luahook/crack.py +++ b/module/luahook/crack.py @@ -48,6 +48,7 @@ ALL_ENABLE_OPS = [ CrackOp.EnableInfiniteBattle, CrackOp.EnableSkipAirStrikeAnimation, CrackOp.EnableAutoOnceAgain, + CrackOp.EnableAutoRetire, ] REMOTE_PORT = 23897 @@ -349,6 +350,11 @@ def do_crack_op(config: AzurLaneConfig, device: Device, ops: Union[Type[CrackOp. api.enable_auto_once_again() elif op == CrackOp.DisableAutoOnceAgain: api.disable_auto_once_again() + elif op == CrackOp.EnableAutoRetire: + if full_config.Hook_Misc_AutoRetire: + api.enable_auto_retire() + elif op == CrackOp.DisableAutoRetire: + api.disable_auto_retire() else: logger.error(f"Unsupported op: {op}") @@ -410,6 +416,7 @@ CHAPTER_CRACK_OPS = [ CrackOp.EnableInfiniteBattle, CrackOp.EnableSkipAirStrikeAnimation, CrackOp.EnableAutoOnceAgain, + CrackOp.EnableAutoRetire, ] diff --git a/module/luahook/op.py b/module/luahook/op.py index 1ff24b00d..dc6b8e272 100644 --- a/module/luahook/op.py +++ b/module/luahook/op.py @@ -196,3 +196,9 @@ class CrackOp: class DisableAutoOnceAgain(Op): ... + + class EnableAutoRetire(Op): + ... + + class DisableAutoRetire(Op): + ...