diff --git a/.gitignore b/.gitignore index a1eb743..9d91c4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target .vscode/ -/assets/worlds/** \ No newline at end of file +/assets/worlds/** +.cargo/config.toml diff --git a/src/actors.rs b/src/actors.rs index c9d2190..86bdff1 100644 --- a/src/actors.rs +++ b/src/actors.rs @@ -9,7 +9,11 @@ pub struct Actor { } //Called before simulations -pub fn add_actors(mut chunk_manager: ResMut, actors: Query<&Actor>) { +pub fn add_actors( + mut chunk_manager: ResMut, + actors: Query<&Actor>, + mut dirty_rects: ResMut, +) { for actor in actors.iter() { for x_off in 0..actor.width as i32 { for y_off in 0..actor.height as i32 { @@ -17,8 +21,18 @@ pub fn add_actors(mut chunk_manager: ResMut, actors: Query<&Actor> if let Some(atom) = chunk_manager.get_mut_atom(pos) { if atom.state == State::Void { *atom = Atom::object(); + } else if atom.state == State::Liquid { + let rand_angle = fastrand::f32() - 0.5; + let vel = actor.vel * -4. * vec2(rand_angle.cos(), rand_angle.sin()); + //Water splashes + atom.velocity = ( + (vel.x).clamp(-126.0, 126.) as i8, + (vel.y).clamp(-126.0, 126.) as i8, + ); + atom.automata_mode = false; } } + update_dirty_rects_3x3(&mut dirty_rects.current, pos); } } } @@ -137,9 +151,6 @@ pub fn move_x(chunk_manager: &mut ChunkManager, actor: &mut Actor, dir: i32) -> if atom.state == State::Powder || atom.state == State::Solid { actor.vel = Vec2::ZERO; return false; - } else if atom.state == State::Liquid { - //Water splashes - atom.velocity = (0, -20) } } else { actor.vel = Vec2::ZERO; @@ -170,9 +181,6 @@ pub fn move_y(chunk_manager: &mut ChunkManager, actor: &mut Actor, dir: i32) -> if atom.state == State::Powder || atom.state == State::Solid { actor.vel = Vec2::ZERO; return false; - } else if atom.state == State::Liquid { - //Water splashes - atom.velocity = (0, -20) } } else { actor.vel = Vec2::ZERO; diff --git a/src/atom.rs b/src/atom.rs index f764fe1..dd9ef4e 100644 --- a/src/atom.rs +++ b/src/atom.rs @@ -12,7 +12,7 @@ pub struct Atom { #[serde(skip)] pub updated_at: u8, #[serde(skip)] - pub fall_speed: u8, + pub automata_mode: bool, // Used when thrown up, etc #[serde(skip)] pub velocity: (i8, i8), @@ -25,7 +25,6 @@ impl Atom { pub fn object() -> Self { Atom { state: State::Object, - color: [255, 255, 255, 255], ..Default::default() } } @@ -134,7 +133,8 @@ pub fn update_liquid(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> HashS if let Some(side) = side { for _ in 0..5 { - if !swapable(chunks, cur_pos + IVec2::new(side, 0), &[], dt) { + let state = get_state(chunks, cur_pos); + if !swapable(chunks, cur_pos + IVec2::new(side, 0), &[], state, dt) { break; } @@ -164,10 +164,18 @@ pub fn update_particle(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> Has // Move for pos in Line::new(cur_pos, vel) { awakened.insert(cur_pos); - if swapable(chunks, pos, &[], dt) { + let state = get_state(chunks, cur_pos); + if swapable(chunks, pos, &[], state, dt) { swap(chunks, cur_pos, pos, dt); cur_pos = pos; awakened.insert(cur_pos); + } else if get_state(chunks, pos) == State::Liquid + && get_state(chunks, cur_pos) == State::Liquid + { + awakened.insert(pos); + set_vel(chunks, pos, vel * 4 / 5); + set_vel(chunks, cur_pos, vel / 5); + break; } else { if vel.abs().x > 4 && vel.abs().y > 4 { set_vel( @@ -175,8 +183,9 @@ pub fn update_particle(chunks: &mut UpdateChunksType, pos: IVec2, dt: u8) -> Has cur_pos, (Vec2::from_angle(PI).rotate(vel.as_vec2()) * 0.5).as_ivec2(), ); - } else { + } else if !swapable(chunks, cur_pos + IVec2::Y, &[], state, dt) { set_vel(chunks, cur_pos, IVec2::ZERO); + set_mode(chunks, cur_pos, true); } break; } diff --git a/src/chunk_manager.rs b/src/chunk_manager.rs index 65df0d8..9a27e0f 100644 --- a/src/chunk_manager.rs +++ b/src/chunk_manager.rs @@ -148,7 +148,7 @@ impl std::ops::IndexMut for ChunkManager { } } -#[derive(Component)] +#[derive(Resource, Default)] pub struct DirtyRects { /// The current chunk update dirty rects pub current: HashMap, @@ -203,23 +203,16 @@ pub fn manager_setup( ChunkTextures, )) .push_children(&images_vec); - - commands.spawn(DirtyRects { - current: HashMap::new(), - new: HashMap::new(), - render: HashMap::new(), - }); } pub fn chunk_manager_update( mut chunk_manager: ResMut, - mut dirty_rects: Query<&mut DirtyRects>, + mut dirty_rects_resource: ResMut, ) { chunk_manager.dt = chunk_manager.dt.wrapping_add(1); let dt = chunk_manager.dt; // Get dirty rects - let mut dirty_rects_resource = dirty_rects.single_mut(); let DirtyRects { current: dirty_rects, new: new_dirty_rects, @@ -256,7 +249,11 @@ pub fn chunk_manager_update( // Loop through deferred tasks while let Ok(update) = dirty_update_rects_recv.recv().await { - update_dirty_rects_3x3(new_dirty_rects, update.chunk_pos); + if update.awake_surrouding { + update_dirty_rects_3x3(new_dirty_rects, update.chunk_pos); + } else { + update_dirty_rects(new_dirty_rects, update.chunk_pos) + } } }); @@ -400,11 +397,18 @@ pub fn update_chunks(chunks: &mut UpdateChunksType, dt: u8, dirty_rect: &URect) let mut awake_self = false; let state; - let vel; + let automata_mode; { let atom = &mut chunks.group[local_pos]; state = atom.state; - vel = atom.velocity != (0, 0); + + if atom.velocity == (0, 0) { + atom.automata_mode = true; + } + automata_mode = atom.automata_mode; + if automata_mode { + atom.velocity = (0, atom.velocity.1.abs()); + } if atom.f_idle < FRAMES_SLEEP && state != State::Void && state != State::Solid { atom.f_idle += 1; @@ -412,21 +416,27 @@ pub fn update_chunks(chunks: &mut UpdateChunksType, dt: u8, dirty_rect: &URect) } } - let mut awakened = if vel { - update_particle(chunks, pos, dt) - } else { + let mut awakened = if automata_mode { match state { State::Powder => update_powder(chunks, pos, dt), State::Liquid => update_liquid(chunks, pos, dt), _ => HashSet::new(), } + } else { + update_particle(chunks, pos, dt) }; + let mut self_awakened = HashSet::new(); if awakened.contains(&pos) { let atom = &mut chunks.group[local_pos]; atom.f_idle = 0; + } else if !automata_mode { + awakened.insert(pos); + let atom = &mut chunks.group[local_pos]; + atom.f_idle = 0; } else if awake_self { awakened.insert(pos); + self_awakened.insert(pos); } for awoke in awakened { @@ -437,6 +447,7 @@ pub fn update_chunks(chunks: &mut UpdateChunksType, dt: u8, dirty_rect: &URect) .dirty_update_rect_send .try_send(DeferredDirtyRectUpdate { chunk_pos: ChunkPos::new(local.0.try_into().unwrap(), chunk), + awake_surrouding: !self_awakened.contains(&awoke), }) .unwrap(); } @@ -582,11 +593,9 @@ struct ExtractedTextureUpdate { fn extract_chunk_texture_updates( chunk_manager: Extract>, - dirty_rects: Extract>, + dirty_rects: Extract>, mut extracted_updates: ResMut, ) { - let dirty_rects = dirty_rects.single(); - for (chunk_pos, chunk) in chunk_manager.chunks.iter() { if let Some(rect) = dirty_rects.render.get(chunk_pos) { let id = chunk.texture.id(); @@ -651,7 +660,8 @@ impl Plugin for ChunkManagerPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, manager_setup) .add_systems(Update, (chunk_manager_update, update_manager_pos)) - .init_resource::(); + .init_resource::() + .init_resource::(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app diff --git a/src/consts.rs b/src/consts.rs index 4698a2a..c2bb2da 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -33,7 +33,7 @@ pub const GRAVITY: u8 = 1; pub const TERM_VEL: u8 = 10; pub const FRAMES_SLEEP: u8 = 4; //Has to be even -pub const LOAD_WIDTH: i32 = 32; -pub const LOAD_HEIGHT: i32 = 20; +pub const LOAD_WIDTH: i32 = 20; +pub const LOAD_HEIGHT: i32 = 12; pub const _CAMERA_SPEED: f32 = 10.; diff --git a/src/debug.rs b/src/debug.rs index bb06a92..5822ca3 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -11,7 +11,7 @@ fn brush( window: Query<&Window>, camera_q: Query<(&Camera, &GlobalTransform)>, mut chunk_manager: ResMut, - mut dirty_rects: Query<&mut DirtyRects>, + mut dirty_rects: ResMut, prev_mpos: Query<&PreviousMousePos>, input: (Res>, Res>), ) { @@ -51,7 +51,6 @@ fn brush( .and_then(|cursor| camera.viewport_to_world(camera_transform, cursor)) .map(|ray| ray.origin.truncate()) { - let mut dirty_rects = dirty_rects.single_mut(); world_position.y *= -1.; let prev_mpos = prev_mpos.single().0.unwrap(); @@ -122,8 +121,7 @@ fn prev_mpos( //Debug Render systems -pub fn render_dirty_rects(mut commands: Commands, dirty_rects: Query<&DirtyRects>) { - let dirty_rects = dirty_rects.single(); +pub fn render_dirty_rects(mut commands: Commands, dirty_rects: Res) { let (dirty_update, render_update) = (&dirty_rects.new, &dirty_rects.render); let mut i = 0.; diff --git a/src/manager_api.rs b/src/manager_api.rs index 112a644..c4b8f6f 100644 --- a/src/manager_api.rs +++ b/src/manager_api.rs @@ -35,6 +35,7 @@ pub fn swap(chunks: &mut UpdateChunksType, pos1: IVec2, pos2: IVec2, dt: u8) { .dirty_render_rect_send .try_send(DeferredDirtyRectUpdate { chunk_pos: ChunkPos::new(pos.try_into().unwrap(), chunk), + ..Default::default() }) .unwrap(); } @@ -92,12 +93,19 @@ pub fn chunk_to_global(pos: ChunkPos) -> IVec2 { /// See if position is swapable, that means it sees if the position is a void /// or if it's a swapable state and has been not updated -pub fn swapable(chunks: &UpdateChunksType, pos: IVec2, states: &[(State, f32)], dt: u8) -> bool { +pub fn swapable( + chunks: &UpdateChunksType, + pos: IVec2, + states: &[(State, f32)], + state: State, + dt: u8, +) -> bool { if let Some(atom) = chunks.group.get_global(pos) { atom.state == State::Void || (states.iter().any(|&(state, prob)| { state == atom.state && rand::thread_rng().gen_range(0.0..1.0) < prob }) && atom.updated_at != dt) + || (atom.state == State::Object && state == State::Liquid) } else { false } @@ -112,8 +120,9 @@ pub fn down_neigh( ) -> [(bool, IVec2); 3] { let mut neigh = [(false, IVec2::ZERO); 3]; + let state = get_state(chunks, pos); for (neigh, x) in neigh.iter_mut().zip([0, -1, 1]) { - neigh.0 = swapable(chunks, pos + IVec2::new(x, 1), states, dt); + neigh.0 = swapable(chunks, pos + IVec2::new(x, 1), states, state, dt); neigh.1 = IVec2::new(x, 1); } @@ -133,8 +142,9 @@ pub fn side_neigh( ) -> [(bool, IVec2); 2] { let mut neigh = [(false, IVec2::ZERO); 2]; + let state = get_state(chunks, pos); for (neigh, x) in neigh.iter_mut().zip([-1, 1]) { - neigh.0 = swapable(chunks, pos + IVec2::new(x, 0), states, dt); + neigh.0 = swapable(chunks, pos + IVec2::new(x, 0), states, state, dt); neigh.1 = IVec2::new(x, 0); } @@ -165,19 +175,24 @@ pub fn set_vel(chunks: &mut UpdateChunksType, pos: IVec2, velocity: IVec2) { } } +/// Sets mode from a global pos +pub fn set_mode(chunks: &mut UpdateChunksType, pos: IVec2, mode: bool) { + chunks.group[pos].automata_mode = mode +} + /// Gets fall speed from a global pos pub fn get_fspeed(chunks: &UpdateChunksType, pos: IVec2) -> u8 { - chunks.group[pos].fall_speed + chunks.group[pos].velocity.1.try_into().unwrap() } /// Gets state from a global pos -pub fn _get_state(chunks: &UpdateChunksType, pos: IVec2) -> State { +pub fn get_state(chunks: &UpdateChunksType, pos: IVec2) -> State { chunks.group[pos].state } /// Sets fall speed from a global pos pub fn set_fspeed(chunks: &mut UpdateChunksType, pos: IVec2, fall_speed: u8) { - chunks.group[pos].fall_speed = fall_speed + chunks.group[pos].velocity.1 = fall_speed as i8 } /// Checks if atom is able to update this frame from a global pos @@ -344,7 +359,7 @@ pub fn update_dirty_rects_3x3(dirty_rects: &mut HashMap, pos: Chun } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct ChunkPos { pub atom: UVec2, pub chunk: IVec2, @@ -358,7 +373,8 @@ impl ChunkPos { /// A deferred update message. /// Indicates that an image or dirty rect should update. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct DeferredDirtyRectUpdate { pub chunk_pos: ChunkPos, + pub awake_surrouding: bool, } diff --git a/src/player.rs b/src/player.rs index f549243..5824ee3 100644 --- a/src/player.rs +++ b/src/player.rs @@ -40,7 +40,7 @@ pub fn player_setup( TextureAtlas::from_grid(player_handle, Vec2::new(24.0, 24.0), 8, 5, None, None); let player_atlas_handle = texture_atlases.add(player_atlas); let animation_indices = AnimationIndices { first: 0, last: 1 }; - let mut player_transform = Transform::from_scale(Vec3::splat(3.0)); + let mut player_transform = Transform::from_scale(Vec3::splat(ATOM_SIZE as f32)); player_transform.translation = vec2(5. * 3., -8. * 3.).extend(2.); let tool_handle = asset_server.load("player/player_tool.png"); @@ -88,7 +88,7 @@ pub fn update_player( mut tool: Query<(&mut Transform, &GlobalTransform, &mut Sprite, &mut Tool)>, mut camera_q: Query<(&Camera, &GlobalTransform, &mut Transform), Without>, mut chunk_manager: ResMut, - mut dirty_rects: Query<&mut DirtyRects>, + mut dirty_rects: ResMut, ) { let (mut actor, mut player, mut textatlas_sprite, mut anim_idxs) = player.single_mut(); let (mut tool_transform, tool_gtransform, mut tool_sprite, mut tool) = tool.single_mut(); @@ -179,7 +179,6 @@ pub fn update_player( let mut pos_to_update = vec![]; if mouse.pressed(MouseButton::Right) { - println!("lol"); let new_tool_front = tool_front + tool_slope * 6.; for i in 0..3 { for vec in Line::new( @@ -222,7 +221,6 @@ pub fn update_player( } } - let mut dirty_rects = dirty_rects.single_mut(); for pos in pos_to_update { update_dirty_rects_3x3(&mut dirty_rects.current, pos); update_dirty_rects(&mut dirty_rects.render, pos);