|
CLEO4新特性浅析---*.CLEO插件的介绍与解读
by kwanz @SA虚拟世界
转载请注明
CLEO4中包含了多项方便的新特性,其中之一就是允许自定CLEO函数,使我们可以编程实现更多灵活的功能。今天我给大家介绍一下自定CLEO函数的核心部件——*.CLEO插件的原理和编程实现(for GTASA)。文中的讲解,基本是自己参考CLEO SDK推测得到的理解,如有错误,敬请高手指正。关于CLEO版本和功能更新的更多信息,请参阅http://www.sannybuilder.com。
本文主要内容包括:
1. 基本原理
2. CLEO SDK简介
3. 两个官方例子的解读
1. *.CLEO插件的基本原理
*.CLEO插件就是CLEO后缀的DLL文件。与同为DLL实现的ASI插件不同,CLEO插件不能直接作用于游戏主程序,它的作用是为*.CS格式的CLEO脚本提供自定义函数,是沟通游戏和操作系统/DirectX库的另一座桥梁。CLEO插件可以用标准的C++实现,引入Windows API和DirectX SDK后可以实现非常强大的功能,而且又服务于CLEO主程序和脚本,与游戏线程的联系方便、紧密,因此使用它兼具有ASI插件和CS脚本的优点。
在讲解CLEO SDK和CLEO范例之前,首先介绍一个重要的概念——回调。CLEO中处处用到了回调。Windows程序员对“回调”一定不陌生,“这是一种高级的技术,但并不是高难度的技术”。(出自张鸿《感悟Visual Basic:细水长流话API》)简单地说,一般编程序写一个函数,是为了在程序运行的某处调用它:
int main () {
...
do_something(); //调用do_something函数
...
}
而回调则不然。Win32编程中的回调函数,是写好了一个函数,把函数的入口告诉系统,让系统在合适的时机执行它。回调函数一般需要指定特殊的函数声明、参数列表和清栈方式等等等等。但是在CLEO插件中,情况没有那么复杂,因为CLEO插件是提供给CLEO主程序(其实就是调用函数的CS脚本)回调,上头是怎么回事做CLEO的人还是大致清楚的,模式也比较固定,只要简单了解一下它的工作原理就行了。
2. CLEO SDK简介
如果你正确安装了Sanny Builder,在文档目录下应该会出现一个CLEO_SDK文件夹,包括内容如下:
demo_plugins <DIR> 示例CLEO插件和代码
CLEO.h CLEO编程所需的头文件
CLEO.lib CLEO编程所需的资源文件
CLEO_SDK.chm CLEO SDK帮助
自带帮助,而且很详细,唯一的问题是那是俄文的(- -),不过没有关系,SDK中可用的函数的名字都是很好懂的英文,而且提供了示例代码,一看便知。
demo_plugins目录包括:
bin <DIR> 编译好的示例CLEO插件
FileSystemOperations <DIR> 文件操作CLEO
IniFiles <DIR> INI配置读写CLEO
IntOperations <DIR> 位运算CLEO
CLEO.h 这货跟外面那个没什么区别
CLEO.lib 同上
CLEO_plugins.sln VS2008打开之后留下的工程文件
根据帮助,CLEO SDK提供的函数包括:获取游戏和CLEO主程序版本,读取/设定CS语句中的传入/传出参数,跳转,返回,等等。CLEO回调函数的通用写法如下:
OpcodeResult WINAPI opcodeHandler(CScriptThread* thread) {...}
opcodeHandler是函数名,thread表示调用函数的线程(游戏主线程/CS脚本),在回调时传入。回调函数的返回值为OR_CONTINUE(成功)或OR_INTERRUPT(失败)。
3. 官方示例讲解
在这里我简单介绍一下两个CLEO:位运算和INI配置读写。前一个会讲的详细些,后一个只是简单提一提它是如何实现调用Windows系统功能的,由此看看CLEO插件的强大威力。
(1) IntOperations
在CS编写中,想要用and,not直接对几个变量做逻辑运算求值,其实是靠CLEO插件实现的。先来看IntOperations的目录结构:
dllmain.c DLL代码主文件
IniFiles.vcproj VC工程文件
IniFiles.vcproj.Rubbish.... 应该是垃圾...
opcodes.c CLEO函数的核心代码
opcodes.h opcodes.c的头文件
stdafx.h VC自动添加的头文件
因为CLEO中要用到Windows API,所以必须包含stdafx.h。其实只不过是VC把包含两个头文件写在里面,并且自动添加到工程中而已。dllmain.c是这个CLEO(DLL)的调用入口,写法是固定的,只是在主程序挂载这个CLEO的时候,执行初始化Opcode和回调函数的操作。下面重点讲解opcodes.c。
以下是opcodes.c的节选代码:
#include "stdafx.h"
//include CLEO header
#include "CLEO.h"
// 必须包含CLEO.h头文件。
#define OPCODE_INTOP_AND 0x0B10
// 定义新Opcode。最好从0Bxx开始定义,因为前面的都用光了。
...
OpcodeResult WINAPI Script_IntOp_AND(CScriptThread* thread);
...
BOOL InitOpcodes()
{
BOOL result = FALSE;
//check cleo version
if (CLEO_GetVersion() < CLEO_VERSION)
{
MessageBox(HWND_DESKTOP, "An incorrect version of CLEO was loaded.", "IntOpearations.cleo", MB_ICONERROR);
return FALSE;
}
// 检查安装的CLEO版本,如果版本比开发时使用的CLEO SDK要低,则报错。
//register opcodes
if (CLEO_RegisterOpcode(OPCODE_INTOP_AND, &Script_IntOp_AND))
result = TRUE;
// 已经把自定义函数的入口挂在刚才定义的Opcode上。Opcode注册成功。
...
return result;
}
OpcodeResult WINAPI Script_IntOp_AND(CScriptThread* thread)
/****************************************************************
Opcode Format
0B10=3,%3d% = %1d% AND %2d%
****************************************************************/
// 注释已经给出了Opcode的调用格式。0B10=3表示语句需要3个参数。也就是在SB中,打上0B10:,状态栏就会出现expecting 3 params.
// %1d%表示参数。1是参数的序号,以下必须顺次读/写。d表示数值型(貌似不分int和float),s表示字符串
// 注意这一句必须写在SB目录下的data\sa\sascm.ini中才能被SB识别。为了在SB Opcode搜索器中显示出来,还要在opcodes.txt中写一句调用示例
{
int a = CLEO_GetIntOpcodeParam(thread);
// 从调用线程中读取整型参数
int b = CLEO_GetIntOpcodeParam(thread);
CLEO_SetIntOpcodeParam(thread, a & b);
// 向调用线程中写入"按位与"操作的结果。先读后写,这也是调用语句中左数是第三参数的原因
return OR_CONTINUE;
// 返回操作成功的状态信息
}
...
sascm.ini中的相关内容如下:
; IntOperations.cleo opcodes
0B10=3,%3d% = %1d% AND %2d%
...
opcodes.txt中的相关内容如下:
0B10: 0@ = 0@ AND 0xFF
...
(2) IniFiles
上面的CLEO只是扩充了CS中的一点简单的运算功能,而INI CLEO则触及了CLEO4新功能的核心:文件操作。有了INI读写功能,CS脚本可以记录游戏里某新功能的详细设置(比如说,N多个自定义地点的一键瞬移),甚至可以把某些变量直接通过INI读写,达到存储大量数据的目的,这大概可以当成虚拟内存的一种朴素的实现吧。
具体的细节就不多说了,这个CLEO的关键之处是用到了如下两个API:
GetPrivateProfileString() // 读INI
WritePrivateProfileString() // 写INI
关于API的细节请自行百度或查MSDN。首先,CLEO需要获取当前完整路径:
char* MakeFullPath(char *path, char *dst)
{
if (path[1] != ':') // 第二个字符不是冒号,说明是相对路径,没有带盘符和游戏目录
{
//get current working directory
GetCurrentDirectory(MAX_PATH, dst);
// GetCurrentDirectory是一个API,返回游戏目录
strcat(dst, "\\");
strcat(dst, path);
// 把相对路径粘到系统目录后边
}
else
{
// 是绝对路径,直接用
strcpy(dst, path);
}
return dst;
}
从INI读整型参数的函数写法如下:
OpcodeResult WINAPI Script_InifileGetInt(CScriptThread* thread)
/****************************************************************
Opcode Format
0AF0=4,%4d% = get_int_from_ini_file %1s% section %2s% key %3s%
****************************************************************/
{
char iniPath[MAX_PATH];
char path[100];
char sectionName[100];
char key[100];
int result;
CLEO_ReadStringOpcodeParam(thread, path, sizeof(path));
CLEO_ReadStringOpcodeParam(thread, sectionName, sizeof(sectionName));
CLEO_ReadStringOpcodeParam(thread, key, sizeof(key));
// 读入参数123:路径,INI的分节和查找的键名
//if path is short, GetPrivateProfileInt() searches for the file in the Windows directory
MakeFullPath(path, iniPath);
// 处理一下路径
result = GetPrivateProfileInt(sectionName, key, 0x80000000, iniPath);
// 直接调API,很简单吧 0x80000000是读取失败返回值
CLEO_SetIntOpcodeParam(thread, result);
CLEO_SetThreadCondResult(thread, result != 0x80000000);
// 返回执行是否成功
return OR_CONTINUE;
}
正如前面所说,CLEO插件编程可以实现非常强大的功能,而且与游戏线程的联系方便、紧密,确实是实现游戏特效的一种好方法。
|
|