adds new 'shop' table

This commit is contained in:
2019-11-18 15:07:35 +01:00
parent cb8dfa9a2a
commit d1a85ed1d0
8 changed files with 173 additions and 54 deletions

View File

@@ -1,3 +1,3 @@
DROP TABLE items; DROP TABLE items;
DROP TABLE looted; DROP TABLE looted;
DROP TABLE shop;

View File

@@ -13,3 +13,10 @@ CREATE TABLE looted (
owner_id INTEGER NOT NULL, owner_id INTEGER NOT NULL,
FOREIGN KEY (owner_id) REFERENCES players(id) FOREIGN KEY (owner_id) REFERENCES players(id)
); );
-- The items that are available in shop
CREATE TABLE shop (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR NOT NULL,
base_price INTEGER NOT NULL
);

View File

@@ -19,7 +19,7 @@ mod schema;
pub use models::{ pub use models::{
claim::{Claim, Claims}, claim::{Claim, Claims},
history::{Event, UpdateList}, history::{Event, UpdateList},
item::{Inventory, Item, LootManager}, item::{Inventory, Shop, Item, LootManager},
player::{AsPlayer, Player, Players, Wealth}, player::{AsPlayer, Player, Players, Wealth},
}; };
@@ -140,8 +140,8 @@ pub fn buy_item_from_inventory(
let item = Inventory(conn).find(item_id)?; let item = Inventory(conn).find(item_id)?;
let new_item = LootManager(conn, id).add_from(&item)?; let new_item = LootManager(conn, id).add_from(&item)?;
let sell_price = match price_mod { let sell_price = match price_mod {
Some(modifier) => item.value() as f64 * modifier, Some(modifier) => item.value() * modifier,
None => item.value() as f64, None => item.value(),
}; };
if let Update::Wealth(diff) = AsPlayer(conn, id).update_wealth(-sell_price)? { if let Update::Wealth(diff) = AsPlayer(conn, id).update_wealth(-sell_price)? {
Ok((new_item, diff)) Ok((new_item, diff))
@@ -151,10 +151,30 @@ pub fn buy_item_from_inventory(
}) })
} }
/// Fetch all existing claims pub fn buy_item_from_shop(
pub fn fetch_claims(conn: &DbConnection) -> QueryResult<Vec<models::Claim>> { conn: &DbConnection,
schema::claims::table.load::<models::Claim>(conn) id: i32,
item_id: i32,
price_mod: Option<f64>,
) -> QueryResult<(Update, Wealth)> {
conn.transaction(|| {
let shop = Shop(conn);
// Find item in inventory
let item = shop.get(item_id)?;
let new_item = LootManager(conn, id).add_from(&item)?;
let _deleted = shop.remove(item_id)?;
let sell_price = match price_mod {
Some(modifier) => item.value() * modifier,
None => item.value(),
};
if let Update::Wealth(diff) = AsPlayer(conn, id).update_wealth(-sell_price)? {
Ok((new_item, diff))
} else {
Err(diesel::result::Error::RollbackTransaction)
} }
})
}
/// Resolve all pending claims and dispatch claimed items. /// Resolve all pending claims and dispatch claimed items.
/// ///
@@ -171,7 +191,7 @@ pub fn resolve_claims(conn: &DbConnection) -> QueryResult<()> {
let winner = claims.get(0).expect("Claims should not be empty !"); let winner = claims.get(0).expect("Claims should not be empty !");
let player_id = winner.player_id; let player_id = winner.player_id;
winner.resolve_claim(conn)?; winner.resolve_claim(conn)?;
models::player::AsPlayer(conn, player_id).update_debt(item.sell_value())?; models::player::AsPlayer(conn, player_id).update_debt(item.sell_value() as i32)?;
} }
Ok(()) Ok(())
}) })

View File

@@ -41,6 +41,11 @@ impl<'q> Claims<'q> {
claims::table.load(self.0) claims::table.load(self.0)
} }
pub fn by_player(&self, id: i32) -> QueryResult<Vec<Claim>> {
claims::table.filter(claims::dsl::player_id.eq(id))
.load(self.0)
}
/// Finds a single claim by association of player and loot ids. /// Finds a single claim by association of player and loot ids.
pub fn find(&self, player_id: i32, loot_id: i32) -> QueryResult<Claim> { pub fn find(&self, player_id: i32, loot_id: i32) -> QueryResult<Claim> {
claims::table claims::table

View File

@@ -2,7 +2,7 @@ use diesel::dsl::{exists, Eq, Filter, Find, Select};
use diesel::expression::exists::Exists; use diesel::expression::exists::Exists;
use diesel::prelude::*; use diesel::prelude::*;
use crate::schema::{items, looted}; use crate::schema::{items, looted, shop};
use crate::{DbConnection, QueryResult, Update, UpdateResult, Claims }; use crate::{DbConnection, QueryResult, Update, UpdateResult, Claims };
type ItemColumns = (looted::id, looted::name, looted::base_price); type ItemColumns = (looted::id, looted::name, looted::base_price);
const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price); const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price);
@@ -18,13 +18,13 @@ pub struct Item {
impl Item { impl Item {
/// Returns this item value /// Returns this item value
pub fn value(&self) -> i32 { pub fn value(&self) -> f64 {
self.base_price self.base_price as f64
} }
/// Returns this item sell value /// Returns this item sell value
pub fn sell_value(&self) -> i32 { pub fn sell_value(&self) -> f64 {
self.base_price / 2 self.base_price as f64 / 2.0
} }
pub fn remove(self, conn: &DbConnection) -> UpdateResult { pub fn remove(self, conn: &DbConnection) -> UpdateResult {
@@ -58,6 +58,47 @@ impl<'q> Inventory<'q> {
} }
} }
pub struct Shop<'q>(pub &'q DbConnection);
impl<'q> Shop<'q> {
pub fn all(&self) -> QueryResult<Vec<Item>> {
shop::table.load(self.0)
}
pub fn get(&self, id: i32) -> QueryResult<Item> {
shop::table.find(&id).first::<Item>(self.0)
}
pub fn remove(&self, id: i32) -> QueryResult<()> {
diesel::delete(
shop::table.find(&id)
).execute(self.0)?;
Ok(())
}
pub fn replace_list(&self, items: Vec<Item>) -> QueryResult<()> {
self.0.transaction(
|| -> QueryResult<()>
{
// Remove all content
diesel::delete(shop::table).execute(self.0)?;
// Adds new list
for item in &items {
let new_item = NewItem {
name: &item.name,
base_price: item.base_price,
};
diesel::insert_into(shop::table)
.values(&new_item)
.execute(self.0)?;
}
Ok(())
}
)
}
}
type WithOwner = Eq<looted::owner_id, i32>; type WithOwner = Eq<looted::owner_id, i32>;
type OwnedLoot = Filter<looted::table, WithOwner>; type OwnedLoot = Filter<looted::table, WithOwner>;
@@ -169,3 +210,10 @@ struct NewLoot<'a> {
base_price: i32, base_price: i32,
owner_id: i32, owner_id: i32,
} }
#[derive(Insertable)]
#[table_name = "shop"]
struct NewItem<'a> {
name: &'a str,
base_price: i32,
}

View File

@@ -54,6 +54,14 @@ table! {
} }
} }
table! {
shop (id) {
id -> Integer,
name -> Text,
base_price -> Integer,
}
}
joinable!(claims -> looted (loot_id)); joinable!(claims -> looted (loot_id));
joinable!(claims -> players (player_id)); joinable!(claims -> players (player_id));
joinable!(history -> players (player_id)); joinable!(history -> players (player_id));
@@ -67,4 +75,5 @@ allow_tables_to_appear_in_same_query!(
looted, looted,
notifications, notifications,
players, players,
shop,
); );

View File

@@ -13,14 +13,12 @@ pub struct BuySellParams {
global_mod: Option<f64>, global_mod: Option<f64>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct NewGroupLoot { pub struct NewGroupLoot {
source_name: String, source_name: String,
pub items: ItemList, pub items: ItemList,
} }
/// A generic response for all queries /// A generic response for all queries
#[derive(Serialize, Debug, Default)] #[derive(Serialize, Debug, Default)]
pub struct ApiResponse { pub struct ApiResponse {
@@ -70,10 +68,12 @@ pub enum ApiActions {
// Application level // Application level
FetchPlayers, FetchPlayers,
FetchInventory, FetchInventory,
FetchShopInventory,
FetchClaims, FetchClaims,
CheckItemList(Vec<String>), CheckItemList(Vec<String>),
// Player level // Player level
FetchPlayer(i32), FetchPlayer(i32),
FetchPlayerClaims(i32),
FetchNotifications(i32), FetchNotifications(i32),
FetchLoot(i32), FetchLoot(i32),
UpdateWealth(i32, f64), UpdateWealth(i32, f64),
@@ -86,6 +86,7 @@ pub enum ApiActions {
// Group level // Group level
AddLoot(NewGroupLoot), AddLoot(NewGroupLoot),
// Admin level // Admin level
RefreshShopInventory(ItemList),
//AddPlayer(String, f64), //AddPlayer(String, f64),
//AddInventoryItem(pub String, pub i32), //AddInventoryItem(pub String, pub i32),
//ResolveClaims, //ResolveClaims,
@@ -106,9 +107,7 @@ pub fn execute(
let mut errors = String::new(); let mut errors = String::new();
let items = db::Inventory(conn).all()?; let items = db::Inventory(conn).all()?;
for name in &names { for name in &names {
if let Some(item) = if let Some(item) = items.iter().filter(|i| &i.name == name).take(1).next() {
items.iter().filter(|i| &i.name == name).take(1).next()
{
found.push(item.clone()) found.push(item.clone())
} else { } else {
errors.push_str(&format!("{},\n", name)); errors.push_str(&format!("{},\n", name));
@@ -130,14 +129,22 @@ pub fn execute(
response.set_value(Value::ItemList(db::Inventory(conn).all()?)); response.set_value(Value::ItemList(db::Inventory(conn).all()?));
None None
} }
ApiActions::FetchShopInventory => {
response.set_value(Value::ItemList(db::Shop(conn).all()?));
None
}
ApiActions::FetchClaims => { ApiActions::FetchClaims => {
response.set_value(Value::ClaimList(db::fetch_claims(conn)?)); response.set_value(Value::ClaimList(db::Claims(conn).all()?));
None None
} }
ApiActions::FetchPlayer(id) => { ApiActions::FetchPlayer(id) => {
response.set_value(Value::Player(db::Players(conn).find(id)?)); response.set_value(Value::Player(db::Players(conn).find(id)?));
None None
} }
ApiActions::FetchPlayerClaims(id) => {
response.set_value(Value::ClaimList(db::Claims(conn).by_player(id)?));
None
}
ApiActions::FetchNotifications(id) => { ApiActions::FetchNotifications(id) => {
response.set_value(Value::Notifications( response.set_value(Value::Notifications(
db::AsPlayer(conn, id).notifications()?, db::AsPlayer(conn, id).notifications()?,
@@ -155,9 +162,13 @@ pub fn execute(
} }
ApiActions::BuyItems(id, params) => { ApiActions::BuyItems(id, params) => {
// TODO: check that player has enough money ! // TODO: check that player has enough money !
let has_enough_gold = true;
if has_enough_gold {
let mut gains: Vec<db::Wealth> = Vec::with_capacity(params.items.len()); let mut gains: Vec<db::Wealth> = Vec::with_capacity(params.items.len());
for (item_id, price_mod) in params.items.into_iter() { for (item_id, price_mod) in params.items.into_iter() {
if let Ok((item, diff)) = db::buy_item_from_inventory(conn, id, item_id, price_mod) if let Ok((item, diff)) =
db::buy_item_from_shop(conn, id, item_id, price_mod)
{ {
response.push_update(item); response.push_update(item);
gains.push(diff); gains.push(diff);
@@ -176,6 +187,10 @@ pub fn execute(
)); ));
response.push_update(Update::Wealth(total_amount)); response.push_update(Update::Wealth(total_amount));
Some((id, "Achat d'objets")) Some((id, "Achat d'objets"))
} else {
response.push_error("Vous n'avez pas assez d'argent !");
None
}
} }
// Behavior differs if player is group or regular. // Behavior differs if player is group or regular.
// Group sells item like players then split the total amount among players. // Group sells item like players then split the total amount among players.
@@ -200,20 +215,15 @@ pub fn execute(
let players = params let players = params
.players .players
.unwrap_or(db::Players(conn).all()?.into_iter().map(|p| p.id).collect()); .unwrap_or(db::Players(conn).all()?.into_iter().map(|p| p.id).collect());
let shared = db::split_and_share(conn, total_amount.to_gp() as i32, &players)?; if let Update::Wealth(shared) =
let shared_amount = { db::split_and_share(conn, total_amount.to_gp() as i32, &players)?
if let Update::Wealth(amount) = shared { {
amount.to_gp()
} else {
panic!("cannot happen")
}
};
response.notify(format!( response.notify(format!(
"Les objets ont été vendus, les joueurs ont reçu (au total) {} po", "Les objets ont été vendus, les joueurs ont reçu (au total) {} po",
shared_amount shared.to_gp()
)); ));
response.push_update(Update::Wealth(total_amount)); response.push_update(Update::Wealth(db::Wealth::from_gp(total_amount.to_gp() - shared.to_gp())));
response.push_update(shared); };
} }
_ => { _ => {
response.notify(format!( response.notify(format!(
@@ -238,8 +248,12 @@ pub fn execute(
} }
ApiActions::ClaimItems(id, items) => { ApiActions::ClaimItems(id, items) => {
conn.transaction(|| -> Result<Option<(i32, &str)>, diesel::result::Error> { conn.transaction(|| -> Result<Option<(i32, &str)>, diesel::result::Error> {
let current_claims: HashSet<i32> = let current_claims: HashSet<i32> = db::Claims(conn)
db::Claims(conn).all()?.iter().filter(|c| c.player_id == id).map(|c| c.loot_id).collect(); .all()?
.iter()
.filter(|c| c.player_id == id)
.map(|c| c.loot_id)
.collect();
let new_claims: HashSet<i32> = items.into_iter().collect(); let new_claims: HashSet<i32> = items.into_iter().collect();
// Claims to delete // Claims to delete
for item in current_claims.difference(&new_claims) { for item in current_claims.difference(&new_claims) {
@@ -284,6 +298,11 @@ pub fn execute(
}; };
Some((0, "Nouveau loot")) Some((0, "Nouveau loot"))
} }
// Admin actions
ApiActions::RefreshShopInventory(items) => {
db::Shop(conn).replace_list(items)?;
None
}
}; };
// Store the event if it can be undone. // Store the event if it can be undone.

View File

@@ -126,7 +126,9 @@ fn configure_api(config: &mut web::ServiceConfig) {
) )
.service( .service(
web::resource("/claims") web::resource("/claims")
//.route(web::get().to_async(endpoints::player_claims)) .route(web::get().to_async(|pool, player: PlayerId| {
db_call(pool, Q::FetchPlayerClaims(*player))
}))
.route(web::post().to_async( .route(web::post().to_async(
|pool, (player, data): (PlayerId, IdList)| { |pool, (player, data): (PlayerId, IdList)| {
db_call(pool, Q::ClaimItems(*player, data.into_inner())) db_call(pool, Q::ClaimItems(*player, data.into_inner()))
@@ -188,6 +190,15 @@ fn configure_api(config: &mut web::ServiceConfig) {
"/claims", "/claims",
web::get().to_async(|pool| db_call(pool, Q::FetchClaims)), web::get().to_async(|pool| db_call(pool, Q::FetchClaims)),
) )
.service(
web::resource("/shop")
.route(web::get().to_async(|pool| db_call(pool, Q::FetchShopInventory)))
.route(
web::post().to_async(|pool, items: web::Json<api::ItemList>| {
db_call(pool, Q::RefreshShopInventory(items.into_inner()))
}),
),
)
.service( .service(
web::resource("/items") web::resource("/items")
.route( .route(