CK2源码分析一
当我们玩十字军之王的时候,常常为游戏角色不能做出符合我们心里预期的而苦恼。遂萌生了自己编写mod的想法,但ck2的
的mod开发难度宛如回到数十年前用文本编辑器写代码的时代。我们常常只能自己化身人肉编译器,检查代码中的bug。难度
与手写代码无异。
所以我就萌生了自己开发一款适合ck编辑mod的想法,今天把首款源码露出,欢迎大家一起讨论。
CK2的代码都是文本该怎么办?
查看CK2的源码的时候我们都能发现,CK2的代码都是由一个个txt组成的,都是字符串。其内容架构与其说是代码,更类似一
个配置文件,其中再夹杂着一些逻辑判断。P社官方可能有自己的代码生成器,可以很轻易生成这些配置文件,以及检查报错
。但P社并没有把生成器放出来,所以我们只能自己通过官方源码逆向的去制作生成器了。
按字符解析文本
工欲善其事必先利其器,如果我们没办法知道每个单词的含义以及它后面所可以跟着值,那自然是没办法制作生成器的。
所以制作生成器的第一步解锁解析整个文本,将它们拆成一个个单词。先将它们做一个简单的分类:
public enum TokenType
{
STRING = 1,
EQUIP = 2,
LEFT_BRACKET = 4,
RIGHT_BRACKET = 8,
NUMBER = 16,
POUND = 32,
NULL = 64,
END = 128,
}
我们要做的第一步,是将每个单词分出来。但带有注释的单词都不要,所以拆分了#和字符串两个。接下来是解析:
public class ReaderChar
{
private const int BUFFER_SIZE = 2;
private StreamReader reader;
private char[] buffer;
private int index;
private int size;
public ReaderChar(StreamReader reader)
{
this.reader = reader;
fillBuffer();
}
public char peek()
{
if (index - 1 >= size)
return '\0';
return buffer[Math.Max(0, index - 1)];
}
public char next()
{
if (!hasMore())
fillBuffer();
if(!hasMore())
return '\0';
return buffer[index++];
}
public bool hasMore()
{
if(index >= size)
fillBuffer();
return index < size;
}
private void fillBuffer()
{
buffer = new char[BUFFER_SIZE];
int n = reader.Read(buffer, 0, BUFFER_SIZE);
if (n == -1)
return;
index = 0;
size = n;
}
}
通过以上的代码就能将一个文本字符串转为一个个的字符供我们解析使用。接下来是重头戏:将它们解析成一个个的单词
public class CK2ParseToken
{
private ReaderChar reader;
private TokenList tokenList = new TokenList();
public void getTokenStream(ReaderChar reader)
{
this.reader = reader;
tokenizer();
}
private void tokenizer()
{
Token token;
do
{
token = parse();
if(token.TokenType != TokenType.NULL)
tokenList.Add(token);
} while (token.TokenType != TokenType.END);
}
private Token parse()
{
char ch = '\0';
while (reader.hasMore())
{
ch = reader.next();
if(isWhiteSpace(ch))
return new Token(TokenType.NULL, "NULL");
else
break;
}
switch (ch)
{
case '=':
return new Token(TokenType.EQUIP, ch.ToString());
case '{':
return new Token(TokenType.LEFT_BRACKET, ch.ToString());
case '}':
return new Token(TokenType.RIGHT_BRACKET, ch.ToString());
case '\0':
return new Token(TokenType.END, "END");
default:
return read(ch);
}
}
private Token read(char start)
{
if (start == '#')
{
string result = start.ToString();
while (reader.hasMore())
{
var ch = reader.next();
if (isOtherLine(ch))
break;
else
result += ch;
}
return new Token(TokenType.POUND, result);
}
else
{
string result = start.ToString();
while (reader.hasMore())
{
var ch = reader.next();
if (isWhiteSpace(ch))
break;
else
result += ch;
}
if (CK2Tool.isNumber(result))
return new Token(TokenType.NUMBER, result);
else
return new Token(TokenType.STRING, result);
}
}
private bool isWhiteSpace(char ch)
{
return ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r';
}
private bool isOtherLine(char ch)
{
return ch == '\n' || ch == '\t' || ch == '\r';
}
}
}
至此,所以的CK2的单词都被我们解析,接下来就是通过对{},=,换行符等关键符号重构整个代码