Vic3 mod 制作教程 第五章 事件、決議、JE(上)(文本版)
終于度過了完全沒有基礎(chǔ)的階段,現(xiàn)在來看我視頻的觀眾多少都會(huì)有點(diǎn)基礎(chǔ)。因此,終于可以正式做起mod了
因此,本章就從創(chuàng)建mod做起,建立起一系列的作弊事件,作弊決議和作弊JE
本章分為上下兩章絕對不是因?yàn)闀r(shí)長問題,而是因?yàn)橹虚g需要講解本地化和界面代碼,才能正常地進(jìn)行下半章的解說,這一章就只需要建立最簡單的事件,決議和JE
## 第一節(jié)??? 新建空白的mod和打開游戲之前的準(zhǔn)備
首先,打開啟動(dòng)器,點(diǎn)擊所有已安裝mod,然后點(diǎn)擊新建mod,鍵入名稱,本教程就以“modding_tutorial”作為名稱。然后在播放集里關(guān)閉其他的mod,點(diǎn)擊modding_tutorial,然后點(diǎn)擊設(shè)置,“以調(diào)試模式啟動(dòng)該游戲”。這里關(guān)閉其他mod的原因是為了排除其他mod對游戲修改的影響,如果你確信你啟動(dòng)的其他mod不會(huì)影響你的mod內(nèi)容,你可以打開。
當(dāng)然,如果就這么打開這游戲,游戲不會(huì)有任何變化,因?yàn)閙od文件什么東西都沒有。因此需要打開`文檔/Paradox Interactive/Victoria 3/mod/modding_tutorial/`,然后就會(huì)發(fā)現(xiàn),除了`.metadata`文件夾,什么也沒有。因此需要添加文件。
mod的文件夾是是原版文件夾`game`的映射,游戲文件里的`jomini`,`binaries`是不能修改的,可以看到,`game`文件夾里面有很多的文件,點(diǎn)開`common`里面更加多,所以需要有的放矢,只添加自己需要的。本章節(jié)中,因?yàn)橹皇呛唵蔚刂谱魇录?、決議、JE,所以相對的,要找到它們對應(yīng)的文件夾。事件的文件夾就是單獨(dú)一個(gè)`events`,決議的文件夾在`common/decisions`,JE的文件夾在`common/journal_entries`,此外為了維護(hù)代碼,還需要添加scripted_effect和scripted_trigger,以及script_value。
如果要修改游戲原來就有的事件、決議、JE,把原版對應(yīng)的事件、決議、JE拖到mod里然后修改就好,但是這里是新建作弊事件,作弊決議和作弊JE,因此需要?jiǎng)?chuàng)建一些`utf-8 with bom`格式的txt文件,在vscode打開然后創(chuàng)建是相對簡單的方法。
調(diào)試模式打開可以進(jìn)行熱更新,可以邊改邊看。但是其具有一定前提,其中一個(gè)前提是,不能新增或者刪減mod文件,但是可以在已經(jīng)有的mod的文件里添加,所以無論如何,建議先新建一個(gè)空白的文件。
新建完空白文檔之后正式進(jìn)入游戲。
## 第二節(jié) 新建一個(gè)事件
最為基本的event如下
event的觸發(fā)機(jī)制經(jīng)過了新版引擎的優(yōu)化,不再擁有MTTH(Mean time to happen)機(jī)制,不熟悉MTTH機(jī)制的也不需要特意去了解,因?yàn)檫@是已經(jīng)被廢棄的功能。event的觸發(fā)機(jī)制通過一種鉤子上下文觸發(fā),這種上下文具有多種參數(shù),基本位于on_action里,有時(shí)候也會(huì)在一些游戲概念里出現(xiàn),一般都會(huì)被P社特意標(biāo)明:“這是一個(gè)on_action鉤子”。這些鉤子上下文一般擁有三種參數(shù),events,effects,random_events,如果不考慮隨機(jī)觸發(fā),希望立即觸發(fā),那么直接在events上下文中填入用空格或者換行隔開的事件名稱即可,如果考慮到隨機(jī)觸發(fā),則需要在事件名稱前添加等于號(hào),等于號(hào)左邊的參數(shù)填寫權(quán)重,如果希望一定權(quán)重不發(fā)生任何事件,等于號(hào)右邊就應(yīng)當(dāng)是0;關(guān)于鉤子上下文,也就是on_action的更詳細(xì)的說明會(huì)在第八章說明。用鉤子觸發(fā)的event會(huì)受到event中名為trigger的trigger上下文影響,而用控制臺(tái)或者effect觸發(fā)的event則不會(huì)。
總之可以在mod文件夾的event文件夾里的文件填入我列舉的事件的文本代碼,保存之后通過控制臺(tái)event example_event.1來調(diào)用,也可以順便在控制臺(tái)看到控制臺(tái)打印出的trigger
```perl
namespace = example_event # 不定義namespace則根本無法調(diào)用event
example_event.1 = {
??? ###########參數(shù)區(qū)###########
??? type = country_event # 定義了ROOT是什么Scope類型
??? placement = root
??? hidden = no # 定義了是否是隱藏事件,如果是隱藏事件最好只有immediate
??? title = "example title"
??? desc = "example desc"
??? flavor = "example favor"
??? icon = "gfx/interface/icons/event_icons/event_skull.dds"
??? event_image = {
??????? # 可以是bk2視頻,也可以是dds圖片
??????? # video should be a bk2 (Bink Video) file
??????? video = "gfx/event_pictures/middleeast_courtroom_upheaval.bk2"
??????? # dds圖片的情況如下
??????? # texture = "gfx/texture.dds"
??? }
??? on_created_soundeffect = "event:/SFX/UI/Alerts/event_appear"
??? on_opened_soundeffect = "event:/SFX/Events/middleeast/courtroom_upheaval"
??? #######行動(dòng)區(qū)########
??? immediate = { # Effect上下文,
??????? set_variable = { name = get_var value = 1 }
??????? # 設(shè)置不存在的變量的操作建議在immediate里
??? }
?? ?
??? trigger = { # 是否會(huì)觸發(fā)的trigger上下文,但是在trigger_event這個(gè)effect調(diào)用的時(shí)候會(huì)被忽略掉
??????? c:FRA ?= this
??? }
??? ########選項(xiàng)區(qū)#########
??? duration = 3
??? option = {
??????? name = "Option 1"
??????? change_infamy = -150
??????? trigger = { # 定義這個(gè)option是否顯示的trigger上下文,如果trigger為假則不會(huì)顯示
??????????? var:get_var > 1
??????? }
??? }
?? ?
??? option = {
??????? name = "Option 2"
??????? highlighted_option = yes? # 是否高亮
??????? default_option = yes # 是否默認(rèn)選項(xiàng)
??????? add_era_researched = era_1
??????? add_era_researched = era_2
??????? add_era_researched = era_3
??????? add_era_researched = era_4
??????? add_era_researched = era_5
??? }
??? option = {
??????? name = "Option 3"
??????? highlighted_option = yes? # 是否高亮
??????? if = {
??????????? limit = { exists = currently_enacting_law.type }
??????????? activate_law = currently_enacting_law.type
??????? }
??????? set_variable = { name = get_var value = 2 }
??? }
}
```
因?yàn)闀簳r(shí)還不說到本地化,所以需要使用本地化的部分姑且用雙引號(hào)括起來的文本。
事件里有很多參數(shù),可以分為參數(shù)區(qū),行動(dòng)區(qū),選項(xiàng)區(qū)。
參數(shù)區(qū)定義了一系列的參數(shù),有type,placement,hidden這些參數(shù)。type參數(shù)定義了ROOT是什么Scope類型,可以是country_event, character_event, 或者state_event,placement定義了發(fā)生的位置,而hidden則定義其是否可見,如果不可見,不建議使用選項(xiàng)區(qū)的內(nèi)容。
另外的風(fēng)味化參數(shù)是title,desc,flavor,icon,event_image,on_created_soundeffect,on_opened_soundeffect,這些參數(shù)是可有可無的參數(shù),但是添加進(jìn)去會(huì)讓事件有標(biāo)題和描述和事件圖片之類的。最需要說的就是event_image,它提供了兩個(gè)參數(shù),video和texture,一般來說是二者選擇其一,video是bk2格式的視頻,而texture是dds格式的圖片,這些都是下半部分再說的東西,因此姑且先使用原版的。
還有一些風(fēng)味化參數(shù)需要留到下半部分才方便描述。
然后是行動(dòng)區(qū),行動(dòng)區(qū)有immediate和trigger兩個(gè)參數(shù)。immediate是事件發(fā)生之后立即發(fā)生的事件,適合預(yù)先定義一些變量和scope。trigger則是在被on_action里的random_events和events這兩個(gè)參數(shù)中調(diào)用時(shí)候是否執(zhí)行的trigger上下文,在trigger_event里就會(huì)忽略掉。
最后是選項(xiàng)區(qū),選項(xiàng)區(qū)包括duration和多個(gè)option。duration表示事件的存續(xù)時(shí)間,如果過了這段時(shí)間就會(huì)自動(dòng)執(zhí)行默認(rèn)的選項(xiàng)。而option則有很多參數(shù),有name,highlighted_option,default_option,trigger這幾個(gè)參數(shù),name是風(fēng)味化參數(shù),定義了選項(xiàng)的名稱。highlighted_option就是指這是否可以高亮,如果是yes就高亮。default_option就是指這是否是默認(rèn)選項(xiàng),如果為yes就是默認(rèn)選項(xiàng),duration過去了之后就會(huì)選擇這一個(gè),因此最好是只指定一個(gè)option是default_option。trigger則是定義這個(gè)option是否顯示的trigger上下文,如果trigger為假則不會(huì)顯示。
介紹完之后就新建一個(gè)event,在immediate里設(shè)置一個(gè)get_var的variable,其值為1,整出三個(gè)option。第一個(gè)option設(shè)置了trigger,只有g(shù)et_var大于1的時(shí)候顯示,可以減少150惡名;第二個(gè)option設(shè)置為默認(rèn)且高亮,可以研究所有科技,查閱文檔即可得;第三個(gè)option設(shè)置get_var為2,然后如果正在通過一個(gè)法律則立即通過之。而這,就是一個(gè)基本的作弊事件了。
第二個(gè)選項(xiàng)的提示文本并不好看,所以可以把option2改成這樣
```perl
option = {
??????? name = "Option 2"
??????? highlighted_option = yes? # 是否高亮
??????? default_option = yes # 是否默認(rèn)選項(xiàng)
??????? custom_tooltip = { # 這是一個(gè)effect
??????????? text = "研究所有科技"
??????????? subject = root # 執(zhí)行對象
??????????? add_era_researched = era_1
??????????? add_era_researched = era_2
??????????? add_era_researched = era_3
??????????? add_era_researched = era_4
??????????? add_era_researched = era_5
??????? }
?????? ?
??? }
```
作弊事件雖然可以通過研究所有科技和通過正在研究的法律,但是對于如何正在研究一個(gè)法律則立即研究之,則會(huì)更加的難,因?yàn)閠echnology不像law和law_type那樣完善,不客氣的說,technology是殘廢的scope。因此達(dá)到這樣的效果則會(huì)更加的艱難。
判斷一個(gè)國家是否在研究某個(gè)科技和立即研究某個(gè)科技都是有的,但是并沒有一個(gè)確定的對象顯示某一個(gè)國家在研究什么科技,所以只能通過非常啰嗦的if,else,else_if的effect來執(zhí)行,這會(huì)顯得事件非常冗長,所以,更正確的做法是,使用scripted_effect定義一個(gè)非常長的effect,然后再在事件里調(diào)用。
對于科技,大概是這樣的
```perl
if = { limit = { is_researching_technology = sericulture? }
??? add_technology_researched = sericulture
}
if = { limit = { is_researching_technology = enclosure? }
??? add_technology_researched = enclosure
}
```
要做出scripted_effect,只需要這樣
```perl
finish_researching_techs = {
??? if = { limit = { is_researching_technology = sericulture? }
??????? add_technology_researched = sericulture
??? } else_if = { limit = { is_researching_technology = enclosure? }
??????? add_technology_researched = enclosure
??? }
}
```
在事件里調(diào)用就只需要這樣寫
```perl
finish_researching_techs = yes
```
這里直接給出遍歷了所有科技之后的scripted_effect,創(chuàng)建新的選項(xiàng)
```perl
option = {
??? name = "Option 4"
??? finish_researching_techs = yes
}
```
此外,scripted_effect還具有含參數(shù)的用法
```perl
functional_scripted_effect = {
??? $PARAMETER$ = {
??????? add_treasury = 10000000
??? }
}
```
調(diào)用的時(shí)候換成這樣
```perl
functional_scripted_effect = { PARAMETER = c:GBR }
```
scripted_effect的參數(shù)是字面量,所以不需要一定要寫成scope,但是使用scope可讀性更強(qiáng)
```perl
functional_scripted_effect_2 = {
??? c:$PARAMETER$ = {
??????? add_treasury = 10000000
??? }
}
functional_scripted_effect_2 = { PARAMETER = GBR }
```
## 第三節(jié) 決議的編輯
相比事件來說,決議的編輯要更為簡單,當(dāng)然,這并不代表這是一件好事,因?yàn)楦唵瓮馕吨俚墓δ堋6盟啦凰?,Vic3的決議是歷代P社游戲里面表現(xiàn)力最差的:沒有任何配圖,樸素到了只有一個(gè)標(biāo)題一個(gè)介紹一個(gè)不知所謂的綠色圓按鈕(在1.3版本里則是不知所謂的藍(lán)色圓按鈕),而且被雪藏到了一個(gè)不好找的地方(日志 -> 決議)??梢哉f除了這玩意叫決議之外和鋼4的決議簡直是地花板和天花板的區(qū)別。
相比之下,鋼4的決議就更為難入門,但是鋼4的一個(gè)決議組就可以完成Vic3里事件+決議+JE的效果,非常超模。但是總之還是來看看Vic3的決議如何構(gòu)成的。
VIc3的決議和JE都有官方文檔,因此就官方文檔進(jìn)而介紹。一個(gè)決議有四個(gè)必要的元素:名稱,is_shown的trigger上下文,possible的trigger上下文,when_taken的effect。如果可以,還可以加入 ai_chance,這是一個(gè)value
顧名思義,is_shown就是判斷是否需要顯示,possible就是判斷是否需要執(zhí)行,when_taken就是執(zhí)行的效果,然后看起來就是這樣的。
```perl
example_decision = {
??? is_shown = {
??????? NOT = { has_variable = dummy_decision_taken }
??????? country_rank = rank_value:great_power # only visible to great powers
??? }
??? possible = {
??????? country_has_primary_culture = cu:french # can be taken only by countries where the french culture is primary
??? }
??? when_taken = {
??????? annex = c:GBR
??????? set_variable = {
??????????? name = dummy_decision_taken
??????????? value = yes
??????? }
??? }
??? ai_chance = {
??????? base = 20
??????? modifier = {
??????????? trigger = { c:PRU ?= root }
??????????? add = 10
??????? }
??? }
}
```
嗯,就是這樣的,然后就沒有然后了,這玩意上限就在這了。后續(xù)也不怎么樣,下半章也不會(huì)太重點(diǎn)描述,甚至有更好的替代方案。
## 第四節(jié) JE的編輯
JE,全稱Journal Entry,或者叫日志,是V3做的一種類似任務(wù)樹的概念。JE和前面二者的最大區(qū)別是,JE是可以在文本代碼中作為Scope調(diào)用的,而事件和決議則不然。此外,很多JE的熱刷新都要求跑一段時(shí)間才可刷新。
作為scope調(diào)用的je需要在國家scope作為輸出,以je:作為event target,然后根據(jù)je名稱得到輸出
JE的完整結(jié)構(gòu)如下
```perl
journal_entry_key = {
??? ###############預(yù)定義描述###########
??? # root JE所屬的國家
??? # scope:journal_entry 這個(gè)JE
??? # scope:target
??? ###############定義區(qū)###############
??? icon = "gfx/interface/icons/event_icons/event_industry.dds"
??? timeout = 720
??? should_be_pinned_by_default = yes
??? progressbar = yes
??? progress_desc = "這是進(jìn)度條"
??? can_deactivate = yes
??? inheritable = no
??? weight = 200
??? # how_tutorial = how_tutorial_lesson_key 教程相關(guān)JE
??? ##############trigger上下文區(qū)#################
??? is_shown_when_inactive =? { }
??? possible =? { }
??? is_progressing = {? }
??? complete = { }
??? fail = { }
??? invalid = { }
??? ##############effect上下文區(qū)#################
??? immediate = { }
?? ?
??? on_timeout = { }
??? on_complete =? { }
?? ?
??? on_fail = { }
??? on_invalid = { }
?? ?
??? ##########value區(qū)#############################
??? current_value = {
??????? value = gdp
??? }
??? goal_add_value = {
??????? value = gdp
??????? multiply = 0.25
??? }
??? ########鉤子區(qū)#########
?? ?
??? on_weekly_pulse = {
??????? random_events = {? }
??? }
??? # on_action which is triggered every first day of the month
??? on_monthly_pulse = {
??????? events = {? }
??? }
??? # on_action which is triggered every first day of the year
??? on_yearly_pulse = {
??????? effect = {? }
??? }
??? ################GUI區(qū)################
??? # 下半章的內(nèi)容
??? # status_desc = {
??? #???? first_valid = {
??? #???????? triggered_desc = {
??? #???????????? desc = journal_entry_key_status_1
??? #???????????? trigger = {
??? #???????????????? has_variable = mining_strike
??? #???????????? }
??? #???????? }
??? #???????? triggered_desc = {
??? #???????????? desc = journal_entry_key_status_2
??? #???????????? trigger = {
??? #???????????????? has_variable = industrial_strike
??? #???????????? }
??? #???????? }
??? #???????? triggered_desc = {
??? #???????????? desc = journal_entry_key_status_2
??? #???????????? trigger = {
??? #???????????????? has_variable = railway_strike
??? #???????????? }
??? #???????? }
??? #???? }
??? # }
??? # scripted_button = scripted_button_key
?? ?
}
```
可以看到我給JE分了七個(gè)區(qū):預(yù)定義描述、定義區(qū)、trigger上下文區(qū)、effect上下文區(qū)、value區(qū)、鉤子區(qū)和GUI區(qū),GUI區(qū)是第七章的內(nèi)容,暫不描述。
首先看預(yù)定義描述:root是激活了該JE的國家,指代這個(gè)JE的就是scope:journal_entry,scope:target就是添加je時(shí)使用的target
添加je使用的effect是`add_journal_entry = { type = <key> target = <scope> }`,參數(shù)里的target可以在JE里通過scope:target調(diào)用
在定義區(qū)中,icon定義了圖標(biāo),timeout定義了超時(shí)時(shí)間,should_be_pinned_by_default定義了其是否可以默認(rèn)釘選。
progressbar定義了其是否有根據(jù)Value區(qū)內(nèi)容設(shè)置的進(jìn)度條,progress_desc定義了進(jìn)度條上的文字。
can_deactivate定義了其能否在激活后當(dāng)possible的上下文為假時(shí)取消激活,inheritable定義了其能否被根據(jù)擁有je的國家生成的國家繼承。
最后兩個(gè)定義參數(shù)是weight和how_tutorial,weight是在界面中顯示的順序,how_tutorial是和教程相關(guān)的,比較無關(guān),所以不說。
trigger上下文區(qū)有六個(gè)trigger上下文:is_shown_when_inactive,possible,is_progressing和complete,fail,invalid。
is_shown_when_inactive默認(rèn)為假,當(dāng)其為真時(shí),即使沒有添加到國家中,也會(huì)在面板中顯示。
possible默認(rèn)為真,當(dāng)其和is_shown_when_inactive為真時(shí),激活該JE,如果can_deactivate = yes,那且其為假,那么取消該JE的激活。
is_progressing是確定其狀態(tài)的trigger,當(dāng)其為真時(shí),JE進(jìn)入到一個(gè)稱為is_progressing的狀態(tài),可以通過輸入為journalentry的scope的is_progressing的trigger判斷。
complete,fail,invalid分別是判斷JE完成、失敗和失效的trigger上下文,當(dāng)其為真時(shí),進(jìn)入完成、失敗或失效的狀態(tài),如果有對應(yīng)的effect上下文則執(zhí)行之。
effect上下文區(qū)有五個(gè)effect上下文:immediate、on_timeout和on_complete、on_fail、on_invalid
immediate是激活該JE之后立即執(zhí)行的effect,和事件的immediate上下文一樣,這適合用以定義一些variable和scope。
on_timeout是當(dāng)超時(shí)執(zhí)行內(nèi)部effect的鉤子。而on_complete、on_fail、on_invalid是對應(yīng)complete,fail,invalid的鉤子,當(dāng)進(jìn)入完成、失敗或失效的狀態(tài)時(shí)執(zhí)行之。
value區(qū)定義了兩個(gè)value,current_value和goal_add_value,其中,current_value實(shí)時(shí)更新,而goal_add_value則是在激活的時(shí)候就確定,不能改變。二者定義型態(tài)和scripted_value一致。
鉤子區(qū)有三個(gè)鉤子:on_weekly_pulse、on_monthly_pulse、on_yearly_pulse,分別是每周、每月、每年執(zhí)行一次內(nèi)容。
這些鉤子有三個(gè)參數(shù),第一個(gè)是稱為effect的effect上下文,顧名思義,這里可以放入effect,第二個(gè)是稱為event的上下文,按照空格或者換行隔開放入列舉的event。
最后一個(gè)是random_events,里面可以填入等號(hào)左邊為權(quán)重?cái)?shù)字,右邊為event名稱的文本代碼,當(dāng)不想執(zhí)行事件的時(shí)候,等號(hào)右邊放入0。
## 第五節(jié) 編輯一個(gè)JE
這么多區(qū)域既沒有必要按照上面為了方便闡述類型而排布的順序而排列,也沒有必要全部列舉,實(shí)踐中編輯一個(gè)JE只需要按照需求添加有必要的參數(shù)就可以
創(chuàng)建一個(gè)JE如下
```perl
journal_five_years_plan_looped = {
??? icon = "gfx/interface/icons/event_icons/event_industry.dds"
??? should_be_pinned_by_default = yes
??? timeout = 720
??? immediate = {
??????? set_variable = { name = five_years_plan_loops value = 1 }
??? }
??? is_shown_when_inactive =? { always = yes }
??? current_value = {
??????? value = gdp
??????? divide = var:five_years_plan_loops
??? }
??? goal_add_value = {
??????? value = gdp
??????? multiply = 0.25
??? }
??? progressbar = yes
??? progress_desc = "這是進(jìn)度條"
??? on_weekly_pulse = {
??????? effect = {
??????????? if = {
??????????????? limit = { scope:journal_entry = { is_goal_complete = yes } }
??????????????? change_variable = { name = five_years_plan_loops add = 0.25 }
??????????? }
??????? }
?????? ?
??? }
??? weight = 100
}
```
當(dāng)然跑gdp調(diào)試就太麻煩了,所以不如設(shè)置一個(gè)variable算了
```perl
journal_five_years_plan_looped_vared = {
??? icon = "gfx/interface/icons/event_icons/event_industry.dds"
??? should_be_pinned_by_default = yes
?? ?
??? immediate = {
??????? set_variable = { name = five_years_plan_vared_loops value = 1 }
??????? set_variable = { name = emulated_gdp value = 10 }
??? }
??? is_shown_when_inactive =? { always = yes }
??? current_value = {
??????? value = var:emulated_gdp
??????? divide = var:five_years_plan_vared_loops
??? }
??? goal_add_value = {
??????? value = var:emulated_gdp
??????? multiply = 0.25
??? }
??? progressbar = yes
??? progress_desc = "這是進(jìn)度條"
??? on_weekly_pulse = {
??????? effect = {
??????????? if = {
??????????????? limit = { scope:journal_entry = { is_goal_complete = yes } }
??????????????? change_variable = { name = five_years_plan_vared_loops add = 0.25 }
??????????? }
??????? }
?????? ?
??? }
}
```
在script explorer修改emulated_gdp值,查看效果。
這樣,就基本的學(xué)會(huì)了JE的編寫。
## 課后
很顯然,我這里的教程項(xiàng)目預(yù)留了一些坑,可以通過觀察這些坑來獲得更好的教學(xué)效果
1. 為什么example_event需要通過同時(shí)觸發(fā)兩個(gè)事件,然后在其中第一個(gè)事件里選擇選項(xiàng)3,然后第二個(gè)事件里才會(huì)有選項(xiàng)1?
2. 很顯然,當(dāng)沒有科技正在研究的時(shí)候,選項(xiàng)4的tooltip仍然會(huì)顯示:完成某某科技的研究,應(yīng)當(dāng)如何改進(jìn),才能讓沒有研究科技的時(shí)候不顯示這個(gè)tooltip?
這些就可以作為課后思考,會(huì)得到更好的收獲。