WELA即World Editor Log Analyser,世界编辑器日志分析工具。为能详尽地分析一次WC3游戏中的战斗记录而生,如果读者玩过魔兽世界,那么一定对Skada(或者Recount)有所了解,WELA即WC3版的Skada。
WELA诞生于笔者2013年的一个仿魔兽世界副本地图项目,因为对职业的输出平衡以及技能的实际效果分析提出了很高的要求,笔者不得不制作了这么一款工具。
对于一般玩家而言,他们很大程度上是不需要这么一件工具的,因为地图作者应该专注于让玩家在一次游戏中获得最佳体验,而不是让玩家反复研究,如何得出这张地图的最优解。
WELA适合有着一颗数值策划的心的PVE地图作者。
如果你是一名地图作者,你希望你设计的PVE型地图里面的英雄都有相对平衡的输出,包括伤害输出,治疗输出,伤害承受等,以及各种技能的实际效能。但是又找不到合理的统计方式,那么WELA将成为你的一个选项。
最后请记得将WELA从发布给玩家的版本移除,因为WELA会让游戏偶尔卡顿。
WELA没有采用任何第三方修改WC3的手段(无JAPI),你可以使用原版的WC3。
用户通过输入聊天信息的方式触发,输出(使用Preload函数)一个或者几个纯文本的日志文件,然后将这些日志文件放入WELA的网页端工具(使用Javascript编写)进行分析。
受限于笔者的时间与技术,WELA现有如下缺陷:
- 集成过程繁琐,需要与地图的伤害系统进行深度整合甚至重写
- 日志文件数据量庞大,每记录4000条信息,会自动输出一个日志,导致WC3卡顿片刻
- 没有错误输入检测,请不要测试工具的健壮性
地图代码中进行必要的事件记录。将在后面详细说明。
游戏中,WELA将持续记录单位所造成的伤害,治疗等数据。当然,记录什么,什么时候记录,记录多少,是完全可控的。
用户需要选择合适自己的方式来输出日志,可以将当前的所有记录输出为一个日志文件,存于硬盘中,具体路径在Jass代码中配置。
笔者使用输入聊天信息的方式来触发,例如在游戏中输入log
命令,就可以生成一个或者数个日志文件,可用任何文本编辑工具如记事本打开。
后期我会将这个工具部署在网上,大家访问一个网站即可,无需自行构建。
创建配置文件./server/config.js
,样例:
module.exports = {
dataDir: "D:\\Documents\\Warcraft III\\CustomMapData",
port: 3000
};
其中:
dataDir
是War3 pld文件输出路径port
是日志服务器运行端口
然后:
npm install
npm run build
node ./server/index.js
在浏览器中打开http://localhost:3000
如下图:
笔者地图中的战斗时间段以“魔兽世界”为标准,即:
- 任意怪物发现玩家单位,作为一次战斗的开始
- 所有怪物失去攻击目标时,包括但不限于所有玩家死亡,战斗结束
- 所有怪物死亡时,战斗结束
伤害部分可以看到一次战斗中,每个玩家单位的伤害输出情况,详细如下:
- 造成的总伤害量,并按降序排序
- 伤害组成,并按降序排序
- DPS曲线
如图:
左侧的 Damages Done By All Players 为总伤害排名。
右侧的 Damage Per Second 为DPS记录。
点击总伤害的任意一条,即可看到分解伤害,如图:
治疗部分可以看到一次战斗中,每个玩家单位的治疗情况,详细如下:
- 造成的总治疗量,并按降序排序
- 造成的有效治疗和溢出治疗
- 按技能分组的治疗组成
- 治疗组成的有效治疗和溢出治疗
- HPS曲线
如图:
左上方的 Healings Done By All Players 为总治疗排名。其中:
- 绿色部分为有效治疗
- 粉红色部分为过量治疗
右上方的 Healing Per Second 为HPS。
下方的 Healings Done by XXXX 为选定玩家单位的详细治疗组成。
- 点击黄色高亮区域的单位名称,可以在左下方看到选择的单位的治疗组成
- 上一个查看过的单位会被挤到右下方
- 饼图的内环是各治疗技能占比
- 饼图的外环是单个治疗的有效治疗和溢出治疗占比
- 所有的比例数据以总治疗量为标准
施法部分可以看到一次战斗中,每个玩家单位的使用技能统计,其中:
- 使用技能的总次数,并按降序排序
- 使用的技能组成
如图:
左侧 Casts By All Players 显示每个玩家单位的总施法次数。
点击黄色高亮区域的单位名称时,可以在右侧看到所有技能的使用组成。
受到伤害部分可以看到一次战斗中,每个玩家单位承受的伤害情况,详细如下:
- 受到的总伤害量,并按降序排序
- 受到的伤害组成,并按降序排序
- DTPS曲线
如图:
左侧的 Damages Taken By All Players 为总受到伤害排名。
右侧的 Damage Taken Per Second 为DTPS记录。
点击总受到伤害的任意一条,即可看到分解伤害,如图:
魔法值部分可以看到每个玩家单位在一次战斗中的法力值变化曲线,如图:
其中左侧的 Max Mana of All Players 显示为每个玩家单位的最大法力值,按降序排序。
右侧的 Mana Curve 为法力值曲线。
整个数据文件为纯文本格式,每一行为一条记录,每一条记录遵循一定的标准,使用竖线分隔,该标准为WELA系统的核心所在。
WELA数据将单位分为如下几类,用来对最终图标的显示数据进行过滤。
- tank - 坦克职业
- healer - 治疗者职业
- dps - DPS职业
- minion - 仆从/召唤单位
- boss - BOSS怪/精英怪
- creep - 小怪/普通怪
WELA记录总共分5种,对于每条记录(竖线分隔的一维数组),字段的数量不等,每种记录的详细说明如下:
该记录为战斗状态分割器,用以分割不同场次的战斗。首次调用时,为战斗开始,第二次调用时,战斗结束,如此往复,可以想象成括号的匹配问题。
15.942|combat
以下为字段详解:
- 时间戳,为当前记录发生的时间,其值为游戏已经流逝的时间
- 固定取值为"combat",用以说明该条记录为战斗状态分割器
该记录为一次伤害。
17.480|damage|鱼人奴隶|creep|萨穆罗|dps|普通攻击|72.000
以下为字段详解:
- 时间戳,为当前记录发生的时间,其值为游戏已经流逝的时间
- 固定取值为"damage",用以说明该条记录为一条伤害记录
- 伤害来源单位的名称
- 伤害来源单位的类型
- 伤害目标单位的名称
- 伤害目标单位的类型
- 造成该次伤害的攻击方式名称,如,"普通攻击","风暴之锤"等等
- 伤害量
该记录为一次治疗。
22.536|heal|玛法里奥|healer|萨穆罗|dps|生命绽放|33.600|0.000
以下为字段详解:
- 时间戳,为当前记录发生的时间,其值为游戏已经流逝的时间
- 固定取值为"heal",用以说明该条记录为一条治疗记录
- 治疗来源单位的名称
- 治疗来源单位的类型
- 治疗目标单位的名称
- 治疗目标单位的类型
- 造成该次治疗的治疗方式名称,如,"治疗术","神圣之光"等等
- 有效治疗量
- 过量治疗量
注:8+9才是这次治疗的总治疗量,所以8不应包含9的部分
记录单位的每次施法。
21.872|cast|萨穆罗|dps|鱼人奴隶|creep|压制
以下为字段详解:
- 时间戳,为当前记录发生的时间,其值为游戏已经流逝的时间
- 固定取值为"cast",用以说明该条记录为一条施法记录
- 施法单位的名称
- 施法单位的类型
- 目标单位的名称
- 目标单位的类型
- 使用的法术或者技能的名称
记录单位的魔法值状态。用户可根据需要和性能自行选择记录周期,在本例中,系统每1秒记录一次所有玩家单位的魔法值。
23.100|mana|本尼迪塔斯|1120.000|1390.000
以下为字段详解:
- 时间戳,为当前记录发生的时间,其值为游戏已经流逝的时间
- 固定取值为"mana",用以说明该条记录为一条魔法值记录
- 记录单位的名称
- 记录单位的当前魔法值
- 记录单位的最大魔法值
事实上,Jass部分并无固定的写法,只需符合WELA的数据标准即可。这里介绍一种记录数据的思路。
使用一个字符串数组来存储所有的记录
globals
...
string array WELA_Logs
integer WELA_Index = 0
...
endglobals
每当发生一次事件
这里以一个伤害事件为例
local string entry
...
// 组装记录
// 以下元数据的获取方式最好自行实现,因为通常情况下,伤害事件的处理属于一张地图比较底层的逻辑了
set entry = "15.000" /* 自行获取游戏当前时间
2 */ + "|damage|" /* 固定取值
3 */ + GetUnitName(GetEventDamageSource()) + "|" /* 获取伤害来源单位的名字
4 */ + GetUnitRaidType(GetUnitTypeId(GetEventDamageSource())) + "|" /* 获取伤害来源单位的类型,GetUnitRaidType可以自行实现,返回WELA约定的类型即可
5 */ + GetUnitName(GetTriggerUnit()) + "|" /*
6 */ + GetUnitRaidType(GetUnitTypeId(GetTriggerUnit())) + "|" /*
7 */ + GetCurrentDamageName() + "|" /* 自行实现,给出当前伤害的名称
8 */ + R2S(GetEventDamage()); // 获取伤害量
set WELA_Logs[WELA_Index] = entry
set WELA_Index = WELA_Index + 1
...
其它类型的记录可使用类似方式。不必太纠结先后问题,WELA网页端工具会根据第一个字段,也就是时间戳来进行排序处理。
使用Preload函数作为输出日志的手段。
function GenerateCombatLog takes string namespace returns nothing
// 设置输出路径,这里直接将文件输出到文档的Warcraft III\CustomMapData目录下
local string filename = "combatLog-0.pld"
local integer i = 0
call PreloadGenStart()
loop
exitwhen i >= WELA_Index
call Preload(WELA_Logs[i])
set i = i + 1
endloop
call PreloadGenEnd(filename)
call PreloadGenClear()
set WELA_Index = 0 // 将索引重置
endfunction
查看实际输出的文件,使用任意文本编辑器打开,可以发现输出的内容变成了这种:
function PreloadFiles takes nothing returns nothing
call Preload( "729.245|damage|Jaina Proudmoore|Ghoul Servant|Normal Attack|112.530|1|0|0|0|0|1|1" )
call Preload( "747.160|combat" )
call Preload( "747.300|mana|Jaina Proudmoore|1760.000|1760.000" )
call Preload( "747.300|mana|Fandral Staghelm|165.000|165.000" )
call Preload( "1261.889|damage|Fandral Staghelm|Malacrass|Normal Attack|87.397|1|0|0|0|0|1|1" )
call Preload( "1261.978|damage|Sylvanas Windrunner|Malacrass|黑箭|893.283|1|0|0|1|0|1|0" )
call PreloadEnd( 3.3 )
endfunction
不过这并没有关系,WELA的网页端工具会将没用的 call Preload 等内容处理掉。
读者可以按照WELA的数据标准自己编写适合自己地图的Jass代码,因此本文并没有提供可直接使用的代码,以上Jass代码仅供参考。
还是那句话,数据格式标准才是WELA的核心所在,因此,我们有了数据,无论使用什么方式输出(Preload也好,JAPI也好,只是应用方式不同而已),使用什么方式解析,都是次要的。
笔者使用的是 Highcharts 库来作为数据图形化的工具,语言采用 Javascript,运行于任何一款现代浏览器。
具体实现原理将不在此赘述,主要技术来源于 Highcharts,读者有兴趣可以自行研究其文档,或者使用其它语言来编写解析工具。
Highcharts 中文站链接: http://www.hcharts.cn/
本文描述了WELA的设计理念,详细功能以及数据标准,希望读者可以写出自己的脚本以生成符合规范的日志文件,并对自己的地图数值平衡产生帮助。
WELA来源于笔者的一个个人项目,因此其数据标准并不适合所有的地图项目,还有极大的提升空间,欢迎提出各种需求。
Fork me on GitHub: https://github.com/yatyricky/WELA