diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 062240bbc0..f75c928036 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -565,6 +565,12 @@ Simple(None, "common.abilities.custom.arthropods.dagonite.leapshockwave"), ], ), + // Crustaceans + Custom("Crab"): ( + primary: Simple(None, "common.abilities.custom.crab.triplestrike"), + secondary: Simple(None, "common.abilities.custom.crab.triplestrike"), + abilities: [], + ), /// TODO: Organize the rest into further catagories and give purple tier droppers+ custom skillsets Custom("Turret"): ( primary: Simple(None, "common.abilities.custom.turret.arrows"), diff --git a/assets/common/abilities/custom/crab/triplestrike.ron b/assets/common/abilities/custom/crab/triplestrike.ron new file mode 100644 index 0000000000..4a026a239b --- /dev/null +++ b/assets/common/abilities/custom/crab/triplestrike.ron @@ -0,0 +1,65 @@ +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 5, + poise: 5, + knockback: 0, + energy_regen: 0, + ), + range: 1.1, + angle: 30.0, + ), + buildup_duration: 1.3, + swing_duration: 0.07, + hit_timing: 0.5, + recover_duration: 0.6, + movement: ( + swing: Some(Forward(0.5)), + ), + ori_modifier: 0.65, + ), + ( + melee_constructor: ( + kind: Slash( + damage: 8, + poise: 8, + knockback: 1, + energy_regen: 0, + ), + range: 1.1, + angle: 30.0, + ), + buildup_duration: 0.8, + swing_duration: 0.07, + hit_timing: 0.5, + recover_duration: 0.6, + movement: ( + swing: Some(Forward(0.5)), + ), + ori_modifier: 0.65, + ), + ( + melee_constructor: ( + kind: Slash( + damage: 12, + poise: 9, + knockback: 1, + energy_regen: 0, + ), + range: 1.1, + angle: 30.0, + ), + buildup_duration: 0.8, + swing_duration: 0.07, + hit_timing: 0.5, + recover_duration: 0.6, + movement: ( + swing: Some(Forward(0.5)), + ), + ori_modifier: 0.65, + ), + ], + energy_cost_per_strike: 0, +) diff --git a/assets/common/entity/wild/peaceful/crab.ron b/assets/common/entity/wild/peaceful/crab.ron new file mode 100644 index 0000000000..c3e4d1196f --- /dev/null +++ b/assets/common/entity/wild/peaceful/crab.ron @@ -0,0 +1,11 @@ +#![enable(implicit_some)] +( + name: Automatic, + body: RandomWith("crab"), + alignment: Alignment(Wild), + loot: LootTable("common.loot_tables.creature.crustacean.crab"), + inventory: ( + loadout: FromBody, + ), + meta: [], +) \ No newline at end of file diff --git a/assets/common/items/npc_weapons/unique/crab_pincer.ron b/assets/common/items/npc_weapons/unique/crab_pincer.ron new file mode 100644 index 0000000000..83b24cc10f --- /dev/null +++ b/assets/common/items/npc_weapons/unique/crab_pincer.ron @@ -0,0 +1,20 @@ +ItemDef( + name: "Crab Pincer", + description: "testing123", + kind: Tool(( + kind: Natural, + hands: Two, + stats: ( + equip_time_secs: 0.5, + power: 1.0, + effect_power: 1.0, + speed: 1.0, + range: 1.0, + energy_efficiency: 1.0, + buff_strength: 1.0, + ), + )), + quality: Low, + tags: [], + ability_spec: Some(Custom("Crab")), +) \ No newline at end of file diff --git a/assets/common/loot_tables/creature/crustacean/crab.ron b/assets/common/loot_tables/creature/crustacean/crab.ron new file mode 100644 index 0000000000..7a60369c4c --- /dev/null +++ b/assets/common/loot_tables/creature/crustacean/crab.ron @@ -0,0 +1,4 @@ +[ + (1.0, Item("common.items.crafting_ing.animal_misc.viscous_ooze")), + (2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.strong_pincer"), 1, 2)), +] \ No newline at end of file diff --git a/assets/common/npc_names.ron b/assets/common/npc_names.ron index 6516ea1b79..c35b0dd555 100644 --- a/assets/common/npc_names.ron +++ b/assets/common/npc_names.ron @@ -1417,4 +1417,16 @@ ), ) ), + crustacean: ( + body: ( + keyword: "crustacean", + names_0: ["Ferris"], + ), + species: ( + crab: ( + keyword: "crab", + generic: "Crab", + ), + ) + ), ) diff --git a/assets/voxygen/voxel/crustacean_central_manifest.ron b/assets/voxygen/voxel/crustacean_central_manifest.ron new file mode 100644 index 0000000000..a9630ff6f3 --- /dev/null +++ b/assets/voxygen/voxel/crustacean_central_manifest.ron @@ -0,0 +1,30 @@ +({ + (Crab, Male): ( + chest: ( + offset: (-5.0, -4.5, 2.0), + central: ("npc.crab.crab"), + ), + tail_f: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ), + tail_b: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ), + ), + (Crab, Female): ( + chest: ( + offset: (-5.0, -4.5, 2.0), + central: ("npc.crab.crab"), + ), + tail_f: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ), + tail_b: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ), + ), +}) \ No newline at end of file diff --git a/assets/voxygen/voxel/crustacean_lateral_manifest.ron b/assets/voxygen/voxel/crustacean_lateral_manifest.ron new file mode 100644 index 0000000000..91cd03c18f --- /dev/null +++ b/assets/voxygen/voxel/crustacean_lateral_manifest.ron @@ -0,0 +1,127 @@ + +({ + (Crab, Male): ( + arm_l: ( + offset: (-6.0, 0.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 1, + ), + pincer_l0: ( + offset: (-9.0, 2.5, 1.0), + lateral: ("npc.crab.crab"), + model_index: 5, + ), + pincer_l1: ( + offset: (-5.0, 4.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 6, + ), + arm_r: ( + offset: (3.0, 0.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 1, + ), + pincer_r0: ( + offset: (2.0, 2.5, 1.0), + lateral: ("npc.crab.crab"), + model_index: 5, + ), + pincer_r1: ( + offset: (2.0, 4.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 6, + ), + leg_fl: ( + offset: (-8.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 4, + ), + leg_cl: ( + offset: (-8.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 3, + ), + leg_bl: ( + offset: (-7.5, -3.0, 0.0), + lateral: ("npc.crab.crab"), + model_index: 2, + ), + leg_fr: ( + offset: (3.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 4, + ), + leg_cr: ( + offset: (3.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 3, + ), + leg_br: ( + offset: (2.5, -3.0, 0.0), + lateral: ("npc.crab.crab"), + model_index: 2, + ), + ), + (Crab, Female): ( + arm_l: ( + offset: (-6.0, 0.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 1, + ), + pincer_l0: ( + offset: (-9.0, 2.5, 1.0), + lateral: ("npc.crab.crab"), + model_index: 5, + ), + pincer_l1: ( + offset: (-5.0, 4.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 6, + ), + arm_r: ( + offset: (3.0, 0.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 1, + ), + pincer_r0: ( + offset: (2.0, 2.5, 1.0), + lateral: ("npc.crab.crab"), + model_index: 5, + ), + pincer_r1: ( + offset: (2.0, 4.5, 2.0), + lateral: ("npc.crab.crab"), + model_index: 6, + ), + leg_fl: ( + offset: (-8.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 4, + ), + leg_cl: ( + offset: (-8.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 3, + ), + leg_bl: ( + offset: (-7.5, -3.0, 0.0), + lateral: ("npc.crab.crab"), + model_index: 2, + ), + leg_fr: ( + offset: (3.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 4, + ), + leg_cr: ( + offset: (3.5, -2.5, 0.0), + lateral: ("npc.crab.crab"), + model_index: 3, + ), + leg_br: ( + offset: (2.5, -3.0, 0.0), + lateral: ("npc.crab.crab"), + model_index: 2, + ), + ), +}) \ No newline at end of file diff --git a/assets/voxygen/voxel/npc/crab/crab.vox b/assets/voxygen/voxel/npc/crab/crab.vox new file mode 100644 index 0000000000..0f66c21bad --- /dev/null +++ b/assets/voxygen/voxel/npc/crab/crab.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:284544e0c847991458fdf59ca0c82be58577e0b38b44d2377813a57a59f330ec +size 5100 diff --git a/assets/world/wildlife/spawn/tropical/beach.ron b/assets/world/wildlife/spawn/tropical/beach.ron index 3890ed270c..2282be4118 100644 --- a/assets/world/wildlife/spawn/tropical/beach.ron +++ b/assets/world/wildlife/spawn/tropical/beach.ron @@ -5,6 +5,7 @@ SpawnEntry ( Pack( groups: [ (3, (1, 3, "common.entity.wild.aggressive.sea_crocodile")), + (3, (8, 16, "common.entity.wild.peaceful.crab")), (1, (1, 1, "common.entity.wild.aggressive.reefsnapper")), ], spawn_mode: Land, diff --git a/assets/world/wildlife/spawn/tropical/river.ron b/assets/world/wildlife/spawn/tropical/river.ron index 948abacd78..b9d8988d81 100644 --- a/assets/world/wildlife/spawn/tropical/river.ron +++ b/assets/world/wildlife/spawn/tropical/river.ron @@ -14,6 +14,7 @@ SpawnEntry ( Pack( groups: [ (2, (1, 1, "common.entity.wild.peaceful.kelpie")), + (2, (8, 16, "common.entity.wild.peaceful.crab")), (2, (1, 1, "common.entity.wild.aggressive.crocodile")), ], spawn_mode: Land, diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 8f4a3d4476..0e5c5e3621 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -380,6 +380,7 @@ impl<'a> From<&'a Body> for Psyche { arthropod::Species::Dagonite => 0.2, arthropod::Species::Emberfly => 0.1, }, + Body::Crustacean(_) => 0.0, }, sight_dist: match body { Body::BirdLarge(_) => 250.0, diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 9331aff0dd..4965f593c7 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -3,6 +3,7 @@ pub mod biped_large; pub mod biped_small; pub mod bird_large; pub mod bird_medium; +pub mod crustacean; pub mod dragon; pub mod fish_medium; pub mod fish_small; @@ -52,6 +53,7 @@ make_case_elim!( Ship(body: ship::Body) = 14, Arthropod(body: arthropod::Body) = 15, ItemDrop(body: item_drop::Body) = 16, + Crustacean(body: crustacean::Body) = 17, } ); @@ -101,6 +103,7 @@ pub struct AllBodies { pub quadruped_low: BodyData>, pub ship: BodyData, pub arthropod: BodyData>, + pub crustacean: BodyData>, } impl AllBodies { @@ -124,6 +127,7 @@ impl AllBodies { Body::Theropod(b) => &self.theropod.species[&b.species], Body::QuadrupedLow(b) => &self.quadruped_low.species[&b.species], Body::Arthropod(b) => &self.arthropod.species[&b.species], + Body::Crustacean(b) => &self.crustacean.species[&b.species], _ => return None, }) } @@ -150,6 +154,7 @@ impl core::ops::Index for AllBodies &self.dragon.body, NpcKind::Crocodile => &self.quadruped_low.body, NpcKind::Tarantula => &self.arthropod.body, + NpcKind::Crab => &self.crustacean.body, } } } @@ -178,6 +183,7 @@ impl<'a, BodyMeta, SpeciesMeta> core::ops::Index<&'a Body> for AllBodies &self.quadruped_low.body, Body::Arthropod(_) => &self.arthropod.body, Body::Ship(_) => &self.ship.body, + Body::Crustacean(_) => &self.crustacean.body, } } } @@ -254,6 +260,10 @@ impl Body { _ => false, }, Body::Ship(_) => false, + Body::Crustacean(b1) => match other { + Body::Crustacean(b2) => b1.species == b2.species, + _ => false, + }, } } @@ -469,6 +479,8 @@ impl Body { }, Body::Ship(ship) => ship.mass().0, Body::Arthropod(_) => 200.0, + // TODO: mass + Body::Crustacean(_) => 50.0, }; Mass(m) } @@ -641,6 +653,7 @@ impl Body { bird_medium::Species::Puffin => Vec3::new(1.0, 1.0, 1.0), bird_medium::Species::Toucan => Vec3::new(2.1, 1.1, 1.2), }, + Body::Crustacean(_) => Vec3::new(1.2, 1.2, 0.7), } } @@ -965,6 +978,7 @@ impl Body { _ => 70, }, Body::Ship(_) => 1000, + Body::Crustacean(_) => 40, } } @@ -1135,7 +1149,13 @@ impl Body { pub fn can_strafe(&self) -> bool { matches!( self, - Body::Humanoid(_) | Body::BipedSmall(_) | Body::BipedLarge(_) + Body::Humanoid(_) + | Body::BipedSmall(_) + | Body::BipedLarge(_) + | Body::Crustacean(crustacean::Body { + species: crustacean::Species::Crab, + .. + }) ) } diff --git a/common/src/comp/body/crustacean.rs b/common/src/comp/body/crustacean.rs new file mode 100644 index 0000000000..7d1a18f671 --- /dev/null +++ b/common/src/comp/body/crustacean.rs @@ -0,0 +1,66 @@ +use rand::{seq::SliceRandom, thread_rng}; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct Body { + pub species: Species, + pub body_type: BodyType, +} + +impl Body { + pub fn random() -> Self { + let mut rng = thread_rng(); + let species = *ALL_SPECIES.choose(&mut rng).unwrap(); + Self::random_with(&mut rng, &species) + } + + #[inline] + pub fn random_with(rng: &mut impl rand::Rng, &species: &Species) -> Self { + let body_type = *ALL_BODY_TYPES.choose(rng).unwrap(); + Self { species, body_type } + } +} + +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::Crustacean(body) } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Species { + Crab = 0, +} + +/// Data representing per-species generic data. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AllSpecies { + pub crab: SpeciesMeta, +} + +impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies { + type Output = SpeciesMeta; + + #[inline] + fn index(&self, &index: &'a Species) -> &Self::Output { + match index { + Species::Crab => &self.crab, + } + } +} + +pub const ALL_SPECIES: [Species; 1] = [Species::Crab]; + +impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies { + type IntoIter = std::iter::Copied>; + type Item = Species; + + fn into_iter(self) -> Self::IntoIter { ALL_SPECIES.iter().copied() } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum BodyType { + Female = 0, + Male = 1, +} +pub const ALL_BODY_TYPES: [BodyType; 2] = [BodyType::Female, BodyType::Male]; diff --git a/common/src/comp/fluid_dynamics.rs b/common/src/comp/fluid_dynamics.rs index 0ab43892ec..97968be1b6 100644 --- a/common/src/comp/fluid_dynamics.rs +++ b/common/src/comp/fluid_dynamics.rs @@ -263,7 +263,7 @@ impl Body { }, // Cross-section, zero-lift angle; exclude the fins (width * 0.2) - Body::FishMedium(_) | Body::FishSmall(_) => { + Body::FishMedium(_) | Body::FishSmall(_) | Body::Crustacean(_) => { let dim = self.dimensions().map(|a| a * 0.5 * scale); // "A Simple Method to Determine Drag Coefficients in Aquatic Animals", // D. Bilo and W. Nachtigall, 1980 diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 3bdcf6ed44..8acb55451b 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -855,6 +855,10 @@ fn default_main_tool(body: &Body) -> Item { "common.items.npc_weapons.unique.birdmediumbasic", )), }, + Body::Crustacean(_) => Some(Item::new_from_asset_expect( + "common.items.npc_weapons.unique.crab_pincer", + )), + _ => None, }; diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 58ed61cd91..29ff961ce7 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -52,9 +52,9 @@ pub use self::{ aura::{Aura, AuraChange, AuraKind, Auras}, beam::Beam, body::{ - arthropod, biped_large, biped_small, bird_large, bird_medium, dragon, fish_medium, - fish_small, golem, humanoid, item_drop, object, quadruped_low, quadruped_medium, - quadruped_small, ship, theropod, AllBodies, Body, BodyData, + arthropod, biped_large, biped_small, bird_large, bird_medium, crustacean, dragon, + fish_medium, fish_small, golem, humanoid, item_drop, object, quadruped_low, + quadruped_medium, quadruped_small, ship, theropod, AllBodies, Body, BodyData, }, buff::{ Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffKey, BuffKind, BuffSource, Buffs, diff --git a/common/src/generation.rs b/common/src/generation.rs index c33202ca0e..7a4a4fafab 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -469,6 +469,7 @@ impl EntityInfo { Body::Golem(body) => Some(get_npc_name(&npc_names.golem, body.species)), Body::BipedLarge(body) => Some(get_npc_name(&npc_names.biped_large, body.species)), Body::Arthropod(body) => Some(get_npc_name(&npc_names.arthropod, body.species)), + Body::Crustacean(body) => Some(get_npc_name(&npc_names.crustacean, body.species)), _ => None, }; self.name = name.map(|name| { diff --git a/common/src/npc.rs b/common/src/npc.rs index 0d40aafa1e..2d7f27ba27 100644 --- a/common/src/npc.rs +++ b/common/src/npc.rs @@ -23,9 +23,10 @@ pub enum NpcKind { Reddragon, Crocodile, Tarantula, + Crab, } -pub const ALL_NPCS: [NpcKind; 14] = [ +pub const ALL_NPCS: [NpcKind; 15] = [ NpcKind::Humanoid, NpcKind::Wolf, NpcKind::Pig, @@ -40,6 +41,7 @@ pub const ALL_NPCS: [NpcKind; 14] = [ NpcKind::Reddragon, NpcKind::Crocodile, NpcKind::Tarantula, + NpcKind::Crab, ]; /// Body-specific NPC name metadata. @@ -136,6 +138,7 @@ pub fn kind_to_body(kind: NpcKind) -> Body { NpcKind::Reddragon => comp::dragon::Body::random().into(), NpcKind::Crocodile => comp::quadruped_low::Body::random().into(), NpcKind::Tarantula => comp::arthropod::Body::random().into(), + NpcKind::Crab => comp::crustacean::Body::random().into(), } } @@ -314,6 +317,14 @@ impl NpcBody { comp::arthropod::Body::random_with, ) }) + .or_else(|| { + parse( + s, + NpcKind::Crab, + &npc_names.crustacean, + comp::crustacean::Body::random_with, + ) + }) .ok_or(()) } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index bc08c65388..dc0b6a0e1d 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -166,6 +166,7 @@ impl Body { arthropod::Species::Dagonite => 70.0, arthropod::Species::Emberfly => 75.0, }, + Body::Crustacean(_) => 80.0, } } @@ -232,6 +233,7 @@ impl Body { Body::Ship(ship) if ship.has_water_thrust() => 5.0 / self.dimensions().y, Body::Ship(_) => 6.0 / self.dimensions().y, Body::Arthropod(_) => 3.5, + Body::Crustacean(_) => 3.5, } } @@ -276,6 +278,7 @@ impl Body { }, Body::QuadrupedSmall(_) => 1500.0 * self.mass().0, Body::Arthropod(_) => 500.0 * self.mass().0, + Body::Crustacean(_) => 400.0 * self.mass().0, } * front_profile, ) } diff --git a/voxygen/anim/src/crustacean/alpha.rs b/voxygen/anim/src/crustacean/alpha.rs new file mode 100644 index 0000000000..a176939bc7 --- /dev/null +++ b/voxygen/anim/src/crustacean/alpha.rs @@ -0,0 +1,56 @@ +use super::{ + super::{vek::*, Animation}, + CrustaceanSkeleton, SkeletonAttr, +}; +use common::states::utils::StageSection; + +pub struct AlphaAnimation; + +impl Animation for AlphaAnimation { + type Dependency<'a> = (f32, f32, Option, f32); + type Skeleton = CrustaceanSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"crustacean_alpha\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_alpha")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (_velocity, global_time, stage_section, timer): Self::Dependency<'_>, + anim_time: f32, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + let (movement1, movement2, movement3) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powi(2), 0.0, 0.0), + Some(StageSection::Action) => (1.0, anim_time.powi(4), 0.0), + Some(StageSection::Recover) => (1.0, 1.0, anim_time), + _ => (0.0, 0.0, 0.0), + }; + let pullback = 1.0 - movement3; + let subtract = global_time - timer; + let check = subtract - subtract.trunc(); + let _mirror = (check - 0.5).signum(); + let _movement1abs = movement1 * pullback; + let _movement2abs = movement2 * pullback; + let _movement3abs = movement3 * pullback; + + next.arm_l.orientation = Quaternion::rotation_x(anim_time); + + next.chest.scale = Vec3::one() * s_a.scaler; + + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1); + + next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + + next.leg_cl.position = Vec3::new(-s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cr.position = Vec3::new(s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + + next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next + } +} diff --git a/voxygen/anim/src/crustacean/combomelee.rs b/voxygen/anim/src/crustacean/combomelee.rs new file mode 100644 index 0000000000..4062ec32fe --- /dev/null +++ b/voxygen/anim/src/crustacean/combomelee.rs @@ -0,0 +1,93 @@ +use super::{ + super::{vek::*, Animation}, + CrustaceanSkeleton, SkeletonAttr, +}; +use common::states::utils::{AbilityInfo, StageSection}; + +pub struct ComboAnimation; +impl Animation for ComboAnimation { + type Dependency<'a> = ( + Option<&'a str>, + Option, + Option, + usize, + f32, + Vec3, + f32, + ); + type Skeleton = CrustaceanSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"crustacean_combo\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_combo")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (ability_id, stage_section, _ability_info, current_strike, global_time, velocity, timer): Self::Dependency<'_>, + anim_time: f32, + rate: &mut f32, + _s_a: &SkeletonAttr, + ) -> Self::Skeleton { + *rate = 1.0; + let mut next = (*skeleton).clone(); + + let multi_strike_pullback = 1.0 + - if matches!(stage_section, Some(StageSection::Recover)) { + anim_time.powi(4) + } else { + 0.0 + }; + for strike in 0..=current_strike { + match ability_id { + Some("common.abilities.custom.crab.triplestrike") => { + let (movement1base, movement2base, movement3) = match stage_section { + Some(StageSection::Buildup) => (anim_time.sqrt(), 0.0, 0.0), + Some(StageSection::Action) => (1.0, anim_time.powi(4), 0.0), + Some(StageSection::Recover) => (1.0, 1.0, anim_time), + _ => (0.0, 0.0, 0.0), + }; + let pullback = multi_strike_pullback; + let subtract = global_time - timer; + let check = subtract - subtract.trunc(); + let mirror = (check - 0.5).signum(); + let twitch3 = (mirror * movement3 * 9.0).sin(); + let _movement1 = mirror * movement1base * pullback; + let _movement2 = mirror * movement2base * pullback; + let movement1abs = movement1base * pullback; + let movement2abs = movement2base * pullback; + let mirror_var = if strike == 1 { -mirror } else { mirror }; + if velocity.xy().magnitude() > 0.1 { + next.chest.orientation = Quaternion::rotation_z(0.0); + } + + next.chest.orientation = Quaternion::rotation_x( + movement1abs * 0.3 + movement2abs * -0.2 + (twitch3 / 5.0), + ); + next.arm_r.orientation = + Quaternion::rotation_z(movement1abs * -0.8 + movement2abs * 1.0) + * Quaternion::rotation_x(movement1abs * 0.4 + movement2abs * 0.3); + next.arm_r.position = Vec3::new( + 0.0, + 7.0 * movement1abs - 3.0 * movement2abs, + -0.8 * mirror_var, + ); + next.pincer_r1.position = + Vec3::new(0.0, -3.0 * movement1abs + 4.0 * movement2abs, 0.0); + + next.arm_l.orientation = + Quaternion::rotation_z(movement1abs * 0.8 + movement2abs * -1.0) + * Quaternion::rotation_x(movement1abs * 0.4 + movement2abs * 0.3); + next.arm_l.position = Vec3::new( + 0.0, + 7.0 * movement1abs - 3.0 * movement2abs, + 0.8 * mirror_var, + ); + next.pincer_l1.position = + Vec3::new(0.0, -3.0 * movement1abs + 4.0 * movement2abs, 0.0); + }, + _ => {}, + } + } + next + } +} diff --git a/voxygen/anim/src/crustacean/idle.rs b/voxygen/anim/src/crustacean/idle.rs new file mode 100644 index 0000000000..98f7a0c34e --- /dev/null +++ b/voxygen/anim/src/crustacean/idle.rs @@ -0,0 +1,65 @@ +use super::{ + super::{vek::*, Animation}, + CrustaceanSkeleton, SkeletonAttr, +}; + +pub struct IdleAnimation; + +impl Animation for IdleAnimation { + type Dependency<'a> = f32; + type Skeleton = CrustaceanSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"crustacean_idle\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_idle")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + _global_time: Self::Dependency<'_>, + anim_time: f32, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + next.chest.scale = Vec3::one() * s_a.scaler; + + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1); + + let arm = (anim_time * 0.2).sin() * 0.03; + let clasp_l = + (((anim_time * 0.1).fract() * 2.0 - 1.0) * (anim_time * 2.0).sin().powi(2)).powi(16); + let clasp_r = (((anim_time * 0.105 + 33.0).fract() * 2.0 - 1.0) + * (anim_time * 1.95 + 20.0).sin().powi(2)) + .powi(16); + + next.arm_l.position = Vec3::zero(); + next.arm_l.orientation = Quaternion::rotation_x(arm); + next.arm_r.position = Vec3::zero(); + next.arm_r.orientation = Quaternion::rotation_x(arm); + + next.pincer_l0.position = Vec3::zero(); + next.pincer_l1.position = Vec3::zero(); + next.pincer_l1.orientation = Quaternion::rotation_z(clasp_l * 0.15); + next.pincer_r0.position = Vec3::zero(); + next.pincer_r1.position = Vec3::zero(); + next.pincer_r1.orientation = Quaternion::rotation_z(-clasp_r * 0.15); + + next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fl.orientation = Quaternion::rotation_z(s_a.leg_ori.0); + next.leg_fr.orientation = Quaternion::rotation_z(-s_a.leg_ori.0); + + next.leg_cl.position = Vec3::new(-s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cr.position = Vec3::new(s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cl.orientation = Quaternion::rotation_z(s_a.leg_ori.1); + next.leg_cr.orientation = Quaternion::rotation_z(-s_a.leg_ori.1); + + next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_bl.orientation = Quaternion::rotation_z(s_a.leg_ori.2); + next.leg_br.orientation = Quaternion::rotation_z(-s_a.leg_ori.2); + + next + } +} diff --git a/voxygen/anim/src/crustacean/jump.rs b/voxygen/anim/src/crustacean/jump.rs new file mode 100644 index 0000000000..6a9027a68c --- /dev/null +++ b/voxygen/anim/src/crustacean/jump.rs @@ -0,0 +1,54 @@ +use super::{ + super::{vek::*, Animation}, + CrustaceanSkeleton, SkeletonAttr, +}; + +pub struct JumpAnimation; + +impl Animation for JumpAnimation { + type Dependency<'a> = (f32, Vec3, Vec3, f32, Vec3); + type Skeleton = CrustaceanSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"crustacean_jump\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_jump")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (_velocity, _orientation, _last_ori, _global_time, _avg_vel): Self::Dependency<'_>, + _anim_time: f32, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + next.chest.scale = Vec3::one() * s_a.scaler; + + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1); + + let up_rot = 0.2; + + next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fl.orientation = + Quaternion::rotation_z(s_a.leg_ori.0) * Quaternion::rotation_y(up_rot); + next.leg_fr.orientation = + Quaternion::rotation_z(-s_a.leg_ori.0) * Quaternion::rotation_y(-up_rot); + + next.leg_cl.position = Vec3::new(-s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cr.position = Vec3::new(s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cl.orientation = + Quaternion::rotation_z(s_a.leg_ori.1) * Quaternion::rotation_y(up_rot); + next.leg_cr.orientation = + Quaternion::rotation_z(-s_a.leg_ori.1) * Quaternion::rotation_y(-up_rot); + + next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_bl.orientation = + Quaternion::rotation_z(s_a.leg_ori.2) * Quaternion::rotation_y(up_rot); + next.leg_br.orientation = + Quaternion::rotation_z(-s_a.leg_ori.2) * Quaternion::rotation_y(-up_rot); + + next + } +} diff --git a/voxygen/anim/src/crustacean/mod.rs b/voxygen/anim/src/crustacean/mod.rs new file mode 100644 index 0000000000..6e3cb93323 --- /dev/null +++ b/voxygen/anim/src/crustacean/mod.rs @@ -0,0 +1,143 @@ +mod alpha; +mod combomelee; +mod idle; +mod jump; +mod run; +mod stunned; +mod swim; + +// Reexports +pub use self::{ + alpha::AlphaAnimation, combomelee::ComboAnimation, idle::IdleAnimation, jump::JumpAnimation, + run::RunAnimation, stunned::StunnedAnimation, swim::SwimAnimation, +}; + +use common::comp::{self}; + +use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton}; + +pub type Body = comp::crustacean::Body; + +skeleton_impls!(struct CrustaceanSkeleton { + + chest, + + tail_f, + + tail_b, + + arm_l, + + pincer_l0, + + pincer_l1, + + arm_r, + + pincer_r0, + + pincer_r1, + + leg_fl, + + leg_cl, + + leg_bl, + + leg_fr, + + leg_cr, + + leg_br, +}); + +impl Skeleton for CrustaceanSkeleton { + type Attr = SkeletonAttr; + type Body = Body; + + const BONE_COUNT: usize = 15; + #[cfg(feature = "use-dyn-lib")] + const COMPUTE_FN: &'static [u8] = b"crustacean_compute_s\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_compute_s")] + + fn compute_matrices_inner( + &self, + base_mat: Mat4, + buf: &mut [FigureBoneData; super::MAX_BONE_COUNT], + body: Self::Body, + ) -> Offsets { + let base_mat = base_mat * Mat4::scaling_3d(SkeletonAttr::from(&body).scaler / 6.0); + + let chest_mat = base_mat * Mat4::::from(self.chest); + let tail_f_mat = chest_mat * Mat4::::from(self.tail_f); + let tail_b_mat = chest_mat * Mat4::::from(self.tail_b); + let arm_l_mat = chest_mat * Mat4::::from(self.arm_l); + let pincer_l0_mat = arm_l_mat * Mat4::::from(self.pincer_l0); + let pincer_l1_mat = pincer_l0_mat * Mat4::::from(self.pincer_l1); + let arm_r_mat = chest_mat * Mat4::::from(self.arm_r); + let pincer_r0_mat = arm_r_mat * Mat4::::from(self.pincer_r0); + let pincer_r1_mat = pincer_r0_mat * Mat4::::from(self.pincer_r1); + let leg_fl_mat = chest_mat * Mat4::::from(self.leg_fl); + let leg_cl_mat = chest_mat * Mat4::::from(self.leg_cl); + let leg_bl_mat = chest_mat * Mat4::::from(self.leg_bl); + let leg_fr_mat = chest_mat * Mat4::::from(self.leg_fr); + let leg_cr_mat = chest_mat * Mat4::::from(self.leg_cr); + let leg_br_mat = chest_mat * Mat4::::from(self.leg_br); + + *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [ + make_bone(chest_mat), + make_bone(tail_f_mat), + make_bone(tail_b_mat), + make_bone(arm_l_mat), + make_bone(pincer_l0_mat), + make_bone(pincer_l1_mat), + make_bone(arm_r_mat), + make_bone(pincer_r0_mat), + make_bone(pincer_r1_mat), + make_bone(leg_fl_mat), + make_bone(leg_cl_mat), + make_bone(leg_bl_mat), + make_bone(leg_fr_mat), + make_bone(leg_cr_mat), + make_bone(leg_br_mat), + ]; + + // TODO: mount points + //use comp::arthropod::Species::*; + let (mount_bone_mat, mount_bone_ori) = (chest_mat, self.chest.orientation); + // Offset from the mounted bone's origin. + // Note: This could be its own bone if we need to animate it independently. + let mount_position = (mount_bone_mat * Vec4::from_point(mount_point(&body))) + .homogenized() + .xyz(); + // NOTE: We apply the ori from base_mat externally so we don't need to worry + // about it here for now. + let mount_orientation = mount_bone_ori; + + Offsets { + lantern: None, + viewpoint: Some((chest_mat * Vec4::new(0.0, 7.0, 0.0, 1.0)).xyz()), + mount_bone: Transform { + position: mount_position, + orientation: mount_orientation, + scale: Vec3::one(), + }, + primary_trail_mat: None, + secondary_trail_mat: None, + } + } +} + +pub struct SkeletonAttr { + chest: (f32, f32), + leg_f: (f32, f32, f32), + leg_c: (f32, f32, f32), + leg_b: (f32, f32, f32), + leg_ori: (f32, f32, f32), + scaler: f32, +} + +impl From<&Body> for SkeletonAttr { + fn from(_value: &Body) -> Self { + Self { + chest: (0.0, 0.0), + leg_f: (0.0, 0.0, 0.0), + leg_c: (0.0, 0.0, 0.0), + leg_b: (0.0, 0.0, 0.0), + leg_ori: (-0.4, 0.0, 0.4), + scaler: 0.62, + } + } +} + +fn mount_point(_body: &Body) -> Vec3 { + // TODO: mount points + //use comp::arthropod::{BodyType::*, Species::*}; + (0.0, -6.0, 6.0).into() +} diff --git a/voxygen/anim/src/crustacean/run.rs b/voxygen/anim/src/crustacean/run.rs new file mode 100644 index 0000000000..caabbbec4f --- /dev/null +++ b/voxygen/anim/src/crustacean/run.rs @@ -0,0 +1,100 @@ +use super::{ + super::{vek::*, Animation}, + CrustaceanSkeleton, SkeletonAttr, +}; +use std::f32::consts::PI; + +pub struct RunAnimation; + +impl Animation for RunAnimation { + type Dependency<'a> = (Vec3, Vec3, Vec3, f32, Vec3, f32); + type Skeleton = CrustaceanSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"crustacean_run\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_run")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (velocity, orientation, _last_ori, _global_time, avg_vel, acc_vel): Self::Dependency<'_>, + anim_time: f32, + rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + let speed = (Vec2::::from(velocity).magnitude()).min(22.0); + *rate = 1.0; + + let speednorm = speed / 13.0; + + let direction = velocity.y * 0.098 * orientation.y + velocity.x * 0.098 * orientation.x; + + let side = + (velocity.x * -0.098 * orientation.y + velocity.y * 0.098 * orientation.x) * -1.0; + let sideabs = side.abs(); + + let mixed_vel = (acc_vel + anim_time * 6.0) * 0.8; //sets run frequency using speed, with anim_time setting a floor + + //create a mix between a sine and a square wave + //(controllable with ratio variable) + let ratio = 0.1; + let wave1 = (mixed_vel).sin(); + let wave2 = (mixed_vel - PI / 2.0).sin(); + let wave3 = (mixed_vel + PI / 2.0).sin(); + let wave4 = (mixed_vel + PI).sin(); + let slow_wave = (mixed_vel / 20.0).sin(); + let foot1 = wave1.abs().powf(ratio) * wave1.signum(); + let foot2 = wave2.abs().powf(ratio) * wave2.signum(); + let foot3 = wave3.abs().powf(ratio) * wave3.signum(); + let foot4 = wave4.abs().powf(ratio) * wave4.signum(); + let turn = slow_wave.abs().powf(ratio) * slow_wave.signum(); + + let x_tilt = avg_vel.z.atan2(avg_vel.xy().magnitude()) * speednorm; + + next.chest.scale = Vec3::one() * s_a.scaler; + + let up_rot = 0.3; + let sideways = (PI / 2.0) * turn; + + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + x_tilt); + next.chest.orientation = Quaternion::rotation_x((mixed_vel).sin().max(0.0) * 0.06 + x_tilt) + * Quaternion::rotation_z(sideways + ((mixed_vel + PI / 2.0).sin() * 0.06)); + + next.arm_l.orientation = Quaternion::rotation_x(1.0) + * Quaternion::rotation_y(foot4.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(0.3 - foot2 * 0.1 * direction); + next.arm_r.orientation = Quaternion::rotation_x(1.0) + * Quaternion::rotation_y(-foot1.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(-0.2 - foot3 * 0.1 * direction); + + next.arm_l.position = Vec3::new(0.0, 5.0, 0.0); + next.arm_r.position = Vec3::new(0.0, 5.0, 0.0); + + next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fl.orientation = Quaternion::rotation_y(foot4.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(s_a.leg_ori.0 + foot2 * 0.1 * direction); + next.leg_fr.orientation = Quaternion::rotation_y(-foot1.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(-s_a.leg_ori.0 - foot3 * 0.1 * direction); + + next.leg_cl.position = Vec3::new(-s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cr.position = Vec3::new(s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cl.orientation = Quaternion::rotation_x(foot4 * 0.1 * direction) + * Quaternion::rotation_y(foot1.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(s_a.leg_ori.1 + foot3 * 0.2 * direction); + next.leg_cr.orientation = Quaternion::rotation_x(foot1 * 0.1 * direction) + * Quaternion::rotation_y(foot1.min(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(-s_a.leg_ori.1 - foot2 * 0.2 * direction); + + next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_bl.orientation = Quaternion::rotation_x(foot4 * 0.2) + * Quaternion::rotation_y(foot4.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(s_a.leg_ori.2 + foot3 * 0.2 * -direction); + next.leg_br.orientation = Quaternion::rotation_x(foot1 * 0.2 * direction) + * Quaternion::rotation_y(foot4.min(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(-s_a.leg_ori.2 - foot2 * 0.2 * -direction); + + next + } +} diff --git a/voxygen/anim/src/crustacean/stunned.rs b/voxygen/anim/src/crustacean/stunned.rs new file mode 100644 index 0000000000..332d47b52c --- /dev/null +++ b/voxygen/anim/src/crustacean/stunned.rs @@ -0,0 +1,72 @@ +use super::{ + super::{vek::*, Animation}, + CrustaceanSkeleton, SkeletonAttr, +}; +use common::states::utils::StageSection; + +pub struct StunnedAnimation; + +impl Animation for StunnedAnimation { + type Dependency<'a> = (f32, f32, Option, f32); + type Skeleton = CrustaceanSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"crustacean_stunned\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_stunned")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (_velocity, global_time, stage_section, timer): Self::Dependency<'_>, + anim_time: f32, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + let (_movement1base, movement2, twitch) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.1), 0.0, anim_time), + Some(StageSection::Recover) => (1.0, anim_time.powf(4.0), 1.0), + _ => (0.0, 0.0, 0.0), + }; + + let pullback = (1.0 - movement2) * 0.1; + + let subtract = global_time - timer; + let check = subtract - subtract.trunc(); + let mirror = (check - 0.5).signum(); + let twitch1 = mirror * (twitch * 5.0).cos() * pullback; + let twitch2 = mirror * (twitch * 5.0).sin() * pullback; + + next.chest.scale = Vec3::one() * s_a.scaler; + + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1); + + next.arm_l.orientation = Quaternion::rotation_x(-twitch2 * 1.2); + next.arm_r.orientation = Quaternion::rotation_x(twitch2 * 1.2); + next.pincer_l1.orientation = Quaternion::rotation_z(0.17); + next.pincer_r1.orientation = Quaternion::rotation_z(-0.17); + + next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fl.orientation = + Quaternion::rotation_z(s_a.leg_ori.0) * Quaternion::rotation_x(twitch1 * 0.8 + 0.4); + next.leg_fr.orientation = + Quaternion::rotation_z(-s_a.leg_ori.0) * Quaternion::rotation_x(-twitch1 * 0.8 - 0.4); + + next.leg_cl.position = Vec3::new(-s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cr.position = Vec3::new(s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cl.orientation = + Quaternion::rotation_z(s_a.leg_ori.1) * Quaternion::rotation_y(twitch2 * 0.4 + 0.4); + next.leg_cr.orientation = + Quaternion::rotation_z(-s_a.leg_ori.1) * Quaternion::rotation_y(-twitch2 * 0.4 - 0.4); + + next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_bl.orientation = + Quaternion::rotation_z(s_a.leg_ori.2) * Quaternion::rotation_y(twitch2 * 0.4 + 0.4); + next.leg_br.orientation = + Quaternion::rotation_z(-s_a.leg_ori.2) * Quaternion::rotation_y(-twitch2 * 0.4 - 0.4); + + next + } +} diff --git a/voxygen/anim/src/crustacean/swim.rs b/voxygen/anim/src/crustacean/swim.rs new file mode 100644 index 0000000000..043a580c11 --- /dev/null +++ b/voxygen/anim/src/crustacean/swim.rs @@ -0,0 +1,99 @@ +use super::{ + super::{vek::*, Animation}, + CrustaceanSkeleton, SkeletonAttr, +}; +use std::f32::consts::PI; + +pub struct SwimAnimation; + +impl Animation for SwimAnimation { + type Dependency<'a> = (Vec3, Vec3, Vec3, f32, Vec3, f32); + type Skeleton = CrustaceanSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"crustacean_swim\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "crustacean_swim")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (velocity, orientation, _last_ori, _global_time, avg_vel, acc_vel): Self::Dependency<'_>, + anim_time: f32, + rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + let speed = (Vec2::::from(velocity).magnitude()).min(22.0); + *rate = 1.0; + + let speednorm = speed / 13.0; + + let direction = velocity.y * 0.098 * orientation.y + velocity.x * 0.098 * orientation.x; + + let side = + (velocity.x * -0.098 * orientation.y + velocity.y * 0.098 * orientation.x) * -1.0; + let sideabs = side.abs(); + + let mixed_vel = (acc_vel + anim_time * 6.0) * 0.8; //sets run frequency using speed, with anim_time setting a floor + + //create a mix between a sine and a square wave + //(controllable with ratio variable) + let ratio = 0.1; + let wave1 = (mixed_vel).sin(); + let wave2 = (mixed_vel - PI / 2.0).sin(); + let wave3 = (mixed_vel + PI / 2.0).sin(); + let wave4 = (mixed_vel + PI).sin(); + + let foot1 = wave1.abs().powf(ratio) * wave1.signum(); + let foot2 = wave2.abs().powf(ratio) * wave2.signum(); + let foot3 = wave3.abs().powf(ratio) * wave3.signum(); + let foot4 = wave4.abs().powf(ratio) * wave4.signum(); + + let x_tilt = avg_vel.z.atan2(avg_vel.xy().magnitude()) * speednorm; + + next.chest.scale = Vec3::one() * s_a.scaler; + + let up_rot = 0.3; + let turnaround = PI; + let swim = -0.3 * foot2; + + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + x_tilt); + next.chest.orientation = Quaternion::rotation_x((mixed_vel).sin().max(0.0) * 0.06 + x_tilt) + * Quaternion::rotation_z(turnaround + ((mixed_vel + PI / 2.0).sin() * 0.06)); + + next.arm_l.orientation = Quaternion::rotation_x(0.1 * foot3) + * Quaternion::rotation_y(foot4.max(sideabs * -1.0) * up_rot) + * Quaternion::rotation_z((PI / -5.0) + 0.3 - foot2 * 0.1 * direction); + next.arm_r.orientation = Quaternion::rotation_x(0.1 * foot3) + * Quaternion::rotation_y(-foot1.max(sideabs * -1.0) * up_rot) + * Quaternion::rotation_z((PI / 5.0) + -0.2 - foot3 * 0.1 * direction); + + next.arm_l.position = Vec3::new(0.0, -1.0, 0.0); + next.arm_r.position = Vec3::new(0.0, -1.0, 0.0); + next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2 + 1.0); + next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2 + 1.0); + next.leg_fl.orientation = Quaternion::rotation_y(foot4.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(swim + s_a.leg_ori.0 + foot2 * 0.1 * -direction); + next.leg_fr.orientation = Quaternion::rotation_y(-foot1.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(-swim + -s_a.leg_ori.0 - foot3 * 0.1 * -direction); + + next.leg_cl.position = Vec3::new(-s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cr.position = Vec3::new(s_a.leg_c.0, s_a.leg_c.1, s_a.leg_c.2); + next.leg_cl.orientation = Quaternion::rotation_x(foot4 * 0.1 * -direction) + * Quaternion::rotation_y(foot1.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(swim + s_a.leg_ori.1 + foot3 * 0.2 * -direction); + next.leg_cr.orientation = Quaternion::rotation_x(foot1 * 0.1 * -direction) + * Quaternion::rotation_y(foot1.min(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(-swim + -s_a.leg_ori.1 - foot2 * 0.2 * -direction); + + next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_bl.orientation = Quaternion::rotation_x(foot4 * 0.2) + * Quaternion::rotation_y(foot4.max(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(swim + s_a.leg_ori.2 + foot3 * 0.2 * direction); + next.leg_br.orientation = Quaternion::rotation_x(foot1 * 0.2 * -direction) + * Quaternion::rotation_y(foot4.min(sideabs * -0.5) * up_rot) + * Quaternion::rotation_z(-swim + -s_a.leg_ori.2 - foot2 * 0.2 * direction); + + next + } +} diff --git a/voxygen/anim/src/lib.rs b/voxygen/anim/src/lib.rs index b3b11e9135..4e8285547c 100644 --- a/voxygen/anim/src/lib.rs +++ b/voxygen/anim/src/lib.rs @@ -53,6 +53,7 @@ pub mod biped_small; pub mod bird_large; pub mod bird_medium; pub mod character; +pub mod crustacean; pub mod dragon; pub mod fish_medium; pub mod fish_small; diff --git a/voxygen/egui/src/lib.rs b/voxygen/egui/src/lib.rs index 923e43dc21..895b1144ab 100644 --- a/voxygen/egui/src/lib.rs +++ b/voxygen/egui/src/lib.rs @@ -788,6 +788,7 @@ fn body_species(body: &Body) -> String { Body::QuadrupedLow(body) => format!("{:?}", body.species), Body::Arthropod(body) => format!("{:?}", body.species), Body::Ship(body) => format!("{:?}", body), + Body::Crustacean(body) => format!("{:?}", body.species), } } diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 7ff430d1b2..b80b733035 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -9,6 +9,7 @@ use common::{ biped_small, bird_large::{self, BodyType as BLABodyType, Species as BLASpecies}, bird_medium::{self, BodyType as BMBodyType, Species as BMSpecies}, + crustacean::{self, BodyType as CBodyType, Species as CSpecies}, dragon::{self, BodyType as DBodyType, Species as DSpecies}, fish_medium::{self, BodyType as FMBodyType, Species as FMSpecies}, fish_small::{self, BodyType as FSBodyType, Species as FSSpecies}, @@ -2688,6 +2689,385 @@ impl ArthropodLateralSpec { } } ////// +#[derive(Deserialize)] +struct CrustaceanCentralSpec(HashMap<(CSpecies, CBodyType), CrustCentralVoxSpec>); +impl_concatenate_for_wrapper!(CrustaceanCentralSpec); + +#[derive(Deserialize)] +struct CrustCentralVoxSpec { + chest: CrustaceanCentralSubSpec, + tail_f: CrustaceanCentralSubSpec, + tail_b: CrustaceanCentralSubSpec, +} +#[derive(Deserialize)] +struct CrustaceanCentralSubSpec { + offset: [f32; 3], // Should be relative to initial origin + central: VoxSimple, + #[serde(default)] + model_index: u32, +} +#[derive(Deserialize)] +struct CrustaceanLateralSpec(HashMap<(CSpecies, CBodyType), CrustLateralVoxSpec>); +impl_concatenate_for_wrapper!(CrustaceanLateralSpec); + +#[derive(Deserialize)] +struct CrustLateralVoxSpec { + arm_l: CrustaceanLateralSubSpec, + pincer_l0: CrustaceanLateralSubSpec, + pincer_l1: CrustaceanLateralSubSpec, + arm_r: CrustaceanLateralSubSpec, + pincer_r0: CrustaceanLateralSubSpec, + pincer_r1: CrustaceanLateralSubSpec, + leg_fl: CrustaceanLateralSubSpec, + leg_cl: CrustaceanLateralSubSpec, + leg_bl: CrustaceanLateralSubSpec, + leg_fr: CrustaceanLateralSubSpec, + leg_cr: CrustaceanLateralSubSpec, + leg_br: CrustaceanLateralSubSpec, +} +#[derive(Deserialize)] +struct CrustaceanLateralSubSpec { + offset: [f32; 3], // Should be relative to initial origin + lateral: VoxSimple, + #[serde(default)] + model_index: u32, +} +make_vox_spec!( + crustacean::Body, + struct CrustaceanSpec { + central: CrustaceanCentralSpec = "voxygen.voxel.crustacean_central_manifest", + lateral: CrustaceanLateralSpec = "voxygen.voxel.crustacean_lateral_manifest", + }, + |FigureKey { body, extra, .. }, spec| { + let third_person = extra.as_ref().and_then(|loadout| loadout.third_person.as_ref()); + + [ + third_person.map(|_| { + spec.central.read().0.mesh_chest( + body.species, + body.body_type, + ) + }), + Some(spec.central.read().0.mesh_tail_f( + body.species, + body.body_type, + )), + Some(spec.central.read().0.mesh_tail_b( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_arm_l( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_pincer_l0( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_pincer_l1( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_arm_r( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_pincer_r0( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_pincer_r1( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_leg_fl( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_leg_cl( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_leg_bl( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_leg_fr( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_leg_cr( + body.species, + body.body_type, + )), + Some(spec.lateral.read().0.mesh_leg_br( + body.species, + body.body_type, + )), + None, + ] + }, +); + +impl CrustaceanCentralSpec { + fn mesh_chest(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No chest specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let central = graceful_load_segment(&spec.chest.central.0, spec.chest.model_index); + + (central, Vec3::from(spec.chest.offset)) + } + + fn mesh_tail_f(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No front tail specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let central = graceful_load_segment(&spec.tail_f.central.0, spec.tail_f.model_index); + + (central, Vec3::from(spec.tail_f.offset)) + } + + fn mesh_tail_b(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No back tail specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let central = graceful_load_segment(&spec.tail_b.central.0, spec.tail_b.model_index); + + (central, Vec3::from(spec.tail_b.offset)) + } +} +impl CrustaceanLateralSpec { + fn mesh_arm_l(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No left arm specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = + graceful_load_segment_flipped(&spec.arm_l.lateral.0, true, spec.arm_l.model_index); + + (lateral, Vec3::from(spec.arm_l.offset)) + } + + fn mesh_pincer_l0(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No left major pincer specification exists for the combination of {:?} and \ + {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment_flipped( + &spec.pincer_l0.lateral.0, + true, + spec.pincer_l0.model_index, + ); + + (lateral, Vec3::from(spec.pincer_l0.offset)) + } + + fn mesh_pincer_l1(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No left minor pincer specification exists for the combination of {:?} and \ + {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment_flipped( + &spec.pincer_l1.lateral.0, + true, + spec.pincer_l1.model_index, + ); + + (lateral, Vec3::from(spec.pincer_l1.offset)) + } + + fn mesh_arm_r(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No right arm specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment(&spec.arm_r.lateral.0, spec.arm_r.model_index); + + (lateral, Vec3::from(spec.arm_r.offset)) + } + + fn mesh_pincer_r0(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No right major pincer specification exists for the combination of {:?} and \ + {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment(&spec.pincer_r0.lateral.0, spec.pincer_r0.model_index); + + (lateral, Vec3::from(spec.pincer_r0.offset)) + } + + fn mesh_pincer_r1(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No right minor pincer specification exists for the combination of {:?} and \ + {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment(&spec.pincer_r1.lateral.0, spec.pincer_r1.model_index); + + (lateral, Vec3::from(spec.pincer_r1.offset)) + } + + fn mesh_leg_fl(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No front left leg specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = + graceful_load_segment_flipped(&spec.leg_fl.lateral.0, true, spec.leg_fl.model_index); + + (lateral, Vec3::from(spec.leg_fl.offset)) + } + + fn mesh_leg_cl(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No center left leg specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = + graceful_load_segment_flipped(&spec.leg_cl.lateral.0, true, spec.leg_cl.model_index); + + (lateral, Vec3::from(spec.leg_cl.offset)) + } + + fn mesh_leg_bl(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No back left leg specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = + graceful_load_segment_flipped(&spec.leg_bl.lateral.0, true, spec.leg_bl.model_index); + + (lateral, Vec3::from(spec.leg_bl.offset)) + } + + fn mesh_leg_fr(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No front right leg specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment(&spec.leg_fr.lateral.0, spec.leg_fr.model_index); + + (lateral, Vec3::from(spec.leg_fr.offset)) + } + + fn mesh_leg_cr(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No center right leg specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment(&spec.leg_cr.lateral.0, spec.leg_cr.model_index); + + (lateral, Vec3::from(spec.leg_cr.offset)) + } + + fn mesh_leg_br(&self, species: CSpecies, body_type: CBodyType) -> BoneMeshes { + let spec = match self.0.get(&(species, body_type)) { + Some(spec) => spec, + None => { + error!( + "No back right leg specification exists for the combination of {:?} and {:?}", + species, body_type + ); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let lateral = graceful_load_segment(&spec.leg_br.lateral.0, spec.leg_br.model_index); + + (lateral, Vec3::from(spec.leg_br.offset)) + } +} + #[derive(Deserialize)] struct FishMediumCentralSpec(HashMap<(FMSpecies, FMBodyType), SidedFMCentralVoxSpec>); impl_concatenate_for_wrapper!(FishMediumCentralSpec); diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 720baee7f0..11858e1f84 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -28,11 +28,11 @@ use crate::{ use anim::{ arthropod::ArthropodSkeleton, biped_large::BipedLargeSkeleton, biped_small::BipedSmallSkeleton, bird_large::BirdLargeSkeleton, bird_medium::BirdMediumSkeleton, character::CharacterSkeleton, - dragon::DragonSkeleton, fish_medium::FishMediumSkeleton, fish_small::FishSmallSkeleton, - golem::GolemSkeleton, item_drop::ItemDropSkeleton, object::ObjectSkeleton, - quadruped_low::QuadrupedLowSkeleton, quadruped_medium::QuadrupedMediumSkeleton, - quadruped_small::QuadrupedSmallSkeleton, ship::ShipSkeleton, theropod::TheropodSkeleton, - Animation, Skeleton, + crustacean::CrustaceanSkeleton, dragon::DragonSkeleton, fish_medium::FishMediumSkeleton, + fish_small::FishSmallSkeleton, golem::GolemSkeleton, item_drop::ItemDropSkeleton, + object::ObjectSkeleton, quadruped_low::QuadrupedLowSkeleton, + quadruped_medium::QuadrupedMediumSkeleton, quadruped_small::QuadrupedSmallSkeleton, + ship::ShipSkeleton, theropod::TheropodSkeleton, Animation, Skeleton, }; use common::{ comp::{ @@ -213,6 +213,7 @@ struct FigureMgrStates { ship_states: HashMap>, volume_states: HashMap>, arthropod_states: HashMap>, + crustacean_states: HashMap>, } impl FigureMgrStates { @@ -236,6 +237,7 @@ impl FigureMgrStates { ship_states: HashMap::new(), volume_states: HashMap::new(), arthropod_states: HashMap::new(), + crustacean_states: HashMap::new(), } } @@ -311,6 +313,10 @@ impl FigureMgrStates { .arthropod_states .get_mut(entity) .map(DerefMut::deref_mut), + Body::Crustacean(_) => self + .crustacean_states + .get_mut(entity) + .map(DerefMut::deref_mut), } } @@ -345,6 +351,7 @@ impl FigureMgrStates { } }, Body::Arthropod(_) => self.arthropod_states.remove(entity).map(|e| e.meta), + Body::Crustacean(_) => self.crustacean_states.remove(entity).map(|e| e.meta), } } @@ -368,6 +375,7 @@ impl FigureMgrStates { self.ship_states.retain(|k, v| f(k, &mut *v)); self.volume_states.retain(|k, v| f(k, &mut *v)); self.arthropod_states.retain(|k, v| f(k, &mut *v)); + self.crustacean_states.retain(|k, v| f(k, &mut *v)); } fn count(&self) -> usize { @@ -390,6 +398,7 @@ impl FigureMgrStates { + self.ship_states.len() + self.volume_states.len() + self.arthropod_states.len() + + self.crustacean_states.len() } fn count_visible(&self) -> usize { @@ -472,6 +481,11 @@ impl FigureMgrStates { .iter() .filter(|(_, c)| c.visible()) .count() + + self + .crustacean_states + .iter() + .filter(|(_, c)| c.visible()) + .count() + self.ship_states.iter().filter(|(_, c)| c.visible()).count() + self .volume_states @@ -524,6 +538,7 @@ pub struct FigureMgr { golem_model_cache: FigureModelCache, volume_model_cache: FigureModelCache, arthropod_model_cache: FigureModelCache, + crustacean_model_cache: FigureModelCache, states: FigureMgrStates, } @@ -549,6 +564,7 @@ impl FigureMgr { golem_model_cache: FigureModelCache::new(), volume_model_cache: FigureModelCache::new(), arthropod_model_cache: FigureModelCache::new(), + crustacean_model_cache: FigureModelCache::new(), states: FigureMgrStates::default(), } } @@ -574,6 +590,7 @@ impl FigureMgr { || self.golem_model_cache.watcher_reloaded() || self.volume_model_cache.watcher_reloaded() || self.arthropod_model_cache.watcher_reloaded() + || self.crustacean_model_cache.watcher_reloaded() } pub fn clean(&mut self, tick: u64) { @@ -600,6 +617,7 @@ impl FigureMgr { self.golem_model_cache.clear_models(); self.volume_model_cache.clear_models(); self.arthropod_model_cache.clear_models(); + self.crustacean_model_cache.clear_models(); } self.model_cache.clean(&mut self.atlas, tick); @@ -622,6 +640,7 @@ impl FigureMgr { self.golem_model_cache.clean(&mut self.atlas, tick); self.volume_model_cache.clean(&mut self.atlas, tick); self.arthropod_model_cache.clean(&mut self.atlas, tick); + self.crustacean_model_cache.clean(&mut self.atlas, tick); } pub fn update_lighting(&mut self, scene_data: &SceneData) { @@ -4828,6 +4847,182 @@ impl FigureMgr { body, ); }, + Body::Crustacean(body) => { + let (model, skeleton_attr) = self.crustacean_model_cache.get_or_create_model( + renderer, + &mut self.atlas, + body, + inventory, + (), + tick, + viewpoint_camera_mode, + viewpoint_character_state, + &slow_jobs, + None, + ); + + let state = self + .states + .crustacean_states + .entry(entity) + .or_insert_with(|| { + FigureState::new(renderer, CrustaceanSkeleton::default(), body) + }); + + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + + let (character, last_character) = match (character, last_character) { + (Some(c), Some(l)) => (c, l), + _ => continue, + }; + + if !character.same_variant(&last_character.0) { + state.state_time = 0.0; + } + + let target_base = match ( + physics.on_ground.is_some(), + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + physics.in_liquid().is_some(), // In water + ) { + // Standing + (true, false, false) => anim::crustacean::IdleAnimation::update_skeleton( + &CrustaceanSkeleton::default(), + time, + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + // Running + (true, true, false) => anim::crustacean::RunAnimation::update_skeleton( + &CrustaceanSkeleton::default(), + ( + rel_vel, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + rel_avg_vel, + state.acc_vel, + ), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + // In air + (false, _, false) => anim::crustacean::JumpAnimation::update_skeleton( + &CrustaceanSkeleton::default(), + ( + rel_vel.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + rel_avg_vel, + ), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + //Swimming + (_, _, true) => anim::crustacean::SwimAnimation::update_skeleton( + &CrustaceanSkeleton::default(), + ( + rel_vel, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + rel_avg_vel, + state.acc_vel, + ), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + }; + let target_bones = match &character { + CharacterState::ComboMelee2(s) => { + let timer = s.timer.as_secs_f32(); + let current_strike = s.completed_strikes % s.static_data.strikes.len(); + let strike_data = s.static_data.strikes[current_strike]; + let progress = match s.stage_section { + StageSection::Buildup => { + timer / strike_data.buildup_duration.as_secs_f32() + }, + StageSection::Action => { + timer / strike_data.swing_duration.as_secs_f32() + }, + StageSection::Recover => { + timer / strike_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; + + anim::crustacean::ComboAnimation::update_skeleton( + &target_base, + ( + ability_id, + Some(s.stage_section), + Some(s.static_data.ability_info), + current_strike, + time, + rel_avg_vel, + state.state_time, + ), + progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, + CharacterState::Stunned(s) => { + let stage_time = s.timer.as_secs_f32(); + let stage_progress = match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f32() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; + match s.static_data.poise_state { + PoiseState::Normal + | PoiseState::Interrupted + | PoiseState::Stunned + | PoiseState::Dazed + | PoiseState::KnockedDown => { + anim::crustacean::StunnedAnimation::update_skeleton( + &target_base, + ( + rel_vel.magnitude(), + time, + Some(s.stage_section), + state.state_time, + ), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, + } + }, + // TODO! + _ => target_base, + }; + + state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); + state.update( + renderer, + trail_mgr, + &mut update_buf, + &common_params, + state_animation_rate, + model, + body, + ); + }, Body::BirdLarge(body) => { let (model, skeleton_attr) = self.bird_large_model_cache.get_or_create_model( renderer, @@ -6876,6 +7071,7 @@ impl FigureMgr { golem_model_cache, volume_model_cache, arthropod_model_cache, + crustacean_model_cache, states: FigureMgrStates { character_states, @@ -6896,6 +7092,7 @@ impl FigureMgr { ship_states, volume_states, arthropod_states, + crustacean_states, }, } = self; let atlas = atlas_; @@ -7166,6 +7363,25 @@ impl FigureMgr { .map(ModelEntryRef::Figure), ) }), + Body::Crustacean(body) => crustacean_states + .get(&entity) + .filter(|state| filter_state(state)) + .map(move |state| { + ( + state.bound(), + crustacean_model_cache + .get_model( + atlas, + body, + inventory, + tick, + viewpoint_camera_mode, + character_state, + None, + ) + .map(ModelEntryRef::Figure), + ) + }), Body::Object(body) => object_states .get(&entity) .filter(|state| filter_state(state)) @@ -7422,6 +7638,11 @@ impl FigureMgr { .item_drop_states .get(&entity) .and_then(|state| state.viewpoint_offset), + Body::Crustacean(_) => self + .states + .crustacean_states + .get(&entity) + .and_then(|state| state.viewpoint_offset), }) .map(|viewpoint| viewpoint.into()) .unwrap_or_else(Vec3::zero)