Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Badges and scoreboards #27

Merged
merged 29 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fbc6019
crank up linter warnings
orsinium Oct 26, 2024
b34b9ed
add badges config
orsinium Oct 26, 2024
244afe0
add boards config
orsinium Oct 26, 2024
e2d251b
write badges
orsinium Oct 27, 2024
8ea2e74
write boards
orsinium Oct 27, 2024
f1922db
use Encode trait from firefly-types
orsinium Oct 27, 2024
7b53e60
create stats
orsinium Oct 27, 2024
5f9fa34
update existing stats
orsinium Oct 27, 2024
c2808e2
add command to list badges
orsinium Oct 27, 2024
45d4a04
show player progress for badges
orsinium Oct 28, 2024
cdf43f1
make Badge.hidden a number
orsinium Oct 28, 2024
265982d
add boards command
orsinium Oct 28, 2024
799ba7d
populate scores in stats
orsinium Oct 30, 2024
120acd1
display scores for each board
orsinium Oct 30, 2024
3bf5815
create default settings
orsinium Oct 30, 2024
2ac860f
actualize tests
orsinium Oct 30, 2024
63d667e
make TOML table keys strings
orsinium Oct 31, 2024
ca42ef3
bugfixes for badges
orsinium Oct 31, 2024
4514d9d
allow negative scores
orsinium Nov 6, 2024
8059da5
use upstream firefly-types
orsinium Nov 9, 2024
53902af
use friends' names in scoreboards
orsinium Nov 9, 2024
a88d66e
test merge_scores
orsinium Nov 9, 2024
4fb6703
test load_friends
orsinium Nov 9, 2024
b40ec7a
simplify tests
orsinium Nov 9, 2024
ba5b357
simplify cmd_badges
orsinium Nov 9, 2024
a1171d5
update dependencies
orsinium Nov 9, 2024
91cc63e
reformat imports
orsinium Nov 9, 2024
bc42df2
decrease scores and badges to 20
orsinium Nov 9, 2024
f40373d
test format_size
orsinium Nov 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
532 changes: 450 additions & 82 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ categories = [

[dependencies]
# Simpler error handling
anyhow = "1.0.90"
anyhow = "1.0.93"
# Get current date and time.
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
# Framework for parsing CLI args
clap = { version = "4.5.20", features = ["derive"] }
# TUI for the "monitor" command, colored terminal output
Expand All @@ -29,11 +31,11 @@ data-encoding = "2.6.0"
# Find the best place to sotre the VFS
directories = "5.0.1"
# Serialize app config into meta file in the ROM
firefly-types = { version = "0.2.0" }
firefly-types = { version = "0.3.0" }
# Decode wav files
hound = "3.5.1"
# Parse PNG images
image = { version = "0.25.4", default-features = false, features = ["png"] }
image = { version = "0.25.5", default-features = false, features = ["png"] }
# Random device name generation
rand = "0.8.5"
# Signatures
Expand All @@ -44,7 +46,7 @@ rsa = { version = "0.9.6", default-features = false, features = [
# REPL
rustyline = "14.0.0"
# Deserialize firefly.toml (required by `toml`)
serde = { version = "1.0.210", features = ["serde_derive", "derive"] }
serde = { version = "1.0.214", features = ["serde_derive", "derive"] }
# Deserialize JSON API responses from the firefly catalog.
serde_json = "1.0.132"
# Calculate file checksum
Expand Down
31 changes: 31 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ pub enum Commands {
#[clap(alias("install"))]
Import(ImportArgs),

/// List all badges (aka achievements) defined in the given app.
#[clap(alias("badge"), alias("achievements"), alias("achievement"))]
Badges(BadgesArgs),

/// List all boards (aka scoreboards or leaderboards) defined in the given app.
#[clap(
alias("board"),
alias("scoreboard"),
alias("leaderboard"),
alias("scoreboards"),
alias("leaderboards"),
alias("scores")
)]
Boards(BoardsArgs),

/// Show the full path to the virtual filesystem.
Vfs,

Expand Down Expand Up @@ -167,6 +182,22 @@ pub struct ExportArgs {
pub output: Option<PathBuf>,
}

#[derive(Debug, Parser)]
pub struct BadgesArgs {
/// Full app ID.
pub id: String,

/// Show hidden badges.
#[arg(long, default_value_t = false)]
pub hidden: bool,
}

#[derive(Debug, Parser)]
pub struct BoardsArgs {
/// Full app ID.
pub id: String,
}

#[derive(Debug, Parser)]
pub struct ImportArgs {
/// The ROM to install.
Expand Down
4 changes: 3 additions & 1 deletion src/audio.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use anyhow::{bail, Context, Result};
use hound::{SampleFormat, WavReader};
use std::{fs::File, io::Write, path::Path};
use std::fs::File;
use std::io::Write;
use std::path::Path;

pub fn convert_wav(input_path: &Path, output_path: &Path) -> Result<()> {
let mut reader = WavReader::open(input_path).context("open wav file")?;
Expand Down
2 changes: 2 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub fn run_command(vfs: PathBuf, command: &Commands) -> anyhow::Result<()> {
Commands::Build(args) => cmd_build(vfs, args),
Commands::Export(args) => cmd_export(&vfs, args),
Commands::Import(args) => cmd_import(&vfs, args),
Commands::Badges(args) => cmd_badges(&vfs, args),
Commands::Boards(args) => cmd_boards(&vfs, args),
Commands::Cheat(args) => cmd_cheat(args),
Commands::Monitor(args) => cmd_monitor(&vfs, args),
Commands::Inspect(args) => cmd_inspect(&vfs, args),
Expand Down
63 changes: 63 additions & 0 deletions src/commands/badges.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::args::BadgesArgs;
use crate::file_names::BADGES;
use anyhow::{bail, Context, Result};
use crossterm::style::Stylize;
use firefly_types::Encode;
use std::path::Path;

pub fn cmd_badges(vfs: &Path, args: &BadgesArgs) -> Result<()> {
let Some((author_id, app_id)) = args.id.split_once('.') else {
bail!("invalid app id: dot not found");
};

// read stats
let stats_path = vfs.join("data").join(author_id).join(app_id).join("stats");
let raw = std::fs::read(stats_path).context("read stats file")?;
let stats = firefly_types::Stats::decode(&raw).context("decode stats")?;

// read badges
let rom_path = vfs.join("roms").join(author_id).join(app_id);
if !rom_path.exists() {
bail!("app {author_id}.{app_id} is not installed");
}
let badges_path = rom_path.join(BADGES);
if !badges_path.exists() {
bail!("the app does not have badges");
}
let raw = std::fs::read(badges_path).context("read badges file")?;
let badges = firefly_types::Badges::decode(&raw).context("decode badges")?;
let mut badges: Vec<_> = badges.badges.iter().zip(1..).collect();
badges.sort_by_key(|(badge, _id)| badge.position);

display_badges(&badges, &stats, args)
}

fn display_badges(
badges: &[(&firefly_types::Badge<'_>, usize)],
stats: &firefly_types::Stats,
args: &BadgesArgs,
) -> Result<()> {
for (badge, id) in badges {
let Some(progress) = stats.badges.get(id - 1) else {
bail!("there are fewer badges in stats file than in the rom");
};
if progress.done < badge.hidden {
if !args.hidden {
continue;
}
print!("{}", "[hidden] ".grey());
};
println!("#{id} {} ({} XP)", badge.name.cyan(), badge.xp);
println!("{}", badge.descr);
let emoji = if progress.earned() {
"✅"
} else if progress.done == 0 {
"🚫"
} else {
"⌛"
};
println!("{emoji} {}/{}", progress.done, progress.goal);
println!();
}
Ok(())
}
Loading
Loading