From a2bf58a6276ef0736c047df51ad8e3a3e72a2fda Mon Sep 17 00:00:00 2001 From: WiseDev <83840010+wisedevik@users.noreply.github.com> Date: Sun, 23 Aug 2026 08:54:43 +0300 Subject: [PATCH] stop minting a new account on every login if the client presents an id we don't know, create it under that id and bump the sequence past it. also chest offers match through BaseChest, so one silver row covers every arena. --- crates/auth-service/src/service.rs | 11 ++++++- crates/auth-service/src/store/postgres.rs | 35 +++++++++++++++++++++++ crates/game-service/src/home_mode.rs | 2 +- crates/game-service/src/shop/catalog.rs | 10 ++++++- crates/logic/src/data/typed.rs | 31 ++++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/crates/auth-service/src/service.rs b/crates/auth-service/src/service.rs index bc90f31..038eac1 100644 --- a/crates/auth-service/src/service.rs +++ b/crates/auth-service/src/service.rs @@ -69,7 +69,7 @@ impl AuthApi for AuthService { message, }); } - let stored = if account.is_zero() || pass_token.is_none() { + let stored = if account.is_zero() { None } else { self.store.get(account).await.map_err(unavailable)? @@ -92,6 +92,15 @@ impl AuthApi for AuthService { } existing } + None if !account.is_zero() => { + let adopted = self + .store + .adopt(account, pass_token.as_deref()) + .await + .map_err(unavailable)?; + tracing::info!(account = %adopted.account_ref(), "adopted the account the client asked for"); + adopted + } None => { let created = self.store.create().await.map_err(unavailable)?; tracing::info!(account = %created.account_ref(), "created account"); diff --git a/crates/auth-service/src/store/postgres.rs b/crates/auth-service/src/store/postgres.rs index 1188da9..b2af3a5 100644 --- a/crates/auth-service/src/store/postgres.rs +++ b/crates/auth-service/src/store/postgres.rs @@ -41,6 +41,41 @@ impl AccountStore { .await?; Ok(account_from_row(&row)) } + pub async fn adopt(&self, account: AccountRef, pass_token: Option<&str>) -> Result { + let now = unix_seconds(); + let token = pass_token + .map(str::to_owned) + .filter(|token| !token.is_empty()) + .unwrap_or_else(generate_pass_token); + let row = storage::sqlx::query( + "insert into accounts ( + account_high, account_low, pass_token, created_at, last_seen_at, + session_count, play_time_seconds, banned + ) + values ($1, $2, $3, $4, $4, 0, 0, false) + on conflict (account_high, account_low) do nothing + returning *", + ) + .bind(account.high) + .bind(account.low) + .bind(token) + .bind(now) + .fetch_optional(self.database.pool()) + .await?; + storage::sqlx::query( + "select setval('accounts_low_seq', greatest($1, (select last_value from accounts_low_seq)))", + ) + .bind(account.low.max(1) as i64) + .execute(self.database.pool()) + .await?; + match row { + Some(row) => Ok(account_from_row(&row)), + None => self + .get(account) + .await? + .ok_or_else(|| storage::StorageError::corrupt("the account vanished mid adoption")), + } + } pub async fn get(&self, account: AccountRef) -> Result> { let row = storage::sqlx::query( "select * from accounts where account_high = $1 and account_low = $2", diff --git a/crates/game-service/src/home_mode.rs b/crates/game-service/src/home_mode.rs index adf589d..99c9f05 100644 --- a/crates/game-service/src/home_mode.rs +++ b/crates/game-service/src/home_mode.rs @@ -66,7 +66,7 @@ impl HomeMode { } fn market_cost(&self, give: &ShopEntry) -> Result { if let Some(chest) = give.data.as_treasure_chest() { - let price = chest.int("ShopPriceWithoutSpeedUp"); + let price = chest.shop_price(); if price < 1 { return Err("that chest is not sold in the shop"); } diff --git a/crates/game-service/src/shop/catalog.rs b/crates/game-service/src/shop/catalog.rs index 8fa9377..36aacce 100644 --- a/crates/game-service/src/shop/catalog.rs +++ b/crates/game-service/src/shop/catalog.rs @@ -11,7 +11,15 @@ pub struct ShopOffer { } impl ShopOffer { pub fn sells(&self, data: &logic::LogicDataRef) -> bool { - &self.give.data == data + if &self.give.data == data { + return true; + } + match (self.give.data.as_treasure_chest(), data.as_treasure_chest()) { + (Some(offered), Some(wanted)) => { + offered.root().global_id() == wanted.root().global_id() + } + _ => false, + } } } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/crates/logic/src/data/typed.rs b/crates/logic/src/data/typed.rs index f7659c2..6c03be1 100644 --- a/crates/logic/src/data/typed.rs +++ b/crates/logic/src/data/typed.rs @@ -47,6 +47,37 @@ impl LogicArenaData { self.int("Arena") } } +impl LogicTreasureChestData { + pub fn base_chest(&self) -> Option { + let base = self.string("BaseChest"); + if base.is_empty() || base == self.name() { + return None; + } + LogicDataRef::by_name(crate::data::tables::table::TREASURE_CHESTS, base).as_treasure_chest() + } + pub fn root(&self) -> LogicTreasureChestData { + match self.base_chest() { + Some(base) => base.root(), + None => self.clone(), + } + } + pub fn arena_data(&self) -> LogicDataRef { + LogicDataRef::by_name(crate::data::tables::table::ARENAS, self.string("Arena")) + } + pub fn shop_price(&self) -> i32 { + self.root().int("ShopPriceWithoutSpeedUp") + } + pub fn speed_up_cost(&self) -> i32 { + self.root().int("SpeedUpCost") + } + pub fn unlock_seconds(&self) -> i32 { + let root = self.root(); + root.int("TimeTakenDays").saturating_mul(86_400) + + root.int("TimeTakenHours").saturating_mul(3_600) + + root.int("TimeTakenMinutes").saturating_mul(60) + + root.int("TimeTakenSeconds") + } +} impl LogicResourcePackData { pub fn resource(&self) -> LogicDataRef { LogicDataRef::by_name(