XuYuanBBK PIXEL GAME CENTER
QQ 群 526266208
公众号 再续前缘游戏平台

资讯/霸业开发指导手册

更新于 2026-10-20全文 10,402 字阅读约 26 分钟

霸业开发指导手册

想改玩法、自己做版本,就得会写钩子。这一本管怎么上手:JavaScript 简要基础、霸业脚本系统原理、编写向导、浏览器里的调试方法,外加六个可直接抄改的示范脚本。查条目请去配套的《霸业 API 及函数手册》。

第一章 · 简要说明与 JS 基础

简要说明:这份手册是什么

这是写给想做游戏、想改玩法的人的,不是写给只玩版本的玩家。三国霸业的网页版(baye)把战斗、AI、内政、道具这些规则做成了可以改的钩子函数:你在脚本里定义 baye.hooks.xxx = function(context){...},引擎跑到那一步就会来问你的脚本该怎么算。学会写钩子,就等于拿到了这套引擎的规则改写在手里。

正文摘录自上游文档仓库 bgwp/baye-doc 的开发者部分(《Javascript 简要基础》《霸业脚本系统原理》《编写向导》《调试方法》《示范脚本》),我们做了排版整理和少量勘误,技术口径以原文为准。同一次摘录里的 API 手册(baye.data 数据项、77 个 baye.hooks 钩子、baye 功能函数)单独放在配套的《霸业 API 及函数手册》里:文档原本没写的 16 个钩子,是对照 iBaye(引擎源码)与 baye-alpha(网页版)的调用点补写的,条目里标了「补写」和源码位置,方便你自己复核。

阅读顺序建议:先扫一遍「Javascript 简要基础」(会写就跳过),再读「霸业脚本系统原理」搞懂钩子是什么,然后照「编写向导」动手写第一个,卡住了看「调试方法」,最后照「示范脚本」抄改,查条目去配套的《霸业 API 及函数手册》。

引擎里那些「原版是怎么算的」的具体数值没有全部收录,那部分属于版本数据;这份手册只讲怎么写脚本改规则。

得益于HTML优势,网页版霸业在原版基础上扩展出Javascript脚本系统,使得lib制作能高度定制游戏逻辑。

Javascript简要基础

基本概念

脚本是一种编程语言,不同于普通文档(如XML),脚本主要由一系列的 数据 和对数据的 操作 (语句)构成。

脚本中语句是一条接着一条顺序执行下去的。如

a = 1; //让对象a的值为1 (赋值)
b = a; //让b的值为a的值,此后b的值也为1
a = 2; //让a的值为2,此后a为2,b仍然为1
c = a; //让c的值为a的值,此后a为2,b为1,c为2

以上脚本有4条语句,顺序执行。

XML文档描述“ 做什么 ”,脚本描述“ 怎么做 ”。

霸业的脚本系统采用javascript语言, 本教程仅讲一些最基本的javascript脚本知识,欲了解更全面,请参考相关资料。

注释

注释的内容是为了帮助程序员阅读用的,代码执行时注释会被忽略。

// 标记单行注释开始,每一行的 // 开始到行末的内容为注释。

/ 和 / 对,用于标记多行注释,如

/*
这是注释
这也是注释
*/

Javascript的数据类型

数字: 123

字符串: "赵云"

对象: {name: "赵云", age: 20}

列表: [1, 2, 3]

赋值

最基础的动作语句是赋值,赋值就是让一个 名字 关联一个 值 , 其后,我们就可以在需要用到这个 值 的地方使用这个 名字 来代替。

基本语法:

变量名称=值;

示例

/*
简单赋值语句
让 a 关联值 1,分号指示语句结束
javascript里面分号是可选的,但为避免歧义,最好每条语句都加上分号
*/

var a = 1;  // var 定义并赋值,首次使用时加上var
a = a + 2;  // 重新赋值, 此后a的值是3

没接触过编程语言的朋友一定要注意: 程序里面的 = 符号代表的是一个动作,区别于我们数学里面学的 = 代表一个相等关系 .

条件判断

有时候程序需要根据不同的条件执行不同的代码,Javasctipt 用 if 来进行条件判断。

示例

var a = 1;
var b = 2;
if (a > b) {
    // 如果 a > b
    console.log("a > b");
}

示例2

var a = 1;
if (a > b) {
    // 如果 a > b
    // console.log语句通常用于调试,它将字符串输出到控制台。
    console.log("a > b");
} else if (a == 1) {
    console.log("a = 1");
} else if (a == 0) {
    console.log("a = 2");
} else {
    // 其它情况
    console.log("其它情况");
}

遍历数据

有时候我们需要遍历一个集合里面的每一个元素做操作,比如霸业里面找出符合某种条件的人物。

我们通常使用 for 循环来实现遍历数据,示例

var numbers = [1, 3, 2, 4];
for (var i = 0; i < numbers.length; i++) {
    if (numbers[i] < 3) {
        console.log(numbers[i]);
    }
}

for 循环语法构成如下

for (表达式1; 表达式2; 表达式3) {
    循环体
}

其它语句

执行流程:

if / while / for 三种循环的执行流程图
if / while / for 三种循环的执行流程图(图摘自上游 baye-doc 文档仓库)

函数(function)

如果一段代码是可复用的或是逻辑相对独立,我们可以将其封装称为函数。

基本语法

function 函数名称(参数列表){
    函数体
}

函数名称可以忽略,称为匿名函数。

// 这种格式是普通的赋值语句, “=”右边的的函数整体是一个值。
变量名称 = function(参数列表) {
    函数体
}

示例

function add(x, y) {
    return x + y;
}
var a = add(1, 2); // now, c = 3
var b = add(a, 1);

或

var add = function(x, y) {
    return x + y;
}
var a = add(1, 2); // now, c = 3
var b = add(a, 1);

第二章 · 系统原理、编写向导与调试方法

霸业脚本系统原理

霸业的脚本系统定义了一个对象 baye.hooks ,称之为 钩子对象 ,此对象作为引擎与脚本衔接的纽带。引擎在一系列特定的时间点会调用 baye.hooks 里面的 钩子函数 来完成特定任务。

当lib开启了脚本功能后,引擎在启动时会执行 script.js 文件内的代码。因此, script.js 文件的主要作用就是往 baye.hooks 对象里面填充我们需要关注的 钩子函数 来实现特定功能。

示例

baye.hooks.giveTool = function(context) {
    //简单示例,赏赐成功
    context.result = 1;
}

如上,每当引擎执行到赏赐功能的时候会调用 baye.hooks.giveTool 这个钩子函数,达到" 询问 脚本是否要允许赏赐“这个目的。

霸业脚本编写向导

了解了原理后,我们就可以一步一步来编写霸业脚本了。

假如我们要实现这么一个功能: 每月对AI控制的人物执行升级

为实现此功能,我们需要在写一些功能代码,并且让引擎在每次策略结束后执行我们的代码。

Step1: 让引擎每次策略结束后执行我们的代码

我们设置钩子函数 baye.hooks.tacticStage4 (详细介绍见配套的《霸业 API 及函数手册》· 钩子函数)

baye.hooks.tacticStage4 = function() {
    //待续
};

Step2: 遍历所有人物,如果有归属,且归属不是玩家君主, 且等级未满上限,升级之

baye.hooks.tacticStage4 = function() {
    var allPerson = baye.data.g_Persons;
    var playerKingId = baye.data.g_PlayerKing + 1;
    var maxLevel = baye.data.g_engineConfig.maxLevel;

    for (var i = 0; i < allPerson.length; i++) {
        var p = allPerson[i];
        if (p.Belong > 0 && p.Belong != playerKingId && p.Level < maxLevel) {
            p.Level = p.Level + 1;
        }
    }
};

注:

  1. 关于 baye.data 内的数据见配套的《霸业 API 及函数手册》· 霸业 API
  2. allPerson[i] : 取列表里第 i 个元素(也就是第i个人物的数据)
  3. && : 并且
  4. != : 不等于

Step3: 进一步完善

当人物处于被俘虏状态时,归属被设置为255,因此进一步排除俘虏被升级

baye.hooks.tacticStage4 = function() {
    var allPerson = baye.data.g_Persons;
    var playerKingId = baye.data.g_PlayerKing + 1;
    var maxLevel = baye.data.g_engineConfig.maxLevel;

    for (var i = 0; i < allPerson.length; i++) {
        var p = allPerson[i];
        if (
            p.Belong > 0
            && p.Belong != playerKingId
            && p.Belong != 255
            && p.Level < maxLevel
        ) {
            p.Level = p.Level + 1;
        }
    }
};

Step4: 加入概率因素

baye.hooks.tacticStage4 = function() {
    var allPerson = baye.data.g_Persons;
    var playerKingId = baye.data.g_PlayerKing + 1;
    var maxLevel = baye.data.g_engineConfig.maxLevel;

    for (var i = 0; i < allPerson.length; i++) {
        var p = allPerson[i];
        if (
            p.Belong > 0
            && p.Belong != playerKingId
            && p.Belong != 255
            && p.Level < maxLevel
        ) {
            var chance = Math.random(); //产生一个0~1之间的随机数
            if (chance < 0.7) {// 随机数小于0.7升级,也就是70%的概率升级
                p.Level = p.Level + 1;
            }
        }
    }
};

调试方法

我们使用PC浏览器的 "开发者工具" 来调试脚本。以Chrome浏览器为例,打开baye页面后按 F12 打开“开发者工具”。如下图:

Chrome 开发者工具:按 F12 打开后切到 Console 标签
Chrome 开发者工具:按 F12 打开后切到 Console 标签(图摘自上游 baye-doc 文档仓库)

实时查看和修改引擎数据

霸业的引擎数据存放在 baye.data 对象中,浏览器控制台中输入 baye.data. 浏览器会弹出自动补全菜单,对象中有哪些属性一览无余。如图:

控制台里输入 baye.data. 会弹出对象成员的自动补全
控制台里输入 baye.data. 会弹出对象成员的自动补全(图摘自上游 baye-doc 文档仓库)

查看第一个城池的归属:

在控制台读取第一个城池的归属
在控制台读取第一个城池的归属(图摘自上游 baye-doc 文档仓库)

修改第一个城池的归属:

在控制台改写第一个城池的归属,改完立刻生效
在控制台改写第一个城池的归属,改完立刻生效(图摘自上游 baye-doc 文档仓库)

修改后移动一下城池光标,可以看到已经实时生效。

实时替换钩子函数

调试钩子函数时,如果每次修改一点脚本,又重新打包lib->重新加载运行,会非常麻烦。当我们修改了钩子函数后,只需在浏览器控制台对该钩子重新赋值,该钩子就即时生效。如图:

在控制台直接替换一个钩子函数,不用重新打包 lib
在控制台直接替换一个钩子函数,不用重新打包 lib(图摘自上游 baye-doc 文档仓库)

添加调试日志

有时候为调试代码中的逻辑是否按预想的逻辑正常执行,我们需要得到一些实时输出信息。我们用 console.log 来实现日志功能。

例如,前面的升级功能,我们要先看升级功能有没有正常执行,可以添加一些日志

baye.hooks.tacticStage4 = function() {
    var allPerson = baye.data.g_Persons;
    var playerKingId = baye.data.g_PlayerKing + 1;
    var maxLevel = baye.data.g_engineConfig.maxLevel;

    for (var i = 0; i < allPerson.length; i++) {
        var p = allPerson[i];
        if (p.Belong > 0 && p.Belong != playerKingId && p.Level < maxLevel) {
            p.Level = p.Level + 1;
            var name = baye.getPersonName(i);
            console.log(name + " 升级为: " + p.Level);
        }
    }
};

策略结束后,可见到控制台打印如下:

策略阶段结束后,控制台打印出脚本里的调试日志
策略阶段结束后,控制台打印出脚本里的调试日志(图摘自上游 baye-doc 文档仓库)

第三章 · 示范脚本

下面六段是上游文档里的完整示例,都是能直接贴进脚本跑的。

计算攻防

baye.hooks.battleBuildAttackAttriutes = function(context) {
    /*
        脚本计算攻防示例
        context.index: 计算结果存放位置序号
        context.generalIndex: 战场将领序号
    */

    var result = baye.data.g_GenAtt[context.index];
    var pIndex = baye.data.g_FgtParam.GenArray[context.generalIndex] - 1;
    var person = baye.data.g_Persons[pIndex];

    // 地形对兵种的影响
    var terEffects = [
    //  草地, 平原, 山地, 森林, 村庄, 城池, 营寨, 河流
        [1, 1, 1, 1, 1, 1, 1, 1], // 骑兵
        [1, 1, 1, 1, 1, 1, 1, 1], // 步兵
        [1, 1, 1, 1, 1, 1, 1, 1], // 弓兵
        [1, 1, 1, 1, 1, 1, 1, 1], // 水兵
        [1, 1, 1, 1, 1, 1, 1, 1], // 极兵
        [1, 1, 1, 1, 1, 1, 1, 1], // 玄兵
    ];

    var AtkModulus = [1.0, 0.8, 0.9, 0.8, 1.3, 0.4];    /* 各兵种攻击系数 */
    var DfModulus =  [0.7, 1.2, 1.0, 1.1, 1.2, 0.6];    /* 各兵种防御系数 */
    var TerrDfModu = [1.0, 1.0, 1.3, 1.15, 1.1, 1.5, 1.2, 0.8];    /* 各种地形防御系数 */

    // 武力/等级/攻击系数影响攻击力
    var at = person.Force * (person.Level + 10) * AtkModulus[person.ArmsType];

    // 智力/等级/防御系数影响防御力
    var df = person.IQ * (person.Level + 10) * DfModulus[person.ArmsType];

    // 叠加地形对攻防的影响
    at = terEffects[person.ArmsType][result.ter] * at;

    df = terEffects[person.ArmsType][result.ter] * df;

    // 地形固有防御系数
    df *= TerrDfModu[result.ter];

    // 输出最终结果
    result.at = at;
    result.df = df;
};

计算伤害

查阅文档可知, 可以使用计算普通攻击伤害的钩子http://bgwp.oschina.io/baye-doc/script/index.html#baye-hooks-countattackhurt

baye.hooks.countAttackHurt 这个钩子是用来计算普通攻击兵力伤害的。

进入这个钩子的时候,攻防双方的战力参数已经计算好,并放到了这:

  1. 攻击方:baye.data.g_GenAtt[0]
  2. 防守方:baye.data.g_GenAtt[1]

这是之前引擎内部或引擎调用钩子:baye.hooks.battleBuildAttackAttriutes 来算好的。

g_GenAtt里面包含了攻防双方将领的战场序号,可用来提取双方将领的其它数据。

那么baye.hooks.countAttackHurt要完成的目标就是根据攻防双方的战力参数或其它条件(如队友的特殊道具)来计算出对防守方造成多少伤害。引擎内置算法是完全根据g_GenAtt里面的战力参数来计算伤害值的。

本示例扩展出逻辑是:查找我方队友是否有携带特殊道具的,如果有,则在原伤害基础上按比例加成。

如下是引擎默认算法

baye.hooks.countAttackHurt = function(context) {
    var KeZhiMatrix = [
        [1.0, 1.2, 0.8, 1.0, 0.7, 1.3],
        [0.8, 1.0, 1.2, 1.0, 0.6, 1.2],
        [1.2, 0.8, 1.0, 1.0, 1.1, 1.2],
        [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
        [1.1, 1.3, 0.9, 1.0, 1.0, 1.5],
        [0.6, 0.6, 0.6, 0.6, 0.6, 0.6]
    ];

    var p0 = baye.data.g_GenAtt[0]; //攻击方数据
    var p1 = baye.data.g_GenAtt[1]; //防守方数据

    var person = baye.getPersonByGeneralIndex(p0.generalIndex);

    /* 基本伤害 hurt = (at / df) * arms / 8 */
    var hurt = p0.at / p1.df * (person.Arms >> 3);

    /* 相克加层 hurt *= modu */
    hurt *= KeZhiMatrix[p0.armsType][p1.armsType];
    context.hurt = hurt;
};

引入其它条件(如特殊道具)影响伤害输出

baye.hooks.countAttackHurt = function(context) {
    var KeZhiMatrix = [
        [1.0, 1.2, 0.8, 1.0, 0.7, 1.3],
        [0.8, 1.0, 1.2, 1.0, 0.6, 1.2],
        [1.2, 0.8, 1.0, 1.0, 1.1, 1.2],
        [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
        [1.1, 1.3, 0.9, 1.0, 1.0, 1.5],
        [0.6, 0.6, 0.6, 0.6, 0.6, 0.6]
    ];

    var p0 = baye.data.g_GenAtt[0]; //攻击方数据
    var p1 = baye.data.g_GenAtt[1]; //防守方数据

    var person = baye.getPersonByGeneralIndex(p0.generalIndex);

    /* 基本伤害 hurt = (at / df) * arms / 8 */
    var hurt = p0.at / p1.df * (person.Arms >> 3);

    /* 相克加层 hurt *= modu */
    hurt *= KeZhiMatrix[p0.armsType][p1.armsType];

    // 查找攻击方是否携带有NB道具
    // baye.data.g_GenAtt里面有20个位置, 前十个为攻城方, 后十个为守城方
    //
    // 首先需要判断攻击者是攻城方还是守城放

    var startIndex = p0.generalIndex < 10 ? 0 : 10;

    var hasNewBeeTool = false;
    var newBeeToolID = 5; //NB道具的ID

    // 查找攻击方有没有NB道具
    for (var ind = startIndex; ind < startIndex + 10; ind++) {
        if (baye.data.g_GenPos[ind].state != 8) { // 8是死亡状态
            var p = baye.getPersonByGeneralIndex(ind);
            if (p.Tool1 == newBeeToolID || p.Tool2 == newBeeToolID) {
                hasNewBeeTool = true; // 找到NB道具
                break;
            }
        }
    }

    if (hasNewBeeTool) {
        // 攻击方团队有NB道具, 百倍伤害
        hurt *= 100;
    }
    context.hurt = hurt;
};

关于状态

STATE_ZC = 0        /* 正常 */
STATE_HL = 1        /* 混乱 */
STATE_JZ = 2        /* 禁咒 */
STATE_DS = 3        /* 定身 */
STATE_QM = 4        /* 奇门 */
STATE_DJ = 5        /* 遁甲 */
STATE_SZ = 6        /* 石阵 */
STATE_QZ = 7        /* 潜踪 */
STATE_SW = 8        /* 死亡 */

道具为人物加额外属性

群魔乱舞将年龄数据用作了统率

// 禁止年龄自动增长
baye.data.g_engineConfig.disableAgeGrow = 1;

// 定义数据表
baye.TOOL_AGE_TAB = [10, 10, 0, 10 /* ... */ ];

// 赏赐时加属性
baye.hooks.giveTool = function(ctx) {
    var up = baye.TOOL_AGE_TAB[ctx.toolIndex];
    if (up > 0) {
        var person = baye.data.g_Persons[ctx.personIndex];
        person.Age += up;
    }
    ctx.result = 1;
};

// 没收时减属性
baye.hooks.takeOffTool = function(ctx) {
    var up = baye.TOOL_AGE_TAB[ctx.toolIndex];
    if (up > 0) {
        var person = baye.data.g_Persons[ctx.personIndex];
        person.Age -= up;
    }
    ctx.result = 1;
};

进一步增加在道具表格中显示统率属性

// 配置道具表格 列数量
baye.data.g_uiCfg.toolPropertiesCount = 6;

// 配置表格 列宽度
baye.data.g_uiCfg.toolPropertiesDisplayWitdh = [4, 6, 6, 6, 6, 6];

// 定义道具表格表头
var baye_tool_heads = ["类型", "加武力", "加智力", "加统率", "加移动", "变兵种"];

// 填写表头的钩子
baye.hooks.getToolPropertyTitle = function(c) {
    c.title = baye_tool_heads[c.propertyIndex];
    return 0;
};

// 填写单元格的钩子
baye.hooks.getToolPropertyValue = function(c) {

    var tool = baye.data.g_Tools[c.toolIndex];

    switch (baye_tool_heads[c.propertyIndex]) {
        case "类型":
            c.value = ["装备", "使用"][tool.useflag];
            break;
        case "加武力":
            c.value = tool.at;
            break;
        case "加智力":
            c.value = tool.iq;
            break;
        case "加统率":
            c.value = baye.TOOL_AGE_TAB[c.toolIndex];
            break;
        case "加移动":
            c.value = tool.move;
            break;
        case "变兵种":
            c.value = ['无', '水', '玄', '极', "骑兵", "步兵", "弓兵", "水兵", "极兵", "玄兵"][tool.arm];
            break;
    }
    return 0;
};

不复用年龄数据,额外为人物增加统率属性

引擎已经固定人物有哪些属性, 因此不能修改固有的人物属性。要增加属性,只能采用"外挂式", 将属性值存储于引擎外(脚本环境中)。

//待续...

弹出提示,和对话

目前脚本系统支持两个UI相关API

baye.alert弹出文字提示
baye.say人物对话

部分钩子支持调用这些API。

示例, 没收物品时拒绝, 并对话

baye.hooks.willTakeOffTool = function(ctx) {
    if (ctx.personIndex == baye.data.g_PlayerKing) return -1;
    baye.say(ctx.personIndex, "你算哪根葱?\n我的宝贝岂是你想拿走就拿走?", function() {
        baye.alert("没收失败!");
        return 0;
    });
};

"工匠"人物

可利用"赏赐"功能打造类似工匠的人物。

以马腾作为示例

baye.hooks.willGiveTool = function(c) {
    // 1号道具 + 2号道具 + 3000金钱 可合成 3号道具

    if (baye.getPersonName(c.personIndex) != "马腾") {
        return -1;
    }

    var person = baye.data.g_Persons[c.personIndex];
    var city = baye.data.g_Cities[c.cityIndex];

    // 检查道具是否满足条件
    if (person.Tool1 - 1 == 1 && c.toolIndex == 2) {

        // 限制条件
        if (city.Money < 10000) {
            baye.say(c.personIndex, "城池不够富足,无法开工!");
            return 0;
        }
        if (city.PeopleDevotion < 99) {
            baye.say(c.personIndex, "城民们对你的治理不太满意,无法开工!");
            return 0;
        }
        if (city.State != 0) {
            baye.say(c.personIndex, "城池灾害,无法开工!");
            return 0;
        }
        city.Money -= 3000; // 扣除3000钱
        baye.deleteToolInCity(c.cityIndex, c.toolIndex); // 删除材料道具
        baye.putToolInCity(c.cityIndex, 3); // 生产出新道具
        baye.say(c.personIndex, "全新[" + baye.getToolName(3) + "]已打造成功!");
        return 0;
    }
    return -1;
};

配套手册:霸业 API 及函数手册

这一本讲怎么学、怎么写、怎么调;写的时候要知道有哪些数据、哪些钩子、哪些函数可用, 那就查另一本:霸业 API 及函数手册。 它把 baye.data 的全部数据项、77 个 baye.hooks 钩子和 baye 功能函数 做成左侧一棵可折叠的树,点一条就在右边展开它的定义、调用时机、输入输出和示例。

开发手册脚本编写调试方法示范脚本 查 API 及函数手册 返回资讯