[ESA]永恒的星际联盟

 找回密码
 立即注册

!connect_header_login!

!connect_header_login_tip!

查看: 829|回复: 3

重置版EUD - MSQC插件使用教程

[复制链接]
发表于 2020-3-5 20:01:35 | 显示全部楼层 |阅读模式
本帖最后由 PereC 于 2020-3-6 15:51 编辑

本文为进阶教程,需要读者有足够多的作图经验,以及对EUD的原理有基础理解。本文初稿写于2020年3月5日,之后可能会有修改。本文所需工具:
1. EUDdraft,目前最新版是0.8.9.9(发行于2019年12月),打开下面的地址后仅下载euddraft0.8.9.9.zip即可
https://github.com/armoha/euddraft/releases
2. 随便一个版本的EUD-editor(这个可有可无)。这里我就扔一个最新版的EE下载地址吧,需要梯子。
https://cafe.naver.com/edac/81918
3. 暴雪战网,星际争霸重置版

本文参考资料:
EUD基础原理
http://www.staredit.net/topic/14226/
星际1.16内存表(需梯子):
http://farty1billion.dyndns.org/EUDDB/
MSQC简单的GUI教程:
http://www.staredit.net/topic/17739/#3
euddraft各种插件(包含MSQC)的全面使用手册(需梯子):
https://cafe.naver.com/edac/78006

我本来打算发布一系列教程的,从基础开始科普,先讲EUD的原理、内存地址的计算、EUDdraft使用方法及地图文件编译方法 等等,最后再谈到MSQC这一进阶话题。但是想来想去,觉得还是尽早把MSQC先讲了更好,能看懂的读者先早点学会了,便于技术交流。看不懂本文的读者们可以先自行学习关于EUD的基础知识,平台内本身也有各个大神写的教程,看懂了之后再来学习。


【第一节】“掉线触发(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,也可能是non-share。同理,写入数据的内存也可能是shared或者non-shared。读取共享内存的“条件”,我们称为shared condition,而读取非共享内存的则称为non-shared conditiondesync 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)”,一个是玩家的“本地玩家序号(貌似是0x0057F1B0?如有错误请指出)”。这个我在此不细讲了。总之,无论这个触发的执行对象是谁,这个触发最终都会像上面写的那样,分别作用于每个玩家的内存!

我们基本可以说,凡是在多人游戏中使用了第3种触发,则必掉线。
针对这种现象,韩国的大神想到了一个方法,可以把非共享内存转化为共享内存,然后再以它为条件,使得我们可以在多人游戏中使用“掉线触发”,而游戏不掉线。他写的这个插件叫做MSQC。
MSQC是“MurakamiShiina QueueCommand”的首字母,其中MurakamiShiina应该是作者喜欢的一个acg人物,而QueueCommand则是该插件运作的基本原理。

注:
旧版MSQC插件的文件名叫MurakamiShiinaQC.py,最初发行于2017年
作者于2019年4月1日更新了此插件,添加了一些功能,并将文件名更换为MSQC.py
两个版本在使用时的语法上有一些细微的差别,本文将以最新版MSQC.py的使用方法为准


 楼主| 发表于 2020-3-5 21:16:45 | 显示全部楼层
本帖最后由 PereC 于 2020-3-5 22:37 编辑

【第二节】MSQC的基本原理及使用方法
为了更好地理解MSQC的语法,我在此先简介一下MSQC的原理。我们知道,在游戏中,我们不断使用鼠标与键盘给游戏单位施加指令,这些指令显然都是非共享数据,是每个玩家自己制造的。那么为何游戏不在一开始就掉线呢?这是因为,系统会把每个玩家私有的这些指令数据(QueueGameCommand)转化为所有玩家共有的“单位指令”数据,然后共享到所有玩家的内存中。比如,玩家1给自己的一个枪兵施加了一个指令“移动到地图(100, 100)的位置”,然后系统就会识别这个指令数据并将其共享给所有玩家。这样,每个玩家的内存中都会被写入“玩家1的那一个枪兵要移动到(100, 100)的位置”,从而使得大家的游戏同步进行。MSQC插件就是巧妙地利用了这一特性:当某个非共享数据被读取时,MSQC会先创造一个“辅助单位(QC Unit)”,将这个非共享数据转化为该玩家对于此单位所施加的指令,这样系统就会顺其自然地将这个指令信息共享给所有玩家,也就实现了“非共享数据”转化为“共享数据”,然后把这个共享数据转化为玩家可以在触发中使用的数据(比如某个单位的死亡数)。这一过程结束后,MSQC会把这个辅助单位从地图上移除。游戏中每扫一轮触发,MSQC就会完成一轮“创建辅助单位、施加指令、数据转化、删除辅助单位”的动作,因此如果想实现非共享内存的实时共享,我们通常要让MSQC配合eudTurbo插件一起使用。由于MSQC是把“非共享数据”转化成了玩家给辅助单位的指令,所以这一过程中,系统会把这一过程误认为是玩家在操作某个单位。所以,使用MSQC的地图中,玩家的APM都会相当高,成千上万的APM都不要见怪。
举一个例子,比如我们想写一个以“玩家按下键盘上的Q键(非共享数据)”为条件,以某个共享内存作为动作的触发:这时我们就可以事先告诉MSQC,让它通过上面的方法来把“玩家x是否按下Q键”转化为“玩家x的小狗死亡数”(假设本游戏内用不到小狗这个单位)。也就是说,每当玩家1按下了Q键,MSQC就会把玩家1的小狗死亡数变成1,而当玩家1松开Q键后,MSQC就会把玩家1的小狗死亡数变成0。同理,当玩家2按下Q键的时候,MSQC就会把玩家2的小狗死亡数变成1,以此类推。因此,我们就可以在触发中以“当前玩家的小狗死亡数”为条件 来写触发了。

上面说到,我们要事先告诉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] : [Unit name or Unit ID], [value to add]
语法2:
Mouse : [Location name or index]
语法3(语法1和2的结合体):
[Non-shared condition 1] ; [Non-shared condition 2] ; Mouse : [Location name or index]
语法4:
[Non-shared condition 1] ; Val, [Address]  : [Unit name or Unit ID]
语法5: (第二行不确定,貌似作者写出bug了)
[Non-shared condition 1] ; xy, [Address] : [Unit name or Unit ID]
[Non-shared condition 1] ; xy, [Address],[Address] : [Unit name or Unit ID], [Unit name or Unit ID]
其他语法:
QCDebug : [True or False] (默认True)
QCUnit : [Unit name or Unit ID](默认Terran Valkyrie)
QCLoc : [location index](默认0)
QCPlayer : [Player ID] (默认10)
QC_XY : [X], [Y] (默认128, 128  单位为像素)

其中Non-shared condition可以写成:
[Address], [cmptype], [Value] (cmptype可为 AtLeast, Exactly, AtMost)
[Address], [Value], [Value] (效果不明,不要用)
[key]
KeyDown([key])
KeyUp([key])
KeyPress([key])
MouseDown([L or M or R])
MouseUp([L or M or R])
MousePress([L or M or R])
NotTyping
注意,condition数量不限,可以写一个也可以多个。如果多于1个condition,则要用" ; "隔开

进阶使用:
冒号右边的[Unit name or Unit ID]也可以是[Array Name],其中这个array的类型必须是PVariable(),即EUDArray(8),可以取代unit。如果用unit,则是把non-shared condition的效果作用在某个unit的death数上(每个玩家都有对应的death数),用array则是把non-shared condition的效果作用在这个array上(PVariable有8个位置可以赋值)。但是需要在eps文件的global环境中先声明这个array然后再在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。

关于其他语法的解释,则直接看使用例。


回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-3-5 21:54:31 | 显示全部楼层
本帖最后由 PereC 于 2020-3-6 10:53 编辑

使用例:
【例1】
在main.edd中写入如下内容:
[MSQC]
0x006CDDC4, AtLeast, 320 : 179, 1
0x006CDDC8, AtLeast, 240 : Cave (Unused), 2
效果:
当Memory(0x006CDDC4, AtLeast, 320)条件满足时,将current player的179号Unit的Death数加1,当条件不满足时,就不加1
当Memory(0x006CDDC8, AtLeast, 240)条件满足时,将current player的"Cave (Unused)"的Death数加2,当条件不满足时,就不加2
注:179号unit就是Cave (Unused),这两个写谁都一样,没区别
注:0x006CDDC4储存的是当前玩家鼠标指针的横坐标,0x006CDDC8储存鼠标指针纵坐标。鼠标在屏幕左上角为时坐标是(0, 0)
最终效果:(MSQC的精髓:将non-shared condition转化为shared condition)
当current player的鼠标指针位于屏幕左上区域时,他的179号unit的death数被设为0
当current player的鼠标指针位于屏幕右上区域时,他的179号unit的death数被设为1
当current player的鼠标指针位于屏幕左下区域时,他的179号unit的death数被设为2
当current player的鼠标指针位于屏幕右下区域时,他的179号unit的death数被设为3
这样的话,我们就可以在触发里以current player的179号unit的death数为条件了。

本例在EE中的实现方法:
(1)

m01.png

(2)

m02.png
(3)
m03.png
(4)
m04.png
(5)
m05.png


【例2】
在main.edd中写入如下内容:
[MSQC]
Mouse : Loc1
含义:
Loc1是一个location的名字,必须要在地图中存在。那么要想让MSQC成功运行,除了必须在地图中的任意位置摆放一个名为"Loc1"的Location之外,还要保证此location之后有至少7个location(假设"Loc1"的Location ID为6, 则ID为7至13的location都必须存在。大小和位置不限)
效果:
假设Loc1的location ID为6,那么就是:
将6号location不断移动到player1的鼠标指针位置
将7号location不断移动到player2的鼠标指针位置
将8号location不断移动到player3的鼠标指针位置
...
将13号location不断移动到player8的鼠标指针位置
这个建议配合eudTurbo使用,否则location的移动不流畅
(注:旧版的MurakamiShiinaQC插件要求给每个玩家的鼠标指针都设定一个对应的location,总共要设定8个location。而新版的MSQC语法中,用户仅需要设定玩家1鼠标对应的location即可,剩下的由插件按照上述方式自动设定)

本例在EE中的实现方法:
(1)
m2-1.png
(2)
m2-2.png
注意,点了确认之后,并不会在Recognition列表中看到这个,而且点了确认之后我也不知道怎么删除,因此我建议使用insert - custom手动自己写:
m2-3.png


【例3】
在main.edd中写入如下内容:
[MSQC]
A : 0, 1
含义:
当current player按下A键时,将他的Terran Marine的death数加1,否则不加1

本例在EE中的实现方法:
m3-1.png



【例4】
[MSQC]
NotTyping ; A : 0, 1
含义:
当current player未开启聊天框(不处于打字状态)时,并且他按下A键时,将他的Terran Marine的death数加1,否则不加1
注意:写A,等价于写KeyDown(A)
这个“按下A键”的含义是指,当他按下去的一瞬间,检测一次,按住不放的话不起作用。所以用这个的话一定要加上eudTurbo,这样才能顺利检测到。
注:nottyping不区分大小写


本例在EE中的实现方法:
m4-1.png



【例5】
[MSQC]
MouseDown(L) : 0, 1
含义:
当current player按下鼠标左键时,将他的Terran Marine的death数加1,否则不加1
注:MouseDown的检测标准与KeyDown类似。如果要检测鼠标中键或右键,则写MouseDown(M)或MouseDown(R)


从本例开始,不再展示EE中的实现方法。因为可以自己用insert - custom写


【例6】
[MSQC]
QCUnit : 29
QCLoc : 0
MouseDown(L) : 0, 1
效果:
将QCUnit改为29号单位(Norad II (Battlecruiser)),本来默认的是Terran Valkyrie
第二行就跟没写一样,因为本来默认的QCLoc就是0
第三行见例5
(还没写完,以后有空接着写)









回复 支持 反对

使用道具 举报

发表于 2020-3-25 10:16:46 | 显示全部楼层
重新看了几遍,长见识了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|站点地图|永恒的星际联盟 ( 苏ICP备17020173号-1 )

GMT+8, 2020-7-12 16:39 , Processed in 0.012048 second(s), 7 queries , Gzip On, File On.

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表