骑马与砍杀服务器日志文件解析–骑砍战绩查询实现1

本系列将介绍暮色骑士团战绩查询系统的实现方法。作为本系列的第一篇文章,我将简要地介绍从骑马与砍杀服务器日志文件中提取各种数据的方法。基本环境:程序使用c#编写,骑砍服务器版本为1.172,使用adimi tools服务器管理员工具。

骑砍服务器日志内有价值的信息主要有三种:进入服务器、击杀和聊天信息。下面简要解释:

1.击杀信息:
A.简单击杀信息(非TK)
00:00:43F_TY <img=ico_axeone> Molssora 
分别有:时间击杀者武器和被击杀者。
B.复杂击杀信息(TK)
00:01:11 – HorN_Nigel team hit pyryz. (31 dmg points)
00:01:11 – HorN_Nigel <img=ico_headshot> pyryz
注意到在adimi tools日志下,为了检测TK,需保留上一行可能出现的队友伤害信息。比对到攻击者和被攻击者与下文击杀/被击杀者一致时,即可认定为TK。
2.进入游戏信息
00:08:00 – SWSSS has joined the game with ID:  7917610 (非真实uid)
进入游戏信息内包含玩家uid,uid和序列号对应,是鉴别玩家身份的主要手段。注意退出游戏的消息是不可靠的,因为玩家可能在换图期间强退游戏,此时不会被服务器记录。因此,战绩查询系统不关注玩家退出游戏的消息。
3.聊天信息
00:04:20 – [LaoWanJia] 我 要 从 天 而 降
聊天信息是处理低素质行为的主要依据,将其录入数据库大大提高了我们处理玩家举报的速度。
注意到骑砍中所有中文字符串中间均有空格,而玩家名长度变化。为简单起见,战绩系统尽管提取时间和玩家名,但将整个字符串保存作为聊天内容。
于是,我们使用如下枚举类型标识某一行的数据类型:
enum EntryType { join, exit/*废弃*/, kill, chat , other };
观察各类型文本之间的一些共性,总结出使用以下结构体来保存每个条目的数据。详细含义见代码注释:
struct Entry
    {

        public EntryType type;
        public DateTime time;
        public string subj; //动作的发出者
        public string obj;  //事件为击杀时,被击杀的玩家
                            //事件为聊天时,聊天的内容
                            //事件为进入时,玩家的uid
        public bool is_team_kill; //仅当事件为击杀时才有效
        public string weapon; //击杀武器,同上
    }
下面讲解如何处理这些字符串并写入上述结构体中。主要采用字符串分割和正则表达式两种方法
1.获取日志条目时间
str = streamReader.ReadLine();
str = str.Trim();//删除开头的空格
string[] entrywords = str.Split(seperator);
//分割字符串
TimeSpan time = new TimeSpan();
try
{
    time = new TimeSpan(int.Parse(entrywords[0]), int.Parse(entrywords[1]), int.Parse(entrywords[2]));
}//得到日志时间
catch (FormatException) { continue; }//有些日志行不是以时间开头的,忽略并跳过本次循环读取下一行。
2.检测玩家聊天
if (Regex.IsMatch(str, @"\[.*\]"))
{//检测 [玩家名] 这一格式判断一个条目是玩家发送聊天的记录
    entry.type = EntryType.chat;
    entry.subj = Regex.Match(str, @"\[.*\]").ToString().Trim('[', ']');//获取发送聊天信息的用户名
    entry.obj = str.Substring(11);
}
2.检测玩家进入记录和击杀记录
if (!(entrywords.Length < 7))
{//检测长度是为了防止地图名等极短的行导致下列代码数组越界
    if (entrywords[6] == "joined")
    {
        entry.type = EntryType.join;
        entry.subj = entrywords[4];
        entry.obj = entrywords[12];
        if (playername_uid_list.ContainsKey(entry.subj))
             playername_uid_list.Remove(entry.subj);
        playername_uid_list.Add(entry.subj, int.Parse(entry.obj));
        //存储此用户的uid信息
    }
    else if (entrywords[5] == "left")
        continue;
    //目前不记录用户退出
    else if (Regex.IsMatch(str, "<.*>"))
    {//利用 <武器图标名> 格式检测是否是击杀消息
        entry.type = EntryType.kill;
        entry.subj = entrywords[4];
        entry.weapon = entrywords[5];
        entry.obj = entrywords[6];
        //formerline:每次循环结束后将本次的行写入,供下一次循环检测TK
        string[] _entrywords = former_line.Split(seperator);
        entry.is_team_kill = former_line.Contains(entry.subj) && former_line.Contains("team hit") && former_line.Contains(entry.obj);
        /*检测上一行是否是对应的TK信息*/
    }
}
最后,将日志当日日期00:00:00的DateTime类对象加上读取到的时间(TimeSpan类),即得完整的日志时间
 entry.time = dateTime + time;//上文已经解读的时间信息
以上是本文的全部内容。今后还将推出下列文章:
  • 此程序另一核心部分——将上述结构体内容转换为HTTP POST请求信息发送给服务器
  • 服务器接收信息web api设计
  • 数据库结构与运行模式
  • 战绩查询系统前端

敬请关注。

发表评论

电子邮件地址不会被公开。 必填项已用*标注