Added gogurt reserve
This commit is contained in:
parent
3ec319b93d
commit
1367e427ed
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1093,7 +1093,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fren"
|
name = "fren"
|
||||||
version = "2.1.1"
|
version = "2.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.8.1",
|
"axum 0.8.1",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fren"
|
name = "fren"
|
||||||
version = "2.1.1"
|
version = "2.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
@ -11,6 +11,7 @@ mod little_fren;
|
|||||||
mod movie;
|
mod movie;
|
||||||
mod role;
|
mod role;
|
||||||
pub(crate) mod shop;
|
pub(crate) mod shop;
|
||||||
|
mod stonks;
|
||||||
mod transit;
|
mod transit;
|
||||||
pub(crate) mod voices;
|
pub(crate) mod voices;
|
||||||
|
|
||||||
@ -379,6 +380,10 @@ pub async fn run_bot(global_data: GlobalData) {
|
|||||||
shop::land_mine(),
|
shop::land_mine(),
|
||||||
shop::phrase_canceler(),
|
shop::phrase_canceler(),
|
||||||
shop::nuke(),
|
shop::nuke(),
|
||||||
|
stonks::contribute_to_gogurt_reserve(),
|
||||||
|
stonks::sell_from_gogurt_reserve(),
|
||||||
|
stonks::gogurt_reserve_stats(),
|
||||||
|
stonks::gogurt_reserve_contribution(),
|
||||||
transit::cta_bets(),
|
transit::cta_bets(),
|
||||||
voices::list_voices(),
|
voices::list_voices(),
|
||||||
voices::list_words(),
|
voices::list_words(),
|
||||||
|
|||||||
106
src/discord/stonks.rs
Normal file
106
src/discord/stonks.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use crate::discord::Context;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::models::gogurt_reserves::GogurtReserves;
|
||||||
|
use poise::serenity_prelude::{Mentionable, MessageBuilder};
|
||||||
|
|
||||||
|
/// Contribute to the Gogurt Reserve
|
||||||
|
#[poise::command(prefix_command, category = "Stonks", aliases("cgr"))]
|
||||||
|
pub async fn contribute_to_gogurt_reserve(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "amount of FCs to spend"] contribute_amount: u64,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let gogurt_bought =
|
||||||
|
GogurtReserves::add_contribution(&ctx.data().db, ctx.author().id, contribute_amount)?;
|
||||||
|
|
||||||
|
ctx.reply(format!("You have purchased {gogurt_bought}lbs of Gogurt"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sell gogurt from the Gogurt Reserve
|
||||||
|
#[poise::command(prefix_command, category = "Stonks", aliases("sgr"))]
|
||||||
|
pub async fn sell_from_gogurt_reserve(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "number of pounds of gogurt to sell"] gogurt_to_sell: f64,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let fc_coin_made =
|
||||||
|
GogurtReserves::take_contribution(&ctx.data().db, ctx.author().id, gogurt_to_sell)?;
|
||||||
|
|
||||||
|
ctx.reply(format!(
|
||||||
|
"You have sold {gogurt_to_sell:.3}lbs of Gogurt for a profit of {fc_coin_made} FC"
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get stats on the reserve
|
||||||
|
#[poise::command(prefix_command, category = "Stonks", aliases("grs"))]
|
||||||
|
pub async fn gogurt_reserve_stats(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let gogurt_reserves = GogurtReserves::get_gogurt_reserve(&ctx.data().db)?;
|
||||||
|
let contributors = gogurt_reserves.get_contributors();
|
||||||
|
let total_amount = gogurt_reserves.get_total_amount();
|
||||||
|
let total_worth = gogurt_reserves.get_total_worth();
|
||||||
|
let going_rate = gogurt_reserves.gogurt_rate_per_pound;
|
||||||
|
|
||||||
|
let mut msg_builder = MessageBuilder::new();
|
||||||
|
|
||||||
|
msg_builder.push_line("# Fren Gogurt Reserves LLC Report");
|
||||||
|
msg_builder
|
||||||
|
.push_italic_line("Where your portable yogurt dreams are our portable yogurt solutions!");
|
||||||
|
msg_builder.push_line("");
|
||||||
|
msg_builder.push_line(format!(
|
||||||
|
"The reserve currently contains **{total_amount:.3} lb** of Gogurt brand Gogurt"
|
||||||
|
));
|
||||||
|
msg_builder.push_line(format!(
|
||||||
|
"The worth of this Gogurt total is **{total_worth} FC **"
|
||||||
|
));
|
||||||
|
msg_builder.push_line("");
|
||||||
|
msg_builder.push_line(format!(
|
||||||
|
"The going rate of Gogurt on the free market is **{going_rate:.3} FC/lb**"
|
||||||
|
));
|
||||||
|
msg_builder.push_line("");
|
||||||
|
msg_builder.push_line("## Top Contributors");
|
||||||
|
|
||||||
|
let top_contributors_cnt = 5.clamp(0, contributors.len());
|
||||||
|
|
||||||
|
for (ndx, (contributor, amount)) in contributors[0..top_contributors_cnt].iter().enumerate() {
|
||||||
|
let mention = contributor.mention();
|
||||||
|
let worth = gogurt_reserves.convert_pounds_to_fc(*amount);
|
||||||
|
let ndx = ndx + 1;
|
||||||
|
msg_builder.push_line(format!(
|
||||||
|
"{ndx}. {mention} with {amount:.3} lb worth {worth} FC"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.reply(msg_builder.build()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get info on your reserve contribution
|
||||||
|
#[poise::command(prefix_command, category = "Stonks", aliases("grc"))]
|
||||||
|
pub async fn gogurt_reserve_contribution(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Optional user to get info on"] user: Option<poise::serenity_prelude::User>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let gogurt_reserves = GogurtReserves::get_gogurt_reserve(&ctx.data().db)?;
|
||||||
|
let user_id = user.map(|u| u.id).unwrap_or(ctx.author().id);
|
||||||
|
let contribution = gogurt_reserves.reserve_contributors.get(&user_id);
|
||||||
|
|
||||||
|
let mention = user_id.mention();
|
||||||
|
if let Some(contribution) = contribution {
|
||||||
|
let worth = gogurt_reserves.convert_pounds_to_fc(*contribution);
|
||||||
|
let rate = gogurt_reserves.gogurt_rate_per_pound;
|
||||||
|
|
||||||
|
ctx.reply(format!("{mention}'s contribution is **{contribution:.3} lb** of Gogurt Brand Gogurt worth **{worth} FC** at the current rate of **{rate:.3} FC/lb**")).await?;
|
||||||
|
} else {
|
||||||
|
ctx.reply(format!(
|
||||||
|
"{mention} has not contributed to our fine reserve yet!"
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@ pub enum Error {
|
|||||||
NoImageFound,
|
NoImageFound,
|
||||||
PipelineArgumentError(ModifyImageArgError),
|
PipelineArgumentError(ModifyImageArgError),
|
||||||
NoRandomFound,
|
NoRandomFound,
|
||||||
|
NotEnoughGogurt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for Error {}
|
impl StdError for Error {}
|
||||||
@ -116,6 +117,10 @@ impl Display for Error {
|
|||||||
Error::NoImageFound => write!(f, "Image not found"),
|
Error::NoImageFound => write!(f, "Image not found"),
|
||||||
Error::PipelineArgumentError(err) => write!(f, "{err}"),
|
Error::PipelineArgumentError(err) => write!(f, "{err}"),
|
||||||
Error::NoRandomFound => write!(f, "No random found"),
|
Error::NoRandomFound => write!(f, "No random found"),
|
||||||
|
Error::NotEnoughGogurt => write!(
|
||||||
|
f,
|
||||||
|
"Wow, you really don't have enough gogurt. What are you 12?? Just got buy some poor."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
153
src/models/gogurt_reserves.rs
Normal file
153
src/models/gogurt_reserves.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
use crate::user::User;
|
||||||
|
use chrono::Utc;
|
||||||
|
use j_db::database::Database;
|
||||||
|
use log::info;
|
||||||
|
use poise::serenity_prelude::UserId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
pub struct GogurtReserves {
|
||||||
|
id: Option<u64>,
|
||||||
|
pub reserve_contributors: HashMap<UserId, f64>,
|
||||||
|
pub gogurt_rate_per_pound: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl j_db::model::JdbModel for GogurtReserves {
|
||||||
|
fn id(&self) -> Option<u64> {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: u64) {
|
||||||
|
self.id = Some(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree() -> String {
|
||||||
|
"GogurtReserves".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GogurtReserves {
|
||||||
|
pub fn get_gogurt_reserve(db: &Database) -> Result<Self, Error> {
|
||||||
|
Ok(db
|
||||||
|
.filter(|_, _: &Self| true)?
|
||||||
|
.next()
|
||||||
|
.unwrap_or(Self::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_contribution(db: &Database, user: UserId, fc_amount: u64) -> Result<f64, Error> {
|
||||||
|
let mut gogurt_reserve = Self::get_gogurt_reserve(db)?;
|
||||||
|
let gogurt_amount = gogurt_reserve.convert_fc_to_pounds(fc_amount);
|
||||||
|
|
||||||
|
let contribution = gogurt_reserve
|
||||||
|
.reserve_contributors
|
||||||
|
.entry(user)
|
||||||
|
.or_insert_with(|| 0.0);
|
||||||
|
|
||||||
|
User::try_take_funds(db, user, fc_amount as u32)?;
|
||||||
|
*contribution += gogurt_amount;
|
||||||
|
|
||||||
|
db.insert(gogurt_reserve)?;
|
||||||
|
|
||||||
|
Ok(gogurt_amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_contribution(
|
||||||
|
db: &Database,
|
||||||
|
user: UserId,
|
||||||
|
pounds_of_gogurt: f64,
|
||||||
|
) -> Result<u64, Error> {
|
||||||
|
let mut gogurt_reserve = Self::get_gogurt_reserve(db)?;
|
||||||
|
let contribution = gogurt_reserve
|
||||||
|
.reserve_contributors
|
||||||
|
.entry(user)
|
||||||
|
.or_insert_with(|| 0.0);
|
||||||
|
|
||||||
|
if pounds_of_gogurt.is_sign_negative() {
|
||||||
|
return Err(Error::CommandError(
|
||||||
|
"You can't take negative gogurt, stop trying".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if pounds_of_gogurt > *contribution {
|
||||||
|
Err(Error::NotEnoughGogurt)
|
||||||
|
} else {
|
||||||
|
*contribution -= pounds_of_gogurt;
|
||||||
|
let fc_profit = gogurt_reserve.convert_pounds_to_fc(pounds_of_gogurt);
|
||||||
|
|
||||||
|
db.insert(gogurt_reserve)?;
|
||||||
|
|
||||||
|
User::give_funds(db, user, fc_profit as u32)?;
|
||||||
|
|
||||||
|
Ok(fc_profit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_contributors(&self) -> Vec<(UserId, f64)> {
|
||||||
|
let mut contributors: Vec<(UserId, f64)> =
|
||||||
|
self.reserve_contributors.clone().into_iter().collect();
|
||||||
|
|
||||||
|
contributors.sort_by(|(_, a), (_, b)| b.total_cmp(a));
|
||||||
|
|
||||||
|
contributors
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_total_amount(&self) -> f64 {
|
||||||
|
self.reserve_contributors.values().sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_total_worth(&self) -> u64 {
|
||||||
|
(self.get_total_amount() * self.gogurt_rate_per_pound) as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_pounds_to_fc(&self, pounds: f64) -> u64 {
|
||||||
|
(pounds * self.gogurt_rate_per_pound) as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_fc_to_pounds(&self, fc: u64) -> f64 {
|
||||||
|
fc as f64 / self.gogurt_rate_per_pound
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn market_function(time: i64) -> f64 {
|
||||||
|
let x = time as f64;
|
||||||
|
(x.sin() + (PI as f64 * x).cos() + (PI as f64 * x).sin() + x.cos()
|
||||||
|
- (x * 4.0).sin()
|
||||||
|
- (x * 3.0).cos())
|
||||||
|
.clamp(0.0, 6.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_new_rate(time: i64) -> f64 {
|
||||||
|
Self::market_function(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_rate(db: &Database) -> Result<(), Error> {
|
||||||
|
let mut reserves = Self::get_gogurt_reserve(db)?;
|
||||||
|
|
||||||
|
let time = Utc::now().timestamp();
|
||||||
|
|
||||||
|
let new_rate = Self::calculate_new_rate(time);
|
||||||
|
|
||||||
|
info!("Updating rate to '{new_rate}'");
|
||||||
|
reserves.gogurt_rate_per_pound = new_rate;
|
||||||
|
|
||||||
|
db.insert(reserves)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::models::gogurt_reserves::GogurtReserves;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_avg_market_function() {
|
||||||
|
let sum: f64 = (0..100000).map(GogurtReserves::market_function).sum();
|
||||||
|
let avg = sum / 100000.0;
|
||||||
|
|
||||||
|
println!("Average = {avg}");
|
||||||
|
assert!(avg < 1.0 && avg > 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
pub mod api_key;
|
pub mod api_key;
|
||||||
pub mod birthday;
|
pub mod birthday;
|
||||||
|
pub mod gogurt_reserves;
|
||||||
pub mod insult_compliment;
|
pub mod insult_compliment;
|
||||||
pub mod lil_fren;
|
pub mod lil_fren;
|
||||||
pub mod managed_roles;
|
pub mod managed_roles;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::config::GlobalData;
|
|||||||
use crate::discord::shop::restock_shop;
|
use crate::discord::shop::restock_shop;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::models::birthday::BirthdayEntry;
|
use crate::models::birthday::BirthdayEntry;
|
||||||
|
use crate::models::gogurt_reserves::GogurtReserves;
|
||||||
use crate::models::insult_compliment::{RandomResponseTemplate, ResponseType};
|
use crate::models::insult_compliment::{RandomResponseTemplate, ResponseType};
|
||||||
use crate::models::lil_fren::lil_fren_task;
|
use crate::models::lil_fren::lil_fren_task;
|
||||||
use chrono::{Days, Duration, TimeDelta, TimeZone, Timelike, Utc};
|
use chrono::{Days, Duration, TimeDelta, TimeZone, Timelike, Utc};
|
||||||
@ -20,6 +21,7 @@ pub enum TaskType {
|
|||||||
HandleReload,
|
HandleReload,
|
||||||
RestockShop,
|
RestockShop,
|
||||||
UpdateLilBuddy,
|
UpdateLilBuddy,
|
||||||
|
UpdateGogurtRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskType {
|
impl TaskType {
|
||||||
@ -30,6 +32,7 @@ impl TaskType {
|
|||||||
| TaskType::HandleReload
|
| TaskType::HandleReload
|
||||||
| TaskType::RestockShop
|
| TaskType::RestockShop
|
||||||
| TaskType::UpdateLilBuddy
|
| TaskType::UpdateLilBuddy
|
||||||
|
| TaskType::UpdateGogurtRate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,6 +93,7 @@ impl Task {
|
|||||||
Task::add_task(&data.db, TaskType::CheckBirthdays, Utc::now())?;
|
Task::add_task(&data.db, TaskType::CheckBirthdays, Utc::now())?;
|
||||||
Task::add_task(&data.db, TaskType::RestockShop, Utc::now())?;
|
Task::add_task(&data.db, TaskType::RestockShop, Utc::now())?;
|
||||||
Task::add_task(&data.db, TaskType::UpdateLilBuddy, Utc::now())?;
|
Task::add_task(&data.db, TaskType::UpdateLilBuddy, Utc::now())?;
|
||||||
|
Task::add_task(&data.db, TaskType::UpdateGogurtRate, Utc::now())?;
|
||||||
Task::add_task(
|
Task::add_task(
|
||||||
&data.db,
|
&data.db,
|
||||||
TaskType::HandleReload,
|
TaskType::HandleReload,
|
||||||
@ -195,6 +199,15 @@ impl Task {
|
|||||||
Utc::now() + Duration::minutes(5),
|
Utc::now() + Duration::minutes(5),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
TaskType::UpdateGogurtRate => {
|
||||||
|
GogurtReserves::update_rate(&data.db)?;
|
||||||
|
|
||||||
|
Task::add_task(
|
||||||
|
&data.db,
|
||||||
|
TaskType::UpdateGogurtRate,
|
||||||
|
Utc::now() + Duration::minutes(20),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = data.db.remove::<Task>(task.id().unwrap()).is_ok();
|
let _ = data.db.remove::<Task>(task.id().unwrap()).is_ok();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user