Skip to content
Snippets Groups Projects
towns.rs 6.47 KiB
Newer Older
use bevy::math::{vec2, Vec2};
Louis's avatar
Louis committed
use bevy::prelude::{Bundle, Component, Handle, Resource, TransformBundle};
use bevy::utils::HashMap;
use ldtk_rust::{EntityInstance, Level};
use serde::{Deserialize, Serialize};
use serde_json::Value;

Louis's avatar
Louis committed
use crate::world::trading::TradeManifestTickState;
use crate::world::utils::{grid_to_px, px_to_grid};
Louis's avatar
Louis committed
use crate::world::{HungerState, TradeManifest, TradingState, WorldLinked};

pub type Town = String;

/// Stores the ID of the most recent town that has been inhabited,
/// as well as the current travel state
Louis's avatar
Louis committed
#[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,
		}
	}
}

Louis's avatar
Louis committed
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct TravelTarget(pub String);

Louis's avatar
Louis committed
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct TownName(pub String);

Louis's avatar
Louis committed
#[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();
Louis's avatar
Louis committed
			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,
							};
						}
					}
Louis's avatar
Louis committed
					"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(),
					},
				);

Louis's avatar
Louis committed
				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(),
						},
					);
				}
Louis's avatar
Louis committed

#[derive(Bundle)]
pub struct TownBundle {
	pub town_name: TownName,
	pub trade_state: TradingState,
	pub hunger_state: HungerState,
	pub manifest: Handle<TradeManifest>,
	pub manifest_state: TradeManifestTickState,
Louis's avatar
Louis committed
	pub world_linked: WorldLinked,
	pub location: TransformBundle,
Louis's avatar
Louis committed
}