merge shop and looted tables

This commit is contained in:
2020-01-09 13:25:36 +01:00
parent 6149dfd297
commit f9cd09431d
8 changed files with 205 additions and 275 deletions

View File

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

View File

@@ -5,8 +5,8 @@ CREATE TABLE items (
base_price INTEGER NOT NULL
);
-- The items that have been looted
CREATE TABLE looted (
-- The loot
CREATE TABLE loot (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR NOT NULL,
base_price INTEGER NOT NULL,
@@ -14,9 +14,3 @@ CREATE TABLE looted (
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

@@ -29,7 +29,7 @@ pub type DbConnection = SqliteConnection;
pub type Pool = r2d2::Pool<ConnectionManager<DbConnection>>;
/// The result of a query on DB
pub type QueryResult<T> = Result<T, diesel::result::Error>;
pub type UpdateResult = QueryResult<Update>;
pub type UpdateResult = QueryResult<Vec<Update>>;
/// Sets up a connection pool and returns it.
/// Uses the DATABASE_URL environment variable (must be set)
@@ -60,8 +60,8 @@ impl Update {
Update::ItemAdded(item) => LootManager(conn, id).find(item.id)?.remove(conn)?,
Update::ItemRemoved(item) => LootManager(conn, id).add_from(&item)?,
// Unused for now
Update::ClaimAdded(claim) => Update::ClaimRemoved(*claim),
Update::ClaimRemoved(claim) => Update::ClaimAdded(*claim),
Update::ClaimAdded(claim) => vec!(Update::ClaimRemoved(*claim)),
Update::ClaimRemoved(claim) => vec!(Update::ClaimAdded(*claim)),
})
}
}
@@ -99,82 +99,6 @@ impl serde::Serialize for Value {
}
}
/// Sells a single item inside a transaction
///
/// # Returns
/// The deleted entity and the updated Wealth (as a difference from previous value)
pub fn sell_item_transaction(
conn: &DbConnection,
id: i32,
loot_id: i32,
price_mod: Option<f64>,
) -> QueryResult<(Update, Wealth)> {
conn.transaction(|| {
let to_delete = LootManager(conn, id).find(loot_id)?;
let mut sell_value = to_delete.sell_value() as f64;
if let Some(modifier) = price_mod {
sell_value *= modifier;
}
let deleted = to_delete.remove(conn)?;
if let Update::Wealth(wealth) = AsPlayer(conn, id).update_wealth(sell_value)? {
Ok((deleted, wealth))
} else {
Err(diesel::result::Error::RollbackTransaction)
}
})
}
/// Buys a single item, copied from inventory.
/// Runs inside a transaction
///
/// # Returns
/// The created entity and the updated Wealth (as a difference from previous value)
pub fn buy_item_from_inventory(
conn: &DbConnection,
id: i32,
item_id: i32,
price_mod: Option<f64>,
) -> QueryResult<(Update, Wealth)> {
conn.transaction(|| {
// Find item in inventory
let item = Inventory(conn).find(item_id)?;
let new_item = LootManager(conn, id).add_from(&item)?;
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)
}
})
}
pub fn buy_item_from_shop(
conn: &DbConnection,
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.
///
@@ -239,7 +163,7 @@ pub fn split_and_share(
}
}
}
Ok(Update::Wealth(Wealth::from_gp(shared_total)))
Ok(vec!(Update::Wealth(Wealth::from_gp(shared_total))))
})
}

View File

@@ -73,22 +73,22 @@ impl<'q> Claims<'q> {
.values(&claim)
.execute(self.0)?;
// Return the created claim
Ok(
Ok(vec!(
Update::ClaimAdded(
claims::table
.order(claims::dsl::id.desc())
.first::<Claim>(self.0)?
)
)
))
}
/// Removes a claim from database, returning it
pub fn remove(self, player_id: i32, loot_id: i32) -> UpdateResult {
let claim = self.find(player_id, loot_id)?;
claim.remove(self.0)?;
Ok(
Ok(vec!(
Update::ClaimRemoved(claim)
)
))
}
pub fn filtered_by_loot(&self, loot_id: i32) -> QueryResult<Vec<Claim>> {

View File

@@ -2,10 +2,10 @@ use diesel::dsl::{exists, Eq, Filter, Find, Select};
use diesel::expression::exists::Exists;
use diesel::prelude::*;
use crate::schema::{items, looted, shop};
use crate::schema::{items, loot};
use crate::{DbConnection, QueryResult, Update, UpdateResult, Claims };
type ItemColumns = (looted::id, looted::name, looted::base_price);
const ITEM_COLUMNS: ItemColumns = (looted::id, looted::name, looted::base_price);
type ItemColumns = (loot::id, loot::name, loot::base_price);
const ITEM_COLUMNS: ItemColumns = (loot::id, loot::name, loot::base_price);
type OwnedBy = Select<OwnedLoot, ItemColumns>;
/// Represents a basic item
@@ -27,57 +27,46 @@ impl Item {
self.base_price as f64 / 2.0
}
pub fn remove(self, conn: &DbConnection) -> UpdateResult {
conn.transaction(
|| -> UpdateResult
{
Claims(conn).delete_for_loot(self.id)?;
diesel::delete(looted::table.find(self.id)).execute(conn)?;
Ok(Update::ItemRemoved(self))
}
)
}
fn owned_by(player: i32) -> OwnedBy {
Loot::owned_by(player).select(ITEM_COLUMNS)
}
}
const OF_GROUP = 0;
const OF_SHOP = -1;
const SOLD = -2;
// Owner status
const OF_GROUP: i32 = 0;
const OF_SHOP: i32 = -1;
const SOLD: i32 = -2;
type WithOwner = Eq<looted::owner_id, i32>;
type OwnedLoot = Filter<looted::table, WithOwner>;
type WithOwner = Eq<loot::owner_id, i32>;
type OwnedLoot = Filter<loot::table, WithOwner>;
/// An owned item
///
/// The owner is a Player, the Group, the Merchant
/// OR the SOLD state.
#[derive(Identifiable, Debug, Queryable)]
#[table_name = "looted"]
#[table_name = "loot"]
pub(super) struct Loot {
id: i32,
name: String,
base_price: i32,
owner: i32,
owner_id: i32,
}
impl Loot {
/// A filter on Loot that is owned by given player
pub(super) fn owned_by(id: i32) -> OwnedLoot {
looted::table.filter(looted::owner_id.eq(id))
loot::table.filter(loot::owner_id.eq(id))
}
fn exists(id: i32) -> Exists<Find<looted::table, i32>> {
exists(looted::table.find(id))
fn exists(id: i32) -> Exists<Find<loot::table, i32>> {
exists(loot::table.find(id))
}
pub(super) fn set_owner(&self, owner: i32, conn: &DbConnection) -> QueryResult<()> {
diesel::update(looted::table.find(self.id))
.set(looted::dsl::owner_id.eq(owner))
diesel::update(loot::table.find(self.id))
.set(loot::dsl::owner_id.eq(owner))
.execute(conn)?;
Ok(())
}
@@ -90,8 +79,8 @@ impl Loot {
}
}
pub(super) fn find(id: i32) -> Find<looted::table, i32> {
looted::table.find(id)
pub(super) fn find(id: i32) -> Find<loot::table, i32> {
loot::table.find(id)
}
}
@@ -107,6 +96,28 @@ impl<'q> Inventory<'q> {
pub fn find(&self, item_id: i32) -> QueryResult<Item> {
items::table.find(item_id).first::<Item>(self.0)
}
pub fn find_by_name(&self, item_name: &str) -> QueryResult<Item> {
Ok(items::table.filter(items::dsl::name.like(item_name)).first(self.0)?)
}
/// Check the inventory against a list of item names
///
/// # Returns
///
/// A tuple of found items and errors
pub fn check_list(&self, item_names: Vec<String>) -> QueryResult<(Vec<Item>, String)> {
let all_items = self.all()?;
let mut found = Vec::new();
let mut errors = String::new();
for name in &item_names {
match self.find_by_name(name) {
Ok(item) => found.push(item),
Err(_) => errors.push_str(&format!("{},\n", name)),
}
}
Ok((found, errors))
}
}
/// The shop resource
@@ -115,39 +126,62 @@ pub struct Shop<'q>(pub &'q DbConnection);
impl<'q> Shop<'q> {
// Rename to list
pub fn all(&self) -> QueryResult<Vec<Item>> {
// Loot::owned_by(OF_SHOP).load(self.0)
shop::table.load(self.0)
Item::owned_by(OF_SHOP).load(self.0)
}
// pub fn buy(&self, items: Vec<(i32, f64)>, buyer: i32) -> UpdateResult {}
pub fn get(&self, id: i32) -> QueryResult<Item> {
shop::table.find(&id).first::<Item>(self.0)
fn buy_single(&self, buyer: i32, item_id: i32, price_mod: Option<f64>) -> UpdateResult {
use crate::AsPlayer;
let item = self.get(item_id)?;
let sell_price = match price_mod {
Some(modifier) => item.base_price as f64 * modifier,
None => item.base_price as f64,
};
self.0.transaction(|| {
let mut updates = AsPlayer(self.0, buyer).update_wealth(-sell_price)?;
item.set_owner(buyer, self.0)?;
updates.push(Update::ItemAdded(item.into_item()));
Ok(updates)
})
}
pub fn remove(&self, id: i32) -> QueryResult<()> {
diesel::delete(
shop::table.find(&id)
).execute(self.0)?;
Ok(())
pub fn buy(&self, items: Vec<(i32, Option<f64>)>, buyer: i32) -> UpdateResult {
// TODO: check that player has enough money !
let has_enough_gold = true;
if has_enough_gold {
let mut updates = Vec::new();
for (item_id, price_mod) in items.into_iter() {
updates.append(
&mut self.buy_single(buyer, item_id, price_mod)?
)
}
Ok(updates)
} else {
unimplemented!();
}
}
fn get(&self, id: i32) -> QueryResult<Loot> {
Loot::owned_by(OF_SHOP).find(&id).first::<Loot>(self.0)
}
pub fn replace_list(&self, items: Vec<Item>) -> QueryResult<()> {
use self::loot::dsl::*;
self.0.transaction(
|| -> QueryResult<()>
{
// Remove all content
diesel::delete(shop::table).execute(self.0)?;
diesel::delete(Loot::owned_by(OF_SHOP)).execute(self.0)?;
// Adds new list
for item in &items {
let new_item = NewItem {
let new_item = NewLoot {
name: &item.name,
base_price: item.base_price,
owner_id: OF_SHOP,
};
diesel::insert_into(shop::table)
diesel::insert_into(loot)
.values(&new_item)
.execute(self.0)?;
}
Ok(())
}
@@ -164,28 +198,50 @@ impl<'q> LootManager<'q> {
Ok(Item::owned_by(self.1).load(self.0)?)
}
fn get(&self, item_id: i32) -> QueryResult<Loot> {
Ok(Loot::owned_by(self.1).find(item_id).first(self.0)?)
}
/// Finds an item by id
///
/// Returns a NotFound error if an item is found by it
/// does not belong to this player
pub fn find(&self, loot_id: i32) -> QueryResult<Item> {
Ok(Loot::find(loot_id).first(self.0).and_then(|loot: Loot| {
if loot.owner != self.1 {
Err(diesel::result::Error::NotFound)
} else {
Ok(Item {
id: loot.id,
name: loot.name,
base_price: loot.base_price,
})
}
})?)
Ok(self.get(loot_id)?.into_item())
}
fn sell_single(&self, loot_id: i32, price_mod: Option<f64>) -> UpdateResult {
let to_sell = self.get(loot_id)?;
let mut sell_value = to_sell.base_price as f64 / 2.0;
if let Some(modifier) = price_mod {
sell_value *= modifier;
}
self.0.transaction(|| {
let mut updates =
crate::AsPlayer(self.0, self.1)
.update_wealth(sell_value)?;
to_sell.set_owner(SOLD, self.0)?;
updates.push(Update::ItemRemoved(to_sell.into_item()));
Ok(updates)
})
}
pub fn sell(&self, items: Vec<(i32, Option<f64>)>) -> UpdateResult {
self.0.transaction(|| {
let mut updates = Vec::new();
for (loot_id, price_mod) in items.into_iter() {
updates.append(&mut self.sell_single(loot_id, price_mod)?);
}
Ok(updates)
})
}
/// The last item added to the chest
pub fn last(&self) -> QueryResult<Item> {
Ok(Item::owned_by(self.1)
.order(looted::dsl::id.desc())
.order(loot::dsl::id.desc())
.first(self.0)?)
}
@@ -204,19 +260,19 @@ impl<'q> LootManager<'q> {
base_price: item.base_price,
owner_id: self.1,
};
diesel::insert_into(looted::table)
diesel::insert_into(loot::table)
.values(&new_item)
.execute(self.0)?;
Ok(Update::ItemAdded(self.last()?))
Ok(vec!(Update::ItemAdded(self.last()?)))
}
}
/// An item being looted or bought.
/// An item being loot or bought.
///
/// The owner is set to 0 in case of looting,
/// to the id of buying player otherwise.
#[derive(Insertable)]
#[table_name = "looted"]
#[table_name = "loot"]
struct NewLoot<'a> {
name: &'a str,
base_price: i32,
@@ -224,7 +280,7 @@ struct NewLoot<'a> {
}
#[derive(Insertable)]
#[table_name = "shop"]
#[table_name = "items"]
struct NewItem<'a> {
name: &'a str,
base_price: i32,

View File

@@ -89,7 +89,7 @@ impl<'q> AsPlayer<'q> {
.filter(id.eq(self.1))
.set(&updated_wealth)
.execute(self.0)?;
Ok(Update::Wealth(updated_wealth - current_wealth))
Ok(vec!(Update::Wealth(updated_wealth - current_wealth)))
}
/// Updates this player's debt

View File

@@ -26,7 +26,7 @@ table! {
}
table! {
looted (id) {
loot (id) {
id -> Integer,
name -> Text,
base_price -> Integer,
@@ -54,26 +54,16 @@ table! {
}
}
table! {
shop (id) {
id -> Integer,
name -> Text,
base_price -> Integer,
}
}
joinable!(claims -> looted (loot_id));
joinable!(claims -> players (player_id));
joinable!(history -> players (player_id));
joinable!(looted -> players (owner_id));
joinable!(loot -> players (owner_id));
joinable!(notifications -> players (player_id));
allow_tables_to_appear_in_same_query!(
claims,
history,
items,
looted,
loot,
notifications,
players,
shop,
);