Fixed loading of modular weapons from the loadout to work with nested components.

Modular weapons can now be correctly dispalyed when wielded.
This commit is contained in:
Sam 2021-08-07 19:48:13 -04:00
parent 0a38567e8d
commit 38cb465722
6 changed files with 422 additions and 453 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
({
"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),
)),
})

View File

@ -138,11 +138,10 @@ impl Loadout {
}
}
pub fn update_item_at_slot_using_persistence_key<F: FnOnce(&mut Item)>(
pub fn get_mut_item_at_slot_using_persistence_key(
&mut self,
persistence_key: &str,
f: F,
) -> Result<(), LoadoutError> {
) -> Result<&mut Item, LoadoutError> {
self.slots
.iter_mut()
.find(|loadout_slot| loadout_slot.persistence_key == persistence_key)
@ -150,11 +149,17 @@ impl Loadout {
loadout_slot
.slot
.as_mut()
.map_or(Err(LoadoutError::NoParentAtSlot), |item| {
f(item);
Ok(())
})
.ok_or(LoadoutError::NoParentAtSlot)
})
// .map_or(Err(LoadoutError::InvalidPersistenceKey), |loadout_slot| {
// loadout_slot
// .slot
// .as_mut()
// .map_or(Err(LoadoutError::NoParentAtSlot), |item| {
// f(item);
// Ok(())
// })
// })
}
/// Swaps the contents of two loadout slots

View File

@ -256,6 +256,56 @@ pub fn convert_waypoint_from_database_json(
))
}
// Used to handle cases of modular items that are composed of components.
// Returns a mutable reference to the parent of an item that is a component. If
// parent item is itself a component, recursively goes through inventory until
// it grabs component.
fn get_mutable_parent_item<'a, 'b, T>(
index: usize,
inventory_items: &'a [Item],
item_indices: &'a HashMap<i64, usize>,
inventory: &'b mut T,
get_mut_item: &'a dyn Fn(&'b mut T, &str) -> Option<&'b mut VelorenItem>,
) -> Option<&'a mut VelorenItem>
where
'b: 'a,
{
// First checks if parent item is itself also a component, if it is, tries to
// get a mutable reference to itself by getting a mutable reference to the item
// that is its own parent
if inventory_items[index].position.contains("component_") {
if let Some(parent) = item_indices
.get(&inventory_items[index].parent_container_item_id)
.and_then(move |i| {
get_mutable_parent_item(
*i,
inventory_items,
item_indices,
inventory,
// slot,
get_mut_item,
)
})
{
// Parses component index
let component_index = inventory_items[index]
.position
.split('_')
.collect::<Vec<_>>()
.get(1)
.and_then(|s| s.parse::<usize>().ok());
// Returns mutable reference to parent item of original item, by grabbing
// the component representing the parent item from the parent item's parent
// item
component_index.and_then(move |i| parent.component_mut(i))
} else {
None
}
} else {
get_mut_item(inventory, &inventory_items[index].position)
}
}
/// Properly-recursive items (currently modular weapons) occupy the same
/// inventory slot as their parent. The caller is responsible for ensuring that
/// inventory_items and loadout_items are topologically sorted (i.e. forall i,
@ -344,59 +394,13 @@ pub fn convert_inventory_from_database_items(
));
}
} else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
// Returns a mutable reference to the parent of an item that is a component. If
// parent item is itself a component, recursively goes through inventory until
// it grabs component.
fn get_mutable_parent_item<'a>(
index: usize,
inventory_items: &'a [Item],
item_indices: &'a HashMap<i64, usize>,
inventory: &'a mut Inventory,
slot: &dyn Fn(&str) -> Result<InvSlotId, PersistenceError>,
) -> Option<&'a mut VelorenItem> {
// First checks if parent item is itself also a component, if it is, tries to
// get a mutable reference to itself by getting a mutable reference to the item
// that is its own parent
if inventory_items[index].position.contains("component_") {
if let Some(parent) = item_indices
.get(&inventory_items[index].parent_container_item_id)
.and_then(move |i| {
get_mutable_parent_item(
*i,
inventory_items,
item_indices,
inventory,
slot,
)
})
{
// Parses component index
let component_index = inventory_items[index]
.position
.split('_')
.collect::<Vec<_>>()
.get(1)
.and_then(|s| s.parse::<usize>().ok());
// Returns mutable reference to parent item of original item, by grabbing
// the component representing the parent item from the parent item's parent
// item
component_index.and_then(move |i| parent.component_mut(i))
} else {
None
}
} else if let Some(parent) =
// Parent item is not itself a component, just grab it from the
// inventory
inventory.slot_mut(slot(&inventory_items[index].position).ok()?)
{
parent.as_mut()
} else {
None
}
}
if let Some(parent) =
get_mutable_parent_item(j, inventory_items, &item_indices, &mut inventory, &slot)
{
if let Some(parent) = get_mutable_parent_item(
j,
inventory_items,
&item_indices,
&mut inventory,
&|inv, s| inv.slot_mut(slot(s).ok()?).and_then(|a| a.as_mut()),
) {
parent.add_component(item, &ABILITY_MAP, &MATERIAL_STATS_MANIFEST);
} else {
return Err(PersistenceError::ConversionError(format!(
@ -451,11 +455,25 @@ pub fn convert_loadout_from_database_items(
.set_item_at_slot_using_persistence_key(&db_item.position, item)
.map_err(convert_error)?;
} else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
loadout
.update_item_at_slot_using_persistence_key(&database_items[j].position, |parent| {
parent.add_component(item, &ABILITY_MAP, &MATERIAL_STATS_MANIFEST);
if let Some(parent) =
get_mutable_parent_item(j, database_items, &item_indices, &mut loadout, &|l, s| {
l.get_mut_item_at_slot_using_persistence_key(s).ok()
})
.map_err(convert_error)?;
{
parent.add_component(item, &ABILITY_MAP, &MATERIAL_STATS_MANIFEST);
} else {
return Err(PersistenceError::ConversionError(format!(
"Parent slot {} for component {} was empty even though it occurred earlier in \
the loop?",
db_item.parent_container_item_id, db_item.item_id
)));
}
// loadout
// .update_item_at_slot_using_persistence_key(&
// database_items[j].position, |parent| {
// parent.add_component(item, &ABILITY_MAP,
// &MATERIAL_STATS_MANIFEST); })
// .map_err(convert_error)?;
} else {
return Err(PersistenceError::ConversionError(format!(
"Couldn't find parent item {} before item {} in loadout",

View File

@ -15,7 +15,7 @@ use common::{
item::{
armor::{Armor, ArmorKind},
item_key::ItemKey,
Item, ItemKind,
modular, Item, ItemKind,
},
CharacterState,
},
@ -26,6 +26,7 @@ use common::{
use core::{hash::Hash, ops::Range};
use crossbeam_utils::atomic;
use hashbrown::{hash_map::Entry, HashMap};
use serde::Deserialize;
use std::sync::Arc;
use vek::*;
@ -71,10 +72,10 @@ pub struct FigureKey<Body> {
pub(super) extra: Option<Arc<CharacterCacheKey>>,
}
#[derive(Eq, Hash, PartialEq)]
pub(super) struct ToolKey {
pub name: String,
pub components: Vec<String>,
#[derive(Deserialize, Eq, Hash, PartialEq, Debug)]
pub enum ToolKey {
Tool(String),
Modular(modular::ModularWeaponKey),
}
/// Character data that should be visible when tools are visible (i.e. in third
@ -214,13 +215,12 @@ 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(),
let tool_key_from_item = |item: &Item| {
if item.is_modular() {
ToolKey::Modular(modular::weapon_to_key(item))
} else {
ToolKey::Tool(item.item_definition_id().to_owned())
}
};
Some(CharacterToolKey {
active: inventory

View File

@ -12,7 +12,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::{item_key::ItemKey, ItemDef, ModularComponentKind},
item::item_key::ItemKey,
item_drop, object,
quadruped_low::{self, BodyType as QLBodyType, Species as QLSpecies},
quadruped_medium::{self, BodyType as QMBodyType, Species as QMSpecies},
@ -28,7 +28,7 @@ use common::{
};
use hashbrown::HashMap;
use serde::Deserialize;
use std::{fmt, hash::Hash, sync::Arc};
use std::{fmt, hash::Hash};
use tracing::{error, warn};
use vek::*;
@ -339,6 +339,7 @@ impl HumHeadSpec {
)
}
}
// Armor aspects should be in the same order, top to bottom.
// These seem overly split up, but wanted to keep the armor seperated
// unlike head which is done above.
@ -365,7 +366,7 @@ struct HumArmorPantsSpec(ArmorVoxSpecMap<String, ArmorVoxSpec>);
#[derive(Deserialize)]
struct HumArmorFootSpec(ArmorVoxSpecMap<String, ArmorVoxSpec>);
#[derive(Deserialize)]
struct HumMainWeaponSpec(HashMap<String, ArmorVoxSpec>);
struct HumMainWeaponSpec(HashMap<ToolKey, ArmorVoxSpec>);
#[derive(Deserialize)]
struct HumModularComponentSpec(HashMap<String, ModularComponentSpec>);
#[derive(Deserialize)]
@ -390,7 +391,6 @@ 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.biped_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",
armor_head: HumArmorHeadSpec = "voxygen.voxel.humanoid_armor_head_manifest",
@ -499,14 +499,12 @@ 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,
)
@ -876,68 +874,20 @@ impl HumArmorFootSpec {
}
impl HumMainWeaponSpec {
fn mesh_main_weapon(
&self,
modular_components: &HumModularComponentSpec,
tool: &ToolKey,
flipped: bool,
) -> BoneMeshes {
// TODO: resolve ItemDef info into the ToolKey earlier
let itemdef = Arc::<ItemDef>::load_expect_cloned(&tool.name);
let not_found = |name: &str| {
error!(?name, "No tool/weapon specification exists");
fn mesh_main_weapon(&self, tool: &ToolKey, flipped: bool) -> BoneMeshes {
let not_found = |tool: &ToolKey| {
error!(?tool, "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::<ItemDef>::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 (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 {
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),
)
let spec = match self.0.get(tool) {
Some(spec) => spec,
None => return not_found(tool),
};
let tool_kind_segment = graceful_load_segment_flipped(&spec.vox_spec.0, flipped);
let mut offset = Vec3::from(spec.vox_spec.1);
if flipped {
offset.x = 0.0 - offset.x - (tool_kind_segment.sz.x as f32);
}
@ -2862,7 +2812,7 @@ impl FishSmallLateralSpec {
////
#[derive(Deserialize)]
struct BipedSmallWeaponSpec(HashMap<String, ArmorVoxSpec>);
struct BipedSmallWeaponSpec(HashMap<ToolKey, ArmorVoxSpec>);
#[derive(Deserialize)]
struct BipedSmallArmorHeadSpec(ArmorVoxSpecMap<String, ArmorVoxSpec>);
#[derive(Deserialize)]
@ -2929,7 +2879,7 @@ make_vox_spec!(
}),
tool.and_then(|tool| tool.active.as_ref()).map(|tool| {
spec.weapon.read().0.mesh_main(
&tool.name,
tool,
false,
)
}),
@ -3106,11 +3056,11 @@ impl BipedSmallArmorFootSpec {
}
impl BipedSmallWeaponSpec {
fn mesh_main(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(item_definition_id) {
fn mesh_main(&self, tool: &ToolKey, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(tool) {
Some(spec) => spec,
None => {
error!(?item_definition_id, "No tool/weapon specification exists");
error!(?tool, "No tool/weapon specification exists");
return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0));
},
};
@ -3905,9 +3855,9 @@ struct BipedLargeLateralSubSpec {
lateral: VoxSimple,
}
#[derive(Deserialize)]
struct BipedLargeMainSpec(HashMap<String, ArmorVoxSpec>);
struct BipedLargeMainSpec(HashMap<ToolKey, ArmorVoxSpec>);
#[derive(Deserialize)]
struct BipedLargeSecondSpec(HashMap<String, ArmorVoxSpec>);
struct BipedLargeSecondSpec(HashMap<ToolKey, ArmorVoxSpec>);
make_vox_spec!(
biped_large::Body,
struct BipedLargeSpec {
@ -3954,13 +3904,13 @@ make_vox_spec!(
)),
tool.and_then(|tool| tool.active.as_ref()).map(|tool| {
spec.main.read().0.mesh_main(
&tool.name,
tool,
false,
)
}),
tool.and_then(|tool| tool.active.as_ref()).map(|tool| {
spec.second.read().0.mesh_second(
&tool.name,
tool,
false,
)
}),
@ -4212,11 +4162,11 @@ impl BipedLargeLateralSpec {
}
}
impl BipedLargeMainSpec {
fn mesh_main(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(item_definition_id) {
fn mesh_main(&self, tool: &ToolKey, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(tool) {
Some(spec) => spec,
None => {
error!(?item_definition_id, "No tool/weapon specification exists");
error!(?tool, "No tool/weapon specification exists");
return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0));
},
};
@ -4241,11 +4191,11 @@ impl BipedLargeMainSpec {
}
}
impl BipedLargeSecondSpec {
fn mesh_second(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(item_definition_id) {
fn mesh_second(&self, tool: &ToolKey, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(tool) {
Some(spec) => spec,
None => {
error!(?item_definition_id, "No tool/weapon specification exists");
error!(?tool, "No tool/weapon specification exists");
return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0));
},
};