babalgame/
tunnel.rs

1use super::scene_helper::*;
2use babalcore::*;
3use gdnative::api::GridMap;
4use gdnative::api::Performance;
5use gdnative::prelude::*;
6
7#[derive(NativeClass)]
8#[inherit(Spatial)]
9pub struct Tunnel {
10    data: Option<Data>,
11    template: Option<Ref<PackedScene, ThreadLocal>>,
12    grid_scale: Vector3,
13    level_row: isize,
14    level_col: isize,
15    player_position: Vector3,
16    player_best_row: isize,
17    player_last_col: isize,
18    player_last_col_acc: f64,
19    fall_state: bool,
20    boost_state: bool,
21    now_sec: f64,
22    far_view: isize,
23    time_since_last_adjust_far_view: f64,
24}
25
26struct Data {
27    level: Level,
28    rail_resource_name: String,
29}
30
31const LENGTH_MARGIN: usize = 100;
32const LEVEL_LENGTH: usize = 10000;
33const SLAB_SIZE: f32 = 1.0;
34const RAIL_OFFSET: isize = 2;
35const RAIL_FAR_VIEW_INIT: isize = 25;
36const RAIL_FAR_VIEW_MAX: isize = 45;
37const RAIL_FAR_VIEW_MIN: isize = 15;
38const ADJUST_FAR_VIEW_DELAY: f64 = 0.5;
39const FPS_TARGET: f64 = 55.0;
40const SLAB_RATIO: f64 = 3.0;
41
42const MATERIAL_FLAVORS: usize = 10;
43const PLAYER_LAST_COL_PERIOD: f64 = 0.5;
44
45#[methods]
46impl Tunnel {
47    fn new(_owner: &Spatial) -> Self {
48        Tunnel {
49            data: None,
50            template: None,
51            grid_scale: Vector3::new(1.0, 1.0, 1.0),
52            level_row: 0,
53            level_col: 0,
54            player_position: Vector3::default(),
55            player_best_row: 0,
56            player_last_col: -1,
57            player_last_col_acc: 1.0,
58            fall_state: false,
59            boost_state: false,
60            now_sec: 0.0,
61            far_view: RAIL_FAR_VIEW_INIT,
62            time_since_last_adjust_far_view: 0.0,
63        }
64    }
65
66    #[export]
67    fn _ready(&self, _owner: &Spatial) {
68        godot_print!("Tunnel is ready.");
69    }
70
71    #[export]
72    fn _process(&mut self, owner: &Spatial, delta: f64) {
73        self.now_sec += delta;
74        self.player_last_col_acc += delta / PLAYER_LAST_COL_PERIOD;
75        self.adjust_far_view(owner, delta);
76        self.sync(owner);
77    }
78
79    #[export]
80    fn setup(
81        &mut self,
82        owner: &Spatial,
83        rail_resource_name: GodotString,
84        skill: GodotString,
85        seed: u64,
86    ) {
87        let skill = Skill::from_str(skill.to_string().as_str());
88        godot_print!("Tunnel setup {}x{}.", skill.width(), LEVEL_LENGTH);
89        let mut level = Level::new(seed, skill);
90        Self::generate_missing(&mut level, 0);
91        let data = Data {
92            level,
93            rail_resource_name: rail_resource_name.to_string(),
94        };
95        self.template = load_scene(data.rail_resource_name.as_str());
96        match &self.template {
97            Some(_) => godot_print!(
98                "Loaded template rail scene \"{}\".",
99                data.rail_resource_name
100            ),
101            None => godot_print!(
102                "Unable to load rail scene \"{}\", please check name.",
103                data.rail_resource_name
104            ),
105        };
106        self.data = Some(data);
107
108        self.remove_all_children(owner);
109        self.add_all_rails(owner);
110        match self.get_grid_scale(owner) {
111            Ok(mut scale) => {
112                scale.z = Self::calc_scale_z(skill.width());
113                match self.set_grid_scale(owner, scale) {
114                    Ok(_) => {}
115                    Err(e) => godot_print!("unable to set grid scale: {}", e),
116                }
117                self.grid_scale = scale;
118            }
119            Err(e) => godot_print!("unable to get grid scale: {}", e),
120        }
121        self.sync(owner);
122    }
123
124    fn calc_scale_z(width: usize) -> f32 {
125        (2.0 * std::f64::consts::PI * SLAB_RATIO as f64 / width as f64) as f32
126    }
127
128    fn generate_missing(level: &mut Level, row: isize) {
129        while level.last() < row + (LENGTH_MARGIN as isize) {
130            level.generate(LENGTH_MARGIN);
131        }
132    }
133
134    #[export]
135    fn remove_last_child(&mut self, owner: &Spatial) -> bool {
136        let num_children = owner.get_child_count();
137        if num_children <= 0 {
138            return false;
139        }
140
141        let last_child = owner.get_child(num_children - 1);
142        if let Some(node) = last_child {
143            owner.remove_child(node);
144            unsafe {
145                node.assume_safe().queue_free();
146            }
147        }
148
149        return num_children == owner.get_child_count() + 1;
150    }
151
152    #[export]
153    fn remove_all_children(&mut self, owner: &Spatial) -> usize {
154        let mut removed: usize = 0;
155        while self.remove_last_child(owner) {
156            removed += 1;
157        }
158        removed
159    }
160
161    fn prepare_rail_grid(&self, grid: &Spatial, n: usize) {
162        let w: f32 = SLAB_SIZE * std::f32::consts::PI * 2f32 / n as f32;
163        let mut translation = grid.translation();
164        translation.x = -w / 2f32;
165        grid.set_translation(translation);
166        let mut scale = grid.scale();
167        scale.x = w;
168        grid.set_scale(scale);
169    }
170
171    fn prepare_rail(&self, rail: &Spatial, i: usize, n: usize) {
172        let key_str = format!("rail_{}", i);
173        rail.set_name(&key_str);
174        let angle: f64 = i as f64 * 360f64 / n as f64;
175        rail.rotate_z(angle * std::f64::consts::PI / 180f64);
176
177        let num_children = rail.get_child_count();
178        if num_children != 1 {
179            godot_print!("Unexpected number of children for rail: {}.", num_children);
180            return;
181        }
182
183        let child = rail.get_child(0);
184        if let Some(grid) = child {
185            let grid = unsafe { grid.assume_safe() };
186            if let Some(grid) = grid.cast::<Spatial>() {
187                self.prepare_rail_grid(&grid, n);
188            }
189        }
190    }
191
192    #[export]
193    fn add_one_rail(&mut self, owner: &Spatial, i: usize, n: usize) -> bool {
194        match &self.template {
195            Some(template) => match instanciate_scene::<Spatial>(template) {
196                Ok(spatial) => {
197                    self.prepare_rail(&spatial, i, n);
198                    owner.add_child(spatial.into_shared(), false);
199                    true
200                }
201                Err(err) => {
202                    godot_print!("Could not instanciate rail scene: \"{:?}\".", err);
203                    false
204                }
205            },
206            None => false,
207        }
208    }
209
210    #[export]
211    fn add_all_rails(&mut self, owner: &Spatial) -> usize {
212        match &self.data {
213            Some(data) => {
214                let mut added: usize = 0;
215                let width = data.level.width();
216                if width == 0 {
217                    return 0;
218                }
219                for i in 0..width {
220                    if !self.add_one_rail(owner, i, width) {
221                        break;
222                    }
223                    added += 1;
224                }
225                added
226            }
227            None => 0,
228        }
229    }
230
231    fn get_grid_scale(&self, owner: &Spatial) -> Result<Vector3, String> {
232        let num_children = owner.get_child_count() as usize;
233        if num_children == 0 {
234            return Err(format!("Please setup tunnel"));
235        }
236        match owner.get_child(0) {
237            Some(rail) => {
238                let rail = unsafe { rail.assume_safe() };
239                match rail.cast::<Spatial>() {
240                    Some(rail) => match rail.get_child(0) {
241                        Some(grid) => {
242                            let grid = unsafe { grid.assume_safe() };
243                            match grid.cast::<GridMap>() {
244                                Some(grid) => return Ok(grid.scale()),
245                                None => Err(format!("Unable to cast child of rail to GridMap.")),
246                            }
247                        }
248                        None => Err(format!("rail has no child")),
249                    },
250                    None => Err(format!("rail is not spatial")),
251                }
252            }
253            None => Err(format!("tunnel has no child")),
254        }
255    }
256
257    fn set_grid_scale(&self, owner: &Spatial, scale: Vector3) -> Result<(), String> {
258        let num_children = owner.get_child_count() as usize;
259        if num_children == 0 {
260            return Err(format!("Please setup tunnel"));
261        }
262        for i in 0..(num_children as isize) {
263            let child = owner.get_child(i as i64);
264            if let Some(rail) = child {
265                let rail = unsafe { rail.assume_safe() };
266                match rail.cast::<Spatial>() {
267                    Some(rail) => match rail.get_child(0) {
268                        Some(grid) => {
269                            let grid = unsafe { grid.assume_safe() };
270                            match grid.cast::<GridMap>() {
271                                Some(grid) => {
272                                    let mut translation = grid.translation();
273                                    translation.z = -scale.z / 2.0;
274                                    grid.set_translation(translation);
275                                    grid.set_scale(scale);
276                                }
277                                None => {
278                                    return Err(format!("Unable to cast child of rail to GridMap."))
279                                }
280                            }
281                        }
282                        None => return Err(format!("rail has no child")),
283                    },
284                    None => return Err(format!("rail is not spatial")),
285                }
286            }
287        }
288        return Ok(());
289    }
290
291    #[export]
292    fn get_max_item(&self, _owner: &Spatial) -> i64 {
293        babalcore::MAX_ITEM as i64
294    }
295
296    #[export]
297    fn get_material_flavors(&self, _owner: &Spatial) -> i64 {
298        MATERIAL_FLAVORS as i64
299    }
300
301    fn sync_rail_grid(&mut self, rail: &GridMap, col: isize) -> bool {
302        //godot_print!("sync {:?} {}", rail, i);
303        rail.clear();
304        let n = match rail.mesh_library() {
305            Some(mesh_library) => {
306                let mesh_library = unsafe { mesh_library.assume_safe() };
307                mesh_library.get_last_unused_item_id()
308            }
309            None => 0,
310        };
311        if n != (babalcore::MAX_ITEM * MATERIAL_FLAVORS) as i64 {
312            godot_print!(
313                "wrong number of items in rail grid, should be {} and got {}",
314                babalcore::MAX_ITEM * MATERIAL_FLAVORS,
315                n
316            );
317            return false;
318        }
319        //  .unwrap(get_last_unused_item_id();
320        match &mut self.data {
321            Some(data) => {
322                data.level.set_now_sec(self.now_sec);
323                let start = -RAIL_OFFSET;
324                let end = std::cmp::min(
325                    start + data.level.len() as isize,
326                    RAIL_OFFSET + self.far_view,
327                );
328                for row in start..end {
329                    let mut kind =
330                        data.level
331                            .slab_kind(col, row + self.level_row, self.boost_state);
332                    if self.level_col == col
333                        && self.player_position.y == 0.0
334                        && row == 0
335                        && kind != SlabKind::Void
336                    {
337                        kind = SlabKind::HighLight;
338                    }
339                    if self.fall_state
340                        && row + self.level_row == self.player_best_row
341                        && self.player_best_row > 0
342                        && kind != SlabKind::Void
343                    {
344                        kind = SlabKind::HighLight;
345                    }
346
347                    let mut item = kind.as_item();
348                    if item >= 0 {
349                        if self.level_col == col && MATERIAL_FLAVORS > 1 {
350                            let blend = babalcore::fade_out(self.player_last_col_acc);
351                            let cat = std::cmp::min(
352                                (blend * (MATERIAL_FLAVORS as f64)).floor() as usize,
353                                MATERIAL_FLAVORS - 1,
354                            );
355                            let offset = (babalcore::MAX_ITEM * cat) as i64;
356                            item += offset;
357                        }
358                        let orientation = 0;
359                        rail.set_cell_item(0, 0, (RAIL_OFFSET - 2 - row) as i64, item, orientation);
360                    }
361                }
362                true
363            }
364            None => false,
365        }
366    }
367
368    fn adjust_far_view(&mut self, _owner: &Spatial, delta: f64) {
369        self.time_since_last_adjust_far_view += delta;
370        if self.time_since_last_adjust_far_view < ADJUST_FAR_VIEW_DELAY {
371            return;
372        }
373        self.time_since_last_adjust_far_view = 0.0;
374        let perf = Performance::godot_singleton();
375        let fps = perf.get_monitor(Performance::TIME_FPS);
376        if fps < FPS_TARGET && self.far_view > RAIL_FAR_VIEW_MIN {
377            self.far_view -= 1;
378            godot_print!("Decreasing far view to {}", self.far_view);
379        }
380        if fps > FPS_TARGET && self.far_view < RAIL_FAR_VIEW_MAX {
381            self.far_view += 1;
382            godot_print!("Increasing far view to {}", self.far_view);
383        }
384    }
385
386    #[export]
387    fn sync(&mut self, owner: &Spatial) -> bool {
388        match &mut self.data {
389            Some(data) => {
390                Self::generate_missing(&mut data.level, self.level_row);
391                let num_children = owner.get_child_count() as usize;
392                let width = data.level.width();
393                if num_children == 0 {
394                    godot_print!("Please setup tunnel");
395                    return false;
396                }
397                if num_children != (data.level.width()) {
398                    godot_print!("Wrong number of rails: {} != {}.", num_children, width);
399                    return false;
400                }
401
402                for i in 0..(num_children as isize) {
403                    let child = owner.get_child(i as i64);
404                    if let Some(rail) = child {
405                        let rail = unsafe { rail.assume_safe() };
406                        match rail.cast::<Spatial>() {
407                            Some(rail) => {
408                                let child = rail.get_child(0);
409                                if let Some(grid) = child {
410                                    let grid = unsafe { grid.assume_safe() };
411                                    match grid.cast::<GridMap>() {
412                                        Some(grid) => {
413                                            self.sync_rail_grid(&grid, i);
414                                        }
415                                        None => {
416                                            godot_print!(
417                                                "Unable to cast child of rail to GridMap.",
418                                            );
419                                        }
420                                    }
421                                }
422                            }
423                            None => {
424                                godot_print!("Unable to cast child of rail to Spatial.",);
425                            }
426                        }
427                    }
428                }
429                true
430            }
431            None => false,
432        }
433    }
434
435    #[export]
436    fn update_player_position(&mut self, owner: &Spatial, pos: Vector3) {
437        let mut rotation = owner.rotation();
438        rotation.z = pos.x;
439        owner.set_rotation(rotation);
440
441        let mut translation = owner.translation();
442        translation.z = pos.z * self.grid_scale.z;
443        owner.set_translation(translation);
444
445        self.player_position = pos;
446    }
447
448    #[export]
449    fn set_fall_state(&mut self, _owner: &Spatial, fall_state: bool) {
450        self.fall_state = fall_state;
451    }
452
453    #[export]
454    fn fall_state(&mut self, _owner: &Spatial) -> bool {
455        self.fall_state
456    }
457
458    #[export]
459    fn set_boost_state(&mut self, _owner: &Spatial, boost_state: bool) {
460        self.boost_state = boost_state;
461    }
462
463    #[export]
464    fn boost_state(&mut self, _owner: &Spatial) -> bool {
465        self.boost_state
466    }
467
468    #[export]
469    fn set_level_row(&mut self, _owner: &Spatial, level_row: isize) {
470        self.level_row = level_row;
471        if self.player_best_row < level_row {
472            self.player_best_row = level_row;
473        }
474    }
475
476    #[export]
477    fn set_level_col(&mut self, _owner: &Spatial, level_col: isize) {
478        self.level_col = level_col;
479        if self.player_last_col != level_col {
480            self.player_last_col = level_col;
481            self.player_last_col_acc = 0.0;
482        }
483    }
484
485    #[export]
486    pub fn width(&self, _owner: &Spatial) -> usize {
487        match &self.data {
488            Some(data) => data.level.width(),
489            None => 0,
490        }
491    }
492
493    #[export]
494    pub fn len(&self, _owner: &Spatial) -> usize {
495        match &self.data {
496            Some(data) => data.level.len(),
497            None => 0,
498        }
499    }
500
501    #[export]
502    pub fn first(&self, _owner: &Spatial) -> isize {
503        match &self.data {
504            Some(data) => data.level.first(),
505            None => 0,
506        }
507    }
508
509    #[export]
510    pub fn last(&self, _owner: &Spatial) -> isize {
511        match &self.data {
512            Some(data) => data.level.last(),
513            None => 0,
514        }
515    }
516
517    #[export]
518    pub fn item(&self, _owner: &Spatial, col: isize, row: isize, boost: bool) -> i64 {
519        match &self.data {
520            Some(data) => data.level.item(col, row, boost),
521            None => 0,
522        }
523    }
524
525    #[export]
526    pub fn find_start_spot(
527        &self,
528        _owner: &Spatial,
529        col: isize,
530        row: isize,
531    ) -> Option<(isize, isize)> {
532        match &self.data {
533            Some(data) => data.level.find_start_spot(col, row),
534            None => None,
535        }
536    }
537
538    #[export]
539    pub fn skill(&self, _owner: &Spatial) -> GodotString {
540        let skill = match &self.data {
541            Some(data) => data.level.skill(),
542            None => Skill::default(),
543        };
544        GodotString::from_str(skill.as_str())
545    }
546}