diff --git a/src/builtin/lightmap/spot.png b/src/builtin/lightmap/spot.png new file mode 100644 index 0000000..f5e807d Binary files /dev/null and b/src/builtin/lightmap/spot.png differ diff --git a/src/builtin_fs.rs b/src/builtin_fs.rs index d253431..7741515 100644 --- a/src/builtin_fs.rs +++ b/src/builtin_fs.rs @@ -111,6 +111,9 @@ impl BuiltinFS { FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")), FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")), FSNode::File("pixtone.pcm", include_bytes!("builtin/pixtone.pcm")), + FSNode::Directory("lightmap", vec![ + FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png")), + ]), ]) ], } diff --git a/src/main.rs b/src/main.rs index d469765..1ab0328 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ use crate::ggez::{Context, ContextBuilder, filesystem, GameResult}; use crate::ggez::conf::{WindowMode, WindowSetup}; use crate::ggez::event::{KeyCode, KeyMods}; use crate::ggez::graphics; -use crate::ggez::graphics::DrawParam; +use crate::ggez::graphics::{Canvas, DrawParam}; use crate::ggez::input::keyboard; use crate::ggez::mint::ColumnMatrix4; use crate::ggez::nalgebra::Vector2; @@ -217,6 +217,7 @@ pub fn main() -> GameResult { WindowEvent::CloseRequested => { game.state.shutdown(); } WindowEvent::Resized(_) => { game.state.handle_resize(ctx).unwrap(); + game.state.lightmap_canvas = Canvas::with_window_size(ctx).unwrap(); gfx_window_glutin::update_views(graphics::window(ctx), &mut game.ui.main_color, &mut game.ui.main_depth); } WindowEvent::KeyboardInput { diff --git a/src/menu.rs b/src/menu.rs index 9123f00..8ac7624 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -19,10 +19,10 @@ pub struct Menu { pub y: isize, pub width: usize, pub height: usize, - selected: usize, + pub selected: usize, + pub entries: Vec, anim_num: u16, anim_wait: u16, - entries: Vec, } static QUOTE_FRAMES: [usize; 4] = [0, 1, 0, 2]; diff --git a/src/npc/misc.rs b/src/npc/misc.rs index 8fd4941..37436c1 100644 --- a/src/npc/misc.rs +++ b/src/npc/misc.rs @@ -190,6 +190,7 @@ impl NPC { } 2 => { self.anim_rect = state.constants.npc.n017_health_refill[0]; + self.anim_num = 0; if self.action_counter > 0 { self.action_counter -= 1; @@ -201,8 +202,10 @@ impl NPC { self.anim_counter += 1; if self.anim_counter % 2 == 0 { + self.anim_num = 1; self.anim_rect = state.constants.npc.n017_health_refill[1]; } else { + self.anim_num = 0; self.anim_rect = state.constants.npc.n017_health_refill[0]; } @@ -213,6 +216,7 @@ impl NPC { } } 4 => { + self.anim_num = 1; self.anim_rect = state.constants.npc.n017_health_refill[1]; if self.action_counter > 0 { diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index b9f0f2e..5d03aad 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -6,7 +6,7 @@ use crate::common::{Direction, FadeDirection, FadeState, Rect}; use crate::entity::GameEntity; use crate::frame::Frame; use crate::ggez::{Context, GameResult, graphics, timer}; -use crate::ggez::graphics::Color; +use crate::ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, Vector2}; use crate::ggez::nalgebra::clamp; use crate::inventory::{Inventory, TakeExperienceResult}; use crate::npc::NPCMap; @@ -16,6 +16,7 @@ use crate::scene::Scene; use crate::shared_game_state::SharedGameState; use crate::stage::{BackgroundType, Stage}; use crate::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptVM}; +use crate::texture_set::SizedBatch; use crate::ui::Components; use crate::weapon::WeaponType; @@ -518,6 +519,136 @@ impl GameScene { Ok(()) } + fn draw_light(&self, x: f32, y: f32, size: f32, color: (u8, u8, u8), batch: &mut SizedBatch) { + batch.add_rect_scaled_tinted(x - size * 32.0, y - size * 32.0, color, + size, + size, + &Rect::new(0, 0, 64, 64)) + } + + fn draw_light_map(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + graphics::set_canvas(ctx, Some(&state.lightmap_canvas)); + graphics::set_blend_mode(ctx, BlendMode::Add)?; + + graphics::clear(ctx, Color::from_rgb(130, 130, 130)); + { + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?; + + if state.control_flags.control_enabled() { + self.draw_light(((self.player.x - self.frame.x) / 0x200) as f32, + ((self.player.y - self.frame.y) / 0x200) as f32, + 3.0, (255, 255, 255), batch); + } + + for bullet in self.bullet_manager.bullets.iter() { + self.draw_light(((bullet.x - self.frame.x) / 0x200) as f32, + ((bullet.y - self.frame.y) / 0x200) as f32, + 1.0, (200, 200, 200), batch); + } + + for caret in state.carets.iter() { + match caret.ctype { + CaretType::ProjectileDissipation | CaretType::Shoot => { + self.draw_light(((caret.x - self.frame.x) / 0x200) as f32, + ((caret.y - self.frame.y) / 0x200) as f32, + 1.0, (200, 200, 200), batch); + } + CaretType::LevelUp if caret.direction == Direction::Left => { + self.draw_light(((caret.x - self.frame.x) / 0x200) as f32, + ((caret.y - self.frame.y) / 0x200) as f32, + 2.0, (0, 100, 160), batch); + } + CaretType::LevelUp if caret.direction == Direction::Right => { + self.draw_light(((caret.x - self.frame.x) / 0x200) as f32, + ((caret.y - self.frame.y) / 0x200) as f32, + 2.0, (255, 30, 30), batch); + } + _ => {} + } + } + + for npc_cell in self.npc_map.npcs.values() { + let npc = npc_cell.borrow(); + + if npc.x < (self.frame.x - 128 * 0x200) || npc.x > (self.frame.x + (state.canvas_size.0 as isize + 128) * 0x200) + && npc.y < (self.frame.y - 128 * 0x200) || npc.y > (self.frame.y + (state.canvas_size.1 as isize + 128) * 0x200) { + continue; + } + + match npc.npc_type { + 1 => self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 0.5, (255, 150, 0), batch), + 4 | 7 => self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 1.0, (200, 200, 200), batch), + 17 if npc.anim_num == 0 => + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 2.0, (255, 30, 30), batch), + 20 if npc.direction == Direction::Right => { + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 2.0, (0, 0, 255), batch); + + if npc.anim_num < 2 { + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 0.5, (0, 0, 255), batch); + } + } + 22 if npc.action_num == 1 && npc.anim_num == 1 => + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 3.0, (0, 0, 255), batch), + 32 => { + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 3.0, (255, 0, 0), batch); + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 2.0, (255, 0, 0), batch); + } + 38 => { + let flicker = 200 + state.effect_rng.range(0..55) as u8; + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 2.0, (flicker, flicker - 80, 0), batch); + } + 66 if npc.action_num == 1 && npc.anim_counter % 2 == 0 => + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 3.0, (0, 100, 255), batch), + 67 => self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 2.0, (0, 100, 200), batch), + 70 => { + let flicker = 100 + npc.anim_num as u8 * 50; + self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 2.0, (flicker, flicker, flicker), batch); + } + 75 | 77 => self.draw_light(((npc.x - self.frame.x) / 0x200) as f32, + ((npc.y - self.frame.y) / 0x200) as f32, + 3.0, (255, 100, 0), batch), + _ => {} + } + } + + batch.draw_filtered(FilterMode::Linear, ctx)?; + } + + graphics::set_canvas(ctx, None); + graphics::set_blend_mode(ctx, BlendMode::Multiply)?; + state.lightmap_canvas.set_filter(FilterMode::Linear); + state.lightmap_canvas.draw(ctx, DrawParam::new() + .scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?; + + graphics::set_blend_mode(ctx, BlendMode::Alpha)?; + + Ok(()) + } + fn draw_tiles(&self, state: &mut SharedGameState, ctx: &mut Context, layer: TileLayer) -> GameResult { let tex = match layer { TileLayer::Snack => "Npc/NpcSym", @@ -849,6 +980,9 @@ impl Scene for GameScene { self.draw_tiles(state, ctx, TileLayer::Foreground)?; self.draw_tiles(state, ctx, TileLayer::Snack)?; self.draw_carets(state, ctx)?; + if state.enhanced_graphics { + self.draw_light_map(state, ctx)?; + } self.draw_black_bars(state, ctx)?; if state.control_flags.control_enabled() { diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 8b8f9de..1b95540 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -26,8 +26,8 @@ impl TitleScene { Self { tick: 0, current_menu: CurrentMenu::MainMenu, - main_menu: Menu::new(0, 0, 100, 5 * 14 + 6), - option_menu: Menu::new(0, 0, 180, 5 * 14 + 6), + main_menu: Menu::new(0, 0, 100, 1 * 14 + 6), + option_menu: Menu::new(0, 0, 180, 1 * 14 + 6), } } @@ -89,13 +89,16 @@ impl Scene for TitleScene { self.main_menu.push_entry(MenuEntry::Active("Options".to_string())); self.main_menu.push_entry(MenuEntry::Disabled("Editor".to_string())); self.main_menu.push_entry(MenuEntry::Active("Quit".to_string())); + self.main_menu.height = self.main_menu.entries.len() * 14 + 6; self.option_menu.push_entry(MenuEntry::Toggle("50 FPS timing".to_string(), state.timing_mode == TimingMode::_50Hz)); self.option_menu.push_entry(MenuEntry::Toggle("Linear scaling".to_string(), ctx.filter_mode == FilterMode::Linear)); + self.option_menu.push_entry(MenuEntry::Toggle("Enhanced graphics".to_string(), state.enhanced_graphics)); self.option_menu.push_entry(MenuEntry::Toggle("2x Speed hack".to_string(), state.speed_hack)); self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string())); self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned())); self.option_menu.push_entry(MenuEntry::Active("Back".to_string())); + self.option_menu.height = self.option_menu.entries.len() * 14 + 6; } self.main_menu.x = ((state.canvas_size.0 - self.main_menu.width as f32) / 2.0).floor() as isize; @@ -152,16 +155,24 @@ impl Scene for TitleScene { } MenuSelectionResult::Selected(2, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { - *value = !(*value); - state.set_speed_hack(*value); + state.enhanced_graphics = !state.enhanced_graphics; + + *value = state.enhanced_graphics; } } - MenuSelectionResult::Selected(3, _) => { + MenuSelectionResult::Selected(3, toggle) => { + if let MenuEntry::Toggle(_, value) = toggle { + state.set_speed_hack(!state.speed_hack); + + *value = state.speed_hack; + } + } + MenuSelectionResult::Selected(4, _) => { if let Err(e) = webbrowser::open(DISCORD_LINK) { log::warn!("Error opening web browser: {}", e); } } - MenuSelectionResult::Selected(5, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(6, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::MainMenu; } _ => {} diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index ee6de91..e16471e 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -18,6 +18,7 @@ use crate::stage::StageData; use crate::str; use crate::text_script::{TextScriptExecutionState, TextScriptVM}; use crate::texture_set::TextureSet; +use crate::ggez::graphics::Canvas; #[derive(PartialEq, Eq, Copy, Clone)] pub enum TimingMode { @@ -58,6 +59,8 @@ pub struct SharedGameState { pub scale: f32, pub god_mode: bool, pub speed_hack: bool, + pub enhanced_graphics: bool, + pub lightmap_canvas: Canvas, pub canvas_size: (f32, f32), pub screen_size: (f32, f32), pub next_scene: Option>, @@ -115,6 +118,8 @@ impl SharedGameState { scale, god_mode: false, speed_hack: false, + enhanced_graphics: true, + lightmap_canvas: Canvas::with_window_size(ctx)?, screen_size, canvas_size, next_scene: None, diff --git a/src/texture_set.rs b/src/texture_set.rs index a46fe55..f2dafe1 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -26,26 +26,32 @@ pub struct SizedBatch { } impl SizedBatch { + #[inline(always)] pub fn width(&self) -> usize { self.width } + #[inline(always)] pub fn height(&self) -> usize { self.height } + #[inline(always)] pub fn dimensions(&self) -> (usize, usize) { (self.width, self.height) } + #[inline(always)] pub fn real_dimensions(&self) -> (usize, usize) { (self.real_width, self.real_height) } + #[inline(always)] pub fn to_rect(&self) -> common::Rect { common::Rect::::new(0, 0, self.width, self.height) } + #[inline(always)] pub fn clear(&mut self) { self.batch.clear(); } @@ -58,6 +64,7 @@ impl SizedBatch { self.batch.add(param); } + #[inline(always)] pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect) { self.add_rect_scaled(x, y, self.scale_x, self.scale_y, rect) } @@ -95,8 +102,13 @@ impl SizedBatch { self.batch.add(param); } + #[inline(always)] pub fn draw(&mut self, ctx: &mut Context) -> GameResult { - self.batch.set_filter(ctx.filter_mode); + self.draw_filtered(ctx.filter_mode, ctx) + } + + pub fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult { + self.batch.set_filter(filter); self.batch.draw(ctx, DrawParam::new())?; self.batch.clear(); Ok(())