diff --git a/config.toml b/config.toml index 886d39b..960c534 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,6 @@ initial_chunk_radius = 7 [spawn_counts] -dorfs = 500 +dorfs = 5 pigs = 5 -rabbits = 50 \ No newline at end of file +rabbits = 5 \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 7964708..9c5199d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,8 @@ use bevy::prelude::Resource; use serde::Deserialize; +use std::collections::HashMap; use std::fs; +use std::sync::OnceLock; #[derive(Debug, Deserialize, Clone, Resource)] pub struct GameConfig { @@ -21,3 +23,51 @@ impl GameConfig { toml::from_str(&config_str).expect("Failed to parse config.toml") } } + +static TILE_REGISTRY: OnceLock = OnceLock::new(); + +#[derive(Debug, Deserialize, Clone)] +pub struct TileRegistry { + pub floor_tiles: HashMap, + pub fixture_tiles: HashMap, +} + +#[derive(Debug, Deserialize, Clone, Copy)] +pub struct FloorTileDef { + pub id: u8, + pub can_stand_in: bool, + pub can_stand_on: bool, + pub transparent: bool, + pub astar_weight: u8, +} + +#[derive(Debug, Deserialize, Clone, Copy)] +pub struct FixtureTileDef { + pub id: u32, + pub solid: bool, +} + +impl TileRegistry { + pub fn load() -> Self { + let config_str = fs::read_to_string("tiles.toml").expect("Failed to find tiles.toml"); + toml::from_str(&config_str).expect("Failed to parse tiles.toml") + } + + pub fn global() -> &'static Self { + TILE_REGISTRY.get_or_init(|| Self::load()) + } + + pub fn floor(&self, name: &str) -> FloorTileDef { + *self + .floor_tiles + .get(name) + .unwrap_or_else(|| panic!("Unknown floor tile: {}", name)) + } + + pub fn fixture(&self, name: &str) -> FixtureTileDef { + *self + .fixture_tiles + .get(name) + .unwrap_or_else(|| panic!("Unknown fixture tile: {}", name)) + } +} diff --git a/src/entities/livestock/pig.rs b/src/entities/livestock/pig.rs index cfff222..d5304ab 100644 --- a/src/entities/livestock/pig.rs +++ b/src/entities/livestock/pig.rs @@ -3,6 +3,7 @@ use crate::constants::TILE_SIZE; use crate::constants::*; use crate::entities::item::{spawn_prefab, MiscPrefab}; use crate::entities::shared_components::Ambulatory; +use crate::game::SpawnDelay; use crate::world::tiles::tilemap; use crate::world::VisibleGameEntity; use bevy::prelude::*; @@ -36,7 +37,7 @@ impl Pig { }, transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)), visibility: Visibility::Hidden, - drop_timer: PigDropTimer(Timer::from_seconds(5.0, TimerMode::Repeating)), // NEW + drop_timer: PigDropTimer(Timer::from_seconds(5.0, TimerMode::Repeating)), } } } @@ -49,18 +50,30 @@ pub fn spawn_pigs( asset_server: Res, mut rng_q: Query<&mut WyRand, With>, config: Res, + mut delay: ResMut, + mut has_spawned: Local, ) { + if *has_spawned { + return; + } + + if delay.0 < 60 { + delay.0 += 1; + return; + } + + *has_spawned = true; + if let Ok(mut rng) = rng_q.single_mut() { for _ in 0..config.spawn_counts.pigs { + let raw_x = rng.random_range(-32.0f32..32.0f32); + let raw_y = rng.random_range(-32.0f32..32.0f32); + let grid_x = (raw_x / TILE_SIZE).round() * TILE_SIZE; + let grid_y = (raw_y / TILE_SIZE).round() * TILE_SIZE; + let grid_z = 35.0 * TILE_SIZE; + let pig = commands - .spawn(Pig::new( - &asset_server, - Vec3::new( - rng.random_range(-32.0f32..32.0f32).round(), - rng.random_range(-32.0f32..32.0f32).round(), - 35.0, - ) * TILE_SIZE, - )) + .spawn(Pig::new(&asset_server, Vec3::new(grid_x, grid_y, grid_z))) .id(); commands.entity(pig).insert(VisibleGameEntity); } diff --git a/src/entities/livestock/rabbit.rs b/src/entities/livestock/rabbit.rs index 345fb87..e044043 100644 --- a/src/entities/livestock/rabbit.rs +++ b/src/entities/livestock/rabbit.rs @@ -2,6 +2,7 @@ use crate::config::GameConfig; use crate::constants::TILE_SIZE; use crate::constants::*; use crate::entities::shared_components::Ambulatory; +use crate::game::SpawnDelay; use crate::world::VisibleGameEntity; use bevy::prelude::*; use bevy_rand::prelude::*; @@ -42,17 +43,32 @@ pub fn spawn_rabbits( asset_server: Res, mut rng_q: Query<&mut WyRand, With>, config: Res, + mut delay: ResMut, + mut has_spawned: Local, ) { + if *has_spawned { + return; + } + + if delay.0 < 60 { + delay.0 += 1; + return; + } + + *has_spawned = true; + if let Ok(mut rng) = rng_q.single_mut() { for _ in 0..config.spawn_counts.rabbits { + let raw_x = rng.random_range(-32.0f32..32.0f32); + let raw_y = rng.random_range(-32.0f32..32.0f32); + let grid_x = (raw_x / TILE_SIZE).round() * TILE_SIZE; + let grid_y = (raw_y / TILE_SIZE).round() * TILE_SIZE; + let grid_z = 35.0 * TILE_SIZE; + let rab = commands .spawn(Rabbit::new( &asset_server, - Vec3::new( - rng.random_range(-32.0f32..32.0f32).round(), - rng.random_range(-32.0f32..32.0f32).round(), - 35.0, - ) * TILE_SIZE, + Vec3::new(grid_x, grid_y, grid_z), )) .id(); commands.entity(rab).insert(VisibleGameEntity); diff --git a/src/entities/sentient/dorf.rs b/src/entities/sentient/dorf.rs index b8fdf94..02210c0 100644 --- a/src/entities/sentient/dorf.rs +++ b/src/entities/sentient/dorf.rs @@ -2,6 +2,7 @@ use crate::config::GameConfig; use crate::constants::TILE_SIZE; use crate::constants::*; use crate::entities::shared_components::Ambulatory; +use crate::game::SpawnDelay; use crate::world::VisibleGameEntity; use bevy::prelude::*; use bevy_rand::prelude::*; @@ -42,18 +43,30 @@ pub fn spawn_dorfs( asset_server: Res, mut rng_q: Query<&mut WyRand, With>, config: Res, + mut delay: ResMut, + mut has_spawned: Local, ) { + if *has_spawned { + return; + } + + if delay.0 < 60 { + delay.0 += 1; + return; + } + + *has_spawned = true; + if let Ok(mut rng) = rng_q.single_mut() { for _ in 0..config.spawn_counts.dorfs { + let raw_x = rng.random_range(-8.0f32..8.0f32); + let raw_y = rng.random_range(-8.0f32..8.0f32); + let grid_x = (raw_x / TILE_SIZE).round() * TILE_SIZE; + let grid_y = (raw_y / TILE_SIZE).round() * TILE_SIZE; + let grid_z = 35.0 * TILE_SIZE; + let cit = commands - .spawn(Dorf::new( - &asset_server, - Vec3::new( - rng.random_range(-8.0f32..8.0f32).round(), - rng.random_range(-8.0f32..8.0f32).round(), - 35.0, - ) * TILE_SIZE, - )) + .spawn(Dorf::new(&asset_server, Vec3::new(grid_x, grid_y, grid_z))) .id(); commands.entity(cit).insert(VisibleGameEntity); } diff --git a/src/entities/shared_systems/pathfinding.rs b/src/entities/shared_systems/pathfinding.rs index 66d6836..6a2ff15 100644 --- a/src/entities/shared_systems/pathfinding.rs +++ b/src/entities/shared_systems/pathfinding.rs @@ -507,7 +507,11 @@ pub fn movement(mut query: Query<(&mut Ambulatory, &mut Transform)>, tilemap: Re } if ambulatory.walk_speed > 0. { - if ambulatory.step_recovery <= ambulatory.walk_speed as u32 { + let tile_weight = get_tile_weight(&tilemap, current_pos.as_ivec3()); + let speed_multiplier = (tile_weight as f32) / 50.0; + let threshold = (ambulatory.walk_speed * speed_multiplier) as u32; + + if ambulatory.step_recovery <= threshold { ambulatory.step_recovery += 1; return; } else { @@ -553,18 +557,37 @@ fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool { tilemap.is_standable(pos) } -fn calculate_movement_cost(move_dir: IVec3) -> i32 { - match ( +/// Get the A* weight for a tile position. Lower is better (faster to traverse). +/// Returns 100 (default) if tile not found. +#[inline] +fn get_tile_weight(tilemap: &TileMap, pos: IVec3) -> u8 { + tilemap.get_astar_weight(pos) +} + +/// Calculate movement cost including tile weight. +/// Base costs: cardinal=10, diagonal=14, vertical~50. +/// Tile weight adds: (weight - 50) / 5 to make heavier tiles more costly. +fn calculate_movement_cost(move_dir: IVec3, tile_weight: u8) -> i32 { + let base_cost = match ( move_dir.x.abs() / ITILE_SIZE, move_dir.y.abs() / ITILE_SIZE, move_dir.z.abs() / ITILE_SIZE, ) { - (1, 0, 0) | (0, 1, 0) => 10, - (1, 1, 0) => 14, - (1, 0, 1) | (0, 1, 1) => 42, - (1, 1, 1) => 56, + (1, 0, 0) | (0, 1, 0) => 10, // Cardinal + (1, 1, 0) => 14, // Diagonal + (1, 0, 1) | (0, 1, 1) => 42, // Vertical + cardinal + (1, 1, 1) => 56, // Vertical + diagonal _ => 0, + }; + + if base_cost == 0 { + return 0; } + + // Weight cost: normalize so rock(50) adds 0, grass(100) adds 10, bedrock(150) adds 20 + let weight_cost = (tile_weight as i32).saturating_sub(50) / 5; + + base_cost + weight_cost } fn octile_distance_3d(a: IVec3, b: IVec3) -> i32 { @@ -677,7 +700,8 @@ fn calculate_path_with_scratchpad( continue; } - let movement_cost = calculate_movement_cost(move_dir); + let tile_weight = get_tile_weight(tilemap, neighbor_pos); + let movement_cost = calculate_movement_cost(move_dir, tile_weight); if movement_cost == 0 { continue; } @@ -770,7 +794,8 @@ pub fn calculate_provisional_path( continue; } - let movement_cost = calculate_movement_cost(move_dir); + let tile_weight = get_tile_weight(tilemap, neighbor_pos); + let movement_cost = calculate_movement_cost(move_dir, tile_weight); if movement_cost == 0 { continue; } diff --git a/src/game.rs b/src/game.rs index 604b02d..dc4a612 100644 --- a/src/game.rs +++ b/src/game.rs @@ -2,3 +2,6 @@ use bevy::prelude::*; #[derive(Resource)] pub struct ZIndex(pub f32); + +#[derive(Resource, Default)] +pub struct SpawnDelay(pub u32); diff --git a/src/main.rs b/src/main.rs index aba9137..53b5879 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use crate::entities::{ item::{initialize_item_rotation_state, item_tile_management_system, ItemRotationTimer}, livestock::pig::pig_drop_system, }; +use crate::game::SpawnDelay; mod camera; mod config; @@ -25,6 +26,7 @@ fn main() { }) .insert_resource(camera::CameraMoved(false)) .insert_resource(world::tiles::QuiltCache::default()) + .init_resource::() .add_systems(PreStartup, world::textures::initialize_textures) .add_plugins( DefaultPlugins @@ -45,13 +47,7 @@ fn main() { .add_plugins(entities::pathfinding::PathfindingPlugin) .add_systems( Startup, - ( - camera::spawn_panning_camera, - cursor::setup_cursor, - entities::sentient::dorf::spawn_dorfs, - entities::livestock::pig::spawn_pigs, - entities::livestock::rabbit::spawn_rabbits, - ), + (camera::spawn_panning_camera, cursor::setup_cursor), ) .add_systems( Update, @@ -60,6 +56,9 @@ fn main() { camera::camera_z_movement, camera::camera_movement, cursor::move_cursor, + entities::sentient::dorf::spawn_dorfs, + entities::livestock::pig::spawn_pigs, + entities::livestock::rabbit::spawn_rabbits, ), ) .add_systems(Update, pig_drop_system) diff --git a/src/world/chunks/management.rs b/src/world/chunks/management.rs index 29917d7..e8a517b 100644 --- a/src/world/chunks/management.rs +++ b/src/world/chunks/management.rs @@ -13,8 +13,8 @@ pub const CHUNK_SIZE_TILE: i32 = CHUNK_SIZE * crate::constants::ITILE_SIZE; #[inline] pub fn world_to_chunk(world_pos: IVec3) -> IVec2 { IVec2::new( - world_pos.x.div_euclid(CHUNK_SIZE), - world_pos.y.div_euclid(CHUNK_SIZE), + world_pos.x.div_euclid(CHUNK_SIZE_TILE), + world_pos.y.div_euclid(CHUNK_SIZE_TILE), ) } diff --git a/src/world/generation/terrain.rs b/src/world/generation/terrain.rs index b5b6951..096ad69 100644 --- a/src/world/generation/terrain.rs +++ b/src/world/generation/terrain.rs @@ -1,11 +1,11 @@ use bevy::prelude::*; use bevy::tasks::AsyncComputeTaskPool; -use bevy_platform::collections::HashMap; use bevy_platform::time::Instant; use noise::{NoiseFn, Perlin}; use std::sync::{Arc, Mutex}; use crate::{ + config::TileRegistry, constants::{SEED, TILE_SIZE}, world::{ tiles::{ChunkData, FloorTileData, TileMap, TerrainSpriteState, CurrentWorldSpriteState}, @@ -70,6 +70,7 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob { let cave_noise = Perlin::new(SEED); let start_x = chunk_pos.x * CHUNK_SIZE; let start_y = chunk_pos.y * CHUNK_SIZE; + let registry = TileRegistry::global(); let mut chunk_data = ChunkData::new(chunk_pos); let mut tile_updates: Vec<(IVec3, FloorTileData)> = Vec::new(); @@ -105,51 +106,147 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob { z as f64 * 0.05, ]); if cave_value < -0.75 { + let tile = registry.floor("air"); tile_spawns.push((position, FloorTilePrefab::air(position))); tile_updates.push(( pos_ivec, - FloorTileData::new(0, true, false, true, 0, [0; 8]), + FloorTileData::new( + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.transparent, + tile.astar_weight, + [0; 8], + ), )); - chunk_data.set_floor_tile(local_x, local_y, local_z, 0, true, false); + chunk_data.set_floor_tile( + local_x, + local_y, + local_z, + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.astar_weight, + ); } else if cave_value < 0.8 { + let tile = registry.floor("rock"); tile_spawns.push((position, FloorTilePrefab::rock(position))); tile_updates.push(( pos_ivec, - FloorTileData::new(2, false, true, false, 50, [0; 8]), + FloorTileData::new( + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.transparent, + tile.astar_weight, + [0; 8], + ), )); - chunk_data.set_floor_tile(local_x, local_y, local_z, 2, false, true); + chunk_data.set_floor_tile( + local_x, + local_y, + local_z, + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.astar_weight, + ); } else { + let tile = registry.floor("dirt"); tile_spawns.push((position, FloorTilePrefab::dirt(position))); tile_updates.push(( pos_ivec, - FloorTileData::new(1, false, true, false, 85, [0; 8]), + FloorTileData::new( + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.transparent, + tile.astar_weight, + [0; 8], + ), )); - chunk_data.set_floor_tile(local_x, local_y, local_z, 1, false, true); + chunk_data.set_floor_tile( + local_x, + local_y, + local_z, + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.astar_weight, + ); } } else if noise_position.z > position.z { if surface_height <= position.z + TILE_SIZE { + let tile = registry.floor("grass"); tile_spawns.push((position, FloorTilePrefab::grass(position))); tile_updates.push(( pos_ivec, - FloorTileData::new(1, false, true, false, 100, [0; 8]), + FloorTileData::new( + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.transparent, + tile.astar_weight, + [0; 8], + ), )); - chunk_data.set_floor_tile(local_x, local_y, local_z, 1, false, true); + chunk_data.set_floor_tile( + local_x, + local_y, + local_z, + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.astar_weight, + ); surface_positions.push((position, "grass".to_string())); } else { + let tile = registry.floor("dirt"); tile_spawns.push((position, FloorTilePrefab::dirt(position))); tile_updates.push(( pos_ivec, - FloorTileData::new(1, false, true, false, 85, [0; 8]), + FloorTileData::new( + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.transparent, + tile.astar_weight, + [0; 8], + ), )); - chunk_data.set_floor_tile(local_x, local_y, local_z, 1, false, true); + chunk_data.set_floor_tile( + local_x, + local_y, + local_z, + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.astar_weight, + ); } } else { + let tile = registry.floor("air"); tile_spawns.push((position, FloorTilePrefab::air(position))); tile_updates.push(( pos_ivec, - FloorTileData::new(0, true, false, true, 0, [0; 8]), + FloorTileData::new( + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.transparent, + tile.astar_weight, + [0; 8], + ), )); - chunk_data.set_floor_tile(local_x, local_y, local_z, 0, true, false); + chunk_data.set_floor_tile( + local_x, + local_y, + local_z, + tile.id, + tile.can_stand_in, + tile.can_stand_on, + tile.astar_weight, + ); } } } diff --git a/src/world/tiles/chunk_data.rs b/src/world/tiles/chunk_data.rs index 9d75705..d8fc275 100644 --- a/src/world/tiles/chunk_data.rs +++ b/src/world/tiles/chunk_data.rs @@ -46,6 +46,10 @@ pub struct ChunkData { /// Tile IDs for rendering. pub tile_ids: Vec, + + /// A* pathfinding weights per tile. Lower = better path. + /// Weight 0 = impassable, weight 50 = fast (rock), weight 100 = normal (grass), weight 150 = slow (bedrock). + pub astar_weights: Vec, } impl ChunkData { @@ -57,6 +61,7 @@ impl ChunkData { stand_in_fixture: [0u32; BITSET_WORDS], stand_on_fixture: [0u32; BITSET_WORDS], tile_ids: vec![0u8; TOTAL_TILES], + astar_weights: vec![100u8; TOTAL_TILES], } } @@ -184,6 +189,7 @@ impl ChunkData { tile_id: u8, can_stand_in: bool, can_stand_on: bool, + astar_weight: u8, ) { let idx = Self::pos_to_index(local_x, local_y, z); let word = idx / 32; @@ -203,6 +209,20 @@ impl ChunkData { } self.tile_ids[idx] = tile_id; + self.astar_weights[idx] = astar_weight; + } + + /// Get A* weight at position. Returns 100 (default) if out of bounds. + #[inline] + pub fn get_astar_weight(&self, local_x: i32, local_y: i32, z: i32) -> u8 { + if local_x < 0 || local_x >= CHUNK_SIZE || local_y < 0 || local_y >= CHUNK_SIZE { + return 100; + } + if z < -(Z_BELOW as i32) || z > (Z_ABOVE as i32) { + return 100; + } + let idx = Self::pos_to_index(local_x, local_y, z); + self.astar_weights[idx] } /// Set only fixture standability bits (for forestry generation). @@ -253,6 +273,7 @@ impl ChunkData { self.stand_in_fixture.fill(0); self.stand_on_fixture.fill(0); self.tile_ids.fill(0); + self.astar_weights.fill(100); } } diff --git a/src/world/tiles/prefabs.rs b/src/world/tiles/prefabs.rs index 36fbd6e..5e3fa00 100644 --- a/src/world/tiles/prefabs.rs +++ b/src/world/tiles/prefabs.rs @@ -1,8 +1,11 @@ use bevy::prelude::*; -use crate::world::{ - tiles::{FixtureTile, FloorTile, TileState}, - FIXTURE_ID_OFFSET, +use crate::{ + config::TileRegistry, + world::{ + tiles::{FixtureTile, FloorTile, TileState}, + FIXTURE_ID_OFFSET, + }, }; #[derive(Bundle)] @@ -15,12 +18,15 @@ pub struct FloorTilePrefab { impl FloorTilePrefab { pub fn grass(position: Vec3) -> Self { + let def = TileRegistry::global().floor("grass"); FloorTilePrefab { transform: Transform::from_translation(position), tile: FloorTile { - id: 1, - astar_weight: 100, - ..Default::default() + id: def.id as u32, + opaque: !def.transparent, + walkable: def.can_stand_on, + astar_weight: def.astar_weight, + visible_range: [0; 8], }, tile_state: TileState { timer: Timer::from_seconds(1.0, TimerMode::Repeating), @@ -30,12 +36,15 @@ impl FloorTilePrefab { } pub fn dirt(position: Vec3) -> Self { + let def = TileRegistry::global().floor("dirt"); FloorTilePrefab { transform: Transform::from_translation(position), tile: FloorTile { - id: 2, - astar_weight: 85, - ..Default::default() + id: def.id as u32, + opaque: !def.transparent, + walkable: def.can_stand_on, + astar_weight: def.astar_weight, + visible_range: [0; 8], }, tile_state: TileState { timer: Timer::from_seconds(1.0, TimerMode::Repeating), @@ -45,12 +54,15 @@ impl FloorTilePrefab { } pub fn rock(position: Vec3) -> Self { + let def = TileRegistry::global().floor("rock"); FloorTilePrefab { transform: Transform::from_translation(position), tile: FloorTile { - id: 3, - astar_weight: 50, - ..Default::default() + id: def.id as u32, + opaque: !def.transparent, + walkable: def.can_stand_on, + astar_weight: def.astar_weight, + visible_range: [0; 8], }, tile_state: TileState { timer: Timer::from_seconds(1.0, TimerMode::Repeating), @@ -60,13 +72,15 @@ impl FloorTilePrefab { } pub fn air(position: Vec3) -> Self { + let def = TileRegistry::global().floor("air"); FloorTilePrefab { transform: Transform::from_translation(position), tile: FloorTile { - id: 0, - opaque: false, - walkable: false, - ..Default::default() + id: def.id as u32, + opaque: !def.transparent, + walkable: def.can_stand_on, + astar_weight: def.astar_weight, + visible_range: [0; 8], }, tile_state: TileState { timer: Timer::from_seconds(1.0, TimerMode::Repeating), @@ -76,12 +90,15 @@ impl FloorTilePrefab { } pub fn bedrock(position: Vec3) -> Self { + let def = TileRegistry::global().floor("bedrock"); FloorTilePrefab { transform: Transform::from_translation(position), tile: FloorTile { - id: 4, - astar_weight: 150, - ..Default::default() + id: def.id as u32, + opaque: !def.transparent, + walkable: def.can_stand_on, + astar_weight: def.astar_weight, + visible_range: [0; 8], }, tile_state: TileState { timer: Timer::from_seconds(1.0, TimerMode::Repeating), @@ -104,55 +121,65 @@ pub struct FixtureTilePrefab { impl FixtureTilePrefab { pub fn dirt_wall(position: Vec3) -> Self { + let def = TileRegistry::global().fixture("dirt_wall"); FixtureTilePrefab { transform: Transform::from_translation(position), tile: FixtureTile { - id: FIXTURE_ID_OFFSET + 1, - ..Default::default() + id: FIXTURE_ID_OFFSET + def.id, + solid: def.solid, + visible_range: [0; 8], }, visibility: Visibility::Hidden, } } pub fn rock_wall(position: Vec3) -> Self { + let def = TileRegistry::global().fixture("rock_wall"); FixtureTilePrefab { transform: Transform::from_translation(position), tile: FixtureTile { - id: FIXTURE_ID_OFFSET + 2, - ..Default::default() + id: FIXTURE_ID_OFFSET + def.id, + solid: def.solid, + visible_range: [0; 8], }, visibility: Visibility::Hidden, } } pub fn log(position: Vec3) -> Self { + let def = TileRegistry::global().fixture("log"); FixtureTilePrefab { transform: Transform::from_translation(position), tile: FixtureTile { - id: FIXTURE_ID_OFFSET + 4, - ..Default::default() + id: FIXTURE_ID_OFFSET + def.id, + solid: def.solid, + visible_range: [0; 8], }, visibility: Visibility::Hidden, } } pub fn leaves(position: Vec3) -> Self { + let def = TileRegistry::global().fixture("leaves"); FixtureTilePrefab { transform: Transform::from_translation(position), tile: FixtureTile { - id: FIXTURE_ID_OFFSET + 5, - ..Default::default() + id: FIXTURE_ID_OFFSET + def.id, + solid: def.solid, + visible_range: [0; 8], }, visibility: Visibility::Hidden, } } pub fn bedrock_wall(position: Vec3) -> Self { + let def = TileRegistry::global().fixture("bedrock_wall"); FixtureTilePrefab { transform: Transform::from_translation(position), tile: FixtureTile { - id: 0, - ..Default::default() + id: FIXTURE_ID_OFFSET + def.id, + solid: def.solid, + visible_range: [0; 8], }, visibility: Visibility::Hidden, } diff --git a/src/world/tiles/tilemap.rs b/src/world/tiles/tilemap.rs index 1e174b9..c5e76df 100644 --- a/src/world/tiles/tilemap.rs +++ b/src/world/tiles/tilemap.rs @@ -269,4 +269,18 @@ impl TileMap { (can_stand_in_floor || can_stand_in_fixture) && (can_stand_on_floor || can_stand_on_fixture) } + + /// Get A* pathfinding weight for a tile position. + /// Returns 100 (default) if tile not found. Lower is better. + pub fn get_astar_weight(&self, world_pos: IVec3) -> u8 { + let chunk_pos = world_to_chunk(world_pos); + if let Some(chunk) = self.chunks.get(&chunk_pos) { + let (local_x, local_y, z) = ChunkData::world_to_local(world_pos); + return chunk.get_astar_weight(local_x, local_y, z); + } + self.floor_tiles + .get(&world_pos) + .map(|t| t.astar_weight) + .unwrap_or(100) + } } diff --git a/tiles.toml b/tiles.toml new file mode 100644 index 0000000..b95edf8 --- /dev/null +++ b/tiles.toml @@ -0,0 +1,61 @@ +# Floor tiles: IDs are used for texture lookup. +# Weight: lower = faster to traverse (rock=50 is best, bedrock=150 is worst). +# Stand flags: can_stand_in (air pocket), can_stand_on (solid surface), transparent (see-through). + +[floor_tiles.air] +id = 0 +can_stand_in = true +can_stand_on = false +transparent = true +astar_weight = 0 + +[floor_tiles.grass] +id = 1 +can_stand_in = false +can_stand_on = true +transparent = false +astar_weight = 50 + +[floor_tiles.dirt] +id = 2 +can_stand_in = false +can_stand_on = true +transparent = false +astar_weight = 50 + +[floor_tiles.rock] +id = 3 +can_stand_in = false +can_stand_on = true +transparent = false +astar_weight = 50 + +[floor_tiles.bedrock] +id = 4 +can_stand_in = false +can_stand_on = true +transparent = false +astar_weight = 50 + +# Fixture tiles: walls and objects above floors. +# IDs are relative to FIXTURE_ID_OFFSET (500000). + +[fixture_tiles.dirt_wall] +id = 1 +solid = true + +[fixture_tiles.rock_wall] +id = 2 +solid = true + +[fixture_tiles.bedrock_wall] +id = 3 +solid = true + +[fixture_tiles.log] +id = 4 +solid = false + +[fixture_tiles.leaves] +id = 5 +solid = false \ No newline at end of file