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(