导表设计
创建:2025-04-27 22:58
更新:2025-04-27 23:50

在游戏设计中,好的导表设计可以大大降低策划的配置量,降低程序与策划的沟通量,以及简化代码程序的使用。

以下时一个可供参考的导表配置测试#xxx#1000.xlsx以及说明:

ID 定义 名称 描述 int bool 枚举 引用 数组 对象 时间 日期 对象数组
id string int bool enum ref>item int[5] {weight:int,val:int} time date {weight:int,val:int}[10]
普通 common 1
稀有 rare 2
史诗 epic 3
only_client only_server
1 test 测试 1 true 普通 金币 1,2,3 1,1 1h 2025-01-01 00:00:00 1,2
1,2
  1. 表导出自动根据从左到右的int列排序
  2. 定义列 会根据填入的名称自动生成对应的const值或者宏定义对应id值。例如#define k_xxx_test 1。对应 #define k_<table_name>_<define_name> <id>。此列的好处是无需开发人员在对各种特殊id写死在代码里,仅需要调表导表即可,可以统一前后端对特殊值的定义
  3. 名称列 是对这一行的简单可视化描述,用于其他表ref引用。例如ref>item 则表示引用道具表,只需填列名称即可,不需要填id。填id可视化极差
  4. int bool time date 列则是对应类型的数据会自动转化成int值。填入的时候只需要按照可视化的方式填写即可。例如time: 1h 1d 1s 1m 分别表示 1小时,1天,1秒,1分钟。date则直接按照时间格式填入会转化成时间戳。
  5. 对象的数据使用 , 隔开,单行填入
  6. 数组可以按照,,;,\n,|隔开。特殊的:,优先用于对象匹配
  7. only_client表示此行仅客户端导出,only_server表示此列仅服务导出。没有填写则表示都导出。第二列没有填写的不导出,第一列id列为空的列不导出。

使用脚本读取表格数据,然后处理各种数据转换,和引用转换,最终生成一份json配置文件(或者按照表分开成多个json文件)。

解析程序参考

根据上边思路,下边使用nodejs实现一个表格数据的解析的参考示例:

注意下边的解析暂时没有加入 only_server, only_client列的区分。导出的是用于c/c++.h文件。string类型区分成string,text,path对应不同长度限制的字符串

-> nodejs依赖

{
  "dependencies": {
    "exceljs": "^4.4.0"
  }
}

-> 导表解析xlsx文件

const ExcelJS = require('exceljs');
const fs = require("fs");
const { makeConfig } = require("./config");

process.on('unhandledRejection', (reason, promise) => {
    console.log('导表出错:\n', reason.message);
    process.exit(-1)
});

(async function () {
    let dir = "./xlsx";
    let files = fs.readdirSync(dir);
    let sheets = [];
    for (const file of files) {
        if (!file.endsWith(".xlsx") || file.startsWith("~") || file.startsWith("!")) {
            continue;
        }
        let ns = file.substring(0, file.length - '.xlsx'.length).split("#");
        if (ns.length != 3) {
            continue;
        }
        const workbook = new ExcelJS.Workbook();
        await workbook.xlsx.readFile(dir + "/" + file);
        for (const sheet of workbook.worksheets) {
            if (sheet.name == "data") { // 仅导出名为data的sheet, 如需多个配置在一个xlsx中,需要修改此行
                let datas = [];
                for (let r = 0; r < sheet.rowCount; r++) {
                    let row = sheet.getRow(r + 1);
                    let rowValues = [];
                    for (let cl = 0; cl < row.cellCount; cl++) {
                        rowValues.push(row.getCell(cl + 1).text);
                    }
                    datas.push(rowValues);
                }
                sheets.push({
                    sheetName: ns[0],
                    key: ns[1],
                    size: Number.parseInt(ns[2]),
                    datas
                });
                break;
            }
        }
    }
    makeConfig(sheets);
})();

-> 导表转化配置数据为json,并且生成.h文件

const strSize = {
    string: 128,
    path: 256,
    text: 1024,
    big_text: 10240,
    huge_text: 102400
};

function md5compute(str){
    const crypto = require('crypto');
    const hash = crypto.createHash('md5');
    hash.update(str, 'utf8');
    return hash.digest('hex');
}

function fileUpdate(path, content, force) {
    const fs = require("fs");
    if (!fs.existsSync(path) || force) {
        console.log("update: " + path);
        fs.writeFileSync(path, content);
        return;
    }
    let oldContent = fs.readFileSync(path).toString();
    if (oldContent == content) {
        return;
    } else {
        console.log("update: " + path);
        fs.writeFileSync(path, content);
    }
}

function makeStruct(sheetName, key, names, fileds, types, enums) {
    let text = "";
    let have_enums = false;
    for (let i = 0; i < enums.length; i++) {
        if (!enums[i]) {
            continue;
        }
        have_enums = true;
        let its = enums[i].replace(/ +/g, " ").split("\n");
        let filed = fileds[i];
        for (let it of its) {
            it = it.trim();
            if (!it) {
                continue;
            }
            let defs = it.split(" ");
            text += `#define k_${key}_${filed}_${defs[1]} ${defs[2]} // ${defs[0].replace(/[\n\r\t]/g, " ")}\n`;
        }
    }
    if (have_enums) {
        text += "\n";
    }
    text += `// ${sheetName.replace(/[\n\r\t]/g, " ")}\n`;
    // 生成struct定义
    text += `struct res_${key} {\n`;
    for (let i = 0; i < fileds.length; i++) {
        if (!fileds[i]) {
            continue;
        }
        let type = types[i].replace(/ /g, "").trim();
        let reg = /(.+)(\[\d+\])/g;
        let rst = reg.exec(type);
        let suffix = "";
        if (rst) {
            type = rst[1];
            suffix = rst[2];
        }

        function append(tab, type, name, suffix, commont) {
            if (type == "int" || type == "enum" || type == "int64" || type == "time" || type == "bool" || type == "date") {
                if (type == "enum") {
                    type = "int";
                }
                if (type == "time") {
                    type = "int64";
                }
                if (type == "date") {
                    type = "int64";
                }
                if (type == "bool") {
                    type = "char";
                }
                text += tab + `${type} ${name}${suffix};${commont}`;
                if (suffix) {
                    text += tab + `int ${name}_count_;\n`;
                }
                return;
            }
            if (type.startsWith("ref>")) {
                if (commont.trim().length != 0) {
                    commont = commont.trim();
                    commont += " ";
                    commont += type;
                } else {
                    commont = " //";
                    commont += type;
                }
                text += tab + `int ${name}${suffix};${commont}\n`;
                if (suffix) {
                    text += tab + `int ${name}_count_;\n`;
                }
                return;
            }
            if (type == "float" || type == "double") {
                text += tab + `${type} ${name}${suffix};${commont}`;
                if (suffix) {
                    text += tab + `int ${name}_count_;\n`;
                }
                return;
            }
            if (type == "string" || type == "path" || type == "text" || type == "big_text" || type == "huge_text") {
                text += tab + `char ${name}[${strSize[type]}];${commont}`;
                return;
            }
            reg = /\{([a-z_0-9]+:[^,]+,?)+\}/g;
            if (reg.test(type)) {
                text += tab + `struct {\n`;
                reg = /([a-z_0-9]+):([^,\}]+),?/g;
                rst = reg.exec(type);
                while (rst && rst[1] && rst[2]) {
                    let _type = rst[2];
                    let _reg = /(.+)(\[\d+\])/g;
                    let _rst = _reg.exec(_type);
                    let _suffix = "";
                    if (_rst) {
                        _type = _rst[1];
                        _suffix = _rst[2];
                    }
                    append(tab + "    ", _type, rst[1], _suffix, "\n"); // 这里不需要支持数组,excel中无法正确输入数据
                    rst = reg.exec(type);
                }
                text += tab + `} ${name}${suffix};${commont}`;
                if (suffix) {
                    text += tab + `int ${name}_count_;\n`;
                }
                return;
            }
            throw new Error(`${sheetName} 字段[${name}]类型[${type}]不合法`);
        }
        append("    ", type, fileds[i], suffix, ` // ${names[i].replace(/[\n\r\t]/g, " ")}\n`);
    }
    text += '};\n\n';
    return text;
}

let allRefs = [];

function makeJson(sheetName, data, fileds, types, enums, values) {
    for (let i = 0; i < fileds.length; i++) {
        if (!fileds[i]) {
            continue;
        }
        let type = types[i].replace(/ /g, "").trim();
        let reg = /(.+)(\[\d+\])/g;
        let rst = reg.exec(type);
        let size = "";
        if (rst) {
            type = rst[1];
            size = Number.parseInt(rst[2].substring(1, rst[2].length - 1));
        }

        function append(target, value, type, name, size, enums) {
            if (type == "int" || type == "int64") {
                if (size) {
                    value = (value + "").trim().split(/,|\n|;|\|/g);
                    target[name] = [];
                    for (const v of value) {
                        if (!/[\-\+0-9\. ]+/.test(v)) {
                            throw new Error(`[${sheetName}] 列${name}值${v}不是整数`);
                        }
                        target[name].push(Number.parseInt(v));
                    }
                    if (target[name].length > size) {
                        throw new Error(`[${sheetName}] 列${name}size大小不足`);
                    }
                } else {
                    value = value || "0";
                    target[name] = Number.parseInt(value);
                }
                return;
            }
            if (type == "bool") {
                if (size) {
                    value = (value + "").trim().split(/,|\n|;|\|/g);
                    target[name] = [];
                    for (const v of value) {
                        target[name].push(v == 'true' || v == '1' ? 1 : 0);
                    }
                    if (target[name].length > size) {
                        throw new Error(`[${sheetName}] 列${name}size大小不足`);
                    }
                } else {
                    target[name] = value == 'true' ? 1 : 0;
                }
                return;
            }
            if (type.startsWith("ref>")) {
                if (size) {
                    value = (value + "").trim().split(/,|\n|;|\|/g);
                    target[name] = [];
                    for (const v of value) {
                        target[name].push(v);
                    }
                    if (target[name].length > size) {
                        throw new Error(`[${sheetName}] 列${name}size大小不足`);
                    }
                } else {
                    value = value || "";
                    target[name] = value;
                }
                allRefs.push({
                    sheetName,
                    target,
                    name,
                    ref: type.substring(4)
                });
                return;
            }
            if (type == "float" || type == "double") {
                if (size) {
                    value = (value + "").trim().split(/,|\n|;|\|/g);
                    target[name] = [];
                    for (const v of value) {
                        if (!/[\-\+0-9\. ]+/.test(v)) {
                            throw new Error(`[${sheetName}] 列${name}值${v}非法`);
                        }
                        target[name].push(Number.parseFloat(v));
                    }
                    if (target[name].length > size) {
                        throw new Error(`[${sheetName}] 列${name}size大小不足`);
                    }
                } else {
                    value = value || "0";
                    target[name] = Number.parseFloat(value);
                }
                return;
            }
            if (type == "time") {
                let list = [];
                if (size) {
                    value = (value + "").trim().split(/,|\n|;|\|/g);
                } else {
                    value = [value + ""];
                }
                for (let v of value) {
                    if (v.endsWith("s" || v.endsWith("second"))) {
                        list.push(Number.parseInt(v.substring(0, v.length - 1)));
                    } else if (v.endsWith("m") || v.endsWith("min")) {
                        list.push(Number.parseInt(v.substring(0, v.length - 1)) * 60);
                    } else if (v.endsWith("h" || v.endsWith("hour"))) {
                        list.push(Number.parseInt(v.substring(0, v.length - 1)) * 60 * 60);
                    } else if (v.endsWith('d' || v.endsWith("day"))) {
                        list.push(Number.parseInt(v.substring(0, v.length - 1)) * 60 * 60 * 24);
                    } else {
                        list.push(Number.parseInt(v));
                    }
                }
                if (size) {
                    target[name] = list;
                } else {
                    target[name] = list[0];
                }
                return;
            }
            if (type == "date") {
                let list = [];
                if (size) {
                    value = (value + "").trim().split(/,|\n|;|\|/g);
                } else {
                    value = [value + ""];
                }
                for (let v of value) {
                    let add = 0;
                    if (v.startsWith("+")) {
                        add = 1704038400; // 2024-01-01 00:00:00 这个是shanghai时间周一,需要根据不同的区域进行调整
                        v = v.substring(1);
                    }
                    if (v.endsWith("s" || v.endsWith("second"))) {
                        list.push(add + Number.parseInt(v.substring(0, v.length - 1)));
                    } else if (v.endsWith("m") || v.endsWith("min")) {
                        list.push(add + Number.parseInt(v.substring(0, v.length - 1)) * 60);
                    } else if (v.endsWith("h" || v.endsWith("hour"))) {
                        list.push(add + Number.parseInt(v.substring(0, v.length - 1)) * 60 * 60);
                    } else if (v.endsWith('d' || v.endsWith("day"))) {
                        list.push(add + Number.parseInt(v.substring(0, v.length - 1)) * 60 * 60 * 24);
                    } else if (/^[0-9]+$/.test(v)) {
                        list.push(add + Number.parseInt(v));
                    } else {
                        list.push(new Date(v).getTime() + 8 * 60 * 60 * 1000);
                    }
                }
                if (size) {
                    target[name] = list;
                } else {
                    target[name] = list[0];
                }
                return;
            }
            if (type == "enum") {
                let its = enums.replace(/ +/g, " ").trim().split("\n");
                let list = [];
                if (size) {
                    value = (value + "").trim().split(/,|\n|;|\|/g);
                } else {
                    value = [value + ""];
                }
                for (const v of value) {
                    let find = false;
                    for (let it of its) {
                        it = it.trim();
                        let defs = it.split(" ");
                        if (defs[0].trim() == v.trim()) {
                            if (defs[2] === null) {
                                throw new Error(`[${sheetName}] 枚举没有对应值: ${v}`);
                            }
                            list.push(Number.parseInt(defs[2]));
                            find = true;
                            break;
                        }
                    }
                    if (!find) {
                        throw new Error(`[${sheetName}] 找不到枚举: ${v}`);
                    }
                }
                if (size) {
                    target[name] = list;
                } else {
                    target[name] = list[0];
                }
                return;
            }
            if (type == "string" || type == "path" || type == "text" || type == "big_text" || type == "huge_text") {
                value = value || "";
                target[name] = value.trim();
                if (Buffer.from(target[name]).byteLength > strSize[type]) {
                    throw new Error(`[${sheetName}] 文本[${name}]大小超出限制[${strSize[type]}]`);
                }
                return;
            }
            reg = /\{([a-z_0-9]+:[^,]+,?)+\}/g;
            if (reg.test(type)) {
                if (size) {
                    value = (value + "").trim().split(/\n|;|\|/g);
                } else {
                    value = [value + ""];
                }
                let list = [];
                for (const v of value) {
                    let vs = v.trim().split(",");
                    let tmp = {};
                    reg = /([a-z_0-9]+):([^,\}]+),?/g;
                    rst = reg.exec(type);
                    let i = 0;
                    while (rst && rst[1] && rst[2]) {
                        let _type = rst[2];
                        let _reg = /(.+)(\[\d+\])/g;
                        let _rst = _reg.exec(_type);
                        let _size = "";
                        if (_rst) {
                            _type = _rst[1];
                            _size = Number.parseInt(_rst[2].substring(1, _rst[2].length - 1));
                        }
                        append(tmp, vs[i++], _type, rst[1], _size, enums);
                        rst = reg.exec(type);
                    }
                    list.push(tmp);
                }
                if (size) {
                    target[name] = list;
                } else {
                    target[name] = list[0];
                }
                return;
            }
            throw new Error(`[${sheetName}] 未知类型: ${type}`);
        }
        if (values[i] !== undefined && values[i] !== null && values[i] !== "") {
            append(data, values[i], type, fileds[i], size, enums[i]);
        }
    }
}

function makeConfig(sheetFiles) {
    const fs = require("fs");

    let codeSource = '#pragma once\n\n';
    codeSource += '#ifndef int64\n';
    codeSource += '#define int64 long long\n';
    codeSource += '#endif\n\n';
    let jsonDatas = {};
    let sheets = {};

    for (const one of sheetFiles) {
        sheets[one.key] = {
            name: one.sheetName,
            size: one.size,
        };
        let sheetName = one.sheetName;
        let key = one.key;
        let datas = one.datas;

        let results = [];
        let names = datas.splice(0, 1)[0];
        let fileds = datas.splice(0, 1)[0];
        let types = datas.splice(0, 1)[0];
        let enums = datas.splice(0, 1)[0] || [];
        let define_at = undefined;
        let ref_at = undefined;

        if (fileds.findIndex(v => v.trim() == "id") >= 0) {
            sheets[one.key].id_sort = true;
        }

        if (!types) {
            throw new Error(`[${sheetName}] 表格前4行内容配置不足`);
        }
        for (let k = 0; k < names.length; k++) {
            if (names[k] == '定义') {
                define_at = k;
            }
            if (names[k] == '引用') {
                ref_at = k;
            }
        }

        let mapData = {};
        for (const it of datas) {
            if (!it[0]) {
                continue;
            }
            let item = {};
            if (ref_at && it[ref_at] !== undefined && it[ref_at] !== null && it[ref_at] !== "") {
                item = JSON.parse(JSON.stringify(mapData[Number.parseInt(it[ref_at])])); // 引用数据
            }
            makeJson(sheetName, item, fileds, types, enums, it);
            mapData[item.id] = item;
            if (define_at && it[define_at]) {
                codeSource += `#define k_${key}_${it[define_at]} ${item.id} // ${item.name ? item.name : ''} \n`;
            }
            results.push(item);
        }

        codeSource += makeStruct(sheetName, key, names, fileds, types, enums);
        jsonDatas[key] = results;
    }

    // 更新数据
    let namesRef = {};
    let idsRef = {};
    for (const sheetName in jsonDatas) {
        namesRef[sheetName] = {};
        idsRef[sheetName] = {};
        for (const one of jsonDatas[sheetName]) {
            if (one.name) {
                if (namesRef[sheetName][one.name]) {
                    throw Error(`表${sheetName}中名字重复: ${one.name}`);
                }
                namesRef[sheetName][one.name] = one.id;
            }
            idsRef[sheetName][one.id] = one.id;
        }
    }

    function findRef(val, ref, sheetName) {
        val = val.trim();
        let idRef = idsRef[ref];
        let nameRef = namesRef[ref];
        if (/^[0-9]+$/.test(val.trim())) {
            if (idRef[val] == undefined) {
                throw new Error(`[${sheetName}] 找不到引用的列ID: ${val}`);
            }
            return idRef[val];
        } else {
            if (nameRef[val] == undefined) {
                throw new Error(`[${sheetName}] 找不到引用的列名称: ${val}`);
            }
            return nameRef[val];
        }
    }
    for (const one of allRefs) {
        let val = one.target[one.name];
        if (typeof (val) == "string") {
            one.target[one.name] = findRef(val, one.ref, one.sheetName);
        } else {
            let list = [];
            for (const v of val) {
                list.push(findRef(v, one.ref, one.sheetName));
            }
            one.target[one.name] = list;
        }
    }

    // 排序
    for (const sheetName in jsonDatas) {
        let sort = {};
        if (jsonDatas[sheetName].length > 0) {
            let sortables = [];
            if (sheets[sheetName].id_sort) {
                sortables = ['id'];
            } else {
                let keys = Object.keys(jsonDatas[sheetName][0]);
                for (const key of keys) {
                    if (typeof (jsonDatas[sheetName][0][key]) == "number") {
                        sortables.push(key);
                    }
                }
            }
            jsonDatas[sheetName].sort((a, b) => {
                for (const key of sortables) {
                    if (a[key] != b[key]) {
                        sort[key] = true;
                        return a[key] - b[key];
                    }
                }
                throw new Error(`[${sheetName}] 无法排序: \n${JSON.stringify(a)} \n${JSON.stringify(b)}`);
            });
        }
    }

    codeSource += "#define k_res_config_uuid_len 33\n";
    codeSource += "struct res_config {\n";
    for (const key in sheets) {
        codeSource += `    struct res_${key} ${key}[${sheets[key].size}];\n`;
        codeSource += `    int ${key}_count_;\n`;
    }
    codeSource += "    char uuid[k_res_config_uuid_len];\n";
    codeSource += "};\n\n";

    let content = JSON.stringify(jsonDatas, null, 4)
    jsonDatas.uuid = md5compute(content);
    // console.log(JSON.stringify(jsonDatas, null, 4));
    // console.log(codeSource);
    fileUpdate("config.json", JSON.stringify(jsonDatas, null, 4));
    fileUpdate("config.h", codeSource);
    console.log("success!");
}

module.exports = {
    makeConfig
};

-> 一个.h文件导出效果参考:

#pragma once

#ifndef int64
#define int64 long long
#endif

#define k_tips_login_password_error 1001 //  
#define k_tips_login_failed 1002 //  
#define k_tips_login_server_closed 1003 //  
#define k_tips_login_server_full 1004 //  
#define k_tips_server_maintenance 1005 //  
#define k_tips_server_will_maintenance 1006 //  
#define k_tips_item_get 2001 //  
#define k_tips_item_not_enough 2002 //  
#define k_tips_item_use 2003 //  
#define k_tips_item_error 2004 //  
#define k_tips_item_full_to_email 2005 //  
#define k_tips_already_cliamed 3001 //  
#define k_tips_cannot_use_self_code 3002 //  
#define k_tips_mail_get_one 4001 //  
#define k_tips_type_notification 1 // 通知
#define k_tips_type_system 2 // 系统
#define k_tips_type_alter 3 // 弹窗

// Tips
struct res_tips {
    int id; // ID
    int type[2]; // 提示类型
    int type_count_;
    int time; // 通知显示时间
    char template[128]; // 模版内容
};

#define k_global_params_draw_card 1 //  
#define k_global_params_init_give 2 //  
#define k_global_params_invited_gift 3 //  
#define k_global_params_invite_reward 4 //  
#define k_global_params_daily_reward 5 //  
#define k_global_params_stage_reward 6 //  
#define k_global_params_ad_draw_limit 7 //  
#define k_global_params_invite_multi_reward 8 //  
#define k_global_params_sweep_cost 9 //  
// 全局参数
struct res_global_params {
    int id; // ID
    int integer[5]; // 整数参数
    int integer_count_;
    int64 time[5]; // 时间参数
    int time_count_;
};

#define k_stage_max 50 // 关卡50 
// 关卡
struct res_stage {
    int id; // 关卡ID
    char name[128]; // 名称
    struct {
        int min;
        int max;
    } reward; // 奖励金币
    struct {
        int attr; //ref>attr
        float min;
        float max;
        int weight;
    } buff_select[6]; // buff选择
    int buff_select_count_;
    char monster_name[128]; // 怪物名称
    struct {
        int attr; //ref>attr
        int val;
    } monster_attrs[10]; // 怪物属性
    int monster_attrs_count_;
};

#define k_skill_define_damage 1 // 伤害 
#define k_skill_define_lock_health 2 // 锁血 
#define k_skill_define_heal 3 // 回血 
#define k_skill_define_add_shield 4 // 加盾 
#define k_skill_define_silence 5 // 沉默 
#define k_skill_define_silence_resist 6 // 沉默抵抗 
#define k_skill_define_block 7 // 格挡 
#define k_skill_define_continuous_heal 8 // 持续回血 
#define k_skill_define_weakness 9 // 无力 
#define k_skill_define_debuff 10 // 弱化 
#define k_skill_define_lifesteal 11 // 吸血 
#define k_skill_define_copy 12 // 复制 
#define k_skill_define_transform 13 // 变换 
#define k_skill_define_card_enhance 14 // 卡牌变强 
#define k_skill_define_cooldown 15 // 冷却 
#define k_skill_define_increase_action 16 // 增加行动力 
#define k_skill_define_purify 17 // 净化 
#define k_skill_define_freeze_type 18 // 冻结类型 
#define k_skill_define_freeze_multiple 19 // 冻结多张 
#define k_skill_define_continuous_damage 20 // 持续掉血 
#define k_skill_define_continuous_damage_buff 21 // 持续增伤 
#define k_skill_define_continuous_damage_reduction 22 // 持续减伤 
#define k_skill_define_hand_type_damage_buff 23 // 手持类型增加伤害 
#define k_skill_define_hand_card_damage_buff 24 // 手持卡牌增加伤害 
#define k_skill_define_discard_pile_type_damage_buff 25 // 弃卡堆类型增加伤害 
#define k_skill_define_discard_pile_card_damage_buff 26 // 弃卡堆牌增加伤害 
#define k_skill_define_draw_card 27 // 抽卡 
#define k_skill_define_attack_draw 28 // 攻击抽卡 
#define k_skill_define_health_cost_draw 29 // 扣血抽卡 
#define k_skill_define_discard_damage 30 // 弃卡伤害 
#define k_skill_define_discard_heal 31 // 弃卡恢复 
#define k_skill_define_discard 32 // 弃卡 
#define k_skill_define_discard_draw 33 // 弃卡抽取 
#define k_skill_define_discard_increase_action 34 // 弃卡增加行动 
#define k_skill_define_no_effect 35 // 无效 
#define k_skill_define_no_effect_target_card 36 // 无效敌方手牌 
#define k_skill_define_move_from_target_card 37 // 移敌方牌 
#define k_skill_define_disappare_target_card 38 // 消敌方牌 
#define k_skill_define_reflect_damage 39 // 反伤 
#define k_skill_define_exchange_health 40 // 换血 
#define k_skill_define_max 44 //  
#define k_skill_define_type_damage 1 // 伤害
#define k_skill_define_type_damage_increase 2 // 增伤
#define k_skill_define_type_shield 3 // 护盾
#define k_skill_define_type_recovery 4 // 恢复
#define k_skill_define_type_effect 5 // 效果
#define k_skill_define_type_discard 6 // 弃卡
#define k_skill_define_type_draw_card 7 // 抽卡
#define k_skill_define_type_no_effect 8 // 无效
#define k_skill_define_type_max 9 // max
#define k_skill_define_target_self 1 // 自己
#define k_skill_define_target_enemy 2 // 敌方

// 卡牌技能定义
struct res_skill_define {
    int id; // ID
    char name[128]; // 名称
    char desc[1024]; // 描述
    int type; // 类型
    int target; // 使用目标
    char negative_effect; // 负面效果
};

// 卡牌表
struct res_card {
    int id; // ID
    char name[128]; // 名称
    int skill;// 技能ID ref>skill_define
    int skill_params[4]; // 技能参数
    int skill_params_count_;
    int discard; // 弃卡回合
    int action; // 消耗行动
    struct {
        int stage;
        int weight;
    } weight_add[10]; // 过关增加权重(可以是负数)
    int weight_add_count_;
    int bottom_line; // 多少抽保底
    int send; // 开局赠送
};

#define k_quality_blue 1 // 蓝色 
#define k_quality_orange 2 // 橙色 
#define k_quality_red 3 // 红色 
// 品质
struct res_quality {
    int id; // ID
    char name[128]; // 名称
};

// 奖励
struct res_reward {
    int id; // ID
    char name[128]; // 名称
    int items[10];// 随机物品列表 ref>item
    int items_count_;
    struct {
        int min;
        int max;
    } nums[10]; // 随机物品数量(max=0表示固定取min)
    int nums_count_;
    int weights[10]; // 权重随机-权重
    int weights_count_;
    int weights_times; // 权重随机-次数
    char weights_repeatable; // 权重随机-重复获得
};

#define k_attr_health 1 // 生命 
#define k_attr_physical_resistance 2 // 物盾 
#define k_attr_magic_resistance 3 // 魔盾 
#define k_attr_critical_strike_rate 4 // 暴率 
#define k_attr_critical_strike_damage 5 // 暴伤 
#define k_attr_physical_damage 7 // 物伤 
#define k_attr_magic_damage 8 // 法伤 
#define k_attr_max 9 //  
// 属性
struct res_attr {
    int id; // ID
    char name[128]; // 名称
    char show[128]; // 显示文本
    char percent; // 是百分比
    char icon[256]; // ICON
    int player_base; // 玩家基础值
};

// 条件
struct res_condition {
    int id; // ID(条件类型*100000+x)
    char use[128]; // 通途
    char name[128]; // 名称
    int condition_type;// 条件类型 ref>condition_type
    int condtion_params[5]; // 条件参数
    int condtion_params_count_;
    int condition_times; // 条件次数
};

#define k_condition_type_condition_type_type_cumulation 1 // 累积
#define k_condition_type_condition_type_type_get 2 // 获取

// 条件类型
struct res_condition_type {
    int id; // ID
    char name[128]; // 名称
    int condition_type_type; // 类型
    char desc[128]; // 描述
};

#define k_help_where_battle_failed 1 // 战败
#define k_help_where_home 2 // 主界面

// 游戏提示表
struct res_help {
    int id; // ID
    int where; // 出现条件
    char content[1024]; // 内容
};

#define k_item_gold 1 // 金币 
#define k_item_type_resource 1 // 资源
#define k_item_type_func_item 2 // 功能
#define k_item_func_reward 1 // 奖励
#define k_item_func_equip 2 // 装备宝箱
#define k_item_level_start1 1 // 蓝色
#define k_item_level_start2 2 // 橙色
#define k_item_level_start3 3 // 红色

// 道具
struct res_item {
    int id; // ID
    char name[128]; // 名称
    int type; // 类型
    int func; // 使用功能
    int func_params[5]; // 功能参数
    int func_params_count_;
    char auto_open; // 自动打开
    int level; // 级别
    int bag; // 背包显示
    char icon[256]; // 图标
    int day_limit; // 每日获取上限
    int limit; // 拥有上限
    char desc[1024]; // 描述
    int get_channel; // 获取渠道
    int64 expire; // 时间限制
};

#define k_mail_level_achive 1 //  
#define k_mail_join_alliance 2 //  
#define k_mail_item_full 3 //  
#define k_mail_type_system 1 // 系统
#define k_mail_type_battle 2 // 战报
#define k_mail_type_notice 3 // 公告
#define k_mail_type_alliance 4 // 公会

// 邮件
struct res_mail {
    int id; // ID
    int type; // 类型
    char title[128]; // 标题
    char simple[128]; // 摘要/发送着
    char template[1024]; // 模板
    int64 expire_time; // 过期时间
};

#define k_res_config_uuid_len 33
struct res_config {
    struct res_tips tips[1000];
    int tips_count_;
    struct res_global_params global_params[1000];
    int global_params_count_;
    struct res_stage stage[100];
    int stage_count_;
    struct res_skill_define skill_define[100];
    int skill_define_count_;
    struct res_card card[1000];
    int card_count_;
    struct res_quality quality[10];
    int quality_count_;
    struct res_reward reward[1000];
    int reward_count_;
    struct res_attr attr[100];
    int attr_count_;
    struct res_condition condition[1000];
    int condition_count_;
    struct res_condition_type condition_type[100];
    int condition_type_count_;
    struct res_help help[100];
    int help_count_;
    struct res_item item[1000];
    int item_count_;
    struct res_mail mail[1000];
    int mail_count_;
    char uuid[k_res_config_uuid_len];
};

该文件可以配合【jsonc】 工具生成对应的json解析代码,快速实现config.json配置文件解析