Files
dorf/src/entities/livestock/rabbit.rs
T
popertots 0ebc48ff40 fix: snap-to-top for above-world spawn, guard rabbit dig at world floor
- Entities spawn at z=35*TILE=560 but Z_ABOVE max is 15*TILE=240. Snap to
  Z_ABOVE*TILE instead of falling one tile per tick through unloaded space.
- Skip rabbit dig when below_pos would be outside ChunkData z-bounds,
  preventing remove_floor assert panic.
2026-03-21 12:16:58 +00:00

152 lines
4.9 KiB
Rust

use crate::config::GameConfig;
use crate::constants::ITILE_SIZE;
use crate::constants::TILE_SIZE;
use crate::constants::*;
use crate::entities::shared_components::Ambulatory;
use crate::game::SpawnDelay;
use crate::world::tiles::visibility::TileOcclusionEvent;
use crate::world::tiles::{TileChangedEvent, TileMap};
use crate::world::VisibleGameEntity;
use bevy::prelude::*;
use bevy_rand::prelude::*;
use rand::RngExt;
/// How often (in seconds) a rabbit digs.
/// 5 seconds at normal speed — slow enough to observe, fast enough to test.
pub const DIG_INTERVAL_SECS: f32 = 5.0;
/// Tracks time until next dig action. Debug component — rabbits dig to demonstrate
/// the TileChangedEvent + path invalidation pipeline. Remove or replace when
/// real digging mechanics are implemented.
#[derive(Component)]
pub struct RabbitDigTimer {
/// Seconds remaining until next dig. Reset to DIG_INTERVAL_SECS after each dig.
pub secs_remaining: f32,
}
impl Default for RabbitDigTimer {
fn default() -> Self {
Self {
secs_remaining: DIG_INTERVAL_SECS,
}
}
}
#[derive(Bundle)]
pub struct Rabbit {
ambulatory: Ambulatory,
sprite: Sprite,
transform: Transform,
visibility: Visibility,
dig_timer: RabbitDigTimer,
}
impl Rabbit {
pub fn new(asset_server: &Res<AssetServer>, position: Vec3) -> Self {
Rabbit {
ambulatory: Ambulatory {
walk_speed: 1.,
run_speed: 6.,
target: None,
current_path: None,
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"),
..Default::default()
},
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
visibility: Visibility::Hidden,
dig_timer: RabbitDigTimer::default(),
}
}
}
pub fn spawn_rabbits(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut rng_q: Query<&mut WyRand, With<GlobalRng>>,
config: Res<GameConfig>,
mut delay: ResMut<SpawnDelay>,
mut has_spawned: Local<bool>,
) {
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(grid_x, grid_y, grid_z),
))
.id();
commands.entity(rab).insert(VisibleGameEntity);
}
}
}
/// Debug system: rabbits periodically dig the floor tile beneath them.
///
/// Removes the FloorTileData at the tile directly below the rabbit's current
/// z-level. Also clears the corresponding ChunkData bits via TileMap::remove_floor.
/// Fires TileChangedEvent so path invalidation reacts automatically.
pub fn rabbit_dig_system(
mut query: Query<(&Transform, &mut RabbitDigTimer)>,
mut tilemap: ResMut<TileMap>,
mut tile_changed: MessageWriter<TileChangedEvent>,
mut occlusion: MessageWriter<TileOcclusionEvent>,
time: Res<Time>,
) {
for (transform, mut dig_timer) in query.iter_mut() {
dig_timer.secs_remaining -= time.delta_secs();
if dig_timer.secs_remaining > 0.0 {
continue;
}
dig_timer.secs_remaining = DIG_INTERVAL_SECS;
let entity_pos = transform.translation.as_ivec3();
let below_pos = IVec3::new(entity_pos.x, entity_pos.y, entity_pos.z - ITILE_SIZE);
// Don't dig below world floor — would panic in remove_floor bounds check.
if below_pos.z < -(crate::world::chunks::Z_BELOW as i32 - 1) * ITILE_SIZE {
continue;
}
if tilemap.remove_floor(&below_pos).is_some() {
tile_changed.write(TileChangedEvent { pos: below_pos });
// Refresh visibility for the dug tile and all 26 neighbours.
// Removing a tile changes what is visible from every adjacent position.
for dz in -1..=1i32 {
for dy in -1..=1i32 {
for dx in -1..=1i32 {
occlusion.write(TileOcclusionEvent {
tile_position: below_pos
+ IVec3::new(dx * ITILE_SIZE, dy * ITILE_SIZE, dz * ITILE_SIZE),
});
}
}
}
}
}
}