From f89413957de3fbfe2f8ea1a9cdc215d08bf8f257 Mon Sep 17 00:00:00 2001 From: Demur Rumed Date: Thu, 25 Jan 2024 05:59:15 +0000 Subject: [PATCH] Alts & flags --- config-sample.json | 9 +- src/Quest.js | 6 +- src/mkAi.js | 6 +- src/parseChat.js | 6 +- src/rs/server/src/handleget.rs | 15 +- src/rs/server/src/handlews.rs | 649 +++++++++++++++++++-------------- src/rs/server/src/json.rs | 20 +- src/rs/server/src/main.rs | 5 +- src/rs/server/src/users.rs | 163 ++++++--- src/sock.jsx | 20 +- src/store.jsx | 35 +- src/vanilla/views/MainMenu.jsx | 2 +- src/vanilla/views/Result.jsx | 2 +- src/views/Alts.jsx | 82 +++++ src/views/ArenaInfo.jsx | 6 +- src/views/Bazaar.jsx | 4 +- src/views/Challenge.jsx | 11 +- src/views/ElementSelect.jsx | 12 +- src/views/KongLogin.jsx | 5 +- src/views/Login.jsx | 11 +- src/views/MainMenu.jsx | 51 +-- src/views/Match.jsx | 8 +- src/views/Result.jsx | 3 +- src/views/WealthTop.jsx | 2 +- 24 files changed, 718 insertions(+), 415 deletions(-) create mode 100644 src/views/Alts.jsx diff --git a/config-sample.json b/config-sample.json index 9148d34d0..9830b505e 100644 --- a/config-sample.json +++ b/config-sample.json @@ -1,10 +1,5 @@ { "listen": 8080, - "pg": { - "user": "postgres", - "host": "localhost", - "port": 5432, - "database": "oetg" - }, + "pg": "user=postgres host=localhost port=5432 database=oetg", "certs": null -} \ No newline at end of file +} diff --git a/src/Quest.js b/src/Quest.js index 6bea88897..78928420f 100644 --- a/src/Quest.js +++ b/src/Quest.js @@ -732,7 +732,7 @@ export function mkQuestAi(quest, datafn) { const drawpower = quest.drawpower ?? 1; const hp = quest.hp ?? 100; const playerHPstart = quest.urhp ?? 100; - const { user } = store.state; + const { user, username } = store.state; let urdeck = quest.urdeck; if (!urdeck) { urdeck = store.getDeck(); @@ -749,8 +749,8 @@ export function mkQuestAi(quest, datafn) { players: [ { idx: 1, - name: user.name, - user: user.name, + name: username, + user: username, deck: urdeck, hp: playerHPstart, }, diff --git a/src/mkAi.js b/src/mkAi.js index e3fe2d497..cde489eaf 100644 --- a/src/mkAi.js +++ b/src/mkAi.js @@ -37,8 +37,8 @@ export function mkPremade(level, daily, datafn = null) { players: [ { idx: 1, - name: user.name, - user: user.name, + name: store.state.username, + user: store.state.username, deck: urdeck, }, { @@ -84,7 +84,7 @@ export function mkAi(level, daily, datafn = null) { { idx: 1, name: user?.name, - user: user ? user.name : '', + user: store.state.username ?? '', deck: urdeck, }, { diff --git a/src/parseChat.js b/src/parseChat.js index a9f01c470..971df496d 100644 --- a/src/parseChat.js +++ b/src/parseChat.js @@ -43,14 +43,14 @@ export default function parseChat(e) { } else if (msg === '/who') { sock.emit({ x: 'who' }); } else if (msg === '/deleteme') { - if (storeState.opts.foename === user.name + 'yesdelete') { + if (storeState.opts.foename === storeState.username + 'yesdelete') { sock.userEmit('delete'); - store.setUser(null); + store.logout(); store.setOpt('remember', false); store.doNav(store.Login); } else { store.chatMsg( - `Input '${user.name}yesdelete' into Player's Name to delete your account`, + `Input '${storeState.username}yesdelete' into Player's Name to delete your account`, 'System', ); } diff --git a/src/rs/server/src/handleget.rs b/src/rs/server/src/handleget.rs index 957644856..1e9016de8 100644 --- a/src/rs/server/src/handleget.rs +++ b/src/rs/server/src/handleget.rs @@ -3,8 +3,8 @@ use std::collections::{BTreeMap, HashMap}; use std::convert::Infallible; use std::fmt::Write; -use std::time::SystemTime; use std::sync::RwLock; +use std::time::SystemTime; use http_body_util::Full; use httpdate::HttpDate; @@ -95,7 +95,11 @@ pub async fn compress_and_cache( content: Bytes::from(compressed.into_boxed_slice()), file: resp.file, }; - cache.write().unwrap_or_else(|e| e.into_inner()).get_map_mut(encoding).insert(path, cached_resp.clone()); + cache + .write() + .unwrap_or_else(|e| e.into_inner()) + .get_map_mut(encoding) + .insert(path, cached_resp.clone()); cached_resp } Err(content) => CachedResponse { @@ -388,8 +392,11 @@ async fn handle_get_core( if let Ok(client) = pgpool.get().await { if let Some(user) = users.write().await.load(&*client, name).await { let user = user.lock().await; - let pool = &user.data.pool; - let bound = &user.data.accountbound; + let Some(userdata) = user.data.get("") else { + return response::Builder::new().status(404).body(Bytes::new()); + }; + let pool = &userdata.pool; + let bound = &userdata.accountbound; let mut cards: BTreeMap = BTreeMap::new(); for i in 0..2 { let (cards0, counts) = if i == 0 { (0, pool) } else { (4, bound) }; diff --git a/src/rs/server/src/handlews.rs b/src/rs/server/src/handlews.rs index 5808f4206..dc3633f30 100644 --- a/src/rs/server/src/handlews.rs +++ b/src/rs/server/src/handlews.rs @@ -29,10 +29,10 @@ use crate::cardpool::Cardpool; use crate::etgutil::{decode_code, encode_code, encode_count, iterraw}; use crate::generated::{DG_COUNT, MAGE_COUNT}; use crate::json::{ - ArenaInfo, AuthMessage, BzBid, GamesData, GamesDataPlayer, GamesMove, LegacyUser, UserMessage, + Alt, ArenaInfo, AuthMessage, BzBid, GamesData, GamesDataPlayer, GamesMove, LegacyUser, UserMessage, WsResponse, }; -use crate::starters::{ORIGINAL_STARTERS, STARTERS}; +use crate::starters::ORIGINAL_STARTERS; use crate::users::{self, HashAlgo, UserData, UserObject, UserRole, Users}; use crate::{get_day, PgPool, WsStream}; @@ -144,31 +144,34 @@ where client.execute("with arank as (select user_id, arena_id, \"rank\", (row_number() over (partition by arena_id order by score desc, day desc, \"rank\"))::int realrank from arena) update arena set \"rank\" = realrank, bestrank = least(bestrank, realrank) from arank where arank.arena_id = arena.arena_id and arank.user_id = arena.user_id and arank.realrank <> arank.\"rank\"", &[]) } -async fn login_success(tx: &WsSender, user: &mut UserObject, username: &str, client: &mut Client) { +async fn login_success(tx: &WsSender, user: &mut UserObject, client: &mut Client) { if user.id != -1 { - let today = get_day(); - let oracle = user.data.oracle; - if oracle < today { - if user.data.ostreakday != today - 1 { - user.data.ostreak = 0; - } - user.data.ostreakday = 0; - user.data.ostreakday2 = today; - user.data.oracle = today; - let mut rng = rand::thread_rng(); - let ocardnymph = rng.gen_range(0..100) < 3; - if let Some(card) = etg::card::OpenSet.random_card(&mut rng, false, |card| { - (card.rarity != 4) ^ ocardnymph && (card.flag & etg::game::Flag::pillar) == 0 - }) { - let ccode = if card.rarity == 4 { etg::card::AsShiny(card.code, true) } else { card.code }; - let curpool = - if card.rarity > 2 { &mut user.data.accountbound } else { &mut user.data.pool }; - let c = curpool.0.entry(ccode).or_default(); - *c = c.saturating_add(1); - user.data.ocard = ccode; - user.data.daily = 0; - user.data.dailymage = rng.gen_range(0..MAGE_COUNT); - user.data.dailydg = rng.gen_range(0..DG_COUNT); + if let Some(userdata) = user.data.get_mut("") { + let today = get_day(); + let oracle = userdata.oracle; + if oracle < today { + if userdata.ostreakday != today - 1 { + userdata.ostreak = 0; + } + userdata.ostreakday = 0; + userdata.ostreakday2 = today; + userdata.oracle = today; + let mut rng = rand::thread_rng(); + let ocardnymph = rng.gen_range(0..100) < 3; + if let Some(card) = etg::card::OpenSet.random_card(&mut rng, false, |card| { + (card.rarity != 4) ^ ocardnymph && (card.flag & etg::game::Flag::pillar) == 0 + }) { + let ccode = + if card.rarity == 4 { etg::card::AsShiny(card.code, true) } else { card.code }; + let curpool = + if card.rarity > 2 { &mut userdata.accountbound } else { &mut userdata.pool }; + let c = curpool.0.entry(ccode).or_default(); + *c = c.saturating_add(1); + userdata.ocard = ccode; + userdata.daily = 0; + userdata.dailymage = rng.gen_range(0..MAGE_COUNT); + userdata.dailydg = rng.gen_range(0..DG_COUNT); + } } } } @@ -177,8 +180,10 @@ async fn login_success(tx: &WsSender, user: &mut UserObject, username: &str, cli tx.send(Message::Text(userstr)).ok(); } - if user.data.daily == 0 { - user.data.daily = 128; + if let Some(userdata) = user.data.get_mut("") { + if userdata.daily == 0 { + userdata.daily = 128; + } } if let Ok(trx) = client.transaction().await { @@ -186,33 +191,38 @@ async fn login_success(tx: &WsSender, user: &mut UserObject, username: &str, cli if let Ok(bids) = trx.query("select code, q, p from bazaar where user_id = $1", &[&user.id]).await { - let mut wealth: i32 = 0; - let mut wealth24: u32 = 0; - for bid in bids.iter() { - let code = bid.get::(0) as i16; - let q: i32 = bid.get(1); - let p: i32 = bid.get(2); - if p < 0 { + let gold = if let Some(userdata) = user.data.get("") { + let mut wealth: i32 = 0; + let mut wealth24: u32 = 0; + for bid in bids.iter() { + let code = bid.get::(0) as i16; + let q: i32 = bid.get(1); + let p: i32 = bid.get(2); + if p < 0 { + if let Some(card) = etg::card::OpenSet.try_get(code) { + let upped = etg::card::Upped(code); + let shiny = etg::card::Shiny(code); + wealth24 += card_val24(card.rarity, upped, shiny) * q as u32; + } + } else { + wealth += p * q; + } + } + for (&code, &count) in userdata.pool.0.iter() { if let Some(card) = etg::card::OpenSet.try_get(code) { let upped = etg::card::Upped(code); let shiny = etg::card::Shiny(code); - wealth24 += card_val24(card.rarity, upped, shiny) * q as u32; + wealth24 += card_val24(card.rarity, upped, shiny) * count as u32; } - } else { - wealth += p * q; } - } - for (&code, &count) in user.data.pool.0.iter() { - if let Some(card) = etg::card::OpenSet.try_get(code) { - let upped = etg::card::Upped(code); - let shiny = etg::card::Shiny(code); - wealth24 += card_val24(card.rarity, upped, shiny) * count as u32; - } - } + userdata.gold.saturating_add(wealth).saturating_add((wealth24 / 24) as i32) + } else { + 0 + }; trx.execute("update users set wealth = $2, auth = $3, salt = $4, iter = $5, algo = $6 where id = $1", &[ &user.id, - &(user.data.gold.saturating_add(wealth + (wealth24 / 24) as i32)), + &gold, &user.auth, &user.salt, &(user.iter as i32), @@ -220,24 +230,6 @@ async fn login_success(tx: &WsSender, user: &mut UserObject, username: &str, cli ]).await.ok(); trx.commit().await.ok(); } - } else { - if let Ok(new_row) = trx.query_one( - "insert into users (name, auth, salt, iter, algo, wealth) values ($1, $2, $3, $4, $5, 0) returning id", - &[ - &username, - &user.auth, - &user.salt, - &(user.iter as i32), - &user.algo - ]).await - { - let userid: i64 = new_row.get(0); - if trx.execute( - "insert into user_data (user_id, type_id, name, data) values ($1, 1, 'Main', $2)", - &[&userid, &Json(&user.data)]).await.is_ok() && trx.commit().await.is_ok() { - user.id = userid; - } - } } } } @@ -338,9 +330,8 @@ pub async fn handle_ws( let Ok(mut client) = pgpool.get().await else { continue }; match msg { - UserMessage::a { u, a, msg } => { - let Some((user, userid)) = - Users::load_with_auth(users, &*client, &u, &a, sockid).await + UserMessage::a { u, a, uname, msg } => { + let Some((user, userid)) = Users::load_with_auth(users, &*client, &u, &a, sockid).await else { continue; }; @@ -427,34 +418,36 @@ pub async fn handle_ws( } } AuthMessage::inituser { e } => { - if e > 0 && e < 14 { + if userid == -1 && e > 0 && e < 14 { + let data = UserData::new(e as usize); let mut user = user.lock().await; - let sid = STARTERS[(e - 1) as usize]; - user.data.accountbound = Cardpool::from(sid.0); - user.data.oracle = 0; - user.data.daily = 0; - user.data.pool = Default::default(); - user.data.freepacks = Some([sid.4, sid.5, 1]); - user.data.selecteddeck = String::from("1"); - user.data.qecks = [ - String::from("1"), - String::from("2"), - String::from("3"), - String::from("4"), - String::from("5"), - String::from("6"), - String::from("7"), - String::from("8"), - String::from("9"), - String::from("10"), - ]; - let mut decks: HashMap = Default::default(); - decks.insert(String::from("1"), String::from(sid.1)); - decks.insert(String::from("2"), String::from(sid.2)); - decks.insert(String::from("3"), String::from(sid.3)); - user.data.decks = decks; - login_success(&tx, &mut *user, &u, &mut client) - .await; + if let Ok(trx) = client.transaction().await { + if let Ok(new_row) = trx.query_one( + "insert into users (name, auth, salt, iter, algo, wealth) values ($1, $2, $3, $4, $5, 0) returning id", + &[ + &u, + &user.auth, + &user.salt, + &(user.iter as i32), + &user.algo + ]).await + { + user.id = new_row.get(0); + } + if trx.execute( + "insert into user_data (user_id, type_id, name, data) values ($1, 1, $2, $3)", + &[&user.id, &"", &Json(&data)]).await.is_ok() + && trx.commit().await.is_ok() + { + user.data.insert(String::new(), data); + if let Ok(userstr) = serde_json::to_string(&WsResponse::login(&*user)) { + tx.send(Message::Text(userstr)).ok(); + } + if let Some(userdata) = user.data.get_mut("") { + userdata.daily = 128; + } + } + } } } AuthMessage::loginoriginal => { @@ -511,6 +504,34 @@ pub async fn handle_ws( } } } + AuthMessage::altcreate { + name, e, flags + } => { + if e > 0 && e < 14 && !name.is_empty() { + let mut user = user.lock().await; + if user.data.contains_key(&name) { + sendmsg(&tx, &WsResponse::chat { mode: 1, msg: "Alt already exists" }); + continue + } + let mut data = UserData::new(e as usize); + data.flags = flags; + if client.execute( + "insert into user_data (user_id, type_id, name, data) values ($1, 1, $2, $3)", + &[&userid, &name, &Json(&data)]).await.is_ok() + { + sendmsg(&tx, &WsResponse::altadd(Alt{ name: &name, data: &data })); + user.data.insert(name, data); + } + } + } + AuthMessage::altdelete { + name + } => { + let mut user = user.lock().await; + if client.execute("delete from user_data where user_id = $1 and name = $2", &[&userid, &name]).await.is_ok() { + user.data.remove(&name); + } + } AuthMessage::setarena { d, r#mod, @@ -521,7 +542,10 @@ pub async fn handle_ws( } => { let (userid, ocard) = { let user = user.lock().await; - (user.id, user.data.ocard as i32) + let Some(userdata) = user.data.get("") else { + continue + }; + (user.id, userdata.ocard as i32) }; if ocard != 0 { let hp = hp as i32; @@ -539,7 +563,10 @@ pub async fn handle_ws( today.saturating_sub(row.get::(0) as u32); if age > 0 { let mut user = user.lock().await; - user.data.gold = user.data.gold.saturating_add( + let Some(userdata) = user.data.get_mut("") else { + continue + }; + userdata.gold = userdata.gold.saturating_add( age.saturating_mul(25).min(350) as i32, ); } @@ -600,8 +627,12 @@ pub async fn handle_ws( if let Some(auserid) = if let Some(other) = wusers.load(&*client, &aname).await { let mut other = other.lock().await; - other.data.gold += if won { 15 } else { 5 }; - Some(other.id) + if let Some(otherdata) = other.data.get_mut("") { + otherdata.gold += if won { 15 } else { 5 }; + Some(other.id) + } else { + None + } } else { None } { @@ -679,17 +710,19 @@ pub async fn handle_ws( if role_check(UserRole::Codesmith, &tx, &client, userid).await { if let Some(tgt) = users.write().await.load(&*client, &t).await { let mut tgt = tgt.lock().await; - sendmsg( - &tx, - &WsResponse::chat { - mode: 1, - msg: &format!( - "Set {} from {}$ to {}$", - t, tgt.data.gold, g - ), - }, - ); - tgt.data.gold = g; + if let Some(tgtdata) = tgt.data.get_mut("") { + sendmsg( + &tx, + &WsResponse::chat { + mode: 1, + msg: &format!( + "Set {} from {}$ to {}$", + t, tgtdata.gold, g + ), + }, + ); + tgtdata.gold = g; + } } } } @@ -697,14 +730,16 @@ pub async fn handle_ws( if role_check(UserRole::Codesmith, &tx, &client, userid).await { if let Some(tgt) = users.write().await.load(&*client, &t).await { let mut tgt = tgt.lock().await; - let curpool = if bound { - &mut tgt.data.accountbound - } else { - &mut tgt.data.pool - }; - for (code, count) in iterraw(pool.as_bytes()) { - let c = curpool.0.entry(code).or_default(); - *c = c.saturating_add(count); + if let Some(tgtdata) = tgt.data.get_mut("") { + let curpool = if bound { + &mut tgtdata.accountbound + } else { + &mut tgtdata.pool + }; + for (code, count) in iterraw(pool.as_bytes()) { + let c = curpool.0.entry(code).or_default(); + *c = c.saturating_add(count); + } } sendmsg( &tx, @@ -787,8 +822,10 @@ pub async fn handle_ws( { if trx.commit().await.is_ok() { let mut user = user.lock().await; - user.data.gold = - user.data.gold.saturating_add(g); + if let Some(userdata) = user.data.get_mut("") { + userdata.gold = + userdata.gold.saturating_add(g); + } sendmsg(&tx, &WsResponse::codegold { g }); } } @@ -813,22 +850,20 @@ pub async fn handle_ws( &[&code], ) .await - .is_ok() - { - if trx.commit().await.is_ok() { + .is_ok() && trx.commit().await.is_ok() { let mut user = user.lock().await; - let c = user - .data - .pool - .0 - .entry(ccode) - .or_default(); - *c = c.saturating_add(1); - sendmsg( - &tx, - &WsResponse::codecode { card: ccode }, - ); - } + if let Some(userdata) = user.data.get_mut("") { + let c = userdata + .pool + .0 + .entry(ccode) + .or_default(); + *c = c.saturating_add(1); + sendmsg( + &tx, + &WsResponse::codecode { card: ccode }, + ); + } } } else { sendmsg( @@ -924,8 +959,8 @@ pub async fn handle_ws( { if trx.commit().await.is_ok() { let mut user = user.lock().await; - let c = user - .data + if let Some(userdata) = user.data.get_mut("") { + let c = userdata .pool .0 .entry(card) @@ -936,6 +971,7 @@ pub async fn handle_ws( &WsResponse::codedone { card }, ); } + } } } else { sendmsg( @@ -989,12 +1025,15 @@ pub async fn handle_ws( user.id, match &set[..] { "Original" => deck, - _ => user - .data - .decks - .get(&user.data.selecteddeck) + _ => { + let Some(userdata) = user.data.get(&uname) else { + continue + }; + userdata.decks + .get(&userdata.selecteddeck) .cloned() - .unwrap_or(String::new()), + .unwrap_or(String::new()) + } }, ) }; @@ -1242,6 +1281,13 @@ pub async fn handle_ws( if let Some(foesock) = socks.read().await.get(&foesockid) { let (mut user, mut foeuser) = ordered_lock(&user, &foeuser).await; + let foeid = foeuser.id; + let (Some(userdata), Some(foedata)) = ( + user.data.get_mut(""), + foeuser.data.get_mut("") + ) else { + continue + }; if let Ok(trx) = client.transaction().await { let g32 = g as i32; let forg32 = forg.map(|g| g as i32); @@ -1250,13 +1296,13 @@ pub async fn handle_ws( { if let Ok(rows) = trx.execute( "delete from trade_request where user_id = $2 and for_user_id = $1 and cards = $5 and g = $6 and forcards = $3 and forg = $4", - &[&user.id, &foeuser.id, &cards, &g32, forcardsref, &forg32]).await { + &[&userid, &foeid, &cards, &g32, forcardsref, &forg32]).await { if rows > 0 { - if trx.execute("delete from trade_request where (user_id = $1 and for_user_id = $2) or (user_id = $2 and for_user_id = $1)", &[&user.id, &foeuser.id]).await.is_ok() && trx.commit().await.is_ok() { + if trx.execute("delete from trade_request where (user_id = $1 and for_user_id = $2) or (user_id = $2 and for_user_id = $1)", &[&userid, &foeid]).await.is_ok() && trx.commit().await.is_ok() { let p1gdelta = forg32 - g32; let p2gdelta = -p1gdelta; let mut err: Option<&'static str> = None; - if user.data.gold < -p1gdelta || foeuser.data.gold < -p2gdelta { + if userdata.gold < -p1gdelta || foedata.gold < -p2gdelta { err = Some("Not enough gold between players"); } { @@ -1265,7 +1311,7 @@ pub async fn handle_ws( let c = usertally.entry(code).or_default(); if let Some(newc) = c.checked_add(count) { *c = newc; - if user.data.pool.0.get(&code).cloned().unwrap_or(0) < newc { + if userdata.pool.0.get(&code).cloned().unwrap_or(0) < newc { err = Some("Not enough cards between players"); } } else { @@ -1279,7 +1325,7 @@ pub async fn handle_ws( let c = foetally.entry(code).or_default(); if let Some(newc) = c.checked_add(count) { *c = newc; - if foeuser.data.pool.0.get(&code).cloned().unwrap_or(0) < newc { + if foedata.pool.0.get(&code).cloned().unwrap_or(0) < newc { err = Some("Not enough cards between players"); } } else { @@ -1313,17 +1359,17 @@ pub async fn handle_ws( newcards: &cards, g: p2gdelta, }); - user.data.gold = user.data.gold.saturating_add(p1gdelta); - foeuser.data.gold = foeuser.data.gold.saturating_add(p2gdelta); + userdata.gold = userdata.gold.saturating_add(p1gdelta); + foedata.gold = foedata.gold.saturating_add(p2gdelta); for (code, count) in iterraw(cards.as_bytes()) { - let foec = foeuser.data.pool.0.entry(code).or_default(); - let userc = user.data.pool.0.entry(code).or_default(); + let foec = foedata.pool.0.entry(code).or_default(); + let userc = userdata.pool.0.entry(code).or_default(); *userc -= count; *foec = foec.saturating_add(count); } for (code, count) in iterraw(forcardsref.as_bytes()) { - let foec = foeuser.data.pool.0.entry(code).or_default(); - let userc = user.data.pool.0.entry(code).or_default(); + let foec = foedata.pool.0.entry(code).or_default(); + let userc = userdata.pool.0.entry(code).or_default(); *foec -= count; *userc = userc.saturating_add(count); } @@ -1477,12 +1523,14 @@ pub async fn handle_ws( (1, 250, &[0, 0, 0], 0.0), ]; let mut user = user.lock().await; + let Some(userdata) = user.data.get_mut(&uname) else { + continue + }; if let Some(&(amount, cost, rares, bumprate)) = PACKS.get(pack as usize) { let mut amount = amount as u16; let mut cost = cost as i32; - let bound = user - .data + let bound = userdata .freepacks .and_then(|fp| fp.get(pack as usize).cloned()) .unwrap_or(0) != 0; @@ -1490,7 +1538,7 @@ pub async fn handle_ws( amount *= bulk as u16; cost *= bulk as i32; } - if bound || user.data.gold >= cost { + if bound || userdata.gold >= cost { let mut newcards: Cardpool = Default::default(); let mut rarity: usize = 1; let mut rng = rand::thread_rng(); @@ -1532,15 +1580,15 @@ pub async fn handle_ws( *c = c.saturating_add(1); } let curpool = if bound { - let freepacks = user.data.freepacks.as_mut().unwrap(); + let freepacks = userdata.freepacks.as_mut().unwrap(); freepacks[pack as usize] -= 1; if freepacks.iter().all(|&x| x == 0) { - user.data.freepacks = None; + userdata.freepacks = None; } - &mut user.data.accountbound + &mut userdata.accountbound } else { - user.data.gold -= cost; - &mut user.data.pool + userdata.gold -= cost; + &mut userdata.pool }; for (&code, &count) in newcards.0.iter() { let c = curpool.0.entry(code).or_default(); @@ -1552,7 +1600,7 @@ pub async fn handle_ws( cards: &newcards, accountbound: bound, packtype: pack, - g: user.data.gold, + g: userdata.gold, }, ); } @@ -1578,11 +1626,14 @@ pub async fn handle_ws( .is_ok() { let mut user = user.lock().await; + let Some(userdata) = user.data.get_mut("") else { + continue + }; let mut sells: Vec = Vec::new(); let mut codecount = if p > 0 { 0 } else { - user.data.pool.0.get(&code).cloned().unwrap_or(0) + userdata.pool.0.get(&code).cloned().unwrap_or(0) }; if p > 0 { if p as i32 <= sellval { @@ -1591,8 +1642,8 @@ pub async fn handle_ws( } else if codecount < count { continue; } else if -(p as i32) <= sellval { - user.data.gold += sellval * count as i32; - let c = user.data.pool.0.entry(code).or_default(); + userdata.gold += sellval * count as i32; + let c = userdata.pool.0.entry(code).or_default(); *c = c.saturating_sub(count); continue; } @@ -1614,12 +1665,12 @@ pub async fn handle_ws( } let cost = bp.abs() as i32 * happened; if happened != 0 && if p > 0 { - user.data.gold >= cost + userdata.gold >= cost } else { codecount as i32 >= happened } { - user.data.gold -= cost; - let c = user.data.pool.0.entry(code).or_default(); + userdata.gold -= cost; + let c = userdata.pool.0.entry(code).or_default(); if happened > 0 { *c = c.saturating_add(happened as u16); codecount = codecount.saturating_add(happened as u16); @@ -1650,12 +1701,12 @@ pub async fn handle_ws( if count > 0 { let mut bidmade = false; if p > 0 { - if user.data.gold >= p as i32 * count as i32 { - user.data.gold -= p as i32 * count as i32; + if userdata.gold >= p as i32 * count as i32 { + userdata.gold -= p as i32 * count as i32; bidmade = true; } } else if codecount >= count { - let c = user.data.pool.0.entry(code).or_default(); + let c = userdata.pool.0.entry(code).or_default(); if let Some(newc) = c.checked_sub(count as u16) { *c = newc; bidmade = true; @@ -1703,7 +1754,7 @@ pub async fn handle_ws( }); trx.execute( "insert into bazaar (user_id, code, q, p) values ($1, $2, $3, $4)", - &[&user.id, &(code as i32), &(q as i32), &(p as i32)] + &[&userid, &(code as i32), &(q as i32), &(p as i32)] ).await } BzBidOp::Update { id, bid, q } => { @@ -1726,8 +1777,8 @@ pub async fn handle_ws( &WsResponse::bzbid { rm: &rm, add: &add, - g: user.data.gold, - pool: &user.data.pool, + g: userdata.gold, + pool: &userdata.pool, }, ); drop(user); @@ -1737,9 +1788,9 @@ pub async fn handle_ws( { if let Some(seller) = wusers.load(&trx, &sell.u).await { let mut seller = seller.lock().await; + if let Some(sellerdata) = seller.data.get_mut("") { if sell.p > 0 { - let c = seller - .data + let c = sellerdata .pool .0 .entry(sell.code) @@ -1753,11 +1804,11 @@ pub async fn handle_ws( newc as u16 }; } else { - seller.data.gold = seller - .data + sellerdata.gold = sellerdata .gold .saturating_add(sell.amt as i32 * -sell.p as i32); } + } } } if let Some(selltx) = if sell.u == u { @@ -1813,18 +1864,21 @@ pub async fn handle_ws( } AuthMessage::bzcancel { c } => { let mut user = user.lock().await; + let Some(userdata) = user.data.get_mut("") else { + continue + }; if let Ok(bids) = client.query( "delete from bazaar where user_id = $1 and code = $2 returning q, p", - &[&user.id, &(c as i32)]).await + &[&userid, &(c as i32)]).await { let mut rm: FxHashMap> = Default::default(); for bid in bids.iter() { let q = bid.get::(0) as u16; let p = bid.get::(1) as i16; if p > 0 { - user.data.gold += p as i32 * q as i32; + userdata.gold += p as i32 * q as i32; } else { - let c = user.data.pool.0.entry(c).or_default(); + let c = userdata.pool.0.entry(c).or_default(); *c = c.saturating_add(q) } rm.entry(c).or_default().push(BzBid { u: Cow::from(u.as_str()), q, p }); @@ -1832,65 +1886,81 @@ pub async fn handle_ws( sendmsg(&tx, &WsResponse::bzbid { add: &Default::default(), rm: &rm, - g: user.data.gold, - pool: &user.data.pool, + g: userdata.gold, + pool: &userdata.pool, }); } } AuthMessage::addgold { g } => { let mut user = user.lock().await; - user.data.gold = user.data.gold.saturating_add(g as i32); + if let Some(userdata) = user.data.get_mut(&uname) { + userdata.gold = userdata.gold.saturating_add(g as i32); + } } AuthMessage::addloss { pvp, l, g } => { let mut user = user.lock().await; - if pvp { - user.data.pvplosses = user.data.pvplosses.saturating_add(1); - } else { - user.data.ailosses = user.data.ailosses.saturating_add(1); - } - if let Some(l) = l { - if user.data.streak.len() > l as usize { - user.data.streak[l as usize] = Some(0); + if let Some(userdata) = user.data.get_mut(&uname) { + if pvp { + userdata.pvplosses = userdata.pvplosses.saturating_add(1); + } else { + userdata.ailosses = userdata.ailosses.saturating_add(1); + } + if let Some(l) = l { + if userdata.streak.len() > l as usize { + userdata.streak[l as usize] = 0; + } + } + if let Some(g) = g { + userdata.gold = userdata.gold.saturating_add(g as i32); } - } - if let Some(g) = g { - user.data.gold = user.data.gold.saturating_add(g as i32); } } AuthMessage::addwin { pvp } => { let mut user = user.lock().await; - if pvp { - user.data.pvpwins = user.data.pvpwins.saturating_add(1); - user.data.pvplosses = user.data.pvplosses.saturating_sub(1); - } else { - user.data.aiwins = user.data.aiwins.saturating_add(1); - user.data.ailosses = user.data.ailosses.saturating_sub(1); + if let Some(userdata) = user.data.get_mut(&uname) { + if pvp { + userdata.pvpwins = userdata.pvpwins.saturating_add(1); + userdata.pvplosses = userdata.pvplosses.saturating_sub(1); + } else { + userdata.aiwins = userdata.aiwins.saturating_add(1); + userdata.ailosses = userdata.ailosses.saturating_sub(1); + } } } AuthMessage::setstreak { l, n } => { + if l > 5 { + continue + } let mut user = user.lock().await; - user.data.streak.resize(l as usize + 1, Some(0)); - user.data.streak[l as usize] = Some(n); + if let Some(userdata) = user.data.get_mut(&uname) { + userdata.streak.resize(l as usize + 1, 0); + userdata.streak[l as usize] = n; + } } AuthMessage::addcards { c } => { let mut user = user.lock().await; - for (code, count) in iterraw(c.as_bytes()) { - let q = user.data.pool.0.entry(code).or_default(); - *q = q.saturating_add(count); + if let Some(userdata) = user.data.get_mut(&uname) { + for (code, count) in iterraw(c.as_bytes()) { + let q = userdata.pool.0.entry(code).or_default(); + *q = q.saturating_add(count); + } } } AuthMessage::addboundcards { c } => { let mut user = user.lock().await; - for (code, count) in iterraw(c.as_bytes()) { - let q = user.data.accountbound.0.entry(code).or_default(); - *q = q.saturating_add(count); + if let Some(userdata) = user.data.get_mut(&uname) { + for (code, count) in iterraw(c.as_bytes()) { + let q = userdata.accountbound.0.entry(code).or_default(); + *q = q.saturating_add(count); + } } } AuthMessage::donedaily { daily, c } => { let mut user = user.lock().await; - if (daily < 3 || daily == 5) && user.data.ostreakday == 0 { - user.data.gold = - user.data.gold.saturating_add(match user.data.ostreak % 5 { + if let Some(userdata) = user.data.get_mut(&uname) { + if (daily < 3 || daily == 5) && userdata.ostreakday == 0 { + userdata.gold = + userdata.gold.saturating_add(match userdata.ostreak % 5 { 0 => 15, 1 => 25, 2 => 77, @@ -1898,40 +1968,48 @@ pub async fn handle_ws( 4 => 250, _ => unreachable!(), }); - user.data.ostreak = user.data.ostreak.saturating_add(1); - user.data.ostreakday = user.data.ostreakday2; - user.data.ostreakday2 = 0; + userdata.ostreak = userdata.ostreak.saturating_add(1); + userdata.ostreakday = userdata.ostreakday2; + userdata.ostreakday2 = 0; } - if daily == 6 && (user.data.daily & 64) == 0 && c != 0 { - let c = user.data.pool.0.entry(c).or_default(); + if daily == 6 && (userdata.daily & 64) == 0 && c != 0 { + let c = userdata.pool.0.entry(c).or_default(); *c = c.saturating_add(1); } - user.data.daily |= 1 << daily; + userdata.daily |= 1 << daily; + } } AuthMessage::changeqeck { number, name } => { let mut user = user.lock().await; - if (number as usize) < user.data.qecks.len() { - user.data.qecks[number as usize] = name; + if let Some(userdata) = user.data.get_mut(&uname) { + if (number as usize) < userdata.qecks.len() { + userdata.qecks[number as usize] = name; + } } } AuthMessage::setdeck { name, d } => { let mut user = user.lock().await; + if let Some(userdata) = user.data.get_mut(&uname) { if let Some(d) = d { - user.data.decks.insert(name.clone(), d); + userdata.decks.insert(name.clone(), d); + } + userdata.selecteddeck = name; } - user.data.selecteddeck = name; } AuthMessage::rmdeck { name } => { let mut user = user.lock().await; - user.data.decks.remove(&name); + if let Some(userdata) = user.data.get_mut(&uname) { + userdata.decks.remove(&name); + } } AuthMessage::setquest { quest } => { let mut user = user.lock().await; - user.data.quests.insert(quest, 1); + if let Some(userdata) = user.data.get_mut(&uname) { + userdata.quests.insert(quest, 1); + } } AuthMessage::upgrade { card } => { if let Some(carddata) = etg::card::OpenSet.try_get(card) { - let mut user = user.lock().await; let copies = if carddata.rarity != -1 && !(carddata.rarity == 4 && etg::card::Shiny(card)) { @@ -1939,18 +2017,20 @@ pub async fn handle_ws( } else { 1 }; + let mut user = user.lock().await; + if let Some(userdata) = user.data.get_mut(&uname) { transmute( - &mut user.data, + userdata, card, etg::card::AsUpped(card, true), copies, 1, ); } + } } AuthMessage::downgrade { card } => { if let Some(carddata) = etg::card::OpenSet.try_get(card) { - let mut user = user.lock().await; let copies = if carddata.rarity != -1 && !(carddata.rarity == 4 && etg::card::Shiny(card)) { @@ -1958,53 +2038,64 @@ pub async fn handle_ws( } else { 1 }; + let mut user = user.lock().await; + if let Some(userdata) = user.data.get_mut(&uname) { transmute( - &mut user.data, + userdata, card, etg::card::AsUpped(card, false), 1, copies, ); } + } } AuthMessage::polish { card } => { if let Some(carddata) = etg::card::OpenSet.try_get(card) { - let mut user = user.lock().await; let copies = if carddata.rarity != -1 { 6 } else { 2 }; + let mut user = user.lock().await; + if let Some(userdata) = user.data.get_mut(&uname) { transmute( - &mut user.data, + userdata, card, etg::card::AsShiny(card, true), copies, 1, ); } + } } AuthMessage::unpolish { card } => { if let Some(carddata) = etg::card::OpenSet.try_get(card) { - let mut user = user.lock().await; let copies = if carddata.rarity != -1 { 6 } else { 2 }; + let mut user = user.lock().await; + if let Some(userdata) = user.data.get_mut(&uname) { transmute( - &mut user.data, + userdata, card, etg::card::AsShiny(card, false), 1, copies, ); } + } } AuthMessage::uppillar { c } => { let mut user = user.lock().await; - upshpi(&mut user.data, c, |code| etg::card::AsUpped(code, true)); + if let Some(userdata) = user.data.get_mut(&uname) { + upshpi(userdata, c, |code| etg::card::AsUpped(code, true)); + } } AuthMessage::shpillar { c } => { let mut user = user.lock().await; - upshpi(&mut user.data, c, |code| etg::card::AsShiny(code, true)); + if let Some(userdata) = user.data.get_mut(&uname) { + upshpi(userdata, c, |code| etg::card::AsShiny(code, true)); + } } AuthMessage::upshall => { let mut user = user.lock().await; - let mut base = user - .data + if let Some(userdata) = user.data.get_mut(&uname) { + let mut base = userdata .pool .0 .keys() @@ -2039,27 +2130,24 @@ pub async fn handle_ws( let shcode = etg::card::AsShiny(code, true); let uhcode = etg::card::AsShiny(upcode, true); let mut un = - user.data.pool.0.get(&code).cloned().unwrap_or(0) - as i32 + user - .data + userdata.pool.0.get(&code).cloned().unwrap_or(0) + as i32 + userdata .accountbound .0 .get(&code) .cloned() .unwrap_or(0) as i32; let mut up = - user.data.pool.0.get(&upcode).cloned().unwrap_or(0) - as i32 + user - .data + userdata.pool.0.get(&upcode).cloned().unwrap_or(0) + as i32 + userdata .accountbound .0 .get(&upcode) .cloned() .unwrap_or(0) as i32; let mut sh = - user.data.pool.0.get(&shcode).cloned().unwrap_or(0) - as i32 + user - .data + userdata.pool.0.get(&shcode).cloned().unwrap_or(0) + as i32 + userdata .accountbound .0 .get(&shcode) @@ -2067,7 +2155,7 @@ pub async fn handle_ws( .unwrap_or(0) as i32; while un >= 12 && up < 6 && convert( - &mut user.data.pool, + &mut userdata.pool, code, 6, upcode, @@ -2078,7 +2166,7 @@ pub async fn handle_ws( if card.rarity < 4 { while un >= 12 && sh < 6 && convert( - &mut user.data.pool, + &mut userdata.pool, code, 6, shcode, @@ -2087,7 +2175,7 @@ pub async fn handle_ws( sh += 1; } while un >= 42 - && convert(&mut user.data.pool, code, 36, uhcode) + && convert(&mut userdata.pool, code, 36, uhcode) { un -= 36; } @@ -2095,6 +2183,7 @@ pub async fn handle_ws( } } } + } } } } @@ -2130,7 +2219,7 @@ pub async fn handle_ws( salt: Vec::new(), iter: 0, algo: users::HASH_ALGO, - data: UserData { oracle: u32::MAX, ..Default::default() }, + data: Default::default(), })); wusers.insert(u.clone(), sockid.get(), user.clone()); user @@ -2168,7 +2257,7 @@ pub async fn handle_ws( user.auth.is_empty() } { wusers.set_sockid(&u, sockid.get()); - login_success(&tx, &mut *user, &u, &mut client).await; + login_success(&tx, &mut *user, &mut client).await; } else { sendmsg(&tx, &WsResponse::loginfail { err: "Authentication failed" }); } @@ -2218,7 +2307,6 @@ pub async fn handle_ws( login_success( &tx, &mut user, - &name, &mut client, ) .await; @@ -2236,7 +2324,6 @@ pub async fn handle_ws( login_success( &tx, &mut newuser, - &name, &mut client, ) .await; @@ -2345,36 +2432,40 @@ pub async fn handle_ws( UserMessage::librarywant { f } => { if let Some(user) = users.write().await.load(&*client, &f).await { let user = user.lock().await; - let mut gold = user.data.gold; - let mut pool = user.data.pool.clone(); - if let Ok(bids) = client - .query("select code, q, p from bazaar where user_id = $1", &[&user.id]) - .await - { - for bid in bids.iter() { - let code = bid.get::(0) as i16; - let q: i32 = bid.get(1); - let p: i32 = bid.get(2); - if p < 0 { - let amt = pool.0.entry(code).or_insert(0); - *amt = amt.saturating_add(q as u16); - } else { - gold += p * q; + if let Some(userdata) = user.data.get("") { + let mut gold = userdata.gold; + let mut pool = userdata.pool.clone(); + if let Ok(bids) = client + .query("select code, q, p from bazaar where user_id = $1", &[&user.id]) + .await + { + for bid in bids.iter() { + let code = bid.get::(0) as i16; + let q: i32 = bid.get(1); + let p: i32 = bid.get(2); + if p < 0 { + let amt = pool.0.entry(code).or_insert(0); + *amt = amt.saturating_add(q as u16); + } else { + gold += p * q; + } } } + sendmsg( + &tx, + &WsResponse::librarygive { + pool: &pool, + bound: &userdata.accountbound, + gold, + pvpwins: userdata.pvpwins, + pvplosses: userdata.pvplosses, + aiwins: userdata.aiwins, + ailosses: userdata.ailosses, + }, + ); + } else { + sendmsg(&tx, &WsResponse::chat { mode: 1, msg: "User does not have data" }); } - sendmsg( - &tx, - &WsResponse::librarygive { - pool: &pool, - bound: &user.data.accountbound, - gold, - pvpwins: user.data.pvpwins, - pvplosses: user.data.pvplosses, - aiwins: user.data.aiwins, - ailosses: user.data.ailosses, - }, - ); } else { sendmsg(&tx, &WsResponse::chat { mode: 1, msg: "User does not exist" }); } diff --git a/src/rs/server/src/json.rs b/src/rs/server/src/json.rs index 307c4487f..d776905d2 100644 --- a/src/rs/server/src/json.rs +++ b/src/rs/server/src/json.rs @@ -2,6 +2,7 @@ #![allow(non_snake_case)] use std::borrow::Cow; +use std::collections::HashSet; use fxhash::FxHashMap; @@ -9,7 +10,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::cardpool::Cardpool; -use crate::users::UserObject; +use crate::users::{UserData, UserObject}; #[derive(Deserialize, Clone)] #[serde(tag = "z")] @@ -49,6 +50,14 @@ pub enum AuthMessage { }, logout, delete, + altcreate { + e: u8, + name: String, + flags: HashSet, + }, + altdelete { + name: String, + }, setarena { d: String, #[serde(default)] @@ -230,6 +239,8 @@ pub enum UserMessage { a { u: String, a: String, + #[serde(default)] + uname: String, #[serde(flatten)] msg: AuthMessage, }, @@ -278,9 +289,16 @@ pub struct ArenaInfo<'a> { pub bestrank: i32, } +#[derive(Serialize, Clone)] +pub struct Alt<'a> { + pub name: &'a str, + pub data: &'a UserData, +} + #[derive(Serialize, Clone)] #[serde(tag = "x")] pub enum WsResponse<'a> { + altadd(Alt<'a>), arenainfo { #[serde(rename = "A")] a1: Option>, diff --git a/src/rs/server/src/main.rs b/src/rs/server/src/main.rs index 0c4e17c1f..53a6134dc 100644 --- a/src/rs/server/src/main.rs +++ b/src/rs/server/src/main.rs @@ -139,9 +139,8 @@ async fn main() { let _sigpipestream = signal(SignalKind::pipe()).expect("Failed to setup pipe handler"); let (listenport, pgpool) = { let configjson = tokio::fs::read("../../../config.json").await.expect("Failed to load config.json"); - let configraw = - serde_json::from_slice::(&configjson).expect("Failed to parse config.json"); - let Config { listen, pg, certs } = Config::from(configraw); + let Config { listen, pg, certs } = + serde_json::from_slice::(&configjson).expect("Failed to parse config.json").into(); if certs.is_some() { panic!("HTTPS cert support isn't implemented"); } diff --git a/src/rs/server/src/users.rs b/src/rs/server/src/users.rs index 969f55746..188cac672 100644 --- a/src/rs/server/src/users.rs +++ b/src/rs/server/src/users.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::num::NonZeroUsize; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; @@ -12,6 +12,7 @@ use tokio::sync::{Mutex, RwLock}; use crate::cardpool::Cardpool; use crate::handlews::AsyncSocks; +use crate::starters::STARTERS; #[derive(Clone, Copy, Debug, ToSql, FromSql)] #[postgres(name = "userrole")] @@ -38,7 +39,7 @@ impl From for pbkdf2::Algorithm { } } -#[derive(Serialize, Deserialize, Default, Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct UserData { #[serde(default)] pub accountbound: Cardpool, @@ -77,14 +78,61 @@ pub struct UserData { #[serde(default)] pub quests: HashMap, #[serde(default)] + pub flags: HashSet, + #[serde(default)] #[serde(rename = "selectedDeck")] pub selecteddeck: String, #[serde(default)] - pub streak: Vec>, + pub streak: Vec, #[serde(default)] pub freepacks: Option<[u8; 3]>, } +impl UserData { + pub fn new(e: usize) -> UserData { + let sid = STARTERS[(e - 1) as usize]; + let mut decks: HashMap = Default::default(); + decks.insert(String::from("1"), String::from(sid.1)); + decks.insert(String::from("2"), String::from(sid.2)); + decks.insert(String::from("3"), String::from(sid.3)); + UserData { + accountbound: Cardpool::from(sid.0), + oracle: 0, + daily: 0, + dailydg: 0, + dailymage: 0, + freepacks: Some([sid.4, sid.5, 1]), + selecteddeck: String::from("1"), + decks, + qecks: [ + String::from("1"), + String::from("2"), + String::from("3"), + String::from("4"), + String::from("5"), + String::from("6"), + String::from("7"), + String::from("8"), + String::from("9"), + String::from("10"), + ], + ailosses: 0, + aiwins: 0, + pvplosses: 0, + pvpwins: 0, + gold: 0, + ocard: 0, + ostreak: 0, + ostreakday: 0, + ostreakday2: 0, + flags: Default::default(), + quests: Default::default(), + pool: Default::default(), + streak: Default::default(), + } + } +} + #[derive(Serialize)] pub struct UserObject { pub name: String, @@ -97,8 +145,7 @@ pub struct UserObject { pub iter: u32, #[serde(skip)] pub algo: HashAlgo, - #[serde(flatten)] - pub data: UserData, + pub data: HashMap, } pub const HASH_ITER: u32 = 99999; @@ -120,6 +167,28 @@ pub type User = Arc>; pub struct Users(HashMap); impl Users { + pub async fn load_data(client: &GC, userid: i64) -> Option> + where + GC: GenericClient, + { + if let Ok(urows) = client + .query("select name, data from user_data where user_id = $1 and type_id = 1", &[&userid]) + .await + { + let mut data = HashMap::with_capacity(urows.len()); + for urow in urows { + let name = urow.get::(0); + let Ok(Json(userdata)) = urow.try_get::>(1) else { + panic!("Invalid json for user {}", name); + }; + data.insert(name, userdata); + } + Some(data) + } else { + None + } + } + pub async fn load_with_auth( users: &RwLock, client: &GC, @@ -153,13 +222,7 @@ impl Users { let userid = row.get::(0); let mut wuserslock = users.write().await; let Users(ref mut wusers) = *wuserslock; - if let Ok(urow) = client - .query_one("select data from user_data where user_id = $1 and type_id = 1", &[&userid]) - .await - { - let Ok(Json(userdata)) = urow.try_get::>(0) else { - panic!("Invalid json for user {}", name); - }; + if let Some(data) = Users::load_data(client, userid).await { let userarc = Arc::new(Mutex::new(UserObject { name: String::from(name), id: row.get::(0), @@ -167,7 +230,7 @@ impl Users { salt: row.get::>(2), iter: row.get::(3) as u32, algo: row.get::(4), - data: userdata, + data, })); wusers.insert( String::from(name), @@ -192,20 +255,31 @@ impl Users { if let Some((ref gc, _, ref user)) = self.0.get(name) { gc.store(false, Ordering::Release); Some(user.clone()) - } else if let Some(row) = client.query_opt("select u.id, u.auth, u.salt, u.iter, u.algo, ud.data from user_data ud join users u on u.id = ud.user_id where u.name = $1 and ud.type_id = 1", &[&name]).await.expect("Connection failed while loading user") { - let Json(userdata) = row.try_get::>(5).expect("Invalid json for user"); - let namestr = name.to_string(); - let userarc = Arc::new(Mutex::new(UserObject { - name: namestr.clone(), - id: row.get::(0), - auth: row.get::(1), - salt: row.get::>(2), - iter: row.get::(3) as u32, - algo: row.get::(4), - data: userdata, - })); - self.insert(namestr, 0, userarc.clone()); - Some(userarc) + } else if let Some(row) = client + .query_opt( + "select u.id, u.auth, u.salt, u.iter, u.algo from users u where u.name = $1", + &[&name], + ) + .await + .expect("Connection failed while loading user") + { + let userid = row.get::(0); + if let Some(data) = Users::load_data(client, userid).await { + let namestr = name.to_string(); + let userarc = Arc::new(Mutex::new(UserObject { + name: namestr.clone(), + id: userid, + auth: row.get::(1), + salt: row.get::>(2), + iter: row.get::(3) as u32, + algo: row.get::(4), + data, + })); + self.insert(namestr, 0, userarc.clone()); + Some(userarc) + } else { + None + } } else { None } @@ -244,30 +318,35 @@ impl Users { pub async fn evict(&mut self, client: &Client, name: &str) { if let Some((_, _, user)) = self.0.remove(name) { let user = user.lock().await; - client - .query( - "update user_data set data = $2 where user_id = $1 and type_id = 1", - &[&user.id, &Json(&user.data)], - ) - .await - .ok(); + for (name, data) in user.data.iter() { + client + .execute( + "update user_data set data = $3 where user_id = $1 and type_id = 1 and name = $2", + &[&user.id, name, &Json(data)], + ) + .await + .ok(); + } } } pub async fn saveall(&self, client: &Client) -> bool { - let mut queries = Vec::new(); for &(_, _, ref user) in self.0.values() { - queries.push(async move { - let user = user.lock().await; - client + let user = user.lock().await; + for (name, data) in user.data.iter() { + if !client .execute( - "update user_data set data = $2 where user_id = $1 and type_id = 1", - &[&user.id, &Json(&user.data)], + "update user_data set data = $3 where user_id = $1 and type_id = 1 and name = $2", + &[&user.id, name, &Json(data)], ) .await - }); + .is_ok() + { + return false; + } + } } - futures::future::join_all(queries).await.into_iter().all(|x| x.is_ok()) + true } pub async fn store(&mut self, client: &Client, socks: &AsyncSocks) { diff --git a/src/sock.jsx b/src/sock.jsx index 1d89b27ad..40b8eb2f5 100644 --- a/src/sock.jsx +++ b/src/sock.jsx @@ -19,6 +19,9 @@ let socket = new WebSocket(endpoint), pvp = null, cmds = {}; const sockEvents = { + altadd(data) { + store.addAlt(data.name, data.data); + }, clear() { store.clearChat('Main'); }, @@ -54,7 +57,7 @@ const sockEvents = { typeof Notification !== 'undefined' && Notification.permission !== 'denied' && state.user && - ~data.msg.indexOf(state.user.name) && + ~data.msg.indexOf(state.username) && !document.hasFocus() ) { Notification.requestPermission().then(result => { @@ -118,13 +121,13 @@ const sockEvents = { ); }, foearena(data) { - const { user } = store.state; + const { username } = store.state; const game = new Game({ players: shuffle([ { idx: 1, - name: user.name, - user: user.name, + name: username, + user: username, deck: store.getDeck(), }, { @@ -252,11 +255,12 @@ export function emit(data) { } } export function userEmit(x, data = {}) { - const { user } = store.state; + const { username, auth, uname } = store.state; data.x = 'a'; data.z = x; - data.u = user.name; - data.a = user.auth; + data.u = username; + data.a = auth; + if (uname) data.uname = uname; emit(data); } export function userExec(x, data = {}) { @@ -282,7 +286,7 @@ export function sendChallenge(foe, orig = false, deckcheck = true) { set: orig ? 'Original' : undefined, deckcheck, }; - userEmit(orig ? 'foewant' : 'foewant', msg); + userEmit('foewant', msg); pvp = foe; } export function setCmds(c) { diff --git a/src/store.jsx b/src/store.jsx index 0abced449..51392b112 100644 --- a/src/store.jsx +++ b/src/store.jsx @@ -1,5 +1,5 @@ import { onCleanup } from 'solid-js'; -import { createStore, reconcile } from 'solid-js/store'; +import { createStore } from 'solid-js/store'; import * as usercmd from './usercmd.js'; import { changeMusic, changeSound } from './audio.js'; @@ -25,6 +25,7 @@ const listeners = new Set(); export let state = { nav: { view: () => null, props: undefined, key: 0 }, opts, + alts: {}, chat: new Map(), muted: new Set(), }; @@ -102,8 +103,36 @@ export function requiresGold(gold) { 'System', ); } -export function setUser(user) { - dispatch({ ...state, user }); +export function setAlt(uname) { + dispatch({ ...state, user: state.alts[uname ?? ''], uname }); +} +export function addAlt(uname, data) { + dispatch({ ...state, alts: { ...state.alts, [uname]: data } }); +} +export function rmAlt(uname) { + const alts = { ...state.alts }; + delete alts[uname]; + dispatch({ ...state, alts }); +} +export function setUser(username, auth, alts) { + dispatch({ + ...state, + user: alts[''] ?? {}, + alts, + username, + auth, + uname: null, + }); +} +export function logout() { + dispatch({ + ...state, + user: null, + alts: {}, + username: null, + auth: null, + uname: null, + }); } export function userCmd(cmd, data) { dispatch({ diff --git a/src/vanilla/views/MainMenu.jsx b/src/vanilla/views/MainMenu.jsx index 56b1360b5..a33010e7f 100644 --- a/src/vanilla/views/MainMenu.jsx +++ b/src/vanilla/views/MainMenu.jsx @@ -106,7 +106,7 @@ export default function OriginalMainMenu() { : 3, rematch: () => vsAi(level, cost, basereward, hpreward), players: shuffle([ - { idx: 1, name: rx.user.name, user: rx.user.name, deck: rx.orig.deck }, + { idx: 1, name: rx.username, user: rx.username, deck: rx.orig.deck }, { idx: 2, ai: 1, diff --git a/src/vanilla/views/Result.jsx b/src/vanilla/views/Result.jsx index 0a7746293..0bc613d4b 100644 --- a/src/vanilla/views/Result.jsx +++ b/src/vanilla/views/Result.jsx @@ -12,7 +12,7 @@ function exitFunc() { } export default function OriginalResult({ game }) { - const p1id = game.userId(store.state.user.name); + const p1id = game.userId(store.state.username); const cardswon = []; let electrumwon = null; diff --git a/src/views/Alts.jsx b/src/views/Alts.jsx new file mode 100644 index 000000000..1e5d2fbf9 --- /dev/null +++ b/src/views/Alts.jsx @@ -0,0 +1,82 @@ +import { userEmit } from '../sock.jsx'; +import ExitBtn from '../Components/ExitBtn.jsx'; +import * as store from '../store.jsx'; + +export default function Alts() { + const rx = store.useRx(); + + const flags = Object.create(null); + let altname, ele; + + return ( + <> + +
+ + + + + + element + + { + if (!altname.value) return; + const uflags = []; + for (const key in flags) { + if (flags[key].checked) { + uflags.push(key); + } + } + userEmit('altcreate', { + name: altname.value, + flags: uflags, + e: ele.value | 0, + }); + }} + /> +
+
+ store.setAlt(null)} /> +
+ + {name => { + if (!name) return null; + return ( +
+ {name} + { + store.setAlt(name); + }} + /> +  TODO put safeguards around Delete + { + store.rmAlt(name); + userEmit('altdelete', { name }); + }} + /> +
+ ); + }} +
+ + ); +} diff --git a/src/views/ArenaInfo.jsx b/src/views/ArenaInfo.jsx index a3b4e5b19..ff14691a9 100644 --- a/src/views/ArenaInfo.jsx +++ b/src/views/ArenaInfo.jsx @@ -123,7 +123,7 @@ function ArenaCard(props) { } export default function ArenaInfo() { - const user = store.useRx(state => state.user); + const rx = store.useRx(); const [AB, setAB] = createSignal({}); onMount(() => { sock.setCmds({ arenainfo: setAB }); @@ -143,10 +143,10 @@ export default function ArenaInfo() { - + - + {!!user.ocard && ( <> diff --git a/src/views/Bazaar.jsx b/src/views/Bazaar.jsx index cdef0ebde..ed4373e85 100644 --- a/src/views/Bazaar.jsx +++ b/src/views/Bazaar.jsx @@ -297,7 +297,7 @@ export default function Bazaar() { { setSell(sell); @@ -333,7 +333,7 @@ export default function Bazaar() { /> {showOrders() && ( { for (const group of groups()) { for (const player of group) { - if (player.user === rx.user.name) { + if (player.user === rx.username) { return player; } } @@ -338,9 +337,9 @@ export default function Challenge(props) { return null; }); - const amhost = () => rx.user.name === groups()[0][0].user; + const amhost = () => rx.username === groups()[0][0].user; const isMultiplayer = () => - groups().some(g => g.some(p => p.user && p.user !== rx.user.name)); + groups().some(g => g.some(p => p.user && p.user !== rx.username)); const allReady = () => amhost() && (!isMultiplayer() || groups().every(g => g.every(p => !p.pending))); @@ -380,7 +379,7 @@ export default function Challenge(props) { {(players, i) => ( groups().some(g => g.some(p => p.user === name)) } diff --git a/src/views/ElementSelect.jsx b/src/views/ElementSelect.jsx index dbb83f4d3..e7f72e425 100644 --- a/src/views/ElementSelect.jsx +++ b/src/views/ElementSelect.jsx @@ -58,12 +58,10 @@ export default function ElementSelect() { setErr( `Failed to register. Try a different username. Server response: ${data.err}`, ); - } else if (!data.accountbound && !data.pool) { - delete data.x; - store.setUser(data); + } else if (!data.data['']) { + store.setUser(data.name, data.auth, data.data); } else if (rx.user) { - delete data.x; - store.setUser(data); + store.setUser(data.name, data.auth, data.data); if (skiptut.checked) { store.doNav(import('./MainMenu.jsx')); } else { @@ -108,7 +106,7 @@ export default function ElementSelect() { type="button" value="Register" style="display:block" - onClick={e => { + onClick={() => { let errmsg = ''; username.value = username.value.trim(); if (!username.value) { @@ -135,7 +133,7 @@ export default function ElementSelect() { onClick={() => { if (rx.user) { userEmit('delete'); - store.setUser(null); + store.logout(); } store.setOpt('remember', false); store.doNav(store.Login); diff --git a/src/views/KongLogin.jsx b/src/views/KongLogin.jsx index 35fb62a6b..9c6a584b4 100644 --- a/src/views/KongLogin.jsx +++ b/src/views/KongLogin.jsx @@ -16,9 +16,8 @@ export default function KongLogin() { setCmds({ login: data => { if (!data.err) { - delete data.x; - setUser(data); - if (!data.accountbound && !data.pool) { + setUser(data.name, data.auth, data.data); + if (!data.data['']) { doNav(import('./ElementSelect.jsx')); } else { doNav(MainMenu); diff --git a/src/views/Login.jsx b/src/views/Login.jsx index 405f7c813..ef03f7d8a 100644 --- a/src/views/Login.jsx +++ b/src/views/Login.jsx @@ -4,7 +4,7 @@ import { emit, setCmds } from '../sock.jsx'; import * as store from '../store.jsx'; const MainMenu = import('./MainMenu.jsx'); -export default function Login(props) { +export default function Login() { const rx = store.useRx(); const [commit, setCommit] = createSignal(null); let password; @@ -27,12 +27,11 @@ export default function Login(props) { setCmds({ login: data => { if (!data.err) { - delete data.x; - store.setUser(data); + store.setUser(data.name, data.auth, data.data); if (rx.opts.remember && typeof localStorage !== 'undefined') { localStorage.auth = data.auth; } - if (!data.accountbound && !data.pool) { + if (!data.data['']) { store.doNav(import('./ElementSelect.jsx')); } else { store.setOptTemp('deck', store.getDeck()); @@ -98,7 +97,7 @@ export default function Login(props) { store.doNav(import('./ElementSelect.jsx'))} + onClick={() => store.doNav(import('./ElementSelect.jsx'))} style="position:absolute;left:430px;top:380px;width:100px" /> @@ -108,7 +107,7 @@ export default function Login(props) { rel="noopener" href={data().html_url} style="max-width:670px;min-width:470px;position:absolute;left:220px;top:460px;height:140px;white-space:pre-wrap;overflow-y:auto"> - {data().commit.message.replace('\n', '\n\n')} + {data().commit.message} )} diff --git a/src/views/MainMenu.jsx b/src/views/MainMenu.jsx index f8f1a384c..4af154fe4 100644 --- a/src/views/MainMenu.jsx +++ b/src/views/MainMenu.jsx @@ -38,7 +38,7 @@ const tipjar = [ 'Wealth T99 is a leaderboard for player wealth. Wealth is a combination of current gold & cardpool', ]; -function AiButton({ name, onClick, onMouseOver, y, lv }) { +function AiButton({ name, onClick, onMouseOver, lv }) { return (
Stats
-
{rx.user?.name}
+
{rx.username}
+ {rx.uname &&
{rx.uname}
}
{rx.user?.gold}
-
+
PvE {rx.user?.aiwins} - {rx.user?.ailosses}
-
+
PvP {rx.user?.pvpwins} - {rx.user?.pvplosses}
@@ -288,6 +289,14 @@ export default function MainMenu(props) { ]} />
+
+ store.doNav(import('./Alts.jsx'))} + onMouseOver={[setTip, 'Manage subaccounts']} + /> +
{showcard ? @@ -325,7 +334,6 @@ export default function MainMenu(props) { store.navGame(mkAi(0))} onMouseOver={[ @@ -335,7 +343,6 @@ export default function MainMenu(props) { /> store.navGame(mkPremade(1))} onMouseOver={[ @@ -345,14 +352,12 @@ export default function MainMenu(props) { /> store.navGame(mkAi(2))} onMouseOver={[setTip, 'Champions have some upgraded cards']} /> store.navGame(mkPremade(3))} onMouseOver={[ @@ -367,7 +372,6 @@ export default function MainMenu(props) { setTip, 'In the arena you will face decks from other players', ]} - y={144} lv={4} /> @@ -394,16 +397,18 @@ export default function MainMenu(props) { {`Deck: ${rx.user?.selectedDeck}`}
{quickslots}
- store.doNav(import('./Shop.jsx'))} - onMouseOver={[ - setTip, - 'Buy booster packs which contain cards from the elements you choose', - ]} - style="position:absolute;left:14px;top:132px" - /> + {!rx.user?.flags?.includes?.('no-shop') && ( + store.doNav(import('./Shop.jsx'))} + onMouseOver={[ + setTip, + 'Buy booster packs which contain cards from the elements you choose', + ]} + style="position:absolute;left:14px;top:132px" + /> + )} { - const name = foename() || rx.user.name; + const name = foename() || rx.username; if (name) store.doNav(import('./Library.jsx'), { name }); }} onMouseOver={[setTip, 'See exactly what cards you or others own']} diff --git a/src/views/Match.jsx b/src/views/Match.jsx index 6b591f52c..9d571357e 100644 --- a/src/views/Match.jsx +++ b/src/views/Match.jsx @@ -764,7 +764,7 @@ export default function Match(props) { props.replay ? replayhistory()[replayindex()] : tempgame() ?? pgame(); const [p1id, setPlayer1] = createSignal( - props.replay ? game().turn : game().userId(rx.user.name), + props.replay ? game().turn : game().userId(rx.username), ); const [p2id, setPlayer2] = createSignal(game().get_foe(p1id())); @@ -904,7 +904,7 @@ export default function Match(props) { forceUpdate(); if ( !iscmd && - game.data.players.some(pl => pl.user && pl.user !== rx.user.name) + game.data.players.some(pl => pl.user && pl.user !== rx.username) ) { userEmit('move', { id: props.gameid, @@ -1045,7 +1045,7 @@ export default function Match(props) { }); const newTurn = game.turn; if (newTurn !== turn) { - if (game.data.players[newTurn - 1].user === rx.user.name) { + if (game.data.players[newTurn - 1].user === rx.username) { setPlayer1(newTurn); } setFoeplays(foeplays => new Map(foeplays).set(newTurn, [])); @@ -1233,7 +1233,7 @@ export default function Match(props) { }; const isMultiplayer = game => - game.data.players.some(pl => pl.user && pl.user !== rx.user.name); + game.data.players.some(pl => pl.user && pl.user !== rx.username); const onkeydown = e => { if (e.target.tagName === 'TEXTAREA') return; diff --git a/src/views/Result.jsx b/src/views/Result.jsx index a8d8250b6..53775e18f 100644 --- a/src/views/Result.jsx +++ b/src/views/Result.jsx @@ -227,9 +227,8 @@ function computeBonuses(game, p1id, lefttext, streakrate, setTip, clearTip) { export default function Result(props) { const rx = store.useRx(); const { game } = props, - p1id = game.userId(rx.user.name); + p1id = game.userId(rx.username); const [tooltip, setTip] = createSignal(null); - const leftext = []; let goldreward = game.data.goldreward, cardreward = game.data.cardreward; diff --git a/src/views/WealthTop.jsx b/src/views/WealthTop.jsx index 5b5967858..982092cd1 100644 --- a/src/views/WealthTop.jsx +++ b/src/views/WealthTop.jsx @@ -3,7 +3,7 @@ import { emit, setCmds } from '../sock.jsx'; import ExitBtn from '../Components/ExitBtn.jsx'; import { doNav } from '../store.jsx'; -export default function WealthTop(props) { +export default function WealthTop() { const [getTop, setTop] = createSignal(null); onMount(() => {