Add the ability to contribute to improvements without buying them outright

This commit is contained in:
Joey Hines 2025-12-06 16:28:06 -07:00
parent 30767e8e0e
commit d03f33ea6d
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
8 changed files with 169 additions and 53 deletions

2
Cargo.lock generated
View File

@ -1093,7 +1093,7 @@ dependencies = [
[[package]]
name = "fren"
version = "2.5.1"
version = "2.6.0"
dependencies = [
"axum 0.8.1",
"base64 0.22.1",

View File

@ -1,6 +1,6 @@
[package]
name = "fren"
version = "2.5.1"
version = "2.6.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -15,13 +15,17 @@ pub async fn improvements(ctx: Context<'_>) -> Result<(), Error> {
msg_builder.push_line("Anything can be improved with enough Fren Coins!");
msg_builder.push_line("");
let improvements = Improvements::get_improvement_config(&ctx.data().db)?;
for improvement_type in ImprovementType::iter() {
msg_builder.push("* ");
if let Some(improvement) = Improvements::get_improvement(&ctx.data().db, improvement_type)?
{
if let Some(improvement) = improvements.get_improvement(&improvement_type) {
if improvement.has_at_least_one_level() {
if improvement_type.has_levels() {
msg_builder.push(format!(
"({} FC Level={}) ",
"({}/{} FC Level={}) ",
improvement.funding_amount.separate_with_commas(),
improvement_type.price().separate_with_commas(),
improvement.level
));
@ -30,7 +34,14 @@ pub async fn improvements(ctx: Context<'_>) -> Result<(), Error> {
}
} else {
msg_builder.push(format!(
"({} FC) ",
"({}/{} FC) ",
improvement.funding_amount.separate_with_commas(),
improvement_type.price().separate_with_commas(),
));
}
} else {
msg_builder.push(format!(
"(0/{} FC) ",
improvement_type.price().separate_with_commas()
));
}
@ -51,13 +62,14 @@ pub async fn improvements(ctx: Context<'_>) -> Result<(), Error> {
#[poise::command(prefix_command, guild_only, category = "Improvements")]
pub async fn buy_improvement(
ctx: Context<'_>,
#[description = "Improvement to buy"]
#[min = 0u32]
#[description = "Amount to contribute"]
amount: u32,
#[description = "Improvement to contribute to"]
#[rest]
improvement_type: ImprovementType,
) -> Result<(), Error> {
if Improvements::get_improvement(&ctx.data().db, improvement_type)?.is_some()
&& !improvement_type.has_levels()
{
if Improvements::check_is_completed(&ctx.data().db, improvement_type)? {
ctx.reply("Sorry, that improvement has already been purchased.")
.await?;
return Ok(());
@ -69,9 +81,18 @@ pub async fn buy_improvement(
improvement_type.price() as u32,
)?;
Improvements::increment_improvement(&ctx.data().db, improvement_type)?;
let (has_improved, _) =
Improvements::contribute_to_improvement(&ctx.data().db, improvement_type, amount as u64)?;
if has_improved {
ctx.reply(improvement_type.post_buy_description()).await?;
} else {
ctx.reply(format!(
"Thank you for contributing to {}",
improvement_type.name()
))
.await?;
}
Ok(())
}
@ -79,9 +100,8 @@ pub async fn buy_improvement(
/// View Friendship Tower and all its glory!!!
#[poise::command(prefix_command, guild_only, category = "Improvements")]
pub async fn friendship_tower(ctx: Context<'_>) -> Result<(), Error> {
if let Some(tower) =
Improvements::get_improvement(&ctx.data().db, ImprovementType::FriendshipTower)?
{
let improvements = Improvements::get_improvement_config(&ctx.data().db)?;
if let Some(tower) = improvements.get_improvement(&ImprovementType::FriendshipTower) {
let buffer = 2;
let pic_height = tower.level as usize + buffer + 1;
let width = 5;
@ -116,9 +136,9 @@ pub async fn friendship_tower(ctx: Context<'_>) -> Result<(), Error> {
/// See how big Jotchua's college fund is!
#[poise::command(prefix_command, guild_only, category = "Improvements")]
pub async fn jotchua_college_fund(ctx: Context<'_>) -> Result<(), Error> {
let funds_level = if let Some(fund) =
Improvements::get_improvement(&ctx.data().db, ImprovementType::JotchuaCollegeFund)?
{
let improvements = Improvements::get_improvement_config(&ctx.data().db)?;
let funds_level =
if let Some(fund) = improvements.get_improvement(&ImprovementType::JotchuaCollegeFund) {
fund.level
} else {
0

View File

@ -0,0 +1,49 @@
use j_db::database::Database;
use j_db::migration::Migration;
use json::JsonValue;
pub struct Migration10AddFundingAmount {}
impl Migration for Migration10AddFundingAmount {
fn up(&self, db: &Database) -> j_db::error::Result<()> {
let tree = db.db.open_tree("Improvements")?;
for improvement_entry in tree.iter() {
let (id, improvement_bytes) = improvement_entry?;
let mut improvement_cfg =
json::parse(std::str::from_utf8(&improvement_bytes).unwrap())?;
for (_, improvement) in improvement_cfg["improvements"].entries_mut() {
improvement["funding_amount"] = JsonValue::Number(json::number::Number::from(0));
}
tree.insert(id, improvement_cfg.to_string().into_bytes())?;
}
Ok(())
}
fn down(&self, db: &Database) -> j_db::error::Result<()> {
let tree = db.db.open_tree("Improvements")?;
for improvement_entry in tree.iter() {
let (id, improvement_bytes) = improvement_entry?;
let mut improvement_cfg =
json::parse(std::str::from_utf8(&improvement_bytes).unwrap())?;
for improvement in improvement_cfg["improvements"].members_mut() {
improvement.remove("funding_amount");
}
tree.insert(id, improvement_cfg.to_string().into_bytes())?;
}
Ok(())
}
fn version(&self) -> u64 {
10
}
}

View File

@ -4,6 +4,7 @@ use crate::migrations::migration_6_add_social_credit::Migration6AddSocialCredit;
use crate::migrations::migration_7_flip_bounds::Migration7FlipBounds;
use crate::migrations::migration_8_fix_metadata_id::Migration8FixMetadataId;
use crate::migrations::migration_9_update_to_emp::Migration9UpdateToEMP;
use crate::migrations::migration_10_add_funding_amount::Migration10AddFundingAmount;
use crate::migrations::migration2_remove_imgur::Migration2RemoveImgur;
use crate::migrations::migration3_remove_img::Migration3RemoveImage;
use j_db::database::Database;
@ -12,6 +13,7 @@ use j_db::migration::Direction;
mod migration2_remove_imgur;
mod migration3_remove_img;
mod migration_10_add_funding_amount;
mod migration_4_update_random;
mod migration_5_update_motivation;
mod migration_6_add_social_credit;
@ -19,7 +21,7 @@ mod migration_7_flip_bounds;
mod migration_8_fix_metadata_id;
mod migration_9_update_to_emp;
pub const CURRENT_DB_VERSION: u64 = 9;
pub const CURRENT_DB_VERSION: u64 = 10;
#[allow(clippy::single_match)]
pub fn do_migration(db: &Database) {
@ -80,6 +82,12 @@ pub fn do_migration(db: &Database) {
Direction::Up,
)
.unwrap(),
10 => migration::do_migration::<Migration10AddFundingAmount>(
db,
Migration10AddFundingAmount {},
Direction::Up,
)
.unwrap(),
_ => {}
}
}

View File

@ -84,7 +84,7 @@ impl GogurtReserves {
}
pub fn has_night_market(db: &Database) -> Result<bool, Error> {
Ok(Improvements::get_improvement(db, ImprovementType::GogurtNightMarket)?.is_some())
Improvements::check_is_completed(db, ImprovementType::GogurtNightMarket)
}
pub fn get_next_market_update_time(db: &Database) -> Result<DateTime<Utc>, Error> {

View File

@ -1,6 +1,7 @@
use crate::error::Error;
use j_db::database::Database;
use j_db::model::JdbModel;
use log::info;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
@ -105,6 +106,13 @@ impl FromStr for ImprovementType {
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
pub struct Improvement {
pub level: u32,
pub funding_amount: u64,
}
impl Improvement {
pub fn has_at_least_one_level(&self) -> bool {
self.level > 0
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
@ -121,40 +129,71 @@ impl Improvements {
}))
}
pub fn get_improvement(
db: &Database,
improvement_type: ImprovementType,
) -> Result<Option<Improvement>, Error> {
let improvements = Self::get_improvement_config(db)?;
Ok(improvements.improvements.get(&improvement_type).cloned())
}
pub fn increment_improvement(
db: &Database,
improvement_type: ImprovementType,
) -> Result<u32, Error> {
let mut improvements = Self::get_improvement_config(db)?;
let entry = improvements
pub fn get_improvement_mut(&mut self, improvement_type: ImprovementType) -> &mut Improvement {
(self
.improvements
.entry(improvement_type)
.or_insert(Improvement { level: 0 });
.or_insert(Improvement {
level: 0,
funding_amount: 0,
})) as _
}
entry.level += 1;
pub fn get_improvement(&self, improvement_type: &ImprovementType) -> Option<&Improvement> {
self.improvements.get(improvement_type)
}
let level = entry.level;
pub fn contribute_to_improvement(
db: &Database,
improvement_type: ImprovementType,
amount: u64,
) -> Result<(bool, u32), Error> {
let mut improvements = Self::get_improvement_config(db)?;
let improvement = improvements.get_improvement_mut(improvement_type);
improvement.funding_amount += amount;
let has_improved = if improvement.funding_amount >= improvement_type.price() {
let levels_to_fund = improvement.funding_amount / improvement_type.price();
info!(
"Increasing improvement {} by {} levels",
improvement_type.name(),
levels_to_fund,
);
improvement.level += levels_to_fund as u32;
improvement.funding_amount -= improvement_type.price() * levels_to_fund;
true
} else {
false
};
let level = improvement.level;
db.insert(improvements)?;
Ok(level)
Ok((has_improved, level))
}
pub fn has_improvement(
pub fn has_improvement(&self, improvement_type: ImprovementType) -> bool {
if let Some(improvement) = self.get_improvement(&improvement_type) {
improvement.has_at_least_one_level()
} else {
false
}
}
pub fn is_completed(&self, improvement_type: ImprovementType) -> bool {
self.has_improvement(improvement_type) && !improvement_type.has_levels()
}
pub fn check_is_completed(
db: &Database,
improvement_type: ImprovementType,
) -> Result<bool, Error> {
Ok(Self::get_improvement(db, improvement_type)?.is_some())
let improvements = Improvements::get_improvement_config(db)?;
Ok(improvements.is_completed(improvement_type))
}
}

View File

@ -338,7 +338,7 @@ impl LilFren {
}
if lil_fren.hunger < 0.25
&& Improvements::has_improvement(db, ImprovementType::LilBuddyAutoFeeder)?
&& Improvements::check_is_completed(db, ImprovementType::LilBuddyAutoFeeder)?
{
lil_fren.hunger = 0.25;
}