疊來套去的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的事件現在讓我們從最基礎的遍歷型開始,深入瞭解一下這些scope語句。
2 遍歷,還是遍歷——遍歷型scope跳躍語句
我在上一章就提到了這一類遍歷型scope跳躍語句,現在讓我們就下面的例項來對它們進行探究。

範例2-1 逃不過的清算
某個圓神信徒在完成了“清算後成功成為河長”的成就後,也興致沖沖地加入了mod製作的行列。第一步就是要加大它生存的難度,搞出各式各樣的圓神清算......
圖二 範例2-1整體程式碼
這一片程式碼看起來好像比較複雜,但其實就是一串並列的效果組合在一起罷了。讓我們把程式碼拆成一個個小的部分,看一看基礎的一些scope跳躍語句是如何使用的。
首先是遍歷型。這一類scope跳躍語句以any、every、random開頭,通常是對符合某一標準的群體,或是對這一群體中的隨機一個進行處理。
圖三 遍歷型scope跳躍語句的例子在effect區域內,這些遍歷型scope跳躍語句以every或random打頭,分別代表所有和隨機一個。在condition區域內,這種語句則以any開頭,代表任何一個的含義。
這些遍歷型scope跳躍語句可以滿足我們寫mod中查詢目標的絕大部分需求,下面由範例2-1,舉一些使用該語句進行事件製作的例子。

範例2-1-1 領袖老化
我們的圓神信徒首先決定從新國家的領袖下手,讓被清算國家在之後的每個月,領袖的年齡保持在150歲......雖然沒考慮壽命迴圈的要素,這樣一個高齡也極大增加了領袖存活的難度。
先我們需要用蠢驢語言翻譯這個需求,也就是每個月,該國家的全部領袖,設定年齡為150。目前不考慮觸發時間的每個月,我們需要先找到這些領袖,在觸發事件國家為ROOT的前提下。
圖四 廢話一堆,這程式碼會英文的都知道啥意思這個遍歷型scope跳躍語句將scope由ROOT對應的觸發國家,轉換到了ROOT所擁有的全部領袖。每個擁有的領袖都會觸發大括號內的effect,也就是:
圖五 設定年齡,神奇的是群星到現在都沒有增加年齡的effect這樣你就能夠令ROOT的所有領袖一夜白頭,前提是這些領袖的種族有頭髮。

範例2-1-2 拆遷大隊
接下來這位仁兄想懲罰一下那些在銀河系中到處建造違章建築的傢伙,讓那些被戴森球遮擋的恆星重見天日...他計劃隨機挑選一個觸發國家的巨構,將其摧毀。
我們的新任務和範例2-1-1有什麼區別呢?沒錯,數量。
我們不再需要選擇全部目標,而是要隨機挑選一個,這種時候every顯然就破壞力太大了,輪到我們的random登場:
圖六 THIS國家擁有的隨機一個巨構然後很簡單的effect系列:
圖七 更加正確的說法是“刪除”,畢竟這樣的程式碼不會留下廢墟
這兩個最簡單的effect帶領我們對every和random有了一些瞭解,接下來,讓我們看看any和limit的運用。

範例2-1-3 眾叛親離
為了更進一步地迫害玩家,這個modder決定從國家間的關係下手,全部的國家......不,應該把條件設的複雜一點,所有擁有擁有“靈能”特質人口的國家,對觸發國家的信任減少,怎樣?
好,現在難度上升了,我們剖析一下需求。
找到目標A:所有國家,條件為[擁有 擁有靈能特質的人口]
執行effect,effect的目標有兩個,一個為觸發國家,一個是目標A。
讓我們一個個實現,首先是找到目標:既然目標還是所有國家,先寫一個這個:
圖八 找到 他們但我們現在要找的並不是毫無限制,我們需要符合條件的國家,這時候我們就需要使用limit了:
圖八 limit,遍歷型的過濾器通過加入limit,並在limit內部填入condition,我們可以過濾我們需要選擇的目標,只讓符合條件的國家執行接下來的effect。
接下來就是確認要符合的條件:[擁有 擁有靈能特質的人口]。我們要判斷這個國家擁有人口的情況,就需要在condition中進行scope跳躍。我們可以這樣:
圖九 在condition中遍歷人口在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,而需要你進行額外的指定。例如下面這個:
圖十二 摧毀艦隊,但你不能填yes不知道蠢驢為何這樣規定,你需要在等號後面確認目標:
圖十三 摧毀艦隊,不用我說此時的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這個東西,可不只是讓我們嘴上說說的。
通過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。
圖十七 使用limit的while
在群星的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,不匹配就會報錯,還請多多注意。
讓我們回到範例,完成這一段程式碼:
圖二十一 增加兩個effectadd_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。可以輔助你的事件寫作。
圖二十六 可以看到事件的FROM,FROMFROM相信這樣的技巧一定能夠幫助到各位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之類淺顯易懂,比如這些:
圖二十七 這些語句的名字就是scope種類
在使用這種語句時一定要注意,只有當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);









