mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-11-29 15:56:53 +00:00
add challenge unlocking (#90)
This commit is contained in:
parent
1795d71b37
commit
7e3fef8d41
|
|
@ -49,6 +49,7 @@ mod macros;
|
||||||
mod map;
|
mod map;
|
||||||
mod menu;
|
mod menu;
|
||||||
mod mod_list;
|
mod mod_list;
|
||||||
|
mod mod_requirements;
|
||||||
#[cfg(feature = "netplay")]
|
#[cfg(feature = "netplay")]
|
||||||
mod netplay;
|
mod netplay;
|
||||||
mod npc;
|
mod npc;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use std::iter::Peekable;
|
||||||
use std::str::Chars;
|
use std::str::Chars;
|
||||||
|
|
||||||
use crate::framework::filesystem;
|
use crate::framework::filesystem;
|
||||||
|
use crate::mod_requirements::ModRequirements;
|
||||||
use crate::{Context, GameResult};
|
use crate::{Context, GameResult};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -17,6 +18,18 @@ pub struct ModInfo {
|
||||||
pub description: String,
|
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)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Requirement {
|
pub enum Requirement {
|
||||||
/// R+
|
/// R+
|
||||||
|
|
@ -85,7 +98,6 @@ impl ModList {
|
||||||
} else if c == '-' {
|
} else if c == '-' {
|
||||||
requirement = Requirement::Locked;
|
requirement = Requirement::Locked;
|
||||||
} else if c == 'I' {
|
} else if c == 'I' {
|
||||||
chars.next();
|
|
||||||
let mut item_id = String::new();
|
let mut item_id = String::new();
|
||||||
for c in &mut chars {
|
for c in &mut chars {
|
||||||
if c == ' ' {
|
if c == ' ' {
|
||||||
|
|
@ -96,7 +108,6 @@ impl ModList {
|
||||||
}
|
}
|
||||||
requirement = Requirement::RequireItem(item_id.parse().unwrap_or(0));
|
requirement = Requirement::RequireItem(item_id.parse().unwrap_or(0));
|
||||||
} else if c == 'A' {
|
} else if c == 'A' {
|
||||||
chars.next();
|
|
||||||
let mut weapon_id = String::new();
|
let mut weapon_id = String::new();
|
||||||
for c in &mut chars {
|
for c in &mut chars {
|
||||||
if c == ' ' {
|
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 {
|
if weapon.weapon_id == 0 {
|
||||||
continue;
|
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);
|
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon.weapon_id as u8);
|
||||||
|
|
||||||
if let Some(wtype) = weapon_type {
|
if let Some(wtype) = weapon_type {
|
||||||
|
|
@ -81,6 +83,8 @@ impl GameProfile {
|
||||||
|
|
||||||
for item in self.items.iter().copied() {
|
for item in self.items.iter().copied() {
|
||||||
let item_id = item as u16;
|
let item_id = item as u16;
|
||||||
|
let _ = state.mod_requirements.append_item(ctx, item_id);
|
||||||
|
|
||||||
let amount = (item >> 16) as u16;
|
let amount = (item >> 16) as u16;
|
||||||
if item_id == 0 {
|
if item_id == 0 {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -171,10 +171,23 @@ impl Scene for TitleScene {
|
||||||
|
|
||||||
self.save_select_menu.init(state, ctx)?;
|
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() {
|
for mod_info in state.mod_list.mods.iter() {
|
||||||
|
if mod_info.satisfies_requirement(&state.mod_requirements) {
|
||||||
self.challenges_menu.push_entry(MenuEntry::Active(mod_info.name.clone()));
|
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.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::Disabled("".to_owned()));
|
||||||
self.confirm_menu.push_entry(MenuEntry::Active(state.t("menus.challenge_menu.start")));
|
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) {
|
if !game_scene.inventory_player1.has_item(item_id) {
|
||||||
game_scene.inventory_player1.add_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) {
|
if !game_scene.inventory_player2.has_item(item_id) {
|
||||||
game_scene.inventory_player2.add_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);
|
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) {
|
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Less, amount) {
|
||||||
game_scene.inventory_player1.add_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_amount(item_id, Ordering::Less, amount) {
|
if game_scene.inventory_player2.has_item_amount(item_id, Ordering::Less, amount) {
|
||||||
game_scene.inventory_player2.add_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);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
|
|
@ -1525,6 +1529,7 @@ impl TextScriptVM {
|
||||||
if let Some(wtype) = weapon_type {
|
if let Some(wtype) = weapon_type {
|
||||||
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
|
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
|
||||||
game_scene.inventory_player2.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);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
|
|
@ -1681,6 +1686,11 @@ impl TextScriptVM {
|
||||||
TSCOpCode::XX1 => {
|
TSCOpCode::XX1 => {
|
||||||
let mode = read_cur_varint(&mut cursor)?;
|
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(
|
exec_state = TextScriptExecutionState::FallingIsland(
|
||||||
event,
|
event,
|
||||||
cursor.position() as u32,
|
cursor.position() as u32,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use crate::hooks::init_hooks;
|
||||||
use crate::i18n::Locale;
|
use crate::i18n::Locale;
|
||||||
use crate::input::touch_controls::TouchControls;
|
use crate::input::touch_controls::TouchControls;
|
||||||
use crate::mod_list::ModList;
|
use crate::mod_list::ModList;
|
||||||
|
use crate::mod_requirements::ModRequirements;
|
||||||
use crate::npc::NPCTable;
|
use crate::npc::NPCTable;
|
||||||
use crate::profile::GameProfile;
|
use crate::profile::GameProfile;
|
||||||
use crate::rng::XorShift;
|
use crate::rng::XorShift;
|
||||||
|
|
@ -265,6 +266,7 @@ pub struct SharedGameState {
|
||||||
pub save_slot: usize,
|
pub save_slot: usize,
|
||||||
pub difficulty: GameDifficulty,
|
pub difficulty: GameDifficulty,
|
||||||
pub replay_state: ReplayState,
|
pub replay_state: ReplayState,
|
||||||
|
pub mod_requirements: ModRequirements,
|
||||||
pub shutdown: bool,
|
pub shutdown: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +275,7 @@ impl SharedGameState {
|
||||||
let mut constants = EngineConstants::defaults();
|
let mut constants = EngineConstants::defaults();
|
||||||
let sound_manager = SoundManager::new(ctx)?;
|
let sound_manager = SoundManager::new(ctx)?;
|
||||||
let settings = Settings::load(ctx)?;
|
let settings = Settings::load(ctx)?;
|
||||||
|
let mod_requirements = ModRequirements::load(ctx)?;
|
||||||
|
|
||||||
constants.load_locales(ctx)?;
|
constants.load_locales(ctx)?;
|
||||||
|
|
||||||
|
|
@ -389,6 +392,7 @@ impl SharedGameState {
|
||||||
save_slot: 1,
|
save_slot: 1,
|
||||||
difficulty: GameDifficulty::Normal,
|
difficulty: GameDifficulty::Normal,
|
||||||
replay_state: ReplayState::None,
|
replay_state: ReplayState::None,
|
||||||
|
mod_requirements,
|
||||||
shutdown: false,
|
shutdown: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue