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