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:
parent
4ba57b925a
commit
b6ea2d8958
11 changed files with 170 additions and 12 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -1599,7 +1599,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest"
|
name = "squad-quest"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
@ -1607,7 +1607,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest-cli"
|
name = "squad-quest-cli"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
@ -1618,7 +1618,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "squad-quest-discord"
|
name = "squad-quest-discord"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
members = ["cli", "discord"]
|
members = ["cli", "discord"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
repository = "https://2ndbeam.ru/git/2ndbeam/squad-quest"
|
repository = "https://2ndbeam.ru/git/2ndbeam/squad-quest"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
|
|
@ -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.8.0", path = ".." }
|
squad-quest = { version = "0.9.0", path = ".." }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ clap = { version = "4.5.53", features = ["derive"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
serde = "1.0.228"
|
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"] }
|
tokio = { version = "1.48.0", features = ["rt-multi-thread"] }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,34 @@ fn account_user_id(account: &Account) -> UserId {
|
||||||
UserId::new(account.id.clone().parse::<u64>().expect("automatically inserted"))
|
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(
|
#[poise::command(
|
||||||
prefix_command,
|
prefix_command,
|
||||||
slash_command,
|
slash_command,
|
||||||
|
|
|
||||||
67
discord/src/commands/map.rs
Normal file
67
discord/src/commands/map.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ pub mod init;
|
||||||
pub mod answer;
|
pub mod answer;
|
||||||
pub mod social;
|
pub mod social;
|
||||||
pub mod account;
|
pub mod account;
|
||||||
|
pub mod map;
|
||||||
|
|
||||||
#[poise::command(prefix_command)]
|
#[poise::command(prefix_command)]
|
||||||
pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
|
@ -15,6 +16,21 @@ pub async fn register(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
Ok(())
|
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>) {
|
pub async fn error_handler(error: poise::FrameworkError<'_, Data, Error>) {
|
||||||
eprintln!("ERROR:");
|
eprintln!("ERROR:");
|
||||||
print_error_recursively(&error);
|
print_error_recursively(&error);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
|
use squad_quest::error::MapError;
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -15,6 +16,9 @@ pub enum Error {
|
||||||
SquadQuestError(squad_quest::error::Error),
|
SquadQuestError(squad_quest::error::Error),
|
||||||
AccountNotFound,
|
AccountNotFound,
|
||||||
InsufficientFunds(u32),
|
InsufficientFunds(u32),
|
||||||
|
RoomNotFound(u16),
|
||||||
|
RoomAlreadyUnlocked(u16),
|
||||||
|
CannotReach(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serenity::Error> for Error {
|
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 {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -41,7 +57,10 @@ impl Display for Error {
|
||||||
Self::SerenityError(_) => write!(f, "discord interaction error"),
|
Self::SerenityError(_) => write!(f, "discord interaction error"),
|
||||||
Self::SquadQuestError(_) => write!(f, "internal logic error"),
|
Self::SquadQuestError(_) => write!(f, "internal logic error"),
|
||||||
Self::AccountNotFound => write!(f, "account not found"),
|
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::NoChannelOrUser |
|
||||||
Self::BothChannelAndUser |
|
Self::BothChannelAndUser |
|
||||||
Self::AccountNotFound |
|
Self::AccountNotFound |
|
||||||
Self::InsufficientFunds(_) => None,
|
Self::InsufficientFunds(_) |
|
||||||
|
Self::RoomNotFound(_) |
|
||||||
|
Self::RoomAlreadyUnlocked(_) |
|
||||||
|
Self::CannotReach(_) => None,
|
||||||
Self::SerenityError(error) => Some(error),
|
Self::SerenityError(error) => Some(error),
|
||||||
Self::SquadQuestError(error) => Some(error),
|
Self::SquadQuestError(error) => Some(error),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,15 @@ async fn main() {
|
||||||
commands: vec![
|
commands: vec![
|
||||||
commands::quest::quest(),
|
commands::quest::quest(),
|
||||||
commands::register(),
|
commands::register(),
|
||||||
|
commands::info(),
|
||||||
commands::init::init(),
|
commands::init::init(),
|
||||||
commands::answer::answer(),
|
commands::answer::answer(),
|
||||||
commands::social::social(),
|
commands::social::social(),
|
||||||
commands::account::scoreboard(),
|
commands::account::scoreboard(),
|
||||||
commands::account::balance(),
|
commands::account::balance(),
|
||||||
|
commands::account::reset(),
|
||||||
|
commands::map::unlock(),
|
||||||
|
commands::map::r#move(),
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,10 @@ pub enum MapError {
|
||||||
RoomNotFound(u16),
|
RoomNotFound(u16),
|
||||||
/// Room (self.0) is already unlocked on account (self.1)
|
/// Room (self.0) is already unlocked on account (self.1)
|
||||||
RoomAlreadyUnlocked(u16, String),
|
RoomAlreadyUnlocked(u16, String),
|
||||||
/// Account (self.1) does not have much money (self.0)
|
/// Account (self.1) does not have much money (self.0), expected (self.2)
|
||||||
InsufficientFunds(u16, String),
|
InsufficientFunds(u16, String, u32),
|
||||||
|
/// Account (self.1) can't reach room (self.0)
|
||||||
|
CannotReach(u16, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MapError {
|
impl fmt::Display for MapError {
|
||||||
|
|
@ -78,7 +80,8 @@ impl fmt::Display for MapError {
|
||||||
match self {
|
match self {
|
||||||
Self::RoomNotFound(id) => write!(f, "could not find room #{id}"),
|
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::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}"),
|
Self::InsufficientFunds(room_id, account_id, amount) => write!(f, "account \"{account_id}\" does not have enough money to unlock room #{room_id}, expected {amount}"),
|
||||||
|
Self::CannotReach(room, account) => write!(f, "account \"{account}\" cannot reach room #{room}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,15 @@ impl Map {
|
||||||
return Err(MapError::RoomAlreadyUnlocked(room_id, account.id.clone()));
|
return Err(MapError::RoomAlreadyUnlocked(room_id, account.id.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Room 0 will always be reachable
|
||||||
|
let reachable = room.reachable_for(&account) || room.id == 0;
|
||||||
|
|
||||||
|
if !reachable {
|
||||||
|
return Err(MapError::CannotReach(room_id, account.id.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
if account.balance < room.value {
|
if account.balance < room.value {
|
||||||
return Err(MapError::InsufficientFunds(room_id, account.id.clone()));
|
return Err(MapError::InsufficientFunds(room_id, account.id.clone(), room.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
account.balance -= room.value;
|
account.balance -= room.value;
|
||||||
|
|
@ -141,3 +148,14 @@ impl Default for Room {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Room {
|
||||||
|
fn reachable_for(&self, account: &Account) -> bool {
|
||||||
|
for rid in account.rooms_unlocked.iter() {
|
||||||
|
if self.children.contains(&rid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue