Board logo

标题: 骑马与砍杀 Mod制作教程中文版 [打印本页]

作者: 風清揚    时间: 2007-12-2 21:56     标题: 骑马与砍杀 Mod制作教程中文版

官方原文地址:http://forums.taleworlds.net/index.php/board,12.0.html

译者:abinchen(第1,2,3,4,5,7,9部分);waterwande(第6,8部分)

-----------------------------------------------------------------------------------------------------------

第一部分:准备开始

1.1 Mod系统是什么?
骑马与砍杀Mod系统是一组Python脚本,通过这些脚本可以生成新的游戏内容和修改已有的内容。我们的官方原版Mod就是使用这个系统来创建的。你可以利用它来增加新兵种、新人物、新任务、新对话等,并可以修改现有的游戏元素。

需要注意的一点的是,M&B并不直接读取Mod的Python脚本,而是读取由Python脚本执行生成的文本文件。

骑马与砍杀实际上从Mount&Blade/Module文件夹下的文本文件中读取内容。因此,理论上你可以通过修改这些文本文件来完成所有的Mod内容修改(实际上,一些Mod作者已经知道了如何利用这些文本文件并开发出了绝妙的Mod)。但是这些文本文件可读性相当差,直接修改它们并不实际。开发新的Mod目前有两种方法,一种是使用本文提到的官方Mod系统,另一种是Effidian的非官方编辑器。Effidian的编辑器拥有图形用户界面,比官方Mod系统更易使用,对于想制作Mod的新手来说是个很棒的工具。(阿斌注:该工具不支持0.800以上版本,也无相应的升级计划,所以还是老老实实用官方的编辑器吧。当然,其他一些新工具也在不断出现,对这些工具感兴趣的朋友可以关注一下官方网站)

1.2 使用Mod系统的必要条件:
Mod系统由Python脚本构成,所以你需要安装和配置好Python。以下地址可以下载到Python。
http://www.python.org/download/
该页面上有若干个下载链接,你只需下载最新的Windows 安装版即可.

下载并安装完毕Python后,需要在Windows环境变量中设置一下。这点非常重要,请确保正确完成此设置。

对于Windows 9x, 编辑 autoexec.bat 文件,把Python文件夹添加进Path中。例如,Python安装在C:\Python24,则添加如下语句:
set PATH=C:\Python24;%PATH%

如果是Windows XP, 操作稍有不同: 右键点击“我的电脑”,选择“属性”,点击“高级”页,点击“环境变量”按钮,然后编辑Path变量把Python路径添加到后面(如添加: ";C:\Python24")

1.3 获取Mod系统
最新版的Mod系统可从官方网页下载:
www.taleworlds.com/mb_module_system.html

下载zip文件并且解压缩. (本文中我假设你把它们解压在“D:\ModuleSystem\”文件夹。当然,你可以自由选择解压路径)

1.4 Mod系统的文件
现在,我们来看一下Mod系统包含的文件。观察所有的Python文件 (由.py结尾的文件),我们可以看到四种文件:
?        header_ 前缀的文件
?        process_ 前缀的文件
?        ID_前缀的文件
?        module_前缀的文件
前两类文件是运行Mod系统所必须的,不要修改它们。 第三类文件(ID_)是生成Mod时建立的临时文件,可以删除之,系统之后会自动生成这些文件。最后一类文件 (module_)是实际包含Mod数据内容的,你将要编辑的就是这些文件。


1.5 建立新Mod
首先我们来为新Mod建立一个文件夹。进入Mount&Blade/Modules文件夹(默认位于"c:/Program Files/Mount&Blade/Modules")。现在,Modules文件夹下应当有一个名为Native的文件夹,这就是所谓的官方原版Mod。你需要给你的新mod在这个位置创建一个新文件夹,然后复制Native下所有的文件到新文件夹中。此文件夹将是你自己的Mod文件夹,所以你可以任意给它一个名字。简单起见,我这里假设它的名字为MyNewModule。

你可以启动骑马与砍杀来看你做得正确与否。现在,M&B的启动窗口应该有一个下拉框让你选择Mod。试着选择你的新Mod然后启动一个新游戏。由于我们是从native文件夹下拷贝了所有内容,我们进入的游戏应该和原版完全一样。

下面,我们需要把Mod系统的目标文件夹设成刚才创建的新文件夹。编辑module_info.py (右键点击此文件,选择"Edit with IDLE";或者用记事本等文本编辑器亦可) ,更改export_dir 指向新文件夹。比如,如果你的Mod文件夹为: c:/Program Files/Mount&Blade/Modules/MyNewModule ,则修改这一行为:

export_dir = "C:/Program Files/Mount&Blade/Modules/MyNewModule/"

现在我们的Mod系统已经配置好了。让我们试着删除新文件夹中的conversation.txt,然后双击build_module.bat。你应当会看见一个命令提示符和类似下面的输出:


Code:
D:\ModuleSystem>python process_strings.py
Exporting strings...

D:\ModuleSystem>python process_items.py
Exporting item data...

D:\ModuleSystem>python process_map_icons.py
Exporting map icons...

D:\ModuleSystem>python process_factions.py
Exporting faction data...

D:\ModuleSystem>python process_scenes.py
Exporting scene data...

D:\ModuleSystem>python process_troops.py
Exporting troops data

D:\ModuleSystem>python process_party_tmps.py
Exporting party_template data...

D:\ModuleSystem>python process_parties.py
Exporting parties

D:\ModuleSystem>python process_quests.py
Exporting quest data...

D:\ModuleSystem>python process_scripts.py
Exporting scripts...

D:\ModuleSystem>python process_mission_tmps.py
Exporting mission_template data...

D:\ModuleSystem>python process_game_menus.py
Exporting game menus data...

D:\ModuleSystem>python process_dialogs.py
exporting triggers...
exporting dialogs...

D:\ModuleSystem>pause
按任意键继续 . . .

现在检查你的新Mod文件夹。如果一切都顺利,其中应当有一个新的conversations.txt 文件。

如果你碰到了错误,请确保你严格按照本教程的步骤做了。如果你确实这样做了,请使用官方论坛的搜索功能,很有可能已经有人遇到了同样的问题并发布了简单的解决方法。

如果没有遇到问题,那么恭喜你,你已经成功设置了M&B Mod系统!请接着看教程第二部分。

-----------------------------------------------------------------------------------------------------------

第二部分:编辑Mod系统文件

上一章中提到,Mod系统是这样使用的:

1 ) 编辑一个或多个Mod文件(以module开头,.py结尾的文件),然后按照你的喜好来修改它们。 (通常你需要右键点击并选择'Edit with Idle'来进行修改)
2 ) 接着,双击build_module.bat。这个操作将尝试建立你的Mod(并且报告任何存在的错误)
3 ) 若没有任何错误,你可以启动M&B测试你更改的内容。有时候你需要创建一个新游戏来使更改生效。

2.1 编辑Mod系统文件
Mod系统使用Python列表来表示游戏对象的集合。( 一个Python列表以'['打头,包括一列由逗号分隔的对象,并且以']' 结尾) ,你打开任意一个mod文件,都会发现它包含这样一个列表。例如module_map_icons.py中有:

map_icons = [
  ("player",0,"player", 0.2, snd_footstep_grass),
  ("player_horseman",0,"player_horseman", 0.2, snd_gallop),
  ("gray_knight",0,"knight_a", 0.2, snd_gallop),
  ("vaegir_knight",0,"knight_b", 0.2, snd_gallop),
  ("peasant",0,"peasant_a", 0.2,snd_footstep_grass),
  ("khergit",0,"khergit_horseman", 0.2,snd_gallop),
  ("axeman",0,"bandit_a", 0.2,snd_footstep_grass),
  ("woman",0,"woman_a", 0.2,snd_footstep_grass),
  ("town",mcn_no_shadow,"City", 0.9,snd_footstep_grass),
]

这里map_icons 被声明为一个Python列表(list),列表中的每一个元素都是一个特定的地图图标对象的声明。在这个例子中,("player",0,"player", 0.2, snd_footstep_grass) 就是这样一个对象。我们称其为元组(tuple)。元组和列表类似,包括由逗号分隔的元素(但是它们以括号作为开头和结尾)。每个元组对象的结构在mod文件的开头定义。对于地图图标,每个元组对象包含:

1 ) 图标名,
2 ) 图标标识,
3 ) 网格(mesh)名,
4 ) 网格缩放(scale),
5 ) 声音id.

因此,对第一个元组("player",0,"player", 0.2, snd_footstep_grass)来说
1 ) 图标名 = "player"
2 ) 图标标识 = 0
3 ) 网格名 = "player"
4 ) 网格缩放 = 0.2
5 ) 声音id = snd_footstep_grass


通过使列表的内容与文档开头部分的定义相匹配,你可以创建任意mod系统文件中的游戏对象结构。


2.2 添加新的游戏对象

了解清楚地图图标元组的结构之后,就可以添加我们自己的地图图标了。让我们再看一遍这个列表。

map_icons = [
  ("player",0,"player", 0.2, snd_footstep_grass),
  ("player_horseman",0,"player_horseman", 0.2, snd_gallop),
  ("gray_knight",0,"knight_a", 0.2, snd_gallop),
  ("vaegir_knight",0,"knight_b", 0.2, snd_gallop),
  ("peasant",0,"peasant_a", 0.2,snd_footstep_grass),
  ("khergit",0,"khergit_horseman", 0.2,snd_gallop),
  ("axeman",0,"bandit_a", 0.2,snd_footstep_grass),
  ("woman",0,"woman_a", 0.2,snd_footstep_grass),
  ("town",mcn_no_shadow,"City", 0.9,snd_footstep_grass),
]

任何一个mod文件中的新游戏对象必须添加在这个列表内。你可以看到module_map_icons 列表刚好在("town",mcn_no_shadow,"City", 0.9,snd_footstep_grass)后结束。为了给我们的新游戏对象腾出空间,必须把后括号往下移一行。

做好上一步后,就可以添加新的对象了。最简便的办法是复制和粘贴一个已存在的对象,然后编辑其内容。例如:


map_icons = [
  ("player",0,"player", 0.2, snd_footstep_grass),
  ("player_horseman",0,"player_horseman", 0.2, snd_gallop),
  ("gray_knight",0,"knight_a", 0.2, snd_gallop),
  ("vaegir_knight",0,"knight_b", 0.2, snd_gallop),
  ("peasant",0,"peasant_a", 0.2,snd_footstep_grass),
  ("khergit",0,"khergit_horseman", 0.2,snd_gallop),
  ("axeman",0,"bandit_a", 0.2,snd_footstep_grass),
  ("woman",0,"woman_a", 0.2,snd_footstep_grass),
  ("town",mcn_no_shadow,"City", 0.9,snd_footstep_grass),
  ("new_icon",mcn_no_shadow,"City", 0.9,snd_footstep_grass),
]


这个例子中,我们拷贝了("town",mcn_no_shadow,"City", 0.9,snd_footstep_grass) 并给了它一个新名字"new_icon"(新图标)。这个新图标具有一个标识。标识是一些可以在相应的地方打开或关闭的设置,可以通过包含或去掉这些标识来打开或关闭它们。例如,mcn_no_shadow这个标识使用在我们的新图标上,会使图标不投下阴影。

我们现在从新图标上删去mcn_no_shadow 标识,把它替换为0,这样就告诉mod系统这个图标没有任何标识

map_icons = [
  ("player",0,"player", 0.2, snd_footstep_grass),
  ("player_horseman",0,"player_horseman", 0.2, snd_gallop),
  ("gray_knight",0,"knight_a", 0.2, snd_gallop),
  ("vaegir_knight",0,"knight_b", 0.2, snd_gallop),
  ("peasant",0,"peasant_a", 0.2,snd_footstep_grass),
  ("khergit",0,"khergit_horseman", 0.2,snd_gallop),
  ("axeman",0,"bandit_a", 0.2,snd_footstep_grass),
  ("woman",0,"woman_a", 0.2,snd_footstep_grass),
  ("town",mcn_no_shadow,"City", 0.9,snd_footstep_grass),
  ("new_icon",0,"City", 0.9,snd_footstep_grass),
]


"town"(城镇)和 "new_icon" 都使用了"City"(城市)网格,这意味着它们都使用了游戏资源文件中名为"City"的3D模型。更改这个字段可以让新图标使用资源文件中的任意3D模型。

因为两个图标都使用了同一个网格,如果我们现在把"new_icon"放进游戏,它将和"town"看起来是一个模子里出来的。

现在我们来稍微改变一下"new_icon" 的外观。

map_icons = [
  ("player",0,"player", 0.2, snd_footstep_grass),
  ("player_horseman",0,"player_horseman", 0.2, snd_gallop),
  ("gray_knight",0,"knight_a", 0.2, snd_gallop),
  ("vaegir_knight",0,"knight_b", 0.2, snd_gallop),
  ("peasant",0,"peasant_a", 0.2,snd_footstep_grass),
  ("khergit",0,"khergit_horseman", 0.2,snd_gallop),
  ("axeman",0,"bandit_a", 0.2,snd_footstep_grass),
  ("woman",0,"woman_a", 0.2,snd_footstep_grass),
  ("town",mcn_no_shadow,"City", 0.9,snd_footstep_grass),
  ("new_icon",0,"City", 5.0,snd_footstep_grass),
]


这个例子中,我们把图标的缩放从0.9更改为5.0,这意味着此图标将会以通常大小的5倍来显示。在游戏中,这可以帮助我们把它和"town" 区分开。

接下来我们来创建一个使用我们的新图标的队伍(party)。为此我们需要从module_parties.py中引用这个图标。


2.3 引用游戏对象

在你的mod系统文件夹中打开module_parties.py 文件,将会看到另一个Python列表, parties = [.

正如你看到的,module_parties.py 中的元组和module_icons稍有不同。许多元组(如果不是全部的话)都是这样。我们利用这个机会仔细看看队伍的结构。

队伍的一个例子:

("zendar","Zendar",icon_town|pf_is_static|pf_always_visible|pf_hide_defenders, "zendar", pt_none, fac_neutral,0,ai_bhvr_hold,0,(2,46),[(trp_swadian_knight,6,0)]),

这个元组把Zendar(禅达)放置在地图上。Zendar的多项属性在合适的字段中进行配置——非常类似我们在module_icons.py中看到的字段。

元组字段解析:

1 ) 队伍id。队伍在其他文件中被引用时的id。
2 ) 队伍名。队伍在游戏中表现的名字。可以根据你的喜好设成跟party-id不同的名字。
3 ) 队伍标识(flag)。每个队伍对象的第一个标识必须为此队伍所用的图标。
4 ) 菜单。这个字段已经被摈弃了,它对M&B 0.730版本以上已经不再有效。
5 ) 队伍模板。此队伍从属的队伍模板id。pt_none 为其默认值。
6 ) 队伍阵营(faction)。这可以是module_factions.py中的任意条目。
7 ) 队伍个性。个性标识的解释详见header_parties.py。
8 ) AI行为。队伍在大地图上的AI行为表现。
9 ) AI目标队伍。AI行为的目标。
10 ) 初始坐标。队伍在大地图上的初始坐标X, Y。
11 ) 部队栏(troop stack)列表。每个部队栏是一个三元组,包含以下字段:
11.1 ) 部队id。可以是module_troops.py中的常规兵种或英雄。
11.2 ) 此部队栏中的数量,是固定的。你在这里输入的数字就是城镇将有的部队数量。
11.3 ) 成员标识,可选的。可以使用pmf_is_prisoner来标注俘虏。


Zendar元组的解释:

("zendar","Zendar",icon_town|pf_is_static|pf_always_visible|pf_hide_defenders, "zendar", pt_none, fac_neutral,0,ai_bhvr_hold,0,(2,46),[(trp_swadian_knight,6,0)]),

1 ) 队伍id = "zendar"
2 ) 队伍名= "Zendar"。
3 ) 队伍标识 = icon_town|pf_is_static|pf_always_visible|pf_hide_defenders
4 ) 菜单 = "zendar"
5 ) 队伍模板 = pt_none
6 ) 队伍阵营 = fac_neutral
7 ) 队伍个性 = 0
8 ) AI行为 = ai_bhvr_hold
9 ) AI目标队伍 = 0
10 ) 初始坐标 = (2,46)
11 ) 部队栏列表:
11.1 ) 部队id = trp_swadian_knight
11.2 ) 此部队栏中的数量 = 6
11.3 ) 成员标识 = 0

观察第三个字段,我们可以看到Zendar通过加入icon_ to前缀,引用了module_icons.py 中的"town"。Mod系统通过这个前缀指向正确的mod文件。要引用module_icons,我们用icon_; 要引用module_factions, 我们用fac_;要引用module_parties, 我们用p_; 诸如此类。每个mod文件都有和其对应的前缀——这一章的结尾列出了所有前缀。.


既然我们已经知道了队伍的结构,就可以来添加自己的队伍了。但是开始之前,请注意:module_parties.py 和其他一些特定的mod文件中,你不可以在列表的尾部添加新的城镇。这些文件中有相关注释警告你不要这样做,因为这会打断本地代码的执行。在module_parties.py 中建议你在"training_ground"和"castle_1"之间添加新的条目。

现在,复制条目"town_14" 然后把它粘贴到"training_ground"和"castle_1"之间。

("training_ground","Training Ground",  icon_town|pf_town|pf_disabled, "training_ground", pt_none, fac_vaegirs,0,ai_bhvr_hold,0,(-2,-3),[(trp_vaegir_knight,6,0)]),

("new_town","Mod_Town",  icon_town|pf_town, "town", pt_none, fac_vaegirs,0,ai_bhvr_hold,0,(-4,-37),[(trp_vaegir_knight,6,0)]),

("castle_1","Culmarr_Castle",icon_town|pf_is_static|pf_always_visible, "castle", pt_none, fac_outlaws,0,ai_bhvr_hold,0,(-47,-51),[(trp_swadian_knight,5,0),(trp_swadian_crossbowman,25,0)]),


这个例子中,我们把队伍的id从"town_14" 改为 "new_town",队伍名从"Halmar"(哈尔玛)改为 "Mod_Town".

通过观察这个元组,我们现在可以确定:

1 ) 从另一个文件引用这个队伍,我们必须使用id "new_town" 和前缀"p_",即"p_new_town".
2 ) 在游戏中,我们只会见到"Mod Town"这个描述队伍的名字,而非它的id。
3 ) 这个队伍使用icon_town图标和pf_town标识——此标识定义了通用的城镇设置。我们下一步将修改这个标识。
4 ) "Mod Town" 当前属于Vaegir(维吉亚)阵营。
5 ) 如果我们现在把这个新城镇放进游戏,它将和Halmar出现在地图的同一个坐标。这个我们接下来也要更改。


("training_ground","Training Ground",  icon_town|pf_town|pf_disabled, "training_ground", pt_none, fac_vaegirs,0,ai_bhvr_hold,0,(-2,-3),[(trp_vaegir_knight,6,0)]),

("new_town","Mod_Town",  icon_new_icon|pf_town, "town", pt_none, fac_neutral,0,ai_bhvr_hold,0,(-1,-1),[(trp_vaegir_knight,6,0)]),

  ("castle_1","Culmarr_Castle",icon_town|pf_is_static|pf_always_visible, "castle", pt_none, fac_outlaws,0,ai_bhvr_hold,0,(-47,-51),[(trp_swadian_knight,5,0),(trp_swadian_crossbowman,25,0)]),


这里我们将新城镇的图标更改为icon_new_icon,将地图坐标改为 (-1,-1)。并且,我们将城镇的阵营改成了fac_neutral(中立).

这个城镇现在使用了我们的新图标,拥有自己独一无二的地图坐标,这使得它可以毫无问题地显示出来。

保存,然后执行build_module.bat。如果一切顺利,启动你的mod看看地图中间附近出现的新城镇和图标。试试吧。

如果不是一切顺利,仔细检查一下拼写和语法。确保所有的逗号和括号都在恰当的位置。语法错误是原版mod中编译器发生错误的最常见根源。

游戏中,到新城镇时会触发一场遭遇战,因为它目前还没有分配游戏菜单。分配菜单有一些复杂,因此我们先把它放着,稍后的章节中我们会回到这里。

正如你所看到的,不同的mod文件之间的交互是十分广泛的。你的mod要想正常工作必须覆盖所有的环节。幸运的是,多数更改最多只需编辑一到两个文件。

既然你已经掌握了制作mod的基础,我们现在可以更进一步地学习不同的mod文件了。请继续看教程的第三章。


mod文件前缀列表:

fac_   -- module_factions.py
icon_   -- module_map_icons.py
itm_   -- module_items.py
mnu_   -- module_game_menus.py
mno_   -- module_game_menus.py ——在module_game_menus中引用单独的菜单项
mt_   -- module_mission_templates.py
psys_   -- module_particle_systems.py
p_      -- module_parties.py
pt_   -- module_party_templates.py
qst_   -- module_quests.py
script_   -- module_scripts.py
scn_   -- module_scenes.py
spr_   -- module_scene_props.py
str_   -- module_strings.py
trp_   -- module_troops.py

module_dialogs.py 从不直接被引用,所以它没有前缀。

-----------------------------------------------------------------------------------------------------------

第三部分:Module_Troops(兵种模块)

这一章我们讨论module_troops.py及其功能。Module_troops中定义所有的常规兵种,英雄,箱子和城镇,包括他们的容貌,能力数值和装备库。要想创建一个新人物或兵种,则修改这个文件。

3.1 Module_Troops解析

这个文件开头的代码块是用来计算武器熟练度的,以及其他一些不能mod的代码。这块代码在Python列表区域之外,所以我们无须编辑它也不用关心它。直接跳到列表troops = [ 吧。

这里我们找到一些元组定义了游戏中的伙伴(玩家)和几个游戏中非常重要的兵种。紧跟着的是我们在Zendar教练处遇到的多个战士。我们来学习一下这些东东,因为这是常规兵种成长升级的很好范例。

观察以下内容:

  ["novice_fighter","novice_fighter","novice_fighters",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners,
   [itm_sword,itm_hide_boots],
   str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],

这是一个垃圾兵种"novice fighter"(见习步兵)。"novice fighter" 是低等兵种,不擅长战斗,拥有低能力值,名不见经传。


元组字段解析:

1 ) 兵种id。用来在其他文件中被引用。
2 ) 兵种名。
3 ) 兵种名复数形式。
4 ) 兵种标识。如果你想保证这个兵种永远有一类武器装备的话,必须设置tf_guarantee_* 标识。否则,此兵种出现的时候可能没有该类装备,如果该兵种的物品库中有近战武器,则只有近战武器会保证被装备上。
5 ) 场景(scene)。这只对英雄适用,定义了英雄将出现在哪个场景和哪个入口。举个例子, scn_reyvadin_castle|entry(1) 把部队放入Reyvadin(日瓦丁)城堡的入口1.
6 ) 预留。目前没用到,值为reserved 或 0。
7 ) 阵营。兵种的阵营,以fac_ prefix为前缀。
8 ) 装备库(inventory)。兵种装备库的一系列物品。常规兵种会随机从这里选择装备。
9 ) 属性(attribute)。兵种的属性值和人物等级,和玩家属性的工作机制一样。
10 ) 武器熟练度。该兵种的武器熟练度值。wp(x)函数将建立一个接近x值的随机武器熟练度,当然你也可以针对特定的熟练度添加附加定义。如,要创造一个其他武器的熟练度为60左右的资深弓箭手,你可以定义如下:
           wp_archery(160) | wp(60)
11 ) 技能(skill)。这和玩家技能一样。注意,在你定义的属性和技能之外,兵种的每一个人物级别也会得到一个随机属性点和一个随机技能点。
12 ) 容貌代码(face code)。游戏会根据这个代码生成容貌。你可以在游戏中的容貌调节窗口按CTRL+E来输出容貌代码,前提是打开编辑模式。
13 ) 容貌代码2。只对常规兵种适用,对英雄可以忽略。游戏将为该兵种的每个个体生成容貌代码1和容貌代码2之间的随机容貌特征。

检查Novice_fighter元组:

1 ) 兵种id = "novice_fighter"
2 ) 兵种名 = "novice_fighter"
3 ) 兵种名复数形式 = "novice_fighters"
4 ) 兵种标识 = tf_guarantee_boots|tf_guarantee_armor
5 ) 场景 = no_scene
6 ) 预留 = reserved
7 ) 阵营 = fac_commoners
8 ) 装备库 = [itm_sword,itm_hide_boots]
9 ) 属性 = str_6|agi_6|level(5)
11 ) 技能 = knows_common
12 ) 容貌代码 = swadian_face1
13 ) 容貌代码 = swadian_face2

关于这个元组,有三样东西是没有作用的。

我们的"novice fighter"设置了tf_guarantee_armor(保证防具), 但是他自己没有防具,然而这并不使tf_guarantee_armor成为多余;部队会穿上他在游戏中获得的任意防具。

刚开始的时候(即第1级),"novice_fighter"力量为6,敏捷为6。游戏启动后,他一下子跳到第5级,同时得到了隐式定义的所有点数(即5个属性点和5个技能点)

他有技能knows_common(懂得普通技能)。knows_common是module_troops开头部分定义的技能集;现在向上翻滚文档并观察这个集合。

knows_common = knows_riding_1|knows_trade_2|knows_inventory_management_2|knows_prisoner_management_1|knows_leadership_1

赋予了knows_common 的兵种将拥有这里列出的所有技能:1点骑术,2点交易,2点物品管理,1点俘虏管理和1点统御。knows_common 是一个常量——代表了其他一些东西的词:如数字,id,另一个常量,或另一个合法对象。一个常量可以代表任意数量的对象,只要那些对象在你需要它们出现的地方以正确的顺序出现。

这个例子中,knows_common 被定义为knows_riding_1|knows_trade_2|knows_inventory_management_2|knows_prisoner_management_1|knows_leadership_1。因此把knows_common放进技能字段的效果是, mod系统的运行方式将和你在技能字段输入knows_riding_1|knows_trade_2|knows_inventory_management_2|knows_prisoner_management_1|knows_leadership_1 一样。

现在,让我们看列表中的下一个条目。

  ["regular_fighter","regular_fighter","regular_fighters",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners,
   [itm_sword,itm_hide_boots],
   str_8|agi_8|level(11),wp(90),knows_common|knows_ironflesh_1|knows_power_strike_1|knows_athletics_1|knows_riding_1|knows_shield_2,swadian_face1, swadian_face2],

这个例子中,你可以看到稍强一点的"regular fighter"(步兵);他拥有较高的能力值,11级,掌握knows_common之外的一些技能。游戏中,如果我们的团队中有"novice fighters" 到达11级所需的经验点,我们可将其升级为"regular fighters"。


3.2 升级部队

什么兵种可以升级成什么是在module_troops的尾部定义的。请翻滚到文档底部。

可以看到,每个兵种的升级方案须在这里用操作符upgrade(troops)定义。第一个字符串是将要被升级的兵种id,第二个字符串是升级后的兵种id。举个例子来说, upgrade(troops,"farmer", "watchman") 将允许一个"farmer"(农民)升级成 "watchman"(看守人), 当"farmer" 得到足够经验点数的时候.

有两种升级操作。

upgrade(troops,"source_troop", "target_troop_1") 提供了唯一的升级选择; "source_troop" 到 "target_troop_1".

upgrade2(troops,"source_troop", "target_troop_1", "target_troop_2"), 则提供了把"source_troop"升级到"target_troop_1" 或者 "target_troop_2"的2种选择。 2是当前可能的升级选择之最大数量。

目前在这个区块中没有"novice_fighter"的条目,所以我们来创造一个。复制upgrade(troops,"farmer", "watchman")并粘贴到区块的底部。然后改"farmer" 为 "novice_fighter" ,改 "watchman" 为 "regular_fighter"。我们的团队中任何 "novice fighters" 现在将可以升级为"regular fighters",正如最后一段代码中所描述的。

接着,我们来做一点修改。在列表最后再添加一个条目,源兵种为 "new_troop" ,目标兵种为"regular_fighter"。然后向上滚动文档到 # Add Extra Quest NPCs below this point 。这里我们看到了 ], 这是Python中兵种列表的后括号。新的兵种须在这个括号之前添加,我们现在就来干这个。


3.3 添加新兵种

把括号往下移两行。然后在空白区域复制/粘贴下面的代码:

  ["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners,
   [itm_sword,itm_hide_boots],
   str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],

这是我们将要玩转的条目,以此来增加新兵种。

首先我们来给他一些防具和头盔。


  ["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor,no_scene,reserved,fac_commoners,
   [itm_sword,itm_hide_boots,itm_leather_jerkin,itm_skullcap],
   str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],


从现在开始,每个"new_troop" 种类的部队将穿着itm_leather_jerkin(无袖皮上衣)。然而,由于标识字段中定义的条目,他们中只有部分人有itm_skullcap(无边帽);这个兵种只确保了防具和靴子。为了保证我们的新兵种都戴着头盔,我们必须把tf_guarantee_helmet(保证头盔)加到标识字段中去。


  ["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor|tf_guarantee_helmet,no_scene,reserved,fac_commoners,
   [itm_sword,itm_hide_boots,itm_leather_jerkin,itm_skullcap],
   str_6|agi_6|level(5),wp(60),knows_common,swadian_face1, swadian_face2],


接下来我们修改兵种的属性。把STR(力量)设为9,AGI(敏捷)设为9。这些做好后,改人物等级为4级,武器熟练度为80。

我们的"new troop"看起来应当像这样:

  ["new_troop","new_troop","new_troops",tf_guarantee_boots|tf_guarantee_armor|tf_guarantee_helmet,no_scene,reserved,fac_commoners,
   [itm_sword,itm_hide_boots,itm_leather_jerkin,itm_skullcap],
   str_9|agi_9|level(4),wp(80),knows_common,swadian_face1, swadian_face2],

作为一个试验,现在已经准备好把它放入游戏中了。


3.4 雇佣兵

保存你的进度,打开module_parties.py。向下滚动文档直到看到"zendar_mercs"团队。

  ("zendar_mercs","zendar_mercs",pf_disabled, no_menu, pt_none, fac_commoners,0,ai_bhvr_hold,0,(0,0),[(trp_farmer,15,0)]),

这是一个雇佣兵团队;这个团队并不放置在地图上,但可以在城镇的酒馆主那里雇佣到。这个特定的团队链接到Zendar的酒馆主那里。

"zendar_mercs" 目前包含15个农民。如果你马上启动游戏,你能否雇到的就是他们。如果我们把"trp_farmer" 改成 "trp_new_troop", 我们将能够雇到15个"new troops"。现在请改之。

保存你的进度,关闭module_parties, 双击build_module.bat。如果生成没有问题,你将在Zendar酒馆主那里找到你的新兵种。(为了让新兵种出现,你需要开始一个新游戏。你需要新启游戏来使团队的改动生效)

启动游戏,雇佣一些"new troops"。然后离开城镇,加入一些战斗,注意到现在当新兵种积累到足够的经验时你可以把新兵种升级成"regular fighters"。


恭喜! 你现在明白了如何创造和处理常规兵种。下一节中我们将讨论英雄,商人和其他NPC。


3.5 NPC

你在游戏中看到的多种商人和NPC与常规兵种非常类似。把他们区分开的最重要的元素是tf_hero标识;正是这个标识使Marnid (马尼德)and Borcha (么么茶)获得特别身份。在游戏中遇到的任何一个NPC都是英雄,即便商人也是如此。英雄和常规兵种的主要区别在于:

1 ) 英雄是不可杀死的。他们的生命用百分比表示,你只能拥有每个英雄的一个个体,除非他们被错误或人为的克隆。即使是人为设计的,克隆英雄也不是一个好主意。
2 ) 每个英雄占用一整个部队栏。
3 ) 英雄在兵种元组中赋予出生地后,会正确地出现在场景中。
4 ) 玩家被敌人打败后英雄仍跟着玩家——英雄不被敌人俘获——但是玩家可以像通常那样俘虏敌人的英雄。

正因为每个英雄只有一个样本,他们没有复数形式的兵种名。英雄元组的第三个字段因此和第二个完全一样。

英雄元组的一个例子:

  ["Marnid","Marnid","Marnid", tf_hero, scn_the_happy_boar|entry(4),reserved, fac_commoners,[itm_linen_tunic,itm_hide_boots,itm_club],def_attrib|level(6),wp(60),knows_trade_3|knows_inventory_management_2|knows_riding_2,0x00000000000c600301c2510211a5b292],

这里是我们的伙伴Marnid,游戏过程中的忠实伴侣。他在标识字段中用tf_hero 标记为英雄。我们可以在快乐野猪酒馆的入口4找到他。他是战斗中的炮灰,但他的不错的交易技能是初期冒险的得力助手,并且他永远不会挂掉除非一些脚本事件把他移除。而且,你会发现Marnid有着独一无二的容貌代码。用游戏内置的容貌编辑器设计容貌,然后为你的mod抓取容貌代码是可以实现的;这将在教程的第十部分(使用内置游戏模式)讨论到。

另一个值得一提要点是,虽然这个文件中Marnid的兵种id "Marnid"用了大写字母M,我们也要用小写字母来引用它。如果你尝试在引用一个id时使用大写字母,Mod系统将抛出错误。因此,我们必须使用id "trp_marnid"来引用"Marnid"。

我们最后一个关注的地方你可能注意到了,在Marnid的属性字段里的def_attrib。def_attrib 是一个类似knows_common的常量,不过定义在header_troops.py中。它的功能和knows_common 相象——设置兵种的默认属性。下面是header_troops的一部分:

def_attrib = str_5 | agi_5 | int_4 | cha_4

这告诉我们设置为def_attrib 的兵种将一开始拥有STR 5, AGI 5, INT (智力)4 and CHA (魅力)4 。def_attrib 之后设置的属性集将覆盖def_attrib中相关的属性。


我们现在可以创造自己的英雄了。复制Marnid的元组, 粘贴到刚才创建的"new_troop" 元组的正下方。

  ["Geoffrey","Geoffrey","Geoffrey", tf_hero, scn_the_happy_boar|entry(4),reserved, fac_commoners,[itm_linen_tunic,itm_hide_boots,itm_club],def_attrib|level(6),wp(60),knows_trade_3|knows_inventory_management_2|knows_riding_2,0x00000000000c600301c2510211a5b292],

此例中,我们把新英雄的id和名字改成了"Geoffrey"。现在请你做这些改动。

这时候,如果我们双击build_module.bat, mod系统会正确的进行编译。我们的新元组和mod系统看见的没有丝毫冲突。然后如果这样做,我们会碰到一个大问题——Geoffrey 和 Marnid 目前在同一个场景中使用同一个入口。Marnid将被放置在该点上,因为他的元组在文件的上方定义,而Geoffrey 则不会显示出来,因为入口已经被占用了。

为了解决这个问题,我们把Geoffrey 设为入口6。这是酒馆的尾部,在那里他不会挡到任何人。

你的元组看起来应当像这样:

  ["Geoffrey","Geoffrey","Geoffrey", tf_hero, scn_the_happy_boar|entry(6),reserved, fac_commoners,[itm_linen_tunic,itm_hide_boots,itm_club],def_attrib|level(6),wp(60),knows_trade_3|knows_inventory_management_2|knows_riding_2,0x00000000000c600301c2510211a5b292],

任意给他一些新装备或属性技能,就像我们为"new_troop"所做的。然后双击build_module.bat, 启动游戏进去Zendar酒馆。如果一切顺利的话,你会在酒馆找到对着后墙站着的Geoffrey 。

试图和Geoffrey对话将引发一个交易,因为他当前没有被指派任何对话。这是文件相互关联的又一个例子。


3.6 商人

商人是英雄的特殊类型。除tf_hero外, 他们还有tf_is_merchant标识。这个标识让他们除了原先在兵种元组中定义好的装备外,不装备物品库中的任何物品。换句话说,这些商人可以获得游戏过程中的所有物品,但不能够装备或使用之,这些物品很大可能是用来出售的。

商人的例子:

  ["zendar_weaponsmith","Dunga","Dunga",tf_hero|tf_is_merchant, scn_zendar_center|entry(3),0,  fac_commoners,[itm_linen_tunic,itm_nomad_boots],def_attrib|level(2),wp(20),knows_inventory_management_10, 0x00000000000021c401f545a49b6eb2bc],

这是Zendar的武器商,名叫 Dunga。在原版M&B中他非常貌似其他商人。如果你凑近看,唯一的区别是他们的id,名字,场景放置点和脸。

添加一个商人稍有点复杂。他们由于这个原因聚在一个组:对于每一类商人,M&B中有脚本来每天更新他们的物品库——为此,这些脚本使用了“范围”,即一些连续的元组,从选择的开始点(下限)到结束点(上限)。例如,防具商的范围包括"zendar_armorer" (下限)到(不包括)"zendar_weaponsmith" (上限) 的所有东东。范围的上限并不包括在范围内,所以上限需要设置到条目的下一个(到 "zendar_weaponsmith" ,如果我们想要防具商的范围包括"town_14_armorer"的话)。


由于这个原因,新的防具商必须加在"zendar_weaponsmith"之前,新武器商必须加在 "zendar_tavernkeeper"之前,新杂货商必须加在 "merchants_end"之前。


3.7 箱子(chest)

箱子兵种是特殊兵种,作为物品栏和玩家在游戏中交互。这些箱子兵种的物品栏是箱子,其本身并非箱子。箱子,正如你游戏中见到的,一部分是场景道具,一部分是信息,一部分是兵种,一部分是硬编码的。新箱子创建起来有些复杂,遍布在不同的mod文件中;这里我们只讨论module_troops中的相关信息。

箱子的例子:

  ["zendar_chest","zendar_chest","zendar_chest",tf_hero|tf_inactive, 0,reserved,  fac_vaegirs,[],def_attrib|level(18),wp(60),knows_common, 0],

所有箱子必须遵循这个例子。关于新箱子兵种,你唯一可以考虑改变的是兵种名字和id,(有可能)兵种级别和技能,以及物品库。上面提到,箱子兵种作为箱子的物品库存在于游戏内;因此,游戏开始的时候,你赋予箱子兵种的任何物品将出现在箱子中。

箱子都需要一个箱子兵种来发挥作用,同时它们也需要对不同的mod文件做一些其他修改,关于这些我们在这些文件各自的文档中会涉及到。


学完这些之后,你已经掌握了所有了解module_troops所必须了解的内容。至于其他你能够创造的兵种,可以在header_troops.py中找到其标识列表。自由实验吧,当你准备好后,请继续教程的下一部分。


-----------------------------------------------------------------------------------------------------------

第四部分:Module_Party_Templates(队伍模板模块)

教程的第二部分中,我们学习了如何创造新队伍,即地图上唯一的位置。我们这里要讨论的是队伍模板(party template),不要把它们和队伍相混淆。

简单而言,队伍模板是队伍出生在地图上时遵守的一组策略。下面这句话说明了队伍和队伍模板的最大区别——队伍是地图上的唯一实体,而模板并不实际存在于游戏世界。它们只是作为队伍出生时的一组策略。因此,应该输入队伍id的地方如果输入了队伍模板id,这些操作将失效。

从模板中生成的队伍不必是唯一的。同一个模板中可以有许多队伍;每个队伍会有一个随机数量的部队,这个数字取决于玩家的等级和模板中定义的最小/最大部队数量。


4.1 Module_Party_Templates解析

该文件的开头是常见的Python列表: party_templates = [, 紧接着是是几个跟游戏联系紧密的模板,这些模板不可编辑。

你会发现module_party_templates中的元组和module_parties中的相当类似,但这两者不可互换。

队伍模板的例子:

  ("farmers","farmers",icon_peasant|pf_auto_start_dialog,0,fac_innocents,merchant_personality,[(trp_farmer,11,22),(trp_peasant_woman,16,44)]),

这是我们都能在游戏中遇到的模板。此模板的队伍叫做"farmers"(农民),你碰到他们时会自动开始对话,在游戏中他们表现得很软弱,每个队伍由农民和农妇两个部队栏组成。

元组字段解析:

1 ) 元组模板id。用于在其他文件中引用这个元组模板。
2 ) 元组模板名。该模板的队伍将使用的名字。
3 ) 队伍标识。你会发现module_party_templates中的所有模板都有一个pf_auto_start_dialogue 设置。这个标识将使玩家碰到此模板的一个队伍时自动开始一段对话。
4 ) 菜单。module_parties文件中已摈弃它。在这里用0值.
5 ) 阵营。
6 ) 个性(personality)。这个字段包括了队伍在地图上行为的标识。
7 ) 栏列表。每个栏是一个包含以下字段的元组:
    7.1) 部队id。
    7.2) 栏内的最小部队数量。
    7.3) 栏内的最大部队数量。
    7.4) 成员标识(可选)。你需要添加一个额外字段来设置成员标识。例如(trp_swadian_crossbowman,5,14,pmf_is_prisoner)

队伍模板中最多可以有6个栏。


观察农民元组:

1 ) 元组模板id = "farmers"
2 ) 元组模板名 = "farmers"
3 ) 队伍标识 = icon_peasant|pf_auto_start_dialog
4 ) 菜单 = 0
5 ) 阵营 = fac_innocents
6 ) 个性 = merchant_personality
7 ) 栏列表:
    7.1) 部队id = trp_farmer, trp_peasant_woman
    7.2) 栏内的最小部队数量 = 11, 16
    7.3) 栏内的最大部队数量 = 22, 44
    7.4) 成员标识(可选) = 未设置。


如果你从第一部分开始学习的本教程,到这里你应当能相当熟练地阅读元组了,你会发现此元组中有一个字段与我们之前遇见的其他字段不太相同:字段6,个性标识字段。下一节我们会讨论这个字段及其功能。


4.2 个性(personality)

正如在元组解析中提到的,个性字段决定了队伍在地图上的行为。这里你可以把自定义的分值赋给勇气值和好斗值,或者套用预定义的个性值,如merchant_personality(商人个性)。这些预设值是常量,包括勇气和好斗的分值。header_parties.py中定义了所有预设值,所以现在打开这个文件并滚动到底部来看这些定义,你也会看到一列勇气和好斗的可能设置。

merchant_personality常量在文件中的很多模板中都被用到。具有这个个性的队伍是友好的,不会出去攻击敌人或袭击弱旅。这是由于merchant_personality把队伍的好斗设成了aggresiveness_0。好斗为aggresiveness_0的队伍永远不会攻击其他队伍。普通战斗队伍的预设是soldier_personality(士兵个性),具有好斗值aggresiveness_8,这使他们攻击敌对阵营的队伍,除非他们的兵力过少。

勇气是决定队伍何时逃离另一个更大队伍的数值。高勇气值意味着他们兵力劣势时不会很快逃离。merchant_personality的队伍勇气值为8,soldier_personality为11。

这些设置值从0到15,允许你精确地为队伍模板设置期望的行为。mod制作新手最好坚持使用预设值。你的第一个mod中需要用到的个性数值它们都包括了。

最后,对于强盗模板,有一个banditness(强盗)标识。这促使强盗队伍始终视附近的队伍为鱼腩,如果这个鱼腩带着数量可观的钱和/或商品,则会被强盗队伍攻击。理想的情况下,强盗队伍好斗指数低或兵力不足,以至于他不会攻击士兵队伍。


4.3 创建新模板

复制"farmers" 元组并粘贴到文件的最后,后括号之前。

  ("new_template","new_template",icon_peasant|pf_auto_start_dialog,0,fac_innocents,merchant_personality,[(trp_farmer,11,22),(trp_peasant_woman,16,44)]),

这个例子中我们将id从"farmers"改为"new_template",名字也是同样。一旦做完这件事,就可以来编辑这个模板的细节了。

首先调整模板的阵营为fac_neutral(中立),把merchant_personality改为soldier_personality。


("new_template","new_template",icon_peasant|pf_auto_start_dialog,0,fac_neutral,soldier_personality,[(trp_farmer,11,22),(trp_peasant_woman,16,44)]),

现在开始,这个模板的队伍将是中立派,当看到敌人时会出击。

下面我们来玩玩兵种组合。


  ("new_template","new_template",icon_peasant|pf_auto_start_dialog,0,fac_neutral,soldier_personality,[(trp_geoffrey,1,1),(trp_new_troop,16,44)]),

这个例子有一个显著变化——最重要的一点是,我们在教程第三部分创建的英雄兵种trp_geoffrey现在领导这个队伍。由于不可能有一个以上的Geoffrey,我们将部队数量的最小最大值都设为1。

我们把第三部分中创建的常规兵trp_new_troop分配给他作为随军。这个队伍中的"new troops"数量永远不会低于16,但随着玩家的等级提升,这个数目将逐渐调整。最终这个"new_template"模板可以生成44数量的"new troops"——但是不会超过44。


保存文件并执行build_module.bat。如果一切顺利,就可以在mod代码中使用这个新模板了。但是依赖队伍模板的队伍必须被生成出来,因为它们不会自说自话地出现在游戏中。如果我们现在启动游戏,将不会看到我们的"new template"生成的队伍在游戏中到处跑。

在以后的教程中我们将学习如何生成模板的队伍。此刻让我们离开Geoffrey和他的小部队,在教程下一部分学习如何创建新物品吧。请继续看第五部分。


-----------------------------------------------------------------------------------------------------------

第五部分:Module_Items(物品模块)

第三、四部分中,我们学习了如何创建和装备新兵种和队伍模板。这个前提下,我们现在可以学怎样为部队创建新物品(item)。


5.1 解析Module_Items

module_items.py的开头部分是一些常量,用来管理物品的修饰语(modifier),修饰语是在游戏中调节物品用的。弯曲的长杆,有缺口的剑,重斧,这些都是由module_items的元组创建出来的,然后给它们一些适当的物品修饰语以调高或调低物品属性。

这里定义的常量由标准物品修饰语构成。你在商人那儿和战利品中找到的物品会从这些常量中随机添加修饰符。商人物品栏和战利品中的物品不会使用物品修饰符常量中没有列出的修饰符。

对于经验老道的mod制作者(modder)来说,下面这个事实非常有趣,即没有在相应常量中列出的修饰符仍然可以通过 "troop_add_item" 操作来使用。举个例子,长弓通常只有"plain"(普通的),"bent"(弯曲的)和"cracked"(破裂的)三种形式,但是如果我们给这个长弓加上修饰符 "balanced" (平衡的),并将其加入到玩家的物品栏,玩家就可以拿到一个平衡的长弓。


常量之后是元组的列表。我们感兴趣的第一个武器是"practice_sword"(练习剑),这是一个很好的范例。


一个物品的例子:

["practice_sword","practice_sword", [("practice_sword",0)], itp_type_one_handed_wpn|itp_melee|itp_primary|itp_secondary, itc_longsword, 3,weight(1.5)|spd_rtng(103)|weapon_length(90)|swing_damage(16,blunt)|thrust_damage(10,blunt),imodbits_none],

这是一个在禅达训练和竞技场格斗中使用广泛的基本练习武器。


元组字段解析:

1 ) 物品id。用来在其他文件中引用物品。
2 ) 物品名。出现在物品栏中的物品名字。
3 ) 网格(mesh)列表。网格是包含以下字段的元组:
    3.1) 网格名。游戏或mod资源文件中的3d模型名。
    3.2) 此网格对应的修饰语位(bit)。用此网格来代替默认网格的物品修饰语列表。
4 ) 物品标识。
5 ) 物品性能(capability)。这个字段包括了物品使用的一系列动画(animation)。
6 ) 物品价值。以第纳尔(denar)表示的基准值。注意:除非玩家的交易技能为10,物品在游戏中的实际价格将比这个基准值高许多。
7 ) 物品属性。这里定义了物品的参数值:重量、充裕度、难度、护甲等级等等。
8 ) 修饰语位(bit)。物品可供使用的修饰语。
9 ) [可选]触发器。与该物品关联的一组简单触发器。


观察Practice_sword(练习剑)元组:

1 ) 物品id = "practice_sword"
2 ) 物品名 = "practice_sword"
3 ) 网格(mesh)列表:
    3.1) 网格名 = "practice_sword"
    3.2) 修饰语位 = 0
4 ) 物品标识 = itp_type_one_handed_wpn|itp_melee|itp_primary|itp_secondary
5 ) 物品性能 = itc_longsword
6 ) 物品价值 = 3
7 ) 物品属性 = weight(1.5)|spd_rtng(103)|weapon_length(90)|swing_damage(16,blunt)|thrust_damage(10,blunt)
8 ) 修饰语位 = imodbits_none
9 ) 触发器 = 未设置


从这个元组中我们可以知道 "practice_sword" 的所有细节。

- 它使用 "practice_sword" 网格作为默认网格。装备"practice_sword"的部队从装备库中选择武器时将视"practice_sword"为主要近战武器,同时他们选择后备(次要)武器时也会考虑"practice_sword"。
(注意:目前次要武器的功能是含糊的,还不清楚它何时使用到甚至是否使用到。近战部队在战斗时肯定不会切换不同的近战武器。)
- 它具有itc_longsword常量中定义的所有动画,所以使用"practice_sword"和使用长剑一样。
- 它的重量为1.5千克,速度是103,武器长度是90。挥舞时的基准钝(blunt)器伤害为16,直刺时的基准钝器伤害为10。
- 它没有修饰语。


5.2 伤害类型

正如在元组中看到的,"practice_sword"发挥钝器伤害。显然这是因为它是练习武器,不过这却促使我们进一步看看M&B中的伤害类型。

首先是砍(cut)伤。砍伤代表着利刃的切割动作,如剑或斧。砍伤对没有护甲或轻甲的敌人有伤害奖励,但对重甲却有很大的伤害惩罚。砍伤若使敌人生命值减到0,则会杀死他。

下面是钝伤。钝伤代表着钝击效果,如钉头棒和锤。钝伤对重甲有50%的加成,但钝器通常比砍伤类武器短,总体伤害较小。钝伤的最大好处是在敌人生命值到0时能够被击昏,而是被杀死。击昏的敌人可被俘虏和作为奴隶出卖。冲刺的马也可以发挥钝伤。

最后,刺伤代表着箭、十字弓箭和类似武器的穿刺。刺伤对重甲有50%加成,但出于平衡其他武器类型的目的,刺伤通常总体伤害较小。刺伤若使敌人生命值减到0,则会杀死他。


5.3 创建物品

把"practice_sword"元组复制粘贴到文件底部,后括号之前。完成后把这个新元组的名字和id改为"new_mace"(新钉头棒)。

["new_mace","new_mace", [("practice_sword",0)], itp_type_one_handed_wpn|itp_melee|itp_primary|itp_secondary, itc_longsword, 3,weight(1.5)|spd_rtng(103)|weapon_length(90)|swing_damage(16,blunt)|thrust_damage(10,blunt),imodbits_none],

M&B物品系统的灵活性相当好,只要几个微小的调节就能使剑变为钉头棒。就这个"practice_sword"来说,它已经被设为钝伤,这使得我们的工作更简单了。

首先,我们把新钉头棒的物品性能从itc_longsword改成itc_scimitar(弯刀)。这使得我们的钉头棒失去了刺的能力,因为M&B的itc_scimitar常量并没有包含直刺的动画。


["new_mace","new_mace", [("practice_sword",0)], itp_type_one_handed_wpn|itp_melee|itp_primary|itp_secondary, itc_scimitar, 3,weight(1.5)|spd_rtng(103)|weapon_length(90)|swing_damage(16,blunt)|thrust_damage(10,blunt),imodbits_none],

接下来把物品的网格由"practice_sword"改成"mace_pear"。这是原版中没有用到的网格,所以这一步将使我们的新钉头棒具有崭新的模样。


["new_mace","new_mace", [("mace_pear",0)], itp_type_one_handed_wpn|itp_melee|itp_primary|itp_secondary, itc_scimitar, 3,weight(1.5)|spd_rtng(103)|weapon_length(90)|swing_damage(16,blunt)|thrust_damage(10,blunt),imodbits_mace],

这个例子中,我们按计划改变了网格,并把修饰语位从imodbits_none改成了imodbits_mace。这将让我们的新钉头棒具有文件头部imodbits_mace常量中定义的所有修饰语。

只要另外的两处修改我们就可以完成这个物品了。我们将要改变其砍伤,以及给它一个额外的物品标识。

观察:


["new_mace","new_mace", [("mace_pear",0)], itp_type_one_handed_wpn|itp_melee|itp_primary|itp_secondary|itp_unique, itc_scimitar, 3,weight(1.5)|spd_rtng(103)|weapon_length(90)|swing_damage(26,blunt)|thrust_damage(10,blunt),imodbits_mace],

正如你所见,我们把砍伤由16调到26,这使得我们的新钉头棒在战斗中更具危险性。特别地,我们给标识字段加入了itp_unique标识。具有itp_unique标识的物品不会在普通的战后战利品栏中出现。由于我们对这个钉头棒有其他打算,这个标识可以让玩家不会过早的得到它。

最后一步调节是把物品名从"new_mace"改为"Geoffrey's_mace"。然后,打开module_trooops.py并把Geoffrey的物品库里的itm_club改成itm_new_mace。

保存所有的文件,执行build_module.bat。

恭喜!你已经制造了一个崭新的物品,而且将其加入了兵种的装备库中。这个兵种是一个英雄,所以他的装备库中总是会有这个新钉头棒,而且总是会使用装备库中能够使用的最佳武器。

另一方面,常规兵种是从装备库中随机选择装备。正因为如此,大多数常规兵穿着各式各样的装备——否则他们会看起来一个样。


现在我们知道了如何创建新物品,可以看一下变化多端的属性值和研究它们的意思了。


5.4 物品属性(Stat)

这一节中你会看到全面的属性列表和其功能的明细。由于一些属性对不同物品类型有着不同意义,我们按照物品类型把列表归了类。

General(综合类)

abundance(充裕度)——百分比。
这个属性决定了物品出现在商人物品栏和战利品栏中的频率。100是基准,可以大于或小于100(最小是0)。

weight(重量)——千克。
用千克数定义了物品的重量。


itp_type_horse(马匹类)

body_armor(护甲)——值。
决定了马匹的护甲值和生命值。值越大意味着护甲和生命值越多。

difficulty(难度)——值。
决定了玩家骑这匹马所需要的骑术。

horse_speed(马匹速度)——值。
战场上的马匹速度。值越大马越快。

horse_maneuver(马匹操控)——值。
战场上的马匹灵活性。

horse_charge(马匹冲刺)——值。
决定了马匹撞上步兵时的输出伤害,以及冲撞后损失的速度。值越大,马给出的伤害越高,通过步兵堆时越艰难。


itp_type_one_handed_wpn(单手武器类)

difficulty(难度)——值。
使用该武器所需的最小力量(STR)值。如果力量达不到该值,就不能够使用它。

spd_rtng(速度)——值。
武器的挥砍和直刺攻击速度。

weapon_length(武器长度)——厘米。
厘米表示的武器长度。这个属性决定了游戏中武器可以够到的范围,与网格的大小无关。

swing_damage(砍伤)——值,伤害类型。
武器挥砍攻击时的基准伤害和伤害类型。

thrust_damage(刺伤)——值,伤害类型。
武器直刺攻击时的基准伤害和伤害类型。


itp_type_two_handed_wpn(双手武器类)

同itp_type_one_handed_wpn。


itp_type_polearm(长柄武器类)

同itp_type_one_handed_wpn。


itp_type_arrows(弓箭类)

weapon_length(武器长度)——厘米。
厘米表示的弓箭长度。

thrust_damage(刺伤)——值,伤害类型。
此类箭在弓的基准伤害之外的附加伤害,以及伤害类型。

max_ammo(最大箭数)——值。
每个格中的箭的数量。


itp_type_bolts(弩箭类)

同itp_type_arrows。


itp_type_shield(盾类)

hit_points(生命值)——值。
盾的基准生命值。

body_armor(护甲)——值。
盾每次被击到的伤害减免。

spd_rtng(速度)——值。
盾切换到防御模式的速度。

weapon_length(武器长度)——值。
盾的覆盖度。值越大,身体被盾覆盖的范围越大,保护身体免受弓箭伤害的作用越大。


itp_type_bow(弓类)

difficulty(难度)——值。
使用该弓所需的最小强弓点数。如果兵种没有达到该强弓点数,将不能装备它。

spd_rtng(速度)——值。
弓的装填速度。也就是说,弓弦颤动、装箭、再次拉弓的迅速程度。值越大装填的速度越快。

shoot_speed(射速)——值。
从该弓射出的箭的速度。值越大箭越快;注意,太快的箭可能会毫发无伤地穿过近距离的敌人。

thrust_damage(刺伤)——值,伤害类型。
弓输出的基准伤害和伤害类型。

accuracy(准确度)——百分比。
射出点刚好和瞄准点重合的几率。100意味着可能性为100%,较低的值会严重降低射中的几率。原版中的弓和弩未使用这个属性,但是可以添加之。


itp_type_crossbow(弩类)

difficulty(难度)——值。
使用该弩所需的最小力量(STR)值。如果力量达不到该值,就不能够使用它。

spd_rtng(速度)——值。
弩的装填速度。也就是说,弩弦颤动、装箭、再次拉弦的迅速程度。值越大装填的速度越快。

shoot_speed(射速)——值。
从该弩射出的箭的速度。值越大箭越快;注意,太快的箭可能会毫发无伤地穿过近距离的敌人。

thrust_damage(刺伤)——值,伤害类型。
弩输出的基准伤害和伤害类型。

max_ammo(最大箭数)——值。
再次装填之前可以射出的箭数。

accuracy(准确度)——百分比。
射出点刚好和瞄准点重合的几率。100意味着可能性为100%,较低的值会严重降低射中的几率。原版中的弓和弩未使用这个属性,但是可以添加之。


itp_type_thrown(投掷类)

difficulty(难度)——值。
使用该弓所需的最小强掷点数。如果没有达到该强掷点数,将不能装备它。

spd_rtng(速度)——值。
武器的装填速度。即准备下一个投掷物的迅速程度。

shoot_speed(射速)——值。
抛掷物的速度。

thrust_damage(刺伤)——值,伤害类型。
武器输出的基准伤害和伤害类型。

max_ammo(最大弹药数)——值。
每个格子的武器(即投掷物)数量。

weapon_length(武器长度)——值。
厘米表示的武器长度。


itp_type_goods(杂货类)

food_quality(食物品质)
食物对队伍士气的影响。高于50的食物被消费时会提高士气,低于50的会降低士气。

max_ammo(最大数量)
物品的可消费数量。


itp_type_head_armor(头盔类)

head_armor(护头)——值。
头盔保护头部免受伤害的数值。

body_armor(护身)——值。
头盔保护身体免受伤害的数值。

leg_armor(护腿)——值。
头盔保护腿部免受伤害的数值。

difficulty(难度)——值。
装备头盔所需的最小力量值。


itp_type_body_armor(护甲类)

同itp_type_head_armor。


itp_type_foot_armor(靴子类)

同itp_type_head_armor。


itp_type_hand_armor(臂铠类)

同itp_type_head_armor。


itp_type_pistol(手枪类)

difficulty(难度)——值。
装备枪械所需的最小力量值。

spd_rtng(速度)——值。
枪械的装填速度。即重新装填弹药并瞄准的速度。

shoot_speed(射速)——值。
从该枪械射出的子弹的速度。

thrust_damage(刺伤)——值,伤害类型。
枪械输出的基准伤害和伤害类型。

max_ammo(最大箭数)——值。
再次装填之前可以射出的子弹数量。

accuracy(准确度)——百分比。
射出点刚好和瞄准点重合的几率。100意味着可能性为100%,较低的值会严重降低射中的几率。


itp_type_musket(步枪类)

同itp_type_pistol。


itp_type_bullets(子弹类)

同itp_type_arrows。

-----------------------------------------------------------------------------------------------------------

第六部分:Module_Constants, Module_Factions, Module_Strings & Module_Quests(常量模块,势力模块,字符串模块和任务模块)

6.1 常量模块
module_constants.py是一个非常简单的文件。它由常量组成。经由阅读前三章的内容,你应该知道它们是如何工作的。在常量模块中的常量和定义在其他地方的常量完全相同,但是用在多个模块文件中的常量被定义在常量模块中以使MOD系统能够找到它们。

常量模块同时作为一个有组织的,易理解的列表,在其中你可以在你需要时改变常量的值。任何改动将会立刻影响所有使用此常量的操作,你可以不用手工寻找/改动。

例如,改变marnid_inn_entry = 2 到 marnid_inn_entry = 4 将立刻改变所有使用marnid_inn_entry的操作,将它的值由2变为4。

6.2 势力模块
module_factions.py包含了所有MOD系统和M&B人工智能使用的势力。我们注意这个小文件负责的如下几点:

文件由factions = [开始,和多数MOD文件一样,前面一小段属于系统指令,不要修改。

例如:
("innocents","Innocents", 0, 0.5,[("outlaws",-0.05)]),

"innocents"(平民)势力,它主要的敌人是 "outlaws"(歹徒)势力。如果其他势力和平民势力的关系没有定义,则自动设为0,表示两个势力间处于完全中立。
此段解析:
1)势力ID:用于在其他文件中对势力进行引用。           势力ID = "innocents"
2)势力名称                                           势力名称 = "Innocents"
3)势力标记                                           势力标记 = 0
4)势力一致性:一个势力中各个成员的关系。             势力一致性 = 0.5
5)势力间关系:每个关系是一个段,包含如下内容:       势力间关系:
  5.1)势力:相关势力名                                 势力 = "outlaws"
    5.2)关系:两势力间关系,取值范围-1~1                                   关系 = 0.05

这个势力没有标记,势利一致性为中,除了歹徒,没有其他敌对势力。可是,如果我们看看其他地方,我们可以发现(例如)在黑暗骑士段中定义了黑暗骑士和平民势力的关系是-0.9。这是由于关系对双方都起作用——它只需要设立一次,对双方势力都起作用。

现在,拷贝平民创建一个新势力,粘贴在列表最后。将势力ID和名称改为"geoffrey",转到关系列表中的"outlaws",将其改为"player_faction"。

保存。完成上面的工作之后,打开 module_party_templates.py 和 module_troops.py,改变"new_template"和部队"Geoffrey"的势力为"fac_geoffrey".。保存,关闭文件。电击build_module.bat。如果一切正常,可以看出"new_template"的势力变为了"Geoffrey",且对玩家怀有敌意。

6.3 字符串模块

module_strings.py是系统中最简单的文件。它包含了字符串——用于在各处显示的文本段。在MOD系统中各处都使用字符串,从module_dialogs到module_quests;无论何时想显示一段文字。字符串模块,存储了独立的字符串,不受单独文件的(使用)约束。

在游戏中,你可以看到这些字符串被用在了种种地方,如屏幕左下白色的滚动文字,或者在指南对话框中,甚者插入游戏菜单或者人物对话中。

字符串的例子:

("door_locked","The door is locked."),

解析:

1)字符串ID。用于在其它文件中引用字符串。
2)字符串文本。

字符串的一个显著特性是可以将寄存器值甚至另一个字符串放于其中。你可以经由在字符串中添加诸如{reg0}的部分来实现。在字符串中,这将显示reg(0)的当前值。{reg10}将显示reg(10)的当前值,诸如此类。

要在字符串内加入字符串,你必须使用字符串寄存器而不是普通寄存器;这是由于字符串是在寄存器中分开存储的。例如{s2}会显示字符串寄存器2的内容——而不是reg(2)的内容。你可以为不同用途同时使用字符串寄存器2和reg(2),他们不会交叉干涉彼此。

在对话中,可以根据人物的性别不同显示一部分(或全部)字符串的不同内容。例如,在字符串中插入{sir/madam}将会在人物是男性是显示“sir”,而在人物是女性时显示“madam”。

所有这些选项(除了根据性别改变对话)在一切字符串域中都工作正常。基于性别的改变只在对话中有效。

为了执行"display_message"(显示信息)的操作,可以加入在操作中加入一个二进制色彩编码来用多种颜色显示一个字符串。例如:(display_message,<string_id>,[hex_colour_code]),

这是用于display_message(显示信息)的可用二进制码列:
蓝       = 0xFFAAAAFF
亮蓝     = 0xFFAAD8FF
红       = 0xFFFFAAAA
黄       = 0xFFFFFFAA
粉       = 0xFFFFAAFF
紫       = 0xFF6AAA89
黑       = 0xFFAAAAAA
白       = 0xFFFFFFFF
绿       = 0xFFAAFFAA
棕       = 0xFF7A4800


6.4 任务模块
module_quests.py是最后一个小而简单的文件。它包含了任务,包括所有关于人物的文字。在这里添加新任务可以经由MOD系统使之运作,因此进程能够毒气任务现在的状态,并且将此状态作为一个条件操作。

例如:
("hunt_down_river_pirates", "hunt down river pirates", qf_show_progression,
  "Constable Hareck asked you to hunt down the river pirates that plague the country around Zendar.\
He promised to pay you 5 denars for each river pirate party you eliminate."
  ),

这个人物不用介绍了。我们都清除过水贼。有趣的一点是标记qf_show_progression,它使用一个斜杠——\——来允许字符串换行。记住,使用斜杠不会在游戏显示的文本中换行。使用它只是为了程序整洁。

段解析:
1)任务ID。用于其他文件中引用                                           任务ID = "hunt_down_river_pirates"
2)任务名称。在任务界面左边的任务列表中显示任务名称。                   任务名称 = "hunt down river pirates"
3)任务标记。                                                           任务标记 = qf_show_progression
4)任务描述。任务的详细描述,在任务界面中右边的大文本框中显示。         任务描述 =  "Constable Hareck asked you
                                                                                                                                                 hunt down the river pirates that plague the country around Zendar.He
                                                                                                                                                 promised to pay you 5 denars for each river pirate party you eliminate."

qf_show_progression给予了任务完成度,显示在任务列表的右边。任务列表在任务界面的左边。这个百分数不会自动增长;如果你想使用它,你需要通过操作设置之。

如果你需要为任务设置标记,只需在标记位置上写入0。

现在,我们已经初步建立了我们的小任务。拷贝粘贴下面一段在Python列表底部。

("speak_with_troublemakers", "Parley with the troublemakers", 0,  "Constable Hareck asked you to deal with a bunch of young nobles making trouble around town. How you want to tackle the problem is up to you; Hareck has promised a purse of silver if you succeed."
),

注意这段的构造。你可以看到,只进行了一点修改就建立一个新任务,但是把它融入游戏中还有很多工作要做。因此,我们下一个目标是module_dialogs.py——我们将在那里放置我们要使用的新任务,部队和队伍模版。

保存,关闭文件,点击build_module.bat。如果一切正常,我们现在可以进入第七部分。


-----------------------------------------------------------------------------------------------------------

第七部分:Module_Dialogs(对话模块)

这一部分我们来学习module_dialogs.py,这是目前为止mod系统中最大最重要的文件之一,包含了骑马与砍杀中所有的对话。此文件中包括了任何你想创建的新对话,文件最后也包括了对话占位符,供没有对话的兵种使用。


7.1 Module_Dialogs(对话模块)解析

文件开头是一个Python列表,之后是游戏中的第一段对话——Constable Hareck(康斯特)的水贼公告。需要记住的一点是,游戏从头到尾扫描module_dialogs,一旦读到第一个符合情形的文本台词即使用这一台词。不同情形下的文本台词都有所不同,无论是地图上遇到一个队伍还是在游戏场景中和NPC聊天。

你将发现每一个对话台词都是一个独立元组,唯一联系它们的是对话声明(dialog-states),这个我们马上就会学习到。

一个对话元组的例子:

  [trp_constable_hareck,"start", [], "What do you want?", "constable_hareck_talk",[]],

这个简单元组很适合做我们的例子。它做了所有对话台词该做的事,在游戏场景中和Constable搭话会打开这个对话,显示"What do you want?"(你想要什么?),然后在点击鼠标时跳到名为"constable_hareck_talk"的对话起始声明。

元组字段解析:

1 ) 对话对象。这要和与玩家对话的人物对应。
2 ) 对话起始(starting)声明。决定了台词的打开方式。
3 ) 条件块(block)。游戏取舍这个台词的一组条件,必须是一个合法的操作块。
4 ) 对话文本。实际显示在游戏中的对话。
5 ) 对话结束(ending)声明。决定了此对话结束后发生什么。
6 ) 结果块。玩家点击这个对话后执行的操作,必须为一个合法的操作块。

观察Hareck元组:
Constable Hareck example tuple examination:

1 ) 对话对象 = trp_constable_hareck
2 ) 对话起始声明 = "start"
3 ) 条件块 = []
4 ) 对话文本 = "What do you want?"
5 ) 对话结束声明 = "constable_hareck_talk"
6 ) 结果块 = []

对话声明,是这个元组中最需要说明的,这在本节的开始已经提及过。现在我们将更深入地研究它们。

对话结束声明 ("constable_hareck_talk")把一个对话从一句台词引导到另一句。你可以任意指定对话结束声明,但必须有一个对话起始声明和它相对应。例如,如果我们想要写一个结束声明为"blue_oyster"的元组,这将会引导到起始声明为"blue_oyster"的元组。必须要有一个贴切的配对,否则,build_module.bat在生成时会抛出错误。

如果有多个元组的起始声明为"blue_oyster",则会发生特别的事情。如果是玩家说的话,则会出现一个菜单供玩家选择;如果是NPC说的,mod系统会使用符合条件的第一个元组,而不管是否有多个元组符合条件。

要结束对话,必须使用"close_window"(关闭窗口)这个对话结束声明。

有数个特殊的对话起始声明供你选择,来启动一个对话。我们把这些称为对话初始(initial)声明。下面是对话初始声明的完整列表:

"start" —— 当在游戏场景中,和NPC对话或者触发一个对话时使用。
"party_encounter" —— 当在大地图上遭遇另一个队伍时使用。
"party_relieved" —— 当玩家在大地图上援助一个战斗队伍并获胜后使用。
"prisoner_liberated" —— 当玩家战胜了拥有英雄俘虏的敌方队伍时使用。
"enemy_defeated" —— 当玩家战胜了由英雄领导的敌方队伍时使用。
"event_triggered" —— 当在游戏场景之外触发对话时使用。
"member_chat" —— 当与本队伍的成员对话时使用。
"prisoner_chat" —— 当与本队伍的俘虏对话时使用。


正如你所看到的,每个对话初始声明都是为特定场合设计的,不会在其指定场合之外使用。

我们的范例元组通过和禅达的Constable对话来启动,所以它的对话初始声明为"start"。


7.2 需求和条件块

对话接口的柔性很好,可以有许多种使用方法。它处理着大地图和游戏场景中的事件,让你能够随时触发对话。

接下来我们来讨论怎样最大限度地使用对话,然后学习如何自己创建复杂的对话。


上一节中我们提到,对话台词只有在所有条件都符合时才被使用到。首先,玩家必须和正确的部队说话,如果玩家和trp_ramun_the_slave_trader对话,将不会调用trp_constable_hareck的台词。如果和玩家对话的所有人都会说某句台词,可以使用常量anyone。

其次,对话起始声明必须保持一致的场合——要么是由一个对话初始声明启动的,要么是从另一个台词的对话结束声明导入的。系统不会使用不符合规范的对话起始声明。

第三,条件块也遵循以上两条逻辑。系统只会使用满足所有条件的对话台词。只要条件块或对话起始状态有一个是错的,就会发生问题,有可能build_module.bat抛出错误,也有可能游戏中的错误台词定死在那里,因为找不到对应的对话结束声明元组。

这就是你必须谨慎处理条件块的原因。确保你没有给你的对话设置错误的条件块,否则会使对话崩溃。

当然,如果一切都能协调工作,条件块将是非常强大的。条件块可以包含尝试(try)语句。你可以调用条件块内部的一个项,然后得到同一个条件块中的条件操作的结果,可以设置寄存器和字符串寄存器供实际对话使用。教程这一部分依次教你做这些工作。

你可以看到module_dialogs的第一个元组中关于条件块的使用。

  [trp_constable_hareck,"start", [(eq,"$constable_announcement",0)]

这个块仅包含一个条件,即要求"$constable_announcement"变量等于0,这是由于新游戏开始时所有寄存器和变量都将置为0。元组后端是这样的:

  "constable_hareck_introduce_1",[(assign,"$constable_announcement",1)]]

这一段中的assign(赋值)操作将在台词显示后把"$constable_announcement"变量置为1。也就是说,这个台词一旦被显示,将永远不会再次出现,因为之后"$constable_announcement"变量将不再等于0。对话系统会忽略这句台词,跳到符合条件的另一个元组:

  [trp_constable_hareck,"start", [], "What do you want?", "constable_hareck_talk",[]],

这个台词的唯一条件是玩家在场景中与Constable说话。Constable做完介绍后,每次在禅达镇同他说话都会调用这句台词。


7.3 添加新对话

我们终于有机会给我们的新兵种Geoffrey设定独一无二的对话了。像module_troops一样,你可以module_dialogs的列表底部添加一些元组。module_dialogs的Python列表末端,包括了针对所有人触发的对话占位符,由于文件是从头到尾扫描匹配台词的,所有在对话占位符之后添加的东东都将被忽略。

建议你在此注释的上方添加新对话:

# Random quests
######################################################


我们的第一个目标是为Geoffrey创建介绍语。在创建完整任务之前,我们将从这里获得一点经验。

复制粘贴下面的元组到module_dialogs的合适位置:

  [trp_geoffrey,"start", [], "What? What do you want? Leave me be, {sir/wench}, I have no time for beggars.", "geoffrey_talk",[]],

这是我们对话中的第一个元组。它由对话初始声明"start"启动,是Geoffrey说的,引导到名为"geoffrey_talk"的对话起始声明。mod系统会根据玩家的性别显示'sir'或者'wench'。

下面我们来创建一条紧跟着的台词,是玩家说的。复制粘贴下面的元组到刚才的元组下方:

  [trp_geoffrey|plyr,"geoffrey_talk", [], "Nothing, never you mind.", "close_window",[]],

这条台词是玩家说的,紧接着刚才第一条台词之后。由于对话结束声明为"close_window",这条台词显示完毕后整个对话就会结束。

我们之前说过,添加多个具有相同对话起始声明的元组,游戏中将会显示一个选项菜单供玩家选择。记住,这个特性只对玩家的台词有效,对其他兵种都无效。复制粘贴以下元组:

  [trp_geoffrey|plyr,"geoffrey_talk", [], "And who are you supposed to be?", "geoffrey_talk_2",[]],
  [trp_geoffrey,"geoffrey_talk_2", [], "Why, I'm Geoffrey Eaglescourt, son of the Baron Eaglescourt! Leader of the Red Riders, bane of bandits, and crusher of pirates!", "geoffrey_talk_3",[]],
  [trp_geoffrey|plyr,"geoffrey_talk_3", [], "Oh, I see. And how many pirates have you killed?", "geoffrey_talk_4",[]],
  [trp_geoffrey,"geoffrey_talk_4", [], "See for yourself! I scalp every one of the dogs I kill. They are my battle trophies.", "geoffrey_talk_5",[]],
  [trp_geoffrey|plyr,"geoffrey_talk_5", [], "That's nice. I'll be going now.", "close_window",[]],

这是一段Geoffrey和玩家之间的完整的来回对话。目前为止没有重要事件发生,但是我们马上就要为之增加一点变化。为这段对话加入以下两个元组:

  [trp_geoffrey|plyr,"geoffrey_talk_5", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",0)], "Really? Those scalps look suspiciously like horse tails to me.", "geoffrey_hostile",[]],

  [trp_geoffrey|plyr,"geoffrey_talk", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",0)], "You look familiar. Haven't I seen your face in a pigsty before?", "geoffrey_hostile",[]],

这两条台词会分别为起始声明为"geoffrey_talk_5"和"geoffrey_talk"的对话增加菜单项。技术上,具有相同对话起始声明的元组所放的位置是无关紧要的。对话系统会找到所有满足条件的元组并加到游戏菜单中。不过,出于代码直观性和可读性的考虑,你应当试着把相同菜单的元组放在一起。

这些台词中最显著的特点是条件块。只有在"qst_speak_with_troublemakers"任务被激活并且"$geoffrey_duel"等于0时,这两条台词才会显示。现在复制粘贴最后的这些元组:

  [trp_geoffrey,"geoffrey_hostile", [], "What?! I'll see you dead for that insult, peasant! Don't you know who I am?", "geoffrey_hostile_2",[]],
  [trp_geoffrey|plyr,"geoffrey_hostile_2", [], "No . . . Was I supposed to remember?", "geoffrey_hostile_3",[]],
  [trp_geoffrey,"geoffrey_hostile_3", [], "Why, I'm Geoffrey Eaglescourt, son of the Baron Eaglescourt, and you have delivered the gravest insult to my family's honour! I demand satisfaction! Meet me outside the town walls tonight, or you will be known a coward to every man in this countryside. Good day, {sirrah/wench}.", "geoffrey_hostile_4",[]],
  [trp_geoffrey|plyr,"geoffrey_hostile_4", [], "Charming lad. Tonight, eh . . . ? I shouldn't miss it . . .", "close_window",[(assign,"$geoffrey_duel",1)]],


这些做完后,我们现在就有了一段对话,它具备了任务相关的条件和结果,以及一个预先创建的任务(quest)元组。不过我们首先需要建立激活任务的一组条件。下一节中我们就来做这个。

7.4 对话和任务

当添加新台词到已有对话中时,我们必须非常小心,保证新元组能和已有的协调好。记住,对话文件是从头至尾扫描的;记住检查你的条件块;记住仔细检查语法。一个拼写错误可以搞毁整块代码。经常编译生成你的mod,从而尽早捕获拼写错误。

首先我们要为Geoffrey添加一个元组,加到他的其他台词之前。这意味着它会在其他台词之前使用到。如果这个元组满足条件,则只会使用这个元组,而不管其他台词是否也满足条件。

当前,Geoffrey的第一个元组是:

  [trp_geoffrey,"start", [], "What? What do you want? Leave me be, {sir/wench}, I have no time for beggars.", "geoffrey_talk",[]],


现在,在这个元组的上方复制粘贴下面的元组:

  [trp_geoffrey,"start", [(eq,"$geoffrey_duel",1)], "Begone! I have nothing to say to you, varlet.", "close_window",[]],


放好这个元组后,Geoffrey一旦向你发出决斗挑衅("$geoffrey_duel"为1),则通常的对话将不再显示。通过这个途径,你就可以根据形势改变对话内容,创建包含条件块的变化多样的对话片段。

Constable最后的的对话元组是:

  [trp_constable_hareck|plyr,"constable_hareck_talk", [], "Nothing. Good-bye.", "close_window",[]],


"constable_hareck_talk"是一个多选项的对话菜单声明,刚才的元组是其中的最后一个选项。要在这个选项的上方显示另一条对话选项,必须在这个元组的上方添加一个新元组。

在刚才的元组的上方复制粘贴下面的元组:

  [trp_constable_hareck|plyr,"constable_hareck_talk", [(neg|check_quest_active,"qst_speak_with_troublemakers"),(neg|check_quest_finished,"qst_speak_with_troublemakers")], "Is something wrong? You look worried.", "constable_hareck_troublemakers",[]],


只有"qst_speak_with_troublemakers"任务没有被激活和没有完成时,这个元组才会被显示出来。这是因为否定前缀 neg| 当条件不满足时使结果值为真。eq 操作符要求两个值相等,neg|eq 要求值不相等。

现在,在这个新元组的正下方复制粘贴:

  [trp_constable_hareck,"constable_hareck_troublemakers", [], "Oh, it's nothing, just . . .", "constable_hareck_troublemakers_2",[]],
  [trp_constable_hareck|plyr,"constable_hareck_troublemakers_2", [], "You can tell me, sir.", "constable_hareck_troublemakers_3",[]],
  [trp_constable_hareck,"constable_hareck_troublemakers_3", [], "No harm in it, I suppose. The trouble is, a few of the town's young nobles . . . spoiled dandies and fops, the lot of them . . . they've decided that suddenly they're men to be respected, and that they should 'take matters into their own hands', to 'take action where the official government has failed'. They say they're going to kill all the river pirates that have been troubling Zendar of late. Of course, they've not actually gone out to fight any river pirates, but they've been making a great ruckus in town and there's not a thing I can do about it.", "constable_hareck_troublemakers_4",[]],
  [trp_constable_hareck|plyr,"constable_hareck_troublemakers_4", [], "Hmm . . . Would there be a reward for solving this problem?", "constable_hareck_troublemakers_5",[]],
  [trp_constable_hareck,"constable_hareck_troublemakers_5", [], "What? What are you saying?", "constable_hareck_troublemakers_6",[]],
  [trp_constable_hareck|plyr,"constable_hareck_troublemakers_6", [], "Nothing, sir. However, it sounds to me like a neutral third party might be just what you need. I could talk to them.", "constable_hareck_troublemakers_7",[]],
  [trp_constable_hareck,"constable_hareck_troublemakers_7", [], "Heh. Well, you can try, friend. If you manage to do any good, I'll even throw in a few coins for getting the sand out of my breeches. Their leader is a boy named Geoffrey, spends most of his time on watered-down ale and whores in the Happy Boar. Chances are you'll find him there.", "constable_hareck_troublemakers_8",[]],
  [trp_constable_hareck|plyr,"constable_hareck_troublemakers_8", [], "Thank you, constable. I shall return.", "close_window",[(setup_quest_text,"qst_speak_with_troublemakers"),(start_quest,"qst_speak_with_troublemakers")]],

  [trp_constable_hareck|plyr,"constable_hareck_talk", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",2)], "Constable, I've taken care of the toublemakers for you. They shouldn't be a worry any longer.", "constable_hareck_troublemakers_10",[]],
  [trp_constable_hareck,"constable_hareck_troublemakers_10", [], "Truly? Thank God! A few more days and I would've thrown them all into a cell and thrown away the key. Here, take this. You've earned it.", "constable_hareck_troublemakers_11",[(troop_add_gold,"trp_player",100),(add_xp_as_reward,750),(succeed_quest,"qst_speak_with_troublemakers")]],
  [trp_constable_hareck|plyr,"constable_hareck_troublemakers_11", [], "My pleasure, constable. If you've any other jobs that need doing, please let me know. Farewell.", "close_window",[]],

  [trp_constable_hareck|plyr,"constable_hareck_talk", [(check_quest_active,"qst_speak_with_troublemakers"),(eq,"$geoffrey_duel",3)], "Constable, I failed. I'm sorry.", "constable_hareck_troublemakers_15",[]],
  [trp_constable_hareck,"constable_hareck_troublemakers_15", [], "Oh . . . Oh well. I suppose you did the best you could. Thanks anyway, friend. Perhaps some other job will suit you better. I shall let you know when I have any. Farewell.", "close_window",[(fail_quest,"qst_speak_with_troublemakers")]],

第一块代码设置了任务,详述了任务内容和如何开始这个任务。第二块代码以一些赏金和经验点数来结束这个任务,一旦Geoffrey被击败,"$geoffrey_duel"变量则设为2,任务即完满结束。第三块代码当玩家被Geoffrey打败时结束这个任务,意味着玩家将得不到任何奖励。


正如你看到的,为了覆盖一个任务的方方面面,需要做不少事情。我们的任务只完成了一半,不过所有的对话已经放置好了,现在准备进入教程下一部分。


-----------------------------------------------------------------------------------------------------------

第八部分:触发器(trigger)

现在我们有了不少使用MOD系统的经验,可以转入一些更加复杂的部分:触发器。触发器是基于时间的操作模块,可以每隔一段

时间间隔或者在特定场合被激活。所有在触发器操作模块中的操作都会被执行。

8.1 简单触发器模块(module_simple_triggers )剖析

目前,简单触发器模块只有一个功能:当在大地图上将遭遇另外一支部队时将玩家指向一个特定的菜单。为了实现这个效果,它

的Python列表之包含一个长段。

  (ti_on_party_encounter,
   [
       (store_encountered_party, reg(1)),
       (store_encountered_party2,reg(2)), # encountered_party2 is set when we come across a battle or siege,

otherwise it's a minus value
       (try_begin),
         (lt, reg(2),0), #Normal encounter. Not battle or siege.
         (try_begin),
           (ge, reg(1), towns_begin),
           (lt, reg(1), towns_end),
           (jump_to_menu, "mnu_town"),
         (else_try),
         (else_try),
           (ge, reg(1), castles_begin),
           (lt, reg(1), castles_end),
           (jump_to_menu, "mnu_castle"),
         (else_try),
           (eq, reg(1), "p_zendar"),
           (jump_to_menu, "mnu_zendar"),
         (else_try),
           (eq, reg(1), "p_salt_mine"),
           (jump_to_menu, "mnu_salt_mine"),
         (else_try),
           (eq, reg(1), "p_four_ways_inn"),
           (jump_to_menu, "mnu_four_ways_inn"),
         (else_try),
           (eq, reg(1), "p_dhorak_keep"),
           (jump_to_menu, "mnu_dhorak_keep"),
         (else_try),
           (eq, reg(1), "p_training_ground"),
           (jump_to_menu, "mnu_training_ground"),
         (else_try),
           (store_current_hours, reg(7)),
           (le, reg(7), "$defended_until_time"),
           (assign, "$defended_until_time", 0),
           (jump_to_menu, "mnu_in_castle_under_attack"),
         (end_try),
       (else_try), #Battle or siege
       (try_end)
    ]),

这是列表中第一个也是唯一的触发器。他的结构很简单,只包含两个分开的域。

段的各个域剖析:
1)检测间隔:触发器检测的时刻或者检测频率。
2)操作块:这必须是有效的操作块。参照 header_operations.py 。

段检测:
1)检测间隔 = ti_on_party_encounter
2)操作块:将会进行检测的操作块。

由ti_on_party_encounter我们可以看出,当队伍在大地图上遇到了另一支队伍的时候,检测此触发器。我们现在将分别突出此

段各个部分来检查它的作用。

第1部分:
       (store_encountered_party, reg(1)),
       (store_encountered_party2,reg(2)), # encountered_party2 is set when we come across a battle or siege,

otherwise it's a minus value

1)"store_encountered_party"找出玩家遇到了何种队伍,并且将队伍的ID存放在寄存器reg(1)中。
2)"store_encountered_party2"完成同样的功能,存储第二个遭遇的队伍到reg(2)中。显然这只有在玩家遇到了一个战斗或者

攻城战时才会发生。如果没有第二支部队,reg(2)的值为0。

第2部分:
       (try_begin),
       (lt, reg(2),0), #Normal encounter. Not battle or siege.

条件(lt, reg(2),0)需要reg(2)小于0。由于reg(2)在玩家遇到一个正在进行的战斗之前一直小于0,这个操作块经继续第3部分

的内容。
这一部分中有趣的内容是操作(try_begin)。此操作打开一个尝试操作(try operation),它的功能和一般操作块相似,除了

一点不同。如果一个尝试操作有一个条件没有满足,并不取消其他操作块全部;它只取消到最近的(try_end)。所有(try_end)之

后的操作正常执行。事实上,一个尝试操作如同操作块中的一个独立操作块一样。

同样,代码中也存在另外的尝试操作(else_try),可以将其插入与一个主动尝试操作中,如同在module_simple_triggers中的一

样。一个else_try块的内容只有在所有else_try之前的尝试操作(包含之前的else_trys)的条件都不能被满足的时候才有可能

被执行。

第3部分:
         (try_begin),
           (ge, reg(1), towns_begin),
           (lt, reg(1), towns_end),
           (jump_to_menu, "mnu_town"),
这部分以一个在尝试操作中的尝试操作开始。如果reg(1)——遭遇到的队伍——在常量towns_begin ("p_town_1"在

module_parties.py中)和 towns_end ("p_salt_mine"在module_parties中)(wander注:这两个之间的表示是城市,begin和end

分别是上下界)之间,这部分将被执行。如果满足条件,玩家将会被转到 "mnu_town"菜单中。反之,如果条件不满足,则转入

第4部分。

第4部分:
         (else_try),
           (ge, reg(1), castles_begin),
           (lt, reg(1), castles_end),
           (jump_to_menu, "mnu_castle"),
这部分只有当上面的尝试操作失败了才可能被执行(注:其实就是if——else格式,说得那么麻烦),只有当遇到的不是城镇的

时候才会被考虑到。因此,已经认定了遇到的不是城镇,这一部分检测遇到的对象是否在castles_begin ("p_castle_1")和

castles_end ("p_river_pirate_spawn_point")之间。如果尝试操作成果,玩家将会被转到"mnu_castle".菜单。否则转入第5部

分。

第5部分:
         (else_try),
           (eq, reg(1), "p_zendar"),
           (jump_to_menu, "mnu_zendar"),
已知遇到的对象并非城镇或城堡,这个尝试操作检测是否遇到的是"p_zendar"(禅达)。如果是,此尝试操作将使玩家转入

"mnu_zendar"(禅达对应的菜单,下同)。否则转入第6部分。


第6部分:
         (else_try),
           (eq, reg(1), "p_salt_mine"),
           (jump_to_menu, "mnu_salt_mine"),
如果遇到"p_salt_mine"(盐矿),转入"mnu_salt_mine"。

第7部分:
         (else_try),
           (eq, reg(1), "p_four_ways_inn"),
           (jump_to_menu, "mnu_four_ways_inn"),
如遇到 "p_four_ways_inn"(四方客栈),转入"mnu_four_ways_inn"。

第8部分:
         (else_try),
           (eq, reg(1), "p_dhorak_keep"),
           (jump_to_menu, "mnu_dhorak_keep"),
如遇到 "p_dhorak_keep"(dhorak哨所),转入"mnu_dhorak_keep"。

第9部分:
         (else_try),
           (eq, reg(1), "p_training_ground"),
           (jump_to_menu, "mnu_training_ground"),
如遇到 "p_training_ground"(训练场),转入"mnu_training_ground"。

第10部分:
         (else_try),
           (store_current_hours, reg(7)),
           (le, reg(7), "$defended_until_time"),
           (assign, "$defended_until_time", 0),
           (jump_to_menu, "mnu_in_castle_under_attack"),
这一部分将游戏开始后经历的时间存入reg(7)中,检测reg(7)是否小于等于变量"$defended_until_time"。如果满足条件的话,

将"$defended_until_time"置0,将玩家转入"mnu_in_castle_under_attack"菜单。否则转入第11部分。

第11部分:
        (end_try),
这部分标记了第二个尝试操作的结束。第一个尝试操作继续进行,不论第二个尝试操作发生了什么。

第12部分:
       (else_try), #Battle or siege
       (try_end)
这个else_try操作包含了所有遇到战斗中的两支部队的情况。由于现在他没有其他操作,结果将遭遇到战斗——就如我们在第二

部分创造的"new_town",它还没有指向菜单的操作,也没有pf_auto_start_dialog的标记。我们将在下一节中对其进行调整。

8.2 编辑队伍遭遇触发器为了让一个城镇工作,我们需要确保每次遇到这个城镇都将玩家转入正确的游戏菜单。这留给我们两个

选择:使用现有的菜单如mnu_town,或者我们建立一个新的。

我们将为我们的新城镇建立一个自定义的菜单,之后我们会解释新建立的城镇如何使用原有的城镇菜单。

首先,所有更多的队伍遭遇触发器条目需要添加在第10部分前面:
         (else_try),
           (store_current_hours, reg(7)),
           (le, reg(7), "$defended_until_time"),
           (assign, "$defended_until_time", 0),
           (jump_to_menu, "mnu_in_castle_under_attack"),

如果你在此后添加了任何条目,则在午夜遇到你的新队伍时会出现问题。确保这不会发生,我们将把我们的新条目放在这之前,

第9部分之后:
         (else_try),
           (eq, reg(1), "p_training_ground"),
           (jump_to_menu, "mnu_training_ground"),

在第9、10两部分间留出一些空白。之后我们将开始建立新条目。

所有在这个触发器中的新条目都必须以else_try开始。之后,我们可以添加条件来判断何时此条目被不被触发。然后,如果所有

条件都满足的话,我们可以添加执行内容,如将玩家转入游戏菜单。执行内容可以是多种多样的,不止是jump_to_menu,但它们

必须都是有效的操作。

拷贝粘贴第9部分(训练场条目)进我们建立的新空间。此后将 "p_training_ground"改

为"p_new_town","mnu_training_ground"改为"mnu_new_town"。

现在为了让我们的新城镇可操作所要做的只是给它建立一个菜单。在我们继续到module_game_menus之前,让我们去了解触发器

的许多其他功能。打开module_triggers.py,转入下一段。


8.3 触发器模块解析
M&B的MOD系统中有两种触发器:简单触发器和扩展触发器。扩展触发器在module_triggers.py中。它和简单触发器的工作原理相

同,但有一些其他选项。

(0.1, 0, ti_once, [(map_free,0)], [(tutorial_box,"str_tutorial_map1")]),

这个文件中的第一个触发器很简单易懂。它是玩家第一次出现在地图上时弹出地图教程的触发器。

解析:
1)检测间隔:检测触发器的间隔时间
2)延迟间隔:在所有条件满足之后,确认触发器执行内容之前的时间长短。
3)重新可用间隔:在执行一次触发器内容之后下次触发器变为可激活的时间。
4)条件块:必须是有效操作段。如果条件块为空,则它一直被满足。即这个触发器一直触发。
5)执行块:(Consequences block直译:结果块)。必须是有效操作段。

1)检测间隔 = 0.1
2)延迟间隔 = 0
3)重新可用间隔 = ti_once
4)条件块 = (map_free,0)
5)执行块 = (tutorial_box,"str_tutorial_map1")

我们可以看出这个触发器每隔0.1小时游戏时间被检测一次;没有延迟间隔;由于设置了ti_once而不可重用。这意味着:如果所

有此触发器的条件被满足,此触发器只执行一次,不再执行。因此,这个触发器在玩家被放到大地图任何地方的时候被触发。

现在我们可以设计自己的触发器了。拷贝教程触发器并将之粘贴在Python列表最下端。
下面我们要对这个新触发器做的是在大地图上生成另一支队伍;叫做Geoffrey的队伍。替换触发器条件块(map_free,0)如下:
(eq,"$geoffrey_duel",1),
(store_time_of_day,reg(1)),
(gt,reg(1),19),

如果你记得我们在教程的第七部分中做的,当你同Geoffrey的对话进行到决斗时,我们将变量"$geoffrey_duel"赋值为1。因此

,(eq,"$geoffrey_duel",1)检测Geoffrey是否向你提出了挑战。如果没有,这个条件将不被满足。

接下来的操作是存储当前在一天中的时间到reg(1)中,检测reg(1)是否大于19;即一天中的时间是否在19:00之后。如果是,条

件满足,触发器被触发。

现在,替换执行块中的 (tutorial_box,"str_tutorial_map1") 为下面的操作:
(set_spawn_radius,0),
(spawn_around_party,"p_zendar","pt_new_template"),
(assign,"$geoffrey_party_id",reg(0)),
(party_set_ai_behavior,"$geoffrey_party_id",ai_bhvr_track_party),
(party_set_ai_object,"$geoffrey_party_id","p_main_party"),

第一个操作设置了生成(部队的)半径;每次你要生成一个队伍的时候你都有做这个,否则队伍将会在最近一次设置的半径中生

成,这是不可预知的。

下一个操作在("p_zendar")附近生成了一个队伍模版("pt_new_template")。这项操作也将生成队伍的ID存入reg(0)中。接下来

我们将这个存入reg(0)中的ID存入一个变量里,这样在reg(0)被改写的时候这个数据不会丢失。

最后,我们设置队伍的AI行为和目标。这些操作很简单。我们使"$geoffrey_party_id"跟随 "p_main_party"。当你设置一个队

伍跟随另一个队伍时,它将无情地追赶另一个队伍并且攻击它,不考虑所属势力或其他因素。

保存。现在你还不能编译,由于我们还要为我们在8.2节中建立的队伍遭遇(party encounter)建立一个菜单。我们接下来将做这

个。现在我们准备好进入教程的第9部分。

-----------------------------------------------------------------------------------------------------------

第九部分:Module_Game_Menus(菜单模块)

我们要关注的下一个mod文件是module_game_menus.py,包括了M&B中的所有游戏菜单。游戏菜单是遇到队伍和跳入新场景之间的过渡,当然它还有其他许多用途。游戏内的通道系统的部分功能也使用到了菜单。

9.1 Module_Game_Menus 解析

这个文件的开头是它的Python列表,然后列出了游戏人物创建时的两个菜单,即玩家的性别选择和阶层选择。这有相当的复杂度,所以我们跳过去,看一个稍简单的例子。

一个游戏菜单的例子:

    (
    "salt_mine",mnf_auto_enter,
    "You enter the salt mine.",
    "none",
    [(reset_price_rates,0),(set_price_rate_for_item,"itm_salt",55)],
    [
      ("enter",[],"Enter.",[[set_jump_mission,"mt_visit_town_horseback"],[jump_to_scene,"scn_salt_mine"],[change_screen_mission]]),
      ("leave",[],"Leave.",[[leave_encounter],[change_screen_return]]),
    ]
  ),

这个菜单处理了你进入盐矿(salt_mine)的情形。除了mnf_auto_enter标识外,此菜单相当直观。当条件都满足时,具有mnf_auto_enter标识的菜单会自动激活第一个菜单项,而不是等待用户输入。就这个盐矿的例子来说,这个菜单项是"enter"(进入)。

元组字段解析:

1) 菜单id。用于在其他文件中引用。
2) 菜单标识。
3) 菜单文本。玩家将在菜单窗口中看到这些文本。
4) 网格名。目前未使用,必须为字符串"none"。
5) 操作块。菜单被激活时所执行的一系列操作。必须为合法操作块。
6) 菜单项列表。包括下列字段:
   6.1) 菜单项id。用于在其他文件中引用。
   6.2) 条件块。每个菜单项都会执行这些条件,来决定该不该对玩家显示这一菜单项。必须为合法条件块。
   6.3) 菜单项文本。菜单项中实际可点击的文本。用一个下划线(_)来代替菜单项文本,可以简单地将这项变为不可点击。
   6.4) 结果块。结果只为玩家点选的菜单项执行。必须为合法操作块。

观察盐矿元组:

1) 菜单id = "salt_mine"
2) 菜单标识 = mnf_auto_enter
3) 菜单文本 = "You enter the salt mine."
4) 网格名 = "none"
5) 操作块 = (reset_price_rates,0),(set_price_rate_for_item,"itm_salt",55)
6) 菜单项列表:
   6.1) 菜单项id = "enter", "leave"
   6.2) 条件块 = Block contains no conditions.
   6.3) 菜单项文本 = "Enter.", "Leave."
   6.4) 结果块 = [set_jump_mission,"mt_visit_town_horseback"],[jump_to_scene,"scn_salt_mine"],[change_screen_mission],
                 [leave_encounter],[change_screen_return]

此菜单做的第一件事是把所有物价恢复成正常,然后把盐价设成正常值的55%。然后,由于mnf_auto_enter的存在,菜单会自动选择满足所有条件的第一个菜单项("enter")并执行之,这样玩家就不用点击,自动跳入盐矿场景了。

下一节我们会以盐矿作为一个模板,来创建我们自己的城镇菜单,以使教程第二部分中创建的mod城镇可用。
作者: zhangjjaa    时间: 2007-12-3 17:49

好复杂啊,都看不懂




欢迎光临 轩辕春秋文化论坛 (http://xycq.org.cn/forum/) Powered by Discuz! 5.0.0