use super::scene_helper::*;
use babalcore::*;
use gdnative::api::GridMap;
use gdnative::prelude::*;
#[derive(NativeClass)]
#[inherit(Spatial)]
pub struct Tunnel {
data: Option<Data>,
template: Option<Ref<PackedScene, ThreadLocal>>,
grid_scale: Vector3,
level_row: isize,
level_col: isize,
player_position: Vector3,
player_best_row: isize,
now_sec: f64,
}
struct Data {
level: Level,
rail_resource_name: String,
}
const LENGTH_MARGIN: usize = 100;
const SLAB_SIZE: f32 = 1.0;
const RAIL_OFFSET: isize = 2;
const RAIL_FAR_VIEW: isize = 30;
#[methods]
impl Tunnel {
fn new(_owner: &Spatial) -> Self {
Tunnel {
data: None,
template: None,
grid_scale: Vector3::new(1.0, 1.0, 1.0),
level_row: 0,
level_col: 0,
player_position: Vector3::default(),
player_best_row: 0,
now_sec: 0.0,
}
}
#[export]
fn _ready(&self, _owner: &Spatial) {
godot_print!("Tunnel is ready.");
}
#[export]
fn _process(&mut self, owner: &Spatial, delta: f64) {
self.now_sec += delta;
self.sync(owner);
}
#[export]
fn setup(
&mut self,
owner: &Spatial,
rail_resource_name: GodotString,
width: usize,
length: usize,
) {
godot_print!("Tunnel setup {}x{}.", width, length);
let mut level = Level::new(0, width, Skill::Easy);
Self::generate_missing(&mut level, 0);
let data = Data {
level,
rail_resource_name: rail_resource_name.to_string(),
};
self.template = load_scene(data.rail_resource_name.as_str());
match &self.template {
Some(_) => godot_print!(
"Loaded template rail scene \"{}\".",
data.rail_resource_name
),
None => godot_print!(
"Unable to load rail scene \"{}\", please check name.",
data.rail_resource_name
),
};
self.data = Some(data);
self.remove_all_children(owner);
self.add_all_rails(owner);
match self.get_grid_scale(owner) {
Ok(scale) => self.grid_scale = scale,
Err(e) => godot_print!("unable to get grid scale: {}", e),
}
self.sync(owner);
}
fn generate_missing(level: &mut Level, row: isize) {
while level.last() < row + (LENGTH_MARGIN as isize) {
level.generate(LENGTH_MARGIN);
}
}
#[export]
fn remove_last_child(&mut self, owner: &Spatial) -> bool {
let num_children = owner.get_child_count();
if num_children <= 0 {
return false;
}
let last_child = owner.get_child(num_children - 1);
if let Some(node) = last_child {
owner.remove_child(node);
unsafe {
node.assume_safe().queue_free();
}
}
return num_children == owner.get_child_count() + 1;
}
#[export]
fn remove_all_children(&mut self, owner: &Spatial) -> usize {
let mut removed: usize = 0;
while self.remove_last_child(owner) {
removed += 1;
}
removed
}
fn prepare_rail_grid(&self, grid: &Spatial, n: usize) {
let w: f32 = SLAB_SIZE * std::f32::consts::PI * 2f32 / n as f32;
let mut translation = grid.translation();
translation.x = -w / 2f32;
grid.set_translation(translation);
let mut scale = grid.scale();
scale.x = w;
grid.set_scale(scale);
}
fn prepare_rail(&self, rail: &Spatial, i: usize, n: usize) {
let key_str = format!("rail_{}", i);
rail.set_name(&key_str);
let angle: f64 = i as f64 * 360f64 / n as f64;
rail.rotate_z(angle * std::f64::consts::PI / 180f64);
let num_children = rail.get_child_count();
if num_children != 1 {
godot_print!("Unexpected number of children for rail: {}.", num_children);
return;
}
let child = rail.get_child(0);
if let Some(grid) = child {
let grid = unsafe { grid.assume_safe() };
if let Some(grid) = grid.cast::<Spatial>() {
self.prepare_rail_grid(&grid, n);
}
}
}
#[export]
fn add_one_rail(&mut self, owner: &Spatial, i: usize, n: usize) -> bool {
match &self.template {
Some(template) => match instanciate_scene::<Spatial>(template) {
Ok(spatial) => {
self.prepare_rail(&spatial, i, n);
owner.add_child(spatial.into_shared(), false);
true
}
Err(err) => {
godot_print!("Could not instanciate rail scene: \"{:?}\".", err);
false
}
},
None => false,
}
}
#[export]
fn add_all_rails(&mut self, owner: &Spatial) -> usize {
match &self.data {
Some(data) => {
let mut added: usize = 0;
let width = data.level.width();
if width == 0 {
return 0;
}
for i in 0..width {
if !self.add_one_rail(owner, i, width) {
break;
}
added += 1;
}
added
}
None => 0,
}
}
fn get_grid_scale(&self, owner: &Spatial) -> Result<Vector3, String> {
let num_children = owner.get_child_count() as usize;
if num_children == 0 {
return Err(format!("Please setup tunnel"));
}
match owner.get_child(0) {
Some(rail) => {
let rail = unsafe { rail.assume_safe() };
match rail.cast::<Spatial>() {
Some(rail) => match rail.get_child(0) {
Some(grid) => {
let grid = unsafe { grid.assume_safe() };
match grid.cast::<GridMap>() {
Some(grid) => return Ok(grid.scale()),
None => Err(format!("Unable to cast child of rail to GridMap.")),
}
}
None => Err(format!("rail has no child")),
},
None => Err(format!("rail is not spatial")),
}
}
None => Err(format!("tunnel has no child")),
}
}
fn sync_rail_grid(&mut self, rail: &GridMap, col: isize) -> bool {
rail.clear();
match &mut self.data {
Some(data) => {
data.level.set_now_sec(self.now_sec);
let start = -RAIL_OFFSET;
let end = std::cmp::min(
start + data.level.len() as isize,
RAIL_OFFSET + RAIL_FAR_VIEW,
);
for row in start..end {
let item = if row == 0 && self.level_col == col && self.player_position.y == 0.0
{
SlabKind::HighLight.as_item()
} else if self.player_best_row > self.level_row
&& row == self.player_best_row - self.level_row
&& data.level.item(col, row + self.level_row) >= 0
{
SlabKind::HighLight.as_item()
} else {
data.level.item(col, row + self.level_row)
};
if item >= 0 {
let orientation = 0;
rail.set_cell_item(0, 0, (RAIL_OFFSET - 2 - row) as i64, item, orientation);
}
}
true
}
None => false,
}
}
#[export]
fn sync(&mut self, owner: &Spatial) -> bool {
match &mut self.data {
Some(data) => {
Self::generate_missing(&mut data.level, self.level_row);
let num_children = owner.get_child_count() as usize;
let width = data.level.width();
if num_children == 0 {
godot_print!("Please setup tunnel");
return false;
}
if num_children != (data.level.width()) {
godot_print!("Wrong number of rails: {} != {}.", num_children, width);
return false;
}
for i in 0..(num_children as isize) {
let child = owner.get_child(i as i64);
if let Some(rail) = child {
let rail = unsafe { rail.assume_safe() };
match rail.cast::<Spatial>() {
Some(rail) => {
let child = rail.get_child(0);
if let Some(grid) = child {
let grid = unsafe { grid.assume_safe() };
match grid.cast::<GridMap>() {
Some(grid) => {
self.sync_rail_grid(&grid, i);
}
None => {
godot_print!(
"Unable to cast child of rail to GridMap.",
);
}
}
}
}
None => {
godot_print!("Unable to cast child of rail to Spatial.",);
}
}
}
}
true
}
None => false,
}
}
#[export]
fn update_player_position(&mut self, owner: &Spatial, pos: Vector3, best_row: isize) {
let mut rotation = owner.rotation();
rotation.z = pos.x;
owner.set_rotation(rotation);
let mut translation = owner.translation();
translation.z = pos.z * self.grid_scale.z;
owner.set_translation(translation);
self.player_position = pos;
self.player_best_row = best_row;
}
#[export]
fn set_level_row(&mut self, _owner: &Spatial, level_row: isize) {
self.level_row = level_row;
}
#[export]
fn set_level_col(&mut self, _owner: &Spatial, level_col: isize) {
self.level_col = level_col;
}
#[export]
pub fn width(&self, _owner: &Spatial) -> usize {
match &self.data {
Some(data) => data.level.width(),
None => 0,
}
}
#[export]
pub fn len(&self, _owner: &Spatial) -> usize {
match &self.data {
Some(data) => data.level.len(),
None => 0,
}
}
#[export]
pub fn first(&self, _owner: &Spatial) -> isize {
match &self.data {
Some(data) => data.level.first(),
None => 0,
}
}
#[export]
pub fn last(&self, _owner: &Spatial) -> isize {
match &self.data {
Some(data) => data.level.last(),
None => 0,
}
}
#[export]
pub fn item(&self, _owner: &Spatial, col: isize, row: isize) -> i64 {
match &self.data {
Some(data) => data.level.item(col, row),
None => 0,
}
}
#[export]
pub fn find_start_spot(
&self,
_owner: &Spatial,
col: isize,
row: isize,
) -> Option<(isize, isize)> {
match &self.data {
Some(data) => data.level.find_start_spot(col, row),
None => None,
}
}
}