diff --git a/3rdparty/include/meojson/json.hpp b/3rdparty/include/meojson/json.hpp index dc18989d31..bbef928765 100644 --- a/3rdparty/include/meojson/json.hpp +++ b/3rdparty/include/meojson/json.hpp @@ -30,7 +30,7 @@ namespace json using array_ptr = std::unique_ptr; using object_ptr = std::unique_ptr; public: - enum class value_type : char + enum class value_type: char { Invalid, Null, @@ -87,6 +87,9 @@ namespace json bool is_string() const noexcept { return _type == value_type::String; } bool is_array() const noexcept { return _type == value_type::Array; } bool is_object() const noexcept { return _type == value_type::Object; } + template + bool is() const noexcept; + bool contains(const std::string& key) const; bool contains(size_t pos) const; bool exists(const std::string& key) const { return contains(key); } @@ -117,6 +120,8 @@ namespace json const std::string as_string() const; const array& as_array() const; const object& as_object() const; + template + Type as() const; array& as_array(); object& as_object(); @@ -394,11 +399,11 @@ namespace json // ************************* // * exception declare * // ************************* - class exception : public std::exception + class exception: public std::exception { public: exception() = default; - exception(const std::string& msg) : _what(msg) {} + exception(const std::string& msg): _what(msg) {} exception(const exception&) = default; exception& operator=(const exception&) = default; @@ -525,6 +530,40 @@ namespace json // for Pimpl MEOJSON_INLINE value::~value() = default; + template + MEOJSON_INLINE bool value::is() const noexcept + { + if constexpr (std::is_same_v) { + return true; + } + else if constexpr (std::is_same_v) { + return _type == value_type::Boolean; + } + else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) { + return _type == value_type::Number; + } + else if constexpr (std::is_same_v) { + return _type == value_type::String; + } + else if constexpr (std::is_same_v) { + return _type == value_type::Array; + } + else if constexpr (std::is_same_v) { + return _type == value_type::Object; + } + else { + static_assert(false, "Unsupported type"); + } + } + MEOJSON_INLINE bool value::contains(const std::string& key) const { return is_object() && as_object().contains(key); @@ -782,6 +821,12 @@ namespace json throw exception("Wrong Type or data empty"); } + template + MEOJSON_INLINE Type value::as() const + { + return static_cast(*this); + } + MEOJSON_INLINE const std::string& value::as_basic_type_str() const { return std::get(_raw_data); @@ -1308,7 +1353,8 @@ namespace json if (!contains(pos)) { return std::nullopt; } - return static_cast(_array_data.at(pos)); + const auto& val = _array_data.at(pos); + return val.is() ? std::optional(val.as()) : std::nullopt; } MEOJSON_INLINE array::iterator array::begin() noexcept @@ -1765,7 +1811,8 @@ namespace json if (iter == _object_data.end()) { return std::nullopt; } - return static_cast(iter->second); + const auto& val = iter->second; + return val.is() ? std::optional(val.as()) : std::nullopt; } MEOJSON_INLINE object::iterator object::begin() noexcept diff --git a/resource/battle_data.json b/resource/battle_data.json index e2b928782c..bea11726f6 100644 --- a/resource/battle_data.json +++ b/resource/battle_data.json @@ -20,7 +20,10 @@ "3-8", "3-10" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "Mon3tr" + ] }, "char_009_12fce": { "name": "12F", @@ -86,7 +89,10 @@ "x-1", "x-1" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "斯卡蒂的海嗣" + ] }, "char_1013_chen2": { "name": "假日威龙陈", @@ -108,7 +114,10 @@ "1-1", "1-1" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "“耀阳”" + ] }, "char_101_sora": { "name": "空", @@ -251,7 +260,10 @@ "3-3", "3-3" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "医疗探机" + ] }, "char_109_fmout": { "name": "远山", @@ -273,7 +285,10 @@ "3-1", "3-1" ], - "rarity": 4 + "rarity": 4, + "tokens": [ + "触手" + ] }, "char_112_siege": { "name": "推进之王", @@ -295,7 +310,10 @@ "3-8", "3-10" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "此面向敌" + ] }, "char_115_headbr": { "name": "凛冬", @@ -746,7 +764,10 @@ "y-2", "y-2" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "幻影" + ] }, "char_180_amgoat": { "name": "艾雅法拉", @@ -933,7 +954,10 @@ "3-6", "3-6" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "\"小自在\"" + ] }, "char_201_moeshd": { "name": "可颂", @@ -955,7 +979,12 @@ "3-1", "3-1" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "“清平”", + "“逍遥”", + "“弦惊”" + ] }, "char_202_demkni": { "name": "塞雷娅", @@ -1219,7 +1248,10 @@ "3-1", "3-1" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "机械水獭" + ] }, "char_243_waaifu": { "name": "槐琥", @@ -1241,7 +1273,12 @@ "3-1", "3-1" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "龙腾.F", + "龙腾.L", + "龙腾.A" + ] }, "char_250_phatom": { "name": "傀影", @@ -1252,7 +1289,10 @@ "1-1", "1-1" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "镜中虚影" + ] }, "char_252_bibeak": { "name": "柏喙", @@ -1285,7 +1325,10 @@ "y-6", "y-6" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "诅咒娃娃" + ] }, "char_258_podego": { "name": "波登可", @@ -1725,7 +1768,10 @@ "3-1", "3-1" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "移动摄影器" + ] }, "char_337_utage": { "name": "宴", @@ -1780,7 +1826,10 @@ "x-1", "x-1" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "沙之碑" + ] }, "char_345_folnic": { "name": "亚叶", @@ -2055,7 +2104,10 @@ "3-9", "3-9" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "迷迭香的战术装备" + ] }, "char_4000_jnight": { "name": "正义骑士号", @@ -2099,7 +2151,10 @@ "1-1", "1-1" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "工程蓄水炮" + ] }, "char_4013_kjera": { "name": "耶拉", @@ -2132,7 +2187,10 @@ "1-1", "1-1" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "纸偶" + ] }, "char_4017_puzzle": { "name": "谜图", @@ -2286,7 +2344,10 @@ "3-14", "3-14" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "旧日残影" + ] }, "char_4047_pianst": { "name": "车尔尼", @@ -2308,7 +2369,10 @@ "3-3", "3-3" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "共振装置" + ] }, "char_4054_malist": { "name": "至简", @@ -2330,7 +2394,10 @@ "3-6", "3-6" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "“打字机”" + ] }, "char_405_absin": { "name": "苦艾", @@ -2429,7 +2496,12 @@ "1-1", "1-1" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "白铁™多功能平台", + "白铁™多功能平台", + "铁钳号·原型机" + ] }, "char_411_tomimi": { "name": "特米米", @@ -2528,7 +2600,10 @@ "3-3", "3-3" ], - "rarity": 6 + "rarity": 6, + "tokens": [ + "狼群" + ] }, "char_430_fartth": { "name": "远牙", @@ -2561,7 +2636,10 @@ "1-1", "1-1" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "可靠电池" + ] }, "char_436_whispr": { "name": "絮雨", @@ -2616,7 +2694,10 @@ "3-3", "3-3" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "\"夹子\"" + ] }, "char_452_bstalk": { "name": "豆苗", @@ -2627,7 +2708,10 @@ "3-3", "3-3" ], - "rarity": 4 + "rarity": 4, + "tokens": [ + "磐蟹护卫队" + ] }, "char_455_nothin": { "name": "乌有", @@ -2671,7 +2755,10 @@ "3-3", "3-3" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "迎宾踏垫" + ] }, "char_459_tachak": { "name": "战车", @@ -2759,7 +2846,10 @@ "3-3", "3-3" ], - "rarity": 5 + "rarity": 5, + "tokens": [ + "眠兽" + ] }, "char_478_kirara": { "name": "绮良", @@ -2792,7 +2882,10 @@ "1-1", "1-1" ], - "rarity": 4 + "rarity": 4, + "tokens": [ + "全自动造型仪" + ] }, "char_485_pallas": { "name": "帕拉斯", diff --git a/src/MaaCore/Common/AsstBattleDef.h b/src/MaaCore/Common/AsstBattleDef.h index a2ed15e78a..2c7fe43313 100644 --- a/src/MaaCore/Common/AsstBattleDef.h +++ b/src/MaaCore/Common/AsstBattleDef.h @@ -129,6 +129,7 @@ namespace asst::battle std::array ranges; int rarity = 0; LocationType location_type = LocationType::None; + std::vector tokens; // 召唤物名字 }; using AttackRange = std::vector; diff --git a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp index 46fd8f356b..6f2a37e197 100644 --- a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp +++ b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.cpp @@ -47,6 +47,12 @@ bool asst::BattleDataConfig::parse(const json::value& json) const auto& rarity = char_data_json.at("rarity").as_integer(); data.rarity = rarity; + if (auto tokens_opt = char_data_json.find("tokens")) { + for (const auto& token : *tokens_opt) { + data.tokens.emplace_back(token.as_string()); + } + } + m_chars.emplace(std::move(name), std::move(data)); } for (const auto& [id, points_json] : json.at("ranges").as_object()) { diff --git a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h index 9ae2ca6609..30bece6647 100644 --- a/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h +++ b/src/MaaCore/Config/Miscellaneous/BattleDataConfig.h @@ -64,6 +64,16 @@ namespace asst return range_iter->second; } + const std::vector& get_tokens(const std::string& name) const + { + auto char_iter = m_chars.find(name); + if (char_iter == m_chars.cend()) { + static const std::vector Empty; + return Empty; + } + return char_iter->second.tokens; + } + protected: virtual bool parse(const json::value& json) override; diff --git a/src/MaaCore/MaaCore.vcxproj.filters b/src/MaaCore/MaaCore.vcxproj.filters index 855761ced7..efce386b86 100644 --- a/src/MaaCore/MaaCore.vcxproj.filters +++ b/src/MaaCore/MaaCore.vcxproj.filters @@ -459,6 +459,9 @@ 源文件\Task + + 源文件\Utils + diff --git a/src/MaaCore/Task/BattleHelper.cpp b/src/MaaCore/Task/BattleHelper.cpp index 4a2f584761..50bf95fba3 100644 --- a/src/MaaCore/Task/BattleHelper.cpp +++ b/src/MaaCore/Task/BattleHelper.cpp @@ -44,6 +44,41 @@ bool asst::BattleHelper::calc_tiles_info(const std::string& stage_name) return true; } +bool asst::BattleHelper::load_avatar_cache(const std::string& name) +{ + LogTraceFunction; + + if (name.empty()) { + return false; + } + auto path = AvatarCacheDir / utils::path(name + CacheExtension); + Log.info(path); + + if (!std::filesystem::exists(path)) { + Log.info(path, "not exists"); + return false; + } + cv::Mat avatar = asst::imread(path); + if (avatar.empty()) { + Log.info(path, "image is empty"); + return false; + } + m_all_deployment_avatars.emplace(name, std::move(avatar)); + Log.info(path, "loaded"); + return true; +} + +void asst::BattleHelper::save_avatar_cache(const std::string& name, const cv::Mat& avatar) +{ + LogTraceFunction; + + auto path = AvatarCacheDir / utils::path(name + CacheExtension); + Log.info(path); + + std::filesystem::create_directories(AvatarCacheDir); + asst::imwrite(path, avatar); +} + bool asst::BattleHelper::pause() { LogTraceFunction; @@ -151,6 +186,7 @@ bool asst::BattleHelper::update_deployment(bool init) oper.name = name; m_cur_deployment_opers.insert_or_assign(name, oper); m_all_deployment_avatars.insert_or_assign(name, oper.avatar); + save_avatar_cache(name, oper.avatar); } pause(); cancel_oper_selection(); diff --git a/src/MaaCore/Task/BattleHelper.h b/src/MaaCore/Task/BattleHelper.h index 29c9193c57..77a78cbfe9 100644 --- a/src/MaaCore/Task/BattleHelper.h +++ b/src/MaaCore/Task/BattleHelper.h @@ -6,6 +6,8 @@ #include "Config/Miscellaneous/TilePack.h" #include "InstHelper.h" #include "Utils/NoWarningCVMat.h" +#include "Utils/Platform.hpp" +#include "Utils/WorkingDir.hpp" #include @@ -13,6 +15,9 @@ namespace asst { class BattleHelper { + inline static const auto AvatarCacheDir = UserDir.get() / utils::path("cache") / utils::path("avatars"); + inline static const std::string CacheExtension = ".png"; + public: ~BattleHelper() = default; @@ -22,6 +27,8 @@ namespace asst virtual bool set_stage_name(const std::string& name); bool calc_tiles_info(const std::string& stage_name); + bool load_avatar_cache(const std::string& name); + void save_avatar_cache(const std::string& name, const cv::Mat& avatar); bool pause(); bool speed_up(); diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp index 155ea2b80a..d88b58386e 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.cpp @@ -7,6 +7,7 @@ #include "Utils/NoWarningCV.h" +#include "Config/Miscellaneous/BattleDataConfig.h" #include "Config/Miscellaneous/CopilotConfig.h" #include "Config/Miscellaneous/TilePack.h" #include "Config/TaskData.h" @@ -35,6 +36,7 @@ bool asst::BattleProcessTask::_run() return false; } + load_cache(); update_deployment(true); to_group(); @@ -63,6 +65,23 @@ bool asst::BattleProcessTask::set_stage_name(const std::string& stage_name) return true; } +void asst::BattleProcessTask::load_cache() +{ + LogTraceFunction; + + for (const auto& oper_list : m_combat_data.groups | views::values) { + for (const auto& oper : oper_list) { + load_avatar_cache(oper.name); + if (auto tokens = BattleData.get_tokens(oper.name); !tokens.empty()) { + for (const std::string& token_name : tokens) { + load_avatar_cache(token_name); + } + } + } + } + // TODO: 识别编队,额外加载编队中有的干员的缓存 +} + bool asst::BattleProcessTask::to_group() { std::unordered_map> groups; diff --git a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h index 6c4420d9dc..10e3965e27 100644 --- a/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h +++ b/src/MaaCore/Task/Miscellaneous/BattleProcessTask.h @@ -21,6 +21,7 @@ namespace asst virtual bool _run() override; virtual AbstractTask& this_task() override { return *this; } + void load_cache(); bool to_group(); bool do_action(size_t action_index); void notify_action(const battle::copilot::Action& action); diff --git a/src/MaaCore/Utils/Algorithm.hpp b/src/MaaCore/Utils/Algorithm.hpp index d6006ea2fe..e4d3948399 100644 --- a/src/MaaCore/Utils/Algorithm.hpp +++ b/src/MaaCore/Utils/Algorithm.hpp @@ -7,7 +7,7 @@ namespace asst::algorithm { - std::optional> get_char_allocation_for_each_group( + static std::optional> get_char_allocation_for_each_group( const std::unordered_map>& group_list, const std::unordered_set& char_set) { diff --git a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp index 118622ce59..5bfa8ae83d 100644 --- a/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/BattleImageAnalyzer.cpp @@ -129,7 +129,10 @@ bool asst::BattleImageAnalyzer::opers_analyze() if (oper.rect.x + oper.rect.width >= m_image.cols) { oper.rect.width = m_image.cols - oper.rect.x; } - oper.avatar = m_image(make_rect(oper.rect)); + auto avatar_rect = make_rect(oper.rect); + avatar_rect.y += 30; + avatar_rect.height = 70; + oper.avatar = m_image(avatar_rect); Rect available_rect = flag_mrect.rect.move(avlb_move); oper.available = oper_available_analyze(available_rect); diff --git a/tools/ResourceUpdater/main.cpp b/tools/ResourceUpdater/main.cpp index 9985e009ca..90fae05658 100644 --- a/tools/ResourceUpdater/main.cpp +++ b/tools/ResourceUpdater/main.cpp @@ -646,7 +646,8 @@ bool update_battle_chars_info(const std::filesystem::path& input_dir, const std: range.emplace(id, std::move(points)); } - auto& chars = result["chars"].as_object(); + auto& chars = result["chars"]; + std::map> tokens; for (auto& [id, char_data] : chars_json.as_object()) { json::value char_new_data; std::string name = char_data["name"].as_string(); @@ -662,15 +663,36 @@ bool update_battle_chars_info(const std::filesystem::path& input_dir, const std: char_new_data["rarity"] = static_cast(char_data["rarity"]) + 1; char_new_data["position"] = char_data["position"]; - chars.emplace(id, std::move(char_new_data)); + if (auto token_opt = char_data.find("tokenKey")) { + tokens[id].emplace_back(*token_opt); + } + if (auto skill_opt = char_data.find("skills")) { + for (const auto& skill_obj : *skill_opt) { + if (auto token_opt = skill_obj.find("overrideTokenKey")) { + tokens[id].emplace_back(*token_opt); + } + } + } + chars.object_emplace(id, std::move(char_new_data)); } + for (const auto& [oper_id, token_id_list] : tokens) { + std::vector token_names_list; + for (const auto& token_id : token_id_list) { + std::string token_name = chars.get(token_id, "name", std::string()); + if (!token_name.empty()) { + token_names_list.emplace_back(token_name); + } + } + chars[oper_id]["tokens"] = json::array(token_names_list); + } + json::value Amiya_data; Amiya_data["name"] = "阿米娅-WARRIOR"; Amiya_data["profession"] = "WARRIOR"; Amiya_data["rangeId"] = json::array { "1-1", "1-1", "1-1" }; Amiya_data["rarity"] = 5; Amiya_data["position"] = "MELEE"; - chars.emplace("char_1001_amiya2", std::move(Amiya_data)); + chars.object_emplace("char_1001_amiya2", std::move(Amiya_data)); const auto& out_file = output_dir / "battle_data.json"; std::ofstream ofs(out_file, std::ios::out);