认识你与event的极限【Stellaris Events Modding 进阶教程#1】

事件究竟能做什么?
大家好,还是我XSkiper,嘛这边应该是叫画饼大师什么的()
总之继续搬教程~
事件,events文件夹里那些成堆的txt文件,究竟能改变这个游戏到何种程度?
这个问题,我曾经以为不会被接触事件不久的萌新遇到,但我发现,如果避开这个问题......就会使很多modder在事件写作中产生了错误的判断,做很多无用功,或者在群里引来高血压。
所以在进一步接触事件写作之前,让我们还是摆正心态,了解一下蠢驴游戏的半壁江山(以上)——events的基本结构,以及它能够为我们带来的东西
1 我是谁,我在哪?——scope
scope,也就是范围,相信在无言的教程中大家已经有了一定的接触。但对scope这个概念,恐怕各位还是没有一个比较直观的印象。
scope的概念是蠢驴语言的一个核心,如果对其没有一个清晰的印象,modder是无法写出较为复杂的事件的。
那么,如何理解scope在蠢驴事件中的作用呢?
大家日常的对话通常包含两个重要部分:主语和谓语。例如萌新A写码,就分为萌新A这个主语,以及写码这个谓语。蠢驴的绝大多数效果语句也是相同的逻辑。
类比萌新A写码这句话,我们不妨看一下下面这个事件。

范例1-1 天灾?AWSL!
由隔壁舰R mod中某联合舰队掀起的天灾热,以相当快的速度席卷了整个modder圈,很多小mod制作者想要制作这样一个天灾以扩大订阅量。
然后我们的主人公——某个萌新modder也想要写一个强力无比的天灾,他想到:“只要把玩家灭国,天灾就无敌了!”最后写下了这个“天灾”。

很简单的一个事件:国家A触发该事件,国家A选择选项,国家A暴毙。wdnmd毫无游戏体验


相信经历了其他教程(大概这个号还没发),大家都明白了trigger、immediate、option的作用......不过我还是在这里稍微复习一下:
id:事件的名字,调用时就靠它,命名规则就不说了。
title&desc:事件的标题&描述,通常是本地化key,游戏内显示对应语言对应key的本地化文本。
is_triggered_only:仅由触发器触发,若这个属性为yes,除非有其他的东西(例如另一个事件,或者控制台语句)去触发它,你是不会在游戏中看见它的。
trigger:触发条件,如果trigger内的条件不满足,这个事件怎么都不会触发的(控制台除外),内部填充condition。
immediate:即时效果,在事件触发瞬间就生效的效果,内部填充effect。
option:事件选项,name属性为选项的名字(本地化key),除了部分属性语句,内部填充的也是effect,与immediate的不同在于其在选项被选择后才生效。
在这里先介绍这些,毕竟只是复习(笑),而且这些属性定义永远是实操比看教程更容易懂。

类比萌新A写码,我们可以说这个事件执行的主要效果就是国家A暴毙。事件代码中的②destroy_country = yes是一个effect(接下来会提到),代表着暴毙这个动作。那么,如何确定暴毙的国家是国家A,为什么这个事件里暴毙的不是国家B,国家C?
答案藏在①country_event中。这代表这个事件是一个国家事件,这个事件的根scope(ROOT)就是看到这个事件弹窗的,触发这个事件的国家A。在没有经历任何scope跳跃时,所有该事件的判断和效果都关于国家A本身。
②destroy_country = yes这一语句没有经历scope跳跃,直接在根scope(ROOT)执行,因此暴毙的就是国家A了。


由此说来,scope可以比喻成一种主语,它代表着所有效果的目标,有了scope,准确地在2200上天的才会是你(而不是住你隔壁的蟑螂兄弟),圆神才能准确地清算你的每家每户(而不是误杀了对面的海星)。
当然这显然不是scope的全部,我还并没有说明如何进行scope跳跃,以及很多关于scope的,别的操作。但作为一个概念理解,绝大部分scope的相关内容都是由我上边的话延伸。我们可以先暂停在此处,更详细的问题留到下一章进行说明。
2 老三样——effect、condition以及modifier
说到蠢驴的代码,最多的就是这三个东西,在自定义程度较高的代码(不止事件!),几乎都能找到这老三样。
下面我借用范例1-1,简要介绍一下老三样的前两个:effect与condition的作用。

回到这个“天灾”事件,大家可以看到我做的后两个标记:
②days_passed > 15
这是一个标准的condition语句,判断的是【距离2200年1月1日的日期】,只有当经过日期大于15天时,才会返回true。
③destroy_country = yes
这是一个标准的effect语句,执行的效果为【摧毁当前scope的国家】(scope会在之后详细介绍,在该事件中被摧毁的就是事件触发者)

很基础的两条语句,相信大家都能看英文字面意思理解它们(真不能的话你可以机翻)。在这里我可以给出一个effect与condition的定义,希望各位不要将两者混淆。

effect
执行的效果,例如【摧毁国家】、【增加资源】、【消灭人口】等,它们都是实际执行的效果,改变了这一局游戏的数据。
例如国家A暴毙,如果说国家A是当前的scope,那么暴毙就是一个effect。destroy_country = yes在这里相当于表动作的谓语。

condition
判断的条件,例如【检查日期】、【检测人口数量】、【判断国家思潮】等,它们是判断的条件,本身不会改变这局游戏的内容。
例如国家A有唯物主义,你可以通过代码is_materialist = yes进行表达,这句话不会将A变成唯物主义,只是进行判断罢了。主语是国家A,is_materialist = yes是有唯物主义的condition,可以说是表状态的谓语。

effect与condition都有各自独立的一套代码,该填effect的地方不要塞condition,反之亦然。倘若你填错了,VSC往往会警告你(只要你正确使用了插件):

那么如何判断这里应该填effect还是condition,或者你手上这条代码是哪种呢?
这里提供四种方法:

1、查表:你可以在https://stellaris.paradoxwikis.com/Effects#Scripted_Effects查询到绝大多数的effect代码,在https://stellaris.paradoxwikis.com/Conditions查询到condition们。只要和已有代码比对,你就能轻松发现这是哪一种。
这是最蠢的办法,正经人谁翻效果表啊()

2、依靠VSC的自动补全和报错功能:看到图4了吗?插件都告诉你不能在这填了,你就别填这类代码了。

一般都用这种法子,自动补全配合一定的英文基础,大部分的代码都能毫不费力的被你找到。

3、找到两者常用的英语词汇:正如我之前的比喻,蠢驴的effect、condition格式都可以总结为主语和谓语,只是存在表动作和表状态的差异。
那我们不妨像做英语语法题时一样,根据代码的字面含义判断其种类,就像这样:
<1>表动作的单词:destroy、set、remove、spawn、add等等——>effect
<2>表状态的单词:has、is、can等等——>condition
<3>特殊情况:check、count这类表示检查的动作词语,是condition。
只要看懂了这些词的大致含义,就能了解这是一个动作还是状态,进而就能分清代码属于effect还是condition。

4、找到两者的常见代码:有些代码的使用频率是远远高于其他的,当你看到一坨代码不知道咋改时,可以通过寻找它们来快速进行判断。
<1>很常见的逻辑判断语句
AND、OR、NOT、NOR、NAND,这些逻辑门的含义相信大家比较熟悉(不熟悉就去百度),它们都是condition、里边包着的是,和它们并列的也是。
<2>很常见的流程控制语句
if、else、else_if、while、switch......这些流程控制语句也在蠢驴语中占有一席之地。它们都是effect、与它们并列的都是,里面包含的(除了limit)也都是。
在if、else_if、while以及很多遍历型scope跳跃语句中,你都可以在内部看到limit语句,这里面需要填condition(limit本身不能单独存在!),具体含义分情况,例如if中的limit代表if判定的条件,遍历型scope跳跃语句中的limit代表目标必须符合的条件(之后还会提到)。
<3>很常见的遍历型scope跳跃语句
遍历型scope跳跃语句,在之后我会进行进一步说明,但你依然可以靠它们的前缀来辨认种类。
前缀为every、random的,例如every_owned_planet、random_owned_pop等代码,都是effect中的遍历型scope跳跃语句。
前缀为any的,例如any_owned_planet、any_owned_pop等代码,都是condition中的遍历型scope跳跃语句。
any不能出现在effect中,反之亦然,不要想当然换着填,没有用的。

你可以发现我并没有在前面提到modifier们,尽管它出现在标题中。因为你是无法直接在事件代码中使用modifier代码的。它们相当于附加在各种东西上的buff与debuff,要想通过事件添加,必须自行创建一组新的静态修正(之后可能提到)。
在这一小节的最后,我再次强调一遍:effect与condition都有各自独立的一套代码,该填effect的地方不要塞condition,该填condition的地方不要塞effect!
3 如何讲蠢驴语——事件代码构思思路
在接触了scope、effect与condition之后,让我们再回首看一下我在第一节做出的比喻。
scope相当于主语,表明要做这个动作的人;effect与condition相当于谓语,表明这个人要做什么动作,或者要满足怎样的条件。将这两部分混合起来,就是一句正常的蠢驴语。
因此我们写作事件时,也要按照蠢驴语的表述方式进行构思。
让我们看看下面这个范例,想一想我们应该怎样构思事件。

范例1-2 烂大街的自动人口迁移(其一)
尽管创意工坊的自动人口迁移mod已经相当多且非常完善了,仍然有很多萌新将其作为事件写作的第一个课题。
......但这个系统,也并没有他们想象的那么简单。
好,现在让我们想想人口迁移怎么写。
作为第一章的一个范例,我们只要求把失业的人口处理掉。(你也看到了,这是其一,迁移的问题咱们目前可解决不了)
让我们先把要求转化为蠢驴事件的表达方式,我们可以得到:
①人口失业;
②人口被鲨;
我们可以认定这两个表述的scope都是pop(人口),其中①包含condition“失业”,②包含effect“消灭人口”。
那么我们写这个事件,就需要首先找到目标人口scope,再对其进行处理。
那么让我们先完成第一步:

我们利用遍历型scope跳转语句,把当前scope转换为触发国家拥有的全部人口,并使用if语句进行判断:如果那个人口失业的话,我们就可以动手了。
当然,我们可以先简化一下代码:

这里利用含limit的遍历型scope跳转语句来获取失业中的人口,可以让代码量短一些(这些处理之后还会详细解释,总之我们找到了帝国的所有失业人口。)
然后是进行处理,我们现在能够鲨掉这个人口:

kill_pop = yes能够直接把当前scope代表的人口消灭,而写下这个effect后,我们第一次的“自动迁移事件”事件写作就结束了。
这个事件能够人道毁灭你的所有失业人口,起码能够让你的岗位界面清爽一些。(说好的迁移呢)

从这个事件的写作历程可以看出,我们的构思与码字是按照这样的逻辑进行:
把你的需求,翻译成一条条蠢驴语表述的句子。
找到每个句子对应的主语,尝试使用事件寻找scope。
找到scope后,对其进行处理。
这是目前我们由上面已经学习过的内容总结出来的事件写作经验。当你想到了一个想用事件完成的新点子时,就要按照这样的思路把你的需求梳理一下。
实际上,我们在写作事件时,可以把它拆成三种需求:
何时何地,触发该事件?
该事件的目标,包含哪些?
我们要对这些目标进行怎样的处置?
在上面的构思逻辑,我们回答了后两个问题,至于第一个,就涉及到on_action等触发器的介绍,我们之后再展开讲。
4 你与事件的极限——mod做不到的事
我们已经讲完了写作一个事件需要的思路,代码分类以及概念,现在就可以开工实现那些只改common无法实现的梦想了。
不过在此之前,我还是需要在最后谈一谈,如何做出一个合理的构思,最起码,可行的构思。
一、事件能不能实现这个?
这是你的构思能否实现的最大物理限制,如果蠢驴提供给你的代码无法实现你想要实现的功能,或者你希望调整的东西是硬代码......
尽快放弃!不要被这种陷阱困住了!
尤其是对于萌新,倘若跟一个无法实现的功能死磕,写了一堆无用无效的代码,不论对接受提问的群友,还是对萌新自己都有害无益。如果萌新自己依旧一意孤行的话,也只能是自己受到最大的伤害。
如何判断这个东西能否完成,你可以通过我对需求的分类,进行思考:
何时何地,触发该事件?——>有没有这样的触发机制?
该事件的目标,包含哪些?——>有没有办法找到目标?
我们要对这些目标进行怎样的处置?——>是否存在这样的effect?
没有effect,你就有极大概率(在你不理解游戏机制时,就是1000%)写不出这个事件,其他的判断要素也是这个道理。
二、我能不能做到这个?
第二个要素,就是modder自身的能力。
在不熟悉机制的情况下,尽量不要尝试去处理过于复杂的事件。
这句话是对那些抄代码的同学说的,当你需求的功能很巧的被包含在某个原版,或者其他mod的事件时,抄是一个很好的主意。
但,很多时候,有些东西不是你单纯抄了改key就能解决的。其中涉及到的封装效果,变量处理,特殊属性很可能大大超出了你的处理能力,进而就会出现你一更改改“炸”了,或者他行你不行的情况。
抄代码的技巧我之后可能会再讲,我还是希望各位能够在抄一段代码时知道自己在抄什么的,毕竟,这大概就是这篇教程的存在价值之一。
5 小结
好啦,负能量发言结束了()
这一篇教程中,我们了解了事件的几个基本要素,以及基本的写作思路。下面我们会像正经教科书一样进行一些总结。
事件效果描述可以按照主语+谓语的格式,scope是主语,effect和condition是谓语。
effect是执行的效果,condition是判断的条件,两者分别有不同的代码,不能搞混。
事件写作思路:触发时机地点——>找到目标——>处理目标。
记住这些内容,就可以开始写一些更复杂的事件了。
下一章,让我们更详细地探讨一下scope,回答一下如何找到目标的问题(以及更加靠谱的人口迁移!)。

习题一
(1)指出以下事件表达句式中scope对应的部分。
① 领袖年龄大于45
② 舰船属于军事舰船
③ 国家有唯物主义
④ 国家不是格式塔
⑤ 人口公民权利为居留权
⑥ 星球大小超过20
⑦ 星系中存在多个宜居星球
⑧ 种族拥有灵能特质
⑨ 舰队存在领袖
⑩ 星球添加建筑:水培农场
(2)下面的代码中,哪些是effect,哪些是condition?
① set_country_flag
② establish_communications
③ create_country
④ has_ethic
⑤ if
⑥ is_moon
⑦ exist
⑧ add_building
⑨ OR
⑩ remove_ship
(3)试着把下面的需求转换为事件表达句式。
① 消灭战力为1的舰队
② 为年龄大于85的领袖添加尊者特质
③ 所有有种族洁癖国策的国家获得5000合金
④ 所有拥有的殖民地获得5个人口,倘若此举使得殖民地人口超过20,摧毁那个殖民地。
⑤ 目标国家失去700能量,500食物。
⑥ 有灵能特质的领袖所有权将被转移到当前国家的宗主国。
(4)试着找到这些效果对应的代码。
① 获得资源
② 检查资源
③ 检查资源月收入
④ 添加modifier(修正)
⑤ 获得随机科研选项
⑥ 播放声音
⑦ 增加信任度
⑧ 检查人口数目
这些习题是放着玩的,启发一下思考即可,没人会去批,大概()