查看“MSQC”的源代码
←
MSQC
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
= 简介&功能 = MSQC,全名为MurakamiShiina QueueCommand,一个功能及其强大的插件 其中MurakamiShiina应该是作者喜欢的一个acg人物,而QueueCommand则是该插件运作的基本原理 旧版MSQC插件的文件名叫MurakamiShiinaQC.py,最初发行于2017年 作者于2019年4月1日更新了此插件,添加了一些功能,并将文件名更换为MSQC.py 由于EUD地图可以随机读写内存,经常会导致作者无意中写出异步触发(或异步代码)导致玩家掉线 针对这种现象,韩国的大神想到了一个方法,可以把非共享内存转化为共享内存,然后再以它为条件,使得我们可以在多人游戏中使用“掉线触发”,而游戏不掉线。他写的这个插件叫做MSQC MSQC是“MurakamiShiina QueueCommand”的首字母,其中MurakamiShiina应该是作者喜欢的一个acg人物,而QueueCommand则是该插件运作的基本原理 = 掉线触发(desync trigger) = 我们在星际争霸的游戏中时,所有的游戏数据都会储存在每个玩家的内存里 我们知道,触发分为条件和动作两部分(触发执行者本质上其实也属于条件) 本质上来讲,“条件”就是读取某个内存地址中的内容并根据内容作出一个判断,“动作”就是向某个内存地址中写入一些内容 从功能上来讲,内存分为两种:共享内存(shared memory) 和 非共享内存(non-shared memory) 共享内存地址中的储存的数据叫做共享数据(shared data),非共享内存地址中储存的数据叫做非共享数据(non-shared data / local data) 游戏中,每个玩家的共享内存内的数据都必须完全相同,否则会掉线 而非共享内存中的数据可以不相同,不会影响游戏的进行 === '''举例说明''' === 地址0x0057F0F0 (至0x0057F0F3)就是一个共享内存,它储存着的4字节数据就是共享数据,数据内容为玩家1当前的水晶数 假设现在游戏中有玩家1和玩家2两个玩家,现在玩家1的钱数为10,玩家2的钱数为20。那么玩家1的内存0x0057F0F0储存的数据就是10,玩家2的内存0x0057F0F0储存的数据也是10,这两个值完全一样 也就是说,在玩家1眼里,玩家1有10块钱;在玩家2眼里,玩家1也是10块钱,这是大家的共识,这就是共享内存和共享数据 如果在某些触发的影响下,玩家1的内存0x0057F0F0储存的数据变成15,而玩家2的内存0x0057F0F0储存的数据变成20,那么这个时候,两个玩家迅速失去同步(desync),互相显示为对方掉线,两个人分别进入各自的平行宇宙,继续自己独自的游戏 这也就是说,如果游戏中的玩家对于共享数据无法达成共识时,这些玩家将无法继续一起游戏 此外,所有玩家的资源、拥有的单位等等等等,都是共享数据 地址0x006CDDC4就是一个非共享内存,它储存的4字节数据,是当前玩家的鼠标指针所在的横坐标,是非共享数据 假设现在玩家1的鼠标在屏幕左上角,而玩家2的鼠标在屏幕右上角,那么玩家1的0x006CDDC4储存的数据内容就是0,而玩家2的0x006CDDC4中储存的数据内容就是639(假设玩家2用的窄屏) 可以看到,两个玩家在同一个内存地址中储存着不同的内容,游戏依然可以继续 这就是非共享内存和非共享数据。触发可以随便修改非共享内存中的数据,而不会使游戏掉线 此外,玩家的屏幕坐标、聊天显示板的内容、玩家当前按下的键等等等等,也都是非共享数据 = 共享内存(shared condition)与非共享内存(non-shared condition) = 上文说过,触发的本质就是用“条件”来读取内存中的数据,用“动作”来向内存中写入数据 读取的内存可能是shared,也可能是non-share,同理,写入数据的内存也可能是shared或者non-shared 读取共享内存的“条件”,我们称为shared condition,而读取非共享内存的则称为non-shared condition或desync condition “动作”也是同理,这里不再赘述。那么,触发就可以被分为以下4种: (1) 读取共享内存(shared condition),写入共享内存(shared action) (2) 读取共享内存(shared condition),写入非共享内存(non-shared action) (3) 读取非共享内存(non-shared condition),写入共享内存(shared action) (4) 读取非共享内存(non-shared condition),写入非共享内存(non-shared action) 其中,第1,2,4种触发都不影响游戏。而只有第3种触发会使游戏掉线,这种触发就是我们所说的掉线触发(desync trigger) 普通的SCMD触发绝大多数都是第1、第2种,所以都不会使游戏掉线。而EUD触发由于可以随意读取内存,也就导致容易写出第3种触发导致掉线 === 举例说明 === 在这里要解释一下,每一条触发都是对所有玩家一视同仁的,它都是读取玩家x的内存,并写入玩家x的内存。比如有一个触发是: 拥有者:玩家1 条件:鼠标指针的横坐标大于100(0x006CDDC4储存的数值大于100) 动作:将当前玩家的钱数改为20(读取0x006509B0内存中的内容,记为x,并向内存 "0x0057F0F0+x*4" 内写入数据20) 这个触发的本质其实是: 在这个触发执行前,所有玩家内存中的“当前玩家(0x006509B0)”都会被设为0,即玩家1,然后: 若玩家1的0x006CDDC4储存的数值大于100,则向玩家1的内存0x0057F0F0中写入数据20 若玩家2的0x006CDDC4储存的数值大于100,则向玩家2的内存0x0057F0F0中写入数据20 ...... 若玩家8的0x006CDDC4储存的数值大于100,则向玩家8的内存0x0057F0F0中写入数据20 那么问题就来了,当这条触发运行的时候,由于0x006CDDC4是个非共享内存,也就是说每个玩家的0x006CDDC4内都可能是不同的内容 即:这个触发的条件对于鼠标位置的横坐标大于100的玩家是成立的,这些玩家的内存0x0057F0F0内就会被写入数据20 而对于其他玩家,条件则是不成立的,所以这条触发的动作就没有给这些玩家执行 这就导致,这条触发运行之后,各个玩家的共享内存0x0057F0F0内储存的数据有差别了,游戏也就掉线了 这条触发就是掉线触发。当然,如果是单人游戏,那么这种触发就完全没有问题 '''这也是很多地图制作者发现自己的地图在单人模式一切正常,但多人模式会疯狂掉线的原因''' 注:“触发拥有者”的本质其实也是处理两个非共享内存,“当前玩家编号(0x006509B0)”和“本地玩家编号(0x512684)”,详见另一个教程 总之,无论这个触发的拥有者是谁,这个触发最终都会像上面写的那样,分别作用于每个玩家的内存! 我们基本可以说,凡是在多人游戏中使用了第3种触发,则必掉线 = 工作原理 = 在游戏中,我们不断使用鼠标与键盘给游戏单位施加指令,这些指令显然都是非共享数据,是每个玩家自己制造的 那么为何游戏不在一开始就掉线呢?这是因为,系统会把每个玩家私有的这些指令数据(QueueGameCommand)转化为所有玩家共有的“单位指令”数据,然后共享到所有玩家的内存中 比如,玩家1给自己的一个枪兵施加了一个指令“移动到地图(100, 100)的位置”,然后系统就会识别这个指令数据并将其共享给所有玩家 这样,每个玩家的内存中都会被写入“玩家1的那一个枪兵要移动到(100, 100)的位置”,从而使得大家的游戏同步进行 MSQC插件就是巧妙地利用了这一特性:当某个非共享数据被读取时,MSQC会先创造一个“辅助单位(QC Unit)” 将这个非共享数据转化为该玩家对于此单位所施加的指令,这样系统就会顺其自然地将这个指令信息共享给所有玩家,也就实现了“非共享数据”转化为“共享数据” 然后把这个共享数据转化为玩家可以在触发中使用的数据(比如某个单位的死亡数) 这一过程结束后,MSQC会把这个辅助单位从地图上移除 游戏中每扫一轮触发,MSQC就会完成一轮“创建辅助单位、施加指令、数据转化、删除辅助单位”的动作 因此如果想实现非共享内存的实时共享,我们通常要让MSQC配合eudTurbo插件一起使用 由于MSQC是把“非共享数据”转化成了玩家给辅助单位的指令,所以这一过程中,系统会把这一过程误认为是玩家在操作某个单位 所以,使用MSQC的地图中,玩家的APM都会相当高,甚至成千上万 === 举例说明 === 举一个例子,比如我们想写一个以“玩家按下键盘上的Q键(非共享数据)”为条件,以某个共享内存作为动作的触发: 这时我们就可以事先告诉MSQC,让它通过上面的方法来把“玩家x是否按下Q键”转化为“玩家x的小狗死亡数”(假设本游戏内用不到小狗这个单位) 也就是说,每当玩家1按下了Q键,MSQC就会把玩家1的小狗死亡数变成1,而当玩家1松开Q键后,MSQC就会把玩家1的小狗死亡数变成0 同理,当玩家2按下Q键的时候,MSQC就会把玩家2的小狗死亡数变成1,以此类推。因此,我们就可以在触发中以“当前玩家的小狗死亡数”为条件 来写触发了 需要强调一点:MSQC将非共享数据转化为共享数据是需要一定的时间的,毕竟在联机游戏中,星际系统识别指令、共享指令都是需要时间的 比如上面的例子,玩家1按下Q键之后,一直到玩家1的小狗死亡数变成1,这之间是有延迟的。经过试验,在无网络延迟的情况下,这个延迟的时间至少为4游戏帧。当网络延迟较高时,则会更长。 = MSQC语法 = 上面说到,我们要事先告诉MSQC,让它把“玩家x是否按下Q键”转化为“玩家x的小狗死亡数”。下面我会讲,如何告诉MSQC让它做这件事,即MSQC的使用语法。 下面的内容会设计到地图的编译。地图的编译需要edd文件(工程文件,类似于makefile),eps文件或者py文件(类似于main)(也称为“插件”),以及输入的scx地图。编译时,要把edd文件送给euddraft.exe进行编译,才能产出成品地图。整个过程其实完全不需要EudEditor。EudEditor仅仅是给上述过程套了一个图形界面而已。对于eps代码的语法我以后有机会会单独开专题来讲,在这里不展开讲了。如有兴趣可以看https://github.com/phu54321/euddraft/wiki/01.-Introduction 自学。 使用MSQC时,主要的代码都要写在edd文件中。下面是一个edd文件的具体实例: [main] input: in.scx output: out.scx [main.eps] [MSQC] MouseDown(L): Goliath Turret, 1 QCDebug : False [eudTurbo] 所有的edd文件的结构,都是由“插件名称”、“指令”所构成。中括号代表使用的插件名称,后面每一行都由 [key] : [value] 构成,是给这个插件的指令,告诉它要做什么。下面我要介绍的就是MSQC如何来写。下面所有的蓝色斜体,都是要用自己写的代码来代替的。黑色的标点及粗体内容都是要原封不动照搬的内容,我称其为关键词。所有的标点符号都为英文(半角),切记不能用中文标点! MSQC的语法(syntax)结构:(中括号代表要用自己写的代码来代替) 语法1: [Non-shared condition 1] ; [Non-shared condition 2] ; [Nonshared condition 3] : [UnitName / UniID / PVariableName], [value to add] 语法2: Mouse : [LocationName / LocationID] 语法3: [Non-shared condition 1] ; [Non-shared condition 2] ; Mouse : [LocationName / LocationID] 语法4: [Non-shared condition 1] ; val, [Address / VariableName] : [UnitName / UniID / PVariableName] 语法5: (第二行不确定,貌似作者写出bug了) [Non-shared condition 1] ; xy, [Address / VariableName] : [UnitName / UniID / PVariableName] [Non-shared condition 1] ; xy, [Address / VariableName], [Address / VariableName] : [UnitName / UniID / PVariableName], [UnitName / UniID / PVariableName] 其他语法: QCDebug : [True / False] (默认True) QCUnit : [UnitName / UniID / PVariableName] (默认Terran Valkyrie) QCLoc : [LocationID](默认0) QCPlayer : [Player ID] (默认10) QC_XY : [X], [Y] (默认128, 128,单位为像素) 注: 语法1至语法5为基本语法,每一条指令的基本结构都类似于一个触发,冒号左边的内容可以理解为“条件”(val和xy除外),每个条件都用分号隔开,等号右边的可以理解为“动作”,写在冒号右边。 语法4和语法5都必须至少有一个non-shared condition,如果一个都不写就会报错。Non-shared condition数量不限上限,虽然上面的语法讲解中只写了一个,但是你可以自己加任意多个,用分号隔开。 [Non-shared condition]有以下几种写法,其中cmptype可为 AtLeast, Exactly, AtMost中的一个,区分大小写: (1) [Address], [cmptype], [Value] 例:0x006CDDC4, AtLeast, 320。意思是“内存0x006CDDC4中的数值等于320” (2) [VariableName].[cmptype]([Value]) 例:x.Exactly(1)。意思是“变量x的值至少为1”。在这种写法中,x为一个EUDVariable,即一个普通变量。此时,需要在eps文件的global环境中先声明这个EUDVariable然后再在onPluginStart()中使用EUDRegisterObjectToNamespace函数来register,具体看使用例。 (3)[key] (4)KeyDown([key]) (5)KeyUp([key]) (6)KeyPress([key]) (7)MouseDown([L or M or R]) (8)MouseUp([L or M or R]) (9)MousePress([L or M or R]) (10)NotTyping 冒号右边的内容[UnitName / UniID / PVariableName]顾名思义有3种写法: (1)写UnitName,即单位名,比如Terran Marine,不用加引号 (2)写UnitID,即单位的ID,比如0,代表Terran Marine (3)写PVariableName,即一个PVariable,即一个EUDArray(8),即一个长度为8的EUDArray的名字,比如y。此时,需要在eps文件的global环境中先声明这个PVariable然后再在onPluginStart()中使用EUDRegisterObjectToNamespace函数来register,具体看使用例。 关于QCUnit等的解释: QCUnit: 用来进行QC操作的辅助单位,建议使用空军单位。默认瓦格雷 QCLoc: QCUnit要在QCLoc(即QC Location)里面生成。默认0,也就是全图第一个location。所以必须保证全图至少有1个location。 QCPlayer: QCUnit的所属玩家。默认10,即玩家11 QC_XY: 在创建QCUnit时,要把QCLoc移动到某个临时位置,创建完了之后再把QCLoc移到原来的位置。这个XY就是要移到的临时位置。这个位置建议设在空旷的地方,不跟玩家单位发生冲突。默认128,128,即地图左上角的(128,128)像素的位置,即(4,4)格子的右下角位置。 QCDebug: 设为True时,则会在error line上print信息,用来辅助debug。默认True。在作图时建议保持True。全部完成之后确认没有bug了准备给玩家玩的时候,再将QCDebug设为False。 重点注意:在euddraft0.9.1.4中,关键词QCDebug、QCUnit、QCLoc、QCPlayer、QC_XY、KeyDown、KeyUp、KeyPress、MouseDown、MouseUp、MousePress是区分大小写的,一定不能把字母大小写写错。val、xy、mouse、NotTyping是不区分大小写的。 关于其他语法的解释,则直接看使用例。
返回至“
MSQC
”。
导航菜单
个人工具
登录
名字空间
页面
讨论
变体
视图
阅读
查看源代码
查看历史
更多
搜索
导航
首页
最近更改
分类目录
常见问题
工作笔记
制图工具
所有页面
工具
链入页面
相关更改
特殊页面
页面信息