mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-03-24 02:49:21 +00:00
add challenge unlocking (#90)
This commit is contained in:
parent
1795d71b37
commit
7e3fef8d41
|
@ -49,6 +49,7 @@ mod macros;
|
|||
mod map;
|
||||
mod menu;
|
||||
mod mod_list;
|
||||
mod mod_requirements;
|
||||
#[cfg(feature = "netplay")]
|
||||
mod netplay;
|
||||
mod npc;
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::iter::Peekable;
|
|||
use std::str::Chars;
|
||||
|
||||
use crate::framework::filesystem;
|
||||
use crate::mod_requirements::ModRequirements;
|
||||
use crate::{Context, GameResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -17,6 +18,18 @@ pub struct ModInfo {
|
|||
pub description: String,
|
||||
}
|
||||
|
||||
impl ModInfo {
|
||||
pub fn satisfies_requirement(&self, mod_requirements: &ModRequirements) -> bool {
|
||||
match self.requirement {
|
||||
Requirement::Unlocked => true,
|
||||
Requirement::Locked => false,
|
||||
Requirement::RequireHell => mod_requirements.beat_hell,
|
||||
Requirement::RequireItem(item_id) => mod_requirements.has_item(item_id),
|
||||
Requirement::RequireWeapon(weapon_id) => mod_requirements.has_weapon(weapon_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Requirement {
|
||||
/// R+
|
||||
|
@ -85,7 +98,6 @@ impl ModList {
|
|||
} 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 == ' ' {
|
||||
|
@ -96,7 +108,6 @@ impl ModList {
|
|||
}
|
||||
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 == ' ' {
|
||||
|
|
74
src/mod_requirements.rs
Normal file
74
src/mod_requirements.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct ModRequirements {
|
||||
#[serde(default = "current_version")]
|
||||
pub version: u32,
|
||||
|
||||
pub beat_hell: bool,
|
||||
pub weapons: Vec<u16>,
|
||||
pub items: Vec<u16>,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn current_version() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
impl ModRequirements {
|
||||
pub fn load(ctx: &Context) -> GameResult<ModRequirements> {
|
||||
if let Ok(file) = filesystem::user_open(ctx, "/mod_req.json") {
|
||||
match serde_json::from_reader::<_, ModRequirements>(file) {
|
||||
Ok(mod_req) => return Ok(mod_req.upgrade()),
|
||||
Err(err) => log::warn!("Failed to deserialize mod requirements: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ModRequirements::default())
|
||||
}
|
||||
|
||||
fn upgrade(mut self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn save(&self, ctx: &Context) -> GameResult {
|
||||
let file = filesystem::user_create(ctx, "/mod_req.json")?;
|
||||
serde_json::to_writer_pretty(file, self)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn append_weapon(&mut self, ctx: &Context, weapon_id: u16) -> GameResult {
|
||||
if self.weapons.contains(&weapon_id) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.weapons.push(weapon_id);
|
||||
self.save(ctx)
|
||||
}
|
||||
|
||||
pub fn append_item(&mut self, ctx: &Context, item_id: u16) -> GameResult {
|
||||
if self.items.contains(&item_id) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.items.push(item_id);
|
||||
self.save(ctx)
|
||||
}
|
||||
|
||||
pub fn has_weapon(&self, weapon_id: u16) -> bool {
|
||||
self.weapons.contains(&weapon_id)
|
||||
}
|
||||
|
||||
pub fn has_item(&self, item_id: u16) -> bool {
|
||||
self.items.contains(&item_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ModRequirements {
|
||||
fn default() -> Self {
|
||||
ModRequirements { version: current_version(), beat_hell: false, weapons: Vec::new(), items: Vec::new() }
|
||||
}
|
||||
}
|
|
@ -62,6 +62,8 @@ impl GameProfile {
|
|||
if weapon.weapon_id == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = state.mod_requirements.append_weapon(ctx, weapon.weapon_id as u16);
|
||||
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon.weapon_id as u8);
|
||||
|
||||
if let Some(wtype) = weapon_type {
|
||||
|
@ -81,6 +83,8 @@ impl GameProfile {
|
|||
|
||||
for item in self.items.iter().copied() {
|
||||
let item_id = item as u16;
|
||||
let _ = state.mod_requirements.append_item(ctx, item_id);
|
||||
|
||||
let amount = (item >> 16) as u16;
|
||||
if item_id == 0 {
|
||||
break;
|
||||
|
|
|
@ -171,10 +171,23 @@ impl Scene for TitleScene {
|
|||
|
||||
self.save_select_menu.init(state, ctx)?;
|
||||
|
||||
let mut selected: usize = 0;
|
||||
let mut mutate_selection = true;
|
||||
|
||||
for mod_info in state.mod_list.mods.iter() {
|
||||
self.challenges_menu.push_entry(MenuEntry::Active(mod_info.name.clone()));
|
||||
if mod_info.satisfies_requirement(&state.mod_requirements) {
|
||||
self.challenges_menu.push_entry(MenuEntry::Active(mod_info.name.clone()));
|
||||
mutate_selection = false;
|
||||
} else {
|
||||
self.challenges_menu.push_entry(MenuEntry::Disabled("???".to_owned()));
|
||||
|
||||
if mutate_selection {
|
||||
selected += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.challenges_menu.push_entry(MenuEntry::Active(state.t("common.back")));
|
||||
self.challenges_menu.selected = selected;
|
||||
|
||||
self.confirm_menu.push_entry(MenuEntry::Disabled("".to_owned()));
|
||||
self.confirm_menu.push_entry(MenuEntry::Active(state.t("menus.challenge_menu.start")));
|
||||
|
|
|
@ -1485,10 +1485,12 @@ impl TextScriptVM {
|
|||
|
||||
if !game_scene.inventory_player1.has_item(item_id) {
|
||||
game_scene.inventory_player1.add_item(item_id);
|
||||
state.mod_requirements.append_item(ctx, item_id)?;
|
||||
}
|
||||
|
||||
if !game_scene.inventory_player2.has_item(item_id) {
|
||||
game_scene.inventory_player2.add_item(item_id);
|
||||
state.mod_requirements.append_item(ctx, item_id)?;
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -1499,10 +1501,12 @@ impl TextScriptVM {
|
|||
|
||||
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Less, amount) {
|
||||
game_scene.inventory_player1.add_item(item_id);
|
||||
state.mod_requirements.append_item(ctx, item_id)?;
|
||||
}
|
||||
|
||||
if game_scene.inventory_player2.has_item_amount(item_id, Ordering::Less, amount) {
|
||||
game_scene.inventory_player2.add_item(item_id);
|
||||
state.mod_requirements.append_item(ctx, item_id)?;
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -1525,6 +1529,7 @@ impl TextScriptVM {
|
|||
if let Some(wtype) = weapon_type {
|
||||
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
|
||||
game_scene.inventory_player2.add_weapon(wtype, max_ammo);
|
||||
state.mod_requirements.append_weapon(ctx, weapon_id as u16)?;
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -1681,6 +1686,11 @@ impl TextScriptVM {
|
|||
TSCOpCode::XX1 => {
|
||||
let mode = read_cur_varint(&mut cursor)?;
|
||||
|
||||
if mode != 0 && !state.mod_requirements.beat_hell {
|
||||
state.mod_requirements.beat_hell = true;
|
||||
state.mod_requirements.save(ctx)?;
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::FallingIsland(
|
||||
event,
|
||||
cursor.position() as u32,
|
||||
|
|
|
@ -21,6 +21,7 @@ use crate::hooks::init_hooks;
|
|||
use crate::i18n::Locale;
|
||||
use crate::input::touch_controls::TouchControls;
|
||||
use crate::mod_list::ModList;
|
||||
use crate::mod_requirements::ModRequirements;
|
||||
use crate::npc::NPCTable;
|
||||
use crate::profile::GameProfile;
|
||||
use crate::rng::XorShift;
|
||||
|
@ -265,6 +266,7 @@ pub struct SharedGameState {
|
|||
pub save_slot: usize,
|
||||
pub difficulty: GameDifficulty,
|
||||
pub replay_state: ReplayState,
|
||||
pub mod_requirements: ModRequirements,
|
||||
pub shutdown: bool,
|
||||
}
|
||||
|
||||
|
@ -273,6 +275,7 @@ impl SharedGameState {
|
|||
let mut constants = EngineConstants::defaults();
|
||||
let sound_manager = SoundManager::new(ctx)?;
|
||||
let settings = Settings::load(ctx)?;
|
||||
let mod_requirements = ModRequirements::load(ctx)?;
|
||||
|
||||
constants.load_locales(ctx)?;
|
||||
|
||||
|
@ -389,6 +392,7 @@ impl SharedGameState {
|
|||
save_slot: 1,
|
||||
difficulty: GameDifficulty::Normal,
|
||||
replay_state: ReplayState::None,
|
||||
mod_requirements,
|
||||
shutdown: false,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue