1
0
Fork 0
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:
József Sallai 2022-03-16 00:18:25 +02:00 committed by GitHub
parent 1795d71b37
commit 7e3fef8d41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 120 additions and 3 deletions

View file

@ -49,6 +49,7 @@ mod macros;
mod map;
mod menu;
mod mod_list;
mod mod_requirements;
#[cfg(feature = "netplay")]
mod netplay;
mod npc;

View file

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

View file

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

View file

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

View file

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

View file

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