Added shop commands
+ Ton of refactoring + clippy + fmt
This commit is contained in:
parent
335751a978
commit
1db0c5b3c0
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@
|
|||||||
/.idea
|
/.idea
|
||||||
config.toml
|
config.toml
|
||||||
stories/
|
stories/
|
||||||
voices/
|
voices/
|
||||||
|
nft/
|
||||||
@ -3,7 +3,8 @@ use crate::error::Error::NoAlbumFound;
|
|||||||
use crate::imgur;
|
use crate::imgur;
|
||||||
use crate::imgur::Image;
|
use crate::imgur::Image;
|
||||||
use crate::insult_compliment::InsultComplimentTemplate;
|
use crate::insult_compliment::InsultComplimentTemplate;
|
||||||
use crate::wallet::WalletManager;
|
use crate::inventory::InventoryManager;
|
||||||
|
use crate::user::UserManager;
|
||||||
use config::{Config, File};
|
use config::{Config, File};
|
||||||
use rand::prelude::SliceRandom;
|
use rand::prelude::SliceRandom;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -48,6 +49,11 @@ pub struct BotConfig {
|
|||||||
pub imgur_client_id: String,
|
pub imgur_client_id: String,
|
||||||
pub story_path: PathBuf,
|
pub story_path: PathBuf,
|
||||||
pub voice_path: PathBuf,
|
pub voice_path: PathBuf,
|
||||||
|
pub nft_path: PathBuf,
|
||||||
|
|
||||||
|
pub user_manager: UserManager,
|
||||||
|
|
||||||
|
pub bot_inventory: InventoryManager,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub albums: Vec<AlbumConfig>,
|
pub albums: Vec<AlbumConfig>,
|
||||||
@ -55,8 +61,6 @@ pub struct BotConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub randoms: Vec<RandomConfig>,
|
pub randoms: Vec<RandomConfig>,
|
||||||
|
|
||||||
pub wallet_manager: WalletManager,
|
|
||||||
|
|
||||||
pub motivation: MotivationConfig,
|
pub motivation: MotivationConfig,
|
||||||
|
|
||||||
pub insults: Vec<InsultComplimentTemplate>,
|
pub insults: Vec<InsultComplimentTemplate>,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::config::{Channel, GlobalData};
|
use crate::config::{Channel, GlobalData};
|
||||||
use crate::wallet::WalletError;
|
use crate::user::UserError;
|
||||||
use crate::{command, group};
|
use crate::{command, group};
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
@ -25,7 +25,7 @@ pub struct EmojiRace;
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RaceError {
|
pub enum RaceError {
|
||||||
RacerNotFound,
|
RacerNotFound,
|
||||||
BetFundError(WalletError),
|
BetFundError(UserError),
|
||||||
UserHasBet,
|
UserHasBet,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +39,8 @@ impl Display for RaceError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<WalletError> for RaceError {
|
impl From<UserError> for RaceError {
|
||||||
fn from(value: WalletError) -> Self {
|
fn from(value: UserError) -> Self {
|
||||||
Self::BetFundError(value)
|
Self::BetFundError(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ async fn add_bet(
|
|||||||
|
|
||||||
global
|
global
|
||||||
.cfg
|
.cfg
|
||||||
.wallet_manager
|
.user_manager
|
||||||
.try_take_funds(bet.author, bet.amount)?;
|
.try_take_funds(bet.author, bet.amount)?;
|
||||||
|
|
||||||
if !racers.iter().any(|r| r.emoji.id == bet.emoji) {
|
if !racers.iter().any(|r| r.emoji.id == bet.emoji) {
|
||||||
@ -298,9 +298,8 @@ async fn race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
|||||||
|
|
||||||
global_data
|
global_data
|
||||||
.cfg
|
.cfg
|
||||||
.wallet_manager
|
.user_manager
|
||||||
.get_user_wallet(winner)
|
.give_funds(winner, payout as u32);
|
||||||
.coin_count += payout;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::config::GlobalData;
|
use crate::config::GlobalData;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::wallet::WalletError;
|
use crate::user::UserError;
|
||||||
use crate::{command, group};
|
use crate::{command, group};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
@ -23,7 +23,7 @@ async fn balance(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
|||||||
|
|
||||||
let user = args.parse::<UserId>().unwrap_or(msg.author.id);
|
let user = args.parse::<UserId>().unwrap_or(msg.author.id);
|
||||||
|
|
||||||
let wallet = global_data.cfg.wallet_manager.get_user_wallet(user);
|
let wallet = global_data.cfg.user_manager.get_user(user);
|
||||||
|
|
||||||
msg.reply(
|
msg.reply(
|
||||||
&ctx.http,
|
&ctx.http,
|
||||||
@ -74,10 +74,10 @@ async fn gift(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||||||
|
|
||||||
if let Err(e) = global_data
|
if let Err(e) = global_data
|
||||||
.cfg
|
.cfg
|
||||||
.wallet_manager
|
.user_manager
|
||||||
.transfer_funds(msg.author.id, target, amount)
|
.transfer_funds(msg.author.id, target, amount)
|
||||||
{
|
{
|
||||||
if let WalletError::NotEnoughFunds = e {
|
if let UserError::NotEnoughFunds = e {
|
||||||
msg.reply(
|
msg.reply(
|
||||||
&ctx.http,
|
&ctx.http,
|
||||||
"Sorry pal, I can't give credit. Come back when you're a bit mmmm richer.",
|
"Sorry pal, I can't give credit. Come back when you're a bit mmmm richer.",
|
||||||
@ -108,7 +108,7 @@ pub async fn give_coin(
|
|||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
user: UserId,
|
user: UserId,
|
||||||
percent: f64,
|
percent: f64,
|
||||||
numer_of_coins: i64,
|
number_of_coins: i64,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
let should_get_coin = {
|
let should_get_coin = {
|
||||||
let mut thread_rng = thread_rng();
|
let mut thread_rng = thread_rng();
|
||||||
@ -120,10 +120,10 @@ pub async fn give_coin(
|
|||||||
let mut data = ctx.data.write().await;
|
let mut data = ctx.data.write().await;
|
||||||
let global_data = data.get_mut::<GlobalData>().unwrap();
|
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||||
|
|
||||||
{
|
global_data
|
||||||
let wallet = global_data.cfg.wallet_manager.get_user_wallet(user);
|
.cfg
|
||||||
wallet.coin_count += numer_of_coins;
|
.user_manager
|
||||||
}
|
.give_funds(user, number_of_coins as u32);
|
||||||
|
|
||||||
global_data
|
global_data
|
||||||
.cfg
|
.cfg
|
||||||
|
|||||||
@ -6,11 +6,13 @@ pub mod emoji_race;
|
|||||||
pub mod fren_coin;
|
pub mod fren_coin;
|
||||||
pub mod joke;
|
pub mod joke;
|
||||||
pub mod motivate;
|
pub mod motivate;
|
||||||
|
pub mod shop;
|
||||||
pub mod story;
|
pub mod story;
|
||||||
pub mod voices;
|
pub mod voices;
|
||||||
|
|
||||||
use crate::discord::fren_coin::give_coin;
|
use crate::discord::fren_coin::give_coin;
|
||||||
use crate::discord::joke::random;
|
use crate::discord::joke::random;
|
||||||
|
use crate::discord::shop::restock_shop;
|
||||||
use crate::{help, hook, GlobalData};
|
use crate::{help, hook, GlobalData};
|
||||||
use rand::prelude::IteratorRandom;
|
use rand::prelude::IteratorRandom;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
@ -29,22 +31,28 @@ use std::time::Duration;
|
|||||||
pub struct Handler;
|
pub struct Handler;
|
||||||
|
|
||||||
static ERROR_MSG: &str =
|
static ERROR_MSG: &str =
|
||||||
"OOPSIE WOOPSIE!! Uwu We made a fucky wucky!! A wittle fucko boingo! The admins at our
|
"OOPSIE WOOPSIE!! Uwu We made a fucky wucky!! A wittle fucko boingo! The admins at our headquarters are working VEWY HAWD to fix this!";
|
||||||
headquarters are working VEWY HAWD to fix this!";
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
|
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(Duration::from_secs(60 * 60)).await;
|
|
||||||
{
|
{
|
||||||
println!("Reloading config...");
|
{
|
||||||
let mut data = ctx.data.write().await;
|
println!("Reloading config...");
|
||||||
let global_data = data.get_mut::<GlobalData>().unwrap();
|
let mut data = ctx.data.write().await;
|
||||||
|
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||||
|
|
||||||
global_data.reload().await.unwrap();
|
global_data.reload().await.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
println!("Restocking shop...");
|
||||||
|
restock_shop(&ctx).await.unwrap();
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(60 * 60)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use magick_rust::{DrawingWand, MagickWand, PixelWand};
|
|||||||
use rand::prelude::IteratorRandom;
|
use rand::prelude::IteratorRandom;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use serenity::framework::standard::{Args, CommandResult};
|
use serenity::framework::standard::{Args, CommandError, CommandResult};
|
||||||
use serenity::model::channel::{AttachmentType, Message};
|
use serenity::model::channel::{AttachmentType, Message};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
@ -12,13 +12,10 @@ use std::borrow::Cow;
|
|||||||
#[commands(motivation)]
|
#[commands(motivation)]
|
||||||
pub struct Motivate;
|
pub struct Motivate;
|
||||||
|
|
||||||
#[command]
|
pub async fn create_image(
|
||||||
#[only_in(guilds)]
|
global_data: &GlobalData,
|
||||||
#[description("Let's give you motivation")]
|
border_color: &str,
|
||||||
async fn motivation(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
) -> Result<Vec<u8>, CommandError> {
|
||||||
let data = ctx.data.read().await;
|
|
||||||
let global_data = data.get::<GlobalData>().unwrap();
|
|
||||||
|
|
||||||
let album = &global_data
|
let album = &global_data
|
||||||
.cfg
|
.cfg
|
||||||
.motivation
|
.motivation
|
||||||
@ -49,45 +46,54 @@ async fn motivation(ctx: &Context, msg: &Message, _args: Args) -> CommandResult
|
|||||||
.iter()
|
.iter()
|
||||||
.choose(&mut thread_rng())
|
.choose(&mut thread_rng())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let motivation = format!("{} {}", action, goal);
|
let motivation = format!("{} {}", action, goal);
|
||||||
|
|
||||||
let image = {
|
let mut wand = MagickWand::new();
|
||||||
let mut wand = MagickWand::new();
|
let mut border_wand = PixelWand::new();
|
||||||
let mut border_wand = PixelWand::new();
|
wand.read_image_blob(motivation_image_blob)?;
|
||||||
wand.read_image_blob(motivation_image_blob)?;
|
|
||||||
|
|
||||||
border_wand.set_color("white")?;
|
border_wand.set_color(border_color)?;
|
||||||
let width = wand.get_image_width();
|
let width = wand.get_image_width();
|
||||||
let border = width / 100;
|
let border = width / 100;
|
||||||
wand.border_image(
|
wand.border_image(
|
||||||
&border_wand,
|
&border_wand,
|
||||||
border,
|
border,
|
||||||
border,
|
border,
|
||||||
magick_rust::bindings::CompositeOperator_OverCompositeOp,
|
magick_rust::bindings::CompositeOperator_OverCompositeOp,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
border_wand.set_color("black")?;
|
border_wand.set_color("black")?;
|
||||||
let width = wand.get_image_width();
|
let width = wand.get_image_width();
|
||||||
let border = width * 20 / 100;
|
let border = width * 20 / 100;
|
||||||
wand.border_image(
|
wand.border_image(
|
||||||
&border_wand,
|
&border_wand,
|
||||||
border,
|
border,
|
||||||
border,
|
border,
|
||||||
magick_rust::bindings::CompositeOperator_OverCompositeOp,
|
magick_rust::bindings::CompositeOperator_OverCompositeOp,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let text_pos_x = wand.get_image_width() as f64 / 2.0;
|
let text_pos_x = wand.get_image_width() as f64 / 2.0;
|
||||||
let text_pos_y = wand.get_image_height() as f64 - (wand.get_image_height() as f64 * 0.05);
|
let text_pos_y = wand.get_image_height() as f64 - (wand.get_image_height() as f64 * 0.05);
|
||||||
|
|
||||||
let mut text_wand = DrawingWand::new();
|
let mut text_wand = DrawingWand::new();
|
||||||
let mut text_color_wand = PixelWand::new();
|
let mut text_color_wand = PixelWand::new();
|
||||||
text_color_wand.set_color("white")?;
|
text_color_wand.set_color("white")?;
|
||||||
text_wand.set_fill_color(&text_color_wand);
|
text_wand.set_fill_color(&text_color_wand);
|
||||||
text_wand.set_font_size(0.07 * (wand.get_image_width() as f64));
|
text_wand.set_font_size(0.07 * (wand.get_image_width() as f64));
|
||||||
text_wand.set_text_alignment(magick_rust::bindings::AlignType_CenterAlign);
|
text_wand.set_text_alignment(magick_rust::bindings::AlignType_CenterAlign);
|
||||||
wand.annotate_image(&text_wand, text_pos_x, text_pos_y, 0.0, &motivation)?;
|
wand.annotate_image(&text_wand, text_pos_x, text_pos_y, 0.0, &motivation)?;
|
||||||
wand.write_image_blob("png")?
|
Ok(wand.write_image_blob("png")?)
|
||||||
};
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
#[description("Let's give you motivation")]
|
||||||
|
async fn motivation(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
|
let data = ctx.data.read().await;
|
||||||
|
let global_data = data.get::<GlobalData>().unwrap();
|
||||||
|
|
||||||
|
let image = create_image(global_data, "white").await?;
|
||||||
|
|
||||||
msg.channel_id
|
msg.channel_id
|
||||||
.send_message(&ctx.http, |m| {
|
.send_message(&ctx.http, |m| {
|
||||||
|
|||||||
295
src/discord/shop.rs
Normal file
295
src/discord/shop.rs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
use crate::discord::motivate::create_image;
|
||||||
|
use crate::inventory::{InventoryError, ItemData, ItemType};
|
||||||
|
use crate::user::UserError;
|
||||||
|
use crate::{command, group, GlobalData};
|
||||||
|
use rand::prelude::SliceRandom;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::framework::standard::{Args, CommandError, CommandResult};
|
||||||
|
use serenity::model::channel::{AttachmentType, Message};
|
||||||
|
use serenity::utils::MessageBuilder;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
#[group]
|
||||||
|
#[commands(shop, buy, inventory, use_item)]
|
||||||
|
pub struct Shop;
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Fren has wares if you have coin")]
|
||||||
|
async fn shop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
|
let mut data = ctx.data.write().await;
|
||||||
|
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||||
|
|
||||||
|
if global_data.cfg.bot_inventory.inventory.is_empty() {
|
||||||
|
msg.reply(&ctx.http, "Sorry shop is closed until we get more wares.")
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
let mut inv_msg = MessageBuilder::new();
|
||||||
|
|
||||||
|
inv_msg.push_bold_line("Fren has wares if you have coin:");
|
||||||
|
|
||||||
|
inv_msg.push_safe(global_data.cfg.bot_inventory.list_items(true));
|
||||||
|
|
||||||
|
msg.reply(&ctx.http, inv_msg.build()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Open your inventory, is specifically can not be rebound to the B key")]
|
||||||
|
async fn inventory(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
|
let mut data = ctx.data.write().await;
|
||||||
|
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||||
|
|
||||||
|
let mut inv_msg = MessageBuilder::new();
|
||||||
|
|
||||||
|
let user = global_data.cfg.user_manager.get_user(msg.author.id);
|
||||||
|
|
||||||
|
if user.inventory.inventory.is_empty() {
|
||||||
|
msg.reply(&ctx, "Sorry your inventory is empty.").await?;
|
||||||
|
} else {
|
||||||
|
inv_msg.push_bold_line("Your inventory: ");
|
||||||
|
|
||||||
|
inv_msg.push_safe(user.inventory.list_items(false));
|
||||||
|
|
||||||
|
msg.reply(&ctx.http, inv_msg.build()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[description("Buying something?")]
|
||||||
|
async fn buy(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
let mut data = ctx.data.write().await;
|
||||||
|
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||||
|
|
||||||
|
if args.is_empty() {
|
||||||
|
msg.reply(
|
||||||
|
&ctx.http,
|
||||||
|
"You gonna buy something or do you just like to stare?",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = match args.rest().parse::<ItemType>() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
msg.reply(&ctx.http, "I don't know what the heck that is tbh.")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(item_slot) = global_data.cfg.bot_inventory.get_item(item) {
|
||||||
|
if let Err(err) = global_data
|
||||||
|
.cfg
|
||||||
|
.user_manager
|
||||||
|
.try_take_funds(msg.author.id, item_slot.value() as u32)
|
||||||
|
{
|
||||||
|
msg.reply(
|
||||||
|
&ctx.http,
|
||||||
|
format!(
|
||||||
|
"I see why you don't get to the cloud district very often: {}",
|
||||||
|
err
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg.reply(&ctx.http, "I don't have that in stock you goob")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let item_data = match global_data.cfg.bot_inventory.try_take_item(item, 1) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(err) => {
|
||||||
|
msg.reply(&ctx.http, format!("I can't just sell you that: {}", err))
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
global_data
|
||||||
|
.cfg
|
||||||
|
.user_manager
|
||||||
|
.give_item(msg.author.id, item, 1, item_data);
|
||||||
|
|
||||||
|
msg.reply(&ctx, format!("Congrats, you now own a '{}'", item))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
#[aliases("use")]
|
||||||
|
#[description("Buying something?")]
|
||||||
|
async fn use_item(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
let mut data = ctx.data.write().await;
|
||||||
|
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||||
|
|
||||||
|
if args.is_empty() {
|
||||||
|
msg.reply(&ctx.http, "You need to select one item to use.")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = match args.rest().parse::<ItemType>() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
msg.reply(&ctx.http, "I don't know what the heck that is tbh.")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let item_data = match global_data
|
||||||
|
.cfg
|
||||||
|
.user_manager
|
||||||
|
.try_use_item(msg.author.id, item)
|
||||||
|
{
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(err) => {
|
||||||
|
if let UserError::InventoryError(InventoryError::NotEnoughItems) = err {
|
||||||
|
msg.reply(
|
||||||
|
&ctx.http,
|
||||||
|
"Looks like you don't have enough of that item to use it",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match item {
|
||||||
|
ItemType::CancelInsurance => {
|
||||||
|
msg.reply(&ctx.http, "You are immune to the next (1) cancelings.")
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
ItemType::TheConceptOfLove => {
|
||||||
|
msg.reply(&ctx.http, "I have DMed you the concept of love.")
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
msg.author
|
||||||
|
.id
|
||||||
|
.create_dm_channel(&ctx.http)
|
||||||
|
.await?
|
||||||
|
.say(
|
||||||
|
&ctx.http,
|
||||||
|
"DO NOT SHARE\nhttps://www.youtube.com/watch?v=HNy_retSME0",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
ItemType::GoodFortune => {
|
||||||
|
let good_fortunes = [
|
||||||
|
"Yes.",
|
||||||
|
"OF COURSE.",
|
||||||
|
"Carolyn, I'm sorry. So yes :)",
|
||||||
|
"That sounds great!",
|
||||||
|
"Yes, I am happy for you!",
|
||||||
|
"||YES||",
|
||||||
|
];
|
||||||
|
let fortune = good_fortunes.choose(&mut thread_rng()).unwrap();
|
||||||
|
|
||||||
|
msg.reply(&ctx.http, fortune).await?;
|
||||||
|
}
|
||||||
|
ItemType::Nft => {
|
||||||
|
if let Some(ItemData::Nft(path)) = item_data {
|
||||||
|
let file: tokio::fs::File = match tokio::fs::File::open(path).await {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => {
|
||||||
|
msg.reply(&ctx.http, "Sorry this was a pump and dump")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
msg.channel_id
|
||||||
|
.send_message(&ctx.http, |m| {
|
||||||
|
m.content("Your NFT my good friend:")
|
||||||
|
.add_file(AttachmentType::File {
|
||||||
|
file: &file,
|
||||||
|
filename: "nft.png".to_string(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
msg.reply(
|
||||||
|
&ctx.http,
|
||||||
|
"Fren NFTs were never my creation, I merely promoted the brand",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn restock_shop(ctx: &Context) -> Result<(), CommandError> {
|
||||||
|
let mut data = ctx.data.write().await;
|
||||||
|
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||||
|
|
||||||
|
global_data.cfg.bot_inventory.inventory.clear();
|
||||||
|
|
||||||
|
global_data.cfg.bot_inventory.give_item(
|
||||||
|
ItemType::TheConceptOfLove,
|
||||||
|
thread_rng().gen_range(0..5),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
global_data.cfg.bot_inventory.give_item(
|
||||||
|
ItemType::GoodFortune,
|
||||||
|
thread_rng().gen_range(0..10),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
global_data.cfg.bot_inventory.give_item(
|
||||||
|
ItemType::CancelInsurance,
|
||||||
|
thread_rng().gen_range(0..10),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut dir = tokio::fs::read_dir(&global_data.cfg.nft_path).await?;
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
while let Ok(Some(_)) = dir.next_entry().await {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 64 {
|
||||||
|
tokio::fs::remove_dir_all(&global_data.cfg.nft_path).await?;
|
||||||
|
tokio::fs::create_dir(&global_data.cfg.nft_path).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
let nft = create_image(global_data, "gold").await.unwrap();
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
hasher.write(&nft);
|
||||||
|
let nft_hash = hasher.finish();
|
||||||
|
|
||||||
|
let path = global_data.cfg.nft_path.join(format!("{}.png", nft_hash));
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = tokio::fs::File::create(path.clone()).await?;
|
||||||
|
file.write_all(&nft).await?;
|
||||||
|
|
||||||
|
global_data.cfg.bot_inventory.give_item(
|
||||||
|
ItemType::Nft,
|
||||||
|
1,
|
||||||
|
Some(ItemData::Nft(path.to_str().unwrap().to_string())),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
global_data.cfg.save(&global_data.args.cfg_path).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
use crate::imgur::ImgurError;
|
use crate::imgur::ImgurError;
|
||||||
|
use crate::user;
|
||||||
use serde::ser::StdError;
|
use serde::ser::StdError;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ pub enum Error {
|
|||||||
SerenityError(serenity::Error),
|
SerenityError(serenity::Error),
|
||||||
TeraError(tera::Error),
|
TeraError(tera::Error),
|
||||||
NoAlbumFound,
|
NoAlbumFound,
|
||||||
FrenCoin(crate::wallet::WalletError),
|
UserError(user::UserError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for Error {}
|
impl StdError for Error {}
|
||||||
@ -48,7 +49,7 @@ impl Display for Error {
|
|||||||
Error::SerenityError(e) => write!(f, "Discord error: {}", e),
|
Error::SerenityError(e) => write!(f, "Discord error: {}", e),
|
||||||
Error::TeraError(e) => write!(f, "Tera error: {}", e),
|
Error::TeraError(e) => write!(f, "Tera error: {}", e),
|
||||||
Error::NoAlbumFound => write!(f, "No album found"),
|
Error::NoAlbumFound => write!(f, "No album found"),
|
||||||
Error::FrenCoin(e) => write!(f, "Fren coin error: {}", e),
|
Error::UserError(e) => write!(f, "User error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
192
src/inventory/mod.rs
Normal file
192
src/inventory/mod.rs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::utils::MessageBuilder;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum InventoryError {
|
||||||
|
NotEnoughItems,
|
||||||
|
UnkownItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for InventoryError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
InventoryError::NotEnoughItems => write!(f, "Not enough items"),
|
||||||
|
InventoryError::UnkownItem => write!(f, "Not sure that that is, maybe check Walmart?"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
|
||||||
|
#[serde(tag = "type", content = "data")]
|
||||||
|
pub enum ItemData {
|
||||||
|
Nft(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, Copy)]
|
||||||
|
pub enum ItemType {
|
||||||
|
CancelInsurance,
|
||||||
|
TheConceptOfLove,
|
||||||
|
GoodFortune,
|
||||||
|
Nft,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ItemType {
|
||||||
|
type Err = InventoryError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let item = s.to_lowercase().replace(' ', "");
|
||||||
|
|
||||||
|
if item.starts_with("cancelinsurance") {
|
||||||
|
Ok(ItemType::CancelInsurance)
|
||||||
|
} else if item.starts_with("theconceptoflove") {
|
||||||
|
Ok(ItemType::TheConceptOfLove)
|
||||||
|
} else if item.starts_with("goodfortune") {
|
||||||
|
Ok(ItemType::GoodFortune)
|
||||||
|
} else if item.starts_with("nft") {
|
||||||
|
Ok(ItemType::Nft)
|
||||||
|
} else {
|
||||||
|
Err(InventoryError::UnkownItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ItemType {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let name = match self {
|
||||||
|
ItemType::CancelInsurance => "Cancel Insurance".to_string(),
|
||||||
|
ItemType::TheConceptOfLove => "The Concept of Love".to_string(),
|
||||||
|
ItemType::GoodFortune => "Good Fortune".to_string(),
|
||||||
|
ItemType::Nft => "NFT".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InventorySlot {
|
||||||
|
pub quantity: i64,
|
||||||
|
pub item_type: ItemType,
|
||||||
|
pub item_data: Option<ItemData>,
|
||||||
|
}
|
||||||
|
impl InventorySlot {
|
||||||
|
pub fn value(&self) -> i64 {
|
||||||
|
match self.item_type {
|
||||||
|
ItemType::CancelInsurance => 50,
|
||||||
|
ItemType::TheConceptOfLove => 300,
|
||||||
|
ItemType::GoodFortune => 75,
|
||||||
|
ItemType::Nft => 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sell_value(&self) -> i64 {
|
||||||
|
match self.item_type {
|
||||||
|
ItemType::Nft => {
|
||||||
|
if let Some(ItemData::Nft(s)) = &self.item_data {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
s.hash(&mut hasher);
|
||||||
|
|
||||||
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
let per_value = (hash as f64) / (u64::MAX as f64);
|
||||||
|
|
||||||
|
((2000.0 * per_value) as i64) - 1000
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self.value() / 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn use_cost(&self) -> i64 {
|
||||||
|
match self.item_type {
|
||||||
|
ItemType::Nft => 0,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct InventoryManager {
|
||||||
|
pub inventory: Vec<InventorySlot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InventoryManager {
|
||||||
|
pub fn get_item(&mut self, item_type: ItemType) -> Option<&mut InventorySlot> {
|
||||||
|
if let Some(inv_ndx) = self.inventory.iter().position(|i| i.item_type == item_type) {
|
||||||
|
Some(&mut self.inventory[inv_ndx])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn give_item(&mut self, item_type: ItemType, quantity: i64, item_data: Option<ItemData>) {
|
||||||
|
match self.get_item(item_type) {
|
||||||
|
None => self.inventory.push(InventorySlot {
|
||||||
|
quantity,
|
||||||
|
item_type,
|
||||||
|
item_data,
|
||||||
|
}),
|
||||||
|
Some(slot) => {
|
||||||
|
slot.item_data = item_data;
|
||||||
|
slot.quantity += quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_take_item(
|
||||||
|
&mut self,
|
||||||
|
item: ItemType,
|
||||||
|
quantity: i64,
|
||||||
|
) -> Result<Option<ItemData>, InventoryError> {
|
||||||
|
let slot = self.get_item(item);
|
||||||
|
|
||||||
|
if let Some(slot) = slot {
|
||||||
|
if slot.quantity < quantity {
|
||||||
|
return Err(InventoryError::NotEnoughItems);
|
||||||
|
}
|
||||||
|
slot.quantity -= quantity;
|
||||||
|
|
||||||
|
Ok(slot.item_data.clone())
|
||||||
|
} else {
|
||||||
|
Err(InventoryError::NotEnoughItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_items(&self, buy_value: bool) -> String {
|
||||||
|
let mut msg_builder = MessageBuilder::new();
|
||||||
|
|
||||||
|
for item_slot in &self.inventory {
|
||||||
|
if item_slot.quantity != 0 {
|
||||||
|
let value = if buy_value {
|
||||||
|
item_slot.value()
|
||||||
|
} else {
|
||||||
|
item_slot.sell_value()
|
||||||
|
};
|
||||||
|
msg_builder.push_line(format!(
|
||||||
|
"* {} [{} coins] (x{})",
|
||||||
|
item_slot.item_type, value, item_slot.quantity
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_use_item(&mut self, item: ItemType) -> Result<Option<ItemData>, InventoryError> {
|
||||||
|
let item_slot = self.get_item(item).ok_or(InventoryError::NotEnoughItems)?;
|
||||||
|
|
||||||
|
if item_slot.quantity < item_slot.use_cost() {
|
||||||
|
return Err(InventoryError::NotEnoughItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
item_slot.quantity -= item_slot.use_cost();
|
||||||
|
|
||||||
|
Ok(item_slot.item_data.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,8 @@ mod discord;
|
|||||||
mod error;
|
mod error;
|
||||||
mod imgur;
|
mod imgur;
|
||||||
mod insult_compliment;
|
mod insult_compliment;
|
||||||
mod wallet;
|
mod inventory;
|
||||||
|
mod user;
|
||||||
|
|
||||||
use crate::config::{Args, BotConfig, Channel, GlobalData};
|
use crate::config::{Args, BotConfig, Channel, GlobalData};
|
||||||
use crate::discord::emoji_race::RaceMessage;
|
use crate::discord::emoji_race::RaceMessage;
|
||||||
@ -56,6 +57,7 @@ async fn main() {
|
|||||||
.group(&discord::emoji_race::EMOJIRACE_GROUP)
|
.group(&discord::emoji_race::EMOJIRACE_GROUP)
|
||||||
.group(&discord::motivate::MOTIVATE_GROUP)
|
.group(&discord::motivate::MOTIVATE_GROUP)
|
||||||
.group(&discord::voices::VOICES_GROUP)
|
.group(&discord::voices::VOICES_GROUP)
|
||||||
|
.group(&discord::shop::SHOP_GROUP)
|
||||||
.unrecognised_command(unrecognised_command_hook)
|
.unrecognised_command(unrecognised_command_hook)
|
||||||
.bucket("bad_apple", |b| b.delay(60 * 10))
|
.bucket("bad_apple", |b| b.delay(60 * 10))
|
||||||
.await
|
.await
|
||||||
|
|||||||
121
src/user/mod.rs
Normal file
121
src/user/mod.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
use crate::inventory::{InventoryError, InventoryManager, InventorySlot, ItemData, ItemType};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::model::id::UserId;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum UserError {
|
||||||
|
NotEnoughFunds,
|
||||||
|
InvalidTarget,
|
||||||
|
InventoryError(InventoryError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UserError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
UserError::NotEnoughFunds => write!(f, "Not enough funds"),
|
||||||
|
UserError::InvalidTarget => write!(f, "Invalid target"),
|
||||||
|
UserError::InventoryError(e) => write!(f, "Inventory error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InventoryError> for UserError {
|
||||||
|
fn from(e: InventoryError) -> Self {
|
||||||
|
Self::InventoryError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct User {
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub coin_count: i64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub inventory: InventoryManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||||
|
pub struct UserManager {
|
||||||
|
#[serde(default)]
|
||||||
|
users: Vec<User>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl UserManager {
|
||||||
|
pub fn get_user(&mut self, discord_id: UserId) -> &mut User {
|
||||||
|
if let Some(user_ndx) = self.users.iter().position(|u| u.user_id == discord_id) {
|
||||||
|
&mut self.users[user_ndx]
|
||||||
|
} else {
|
||||||
|
self.users.push(User {
|
||||||
|
user_id: discord_id,
|
||||||
|
coin_count: 100,
|
||||||
|
inventory: Default::default(),
|
||||||
|
});
|
||||||
|
self.users.last_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_funds(
|
||||||
|
&mut self,
|
||||||
|
src: UserId,
|
||||||
|
dest: UserId,
|
||||||
|
amount: u32,
|
||||||
|
) -> Result<(), UserError> {
|
||||||
|
self.try_take_funds(src, amount)?;
|
||||||
|
|
||||||
|
self.give_funds(dest, amount);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn give_funds(&mut self, discord_id: UserId, amount: u32) {
|
||||||
|
let mut wallet = self.get_user(discord_id);
|
||||||
|
wallet.coin_count += amount as i64;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_take_funds(&mut self, discord_id: UserId, amount: u32) -> Result<(), UserError> {
|
||||||
|
let mut wallet = self.get_user(discord_id);
|
||||||
|
if wallet.coin_count < amount as i64 {
|
||||||
|
Err(UserError::NotEnoughFunds)
|
||||||
|
} else {
|
||||||
|
wallet.coin_count -= amount as i64;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_item(&mut self, discord_id: UserId, item: ItemType) -> Option<&mut InventorySlot> {
|
||||||
|
self.get_user(discord_id).inventory.get_item(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn give_item(
|
||||||
|
&mut self,
|
||||||
|
discord_id: UserId,
|
||||||
|
item: ItemType,
|
||||||
|
quantity: i64,
|
||||||
|
item_data: Option<ItemData>,
|
||||||
|
) {
|
||||||
|
self.get_user(discord_id)
|
||||||
|
.inventory
|
||||||
|
.give_item(item, quantity, item_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_take_item(
|
||||||
|
&mut self,
|
||||||
|
discord_id: UserId,
|
||||||
|
item: ItemType,
|
||||||
|
quantity: i64,
|
||||||
|
) -> Result<Option<ItemData>, UserError> {
|
||||||
|
Ok(self
|
||||||
|
.get_user(discord_id)
|
||||||
|
.inventory
|
||||||
|
.try_take_item(item, quantity)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_use_item(
|
||||||
|
&mut self,
|
||||||
|
discord_id: UserId,
|
||||||
|
item: ItemType,
|
||||||
|
) -> Result<Option<ItemData>, UserError> {
|
||||||
|
Ok(self.get_user(discord_id).inventory.try_use_item(item)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,72 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serenity::model::id::UserId;
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum WalletError {
|
|
||||||
NotEnoughFunds,
|
|
||||||
InvalidTarget,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for WalletError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
WalletError::NotEnoughFunds => write!(f, "Not enough funds"),
|
|
||||||
WalletError::InvalidTarget => write!(f, "Invalid target"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct Wallet {
|
|
||||||
pub owner: UserId,
|
|
||||||
pub coin_count: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
|
||||||
pub struct WalletManager {
|
|
||||||
#[serde(default)]
|
|
||||||
wallets: Vec<Wallet>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WalletManager {
|
|
||||||
pub fn get_user_wallet(&mut self, discord_id: UserId) -> &mut Wallet {
|
|
||||||
if let Some(user_ndx) = self.wallets.iter().position(|u| u.owner == discord_id) {
|
|
||||||
&mut self.wallets[user_ndx]
|
|
||||||
} else {
|
|
||||||
self.wallets.push(Wallet {
|
|
||||||
owner: discord_id,
|
|
||||||
coin_count: 100,
|
|
||||||
});
|
|
||||||
self.wallets.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transfer_funds(
|
|
||||||
&mut self,
|
|
||||||
src: UserId,
|
|
||||||
dest: UserId,
|
|
||||||
amount: u32,
|
|
||||||
) -> Result<(), WalletError> {
|
|
||||||
self.try_take_funds(src, amount)?;
|
|
||||||
|
|
||||||
self.give_funds(dest, amount);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn give_funds(&mut self, discord_id: UserId, amount: u32) {
|
|
||||||
let mut wallet = self.get_user_wallet(discord_id);
|
|
||||||
wallet.coin_count += amount as i64;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_take_funds(&mut self, discord_id: UserId, amount: u32) -> Result<(), WalletError> {
|
|
||||||
let mut wallet = self.get_user_wallet(discord_id);
|
|
||||||
if wallet.coin_count < amount as i64 {
|
|
||||||
Err(WalletError::NotEnoughFunds)
|
|
||||||
} else {
|
|
||||||
wallet.coin_count -= amount as i64;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user