214 lines
5.7 KiB
Rust
214 lines
5.7 KiB
Rust
use crate::schema::players;
|
|
use crate::{DbConnection, QueryResult};
|
|
use diesel::prelude::*;
|
|
|
|
/// Representation of a player in database
|
|
#[derive(Identifiable, Queryable, Serialize, Deserialize, Debug)]
|
|
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<Vec<Player>> {
|
|
players::table.load(self.0)
|
|
}
|
|
|
|
pub fn find(&self, id: i32) -> QueryResult<Player> {
|
|
players::table.find(id).first(self.0)
|
|
}
|
|
|
|
pub fn add(&self, name: &str, wealth: f64) -> QueryResult<Player> {
|
|
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: f64) -> QueryResult<Wealth> {
|
|
use crate::schema::players::dsl::*;
|
|
let current_wealth = players
|
|
.find(self.1)
|
|
.select((cp, sp, gp, pp))
|
|
.first::<Wealth>(self.0)?;
|
|
let updated_wealth = Wealth::from_gp(current_wealth.to_gp() + value_in_gp);
|
|
diesel::update(players)
|
|
.filter(id.eq(self.1))
|
|
.set(&updated_wealth)
|
|
.execute(self.0)?;
|
|
// Difference in coins that is sent back
|
|
Ok(updated_wealth - current_wealth)
|
|
}
|
|
|
|
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: f64) -> (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, Serialize, Deserialize, 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: f64) -> 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: 5, sp: 8, cp: 4};
|
|
/// assert_eq!(wealth.to_gp(), 405.84);
|
|
/// ```
|
|
pub fn to_gp(&self) -> f64 {
|
|
let i = self.pp * 100 + self.gp;
|
|
let f = (self.sp * 10 + self.cp) as f64 / 100.0;
|
|
i as f64 + 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)
|
|
}
|
|
}
|
|
|
|
use std::ops::Sub;
|
|
|
|
impl Sub for Wealth {
|
|
type Output = Self;
|
|
/// What needs to be added to 'other' so that
|
|
/// the result equals 'self'
|
|
fn sub(self, other: Self) -> Self {
|
|
Wealth {
|
|
cp: self.cp - other.cp,
|
|
sp: self.sp - other.sp,
|
|
gp: self.gp - other.gp,
|
|
pp: self.pp - other.pp,
|
|
}
|
|
}
|
|
}
|
|
|
|
use std::ops::Add;
|
|
|
|
impl Add for Wealth {
|
|
type Output = Self;
|
|
|
|
fn add(self, other: Self) -> Self {
|
|
Wealth {
|
|
cp: self.cp + other.cp,
|
|
sp: self.sp + other.sp,
|
|
gp: self.gp + other.gp,
|
|
pp: self.pp + other.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: f64) -> 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 = [
|
|
(0.01, (1, 0, 0, 0)),
|
|
(0.1, (0, 1, 0, 0)),
|
|
(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)),
|
|
(141805.9, (0, 9, 5, 1418)),
|
|
(123141805.9, (0, 9, 5, 1231418)),
|
|
(-8090.20, (0, -2, -90, -80)),
|
|
];
|
|
|
|
for (tested, expected) in test_values.into_iter() {
|
|
assert_eq!(unpack_gold_value(*tested), *expected);
|
|
}
|
|
}
|
|
|
|
}
|