diff --git a/src/builtin/touch.png b/src/builtin/touch.png index cd65c51..750980e 100644 Binary files a/src/builtin/touch.png and b/src/builtin/touch.png differ diff --git a/src/components/inventory.rs b/src/components/inventory.rs index dda8e85..faf890c 100644 --- a/src/components/inventory.rs +++ b/src/components/inventory.rs @@ -1,11 +1,15 @@ +use crate::common::Rect; +use crate::components::draw_common::{Alignment, draw_number}; use crate::entity::GameEntity; use crate::frame::Frame; use crate::framework::context::Context; use crate::framework::error::GameResult; +use crate::input::touch_controls::TouchControlType; use crate::inventory::Inventory; use crate::player::Player; use crate::shared_game_state::SharedGameState; use crate::text_script::ScriptMode; +use crate::weapon::{WeaponLevel, WeaponType}; #[derive(Copy, Clone, PartialEq, Eq)] #[repr(u8)] @@ -15,41 +19,240 @@ enum InventoryFocus { Items, } +#[derive(Copy, Clone)] +struct InvWeaponData { + wtype: WeaponType, + level: WeaponLevel, + ammo: u16, + max_ammo: u16, +} + pub struct InventoryUI { - text_y_pos: usize, tick: usize, - current_script: usize, + text_y_pos: u16, + current_script: u16, + selected_weapon: u16, + selected_item: u16, + weapon_count: u16, + item_count: u16, + weapon_data: [InvWeaponData; 8], + item_data: [(u16, u16); 32], focus: InventoryFocus, } impl InventoryUI { pub fn new() -> InventoryUI { - InventoryUI { text_y_pos: 24, tick: 0, current_script: 0, focus: InventoryFocus::None } + InventoryUI { + text_y_pos: 16, + tick: 0, + current_script: 0, + selected_weapon: 0, + selected_item: 0, + weapon_count: 0, + item_count: 0, + weapon_data: [InvWeaponData { wtype: WeaponType::None, level: WeaponLevel::None, ammo: 0, max_ammo: 0 }; 8], + item_data: [(0u16, 0u16); 32], + focus: InventoryFocus::None, + } + } + + fn get_item_event_number(&self, inventory: &Inventory) -> u16 { + inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 5000).unwrap_or(5000) + } + + 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) } } -impl GameEntity<(&mut Player, &mut Inventory)> for InventoryUI { +impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI { fn tick( &mut self, state: &mut SharedGameState, - (player, inventory): (&mut Player, &mut Inventory), + (ctx, player, inventory): (&mut Context, &mut Player, &mut Inventory), ) -> GameResult<()> { + let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale); + let mut slot_rect = + Rect::new_size(state.canvas_size.0 as isize - 34 - off_right as isize, 8 + off_top as isize, 26, 26); + + state.touch_controls.control_type = TouchControlType::None; + if state.control_flags.control_enabled() - && (player.controller.trigger_inventory() || player.controller.trigger_menu_back()) + && (player.controller.trigger_inventory() + || 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(); return Ok(()); } + if self.text_y_pos > 8 { + self.text_y_pos -= 1; + } + + self.weapon_count = 0; + for (idx, weapon) in self.weapon_data.iter_mut().enumerate() { + if let Some(weapon_data) = inventory.get_weapon(idx) { + weapon.wtype = weapon_data.wtype; + weapon.level = weapon_data.level; + weapon.ammo = weapon_data.ammo; + weapon.max_ammo = weapon_data.max_ammo; + + self.weapon_count += 1; + } else { + weapon.wtype = WeaponType::None; + break; + } + } + + self.item_count = 0; + for (idx, (item_id, amount)) in self.item_data.iter_mut().enumerate() { + if let Some(item_data) = inventory.get_item_idx(idx) { + *item_id = item_data.0; + *amount = item_data.1; + self.item_count += 1; + } else { + *item_id = 0; + break; + } + } + + fn get_weapon_event_number(inventory: &Inventory) -> u16 { + inventory.get_current_weapon().map(|w| w.wtype as u16 + 1000).unwrap_or(1000) + } + + self.selected_item = inventory.current_item; + self.selected_weapon = inventory.current_weapon; + + let count_x = state.constants.textscript.inventory_item_count_x as u16; + match self.focus { InventoryFocus::None => { self.focus = InventoryFocus::Weapons; - state.textscript_vm.start_script(1004); + state.textscript_vm.start_script(get_weapon_event_number(inventory)); + } + InventoryFocus::Weapons if state.control_flags.control_enabled() => { + if player.controller.trigger_left() { + state.sound_manager.play_sfx(4); + inventory.prev_weapon(); + state.textscript_vm.start_script(get_weapon_event_number(inventory)); + } + + if player.controller.trigger_right() { + state.sound_manager.play_sfx(4); + inventory.next_weapon(); + state.textscript_vm.start_script(get_weapon_event_number(inventory)); + } + + if player.controller.trigger_up() || player.controller.trigger_down() { + self.focus = InventoryFocus::Items; + state.textscript_vm.start_script(self.get_item_event_number(inventory)); + } + } + InventoryFocus::Items if self.item_count != 0 && state.control_flags.control_enabled() => { + if player.controller.trigger_left() { + state.sound_manager.play_sfx(1); + + if (self.selected_item % count_x) != 0 { + self.selected_item -= 1; + } else { + self.selected_item += count_x - 1; + } + + state.textscript_vm.start_script(self.get_item_event_number(inventory)); + } + + if player.controller.trigger_right() { + match () { + _ if self.selected_item == self.item_count + 1 => { + self.selected_item = count_x * (self.selected_item / count_x); + } + _ if (self.selected_item % count_x) + 1 == count_x => { + self.selected_item = self.selected_item.saturating_sub(count_x) + 1; + } + _ => self.selected_item += 1, + } + + state.sound_manager.play_sfx(1); + state.textscript_vm.start_script(self.get_item_event_number(inventory)); + } + + if player.controller.trigger_up() { + if self.selected_item < count_x { + self.focus = InventoryFocus::Weapons; + + state.sound_manager.play_sfx(4); + state.textscript_vm.start_script(get_weapon_event_number(inventory)); + } else { + self.selected_item -= count_x; + + state.sound_manager.play_sfx(1); + state.textscript_vm.start_script(self.get_item_event_number(inventory)); + } + } + + if player.controller.trigger_down() { + if self.selected_item / 6 == self.item_count.saturating_sub(1) / 6 { + self.focus = InventoryFocus::Weapons; + + state.sound_manager.play_sfx(4); + state.textscript_vm.start_script(get_weapon_event_number(inventory)); + } else { + self.selected_item += count_x; + + state.sound_manager.play_sfx(1); + state.textscript_vm.start_script(self.get_item_event_number(inventory)); + } + } + + if player.controller.trigger_menu_ok() { + state.textscript_vm.start_script(self.get_item_event_number_action(inventory)); + } + + self.selected_item = self.selected_item.min(self.item_count - 1); + inventory.current_item = self.selected_item; + } + _ => {} + } + + if state.settings.touch_controls && state.control_flags.control_enabled() { + let x = ((((state.canvas_size.0 - off_left - off_right) - 244.0) / 2.0).floor() + off_left) as isize; + let y = 8 + off_top as isize; + + for i in 0..self.weapon_count { + slot_rect = Rect::new_size(x + 12 + i as isize * 40, y + 16, 40, 40); + + if state.touch_controls.consume_click_in(slot_rect) { + self.focus = InventoryFocus::Weapons; + state.sound_manager.play_sfx(4); + self.selected_weapon = i; + inventory.current_weapon = i; + state.textscript_vm.start_script(get_weapon_event_number(inventory)); + } + } + + for i in 0..self.item_count { + slot_rect = + Rect::new_size(x + 12 + (i % count_x) as isize * 32, y + 68 + (i / count_x) as isize * 16, 32, 16); + + if state.touch_controls.consume_click_in(slot_rect) { + state.sound_manager.play_sfx(1); + + if self.focus == InventoryFocus::Items && inventory.current_item == i { + state.textscript_vm.start_script(self.get_item_event_number_action(inventory)); + } else { + self.selected_item = i; + inventory.current_item = self.selected_item; + self.focus = InventoryFocus::Items; + state.textscript_vm.start_script(self.get_item_event_number(inventory)); + } + } } - InventoryFocus::Weapons => {} - InventoryFocus::Items => {} } self.tick = self.tick.wrapping_add(1); @@ -58,7 +261,10 @@ impl GameEntity<(&mut Player, &mut Inventory)> for InventoryUI { } fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult<()> { - let mut y = 8.0; + let mut tmp_rect = Rect { left: 0, top: 0, right: 0, bottom: 0 }; + let (off_left, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale); + let x = (((state.canvas_size.0 - off_left - off_right) - 244.0) / 2.0).floor() + off_left; + let y = 8.0 + off_top; let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?; for i in 0..=18 { @@ -68,12 +274,124 @@ impl GameEntity<(&mut Player, &mut Inventory)> for InventoryUI { _ => &state.constants.textscript.inventory_rect_middle, }; - batch.add_rect(((state.canvas_size.0 - 244.0) / 2.0).floor(), y, rect); - y += 8.0; + batch.add_rect(x, y + i as f32 * 8.0, rect); + } + + batch.add_rect(x + 12.0, y + self.text_y_pos as f32, &state.constants.textscript.inventory_text_arms); + + batch.add_rect(x + 12.0, y + 52.0 + self.text_y_pos as f32, &state.constants.textscript.inventory_text_item); + + let (item_cursor_frame, weapon_cursor_frame) = match self.focus { + InventoryFocus::None => (1, 1), + InventoryFocus::Weapons => (1, self.tick & 1), + InventoryFocus::Items => (self.tick & 1, 1), + }; + + batch.add_rect( + x + 12.0 + self.selected_weapon as f32 * 40.0, + y + 16.0, + &state.constants.textscript.cursor_inventory_weapon[weapon_cursor_frame], + ); + + let count_x = state.constants.textscript.inventory_item_count_x as usize; + batch.add_rect( + x + 12.0 + (self.selected_item as usize % count_x) as f32 * 32.0, + y + 68.0 + (self.selected_item as usize / count_x) as f32 * 16.0, + &state.constants.textscript.cursor_inventory_item[item_cursor_frame], + ); + + for (idx, weapon) in self.weapon_data.iter().enumerate() { + if weapon.wtype == WeaponType::None { + break; + } + + // lv + batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 32.0, &Rect::new_size(80, 80, 16, 8)); + // per + batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 48.0, &Rect::new_size(72, 48, 8, 8)); + + if weapon.max_ammo == 0 { + batch.add_rect(x + 28.0 + idx as f32 * 40.0, y + 40.0, &Rect::new_size(80, 48, 16, 8)); + batch.add_rect(x + 28.0 + idx as f32 * 40.0, y + 48.0, &Rect::new_size(80, 48, 16, 8)); + } } batch.draw(ctx)?; + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?; + for (idx, weapon) in self.weapon_data.iter().enumerate() { + if weapon.wtype == WeaponType::None { + break; + } + + tmp_rect.left = (weapon.wtype as u16 % 16) * 16; + tmp_rect.top = (weapon.wtype as u16 / 16) * 16; + tmp_rect.right = tmp_rect.left + 16; + tmp_rect.bottom = tmp_rect.top + 16; + + batch.add_rect(x + 12.0 + idx as f32 * 40.0, y + 16.0, &tmp_rect); + } + + batch.draw(ctx)?; + + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ItemImage")?; + + for (idx, (item_id, amount)) in self.item_data.iter().enumerate() { + if *item_id == 0 { + break; + } + + tmp_rect.left = (*item_id % 8) * 32; + tmp_rect.top = (*item_id / 8) * 16; + tmp_rect.right = tmp_rect.left + 32; + tmp_rect.bottom = tmp_rect.top + 16; + + batch.add_rect( + x + 12.0 + (idx % count_x) as f32 * 32.0, + y + 68.0 + (idx / count_x) as f32 * 16.0, + &tmp_rect, + ); + } + + batch.draw(ctx)?; + + let batch = (); // unbind batch to make borrow checker happy + + for (idx, weapon) in self.weapon_data.iter().enumerate() { + if weapon.wtype == WeaponType::None { + break; + } + + draw_number(x + 44.0 + idx as f32 * 40.0, y + 32.0, weapon.level as usize, Alignment::Right, state, ctx)?; + + if weapon.max_ammo != 0 { + draw_number( + x + 44.0 + idx as f32 * 40.0, + y + 40.0, + weapon.ammo as usize, + Alignment::Right, + state, + ctx, + )?; + draw_number( + x + 44.0 + idx as f32 * 40.0, + y + 48.0, + weapon.max_ammo as usize, + Alignment::Right, + state, + 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 - off_right - 30.0, 12.0 + off_top, &close_rect); + batch.draw(ctx)?; + } + Ok(()) } } diff --git a/src/components/stage_select.rs b/src/components/stage_select.rs index 9c6b015..e0d6885 100644 --- a/src/components/stage_select.rs +++ b/src/components/stage_select.rs @@ -32,8 +32,8 @@ impl StageSelect { } } -impl GameEntity<(&Player, &Player)> for StageSelect { - fn tick(&mut self, state: &mut SharedGameState, (player1, player2): (&Player, &Player)) -> GameResult { +impl GameEntity<(&mut Context, &Player, &Player)> for StageSelect { + fn tick(&mut self, state: &mut SharedGameState, (ctx, player1, player2): (&mut Context, &Player, &Player)) -> GameResult { state.touch_controls.control_type = TouchControlType::None; let slot_count = state.teleporter_slots.iter() @@ -97,8 +97,8 @@ impl GameEntity<(&Player, &Player)> for StageSelect { } } - - slot_rect = Rect::new_size(state.canvas_size.0 as isize - 34, 8, 26, 26); + let (_, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale); + slot_rect = Rect::new_size(state.canvas_size.0 as isize - 34 - off_right as isize, 8 + off_top as isize, 26, 26); if state.touch_controls.consume_click_in(slot_rect) { state.sound_manager.play_sfx(5); @@ -157,10 +157,12 @@ impl GameEntity<(&Player, &Player)> for StageSelect { batch.draw(ctx)?; if state.settings.touch_controls { + let (_, off_top, off_right, _) = crate::framework::graphics::screen_insets_scaled(ctx, state.scale); + 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.add_rect(state.canvas_size.0 - off_right - 30.0, 12.0 + off_top, &close_rect); batch.draw(ctx)?; } diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index afe3ca8..343d136 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -180,6 +180,8 @@ pub struct TextScriptConsts { pub inventory_rect_top: Rect, pub inventory_rect_middle: Rect, pub inventory_rect_bottom: Rect, + pub inventory_text_arms: Rect, + pub inventory_text_item: Rect, pub get_item_top_left: Rect, pub get_item_bottom_left: Rect, pub get_item_top_right: Rect, @@ -187,6 +189,9 @@ pub struct TextScriptConsts { pub get_item_bottom_right: Rect, pub stage_select_text: Rect, pub cursor: [Rect; 2], + pub cursor_inventory_weapon: [Rect; 2], + pub cursor_inventory_item: [Rect; 2], + pub inventory_item_count_x: u8, } #[derive(Debug)] @@ -1362,6 +1367,8 @@ impl EngineConstants { inventory_rect_top: Rect { left: 0, top: 0, right: 244, bottom: 8 }, inventory_rect_middle: Rect { left: 0, top: 8, right: 244, bottom: 16 }, inventory_rect_bottom: Rect { left: 0, top: 16, right: 244, bottom: 24 }, + inventory_text_arms: Rect { left: 80, top: 48, right: 144, bottom: 56 }, + inventory_text_item: Rect { left: 80, top: 56, right: 144, bottom: 64 }, get_item_top_left: Rect { left: 0, top: 0, right: 72, bottom: 16 }, get_item_bottom_left: Rect { left: 0, top: 8, right: 72, bottom: 24 }, get_item_top_right: Rect { left: 240, top: 0, right: 244, bottom: 8 }, @@ -1372,6 +1379,15 @@ impl EngineConstants { Rect { left: 80, top: 88, right: 112, bottom: 104 }, Rect { left: 80, top: 104, right: 112, bottom: 120 }, ], + cursor_inventory_weapon: [ + Rect { left: 0, top: 88, right: 40, bottom: 128 }, + Rect { left: 40, top: 88, right: 80, bottom: 128 }, + ], + cursor_inventory_item: [ + Rect { left: 80, top: 88, right: 112, bottom: 104 }, + Rect { left: 80, top: 104, right: 112, bottom: 120 }, + ], + inventory_item_count_x: 6, }, title: TitleConsts { intro_text: "Studio Pixel presents".to_string(), diff --git a/src/input/touch_controls.rs b/src/input/touch_controls.rs index 585b888..cc7806a 100644 --- a/src/input/touch_controls.rs +++ b/src/input/touch_controls.rs @@ -83,7 +83,7 @@ impl TouchControls { let batch = texture_set.get_or_load_batch(ctx, constants, "builtin/touch")?; let color = (255, 255, 255, 160); - let (left, _, right, bottom) = screen_insets_scaled(ctx, scale); + let (left, top, right, bottom) = screen_insets_scaled(ctx, scale); for x in 0..3 { for y in 0..3 { @@ -117,6 +117,13 @@ impl TouchControls { &Rect::new_size(3 * 32, 0, 32, 32), ); + batch.add_rect_tinted( + canvas_size.0 - (4.0 + 48.0) + 8.0 - right, + 4.0 + 8.0 + top, + color, + &Rect::new_size(0, 3 * 32, 32, 32), + ); + batch.draw(ctx)?; } diff --git a/src/input/touch_player_controller.rs b/src/input/touch_player_controller.rs index 2af38bb..a80a270 100644 --- a/src/input/touch_player_controller.rs +++ b/src/input/touch_player_controller.rs @@ -58,9 +58,10 @@ impl PlayerController for TouchPlayerController { } } TouchControlType::Controls => { - let (left, _, right, bottom) = screen_insets_scaled(ctx, state.scale); + let (left, top, right, bottom) = screen_insets_scaled(ctx, state.scale); let left = 4 + left as isize; + let top = 4 + bottom as isize; let bottom = 4 + bottom as isize; let right = 4 + right as isize; @@ -180,6 +181,19 @@ impl PlayerController for TouchPlayerController { .is_some(), ); + self.state.set_inventory( + self.state.inventory() + || state + .touch_controls + .point_in(Rect::new_size( + state.canvas_size.0 as isize - 48 - right, + top, + 48, + 48, + )) + .is_some(), + ); + self.state.set_jump( self.state.jump() || state diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 7650697..f03357a 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1470,8 +1470,8 @@ impl Scene for GameScene { match state.textscript_vm.mode { ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?, - ScriptMode::StageSelect => self.stage_select.tick(state, (&self.player1, &self.player2))?, - ScriptMode::Inventory => self.inventory_ui.tick(state, (&mut self.player1, &mut self.inventory_player1))?, + ScriptMode::StageSelect => self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?, + ScriptMode::Inventory => self.inventory_ui.tick(state, (ctx, &mut self.player1, &mut self.inventory_player1))?, _ => {} }