1use crate::{
2 comp::{Collider, Density, Mass},
3 consts::{AIR_DENSITY, WATER_DENSITY},
4 terrain::{Block, BlockKind, SpriteKind},
5};
6use rand::prelude::SliceRandom;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9use strum::EnumIter;
10use vek::*;
11
12pub const ALL_BODIES: [Body; 6] = [
15 Body::DefaultAirship,
16 Body::AirBalloon,
17 Body::SailBoat,
18 Body::Galleon,
19 Body::Skiff,
20 Body::Submarine,
21];
22
23pub const ALL_AIRSHIPS: [Body; 2] = [Body::DefaultAirship, Body::AirBalloon];
24pub const ALL_SHIPS: [Body; 7] = [
25 Body::SailBoat,
26 Body::Galleon,
27 Body::Skiff,
28 Body::Submarine,
29 Body::Carriage,
30 Body::Cart,
31 Body::Train,
32];
33
34#[derive(
35 Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, EnumIter,
36)]
37#[repr(u32)]
38pub enum Body {
39 DefaultAirship = 0,
40 AirBalloon = 1,
41 SailBoat = 2,
42 Galleon = 3,
43 Volume = 4,
44 Skiff = 5,
45 Submarine = 6,
46 Carriage = 7,
47 Cart = 8,
48 Train = 9,
49}
50
51impl From<Body> for super::Body {
52 fn from(body: Body) -> Self { super::Body::Ship(body) }
53}
54
55impl Body {
56 pub fn random() -> Self {
57 let mut rng = rand::thread_rng();
58 Self::random_with(&mut rng)
59 }
60
61 pub fn random_with(rng: &mut impl rand::Rng) -> Self { *ALL_BODIES.choose(rng).unwrap() }
62
63 pub fn random_airship_with(rng: &mut impl rand::Rng) -> Self {
64 *ALL_AIRSHIPS.choose(rng).unwrap()
65 }
66
67 pub fn random_ship_with(rng: &mut impl rand::Rng) -> Self { *ALL_SHIPS.choose(rng).unwrap() }
68
69 pub fn manifest_entry(&self) -> Option<&'static str> {
72 match self {
73 Body::DefaultAirship => Some("airship_human.structure"),
74 Body::AirBalloon => Some("air_balloon.structure"),
75 Body::SailBoat => Some("sail_boat.structure"),
76 Body::Galleon => Some("galleon.structure"),
77 Body::Skiff => Some("skiff.structure"),
78 Body::Submarine => Some("submarine.structure"),
79 Body::Carriage => Some("carriage.structure"),
80 Body::Cart => Some("cart.structure"),
81 Body::Volume => None,
82 Body::Train => Some("train.loco"),
83 }
84 }
85
86 pub fn dimensions(&self) -> Vec3<f32> {
87 match self {
88 Body::DefaultAirship | Body::Volume => Vec3::new(25.0, 50.0, 40.0),
89 Body::AirBalloon => Vec3::new(25.0, 50.0, 40.0),
90 Body::SailBoat => Vec3::new(12.0, 32.0, 6.0),
91 Body::Galleon => Vec3::new(14.0, 48.0, 10.0),
92 Body::Skiff => Vec3::new(7.0, 15.0, 10.0),
93 Body::Submarine => Vec3::new(2.0, 15.0, 8.0),
94 Body::Carriage => Vec3::new(5.0, 12.0, 2.0),
95 Body::Cart => Vec3::new(3.0, 6.0, 1.0),
96 Body::Train => Vec3::new(7.0, 32.0, 5.0),
97 }
98 }
99
100 fn balloon_vol(&self) -> f32 {
101 match self {
102 Body::DefaultAirship | Body::AirBalloon | Body::Volume => {
103 let spheroid_vol = |equat_d: f32, polar_d: f32| -> f32 {
104 (std::f32::consts::PI / 6.0) * equat_d.powi(2) * polar_d
105 };
106 let dim = self.dimensions();
107 spheroid_vol(dim.z, dim.y)
108 },
109 _ => 0.0,
110 }
111 }
112
113 fn hull_vol(&self) -> f32 {
114 let deck_height = 10_f32;
116 let dim = self.dimensions();
117 (std::f32::consts::PI / 6.0) * (deck_height * 1.5).powi(2) * dim.y
118 }
119
120 pub fn hull_density(&self) -> Density {
121 let oak_density = 600_f32;
122 let ratio = 0.1;
123 Density(ratio * oak_density + (1.0 - ratio) * AIR_DENSITY)
124 }
125
126 pub fn density(&self) -> Density {
127 match self {
128 Body::DefaultAirship | Body::AirBalloon | Body::Volume => Density(AIR_DENSITY),
129 Body::Submarine => Density(WATER_DENSITY), Body::Carriage => Density(WATER_DENSITY * 0.5),
131 Body::Cart => Density(500.0 / self.dimensions().product()), Body::Train => Density(WATER_DENSITY * 1.5),
134 _ => Density(AIR_DENSITY * 0.95 + WATER_DENSITY * 0.05), }
137 }
138
139 pub fn mass(&self) -> Mass {
140 if self.can_fly() {
141 Mass((self.hull_vol() + self.balloon_vol()) * self.density().0)
142 } else {
143 Mass(self.density().0 * self.dimensions().product())
144 }
145 }
146
147 pub fn can_fly(&self) -> bool {
148 matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume)
149 }
150
151 pub fn vectored_propulsion(&self) -> bool { matches!(self, Body::DefaultAirship) }
152
153 pub fn flying_height(&self) -> f32 {
154 if self.can_fly() {
155 match self {
156 Body::DefaultAirship => 300.0,
157 Body::AirBalloon => 200.0,
158 _ => 0.0,
159 }
160 } else {
161 0.0
162 }
163 }
164
165 pub fn has_water_thrust(&self) -> bool {
166 matches!(self, Body::SailBoat | Body::Galleon | Body::Skiff)
167 }
168
169 pub fn has_wheels(&self) -> bool { matches!(self, Body::Carriage | Body::Cart | Body::Train) }
170
171 pub fn make_collider(&self) -> Collider {
172 match self.manifest_entry() {
173 Some(manifest_entry) => Collider::Voxel {
174 id: manifest_entry.to_string(),
175 },
176 None => {
177 use rand::prelude::*;
178 let sz = Vec3::broadcast(11);
179 Collider::Volume(Arc::new(figuredata::VoxelCollider::from_fn(sz, |_pos| {
180 if thread_rng().gen_bool(0.25) {
181 Block::new(BlockKind::Rock, Rgb::new(255, 0, 0))
182 } else {
183 Block::air(SpriteKind::Empty)
184 }
185 })))
186 },
187 }
188 }
189
190 pub fn get_speed(&self) -> f32 {
215 match self {
216 Body::DefaultAirship => 23.0,
217 Body::AirBalloon => 8.0,
218 Body::SailBoat => 5.0,
219 Body::Galleon => 6.0,
220 Body::Skiff => 6.0,
221 Body::Submarine => 4.0,
222 Body::Train => 12.0,
223 _ => 10.0,
224 }
225 }
226}
227
228pub const AIRSHIP_SCALE: f32 = 11.0;
231
232pub mod figuredata {
236 use crate::{
237 assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron},
238 figure::TerrainSegment,
239 terrain::{
240 StructureSprite,
241 block::{Block, BlockKind},
242 structure::load_base_structure,
243 },
244 };
245 use hashbrown::HashMap;
246 use lazy_static::lazy_static;
247 use serde::{Deserialize, Serialize};
248 use vek::{Rgb, Vec3};
249
250 #[derive(Deserialize)]
251 pub struct VoxSimple(pub String);
252
253 #[derive(Deserialize)]
254 pub struct ShipCentralSpec(pub HashMap<super::Body, SidedShipCentralVoxSpec>);
255
256 #[derive(Deserialize)]
257 pub enum DeBlock {
258 Block(BlockKind),
259 Air(StructureSprite),
260 Water(StructureSprite),
261 }
262
263 impl DeBlock {
264 fn to_block(&self, color: Rgb<u8>) -> Block {
265 match *self {
266 DeBlock::Block(block) => Block::new(block, color),
267 DeBlock::Air(sprite) => sprite.get_block(Block::air),
268 DeBlock::Water(sprite) => sprite.get_block(Block::water),
269 }
270 }
271 }
272
273 #[derive(Deserialize)]
274 pub struct SidedShipCentralVoxSpec {
275 pub bone0: ShipCentralSubSpec,
276 pub bone1: ShipCentralSubSpec,
277 pub bone2: ShipCentralSubSpec,
278 pub bone3: ShipCentralSubSpec,
279
280 #[serde(default)]
284 pub custom_indices: HashMap<u8, DeBlock>,
285 }
286
287 #[derive(Deserialize)]
288 pub struct ShipCentralSubSpec {
289 pub offset: [f32; 3],
290 pub central: VoxSimple,
291 #[serde(default)]
292 pub model_index: u32,
293 }
294
295 #[derive(Clone)]
297 pub struct ShipSpec {
298 pub central: AssetHandle<Ron<ShipCentralSpec>>,
299 pub colliders: HashMap<String, VoxelCollider>,
300 }
301
302 #[derive(Clone, Debug, Serialize, Deserialize)]
303 pub struct VoxelCollider {
304 pub(super) dyna: TerrainSegment,
305 pub translation: Vec3<f32>,
306 pub mut_count: usize,
309 }
310
311 impl VoxelCollider {
312 pub fn from_fn<F: FnMut(Vec3<i32>) -> Block>(sz: Vec3<u32>, f: F) -> Self {
313 Self {
314 dyna: TerrainSegment::from_fn(sz, (), f),
315 translation: -sz.map(|e| e as f32) / 2.0,
316 mut_count: 0,
317 }
318 }
319
320 pub fn volume(&self) -> &TerrainSegment { &self.dyna }
321 }
322
323 impl assets::Compound for ShipSpec {
324 fn load(
325 cache: assets::AnyCache,
326 _: &assets::SharedString,
327 ) -> Result<Self, assets::BoxedError> {
328 let manifest: AssetHandle<Ron<ShipCentralSpec>> =
329 AssetExt::load("common.manifests.ship_manifest")?;
330 let mut colliders = HashMap::new();
331 for (_, spec) in (manifest.read().0).0.iter() {
332 for (index, bone) in [&spec.bone0, &spec.bone1, &spec.bone2, &spec.bone3]
333 .iter()
334 .enumerate()
335 {
336 let vox =
340 cache.load::<DotVoxAsset>(&["common.voxel.", &bone.central.0].concat())?;
341
342 let base_structure = load_base_structure(&vox.read().0, |col| col);
343 let dyna = base_structure.vol.map_into(|cell| {
344 if let Some(i) = cell {
345 let color = base_structure.palette[u8::from(i) as usize];
346 if let Some(block) = spec.custom_indices.get(&i.get())
347 && index == 0
348 {
349 block.to_block(color)
350 } else {
351 Block::new(BlockKind::Misc, color)
352 }
353 } else {
354 Block::empty()
355 }
356 });
357 let collider = VoxelCollider {
358 dyna,
359 translation: Vec3::from(bone.offset),
360 mut_count: 0,
361 };
362 colliders.insert(bone.central.0.clone(), collider);
363 }
364 }
365 Ok(ShipSpec {
366 central: manifest,
367 colliders,
368 })
369 }
370 }
371
372 lazy_static! {
373 pub static ref VOXEL_COLLIDER_MANIFEST: AssetHandle<ShipSpec> = AssetExt::load_expect("common.manifests.ship_manifest");
377 }
378
379 #[test]
380 fn test_ship_manifest_entries() {
381 for body in super::ALL_BODIES {
382 if let Some(entry) = body.manifest_entry() {
383 assert!(
384 VOXEL_COLLIDER_MANIFEST
385 .read()
386 .colliders
387 .get(entry)
388 .is_some()
389 );
390 }
391 }
392 }
393}