diff --git a/src/components/tilemap.rs b/src/components/tilemap.rs index 6caf40e..81f93a0 100644 --- a/src/components/tilemap.rs +++ b/src/components/tilemap.rs @@ -8,6 +8,7 @@ use crate::stage::{BackgroundType, Stage, StageTexturePaths}; pub struct Tilemap { tick: u32, prev_tick: u32, + pub no_water: bool, } #[derive(Debug, PartialEq, Eq, Copy, Clone)] @@ -20,7 +21,7 @@ pub enum TileLayer { impl Tilemap { pub fn new() -> Self { - Tilemap { tick: 0, prev_tick: 0 } + Tilemap { tick: 0, prev_tick: 0, no_water: false } } pub fn tick(&mut self) -> GameResult { @@ -160,7 +161,7 @@ impl Tilemap { batch.draw(ctx)?; - if layer == TileLayer::Foreground && stage.data.background_type == BackgroundType::Water { + if !self.no_water && layer == TileLayer::Foreground && stage.data.background_type == BackgroundType::Water { let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &textures.background)?; let rect_top = Rect { left: 0, top: 0, right: 32, bottom: 16 }; let rect_middle = Rect { left: 0, top: 16, right: 32, bottom: 48 }; diff --git a/src/components/water_renderer.rs b/src/components/water_renderer.rs index 617139d..edf8cea 100644 --- a/src/components/water_renderer.rs +++ b/src/components/water_renderer.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; use crate::common::{Color, Rect}; -use crate::entity::GameEntity; use crate::frame::Frame; use crate::framework::backend::{BackendShader, SpriteBatchCommand, VertexData}; use crate::framework::context::Context; @@ -13,11 +12,18 @@ use crate::npc::list::NPCList; use crate::physics::PhysicalEntity; use crate::player::Player; use crate::shared_game_state::SharedGameState; +use crate::stage::{BackgroundType, Stage}; const TENSION: f32 = 0.03; const DAMPENING: f32 = 0.01; const SPREAD: f32 = 0.02; +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum WaterLayer { + Front, + Back, +} + struct DynamicWaterColumn { target_height: f32, height: f32, @@ -36,9 +42,9 @@ impl DynamicWaterColumn { } pub struct DynamicWater { - x: u16, - y: u16, - end_x: u16, + x: f32, + y: f32, + end_x: f32, columns: Vec, color: WaterParamEntry, } @@ -52,7 +58,7 @@ impl DynamicWater { columns.push(DynamicWaterColumn::new()); } - DynamicWater { x, y, end_x: x + length, columns, color } + DynamicWater { x: x as f32 * 16.0, y: y as f32 * 16.0, end_x: (x + length) as f32 * 16.0, columns, color } } pub fn tick(&mut self) { @@ -93,20 +99,88 @@ impl DynamicWater { } } } + + pub fn interact(&mut self, players: &[&Player], npc_list: &NPCList) { + let cols_i32 = self.columns.len() as i32; + + let mut tick_object = |obj: &dyn PhysicalEntity| { + let obj_x = obj.x() as f32 / 512.0 + 8.0; + let obj_y = obj.y() as f32 / 512.0 + 8.0; + + if (obj.vel_y() > 0x80 || obj.vel_y() < -0x80) + && obj_x > self.x + && obj_x < self.end_x as f32 + && obj_y > self.y - 5.0 + && obj_y < self.y + 4.0 + { + let col_idx_center = (((obj_x - self.x) / 2.0) as i32).clamp(0, cols_i32); + let col_idx_left = + (col_idx_center - (obj.hit_bounds().left as i32 / (8 * 0x200))).clamp(0, cols_i32) as usize; + let col_idx_right = + (col_idx_center + (obj.hit_bounds().left as i32 / (8 * 0x200))).clamp(0, cols_i32) as usize; + + for col in &mut self.columns[col_idx_left..=col_idx_right] { + col.speed = (obj.vel_y() as f32 / 512.0) * (obj.hit_rect_size() as f32 * 0.25).clamp(0.1, 1.0); + } + } + }; + + for player in players { + tick_object(*player); + } + + for npc in npc_list.iter_alive() { + static NO_COLL_NPCS: [u16; 6] = [0, 3, 4, 18, 191, 195]; + if NO_COLL_NPCS.contains(&npc.npc_type) { + continue; + } + + tick_object(npc); + } + } +} + +pub struct DepthRegion { + rect: Rect, + color: WaterParamEntry, +} + +impl DepthRegion { + pub fn new_tile(rect: Rect, color: WaterParamEntry) -> DepthRegion { + DepthRegion { + rect: Rect { + left: rect.left as f32 * 16.0, + top: rect.top as f32 * 16.0, + right: rect.right as f32 * 16.0, + bottom: rect.bottom as f32 * 16.0, + }, + color, + } + } + + pub fn new(rect: Rect, color: WaterParamEntry) -> DepthRegion { + DepthRegion { rect, color } + } } pub struct WaterRenderer { - depth_regions: Vec<(Rect, WaterParamEntry)>, + depth_regions: Vec, water_surfaces: Vec, + core_water: Option<(DynamicWater, DepthRegion)>, t: RefCell, } impl WaterRenderer { pub fn new() -> WaterRenderer { - WaterRenderer { depth_regions: Vec::new(), water_surfaces: Vec::new(), t: RefCell::new(0) } + WaterRenderer { depth_regions: Vec::new(), water_surfaces: Vec::new(), core_water: None, t: RefCell::new(0) } } - pub fn initialize(&mut self, regions: Vec<(WaterRegionType, Rect, u8)>, water_params: &WaterParams) { + pub fn initialize( + &mut self, + regions: Vec<(WaterRegionType, Rect, u8)>, + water_params: &WaterParams, + stage: &Stage, + ) { for (reg_type, bounds, color_idx) in regions { let color = water_params.get_entry(color_idx); @@ -115,64 +189,56 @@ impl WaterRenderer { self.water_surfaces.push(DynamicWater::new(bounds.left, bounds.top, bounds.width() + 1, *color)); } WaterRegionType::WaterDepth => { - self.depth_regions.push((bounds, *color)); + self.depth_regions.push(DepthRegion::new_tile(bounds, *color)); } } } + + if stage.data.background_type == BackgroundType::Water { + let core_water_color = water_params.get_entry(0); + self.core_water = Some(( + DynamicWater::new(0, 32768, stage.map.width, *core_water_color), + DepthRegion::new( + Rect { + left: 0.0, + top: stage.map.height as f32 * 16.0, + right: stage.map.width as f32 * 16.0, + bottom: stage.map.height as f32 * 16.0 + 1.0, + }, + *core_water_color, + ), + )); + } } -} -impl GameEntity<(&[&Player], &NPCList)> for WaterRenderer { - fn tick(&mut self, state: &mut SharedGameState, (players, npc_list): (&[&Player], &NPCList)) -> GameResult<()> { + pub fn tick(&mut self, state: &mut SharedGameState, (players, npc_list): (&[&Player], &NPCList)) -> GameResult<()> { for surf in &mut self.water_surfaces { - let line_x = surf.x as f32 * 16.0; - let line_y = surf.y as f32 * 16.0; - - let mut tick_object = |obj: &dyn PhysicalEntity| { - let obj_x = obj.x() as f32 / 512.0 + 8.0; - let obj_y = obj.y() as f32 / 512.0 + 8.0; - - if (obj.vel_y() > 0x80 || obj.vel_y() < -0x80) - && obj_x > line_x - && obj_x < surf.end_x as f32 * 16.0 - && obj_y > line_y - 5.0 - && obj_y < line_y + 4.0 - { - let col_idx_center = (((obj_x - line_x) / 2.0) as i32).clamp(0, surf.columns.len() as i32); - let col_idx_left = (col_idx_center - (obj.hit_bounds().left as i32 / (8 * 0x200))) - .clamp(0, surf.columns.len() as i32) as usize; - let col_idx_right = (col_idx_center + (obj.hit_bounds().left as i32 / (8 * 0x200))) - .clamp(0, surf.columns.len() as i32) as usize; - - for col in &mut surf.columns[col_idx_left..=col_idx_right] { - col.speed = (obj.vel_y() as f32 / 1024.0) * (obj.hit_rect_size() as f32 * 0.25).clamp(0.1, 1.0); - } - } - }; - - for player in players { - tick_object(*player); - } - - for npc in npc_list.iter_alive() { - static NO_COLL_NPCS: [u16; 3] = [0, 3, 4]; - if NO_COLL_NPCS.contains(&npc.npc_type) { - continue; - } - - tick_object(npc); - } - + surf.interact(players, npc_list); surf.tick(); } + if let Some((ref mut core_water, ref mut core_depth)) = &mut self.core_water { + let level = state.water_level as f32 / 512.0 + 8.0; + core_water.y = level; + core_depth.rect.top = (level + 16.0).min(core_depth.rect.bottom); + + core_water.interact(players, npc_list); + core_water.tick(); + } + let mut t_ref = self.t.borrow_mut(); *t_ref = t_ref.wrapping_add(1); Ok(()) } - fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> { + pub fn draw( + &self, + state: &mut SharedGameState, + ctx: &mut Context, + frame: &Frame, + layer: WaterLayer, + ) -> GameResult<()> { if !graphics::supports_vertex_draw(ctx)? { return Ok(()); } @@ -182,74 +248,98 @@ impl GameEntity<(&[&Player], &NPCList)> for WaterRenderer { graphics::set_blend_mode(ctx, BlendMode::None)?; let (o_x, o_y) = frame.xy_interpolated(state.frame_time); - let uv = (0.5, 0.5); + let uv = (0.0, 0.0); let t = *self.t.borrow_mut() as f32 + state.frame_time as f32; let shader = BackendShader::WaterFill(state.scale, t, (o_x, o_y)); + let mut vertices = Vec::new(); - for (region, color) in &self.depth_regions { - let color_mid_rgba = color.color_middle.to_rgba(); - let color_btm_rgba = color.color_bottom.to_rgba(); - let mut vertices = vec![]; - vertices.reserve(6); + { + let mut draw_region = |region: &DepthRegion| -> GameResult { + let color_mid_rgba = region.color.color_middle.to_rgba(); + let color_btm_rgba = region.color.color_bottom.to_rgba(); + vertices.clear(); + vertices.reserve(6); - let left = (region.left as f32 * 16.0 - o_x - 8.0) * state.scale; - let top = (region.top as f32 * 16.0 - o_y - 8.0) * state.scale; - let right = (region.right as f32 * 16.0 - o_x + 8.0) * state.scale; - let bottom = (region.bottom as f32 * 16.0 - o_y + 8.0) * state.scale; + let left = (region.rect.left - o_x - 8.0) * state.scale; + let top = (region.rect.top - o_y - 8.0) * state.scale; + let right = (region.rect.right - o_x + 8.0) * state.scale; + let bottom = (region.rect.bottom - o_y + 8.0) * state.scale; - vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba }); - vertices.push(VertexData { position: (left, top), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba }); - vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (right, bottom), uv, color: color_btm_rgba }); + vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba }); + vertices.push(VertexData { position: (left, top), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (left, bottom), uv, color: color_btm_rgba }); + vertices.push(VertexData { position: (right, top), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (right, bottom), uv, color: color_btm_rgba }); - graphics::draw_triangle_list(ctx, vertices, None, shader)?; + graphics::draw_triangle_list(ctx, &vertices, None, shader)?; + Ok(()) + }; + + if layer == WaterLayer::Back { + for region in &self.depth_regions { + draw_region(region)?; + } + } else if let Some((_, ref core_depth)) = &self.core_water { + draw_region(core_depth)?; + } } - for surf in &self.water_surfaces { - let pos_x = surf.x as f32 * 16.0; - let pos_y = surf.y as f32 * 16.0; - let color_top_rgba = surf.color.color_top.to_rgba(); - let color_mid_rgba = surf.color.color_middle.to_rgba(); - let color_btm_rgba = surf.color.color_bottom.to_rgba(); + { + let mut draw_region = |surf: &DynamicWater| -> GameResult { + let pos_x = surf.x; + let pos_y = surf.y; + let color_top_rgba = surf.color.color_top.to_rgba(); + let color_mid_rgba = surf.color.color_middle.to_rgba(); + let color_btm_rgba = surf.color.color_bottom.to_rgba(); - if (pos_x - o_x - 16.0) > state.canvas_size.0 - || (pos_x - o_x + 16.0 + surf.end_x as f32 * 16.0) < 0.0 - || (pos_y - o_y - 16.0) > state.canvas_size.1 - || (pos_y - o_y + 16.0) < 0.0 - { - continue; + if (pos_x - o_x - 16.0) > state.canvas_size.0 + || (pos_x - o_x + 16.0 + surf.end_x) < 0.0 + || (pos_y - o_y - 16.0) > state.canvas_size.1 + || (pos_y - o_y + 16.0) < 0.0 + { + return Ok(()); + } + + vertices.clear(); + vertices.reserve(12 * surf.columns.len()); + + let bottom = (pos_y - o_y + 8.0) * state.scale; + for i in 1..surf.columns.len() { + let x_right = (pos_x - 8.0 - o_x + i as f32 * 2.0) * state.scale; + let x_left = x_right - 2.0 * state.scale; + let top_left = (pos_y - o_y - 13.0 + surf.columns[i - 1].height) * state.scale; + let top_right = (pos_y - o_y - 13.0 + surf.columns[i].height) * state.scale; + let middle_left = top_left + 6.0 * state.scale; + let middle_right = top_left + 6.0 * state.scale; + + vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (x_left, top_left), uv, color: color_top_rgba }); + vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba }); + vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba }); + vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba }); + + vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba }); + vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba }); + vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba }); + vertices.push(VertexData { position: (x_right, bottom), uv, color: color_btm_rgba }); + } + + graphics::draw_triangle_list(ctx, &vertices, None, shader)?; + + Ok(()) + }; + + if layer == WaterLayer::Back { + for surf in &self.water_surfaces { + draw_region(surf)?; + } + } else if let Some((ref surf, _)) = &self.core_water { + draw_region(surf)?; } - - let mut vertices = vec![]; - vertices.reserve(12 * surf.columns.len()); - - let bottom = (pos_y - o_y + 8.0) * state.scale; - for i in 1..surf.columns.len() { - let x_right = (pos_x - 8.0 - o_x + i as f32 * 2.0) * state.scale; - let x_left = x_right - 2.0 * state.scale; - let top_left = (pos_y - o_y - 13.0 + surf.columns[i - 1].height) * state.scale; - let top_right = (pos_y - o_y - 13.0 + surf.columns[i].height) * state.scale; - let middle_left = top_left + 6.0 * state.scale; - let middle_right = top_left + 6.0 * state.scale; - - vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (x_left, top_left), uv, color: color_top_rgba }); - vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba }); - vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (x_right, top_right), uv, color: color_top_rgba }); - vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba }); - - vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba }); - vertices.push(VertexData { position: (x_left, middle_left), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (x_left, bottom), uv, color: color_btm_rgba }); - vertices.push(VertexData { position: (x_right, middle_right), uv, color: color_mid_rgba }); - vertices.push(VertexData { position: (x_right, bottom), uv, color: color_btm_rgba }); - } - - graphics::draw_triangle_list(ctx, vertices, None, shader)?; } graphics::set_blend_mode(ctx, BlendMode::Alpha)?; diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 805efee..0341b3f 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -20,7 +20,7 @@ use crate::components::nikumaru::NikumaruCounter; use crate::components::stage_select::StageSelect; use crate::components::text_boxes::TextBoxes; use crate::components::tilemap::{TileLayer, Tilemap}; -use crate::components::water_renderer::WaterRenderer; +use crate::components::water_renderer::{WaterLayer, WaterRenderer}; use crate::components::whimsical_star::WhimsicalStar; use crate::entity::GameEntity; use crate::frame::{Frame, UpdateTarget}; @@ -111,6 +111,7 @@ impl GameScene { pub fn from_stage(state: &mut SharedGameState, ctx: &mut Context, stage: Stage, id: usize) -> GameResult { let mut water_params = WaterParams::new(); let mut water_renderer = WaterRenderer::new(); + let mut tilemap = Tilemap::new(); if !state.settings.original_textures { if let Ok(water_param_file) = filesystem::open_find( @@ -122,7 +123,8 @@ impl GameScene { info!("Loaded water parameters file."); let regions = stage.map.find_water_regions(&water_params); - water_renderer.initialize(regions, &water_params); + water_renderer.initialize(regions, &water_params, &stage); + tilemap.no_water = true; } } @@ -153,7 +155,7 @@ impl GameScene { nikumaru: NikumaruCounter::new(), whimsical_star: WhimsicalStar::new(), background: Background::new(), - tilemap: Tilemap::new(), + tilemap, text_boxes: TextBoxes::new(), fade: Fade::new(), frame: Frame::new(), @@ -1816,9 +1818,10 @@ impl Scene for GameScene { self.whimsical_star.draw(state, ctx, &self.frame)?; } - self.water_renderer.draw(state, ctx, &self.frame)?; + self.water_renderer.draw(state, ctx, &self.frame, WaterLayer::Back)?; self.tilemap.draw(state, ctx, &self.frame, TileLayer::Foreground, stage_textures_ref, &self.stage)?; self.tilemap.draw(state, ctx, &self.frame, TileLayer::Snack, stage_textures_ref, &self.stage)?; + self.water_renderer.draw(state, ctx, &self.frame, WaterLayer::Front)?; self.draw_carets(state, ctx)?; self.player1.popup.draw(state, ctx, &self.frame)?;