use std::path::PathBuf; use serde::{Deserialize, Serialize}; use storage::DatabaseConfig; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum DataSelector { Named(String), Instance(i32), } impl DataSelector { pub fn named(name: impl Into) -> Self { DataSelector::Named(name.into()) } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum CardRef { Named(String), Explicit { table: i32, instance: i32 }, } impl CardRef { pub const fn explicit(table: i32, instance: i32) -> Self { CardRef::Explicit { table, instance } } pub fn named(name: impl Into) -> Self { CardRef::Named(name.into()) } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct StarterProfile { pub name_prefix: String, pub exp_level: i32, pub exp_points: i32, pub gold: i32, pub diamonds: i32, pub free_diamonds: i32, pub trophies: i32, pub npc_win_count: i32, pub npc_lose_count: i32, pub arena: DataSelector, pub gold_resource: DataSelector, pub chest_slot_count: usize, pub deck: Vec, pub extra_collection: Vec, pub card_level_index: i32, pub card_count: i32, pub finish_tutorials: bool, } impl Default for StarterProfile { fn default() -> Self { Self { name_prefix: "Scroll".to_owned(), exp_level: 1, exp_points: 0, gold: 5_000, diamonds: 1_000, free_diamonds: 0, trophies: 0, npc_win_count: 1, npc_lose_count: 0, arena: DataSelector::Named("Arena1".to_owned()), gold_resource: DataSelector::Named("Gold".to_owned()), chest_slot_count: 4, deck: (0..8) .map(|instance| CardRef::explicit(logic::table::SPELLS_CHARACTERS, instance)) .collect(), extra_collection: (8..16) .map(|instance| CardRef::explicit(logic::table::SPELLS_CHARACTERS, instance)) .collect(), card_level_index: 0, card_count: 10, finish_tutorials: true, } } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ChestRewardConfig { pub gold_min: i32, pub gold_max: i32, pub diamonds: i32, pub different_cards: usize, pub copies_per_card: i32, } impl Default for ChestRewardConfig { fn default() -> Self { Self { gold_min: 15, gold_max: 45, diamonds: 0, different_cards: 2, copies_per_card: 3, } } } #[derive(Debug, Clone)] pub struct GameConfig { pub database: Option, pub listen: String, pub csv_root: Option, pub shop_path: Option, pub starter: StarterProfile, pub chest_reward: ChestRewardConfig, pub random_seed: i32, } impl Default for GameConfig { fn default() -> Self { Self { database: None, listen: "127.0.0.1:9402".to_owned(), csv_root: Some(PathBuf::from("assets")), shop_path: Some(PathBuf::from("config/shop.json")), starter: StarterProfile::default(), chest_reward: ChestRewardConfig::default(), random_seed: 0x5EED, } } } impl GameConfig { pub fn from_env() -> Self { let defaults = Self::default(); let starter = match env_string("SCROLL_STARTER_PROFILE") { Some(path) => std::fs::read(&path) .ok() .and_then(|bytes| serde_json::from_slice(&bytes).ok()) .unwrap_or(defaults.starter), None => defaults.starter, }; Self { database: DatabaseConfig::from_env(), listen: env_string("SCROLL_GAME_LISTEN").unwrap_or(defaults.listen), csv_root: env_string("SCROLL_CSV_ROOT") .map(PathBuf::from) .or(defaults.csv_root), shop_path: env_string("SCROLL_SHOP") .map(PathBuf::from) .or(defaults.shop_path), starter, chest_reward: defaults.chest_reward, random_seed: env_string("SCROLL_RANDOM_SEED") .and_then(|value| value.parse().ok()) .unwrap_or(defaults.random_seed), } } } fn env_string(key: &str) -> Option { std::env::var(key).ok().filter(|value| !value.is_empty()) }