use crate::config::{GameConfig, TileRegistry}; 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::{FloorTileData, 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; /// Temporary debug component. Attached to a rabbit for N ticks after it digs. /// Removed automatically when tick_count reaches 0. #[derive(Component)] pub struct RabbitFallDebug { pub ticks_remaining: u8, } /// 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, 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, 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(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 commands: Commands, mut query: Query<(Entity, &Transform, &mut RabbitDigTimer)>, mut tilemap: ResMut, mut tile_changed: MessageWriter, mut occlusion: MessageWriter, time: Res