查看“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是不区分大小写的。 关于其他语法的解释,则直接看使用例 = 使用例 = === 例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数为条件了。 需要注意,本例子只是对MSQC的基本语法做一个演示。在真正作图的过程中,不建议这么写。因为当MSQC写了有若干条指令时,当两条指令的条件同时满足时,冒号右边的PVariable或DeathUnit的值不一定同时变化。比如说我们这样写MSQC: [MSQC] 0x57F23C, Exactly, 100; 0x006CDDC4, AtLeast, 320 : 179, 1 0x57F23C, Exactly, 100; 0x006CDDC8, AtLeast, 240 : 179, 2 0x57F23C这个内存储存的是当前游戏的时刻,单位是游戏帧。所以这两行MSQC指令的含义是在t=100游戏帧这个时刻,若P1的鼠标在屏幕右半边,则给P1的179单位加1死亡数;且,若P1的鼠标在屏幕下半边,则给P1的179单位加2死亡数。 我们假设,当t=100帧时,P1的鼠标位于屏幕右下角区域,显然此时“0x006CDDC4, AtLeast, 320”和“0x006CDDC8, AtLeast, 240”两个条件同时满足。经过试验,MSQC在网络条件较好的绝大多数情况下,指令的条件满足时都会稳定地延迟4帧生效。但是在极少数情况下,两条同时满足条件的指令的生效时间可能不同。比如第一个指令可能需要4帧的延迟才能生效,而第二个指令可能需要5帧。所以,在t=104帧时,P1的179号单位的death数为1,而在t=105帧时,P1的179号单位的death数为2。所以在这种情况下,虽然在t=100的时刻P1的鼠标位置在屏幕右下,但是P1的179号单位的death数在任何时刻都不等于3。在这里,由于我的两条指令都加了“0x57F23C, Exactly, 100”这个条件,所以我保证了MSQC只会在t=100的时刻传递1条或2条指令,且二者不互相影响。无论两条指令是否同时生效,我们都可以通过检测t=100帧之后的时刻各个玩家的179号单位的死亡数,来得知t=100时刻各个玩家的鼠标位于屏幕左上、右上、左下、还是右下。 但是,如果不加t=100帧这个条件,那么MSQC就相当于在实时监控各个玩家的鼠标位置,无时无刻不在共享信息。这就会比较麻烦了,比如:在t=2帧时,P1的鼠标在屏幕左下,即此时只有第二个指令满足条件,假设这个指令需要5帧生效,即在t=7帧时生效。假设在t=3帧时,P1的鼠标在屏幕右上,此时只有第一个指令满足条件,假设这个指令需要4帧生效,即也在t=7生效。那么在t=7时,我们就会发现P1的179号单位死亡数为3,然而这个信息并不能反推出之前的时刻里面P1的鼠标位置,这就是一个废的信息。再比如,t=2和t=3时,第二条指令的条件都是满足的,且t=2和t=3两次满足条件之后都在t=7时刻生效,那么t=2时的指令就会被t=3时的指令覆盖掉。 所以说,在使用MSQC时,我们应该: (1)尽量避免连续不断地共享指令, (2)尽量避免影响同一个单位/变量的两条指令的条件同时满足。 如果想要实现“左上、右上、左下、右下”这个四元判定,则可以: [MSQC] 0x006CDDC4, AtMost, 319; 0x006CDDC8, AtMost, 239: 179, 1 0x006CDDC4, AtMost, 319; 0x006CDDC8, AtLeast, 240: 179, 2 0x006CDDC4, AtLeast, 320; 0x006CDDC8, AtMost, 239: 179, 3 0x006CDDC4, AtLeast, 320; 0x006CDDC8, AtLeast, 240: 179, 4 可以发现,上面4条指令的条件是互斥的,不可能有任何两个指令的条件同时被满足。所以,MSQC也就不会在同一时刻执行两条指令了。当然,这种写法仍然是在连续不断地共享指令,因此还需要增加更多的限定条件,来避免不断地执行MSQC指令。 我们可以使用变量来把上面四条指令转化为1条指令,见之后的例子 === 例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即可,剩下的由插件按照上述方式自动设定) 强调:Mouse : Loc1一行代码即可直接设定8个location分别跟踪P1至P8的鼠标位置 === 例3 === 在main.edd中写入如下内容: [MSQC] A : 0, 1 含义: 当current player按下A键时,将他的Terran Marine的death数加1,否则不加1 注意:写A,等价于写KeyDown(A) 这个“按下A键”的含义是指,当他按下去的一瞬间,检测一次,按住不放的话不起作用。所以用这个的话一定要加上eudTurbo,这样才能顺利检测到。 注: 实际作图时,不建议这么写。建议加上NotTyping条件,见例4 === 例4 === [MSQC] NotTyping ; A : 0, 1 含义: 当current player未开启聊天框(不处于打字状态)时,并且他按下A键时,将他的Terran Marine的death数加1,否则不加1 注意:写A,等价于写KeyDown(A) 这个“按下A键”的含义是指,当他按下去的一瞬间,检测一次,按住不放的话不起作用。所以用这个的话一定要加上eudTurbo,这样才能顺利检测到。 注: nottyping不区分大小写 NotTyping条件等价于0x68C144, Exactly, 0 检测键盘按键时,最好要配上NotTyping条件。以免玩家在打字过程中按到目标按键,在无意中触发指令。 键盘上的每一个按键都有对应的写法,比如小键盘数字0是NUMPAD0,左ctrl键是LCTRL,具体见附录 === 例5 === [MSQC] MouseDown(L) : 0, 1 含义: 当current player按下鼠标左键时,将他的Terran Marine的death数加1,否则不加1 注:MouseDown的检测标准与KeyDown类似。如果要检测鼠标中键或右键,则写MouseDown(M)或MouseDown(R) === 例6 === [MSQC] QCUnit : 29 QCLoc : 0 MouseDown(L) : 0, 1 效果: 将QCUnit改为29号单位(Norad II (Battlecruiser)),本来默认的是Terran Valkyrie 第二行就跟没写一样,因为本来默认的QCLoc就是0 第三行见例5 === 例7 === main.edd文件内容: [main] input: in.scx output: out.scx [main.eps] [MSQC] MouseDown(L) : receive, 1 [eudTurbo] main.eps文件内容: const rc = PVariable(); //写成rc = EUDArray(8)或者rc = [0, 0, 0, 0, 0, 0, 0, 0]也是一样的 function onPluginStart() { EUDRegisterObjectToNamespace("receive", rc); //EUDRegisterObjectToNamespace是函数名,用来register变量 } function afterTriggerExec() { const cp = getcurpl(); setcurpl(getuserplayerid()); foreach(p: EUDLoopPlayer("Human")) { //做一次loop,让变量p取遍每一个人类玩家的playerID, StringBuffer(256).printfAt(p, "玩家{}是否按下鼠标左键: {}", p+1, rc[p]); if (rc[p] == 1) CreateUnit(1, "Terran Marine", "Anywhere", p); //为按下鼠标左键的玩家创建一个枪兵 } setcurpl(cp); } 本例讲的是如何在MSQC中使用变量。其实,单位的死亡数跟PVariable是完全等价的,所以我们也可以用PVariable来代替单位死亡数。这样会增加代码的可读性,因为我们可以随便给变量起名。 MouseDown(L) : receive, 1 的含义: 当P1按下鼠标左键时,rc[0]的值加1,当P1未按下鼠标左键时,rc[0]的值为0 当P2按下鼠标左键时,rc[1]的值加1,... ... 当P8按下鼠标左键时,rc[7]的值加1,... 其中,receive是在MSQC中使用的变量名,rc是在eps代码中使用的变量名。我们需要通过EUDRegisterObjectToNamespace("receive", rc)来定义这个MSQC变量receive。EUDRegisterObjectToNamespace函数的第一个参数需要带引号,是指MSQC中的变量名,第二个参数不要带引号,是指eps中的变量名。这两个变量本质上是同一个变量,只是变量名字不同而已。注:这个变量在MSQC中的变量名可以跟它在eps中的变量名完全相同。 在实际作图过程中,我推荐使用变量来处理MSQC指令,不推荐使用单位死亡数 === 例8 === main.edd文件内容: [main] input: in.scx output: out.scx [main.eps] [MSQC] 0x57F23C, Exactly, 100; send.Exactly(1) : receive, 1 [eudTurbo] main.eps文件内容: var send = 0; //eps代码中的所有变量均为私有变量(non-shared variable),即其所在地址为non-shared memory const receive = PVariable(); //写成receive = EUDArray(8)或者receive = [0, 0, 0, 0, 0, 0, 0, 0]也是一样的 function onPluginStart() { EUDRegisterObjectToNamespace("send", send); EUDRegisterObjectToNamespace("receive", receive); } function afterTriggerExec() { if(Memory(0x006CDDC4, AtMost, 319) && Memory(0x006CDDC8, AtMost, 239)) send = 1; //本地玩家鼠标指针位于屏幕左上区域,将变量send的值设为1 else send = 0; //鼠标不在左上区域时,变量send值设为0 foreach(p: EUDLoopPlayer("Human")) { if (receive[p] == 1) CreateUnit(1, "Terran Marine", "Anywhere", p); //为在t=100帧时鼠标在屏幕左上的玩家创建一个枪兵 } } MSQC指令中,使用内存地址和使用变量的语法是有区别的。send.Exactly(1)就是使用变量的值作为条件的语法,其含义等同于Memory(变量send所在的内存地址, Exactly, 1)。很好理解,不再赘述。 本例中,MSQC指令0x57F23C, Exactly, 100; send.Exactly(1) : receive, 1 的直接含义就是: 在t=100帧的时刻,若玩家1内存中的send变量值恰好为1,则将receive[0]的值加1 在t=100帧的时刻,若玩家2内存中的send变量值恰好为1,则将receive[1]的值加1 ... 在t=100帧的时刻,若玩家8内存中的send变量值恰好为1,则将receive[7]的值加1 === 例9 === main.edd文件内容: [main] input: in.scx output: out.scx [main.eps] [MSQC] 0x57F23C, Exactly, 100; val, send : receive [eudTurbo] main.eps文件内容: var send = 0; const receive = PVariable(); function onPluginStart() { EUDRegisterObjectToNamespace("send", send); EUDRegisterObjectToNamespace("receive", receive); } function afterTriggerExec() { send = 1; //每轮扫触发时都先将send的值设为1 if(Memory(0x006CDDC4, AtLeast, 320)) send += 1; //本地玩家鼠标指针位于屏幕右半边时,将变量send的值设加1 if(Memory(0x006CDDC8, AtLeast, 240)) send += 2; //本地玩家鼠标指针位于屏幕下半边时,将变量send的值设加2 foreach(p: EUDLoopPlayer("Human")) { if (receive[p] == 1) //玩家p+1在t=100帧时鼠标位于屏幕左上区域 CreateUnit(1, "Terran Marine", "Anywhere", p); if (receive[p] == 2)) //玩家p+1在t=100帧时鼠标位于屏幕右上区域 CreateUnit(1, "Terran Ghost", "Anywhere", p); if (receive[p] == 3)) //玩家p+1在t=100帧时鼠标位于屏幕左下区域 CreateUnit(1, "Terran Vulture", "Anywhere", p); if (receive[p] == 4)) //玩家p+1在t=100帧时鼠标位于屏幕右下区域 CreateUnit(1, "Terran Goliath", "Anywhere", p); } } 这个例子就是实现【例1】目的的最佳写法,使用val语法来实现功能。val语法的本质就是将某个私有内存所储存的值直接共享。 指令"若干条件; val, send : receive"的作用是: 当P1内存中的情况满足所给条件时,将receive[0]的值赋值为P1内存中send变量的值;当不满足条件时,将receive[0]的值设为-1 当P2内存中的情况满足所给条件时,将receive[1]的值赋值为P2内存中send变量的值;当不满足条件时,将receive[1]的值设为-1 ... 当P8内存中的情况满足所给条件时,将receive[7]的值赋值为P8内存中send变量的值;当不满足条件时,将receive[7]的值设为-1 注: (1)EUD中的所有变量均为u32类型,所以将其值设为-1等同于将其值设为0xFFFFFFFF,即4294967295 (2)send变量的值在一定的取值范围内,MSQC才能正确工作,将其值传递给receive。这个取值范围仅与地图尺寸有关。若地图尺寸(以格为单位)为x×y,则send的取值范围为0至256*x*y-1。比如地图尺寸是128x128,则send的取值范围是0至4194303,即0至0x003FFFFF,这个取值范围相对来讲还是比较大的,所以一般不用担心。但是如果你想传递的值超过了这个范围,就要使用其他方法了 === 例10 === main.edd文件内容: [main] input: in.scx output: out.scx [main.eps] [MSQC] 0x57F23C, Exactly, 100; xy, mouseXY_local: mouseXY_rc [eudTurbo] main.eps文件内容: const mouseXaddr = 0x6CDDC4; const mouseYaddr = 0x6CDDC8; const mouseXY_rc = PVariable(); //received const mouseX_rc = PVariable(); const mouseY_rc = PVariable(); var mouseX_local = dwread_epd(EPD(mouseXaddr)); var mouseY_local = dwread_epd(EPD(mouseXaddr)); var mouseXY_local = mouseY_local * 0x10000 + mouseX_local; //将本地玩家的鼠标横纵坐标储存在一个变量里 function onPluginStart() { EUDRegisterObjectToNamespace("mouseXY_local", mouseXY_local); EUDRegisterObjectToNamespace("mouseXY_rc", mouseXY_rc); } function afterTriggerExec() { mouseX_local = dwread_epd(EPD(mouseXaddr)); mouseY_local = dwread_epd(EPD(mouseYaddr)); mouseXY_local = mouseY_local * 0x10000 + mouseX_local; //将本地玩家的鼠标横纵坐标储存在一个变量里,每一轮扫触发都要更新 foreach (p: EUDLoopPlayer("Human")) { mouseY_rc[p], mouseX_rc[p] = div(mouseXY_rc[p], 0x10000); //得到的纵坐标和横坐标分别是mouseXY_rc除以0x10000的商和余数 //当玩家p+1的MSQC指令未触发时,mouseXY_rc[p]的值为0xFFFFFFFF,此时拆解到的mouseY_rc和mouseX_rc值均为0x0000FFFF } const cp = getcurpl(); setcurpl(getuserplayerid()); foreach(p: EUDLoopPlayer("Human")) { StringBuffer(256).printfAt(p, "玩家{}的鼠标指针在其屏幕内的坐标为: {}, {}", p+1, mouseX_rc[p], mouseY_rc[p]); } setcurpl(cp); } 以上内容,可以把每个玩家的鼠标坐标通过一个MSQC变量共享给所有人。xy语法的作用和val基本完全相同,也是直接将某个变量的值共享给所有玩家,唯一的不同就是取值范围。xy共享变量的机理是,将被共享的变量拆解为高字节和低字节,各2字节,以这种方式共享。因此,128x128的地图使用MSQC的xy语法所共享的变量取值范围为:被共享的变量的高2字节取值范围为0至0x7FF,低2字节的取值范围也是0x7FF。所以,使用xy共享的变量的最大值为0x07FF07FF。 所以说,xy和val的区别就在于,如果地图尺寸为128x128,两种语法所能共享的变量的取值个数皆为0x400000个值,即4194304个值, 只不过, val的取值范围为0至0x3FFFFF的所有数值 而xy的取值范围是:高2字节范围为0至0x7FF,低2字节范围为0至0x7FF 读者可以将条件“0x57F23C, Exactly, 100”改为更宽松的条件,比如NotTyping,并观察结果
返回至“
MSQC
”。
导航菜单
个人工具
登录
名字空间
页面
讨论
变体
视图
阅读
查看源代码
查看历史
更多
搜索
导航
首页
最近更改
分类目录
常见问题
工作笔记
制图工具
所有页面
工具
链入页面
相关更改
特殊页面
页面信息