diff --git a/client/src/socket.jsx b/client/src/socket.jsx index c99fe68d..b79d2782 100644 --- a/client/src/socket.jsx +++ b/client/src/socket.jsx @@ -59,6 +59,7 @@ function createSocket(store) { store.dispatch(actions.setAccount(login)); // send({ method: 'cryp_spawn', params: { name: 'muji' } }); send({ method: 'account_cryps', params: {} }); + send({ method: 'item_list', params: {}}); console.log(account); } @@ -83,6 +84,10 @@ function createSocket(store) { console.log('got a new battle', battle); } + function itemList(response) { + const [structName, items] = response; + console.log('got my items', items); + } // ------------- // Outgoing @@ -113,6 +118,7 @@ function createSocket(store) { account_login: accountLogin, account_create: accountLogin, account_cryps: accountCryps, + item_list: itemList, }; // decodes the cbor and diff --git a/ops/migrations/20181014141623_inventory.js b/ops/migrations/20181014141623_inventory.js new file mode 100644 index 00000000..0e613ca8 --- /dev/null +++ b/ops/migrations/20181014141623_inventory.js @@ -0,0 +1,14 @@ +exports.up = async knex => { + return knex.schema.createTable('items', table => { + table.uuid('id').primary(); + table.uuid('account').notNullable() + table.foreign('account') + .references('id') + .inTable('accounts') + .onDelete('CASCADE'); + table.binary('data').notNullable(); + table.index('id'); + }); +}; + +exports.down = async () => {}; \ No newline at end of file diff --git a/ops/package.json b/ops/package.json index b9973c69..3ee5cdf7 100755 --- a/ops/package.json +++ b/ops/package.json @@ -5,10 +5,11 @@ "main": "index.js", "scripts": { "migrate": "knex migrate:latest", + "migrate:make": "knex migrate:make --", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", - "license": "ISC", + "license": "UNLICENSED", "dependencies": { "knex": "^0.15.2", "pg": "^7.4.3" diff --git a/server/WORKLOG.md b/server/WORKLOG.md index b75a3e70..d1bdf0e2 100755 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -3,7 +3,6 @@ * auto login * ws reconnect ✔️ * Levelling - * KO cooldowns * Global rolls * Logins ✔️ * Cryp Ownership ✔ @@ -14,7 +13,11 @@ * Resolve * Stats * Scrabble grid + * skills + * offensive -> choose target + * defensive * Items + * rez * Grid reroll * Colour scheme * Missions diff --git a/server/src/account.rs b/server/src/account.rs index 265e9087..43d683aa 100755 --- a/server/src/account.rs +++ b/server/src/account.rs @@ -3,13 +3,13 @@ use bcrypt::{hash, verify}; use rand::{thread_rng, Rng}; use rand::distributions::Alphanumeric; use std::iter; -use serde_cbor::{from_slice, to_vec}; - +use serde_cbor::{from_slice}; use std::str; use net::Db; -use rpc::{AccountCreateParams, AccountLoginParams, RpcResult}; +use rpc::{AccountCreateParams, AccountLoginParams}; +use item::{Item, ItemAction, item_create}; use cryp::Cryp; @@ -35,7 +35,7 @@ struct AccountEntry { // MAYBE // hash tokens with a secret -pub fn from_token(token: String, db: &Db) -> Result { +pub fn account_from_token(token: String, db: &Db) -> Result { let query = " SELECT id, name, token FROM accounts @@ -59,7 +59,7 @@ pub fn from_token(token: String, db: &Db) -> Result { return Ok(entry); } -pub fn create(params: AccountCreateParams, db: &Db) -> Result { +pub fn account_create(params: AccountCreateParams, db: &Db) -> Result { let id = Uuid::new_v4(); if params.password.len() < PASSWORD_MIN_LEN { @@ -86,7 +86,7 @@ pub fn create(params: AccountCreateParams, db: &Db) -> Result { RETURNING id, name, token; "; - let tx = db.transaction()?; + let mut tx = db.transaction()?; let result = tx .query(query, &[&account.id, &account.name, &account.password, &account.token])?; @@ -101,12 +101,16 @@ pub fn create(params: AccountCreateParams, db: &Db) -> Result { println!("{:?}", entry); + // give them a revive + let revive = Item::new(ItemAction::Revive, &entry); + item_create(revive, &mut tx, &entry)?; + tx.commit()?; return Ok(entry); } -pub fn login(params: AccountLoginParams, db: &Db) -> Result { +pub fn account_login(params: AccountLoginParams, db: &Db) -> Result { let query = " SELECT id, name, token, password FROM accounts @@ -149,7 +153,7 @@ pub fn login(params: AccountLoginParams, db: &Db) -> Result { return Ok(account); } -pub fn fetch_cryps(db: &Db, account: &Account) -> Result, Error> { +pub fn account_cryps(db: &Db, account: &Account) -> Result, Error> { let query = " SELECT data FROM cryps diff --git a/server/src/combat.rs b/server/src/combat.rs index c4ff9404..fe4d51ac 100755 --- a/server/src/combat.rs +++ b/server/src/combat.rs @@ -1,5 +1,3 @@ -use std::{thread, time}; - use rand::prelude::*; use serde_cbor::{from_slice}; @@ -11,15 +9,15 @@ use net::Db; use account::Account; use rpc::{CombatPveParams}; -use cryp::{Cryp, write_cryp}; +use cryp::{Cryp, cryp_write}; use battle::Battle; use skill::Skill; -struct Encounter { - mob: Cryp, - success: bool, - player: Cryp, -} +// struct Encounter { +// mob: Cryp, +// success: bool, +// player: Cryp, +// } pub fn battle_resolve(a: &Cryp, b: &Cryp) -> Battle { @@ -32,51 +30,51 @@ pub fn battle_resolve(a: &Cryp, b: &Cryp) -> Battle { } } -fn pve_completion(plr: Cryp) -> Encounter { - let mut rng = thread_rng(); - let mob_lvl: u8 = rng.gen_range(1, plr.lvl); +// fn pve_completion(plr: Cryp) -> Encounter { +// let mut rng = thread_rng(); +// let mob_lvl: u8 = rng.gen_range(1, plr.lvl); - let mob = Cryp::new() - .named(&"bamboo basher".to_string()) - .level(mob_lvl) - .create(); +// let mob = Cryp::new() +// .named(&"bamboo basher".to_string()) +// .level(mob_lvl) +// .create(); - let outcome = battle_resolve(&plr, &mob); +// let outcome = battle_resolve(&plr, &mob); - let success = match outcome.winner() { - Some(c) => c.id == plr.id, - None => false, - }; +// let success = match outcome.winner() { +// Some(c) => c.id == plr.id, +// None => false, +// }; - return Encounter { - mob: mob, - success, - player: plr, - }; -} +// return Encounter { +// mob: mob, +// success, +// player: plr, +// }; +// } -pub fn keep_levelling(mut c: Cryp) -> Cryp { - loop { - let enc = pve_completion(c); - c = enc.player; +// pub fn keep_levelling(mut c: Cryp) -> Cryp { +// loop { +// let enc = pve_completion(c); +// c = enc.player; - if !enc.success { - println!("{:?} has been KO'd", c.name); - break c; - } +// if !enc.success { +// println!("{:?} has been KO'd", c.name); +// break c; +// } - println!("{:?} rekt {:?}", c.name, enc.mob.name); - c = c.add_xp(); - println!("{:?} now has {:?} xp and is lvl {:?}", c.name, c.xp, c.lvl); +// println!("{:?} rekt {:?}", c.name, enc.mob.name); +// c = c.add_xp(); +// println!("{:?} now has {:?} xp and is lvl {:?}", c.name, c.xp, c.lvl); - // LEVEL CAP - if c.lvl == 12 { - break c; - } +// // LEVEL CAP +// if c.lvl == 12 { +// break c; +// } - continue; - } -} +// continue; +// } +// } fn generate_mob(plr: &Cryp) -> Cryp { let mut rng = thread_rng(); @@ -102,7 +100,9 @@ pub fn pve(params: CombatPveParams, db: &Db, account: &Account) -> Result Result = returned.get("data"); - let mut plr: Cryp = from_slice::(&cryp_bytes)?; + let plr: Cryp = from_slice::(&cryp_bytes)?; // TEMP if plr.hp.value == 0 { @@ -138,7 +138,9 @@ pub fn pve(params: CombatPveParams, db: &Db, account: &Account) -> Result Result { +pub fn cryp_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result { + let query = " + SELECT data + FROM cryps + WHERE id = $1 + AND account = $2; + "; + + let result = tx + .query(query, &[&id, &account_id])?; + + let result = result.iter().next().ok_or(err_msg("cryp not found"))?; + let cryp_bytes: Vec = result.get(0); + let cryp = from_slice::(&cryp_bytes)?; + + return Ok(cryp); +} + +pub fn cryp_spawn(params: CrypSpawnParams, db: &Db, account: &Account) -> Result { let cryp = Cryp::new() .named(¶ms.name) .level(1) @@ -215,7 +234,7 @@ pub fn spawn(params: CrypSpawnParams, db: &Db, account: &Account) -> Result Result Result { +pub fn cryp_write(cryp: Cryp, tx: &mut Transaction) -> Result { let cryp_bytes = to_vec(&cryp)?; let query = " UPDATE cryps SET data = $1 WHERE id = $2 - AND account = $3 RETURNING id, account, data; "; - let tx = db.transaction()?; - let result = tx - .query(query, &[&cryp_bytes, &cryp.id, &account.id])?; + .query(query, &[&cryp_bytes, &cryp.id])?; let _returned = result.iter().next().expect("no row returned"); - println!("{:?} wrote cryp {:}", account.id, cryp.id); - - tx.commit()?; + println!("{:?} wrote cryp", cryp.id); return Ok(cryp); } diff --git a/server/src/item.rs b/server/src/item.rs new file mode 100644 index 00000000..b8729c75 --- /dev/null +++ b/server/src/item.rs @@ -0,0 +1,161 @@ +use account::Account; +use uuid::Uuid; +use serde_cbor::{from_slice, to_vec}; + +use net::Db; +use postgres::transaction::Transaction; +use rpc::{ItemUseParams}; +use cryp::{cryp_get, cryp_write}; + +use failure::Error; +use failure::err_msg; + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum ItemAction { + Revive, +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub struct Item { + // mods: Vec, + id: Uuid, + account: Uuid, + action: ItemAction, +} + +impl Item { + pub fn new(action: ItemAction, account: &Account) -> Item { + let id = Uuid::new_v4(); + return Item { + id, + account: account.id, + action, + }; + } + + fn apply(&mut self, tx: &mut Transaction, target: Uuid) -> Result<(), Error> { + match self.action { + ItemAction::Revive => revive(self, tx, target), + _ => panic!("missing item action"), + } + } +} + +fn revive(item: &mut Item, tx: &mut Transaction, target: Uuid) -> Result<(), Error> { + let mut cryp = cryp_get(tx, target, item.account)?; + cryp.rez(); + cryp_write(cryp, tx)?; + return Ok(()); +} + +pub fn item_create(item: Item, tx: &mut Transaction, account: &Account) -> Result { + let item_bytes = to_vec(&item)?; + + let query = " + INSERT INTO items (id, account, data) + VALUES ($1, $2, $3) + RETURNING id, account, data; + "; + + let result = tx + .query(query, &[&item.id, &account.id, &item_bytes])?; + + let _returned = result.iter().next().expect("no row returned"); + + println!("{:?} wrote item {:}", account.id, item.id); + + return Ok(item); +} + +pub fn item_use(params: ItemUseParams, db: &Db, account: &Account) -> Result<(), Error> { + let query = " + SELECT data + FOR UPDATE + FROM items + WHERE id = $1 + AND account = $2; + "; + + let mut tx = db.transaction()?; + + let result = tx + .query(query, &[¶ms.item, &account.id])?; + + let returned = result.iter().next().expect("no row returned"); + + let item_bytes: Vec = returned.get(0); + let mut item = from_slice::(&item_bytes)?; + + item.apply(&mut tx, params.target)?; + + tx.commit()?; + + return Ok(()); +} + +pub fn items_list(db: &Db, account: &Account) -> Result, Error> { + let query = " + SELECT data + FROM items + WHERE account = $1; + "; + + let result = db + .query(query, &[&account.id])?; + + let items: Result, _> = result.iter().map(|row| { + let item_bytes: Vec = row.get(0); + from_slice::(&item_bytes) + }).collect(); + + // catch any errors + if items.is_err() { + return Err(err_msg("could not deserialize an item")); + } + + // now unwrap is safe + return Ok(items.unwrap()); +} + +// # max damage potion +// name + +// "MapMonstersCurseEffectOnSelfFinal3": { +// "adds_tags": [], +// "domain": "area", +// "generation_type": "prefix", +// "generation_weights": [], +// "grants_buff": {}, +// "grants_effect": {}, +// "group": "MapHexproof", +// "is_essence_only": false, +// "name": "Hexwarded", +// "required_level": 1, +// "spawn_weights": [ +// { +// "tag": "top_tier_map", +// "weight": 0 +// }, +// { +// "tag": "default", +// "weight": 0 +// } +// ], +// "stats": [ +// { +// "id": "map_item_drop_quantity_+%", +// "max": 15, +// "min": 15 +// }, +// { +// "id": "map_item_drop_rarity_+%", +// "max": 8, +// "min": 8 +// }, +// { +// "id": "map_monsters_curse_effect_on_self_+%_final", +// "max": -60, +// "min": -60 +// } +// ] +// }, diff --git a/server/src/main.rs b/server/src/main.rs index da848e3b..2c709cae 100755 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -25,6 +25,7 @@ mod combat; mod skill; mod rpc; mod account; +mod item; use dotenv::dotenv; use net::{start}; diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 3a53cb30..eb60f8ab 100755 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -9,10 +9,11 @@ use failure::Error; use failure::err_msg; use net::Db; -use cryp::{Cryp, spawn}; +use cryp::{Cryp, cryp_spawn}; use battle::{Battle}; use combat::{pve}; -use account::{Account, create, login, from_token, fetch_cryps}; +use account::{Account, account_create, account_login, account_from_token, account_cryps}; +use item::{Item, items_list, item_use}; pub struct Rpc; @@ -26,7 +27,7 @@ impl Rpc { Ok(v) => { let account: Option = match v.token { - Some(t) => Some(from_token(t, &db)?), + Some(t) => Some(account_from_token(t, &db)?), None => None, }; @@ -40,6 +41,8 @@ impl Rpc { "account_create" => Rpc::account_create(data, db), "account_login" => Rpc::account_login(data, db), "account_cryps" => Rpc::account_cryps(db, account), + "item_list" => Rpc::item_list(db, account), + "item_use" => Rpc::item_use(data, db, account), _ => Err(err_msg("unknown method")), } @@ -57,8 +60,8 @@ impl Rpc { } fn combat_pve(data: Vec, db: &Db, account: Option, client: &mut WebSocket) -> Result { - let u = match account { - Some(u) => u, + let a = match account { + Some(a) => a, None => return Err(err_msg("auth required")), }; @@ -66,12 +69,12 @@ impl Rpc { let battle_response = RpcResponse { method: "battle_state".to_string(), - params: RpcResult::Pve(pve(msg.params, db, &u)?) + params: RpcResult::Pve(pve(msg.params, db, &a)?) }; Rpc::send_msg(client, RpcResponse { method: "account_cryps".to_string(), - params: RpcResult::CrypList(fetch_cryps(db, &u)?) + params: RpcResult::CrypList(account_cryps(db, &a)?) })?; return Ok(battle_response); @@ -81,9 +84,9 @@ impl Rpc { match from_slice::(&data) { Ok(v) => { match account { - Some(u) => Ok(RpcResponse { + Some(a) => Ok(RpcResponse { method: v.method, - params: RpcResult::SpawnCryp(spawn(v.params, db, &u)?) + params: RpcResult::SpawnCryp(cryp_spawn(v.params, db, &a)?) }), None => Err(err_msg("auth required")), } @@ -96,7 +99,7 @@ impl Rpc { match from_slice::(&data) { Ok(v) => Ok(RpcResponse { method: v.method, - params: RpcResult::Account(create(v.params, db)?) + params: RpcResult::Account(account_create(v.params, db)?) }), Err(_e) => Err(err_msg("invalid params")), } @@ -106,7 +109,7 @@ impl Rpc { match from_slice::(&data) { Ok(v) => Ok(RpcResponse { method: v.method, - params: RpcResult::Account(login(v.params, db)?) + params: RpcResult::Account(account_login(v.params, db)?) }), Err(_e) => Err(err_msg("invalid params")), } @@ -114,13 +117,40 @@ impl Rpc { fn account_cryps(db: &Db, account: Option) -> Result { match account { - Some(u) => Ok(RpcResponse { + Some(a) => Ok(RpcResponse { method: "account_cryps".to_string(), - params: RpcResult::CrypList(fetch_cryps(db, &u)?) + params: RpcResult::CrypList(account_cryps(db, &a)?) }), None => Err(err_msg("auth required")), } } + + fn item_list(db: &Db, account: Option) -> Result { + match account { + Some(a) => Ok(RpcResponse { + method: "item_list".to_string(), + params: RpcResult::ItemList(items_list(db, &a)?) + }), + None => Err(err_msg("auth required")), + } + } + + fn item_use(data: Vec, db: &Db, account: Option) -> Result { + match from_slice::(&data) { + Ok(v) => { + match account { + Some(a) => Ok(RpcResponse { + method: v.method, + params: RpcResult::ItemUse(item_use(v.params, db, &a)?) + }), + None => Err(err_msg("auth required")), + } + } + Err(_e) => Err(err_msg("invalid params")), + } + } + + } #[derive(Debug,Clone,Serialize,Deserialize)] @@ -135,6 +165,8 @@ pub enum RpcResult { Account(Account), CrypList(Vec), Pve(Battle), + ItemList(Vec), + ItemUse(()), } #[derive(Debug,Clone,Serialize,Deserialize)] @@ -195,6 +227,24 @@ struct AccountCrypsMsg { params: (), } +#[derive(Debug,Clone,Serialize,Deserialize)] +struct ItemListMsg { + method: String, + params: (), +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +struct ItemUseMsg { + method: String, + params: ItemUseParams, +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct ItemUseParams { + pub item: Uuid, + pub target: Uuid, +} + // #[cfg(test)] // mod tests { // use super::*;