Files
mtgodot-poc/formats/tests/combo_table_test.cpp
T

143 lines
7.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// M2 §3.5 —— playersettingmodule.py 的 ComboAttackNew 调用 -> PC 连击段表。
// 照 GameLib/RaceData.cpp CRaceData::ReserveComboAttack / RegisterComboAttack 语义。
#include <combo_table.h>
#include <cstdio>
#include <cstdlib>
#include <string>
using namespace fmt;
static int g_fail = 0;
#define CHECK(c, msg) \
do { \
if (!(c)) { \
std::fprintf(stderr, "FAIL: %s\n", (msg)); \
++g_fail; \
} \
} while (0)
// warrior_m/_w 共用的 __LoadGameWarriorEx 片段 + assassin 的 DUALHAND(男限),
// 外加一个非 Ex 的 def 用来验证块复位。缩进 / tab 混用照原文。
static const char *kPy = R"PY(
COMBO_TYPE_1 = 0
COMBO_TYPE_2 = 1
COMBO_TYPE_3 = 2
COMBO_INDEX_1 = 0
COMBO_INDEX_6 = 5
def __LoadGameWarriorEx(race, path):
chrmgr.SelectRace(race)
chrmgr.ReserveComboAttackNew(chr.MOTION_MODE_GENERAL, COMBO_TYPE_1, 1)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_GENERAL, COMBO_TYPE_1, COMBO_INDEX_1, chr.MOTION_COMBO_ATTACK_1)
## Combo Type 1
chrmgr.ReserveComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_1, 4)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_1, COMBO_INDEX_1, chr.MOTION_COMBO_ATTACK_1)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_1, COMBO_INDEX_2, chr.MOTION_COMBO_ATTACK_2)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_1, COMBO_INDEX_3, chr.MOTION_COMBO_ATTACK_3)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_1, COMBO_INDEX_4, chr.MOTION_COMBO_ATTACK_4)
## Combo Type 2 —— 段号跳到 5 / 7(非连续,验证按下标写而非追加)
chrmgr.ReserveComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, 5)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, COMBO_INDEX_1, chr.MOTION_COMBO_ATTACK_1)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, COMBO_INDEX_2, chr.MOTION_COMBO_ATTACK_2)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, COMBO_INDEX_3, chr.MOTION_COMBO_ATTACK_3)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, COMBO_INDEX_4, chr.MOTION_COMBO_ATTACK_5)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, COMBO_INDEX_5, chr.MOTION_COMBO_ATTACK_7)
# 越界的 Register(下标 5 >= size 5):照参考端 AssertLog + return,忽略
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, 5, chr.MOTION_COMBO_ATTACK_8)
# 对已存在 key 的重复 Reservemap::insert 不覆盖,原向量保留
chrmgr.ReserveComboAttackNew(chr.MOTION_MODE_ONEHAND_SWORD, COMBO_TYPE_2, 99)
def __LoadGameAssassinEx(race, path):
chrmgr.SelectRace(race)
chrmgr.ReserveComboAttackNew(chr.MOTION_MODE_DUALHAND_SWORD, COMBO_TYPE_3, 6)
chrmgr.RegisterComboAttackNew(chr.MOTION_MODE_DUALHAND_SWORD, COMBO_TYPE_3, COMBO_INDEX_6, chr.MOTION_COMBO_ATTACK_8)
def __LoadGameNPC():
# 块外的调用不该落到任何职业里
chrmgr.ReserveComboAttackNew(chr.MOTION_MODE_GENERAL, COMBO_TYPE_1, 3)
)PY";
static void check_vec(const std::vector<uint16_t> *v, std::initializer_list<uint16_t> want, const char *msg) {
if (!v) { std::fprintf(stderr, "FAIL: %s (nullptr)\n", msg); ++g_fail; return; }
if (v->size() != want.size()) { std::fprintf(stderr, "FAIL: %s (size %zu != %zu)\n", msg, v->size(), want.size()); ++g_fail; return; }
size_t i = 0;
for (uint16_t w : want) {
if ((*v)[i] != w) { std::fprintf(stderr, "FAIL: %s ([%zu]=%u != %u)\n", msg, i, (*v)[i], w); ++g_fail; return; }
++i;
}
}
int main() {
// ---- key 编码:RaceData.h MAKE_COMBO_KEY -------------------------------
CHECK(make_combo_key(COMBO_MODE_ONEHAND_SWORD, 1) == ((2u << 16) | 1u), "make_combo_key");
CHECK(combo_key_mode(make_combo_key(5, 2)) == 5 && combo_key_type(make_combo_key(5, 2)) == 2, "combo_key split");
// ---- 解析 ------------------------------------------------------------
PlayerComboTables tbl;
std::string err;
if (!parse_player_combo_tables(kPy, tbl, &err)) {
std::fprintf(stderr, "parse failed: %s\n", err.c_str());
return 1;
}
const ComboTable &w = tbl.klass(COMBO_CLASS_WARRIOR);
const ComboTable &a = tbl.klass(COMBO_CLASS_ASSASSIN);
const ComboTable &s = tbl.klass(COMBO_CLASS_SURA);
// GENERAL / type1 —— 单段普攻连击(NAME_COMBO_ATTACK_1 = 14
check_vec(w.get(COMBO_MODE_GENERAL, 0), {14}, "warrior GENERAL type1 = [14]");
// ONEHAND_SWORD / type1 —— 连续 4 段 14..17
check_vec(w.get(COMBO_MODE_ONEHAND_SWORD, 0), {14, 15, 16, 17}, "warrior 1H type1 = [14,15,16,17]");
// ONEHAND_SWORD / type2 —— 5 段,末两段跳到 18 / 20COMBO_ATTACK_5 / _7
check_vec(w.get(COMBO_MODE_ONEHAND_SWORD, 1), {14, 15, 16, 18, 20}, "warrior 1H type2 = [14,15,16,18,20]");
// 越界 Register 被忽略;重复 Reserve 不覆盖(size 仍是 5,不是 99)
CHECK(w.get(COMBO_MODE_ONEHAND_SWORD, 1)->size() == 5, "dup Reserve ignored / OOB Register ignored");
// type3 从没 Reserve 过 -> GetComboDataPointer 应为 nullptr
CHECK(w.get(COMBO_MODE_ONEHAND_SWORD, 2) == nullptr, "warrior 1H type3 absent -> nullptr");
// 职业隔离:DUALHAND 只在 assassin 块里;warrior/sura 没有
check_vec(a.get(COMBO_MODE_DUALHAND_SWORD, 2), {0, 0, 0, 0, 0, 21}, "assassin DUAL type3 idx5 = COMBO_ATTACK_8(21)");
CHECK(w.get(COMBO_MODE_DUALHAND_SWORD, 2) == nullptr, "warrior has no DUALHAND table");
CHECK(s.empty(), "sura block absent in snippet -> empty table");
// 块外(__LoadGameNPC)的 Reserve 不该落进任何职业
size_t total = 0;
for (int c = 0; c < COMBO_CLASS_COUNT; ++c) total += tbl.klass(c).key_count();
CHECK(total == 4, "only the 4 in-Ex-block combo keys registered (2 warrior 1H + 1 warrior GENERAL + 1 assassin DUAL)");
// 越界职业索引 -> 空表,不崩
CHECK(tbl.klass(-1).empty() && tbl.klass(99).empty(), "out-of-range class -> empty");
// ---- 可选:真实 playersettingmodule.py(设了 M2_ASSETS 才跑)----------
if (const char *root = std::getenv("M2_ASSETS")) {
const std::string path = std::string(root) + "/root/playersettingmodule.py";
PlayerComboTables live;
std::string lerr;
if (parse_player_combo_tables_file(path, live, &lerr)) {
check_vec(live.klass(COMBO_CLASS_WARRIOR).get(COMBO_MODE_ONEHAND_SWORD, 0),
{14, 15, 16, 17}, "live warrior 1H type1");
// sura ONEHAND type3 末段回到 COMBO_ATTACK_4 (17)
const auto *sv = live.klass(COMBO_CLASS_SURA).get(COMBO_MODE_ONEHAND_SWORD, 2);
CHECK(sv && sv->size() == 6 && sv->back() == 17, "live sura 1H type3 tail = COMBO_ATTACK_4");
// shaman FAN type1 = 4 连续段
check_vec(live.klass(COMBO_CLASS_SHAMAN).get(COMBO_MODE_FAN, 0),
{14, 15, 16, 17}, "live shaman FAN type1");
std::printf("live playersettingmodule.py: OK\n");
} else {
std::fprintf(stderr, "live parse skipped: %s\n", lerr.c_str());
}
}
if (g_fail == 0) {
std::printf("PASS: formats_combo_table_test (§3.5 PC 连击段表)\n");
}
return g_fail == 0 ? 0 : 1;
}