« November 2015 | Main | January 2016 »

December 28, 2015

Skynet 1.0.0 RC 版发布

拖了很久,终于决定给 skynet 1.0.0 封版了。比预期的时间 足足晚了半年,好在还是在 2015 年把这个事情启动了。

其实已经很久没有对已有特性做修改了,如果的项目是在今年 3 月份以后跟进的 1.0 alpha 版的话,升级到目前的最新版本应该不会有太大痛苦。最近几个月几乎没有增加新的特性,反而是在裁减一些多余的,用的人不多的东西(为了兼容,把这样一些 API 移到了一些独立的模块中,方便废弃)。

据我所知,skynet 用于的商业游戏项目(以及一些非游戏项目)早已经超过了 2 位数,收获了不错的口碑。它不再是我们自己公司的内部项目,持续收到不同人的 PR 说明很多同学不仅仅在使用,更是用心在 review 代码,让它真正成为一组公众视野下的代码。我相信这是开源的终极意义:众目睽睽之下, Bug 无所遁形。

我们自己的项目也从 skynet 开源经营中获益良多。有好几处来源于外部的 bugfix 都是在错误发生前被堵住,只是可惜的是,我们也有项目未能及时跟进,只到真的出错了才回头发现在主干上早已改过。这些事故反而证明了开源对于提高项目质量的作用。

这次发布 1.0.0 正式版本的候选版 (RC) ,并专门公告,就是希望有在使用 skynet 的同学,尤其是已经有项目上线运营的,能够在最后这几天将遗留问题提出来,issue 或 pr 都可以。不要把遗憾留到 1.0.1 :)

我希望这次把 RC 标签保留一个月,在农历新年前换成正式版。


对于因为 skynet 常年挂着 alpha (其实 beta 已经一个月了)标签还在犹豫的同学,希望换上正式版标签后可以打消你的疑虑(当然,我个人并不觉得标签换了后,代码质量会有本质变化)。

同时不要再不断的问 “真的有项目用 skynet 的吗?”,“skynet 有文档吗?”。

尤其对于后一个问题,我对连 README 都不看的同学,真的很烦回答啊。

skynet 不仅有 FAQ ,也有中文的文档,而且文档更新的还很及时。麻烦你读一下 readme 以及跟着链接去看看 wiki 吧,能问出 skynet 有文档吗这种问题的同学,我相信把文档摆在他面前也是读不下去的,文档对他就没有存在意义。


算起来从 2012 年 8 月开源发布(7 月开始写第一行代码)到现在居然已经有 3 年有余。这么一个小小的项目经历了三年,整个过程都有线上运营的项目在紧跟。为了历史兼容问题,必然有无数遗憾。传言 linus 说过,所有项目你都要做两次才明白到底怎么做。

我不知道有没有机会重新来做 skynet 2.0 (目前没有任何这方面的计划),把我认为错误的设计,更好的设计推倒重来一次。至少可以把项目的代码风格统一一点,看起来更漂亮。

但眼下要做的事情仅仅只是:赶紧发布第一个稳定版,让更多的人放心来用。用的人越多,后来人也就越放心。

December 18, 2015

skynet 里的 coroutine

skynet 本质上只是一个消息分发器,以服务为单位,给每个服务一个独立的 id ,可以从任意服务向另一个服务发送消息。

在此基础上,我们在服务中接入 Lua 虚拟机,并将消息收发的 api 封装成 lua 模块。目前用 lua 编写的服务在最底层只有一个入口,就是接收并处理一条 skynet 框架转发过来的消息。我们可以通过 skynet.core.callback (这是一个内部 API ,用 C 编写,通常由 skynet.start 调用)把一个 lua 函数设置到所属的服务模块中。每个服务必须设置,且只能设置一个回调函数。这个回调函数在每次收到一条消息时,接收 5 个参数:消息类型、消息指针、消息长度、消息 session 、消息来源。

消息大致分两大类,一类是别人对你发起的请求,一类是你过去对外的请求收到的回应。无论是哪类,都是通过同一个回调函数进入。

在实际使用 skynet 时,你可以直接使用 rpc 的语法,向外部服务发起一个远程调用,等对方发送了回应消息后,逻辑接着走下去。那么,框架是如何把回调函数的模式转换为阻塞 API 调用的形式呢?

这多亏了 lua 支持 coroutine 。可以让一段代码运行了一半时挂起,在之后合适的时候在继续运行。

为了实现这点,我们需要在收到每条请求消息时,先创建一个 coroutine ,在 coroutine 中去运行该类消息的 dispatch 函数(使用框架时,可通过 skynet.dispatch 设置消息的处理函数)。之所以必须先创建 coroutine 而不能直接调用消息处理函数,是因为我们无法预知在消息处理的过程中会不会因为阻塞 API 而需要挂起执行流程。等到第一次需要挂起时才把执行流程绑定到 coroutine 上是做不到的。

然后,所有的阻塞 API 都是通过 coroutine.yield 挂起当前 coroutine ,并把挂起类型以及可能用到的数据传出来。而框架会捕获住这些参数,也就进一步知道该去做些什么。这也就解释了阻塞 API 为什么必须在消息处理函数中调用,而不能直接写在服务的主体代码中的原因。因为初始化部分的代码并不运行在框架创建出来的 coroutine 中,coroutine.yield 也就无处捕获处理。

例如,对于 skynet.call ,其实是生成了一个对当前服务来说唯一的 session 号调用 yield 给框架发送 "CALL" 这个指令。框架中的 resume 捕获到 "CALL" 后,就会把 session 和 coroutine 对象记录在表中,然后挂起 coroutine ,结束当前的回调函数。等待 skynet 底层框架后后续消息进来时再处理。(实际上,这里还会处理 skynet.fork 创建的额外线程)

当收到回应消息时,会根据 session 号找到之前记录的 coroutine 对象。然后 resume 之前没有做完的业务即可。从应用层角度看起来,就只是一次阻塞调用而已。

以上仅仅是 skynet 在搭建框架时利用 coroutine 把回调转换为阻塞调用用的一种手法,并不是唯一的方案。比如,你还可以不通过 yield 传递任何数据给框架,全部都塞在额外的 table 中。而调度器仅仅在收到 yield 后结束当前的回调函数就够了。但如果这样做,在满足下面的一个需求时,就会遇到一些小麻烦(可能需要借助一些额外的全局标记变量才能搞定):


如果我们希望在 skynet 框架下的消息函数中使用 coroutine 库怎么办?

也就是把 skynet 框架和正常的 coroutine 机制混合使用。如果你直接使用 coroutine ,那么所有 skynet 的阻塞 API 都不能正常工作了。它们会触发 yield ,而被用户你自己写的 resume 捕获到,而不能正确处理。

这时,我们引入了 skynet.coroutine 这个库。你可以用 skynet.coroutine 全面替代 lua 原生的 coroutine 库,api 是一致的。

它所做的事情就是在 yield coroutine 的时候,在传出参数的最前面加上一个 "USER" 类型(也就是在 skynet 框架下,所有的 yield 都必须在第一个参数给出挂起类型),而在 resume coroutine 时,一旦发现是 "USER" 类型时,就去掉这个类型值,而把剩下的参数直接返回,阻止外框架结束消息处理;而如果是其它类型,则向上传播给框架,让框架挂起当前消息处理流程,等待底层的回应再继续。这种做可以让应用层的 coroutine 看起来不被 skynet 框架的阻塞调用所打断。

当然,这还需要给 skynet.coroutine.status 在 "normal" "running" "suspended" "dead" 等类型之外增加一类 coroutine 状态,叫做 "blocked" 。意思是,coroutine 被底层框架挂起,但不可以由引用层 resume 。

这个状态类似于 "normal" ,是 skynet 框架下的一个特例。因为 skynet 框架下两个独立的请求消息处理流程可视为并行的处理线程。线程之间数据是共享的,也意味着一条线程创建的 coroutine 对象对另一线程可见,也可以调用 resume 。而 "blocked" 状态可以阻止错误的 resume 调用。

实现 skynet.coroutine 封装并不算复杂,里面最大的难点其实在于 pofile 对 skynet 线程的时间分析。profile 会在 yield 的时候暂停计时,而在 resume 时继续。这样才可能正确统计出一个请求的完整流程到底消耗了多少 CPU 时间。而用户 coroutine 的引入会增加统计的复杂性。我们需要跟踪 resume 的调用,回溯到底是哪条消息间接执行了 coroutine 中的代码,才能正确的把耗时加上去。有兴趣了解细节的同学可以直接阅读代码。


一般我们不太需要直接使用 coroutine 模块。skynet 本身提供了 skynet.fork() 方法来创建一条新的业务线程,可以用 skynet.wait(co) 来挂起,并用 skynet.wakeup(co) 来唤醒。区别在于 wakeup 只是向框架发送一个信号,需要等框架来调度;而不像 coroutine.resume(co) 会直接延续挂起的 coroutine 。

那么什么时候可能需要使用 coroutine 模块呢?

我认为最大的用途是把 coroutine 作为迭代器来使用。PIL 里有一个不错的例子

正巧半年前我自己也遇到一个实际需求利用 coroutine 实现迭代器,下面分享一下这个案例(和 skynet 关系不大)。

由于 lua 可定制的内存管理器比标准 CRT 的内存管理 api 约定提供更多的信息,以及 lua vm 本身的内存取用工作模式可以被预知,所以有可能定制一个比通用分配器更好的内存管理器。(对于 skynet 还有额外的意义:可以让不同的 lua vm 使用不同区块的内存页,在 vm 关闭时减少内存碎片)

内存管理器写起来容易,想做的稳定放心且判断是否真的更高效却不那么容易。所以我想了一个办法来制作测试用的数据。

我在项目实际运行的环境里定制了一个特别的内存管理器,log 了所有的内存管理器调用行为。在实际项目运行相当长一段时间后,就得到了好多组,每组几 G 的数据。这些数据严格反应了真实项目在运行过程中内存是如何在使用的。

我可以用这些数据严格测试自定义的内存管理模块,它将和线上产品经受完全一致的使用。我可以加上额外的检查,即在分配出内存后填充独有的数据,并在释放的时候严格检查填充。还可以检测任意时间点的碎片率,峰值内存占用情况等等。当然也可以在关掉检测后和标准内存分配器在速度上一较高下。或是针对项目做算法微调以观成效。

只是,直接使用这份 log 数据并不容易。log 中记录的是每次内存配及释放的地址信息。如果我在测试代码中建一张大的 hash 表来动态保存它们有额外的开销,这个开销很可能会对内存分配器性能测量本身造成影响。因为内存分配模块本身运行速度就很快,甚至比 hash 表的实现要快。而且如果是个动态 hash 表,它本身也需要使用内存管理函数,这样干扰就更大了。

我倾向于在测试函数中建一个足够大的静态数组,把测试数据以流式读进去。理论上,数组的大小不会超过实际运行过程中同时存在的内存块条目数量。只要对原始 log 数据做一点处理,把内存地址转换为数组里的序号就好了。

测试程序读取处理过的 log ,只需要知道一条内存分配请求应该放在静态数组的第几项中,而释放请求应该去释放静态数组中的第几项就好了。这样对测试本身的干扰最小。

我的任务就是加工原始 log 。

一开始我想的比较简单,这些 log 也就转换一次而已,随便写个脚本算一下就好了。无非是把整个 log 加载到一个 lua table 里,比对一下地址,转换成编号。

实际做的时候我发现遇到了点小麻烦:log 文件实在是太大了,处理起来迅速超过了我的物理内存上限,变得非常缓慢。随后我想到了用一个 coroutine 做成迭代器,一边处理源数据流一边做转换,一边输出。

虽然不用 coroutine 也可以做到这点,但实现起来会麻烦许多。用 coroutine 完成这些需求非常的自然。如果你不介意读一下我当初随手写的脚本,可以在 gist 上找到它们

December 15, 2015

Rogue's Tale 基础系统设定

Rogue's Tale 是个相当不错的硬核 RogueLike 游戏,之前我在 blog 里多次介绍过。前段时间还在 steam 上写了一篇新手上手指南 。这款游戏的系统数值公式设计的非常精巧,在官方主页有详细的英文介绍,对熟悉游戏很有帮助。我选取了一部分基础设定翻译成中文。当然不仅仅是为了玩好游戏,也是对类似游戏的设计有所借鉴。

以下基于 http://rogue.epixx.org/ 翻译


基础系统

2D6 系统

游戏里所有的行动都使用一套统一规则,其行动结果由对比扔出的两个六面骰数字与目标的难度级别来决定。在和目标难度级别做比较前,扔出的数字通常还会做一次增益或惩罚修正,该修正就是简单的加在扔出的数字上。

如果最终修正值大于等于目标难度级,行动成功。如果低于难度级,行动失败。如果骰子扔出两个 1 ,行动被直接视为致命失败而无视修正值。如果骰子扔出两个 6 ,行动则直接判定为暴击而不需要加上修正值和难度级做对比。

属性修正

属性修正在生物依靠属性去做点什么事情的时候起效果。属性修正还用于修正伤害骰以及一些直接或间接针对生物的事件。正的修正被称为增益,负的修正被称为惩罚。

人类的属性修正范围从 -5 到 +5 间线性变化,2 点时修正为 -5 ,7 点时修正为 0 ,12 点时修正为 +5 。属性点可以超过 12 点,此时修正值可以超过 +5 ,但和其它的增益或惩罚合并后,最终的结果会截取到 +5 再作用于行动(有一些例外:在计算近程和远程伤害时,有些情况例如瞄准射击和致命打击,可以超过 5 )。

其他种族有一些不同的属性基础值,他们的属性修正范围可能超过 -5 到 +5 。比如兽人比人类的平均水平要强壮一些,他们的力量修正范围就是 -4 到 +6 的。更不用说,兽人怪物可以把你一般的冒险角色轻松撕成碎片。

属性检定

每个行动都需要做一种属性检定。属性检定的难度视情况而变化。默认的属性检定难度级别是 7 ,这意味着当 2D6 投出的数字和加上所有修正后结果等于或超过 7 就视为成功。

属性点

所有的生物都由四个属性来影响行动。这四个属性分别是力量、敏捷、耐力和魅力。

  • 力量(STR) 力量影响着生物的物理能力。力量修正作用于近战命中骰、近战伤害骰,投掷武器的伤害骰、格档骰、打断检定、陷阱检定,攀爬检定、恐吓鉴定。

  • 敏捷(AGI) 敏捷影响着生物的反应和平衡。敏捷修正作用于远程命中骰、远程伤害骰、投掷武器的命中骰、特定武器的近战命中及伤害骰、闪避修正、招架修正、解除陷阱检定、开锁检定、潜行检定以及跳跃检定。

  • 耐力(STA) 耐力影响着生物的物理耐久性。耐力修正作用于毁灭性或进攻性的法术命中骰、精神检定、搜索检定、HP、精力点、以及 HP 和精力的回复检定。

  • 魅力(CHA) 魅力影响着生物的人格力量。魅力修正作用于恢复或防御性的法术命中骰、说服检定、商品价格,还有机会开启和 NPC 的特殊交互菜单。

差异三规则

在达到 20 级前,人类的最高和最低属性点差异不可以超过 3 。在 20 级时可获得最后一点属性点,该点属性不受此限制。

对于女性角色有所优待,她们的属性点在最低的属性是力量,其它属性点都比力量高,且最高属性点是魅力时,差异可以是 4 。不过最后一点属性点不能把差异扩大到 5 。

血量

血量决定了生物在被杀死前可以承受多少点伤害。生物的血量 等于它的基础耐力值加上它的基础耐力修正乘以血量 倍率再加上耐力最终修正较基础耐力修正超出的部分。血量倍率基于生物等级,从一级开始每三级加一(1 级、4 级、7级等对应着 1 倍、2 倍、3 倍,依此类推)。

例如,一个 4 级生物,基础耐力为 8,有额外的一点耐力强化,就有 11 点的血量(8 加上 2 倍的基础耐力修正 1 ,再加上 1 点强化而来的耐力修正,这 1 点正是最终耐力修正 2 和基础耐力修正 1 的差值)。

有很多方法来回血,方法不限于法术、天赋、特别技能,以及休息。最廉价的回血方法是先吃饱喝足,然后休息。为了在休息中回血,生物先要确保饥渴两状态中至少有一项是 satiated (饱腹),而另一项不能是 hungry (饥饿)或 thirsty (干渴)。然后每个休息回合用下面的公式来判定该回合是否可以回血:

回血检定:2D6 + 耐力修正 >= 12 (如果吃饱喝足两项满足则为 10)

精力

精力决定了生物在休息前可以发动多少次特殊行动。生物的精力点为它的基础耐力修正加 3 。

当生物休息或移动时,都会用下列公式检定而获得一次精力回复的机会:

精力回复检定:2D6 + 耐力修正 >=7 (移动时为 10 )

抗性

抗性在承受直接伤害时可获得直接伤害减免,但不能帮助抵抗以受到的效果影响。例如,如果生物被疾病影响,那么只能通过治疗疾病来消除影响或是获得疾病免疫效果。

抗性仅针对下列伤害类型:物理、疾病、毒药、火焰、冰霜、震击。对于魔法伤害和陷阱造成的伤害无法以任何方式抵抗。

免疫

免疫可以避免次生伤害,但对直接伤害不起作用。例如,如果生物对火焰效果免疫,那么他在被火焰法术击中后不会燃烧,但依旧要承受火焰法书造成的伤害。

弱点

针对特定伤害类型的弱点意思是该种伤害会导致双倍伤害值。生物有弱点的话就一定会中次生伤害效果 —— 火焰或导致燃烧,毒伤或导致中毒,等等。同种弱点并不叠加。

气场伤害

有气场的生物会在每回合结束时对所有邻近的生物都造成气场伤害。对于有相同气场的生物邻接在一起时,气场会给生物回血。气场造成的伤害值或回复量为 D3 + 耐力修正。

如果是 monster (怪物)带气场的话,气场半径从 1 增加到 2 ,伤害或回复值乘 1.5 倍。

带气场的生物(例如,火焰、冰霜、震击,等等)将完全免疫和气场相同类型的伤害以及其此生效果。在目标没有对应类型的弱点时,气场不引发次生效果。

注意气场会自动攻击,并不会导致爆击。针对它的抗性取身体和头部装备上抗性和的平均值,并向下取整。

饥渴度

每个生物都会被饥饿和干渴影响。食物和水是生存的必需品,如果缺少它们将影响特定生物的行为。

饥饿和干渴有七种不同的程度表示生物的饥渴度。饥饿和干渴程度是分别计算的。

饥饿条:Famished, Starving, Hungry, Not Hungry, Satiated, Bloated, Choking.

干渴条:Parched, Dehydrated, Thirsty, Not Thirsty, Satiated, Hydrated, Drenched.

每级饥渴条都有内部 360 回合倒数。直接设置等级的法术和效果将会把内部回合数设为 180 。向上或向下调整级别的法术和效果将会把内部回合数分别设为 90 或 270 。吃喝食物则直接调整内部回合数。

任何一个将状态条向下或向上超出的效果(少于 1 会合或超过 2520 回合)都会造成每回合一点的伤害。

对于处于 Famished 或 Parched 两状态其一的生物,在所有基于属性的鉴定或投骰中都要承受 -2 的惩罚,并且无法回复精力。对于处于 Starving, Dehydrated, Choking, Drenched 等任一状态的,所有检定和投骰中都承受 -1 的惩罚。

吃东西可以维持饥饿条,回复的回合数取决于事物的类型和质量。干渴条可以用喝水来维持,回复的回合数取决于饮料的类型和质量。干渴条还可以由含水的食物来回复。

注意,如果不是被诅咒强迫进食,吃喝都会停在 satiated 状态而不会过量。

回合次序

在同一层的所有生物在玩家进入该层时就在预定次序排好了。每个回合一个生物通常只能做一个行动,移动、射击、释放法术、使用天赋等等。

由生物召唤出来的生物会在召唤者之后行动。由环境召唤出来的生物在所有已存在的生物之后行动。

速度效果

一些生物天生加速或迟缓,还有一些魔法效果可以加速或减慢速度。无论来源如何,带有一个或多个加速效果的生物在同一回合有机会行动两次,而迟缓效果则有可能导致它整个回合无法行动。

和每回合的正常行动不同,带有一个或多个加速效果的生物,每个加速效果扔两个六面骰,只要有一次投出 8 或以上则在该回合可以行动两次。此外,带有一个或多个迟缓效果的生物,则需要为每个迟缓效果扔两个六面骰,并以下面的公式检定是否会失去整个回合:

迟缓检定:2D6 + 耐力修正 >= 7 + 效果能级修正

对于加速生物,无论是否通过鉴定,它的饥渴进度条在每个行动上都会消耗 1 加上加速效果的数量倍食物。(例如,带有加速戒指的角色每个行动讲消耗 (1+1) x 2 = 4 个食物和水,当加速效果起作用时一个回合消耗 8 个)。

生物可以同时受多个速度效果影响。这些效果的处理会使得迟缓效果可能取消整个回合的行动而抵消掉所有的加速效果。例如,一个生物被两个加速效果和一个迟缓效果影响时,该生物有可能在同个回合行动行动一次或两次,也可能什么都做不了。

被诅咒的物品

游戏中所有物品都可能被诅咒。被诅咒的物品效果可能只是一些小麻烦,也可能是可怕到直接搞死该生物。不过任何被诅咒的物品上都可能发生两件事。第一件是生物不能出于自己的意志解除对被诅咒物品的持有,第二件是在生物装备了该物品后,直接失败的修正增加 1 。

在多数情况下,对于自由取下的被诅咒物品,只在该物品被装备时诅咒才生效。但有些物品会魔法绑定在装备者身上,这些物品只有通过解除绑定其中的魔法来摧毁掉它。

被祝福的物品

游戏中所有物品都可能被祝福。被祝福的物品有一些隐秘特性,所有被祝福的物品遵循下列设计准则:

  1. 消费类的物品可以使用更多次 (药水,魔法书、魔杖)。
  2. 会损坏的物品更坚固(护甲、头盔、盾牌、武器)。
  3. 可装备的物品有更多特性(项链、戒指)。

战斗系统

命中检定

当一个生物攻击另一个生物时,无论是近战还是远程还是法术,都需要做一次命中检定。命中检定用下列公式来判定是否成功:

  • 近战命中检定:2D6 + 攻击修正 >= 7 + 防御修正
  • 远程命中检定:2D6 + 攻击修正 >= 7 + 距离修正
  • 法术命中检定:2D6 + 攻击修正 >= 7 + 距离修正

对无抵抗能力的生物发起近战攻击将自动命中,且爆击率上升(D6 + 暴击修正 >= 6 )。生物在睡眠、麻痹、震击、眩晕时失去抵抗能力。

针对隐形生物的近战攻击,基础难度从 7 上调到 12 。隐身状态的生物发起的近战攻击无法被格档、闪避、招架。

命中修正

攻击修正通常等于武器匹配的属性修正。该修正可能对应任何一种属性修正。通常来说,近战武器使用力量修正,远程武器使用敏捷修正、法术使用耐力修正或魅力修正。

防御修正包括闪避修正,以及在装备了对应武器后,可能触发格档或招架修正中的一种。

在没有天赋的情况下,副手近战武器攻击会加上 -2 的命中修正(参见双持)。

  • 闪避修正 任何可以完全控制自己行动的生物都可能在同一回合闪避一次攻击。闪避修正取生物的敏捷修正,最低为零,最高由护甲和头盔上列出的最大闪避修正值截断。

  • 格档修正 任何可以完全控制自己行动的生物都可能在同一回合使用盾牌格档一次攻击。格档修正取生物的力量修正,最低为零,最高由盾牌上列出的最大格档修正值截断。

  • 招架修正 任何可以完全控制自己行动的生物都可能在同一回合使用小型副手武器或双手拿一中型武器来招架一次攻击。招架修正取一半的敏捷修正并向上取整。最高被武器上列出的最大招架修正值截断。

  • 距离修正 距离修正等于距离除二并向下取整。例如,如果目标在右侧三格、向上四格处,距离为 sqrt(3^2 + 4^2) ,也就是 5 ,最终的距离修正是 2 。

    • 偏斜修正 偏斜修正等于目标生物装备的盾牌上的最小格档修正。该值在计算攻击者的天赋和装备效果前加在距离修正上。

是否发生闪避,格档或招架由命中骰来决定,只要没有正好投出该类防御修正指定的数值,那么防御修正就还可以用于后续的命中检定。

例如,一个生物在没有任何命中修正时想攻击另一个有 +1 敏捷 +1 力量修正且带盾的生物。攻击方需要扔出 9 来击中,因为防御方这个回合还没有闪避或格档。如果攻击方投出了 8 或 7 ,防御方会格档或闪避掉攻击,并且在该回合中余下的过程失去该形式的防御。假设这个攻击者投出了 8 ,这样防御者就失去了格档机会;下一个攻击者(不带增益修正)就需要投出 8 来命中,此刻防御者只能闪避。

防御修正对应的骰值根据攻击者的增益修正来调整,同时会降低闪避、招架、格档发生的几率。

例如,当一个有 +4 增益修正的生物攻击上面例子中生物,只需要扔出 5 或更大来击中。在这个例子中,格档发生在扔出 4 的时候,闪避发生在扔出 3 的时候。格档概率从 5/36 下降到 3/36 而闪避概率从 6/36 下降到 2/36 。

击中部位

每个生物有两个部位可能被击中,身体和头部。普通攻击击中身体,暴击击中头部。当命中骰等于或超过暴击阀值时,或是投出两个六,都被视为暴击。

默认的暴击阀值为 12 ,但会根据等级差,体积差,以及魔法效果而变化。例如,兽人是一种比人类更大的单位,如果再加上一级的等级差,那么就需要投出 10 ,11 或 12 产生暴击。

无论何时一个生物被暴击,都有一定几率产生混乱。以下公式被用于检定暴击效果:

暴击效果检定:2D6 + 耐力修正 >= 7 + 未被抵抗的伤害

如果检定未通过,生物将混乱检定不够的回合数。注意,即使伤害全部被抵抗,也需要通过检定。

伤害

当攻击成功命中目标,就会造成伤害。伤害的总量取决于武器或是法术,以及对应的伤害修正。

伤害修正通常等于攻击者武器对应的属性修正。修正可以对应于任何一种属性,通常近战和投掷武器使用力量修正,远程武器使用敏捷增益修正,攻击法术使用耐力或魅力修正。

副手近战武器伤害等于两倍的敏捷惩罚修正,或是 -1 ,这取决于哪样更小一些。

双手近战武器以及被双手使用的单手近战武器的修正等于一半的对应属性修正并向上取整,或是 0 ,这取决于哪个更高。

伤害修正还可能包括一项或多项魔法修正。魔法修正不受生物装备武器的形式包括主副手的影响。


天赋系统

瞄准射击 (Aimed Shot)

远程攻击每点敏捷增益修正获得 D3 的额外伤害,取代普通的 1 点额外伤害。学习多重射击和快速射击前都必须先学习瞄准射击。

多重射击(Multi Shot)

远程攻击 将命中目标生物邻接的多个额外生物目标,数量等于敏捷增益修正值。多重射击不能用于慢速武器。

快速射击(Quick Shot)

远程攻击的射击前生物可以先移动。快速射击不能用于慢武器。

稳定射击(Steady Shot)

远程攻击时,可以重投失败的命中检定,在隐秘状态下能造成双倍伤害。

剑刃乱舞(Blade Flurry)

近战攻击的每把武器(当敏捷增益修正 +1 或更高时,可针对主手武器;+3 或更高时针对副手武器)获得最一次额外攻击机会。剑刃乱舞需要两把小型武器或将一把中型武器双手使用。

致命投掷(Deadly Throw)

投掷武器攻击的每点属性增益修正获得 D3 额外伤害,取代普通的 1 点额外伤害,依据的属性取力量和敏捷中的较高值。致命投掷使用所有投掷武器攻击时,允许生物使用力量和敏捷修正中较高的那个做命中和伤害修正。

英勇冲锋(Heroic Charge)

允许生物最多移动两格,然后在移动停止后攻击目标。英勇冲锋还会增加 1 点伤害修正。

粉碎打击(Crushing Blow)

主手近战武器攻击的每点力量增益修正都获得 D3 额外伤害,取代普通的 1 点额外伤害。粉碎打击需要一把中型或大型武器。学习致命一击或横扫打击前必须先学习粉碎打击。

  • 致命一击(Killing Blow) 主手近战攻击在攻击命中且命中骰投出一对,且目标生物不免疫即死(instant death)时,直接杀死对方。致命一击需要一把中型或大型武器。

  • 横扫打击(Sweeping Blow) 主手近战武器将命中和攻击者邻接的多个额外生物目标,其数量取决于力量增益修正。横扫打击需要一把中型或大型武器。当装备大型武器时,还会获得 +1 最小格档修正以及 +1 最大招架修正。

盾击(Shield Bash)

副手盾牌攻击,将目标击退并在实际造成伤害后眩晕目标 D3 回合。盾击不会被格档、闪避或招架,它需要装备一把近战武器和一面盾牌。盾击还会将偏斜修正从最小格档修正改为格档修正。

潜行(Silent Move)

移动行动让生物在视野中隐秘。在隐秘状态下发动近战攻击将造成双倍伤害,且无法格档、闪避和招架。

读写:常识(Literacy:Common)

允许生物常规读写,释放记录在魔法卷轴上的法术,释放魔杖的能力。学习读写:天使,读写:龙系,读写:地狱 前必须先学习读写:常识。

  • 读写:天使(Literacy:Celestial) 允许生物从魔法书中学习天使系(Celestial Spells)法术。

  • 读写:龙系(Literacy:Draconic) 允许生物从魔法书中学习龙系(Deaconic Spells)法术。

  • 读写:地狱(Literacy:Infernal) 允许生物从魔法书中学习地狱系(Infernal Speels)法术。

专注施法(Focused Cast)

允许生物定位任何在视野直线可达区的目标并释放法术(通常法术只能定位邻接格的目标)。另外,直接伤害或治疗法术都会因每点对应的属性增益修正获得额外的 D3 点伤害或治疗效果,而不是常规的 1 点。

沉默施法(Silent Cast)

允许生物在沉默状态释放法术,且将失法的距离修正减半向下取整。沉默施法还会教会沉净施法 (Clearcasting) 。

黑暗视觉(Dark Vision)

增加一格视野,并将距离修正减半向下取整。

双持(Dual Wield)

副手攻击不再承受的命中修正的惩罚,伤害和招架修正取敏捷修正而不是其一半。

锐利目光(Keen Eye)

增加一格搜索半径,并在移动或休息行动时获得一次搜索周围的免费行动。锐利目光还会在失败的搜索检定后重掷。

第六感(Sixth Sense)

允许生物重掷反射检定,并在拾起装备或使用未鉴定的被诅咒物品前给出警告。

坚强意志(Strong Will)

允许生物重掷失败的精神检定,并将媚惑、忌妒、恐惧、狂暴、贪婪、欲望、鲁莽效果的持续时间减半,向下取整。

陷阱大师(Trap Master)

允许生物看见隐藏的捕熊夹,站在其上不会触发效果。陷阱大师还能拆解可见的捕熊夹留到以后使用。

变形:狼(Shapeshift:Wolf)

将生物变为一只狼,把装备的武器和天赋替换成下列:

  • 残忍撕咬 (Ferocious Bite) (D6, Melee/Medium/Crushing)
  • 英勇冲锋 (Level 3)
  • 潜行 (Level 6)
  • 第六感 (Level 9)
  • 锐利目光(Level 12)

变形:熊(Shapeshift:Bear)

将生物变为一头熊,把装备的武器和天赋替换成下列:

  • 残忍撕咬 (Ferocious Bite) (D6, Melee/Medium/Crushing)
  • 英勇冲锋 (Level 3)
  • 粉碎打击 (Level 6)
  • 坚强意志 (Level 9)
  • 锐利目光(Level 12)

狂野怒火 (Bestial Wrath)

在变形时增加 D3 物理伤害。这个天赋还会被下列遗产增强:Bestial Vigour ,Leader of the Pack 。


法术

和其它大多数行动不同,法术可以使用精力和血来施放 。如果生物没有精力施法的话,可以献祭 D3 点血来施放。

施法通常需要通过两个独立的检定。第一个检定称为施法命中检定,用来决定法术是否命中想作用的目标(见战斗系统)。第二个检定根据法术的不同有很多名字,它用来决定法术是否附加一或多个随机效果。

施法命中检定使用施法者的耐力或魅力修正作为施法修正(攻击修正),用哪个取决于法术。

用来移除已有效果的法术使用施法增益修正来对抗需移除效果的强度等级修正,以下列公式来决定是否成功:

移除效果检定:2D6 + 施法修正 >= 7 + 强度等级修正

下列法术描述中用 X 指代耐力增益修正,Y 指代魅力增益修正,Z 指代 生物经验等级 / 4 。例如,如果一个效果的持续时间是 XD6 x 10 回合,而施法者的耐力增益修正是 3 ,则该效果可持续 3D6 x 10 回合。

沉净施法 (Clearcasting)

沉净施法是一个可学习的被动技能,它赋予施法者一次不消耗精力的施法机会。

专注施法检定:D6 + 暴击修正 >= 6

注意,当施法者没有精力或投出 1 时,检定都直接失败。

天使法术(Celestial Spells)

  • 治疗疾病(Cure Disease) 尝试移除目标生物身上的一个疫病。治疗疾病使用魅力修正做法术命中检定。

  • 治疗中毒(Cure Poison) 尝试移除目标生物身上的一个中毒效果。治疗中毒使用魅力修正做法术命中检定。

  • 驱散魔法(Dispel Magic) 尝试移除目标生物身上,或特定地点,施法者物品栏中的物品上的一个魔法效果。驱散魔法 使用魅力修正做法术命中检定。

  • 神之恩典(Divine Grace) 赋予施法者和他视线内所有友方单位一个 (D6+Y) x 10 回合的持续效果,该效果增强敏捷、魅力各 1 点,物理抗性、毒药抗性、疾病抗性各 1 点。神之恩典使用魅力修正做法术命中检定。

  • 天赋神力(Divine Might) 赋予施法者和他视线内所有友方单位一个 (D6+X) x 10 回合的持续效果,该效果增强力量、耐力各 1 点,火焰抗性、冰霜抗性、震击抗性各 1 点。神之恩典使用耐力修正做法术命中检定。

  • 神圣领域(Divine Reach) 赋予施法者一个 (D6+X) x 10 回合的持续效果,允许他对视线内的任何目标施放法术。如果生物同时受神圣领域和专注施法天赋的影响,施法命中距离修正减半并向下取整。神圣领域使用耐力修正做法术命中检定。

  • 治疗之触(Healing Touch) 治疗目标生物及 D6 + Y 点伤害,并尝试回复一点属性伤害。治疗之触使用魅力修正做法术命中检定。

  • 治疗光波(Healing Wave) 治疗目标生物及所有和该目标邻接的生物 D3 + Y 点伤害。治疗光波使用魅力修正做法术命中检定。

  • 回复之触(Renewing Touch) 赋予目标生物一个 D6+Y 回合的持续效果,每个回合治疗 D3 + Y 点。回复之触使用魅力修正做法术命中检定。注意,该持续效果在受到伤害(且伤害高于施法者魅力修正)时,减少到施法者魅力修正值。

  • 超度亡魂(Turn Undead) 对目标不死生物造成 D6 + Y 点魔法伤害。超度亡魂有一定机会立刻杀死不死生物。这个机会是 2D6 + Y >= 10 + Z 。超度亡魂使用魅力修正做法术命中检定,视为一个法术球,但不能被增大。

龙系法术(Draconic Spells)

  • 魔法增效(Amplify Magic) 赋予施法者一个 (D6+X) x 10 回合的持续效果,增强法术球的伤害 D6 。魔法增效使用耐力修正做法术命中检定。

  • 电球术(Electric Orb) 对目标生物造成 D6 + X 点震击伤害。电球有一定机会使目标休克 X / 2 回合。这个机会有 2D6 + X >= 10 + 震击抗性。电球术使用耐力修正做法术命中检定。

  • 法术球增大(Enlarge Orb) 赋予施法者一个 2D6+X 回合的持续效果,该效果可让下一发法术球爆炸造成额外 D6 + X 点伤害,并对目标邻接的生物造成法术原本的伤害。法术球增大使用耐力修正做法术命中检定。

  • 火球术(Flaming Orb) 对目标生物造成 D6 + X 点火焰伤害。火球有一定机会给目标附加 D6 + X 回合的燃烧效果,每回合会造成 D3 点火焰伤害。这个机会有 2D6 + X >= 10 + 火焰抗性。火球术使用耐力修正做法术命中检定。

  • 冰球术(Frozen Orb) 对目标生物造成 D6 + X 点冰霜伤害。冰球有一定机减慢目标 D6 + X 回合.这个机会有 2D6 + X >= 10 + 冰霜抗性。冰球术使用耐力修正做法术命中检定。

  • 照明术(Illumination) 赋予施法者和他视线内所有友方单位一个 (D6+Y) x 10 回合的持续效果,增加他们的视野范围及搜索范围 1 格。照明术使用魅力修正做法术命中检定。

  • 魔法盔甲(Mage Armour) 赋予施法者一个 (D6+X) x 10 回合的持续效果,增强所有的抗性。增强值为 X / 2 或 1 里较高的一个。当施法者装备的物品因任何理由发生变化时,该效果自动解除。魔法盔甲使用耐力修正做法术命中检定。

  • 镜像术(Mirror Image) 复制一个施法者持续 (D6 + Y) x 10 回合,该复制体看起来会发动和施法者相同的行动。复制体收到的任何伤害都会减少持续时间的 (伤害)D6 个回合。镜像术使用魅力修正做法术命中检定。

  • 移除诅咒(Remove Curse) 尝试移除目标生物身上,或特定地点,施法者物品栏中的物品上的一个诅咒效果。移除诅咒使用魅力修正做法术命中检定。

  • 念动力(Telekinesis) 允许施法者使用魔法来操纵远程物体,像门、宝箱、开关等这类东西。念动力使用耐力修正做法术命中检定。

地狱法术(Infernal Spells)

  • 放逐恶魔(Banish Daemon) 对目标恶魔造成 D6 + Y 点魔法伤害。放逐恶魔有一定几率立刻杀死恶魔。这个几率是 2D6 + X >= 10 + Z 。放逐恶魔使用耐力修正做法术命中检定,视为一个法术球,但不能被增大。

  • 降咒术(Bestow Curse) 尝试给目标生物施加一个随机的持续 (D6 + X) x 10 回合的诅咒效果。几率为 2D6 + X >= 7 + Z 。降咒术使用耐力修正做法术命中检定。如果检定失败,则改为释放 Curse of Agony 。

  • 灵魂捕捉(Capture Soul) 尝试给目标生物附加一个持续 D6 + X 回合的持续效果,若目标生物死亡,该生物的灵魂将被捕获进一颗晶石中。当 X 不大于 0 时,该法术无效。灵魂捕捉使用耐力修正做法术命中检定。

  • 魔鬼面容(Demonic Visage) 赋予施法者一个 (D6+Y) x 10 回合的持续效果,任何试图攻击他的生物都必须先通过一次精神检定,失败后会造成 D3 回合的恐惧。受到任何伤害都会使得魔怪面容的持续时间减少 (伤害)D6 回合。魔鬼面容使用魅力修正做法术命中检定。

  • 奴役恶魔(Enslave Daemon) 尝试魅惑一个恶魔 (D6 + Y) x 10 回合。几率为 2D6 + Y >= 8 + Z 。奴役恶魔使用魅力修正做法术命中检定。

  • 位面传送(Plane Shift) 将施法者立即传送到视野内的另一个位置。位面传送使用耐力修正做法术命中检定。

  • 释放灵魂(Release Soul) 从施法者的物品栏中随机选取一颗魂晶,释放其中的灵魂。释放的灵魂可以用于对目标生物造成 2D6 + X 点魔法伤害,也可以用于给物品栏中的一个装备做一次随机强化。如果强化失败,将对施法者造成 2D6 + X 点魔法伤害。释放灵魂使用耐力修正做法术命中检定。

  • 召唤恶魔(Summon Daemon) 尝试召唤一个恶魔并奴役它(见奴役恶魔)。召唤恶魔使用魅力修正做法术命中检定。

  • 毒球术(Venomous Orb) 对目标生物造成 D6 + X 点毒药伤害。毒球术另外有一次机会附加一个持续 (D6 + X) x 10 回合的随机中毒效果。这个几率为 2D6 + X >= 10 + 毒药抗性。毒球术使用耐力修正做法术命中检定。

  • 病球术(Virulent Orb) 对目标生物造成 D6 + X 点疾病伤害。病球术另外有一次机会附加一个持续 (D6 + X) x 10 回合的随机疾病效果。这个几率为 2D6 + X >= 10 + 疾病抗性。病球术使用耐力修正做法术命中检定。


诅咒(Curse)

被诅咒后,每个回合都有 1/36 (2D6 投出 2) 的机会效果生效。如果诅咒是由法术造成的,那么这个几率会随施法者的耐力修正而上升。如果诅咒来源于其它地方,则根据来源的强度等级调高几率。

效果是否生效是用修正值加 2 和投骰的值相比较来决定。如果投出值小于等于比较值,效果生效。

例如:如果一个生物被下了强度等级 2 的诅咒,那么在诅咒持续时间内每个回合都会因为 2D6 投出 2,3,4 而效果生效。

在下列描述中,使用 X 代替强度等级。例如,如果描述中说一个效果持续 XD6 x 10 回合,而效果强度等级为 3 的话,该效果持续 3D6 x 10 回合。

痛苦诅咒(Curse of Agony) 有机会导致 D3 + X 魔法伤害的持续效果。

忌妒诅咒(Curse of Envy) 有机会使生物陷入 D6 回合忌妒状态的持续效果。忌妒状态的生物将被强制向最近的同性别生物移动并攻击。如果没有这样的生物则失去回合。

贪食诅咒(Curse of Gluttony) 有机会使生物花一个回合吃喝的持续效果。如果生物没有可食用的东西,则狂暴 D6 回合。

贪婪诅咒(Curse of Greed) 有机会使生物陷入 D6 回合贪婪状态的持续效果。陷入贪婪状态的生物被强制向最近的物品移动并尝试捡起来。如果没有物品则失去回合。

饥饿诅咒(Curse of Hunger) 有机会使生物在七级饥饿条(Famished, Starving, Hungry, Not Hungry, Satiated, Bloated, Choking)上失去一个等级。这个效果仅在扔出骰未修正的情况下为 2 才发生。别的诅咒成功的情况会减少饥饿值 (4+X)D6 。

淫欲诅咒(Curse of Lust) 有机会使生物陷入D6 回合淫欲状态的持续效果。淫欲状态的生物将被强制向最近的异性移动并打的对方屈服,如果没有合适的生物则失去回合。

傲慢诅咒(Curse of Pride) 有机会使生物陷入 D6 回合傲慢状态的持续效果。傲慢状态的生物将强制向最近的敌人移动并攻击,如果没有敌人则失去回合。

懒惰诅咒(Curse of Sloth) 有机会使生物失去一点精力,并失去下个回合。该效果还会重扔成功的精力回复骰。

干渴诅咒(Curse of Thirst) 有机会使生物在七级干渴条(Parched, Dehydrated, Thirsty, Not Thirsty, Satiated, Hydrated, Drenched)上失去一个等级。这个效果仅在扔出骰未修正的情况下为 2 才发生。别的诅咒成功的情况会减少干渴值 (4+X)D6 。

愤怒诅咒(Curse of Wrath) 有机会使生物陷入 D6 回合的狂暴状态。狂暴生物强制向最近的生物移动并攻击,如果不存在生物则失去回合。


毒药(Poison)

中毒后,每个回合都有 1/36 (2D6 投出 2) 的机会效果生效。如果中毒是由法术造成的,那么这个几率会随施法者的耐力修正而上升。如果中毒来源于其它地方,则根据来源的强度等级调高几率。

效果是否生效是用修正值或效力加 2 和投骰的值相比较来决定。如果投出值小于等于比较值,效果生效。

例如:如果一个生物被下了强度等级 2 的毒药,那么在中毒持续时间内每个回合都会因为 2D6 投出 2,3,4 而效果生效。

致盲之毒(Blinding Poison) 有机会使生物持续 D6 回合盲眼的持续效果。盲眼的生物除了什么都看不见外,和平常一样。致盲之毒同时还会根据毒药强度给命中骰附加一个负修正。

魅惑之毒(Charming Poison) 有机会使生物持续 D6 回合陷入魅惑状态的持续效果。被魅惑的生物被强制攻击施展魅惑者的敌人中最近的那个。如果不存在敌人则失去回合。魅惑之毒同时还会让生物攻击施展魅惑者前先要通过一次精神检定。

混乱之毒(Confusing Poison) 有机会使生物持续 D6 回合混乱的持续效果。混乱生物向随机方向移动攻击。换乱之毒同时还会根据毒药强度给命中骰附加一个负修正。

死亡之毒(Deadly Poison) 有机会对生物造成 D3 + X 点毒伤害的持续效果。死亡之毒同时还会重投成功的回血骰。

震聋之毒(Deafening Poison) 有机会使生物持续 D6 回合耳聋的持续效果。震聋只毒同时还会根据毒药强度给施法骰附加一个负修正。对有沉默施法天赋的生物无效。

枯竭之毒(Draining Poison) 有机会让生物失去一点精力的持续效果。枯竭之毒同时还会根据毒药强度给精力回复骰附加一个负修正。从免疫角度上看,这个毒药的伤害被视为精力吸收。

吸血之毒(Leeching Poison) 有机会对生物失去 D3 + X 点血。吸血之毒同时还会根据毒药强度给回血骰附加一个负修正。从免疫角度上看,这个毒药的伤害被视为吸血。

麻痹之毒(Paralytic Poison) 有机会麻痹生物 D6 回合的持续效果。麻痹之毒被看作生物身上的一个迟缓效果。

沉默之毒(Silencing Poison) 有机会沉默生物 D6 回合的持续效果。被沉默的生物不能释放法术,阅读卷轴以及和别的生物对话。沉默之毒同时还会根据毒药强度给施法骰附加一个负修正。对有沉默失法天赋的生物无效。

沉睡之毒(Sleeping Poison) 有机会让生物沉睡 D6 回合的持续效果。任何伤害都会打断睡眠效果,但只要还在中毒,就有可能下个回合再次让生物睡去。

迟缓之毒(Slowing Poison) 视为一个持续的迟缓效果,并有持续可能让生物失去下个回合。迟缓之毒还会根据毒药强度来降低闪避、招架以及格档修正。但这个效果不能把各自修正降低到固有最小等级之下(例如装备了圆盾后至少有 +1 格档修正)。


疾病(Diseases)

疾病在每个回合都有 1/36 (2D6 投出 2) 的机会效果生效。如果疾病是由法术造成的,那么这个几率会随施法者的耐力修正而上升。如果疾病来源于其它地方,则根据来源的强度等级调高几率。

效果是否生效是用修正值或效力加 2 和投骰的值相比较来决定。如果投出值小于等于比较值,效果生效。

例如:如果一个生物被下了强度等级 2 的疾病,那么在中毒持续时间内每个回合都会因为 2D6 投出 2,3,4 而效果生效。

肌肉疼痛(Aching Muscle) 可能造成 1 点力量损伤的持续效果,并有机会对生物造成 D6 回合的迟缓效果。

血瘟(Bloody Flux) 可能造成 1 点耐力损伤的持续效果,并有机会对生物造成 D3 + X 的伤害。

骨病(Bone Plague) 可能造成 1 点力量损伤的持续效果,并有机会对生物造成 D6 回合的麻痹效果。

肺病(Burning Lung) 可能造成 1 点耐力损伤的持续效果,并有机会对生物造成 D3 点的精力伤害。

地城热(Dungeon Fever) 可能触发其它疾病效果的持续效果。

下体瘙痒(Itching Groin) 可能造成 1 点敏捷损伤的持续效果,并有机会对生物造成 D6 回合的盲眼。

腐烂气息(Putrid Breath) 可能造成 1 点魅力损伤的持续效果,并有机会对生物造成 D6 回合的沉默。

关节肿胀(Swollen Joint) 可能造成 1 点敏捷损伤的持续效果,并有机会对生物造成 D6 回合的混乱。

肌肉腐烂(Rotting Flesh) 可能造成 1 点魅力损伤的持续效果,并有机会对生物造成 D6 回合的沉睡。