From 2fa7310aaaf103bf207a4b166858aa1beab1ef0b Mon Sep 17 00:00:00 2001 From: ntr Date: Sat, 13 Jul 2019 18:27:38 +1000 Subject: [PATCH] shop bois --- client/src/actions.jsx | 1 + client/src/components/account.status.jsx | 2 +- client/src/components/anims/idle.jsx | 1 - client/src/components/construct.jsx | 2 +- client/src/components/inventory.jsx | 51 ++++--- client/src/events.jsx | 5 + client/src/reducers.jsx | 1 + client/src/socket.jsx | 10 ++ .../20180913000513_create_accounts.js | 4 +- server/src/account.rs | 66 ++++----- server/src/construct.rs | 4 +- server/src/img.rs | 26 ++-- server/src/instance.rs | 5 +- server/src/mtx.rs | 127 +++++++++++------- server/src/rpc.rs | 13 +- server/src/ws.rs | 13 ++ 16 files changed, 210 insertions(+), 121 deletions(-) diff --git a/client/src/actions.jsx b/client/src/actions.jsx index a15f753a..06e644d5 100644 --- a/client/src/actions.jsx +++ b/client/src/actions.jsx @@ -23,6 +23,7 @@ export const setResolution = value => ({ type: 'SET_RESOLUTION', value }); export const setShowLog = value => ({ type: 'SET_SHOW_LOG', value }); export const setShowNav = value => ({ type: 'SET_SHOW_NAV', value }); export const setSkip = value => ({ type: 'SET_SKIP', value }); +export const setShop = value => ({ type: 'SET_SHOP', value }); export const setTeam = value => ({ type: 'SET_SELECTED_CONSTRUCTS', value: Array.from(value) }); export const setVboxHighlight = value => ({ type: 'SET_VBOX_HIGHLIGHT', value }); export const setWs = value => ({ type: 'SET_WS', value }); diff --git a/client/src/components/account.status.jsx b/client/src/components/account.status.jsx index a2e61b0b..69544435 100644 --- a/client/src/components/account.status.jsx +++ b/client/src/components/account.status.jsx @@ -96,7 +96,7 @@ function AccountStatus(args) { {saw(pingColour(ping))}
{ping}ms
-

{`¤${account.credits}`}

+

{`¤${account.balance}`}

diff --git a/client/src/components/anims/idle.jsx b/client/src/components/anims/idle.jsx index 2b7c2986..0972b4ac 100644 --- a/client/src/components/anims/idle.jsx +++ b/client/src/components/anims/idle.jsx @@ -1,7 +1,6 @@ const anime = require('animejs').default; function idle(id) { - return false; const duration = anime.random(2000, 18000); const target = document.getElementById(id); return anime({ diff --git a/client/src/components/construct.jsx b/client/src/components/construct.jsx index d7ef126d..9c669810 100644 --- a/client/src/components/construct.jsx +++ b/client/src/components/construct.jsx @@ -31,7 +31,7 @@ class ConstructAvatar extends Component {
); } diff --git a/client/src/components/inventory.jsx b/client/src/components/inventory.jsx index 66079ece..4b212ad1 100644 --- a/client/src/components/inventory.jsx +++ b/client/src/components/inventory.jsx @@ -6,12 +6,19 @@ const actions = require('./../actions'); const addState = connect( function receiveState(state) { const { - // ws, + ws, account, + shop, } = state; + function mtxBuy(mtx) { + return ws.sendMtxBuy(mtx.variant); + } + return { account, + shop, + mtxBuy, }; }, @@ -29,29 +36,37 @@ const addState = connect( function Inventory(args) { const { account, + shop, + setMtxActive, + mtxBuy, } = args; + if (!shop) return false; + + const useMtx = (item, i) => ( +
setMtxActive(item)} > +
{item}
+ +
+ ); + + const availableMtx = (item, i) => ( +
mtxBuy(item)} > +
{item.variant}
+ +
+ ); + return (
-

¤ {account.credits}

+

¤ {account.balance}

-
setMtxActive('Reimage')} > -
Reimage
- -
-
setMtxActive('Rename')} > -
Rename
- -
-
-
Invader Architecture
- -
-
-
Molecular Architecture
- -
+ {shop.owned.map(useMtx)} +
+

Shop

+
+ {shop.available.map(availableMtx)}
); diff --git a/client/src/events.jsx b/client/src/events.jsx index 26d1926a..c28acca4 100644 --- a/client/src/events.jsx +++ b/client/src/events.jsx @@ -111,6 +111,10 @@ function registerEvents(store) { store.dispatch(actions.setAccount(account)); } + function setShop(v) { + store.dispatch(actions.setShop(v)); + } + function clearCombiner() { store.dispatch(actions.setInfo([])); store.dispatch(actions.setCombiner([null, null, null])); @@ -233,6 +237,7 @@ function registerEvents(store) { setInstanceList, setItemInfo, setPing, + setShop, setWs, }; } diff --git a/client/src/reducers.jsx b/client/src/reducers.jsx index ac770f8c..161044ce 100644 --- a/client/src/reducers.jsx +++ b/client/src/reducers.jsx @@ -35,6 +35,7 @@ module.exports = { reclaiming: createReducer(false, 'SET_RECLAIMING'), resolution: createReducer(null, 'SET_RESOLUTION'), skip: createReducer(false, 'SET_SKIP'), + shop: createReducer(false, 'SET_SHOP'), team: createReducer([null, null, null], 'SET_SELECTED_CONSTRUCTS'), vboxHighlight: createReducer([], 'SET_VBOX_HIGHLIGHT'), ws: createReducer(null, 'SET_WS'), diff --git a/client/src/socket.jsx b/client/src/socket.jsx index 71230f83..f79131ea 100644 --- a/client/src/socket.jsx +++ b/client/src/socket.jsx @@ -124,6 +124,10 @@ function createSocket(events) { // events.clearMtxActive(); } + function sendMtxBuy(mtx) { + send(['MtxBuy', { mtx }]); + } + // ------------- // Incoming // ------------- @@ -133,6 +137,10 @@ function createSocket(events) { sendAccountInstances(); } + function onAccountShop(shop) { + events.setShop(shop); + } + function onAccountInstances(list) { events.setAccountInstances(list); setTimeout(sendAccountInstances, 5000); @@ -198,6 +206,7 @@ function createSocket(events) { AccountState: onAccount, AccountConstructs: onAccountConstructs, AccountInstances: onAccountInstances, + AccountShop: onAccountShop, GameState: onGameState, InstanceState: onInstanceState, ItemInfo: onItemInfo, @@ -308,6 +317,7 @@ function createSocket(events) { sendVboxUnequip, sendItemInfo, sendMtxApply, + sendMtxBuy, startInstanceStateTimeout, startGameStateTimeout, connect, diff --git a/ops/migrations/20180913000513_create_accounts.js b/ops/migrations/20180913000513_create_accounts.js index f693e90c..4c0b2070 100755 --- a/ops/migrations/20180913000513_create_accounts.js +++ b/ops/migrations/20180913000513_create_accounts.js @@ -9,7 +9,7 @@ exports.up = async knex => { table.string('token', 64).notNullable(); table.timestamp('token_expiry').notNullable(); - table.bigInteger('credits') + table.bigInteger('balance') .defaultTo(0) .notNullable(); @@ -23,7 +23,7 @@ exports.up = async knex => { await knex.schema.raw(` ALTER TABLE accounts - ADD CHECK (credits > 0); + ADD CHECK (balance > 0); `); }; diff --git a/server/src/account.rs b/server/src/account.rs index dd5f988b..a8bc95f7 100644 --- a/server/src/account.rs +++ b/server/src/account.rs @@ -14,6 +14,7 @@ use instance::{Instance, instance_delete}; use mtx::{Mtx, FREE_MTX}; use pg::Db; + use failure::Error; use failure::{err_msg, format_err}; @@ -23,13 +24,13 @@ static PASSWORD_MIN_LEN: usize = 11; pub struct Account { pub id: Uuid, pub name: String, - pub credits: u32, + pub balance: u32, pub subscribed: bool, } pub fn select(tx: &mut Transaction, id: Uuid) -> Result { let query = " - SELECT id, name, credits, subscribed + SELECT id, name, balance, subscribed FROM accounts WHERE id = $1; "; @@ -40,18 +41,18 @@ pub fn select(tx: &mut Transaction, id: Uuid) -> Result { let row = result.iter().next() .ok_or(format_err!("account not found {:?}", id))?; - let db_credits: i64 = row.get(2); - let credits = u32::try_from(db_credits) - .or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_credits)))?; + let db_balance: i64 = row.get(2); + let balance = u32::try_from(db_balance) + .or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?; let subscribed: bool = row.get(3); - Ok(Account { id, name: row.get(1), credits, subscribed }) + Ok(Account { id, name: row.get(1), balance, subscribed }) } pub fn from_token(db: &Db, token: String) -> Result { let query = " - SELECT id, name, subscribed, credits + SELECT id, name, subscribed, balance FROM accounts WHERE token = $1 AND token_expiry > now(); @@ -66,17 +67,17 @@ pub fn from_token(db: &Db, token: String) -> Result { let id: Uuid = row.get(0); let name: String = row.get(1); let subscribed: bool = row.get(2); - let db_credits: i64 = row.get(3); + let db_balance: i64 = row.get(3); - let credits = u32::try_from(db_credits) - .or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_credits)))?; + let balance = u32::try_from(db_balance) + .or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?; - Ok(Account { id, name, credits, subscribed }) + Ok(Account { id, name, balance, subscribed }) } pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result { let query = " - SELECT id, password, name, credits, subscribed + SELECT id, password, name, balance, subscribed FROM accounts WHERE name = $1 "; @@ -102,17 +103,17 @@ pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result Result { @@ -133,7 +134,7 @@ pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result { let result = tx .query(query, &[&token, &id])?; - let row = result.iter().next() + result.iter().next() .ok_or(format_err!("account not updated {:?}", id))?; Ok(token) @@ -142,9 +143,9 @@ pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result { pub fn credit(tx: &mut Transaction, id: Uuid, credits: i64) -> Result { let query = " UPDATE accounts - SET credits = credits + $1 + SET balance = balance + $1 WHERE id = $2 - RETURNING credits, name; + RETURNING balance, name; "; let result = tx @@ -153,38 +154,41 @@ pub fn credit(tx: &mut Transaction, id: Uuid, credits: i64) -> Result Result { +pub fn debit(tx: &mut Transaction, id: Uuid, debit: i64) -> Result { let query = " UPDATE accounts - SET credits = credits - $1 + SET balance = balance - $1 WHERE id = $2 - RETURNING id, name, credits, subscribed + RETURNING id, name, balance, subscribed "; let result = tx - .query(query, &[&credits, &id])?; + .query(query, &[&debit, &id])?; let row = result.iter().next() .ok_or(format_err!("account not found {:?}", id))?; - let db_credits: i64 = row.get(2); - let credits = u32::try_from(db_credits) - .or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_credits)))?; + + let name: String = row.get(1); + let db_balance: i64 = row.get(2); + let balance = u32::try_from(db_balance) + .or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?; let subscribed: bool = row.get(3); - Ok(Account { id, name: row.get(1), credits, subscribed }) + info!("account debited name={:?} debited={:?} balance={:?}", name, debit, balance); + Ok(Account { id, name: row.get(1), balance, subscribed }) } pub fn set_subscribed(tx: &mut Transaction, id: Uuid, subscribed: bool) -> Result { diff --git a/server/src/construct.rs b/server/src/construct.rs index 0d2eba4a..3137184b 100644 --- a/server/src/construct.rs +++ b/server/src/construct.rs @@ -11,7 +11,7 @@ use skill::{Skill, Cast, Immunity, Disable, Event}; use effect::{Cooldown, Effect, Colour}; use spec::{Spec}; use item::{Item}; -use img::{img_molecular_write}; +use img; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Colours { @@ -883,7 +883,7 @@ pub fn construct_spawn(tx: &mut Transaction, account: Uuid, name: String) -> Res let _returned = result.iter().next().ok_or(err_msg("no row returned"))?; - img_molecular_write(construct.img)?; + img::molecular_write(construct.img)?; info!("spawned construct account={:} construct={:?}", account, construct); return Ok(construct); diff --git a/server/src/img.rs b/server/src/img.rs index 755a2b13..36b76d95 100644 --- a/server/src/img.rs +++ b/server/src/img.rs @@ -9,7 +9,7 @@ use std::io::prelude::*; use failure::Error; use failure::err_msg; -pub fn img_molecular_write(id: Uuid) -> Result { +pub fn molecular_write(id: Uuid) -> Result { let mut rng = thread_rng(); for _i in 0..100 { @@ -27,9 +27,7 @@ pub fn img_molecular_write(id: Uuid) -> Result { return Err(err_msg("too many missing molecules. wrong directory?")) } - - -fn invader_img_write(id: Uuid) -> Result { +pub fn invader_write(id: Uuid) -> Result { let mut rng = thread_rng(); let mut svg = Vec::new(); @@ -86,17 +84,17 @@ fn invader_img_write(id: Uuid) -> Result { } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - #[test] - fn invader_img_test() { - for i in 0..100 { - invader_img_write(Uuid::new_v4()).unwrap(); - } - } -} +// #[test] +// fn invader_img_test() { +// for i in 0..100 { +// invader_img_write(Uuid::new_v4()).unwrap(); +// } +// } +// } diff --git a/server/src/instance.rs b/server/src/instance.rs index e20ddcf3..2ffaa0c5 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -18,8 +18,7 @@ use mob::{bot_player, instance_mobs}; use game::{Game, Phase, game_get, game_write}; use item::{Item}; use rpc::{RpcResult}; - -use img::{img_molecular_write}; +use img; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] enum InstancePhase { @@ -706,7 +705,7 @@ pub fn instance_new(tx: &mut Transaction, account: &Account, construct_ids: Vec< // generate bot imgs only in the real world for c in bot.constructs.iter() { - img_molecular_write(c.img)?; + img::molecular_write(c.img)?; } let mut instance = Instance::new() diff --git a/server/src/mtx.rs b/server/src/mtx.rs index 9f2e5ae9..7c450ba1 100644 --- a/server/src/mtx.rs +++ b/server/src/mtx.rs @@ -2,7 +2,6 @@ use std::convert::TryFrom; use uuid::Uuid; // use rand::prelude::*; -use serde_cbor::{from_slice}; use postgres::transaction::Transaction; use failure::Error; @@ -12,17 +11,32 @@ use account; use account::Account; use construct::{Construct, construct_select, construct_write}; -use img::{img_molecular_write}; +use img; -pub const FREE_MTX: [MtxVariant; 2] = [ +pub const FREE_MTX: [MtxVariant; 1] = [ MtxVariant::Rename, - MtxVariant::Reimage, ]; -#[derive(Debug,Copy,Clone,Serialize,Deserialize)] +pub const SHOP_LISTINGS: [Listing; 2] = [ + Listing { variant: MtxVariant::ArchitectureMolecular, credits: 10 }, + Listing { variant: MtxVariant::ArchitectureInvader, credits: 10 }, +]; + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub struct Shop { + owned: Vec, + available: Vec, +} + +#[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)] +pub struct Listing { + variant: MtxVariant, + credits: u16, +} + +#[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)] pub enum MtxVariant { Rename, - Reimage, ArchitectureMolecular, ArchitectureInvader, } @@ -40,7 +54,6 @@ impl TryFrom for MtxVariant { fn try_from(v: String) -> Result { match v.as_ref() { "Rename" => Ok(MtxVariant::Rename), - "Reimage" => Ok(MtxVariant::Reimage), "ArchitectureMolecular" => Ok(MtxVariant::ArchitectureMolecular), "ArchitectureInvader" => Ok(MtxVariant::ArchitectureInvader), _ => Err(format_err!("mtx variant not found variant={:?}", v)), @@ -57,25 +70,20 @@ pub struct Mtx { pub fn apply(tx: &mut Transaction, account: &Account, variant: MtxVariant, construct_id: Uuid) -> Result, Error> { let mtx = select(tx, variant, account.id)?; - let construct = construct_select(tx, construct_id, account.id)?; - - match mtx.variant { - MtxVariant::Reimage => reimage(tx, construct)?, - _ => unimplemented!(), - }; + let mut construct = construct_select(tx, construct_id, account.id)?; account::debit(tx, account.id, 1)?; - account::account_constructs(tx, account) -} -pub fn reimage(tx: &mut Transaction, mut construct: Construct) -> Result { construct = construct.new_img(); + match mtx.variant { + MtxVariant::ArchitectureInvader => img::invader_write(construct.img)?, + MtxVariant::ArchitectureMolecular => img::molecular_write(construct.img)?, + MtxVariant::Rename => unimplemented!(), + // _ => unimplemented!(), + }; - img_molecular_write(construct.img)?; - - construct = construct_write(tx, construct)?; - - Ok(construct) + construct_write(tx, construct)?; + account::account_constructs(tx, account) } pub fn select(tx: &mut Transaction, variant: MtxVariant, account: Uuid) -> Result { @@ -111,32 +119,6 @@ impl Mtx { } } - pub fn account_list(tx: &mut Transaction, account: Uuid) -> Result, Error> { - let query = " - SELECT data, id - FROM mtx - WHERE account = $1; - "; - - let result = tx - .query(query, &[&account])?; - - let values = result.into_iter().filter_map(|row| { - let bytes: Vec = row.get(0); - // let id: Uuid = row.get(1); - - match from_slice::(&bytes) { - Ok(i) => Some(i), - Err(e) => { - warn!("{:?}", e); - None - } - } - }).collect::>(); - - return Ok(values); - } - pub fn delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { let query = " DELETE @@ -193,3 +175,54 @@ impl Mtx { // return Ok(self); // } } + +pub fn account_shop(tx: &mut Transaction, account: &Account) -> Result { + let query = " + SELECT variant + FROM mtx + WHERE account = $1; + "; + + let result = tx + .query(query, &[&account.id])?; + + let mut owned = vec![]; + + for row in result.iter() { + let v_str: String = row.get(0); + let variant = match MtxVariant::try_from(v_str) { + Ok(v) => v, + Err(e) => { + warn!("{:?}", e); + continue; + }, + }; + owned.push(variant); + }; + + let available = SHOP_LISTINGS.iter() + .filter(|l| !owned.contains(&l.variant)) + .map(|l| *l) + .collect::>(); + + debug!("account shop acount={:?} owned={:?} available={:?}", + account.name, owned, available); + + return Ok(Shop { owned, available }); +} + +pub fn buy(tx: &mut Transaction, account: &Account, mtx: MtxVariant) -> Result { + let listing = SHOP_LISTINGS + .iter() + .find(|l| l.variant == mtx) + .ok_or(format_err!("mtx variant not found variant={:?}", mtx))?; + + let account = account::debit(tx, account.id, listing.credits as i64)?; + + Mtx::new(mtx, account.id) + .insert(tx)?; + + info!("account purchased mtx account={:?} mtx={:?} cost={:?} balance={:?}", + account.name, mtx, listing.credits, account.balance); + account_shop(tx, &account) +} \ No newline at end of file diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 12d1fc83..9776e444 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -24,6 +24,7 @@ pub enum RpcResult { AccountState(Account), AccountConstructs(Vec), AccountInstances(Vec), + AccountShop(mtx::Shop), GameState(Game), ItemInfo(ItemInfoCtr), @@ -43,12 +44,14 @@ enum RpcRequest { MtxConstructApply { mtx: mtx::MtxVariant, construct_id: Uuid }, MtxAccountApply { mtx: mtx::MtxVariant }, + MtxBuy { mtx: mtx::MtxVariant }, GameState { id: Uuid }, GameReady { id: Uuid }, GameSkill { game_id: Uuid, construct_id: Uuid, target_construct_id: Option, skill: Skill }, AccountState {}, + AccountShop {}, AccountConstructs {}, AccountInstances {}, @@ -100,6 +103,10 @@ pub fn receive(data: Vec, db: &Db, _client: &mut WebSocket, begin RpcRequest::AccountInstances {} => Ok(RpcResult::AccountInstances(account_instances(&mut tx, &account)?)), + // RpcRequest::AccountShop {} => + // Ok(RpcResult::AccountShop(mtx::account_shop(&mut tx, &account)?)), + + // RpcRequest::ConstructDelete" => handle_construct_delete(data, &mut tx, account), @@ -145,6 +152,9 @@ pub fn receive(data: Vec, db: &Db, _client: &mut WebSocket, begin RpcRequest::MtxConstructApply { mtx, construct_id } => Ok(RpcResult::AccountConstructs(mtx::apply(&mut tx, account, mtx, construct_id)?)), + RpcRequest::MtxBuy { mtx } => + Ok(RpcResult::AccountShop(mtx::buy(&mut tx, account, mtx)?)), + _ => Err(format_err!("unknown request request={:?}", request)), }; @@ -155,7 +165,8 @@ pub fn receive(data: Vec, db: &Db, _client: &mut WebSocket, begin return response; }, Err(e) => { - Err(format_err!("invalid message data={:?}", data)) + warn!("{:?}", e); + Err(err_msg("invalid message")) }, } } diff --git a/server/src/ws.rs b/server/src/ws.rs index 444cd744..7a759246 100644 --- a/server/src/ws.rs +++ b/server/src/ws.rs @@ -19,6 +19,7 @@ use serde_cbor::{to_vec}; use net::TOKEN_HEADER; use rpc; +use mtx; use pg::PgPool; use account; @@ -66,10 +67,22 @@ pub fn start(pool: PgPool) { Some(t) => { let db = ws_pool.get() .expect("unable to get db connection"); + + match account::from_token(&db, t) { Ok(a) => { let state = to_vec(&rpc::RpcResult::AccountState(a.clone())).unwrap(); websocket.write_message(Binary(state)).unwrap(); + + let mut tx = db.transaction().unwrap(); + let shop = mtx::account_shop(&mut tx, &a).unwrap(); + let shop = to_vec(&rpc::RpcResult::AccountShop(shop)).unwrap(); + + websocket.write_message(Binary(shop)).unwrap(); + + // tx doesn't change anything + tx.commit().unwrap(); + Some(a) }, Err(e) => {