Merge branch 'isse/crustacean-skeleton' into 'master'

crustacean-skeleton and crab

See merge request veloren/veloren!4200
This commit is contained in:
flo 2023-12-16 13:47:56 +00:00
commit 18fd06fd88
32 changed files with 1682 additions and 11 deletions

View File

@ -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"),

View File

@ -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,
)

View File

@ -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: [],
)

View File

@ -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")),
)

View File

@ -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)),
]

View File

@ -1417,4 +1417,16 @@
),
)
),
crustacean: (
body: (
keyword: "crustacean",
names_0: ["Ferris"],
),
species: (
crab: (
keyword: "crab",
generic: "Crab",
),
)
),
)

View File

@ -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"),
),
),
})

View File

@ -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,
),
),
})

BIN
assets/voxygen/voxel/npc/crab/crab.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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<BodyMeta, SpeciesMeta> {
pub quadruped_low: BodyData<BodyMeta, quadruped_low::AllSpecies<SpeciesMeta>>,
pub ship: BodyData<BodyMeta, ()>,
pub arthropod: BodyData<BodyMeta, arthropod::AllSpecies<SpeciesMeta>>,
pub crustacean: BodyData<BodyMeta, crustacean::AllSpecies<SpeciesMeta>>,
}
impl<BodyMeta, SpeciesMeta> AllBodies<BodyMeta, SpeciesMeta> {
@ -124,6 +127,7 @@ impl<BodyMeta, SpeciesMeta> AllBodies<BodyMeta, SpeciesMeta> {
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<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, Sp
NpcKind::Reddragon => &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<BodyMet
Body::QuadrupedLow(_) => &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,
..
})
)
}

View File

@ -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<Body> 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<SpeciesMeta> {
pub crab: SpeciesMeta,
}
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
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<SpeciesMeta> {
type IntoIter = std::iter::Copied<std::slice::Iter<'static, Self::Item>>;
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];

View File

@ -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

View File

@ -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,
};

View File

@ -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,

View File

@ -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| {

View File

@ -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(())
}
}

View File

@ -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,
)
}

View File

@ -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<StageSection>, 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
}
}

View File

@ -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<StageSection>,
Option<AbilityInfo>,
usize,
f32,
Vec3<f32>,
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
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,54 @@
use super::{
super::{vek::*, Animation},
CrustaceanSkeleton, SkeletonAttr,
};
pub struct JumpAnimation;
impl Animation for JumpAnimation {
type Dependency<'a> = (f32, Vec3<f32>, Vec3<f32>, f32, Vec3<f32>);
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
}
}

View File

@ -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<f32>,
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::<f32>::from(self.chest);
let tail_f_mat = chest_mat * Mat4::<f32>::from(self.tail_f);
let tail_b_mat = chest_mat * Mat4::<f32>::from(self.tail_b);
let arm_l_mat = chest_mat * Mat4::<f32>::from(self.arm_l);
let pincer_l0_mat = arm_l_mat * Mat4::<f32>::from(self.pincer_l0);
let pincer_l1_mat = pincer_l0_mat * Mat4::<f32>::from(self.pincer_l1);
let arm_r_mat = chest_mat * Mat4::<f32>::from(self.arm_r);
let pincer_r0_mat = arm_r_mat * Mat4::<f32>::from(self.pincer_r0);
let pincer_r1_mat = pincer_r0_mat * Mat4::<f32>::from(self.pincer_r1);
let leg_fl_mat = chest_mat * Mat4::<f32>::from(self.leg_fl);
let leg_cl_mat = chest_mat * Mat4::<f32>::from(self.leg_cl);
let leg_bl_mat = chest_mat * Mat4::<f32>::from(self.leg_bl);
let leg_fr_mat = chest_mat * Mat4::<f32>::from(self.leg_fr);
let leg_cr_mat = chest_mat * Mat4::<f32>::from(self.leg_cr);
let leg_br_mat = chest_mat * Mat4::<f32>::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<f32> {
// TODO: mount points
//use comp::arthropod::{BodyType::*, Species::*};
(0.0, -6.0, 6.0).into()
}

View File

@ -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<f32>, Vec3<f32>, Vec3<f32>, f32, Vec3<f32>, 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::<f32>::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
}
}

View File

@ -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<StageSection>, 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
}
}

View File

@ -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<f32>, Vec3<f32>, Vec3<f32>, f32, Vec3<f32>, 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::<f32>::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
}
}

View File

@ -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;

View File

@ -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),
}
}

View File

@ -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);

View File

@ -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<EcsEntity, FigureState<ShipSkeleton, BoundTerrainLocals>>,
volume_states: HashMap<EcsEntity, FigureState<VolumeKey, BoundTerrainLocals>>,
arthropod_states: HashMap<EcsEntity, FigureState<ArthropodSkeleton>>,
crustacean_states: HashMap<EcsEntity, FigureState<CrustaceanSkeleton>>,
}
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<GolemSkeleton>,
volume_model_cache: FigureModelCache<VolumeKey>,
arthropod_model_cache: FigureModelCache<ArthropodSkeleton>,
crustacean_model_cache: FigureModelCache<CrustaceanSkeleton>,
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::<f32>::unit_y(),
state.last_ori * anim::vek::Vec3::<f32>::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::<f32>::unit_y(),
state.last_ori * anim::vek::Vec3::<f32>::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::<f32>::unit_y(),
state.last_ori * anim::vek::Vec3::<f32>::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)