use std::collections::VecDeque; use bevy::prelude::*; use crate::{ climate::Warmth, food::Hunger, items::{Carrying, ItemAssets}, jobs::{CurrentJob, IdleTimer, WorkPriorities}, needs::{Rest, sleep_marker}, pathfinding::find_path, population::Starvation, sim::{SimPos, SimSystems, sim_pos}, skills::{Skills, WorkCarry}, social::Personality, threats::Health, world::{TILE_SIZE, TilePos, WorldMap}, worldgen::{WorldGenerated, spawn_tiles}, }; pub struct ColonistPlugin; impl Plugin for ColonistPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, create_colonist_assets) .add_systems(Update, spawn_colonists.run_if(on_message::)) .add_systems(FixedUpdate, follow_path.in_set(SimSystems::Move)); } } const TILES_PER_SECOND: f32 = 4.0; pub const NAMES: [&str; 26] = [ "Ada", "Bram", "Cleo", "Dana", "Emil", "Fenna", "Gijs", "Hanna", "Ivo", "Juul", "Kees", "Lotte", "Milan", "Noor", "Otto", "Pien", "Quinn", "Roos", "Sem", "Tess", "Ugo", "Vera", "Wout", "Xem", "Yara", "Zeno", ]; pub fn colonist_name(index: usize) -> String { let base = NAMES[index % NAMES.len()]; match index / NAMES.len() { 0 => base.to_string(), n => format!("{base} {}", n + 1), } } pub fn free_name_index<'a>(taken: impl Iterator + Clone) -> usize { (0..) .find(|&i| !taken.clone().any(|name| name == colonist_name(i))) .unwrap_or(0) } const COLORS: [Color; 8] = [ Color::srgb(0.9, 0.3, 0.3), Color::srgb(0.3, 0.5, 0.95), Color::srgb(0.95, 0.75, 0.3), Color::srgb(0.6, 0.35, 0.85), Color::srgb(0.3, 0.8, 0.75), Color::srgb(0.95, 0.5, 0.75), Color::srgb(0.55, 0.8, 0.3), Color::srgb(0.95, 0.95, 0.95), ]; const STARTING_COLONISTS: usize = 3; #[derive(Resource, Default)] pub struct ColonistAssets { mesh: Handle, } fn create_colonist_assets(mut commands: Commands, mut meshes: ResMut>) { commands.insert_resource(ColonistAssets { mesh: meshes.add(Circle::new(TILE_SIZE * 0.35)), }); } pub fn spawn_colonist( commands: &mut Commands, assets: &ColonistAssets, item_assets: &ItemAssets, materials: &mut Assets, index: usize, tile: UVec2, ) -> Entity { let name = colonist_name(index); commands .spawn(( Name::new(name.clone()), Colonist { name }, TilePos(tile), Mesh2d(assets.mesh.clone()), MeshMaterial2d(materials.add(COLORS[index % COLORS.len()])), sim_pos(WorldMap::tile_center(tile)), Transform::from_translation(WorldMap::tile_center(tile).extend(1.0)), children![item_assets.carried_visual(), sleep_marker()], )) .id() } #[derive(Component, Debug)] #[require( Transform, SimPos, TilePos, Path, CurrentJob, IdleTimer, Carrying, Rest, Hunger, Starvation, WorkPriorities, Warmth, Skills, WorkCarry, Health, Personality )] pub struct Colonist { pub name: String, } #[derive(Component, Default, Debug)] pub struct Path(pub VecDeque); fn spawn_colonists( mut commands: Commands, map: Res, existing: Query>, assets: Res, item_assets: Res, mut materials: ResMut>, ) { for entity in &existing { commands.entity(entity).despawn(); } let tiles = spawn_tiles(&map, STARTING_COLONISTS); for (index, tile) in tiles.into_iter().enumerate() { spawn_colonist( &mut commands, &assets, &item_assets, &mut materials, index, tile, ); } } pub fn repath( map: &WorldMap, tile: UVec2, current: &VecDeque, goal: UVec2, ) -> Option> { match current.front() { Some(&next) => { let mut path: VecDeque<_> = find_path(map, next, goal)?.into(); path.push_front(next); Some(path) } None => find_path(map, tile, goal).map(VecDeque::from), } } #[derive(Component, Debug, Clone, Copy, PartialEq)] pub struct MoveSpeed(pub f32); pub fn follow_path( time: Res