babalcore/
player.rs

1use super::input_query::*;
2use super::level_query::*;
3use super::player_event::*;
4use super::points_counter::*;
5use super::slab_kind::*;
6use super::time_tracker::*;
7use euclid::default::Vector3D;
8use std::f64::consts::PI;
9
10const STEER_SLOW_DOWN: f64 = STEER_WIDTH_SCALE * 3.0 / 5.0;
11const STEER_MAX_SPEED: f64 = STEER_WIDTH_SCALE * 2.0 / 5.0;
12const STEER_WIDTH_SCALE: f64 = 500.0;
13
14const JUMP_VY_INIT: f64 = JUMP_HEIGHT_SCALE * 3.0;
15const JUMP_VY_BOOST_INIT: f64 = JUMP_HEIGHT_SCALE * 3.4;
16const JUMP_VY_OVERDRIVE_INIT: f64 = JUMP_HEIGHT_SCALE * 3.6;
17const JUMP_POSY_INIT: f64 = JUMP_HEIGHT_SCALE / 1000.0;
18const JUMP_GRAVITY: f64 = JUMP_HEIGHT_SCALE * 6.0;
19const JUMP_HEIGHT_SCALE: f64 = 100.0;
20
21const FORWARD_JUMP_BOOST: f64 = FORWARD_DEPTH_SCALE * 2.0;
22const FORWARD_STEER_BOOST_ON_GROUND: f64 = FORWARD_DEPTH_SCALE * 6.0;
23const FORWARD_STEER_BOOST_IN_AIR: f64 = FORWARD_DEPTH_SCALE * 2.0;
24const FORWARD_MAX_SPEED: f64 = FORWARD_DEPTH_SCALE * 15.0;
25const FORWARD_BOOST_SPEED: f64 = FORWARD_DEPTH_SCALE * 6.0;
26const FORWARD_OVERDRIVE_SPEED: f64 = FORWARD_DEPTH_SCALE * 12.0;
27const FORWARD_SLOW_DOWN_LINEAR_ON_GROUND: f64 = FORWARD_DEPTH_SCALE * 0.2;
28const FORWARD_SLOW_DOWN_SQUARE_ON_GROUND: f64 = 0.5;
29const FORWARD_SLOW_DOWN_LINEAR_IN_AIR: f64 = FORWARD_DEPTH_SCALE * 0.1;
30const FORWARD_SLOW_DOWN_SQUARE_IN_AIR: f64 = 0.4;
31const FORWARD_DEPTH_SCALE: f64 = 100.0;
32
33const BACKING_REPOS_DELAY: f64 = 0.05;
34const BACKING_FALL_DELAY: f64 = 0.55;
35const BACKING_REWIND_DELAY: f64 = 0.4;
36const BACKING_SIMULATED_DELAY: f64 = 5.0;
37
38const BACKING_FALL_DEPTH: f64 = 5.0;
39
40const EXTRA_TIME_ON_BOOST: f64 = 0.01;
41const EXTRA_TIME_ON_OVERDRIVE: f64 = 0.05;
42
43#[derive(Debug, Clone, Copy)]
44struct PointInTime {
45    position_vec: Vector3D<f64>,
46    time_sec: f64,
47}
48
49#[derive(Debug, Clone, Copy)]
50struct BackingData {
51    begin: PointInTime,
52    top_fall: PointInTime,
53    bottom_fall: PointInTime,
54    end: PointInTime,
55}
56
57#[derive(Debug)]
58pub struct Player {
59    initialized: bool,
60    steer_factor: f64,
61    position_vec: Vector3D<f64>,
62    speed_vec: Vector3D<f64>,
63    time_tracker: TimeTracker,
64    is_jumping: bool,
65    boost_state: bool,
66    cur_level_col: isize,
67    backing_data: Option<BackingData>,
68    points_counter: PointsCounter,
69    boost_count: isize,
70    overdrive_count: isize,
71}
72
73impl Player {
74    pub fn new() -> Player {
75        Player::default()
76    }
77
78    pub fn tick(
79        &mut self,
80        delta: f64,
81        level: &impl LevelQuery,
82        input: &mut impl InputQuery,
83    ) -> Vec<PlayerEvent> {
84        let mut events = Vec::new();
85        if !self.initialized {
86            self.replace(level);
87            self.initialized = true;
88            events.push(PlayerEvent::new_start());
89        }
90        self.time_tracker.add(delta);
91        events.append(&mut self.handle_steer(delta, input, level.width()));
92        events.append(&mut self.handle_jump(delta, input, level));
93        events.append(&mut self.handle_boost(level));
94        events.append(&mut self.handle_forward(delta, level));
95        events.append(&mut self.handle_fall(level));
96        events.append(&mut self.points_counter.handle_dist(
97            delta,
98            self.dist(),
99            self.time_to_complete(level),
100        ));
101        events
102    }
103
104    fn steer_max_speed(&self) -> f64 {
105        if self.steer_factor > 0.0 {
106            (STEER_MAX_SPEED * self.steer_factor).abs()
107        } else {
108            STEER_MAX_SPEED
109        }
110    }
111
112    fn steer_slow_down(&self) -> f64 {
113        if self.steer_factor > 0.0 {
114            (STEER_SLOW_DOWN * self.steer_factor).abs()
115        } else {
116            STEER_SLOW_DOWN
117        }
118    }
119
120    fn replace(&mut self, level: &impl LevelQuery) {
121        if let Some(start_spot) =
122            level.find_start_spot(self.level_col(level.width()), self.level_row())
123        {
124            self.position_vec = Self::from_level_coord(start_spot.0, start_spot.1, level.width())
125        };
126    }
127
128    fn handle_fall(&mut self, level: &impl LevelQuery) -> Vec<PlayerEvent> {
129        match self.backing_data {
130            Some(backing_data) => self.update_fall(backing_data),
131            None => {
132                if !self.check_valid_position(level) {
133                    self.begin_fall(level)
134                } else {
135                    Vec::new()
136                }
137            }
138        }
139    }
140
141    fn update_fall(&mut self, backing_data: BackingData) -> Vec<PlayerEvent> {
142        let now_sec = self.time_tracker.now_sec();
143
144        let end_sec = backing_data.end.time_sec;
145        if now_sec >= end_sec {
146            return self.end_fall(backing_data);
147        }
148
149        let bottom_fall_sec = backing_data.bottom_fall.time_sec;
150        if now_sec >= bottom_fall_sec {
151            let alpha = (now_sec - bottom_fall_sec) / (end_sec - bottom_fall_sec);
152            self.position_vec = backing_data
153                .top_fall
154                .position_vec
155                .lerp(backing_data.end.position_vec, alpha);
156            return Vec::new();
157        }
158
159        let top_fall_sec = backing_data.top_fall.time_sec;
160        if now_sec >= top_fall_sec {
161            let alpha = (now_sec - top_fall_sec) / (bottom_fall_sec - top_fall_sec);
162            self.position_vec = backing_data
163                .top_fall
164                .position_vec
165                .lerp(backing_data.bottom_fall.position_vec, alpha);
166            return Vec::new();
167        }
168
169        let begin_sec = backing_data.begin.time_sec;
170        if now_sec >= begin_sec {
171            let alpha = (now_sec - begin_sec) / (top_fall_sec - begin_sec);
172            self.position_vec = backing_data
173                .begin
174                .position_vec
175                .lerp(backing_data.begin.position_vec, alpha);
176            return Vec::new();
177        }
178
179        return Vec::new();
180    }
181
182    fn begin_fall(&mut self, level: &impl LevelQuery) -> Vec<PlayerEvent> {
183        let offset = BACKING_SIMULATED_DELAY * self.speed_vec.z / FORWARD_DEPTH_SCALE;
184        let backing_col = self.level_col(level.width());
185        let backing_src_row = self.level_row();
186        let backing_dst_row = backing_src_row - (offset as isize);
187        if let Some(start_spot) = level.find_start_spot(backing_col, backing_dst_row) {
188            let now_sec = self.time_tracker.now_sec();
189            let begin = PointInTime {
190                position_vec: self.position_vec,
191                time_sec: now_sec,
192            };
193            let top_fall = PointInTime {
194                position_vec: Self::from_level_coord(backing_col, backing_src_row, level.width()),
195                time_sec: now_sec + BACKING_REPOS_DELAY,
196            };
197            let bottom_fall = PointInTime {
198                position_vec: (Self::from_level_coord(backing_col, backing_src_row, level.width())
199                    - Vector3D::new(0.0, BACKING_FALL_DEPTH * JUMP_HEIGHT_SCALE, 0.0)),
200                time_sec: now_sec + BACKING_REPOS_DELAY + BACKING_FALL_DELAY,
201            };
202            let end = PointInTime {
203                position_vec: Self::from_level_coord(start_spot.0, start_spot.1, level.width()),
204                time_sec: now_sec + BACKING_REPOS_DELAY + BACKING_FALL_DELAY + BACKING_REWIND_DELAY,
205            };
206            self.backing_data = Some(BackingData {
207                begin,
208                top_fall,
209                bottom_fall,
210                end,
211            });
212        };
213        self.speed_vec = Vector3D::new(0.0, 0.0, 0.0);
214        vec![PlayerEvent::new_fall()]
215    }
216
217    fn end_fall(&mut self, backing_data: BackingData) -> Vec<PlayerEvent> {
218        let mut vec = Vec::new();
219        self.position_vec = backing_data.end.position_vec;
220        if self.level_row() >= self.best_row() {
221            vec.push(PlayerEvent::new_catch_up());
222        }
223        self.speed_vec = Vector3D::new(0.0, 0.0, 0.0);
224        self.backing_data = None;
225        vec.push(PlayerEvent::new_start());
226        vec
227    }
228
229    fn export_position3(v: Vector3D<f64>) -> Vector3D<f32> {
230        Vector3D::<f32>::new(
231            (-v.x * 2.0 * PI / STEER_WIDTH_SCALE) as f32,
232            (v.y / JUMP_HEIGHT_SCALE) as f32,
233            (((v.z / FORWARD_DEPTH_SCALE) + 0.5).fract() - 0.5) as f32,
234        )
235    }
236
237    pub fn position(&self) -> Vector3D<f32> {
238        Self::export_position3(self.position_vec)
239    }
240
241    fn export_speed3(v: Vector3D<f64>) -> Vector3D<f32> {
242        Vector3D::<f32>::new(
243            (-v.x * 2.0 * PI / STEER_WIDTH_SCALE) as f32,
244            (v.y / JUMP_HEIGHT_SCALE) as f32,
245            (v.z / FORWARD_DEPTH_SCALE) as f32,
246        )
247    }
248
249    pub fn speed(&self) -> Vector3D<f32> {
250        Self::export_speed3(self.speed_vec)
251    }
252
253    pub fn level_col(&self, width: usize) -> isize {
254        ((self.position_vec.x * width as f64 / STEER_WIDTH_SCALE).round() as isize)
255            % (width as isize)
256    }
257
258    pub fn level_row(&self) -> isize {
259        (self.dist()).round() as isize
260    }
261
262    pub fn dist(&self) -> f64 {
263        self.position_vec.z / FORWARD_DEPTH_SCALE
264    }
265
266    pub fn from_level_coord(col: isize, row: isize, width: usize) -> Vector3D<f64> {
267        let mut real_col = col;
268        while real_col < 0 {
269            real_col += width as isize;
270        }
271        real_col = real_col % (width as isize);
272        let real_row = std::cmp::max(row, 0);
273        Vector3D::new(
274            real_col as f64 * STEER_WIDTH_SCALE / width as f64,
275            0.0,
276            real_row as f64 * FORWARD_DEPTH_SCALE,
277        )
278    }
279
280    pub fn set_steer_factor(&mut self, sensibility: f64) {
281        self.steer_factor = sensibility;
282    }
283
284    fn check_valid_position(&self, level: &impl LevelQuery) -> bool {
285        let kind = SlabKind::from_item(level.item(
286            self.level_col(level.width()),
287            self.level_row(),
288            self.boost_state,
289        ));
290        !(kind == SlabKind::Void && self.position_vec.y <= 0.0)
291    }
292
293    fn handle_steer(
294        &mut self,
295        delta: f64,
296        input: &mut impl InputQuery,
297        width: usize,
298    ) -> Vec<PlayerEvent> {
299        if let Some(_) = self.backing_data {
300            return Vec::new();
301        }
302
303        let steer = input.pop_steer();
304        self.speed_vec.x += steer;
305
306        let steer_max_speed = self.steer_max_speed();
307        if self.speed_vec.x > steer_max_speed {
308            self.speed_vec.x = steer_max_speed;
309        }
310        if self.speed_vec.x < -steer_max_speed {
311            self.speed_vec.x = -steer_max_speed;
312        }
313
314        let steer_slow_down = self.steer_slow_down();
315        if self.speed_vec.x > 0.0 {
316            self.speed_vec.x -= delta * steer_slow_down;
317            if self.speed_vec.x < 0.0 {
318                self.speed_vec.x = 0.0;
319            }
320            self.speed_vec.z +=
321                delta * (self.speed_vec.x / steer_max_speed) * self.forward_steer_boost();
322        }
323        if self.speed_vec.x < 0.0 {
324            self.speed_vec.x += delta * steer_slow_down;
325            if self.speed_vec.x > 0.0 {
326                self.speed_vec.x = 0.0;
327            }
328            self.speed_vec.z -=
329                delta * (self.speed_vec.x / steer_max_speed) * self.forward_steer_boost();
330        }
331
332        self.position_vec.x += self.speed_vec.x * (delta);
333
334        while self.position_vec.x >= STEER_WIDTH_SCALE {
335            self.position_vec.x -= STEER_WIDTH_SCALE;
336        }
337        while self.position_vec.x < 0.0 {
338            self.position_vec.x += STEER_WIDTH_SCALE;
339        }
340        if self.cur_level_col != self.level_col(width) {
341            self.cur_level_col = self.level_col(width);
342            return vec![PlayerEvent::new_change_column()];
343        }
344        Vec::new()
345    }
346
347    fn handle_jump(
348        &mut self,
349        delta: f64,
350        input: &mut impl InputQuery,
351        _level: &impl LevelQuery,
352    ) -> Vec<PlayerEvent> {
353        if let Some(_) = self.backing_data {
354            return Vec::new();
355        }
356
357        let jump = input.pop_jump();
358        if self.is_jumping {
359            self.speed_vec.y -= delta * JUMP_GRAVITY;
360            self.position_vec.y += delta * self.speed_vec.y;
361
362            if self.position_vec.y < 0.0 {
363                self.speed_vec.y = 0.0;
364                self.position_vec.y = 0.0;
365                self.is_jumping = false;
366                return vec![PlayerEvent::new_land()];
367            }
368            if self.speed_vec.y > 0.0 {
369                self.speed_vec.z += delta * FORWARD_JUMP_BOOST;
370            }
371        } else if jump {
372            self.is_jumping = true;
373            self.speed_vec.y = JUMP_VY_INIT;
374            self.position_vec.y = JUMP_POSY_INIT;
375            return vec![PlayerEvent::new_jump()];
376        }
377        Vec::new()
378    }
379
380    fn is_backing(&self) -> bool {
381        if let Some(_) = self.backing_data {
382            return true;
383        }
384        false
385    }
386
387    pub fn backing_camera_blend(&self) -> f64 {
388        match self.backing_data {
389            Some(backing_data) => {
390                let now_sec = self.time_tracker.now_sec();
391                let begin_sec = backing_data.begin.time_sec;
392                let end_sec = backing_data.end.time_sec;
393                if now_sec <= begin_sec || now_sec >= end_sec {
394                    0.0
395                } else {
396                    let middle_sec = (begin_sec + end_sec) / 2.0;
397                    let half_range = middle_sec - begin_sec;
398                    if half_range > 0.0 {
399                        if now_sec <= middle_sec {
400                            (now_sec - begin_sec) / half_range
401                        } else {
402                            (end_sec - now_sec) / half_range
403                        }
404                    } else {
405                        0.0
406                    }
407                }
408            }
409            None => 0.0,
410        }
411    }
412
413    fn is_on_ground(&self) -> bool {
414        self.position_vec.y == 0.0
415    }
416
417    fn handle_boost(&mut self, level: &impl LevelQuery) -> Vec<PlayerEvent> {
418        if self.is_on_ground() && !self.is_backing() {
419            let kind = SlabKind::from_item(level.item(
420                self.level_col(level.width()),
421                self.level_row(),
422                self.boost_state,
423            ));
424            if kind == SlabKind::Boost || kind == SlabKind::Overdrive {
425                let target_speed = if self.boost_state {
426                    FORWARD_OVERDRIVE_SPEED
427                } else {
428                    FORWARD_BOOST_SPEED
429                };
430                if self.speed_vec.z < target_speed {
431                    self.speed_vec.z = target_speed;
432                }
433                let ret = if self.boost_state {
434                    if self.level_row() >= self.best_row() {
435                        self.overdrive_count += 1;
436                    }
437                    vec![PlayerEvent::new_overdrive()]
438                } else {
439                    if self.level_row() >= self.best_row() {
440                        self.boost_count += 1;
441                    }
442                    vec![PlayerEvent::new_boost()]
443                };
444                self.speed_vec.y = if self.boost_state {
445                    JUMP_VY_OVERDRIVE_INIT
446                } else {
447                    JUMP_VY_BOOST_INIT
448                };
449                self.position_vec.y = JUMP_POSY_INIT;
450                self.is_jumping = true;
451                self.boost_state = true;
452                return ret;
453            } else {
454                self.boost_state = false;
455            }
456        }
457        Vec::new()
458    }
459
460    fn forward_slow_down_linear(&self) -> f64 {
461        if self.is_on_ground() {
462            FORWARD_SLOW_DOWN_LINEAR_ON_GROUND
463        } else {
464            FORWARD_SLOW_DOWN_LINEAR_IN_AIR
465        }
466    }
467
468    fn forward_slow_down_square(&self) -> f64 {
469        if self.is_on_ground() {
470            FORWARD_SLOW_DOWN_SQUARE_ON_GROUND
471        } else {
472            FORWARD_SLOW_DOWN_SQUARE_IN_AIR
473        }
474    }
475
476    fn forward_steer_boost(&self) -> f64 {
477        if self.is_on_ground() {
478            FORWARD_STEER_BOOST_ON_GROUND
479        } else {
480            FORWARD_STEER_BOOST_IN_AIR
481        }
482    }
483
484    fn handle_forward(&mut self, delta: f64, level: &impl LevelQuery) -> Vec<PlayerEvent> {
485        if self.is_backing() {
486            return Vec::new();
487        }
488
489        self.speed_vec.z -= delta * self.forward_slow_down_linear();
490        self.speed_vec.z -= delta * self.forward_slow_down_square() * self.speed_vec.z;
491
492        if self.speed_vec.z > FORWARD_MAX_SPEED {
493            self.speed_vec.z = FORWARD_MAX_SPEED;
494        }
495
496        if self.speed_vec.z < 0.0 {
497            self.speed_vec.z = 0.0;
498        }
499
500        self.position_vec.z += delta * self.speed_vec.z * self.speed_factor(level);
501        if self.position_vec.z < 0.0 {
502            self.position_vec.z = 0.0;
503        }
504
505        Vec::new()
506    }
507
508    pub fn speed_factor(&self, level: &impl LevelQuery) -> f64 {
509        let time_pct = self.time_pct(level);
510        let speed_factor = ((100.0 - time_pct) * level.skill().begin_speed_factor()
511            + time_pct * level.skill().end_speed_factor())
512            / 100.0;
513        speed_factor
514    }
515
516    pub fn score(&self) -> i32 {
517        self.points_counter.score()
518    }
519
520    pub fn set_best_score(&mut self, best_score: i32) {
521        self.points_counter.set_best_score(best_score);
522    }
523
524    pub fn best_row(&self) -> isize {
525        self.points_counter.best_row()
526    }
527
528    pub fn best_dist(&self) -> f64 {
529        self.points_counter.best_dist()
530    }
531
532    pub fn is_boosting(&self) -> bool {
533        self.boost_state
534    }
535
536    fn time_pct(&self, level: &impl LevelQuery) -> f64 {
537        self.points_counter.time_pct(self.time_to_complete(level))
538    }
539
540    fn time_to_complete(&self, level: &impl LevelQuery) -> f64 {
541        let base = level.skill().time_to_complete();
542        base * self.time_progress_total() / 100.0
543    }
544
545    pub fn time_progress_done(&self, level: &impl LevelQuery) -> f64 {
546        let pct = self.time_pct(level);
547        if pct >= 100.0 {
548            self.time_progress_total()
549        } else {
550            pct * self.time_progress_total() / 100.0
551        }
552    }
553
554    pub fn time_progress_total(&self) -> f64 {
555        let extra_time_from_boost = EXTRA_TIME_ON_BOOST * (self.boost_count as f64);
556        let extra_time_from_overdrive = EXTRA_TIME_ON_OVERDRIVE * (self.overdrive_count as f64);
557        100.0 * (1.0 + extra_time_from_boost + extra_time_from_overdrive)
558    }
559}
560
561impl std::default::Default for Player {
562    fn default() -> Self {
563        Player {
564            initialized: false,
565            steer_factor: 1.0,
566            position_vec: Vector3D::default(),
567            speed_vec: Vector3D::default(),
568            time_tracker: TimeTracker::default(),
569            is_jumping: false,
570            boost_state: false,
571            cur_level_col: 0,
572            backing_data: None,
573            points_counter: PointsCounter::default(),
574            boost_count: 0,
575            overdrive_count: 0,
576        }
577    }
578}