cleanup: remove Godot-era test data, formats/, dead proto reader and duplicates
- test/: bgfx/Godot render goldens and compare shots nothing reads any more; the Granny oracle result m2-numeric.json moves to oracle/ - formats/: only the .spt sniffer was still linked; it moves into platform/SpeedTreeLib, the rest and tools/map_probe go - bgm/ duplicated Client/BGM byte for byte; tools/root archived root scripts that root.epk already holds; extension/src/proto had no build reference - GEMINI.md duplicated AGENTS.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0137f852d3
commit
70710477cf
@@ -10,8 +10,7 @@ if(NOT TARGET mtpython)
|
||||
list(FILTER MT_PLATFORM_SOURCES EXCLUDE REGEX "/ScriptLib/")
|
||||
endif()
|
||||
add_library(port_platform STATIC ${MT_PLATFORM_SOURCES})
|
||||
target_link_libraries(port_platform PUBLIC port_logic PRIVATE mt3p::freetype xrender::libgr2 xrender::formats)
|
||||
target_include_directories(port_platform PRIVATE ${CMAKE_SOURCE_DIR}/formats)
|
||||
target_link_libraries(port_platform PUBLIC port_logic PRIVATE mt3p::freetype xrender::libgr2)
|
||||
foreach(src IN LISTS MT_PLATFORM_SOURCES)
|
||||
mt_port_apply_defines(${src})
|
||||
endforeach()
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include "SpeedTreeLib/StdAfx.h"
|
||||
|
||||
#include <SpeedTreeRT.h>
|
||||
#include <spt.h>
|
||||
#include "spt.h"
|
||||
|
||||
#include "../EterLib/RenderCommands3D.h"
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "spt.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace fmt {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ends_with_ci(const std::string& s, const char* suf) {
|
||||
size_t n = std::strlen(suf);
|
||||
if (s.size() < n) return false;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
if (std::tolower((unsigned char)s[s.size() - n + i]) != std::tolower((unsigned char)suf[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string token_ci(const std::string& bytes, const char* needle) {
|
||||
const size_t n = std::strlen(needle);
|
||||
if (n == 0 || bytes.size() < n) return "";
|
||||
for (size_t i = 0; i + n <= bytes.size(); ++i) {
|
||||
bool match = true;
|
||||
for (size_t j = 0; j < n; ++j) {
|
||||
if (std::tolower((unsigned char)bytes[i + j]) !=
|
||||
std::tolower((unsigned char)needle[j])) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) continue;
|
||||
size_t e = i + n;
|
||||
while (e < bytes.size()) {
|
||||
const unsigned char c = (unsigned char)bytes[e];
|
||||
if (!(std::isalnum(c) || c == '_' || c == '-')) break;
|
||||
++e;
|
||||
}
|
||||
return bytes.substr(i, e - i);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string as_dds(std::string s) {
|
||||
if (s.empty()) return s;
|
||||
const size_t dot = s.find_last_of('.');
|
||||
if (dot != std::string::npos) s.resize(dot);
|
||||
return s + ".dds";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool sniff_spt(const std::string& b, SptInfo& out) {
|
||||
out = SptInfo{};
|
||||
if (b.size() < 20) return false;
|
||||
|
||||
std::memcpy(&out.header_dword, b.data(), 4);
|
||||
uint32_t maglen = 0;
|
||||
std::memcpy(&maglen, b.data() + 4, 4);
|
||||
if (maglen >= 4 && maglen <= 32 && 8 + maglen <= b.size()) {
|
||||
out.magic.assign(b.data() + 8, b.data() + 8 + maglen);
|
||||
// 去掉可能的尾随 NUL
|
||||
while (!out.magic.empty() && out.magic.back() == '\0') out.magic.pop_back();
|
||||
}
|
||||
out.ok = out.magic.rfind("__IdvSpt", 0) == 0;
|
||||
|
||||
// 扫描可打印 ASCII 段,收 .tga / .dds 结尾的
|
||||
std::string cur;
|
||||
auto flush = [&] {
|
||||
if (cur.size() >= 5 && (ends_with_ci(cur, ".tga") || ends_with_ci(cur, ".dds"))) {
|
||||
// 只留文件名基础部分之后的路径(原样)
|
||||
out.texture_refs.push_back(cur);
|
||||
}
|
||||
cur.clear();
|
||||
};
|
||||
for (unsigned char c : b) {
|
||||
if (c >= 0x20 && c < 0x7F) {
|
||||
cur += (char)c;
|
||||
} else {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
flush();
|
||||
|
||||
// 这两个名字在真实 v2 .spt 中常紧跟二进制标记,不会由上面的
|
||||
// "字符串必须正好以扩展名结尾" 规则捕获。
|
||||
out.composite_texture = as_dds(token_ci(b, "CompositeMap"));
|
||||
out.self_shadow_texture = as_dds(token_ci(b, "CompositeShadowMap"));
|
||||
return out.ok;
|
||||
}
|
||||
|
||||
|
||||
} // namespace fmt
|
||||
@@ -0,0 +1,34 @@
|
||||
// .spt —— SpeedTree RT 私有格式(IDV Inc.)。
|
||||
//
|
||||
// W4 调研结论(2026-08-29):**几何无法读**。`.spt` 的解析在闭源 `CSpeedTreeRT::LoadTree()`
|
||||
// 里(`m2dev-client-src-main/extern/library/SpeedTree/speedtree_static.lib`,实测 COFF x86-64,
|
||||
// IDV 专有,不能链进 macOS/移动端、不可随意分发);`SpeedTreeLib/` 是渲染 wrapper,不含 parser。
|
||||
// → 运行时使用读取真实 bark/composite atlas 的 proxy;真几何走经授权的 Windows x64 离线 exporter。
|
||||
// 2026-09-11: hash-matched native exports for 87 local species are now staged in
|
||||
// assets/TreeGeometry and loaded preferentially by the runtime. This sniffer
|
||||
// remains only the fallback texture reader, not a geometry decoder.
|
||||
//
|
||||
// 这里只做「嗅探」:读文件头 magic + version,抽取内嵌贴图和 composite atlas token。
|
||||
// 不解析枝干/叶片几何。无 godot 依赖。
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fmt {
|
||||
|
||||
struct SptInfo {
|
||||
bool ok = false;
|
||||
std::string magic; // "__IdvSpt_02_" 等
|
||||
uint32_t header_dword = 0; // magic 前的第一个 u32(版本/大小,未定)
|
||||
std::vector<std::string> texture_refs; // 内嵌的 *.tga / *.dds 路径(原样)
|
||||
// SpeedTree 2 把这两个 atlas 名写成不一定以 NUL 结尾的 token(例如
|
||||
// "CompositeMapB1#N"),普通 printable-string 扫描会漏掉。统一转成 .dds,
|
||||
// 供无几何 reader 的 proxy 渲染使用。
|
||||
std::string composite_texture;
|
||||
std::string self_shadow_texture;
|
||||
};
|
||||
|
||||
bool sniff_spt(const std::string& bytes, SptInfo& out);
|
||||
|
||||
} // namespace fmt
|
||||
@@ -1,253 +0,0 @@
|
||||
#include "proto.h"
|
||||
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/lzo.h"
|
||||
#include "GameLib/StdAfx.h"
|
||||
#include "GameLib/ItemData.h"
|
||||
#include "../platform/UserInterface/UserInterface.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace mtproto {
|
||||
|
||||
namespace {
|
||||
|
||||
// CPythonNonPlayer::SMobTable (UserInterface/PythonNonPlayer.h), the 40250 ILP32 layout. The mirror of that header
|
||||
// needs UserInterface/StdAfx.h, which includes the Python headers (after batch 2P); until then the table is here.
|
||||
#pragma pack(push, 1)
|
||||
struct SMobSkillLevel {
|
||||
DWORD dwVnum;
|
||||
BYTE bLevel;
|
||||
};
|
||||
|
||||
struct SMobTable {
|
||||
DWORD dwVnum;
|
||||
char szName[24 + 1]; // CHARACTER_NAME_MAX_LEN (UserInterface/StdAfx.h)
|
||||
char szLocaleName[24 + 1];
|
||||
|
||||
BYTE bType;
|
||||
BYTE bRank;
|
||||
BYTE bBattleType;
|
||||
BYTE bLevel;
|
||||
BYTE bSize;
|
||||
|
||||
DWORD dwGoldMin;
|
||||
DWORD dwGoldMax;
|
||||
DWORD dwExp;
|
||||
DWORD dwMaxHP;
|
||||
BYTE bRegenCycle;
|
||||
BYTE bRegenPercent;
|
||||
WORD wDef;
|
||||
|
||||
DWORD dwAIFlag;
|
||||
DWORD dwRaceFlag;
|
||||
DWORD dwImmuneFlag;
|
||||
|
||||
BYTE bStr, bDex, bCon, bInt;
|
||||
DWORD dwDamageRange[2];
|
||||
|
||||
short sAttackSpeed;
|
||||
short sMovingSpeed;
|
||||
BYTE bAggresiveHPPct;
|
||||
WORD wAggressiveSight;
|
||||
WORD wAttackRange;
|
||||
|
||||
char cEnchants[6]; // MOB_ENCHANTS_MAX_NUM
|
||||
char cResists[11]; // MOB_RESISTS_MAX_NUM
|
||||
|
||||
DWORD dwResurrectionVnum;
|
||||
DWORD dwDropItemVnum;
|
||||
|
||||
BYTE bMountCapacity;
|
||||
BYTE bOnClickType;
|
||||
|
||||
BYTE bEmpire;
|
||||
char szFolder[64 + 1];
|
||||
float fDamMultiply;
|
||||
DWORD dwSummonVnum;
|
||||
DWORD dwDrainSP;
|
||||
DWORD dwMonsterColor;
|
||||
DWORD dwPolymorphItemVnum;
|
||||
|
||||
SMobSkillLevel Skills[5]; // MOB_SKILL_MAX_NUM
|
||||
|
||||
BYTE bBerserkPoint;
|
||||
BYTE bStoneSkinPoint;
|
||||
BYTE bGodSpeedPoint;
|
||||
BYTE bDeathBlowPoint;
|
||||
BYTE bRevivePoint;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(SMobTable) == 255, "mob_proto record");
|
||||
|
||||
using TItemTable = CItemData::TItemTable;
|
||||
|
||||
uint32_t rd_u32(const uint8_t *p) {
|
||||
uint32_t v;
|
||||
std::memcpy(&v, p, 4);
|
||||
return v;
|
||||
}
|
||||
|
||||
std::string cstr(const char *p, size_t maxn) {
|
||||
size_t n = 0;
|
||||
while (n < maxn && p[n]) {
|
||||
++n;
|
||||
}
|
||||
return std::string(p, n);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool load_proto(const std::string &path, const std::array<uint32_t, 4> &key, Proto &out,
|
||||
std::string *err) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) {
|
||||
if (err) *err = "cannot open " + path;
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> file((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
return load_proto_bytes(file, key, out, err);
|
||||
}
|
||||
|
||||
// The header checks of CItemManager::LoadItemTable / CPythonNonPlayer::LoadNonPlayerData, then CLZO::Decompress.
|
||||
bool load_proto_bytes(const std::vector<uint8_t> &file, const std::array<uint32_t, 4> &key,
|
||||
Proto &out, std::string *err) {
|
||||
out = Proto{};
|
||||
if (file.size() < 12) {
|
||||
if (err) *err = "file too small";
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
auto read_u32 = [&](uint32_t &v) {
|
||||
if (pos + 4 > file.size()) {
|
||||
return false;
|
||||
}
|
||||
v = rd_u32(file.data() + pos);
|
||||
pos += 4;
|
||||
return true;
|
||||
};
|
||||
|
||||
read_u32(out.fourcc);
|
||||
if (out.fourcc == MAKEFOURCC('M', 'I', 'P', 'X')) {
|
||||
if (!read_u32(out.version) || !read_u32(out.stride)) {
|
||||
if (err) *err = "truncated MIPX header";
|
||||
return false;
|
||||
}
|
||||
if (out.version != 1) {
|
||||
if (err) *err = "invalid item_proto VERSION " + std::to_string(out.version);
|
||||
return false;
|
||||
}
|
||||
if (out.stride != sizeof(TItemTable)) {
|
||||
if (err) *err = "invalid item_proto STRIDE " + std::to_string(out.stride) + " != sizeof(SItemTable)";
|
||||
return false;
|
||||
}
|
||||
} else if (out.fourcc == MAKEFOURCC('M', 'I', 'P', 'T')) {
|
||||
out.stride = sizeof(TItemTable);
|
||||
} else if (out.fourcc == MAKEFOURCC('M', 'M', 'P', 'T')) {
|
||||
out.stride = sizeof(SMobTable);
|
||||
} else {
|
||||
if (err) *err = "invalid proto type";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t data_size = 0;
|
||||
if (!read_u32(out.elements) || !read_u32(data_size)) {
|
||||
if (err) *err = "truncated proto header";
|
||||
return false;
|
||||
}
|
||||
// 40250 reads dwDataSize bytes without checking; CLZO then reads its own header lengths.
|
||||
if (pos + data_size > file.size() || data_size < sizeof(CLZObject::THeader)) {
|
||||
if (err) *err = "declared data_size past end of file";
|
||||
return false;
|
||||
}
|
||||
|
||||
PackSingletons(); // CLZO, as 40250 Main() creates it
|
||||
DWORD dwKey[4] = {key[0], key[1], key[2], key[3]};
|
||||
CLZObject zObj;
|
||||
if (!CLZO::Instance().Decompress(zObj, file.data() + pos, dwKey)) {
|
||||
if (err) *err = "CLZO::Decompress failed (wrong key or corrupt data)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out.fourcc == MAKEFOURCC('M', 'M', 'P', 'T') && (zObj.GetSize() % sizeof(SMobTable)) != 0) {
|
||||
if (err) *err = "invalid mob_proto size " + std::to_string(zObj.GetSize()) + ", check data format";
|
||||
return false;
|
||||
}
|
||||
// 40250 walks dwElements records from the buffer without comparing against its size.
|
||||
if (static_cast<uint64_t>(out.stride) * out.elements > zObj.GetSize()) {
|
||||
if (err) *err = "elements * stride past decompressed size";
|
||||
return false;
|
||||
}
|
||||
const BYTE *buf = zObj.GetBuffer();
|
||||
out.blob.assign(buf, buf + static_cast<size_t>(out.stride) * out.elements);
|
||||
return true;
|
||||
}
|
||||
|
||||
ItemRecord parse_item(const uint8_t *r, uint32_t stride) {
|
||||
ItemRecord it;
|
||||
if (!r || stride != sizeof(TItemTable)) {
|
||||
return it;
|
||||
}
|
||||
TItemTable t;
|
||||
std::memcpy(&t, r, sizeof(t));
|
||||
it.vnum = t.dwVnum;
|
||||
it.vnum_range = t.dwVnumRange;
|
||||
it.name = cstr(t.szName, sizeof(t.szName));
|
||||
it.locale_name = cstr(t.szLocaleName, sizeof(t.szLocaleName));
|
||||
it.type = t.bType;
|
||||
it.sub_type = t.bSubType;
|
||||
it.weight = t.bWeight;
|
||||
it.size = t.bSize;
|
||||
it.anti_flags = t.dwAntiFlags;
|
||||
it.flags = t.dwFlags;
|
||||
it.wear_flags = t.dwWearFlags;
|
||||
it.immune_flag = t.dwImmuneFlag;
|
||||
it.buy_price = t.dwIBuyItemPrice;
|
||||
it.sell_price = t.dwISellItemPrice;
|
||||
for (int i = 0; i < CItemData::ITEM_LIMIT_MAX_NUM; ++i) {
|
||||
it.limits[i] = {t.aLimits[i].bType, t.aLimits[i].lValue};
|
||||
}
|
||||
for (int i = 0; i < CItemData::ITEM_APPLY_MAX_NUM; ++i) {
|
||||
it.applies[i] = {t.aApplies[i].bType, t.aApplies[i].lValue};
|
||||
}
|
||||
for (int i = 0; i < CItemData::ITEM_VALUES_MAX_NUM; ++i) {
|
||||
it.values[i] = t.alValues[i];
|
||||
}
|
||||
for (int i = 0; i < CItemData::ITEM_SOCKET_MAX_NUM; ++i) {
|
||||
it.sockets[i] = t.alSockets[i];
|
||||
}
|
||||
it.refined_vnum = t.dwRefinedVnum;
|
||||
it.refine_set = t.wRefineSet;
|
||||
it.alter_to_magic_pct = t.bAlterToMagicItemPct;
|
||||
it.specular = t.bSpecular;
|
||||
it.gain_socket_pct = t.bGainSocketPct;
|
||||
return it;
|
||||
}
|
||||
|
||||
MobRecord parse_mob(const uint8_t *r, uint32_t stride) {
|
||||
MobRecord m;
|
||||
if (!r || stride != sizeof(SMobTable)) {
|
||||
return m;
|
||||
}
|
||||
SMobTable t;
|
||||
std::memcpy(&t, r, sizeof(t));
|
||||
m.vnum = t.dwVnum;
|
||||
m.name = cstr(t.szName, sizeof(t.szName));
|
||||
m.locale_name = cstr(t.szLocaleName, sizeof(t.szLocaleName));
|
||||
m.type = t.bType;
|
||||
m.rank = t.bRank;
|
||||
m.battle_type = t.bBattleType;
|
||||
m.level = t.bLevel;
|
||||
m.size = t.bSize;
|
||||
m.attack_speed = t.sAttackSpeed;
|
||||
m.moving_speed = t.sMovingSpeed;
|
||||
m.on_click_type = t.bOnClickType;
|
||||
m.empire = t.bEmpire;
|
||||
m.folder = cstr(t.szFolder, sizeof(t.szFolder));
|
||||
m.monster_color = t.dwMonsterColor;
|
||||
return m;
|
||||
}
|
||||
|
||||
} // namespace mtproto
|
||||
@@ -1,104 +0,0 @@
|
||||
#pragma once
|
||||
// item_proto / mob_proto reader, the 40250 format (GameLib/ItemManager.cpp CItemManager::LoadItemTable,
|
||||
// UserInterface/PythonNonPlayer.cpp CPythonNonPlayer::LoadNonPlayerData).
|
||||
//
|
||||
// Outer: item = [u32 'MIPX'][u32 ver=1][u32 stride=156][u32 elements][u32 datasize][CLZObject]
|
||||
// (or the older [u32 'MIPT'][u32 elements][u32 datasize][CLZObject])
|
||||
// mob = [u32 'MMPT'][u32 elements][u32 datasize][CLZObject]
|
||||
// CLZObject = [u32 'MCOZ'][u32 encryptSize][u32 compressedSize][u32 realSize] + TEA/LZO payload, decoded by the
|
||||
// ported CLZO::Decompress with the table's key. Records are #pragma pack(1) ILP32 structs:
|
||||
// CItemData::TItemTable (156 bytes, port/GameLib/ItemData.h) and CPythonNonPlayer::TMobTable (255 bytes).
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mtproto {
|
||||
|
||||
// The 4-DWORD keys 40250 hardcodes: s_adwItemProtoKey (GameLib/ItemManager.cpp), s_adwMobProtoKey
|
||||
// (UserInterface/PythonNonPlayer.cpp).
|
||||
inline constexpr std::array<uint32_t, 4> ITEM_PROTO_KEY = {173217u, 72619434u, 408587239u, 27973291u};
|
||||
inline constexpr std::array<uint32_t, 4> MOB_PROTO_KEY = {4813894u, 18955u, 552631u, 6822045u};
|
||||
|
||||
struct Proto {
|
||||
uint32_t fourcc = 0;
|
||||
uint32_t version = 0;
|
||||
uint32_t stride = 0; // record size: sizeof(TItemTable) or sizeof(TMobTable)
|
||||
uint32_t elements = 0; // record count
|
||||
std::vector<uint8_t> blob; // elements * stride bytes
|
||||
|
||||
const uint8_t *record(uint32_t i) const {
|
||||
return (i < elements) ? blob.data() + static_cast<size_t>(i) * stride : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Load + decompress. `key` is ITEM_PROTO_KEY or MOB_PROTO_KEY; the format follows the fourcc.
|
||||
// Parse from an already-read buffer (the extension reads through asset_io, e.g. pack://locale/en/item_proto).
|
||||
bool load_proto_bytes(const std::vector<uint8_t> &bytes, const std::array<uint32_t, 4> &key,
|
||||
Proto &out, std::string *err);
|
||||
bool load_proto(const std::string &path, const std::array<uint32_t, 4> &key, Proto &out,
|
||||
std::string *err = nullptr);
|
||||
|
||||
// --- typed views over the records ---
|
||||
|
||||
struct ItemLimit {
|
||||
uint8_t type = 0;
|
||||
int32_t value = 0;
|
||||
};
|
||||
|
||||
struct ItemApply {
|
||||
uint8_t type = 0;
|
||||
int32_t value = 0;
|
||||
};
|
||||
|
||||
// CItemData::TItemTable, field for field.
|
||||
struct ItemRecord {
|
||||
uint32_t vnum = 0;
|
||||
uint32_t vnum_range = 0;
|
||||
std::string name; // szName[25]
|
||||
std::string locale_name; // szLocaleName[25]
|
||||
uint8_t type = 0;
|
||||
uint8_t sub_type = 0;
|
||||
uint8_t weight = 0;
|
||||
uint8_t size = 0;
|
||||
uint32_t anti_flags = 0; // ITEM_ANTIFLAG_*
|
||||
uint32_t flags = 0; // ITEM_FLAG_*
|
||||
uint32_t wear_flags = 0;
|
||||
uint32_t immune_flag = 0;
|
||||
uint32_t buy_price = 0; // dwIBuyItemPrice
|
||||
uint32_t sell_price = 0; // dwISellItemPrice
|
||||
std::array<ItemLimit, 2> limits{};
|
||||
std::array<ItemApply, 3> applies{};
|
||||
int32_t values[6] = {0}; // alValues (armor: values[3] = body shape index)
|
||||
int32_t sockets[3] = {0};
|
||||
uint32_t refined_vnum = 0;
|
||||
uint16_t refine_set = 0;
|
||||
uint8_t alter_to_magic_pct = 0;
|
||||
uint8_t specular = 0; // PARITY §2.7 fSpecular = specular/100
|
||||
uint8_t gain_socket_pct = 0;
|
||||
};
|
||||
// Empty record when `stride` is not sizeof(TItemTable).
|
||||
ItemRecord parse_item(const uint8_t *rec, uint32_t stride);
|
||||
|
||||
// The leading CPythonNonPlayer::TMobTable fields plus the ones the client reads for display.
|
||||
struct MobRecord {
|
||||
uint32_t vnum = 0;
|
||||
std::string name; // szName[25]
|
||||
std::string locale_name; // szLocaleName[25]
|
||||
uint8_t type = 0;
|
||||
uint8_t rank = 0;
|
||||
uint8_t battle_type = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t size = 0;
|
||||
int16_t attack_speed = 0;
|
||||
int16_t moving_speed = 0;
|
||||
uint8_t on_click_type = 0;
|
||||
uint8_t empire = 0;
|
||||
std::string folder; // szFolder[65]
|
||||
uint32_t monster_color = 0;
|
||||
};
|
||||
// Empty record when `stride` is not sizeof(TMobTable).
|
||||
MobRecord parse_mob(const uint8_t *rec, uint32_t stride);
|
||||
|
||||
} // namespace mtproto
|
||||
Reference in New Issue
Block a user