如何找到目标Scope?【Stellaris Events Modding 进阶教程#2】

叠来套去的scope语句
写作事件时,我们都按照触发时机地点——>找到目标——>处理目标的思路进行分析,其中找到目标这一部分,往往就是指找到目标对应的scope。这一章我们将聚焦于各种scope的跳跃和调用语句,让各位对scope的利用有一个比较好的理解,顺利地找到自己的理想对象(笑)。
1 对象转换——scope跳跃与调用语句的简要介绍
在蠢驴语言的结构中,scope是表达的主语,相信大家已经有所体会了。大多数事件都有它默认的基本scope(根/ROOT scope),例如country_event的基本scope便是触发者对应的国家。
但是很多效果,都需要scope的转换:比如人口的迁移需要寻找到一个国家的两个星球,外交事件需要两个国家之间的互动等等。这个时候就需要我们使用scope跳跃语句,对scope进行切换。
scope跳跃语句,是蠢驴effect和condition里一类语句的统称。这类语句都用等号连接着一个{大括号},在{大括号}内的语句,在下一次scope跳跃前,就是在新的scope中,使用了新的主语。
另一方面,有些effect或者condition拥有复数个目标,例如判断两国是否在交战状态,或者改善两国关系都需要两个scope的支持。其中一个scope可以作为主语,但另一个scope呢?只能在这种语句中充当宾语的定位。
这种时候我们需要在语句中调用出另一个scope:使用scope调用语句,这一类语句可以指代一个scope,它们可以是特定的代码流程词汇,也可以是你事先保存的目标。

现在让我们从最基础的遍历型开始,深入了解一下这些scope语句。
2 遍历,还是遍历——遍历型scope跳跃语句
我在上一章就提到了这一类遍历型scope跳跃语句,现在让我们就下面的实例来对它们进行探究。

范例2-1 逃不过的清算
某个圆神信徒在完成了“清算后成功成为河长”的成就后,也兴致冲冲地加入了mod制作的行列。第一步就是要加大它生存的难度,搞出各式各样的圆神清算......


这一片代码看起来好像比较复杂,但其实就是一串并列的效果组合在一起罢了。让我们把代码拆成一个个小的部分,看一看基础的一些scope跳跃语句是如何使用的。
首先是遍历型。这一类scope跳跃语句以any、every、random开头,通常是对符合某一标准的群体,或是对这一群体中的随机一个进行处理。



在effect区域内,这些遍历型scope跳跃语句以every或random打头,分别代表所有和随机一个。在condition区域内,这种语句则以any开头,代表任何一个的含义。
这些遍历型scope跳跃语句可以满足我们写mod中查询目标的绝大部分需求,下面由范例2-1,举一些使用该语句进行事件制作的例子。

范例2-1-1 领袖老化
我们的圆神信徒首先决定从新国家的领袖下手,让被清算国家在之后的每个月,领袖的年龄保持在150岁......虽然没考虑寿命循环的要素,这样一个高龄也极大增加了领袖存活的难度。
先我们需要用蠢驴语言翻译这个需求,也就是每个月,该国家的全部领袖,设定年龄为150。目前不考虑触发时间的每个月,我们需要先找到这些领袖,在触发事件国家为ROOT的前提下。

这个遍历型scope跳跃语句将scope由ROOT对应的触发国家,转换到了ROOT所拥有的全部领袖。每个拥有的领袖都会触发大括号内的effect,也就是:

这样你就能够令ROOT的所有领袖一夜白头,前提是这些领袖的种族有头发。

范例2-1-2 拆迁大队
接下来这位仁兄想惩罚一下那些在银河系中到处建造违章建筑的家伙,让那些被戴森球遮挡的恒星重见天日...他计划随机挑选一个触发国家的巨构,将其摧毁。
我们的新任务和范例2-1-1有什么区别呢?没错,数量。
我们不再需要选择全部目标,而是要随机挑选一个,这种时候every显然就破坏力太大了,轮到我们的random登场:

然后很简单的effect系列:


这两个最简单的effect带领我们对every和random有了一些了解,接下来,让我们看看any和limit的运用。

范例2-1-3 众叛亲离
为了更进一步地迫害玩家,这个modder决定从国家间的关系下手,全部的国家......不,应该把条件设的复杂一点,所有拥有拥有“灵能”特质人口的国家,对触发国家的信任减少,怎样?
好,现在难度上升了,我们剖析一下需求。
找到目标A:所有国家,条件为[拥有 拥有灵能特质的人口]
执行effect,effect的目标有两个,一个为触发国家,一个是目标A。
让我们一个个实现,首先是找到目标:既然目标还是所有国家,先写一个这个:

但我们现在要找的并不是毫无限制,我们需要符合条件的国家,这时候我们就需要使用limit了:

通过加入limit,并在limit内部填入condition,我们可以过滤我们需要选择的目标,只让符合条件的国家执行接下来的effect。
接下来就是确认要符合的条件:[拥有 拥有灵能特质的人口]。我们要判断这个国家拥有人口的情况,就需要在condition中进行scope跳跃。我们可以这样:

在condition中的遍历型scope跳跃语句,往往是以any开头,这类语句的含义为任意一个,当存在任意一个满足括号内条件的目标,就返回真。
在这里,any_owned_pop就代表着任意一个拥有的人口,括号内的scope切换至pop,使得我们可以运用pop对应的condition语句进行判断。
接下来我们便可以进入effect的讨论了,减少信任度——按照trust的自动补全,我们可以轻松地找到这样的语句:

很快我们就发现这个语句需要两个目标,其中一个是语句所在的scope,另一个,就是该语句属性中的who了。
那么who应该对应的是什么呢?这个语句的效果是:增加[当前scope国家]对[who对应scope国家]的信任度,也就是说who要填入触发国家对应的scope。
那么就应该这样填写:

……等一下。
ROOT?这是?
3 PREVPREV.FROMFROMFROM——流程scope跳跃语句
ROOT很明显不是一个遍历型scope跳跃语句。毕竟它不包含every或者random。很多时候我们会遇到像add_trust这样,需要指定多个目标scope的effect语句,这时候我们除了需要寻找到多个对应的scope,还需要把这些scope正确地填入到effect的参数中。
这时候我们有两种选择,一种是event_target,我们之后会提到;另一种便是采用流程scope跳跃语句。这类语句不像遍历型,它们往往并不能帮助我们找到符合条件的目标,相反,它们的作用是调用那些我们早已找到的目标,减少我们寻找的次数,实现只靠遍历无法实现的功能。
一、就在此处——THIS
当前scope,当前scope我说着实在是过于绕口了,在蠢驴的语法中,现在所处的scope,被称为THIS。
THIS指代的是当前所在的scope,例如范例2-1-3中add_trust所在位置的THIS就是every_country找到的国家。
像遍历型一样,你可以通过={}的方法把当前scope跳跃到THIS所对应的scope。此举同时对effect和condition生效。
你可以写这样的代码:

但这个代码基本上没有作用,THIS指代的是当前scope,从当前scope跳到当前scope是没什么意义的()
但THIS并非完全没用,有一些语句虽然从字面意义上理解只需要一个目标,它们的目标并不是当前scope,而需要你进行额外的指定。例如下面这个:

不知道蠢驴为何这样规定,你需要在等号后面确认目标:

因此理解THIS的含义也是相当重要的,接下来我会逐渐用THIS取代“当前scope”。
二、追本溯源——ROOT
在第一小节,我们提到大多数事件都有它默认的基本scope(根/ROOT scope),例如country_event的基本scope便是触发者对应的国家。这个基本scope就是我们之前写事件,没有使用scope跳跃语句时一直处于的scope,能取得的,最基本的目标。

我们能够使用的事件种类有很多:
event (无ROOT)
pop_event
ship_event
fleet_event
planet_event
system_event
country_event
observer_event
starbase_event
pop_faction_event
first_contact_event
espionage_operation_event
……
随着版本更新,很多新的事件种类被添加到了群星中,蠢驴也不是只做了一些傻事(但太多事件种类还是过于繁琐了)但对于刚进入事件写作的你,我建议优先使用以下的事件种类:
country_event ROOT为触发的国家,国家即玩家,易于理解。
planet_event ROOT为触发的星球,对于大多数剧情向事件,这样的事件更方便你取到那个星球的相关信息,例如名字。
event 没有ROOT,主要用于全局设置,或者无可奈何的on_action限制。
ship_event ROOT为触发的舰船,挖坟都是这种事件,同样,剧情方便。
当然这些事件除了基本的ROOT不同以外都有着类似的写法,你选择事件种类可以相当随意......除了你在使用on_action的时候,会发现每种on_action都有着不同的event种类限制。

......看起来跑题了。
我们了解到每种事件的触发者,基本scope,也就是ROOT之后,便可以看一下ROOT作为effect和condition在事件中的作用了。
就比方说范例2-1-3,我们见到了这样的代码:

蠢驴创造了ROOT这个东西,可不只是让我们嘴上说说的。
通过ROOT,我们可以在事件中随时调用其基本scope,就算你套了364层括号,我也能通过一层ROOT识破你的真面目。
ROOT在事件内的任何地方都代表该事件的基本scope,除了在这类多目标语句内可以作为目标指代,ROOT还能够这样使用:

像之前的THIS一样,你可以通过={}的方法把当前scope跳跃到ROOT所对应的scope。此举同时对effect和condition生效。
三、回忆上一步——PREV
那么除了ROOT,我们在之前的事件写作中还找到了很多别的scope,它们不是事件的基本scope,无法通过ROOT来查找......难道必须使用event_target了吗?大可不必()
下一个杀器,PREV在事件中有着更加灵活的运用。

范例2-1-4 航道重组
是时候让我们接触一些更炫酷的功能了不是吗?我们学会了遍历型,还有着ROOT指引方向......我们的modder决定让玩家帝国内的航道发生改变,就想他玩过的更多巨构mod,其中的彭罗斯炸弹一样。
他的需求是选择触发国家境内的一个随机星系,摧毁三条航道,然后与随机相邻星系创建三条航道。
这个玩意看上去有那么一点高端了,但确实一点也不复杂。
首先我们可以尝试去找到目标:

没错,当你在VSC中输入random或者every、any时,常常会出现惊喜()很多蠢驴已经用到的遍历型scope跳跃语句极大简化了我们的写作难度。

while,一个非常常见的循环语句,在while里的语句会被重复执行。限制执行的次数有两种方法,一种是count语句,如上图,我们可以通过在while内写count=x来让其内部effect仅被执行x次;另一种是使用limit,类似if,while只会在limit内部条件成立时执行内部的effect。

在群星的mod制作中,while的执行次数是有限的,也就是说会出现limit仍然满足,while却不再执行的情况,需要注意。
其实遍历型中的every已经可以执行很多循环的效果,所以建议多多思考来优化关于循环的代码,之后会详细介绍。

接下来就要去执行效果了。不过在此之前我们还需要考虑一个问题:相邻星系就一定存在航道吗?
显然不是,那么我们就需要增加一个limit。

锵锵,PREV的初 次 登 场。(是不是还有奇怪的逻辑语句混入了?)
PREV和ROOT都是流程scope跳跃语句,和ROOT不同,PREV代表的是,切换至THIS(当前scope)之前的scope。有点绕?我们就该范例给一个详细说明。

我们是如何找到目标星系的相邻星系的?代码经历了三个scope。
首先,我们利用random_system_within_border将scope由ROOT,也就是触发国家切换到了随机一个境内的星系。
然后我们利用random_neighbor_system将scope由目标星系切换至其随机一个相邻星系,也就是THIS。
我们经历了两次scope转换,三个不同的scope。PREV就是抵达THIS(当前scope)在跳跃之前所处的scope,也就是这里random_system_within_border所选中的星系。要判断其相邻星系是否与其有航道连接,我们只需使用PREV便能解决目标寻找的问题。
x个PREV相连,可以指代x次跳跃前的scope,例如将范例中的PREV换为PREVPREV就会指代ROOT对应的触发国家。(当然,对于has_hyperlane_to,PREVPREV是国家而不是星系,因此不是一个合法的目标。)
PREV和ROOT有着类似的特性,也就是说下面这样是可行的:

如果你还是觉得判断PREV是哪个有一点困难,你可以借助VSC的帮助:鼠标停留在任意语句上,你可以看到代码介绍的方框中显示着PREV和THIS,ROOT的scope种类,辅助你对代码的选择。

每个种类的scope都有不同的condition和effect,不匹配就会报错,还请多多注意。
让我们回到范例,完成这一段代码:



add_hyperlane和remove_hyperlane对应着创建/删除超时空航道,如图,你可以发现这两条代码也与THIS不直接相关,需要你进行指定。

三、萌新永远的噩梦——FROM简述
终于,我们还是到了最后一个流程scope跳跃语句:FROM。
FROM指的到底是什么?为什么他的代码我复制过来就失效了?这样或那样的问题中我们常常见到FROM的身影。FROM就是每个写事件萌新的一大boss,引起了无数的bug,因此恐怕这一章我们还没办法做一个完整的介绍。

范例2-2 来击个掌
还是让我们把那个整天搞破坏的神经病忘掉吧,没人会喜欢那种mod的。
来击个掌,如何?
规则很简单,AI国家触发事件后,随机一个玩家国家会接收到一个消息“AI和你击掌!”你有两个选择:接受或拒绝,如果接受,什么都不会发生;倘若拒绝......那个发起击掌的AI国家就会灭亡。

这个功能包含三个事件,一个测试事件和AI方的测试事件放在下面,可以试试解读一下......很简单的。

想要测试事件,你只需要控制台触发xskiper_teach.202事件,便能随机抽取一个幸运ai与你击掌了。
如图,在AI国家触发的事件xskiper_teach.2020中,选取了一个随机非ai国家(就是随机玩家啦)触发了一个新的事件,这个事件是这样的:

这个事件只有三行是我们需要关注的:

FROM在这里首次出现了。
阅读我们的事件要求,我们可以发现,在玩家国家触发事件时,我们需要逆推到上个事件,找到上个事件的ROOT,从而可以对正确的目标进行灭国。
PREV只在一个事件内部的scope转换有效,这个时候我们就需要FROM。

在事件A触发事件B的情况下,事件B中的FROM代表着事件A的ROOT。
同样,FROM可以连续叠加,例如事件C中的FROMFROM在事件A触发事件B,事件B触发事件C的情况下,代表着事件A的ROOT。
这是FROM出现的第一种情况,也是最简单的情况,但正如这个范例,这种FROM往往只在触发国家切换转变时不可替代,也就是国家关系,外交相关的那些事件。其他时候,萌新可以采用之后会介绍的event_target来避免其使用。
或许有萌新会在听了之前的介绍依旧对FROM有点理解困难,不妨在游戏内触发事件时输入debugtooltip的控制台指令,鼠标放到事件的选项后,你可以看到事件的FROM和ROOT。可以辅助你的事件写作。

相信这样的技巧一定能够帮助到各位modder。
FROM的功能绝不仅限于此,它对于刚进行event制作的modder的更大意义可能在于on_action提供的可调用scope上,关于这一点,我在下一篇会进行更详尽的介绍。
四、流程scope跳跃语句:小结
流程scope跳跃语句和遍历型有着很大的不同,它们指代的是一个独立的目标而非一个模糊的群体。因此它们能够成为那些,能够在内部指定额外目标的effect或condition语句中填写的目标。同时,他们引用着我们之前所使用过的目标,一方面便于我们简化流程,另一方面为我们进行一些更复杂的事件设计做好了铺垫。
他们可以作为独立的scope跳跃语句,也能够作为目标填写。其中的PREV和FROM可以通过堆叠进行跨越多层的引用。熟练运用ROOT和PREV是之后各位事件写作的基本要求,所以请多练多学。
4 从前有艘船,船有所有者......——属性scope跳跃语句
从前有艘船,船有所有者,所有者有首都,首都有星系,星系有星基......这样的套娃可以无穷无尽,接下来我们接触的一种scope跳跃语句,也是我们今天学习的最后一种scope跳跃语句,也就是属性scope跳跃语句。
属性scope跳跃语句,调用的是与THIS相关的其他scope,例如语句owner,调用的就是THIS的所有者,一个国家。同样的还有语句ruler,调用的就是THIS国家scope的统治者。这样的语句和遍历型不同,和流程scope跳跃语句同样只指代一个具体的目标。这些语句调用的是THIS的属性,取得一个和THIS通过某些方式对应的scope。
其中有一些属性scope跳跃语句是直接以scope类型来命名,不像owner之类浅显易懂,比如这些:

在使用这种语句时一定要注意,只有当THIS对应的目标只有一个时,这样的语句才会正常运作。比方说,对于一个国家,它有无数个pop,THIS为国家时,使用pop来跳跃scope显然是错误的;而对于一个pop,他只在一个星球上,THIS种类为pop时使用planet来跳跃scope就是正确的。
同时这些scope语句也能作为目标填写到那些需要填写目标的effect或condition语句中。这一点和流程scope跳跃语句是类似的。
5 小结
终于写完了......要吐了。
我们在本章学习了三种不同的常用scope跳跃语句:遍历型scope跳跃语句、流程scope跳跃语句、属性scope跳跃语句。运用这些语句我们可以轻松地找到各种各样的目标。下面的练习题也将会侧重于寻找目标的方法,希望各位对scope转换能够在参考习题后有了更深的理解。
再列举一次吧……
遍历型scope跳跃语句:搜索模糊的群体,批量处理大量符合条件的目标。
流程scope跳跃语句:指代独立的个体,寻找在之前的事件代码中已经找到的目标。
属性scope跳跃语句:指代独立的个体,来源为当前scope,也就是THIS的属性。
接下来让我们回到上一章,也就是范例1-2,现在我们已经有了自动迁移人口的能力了。让我们再解析一下目前需要的目标。

范例2-3 烂大街的自动人口迁移(其二)
找到一个ROOT(触发国家)拥有的,有空闲岗位的星球。
找到一个ROOT拥有的,没有工作的pop。
将目标pop迁移到目标星球上。
在熟悉掌握了流程scope跳跃语句后,我们可以这样填写:

当然这还不是人口自动迁移的全部,但作为一个基础,它已经有了足够的功能......我们在(其三)见。
习题二
(1)请找到有这些功能的遍历型scope跳跃语句,并尝试指出其适用的THIS scope,以及指向的新scope的类型。
① 每一艘拥有的舰船;
② 银河中的每一个物种;
③ 每一个接壤国家;
④ 恒星系内的全部星球;
⑤ 轨道内的全部舰队。
(2)请指出下面几段代码中,注释处THIS的scope类型。


(3)试着找到下面语句所描述的目标
① ROOT为国家,试着找到首都与ROOT首都星系距离小于20跳的国家。(首都capital_scope,查询星系与星球/星系距离distance);
② ROOT为星球,试着找到该星球星系内的军事舰队的上将(星系solar_system,星系内舰队every_fleet_in_system,上将可以直接靠leader取到);
③ ROOT为舰队,试着找到舰队所有者的全部爬行类人口(判断种族小类:is_species_class)。
(4)试着写出满足下面要求的effect语句
① 令所有国家对与其关系低于-50的国家宣战(判断国家关系使用opinion);
② 使我境内的所有舰船归我所有(设定所有权使用set_owner);