Modular weapons can now be dropped as loot or assigned to enemies.

This commit is contained in:
Sam 2021-08-04 23:23:08 -05:00
parent c01fe655f1
commit 762b3be3c3
36 changed files with 220 additions and 75 deletions

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Cotton",
),
quality: Direct(Low),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Lifecloth",
),
quality: Direct(High),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Linen",
),
quality: Direct(Low),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -7,5 +7,5 @@ ItemDef(
descriptor: "",
),
quality: Direct(Low),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Moonwoven",
),
quality: Direct(Epic),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Silken",
),
quality: Direct(Moderate),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Sunsilken",
),
quality: Direct(Legendary),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Woolen",
),
quality: Direct(Common),
tags: [Textile],
tags: [MaterialKind(Cloth)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Carapace",
),
quality: Direct(High),
tags: [],
tags: [MaterialKind(Hide)],
)

View File

@ -3,8 +3,8 @@ ItemDef(
description: "Tough scale from a legendary beast.",
kind: Ingredient(
kind: "DragonScale",
descriptor: "Dragon Scaled",
descriptor: "Dragonscale",
),
quality: Direct(Legendary),
tags: [],
tags: [MaterialKind(Hide)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Plate",
),
quality: Direct(Epic),
tags: [],
tags: [MaterialKind(Hide)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Scale",
),
quality: Direct(Moderate),
tags: [],
tags: [MaterialKind(Hide)],
)

View File

@ -3,9 +3,8 @@ ItemDef(
description: "Light and flexible.",
kind: Ingredient(
kind: "SimpleLeather",
// Descriptor not needed
descriptor: "",
descriptor: "Raw Hide",
),
quality: Direct(Low),
tags: [BaseMaterial, Leather],
tags: [BaseMaterial, Leather, MaterialKind(Hide)],
)

View File

@ -3,9 +3,8 @@ ItemDef(
description: "Strong and durable.",
kind: Ingredient(
kind: "ThickLeather",
// Descriptor not needed
descriptor: "",
descriptor: "Leather",
),
quality: Direct(Common),
tags: [BaseMaterial, Leather],
tags: [BaseMaterial, Leather, MaterialKind(Hide)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Bamboo",
),
quality: Direct(Common),
tags: [],
tags: [MaterialKind(Wood)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Eldwood",
),
quality: Direct(Common),
tags: [],
tags: [MaterialKind(Wood)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Frostwood",
),
quality: Direct(Common),
tags: [],
tags: [MaterialKind(Wood)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Hardwood",
),
quality: Direct(Common),
tags: [],
tags: [MaterialKind(Wood)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Ironwood",
),
quality: Direct(High),
tags: [],
tags: [MaterialKind(Wood)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Wooden",
),
quality: Direct(Common),
tags: [],
tags: [MaterialKind(Wood)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Bloodsteel",
),
quality: Direct(Epic),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Bronze",
),
quality: Direct(Low),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Cobalt",
),
quality: Direct(High),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Copper",
),
quality: Direct(Low),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Golden",
),
quality: Direct(Epic),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Iron",
),
quality: Direct(Common),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Orichalcum",
),
quality: Direct(Legendary),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Silver",
),
quality: Direct(Epic),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Steel",
),
quality: Direct(Moderate),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -6,5 +6,5 @@ ItemDef(
descriptor: "Tin",
),
quality: Direct(Common),
tags: [MetalIngot],
tags: [MaterialKind(Metal)],
)

View File

@ -1902,7 +1902,7 @@
"longsword blade": (
output: ("common.items.crafting_ing.modular.damage.sword.longsword", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1910,7 +1910,7 @@
"sawblade": (
output: ("common.items.crafting_ing.modular.damage.sword.sawblade", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1918,7 +1918,7 @@
"katana blade": (
output: ("common.items.crafting_ing.modular.damage.sword.katana", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1926,7 +1926,7 @@
"zweihander blade": (
output: ("common.items.crafting_ing.modular.damage.sword.zweihander", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1934,7 +1934,7 @@
"sabre blade": (
output: ("common.items.crafting_ing.modular.damage.sword.sabre", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1942,7 +1942,7 @@
"greatsword blade": (
output: ("common.items.crafting_ing.modular.damage.sword.greatsword", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1950,7 +1950,7 @@
"ornate sword blade": (
output: ("common.items.crafting_ing.modular.damage.sword.ornate", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1984,7 +1984,7 @@
"hammer head": (
output: ("common.items.crafting_ing.modular.damage.hammer.hammer", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -1992,7 +1992,7 @@
"spiked mace head": (
output: ("common.items.crafting_ing.modular.damage.hammer.spikedmace", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2000,7 +2000,7 @@
"warhammer head": (
output: ("common.items.crafting_ing.modular.damage.hammer.warhammer", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2008,7 +2008,7 @@
"maul head": (
output: ("common.items.crafting_ing.modular.damage.hammer.maul", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2016,7 +2016,7 @@
"great mace head": (
output: ("common.items.crafting_ing.modular.damage.hammer.greatmace", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2024,7 +2024,7 @@
"greathammer head": (
output: ("common.items.crafting_ing.modular.damage.hammer.greathammer", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2032,7 +2032,7 @@
"ornate hammer head": (
output: ("common.items.crafting_ing.modular.damage.hammer.ornate", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2066,7 +2066,7 @@
"axe head": (
output: ("common.items.crafting_ing.modular.damage.axe.axe", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2074,7 +2074,7 @@
"jagged axe head": (
output: ("common.items.crafting_ing.modular.damage.axe.jagged", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2082,7 +2082,7 @@
"battleaxe head": (
output: ("common.items.crafting_ing.modular.damage.axe.battleaxe", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2090,7 +2090,7 @@
"poleaxe head": (
output: ("common.items.crafting_ing.modular.damage.axe.poleaxe", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2098,7 +2098,7 @@
"labrys axe head": (
output: ("common.items.crafting_ing.modular.damage.axe.labrys", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2106,7 +2106,7 @@
"greataxe head": (
output: ("common.items.crafting_ing.modular.damage.axe.greataxe", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,
@ -2114,7 +2114,7 @@
"ornate axe head": (
output: ("common.items.crafting_ing.modular.damage.axe.ornate", 1),
inputs: [
(TagSameItem(MetalIngot), 5, true),
(TagSameItem(MaterialKind(Metal)), 5, true),
],
craft_sprite: Some(Anvil),
is_recycling: false,

View File

@ -96,7 +96,7 @@ pub trait TagExampleInfo {
fn exemplar_identifier(&self) -> Cow<'static, str>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)]
pub enum MaterialKind {
Metal,
Wood,
@ -184,12 +184,12 @@ impl Material {
Material::Cobalt => Some("common.items.mineral.ingot.cobalt"),
Material::Bloodsteel => Some("common.items.mineral.ingot.bloodsteel"),
Material::Orichalcum => Some("common.items.mineral.ingot.orichalcum"),
Material::Wood
| Material::Bamboo
| Material::Hardwood
| Material::Ironwood
| Material::Frostwood
| Material::Eldwood => None,
Material::Wood => Some("common.items.log.wood"),
Material::Bamboo => Some("common.items.log.bamboo"),
Material::Hardwood => Some("common.items.log.hardwood"),
Material::Ironwood => Some("common.items.log.ironwood"),
Material::Frostwood => Some("common.items.log.frostwood"),
Material::Eldwood => Some("common.items.log.eldwood"),
Material::Rock
| Material::Granite
| Material::Bone
@ -232,10 +232,9 @@ impl TagExampleInfo for MaterialTag {
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum ItemTag {
MetalIngot,
Textile,
Leather,
Material(MaterialTag),
MaterialKind(MaterialKind),
ModularComponent(ModularComponentTag),
Cultist,
Potion,
@ -252,8 +251,7 @@ impl TagExampleInfo for ItemTag {
match self {
ItemTag::Material(material) => material.name(),
ItemTag::ModularComponent(kind) => kind.name(),
ItemTag::MetalIngot => Cow::Borrowed("metal ingot"),
ItemTag::Textile => Cow::Borrowed("textile"),
ItemTag::MaterialKind(material_kind) => Cow::Borrowed(material_kind.into()),
ItemTag::Leather => Cow::Borrowed("leather"),
ItemTag::Cultist => Cow::Borrowed("cultist"),
ItemTag::Potion => Cow::Borrowed("potion"),
@ -271,8 +269,7 @@ impl TagExampleInfo for ItemTag {
match self {
ItemTag::Material(_) => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::ModularComponent(tag) => tag.exemplar_identifier(),
ItemTag::MetalIngot => Cow::Borrowed("common.items.tag_examples.metal_ingot"),
ItemTag::Textile => Cow::Borrowed("common.items.tag_examples.textile"),
ItemTag::MaterialKind(_) => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::Leather => Cow::Borrowed("common.items.tag_examples.leather"),
ItemTag::Cultist => Cow::Borrowed("common.items.tag_examples.cultist"),
ItemTag::Potion => Cow::Borrowed("common.items.tag_examples.placeholder"),

View File

@ -2,7 +2,11 @@ use super::{
tool::{self, Hands},
Item, ItemKind, ItemName, ItemTag, RawItemDef, TagExampleInfo, ToolKind,
};
use crate::recipe::{RawRecipe, RawRecipeBook, RawRecipeInput};
use crate::{
assets::AssetExt,
lottery::Lottery,
recipe::{self, RawRecipe, RawRecipeBook, RawRecipeInput},
};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
@ -21,6 +25,16 @@ impl ModularComponentKind {
ModularComponentKind::Held => "held",
}
}
/// Returns the main component of a weapon, i.e. which component has a
/// material component
fn main_component(tool: ToolKind) -> Self {
match tool {
ToolKind::Sword | ToolKind::Axe | ToolKind::Hammer => Self::Damage,
ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre => Self::Held,
_ => unreachable!(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -328,3 +342,115 @@ pub(super) fn resolve_quality(item: &Item) -> super::Quality {
.iter()
.fold(super::Quality::Common, |a, b| a.max(b.quality()))
}
/// Returns directory that contains components for a particular combination of
/// toolkind and modular component kind
fn make_mod_comp_dir_def(tool: ToolKind, mod_kind: ModularComponentKind) -> String {
const MOD_COMP_DIR_PREFIX: &str = "common.items.crafting_ing.modular";
format!(
"{}.{}.{}",
MOD_COMP_DIR_PREFIX,
mod_kind.identifier_name(),
tool.identifier_name()
)
}
/// Creates a random modular weapon when provided with a toolkind, material, and
/// optionally the handedness
pub fn random_weapon(tool: ToolKind, material: super::Material, hands: Option<Hands>) -> Item {
// Returns inner modular component of an item if it has one
fn unwrap_modular_component(item: &Item) -> Option<&ModularComponent> {
if let ItemKind::ModularComponent(mod_comp) = item.kind() {
Some(mod_comp)
} else {
None
}
}
// Loads default ability map and material stat manifest for later use
let ability_map = Default::default();
let msm = Default::default();
// Initialize modular weapon
let mut modular_weapon = Item::new_from_asset_expect(&make_weapon_def(tool).0);
// Load recipe book (done to check that material is valid for a particular
// component)
let recipe::RawRecipeBook(recipes) =
recipe::RawRecipeBook::load_expect_cloned("common.recipe_book");
// Closure to check that an Item has a recipe that uses the provided material
let is_composed_of = |item: &str| {
// Iterate over all recipes in the raw recipe book
recipes
.values()
// Filter by recipes that have an output of the item of interest
.filter(|recipe| recipe.output.0.eq(item))
// Check that item is composed of material, uses heuristic that assumes all modular components use the TagSameItem recipe input
.any(|recipe| {
recipe
.inputs
.iter()
.any(|input| {
matches!(input.0, recipe::RawRecipeInput::TagSameItem(item_tag) if item_tag == super::ItemTag::MaterialKind(material.material_kind()))
})
})
};
// Finds which component has a material as a subcomponent
let material_comp = ModularComponentKind::main_component(tool);
// Closure to return vec of components that are eligible to be used in the
// modular weapon
let create_component = |directory, hands| {
// Load directory of components
let components = Item::new_from_asset_glob(directory)
.expect("Asset directory did not properly load")
.into_iter()
// Filter by handedness requirement
.filter(|item| {
matches!(unwrap_modular_component(item), Some(ModularComponent { hand_restriction, .. }) if hand_restriction.zip(hands).map_or(true, |(hr1, hr2)| hr1 == hr2))
})
// Filter by if component does not have a material, or if material can be used in the modular component
.filter(|item| {
matches!(unwrap_modular_component(item), Some(ModularComponent { modkind, .. }) if *modkind != material_comp)
|| is_composed_of(item.item_definition_id())
})
.map(|item| (1.0, item))
.collect::<Vec<_>>();
// Create lottery and choose item
Lottery::<Item>::from(components).choose_owned()
};
// Creates components of modular weapon
let damage_comp_dir = make_mod_comp_dir_def(tool, ModularComponentKind::Damage);
let mut damage_component = create_component(&damage_comp_dir, hands);
// Takes whichever is more restrictive of hand restriction passed in and hand
// restriction from damage component e.g. if None is passed to function, and
// damage component chooses piece with two handed restriction, then makes held
// component have two handed restriction as well
let damage_hands = unwrap_modular_component(&damage_component)
.and_then(|mc| mc.hand_restriction)
.map_or(hands, Some);
let held_comp_dir = make_mod_comp_dir_def(tool, ModularComponentKind::Held);
let mut held_component = create_component(&held_comp_dir, damage_hands);
let material_component = Item::new_from_asset_expect(material.asset_identifier().expect("Code reviewers: open comment here if I forget about this, I got lazy during a rebase"));
// Insert material item into modular component of appropriate kind
match material_comp {
ModularComponentKind::Damage => {
damage_component.add_component(material_component, &ability_map, &msm);
},
ModularComponentKind::Held => {
held_component.add_component(material_component, &ability_map, &msm);
},
}
// Insert components onto modular weapon
modular_weapon.add_component(damage_component, &ability_map, &msm);
modular_weapon.add_component(held_component, &ability_map, &msm);
// Returns fully created modular weapon
modular_weapon
}

View File

@ -260,6 +260,8 @@ impl From<Vec<(f32, LootSpec<String>)>> for ProbabilityFile {
.into_iter()
},
LootSpec::Nothing => Vec::new().into_iter(),
// TODO: Let someone else wrangle modular weapons into the economy
LootSpec::ModularWeapon { .. } => vec![].into_iter(),
})
.collect(),
}

View File

@ -28,7 +28,7 @@
use crate::{
assets::{self, AssetExt},
comp::Item,
comp::{inventory::item, Item},
};
use rand::prelude::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -69,8 +69,21 @@ impl<T> Lottery<T> {
.1
}
pub fn choose_seeded_owned(mut self, seed: u32) -> T {
let x = ((seed % 65536) as f32 / 65536.0) * self.total;
self.items
.remove(
self.items
.binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap())
.unwrap_or_else(|i| i.saturating_sub(1)),
)
.1
}
pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) }
pub fn choose_owned(self) -> T { self.choose_seeded_owned(thread_rng().gen()) }
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
pub fn total(&self) -> f32 { self.total }
@ -86,6 +99,12 @@ pub enum LootSpec<T: AsRef<str>> {
LootTable(T),
/// No loot given
Nothing,
/// Modular weapon
ModularWeapon {
tool: item::tool::ToolKind,
material: item::Material,
hands: Option<item::tool::Hands>,
},
}
impl<T: AsRef<str>> LootSpec<T> {
@ -120,6 +139,7 @@ impl<T: AsRef<str>> LootSpec<T> {
.choose()
.to_item(),
Self::Nothing => None,
Self::ModularWeapon { tool, material, hands } => Some(item::modular::random_weapon(*tool, *material, *hands)),
}
}
}

View File

@ -199,10 +199,12 @@ impl CraftingTab {
CraftingTab::Glider => matches!(item.kind(), ItemKind::Glider(_)),
CraftingTab::Potion => item.tags().contains(&ItemTag::Potion),
CraftingTab::ProcessedMaterial => {
item.tags().contains(&ItemTag::MetalIngot)
|| item.tags().contains(&ItemTag::Textile)
|| item.tags().contains(&ItemTag::Leather)
|| item.tags().contains(&ItemTag::BaseMaterial)
item.tags().iter().any(|tag| {
matches!(
tag,
&ItemTag::MaterialKind(_) | &ItemTag::Leather | &ItemTag::BaseMaterial
)
})
},
CraftingTab::Bag => item.tags().contains(&ItemTag::Bag),
CraftingTab::Tool => item.tags().contains(&ItemTag::CraftingTool),