diff --git a/.gitignore b/.gitignore index c286cb7..65b171c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ */tileset.png -*.csv \ No newline at end of file +*.csv +*.patch +*.diff \ No newline at end of file diff --git a/src/entities/livestock/pig.rs b/src/entities/livestock/pig.rs index d5304ab..dadeb2a 100644 --- a/src/entities/livestock/pig.rs +++ b/src/entities/livestock/pig.rs @@ -30,6 +30,8 @@ impl Pig { path_index: 0, step_recovery: 0, validation_cooldown: 0, + move_direction: Vec2::ZERO, + step_history: [0i16; 4], }, sprite: Sprite { image: asset_server.load("pig.png"), diff --git a/src/entities/livestock/rabbit.rs b/src/entities/livestock/rabbit.rs index e044043..7cc7006 100644 --- a/src/entities/livestock/rabbit.rs +++ b/src/entities/livestock/rabbit.rs @@ -27,6 +27,8 @@ impl Rabbit { path_index: 0, step_recovery: 0, validation_cooldown: 0, + move_direction: Vec2::ZERO, + step_history: [0i16; 4], }, sprite: Sprite { image: asset_server.load("rabbit.png"), diff --git a/src/entities/sentient/dorf.rs b/src/entities/sentient/dorf.rs index 02210c0..8d9ba88 100644 --- a/src/entities/sentient/dorf.rs +++ b/src/entities/sentient/dorf.rs @@ -27,6 +27,8 @@ impl Dorf { path_index: 0, step_recovery: 0, validation_cooldown: 0, + move_direction: Vec2::ZERO, + step_history: [0i16; 4], }, sprite: Sprite { image: asset_server.load("dorf.png"), diff --git a/src/entities/shared_components/ambulatory.rs b/src/entities/shared_components/ambulatory.rs index e2f4b8d..d4097d3 100644 --- a/src/entities/shared_components/ambulatory.rs +++ b/src/entities/shared_components/ambulatory.rs @@ -10,6 +10,8 @@ pub struct Ambulatory { pub target: Option, pub step_recovery: u32, pub validation_cooldown: u8, + pub move_direction: Vec2, + pub step_history: [i16; 4], } impl Default for Ambulatory { @@ -22,6 +24,8 @@ impl Default for Ambulatory { target: None, step_recovery: 0, validation_cooldown: 0, + move_direction: Vec2::ZERO, + step_history: [0i16; 4], } } } diff --git a/src/entities/shared_systems/occupancy.rs b/src/entities/shared_systems/occupancy.rs index 58e7597..8bb564f 100644 --- a/src/entities/shared_systems/occupancy.rs +++ b/src/entities/shared_systems/occupancy.rs @@ -6,6 +6,7 @@ use rustc_hash::FxHashMap; #[derive(Resource, Default)] pub struct TileOccupancy { pub counts: FxHashMap<(i32, i32), u8>, + pub directions: FxHashMap<(i32, i32), Vec2>, } impl TileOccupancy { @@ -15,19 +16,32 @@ impl TileOccupancy { let ty = (world_pos.y as i32) / ITILE_SIZE; *self.counts.get(&(tx, ty)).unwrap_or(&0) } + + #[inline] + pub fn direction_at(&self, world_pos: Vec3) -> Vec2 { + let tx = (world_pos.x as i32) / ITILE_SIZE; + let ty = (world_pos.y as i32) / ITILE_SIZE; + *self.directions.get(&(tx, ty)).unwrap_or(&Vec2::ZERO) + } } pub fn rebuild_tile_occupancy( mut occupancy: ResMut, - query: Query<&Transform, With>, + query: Query<(&Transform, &Ambulatory)>, ) { occupancy.counts.clear(); - for transform in query.iter() { + occupancy.directions.clear(); + for (transform, ambulatory) in query.iter() { let tx = (transform.translation.x as i32) / ITILE_SIZE; let ty = (transform.translation.y as i32) / ITILE_SIZE; let count = occupancy.counts.entry((tx, ty)).or_insert(0); if *count < 255 { *count += 1; } + if ambulatory.move_direction != Vec2::ZERO { + occupancy + .directions + .insert((tx, ty), ambulatory.move_direction); + } } } diff --git a/src/entities/shared_systems/pathfinding.rs b/src/entities/shared_systems/pathfinding.rs index ee32237..c0b335e 100644 --- a/src/entities/shared_systems/pathfinding.rs +++ b/src/entities/shared_systems/pathfinding.rs @@ -594,48 +594,145 @@ pub fn movement( if let Some(path) = &ambulatory.current_path { if ambulatory.path_index < path.len() { + let old_translation = transform.translation; let next_point = path[ambulatory.path_index]; let current_count = occupancy.count_at(transform.translation); let next_count = occupancy.count_at(next_point); let current_crowded = current_count > 3; let occupied = !current_crowded && next_count > current_count; - if occupied { - let move_dir = next_point - transform.translation; - if move_dir.length_squared() > 0.0 { - let left_dir = - Vec3::new(-move_dir.y, move_dir.x, 0.0).normalize() * TILE_SIZE; - let left_raw = transform.translation + left_dir; - let left_point = Vec3::new( - (left_raw.x / TILE_SIZE).round() * TILE_SIZE, - (left_raw.y / TILE_SIZE).round() * TILE_SIZE, - transform.translation.z, - ); - let left_free = tilemap.is_standable(left_point.as_ivec3()) - && occupancy.count_at(left_point) == 0; - if left_free { - if left_dir.x > 0.0 { - transform.scale.x = PIXEL_RATIO; - } else if left_dir.x < 0.0 { - transform.scale.x = -PIXEL_RATIO; - } - transform.translation = left_point; + let actual_move: Vec3; + let advance_path: bool; + + let move_dir = next_point - transform.translation; + if occupied && move_dir.length_squared() > 0.0 { + let forward_2d = Vec2::new(move_dir.x, move_dir.y).normalize(); + let left_2d = Vec2::new(-forward_2d.y, forward_2d.x); + let right_2d = Vec2::new(forward_2d.y, -forward_2d.x); + let cur_z = transform.translation.z; + + let candidates = [ + snap_to_grid( + transform.translation + + Vec3::new( + (left_2d.x + forward_2d.x).signum() * TILE_SIZE, + (left_2d.y + forward_2d.y).signum() * TILE_SIZE, + 0.0, + ), + cur_z, + TILE_SIZE, + ), + snap_to_grid( + transform.translation + + Vec3::new(left_2d.x * TILE_SIZE, left_2d.y * TILE_SIZE, 0.0), + cur_z, + TILE_SIZE, + ), + snap_to_grid( + transform.translation + + Vec3::new( + (right_2d.x + forward_2d.x).signum() * TILE_SIZE, + (right_2d.y + forward_2d.y).signum() * TILE_SIZE, + 0.0, + ), + cur_z, + TILE_SIZE, + ), + snap_to_grid( + transform.translation + + Vec3::new( + right_2d.x * TILE_SIZE, + right_2d.y * TILE_SIZE, + 0.0, + ), + cur_z, + TILE_SIZE, + ), + ]; + + let sidestep = candidates.iter().copied().find(|&c| { + tilemap.is_standable(c.as_ivec3()) + && occupancy.count_at(c) <= current_count + }); + + if let Some(step) = sidestep { + actual_move = step; + advance_path = false; + } else { + actual_move = next_point; + advance_path = true; + let tile_weight = + get_tile_weight(&tilemap, transform.translation.as_ivec3()); + let threshold = if ambulatory.walk_speed > 0. { + (ambulatory.walk_speed as i32 * tile_weight as i32 / 50) as u32 + } else { + 0 + }; + if ambulatory.step_recovery == 0 { + ambulatory.step_recovery = threshold; } } } else { - let direction = (next_point - transform.translation).normalize(); - transform.translation = next_point; + actual_move = next_point; + advance_path = true; + } - if direction.x > 0.0 { - transform.scale.x = PIXEL_RATIO; - } else if direction.x < 0.0 { - transform.scale.x = -PIXEL_RATIO; + let direction = (actual_move - transform.translation).normalize_or_zero(); + transform.translation = actual_move; + if direction.x > 0.0 { + transform.scale.x = PIXEL_RATIO; + } else if direction.x < 0.0 { + transform.scale.x = -PIXEL_RATIO; + } + + if advance_path && transform.translation.distance(next_point) < TILE_SIZE { + let ITILE: i16 = ITILE_SIZE as i16; + + ambulatory.step_history[0] = ambulatory.step_history[2]; + ambulatory.step_history[1] = ambulatory.step_history[3]; + ambulatory.step_history[2] = (old_translation.x as i16) / ITILE; + ambulatory.step_history[3] = (old_translation.y as i16) / ITILE; + + let mut dir_sum = Vec2::ZERO; + let mut n = 0u32; + + let h = &ambulatory.step_history; + if h[0] != 0 || h[1] != 0 { + let d = Vec2::new((h[2] - h[0]) as f32, (h[3] - h[1]) as f32); + if d.length_squared() > 0.0 { + dir_sum += d.normalize(); + n += 1; + } } - if transform.translation.distance(next_point) < TILE_SIZE { - ambulatory.path_index += 1; + let cur_d = Vec2::new( + actual_move.x - old_translation.x, + actual_move.y - old_translation.y, + ); + if cur_d.length_squared() > 0.0 { + dir_sum += cur_d.normalize(); + n += 1; } + + if let Some(ref path) = ambulatory.current_path { + let start = ambulatory.path_index; + for i in start..((start + 2).min(path.len().saturating_sub(1))) { + let a = path[i]; + let b = path[i + 1]; + let d = Vec2::new(b.x - a.x, b.y - a.y); + if d.length_squared() > 0.0 { + dir_sum += d.normalize(); + n += 1; + } + } + } + + if n > 0 { + ambulatory.move_direction = (dir_sum / n as f32).normalize_or_zero(); + } + + ambulatory.path_index += 1; } } else { ambulatory.current_path = None; @@ -664,6 +761,15 @@ fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool { tilemap.is_standable(pos) } +#[inline] +fn snap_to_grid(raw: Vec3, preserve_z: f32, tile_size: f32) -> Vec3 { + Vec3::new( + (raw.x / tile_size).round() * tile_size, + (raw.y / tile_size).round() * tile_size, + preserve_z, + ) +} + /// Sample a random standable tile on an edge of `next_chunk`, picking the one /// whose world position is closest to the straight-line projection from /// `current_pos` toward `goal`. Falls back to chunk centre if no standable diff --git a/src/world/tiles/tilemap.rs b/src/world/tiles/tilemap.rs index 95c012f..79888b7 100644 --- a/src/world/tiles/tilemap.rs +++ b/src/world/tiles/tilemap.rs @@ -194,6 +194,11 @@ impl TileMap { #[inline] pub fn insert_fixture(&mut self, pos: IVec3, tile: FixtureTileData) { + let chunk_pos = world_to_chunk(pos); + if let Some(chunk) = self.chunks.get_mut(&chunk_pos) { + let (lx, ly, z) = ChunkData::world_to_local(pos); + chunk.set_fixture_tile(lx, ly, z, tile.can_stand_in(), tile.can_stand_on()); + } self.fixture_tiles.insert(pos, tile); }