diff --git a/src/components/inventory.rs b/src/components/inventory.rs index cd1d952..64afdf9 100644 --- a/src/components/inventory.rs +++ b/src/components/inventory.rs @@ -1,5 +1,5 @@ use crate::common::Rect; -use crate::components::draw_common::{Alignment, draw_number}; +use crate::components::draw_common::{draw_number, Alignment}; use crate::entity::GameEntity; use crate::frame::Frame; use crate::framework::context::Context; @@ -61,6 +61,15 @@ impl InventoryUI { fn get_item_event_number_action(&self, inventory: &Inventory) -> u16 { inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 6000).unwrap_or(6000) } + + fn exit(&mut self, state: &mut SharedGameState, player: &mut Player, inventory: &mut Inventory) { + self.focus = InventoryFocus::None; + inventory.current_item = 0; + self.text_y_pos = 16; + state.textscript_vm.reset(); + state.textscript_vm.set_mode(ScriptMode::Map); + player.controller.update_trigger(); + } } impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI { @@ -80,12 +89,7 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI { || player.controller.trigger_menu_back() || (state.settings.touch_controls && state.touch_controls.consume_click_in(slot_rect))) { - self.focus = InventoryFocus::None; - inventory.current_item = 0; - self.text_y_pos = 16; - state.textscript_vm.reset(); - state.textscript_vm.set_mode(ScriptMode::Map); - player.controller.update_trigger(); + self.exit(state, player, inventory); return Ok(()); } @@ -231,6 +235,7 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI { self.selected_weapon = i; inventory.current_weapon = i; state.textscript_vm.start_script(get_weapon_event_number(inventory)); + self.exit(state, player, inventory); } } diff --git a/src/framework/backend_glutin.rs b/src/framework/backend_glutin.rs index 89e6197..73d110b 100644 --- a/src/framework/backend_glutin.rs +++ b/src/framework/backend_glutin.rs @@ -128,6 +128,13 @@ fn get_insets() -> GameResult<(f32, f32, f32, f32)> { } } +fn get_scaled_size(width: u32, height: u32) -> (f32, f32) { + let scaled_height = ((height / 480).max(1) * 480) as f32; + let scaled_width = width as f32 * (scaled_height as f32 / height as f32); + + (scaled_width, scaled_height) +} + impl BackendEventLoop for GlutinEventLoop { fn run(&mut self, game: &mut Game, ctx: &mut Context) { let event_loop = EventLoop::new(); @@ -137,7 +144,8 @@ impl BackendEventLoop for GlutinEventLoop { { let size = window.window().inner_size(); - ctx.screen_size = (size.width.max(1) as f32, size.height.max(1) as f32); + ctx.real_screen_size = (size.width, size.height); + ctx.screen_size = get_scaled_size(size.width.max(1), size.height.max(1)); state_ref.handle_resize(ctx).unwrap(); } @@ -188,7 +196,8 @@ impl BackendEventLoop for GlutinEventLoop { imgui.io_mut().display_size = [size.width as f32, size.height as f32]; } - ctx.screen_size = (size.width as f32, size.height as f32); + ctx.real_screen_size = (size.width, size.height); + ctx.screen_size = get_scaled_size(size.width.max(1), size.height.max(1)); state_ref.handle_resize(ctx).unwrap(); } } @@ -197,19 +206,21 @@ impl BackendEventLoop for GlutinEventLoop { { let mut controls = &mut state_ref.touch_controls; let scale = state_ref.scale as f64; + let loc_x = (touch.location.x * ctx.screen_size.0 as f64 / ctx.real_screen_size.0 as f64) / scale; + let loc_y = (touch.location.y * ctx.screen_size.1 as f64 / ctx.real_screen_size.1 as f64) / scale; match touch.phase { TouchPhase::Started | TouchPhase::Moved => { if let Some(point) = controls.points.iter_mut().find(|p| p.id == touch.id) { point.last_position = point.position; - point.position = (touch.location.x / scale, touch.location.y / scale); + point.position = (loc_x, loc_y); } else { controls.touch_id_counter = controls.touch_id_counter.wrapping_add(1); let point = TouchPoint { id: touch.id, touch_id: controls.touch_id_counter, - position: (touch.location.x / scale, touch.location.y / scale), + position: (loc_x, loc_y), last_position: (0.0, 0.0), }; controls.points.push(point); diff --git a/src/framework/context.rs b/src/framework/context.rs index 4ca2e03..3a641c9 100644 --- a/src/framework/context.rs +++ b/src/framework/context.rs @@ -9,6 +9,7 @@ pub struct Context { pub(crate) filesystem: Filesystem, pub(crate) renderer: Option>, pub(crate) keyboard_context: KeyboardContext, + pub(crate) real_screen_size: (u32, u32), pub(crate) screen_size: (f32, f32), pub(crate) screen_insets: (f32, f32, f32, f32), } @@ -20,6 +21,7 @@ impl Context { filesystem: Filesystem::new(), renderer: None, keyboard_context: KeyboardContext::new(), + real_screen_size: (320, 240), screen_size: (320.0, 240.0), screen_insets: (0.0, 0.0, 0.0, 0.0), } diff --git a/src/framework/render_opengl.rs b/src/framework/render_opengl.rs index 2d7d5a7..c7516a7 100644 --- a/src/framework/render_opengl.rs +++ b/src/framework/render_opengl.rs @@ -2,6 +2,7 @@ use std::cell::{RefCell, UnsafeCell}; use std::ffi::{c_void, CStr}; use std::mem; use std::mem::MaybeUninit; +use std::ptr::null; use std::sync::Arc; use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert}; @@ -395,6 +396,9 @@ struct ImguiData { ebo: GLuint, font_texture: GLuint, font_tex_size: (f32, f32), + surf_framebuffer: GLuint, + surf_texture: GLuint, + last_size: (u32, u32), } impl ImguiData { @@ -409,6 +413,9 @@ impl ImguiData { ebo: 0, font_texture: 0, font_tex_size: (1.0, 1.0), + surf_framebuffer: 0, + surf_texture: 0, + last_size: (320, 240), } } @@ -510,7 +517,36 @@ impl ImguiData { atlas.tex_id = (self.font_texture as usize).into(); } - gl.gl.BindTexture(gl::TEXTURE_2D, current_texture as _); + let texture_id = return_param(|x| gl.gl.GenTextures(1, x)); + + gl.gl.BindTexture(gl::TEXTURE_2D, texture_id); + gl.gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _); + gl.gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); + + gl.gl.TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA as _, + 320 as _, + 240 as _, + 0, + gl::RGBA, + gl::UNSIGNED_BYTE, + null() as _, + ); + + gl.gl.BindTexture(gl::TEXTURE_2D, 0 as _); + + self.surf_texture = texture_id; + + let framebuffer_id = return_param(|x| gl.gl.GenFramebuffers(1, x)); + + gl.gl.BindFramebuffer(gl::FRAMEBUFFER, framebuffer_id); + gl.gl.FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, texture_id, 0); + let draw_buffers = [gl::COLOR_ATTACHMENT0]; + gl.gl.DrawBuffers(1, draw_buffers.as_ptr() as _); + + self.surf_framebuffer = framebuffer_id; } } } @@ -631,10 +667,36 @@ impl BackendRenderer for OpenGLRenderer { } } - if let Some((context, gl)) = self.get_context() { - unsafe { - gl.gl.Finish(); + let ImguiData { program_tex, surf_texture, tex_locs: Locs { proj_mtx, .. }, .. } = self.imgui_data; + unsafe { + if let Some((_, gl)) = self.get_context() { + gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0); + gl.gl.ClearColor(0.0, 0.0, 0.0, 1.0); + gl.gl.Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); + + let matrix = + [[2.0f32, 0.0, 0.0, 0.0], [0.0, -2.0, 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [-1.0, 1.0, 0.0, 1.0]]; + + gl.gl.UseProgram(program_tex); + gl.gl.UniformMatrix4fv(proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _); + + let color = (255, 255, 255, 255); + let vertices = vec![ + VertexData { position: (0.0, 1.0), uv: (0.0, 0.0), color }, + VertexData { position: (0.0, 0.0), uv: (0.0, 1.0), color }, + VertexData { position: (1.0, 0.0), uv: (1.0, 1.0), color }, + VertexData { position: (0.0, 1.0), uv: (0.0, 0.0), color }, + VertexData { position: (1.0, 0.0), uv: (1.0, 1.0), color }, + VertexData { position: (1.0, 1.0), uv: (1.0, 0.0), color }, + ]; + + self.draw_arrays_tex_id(gl::TRIANGLES, vertices, surf_texture, BackendShader::Texture)?; + + gl.gl.Finish(); + } + + if let Some((context, _)) = self.get_context() { (context.swap_buffers)(&mut context.user_data); } } @@ -645,11 +707,35 @@ impl BackendRenderer for OpenGLRenderer { fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult { if let Some((_, gl)) = self.get_context() { unsafe { + let (width_u, height_u) = (width as u32, height as u32); + if self.imgui_data.last_size != (width_u, height_u) { + gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0); + gl.gl.BindTexture(gl::TEXTURE_2D, self.imgui_data.surf_texture); + + gl.gl.TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA as _, + width_u as _, + height_u as _, + 0, + gl::RGBA, + gl::UNSIGNED_BYTE, + null() as _, + ); + + gl.gl.BindTexture(gl::TEXTURE_2D, 0 as _); + } + + gl.gl.BindFramebuffer(gl::FRAMEBUFFER, self.imgui_data.surf_framebuffer); + gl.gl.ClearColor(0.0, 0.0, 0.0, 0.0); + gl.gl.Clear(gl::COLOR_BUFFER_BIT); + gl.gl.ActiveTexture(gl::TEXTURE0); gl.gl.BlendEquation(gl::FUNC_ADD); gl.gl.BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); - gl.gl.Viewport(0, 0, width as _, height as _); + gl.gl.Viewport(0, 0, width_u as _, height_u as _); self.def_matrix = [ [2.0 / width, 0.0, 0.0, 0.0], @@ -677,7 +763,6 @@ impl BackendRenderer for OpenGLRenderer { fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult> { if let Some((_, gl)) = self.get_context() { unsafe { - let data = vec![0u8; width as usize * height as usize * 4]; let current_texture_id = return_param(|x| gl.gl.GetIntegerv(gl::TEXTURE_BINDING_2D, x)) as u32; let texture_id = return_param(|x| gl.gl.GenTextures(1, x)); @@ -694,7 +779,7 @@ impl BackendRenderer for OpenGLRenderer { 0, gl::RGBA, gl::UNSIGNED_BYTE, - data.as_ptr() as _, + null() as _, ); gl.gl.BindTexture(gl::TEXTURE_2D, current_texture_id); @@ -707,6 +792,8 @@ impl BackendRenderer for OpenGLRenderer { gl.gl.DrawBuffers(1, draw_buffers.as_ptr() as _); gl.gl.Viewport(0, 0, width as _, height as _); + gl.gl.ClearColor(0.0, 0.0, 0.0, 0.0); + gl.gl.Clear(gl::COLOR_BUFFER_BIT); gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0); // todo error checking: glCheckFramebufferStatus() @@ -805,10 +892,20 @@ impl BackendRenderer for OpenGLRenderer { ]; gl.gl.UseProgram(self.imgui_data.program_fill); - gl.gl.UniformMatrix4fv(self.imgui_data.fill_locs.proj_mtx, 1, gl::FALSE, self.curr_matrix.as_ptr() as _); + gl.gl.UniformMatrix4fv( + self.imgui_data.fill_locs.proj_mtx, + 1, + gl::FALSE, + self.curr_matrix.as_ptr() as _, + ); gl.gl.UseProgram(self.imgui_data.program_tex); gl.gl.Uniform1i(self.imgui_data.tex_locs.texture, 0); - gl.gl.UniformMatrix4fv(self.imgui_data.tex_locs.proj_mtx, 1, gl::FALSE, self.curr_matrix.as_ptr() as _); + gl.gl.UniformMatrix4fv( + self.imgui_data.tex_locs.proj_mtx, + 1, + gl::FALSE, + self.curr_matrix.as_ptr() as _, + ); gl.gl.BindFramebuffer(gl::FRAMEBUFFER, gl_texture.framebuffer_id); } else { @@ -829,7 +926,7 @@ impl BackendRenderer for OpenGLRenderer { gl::FALSE, self.def_matrix.as_ptr() as _, ); - gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0); + gl.gl.BindFramebuffer(gl::FRAMEBUFFER, self.imgui_data.surf_framebuffer); } } @@ -1076,6 +1173,23 @@ impl OpenGLRenderer { return Ok(()); } + let texture_id = if let Some(texture) = texture { + let gl_texture: &Box = std::mem::transmute(texture); + gl_texture.texture_id + } else { + 0 + }; + + self.draw_arrays_tex_id(vert_type, vertices, texture_id, shader) + } + + unsafe fn draw_arrays_tex_id( + &mut self, + vert_type: GLenum, + vertices: Vec, + texture: u32, + shader: BackendShader, + ) -> GameResult<()> { if let Some(gl) = GL_PROC.as_ref() { match shader { BackendShader::Fill => { @@ -1148,14 +1262,7 @@ impl OpenGLRenderer { } } - let texture_id = if let Some(texture) = texture { - let gl_texture: &Box = std::mem::transmute(texture); - gl_texture.texture_id - } else { - 0 - }; - - gl.gl.BindTexture(gl::TEXTURE_2D, texture_id); + gl.gl.BindTexture(gl::TEXTURE_2D, texture); gl.gl.BufferData( gl::ARRAY_BUFFER, (vertices.len() * mem::size_of::()) as _, diff --git a/src/lib.rs b/src/lib.rs index cbf458a..50669d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,16 +104,16 @@ impl Game { if let Some(scene) = self.scene.as_mut() { let state_ref = unsafe { &mut *self.state.get() }; - match state_ref.timing_mode { + match state_ref.settings.timing_mode { TimingMode::_50Hz | TimingMode::_60Hz => { let last_tick = self.next_tick; while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 10 { if (state_ref.settings.speed - 1.0).abs() < 0.01 { - self.next_tick += state_ref.timing_mode.get_delta() as u128; + self.next_tick += state_ref.settings.timing_mode.get_delta() as u128; } else { self.next_tick += - (state_ref.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128; + (state_ref.settings.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128; } self.loops += 1; } @@ -122,7 +122,7 @@ impl Game { log::warn!("Frame skip is way too high, a long system lag occurred?"); self.last_tick = self.start_time.elapsed().as_nanos(); self.next_tick = self.last_tick - + (state_ref.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128; + + (state_ref.settings.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128; self.loops = 0; } @@ -152,7 +152,7 @@ impl Game { return Ok(()); } - if state_ref.timing_mode != TimingMode::FrameSynchronized { + if state_ref.settings.timing_mode != TimingMode::FrameSynchronized { let mut elapsed = self.start_time.elapsed().as_nanos(); // Even with the non-monotonic Instant mitigation at the start of the event loop, there's still a chance of it not working. diff --git a/src/menu/settings_menu.rs b/src/menu/settings_menu.rs index 55ee07a..d5b3707 100644 --- a/src/menu/settings_menu.rs +++ b/src/menu/settings_menu.rs @@ -64,7 +64,7 @@ impl SettingsMenu { self.main.push_entry(MenuEntry::Options( "Game timing:".to_owned(), - if state.timing_mode == TimingMode::_50Hz { 0 } else { 1 }, + if state.settings.timing_mode == TimingMode::_50Hz { 0 } else { 1 }, vec!["50tps (freeware)".to_owned(), "60tps (CS+)".to_owned()], )); @@ -125,13 +125,13 @@ impl SettingsMenu { } MenuSelectionResult::Selected(2, toggle) => { if let MenuEntry::Options(_, value, _) = toggle { - match state.timing_mode { + match state.settings.timing_mode { TimingMode::_50Hz => { - state.timing_mode = TimingMode::_60Hz; + state.settings.timing_mode = TimingMode::_60Hz; *value = 1; } TimingMode::_60Hz => { - state.timing_mode = TimingMode::_50Hz; + state.settings.timing_mode = TimingMode::_50Hz; *value = 0; } _ => {} diff --git a/src/settings.rs b/src/settings.rs index d4417b4..a7dd550 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -6,6 +6,7 @@ use crate::input::keyboard_player_controller::KeyboardController; use crate::input::player_controller::PlayerController; use crate::input::touch_player_controller::TouchPlayerController; use crate::player::TargetPlayer; +use crate::shared_game_state::TimingMode; use crate::sound::InterpolationMode; #[derive(serde::Serialize, serde::Deserialize)] @@ -24,6 +25,8 @@ pub struct Settings { pub motion_interpolation: bool, pub touch_controls: bool, pub soundtrack: String, + #[serde(default = "default_timing")] + pub timing_mode: TimingMode, #[serde(default = "default_interpolation")] pub organya_interpolation: InterpolationMode, #[serde(default = "p1_default_keymap")] @@ -43,7 +46,10 @@ pub struct Settings { fn default_true() -> bool { true } #[inline(always)] -fn current_version() -> u32 { 3 } +fn current_version() -> u32 { 4 } + +#[inline(always)] +fn default_timing() -> TimingMode { TimingMode::_50Hz } #[inline(always)] fn default_interpolation() -> InterpolationMode { InterpolationMode::Linear } @@ -73,6 +79,11 @@ impl Settings { self.light_cone = true; } + if self.version == 3 { + self.version = 4; + self.timing_mode = default_timing(); + } + if self.version != initial_version { log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version); } @@ -112,6 +123,7 @@ impl Default for Settings { motion_interpolation: true, touch_controls: cfg!(target_os = "android"), soundtrack: "".to_string(), + timing_mode: default_timing(), organya_interpolation: InterpolationMode::Linear, player1_key_map: p1_default_keymap(), player2_key_map: p2_default_keymap(), diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 2b1cfad..e39b89d 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -32,7 +32,7 @@ use crate::str; use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM}; use crate::texture_set::TextureSet; -#[derive(PartialEq, Eq, Copy, Clone)] +#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] pub enum TimingMode { _50Hz, _60Hz, @@ -109,7 +109,6 @@ impl TileSize { } pub struct SharedGameState { - pub timing_mode: TimingMode, pub control_flags: ControlFlags, pub game_flags: BitVec, pub skip_flags: BitVec, @@ -212,7 +211,6 @@ impl SharedGameState { init_hooks(); Ok(SharedGameState { - timing_mode: TimingMode::_50Hz, control_flags: ControlFlags(0), game_flags: bitvec::bitvec![0; 8000], skip_flags: bitvec::bitvec![0; 64], @@ -422,7 +420,7 @@ impl SharedGameState { } pub fn current_tps(&self) -> f64 { - self.timing_mode.get_tps() as f64 * self.settings.speed + self.settings.timing_mode.get_tps() as f64 * self.settings.speed } pub fn shutdown(&mut self) {