mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-03-23 10:29:18 +00:00
initial cs+ challenge support
This commit is contained in:
parent
c127ee4bd4
commit
8e2088adb4
|
@ -1687,8 +1687,12 @@ impl EngineConstants {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(mod_path) = mod_path {
|
||||
self.base_paths.insert(0, mod_path);
|
||||
if let Some(mut mod_path) = mod_path {
|
||||
self.base_paths.insert(0, mod_path.clone());
|
||||
if settings.original_textures {
|
||||
mod_path.push_str("ogph/");
|
||||
self.base_paths.insert(0, mod_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ mod live_debugger;
|
|||
mod macros;
|
||||
mod map;
|
||||
mod menu;
|
||||
mod mod_list;
|
||||
#[cfg(feature = "netplay")]
|
||||
mod netplay;
|
||||
mod npc;
|
||||
|
|
143
src/mod_list.rs
Normal file
143
src/mod_list.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
use std::io::{BufRead, BufReader};
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
|
||||
use crate::framework::filesystem;
|
||||
use crate::{Context, GameResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModInfo {
|
||||
pub id: String,
|
||||
pub requirement: Requirement,
|
||||
pub priority: u32,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Requirement {
|
||||
/// R+
|
||||
Unlocked,
|
||||
/// R-
|
||||
Locked,
|
||||
/// RI##
|
||||
RequireItem(u16),
|
||||
/// RA##
|
||||
RequireWeapon(u16),
|
||||
/// RG
|
||||
RequireHell,
|
||||
}
|
||||
|
||||
pub struct ModList {
|
||||
pub mods: Vec<ModInfo>,
|
||||
}
|
||||
|
||||
impl ModList {
|
||||
pub fn load(ctx: &mut Context) -> GameResult<ModList> {
|
||||
let mut mods = Vec::new();
|
||||
|
||||
if let Ok(file) = filesystem::open(ctx, "/mods.txt") {
|
||||
let reader = BufReader::new(file);
|
||||
let mut lines = reader.lines();
|
||||
|
||||
while let Some(Ok(line)) = lines.next() {
|
||||
if line == "=MOD LIST START=" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(Ok(line)) = lines.next() {
|
||||
let mut id = String::new();
|
||||
let mut requirement = Requirement::Unlocked;
|
||||
let mut priority = 1000u32;
|
||||
let mut path = String::new();
|
||||
let mut chars = line.chars().peekable();
|
||||
|
||||
fn consume_spaces(chars: &mut Peekable<Chars>) {
|
||||
while let Some(&c) = chars.peek() {
|
||||
if c != ' ' {
|
||||
break;
|
||||
}
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
|
||||
consume_spaces(&mut chars);
|
||||
id.push_str("csmod_");
|
||||
for c in &mut chars {
|
||||
if c == ' ' {
|
||||
break;
|
||||
}
|
||||
|
||||
id.push(c);
|
||||
}
|
||||
consume_spaces(&mut chars);
|
||||
|
||||
loop {
|
||||
if let Some(c) = chars.next() {
|
||||
if c == 'R' {
|
||||
if let Some(c) = chars.next() {
|
||||
if c == '+' {
|
||||
requirement = Requirement::Unlocked;
|
||||
} else if c == '-' {
|
||||
requirement = Requirement::Locked;
|
||||
} else if c == 'I' {
|
||||
chars.next();
|
||||
let mut item_id = String::new();
|
||||
for c in &mut chars {
|
||||
if c == ' ' {
|
||||
break;
|
||||
}
|
||||
|
||||
item_id.push(c);
|
||||
}
|
||||
requirement = Requirement::RequireItem(item_id.parse().unwrap_or(0));
|
||||
} else if c == 'A' {
|
||||
chars.next();
|
||||
let mut weapon_id = String::new();
|
||||
for c in &mut chars {
|
||||
if c == ' ' {
|
||||
break;
|
||||
}
|
||||
|
||||
weapon_id.push(c);
|
||||
}
|
||||
requirement = Requirement::RequireWeapon(weapon_id.parse().unwrap_or(0));
|
||||
} else if c == 'G' {
|
||||
requirement = Requirement::RequireHell;
|
||||
}
|
||||
}
|
||||
} else if c == 'P' {
|
||||
priority = 0;
|
||||
|
||||
for c in &mut chars {
|
||||
if c == ' ' {
|
||||
break;
|
||||
}
|
||||
|
||||
priority = priority.saturating_mul(10).saturating_add(c.to_digit(10).unwrap_or(0));
|
||||
}
|
||||
} else if c == '/' {
|
||||
path.push(c);
|
||||
while let Some(&c) = chars.peek() {
|
||||
path.push(c);
|
||||
chars.next();
|
||||
}
|
||||
|
||||
break;
|
||||
} else if c == ' ' {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mods.push(ModInfo { id, requirement, priority, path })
|
||||
}
|
||||
}
|
||||
|
||||
mods.sort_by(|a, b| a.priority.cmp(&b.priority));
|
||||
|
||||
Ok(ModList { mods })
|
||||
}
|
||||
}
|
|
@ -1,14 +1,8 @@
|
|||
use imgui::Ui;
|
||||
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::ui::Components;
|
||||
use crate::npc::NPCTable;
|
||||
use crate::scene::no_data_scene::NoDataScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::StageData;
|
||||
|
||||
pub struct LoadingScene {
|
||||
tick: usize,
|
||||
|
|
|
@ -33,8 +33,9 @@ pub struct TitleScene {
|
|||
controller: CombinedMenuController,
|
||||
current_menu: CurrentMenu,
|
||||
main_menu: Menu,
|
||||
settings_menu: SettingsMenu,
|
||||
save_select_menu: SaveSelectMenu,
|
||||
challenges_menu: Menu,
|
||||
settings_menu: SettingsMenu,
|
||||
background: Background,
|
||||
frame: Frame,
|
||||
nikumaru_rec: NikumaruCounter,
|
||||
|
@ -70,8 +71,9 @@ impl TitleScene {
|
|||
controller: CombinedMenuController::new(),
|
||||
current_menu: CurrentMenu::MainMenu,
|
||||
main_menu: Menu::new(0, 0, 100, 0),
|
||||
settings_menu,
|
||||
save_select_menu: SaveSelectMenu::new(),
|
||||
challenges_menu: Menu::new(0, 0, 150, 0),
|
||||
settings_menu,
|
||||
background: Background::new(),
|
||||
frame: Frame::new(),
|
||||
nikumaru_rec: NikumaruCounter::new(),
|
||||
|
@ -127,10 +129,20 @@ static COPYRIGHT_PIXEL: &str = "2004.12 Studio Pixel";
|
|||
|
||||
impl Scene for TitleScene {
|
||||
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if !state.mod_path.is_none() {
|
||||
state.mod_path = None;
|
||||
state.reload_resources(ctx)?;
|
||||
}
|
||||
|
||||
self.controller.add(state.settings.create_player1_controller());
|
||||
self.controller.add(state.settings.create_player2_controller());
|
||||
|
||||
self.main_menu.push_entry(MenuEntry::Active("Start Game".to_string()));
|
||||
if state.constants.is_cs_plus {
|
||||
self.main_menu.push_entry(MenuEntry::Active("Challenges".to_string()));
|
||||
} else {
|
||||
self.main_menu.push_entry(MenuEntry::Hidden);
|
||||
}
|
||||
self.main_menu.push_entry(MenuEntry::Active("Options".to_string()));
|
||||
if cfg!(feature = "editor") {
|
||||
self.main_menu.push_entry(MenuEntry::Active("Editor".to_string()));
|
||||
|
@ -143,6 +155,11 @@ impl Scene for TitleScene {
|
|||
|
||||
self.save_select_menu.init(state, ctx)?;
|
||||
|
||||
for mod_info in state.mod_list.mods.iter() {
|
||||
self.challenges_menu.push_entry(MenuEntry::Active(mod_info.path.clone()));
|
||||
}
|
||||
self.challenges_menu.push_entry(MenuEntry::Active("< Back".to_string()));
|
||||
|
||||
self.controller.update(state, ctx)?;
|
||||
self.controller.update_trigger();
|
||||
|
||||
|
@ -162,15 +179,23 @@ impl Scene for TitleScene {
|
|||
self.main_menu.x = ((state.canvas_size.0 - self.main_menu.width as f32) / 2.0).floor() as isize;
|
||||
self.main_menu.y = ((state.canvas_size.1 + 70.0 - self.main_menu.height as f32) / 2.0).floor() as isize;
|
||||
|
||||
self.challenges_menu.update_height();
|
||||
self.challenges_menu.x = ((state.canvas_size.0 - self.challenges_menu.width as f32) / 2.0).floor() as isize;
|
||||
self.challenges_menu.y =
|
||||
((state.canvas_size.1 + 30.0 - self.challenges_menu.height as f32) / 2.0).floor() as isize;
|
||||
|
||||
match self.current_menu {
|
||||
CurrentMenu::MainMenu => match self.main_menu.tick(&mut self.controller, state) {
|
||||
MenuSelectionResult::Selected(0, _) => {
|
||||
self.current_menu = CurrentMenu::SaveSelectMenu;
|
||||
}
|
||||
MenuSelectionResult::Selected(1, _) => {
|
||||
self.current_menu = CurrentMenu::OptionMenu;
|
||||
self.current_menu = CurrentMenu::ChallengesMenu;
|
||||
}
|
||||
MenuSelectionResult::Selected(2, _) => {
|
||||
self.current_menu = CurrentMenu::OptionMenu;
|
||||
}
|
||||
MenuSelectionResult::Selected(3, _) => {
|
||||
// this comment is just there because rustfmt removes parenthesis around the match case and breaks compilation
|
||||
#[cfg(feature = "editor")]
|
||||
{
|
||||
|
@ -178,7 +203,7 @@ impl Scene for TitleScene {
|
|||
state.next_scene = Some(Box::new(EditorScene::new()));
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(3, _) => {
|
||||
MenuSelectionResult::Selected(4, _) => {
|
||||
state.shutdown();
|
||||
}
|
||||
_ => {}
|
||||
|
@ -220,7 +245,25 @@ impl Scene for TitleScene {
|
|||
state.load_or_start_game(ctx)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
CurrentMenu::ChallengesMenu => {
|
||||
let last_idx = self.challenges_menu.entries.len() - 1;
|
||||
match self.challenges_menu.tick(&mut self.controller, state) {
|
||||
MenuSelectionResult::Selected(idx, _) => {
|
||||
if last_idx == idx {
|
||||
self.current_menu = CurrentMenu::MainMenu;
|
||||
} else if let Some(mod_info) = state.mod_list.mods.get(idx) {
|
||||
state.save_slot = 4;
|
||||
state.mod_path = Some(mod_info.path.clone());
|
||||
state.reload_resources(ctx)?;
|
||||
state.start_new_game(ctx)?;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Canceled => {
|
||||
self.current_menu = CurrentMenu::MainMenu;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.tick += 1;
|
||||
|
@ -254,6 +297,7 @@ impl Scene for TitleScene {
|
|||
|
||||
match self.current_menu {
|
||||
CurrentMenu::MainMenu => self.main_menu.draw(state, ctx)?,
|
||||
CurrentMenu::ChallengesMenu => self.challenges_menu.draw(state, ctx)?,
|
||||
CurrentMenu::OptionMenu => self.settings_menu.draw(state, ctx)?,
|
||||
CurrentMenu::SaveSelectMenu => self.save_select_menu.draw(state, ctx)?,
|
||||
_ => {}
|
||||
|
|
|
@ -18,6 +18,7 @@ use crate::framework::vfs::OpenOptions;
|
|||
#[cfg(feature = "hooks")]
|
||||
use crate::hooks::init_hooks;
|
||||
use crate::input::touch_controls::TouchControls;
|
||||
use crate::mod_list::ModList;
|
||||
use crate::npc::NPCTable;
|
||||
use crate::profile::GameProfile;
|
||||
use crate::rng::XorShift;
|
||||
|
@ -159,6 +160,7 @@ pub struct SharedGameState {
|
|||
pub carets: Vec<Caret>,
|
||||
pub touch_controls: TouchControls,
|
||||
pub mod_path: Option<String>,
|
||||
pub mod_list: ModList,
|
||||
pub npc_table: NPCTable,
|
||||
pub npc_super_pos: (i32, i32),
|
||||
pub npc_curly_target: (i32, i32),
|
||||
|
@ -211,11 +213,12 @@ impl SharedGameState {
|
|||
let season = Season::current();
|
||||
constants.rebuild_path_list(None, season, &settings);
|
||||
|
||||
let font = BMFontRenderer::load(&constants.base_paths, &constants.font_path, ctx)
|
||||
.or_else(|e| {
|
||||
log::warn!("Failed to load font, using built-in: {}", e);
|
||||
BMFontRenderer::load(&vec!["/".to_owned()], "/builtin/builtin_font.fnt", ctx)
|
||||
})?;
|
||||
let font = BMFontRenderer::load(&constants.base_paths, &constants.font_path, ctx).or_else(|e| {
|
||||
log::warn!("Failed to load font, using built-in: {}", e);
|
||||
BMFontRenderer::load(&vec!["/".to_owned()], "/builtin/builtin_font.fnt", ctx)
|
||||
})?;
|
||||
|
||||
let mut mod_list = ModList::load(ctx)?;
|
||||
|
||||
for i in 0..0xffu8 {
|
||||
let path = format!("/pxt/fx{:02x}.pxt", i);
|
||||
|
@ -252,6 +255,7 @@ impl SharedGameState {
|
|||
carets: Vec::with_capacity(32),
|
||||
touch_controls: TouchControls::new(),
|
||||
mod_path: None,
|
||||
mod_list,
|
||||
npc_table: NPCTable::new(),
|
||||
npc_super_pos: (0, 0),
|
||||
npc_curly_target: (0, 0),
|
||||
|
|
Loading…
Reference in a new issue