initial cs+ challenge support

This commit is contained in:
Alula 2022-02-10 10:21:28 +01:00
parent c127ee4bd4
commit 8e2088adb4
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
6 changed files with 208 additions and 18 deletions

View File

@ -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);
}
}
}

View File

@ -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
View 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 })
}
}

View File

@ -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,

View File

@ -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)?,
_ => {}

View File

@ -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),