From ebe070b77e62dabd47350aac6465081429be1f9a Mon Sep 17 00:00:00 2001 From: Alula Date: Tue, 29 Sep 2020 22:19:47 +0200 Subject: [PATCH] teleporter slot menu --- src/engine_constants.rs | 7 + src/scene/game_scene.rs | 292 +++++++++++++++++++++++++++------------ src/shared_game_state.rs | 7 +- src/text_script.rs | 60 ++++++-- 4 files changed, 262 insertions(+), 104 deletions(-) diff --git a/src/engine_constants.rs b/src/engine_constants.rs index d2d2674..29efa13 100644 --- a/src/engine_constants.rs +++ b/src/engine_constants.rs @@ -212,6 +212,8 @@ pub struct TextScriptConsts { pub get_item_top_right: Rect, pub get_item_right: Rect, pub get_item_bottom_right: Rect, + pub stage_select_text: Rect, + pub cursor: [Rect; 2], } @@ -1177,6 +1179,11 @@ impl EngineConstants { get_item_top_right: Rect { left: 240, top: 0, right: 244, bottom: 8 }, get_item_right: Rect { left: 240, top: 8, right: 244, bottom: 16 }, get_item_bottom_right: Rect { left: 240, top: 16, right: 244, bottom: 24 }, + stage_select_text: Rect { left: 80, top: 64, right: 144, bottom: 72 }, + cursor: [ + Rect { left: 80, top: 88, right: 112, bottom: 104 }, + Rect { left: 80, top: 104, right: 112, bottom: 120 }, + ], }, title: TitleConsts { logo_rect: Rect { left: 0, top: 0, right: 144, bottom: 40 }, diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 57605a3..779bdb3 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -12,13 +12,14 @@ use crate::inventory::{Inventory, TakeExperienceResult}; use crate::npc::NPCMap; use crate::physics::PhysicalEntity; use crate::player::Player; +use crate::rng::RNG; use crate::scene::Scene; use crate::shared_game_state::SharedGameState; use crate::stage::{BackgroundType, Stage}; -use crate::text_script::{ConfirmSelection, TextScriptExecutionState, TextScriptVM}; +use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptVM}; use crate::texture_set::SizedBatch; use crate::ui::Components; -use crate::weapon::WeaponType; +use itertools::Itertools; pub struct GameScene { pub tick: usize, @@ -29,11 +30,13 @@ pub struct GameScene { pub stage_id: usize, pub npc_map: NPCMap, pub bullet_manager: BulletManager, + pub current_teleport_slot: u8, tex_background_name: String, tex_tileset_name: String, life_bar: u16, life_bar_counter: u16, map_name_counter: u16, + stage_select_text_y_pos: usize, weapon_x_pos: isize, } @@ -81,6 +84,8 @@ impl GameScene { life_bar: 0, life_bar_counter: 0, map_name_counter: 0, + stage_select_text_y_pos: 54, + current_teleport_slot: 0, weapon_x_pos: 16, }) } @@ -740,7 +745,7 @@ impl GameScene { Ok(()) } - pub fn tick_npc_bullet_collissions(&mut self, state: &mut SharedGameState) { + fn tick_npc_bullet_collissions(&mut self, state: &mut SharedGameState) { let mut dead_npcs = Vec::new(); for npc_id in self.npc_map.npc_ids.iter() { @@ -830,10 +835,194 @@ impl GameScene { self.npc_map.garbage_collect(); } } + + fn tick_world(&mut self, state: &mut SharedGameState) -> GameResult { + self.stage_select_text_y_pos = 54; + self.current_teleport_slot = 0; + + self.player.current_weapon = { + if let Some(weapon) = self.inventory.get_current_weapon_mut() { + weapon.wtype as u8 + } else { + 0 + } + }; + self.player.tick(state, ())?; + + if self.player.damage > 0 { + let xp_loss = self.player.damage * if self.player.equip.has_arms_barrier() { 1 } else { 2 }; + match self.inventory.take_xp(xp_loss, state) { + TakeExperienceResult::LevelDown if self.player.life > 0 => { + state.create_caret(self.player.x, self.player.y, CaretType::LevelUp, Direction::Right); + } + _ => {} + } + + self.player.damage = 0; + } + + for npc_id in self.npc_map.npc_ids.iter() { + if let Some(npc_cell) = self.npc_map.npcs.get(npc_id) { + let mut npc = npc_cell.borrow_mut(); + + if npc.cond.alive() { + npc.tick(state, (&mut self.player, &self.npc_map.npcs, &self.stage))?; + } + } + } + self.npc_map.process_npc_changes(state); + self.npc_map.garbage_collect(); + + self.player.flags.0 = 0; + + self.player.tick_map_collisions(state, &mut self.stage); + self.player.tick_npc_collisions(state, &mut self.npc_map, &mut self.inventory); + self.npc_map.process_npc_changes(state); + for npc_id in self.npc_map.npc_ids.iter() { + if let Some(npc_cell) = self.npc_map.npcs.get_mut(npc_id) { + let mut npc = npc_cell.borrow_mut(); + + if npc.cond.alive() && !npc.npc_flags.ignore_solidity() { + npc.flags.0 = 0; + npc.tick_map_collisions(state, &mut self.stage); + } + } + } + self.npc_map.process_npc_changes(state); + self.npc_map.garbage_collect(); + self.tick_npc_bullet_collissions(state); + + state.tick_carets(); + self.bullet_manager.tick_bullets(state, &self.player, &mut self.stage); + + self.frame.update(state, &self.player, &self.stage); + + if state.control_flags.control_enabled() { + if let Some(weapon) = self.inventory.get_current_weapon_mut() { + weapon.shoot_bullet(&self.player, &mut self.bullet_manager, state); + } + + if state.key_trigger.weapon_next() { + state.sound_manager.play_sfx(4); + self.inventory.next_weapon(); + self.weapon_x_pos = 32; + } + + if state.key_trigger.weapon_prev() { + state.sound_manager.play_sfx(4); + self.inventory.prev_weapon(); + self.weapon_x_pos = 0; + } + + // update health bar + if self.life_bar < self.player.life as u16 { + self.life_bar = self.player.life as u16; + } + + if self.life_bar > self.player.life as u16 { + self.life_bar_counter += 1; + if self.life_bar_counter > 30 { + self.life_bar -= 1; + } + } else { + self.life_bar_counter = 0; + } + } + + Ok(()) + } + + fn tick_stage_select(&mut self, state: &mut SharedGameState) -> GameResult { + let slot_count = state.teleporter_slots.iter() + .filter(|&&(index, _event_num)| index != 0) + .count(); + + if self.stage_select_text_y_pos > 46 { + self.stage_select_text_y_pos -= 1; + } + + if state.key_trigger.left() { + if self.current_teleport_slot == 0 { + self.current_teleport_slot = slot_count.saturating_sub(1) as u8; + } else { + self.current_teleport_slot -= 1; + } + } else if state.key_trigger.right() { + if self.current_teleport_slot == slot_count.saturating_sub(1) as u8 { + self.current_teleport_slot = 0; + } else { + self.current_teleport_slot += 1; + } + } + + if state.key_trigger.left() || state.key_trigger.right() { + 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); + } else { + state.textscript_vm.start_script(1000); + } + } + + if state.key_trigger.jump() | state.key_trigger.fire() { + state.textscript_vm.set_mode(ScriptMode::Map); + state.control_flags.set_tick_world(true); + state.control_flags.set_control_enabled(true); + state.control_flags.set_interactions_disabled(false); + + if state.key_trigger.jump() { + if let Some(&(_index, event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) { + state.textscript_vm.start_script(event_num); + } + } + } + + Ok(()) + } + + fn draw_stage_select(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "StageImage")?; + + let slot_count = state.teleporter_slots.iter() + .filter(|&&(index, _event_num)| index != 0) + .count(); + let slot_offset = ((state.canvas_size.0 - 40.0 * slot_count as f32) / 2.0).floor(); + let mut slot_rect = Rect::new(0,0,0,0); + + for i in 0..slot_count { + let index = state.teleporter_slots[i].0; + + slot_rect.left = 32 * (index as usize % 8); + slot_rect.top = 16 * (index as usize / 8); + slot_rect.right = slot_rect.left + 32; + slot_rect.bottom = slot_rect.top + 16; + + batch.add_rect(slot_offset + i as f32 * 40.0, 64.0, &slot_rect); + } + + batch.draw(ctx)?; + + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?; + + batch.add_rect(128.0, self.stage_select_text_y_pos as f32, &state.constants.textscript.stage_select_text); + if slot_count > 0 { + batch.add_rect(slot_offset + self.current_teleport_slot as f32 * 40.0, 64.0, &state.constants.textscript.cursor[self.tick / 2 % 2]); + } + + batch.draw(ctx)?; + + Ok(()) + } } impl Scene for GameScene { fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + let seed = (self.player.max_life as i32) + .wrapping_add(self.player.x as i32) + .wrapping_add(self.player.y as i32) + .wrapping_add(self.stage_id as i32) + .wrapping_mul(7); + state.game_rng = RNG::new(seed); state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?); state.textscript_vm.suspend = false; @@ -872,95 +1061,10 @@ impl Scene for GameScene { fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { state.update_key_trigger(); - if state.control_flags.tick_world() { - self.player.current_weapon = { - if let Some(weapon) = self.inventory.get_current_weapon_mut() { - weapon.wtype as u8 - } else { - 0 - } - }; - self.player.tick(state, ())?; - - if self.player.damage > 0 { - let xp_loss = self.player.damage * if self.player.equip.has_arms_barrier() { 1 } else { 2 }; - match self.inventory.take_xp(xp_loss, state) { - TakeExperienceResult::LevelDown if self.player.life > 0 => { - state.create_caret(self.player.x, self.player.y, CaretType::LevelUp, Direction::Right); - } - _ => {} - } - - self.player.damage = 0; - } - - for npc_id in self.npc_map.npc_ids.iter() { - if let Some(npc_cell) = self.npc_map.npcs.get(npc_id) { - let mut npc = npc_cell.borrow_mut(); - - if npc.cond.alive() { - npc.tick(state, (&mut self.player, &self.npc_map.npcs, &self.stage))?; - } - } - } - self.npc_map.process_npc_changes(state); - self.npc_map.garbage_collect(); - - self.player.flags.0 = 0; - - self.player.tick_map_collisions(state, &mut self.stage); - self.player.tick_npc_collisions(state, &mut self.npc_map, &mut self.inventory); - self.npc_map.process_npc_changes(state); - for npc_id in self.npc_map.npc_ids.iter() { - if let Some(npc_cell) = self.npc_map.npcs.get_mut(npc_id) { - let mut npc = npc_cell.borrow_mut(); - - if npc.cond.alive() && !npc.npc_flags.ignore_solidity() { - npc.flags.0 = 0; - npc.tick_map_collisions(state, &mut self.stage); - } - } - } - self.npc_map.process_npc_changes(state); - self.npc_map.garbage_collect(); - self.tick_npc_bullet_collissions(state); - - state.tick_carets(); - self.bullet_manager.tick_bullets(state, &self.player, &mut self.stage); - - self.frame.update(state, &self.player, &self.stage); - } - - if state.control_flags.control_enabled() { - if let Some(weapon) = self.inventory.get_current_weapon_mut() { - weapon.shoot_bullet(&self.player, &mut self.bullet_manager, state); - } - - if state.key_trigger.weapon_next() { - state.sound_manager.play_sfx(4); - self.inventory.next_weapon(); - self.weapon_x_pos = 32; - } - - if state.key_trigger.weapon_prev() { - state.sound_manager.play_sfx(4); - self.inventory.prev_weapon(); - self.weapon_x_pos = 0; - } - - // update health bar - if self.life_bar < self.player.life as u16 { - self.life_bar = self.player.life as u16; - } - - if self.life_bar > self.player.life as u16 { - self.life_bar_counter += 1; - if self.life_bar_counter > 30 { - self.life_bar -= 1; - } - } else { - self.life_bar_counter = 0; - } + match state.textscript_vm.mode { + ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?, + ScriptMode::StageSelect => self.tick_stage_select(state)?, + _ => {} } if self.map_name_counter > 0 { @@ -1017,6 +1121,10 @@ impl Scene for GameScene { self.draw_hud(state, ctx)?; } + if state.textscript_vm.mode == ScriptMode::StageSelect { + self.draw_stage_select(state, ctx)?; + } + self.draw_fade(state, ctx)?; if self.map_name_counter > 0 { let width = state.font.text_width(self.stage.data.name.chars(), &state.constants); diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index a29ad62..e0e47b6 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -16,7 +16,7 @@ use crate::scene::Scene; use crate::sound::SoundManager; use crate::stage::StageData; use crate::str; -use crate::text_script::{TextScriptExecutionState, TextScriptVM}; +use crate::text_script::{TextScriptExecutionState, TextScriptVM, ScriptMode}; use crate::texture_set::TextureSet; use crate::ggez::graphics::Canvas; @@ -52,6 +52,7 @@ pub struct SharedGameState { pub game_rng: RNG, pub effect_rng: RNG, pub quake_counter: u16, + pub teleporter_slots: Vec<(u16, u16)>, pub carets: Vec, pub key_state: KeyState, pub key_trigger: KeyState, @@ -109,6 +110,7 @@ impl SharedGameState { game_rng: RNG::new(0), effect_rng: RNG::new(Instant::now().elapsed().as_nanos() as i32), quake_counter: 0, + teleporter_slots: Vec::with_capacity(8), carets: Vec::with_capacity(32), key_state: KeyState(0), key_trigger: KeyState(0), @@ -177,13 +179,14 @@ impl SharedGameState { self.game_flags = bitvec::bitvec![0; 8000]; self.fade_state = FadeState::Hidden; self.game_rng = RNG::new(0); + self.teleporter_slots.clear(); self.quake_counter = 0; self.carets.clear(); self.key_state.0 = 0; self.key_trigger.0 = 0; self.key_old = 0; self.new_npcs.clear(); - self.textscript_vm.reset(); + self.textscript_vm.set_mode(ScriptMode::Map); self.textscript_vm.suspend = true; } diff --git a/src/text_script.rs b/src/text_script.rs index 8a3bbbf..e231456 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -390,11 +390,25 @@ pub struct Scripts { } impl Scripts { - pub fn find_script(&self, event_num: u16) -> Option<&Vec> { - if let Some(tsc) = self.scene_script.event_map.get(&event_num) { - return Some(tsc); - } else if let Some(tsc) = self.global_script.event_map.get(&event_num) { - return Some(tsc); + pub fn find_script(&self, mode: ScriptMode, event_num: u16) -> Option<&Vec> { + match mode { + ScriptMode::Map => { + if let Some(tsc) = self.scene_script.event_map.get(&event_num) { + return Some(tsc); + } else if let Some(tsc) = self.global_script.event_map.get(&event_num) { + return Some(tsc); + } + } + ScriptMode::Inventory => { + if let Some(tsc) = self.inventory_script.event_map.get(&event_num) { + return Some(tsc); + } + } + ScriptMode::StageSelect => { + if let Some(tsc) = self.stage_select_script.event_map.get(&event_num) { + return Some(tsc); + } + } } None @@ -473,7 +487,10 @@ impl TextScriptVM { self.line_3.clear(); } - pub fn set_mode(&mut self, mode: ScriptMode) {} + pub fn set_mode(&mut self, mode: ScriptMode) { + self.reset(); + self.mode = mode; + } pub fn start_script(&mut self, event_num: u16) { self.reset(); @@ -505,7 +522,7 @@ impl TextScriptVM { break; } - if let Some(bytecode) = state.textscript_vm.scripts.find_script(event) { + if let Some(bytecode) = state.textscript_vm.scripts.find_script(state.textscript_vm.mode, event) { let mut cursor = Cursor::new(bytecode); cursor.seek(SeekFrom::Start(ip as u64))?; @@ -613,7 +630,7 @@ impl TextScriptVM { let mut exec_state = state.textscript_vm.state; let mut tick_npc = 0u16; - if let Some(bytecode) = state.textscript_vm.scripts.find_script(event) { + if let Some(bytecode) = state.textscript_vm.scripts.find_script(state.textscript_vm.mode, event) { let mut cursor = Cursor::new(bytecode); cursor.seek(SeekFrom::Start(ip as u64))?; @@ -656,6 +673,29 @@ impl TextScriptVM { exec_state = TextScriptExecutionState::Ended; } + OpCode::SLP => { + state.textscript_vm.set_mode(ScriptMode::StageSelect); + + let event_num = if let Some(slot) = state.teleporter_slots.get(game_scene.current_teleport_slot as usize) { + 1000 + slot.0 + } else { + 1000 + }; + + exec_state = TextScriptExecutionState::Running(event_num, 0); + } + OpCode::PSp => { + let index = read_cur_varint(&mut cursor)? as u16; + let event_num = read_cur_varint(&mut cursor)? as u16; + + if let Some(slot) = state.teleporter_slots.iter_mut().find(|s| s.0 == index) { + slot.1 = event_num; + } else { + state.teleporter_slots.push((index, event_num)); + } + + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } OpCode::PRI => { state.control_flags.set_tick_world(false); state.control_flags.set_control_enabled(false); @@ -1250,7 +1290,7 @@ impl TextScriptVM { // Zero operands OpCode::CAT | OpCode::CIL | OpCode::CPS | OpCode::KE2 | OpCode::CRE | OpCode::CSS | OpCode::FLA | OpCode::MLP | - OpCode::SAT | OpCode::SLP | OpCode::SPS | OpCode::FR2 | + OpCode::SAT | OpCode::SPS | OpCode::FR2 | OpCode::STC | OpCode::SVP | OpCode::TUR | OpCode::HM2 => { log::warn!("unimplemented opcode: {:?}", op); @@ -1268,7 +1308,7 @@ impl TextScriptVM { exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } // Two operand codes - OpCode::SKJ | OpCode::SMP | OpCode::PSp => { + OpCode::SKJ | OpCode::SMP => { let par_a = read_cur_varint(&mut cursor)?; let par_b = read_cur_varint(&mut cursor)?;