From 613444d0666d67a9af5bd3cabf90350afb407807 Mon Sep 17 00:00:00 2001 From: ntr Date: Thu, 21 Feb 2019 12:05:27 +1100 Subject: [PATCH] wip --- ops/migrations/20181020104420_games.js | 49 ++++++--- ops/migrations/20190216123550_vbox.js | 36 ------- server/WORKLOG.md | 76 ++++++-------- server/src/game.rs | 22 ---- server/src/instance.rs | 137 +++++++++++++++++++++++++ server/src/main.rs | 3 +- server/src/player.rs | 82 +++++++++++++++ server/src/rpc.rs | 35 +++++-- 8 files changed, 311 insertions(+), 129 deletions(-) delete mode 100644 ops/migrations/20190216123550_vbox.js create mode 100644 server/src/instance.rs create mode 100644 server/src/player.rs diff --git a/ops/migrations/20181020104420_games.js b/ops/migrations/20181020104420_games.js index 0fc18d17..f836ddb3 100644 --- a/ops/migrations/20181020104420_games.js +++ b/ops/migrations/20181020104420_games.js @@ -1,35 +1,58 @@ +const NULL_UUID = '00000000-0000-0000-0000-000000000000'; + exports.up = async knex => { await knex.schema.createTable('games', table => { table.uuid('id').primary(); table.index('id'); table.timestamps(); + table.binary('data').notNullable(); + }); + + await knex.schema.createTable('instances', table => { + table.uuid('id').primary(); + table.index('id'); + table.timestamps(); table.binary('data').notNullable(); - table.boolean('joinable') - .defaultTo(true) - .notNullable(); + table.boolean('open') + .defaultTo(true) + .notNullable(); + + await knex.schema.raw( + // eslint-disable-next-line max-len + 'CREATE UNIQUE INDEX instances_joinable ON instances WHERE open = true;' + ); }); await knex.schema.createTable('players', table => { table.uuid('id').primary(); table.index('id'); - // the game itself - table.uuid('game').notNullable() - table.foreign('game') - .references('id') - .inTable('games') - .onDelete('CASCADE'); - table.index('game'); + // the instance + table.uuid('instance').notNullable() + table.foreign('instance') + .references('id') + .inTable('instances') + .onDelete('CASCADE'); + table.index('instance'); // account in a game table.uuid('account').notNullable() table.foreign('account') - .references('id') - .inTable('accounts') - .onDelete('CASCADE'); + .references('id') + .inTable('accounts') + .onDelete('CASCADE'); table.index('account'); + table.unique(['account', 'instance']); + + }); + + // not really sure if this is a good idea + await knex('instances').insert({ + id: NULL_UUID, + data: 'INVALID', + joinable: false, }); }; diff --git a/ops/migrations/20190216123550_vbox.js b/ops/migrations/20190216123550_vbox.js deleted file mode 100644 index 4ed2fcae..00000000 --- a/ops/migrations/20190216123550_vbox.js +++ /dev/null @@ -1,36 +0,0 @@ -const NULL_UUID = '00000000-0000-0000-0000-000000000000'; - -exports.up = async knex => { - await knex.schema.createTable('vbox', table => { - table.timestamps(); - table.uuid('id').primary(); - table.uuid('account').notNullable() - table.uuid('game').notNullable() - table.binary('data').notNullable(); - - table.foreign('game') - .references('id') - .inTable('games') - .onDelete('CASCADE'); - - table.foreign('account') - .references('id') - .inTable('accounts') - .onDelete('CASCADE'); - - table.index('id'); - table.index('account'); - table.unique(['account', 'game']); - }); - - // not really sure if this is a good idea - await knex('games').insert({ - id: NULL_UUID, - data: 'INVALID', - joinable: false, - }); - - return true; -}; - -exports.down = async () => {}; \ No newline at end of file diff --git a/server/WORKLOG.md b/server/WORKLOG.md index 7922daf0..7510c68a 100644 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -1,30 +1,3 @@ -# Principles -* Experience something -* Express something -* Prove something - -* 1: Fighting against human nature is a losing game -* 2: Aesthetics matter -* 3: Resonance is important -* 4: Make use of piggybacking -* 5: Don't confuse "interesting" with "fun" -* 6: Understand what emotion your game is trying to evoke -* 7: Allow the players the skill to make the game personal -* 8: The details are where the players fall in love with your game -* 9: Allow your players to have a sense of ownership -* 10: Leave room for the player to explore -* 11: If everyone likes your game, but no one loves it, it will fail -* 12: Don't design to prove you can do something -* 13: Make the fun part also the correct strategy to win -* 14: Don't be afraid to be blunt -* 15: Design the component for its intended audience -* 16: Be more afraid of boring your players than challenging them -* 17: You don't have to change much to change everything -* 18: Restrictions breed creativity -* 19: Your audience is good at recognizing problems and bad at solving them -* 20: All the lessons connect - - # Key Mechanics * 10d chaos maths, not rock paper scissors * phys is faster and chaotic @@ -33,28 +6,13 @@ * red_shield is restored, not gained * players can feel aggressive -# ask sam -* main screen layout - * ping icon - * -* combat screen layout - * health bars - * statuses -* icons - * skill type / damage type - * skills themselves - * passive nodes / notables - -* tell him about discord # WORK WORK - -broken skills -strangle -taunt - - ## NOW +cost system for items +design / implement specs +combo specs +round system for games ## SOON * clean up categories @@ -119,3 +77,29 @@ gem td style attr combinations slimey ghostly + +# Principles +* Experience something +* Express something +* Prove something + +* 1: Fighting against human nature is a losing game +* 2: Aesthetics matter +* 3: Resonance is important +* 4: Make use of piggybacking +* 5: Don't confuse "interesting" with "fun" +* 6: Understand what emotion your game is trying to evoke +* 7: Allow the players the skill to make the game personal +* 8: The details are where the players fall in love with your game +* 9: Allow your players to have a sense of ownership +* 10: Leave room for the player to explore +* 11: If everyone likes your game, but no one loves it, it will fail +* 12: Don't design to prove you can do something +* 13: Make the fun part also the correct strategy to win +* 14: Don't be afraid to be blunt +* 15: Design the component for its intended audience +* 16: Be more afraid of boring your players than challenging them +* 17: You don't have to change much to change everything +* 18: Restrictions breed creativity +* 19: Your audience is good at recognizing problems and bad at solving them +* 20: All the lessons connect diff --git a/server/src/game.rs b/server/src/game.rs index cc060432..8f0bd01f 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -648,26 +648,6 @@ pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result { return Ok(game); } -pub fn players_write(account: &Account, game_id: Uuid, tx: &mut Transaction) -> Result<(), Error> { - // pve - let id = Uuid::new_v4(); - - let query = " - INSERT INTO players (id, game, account) - VALUES ($1, $2, $3) - RETURNING id, account; - "; - - let result = tx - .query(query, &[&id, &game_id, &account.id])?; - - let _returned = result.iter().next().expect("no row written"); - - println!("wrote player {:} joined game: {:}", account.name, game_id); - - return Ok(()); -} - pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { let game_bytes = to_vec(&game)?; @@ -769,7 +749,6 @@ pub fn game_pvp(params: GamePvpParams, tx: &mut Transaction, account: &Account) // persist game_write(&game, tx)?; - players_write(account, game_id, tx)?; Ok(game) } @@ -803,7 +782,6 @@ pub fn game_join(params: GameJoinParams, tx: &mut Transaction, account: &Account } game_update(&game, tx)?; - players_write(account, game_id, tx)?; Ok(game) } diff --git a/server/src/instance.rs b/server/src/instance.rs new file mode 100644 index 00000000..298e4be6 --- /dev/null +++ b/server/src/instance.rs @@ -0,0 +1,137 @@ +use uuid::Uuid; + +use serde_cbor::{from_slice, to_vec}; + +use postgres::transaction::Transaction; + +use failure::Error; +use failure::err_msg; + +use account::Account; +use player::{Player, player_write}; + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +enum InstancePhase { + Open, + Vbox, + Combat, + Finished, +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Instance { + id: Uuid, + players: Vec, + phase: InstancePhase, + open: bool, +} + +impl Instance { + fn new() -> Instance { + Instance { + id: Uuid::new_v4(), + players: vec![], + phase: InstancePhase::Open, + open: true, + } + } + + fn add_player(&mut self, player: Player) -> &mut Instance { + self.players.push(player.id); + self + } +} + +pub fn instance_create(instance: Instance, tx: &mut Transaction) -> Result { + let instance_bytes = to_vec(&instance)?; + + let query = " + INSERT INTO instances (id, data) + VALUES ($1, $2) + RETURNING id; + "; + + let result = tx + .query(query, &[&instance.id, &instance_bytes])?; + + result.iter().next().ok_or(format_err!("no instances written"))?; + + return Ok(instance); +} + +pub fn instance_write(instance: Instance, tx: &mut Transaction) -> Result { + let instance_bytes = to_vec(&instance)?; + + let query = " + UPDATE instance + SET data = $1 + WHERE id = $2 + RETURNING id, data; + "; + + let result = tx + .query(query, &[&instance_bytes, &instance.id])?; + + result.iter().next().ok_or(err_msg("no instance row returned"))?; + + // println!("{:?} wrote instance", instance.id); + + return Ok(instance); +} + +pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result { + let query = " + SELECT * + FROM instance + WHERE id = $1; + "; + + let result = tx + .query(query, &[&instance_id])?; + + let returned = match result.iter().next() { + Some(row) => row, + None => return Err(err_msg("instance not found")), + }; + + let instance_bytes: Vec = returned.get("data"); + let instance = from_slice::(&instance_bytes)?; + + return Ok(instance); +} + +pub fn instance_get_open(tx: &mut Transaction) -> Result { + let query = " + SELECT * + FROM instance + AND open = true; + "; + + let result = tx + .query(query, &[])?; + + let returned = match result.iter().next() { + Some(row) => row, + None => return Err(err_msg("instance not found")), + }; + + let instance_bytes: Vec = returned.get("data"); + let instance = from_slice::(&instance_bytes)?; + + return Ok(instance); +} + + +pub fn join_instance(tx: &mut Transaction, account: &Account) -> Result { + let mut instance = match instance_get_open(tx) { + Ok(i) => i, + Err(_) => instance_create(Instance::new())?, + }; + + // get add them + let player = Player::new(account, instance.id); + instance.add_player(account); + + instance_write(instance, tx)?; + return player_write(player, tx); +} diff --git a/server/src/main.rs b/server/src/main.rs index 570cfd98..1391afdb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -26,7 +26,8 @@ mod spec; // mod passives; mod rpc; mod account; -// mod item; +mod instance; +mod player; mod zone; mod mob; diff --git a/server/src/player.rs b/server/src/player.rs new file mode 100644 index 00000000..1fedd3e1 --- /dev/null +++ b/server/src/player.rs @@ -0,0 +1,82 @@ +use uuid::Uuid; + +use serde_cbor::{from_slice, to_vec}; + +use postgres::transaction::Transaction; + +use failure::Error; +use failure::err_msg; + +use account::Account; +use rpc::{VboxStateParams, VboxAcceptParams, VboxDiscardParams, VboxCombineParams, VboxApplyParams, VboxDropParams}; +use cryp::{Cryp, cryp_get, cryp_write}; +use game::{Team, GameMode}; +use vbox::{Vbox}; + +pub struct Score { + wins: u8, + losses: u8, +} + +pub struct Player { + id: Uuid, + // name: String, + vbox: Vbox, + score: Score, + cryps: Vec, +} + +impl Player { + pub fn new(account: Uuid, instance: Uuid, cryps: Vec) -> Player { + Player { + id: account, + vbox: Vbox::new(account, instance), + score: Score { wins: 0, losses: 0 }, + cryps, + } + } +} + +pub fn player_get(tx: &mut Transaction, instance_id: Uuid) -> Result { + let query = " + SELECT * + FROM players + WHERE account = $1 + AND instance = $2; + "; + + let result = tx + .query(query, &[&instance_id])?; + + let returned = match result.iter().next() { + Some(row) => row, + None => return Err(err_msg("instance not found")), + }; + + // tells from_slice to cast into a cryp + let instance_bytes: Vec = returned.get("data"); + let instance = from_slice::(&instance_bytes)?; + + return Ok(instance); +} + +pub fn player_create(player: Player, account: &Account, instance_id: Uuid, tx: &mut Transaction) -> Result<(), Error> { + let player_bytes = to_vec(&player)?; + + let id = Uuid::new_v4(); + + let query = " + INSERT INTO players (id, instance, account, data) + VALUES ($1, $2, $3, $4) + RETURNING id, account; + "; + + let result = tx + .query(query, &[&id, &instance_id, &account.id, player_bytes])?; + + let _returned = result.iter().next().expect("no row written"); + + println!("wrote player {:} joined game: {:}", account.name, instance_id); + + return Ok(()); +} \ No newline at end of file diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 22e4bc61..3bf8ea2f 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -62,14 +62,14 @@ impl Rpc { // auth methods "cryp_spawn" => Rpc::cryp_spawn(data, &mut tx, account.unwrap(), client), - "cryp_learn" => Rpc::cryp_learn(data, &mut tx, account.unwrap(), client), - "cryp_forget" => Rpc::cryp_forget(data, &mut tx, account.unwrap(), client), - "cryp_unspec" => Rpc::cryp_unspec(data, &mut tx, account.unwrap(), client), + // "cryp_learn" => Rpc::cryp_learn(data, &mut tx, account.unwrap(), client), + // "cryp_forget" => Rpc::cryp_forget(data, &mut tx, account.unwrap(), client), + // "cryp_unspec" => Rpc::cryp_unspec(data, &mut tx, account.unwrap(), client), "game_state" => Rpc::game_state(data, &mut tx, account.unwrap(), client), "game_pve" => Rpc::game_pve(data, &mut tx, account.unwrap(), client), "game_pvp" => Rpc::game_pvp(data, &mut tx, account.unwrap(), client), - "game_join" => Rpc::game_join(data, &mut tx, account.unwrap(), client), - "game_joinable_list" => Rpc::game_joinable_list(data, &mut tx, account.unwrap(), client), + // "game_join" => Rpc::game_join(data, &mut tx, account.unwrap(), client), + // "game_joinable_list" => Rpc::game_joinable_list(data, &mut tx, account.unwrap(), client), "game_skill" => Rpc::game_skill(data, &mut tx, account.unwrap(), client), // "game_target" => Rpc::game_target(data, &mut tx, account.unwrap(), client), "zone_create" => Rpc::zone_create(data, &mut tx, account.unwrap(), client), @@ -78,12 +78,13 @@ impl Rpc { "account_cryps" => Rpc::account_cryps(data, &mut tx, account.unwrap(), client), "account_zone" => Rpc::account_zone(data, &mut tx, account.unwrap(), client), - "vbox_state" => Rpc::vbox_state(data, &mut tx, account.unwrap(), client), - "vbox_accept" => Rpc::vbox_accept(data, &mut tx, account.unwrap(), client), - "vbox_apply" => Rpc::vbox_apply(data, &mut tx, account.unwrap(), client), - "vbox_drop" => Rpc::vbox_drop(data, &mut tx, account.unwrap(), client), - "vbox_combine" => Rpc::vbox_combine(data, &mut tx, account.unwrap(), client), - "vbox_discard" => Rpc::vbox_discard(data, &mut tx, account.unwrap(), client), + "instance_join" => Rpc::instance_join(data, &mut tx, account.unwrap(), client), + "player_state" => Rpc::vbox_state(data, &mut tx, account.unwrap(), client), + "player_vbox_accept" => Rpc::player_vbox_accept(data, &mut tx, account.unwrap(), client), + "player_vbox_apply" => Rpc::player_vbox_apply(data, &mut tx, account.unwrap(), client), + "player_vbox_drop" => Rpc::player_vbox_drop(data, &mut tx, account.unwrap(), client), + "player_vbox_combine" => Rpc::player_vbox_combine(data, &mut tx, account.unwrap(), client), + "player_vbox_discard" => Rpc::player_vbox_discard(data, &mut tx, account.unwrap(), client), _ => Err(format_err!("unknown method - {:?}", v.method)), }; @@ -426,6 +427,18 @@ impl Rpc { return Ok(response); } + + fn vbox_drop(data: Vec, tx: &mut Transaction, account: Account, _client: &mut WebSocket) -> Result { + let msg = from_slice::(&data).or(Err(err_msg("invalid params")))?; + + let response = RpcResponse { + method: "vbox_state".to_string(), + params: RpcResult::VboxState(vbox_drop(msg.params, tx, &account)?) + }; + + return Ok(response); + } + } #[derive(Debug,Clone,Serialize,Deserialize)]