diff --git a/Cargo.toml b/Cargo.toml index 2f020c0..a1260be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ min_sdk_version = 26 build_targets = ["aarch64-linux-android"] package_name = "io.github.doukutsu_rs.android" apk_label = "doukutsu-rs" -opengles_version = [2, 0] +opengles_version = [3, 1] fullscreen = true orientation = "sensorLandscape" permission = [ @@ -55,11 +55,11 @@ directories = "2" gfx = "0.18" gfx_core = "0.9" gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"} -ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "aad56b0d173ca9f4aeb28599075b5af49ab9214e"} -glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "android-support"} -imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"} -imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"} -imgui-winit-support = {git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "a990a538b66cb67dba3a072bf299b6a51c001447"} +ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "43631b0401271d4bc8fe4a5afba8aad63976dba1"} +glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "master"} +imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"} +imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"} +imgui-winit-support = {git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"} image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]} itertools = "0.9.0" lazy_static = "1.4.0" @@ -77,9 +77,9 @@ strum_macros = "0.18.0" # remove and replace when drain_filter is in stable vec_mut_scan = "0.3.0" webbrowser = "0.5.5" -winit = {version = "0.23.0", features = ["serde"]} +winit = {version = "0.24.0", features = ["serde"]} [target.'cfg(target_os = "android")'.dependencies] -ndk = "0.2.0" -ndk-glue = "0.2.0" +ndk = "0.2" +ndk-glue = "0.2" jni = "0.17" diff --git a/src/bmfont_renderer.rs b/src/bmfont_renderer.rs index 9fb9319..19270bb 100644 --- a/src/bmfont_renderer.rs +++ b/src/bmfont_renderer.rs @@ -67,10 +67,10 @@ impl BMFontRenderer { pub fn draw_text>(&self, iter: I, x: f32, y: f32, constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult { - self.draw_colored_text(iter, x, y, (255, 255, 255), constants, texture_set, ctx) + self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx) } - pub fn draw_colored_text>(&self, iter: I, x: f32, y: f32, color: (u8, u8, u8), + pub fn draw_colored_text>(&self, iter: I, x: f32, y: f32, color: (u8, u8, u8, u8), constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult { if self.pages.len() == 1 { let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?; diff --git a/src/builtin/shaders/basic_es100.frag.glsl b/src/builtin/shaders/basic_es100.frag.glsl new file mode 100644 index 0000000..16b511e --- /dev/null +++ b/src/builtin/shaders/basic_es100.frag.glsl @@ -0,0 +1,13 @@ +#version 300 es + +uniform mediump sampler2D t_Texture; +varying mediump vec2 v_Uv; +varying mediump vec4 v_Color; + +//uniform mediump mat4 u_MVP; + +mediump vec4 Target0; + +void main() { + gl_FragColor = texture2D(t_Texture, v_Uv) * v_Color; +} diff --git a/src/builtin/shaders/basic_es100.vert.glsl b/src/builtin/shaders/basic_es100.vert.glsl new file mode 100644 index 0000000..143770d --- /dev/null +++ b/src/builtin/shaders/basic_es100.vert.glsl @@ -0,0 +1,26 @@ +#version 300 es + +attribute mediump vec2 a_Pos; +attribute mediump vec2 a_Uv; +attribute mediump vec4 a_VertColor; + +attribute mediump vec4 a_Src; +attribute mediump vec4 a_TCol1; +attribute mediump vec4 a_TCol2; +attribute mediump vec4 a_TCol3; +attribute mediump vec4 a_TCol4; +attribute mediump vec4 a_Color; + +uniform mediump mat4 u_MVP; + +varying mediump vec2 v_Uv; +varying mediump vec4 v_Color; + +void main() { + v_Uv = a_Uv * a_Src.zw + a_Src.xy; + v_Color = a_Color * a_VertColor; + mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4); + vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0); + + gl_Position = u_MVP * position; +} diff --git a/src/builtin/shaders/basic_es300.vert.glsl b/src/builtin/shaders/basic_es300.vert.glsl new file mode 100644 index 0000000..5cefa11 --- /dev/null +++ b/src/builtin/shaders/basic_es300.vert.glsl @@ -0,0 +1,27 @@ +#version 300 es + +in vec2 a_Pos; +in vec2 a_Uv; + +in vec4 a_Src; +in vec4 a_TCol1; +in vec4 a_TCol2; +in vec4 a_TCol3; +in vec4 a_TCol4; +in vec4 a_Color; + +layout (std140) uniform Globals { + mat4 u_MVP; +}; + +out vec2 v_Uv; +out vec4 v_Color; + +void main() { + v_Uv = a_Uv * a_Src.zw + a_Src.xy; + v_Color = a_Color; + mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4); + vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0); + + gl_Position = u_MVP * position; +} diff --git a/src/builtin/shaders/water_es300.frag.glsl b/src/builtin/shaders/water_es300.frag.glsl new file mode 100644 index 0000000..8da84ba --- /dev/null +++ b/src/builtin/shaders/water_es300.frag.glsl @@ -0,0 +1,54 @@ +#version 300 es + +precision mediump float; + +uniform sampler2D t_Texture; +in vec2 v_Uv; +in vec4 v_Color; +out vec4 Target0; + +layout (std140) uniform Globals { + mat4 u_MVP; +}; + +layout (std140) uniform WaterShaderParams { + vec2 u_Resolution; + vec2 u_FramePos; + float u_Tick; +}; + +void main() { + vec2 wave = v_Uv; + wave.x += sin((-u_FramePos.y / u_Resolution.y + v_Uv.x * 16.0) + u_Tick / 20.0) * 2.0 / u_Resolution.x; + wave.y -= cos((-u_FramePos.x / u_Resolution.x + v_Uv.y * 16.0) + u_Tick / 5.0) * 2.0 / u_Resolution.y; + float off = 0.4 / u_Resolution.y; + vec4 color = texture(t_Texture, wave); + color.r = texture(t_Texture, wave + off).r; + color.b = texture(t_Texture, wave - off).b; + + Target0 = (vec4(0.4, 0.6, 0.8, 1.0) * 0.3) + (color * v_Color * 0.7); +} + +/* +precision mediump float; + +uniform sampler2D t_Texture; +varying vec2 v_Uv; +varying vec4 v_Color; + +uniform mat4 u_MVP; +uniform vec2 u_Resolution; +uniform vec2 u_FramePos; +uniform float u_Tick; + +void main() { + vec2 wave = v_Uv; + wave.x += sin((-u_FramePos.y / u_Resolution.y + v_Uv.x * 16.0) + u_Tick / 20.0) * 2.0 / u_Resolution.x; + wave.y -= cos((-u_FramePos.x / u_Resolution.x + v_Uv.y * 16.0) + u_Tick / 5.0) * 2.0 / u_Resolution.y; + float off = 0.4 / u_Resolution.y; + vec4 color = texture2D(t_Texture, wave); + color.r = texture2D(t_Texture, wave + off).r; + color.b = texture2D(t_Texture, wave - off).b; + + gl_FragColor = (vec4(0.4, 0.6, 0.8, 1.0) * 0.3) + (color * v_Color * 0.7); +}*/ diff --git a/src/builtin/touch.png b/src/builtin/touch.png index 7fcfdbe..cd65c51 100644 Binary files a/src/builtin/touch.png and b/src/builtin/touch.png differ diff --git a/src/builtin_fs.rs b/src/builtin_fs.rs index a3d9b2a..11df145 100644 --- a/src/builtin_fs.rs +++ b/src/builtin_fs.rs @@ -111,9 +111,12 @@ 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::File("touch.png", include_bytes!("builtin/touch.png")), FSNode::Directory("shaders", vec![ FSNode::File("basic_150.vert.glsl", include_bytes!("builtin/shaders/basic_150.vert.glsl")), FSNode::File("water_150.frag.glsl", include_bytes!("builtin/shaders/water_150.frag.glsl")), + FSNode::File("basic_es300.vert.glsl", include_bytes!("builtin/shaders/basic_es300.vert.glsl")), + FSNode::File("water_es300.frag.glsl", include_bytes!("builtin/shaders/water_es300.frag.glsl")), ]), FSNode::Directory("lightmap", vec![ FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png")), diff --git a/src/components/hud.rs b/src/components/hud.rs index 0ef68db..e01b6ea 100644 --- a/src/components/hud.rs +++ b/src/components/hud.rs @@ -56,8 +56,8 @@ impl HUD { } } -impl GameEntity<(&Player, &Inventory)> for HUD { - fn tick(&mut self, state: &mut SharedGameState, (player, inventory): (&Player, &Inventory)) -> GameResult { +impl GameEntity<(&Player, &mut Inventory)> for HUD { + fn tick(&mut self, state: &mut SharedGameState, (player, inventory): (&Player, &mut Inventory)) -> GameResult { let (ammo, max_ammo) = inventory.get_current_ammo(); let (xp, max_xp, max_level) = inventory.get_current_max_exp(&state.constants); @@ -104,6 +104,56 @@ impl GameEntity<(&Player, &Inventory)> for HUD { self.weapon_x_pos += 2; } + if player.cond.alive() { + if player.controller.trigger_next_weapon() { + state.sound_manager.play_sfx(4); + inventory.next_weapon(); + self.weapon_x_pos = 32; + } + + if player.controller.trigger_prev_weapon() { + state.sound_manager.play_sfx(4); + inventory.prev_weapon(); + self.weapon_x_pos = 0; + } + } + + // touch handler + if state.settings.touch_controls && self.weapon_count != 0 { + let mut rect = Rect::new(0, 0, 0, 16); + let weapon_offset = match self.alignment { + Alignment::Left => 0, + Alignment::Right => (state.canvas_size.0 - 104.0) as isize, + }; + + for a in 0..self.weapon_count { + let mut pos_x = ((a as isize - self.current_weapon) * 16) + self.weapon_x_pos as isize; + + if pos_x < 8 { + pos_x += 48 + self.weapon_count as isize * 16; + } else if pos_x >= 24 { + pos_x += 48; + } + + if pos_x >= 72 + ((self.weapon_count as isize - 1) * 16) { + pos_x -= 48 + self.weapon_count as isize * 16; + } else if pos_x < 72 && pos_x >= 24 { + pos_x -= 48; + } + + let wtype = unsafe { *self.weapon_types.get_unchecked(a) }; + if wtype != 0 { + rect = Rect::new_size(pos_x + weapon_offset - 4, 16 - 4, 24, 24); + + if state.touch_controls.consume_click_in(rect) { + state.sound_manager.play_sfx(4); + inventory.current_weapon = a as u16; + self.weapon_x_pos = 32; + } + } + } + } + Ok(()) } diff --git a/src/components/stage_select.rs b/src/components/stage_select.rs index ed79b9c..e2372a6 100644 --- a/src/components/stage_select.rs +++ b/src/components/stage_select.rs @@ -3,12 +3,14 @@ use ggez::{Context, GameResult}; use crate::common::Rect; use crate::entity::GameEntity; use crate::frame::Frame; +use crate::input::touch_controls::TouchControlType; use crate::player::Player; use crate::shared_game_state::SharedGameState; use crate::text_script::ScriptMode; pub struct StageSelect { pub current_teleport_slot: u8, + prev_teleport_slot: u8, stage_select_text_y_pos: usize, tick: usize, } @@ -17,6 +19,7 @@ impl StageSelect { pub fn new() -> StageSelect { StageSelect { current_teleport_slot: 0, + prev_teleport_slot: 0, stage_select_text_y_pos: 54, tick: 0, } @@ -30,6 +33,8 @@ impl StageSelect { impl GameEntity<(&Player, &Player)> for StageSelect { fn tick(&mut self, state: &mut SharedGameState, (player1, player2): (&Player, &Player)) -> GameResult { + state.touch_controls.control_type = TouchControlType::None; + let slot_count = state.teleporter_slots.iter() .filter(|&&(index, _event_num)| index != 0) .count(); @@ -44,9 +49,9 @@ impl GameEntity<(&Player, &Player)> for StageSelect { let left_pressed = player1.controller.trigger_left() || player2.controller.trigger_left(); let right_pressed = player1.controller.trigger_right() || player2.controller.trigger_right(); - let ok_pressed = player1.controller.trigger_jump() || player1.controller.trigger_menu_ok() + let mut ok_pressed = player1.controller.trigger_jump() || player1.controller.trigger_menu_ok() || player2.controller.trigger_jump() || player2.controller.trigger_menu_ok(); - let cancel_pressed = player1.controller.trigger_shoot() || player2.controller.trigger_shoot(); + let mut cancel_pressed = player1.controller.trigger_shoot() || player2.controller.trigger_shoot(); if left_pressed { if self.current_teleport_slot == 0 { @@ -62,7 +67,8 @@ impl GameEntity<(&Player, &Player)> for StageSelect { } } - if left_pressed || right_pressed { + if self.prev_teleport_slot != self.current_teleport_slot { + self.prev_teleport_slot = self.current_teleport_slot; state.sound_manager.play_sfx(1); if let Some(&(index, _event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) { state.textscript_vm.start_script(1000 + index); @@ -71,6 +77,34 @@ impl GameEntity<(&Player, &Player)> for StageSelect { } } + if state.settings.touch_controls { + let slot_offset = ((state.canvas_size.0 - 40.0 * slot_count as f32) / 2.0).floor(); + let mut slot_rect; + + for i in 0..slot_count { + slot_rect = Rect::new_size(slot_offset as isize + i as isize * 40 - 2, 64 - 8, 36, 32); + + if state.touch_controls.consume_click_in(slot_rect) { + if self.current_teleport_slot as usize == i { + ok_pressed = true; + } else { + state.sound_manager.play_sfx(1); + self.current_teleport_slot = i as u8; + } + + break; + } + } + + + slot_rect = Rect::new_size(state.canvas_size.0 as isize - 34, 8, 26, 26); + + if state.touch_controls.consume_click_in(slot_rect) { + state.sound_manager.play_sfx(5); + cancel_pressed = true; + } + } + if ok_pressed || cancel_pressed { self.reset(); state.textscript_vm.set_mode(ScriptMode::Map); @@ -121,6 +155,14 @@ impl GameEntity<(&Player, &Player)> for StageSelect { batch.draw(ctx)?; + if state.settings.touch_controls { + let close_rect = Rect { left: 110, top: 110, right: 128, bottom: 128 }; + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/touch")?; + + batch.add_rect(state.canvas_size.0 - 30.0, 12.0, &close_rect); + batch.draw(ctx)?; + } + Ok(()) } } diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 936e54b..9c6b3b6 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -707,7 +707,7 @@ impl EngineConstants { "Title" => (320, 48), }, textscript: TextScriptConsts { - encoding: TextScriptEncoding::ShiftJIS, + encoding: TextScriptEncoding::UTF8, encrypted: true, animated_face_pics: false, textbox_rect_top: Rect { left: 0, top: 0, right: 244, bottom: 8 }, @@ -741,7 +741,7 @@ impl EngineConstants { }, font_path: "builtin/builtin_font.fnt".to_string(), font_scale: 1.0, - font_space_offset: -3.0, + font_space_offset: 0.0, organya_paths: vec![ str!("/org/"), // NXEngine str!("/base/Org/"), // CS+ diff --git a/src/input/dummy_player_controller.rs b/src/input/dummy_player_controller.rs index 127d826..e572e6e 100644 --- a/src/input/dummy_player_controller.rs +++ b/src/input/dummy_player_controller.rs @@ -14,7 +14,7 @@ impl DummyPlayerController { } impl PlayerController for DummyPlayerController { - fn update(&mut self, _state: &SharedGameState, _ctx: &mut Context) -> GameResult { + fn update(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) } diff --git a/src/input/keyboard_player_controller.rs b/src/input/keyboard_player_controller.rs index a39c864..ea7c12f 100644 --- a/src/input/keyboard_player_controller.rs +++ b/src/input/keyboard_player_controller.rs @@ -23,7 +23,7 @@ bitfield! { pub next_weapon, set_next_weapon: 8; pub prev_weapon, set_prev_weapon: 9; pub escape, set_escape: 10; - pub enter, set_enter: 10; + pub enter, set_enter: 11; } #[derive(Clone)] @@ -46,7 +46,7 @@ impl KeyboardController { } impl PlayerController for KeyboardController { - fn update(&mut self, state: &SharedGameState, ctx: &mut Context) -> GameResult { + fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { let keymap = match self.target { TargetPlayer::Player1 => &state.settings.player1_key_map, TargetPlayer::Player2 => &state.settings.player2_key_map, diff --git a/src/input/mod.rs b/src/input/mod.rs index 25d7fce..bf3d535 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -3,3 +3,4 @@ pub mod dummy_player_controller; pub mod keyboard_player_controller; pub mod player_controller; pub mod touch_controls; +pub mod touch_player_controller; diff --git a/src/input/player_controller.rs b/src/input/player_controller.rs index 9084b2a..e71ea4b 100644 --- a/src/input/player_controller.rs +++ b/src/input/player_controller.rs @@ -3,7 +3,7 @@ use ggez::{Context, GameResult}; use crate::shared_game_state::SharedGameState; pub trait PlayerController: PlayerControllerClone { - fn update(&mut self, state: &SharedGameState, ctx: &mut Context) -> GameResult; + fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult; fn update_trigger(&mut self); diff --git a/src/input/touch_controls.rs b/src/input/touch_controls.rs index e534189..a30bfb7 100644 --- a/src/input/touch_controls.rs +++ b/src/input/touch_controls.rs @@ -5,20 +5,37 @@ use crate::common::Rect; use crate::engine_constants::EngineConstants; use crate::texture_set::TextureSet; -struct TouchPoint { +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum TouchControlType { + None, + Dialog, + Controls, +} + +#[derive(Copy, Clone)] +pub struct TouchPoint { id: u64, + touch_id: u64, position: (f64, f64), last_position: (f64, f64), } pub struct TouchControls { - points: Vec, + pub control_type: TouchControlType, + pub points: Vec, + pub interact_icon: bool, + touch_id_counter: u64, + clicks: Vec, } impl TouchControls { pub fn new() -> TouchControls { TouchControls { + control_type: TouchControlType::None, + touch_id_counter: 0, + interact_icon: false, points: Vec::with_capacity(8), + clicks: Vec::with_capacity(8), } } @@ -27,22 +44,61 @@ impl TouchControls { TouchPhase::Started | TouchPhase::Moved => { if let Some(point) = self.points.iter_mut().find(|p| p.id == touch.id) { point.last_position = point.position; - point.position = (touch.location.x, touch.location.y); + point.position = (touch.location.x / scale as f64, touch.location.y / scale as f64); } else { - self.points.push(TouchPoint { + self.touch_id_counter = self.touch_id_counter.wrapping_add(1); + + let point = TouchPoint { id: touch.id, - position: (touch.location.x, touch.location.y), + touch_id: self.touch_id_counter, + position: (touch.location.x / scale as f64, touch.location.y / scale as f64), last_position: (0.0, 0.0), - }); + }; + self.points.push(point); + + if touch.phase == TouchPhase::Started { + self.clicks.push(point); + } } } TouchPhase::Ended | TouchPhase::Cancelled => { self.points.retain(|p| p.id != touch.id); + self.clicks.retain(|p| p.id != touch.id); } } } - pub fn draw(&self, constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult { + pub fn point_in(&self, bounds: Rect) -> Option { + for point in self.points.iter() { + if (point.position.0 as isize) > bounds.left + && (point.position.0 as isize) < bounds.right + && (point.position.1 as isize) > bounds.top + && (point.position.1 as isize) < bounds.bottom { + return Some(point.touch_id); + } + } + + None + } + + pub fn consume_click_in(&mut self, bounds: Rect) -> bool { + self.clicks.retain(|p| p.touch_id != 0); + + for point in self.clicks.iter_mut() { + if (point.position.0 as isize) > bounds.left + && (point.position.0 as isize) < bounds.right + && (point.position.1 as isize) > bounds.top + && (point.position.1 as isize) < bounds.bottom { + point.touch_id = 0; + + return true; + } + } + + false + } + + pub fn draw(&self, canvas_size: (f32, f32), constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult { let batch = texture_set.get_or_load_batch(ctx, constants, "Caret")?; let rect = Rect::new_size(104, 120, 24, 24); for point in self.points.iter() { @@ -51,6 +107,38 @@ impl TouchControls { batch.draw(ctx)?; + if self.control_type == TouchControlType::Controls { + let batch = texture_set.get_or_load_batch(ctx, constants, "builtin/touch")?; + let color = (255, 255, 255, 160); + + for x in 0..3 { + for y in 0..3 { + let mut icon_x = x; + let icon_y = y; + + if self.interact_icon && x == 1 && y == 2 { + icon_x = 3; + } + + batch.add_rect_tinted(4.0 + 48.0 * x as f32 + 8.0, + (canvas_size.1 - 4.0 - 48.0 * 3.0) + 48.0 * y as f32 + 8.0, + color, + &Rect::new_size(icon_x * 32, icon_y * 32, 32, 32)); + } + } + + + batch.add_rect_tinted(canvas_size.0 - (4.0 + 48.0) + 8.0, canvas_size.1 - (4.0 + 48.0) + 8.0, + color, + &Rect::new_size(3 * 32, 32, 32, 32)); + + batch.add_rect_tinted(canvas_size.0 - (4.0 + 48.0) + 8.0, canvas_size.1 - (4.0 + 48.0) * 2.0 + 8.0, + color, + &Rect::new_size(3 * 32, 0, 32, 32)); + + batch.draw(ctx)?; + } + Ok(()) } } diff --git a/src/input/touch_player_controller.rs b/src/input/touch_player_controller.rs new file mode 100644 index 0000000..a02251b --- /dev/null +++ b/src/input/touch_player_controller.rs @@ -0,0 +1,219 @@ +use ggez::{Context, GameResult}; + +use crate::bitfield; +use crate::common::Rect; +use crate::input::player_controller::PlayerController; +use crate::input::touch_controls::TouchControlType; +use crate::shared_game_state::SharedGameState; + +/// A no-op implementation of player controller. +#[derive(Clone)] +pub struct TouchPlayerController { + state: KeyState, + old_state: KeyState, + trigger: KeyState, + prev_touch_len: usize, +} + +bitfield! { + #[derive(Clone, Copy)] + pub struct KeyState(u16); + impl Debug; + + pub left, set_left: 0; + pub right, set_right: 1; + pub up, set_up: 2; + pub down, set_down: 3; + pub map, set_map: 4; + pub inventory, set_inventory: 5; + pub jump, set_jump: 6; + pub shoot, set_shoot: 7; + pub next_weapon, set_next_weapon: 8; + pub prev_weapon, set_prev_weapon: 9; + pub pause, set_pause: 10; +} + +impl TouchPlayerController { + pub fn new() -> TouchPlayerController { + TouchPlayerController { + state: KeyState(0), + old_state: KeyState(0), + trigger: KeyState(0), + prev_touch_len: 0, + } + } +} + +impl PlayerController for TouchPlayerController { + fn update(&mut self, state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { + match state.touch_controls.control_type { + TouchControlType::None => {} + TouchControlType::Dialog => { + self.state.set_jump(state.touch_controls.point_in(Rect::new_size(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize)).is_some()); + + if state.touch_controls.points.len() > 1 && self.prev_touch_len != state.touch_controls.points.len() { + self.prev_touch_len = state.touch_controls.points.len(); + self.old_state.set_jump(false); + } + } + TouchControlType::Controls => { + self.state.0 = 0; + // left + self.state.set_left(self.state.left() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 4 - 48 * 2, 48, 48)).is_some()); + + // up + self.state.set_up(self.state.up() || state.touch_controls.point_in(Rect::new_size(48 + 4, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some()); + + // right + self.state.set_right(self.state.right() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 4 - 48 * 2, 48, 48)).is_some()); + + // down + self.state.set_down(self.state.down() || state.touch_controls.point_in(Rect::new_size(48 + 4, state.canvas_size.1 as isize - 4 - 48, 48, 48)).is_some()); + + // left+up + self.state.set_left(self.state.left() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some()); + self.state.set_up(self.state.up() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some()); + + // right+up + self.state.set_right(self.state.right() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some()); + self.state.set_up(self.state.up() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some()); + + // left+down + self.state.set_left(self.state.left() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some()); + self.state.set_down(self.state.down() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some()); + + // right+down + self.state.set_right(self.state.right() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some()); + self.state.set_down(self.state.down() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some()); + + self.state.set_jump(self.state.jump() || state.touch_controls.point_in(Rect::new_size(state.canvas_size.0 as isize - 48 - 4, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some()); + self.state.set_shoot(self.state.shoot() || state.touch_controls.point_in(Rect::new_size(state.canvas_size.0 as isize - 48 - 4, state.canvas_size.1 as isize - (48 - 4) * 2, 48, 48)).is_some()); + } + } + + Ok(()) + } + + fn update_trigger(&mut self) { + let mut trigger = self.state.0 ^ self.old_state.0; + trigger &= self.state.0; + self.old_state = self.state; + self.trigger = KeyState(trigger); + } + + fn move_up(&self) -> bool { + self.state.up() + } + + fn move_left(&self) -> bool { + self.state.left() + } + + fn move_down(&self) -> bool { + self.state.down() + } + + fn move_right(&self) -> bool { + self.state.right() + } + + fn prev_weapon(&self) -> bool { + self.state.prev_weapon() + } + + fn next_weapon(&self) -> bool { + self.state.next_weapon() + } + + fn jump(&self) -> bool { + self.state.jump() + } + + fn shoot(&self) -> bool { + self.state.shoot() + } + + fn trigger_up(&self) -> bool { + self.trigger.up() + } + + fn trigger_left(&self) -> bool { + self.trigger.left() + } + + fn trigger_down(&self) -> bool { + self.trigger.down() + } + + fn trigger_right(&self) -> bool { + self.trigger.right() + } + + fn trigger_prev_weapon(&self) -> bool { + self.trigger.prev_weapon() + } + + fn trigger_next_weapon(&self) -> bool { + self.trigger.next_weapon() + } + + fn trigger_jump(&self) -> bool { + self.trigger.jump() + } + + fn trigger_shoot(&self) -> bool { + self.trigger.shoot() + } + + fn trigger_menu_ok(&self) -> bool { + self.trigger.jump() + } + + fn trigger_menu_back(&self) -> bool { + self.trigger.shoot() + } + + fn trigger_menu_pause(&self) -> bool { + self.trigger.pause() + } + + fn look_up(&self) -> bool { + self.state.up() + } + + fn look_left(&self) -> bool { + self.state.left() + } + + fn look_down(&self) -> bool { + self.state.down() + } + + fn look_right(&self) -> bool { + self.state.right() + } + + fn move_analog_x(&self) -> f64 { + if self.state.left() && self.state.right() { + 0.0 + } else if self.state.left() { + -1.0 + } else if self.state.right() { + 1.0 + } else { + 0.0 + } + } + + fn move_analog_y(&self) -> f64 { + if self.state.up() && self.state.down() { + 0.0 + } else if self.state.up() { + -1.0 + } else if self.state.down() { + 1.0 + } else { + 0.0 + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 646a59b..6aee158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,7 +148,7 @@ impl Game { if let Some(scene) = self.scene.as_mut() { scene.draw(&mut self.state, ctx)?; if self.state.settings.touch_controls { - self.state.touch_controls.draw(&self.state.constants, &mut self.state.texture_set, ctx)?; + self.state.touch_controls.draw(self.state.canvas_size, &self.state.constants, &mut self.state.texture_set, ctx)?; } graphics::set_transform(ctx, self.def_matrix); @@ -244,6 +244,13 @@ pub fn android_main() { init().unwrap(); } +#[cfg(target_os = "android")] +static BACKENDS: [Backend; 2] = [ + Backend::OpenGLES { major: 3, minor: 0 }, + Backend::OpenGLES { major: 2, minor: 0 } +]; + +#[cfg(not(target_os = "android"))] static BACKENDS: [Backend; 4] = [ Backend::OpenGL { major: 3, minor: 2 }, Backend::OpenGLES { major: 3, minor: 2 }, @@ -263,10 +270,15 @@ fn init_ctx + Clone>(event_loop: &winit::event_loop::Even .backend(*backend) .build(event_loop); - if let Ok(mut ctx) = ctx { - mount_vfs(&mut ctx, Box::new(BuiltinFS::new())); + match ctx { + Ok(mut ctx) => { + mount_vfs(&mut ctx, Box::new(BuiltinFS::new())); - return Ok(ctx); + return Ok(ctx); + } + Err(err) => { + log::warn!("Failed to create backend using config {:?}: {}", backend, err); + } } } diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 13ea4be..09d50b2 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -1,7 +1,8 @@ -use crate::common::Rect; use ggez::{Context, GameResult}; -use crate::shared_game_state::SharedGameState; + +use crate::common::Rect; use crate::input::combined_menu_controller::CombinedMenuController; +use crate::shared_game_state::SharedGameState; pub enum MenuEntry { Active(String), @@ -9,6 +10,12 @@ pub enum MenuEntry { Toggle(String, bool), } +impl MenuEntry { + pub fn height(&self) -> f64 { + 14.0 + } +} + pub enum MenuSelectionResult<'a> { None, Canceled, @@ -160,7 +167,7 @@ impl Menu { state.font.draw_text(name.chars(), self.x as f32 + 20.0, y, &state.constants, &mut state.texture_set, ctx)?; } MenuEntry::Disabled(name) => { - state.font.draw_colored_text(name.chars(), self.x as f32 + 20.0, y, (0xa0, 0xa0, 0xff), &state.constants, &mut state.texture_set, ctx)?; + state.font.draw_colored_text(name.chars(), self.x as f32 + 20.0, y, (0xa0, 0xa0, 0xff, 0xff), &state.constants, &mut state.texture_set, ctx)?; } MenuEntry::Toggle(name, value) => { let value_text = if *value { "ON" } else { "OFF" }; @@ -172,7 +179,7 @@ impl Menu { } } - y += 14.0; + y += entry.height() as f32; } Ok(()) @@ -211,15 +218,23 @@ impl Menu { } } - if controller.trigger_ok() && !self.entries.is_empty() { - if let Some(entry) = self.entries.get_mut(self.selected) { - match entry { - MenuEntry::Active(_) | MenuEntry::Toggle(_, _) => { - state.sound_manager.play_sfx(18); - return MenuSelectionResult::Selected(self.selected, entry); - } - _ => {} + let mut y = self.y as f32 + 6.0; + for (idx, entry) in self.entries.iter_mut().enumerate() { + let entry_bounds = Rect::new_size(self.x, y as isize, self.width as isize, entry.height() as isize); + y += entry.height() as f32; + + if !((controller.trigger_ok() && self.selected == idx) + || state.touch_controls.consume_click_in(entry_bounds)) { + continue; + } + + match entry { + MenuEntry::Active(_) | MenuEntry::Toggle(_, _) => { + self.selected = idx; + state.sound_manager.play_sfx(18); + return MenuSelectionResult::Selected(idx, entry); } + _ => {} } } diff --git a/src/player/player_hit.rs b/src/player/player_hit.rs index af3185f..d39d908 100644 --- a/src/player/player_hit.rs +++ b/src/player/player_hit.rs @@ -286,6 +286,11 @@ impl Player { } } + if state.settings.touch_controls && npc.npc_flags.interactable() && flags.0 != 0 { + // todo make it less hacky + state.touch_controls.interact_icon = true; + } + if npc.npc_flags.interactable() && !state.control_flags.interactions_disabled() && flags.0 != 0 && self.cond.interacted() { state.control_flags.set_tick_world(true); state.control_flags.set_interactions_disabled(true); diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index d6a82b4..2d6c6f3 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -29,6 +29,7 @@ use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, use crate::texture_set::SizedBatch; use crate::ui::Components; use crate::weapon::WeaponType; +use crate::input::touch_controls::TouchControlType; pub struct GameScene { pub tick: usize, @@ -492,7 +493,7 @@ impl GameScene { } 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, + batch.add_rect_scaled_tinted(x - size * 32.0, y - size * 32.0, (color.0, color.1, color.2, 255), size, size, &Rect::new(0, 0, 64, 64)) @@ -1111,36 +1112,8 @@ impl GameScene { weapon.shoot_bullet(&self.player2, TargetPlayer::Player2, &mut self.bullet_manager, state); } - if self.player1.cond.alive() { - if self.player1.controller.trigger_next_weapon() { - state.sound_manager.play_sfx(4); - self.inventory_player1.next_weapon(); - self.hud_player1.weapon_x_pos = 32; - } - - if self.player1.controller.trigger_prev_weapon() { - state.sound_manager.play_sfx(4); - self.inventory_player1.prev_weapon(); - self.hud_player1.weapon_x_pos = 0; - } - } - - if self.player2.cond.alive() { - if self.player2.controller.trigger_next_weapon() { - state.sound_manager.play_sfx(4); - self.inventory_player2.next_weapon(); - self.hud_player2.weapon_x_pos = 32; - } - - if self.player2.controller.trigger_prev_weapon() { - state.sound_manager.play_sfx(4); - self.inventory_player2.prev_weapon(); - self.hud_player2.weapon_x_pos = 0; - } - } - - self.hud_player1.tick(state, (&self.player1, &self.inventory_player1))?; - self.hud_player2.tick(state, (&self.player2, &self.inventory_player2))?; + self.hud_player1.tick(state, (&self.player1, &mut self.inventory_player1))?; + self.hud_player2.tick(state, (&self.player2, &mut self.inventory_player2))?; self.boss_life_bar.tick(state, &self.npc_map)?; } @@ -1187,7 +1160,7 @@ impl GameScene { let text = format!("{}:{}:{}", npc.id, npc.npc_type, npc.action_num); state.font.draw_colored_text(text.chars(), ((npc.x - self.frame.x) / 0x200) as f32, ((npc.y - self.frame.y) / 0x200) as f32, - ((npc.id & 0xf0) as u8, (npc.cond.0 >> 8) as u8, (npc.id & 0x0f << 4) as u8), + ((npc.id & 0xf0) as u8, (npc.cond.0 >> 8) as u8, (npc.id & 0x0f << 4) as u8, 255), &state.constants, &mut state.texture_set, ctx)?; Ok(()) @@ -1266,6 +1239,16 @@ impl Scene for GameScene { self.player2.controller.update(state, ctx)?; self.player2.controller.update_trigger(); + state.touch_controls.control_type = if state.control_flags.control_enabled() { + TouchControlType::Controls + } else { + TouchControlType::Dialog + }; + + if state.settings.touch_controls { + state.touch_controls.interact_icon = false; + } + if self.intro_mode && (self.player1.controller.trigger_menu_ok() || self.tick >= 500) { state.next_scene = Some(Box::new(TitleScene::new())); } @@ -1400,21 +1383,21 @@ impl Scene for GameScene { if self.player2.x + 8 * 0x200 < self.frame.x { state.font.draw_colored_text(P2_LEFT_TEXT.chars(), 9.0, y + 1.0, - (0, 0, 130), &state.constants, &mut state.texture_set, ctx)?; + (0, 0, 130, 255), &state.constants, &mut state.texture_set, ctx)?; state.font.draw_colored_text(P2_LEFT_TEXT.chars(), 8.0, y, - (96, 96, 255), &state.constants, &mut state.texture_set, ctx)?; + (96, 96, 255, 255), &state.constants, &mut state.texture_set, ctx)?; } else if self.player2.x - 8 * 0x200 > self.frame.x + state.canvas_size.0 as isize * 0x200 { let width = state.font.text_width(P2_RIGHT_TEXT.chars(), &state.constants); state.font.draw_colored_text(P2_RIGHT_TEXT.chars(), state.canvas_size.0 - width - 8.0 + 1.0, y + 1.0, - (0, 0, 130), &state.constants, &mut state.texture_set, ctx)?; + (0, 0, 130, 255), &state.constants, &mut state.texture_set, ctx)?; state.font.draw_colored_text(P2_RIGHT_TEXT.chars(), state.canvas_size.0 - width - 8.0, y, - (96, 96, 255), &state.constants, &mut state.texture_set, ctx)?; + (96, 96, 255, 255), &state.constants, &mut state.texture_set, ctx)?; } } } diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index e64123b..1f35c2d 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -6,6 +6,7 @@ use crate::menu::{Menu, MenuEntry, MenuSelectionResult}; use crate::scene::Scene; use crate::shared_game_state::{SharedGameState, TimingMode}; use crate::input::combined_menu_controller::CombinedMenuController; +use crate::input::touch_controls::TouchControlType; #[derive(PartialEq, Eq, Copy, Clone)] #[repr(u8)] @@ -121,6 +122,7 @@ impl Scene for TitleScene { } fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + state.touch_controls.control_type = TouchControlType::None; self.controller.update(state, ctx)?; self.controller.update_trigger(); diff --git a/src/settings.rs b/src/settings.rs index 088b601..b8251ee 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -5,6 +5,7 @@ use winit::event::VirtualKeyCode; use crate::input::keyboard_player_controller::KeyboardController; use crate::input::player_controller::PlayerController; use crate::player::TargetPlayer; +use crate::input::touch_player_controller::TouchPlayerController; #[derive(Serialize, Deserialize)] pub struct Settings { @@ -31,6 +32,10 @@ impl Settings { } pub fn create_player1_controller(&self) -> Box { + if self.touch_controls { + return Box::new(TouchPlayerController::new()); + } + Box::new(KeyboardController::new(TargetPlayer::Player1)) } diff --git a/src/shaders.rs b/src/shaders.rs index 9256fca..099a767 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -26,8 +26,8 @@ impl Shaders { Ok(Shaders { water_shader: Shader::new( ctx, - "/builtin/shaders/basic_150.vert.glsl", - "/builtin/shaders/water_150.frag.glsl", + "/builtin/shaders/basic_es300.vert.glsl", + "/builtin/shaders/water_es300.frag.glsl", water_shader_params, "WaterShaderParams", None, diff --git a/src/sound/mod.rs b/src/sound/mod.rs index f3e2fe9..e4cb4e1 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -197,8 +197,9 @@ fn run(rx: Receiver, bank: SoundBank, org_engine.set_sample_rate(sample_rate as usize); org_engine.loops = usize::MAX; - let mut bgm_buf = vec![0x8080; 441]; - let mut pxt_buf = vec![0x8000; 441]; + let buf_size = sample_rate as usize * 30 / 1000; + let mut bgm_buf = vec![0x8080; buf_size]; + let mut pxt_buf = vec![0x8000; buf_size]; let mut bgm_index = 0; let mut pxt_index = 0; let mut frames = org_engine.render_to(&mut bgm_buf); diff --git a/src/texture_set.rs b/src/texture_set.rs index e52d6a3..3d6afe7 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -79,6 +79,11 @@ impl SizedBatch { self.add_rect_scaled(x, y, self.scale_x, self.scale_y, rect) } + #[inline(always)] + pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect) { + self.add_rect_scaled_tinted(x, y, color, self.scale_x, self.scale_y, rect) + } + pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; @@ -100,7 +105,7 @@ impl SizedBatch { self.batch.add(param); } - pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect) { + pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; }