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