use bevy::math::{vec2, Vec2}; use bevy::prelude::{Bundle, Component, Resource}; use bevy::utils::HashMap; use ldtk_rust::{EntityInstance, Level}; use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::world::utils::{grid_to_px, px_to_grid}; pub type Town = String; /// Stores the ID of the most recent town that has been inhabited, /// as well as the current travel state #[derive(Component, Serialize, Deserialize, Debug, Clone)] pub enum CurrentResidence { TravellingFrom(String), RestingAt(String), } impl CurrentResidence { pub fn is_travelling(&self) -> bool { match self { CurrentResidence::TravellingFrom(..) => true, _ => false, } } pub fn get_location(&self) -> &String { match self { CurrentResidence::TravellingFrom(place) | CurrentResidence::RestingAt(place) => place, } } } #[derive(Component, Serialize, Deserialize, Debug, Clone)] pub struct TravelTarget(pub String); #[derive(Component, Serialize, Deserialize, Debug, Clone)] pub struct TravelPath { pub path: Vec<Vec2>, last_index: usize, next_index: usize, } pub enum PathingResult { Travelling, NextNode, PathComplete, TravelTo(Vec2), } fn calculate_distance(points: &mut impl Iterator<Item = Vec2>) -> f32 { let mut total = 0.0; let mut previous = points.next().unwrap(); while let Some(point) = points.next() { total += previous.distance(point); previous = point; } total } impl TravelPath { pub fn increment_indexes(&mut self) { self.last_index += 1; self.next_index += 1; } pub fn get_step(&self, current: Vec2, speed: f32) -> Vec2 { if let Some(next) = self.path.get(self.next_index) { let step = current - *next; step.normalize_or_zero() * speed } else { Vec2::ZERO } } pub fn get_route_time(&self, speed: f32) -> f32 { if let (Some(last), Some(next)) = ( self.path.get(self.last_index), self.path.get(self.next_index), ) { next.distance(*last) / speed } else { 0.0 } } pub fn check_position(&mut self, current: Vec2) -> PathingResult { if let Some(next) = self.path.get(self.next_index) { if current.distance(*next).abs() < 0.05 { PathingResult::NextNode } else if current.distance(*next).abs() < 0.15 { PathingResult::TravelTo(*next) } else { PathingResult::Travelling } } else { PathingResult::PathComplete } } pub fn ui_distance_remaining(&self) -> f32 { calculate_distance(&mut self.path.iter().skip(self.last_index).copied()) } pub fn distance_to_next_node(&self, other: Vec2) -> f32 { if let Some(next) = self.path.get(self.next_index) { other.distance(*next).abs() } else { 0.0 } } pub fn get_current_edge_distance(&self) -> f32 { if let (Some(last), Some(next)) = ( self.path.get(self.last_index), self.path.get(self.next_index), ) { last.distance(*next).abs() } else { 0.0 } } } #[derive(Serialize, Deserialize, Clone, Copy, Eq, Ord, PartialOrd, PartialEq, Debug)] pub struct RouteNode { #[serde(alias = "cx")] pub tile_x: usize, #[serde(alias = "cy")] pub tile_y: usize, } #[derive(Clone, Eq, Ord, PartialOrd, PartialEq, Debug)] pub struct Route { pub nodes: Vec<RouteNode>, } impl Route { pub fn create_travel_path_for(&self, level: &Level) -> TravelPath { TravelPath { path: self .nodes .iter() .map(|node| { vec2( grid_to_px(node.tile_x), level.px_hei as f32 - grid_to_px(node.tile_y), ) }) .collect(), last_index: 0, next_index: 1, } } pub fn calculate_distance(&self, level: &Level) -> f32 { calculate_distance(&mut self.nodes.iter().map(|node| { vec2( grid_to_px(node.tile_x), level.px_hei as f32 - grid_to_px(node.tile_y), ) })) } } #[derive(Clone, Eq, PartialEq, Debug)] pub struct Destinations { pub source: Town, pub routes: HashMap<String, Route>, } impl Destinations { pub fn create_route_bundle_for(&self, target: String, level: &Level) -> Option<impl Bundle> { self.routes.get(&target).map(|route| { ( route.create_travel_path_for(level), CurrentResidence::TravellingFrom(self.source.clone()), TravelTarget(target.clone()), ) }) } } #[derive(Clone, Eq, PartialEq, Debug, Default, Resource)] pub struct TownPaths { pub routes: HashMap<Town, Destinations>, } impl From<Vec<&EntityInstance>> for TownPaths { fn from(value: Vec<&EntityInstance>) -> Self { let mut routes = HashMap::with_capacity(value.len() * 2); 'process_entity: for entity in value { if entity.identifier != String::from("TradeRoute") { continue; } let mut from_place = None; let mut to_place = None; let mut nodes: Vec<RouteNode> = Vec::new(); let mut is_one_way = false; for field in &entity.field_instances { match &*field.identifier { "from" => match &field.value { Some(Value::String(string)) => from_place = Some((*string).clone()), _ => continue 'process_entity, }, "to" => match &field.value { Some(Value::String(string)) => to_place = Some((*string).clone()), _ => continue 'process_entity, }, "nodes" => { if let Some(field_value) = &field.value { nodes = match serde_json::from_value(field_value.clone()) { Ok(val) => val, _ => continue 'process_entity, }; } } "one_way" => { if let Some(Value::Bool(val)) = &field.value { is_one_way = *val; } } _ => {} } } if let (Some(from), Some(to)) = (from_place, to_place) { let mut route_entry = routes.entry(from.clone()).or_insert_with(|| Destinations { source: from.clone(), routes: Default::default(), }); route_entry.routes.insert( to.clone(), Route { nodes: nodes.clone(), }, ); if !is_one_way { let mut route_entry = routes.entry(to.clone()).or_insert_with(|| Destinations { source: to.clone(), routes: Default::default(), }); route_entry.routes.insert( from.clone(), Route { nodes: nodes.iter().rev().cloned().collect(), }, ); } } } TownPaths { routes } } }