refactor: extract generic digging system, drop tables, remove debug code
- New src/entities/item/drop_table.rs: DropEntry (chance, min/max count, pseudo-RNG roll) + DropTable wrapper. grass=5% coin 1-2, rock=10% coin 1, dirt/air=none. - New src/entities/shared_systems/digging.rs: Digger component + dig_system. Any entity with Digger digs the tile below on its interval. Replaces rabbit_dig_system/rabbit_fall_debug_system/RabbitDigTimer/RabbitFallDebug. - New TileMap::dig_floor: remove_floor + insert air. Used by dig_system. - FloorTileData: added drop_table field. Lost Copy derive (Vec field). Updated all 6 terrain.rs call sites with per-tile drop tables. - Pig: removed PigDropTimer + pig_drop_system. Drops were debug placeholder. TODO added for future loot-on-death/butcher system. - Rabbit: removed all debug components/systems. Now uses Digger::new(5.0). - main.rs: removed rabbit_dig_system/rabbit_fall_debug_system/pig_drop_system, added shared_systems::digging::dig_system.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
use crate::entities::item::prefabs::misc::misc_prefabs::MiscPrefab;
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DropEntry {
|
||||
pub prefab: MiscPrefab,
|
||||
pub chance: f32,
|
||||
pub min_count: u32,
|
||||
pub max_count: u32,
|
||||
}
|
||||
|
||||
impl DropEntry {
|
||||
pub fn always(prefab: MiscPrefab) -> Self {
|
||||
Self {
|
||||
prefab,
|
||||
chance: 1.0,
|
||||
min_count: 1,
|
||||
max_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chance(prefab: MiscPrefab, chance: f32, min_count: u32, max_count: u32) -> Self {
|
||||
Self {
|
||||
prefab,
|
||||
chance,
|
||||
min_count,
|
||||
max_count,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn roll(&self, pos: IVec3) -> u32 {
|
||||
if self.chance < 1.0 {
|
||||
let r = pseudo_rand_f32(pos);
|
||||
if r >= self.chance {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
pseudo_rand_u32(pos) % (self.max_count - self.min_count + 1) + self.min_count
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DropTable(pub Vec<DropEntry>);
|
||||
|
||||
impl DropTable {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn hash3(pos: IVec3) -> u32 {
|
||||
let mut h: u32 = 0;
|
||||
h = h.wrapping_mul(374761393).wrapping_add(pos.x as u32);
|
||||
h = h.wrapping_mul(374761393).wrapping_add(pos.y as u32);
|
||||
h = h.wrapping_mul(374761393).wrapping_add(pos.z as u32);
|
||||
h ^= h >> 13;
|
||||
h = h.wrapping_mul(1274126177);
|
||||
h ^= h >> 16;
|
||||
h
|
||||
}
|
||||
|
||||
fn pseudo_rand_u32(pos: IVec3) -> u32 {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u32;
|
||||
hash3(pos) ^ timestamp
|
||||
}
|
||||
|
||||
fn pseudo_rand_f32(pos: IVec3) -> f32 {
|
||||
let val = pseudo_rand_u32(pos);
|
||||
(val as f32) / (u32::MAX as f32)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod container;
|
||||
pub mod core;
|
||||
pub mod decorations;
|
||||
pub mod drop_table;
|
||||
pub mod material;
|
||||
pub mod perishable;
|
||||
pub mod prefabs;
|
||||
@@ -11,6 +12,7 @@ pub mod types;
|
||||
pub use container::*;
|
||||
pub use core::*;
|
||||
pub use decorations::*;
|
||||
pub use drop_table::*;
|
||||
pub use material::*;
|
||||
pub use perishable::*;
|
||||
pub use prefabs::*;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use crate::config::GameConfig;
|
||||
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::*;
|
||||
use bevy_rand::prelude::*;
|
||||
@@ -16,7 +14,6 @@ pub struct Pig {
|
||||
sprite: Sprite,
|
||||
transform: Transform,
|
||||
visibility: Visibility,
|
||||
drop_timer: PigDropTimer,
|
||||
}
|
||||
|
||||
impl Pig {
|
||||
@@ -39,14 +36,10 @@ 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Deref, DerefMut)]
|
||||
pub struct PigDropTimer(pub Timer);
|
||||
|
||||
pub fn spawn_pigs(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
@@ -81,40 +74,3 @@ pub fn spawn_pigs(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pig_drop_system(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
time: Res<Time>,
|
||||
mut pigs: Query<(&mut PigDropTimer, &Transform)>,
|
||||
mut tilemap: ResMut<tilemap::TileMap>,
|
||||
mut rng_q: Query<&mut WyRand, With<GlobalRng>>,
|
||||
) {
|
||||
for (mut timer, transform) in &mut pigs {
|
||||
timer.tick(time.delta());
|
||||
|
||||
if timer.just_finished() {
|
||||
if let Ok(mut rng) = rng_q.single_mut() {
|
||||
if rng.random_range(0..10) <= 5 {
|
||||
// Example: pig drops raw meat
|
||||
spawn_prefab(
|
||||
&mut commands,
|
||||
&asset_server,
|
||||
MiscPrefab::RawMeat,
|
||||
transform.translation,
|
||||
&mut tilemap,
|
||||
);
|
||||
} else {
|
||||
//example: pig drops a coin
|
||||
spawn_prefab(
|
||||
&mut commands,
|
||||
&asset_server,
|
||||
MiscPrefab::Coin,
|
||||
transform.translation,
|
||||
&mut tilemap,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,20 @@
|
||||
use crate::config::{GameConfig, TileRegistry};
|
||||
use crate::constants::ITILE_SIZE;
|
||||
use crate::constants::TILE_SIZE;
|
||||
use crate::config::GameConfig;
|
||||
use crate::constants::*;
|
||||
use crate::entities::shared_components::Ambulatory;
|
||||
use crate::entities::shared_systems::digging::Digger;
|
||||
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,
|
||||
digger: Digger,
|
||||
}
|
||||
|
||||
impl Rabbit {
|
||||
@@ -68,7 +37,7 @@ impl Rabbit {
|
||||
},
|
||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
||||
visibility: Visibility::Hidden,
|
||||
dig_timer: RabbitDigTimer::default(),
|
||||
digger: Digger::new(5.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,203 +79,3 @@ pub fn spawn_rabbits(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<TileMap>,
|
||||
mut tile_changed: MessageWriter<TileChangedEvent>,
|
||||
mut occlusion: MessageWriter<TileOcclusionEvent>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for (entity, transform, mut dig_timer) in query.iter_mut() {
|
||||
if !tilemap.is_standable(transform.translation.as_ivec3()) {
|
||||
dig_timer.secs_remaining = DIG_INTERVAL_SECS;
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// --- DEBUG: pre-dig state ---
|
||||
let (lx, ly, lz) =
|
||||
crate::world::tiles::ChunkData::world_to_local(transform.translation.as_ivec3());
|
||||
let chunk_pos = crate::world::chunks::world_to_chunk(transform.translation.as_ivec3());
|
||||
let standable_now = tilemap.is_standable(transform.translation.as_ivec3());
|
||||
println!(
|
||||
"[DIG PRE] entity={:?} world_pos=({:.1},{:.1},{:.1}) ivec={:?} \
|
||||
tile_local=({},{},{}) chunk={:?} standable={}",
|
||||
entity,
|
||||
transform.translation.x,
|
||||
transform.translation.y,
|
||||
transform.translation.z,
|
||||
entity_pos,
|
||||
lx,
|
||||
ly,
|
||||
lz,
|
||||
chunk_pos,
|
||||
standable_now
|
||||
);
|
||||
println!(
|
||||
"[DIG PRE] below_pos={:?} below_tile_z={}",
|
||||
below_pos,
|
||||
below_pos.z / ITILE_SIZE
|
||||
);
|
||||
|
||||
let removed = tilemap.remove_floor(&below_pos);
|
||||
|
||||
// Replace the dug tile with air. The slot must become passable — if the entry
|
||||
// is simply absent, ChunkData bits remain 0 (impassable), and is_standable
|
||||
// returns false. Air has can_stand_in=true so entities can fall through it.
|
||||
if removed.is_some() {
|
||||
let air = TileRegistry::global().floor("air");
|
||||
tilemap.insert_floor(
|
||||
below_pos,
|
||||
FloorTileData::new(
|
||||
air.id,
|
||||
air.can_stand_in,
|
||||
air.can_stand_on,
|
||||
air.transparent,
|
||||
air.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"[DIG] remove_floor({:?}) => {}",
|
||||
below_pos,
|
||||
if removed.is_some() {
|
||||
"REMOVED"
|
||||
} else {
|
||||
"NOT FOUND"
|
||||
}
|
||||
);
|
||||
|
||||
// --- DEBUG: post-dig standability ---
|
||||
let standable_after = tilemap.is_standable(transform.translation.as_ivec3());
|
||||
println!(
|
||||
"[DIG POST] standable at entity pos after dig: {}",
|
||||
standable_after
|
||||
);
|
||||
|
||||
if removed.is_some() {
|
||||
tile_changed.write(TileChangedEvent { pos: below_pos });
|
||||
|
||||
// Refresh the full column below the dig and its XY neighbours.
|
||||
// calculate_visibility traces up to Z_TOTAL+Z_BELOW+1 tiles above each tile,
|
||||
// so removing one tile can affect visibility arbitrarily deep below.
|
||||
for dz in 0..=crate::world::chunks::Z_BELOW as i32 {
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Attach fall debug component — tracks for 12 ticks
|
||||
commands.entity(entity).insert(RabbitFallDebug {
|
||||
ticks_remaining: 12,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporary debug system. Prints rabbit position and standability for N ticks
|
||||
/// after a dig event. Removes the RabbitFallDebug component when done.
|
||||
pub fn rabbit_fall_debug_system(
|
||||
mut commands: Commands,
|
||||
mut query: Query<(Entity, &Transform, &mut RabbitFallDebug)>,
|
||||
tilemap: Res<TileMap>,
|
||||
) {
|
||||
use crate::world::chunks::world_to_chunk;
|
||||
use crate::world::tiles::ChunkData;
|
||||
|
||||
for (entity, transform, mut debug) in query.iter_mut() {
|
||||
let world_pos = transform.translation;
|
||||
let ivec = world_pos.as_ivec3();
|
||||
let (lx, ly, lz) = ChunkData::world_to_local(ivec);
|
||||
let lz_euclid = ivec.z.div_euclid(ITILE_SIZE); // compare truncation vs floor
|
||||
let chunk_pos = world_to_chunk(ivec);
|
||||
let standable = tilemap.is_standable(ivec);
|
||||
|
||||
// Read ChunkData bits directly if chunk is loaded
|
||||
let (stand_in, stand_on_below) = if let Some(chunk) = tilemap.chunks.get(&chunk_pos) {
|
||||
let in_f = if lz >= -(crate::world::chunks::Z_BELOW as i32)
|
||||
&& lz <= crate::world::chunks::Z_ABOVE as i32
|
||||
&& lx >= 0
|
||||
&& lx < crate::world::chunks::CHUNK_SIZE
|
||||
&& ly >= 0
|
||||
&& ly < crate::world::chunks::CHUNK_SIZE
|
||||
{
|
||||
let idx = ChunkData::pos_to_index(lx, ly, lz);
|
||||
let word = idx / 32;
|
||||
let mask = 1u32 << (idx % 32);
|
||||
let in_floor = (chunk.stand_in_floor[word] & mask) != 0;
|
||||
let in_fix = (chunk.stand_in_fixture[word] & mask) != 0;
|
||||
format!("in_floor={} in_fixture={}", in_floor, in_fix)
|
||||
} else {
|
||||
format!("OUT_OF_BOUNDS(lz={})", lz)
|
||||
};
|
||||
|
||||
let on_f = if lz - 1 >= -(crate::world::chunks::Z_BELOW as i32)
|
||||
&& lz - 1 <= crate::world::chunks::Z_ABOVE as i32
|
||||
&& lx >= 0
|
||||
&& lx < crate::world::chunks::CHUNK_SIZE
|
||||
&& ly >= 0
|
||||
&& ly < crate::world::chunks::CHUNK_SIZE
|
||||
{
|
||||
let idx = ChunkData::pos_to_index(lx, ly, lz - 1);
|
||||
let word = idx / 32;
|
||||
let mask = 1u32 << (idx % 32);
|
||||
let on_floor = (chunk.stand_on_floor[word] & mask) != 0;
|
||||
let on_fix = (chunk.stand_on_fixture[word] & mask) != 0;
|
||||
format!("on_floor[z-1]={} on_fixture[z-1]={}", on_floor, on_fix)
|
||||
} else {
|
||||
"BELOW_BOUNDS".to_string()
|
||||
};
|
||||
|
||||
(in_f, on_f)
|
||||
} else {
|
||||
(
|
||||
"chunk_not_loaded".to_string(),
|
||||
"chunk_not_loaded".to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
println!(
|
||||
"[FALL {:2}] entity={:?} world_z={:.1} ivec_z={} lz_trunc={} lz_euclid={} \
|
||||
standable={} | {} | {}",
|
||||
debug.ticks_remaining,
|
||||
entity,
|
||||
world_pos.z,
|
||||
ivec.z,
|
||||
lz,
|
||||
lz_euclid,
|
||||
standable,
|
||||
stand_in,
|
||||
stand_on_below
|
||||
);
|
||||
|
||||
debug.ticks_remaining -= 1;
|
||||
if debug.ticks_remaining == 0 {
|
||||
commands.entity(entity).remove::<RabbitFallDebug>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use crate::constants::ITILE_SIZE;
|
||||
use crate::entities::item::prefabs::misc::misc_prefabs::spawn_prefab;
|
||||
use crate::world::chunks::{Z_ABOVE, Z_BELOW};
|
||||
use crate::world::tiles::tile_changed::TileChangedEvent;
|
||||
use crate::world::tiles::visibility::TileOcclusionEvent;
|
||||
use crate::world::tiles::TileMap;
|
||||
use bevy::prelude::*;
|
||||
use rand::RngExt;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Digger {
|
||||
pub secs_remaining: f32,
|
||||
pub interval_secs: f32,
|
||||
}
|
||||
|
||||
impl Digger {
|
||||
pub fn new(interval_secs: f32) -> Self {
|
||||
Self {
|
||||
secs_remaining: interval_secs,
|
||||
interval_secs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dig_system(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
time: Res<Time>,
|
||||
mut query: Query<(&Transform, &mut Digger)>,
|
||||
mut tilemap: ResMut<TileMap>,
|
||||
mut tile_changed: MessageWriter<TileChangedEvent>,
|
||||
mut occlusion: MessageWriter<TileOcclusionEvent>,
|
||||
) {
|
||||
for (transform, mut digger) in &mut query {
|
||||
let pos = transform.translation.as_ivec3();
|
||||
|
||||
if !tilemap.is_standable(pos) {
|
||||
digger.secs_remaining = digger.interval_secs;
|
||||
continue;
|
||||
}
|
||||
|
||||
digger.secs_remaining -= time.delta_secs();
|
||||
if digger.secs_remaining > 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
digger.secs_remaining = digger.interval_secs;
|
||||
|
||||
let below_pos = pos - IVec3::new(0, 0, ITILE_SIZE);
|
||||
|
||||
if below_pos.z < -(Z_BELOW as i32 - 1) * ITILE_SIZE {
|
||||
continue;
|
||||
}
|
||||
|
||||
let drop_table = tilemap
|
||||
.floor_tiles
|
||||
.get(&below_pos)
|
||||
.map(|t| t.drop_table.clone());
|
||||
|
||||
if let Some(_removed) = tilemap.dig_floor(&below_pos) {
|
||||
if let Some(table) = drop_table {
|
||||
for entry in table.0 {
|
||||
let count = entry.roll(below_pos);
|
||||
for _ in 0..count {
|
||||
spawn_prefab(
|
||||
&mut commands,
|
||||
&asset_server,
|
||||
entry.prefab.clone(),
|
||||
below_pos.as_vec3(),
|
||||
&mut tilemap,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tile_changed.write(TileChangedEvent { pos: below_pos });
|
||||
|
||||
// Refresh the full column below the dig and its XY neighbours.
|
||||
// calculate_visibility traces up to Z_TOTAL tiles above each tile,
|
||||
// so removing one tile can affect visibility arbitrarily deep below.
|
||||
for dz in 0..=Z_BELOW as i32 {
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod digging;
|
||||
pub mod occupancy;
|
||||
pub mod pathfinding;
|
||||
|
||||
+3
-7
@@ -3,7 +3,7 @@ use bevy_rand::prelude::*;
|
||||
|
||||
use crate::entities::{
|
||||
item::{initialize_item_rotation_state, item_tile_management_system, ItemRotationTimer},
|
||||
livestock::pig::pig_drop_system,
|
||||
shared_systems::digging::dig_system,
|
||||
};
|
||||
use crate::game::SpawnDelay;
|
||||
|
||||
@@ -45,10 +45,6 @@ fn main() {
|
||||
.insert_resource(ClearColor(Color::srgb(0., 0., 0.)))
|
||||
.add_plugins(world::WorldPlugin)
|
||||
.add_plugins(entities::pathfinding::PathfindingPlugin)
|
||||
.add_systems(
|
||||
FixedUpdate,
|
||||
entities::livestock::rabbit::rabbit_fall_debug_system,
|
||||
)
|
||||
.add_systems(
|
||||
Startup,
|
||||
(camera::spawn_panning_camera, cursor::setup_cursor),
|
||||
@@ -63,10 +59,10 @@ fn main() {
|
||||
entities::sentient::dorf::spawn_dorfs,
|
||||
entities::livestock::pig::spawn_pigs,
|
||||
entities::livestock::rabbit::spawn_rabbits,
|
||||
entities::livestock::rabbit::rabbit_dig_system,
|
||||
dig_system,
|
||||
),
|
||||
)
|
||||
.add_systems(Update, pig_drop_system)
|
||||
|
||||
.insert_resource(ItemRotationTimer::default())
|
||||
.add_systems(Update, initialize_item_rotation_state)
|
||||
.add_systems(
|
||||
|
||||
@@ -4,6 +4,9 @@ use bevy_platform::time::Instant;
|
||||
use noise::{NoiseFn, Perlin};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::entities::item::drop_table::{DropEntry, DropTable};
|
||||
use crate::entities::item::prefabs::misc::misc_prefabs::MiscPrefab;
|
||||
|
||||
use crate::{
|
||||
config::TileRegistry,
|
||||
constants::{SEED, TILE_SIZE},
|
||||
@@ -14,6 +17,19 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
fn drop_table_for(tile_name: &str) -> DropTable {
|
||||
match tile_name {
|
||||
"grass" => DropTable(vec![
|
||||
DropEntry::chance(MiscPrefab::Coin, 0.05, 1, 2),
|
||||
]),
|
||||
"dirt" => DropTable::default(),
|
||||
"rock" => DropTable(vec![
|
||||
DropEntry::chance(MiscPrefab::Coin, 0.1, 1, 1),
|
||||
]),
|
||||
_ => DropTable::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe storage for completed terrain blobs.
|
||||
/// Uses type erasure to avoid Debug bounds on TerrainBlob.
|
||||
type BlobStorage = Arc<Mutex<Box<dyn Send + Sync>>>;
|
||||
@@ -114,6 +130,7 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
drop_table_for("air"),
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(
|
||||
@@ -136,6 +153,7 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
drop_table_for("rock"),
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(
|
||||
@@ -146,20 +164,21 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.astar_weight,
|
||||
);
|
||||
} else {
|
||||
let tile = registry.floor("dirt");
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
),
|
||||
));
|
||||
);
|
||||
} else {
|
||||
let tile = registry.floor("dirt");
|
||||
tile_updates.push((
|
||||
pos_ivec,
|
||||
FloorTileData::new(
|
||||
tile.id,
|
||||
tile.can_stand_in,
|
||||
tile.can_stand_on,
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
drop_table_for("dirt"),
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(
|
||||
local_x,
|
||||
local_y,
|
||||
@@ -182,6 +201,7 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
drop_table_for("grass"),
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(
|
||||
@@ -205,6 +225,7 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
drop_table_for("dirt"),
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(
|
||||
@@ -228,6 +249,7 @@ fn generate_terrain_blob(chunk_pos: IVec2) -> TerrainBlob {
|
||||
tile.transparent,
|
||||
tile.astar_weight,
|
||||
[0; 8],
|
||||
drop_table_for("air"),
|
||||
),
|
||||
));
|
||||
chunk_data.set_floor_tile(
|
||||
|
||||
@@ -33,16 +33,18 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use super::chunk_data::ChunkData;
|
||||
use crate::constants::ITILE_SIZE;
|
||||
use crate::entities::item::drop_table::DropTable;
|
||||
use crate::world::chunks::{world_to_chunk, CHUNK_SIZE, Z_ABOVE, Z_BELOW};
|
||||
|
||||
/// Packed floor tile data for efficient storage. ~35 bytes vs 76 bytes tuple.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FloorTileData {
|
||||
pub id: u8,
|
||||
/// bit0=can_stand_in, bit1=can_stand_on, bit2=visibly_transparent
|
||||
pub flags: u8,
|
||||
pub astar_weight: u8,
|
||||
pub visible_range: [u32; 8],
|
||||
pub drop_table: DropTable,
|
||||
}
|
||||
|
||||
impl Default for FloorTileData {
|
||||
@@ -52,6 +54,7 @@ impl Default for FloorTileData {
|
||||
flags: 0b001,
|
||||
astar_weight: 0,
|
||||
visible_range: [0; 8],
|
||||
drop_table: DropTable::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +67,7 @@ impl FloorTileData {
|
||||
visibly_transparent: bool,
|
||||
astar_weight: u8,
|
||||
visible_range: [u32; 8],
|
||||
drop_table: DropTable,
|
||||
) -> Self {
|
||||
let mut flags = 0u8;
|
||||
if can_stand_in {
|
||||
@@ -80,6 +84,7 @@ impl FloorTileData {
|
||||
flags,
|
||||
astar_weight,
|
||||
visible_range,
|
||||
drop_table,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +219,26 @@ impl TileMap {
|
||||
self.floor_tiles.insert(pos, tile);
|
||||
}
|
||||
|
||||
pub fn dig_floor(&mut self, pos: &IVec3) -> Option<FloorTileData> {
|
||||
let removed = self.remove_floor(pos);
|
||||
if removed.is_some() {
|
||||
let air = crate::config::TileRegistry::global().floor("air");
|
||||
self.insert_floor(
|
||||
*pos,
|
||||
FloorTileData::new(
|
||||
air.id,
|
||||
air.can_stand_in,
|
||||
air.can_stand_on,
|
||||
air.transparent,
|
||||
air.astar_weight,
|
||||
[0; 8],
|
||||
DropTable::default(),
|
||||
),
|
||||
);
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert_fixture(&mut self, pos: IVec3, tile: FixtureTileData) {
|
||||
let chunk_pos = world_to_chunk(pos);
|
||||
|
||||
Reference in New Issue
Block a user