Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2960b6dfc4 | |||
| f88e010b4f |
12 changed files with 338 additions and 159 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -332,7 +332,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest"
|
name = "squad-quest"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
@ -340,7 +340,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest-cli"
|
name = "squad-quest-cli"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
members = ["cli"]
|
members = ["cli"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.4.0"
|
version = "0.5.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
repository = "https://2ndbeam.ru/git/2ndbeam/squad-quest"
|
repository = "https://2ndbeam.ru/git/2ndbeam/squad-quest"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
# Default config
|
quests_path = "quests"
|
||||||
|
accounts_path = "accounts"
|
||||||
# Path to quests folder relative to config
|
map = "map.toml"
|
||||||
quests_path = "./quests"
|
verbose = true
|
||||||
|
|
||||||
# Path to accounts folder relative to config
|
|
||||||
accounts_path = "./accounts"
|
|
||||||
|
|
||||||
# Path to map .toml file relative to config
|
|
||||||
map = "./map.toml"
|
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,5 @@ license.workspace = true
|
||||||
chrono = "0.4.42"
|
chrono = "0.4.42"
|
||||||
clap = { version = "4.5.53", features = ["derive"] }
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
squad-quest = { version = "0.4.0", path = ".." }
|
squad-quest = { version = "0.5.0", path = ".." }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ pub enum AccountCommands {
|
||||||
Complete(AccountCompleteArgs),
|
Complete(AccountCompleteArgs),
|
||||||
/// Delete account
|
/// Delete account
|
||||||
Delete(AccountDeleteArgs),
|
Delete(AccountDeleteArgs),
|
||||||
|
/// Unlock room for account if it has enough balance
|
||||||
|
Unlock(AccountUnlockArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -55,3 +57,10 @@ pub struct AccountDeleteArgs {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct AccountUnlockArgs {
|
||||||
|
/// Id of the account
|
||||||
|
pub account: String,
|
||||||
|
/// Id of the room to unlock
|
||||||
|
pub room: u16,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Parser,Subcommand};
|
use clap::{Args,Parser,Subcommand};
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
|
|
@ -23,6 +23,8 @@ pub struct Cli {
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum Objects {
|
pub enum Objects {
|
||||||
|
/// Initialize new SquadQuest in current working directory
|
||||||
|
Init(InitArgs),
|
||||||
/// Operations on the quests
|
/// Operations on the quests
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Quest(quest::QuestCommands),
|
Quest(quest::QuestCommands),
|
||||||
|
|
@ -34,3 +36,8 @@ pub enum Objects {
|
||||||
Map(map::MapCommands),
|
Map(map::MapCommands),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct InitArgs {
|
||||||
|
#[arg(long,short)]
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ pub struct QuestListArgs {
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub struct QuestCreateArgs {
|
pub struct QuestCreateArgs {
|
||||||
/// Difficulty of the quest #[arg(value_enum)]
|
/// Difficulty of the quest
|
||||||
|
#[arg(value_enum)]
|
||||||
pub difficulty: QuestDifficulty,
|
pub difficulty: QuestDifficulty,
|
||||||
/// Reward for the quest
|
/// Reward for the quest
|
||||||
pub reward: u32,
|
pub reward: u32,
|
||||||
|
|
|
||||||
295
cli/src/main.rs
295
cli/src/main.rs
|
|
@ -1,8 +1,8 @@
|
||||||
use std::path::Path;
|
use std::{fs::DirBuilder, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use squad_quest_cli::cli::{Cli,Objects,account::*,map::*,quest::*};
|
use squad_quest_cli::cli::{Cli,Objects,account::*,map::*,quest::*};
|
||||||
use squad_quest::{SquadObject, account::Account, config::Config, map::{Map, Room}, quest::Quest};
|
use squad_quest::{SquadObject, account::Account, config::Config, error::Error, map::{Map, Room}, quest::Quest};
|
||||||
use toml::value::Date;
|
use toml::value::Date;
|
||||||
use chrono::{Datelike, NaiveDate, Utc};
|
use chrono::{Datelike, NaiveDate, Utc};
|
||||||
|
|
||||||
|
|
@ -17,19 +17,25 @@ fn print_quest_long(quest: &Quest) {
|
||||||
println!("Answer:\n{}", quest.answer);
|
println!("Answer:\n{}", quest.answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn do_and_log(result: Result<(),Error>, log: bool, ok_text: String) {
|
||||||
let cli = Cli::parse();
|
match result {
|
||||||
|
Ok(_) if log => println!("{ok_text}"),
|
||||||
|
Err(error) if log => eprintln!("Error: {error}"),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let config = match cli.quiet {
|
fn load_config_silent(quiet: bool, path: PathBuf) -> Config {
|
||||||
false => Config::load(cli.config.clone()),
|
match quiet {
|
||||||
|
false => Config::load(path.clone()),
|
||||||
true => {
|
true => {
|
||||||
match Config::try_load(cli.config.clone()) {
|
match Config::try_load(path.clone()) {
|
||||||
Ok(mut config) => {
|
Ok(mut config) => {
|
||||||
config.verbose = false;
|
config.verbose = false;
|
||||||
config
|
config
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let path = cli.config.clone().parent().unwrap_or(&Path::new(".")).to_owned();
|
let path = path.clone().parent().unwrap_or(&Path::new(".")).to_owned();
|
||||||
Config {
|
Config {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
path,
|
path,
|
||||||
|
|
@ -38,9 +44,61 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let config = load_config_silent(cli.quiet, cli.config.clone());
|
||||||
|
let map_save = |map: Map, map_path: PathBuf| { map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) };
|
||||||
|
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
|
Objects::Init(args) => {
|
||||||
|
let path = match args.path.clone() {
|
||||||
|
Some(path) => path,
|
||||||
|
None => PathBuf::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match DirBuilder::new().recursive(true).create(path.clone()) {
|
||||||
|
Ok(_) if !cli.quiet => println!("Created directory {:?}", path),
|
||||||
|
Err(error) => {
|
||||||
|
if !cli.quiet { eprintln!("Error: {error}"); }
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
path: path.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
do_and_log(config.save(path.clone()), !cli.quiet, format!("Created file {:?}/config.toml", path));
|
||||||
|
let mut config_path = path.clone();
|
||||||
|
config_path.push("config.toml");
|
||||||
|
let mut config = load_config_silent(true, config_path);
|
||||||
|
config.verbose = Config::default().verbose;
|
||||||
|
|
||||||
|
let map = Map::default();
|
||||||
|
let map_path = config.full_map_path();
|
||||||
|
|
||||||
|
do_and_log(map_save(map, map_path.clone()), !cli.quiet, format!("Created file {:?}/map.toml", map_path));
|
||||||
|
|
||||||
|
let quests_path = config.full_quests_path();
|
||||||
|
let accounts_path = config.full_accounts_path();
|
||||||
|
|
||||||
|
for path in [quests_path, accounts_path] {
|
||||||
|
match DirBuilder::new().recursive(true).create(path.clone()) {
|
||||||
|
Ok(_) if !cli.quiet => println!("Created directory {:?}", path),
|
||||||
|
Err(error) => {
|
||||||
|
if !cli.quiet { eprintln!("Error: {error}"); }
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Objects::Quest(commands) => {
|
Objects::Quest(commands) => {
|
||||||
let mut quests = config.load_quests();
|
let mut quests = config.load_quests();
|
||||||
let mut path = config.full_quests_path();
|
let mut path = config.full_quests_path();
|
||||||
|
|
@ -62,19 +120,18 @@ fn main() {
|
||||||
None => 0u16
|
None => 0u16
|
||||||
};
|
};
|
||||||
|
|
||||||
path.push(format!("{next_id}.toml"));
|
let mut check_path = path.clone();
|
||||||
match std::fs::exists(&path) {
|
check_path.push(format!("{next_id}.toml"));
|
||||||
|
match std::fs::exists(&check_path) {
|
||||||
Ok(exists) => {
|
Ok(exists) => {
|
||||||
if exists {
|
if exists {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: {:?} is not empty.", path); }
|
||||||
eprintln!("Error: {:?} is not empty.", path);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
if !cli.quiet {
|
if !cli.quiet {
|
||||||
eprintln!("Error while retrieving {:?}: {}.", path, error);
|
eprintln!("Error: {error}");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -92,17 +149,11 @@ fn main() {
|
||||||
deadline: args.deadline.clone()
|
deadline: args.deadline.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
match quest.save(path) {
|
do_and_log(quest.save(path), !cli.quiet, format!("Created quest #{}.", quest.id));
|
||||||
Ok(_) if !cli.quiet => println!("Successfully saved quest #{}", quest.id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving quest: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
QuestCommands::Update(args) => {
|
QuestCommands::Update(args) => {
|
||||||
let Some(quest) = quests.iter().find(|q| q.id == args.id) else {
|
let Some(quest) = quests.iter().find(|q| q.id == args.id) else {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: Quest #{} not found.", args.id); }
|
||||||
eprintln!("Error: Quest #{} not found.", args.id);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let quest = Quest {
|
let quest = Quest {
|
||||||
|
|
@ -119,16 +170,27 @@ fn main() {
|
||||||
available_on: args.available.clone().or(quest.available_on.clone()),
|
available_on: args.available.clone().or(quest.available_on.clone()),
|
||||||
deadline: args.deadline.clone().or(quest.deadline.clone())
|
deadline: args.deadline.clone().or(quest.deadline.clone())
|
||||||
};
|
};
|
||||||
match quest.save(path) {
|
|
||||||
Ok(_) if !cli.quiet => println!("Updated quest #{}", quest.id),
|
do_and_log(quest.save(path), !cli.quiet, format!("Updated quest #{}.", quest.id));
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while updating quest: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
QuestCommands::Delete(args) => {
|
QuestCommands::Delete(args) => {
|
||||||
|
path.push(format!("{}.toml", args.id));
|
||||||
match Quest::delete(path) {
|
match Quest::delete(path) {
|
||||||
Ok(_) if !cli.quiet => println!("Successfully deleted quest #{}", args.id),
|
Ok(_) => {
|
||||||
Err(error) if !cli.quiet => eprintln!("Error deleting quest #{}: {}", args.id, error),
|
if !cli.quiet { println!("Deleted quest #{}.", args.id); }
|
||||||
|
|
||||||
|
let mut accounts = config.load_accounts();
|
||||||
|
let accounts_path = config.full_accounts_path();
|
||||||
|
for account in accounts.iter_mut() {
|
||||||
|
if let Some(index) = account.quests_completed.iter().position(|qid| *qid == args.id) {
|
||||||
|
account.quests_completed.remove(index);
|
||||||
|
do_and_log(account.save(accounts_path.clone()), !cli.quiet, format!("Removed quest #{} from account \"{}\" completed quests", args.id, account.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) if !cli.quiet => {
|
||||||
|
eprintln!("Error: {error}");
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -142,12 +204,7 @@ fn main() {
|
||||||
|
|
||||||
for quest in quests.iter_mut().filter(|q| !q.public && q.available_on.is_some_and(|date| date.le(&toml_today))) {
|
for quest in quests.iter_mut().filter(|q| !q.public && q.available_on.is_some_and(|date| date.le(&toml_today))) {
|
||||||
quest.public = true;
|
quest.public = true;
|
||||||
|
do_and_log(quest.save(path.clone()), !cli.quiet, format!("Published quest #{}.", quest.id));
|
||||||
match quest.save(path.clone()) {
|
|
||||||
Ok(_) if !cli.quiet => println!("Published quest #{}", quest.id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while publishing quest: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
QuestCommands::Publish(args) => {
|
QuestCommands::Publish(args) => {
|
||||||
|
|
@ -158,23 +215,14 @@ fn main() {
|
||||||
let not_str = if args.reverse {" not "} else {" "};
|
let not_str = if args.reverse {" not "} else {" "};
|
||||||
|
|
||||||
if quest.public != args.reverse {
|
if quest.public != args.reverse {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: quest #{} is already{}public.", quest.id, not_str); }
|
||||||
eprintln!("Quest #{} is already{}public", quest.id, not_str);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
quest.public = !args.reverse;
|
quest.public = !args.reverse;
|
||||||
|
do_and_log(quest.save(path), !cli.quiet, format!("Published quest #{}.", quest.id));
|
||||||
match quest.save(path.clone()) {
|
|
||||||
Ok(_) if !cli.quiet => println!("Published quest #{}", quest.id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while publishing quest: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None if !cli.quiet => {
|
|
||||||
eprintln!("Error: couldn't find quest with id {}.", args.id);
|
|
||||||
},
|
},
|
||||||
|
None if !cli.quiet => eprintln!("Error: quest #{} not found.", args.id),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -186,7 +234,6 @@ fn main() {
|
||||||
|
|
||||||
match commands {
|
match commands {
|
||||||
AccountCommands::List => {
|
AccountCommands::List => {
|
||||||
|
|
||||||
for account in accounts {
|
for account in accounts {
|
||||||
println!("\"{}\": Balance {}", account.id, account.balance);
|
println!("\"{}\": Balance {}", account.id, account.balance);
|
||||||
}
|
}
|
||||||
|
|
@ -198,27 +245,16 @@ fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(_) = accounts.iter().find(|a| a.id == account.id) {
|
if let Some(_) = accounts.iter().find(|a| a.id == account.id) {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: account \"{}\" exists.", account.id); }
|
||||||
eprintln!("Error: account {} exists.", account.id);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match account.save(path) {
|
do_and_log(account.save(path), !cli.quiet, format!("Created account \"{}\".", account.id));
|
||||||
Ok(_) if !cli.quiet => println!("Successfully created account \"{}\"", account.id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving account: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
AccountCommands::Balance(args) => {
|
AccountCommands::Balance(args) => {
|
||||||
let account = match accounts.iter_mut().find(|a| a.id == args.id) {
|
let Some(account) = accounts.iter_mut().find(|a| a.id == args.id) else {
|
||||||
Some(acc) => acc,
|
if !cli.quiet { eprintln!("Error: account \"{}\" not found.", args.id); }
|
||||||
None => {
|
|
||||||
if !cli.quiet {
|
|
||||||
eprintln!("Could not find account \"{}\"", args.id);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
|
|
@ -233,9 +269,7 @@ fn main() {
|
||||||
if args.negative_ok {
|
if args.negative_ok {
|
||||||
account.balance = 0u32;
|
account.balance = 0u32;
|
||||||
} else {
|
} else {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: account \"{}\" balance is less than {}.", account.id, args.value); }
|
||||||
eprintln!("Error: balance ({}) is less than {}.", account.balance, args.value);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -244,54 +278,54 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match account.save(path) {
|
do_and_log(account.save(path), !cli.quiet, format!("Updated balance of account \"{}\".", account.id));
|
||||||
Ok(_) if !cli.quiet => println!("Successfully updated account \"{}\" balance", account.id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving account: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
AccountCommands::Complete(args) => {
|
AccountCommands::Complete(args) => {
|
||||||
let account = match accounts.iter_mut().find(|a| a.id == args.account) {
|
let Some(account) = accounts.iter_mut().find(|a| a.id == args.account) else {
|
||||||
Some(acc) => acc,
|
if !cli.quiet { eprintln!("Error: account \"{}\" not found.", args.account); }
|
||||||
None => {
|
|
||||||
if !cli.quiet {
|
|
||||||
eprintln!("Could not find account \"{}\"", args.account);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let quests = config.load_quests();
|
let quests = config.load_quests();
|
||||||
|
|
||||||
if let None = quests.iter().find(|q| q.id == args.quest) {
|
let quest = match quests.iter().find(|q| q.id == args.quest) {
|
||||||
if !cli.quiet {
|
Some(quest) => quest,
|
||||||
eprintln!("Could not find quest #{}", args.quest);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match account.quests_completed.iter().find(|qid| **qid == args.quest) {
|
|
||||||
Some(_) if !cli.quiet => {
|
|
||||||
println!("Quest #{} is already completed on account \"{}\"", args.quest, args.account);
|
|
||||||
},
|
|
||||||
None => {
|
None => {
|
||||||
account.quests_completed.push(args.quest);
|
if !cli.quiet { eprintln!("Error: quest #{} not found.", args.quest); }
|
||||||
match account.save(path) {
|
return;
|
||||||
Ok(_) if !cli.quiet => println!("Successfully completed quest #{} on account \"{}\".", args.quest, account.id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving account: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match quest.complete_for_account(account) {
|
||||||
|
Err(error) if !cli.quiet => println!("Error: {error}"),
|
||||||
|
Ok(_) => do_and_log(account.save(path), !cli.quiet, format!("Completed quest #{} on account \"{}\".", args.quest, account.id)),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AccountCommands::Delete(args) => {
|
AccountCommands::Delete(args) => {
|
||||||
path.push(format!("{}.toml", args.id));
|
path.push(format!("{}.toml", args.id));
|
||||||
match Account::delete(path) {
|
do_and_log(Account::delete(path), !cli.quiet, format!("Deleted account \"{}\".", args.id))
|
||||||
Ok(_) if !cli.quiet => println!("Successfully deleted account \"{}\".", args.id),
|
},
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while deleting account: {error}"),
|
AccountCommands::Unlock(args) => {
|
||||||
_ => {},
|
let Some(account) = accounts.iter_mut().find(|a| a.id == args.account) else {
|
||||||
|
if !cli.quiet { eprintln!("Error: account \"{}\" not found.", args.account) };
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let map = match Map::load(config.full_map_path()) {
|
||||||
|
Ok(map) => map,
|
||||||
|
Err(error) => {
|
||||||
|
if !cli.quiet { eprintln!("Error: {error}"); }
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = map.unlock_room_for_account(args.room, account) {
|
||||||
|
eprintln!("Error: {error}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
do_and_log(account.save(path), !cli.quiet, format!("Unlocked room #{} for account \"{}\"", args.room, args.account));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -300,12 +334,11 @@ fn main() {
|
||||||
let mut map = match Map::load(map_path.clone()) {
|
let mut map = match Map::load(map_path.clone()) {
|
||||||
Ok(map) => map,
|
Ok(map) => map,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: {error}"); }
|
||||||
eprintln!("Error while loading map: {error}");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match commands {
|
match commands {
|
||||||
MapCommands::List => {
|
MapCommands::List => {
|
||||||
for room in map.room {
|
for room in map.room {
|
||||||
|
|
@ -321,22 +354,17 @@ fn main() {
|
||||||
let room = Room {
|
let room = Room {
|
||||||
id: last_id,
|
id: last_id,
|
||||||
name: args.name.clone(),
|
name: args.name.clone(),
|
||||||
|
value: args.value,
|
||||||
description: args.description.clone(),
|
description: args.description.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let r_id = room.id;
|
let r_id = room.id;
|
||||||
map.room.push(room);
|
map.room.push(room);
|
||||||
match map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) {
|
do_and_log(map_save(map, map_path), !cli.quiet, format!("Created room #{r_id}."))
|
||||||
Ok(_) if !cli.quiet => println!("Created room #{}.", r_id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving map: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
MapCommands::Delete(args) => {
|
MapCommands::Delete(args) => {
|
||||||
let Some(room) = map.room.iter().find(|r| r.id == args.id) else {
|
let Some(room) = map.room.iter().find(|r| r.id == args.id) else {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: room #{} not found.", args.id); }
|
||||||
eprintln!("Error: Room #{} not found", args.id);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -351,17 +379,29 @@ fn main() {
|
||||||
room.children.remove(idx);
|
room.children.remove(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
match map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) {
|
match map_save(map, map_path) {
|
||||||
Ok(_) if !cli.quiet => println!("Deleted room #{}.", r_id),
|
Ok(_) => {
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving map: {error}"),
|
if !cli.quiet { println!("Deleted room #{r_id}."); }
|
||||||
|
|
||||||
|
let mut accounts = config.load_accounts();
|
||||||
|
let accounts_path = config.full_accounts_path();
|
||||||
|
|
||||||
|
for account in accounts.iter_mut() {
|
||||||
|
if let Some(index) = account.rooms_unlocked.iter().position(|rid| *rid == r_id) {
|
||||||
|
account.rooms_unlocked.remove(index);
|
||||||
|
do_and_log(account.save(accounts_path.clone()), !cli.quiet, format!("Removed room #{r_id} from account \"{}\" unlocked rooms.", account.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) if !cli.quiet => {
|
||||||
|
eprintln!("Error: {error}");
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MapCommands::Update(args) => {
|
MapCommands::Update(args) => {
|
||||||
let Some(room) = map.room.iter_mut().find(|r| r.id == args.id) else {
|
let Some(room) = map.room.iter_mut().find(|r| r.id == args.id) else {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: room #{} not found", args.id); }
|
||||||
eprintln!("Error: Room #{} not found", args.id);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -377,11 +417,7 @@ fn main() {
|
||||||
room.value = value;
|
room.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
match map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) {
|
do_and_log(map_save(map, map_path), !cli.quiet, format!("Updated room #{}.", args.id))
|
||||||
Ok(_) if !cli.quiet => println!("Updated room #{}.", args.id),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving map: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
MapCommands::Connect(args) | MapCommands::Disconnect(args) => {
|
MapCommands::Connect(args) | MapCommands::Disconnect(args) => {
|
||||||
let connect = match commands {
|
let connect = match commands {
|
||||||
|
|
@ -392,28 +428,21 @@ fn main() {
|
||||||
// We iterate twice to make references first->second and second->first
|
// We iterate twice to make references first->second and second->first
|
||||||
for (first, second) in [(args.first, args.second),(args.second, args.first)] {
|
for (first, second) in [(args.first, args.second),(args.second, args.first)] {
|
||||||
let Some(room) = map.room.iter_mut().find(|r| r.id == first) else {
|
let Some(room) = map.room.iter_mut().find(|r| r.id == first) else {
|
||||||
if !cli.quiet {
|
if !cli.quiet { eprintln!("Error: room #{} not found.", first); }
|
||||||
eprintln!("Error: Room #{} not found", first);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match room.children.iter().position(|id| *id == second) {
|
match room.children.iter().position(|id| *id == second) {
|
||||||
Some(_) if connect && !cli.quiet => println!("Room #{} already has reference to #{}", first, second),
|
Some(_) if connect && !cli.quiet => println!("Room #{} already has reference to #{}.", first, second),
|
||||||
None if connect => room.children.push(second),
|
None if connect => room.children.push(second),
|
||||||
Some(id) if !connect => {room.children.remove(id as usize);},
|
Some(id) if !connect => {room.children.remove(id as usize);},
|
||||||
None if !connect && !cli.quiet => println!("Room #{} has no reference to #{}", first, second),
|
None if !connect && !cli.quiet => println!("Room #{} has no reference to #{}.", first, second),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let connected = if connect { "Connected" } else { "Disconnected" };
|
let connected = if connect { "Connected" } else { "Disconnected" };
|
||||||
|
do_and_log(map_save(map, map_path), !cli.quiet, format!("{connected} rooms #{} <-> #{}.", args.first, args.second));
|
||||||
match map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) {
|
|
||||||
Ok(_) if !cli.quiet => println!("{connected} rooms #{} <-> #{}.", args.first, args.second),
|
|
||||||
Err(error) if !cli.quiet => eprintln!("Error while saving map: {error}"),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
//! Configuration file that handles (de-)serializing other components
|
//! Configuration file that handles (de-)serializing other components
|
||||||
|
|
||||||
use std::{fs::{self, DirEntry},path::{Path, PathBuf}};
|
use std::{fs::{self, DirEntry}, io::Write, path::{Path, PathBuf}};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{SquadObject, account::Account, error::Error, quest::Quest};
|
use crate::{SquadObject, account::Account, error::Error, quest::Quest};
|
||||||
|
|
||||||
/// Struct for containing paths to other (de-)serializable things
|
/// Struct for containing paths to other (de-)serializable things
|
||||||
#[derive(Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Path to config directory
|
/// Path to config directory
|
||||||
|
|
@ -112,6 +112,42 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialize config into TOML.
|
||||||
|
/// Config will be saved as `path/config.toml`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// use squad_quest::config::Config;
|
||||||
|
///
|
||||||
|
/// let path = "cfg".into();
|
||||||
|
///
|
||||||
|
/// let config = Config::default();
|
||||||
|
///
|
||||||
|
/// if let Err(error) = config.save(path) {
|
||||||
|
/// // handle error
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn save(&self, path: PathBuf) -> Result<(), Error> {
|
||||||
|
let mut path = path;
|
||||||
|
path.push("config.toml");
|
||||||
|
|
||||||
|
let str = match toml::to_string_pretty(&self) {
|
||||||
|
Ok(string) => string,
|
||||||
|
Err(error) => return Err(Error::TomlSerializeError(error)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = match fs::File::create(path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(error) => return Err(Error::IoError(error)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = file.write_all(str.as_bytes()) {
|
||||||
|
return Err(Error::IoError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Deserialize config from TOML
|
/// Deserialize config from TOML
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
|
||||||
38
src/error.rs
38
src/error.rs
|
|
@ -26,3 +26,41 @@ impl fmt::Display for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error related to quest logic
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum QuestError {
|
||||||
|
/// Quest (self.0) is already completed for given account (self.1)
|
||||||
|
AlreadyCompleted(u16, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for QuestError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::AlreadyCompleted(quest_id, account_id) => write!(f, "quest #{quest_id} is already completed for account \"{account_id}\""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error related to map logic
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum MapError {
|
||||||
|
/// Room not found in map file
|
||||||
|
RoomNotFound(u16),
|
||||||
|
/// Room (self.0) is already unlocked on account (self.1)
|
||||||
|
RoomAlreadyUnlocked(u16, String),
|
||||||
|
/// Account (self.1) does not have much money (self.0)
|
||||||
|
InsufficientFunds(u16, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MapError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::RoomNotFound(id) => write!(f, "could not find room #{id}"),
|
||||||
|
Self::RoomAlreadyUnlocked(room_id, account_id) => write!(f, "room #{room_id} is already unlocked on account \"{account_id}\""),
|
||||||
|
Self::InsufficientFunds(room_id, account_id) => write!(f, "account \"{account_id}\" does not have enough money to unlock room #{room_id}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::{fs, io::Write, path::PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{SquadObject, error::Error};
|
use crate::{SquadObject, account::Account, error::{Error, MapError}};
|
||||||
|
|
||||||
/// THE Graph. Actually, this is a Vec.
|
/// THE Graph. Actually, this is a Vec.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
@ -71,7 +71,43 @@ impl SquadObject for Map {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Map {
|
||||||
|
/// Try to unlock room for account, or return [MapError]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// use squad_quest::{account::Account,map::{Map,Room},error::MapError};
|
||||||
|
///
|
||||||
|
/// let map = Map {
|
||||||
|
/// room: vec![Room { id: 0, value: 100, ..Default::default() }],
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let mut account = Account { balance: 100, ..Default::default() };
|
||||||
|
///
|
||||||
|
/// if let Err(error) = map.unlock_room_for_account(0, &mut account) {
|
||||||
|
/// // handle error
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn unlock_room_for_account(&self, room_id: u16, account: &mut Account) -> Result<(), MapError> {
|
||||||
|
let Some(room) = self.room.iter().find(|r| r.id == room_id) else {
|
||||||
|
return Err(MapError::RoomNotFound(room_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(_) = account.rooms_unlocked.iter().find(|rid| **rid == room_id) {
|
||||||
|
return Err(MapError::RoomAlreadyUnlocked(room_id, account.id.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.balance < room.value {
|
||||||
|
return Err(MapError::InsufficientFunds(room_id, account.id.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
account.balance -= room.value;
|
||||||
|
account.rooms_unlocked.push(room_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Component of the map
|
/// Component of the map
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use std::{fs, io::Write, path::PathBuf};
|
use std::{fs, io::Write, path::PathBuf};
|
||||||
|
|
||||||
use serde::{ Serialize, Deserialize };
|
use serde::{ Serialize, Deserialize };
|
||||||
use crate::{SquadObject, error::Error};
|
use crate::{SquadObject, account::Account, error::{Error, QuestError}};
|
||||||
use toml::value::Date;
|
use toml::value::Date;
|
||||||
|
|
||||||
/// Difficulty of the quest
|
/// Difficulty of the quest
|
||||||
|
|
@ -137,3 +137,32 @@ impl SquadObject for Quest {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Quest {
|
||||||
|
/// Complete quest for account and add reward to it's balance.
|
||||||
|
/// Does nothing and returns [QuestError::AlreadyCompleted]
|
||||||
|
/// if it is already completed.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use squad_quest::{account::Account,quest::Quest};
|
||||||
|
///
|
||||||
|
/// let quest = Quest::default();
|
||||||
|
/// let mut account = Account::default();
|
||||||
|
///
|
||||||
|
/// if let Err(error) = quest.complete_for_account(&mut account) {
|
||||||
|
/// // handle error
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn complete_for_account(&self, account: &mut Account) -> Result<(),QuestError> {
|
||||||
|
match account.quests_completed.iter().find(|qid| **qid == self.id) {
|
||||||
|
Some(_) => Err(QuestError::AlreadyCompleted(self.id, account.id.clone())),
|
||||||
|
None => {
|
||||||
|
account.quests_completed.push(self.id);
|
||||||
|
account.balance += self.reward;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue