幽深蘑菇森林
关于本教程
本教程是免费且开源的,所有代码均使用 MIT 许可证 - 因此您可以随意使用。我希望您会喜欢本教程,并制作出色的游戏!
如果您喜欢这个教程并希望我继续写作,请考虑支持我的 Patreon。
本章将为游戏添加另一个蘑菇林地关卡,这次没有矮人要塞。它还将添加最终的蘑菇关卡,根据设计文档,这个关卡将通往黑暗精灵城市。最后,我们将通过自动化添加魔法和诅咒物品的一些繁琐工作,进一步改进我们的物品故事。
构建蘑菇森林
我们将从打开 map_builders/mod.rs
并向地图构建器调用添加另一行开始:
#![allow(unused)] fn main() { pub fn level_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { rltk::console::log(format!("Depth: {}", new_depth)); match new_depth { 1 => town_builder(new_depth, rng, width, height), 2 => forest_builder(new_depth, rng, width, height), 3 => limestone_cavern_builder(new_depth, rng, width, height), 4 => limestone_deep_cavern_builder(new_depth, rng, width, height), 5 => limestone_transition_builder(new_depth, rng, width, height), 6 => dwarf_fort_builder(new_depth, rng, width, height), 7 => mushroom_entrance(new_depth, rng, width, height), 8 => mushroom_builder(new_depth, rng, width, height), _ => random_builder(new_depth, rng, width, height) } } }
然后我们将打开 map_builders/mushroom_forest.rs
并为该关卡存根一个基本的地图构建器:
#![allow(unused)] fn main() { pub fn mushroom_builder(new_depth: i32, _rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { let mut chain = BuilderChain::new(new_depth, width, height, "Into The Mushroom Grove"); chain.start_with(CellularAutomataBuilder::new()); chain.with(WaveformCollapseBuilder::new()); chain.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER)); chain.with(CullUnreachable::new()); chain.with(AreaStartingPosition::new(XStart::RIGHT, YStart::CENTER)); chain.with(AreaEndingPosition::new(XEnd::LEFT, YEnd::CENTER)); chain.with(VoronoiSpawning::new()); chain } }
这基本上与另一个蘑菇构建器相同,但没有预制件覆盖。如果您进入 main.rs
并更改起始关卡:
#![allow(unused)] fn main() { gs.generate_world_map(8, 0); rltk::main_loop(context, gs) }
并 cargo run
,您将获得一个相当不错的关卡。它保留了我们之前关卡的怪物生成,因为我们仔细地将它们包含在我们的生成关卡范围中。
真菌森林的尽头
再一次,我们将在 map_builders/mod.rs
中添加另一个关卡:
#![allow(unused)] fn main() { pub fn level_builder(new_depth: i32, rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { rltk::console::log(format!("Depth: {}", new_depth)); match new_depth { 1 => town_builder(new_depth, rng, width, height), 2 => forest_builder(new_depth, rng, width, height), 3 => limestone_cavern_builder(new_depth, rng, width, height), 4 => limestone_deep_cavern_builder(new_depth, rng, width, height), 5 => limestone_transition_builder(new_depth, rng, width, height), 6 => dwarf_fort_builder(new_depth, rng, width, height), 7 => mushroom_entrance(new_depth, rng, width, height), 8 => mushroom_builder(new_depth, rng, width, height), 9 => mushroom_exit(new_depth, rng, width, height), _ => random_builder(new_depth, rng, width, height) } } }
并赋予它与 mushroom_builder
相同的起始代码:
#![allow(unused)] fn main() { pub fn mushroom_exit(new_depth: i32, _rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { let mut chain = BuilderChain::new(new_depth, width, height, "Into The Mushroom Grove"); chain.start_with(CellularAutomataBuilder::new()); chain.with(WaveformCollapseBuilder::new()); chain.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER)); chain.with(CullUnreachable::new()); chain.with(AreaStartingPosition::new(XStart::RIGHT, YStart::CENTER)); chain.with(AreaEndingPosition::new(XEnd::LEFT, YEnd::CENTER)); chain.with(VoronoiSpawning::new()); chain } }
我们还将访问 main.rs
以使我们从这个关卡开始:
#![allow(unused)] fn main() { gs.generate_world_map(9, 0); }
连续两个相同的关卡(设计方面;内容会因程序生成而异)非常枯燥,我们需要传达这里有一个通往黑暗精灵城市的入口的想法。我们将从向地图添加一个新的预制件截面开始:
#![allow(unused)] fn main() { #[allow(dead_code)] pub const DROW_ENTRY : PrefabSection = PrefabSection{ template : DROW_ENTRY_TXT, width: 12, height: 10, placement: ( HorizontalPlacement::Center, VerticalPlacement::Center ) }; #[allow(dead_code)] const DROW_ENTRY_TXT : &str = " ######### # > # # e # e # e # ######### "; }
注意空格:预制件周围有空格,这些空格是有意的 - 以确保它周围有一个“沟槽”。现在我们修改我们的 mushroom_exit
函数来生成它:
#![allow(unused)] fn main() { pub fn mushroom_exit(new_depth: i32, _rng: &mut rltk::RandomNumberGenerator, width: i32, height: i32) -> BuilderChain { let mut chain = BuilderChain::new(new_depth, width, height, "Into The Mushroom Grove"); chain.start_with(CellularAutomataBuilder::new()); chain.with(WaveformCollapseBuilder::new()); chain.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER)); chain.with(CullUnreachable::new()); chain.with(AreaStartingPosition::new(XStart::RIGHT, YStart::CENTER)); chain.with(AreaEndingPosition::new(XEnd::LEFT, YEnd::CENTER)); chain.with(VoronoiSpawning::new()); chain.with(PrefabBuilder::sectional(DROW_ENTRY)); chain } }
未知字形加载地图: e
您可以 cargo run
并找到现在位于中间的出口,但是没有黑暗精灵! “e”什么也没生成,并生成一个警告。没关系 - 我们还没有实现任何黑暗精灵。在 map_builders/prefab_builder/mod.rs
中,我们将添加 e
以在加载器文件中表示“Dark Elf”:
#![allow(unused)] fn main() { fn char_to_map(&mut self, ch : char, idx: usize, build_data : &mut BuilderMap) { // 边界检查 if idx >= build_data.map.tiles.len()-1 { return; } match ch { ' ' => build_data.map.tiles[idx] = TileType::Floor, '#' => build_data.map.tiles[idx] = TileType::Wall, '≈' => build_data.map.tiles[idx] = TileType::DeepWater, '@' => { let x = idx as i32 % build_data.map.width; let y = idx as i32 / build_data.map.width; build_data.map.tiles[idx] = TileType::Floor; build_data.starting_position = Some(Position{ x:x as i32, y:y as i32 }); } '>' => build_data.map.tiles[idx] = TileType::DownStairs, 'e' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Dark Elf".to_string())); } 'g' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Goblin".to_string())); } 'o' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Orc".to_string())); } 'O' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Orc Leader".to_string())); } '^' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Bear Trap".to_string())); } '%' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Rations".to_string())); } '!' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Health Potion".to_string())); } '☼' => { build_data.map.tiles[idx] = TileType::Floor; build_data.spawn_list.push((idx, "Watch Fire".to_string())); } _ => { rltk::console::log(format!("Unknown glyph loading map: {}", (ch as u8) as char)); } } } }
如果您 cargo run
,则错误现在被 WARNING: We don't know how to spawn [Dark Elf]!
取代 - 这是进步。
为了解决这个问题,我们将定义黑暗精灵!让我们从一个非常简单的 spawns.json
条目开始:
{
"name" : "Dark Elf",
"renderable": {
"glyph" : "e",
"fg" : "#FF0000",
"bg" : "#000000",
"order" : 1
},
"blocks_tile" : true,
"vision_range" : 8,
"movement" : "random_waypoint",
"attributes" : {},
"equipped" : [ "Dagger", "Shield", "Leather Armor", "Leather Boots" ],
"faction" : "DarkElf",
"gold" : "3d6",
"level" : 6
},
我们还将为他们提供一个 faction 条目:
{ "name" : "DarkElf", "responses" : { "Default" : "attack", "DarkElf" : "ignore" } }
如果您现在 cargo run
,您将有一些中等强度的黑暗精灵需要对付。问题是,它们不是很“黑暗精灵”:它们基本上是重新蒙皮的土匪。当您想到“黑暗精灵”时,您会想到什么(除了 Drizzt Do'Urden,如果我包含他,他的版权所有者会从远处击打我)?他们非常邪恶,魔法,行动迅速,并且通常非常强大。他们也倾向于拥有自己的黑暗技术,并用远程武器攻击他们的敌人!
在下一章之前,我们不会支持远程武器,但是我们可以采取一些步骤使它们更像黑暗精灵。让我们给他们一套更像黑暗精灵的物品。在 equipped
标签中,我们将使用:
"equipped" : [ "Scimitar", "Buckler", "Drow Chain", "Drow Leggings", "Drow Boots" ],
我们还需要为这些制作物品条目。我们将 弯刀 基本视为长剑,但稍微好一点:
{
"name" : "Scimitar",
"renderable": {
"glyph" : "/",
"fg" : "#FFAAFF",
"bg" : "#000000",
"order" : 2
},
"weapon" : {
"range" : "melee",
"attribute" : "might",
"base_damage" : "1d6+2",
"hit_bonus" : 1
},
"weight_lbs" : 2.5,
"base_value" : 25.0,
"initiative_penalty" : 1,
"vendor_category" : "weapon"
},
我们将遵循 卓尔盔甲 的趋势:它基本上是链甲,但启动惩罚要小得多:
{
"name" : "Drow Leggings",
"renderable": {
"glyph" : "[",
"fg" : "#00FFFF",
"bg" : "#000000",
"order" : 2
},
"wearable" : {
"slot" : "Legs",
"armor_class" : 0.4
},
"weight_lbs" : 10.0,
"base_value" : 50.0,
"initiative_penalty" : 0.1,
"vendor_category" : "clothes"
},
{
"name" : "Drow Chain",
"renderable": {
"glyph" : "[",
"fg" : "#00FF00",
"bg" : "#000000",
"order" : 2
},
"wearable" : {
"slot" : "Torso",
"armor_class" : 3.0
},
"weight_lbs" : 5.0,
"base_value" : 50.0,
"initiative_penalty" : 0.0,
"vendor_category" : "armor"
},
{
"name" : "Drow Boots",
"renderable": {
"glyph" : "[",
"fg" : "#00FF00",
"bg" : "#000000",
"order" : 2
},
"wearable" : {
"slot" : "Feet",
"armor_class" : 0.4
},
"weight_lbs" : 2.0,
"base_value" : 10.0,
"initiative_penalty" : 0.1,
"vendor_category" : "armor"
},
这些的结果是它们很快 - 它们的启动惩罚比类似装甲的玩家小得多。另一个好处是,您可以杀死一个,拿走他们的东西 - 并获得相同的好处!
至此,我们已经添加了两个可玩的关卡 - 只需几行代码。收获在通用系统上努力工作的回报!所以现在,让我们使事情更通用一些 - 并为自己节省一些打字时间。
程序化生成的魔法物品
我们一直在添加“长剑 +1”、“长剑 -1”等等。我们可以坐下来费力地输入每个物品的每个魔法变体,我们将拥有一个相当可玩的游戏。或者 - 我们可以自动化一些繁琐的工作!
如果我们可以在 spawns.json
中的武器定义中附加一个“template”属性,并使其自动为我们生成变体呢?这并不像听起来那么牵强。让我们草拟一下我们想要的东西:
{
"name" : "Longsword",
"renderable": {
"glyph" : "/",
"fg" : "#FFAAFF",
"bg" : "#000000",
"order" : 2
},
"weapon" : {
"range" : "melee",
"attribute" : "might",
"base_damage" : "1d8",
"hit_bonus" : 0
},
"weight_lbs" : 3.0,
"base_value" : 15.0,
"initiative_penalty" : 2,
"vendor_category" : "weapon",
"template_magic" : {
"unidentified_name" : "Unidentified Longsword",
"bonus_min" : 1,
"bonus_max" : 5,
"include_cursed" : true
}
},
因此,我们添加了一个 template_magic
部分,描述了我们想要添加的物品类型。我们需要扩展 raws/item_structs.rs
以支持加载此信息:
#![allow(unused)] fn main() { #[derive(Deserialize, Debug, Clone)] pub struct Item { pub name : String, pub renderable : Option<Renderable>, pub consumable : Option<Consumable>, pub weapon : Option<Weapon>, pub wearable : Option<Wearable>, pub initiative_penalty : Option<f32>, pub weight_lbs : Option<f32>, pub base_value : Option<f32>, pub vendor_category : Option<String>, pub magic : Option<MagicItem>, pub attributes : Option<ItemAttributeBonus>, pub template_magic : Option<ItemMagicTemplate> } ... #[derive(Deserialize, Debug, Clone)] pub struct ItemMagicTemplate { pub unidentified_name: String, pub bonus_min: i32, pub bonus_max: i32, pub include_cursed: bool } }
这足以加载额外的信息 - 它只是什么也不做。我们还需要遍历并为该文件中的所有结构在 #[derive]
列表中添加 Clone
。我们将使用 clone()
制作副本,然后为每个变体进行修改。
与其他添加不同,这不会修改我们在 rawmaster.rs
中的 spawn_named_item
函数;我们希望在开始生成之前修改原始文件模板。相反,我们将后处理 load
函数本身构建的物品列表(包括修改生成列表)。在该函数的顶部,我们将读取每个物品,如果它附加了模板(并且是武器或盔甲物品),我们将它添加到要处理的列表中:
#![allow(unused)] fn main() { pub fn load(&mut self, raws : Raws) { self.raws = raws; self.item_index = HashMap::new(); let mut used_names : HashSet<String> = HashSet::new(); struct NewMagicItem { name : String, bonus : i32 } let mut items_to_build : Vec<NewMagicItem> = Vec::new(); for (i,item) in self.raws.items.iter().enumerate() { if used_names.contains(&item.name) { rltk::console::log(format!("WARNING - duplicate item name in raws [{}]", item.name)); } self.item_index.insert(item.name.clone(), i); used_names.insert(item.name.clone()); if let Some(template) = &item.template_magic { if item.weapon.is_some() || item.wearable.is_some() { if template.include_cursed { items_to_build.push(NewMagicItem{ name : item.name.clone(), bonus : -1 }); } for bonus in template.bonus_min ..= template.bonus_max { items_to_build.push(NewMagicItem{ name : item.name.clone(), bonus }); } } else { rltk::console::log(format!("{} is marked as templated, but isn't a weapon or armor.", item.name)); } } } }
然后,在我们完成读取物品后,我们将在最后添加一个循环来创建这些物品:
#![allow(unused)] fn main() { for nmw in items_to_build.iter() { let base_item_index = self.item_index[&nmw.name]; let mut base_item_copy = self.raws.items[base_item_index].clone(); if nmw.bonus == -1 { base_item_copy.name = format!("{} -1", nmw.name); } else { base_item_copy.name = format!("{} +{}", nmw.name, nmw.bonus); } base_item_copy.magic = Some(super::MagicItem{ class : match nmw.bonus { 2 => "rare".to_string(), 3 => "rare".to_string(), 4 => "rare".to_string(), 5 => "legendary".to_string(), _ => "common".to_string() }, naming : base_item_copy.template_magic.as_ref().unwrap().unidentified_name.clone(), cursed: if nmw.bonus == -1 { Some(true) } else { None } }); if let Some(initiative_penalty) = base_item_copy.initiative_penalty.as_mut() { *initiative_penalty -= nmw.bonus as f32; } if let Some(base_value) = base_item_copy.base_value.as_mut() { *base_value += (nmw.bonus as f32 + 1.0) * 50.0; } if let Some(mut weapon) = base_item_copy.weapon.as_mut() { weapon.hit_bonus += nmw.bonus; let (n,die,plus) = parse_dice_string(&weapon.base_damage); let final_bonus = plus+nmw.bonus; if final_bonus > 0 { weapon.base_damage = format!("{}d{}+{}", n, die, final_bonus); } else if final_bonus < 0 { weapon.base_damage = format!("{}d{}-{}", n, die, i32::abs(final_bonus)); } } if let Some(mut armor) = base_item_copy.wearable.as_mut() { armor.armor_class += nmw.bonus as f32; } let real_name = base_item_copy.name.clone(); self.raws.items.push(base_item_copy); self.item_index.insert(real_name.clone(), self.raws.items.len()-1); self.raws.spawn_table.push(super::SpawnTableEntry{ name : real_name.clone(), weight : 10 - i32::abs(nmw.bonus), min_depth : 1 + i32::abs((nmw.bonus-1)*3), max_depth : 100, add_map_depth_to_weight : None }); } }
因此,这循环遍历我们在初始解析期间创建的所有“长剑 +1”、“长剑 -1”、“长剑 +2”等。然后它:
- 获取原始物品的副本。
- 如果奖励是
-1
,则将其重命名为“物品 -x”;否则将其重命名为“物品 +x”,其中 x 是奖励。 - 它为物品创建一个新的
magic
条目,并按奖励设置 common/rare/legendary 状态,并根据需要设置诅咒标志。 - 如果该物品有启动惩罚,它会从其中减去奖励(使诅咒物品更糟,魔法物品更好)。
- 它将基本价值提高奖励 +1 * 50 金币。
- 如果是武器,则将奖励添加到
to_hit
奖励和伤害骰子。它通过重新格式化骰子数字来完成伤害骰子。 - 如果是盔甲,它会将奖励添加到护甲等级。
- 然后,它将新物品插入生成表,更好的物品权重更低,更好的物品稍后出现在地下城中。
如果您查看 在线源代码 - 我已经遍历并删除了所有 +1、+2 和简单的诅咒盔甲和武器 - 并将 template_magic
附加到它们中的每一个。这导致生成了 168 个新物品!这比全部输入它们要好得多。
如果您现在 cargo run
,您会在整个地下城中找到逐渐改进的各种类型的魔法物品。更好的物品会在您深入地下城时出现,因此玩家的力量会很好地提升。
特性物品
通过 dagger of venom
,我们引入了一种新型的物品:一种在您击中时会产生效果的物品。鉴于这可以是游戏中的任何效果,因此效果有很多可能性!手动添加所有效果将花费一段时间 - 可能更快地提出一个通用系统,并因此在我们的物品中获得真正的多样性(以及不会忘记添加它们!)。
让我们从在 spawns.json
中添加一个新部分开始,专门用于武器特性:
"weapon_traits" : [
{
"name" : "Venomous",
"effects" : { "damage_over_time" : "2" }
}
]
稍后我们将添加更多特性,现在我们将专注于使系统完全工作!为了读取数据,我们将创建一个新文件 raws/weapon_traits.rs
(不要将 Rust 特性和武器特性混淆;它们根本不是一回事)。我们将放入足够的结构以允许 Serde 读取 JSON 文件:
#![allow(unused)] fn main() { use serde::{Deserialize}; use std::collections::HashMap; #[derive(Deserialize, Debug)] pub struct WeaponTrait { pub name : String, pub effects : HashMap<String, String> } }
现在我们需要扩展 raws/mod.rs
中的数据以包含它。在文件的顶部,包含:
#![allow(unused)] fn main() { mod weapon_traits; pub use weapon_traits::*; }
然后我们将它添加到 Raws
结构中,就像我们对法术所做的那样:
#![allow(unused)] fn main() { #[derive(Deserialize, Debug)] pub struct Raws { pub items : Vec<Item>, pub mobs : Vec<Mob>, pub props : Vec<Prop>, pub spawn_table : Vec<SpawnTableEntry>, pub loot_tables : Vec<LootTable>, pub faction_table : Vec<FactionInfo>, pub spells : Vec<Spell>, pub weapon_traits : Vec<WeaponTrait> } }
反过来,我们必须扩展 raws/rawmaster.rs
中的构造函数以包含一个空的特性列表:
#![allow(unused)] fn main() { impl RawMaster { pub fn empty() -> RawMaster { RawMaster { raws : Raws{ items: Vec::new(), mobs: Vec::new(), props: Vec::new(), spawn_table: Vec::new(), loot_tables: Vec::new(), faction_table : Vec::new(), spells : Vec::new(), weapon_traits : Vec::new() }, item_index : HashMap::new(), mob_index : HashMap::new(), prop_index : HashMap::new(), loot_index : HashMap::new(), faction_index : HashMap::new(), spell_index : HashMap::new() } } ... }
感谢 Serde 的魔力,这就是实际加载数据的所有内容!现在是困难的部分:程序化生成具有一个或多个特性的魔法物品。为了避免重复自己,我们将把之前编写的代码分离成可重用的函数:
#![allow(unused)] fn main() { // 将此放在 raws 实现之上 struct NewMagicItem { name : String, bonus : i32 } ... // 在 raws 实现内部 fn append_magic_template(items_to_build : &mut Vec<NewMagicItem>, item : &super::Item) { if let Some(template) = &item.template_magic { if item.weapon.is_some() || item.wearable.is_some() { if template.include_cursed { items_to_build.push(NewMagicItem{ name : item.name.clone(), bonus : -1 }); } for bonus in template.bonus_min ..= template.bonus_max { items_to_build.push(NewMagicItem{ name : item.name.clone(), bonus }); } } else { rltk::console::log(format!("{} is marked as templated, but isn't a weapon or armor.", item.name)); } } } fn build_base_magic_item(&self, nmw : &NewMagicItem) -> super::Item { let base_item_index = self.item_index[&nmw.name]; let mut base_item_copy = self.raws.items[base_item_index].clone(); base_item_copy.vendor_category = None; // 不要出售魔法物品! if nmw.bonus == -1 { base_item_copy.name = format!("{} -1", nmw.name); } else { base_item_copy.name = format!("{} +{}", nmw.name, nmw.bonus); } base_item_copy.magic = Some(super::MagicItem{ class : match nmw.bonus { 2 => "rare".to_string(), 3 => "rare".to_string(), 4 => "rare".to_string(), 5 => "legendary".to_string(), _ => "common".to_string() }, naming : base_item_copy.template_magic.as_ref().unwrap().unidentified_name.clone(), cursed: if nmw.bonus == -1 { Some(true) } else { None } }); if let Some(initiative_penalty) = base_item_copy.initiative_penalty.as_mut() { *initiative_penalty -= nmw.bonus as f32; } if let Some(base_value) = base_item_copy.base_value.as_mut() { *base_value += (nmw.bonus as f32 + 1.0) * 50.0; } if let Some(mut weapon) = base_item_copy.weapon.as_mut() { weapon.hit_bonus += nmw.bonus; let (n,die,plus) = parse_dice_string(&weapon.base_damage); let final_bonus = plus+nmw.bonus; if final_bonus > 0 { weapon.base_damage = format!("{}d{}+{}", n, die, final_bonus); } else if final_bonus < 0 { weapon.base_damage = format!("{}d{}-{}", n, die, i32::abs(final_bonus)); } } if let Some(mut armor) = base_item_copy.wearable.as_mut() { armor.armor_class += nmw.bonus as f32; } base_item_copy } fn build_magic_weapon_or_armor(&mut self, items_to_build : &[NewMagicItem]) { for nmw in items_to_build.iter() { let base_item_copy = self.build_base_magic_item(&nmw); let real_name = base_item_copy.name.clone(); self.raws.items.push(base_item_copy); self.item_index.insert(real_name.clone(), self.raws.items.len()-1); self.raws.spawn_table.push(super::SpawnTableEntry{ name : real_name.clone(), weight : 10 - i32::abs(nmw.bonus), min_depth : 1 + i32::abs((nmw.bonus-1)*3), max_depth : 100, add_map_depth_to_weight : None }); } } fn build_traited_weapons(&mut self, items_to_build : &[NewMagicItem]) { items_to_build.iter().filter(|i| i.bonus > 0).for_each(|nmw| { for wt in self.raws.weapon_traits.iter() { let mut base_item_copy = self.build_base_magic_item(&nmw); if let Some(mut weapon) = base_item_copy.weapon.as_mut() { base_item_copy.name = format!("{} {}", wt.name, base_item_copy.name); if let Some(base_value) = base_item_copy.base_value.as_mut() { *base_value *= 2.0; } weapon.proc_chance = Some(0.25); weapon.proc_effects = Some(wt.effects.clone()); let real_name = base_item_copy.name.clone(); self.raws.items.push(base_item_copy); self.item_index.insert(real_name.clone(), self.raws.items.len()-1); self.raws.spawn_table.push(super::SpawnTableEntry{ name : real_name.clone(), weight : 9 - i32::abs(nmw.bonus), min_depth : 2 + i32::abs((nmw.bonus-1)*3), max_depth : 100, add_map_depth_to_weight : None }); } } }); } pub fn load(&mut self, raws : Raws) { self.raws = raws; self.item_index = HashMap::new(); let mut used_names : HashSet<String> = HashSet::new(); let mut items_to_build = Vec::new(); for (i,item) in self.raws.items.iter().enumerate() { if used_names.contains(&item.name) { rltk::console::log(format!("WARNING - duplicate item name in raws [{}]", item.name)); } self.item_index.insert(item.name.clone(), i); used_names.insert(item.name.clone()); RawMaster::append_magic_template(&mut items_to_build, item); } for (i,mob) in self.raws.mobs.iter().enumerate() { if used_names.contains(&mob.name) { rltk::console::log(format!("WARNING - duplicate mob name in raws [{}]", mob.name)); } self.mob_index.insert(mob.name.clone(), i); used_names.insert(mob.name.clone()); } for (i,prop) in self.raws.props.iter().enumerate() { if used_names.contains(&prop.name) { rltk::console::log(format!("WARNING - duplicate prop name in raws [{}]", prop.name)); } self.prop_index.insert(prop.name.clone(), i); used_names.insert(prop.name.clone()); } for spawn in self.raws.spawn_table.iter() { if !used_names.contains(&spawn.name) { rltk::console::log(format!("WARNING - Spawn tables references unspecified entity {}", spawn.name)); } } for (i,loot) in self.raws.loot_tables.iter().enumerate() { self.loot_index.insert(loot.name.clone(), i); } for faction in self.raws.faction_table.iter() { let mut reactions : HashMap<String, Reaction> = HashMap::new(); for other in faction.responses.iter() { reactions.insert( other.0.clone(), match other.1.as_str() { "ignore" => Reaction::Ignore, "flee" => Reaction::Flee, _ => Reaction::Attack } ); } self.faction_index.insert(faction.name.clone(), reactions); } for (i,spell) in self.raws.spells.iter().enumerate() { self.spell_index.insert(spell.name.clone(), i); } self.build_magic_weapon_or_armor(&items_to_build); self.build_traited_weapons(&items_to_build); } }
您会注意到其中有一个新函数 build_traited_weapons
。它迭代魔法物品,仅过滤武器 - 并且仅过滤那些带有奖励的武器(我现在真的不想深入研究诅咒的剧毒匕首的作用)。它读取所有特性,并使用应用的特性制作每个魔法武器的(更稀有的)版本。
让我们继续在 spawns.json
中添加一个特性:
"weapon_traits" : [
{
"name" : "Venomous",
"effects" : { "damage_over_time" : "2" }
},
{
"name" : "Dazzling",
"effects" : { "confusion" : "2" }
}
]
如果您现在 cargo run
并玩游戏,您有时会发现诸如 Dazzling Longsword +1 或 Venomous Dagger +2 之类的奇迹。
总结
在本章中,我们构建了一个蘑菇林地关卡,以及一个过渡到黑暗精灵据点的第二个关卡。我们已经开始添加黑暗精灵,为了增强功能(并节省打字时间),我们正在自动生成从 -1 到 +5 的魔法物品。然后我们生成了相同武器的“特性化”版本。现在,运行之间存在着巨大的差异,这应该会让注重装备的玩家感到高兴。关卡也有很好的进展,我们已准备好迎接黑暗精灵城市 - 以及远程武器!
...
本章的源代码可以在这里找到
在您的浏览器中使用 web assembly 运行本章的示例 (需要 WebGL2)
版权所有 (C) 2019, Herbert Wolverson。