在上一篇博文中,通过解析压缩数据块解压缩后的数据的前一部分,可以获取到游戏开始前的一些信息,紧接着游戏开始前的信息之后,就是游戏进行时的信息了,其中包括玩家游戏中的操作,例如造建筑,出兵,攻击,移动等,还包括玩家游戏中的聊天信息,玩家退出游戏等。
游戏进行时的信息由很多个数据块组成。这些数据块有几种类型,每个数据块的第一个字节就是数据块ID,用于标识数据块的类型。
下面列出各种数据块类型及其对应的数据块ID、数据块字节数和数据块结构:
1、0x17 - 玩家离开游戏的数据块
数据块ID:0x17
数据块字节数:14字节
结构:
1字节:数据块ID,0x17;
2~5字节:原因;
6字节:玩家ID;
7~10字节:结果;
11~14字节:未知。
2、0x20 - 玩家聊天信息的数据块
数据块ID:0x20
数据块字节数:n + 4字节
结构:
1字节:数据块ID,0x20;
2字节:玩家ID;
3~4字节:数据块剩余的数据的字节数n;
5字节:flag;
6~9字节:聊天模式,0x00:对所有玩家,0x01:对队友,0x02:对裁判或观看者,0x03或大于0x03:对指定玩家,玩家的slotNumber是该值减去3的结果,注意这里是slotNumber而不是玩家ID;
10~n+4字节:聊天内容,字符串,最后一个字节是0x00。
3、0x1E/0x1F – 游戏时间段(TimeSlot)数据块
数据块ID:0x1E或0x1F
数据块字节数:n+3字节
结构:
1字节:数据块ID,0x1E或0x1F;
2~3字节:数据块剩余的数据的字节数n,最小值可能是2;
4~5字节:时间段的时间长度(毫秒,值一般是100毫秒左右);
6~n+3字节:玩家在这个时间段内的操作信息,当n为2时这部分不存在。这部分内容在下面一篇博文中再进行解析。
以上三种类型的数据块是需要解析的数据块,下面还有几种类型的数据块就不用去解析:
4、0x1A/0x1B/0x1C,5字节;
5、0x22,6字节;
6、0x23,11字节;
7、0x2F,9字节。
其中,游戏时间段(TimeSlot)数据块是游戏时间进度的标识,每个时间段100毫秒左右,其中包含玩家在这段时间内的操作信息。而游戏时间段数据块中的毫秒数累加后,就是游戏进行到的时间。比如玩家的操作、聊天、离开游戏的时间,就可以用其对应数据块之前的所有TimeSlot数据块中的时间累加来计算。
Java解析游戏进行时的信息:
添加ReplayData.java用来解析游戏进行时的信息。
ReplayData.java
package com.xxg.w3gparser;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
public class ReplayData {
/**
* 解压缩的字节数组
*/
private byte[] uncompressedDataBytes;
/**
* 解析的字节位置
*/
private int offset;
/**
* 玩家列表
*/
private List<Player> playerList;
/**
* 游戏进行时的时间(毫秒)
*/
private long time;
/**
* 聊天信息集合
*/
private List<ChatMessage> chatList = new ArrayList<ChatMessage>();
public ReplayData(byte[] uncompressedDataBytes, int offset, List<Player> playerList) throws W3GException, UnsupportedEncodingException {
this.uncompressedDataBytes = uncompressedDataBytes;
this.offset = offset;
this.playerList = playerList;
analysis();
}
/**
* 解析
*/
private void analysis() throws UnsupportedEncodingException, W3GException
{
byte blockId = 0;
while ((blockId = uncompressedDataBytes[offset]) != 0) {
switch (blockId) {
// 聊天信息
case 0x20:
analysisChatMessage();
break;
// 时间段(一般是100毫秒左右一段)
case 0x1E:
case 0x1F:
analysisTimeSlot();
break;
// 玩家离开游戏
case 0x17:
analysisLeaveGame();
break;
// 未知的BlockId
case 0x1A:
case 0x1B:
case 0x1C:
offset += 5;
break;
case 0x22:
offset += 6;
break;
case 0x23:
offset += 11;
break;
case 0x2F:
offset += 9;
break;
// 无效的Block
default:
throw new W3GException("无效Block,ID:" + blockId);
}
}
}
/**
* 解析聊天信息
*/
private void analysisChatMessage() throws UnsupportedEncodingException {
ChatMessage chatMessage = new ChatMessage();
offset++;
byte playerId = uncompressedDataBytes[offset];
chatMessage.setFrom(getPlayById(playerId));
offset++;
int bytes = LittleEndianTool.getUnsignedInt16(uncompressedDataBytes, offset);
offset += 2;
offset++;
long mode = LittleEndianTool.getUnsignedInt32(uncompressedDataBytes, offset);
if(mode >= 3) {
int receiverPlayerId = (int) (mode - 3);
chatMessage.setTo(getPlayBySlotNumber(receiverPlayerId));
}
chatMessage.setMode(mode);
offset += 4;
String message = new String(uncompressedDataBytes, offset, bytes - 6, "UTF-8");
chatMessage.setMessage(message);
offset += bytes - 5;
chatMessage.setTime(time);
chatList.add(chatMessage);
}
/**
* 解析一个时间块
*/
private void analysisTimeSlot() {
offset++;
int bytes = LittleEndianTool.getUnsignedInt16(uncompressedDataBytes, offset);
offset += 2;
int timeIncrement = LittleEndianTool.getUnsignedInt16(uncompressedDataBytes, offset);
time += timeIncrement;
offset += 2;
offset += bytes - 2;
}
/**
* 玩家离开游戏Block解析
*/
private void analysisLeaveGame() {
offset += 5;
// 玩家离开游戏就不再计算游戏时间
byte playerId = uncompressedDataBytes[offset];
Player player = getPlayById(playerId);
player.setPlayTime(time);
offset += 9;
}
/**
* 通过玩家ID获取Player对象
* @param playerId 玩家ID
* @return 对应的Player对象
*/
private Player getPlayById(byte playerId) {
Player p = null;
for(Player player : playerList) {
if(playerId == player.getPlayerId()) {
p = player;
break;
}
}
return p;
}
/**
* 通过玩家SlotNumber获取Player对象
* @param slotNumber 玩家SlotNumber
* @return 对应的Player对象
*/
private Player getPlayBySlotNumber(int slotNumber) {
Player p = null;
for(Player player : playerList) {
if(slotNumber == player.getSlotNumber()) {
p = player;
break;
}
}
return p;
}
public List<ChatMessage> getChatList() {
return chatList;
}
}
ChatMessage.java是玩家游戏过程中的聊天信息对应的Java对象,通过解析玩家聊天信息的数据块获取。
ChatMessage.java
package com.xxg.w3gparser;
public class ChatMessage {
/**
* 发送者
*/
private Player from;
/**
* 发送方式
* 0:发送给所有玩家
* 1:发送给队友
* 2:发送给裁判或观看者
* 3+N:发送给指定玩家
*/
private long mode;
/**
* 接收者(mode为3+N时有效)
*/
private Player to;
/**
* 消息发送时间
*/
private long time;
/**
* 消息内容
*/
private String message;
public Player getFrom() {
return from;
}
public void setFrom(Player from) {
this.from = from;
}
public long getMode() {
return mode;
}
public void setMode(long mode) {
this.mode = mode;
}
public Player getTo() {
return to;
}
public void setTo(Player to) {
this.to = to;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
由于玩家可能在游戏过程中离开游戏,要想知道玩家的实际游戏时间,就要排除玩家离开游戏之后的时间,而不能直接使用录像的时长。如果要计算APM的话,就必须使用玩家的实际游戏时间来计算。所以在Player.java中加入playTime表示实际游戏时间,通过解析玩家离开游戏的数据块来设置。
Player.java
/**
* 游戏时间
*/
private long playTime;
public long getPlayTime() {
return playTime;
}
public void setPlayTime(long playTime) {
this.playTime = playTime;
}
在UncompressedData类中加入对游戏进行时的信息的解析。
UncompressedData.java
/**
* 游戏进行时的信息
*/
private ReplayData replayData;
public UncompressedData(byte[] uncompressedDataBytes) throws UnsupportedEncodingException, W3GException {
this.uncompressedDataBytes = uncompressedDataBytes;
// 跳过前4个未知字节
offset += 4;
// 解析第一个玩家
analysisPlayerRecode();
// 游戏名称(UTF-8编码)
int begin = offset;
while(uncompressedDataBytes[offset] != 0) {
offset++;
}
gameName = new String(uncompressedDataBytes, begin, offset - begin, "UTF-8");
offset++;
// 跳过一个空字节
offset++;
// 解析一段特殊编码的字节串,其中包含游戏设置、地图和创建者
analysisEncodedBytes();
// 跳过PlayerCount、GameType、LanguageID
offset += 12;
// 解析玩家列表
while(uncompressedDataBytes[offset] == 0x16) {
analysisPlayerRecode();
// 跳过4个未知的字节0x00000000
offset += 4;
}
// GameStartRecord - RecordID、number of data bytes following
offset += 3;
// 解析每个Slot
byte slotCount = uncompressedDataBytes[offset];
offset++;
for(int i = 0; i < slotCount; i++) {
analysisSlotRecode(i);
}
// RandomSeed、RandomSeed、StartSpotCount
offset += 6;
// 游戏进行时的信息解析
replayData = new ReplayData(uncompressedDataBytes, offset, playerList);
}
public ReplayData getReplayData() {
return replayData;
}
修改Test.java类中main方法进行测试。
Test.java
package com.xxg.w3gparser;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.zip.DataFormatException;
public class Test {
public static void main(String[] args) throws IOException, W3GException, DataFormatException {
Replay replay = new Replay(new File("C:/Documents and Settings/Administrator/桌面/131229_[ORC]Sickofpast_VS_[NE]_AncientIsles_RN.w3g"));
Header header = replay.getHeader();
System.out.println("版本:1." + header.getVersionNumber() + "." + header.getBuildNumber());
long duration = header.getDuration();
System.out.println("时长:" + convertMillisecondToString(duration));
UncompressedData uncompressedData = replay.getUncompressedData();
System.out.println("游戏名称:" + uncompressedData.getGameName());
System.out.println("游戏创建者:" + uncompressedData.getCreaterName());
System.out.println("游戏地图:" + uncompressedData.getMap());
List<Player> list = uncompressedData.getPlayerList();
for(Player player : list) {
System.out.println("---玩家" + player.getPlayerId() + "---");
System.out.println("玩家名称:" + player.getPlayerName());
System.out.println("游戏时长:" + convertMillisecondToString(player.getPlayTime()));
if(player.isHost()) {
System.out.println("是否主机:主机");
} else {
System.out.println("是否主机:否");
}
if(player.getTeamNumber() != 12) {
System.out.println("玩家队伍:" + (player.getTeamNumber() + 1));
switch(player.getRace()) {
case 0x01:
case 0x41:
System.out.println("玩家种族:人族");
break;
case 0x02:
case 0x42:
System.out.println("玩家种族:兽族");
break;
case 0x04:
case 0x44:
System.out.println("玩家种族:暗夜精灵");
break;
case 0x08:
case 0x48:
System.out.println("玩家种族:不死族");
break;
case 0x20:
case 0x60:
System.out.println("玩家种族:随机");
break;
}
switch(player.getColor()) {
case 0:
System.out.println("玩家颜色:红");
break;
case 1:
System.out.println("玩家颜色:蓝");
break;
case 2:
System.out.println("玩家颜色:青");
break;
case 3:
System.out.println("玩家颜色:紫");
break;
case 4:
System.out.println("玩家颜色:黄");
break;
case 5:
System.out.println("玩家颜色:橘");
break;
case 6:
System.out.println("玩家颜色:绿");
break;
case 7:
System.out.println("玩家颜色:粉");
break;
case 8:
System.out.println("玩家颜色:灰");
break;
case 9:
System.out.println("玩家颜色:浅蓝");
break;
case 10:
System.out.println("玩家颜色:深绿");
break;
case 11:
System.out.println("玩家颜色:棕");
break;
}
System.out.println("障碍(血量):" + player.getHandicap() + "%");
if(player.isComputer()) {
System.out.println("是否电脑玩家:电脑玩家");
switch (player.getAiStrength()) {
case 0:
System.out.println("电脑难度:简单的");
break;
case 1:
System.out.println("电脑难度:中等难度的");
break;
case 2:
System.out.println("电脑难度:令人发狂的");
break;
}
} else {
System.out.println("是否电脑玩家:否");
}
} else {
System.out.println("玩家队伍:裁判或观看者");
}
}
List<ChatMessage> chatList = uncompressedData.getReplayData().getChatList();
for(ChatMessage chatMessage : chatList) {
String chatString = "[" + convertMillisecondToString(chatMessage.getTime()) + "]";
chatString += chatMessage.getFrom().getPlayerName() + " 对 ";
switch ((int)chatMessage.getMode()) {
case 0:
chatString += "所有人";
break;
case 1:
chatString += "队伍";
break;
case 2:
chatString += "裁判或观看者";
break;
default:
chatString += chatMessage.getTo().getPlayerName();
}
chatString += " 说:" + chatMessage.getMessage();
System.out.println(chatString);
}
}
private static String convertMillisecondToString(long millisecond) {
long second = (millisecond / 1000) % 60;
long minite = (millisecond / 1000) / 60;
if (second < 10) {
return minite + ":0" + second;
} else {
return minite + ":" + second;
}
}
}
运行程序,输出结果:
版本:1.26.6059
时长:14:43
游戏名称:当地局域网内的游戏 (Si
游戏创建者:Sickofpast
游戏地图:Maps\WAR3\(2)AncientIsles-2.w3x
---玩家1---
玩家名称:Sickofpast
游戏时长:14:43
是否主机:主机
玩家队伍:1
玩家种族:兽族
玩家颜色:红
障碍(血量):100%
是否电脑玩家:否
---玩家2---
玩家名称:尼德霍格
游戏时长:14:42
是否主机:否
玩家队伍:2
玩家种族:暗夜精灵
玩家颜色:蓝
障碍(血量):100%
是否电脑玩家:否
[0:10]尼德霍格 对 所有人 说:All rights reserved by Blizzard
[0:10]尼德霍格 对 所有人 说:w3g files released by www.Replays.Net.
[0:15]Sickofpast 对 所有人 说:yy上都是菜鸟啊
[0:28]尼德霍格 对 所有人 说:diyi orc!
[0:37]尼德霍格 对 所有人 说:就yy有人玩了
[0:39]Sickofpast 对 所有人 说:打ne没赢过
[0:40]尼德霍格 对 所有人 说:中国第一ORC
[14:33]尼德霍格 对 所有人 说:For more replays, plz visit www.Replays.Net
[14:42]尼德霍格 对 所有人 说:g
参考文档:http://w3g.deepnode.de/files/w3g_format.txt
作者:叉叉哥 转载请注明出处:http://blog.csdn.net/xiao__gui/article/details/18350789
分享到:
相关推荐
《C#魔兽录像分析器W3G格式分析器源代码》是针对魔兽争霸III游戏的录像文件(.w3g)进行解析的程序。在魔兽争霸III游戏中,玩家可以通过录制游戏过程来保存战斗记录,而.w3g文件就是这种记录的载体。本项目的核心是...
Java Warcraft Ⅲ Replay Parser(Java解析《魔兽争霸3》游戏录像工具).zip Java Warcraft Ⅲ Replay Parser(Java解析《魔兽争霸3》游戏录像工具).zip Java Warcraft Ⅲ Replay Parser(Java解析《魔兽争霸3》...
《魔兽争霸3战网延迟消除器W3DR详解与应用》 魔兽争霸3,这款经典的即时战略游戏,自发布以来就深受玩家喜爱。然而,在网络对战中,延迟问题一直是困扰玩家的一大难题,特别是对于校园网和局域网用户而言。为了解决...
在实际编写易语言程序时,你需要对魔兽录像文件的内部结构有深入的理解,这包括但不限于文件头信息、时间戳、玩家状态、游戏事件等。同时,易语言的语法特性,如“置数据”用于设置变量值,也需要熟练掌握。编写这样...
魔兽争霸的录像文件(.wre或.w3g)是经过压缩的数据文件,包含了游戏中的所有动作、单位位置、玩家视角等信息。为了能够读取并理解这些数据,需要对录像文件的内部结构有深入的理解。这个源码项目正是提供了这样一种...
魔兽争霸3作为一款经典的即时战略游戏,其地图文件和MPQ文件是游戏的核心组成部分。MPQ,全称“Microsoft Packing Queue”,是由Blizzard Entertainment设计的一种文件打包格式,用于存储游戏资源,如图像、音频和...
2. **地图数据解析**:工具能够解析.W3M、.W3X格式的地图文件,展示地图中的单位、建筑、物品等信息,为地图制作者提供参考。 3. **模型预览**:支持对提取的模型进行实时预览,用户可以直观地看到模型在游戏中的...
在电子竞技和网络游戏中,尤其是像《魔兽争霸3》(Warcraft III,简称War3)这样的实时战略游戏,每一毫秒的延迟都可能影响到战局的走向。为了提供更加流畅的游戏体验,玩家社区中诞生了一款名为"W3DR"(魔兽消除...
W3X文件是暴雪娱乐公司出品的游戏《魔兽争霸3》的地图文件,通常包含了游戏地图的布局、单位、触发器等详细信息。 首先,我们要理解易语言的基础概念。易语言的核心设计理念是“易”,即简单易学。它的语法结构清晰...
《魔兽争霸3模型查看器》是一款专为《魔兽争霸3》玩家和地图编辑者设计的实用工具,它允许用户在不启动游戏的情况下查看、研究和分析游戏中的模型资源。这款软件对于想要深入了解游戏美术设计或者进行自定义地图开发...
《最新魔兽争霸录像分析器》是一款专为魔兽争霸玩家设计的专业工具,主要用于解析和分析1.14到1.24各个版本的游戏录像文件。这款分析器的强大功能和精细程度,使得玩家能够深入理解游戏中的战术策略,提高自己的游戏...
w3g是魔兽争霸III游戏的录像文件格式,它记录了游戏中的所有事件,包括单位移动、攻击、技能施放等,使得玩家可以回放和分析战斗过程。deerchao的魔兽录像分析器正是用于解析这些w3g文件,提取出关键信息的工具。 ...
"魔兽争霸孙悟空模型"就是玩家对游戏进行个性化改造的一个实例,它涉及到的知识点主要集中在模型替换、3D建模以及游戏资源的修改。 首先,我们要了解模型在游戏中的作用。在《魔兽争霸》中,每个单位、建筑或英雄都...
《魔兽争霸3:冰封王座》(War3)是一款深受玩家喜爱的即时战略游戏,其强大的地图编辑器为玩家提供了无限的创意空间。在这个场景中,我们关注的是犬夜叉这一动漫角色在游戏中的应用,涉及到的是模型资源的下载与...
其中,针对经典即时战略游戏《魔兽争霸III》的地图探索工具W3MapHack备受玩家关注。名为"W3MapHackghfvs531.zip"的压缩包,就集中了这类工具的核心功能与使用说明。 首先,W3MapHack是一款特别设计给《魔兽争霸III...
在电子游戏中,特别是即时战略游戏(如《魔兽争霸III》及其扩展包《冰封王座》),`.w3x` 文件是一种重要的文件格式。这些文件主要用于存储地图、战役和其他游戏内容。`.w3x` 文件是由暴雪娱乐公司开发的游戏编辑器...
《魔兽争霸3分辨率修改器》是一款专为魔兽争霸3(Warcraft III)设计的实用工具,由C#编程语言编写,旨在帮助玩家调整游戏的显示分辨率,以获得更佳的游戏体验。这款修改器包含了源代码,对于编程爱好者来说,不仅...