use super::input_query::*;
use super::level_query::*;
use super::player_event::*;
use super::points_counter::*;
use super::slab_kind::*;
use super::time_tracker::*;
use euclid::default::Vector3D;
use std::f64::consts::PI;
const STEER_SLOW_DOWN: f64 = STEER_WIDTH_SCALE * 3.0 / 5.0;
const STEER_MAX_SPEED: f64 = STEER_WIDTH_SCALE * 2.0 / 5.0;
const STEER_WIDTH_SCALE: f64 = 500.0;
const JUMP_VY_INIT: f64 = JUMP_HEIGHT_SCALE * 3.0;
const JUMP_VY_BOOST_INIT: f64 = JUMP_HEIGHT_SCALE * 3.4;
const JUMP_VY_OVERDRIVE_INIT: f64 = JUMP_HEIGHT_SCALE * 3.6;
const JUMP_POSY_INIT: f64 = JUMP_HEIGHT_SCALE / 1000.0;
const JUMP_GRAVITY: f64 = JUMP_HEIGHT_SCALE * 6.0;
const JUMP_HEIGHT_SCALE: f64 = 100.0;
const FORWARD_JUMP_BOOST: f64 = FORWARD_DEPTH_SCALE * 2.0;
const FORWARD_STEER_BOOST_ON_GROUND: f64 = FORWARD_DEPTH_SCALE * 6.0;
const FORWARD_STEER_BOOST_IN_AIR: f64 = FORWARD_DEPTH_SCALE * 2.0;
const FORWARD_MAX_SPEED: f64 = FORWARD_DEPTH_SCALE * 15.0;
const FORWARD_BOOST_SPEED: f64 = FORWARD_DEPTH_SCALE * 6.0;
const FORWARD_OVERDRIVE_SPEED: f64 = FORWARD_DEPTH_SCALE * 12.0;
const FORWARD_SLOW_DOWN_LINEAR_ON_GROUND: f64 = FORWARD_DEPTH_SCALE * 0.2;
const FORWARD_SLOW_DOWN_SQUARE_ON_GROUND: f64 = 0.5;
const FORWARD_SLOW_DOWN_LINEAR_IN_AIR: f64 = FORWARD_DEPTH_SCALE * 0.1;
const FORWARD_SLOW_DOWN_SQUARE_IN_AIR: f64 = 0.4;
const FORWARD_DEPTH_SCALE: f64 = 100.0;
const BACKING_REPOS_DELAY: f64 = 0.05;
const BACKING_FALL_DELAY: f64 = 0.55;
const BACKING_REWIND_DELAY: f64 = 0.4;
const BACKING_SIMULATED_DELAY: f64 = 5.0;
const BACKING_FALL_DEPTH: f64 = 5.0;
const EXTRA_TIME_ON_BOOST: f64 = 0.01;
const EXTRA_TIME_ON_OVERDRIVE: f64 = 0.05;
#[derive(Debug, Clone, Copy)]
struct PointInTime {
position_vec: Vector3D<f64>,
time_sec: f64,
}
#[derive(Debug, Clone, Copy)]
struct BackingData {
begin: PointInTime,
top_fall: PointInTime,
bottom_fall: PointInTime,
end: PointInTime,
}
#[derive(Debug)]
pub struct Player {
initialized: bool,
steer_factor: f64,
position_vec: Vector3D<f64>,
speed_vec: Vector3D<f64>,
time_tracker: TimeTracker,
is_jumping: bool,
boost_state: bool,
cur_level_col: isize,
backing_data: Option<BackingData>,
points_counter: PointsCounter,
boost_count: isize,
overdrive_count: isize,
}
impl Player {
pub fn new() -> Player {
Player::default()
}
pub fn tick(
&mut self,
delta: f64,
level: &impl LevelQuery,
input: &mut impl InputQuery,
) -> Vec<PlayerEvent> {
let mut events = Vec::new();
if !self.initialized {
self.replace(level);
self.initialized = true;
events.push(PlayerEvent::new_start());
}
self.time_tracker.add(delta);
events.append(&mut self.handle_steer(delta, input, level.width()));
events.append(&mut self.handle_jump(delta, input, level));
events.append(&mut self.handle_boost(level));
events.append(&mut self.handle_forward(delta, level));
events.append(&mut self.handle_fall(level));
events.append(&mut self.points_counter.handle_dist(
delta,
self.dist(),
self.time_to_complete(level),
));
events
}
fn steer_max_speed(&self) -> f64 {
if self.steer_factor > 0.0 {
(STEER_MAX_SPEED * self.steer_factor).abs()
} else {
STEER_MAX_SPEED
}
}
fn steer_slow_down(&self) -> f64 {
if self.steer_factor > 0.0 {
(STEER_SLOW_DOWN * self.steer_factor).abs()
} else {
STEER_SLOW_DOWN
}
}
fn replace(&mut self, level: &impl LevelQuery) {
if let Some(start_spot) =
level.find_start_spot(self.level_col(level.width()), self.level_row())
{
self.position_vec = Self::from_level_coord(start_spot.0, start_spot.1, level.width())
};
}
fn handle_fall(&mut self, level: &impl LevelQuery) -> Vec<PlayerEvent> {
match self.backing_data {
Some(backing_data) => self.update_fall(backing_data),
None => {
if !self.check_valid_position(level) {
self.begin_fall(level)
} else {
Vec::new()
}
}
}
}
fn update_fall(&mut self, backing_data: BackingData) -> Vec<PlayerEvent> {
let now_sec = self.time_tracker.now_sec();
let end_sec = backing_data.end.time_sec;
if now_sec >= end_sec {
return self.end_fall(backing_data);
}
let bottom_fall_sec = backing_data.bottom_fall.time_sec;
if now_sec >= bottom_fall_sec {
let alpha = (now_sec - bottom_fall_sec) / (end_sec - bottom_fall_sec);
self.position_vec = backing_data
.top_fall
.position_vec
.lerp(backing_data.end.position_vec, alpha);
return Vec::new();
}
let top_fall_sec = backing_data.top_fall.time_sec;
if now_sec >= top_fall_sec {
let alpha = (now_sec - top_fall_sec) / (bottom_fall_sec - top_fall_sec);
self.position_vec = backing_data
.top_fall
.position_vec
.lerp(backing_data.bottom_fall.position_vec, alpha);
return Vec::new();
}
let begin_sec = backing_data.begin.time_sec;
if now_sec >= begin_sec {
let alpha = (now_sec - begin_sec) / (top_fall_sec - begin_sec);
self.position_vec = backing_data
.begin
.position_vec
.lerp(backing_data.begin.position_vec, alpha);
return Vec::new();
}
return Vec::new();
}
fn begin_fall(&mut self, level: &impl LevelQuery) -> Vec<PlayerEvent> {
let offset = BACKING_SIMULATED_DELAY * self.speed_vec.z / FORWARD_DEPTH_SCALE;
let backing_col = self.level_col(level.width());
let backing_src_row = self.level_row();
let backing_dst_row = backing_src_row - (offset as isize);
if let Some(start_spot) = level.find_start_spot(backing_col, backing_dst_row) {
let now_sec = self.time_tracker.now_sec();
let begin = PointInTime {
position_vec: self.position_vec,
time_sec: now_sec,
};
let top_fall = PointInTime {
position_vec: Self::from_level_coord(backing_col, backing_src_row, level.width()),
time_sec: now_sec + BACKING_REPOS_DELAY,
};
let bottom_fall = PointInTime {
position_vec: (Self::from_level_coord(backing_col, backing_src_row, level.width())
- Vector3D::new(0.0, BACKING_FALL_DEPTH * JUMP_HEIGHT_SCALE, 0.0)),
time_sec: now_sec + BACKING_REPOS_DELAY + BACKING_FALL_DELAY,
};
let end = PointInTime {
position_vec: Self::from_level_coord(start_spot.0, start_spot.1, level.width()),
time_sec: now_sec + BACKING_REPOS_DELAY + BACKING_FALL_DELAY + BACKING_REWIND_DELAY,
};
self.backing_data = Some(BackingData {
begin,
top_fall,
bottom_fall,
end,
});
};
self.speed_vec = Vector3D::new(0.0, 0.0, 0.0);
vec![PlayerEvent::new_fall()]
}
fn end_fall(&mut self, backing_data: BackingData) -> Vec<PlayerEvent> {
let mut vec = Vec::new();
self.position_vec = backing_data.end.position_vec;
if self.level_row() >= self.best_row() {
vec.push(PlayerEvent::new_catch_up());
}
self.speed_vec = Vector3D::new(0.0, 0.0, 0.0);
self.backing_data = None;
vec.push(PlayerEvent::new_start());
vec
}
fn export_position3(v: Vector3D<f64>) -> Vector3D<f32> {
Vector3D::<f32>::new(
(-v.x * 2.0 * PI / STEER_WIDTH_SCALE) as f32,
(v.y / JUMP_HEIGHT_SCALE) as f32,
(((v.z / FORWARD_DEPTH_SCALE) + 0.5).fract() - 0.5) as f32,
)
}
pub fn position(&self) -> Vector3D<f32> {
Self::export_position3(self.position_vec)
}
fn export_speed3(v: Vector3D<f64>) -> Vector3D<f32> {
Vector3D::<f32>::new(
(-v.x * 2.0 * PI / STEER_WIDTH_SCALE) as f32,
(v.y / JUMP_HEIGHT_SCALE) as f32,
(v.z / FORWARD_DEPTH_SCALE) as f32,
)
}
pub fn speed(&self) -> Vector3D<f32> {
Self::export_speed3(self.speed_vec)
}
pub fn level_col(&self, width: usize) -> isize {
((self.position_vec.x * width as f64 / STEER_WIDTH_SCALE).round() as isize)
% (width as isize)
}
pub fn level_row(&self) -> isize {
(self.dist()).round() as isize
}
pub fn dist(&self) -> f64 {
self.position_vec.z / FORWARD_DEPTH_SCALE
}
pub fn from_level_coord(col: isize, row: isize, width: usize) -> Vector3D<f64> {
let mut real_col = col;
while real_col < 0 {
real_col += width as isize;
}
real_col = real_col % (width as isize);
let real_row = std::cmp::max(row, 0);
Vector3D::new(
real_col as f64 * STEER_WIDTH_SCALE / width as f64,
0.0,
real_row as f64 * FORWARD_DEPTH_SCALE,
)
}
pub fn set_steer_factor(&mut self, sensibility: f64) {
self.steer_factor = sensibility;
}
fn check_valid_position(&self, level: &impl LevelQuery) -> bool {
let kind = SlabKind::from_item(level.item(
self.level_col(level.width()),
self.level_row(),
self.boost_state,
));
!(kind == SlabKind::Void && self.position_vec.y <= 0.0)
}
fn handle_steer(
&mut self,
delta: f64,
input: &mut impl InputQuery,
width: usize,
) -> Vec<PlayerEvent> {
if let Some(_) = self.backing_data {
return Vec::new();
}
let steer = input.pop_steer();
self.speed_vec.x += steer;
let steer_max_speed = self.steer_max_speed();
if self.speed_vec.x > steer_max_speed {
self.speed_vec.x = steer_max_speed;
}
if self.speed_vec.x < -steer_max_speed {
self.speed_vec.x = -steer_max_speed;
}
let steer_slow_down = self.steer_slow_down();
if self.speed_vec.x > 0.0 {
self.speed_vec.x -= delta * steer_slow_down;
if self.speed_vec.x < 0.0 {
self.speed_vec.x = 0.0;
}
self.speed_vec.z +=
delta * (self.speed_vec.x / steer_max_speed) * self.forward_steer_boost();
}
if self.speed_vec.x < 0.0 {
self.speed_vec.x += delta * steer_slow_down;
if self.speed_vec.x > 0.0 {
self.speed_vec.x = 0.0;
}
self.speed_vec.z -=
delta * (self.speed_vec.x / steer_max_speed) * self.forward_steer_boost();
}
self.position_vec.x += self.speed_vec.x * (delta);
while self.position_vec.x >= STEER_WIDTH_SCALE {
self.position_vec.x -= STEER_WIDTH_SCALE;
}
while self.position_vec.x < 0.0 {
self.position_vec.x += STEER_WIDTH_SCALE;
}
if self.cur_level_col != self.level_col(width) {
self.cur_level_col = self.level_col(width);
return vec![PlayerEvent::new_change_column()];
}
Vec::new()
}
fn handle_jump(
&mut self,
delta: f64,
input: &mut impl InputQuery,
_level: &impl LevelQuery,
) -> Vec<PlayerEvent> {
if let Some(_) = self.backing_data {
return Vec::new();
}
let jump = input.pop_jump();
if self.is_jumping {
self.speed_vec.y -= delta * JUMP_GRAVITY;
self.position_vec.y += delta * self.speed_vec.y;
if self.position_vec.y < 0.0 {
self.speed_vec.y = 0.0;
self.position_vec.y = 0.0;
self.is_jumping = false;
return vec![PlayerEvent::new_land()];
}
if self.speed_vec.y > 0.0 {
self.speed_vec.z += delta * FORWARD_JUMP_BOOST;
}
} else if jump {
self.is_jumping = true;
self.speed_vec.y = JUMP_VY_INIT;
self.position_vec.y = JUMP_POSY_INIT;
return vec![PlayerEvent::new_jump()];
}
Vec::new()
}
fn is_backing(&self) -> bool {
if let Some(_) = self.backing_data {
return true;
}
false
}
pub fn backing_camera_blend(&self) -> f64 {
match self.backing_data {
Some(backing_data) => {
let now_sec = self.time_tracker.now_sec();
let begin_sec = backing_data.begin.time_sec;
let end_sec = backing_data.end.time_sec;
if now_sec <= begin_sec || now_sec >= end_sec {
0.0
} else {
let middle_sec = (begin_sec + end_sec) / 2.0;
let half_range = middle_sec - begin_sec;
if half_range > 0.0 {
if now_sec <= middle_sec {
(now_sec - begin_sec) / half_range
} else {
(end_sec - now_sec) / half_range
}
} else {
0.0
}
}
}
None => 0.0,
}
}
fn is_on_ground(&self) -> bool {
self.position_vec.y == 0.0
}
fn handle_boost(&mut self, level: &impl LevelQuery) -> Vec<PlayerEvent> {
if self.is_on_ground() && !self.is_backing() {
let kind = SlabKind::from_item(level.item(
self.level_col(level.width()),
self.level_row(),
self.boost_state,
));
if kind == SlabKind::Boost || kind == SlabKind::Overdrive {
let target_speed = if self.boost_state {
FORWARD_OVERDRIVE_SPEED
} else {
FORWARD_BOOST_SPEED
};
if self.speed_vec.z < target_speed {
self.speed_vec.z = target_speed;
}
let ret = if self.boost_state {
if self.level_row() >= self.best_row() {
self.overdrive_count += 1;
}
vec![PlayerEvent::new_overdrive()]
} else {
if self.level_row() >= self.best_row() {
self.boost_count += 1;
}
vec![PlayerEvent::new_boost()]
};
self.speed_vec.y = if self.boost_state {
JUMP_VY_OVERDRIVE_INIT
} else {
JUMP_VY_BOOST_INIT
};
self.position_vec.y = JUMP_POSY_INIT;
self.is_jumping = true;
self.boost_state = true;
return ret;
} else {
self.boost_state = false;
}
}
Vec::new()
}
fn forward_slow_down_linear(&self) -> f64 {
if self.is_on_ground() {
FORWARD_SLOW_DOWN_LINEAR_ON_GROUND
} else {
FORWARD_SLOW_DOWN_LINEAR_IN_AIR
}
}
fn forward_slow_down_square(&self) -> f64 {
if self.is_on_ground() {
FORWARD_SLOW_DOWN_SQUARE_ON_GROUND
} else {
FORWARD_SLOW_DOWN_SQUARE_IN_AIR
}
}
fn forward_steer_boost(&self) -> f64 {
if self.is_on_ground() {
FORWARD_STEER_BOOST_ON_GROUND
} else {
FORWARD_STEER_BOOST_IN_AIR
}
}
fn handle_forward(&mut self, delta: f64, level: &impl LevelQuery) -> Vec<PlayerEvent> {
if self.is_backing() {
return Vec::new();
}
self.speed_vec.z -= delta * self.forward_slow_down_linear();
self.speed_vec.z -= delta * self.forward_slow_down_square() * self.speed_vec.z;
if self.speed_vec.z > FORWARD_MAX_SPEED {
self.speed_vec.z = FORWARD_MAX_SPEED;
}
if self.speed_vec.z < 0.0 {
self.speed_vec.z = 0.0;
}
self.position_vec.z += delta * self.speed_vec.z * self.speed_factor(level);
if self.position_vec.z < 0.0 {
self.position_vec.z = 0.0;
}
Vec::new()
}
pub fn speed_factor(&self, level: &impl LevelQuery) -> f64 {
let time_pct = self.time_pct(level);
let speed_factor = ((100.0 - time_pct) * level.skill().begin_speed_factor()
+ time_pct * level.skill().end_speed_factor())
/ 100.0;
speed_factor
}
pub fn score(&self) -> i32 {
self.points_counter.score()
}
pub fn set_best_score(&mut self, best_score: i32) {
self.points_counter.set_best_score(best_score);
}
pub fn best_row(&self) -> isize {
self.points_counter.best_row()
}
pub fn best_dist(&self) -> f64 {
self.points_counter.best_dist()
}
pub fn is_boosting(&self) -> bool {
self.boost_state
}
fn time_pct(&self, level: &impl LevelQuery) -> f64 {
self.points_counter.time_pct(self.time_to_complete(level))
}
fn time_to_complete(&self, level: &impl LevelQuery) -> f64 {
let base = level.skill().time_to_complete();
base * self.time_progress_total() / 100.0
}
pub fn time_progress_done(&self, level: &impl LevelQuery) -> f64 {
let pct = self.time_pct(level);
if pct >= 100.0 {
self.time_progress_total()
} else {
pct * self.time_progress_total() / 100.0
}
}
pub fn time_progress_total(&self) -> f64 {
let extra_time_from_boost = EXTRA_TIME_ON_BOOST * (self.boost_count as f64);
let extra_time_from_overdrive = EXTRA_TIME_ON_OVERDRIVE * (self.overdrive_count as f64);
100.0 * (1.0 + extra_time_from_boost + extra_time_from_overdrive)
}
}
impl std::default::Default for Player {
fn default() -> Self {
Player {
initialized: false,
steer_factor: 1.0,
position_vec: Vector3D::default(),
speed_vec: Vector3D::default(),
time_tracker: TimeTracker::default(),
is_jumping: false,
boost_state: false,
cur_level_col: 0,
backing_data: None,
points_counter: PointsCounter::default(),
boost_count: 0,
overdrive_count: 0,
}
}
}