feat: Completed commands list

- Added MapError::CannotReach variant
- Updated Map::unlock_room_for_account to check reachableness
- Added /info command
- Added /unlock command
- Added /move command
- Added /reset command
This commit is contained in:
Alexey 2025-12-15 15:19:07 +03:00
commit b6ea2d8958
11 changed files with 170 additions and 12 deletions

View file

@ -10,6 +10,6 @@ clap = { version = "4.5.53", features = ["derive"] }
dotenvy = "0.15.7"
poise = "0.6.1"
serde = "1.0.228"
squad-quest = { version = "0.8.0", path = ".." }
squad-quest = { version = "0.9.0", path = ".." }
tokio = { version = "1.48.0", features = ["rt-multi-thread"] }
toml = "0.9.8"

View file

@ -40,6 +40,34 @@ fn account_user_id(account: &Account) -> UserId {
UserId::new(account.id.clone().parse::<u64>().expect("automatically inserted"))
}
#[poise::command(
prefix_command,
slash_command,
guild_only,
required_permissions = "ADMINISTRATOR",
)]
pub async fn reset(
ctx: Context<'_>,
who: UserId,
) -> Result<(), Error> {
let accounts = ctx.data().config.load_accounts();
let acc_id = format!("{}", who.get());
if let None = accounts.iter().find(|a| a.id == acc_id) {
return Err(Error::AccountNotFound);
}
let mut path = ctx.data().config.full_accounts_path();
path.push(format!("{acc_id}.toml"));
Account::delete(path)?;
let reply_string = "User was successfully reset.".to_string();
ctx.reply(reply_string).await?;
Ok(())
}
#[poise::command(
prefix_command,
slash_command,

View file

@ -0,0 +1,67 @@
use squad_quest::{SquadObject, map::Map};
use crate::{Context, account::fetch_or_init_account, error::Error};
#[poise::command(
prefix_command,
slash_command,
guild_only,
)]
pub async fn unlock(
ctx: Context<'_>,
id: u16,
) -> Result<(), Error> {
let conf = &ctx.data().config;
let map_path = conf.full_map_path();
let map = Map::load(map_path)?;
let Some(room) = map.room.iter().find(|r| r.id == id) else {
return Err(Error::RoomNotFound(id));
};
let acc_id = format!("{}", ctx.author().id.get());
let mut account = fetch_or_init_account(conf, acc_id);
if account.balance < room.value {
return Err(Error::InsufficientFunds(room.value));
}
map.unlock_room_for_account(id, &mut account)?;
let account_path = conf.full_accounts_path();
account.save(account_path)?;
let reply_string = format!("Unlocked room #{id}. Your balance: {} points", account.balance);
ctx.reply(reply_string).await?;
Ok(())
}
#[poise::command(
prefix_command,
slash_command,
guild_only,
)]
pub async fn r#move(
ctx: Context<'_>,
id: u16,
) -> Result<(), Error> {
let conf = &ctx.data().config;
let acc_id = format!("{}", ctx.author().id.get());
let mut account = fetch_or_init_account(conf, acc_id);
if let None = account.rooms_unlocked.iter().find(|rid| **rid == id) {
return Err(Error::RoomNotFound(id));
}
account.location = id;
let account_path = conf.full_accounts_path();
account.save(account_path)?;
let reply_string = format!("Moved to room #{id}.");
ctx.reply(reply_string).await?;
Ok(())
}

View file

@ -8,6 +8,7 @@ pub mod init;
pub mod answer;
pub mod social;
pub mod account;
pub mod map;
#[poise::command(prefix_command)]
pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
@ -15,6 +16,21 @@ pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
#[poise::command(
prefix_command,
slash_command,
)]
pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
let reply_string = format!("\
SquadQuest version {ver}\n\
Find the map here: {url}",
ver = env!("CARGO_PKG_VERSION"),
url = "not implemented yet!",
);
ctx.say(reply_string).await?;
Ok(())
}
pub async fn error_handler(error: poise::FrameworkError<'_, Data, Error>) {
eprintln!("ERROR:");
print_error_recursively(&error);

View file

@ -1,6 +1,7 @@
use std::fmt::Display;
use poise::serenity_prelude as serenity;
use squad_quest::error::MapError;
#[non_exhaustive]
#[derive(Debug)]
@ -15,6 +16,9 @@ pub enum Error {
SquadQuestError(squad_quest::error::Error),
AccountNotFound,
InsufficientFunds(u32),
RoomNotFound(u16),
RoomAlreadyUnlocked(u16),
CannotReach(u16),
}
impl From<serenity::Error> for Error {
@ -29,6 +33,18 @@ impl From<squad_quest::error::Error> for Error {
}
}
impl From<squad_quest::error::MapError> for Error {
fn from(value: squad_quest::error::MapError) -> Self {
match value {
MapError::RoomNotFound(id) => Self::RoomNotFound(id),
MapError::RoomAlreadyUnlocked(id, _) => Self::RoomAlreadyUnlocked(id),
MapError::InsufficientFunds(_, _, amount) => Self::InsufficientFunds(amount),
MapError::CannotReach(id, _) => Self::CannotReach(id),
_ => unreachable!(),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -41,7 +57,10 @@ impl Display for Error {
Self::SerenityError(_) => write!(f, "discord interaction error"),
Self::SquadQuestError(_) => write!(f, "internal logic error"),
Self::AccountNotFound => write!(f, "account not found"),
Self::InsufficientFunds(amount) => write!(f, "account does not have {amount} points"),
Self::InsufficientFunds(amount) => write!(f, "user does not have {amount} points"),
Self::RoomNotFound(id) => write!(f, "room #{id} not found"),
Self::RoomAlreadyUnlocked(id) => write!(f, "room #{id} is already unlocked for this user"),
Self::CannotReach(id) => write!(f, "user cannot reach room #{id}"),
}
}
}
@ -56,7 +75,10 @@ impl std::error::Error for Error {
Self::NoChannelOrUser |
Self::BothChannelAndUser |
Self::AccountNotFound |
Self::InsufficientFunds(_) => None,
Self::InsufficientFunds(_) |
Self::RoomNotFound(_) |
Self::RoomAlreadyUnlocked(_) |
Self::CannotReach(_) => None,
Self::SerenityError(error) => Some(error),
Self::SquadQuestError(error) => Some(error),
}

View file

@ -42,11 +42,15 @@ async fn main() {
commands: vec![
commands::quest::quest(),
commands::register(),
commands::info(),
commands::init::init(),
commands::answer::answer(),
commands::social::social(),
commands::account::scoreboard(),
commands::account::balance(),
commands::account::reset(),
commands::map::unlock(),
commands::map::r#move(),
],
..Default::default()
})