From c3ac8a1b513d42f25420078fd12f919a0469a568 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Tue, 23 Feb 2021 00:45:26 +0000 Subject: [PATCH] Support modular weapon voxel meshes being made by assembling their components. --- assets/voxygen/item_image_manifest.ron | 8 ++ .../humanoid_modular_component_manifest.ron | 8 ++ .../sword/cultist/cultist_purp_2h-0_blade.vox | 3 + .../cultist/cultist_purp_2h-0_handle.vox | 3 + common/src/comp/inventory/item/mod.rs | 8 ++ common/src/figure/mod.rs | 10 ++ voxygen/src/scene/figure/cache.rs | 24 +++- voxygen/src/scene/figure/load.rs | 106 +++++++++++++----- 8 files changed, 139 insertions(+), 31 deletions(-) create mode 100644 assets/voxygen/voxel/humanoid_modular_component_manifest.ron create mode 100644 assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_blade.vox create mode 100644 assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_handle.vox diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 203887d3c0..ec45a3fbf1 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -1845,4 +1845,12 @@ "voxel.sprite.gem.diamondgem", (0.0, 0.0, 0.0), (-55.0, 30.0, 20.0), 0.6, ), + ModularComponent("common.items.crafting_ing.modular.damage.sword.tier5"): VoxTrans( + "voxel.weapon_components.sword.cultist.cultist_purp_2h-0_blade", + (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.5, + ), + ModularComponent("common.items.crafting_ing.modular.held.sword.tier5"): VoxTrans( + "voxel.weapon_components.sword.cultist.cultist_purp_2h-0_handle", + (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.5, + ), }) diff --git a/assets/voxygen/voxel/humanoid_modular_component_manifest.ron b/assets/voxygen/voxel/humanoid_modular_component_manifest.ron new file mode 100644 index 0000000000..fe63e31cff --- /dev/null +++ b/assets/voxygen/voxel/humanoid_modular_component_manifest.ron @@ -0,0 +1,8 @@ +({ + "common.items.crafting_ing.modular.damage.sword.tier5": Damage(( + "weapon_components.sword.cultist.cultist_purp_2h-0_blade", (1, 2, 11), + )), + "common.items.crafting_ing.modular.held.sword.tier5": Held(( + "weapon_components.sword.cultist.cultist_purp_2h-0_handle", (-2.0, -4.5, -7.5), (0, 0, 0), + )), +}) diff --git a/assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_blade.vox b/assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_blade.vox new file mode 100644 index 0000000000..6844cad656 --- /dev/null +++ b/assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_blade.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40f9609a930b45beceaf8dd4293a7e9f392fb82fb5bc91ee1e49271e73225e1d +size 1464 diff --git a/assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_handle.vox b/assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_handle.vox new file mode 100644 index 0000000000..814f71f710 --- /dev/null +++ b/assets/voxygen/voxel/weapon_components/sword/cultist/cultist_purp_2h-0_handle.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8ec834aa74544af2f225a239543d3c9c521ff9582af6c573e495252b37aa2f0 +size 1288 diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index bc045dae59..2a5b6399a8 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -249,6 +249,14 @@ impl ItemDef { ) } + pub fn is_component(&self, kind: ModularComponentKind) -> bool { + if let ItemKind::ModularComponent(ModularComponent { modkind, .. }) = self.kind { + kind == modkind + } else { + false + } + } + #[cfg(test)] pub fn new_test( item_definition_id: String, diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index 211dd00161..0aa75d50eb 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -22,6 +22,16 @@ impl From<&DotVoxData> for Segment { } impl Segment { + /// Take a list of voxel data, offsets, and x-mirror flags, and assembled + /// them into a combined segment + pub fn from_voxes(data: &[(&DotVoxData, Vec3, bool)]) -> (Self, Vec3) { + let mut union = DynaUnionizer::new(); + for (datum, offset, xmirror) in data.iter() { + union = union.add(Segment::from_vox(datum, *xmirror), *offset); + } + union.unify() + } + pub fn from_vox(dot_vox_data: &DotVoxData, flipped: bool) -> Self { if let Some(model) = dot_vox_data.models.get(0) { let palette = dot_vox_data diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index 38c90a4eaf..ae89dbadd8 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -16,7 +16,7 @@ use common::{ }, item::{ armor::{Armor, ArmorKind}, - ItemKind, + Item, ItemKind, }, CharacterState, }, @@ -68,12 +68,18 @@ pub struct FigureKey { pub(super) extra: Option>, } +#[derive(Eq, Hash, PartialEq)] +pub(super) struct ToolKey { + pub name: String, + pub components: Vec, +} + /// Character data that should be visible when tools are visible (i.e. in third /// person or when the character is in a tool-using state). #[derive(Eq, Hash, PartialEq)] pub(super) struct CharacterToolKey { - pub active: Option, - pub second: Option, + pub active: Option, + pub second: Option, } /// Character data that exists in third person only. @@ -192,13 +198,21 @@ impl CharacterCacheKey { }) }, tool: if are_tools_visible { + let tool_key_from_item = |item: &Item| ToolKey { + name: item.item_definition_id().to_owned(), + components: item + .components() + .iter() + .map(|comp| comp.item_definition_id().to_owned()) + .collect(), + }; Some(CharacterToolKey { active: inventory .equipped(EquipSlot::Mainhand) - .map(|i| i.item_definition_id().to_owned()), + .map(tool_key_from_item), second: inventory .equipped(EquipSlot::Offhand) - .map(|i| i.item_definition_id().to_owned()), + .map(tool_key_from_item), }) } else { None diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 089c216c86..f0c2f3b7e3 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -1,4 +1,4 @@ -use super::cache::FigureKey; +use super::cache::{FigureKey, ToolKey}; use common::{ assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron}, comp::{ @@ -10,6 +10,7 @@ use common::{ fish_small::{self, BodyType as FSBodyType, Species as FSSpecies}, golem::{self, BodyType as GBodyType, Species as GSpecies}, humanoid::{self, Body, BodyType, EyeColor, Skin, Species}, + item::{ItemDef, ModularComponentKind}, object, quadruped_low::{self, BodyType as QLBodyType, Species as QLSpecies}, quadruped_medium::{self, BodyType as QMBodyType, Species as QMSpecies}, @@ -20,6 +21,7 @@ use common::{ }; use hashbrown::HashMap; use serde::Deserialize; +use std::sync::Arc; use tracing::{error, warn}; use vek::*; @@ -42,8 +44,8 @@ fn graceful_load_vox(mesh_name: &str) -> AssetHandle { fn graceful_load_segment(mesh_name: &str) -> Segment { Segment::from(&graceful_load_vox(mesh_name).read().0) } -fn graceful_load_segment_flipped(mesh_name: &str) -> Segment { - Segment::from_vox(&graceful_load_vox(mesh_name).read().0, true) +fn graceful_load_segment_flipped(mesh_name: &str, flipped: bool) -> Segment { + Segment::from_vox(&graceful_load_vox(mesh_name).read().0, flipped) } fn graceful_load_mat_segment(mesh_name: &str) -> MatSegment { MatSegment::from(&graceful_load_vox(mesh_name).read().0) @@ -141,6 +143,14 @@ struct ArmorVoxSpec { color: Option<[u8; 3]>, } +#[derive(Deserialize)] +enum ModularComponentSpec { + /// item id, offset from origin to mount point + Damage((String, [i32; 3])), + /// item id, offset from origin to hand, offset from origin to mount point + Held((String, [f32; 3], [i32; 3])), +} + // For use by armor with a left and right component #[derive(Deserialize)] struct SidedArmorVoxSpec { @@ -323,6 +333,8 @@ struct HumArmorFootSpec(ArmorVoxSpecMap); #[derive(Deserialize)] struct HumMainWeaponSpec(HashMap); #[derive(Deserialize)] +struct HumModularComponentSpec(HashMap); +#[derive(Deserialize)] struct HumArmorLanternSpec(ArmorVoxSpecMap); #[derive(Deserialize)] struct HumArmorGliderSpec(ArmorVoxSpecMap); @@ -344,6 +356,7 @@ make_vox_spec!( armor_pants: HumArmorPantsSpec = "voxygen.voxel.humanoid_armor_pants_manifest", armor_foot: HumArmorFootSpec = "voxygen.voxel.humanoid_armor_foot_manifest", main_weapon: HumMainWeaponSpec = "voxygen.voxel.humanoid_main_weapon_manifest", + modular_components: HumModularComponentSpec = "voxygen.voxel.humanoid_modular_component_manifest", armor_lantern: HumArmorLanternSpec = "voxygen.voxel.humanoid_lantern_manifest", armor_glider: HumArmorGliderSpec = "voxygen.voxel.humanoid_glider_manifest", // TODO: Add these. @@ -447,12 +460,14 @@ make_vox_spec!( )), tool.and_then(|tool| tool.active.as_ref()).map(|tool| { spec.main_weapon.read().0.mesh_main_weapon( + &spec.modular_components.read().0, tool, false, ) }), tool.and_then(|tool| tool.second.as_ref()).map(|tool| { spec.main_weapon.read().0.mesh_main_weapon( + &spec.modular_components.read().0, tool, true, ) @@ -822,32 +837,71 @@ impl HumArmorFootSpec { } impl HumMainWeaponSpec { - fn mesh_main_weapon(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes { - let spec = match self.0.get(item_definition_id) { - Some(spec) => spec, - None => { - error!(?item_definition_id, "No tool/weapon specification exists"); - return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0)); - }, + fn mesh_main_weapon( + &self, + modular_components: &HumModularComponentSpec, + tool: &ToolKey, + flipped: bool, + ) -> BoneMeshes { + // TODO: resolve ItemDef info into the ToolKey earlier + let itemdef = Arc::::load_expect_cloned(&tool.name); + let not_found = |name: &str| { + error!(?name, "No tool/weapon specification exists"); + load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0)) }; + let (tool_kind_segment, mut offset) = if itemdef.is_modular() { + let (mut damage, mut held) = (None, None); + for comp in tool.components.iter() { + let compdef = Arc::::load_expect_cloned(comp); + if compdef.is_component(ModularComponentKind::Held) { + held = Some(comp.clone()); + } + if compdef.is_component(ModularComponentKind::Damage) { + damage = Some(comp.clone()); + } + // TODO: when enhancements are added, line them up to make a + // pretty row of gems on the hilt or something + } + if let (Some(damage), Some(held)) = (damage.as_ref(), held.as_ref()) { + use ModularComponentSpec::*; + let damagespec = match modular_components.0.get(&*damage) { + Some(Damage(spec)) => spec, + _ => return not_found(&damage), + }; + let heldspec = match modular_components.0.get(&*held) { + Some(Held(spec)) => spec, + _ => return not_found(&held), + }; + let damage_asset = graceful_load_vox(&damagespec.0).read(); + let held_asset = graceful_load_vox(&heldspec.0).read(); - let tool_kind_segment = if flipped { - graceful_load_segment_flipped(&spec.vox_spec.0) - } else { - graceful_load_segment(&spec.vox_spec.0) - }; - - let offset = Vec3::new( - if flipped { - //log::warn!("tool kind segment {:?}", ); - //tool_kind_segment.; - 0.0 - spec.vox_spec.1[0] - (tool_kind_segment.sz.x as f32) + let (segment, segment_origin) = Segment::from_voxes(&[ + (&damage_asset.0, Vec3::from(damagespec.1), flipped), + (&held_asset.0, Vec3::from(heldspec.2), flipped), + ]); + (segment, segment_origin.map(|x: i32| x as f32) + heldspec.1) } else { - spec.vox_spec.1[0] - }, - spec.vox_spec.1[1], - spec.vox_spec.1[2], - ); + error!( + "A modular weapon is missing some components (damage: {:?}, held: {:?})", + damage, held + ); + return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0)); + } + } else { + let spec = match self.0.get(&tool.name) { + Some(spec) => spec, + None => return not_found(&tool.name), + }; + + ( + graceful_load_segment_flipped(&spec.vox_spec.0, flipped), + Vec3::from(spec.vox_spec.1), + ) + }; + + if flipped { + offset.x = 0.0 - offset.x - (tool_kind_segment.sz.x as f32); + } (tool_kind_segment, offset) }