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