use diesel::prelude::*; use crate::{DbConnection, QueryResult}; use crate::schema::players; /// Representation of a player in database #[derive(Debug, Queryable, Serialize)] pub struct Player { /// DB Identitier pub id: i32, /// Full name of the character pub name: String, /// Amount of gold coins owed to the group. /// Taking a looted items will increase the debt by it's sell value pub debt: i32, /// Count of copper pieces pub cp: i32, /// Count of silver pieces pub sp: i32, /// Count of gold pieces pub gp: i32, /// Count of platinum pieces pub pp: i32, } pub struct Players<'q>(pub &'q DbConnection); impl<'q> Players<'q> { pub fn all(&self) -> QueryResult> { players::table .load(self.0) } pub fn add(&self, name: &str, wealth: f32) -> QueryResult { diesel::insert_into(players::table) .values(&NewPlayer::create(name, wealth)) .execute(self.0)?; players::table .order(players::dsl::id.desc()) .first(self.0) } } pub struct AsPlayer<'q>(pub &'q DbConnection, pub i32); impl<'q> AsPlayer<'q> { pub fn update_wealth(&self, value_in_gp: f32) -> QueryResult { use crate::schema::players::dsl::*; let current_wealth = players .find(self.1) .select((cp, sp, gp, pp)) .first::(self.0)?; let updated_wealth = Wealth::from_gp(current_wealth.to_gp() + value_in_gp); // Difference in coins that is sent back let difference = Wealth { cp: updated_wealth.cp - current_wealth.cp, sp: updated_wealth.sp - current_wealth.sp, gp: updated_wealth.gp - current_wealth.gp, pp: updated_wealth.pp - current_wealth.pp, }; diesel::update(players) .filter(id.eq(self.1)) .set(&updated_wealth) .execute(self.0)?; Ok(difference) } pub fn update_debt(&self, value_in_gp: i32) -> QueryResult<()> { diesel::update(players::table.find(self.1)) .set(players::dsl::debt.eq( players::dsl::debt + value_in_gp )) .execute(self.0)?; Ok(()) } } /// Unpack a floating value of gold pieces to integer /// values of copper, silver, gold and platinum pieces /// /// # Note /// /// The conversion is slightly different than standard rules : /// ``` 1pp = 100gp = 1000sp = 10000 cp ``` /// fn unpack_gold_value(gold: f32) -> (i32, i32, i32, i32) { let rest = (gold.fract() * 100.0).round() as i32; let gold = gold.trunc() as i32; let pp = gold / 100; let gp = gold % 100; let sp = rest / 10; let cp = rest % 10; (cp, sp, gp, pp) } /// State of a player's wealth /// /// Values are held as individual pieces counts. /// Allows conversion from and to a floating amount of gold pieces. #[derive(Queryable, AsChangeset, Debug)] #[table_name = "players"] pub struct Wealth { pub cp: i32, pub sp: i32, pub gp: i32, pub pp: i32, } impl Wealth { /// Unpack individual pieces counts from gold value /// /// # Examples /// ``` /// # use lootalot_db::models::Wealth; /// let wealth = Wealth::from_gp(403.21); /// assert_eq!(wealth.as_tuple(), (1, 2, 3, 4)); /// ``` pub fn from_gp(gp: f32) -> Self { let (cp, sp, gp, pp) = unpack_gold_value(gp); Self { cp, sp, gp, pp } } /// Convert total value to a floating value in gold pieces /// /// # Examples /// ``` /// # use lootalot_db::models::Wealth; /// let wealth = Wealth{ pp: 4, gp: 3, sp: 2, cp: 1}; /// assert_eq!(wealth.to_gp(), 403.21); /// ``` pub fn to_gp(&self) -> f32 { let i = self.pp * 100 + self.gp; let f = (self.sp * 10 + self.cp) as f32 / 100.0; i as f32 + f } /// Pack the counts inside a tuple, from lower to higher coin value. pub fn as_tuple(&self) -> (i32, i32, i32, i32) { (self.cp, self.sp, self.gp, self.pp) } } /// Representation of a new player record #[derive(Insertable)] #[table_name = "players"] pub(crate) struct NewPlayer<'a> { name: &'a str, cp: i32, sp: i32, gp: i32, pp: i32, } impl<'a> NewPlayer<'a> { pub(crate) fn create(name: &'a str, wealth_in_gp: f32) -> Self { let (cp, sp, gp, pp) = Wealth::from_gp(wealth_in_gp).as_tuple(); Self { name, cp, sp, gp, pp, } } } #[cfg(test)] mod tests { #[test] fn test_unpack_gold_values() { use super::unpack_gold_value; let test_values = [ (1.0, (0, 0, 1, 0)), (1.23, (3, 2, 1, 0)), (1.03, (3, 0, 1, 0)), (100.23, (3, 2, 0, 1)), (-100.23, (-3, -2, -0, -1)), (10189.23, (3, 2, 89, 101)), (-8090.20, (0, -2, -90, -80)), ]; for (tested, expected) in test_values.into_iter() { assert_eq!(unpack_gold_value(*tested), *expected); } } }