# Steam Integration & Premium System Plan ## Overview Transform Echoes of the Ashes into a premium game with multiple distribution channels: - **Web Version**: Free trial with level cap, upgrade to premium - **Steam Version**: Full game, integrated authentication - **Standalone Executable**: Bundled client for Windows/Mac/Linux --- ## 1. Account System ### Account Types ```sql account_type ENUM('web', 'steam') ``` - **web**: Email/password registration, optional premium upgrade - **steam**: Auto-premium via Steam ownership verification ### Premium System ```sql premium_expires_at TIMESTAMP NULL ``` - **NULL** = Lifetime premium (Steam users, purchased premium) - **Timestamp** = Free trial expires at this time - **Non-premium restrictions**: - Level cap at 10 - No XP gain after level 10 - Full map access (level-gated naturally by difficulty) - Can play with premium users ### Database Schema ```sql CREATE TABLE players ( id SERIAL PRIMARY KEY, username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255), -- Required for web, NULL for steam password_hash VARCHAR(255), -- Required for web, NULL for steam steam_id VARCHAR(255) UNIQUE, -- Steam ID for steam users account_type VARCHAR(20) DEFAULT 'web', premium_expires_at TIMESTAMP NULL, -- NULL = premium forever -- ... existing fields ... ); ``` --- ## 2. Distribution Channels ### A. Web Version (Current) **Registration Flow:** ``` POST /api/auth/register { "username": "player123", "email": "player@example.com", // NEW: Required "password": "securepass" } ``` **Premium Upgrade:** ``` POST /api/payment/upgrade-premium { "payment_method": "stripe|paypal|crypto", "payment_token": "..." } ``` ### B. Steam Version **Architecture:** ``` [Steam Client] <-> [Steamworks API] <-> [Game Server] | [Steam Auth] ``` **Steam Authentication Flow:** 1. User launches game via Steam 2. Game requests Steam session ticket via Steamworks SDK 3. Client sends ticket to game server 4. Server validates ticket with Steam API 5. Server creates/logs in user with `steam_id` 6. Premium status = automatic (owns game on Steam) **Endpoints:** ``` POST /api/auth/steam/login { "steam_ticket": "...", "steam_id": "76561198..." } Response: { "access_token": "...", "player": {...}, "is_premium": true, // Always true for Steam "account_type": "steam" } ``` ### C. Standalone Executable **Two Approaches:** #### Option 1: Electron/Tauri App (Recommended) **Pros:** - Web technologies (reuse existing PWA) - Easy to bundle assets - Cross-platform (Windows, Mac, Linux) - Auto-updates - Local caching **Cons:** - Larger download size (~100-200MB with bundled assets) **Tech Stack:** - **Tauri** (Rust + Web) - smaller, more secure - **Electron** (Node + Web) - more mature, larger **Architecture:** ``` [Desktop App] ├── Bundled Assets/ │ ├── images/ (all icons, locations, NPCs) │ ├── data/ (items.json, npcs.json, etc.) │ └── sounds/ (future) ├── Local Cache/ │ └── user_data/ └── API Client -> Game Server ``` #### Option 2: Native Build (Godot/Unity) **Pros:** - True native performance - Better for future 3D/advanced graphics - Full offline capability **Cons:** - Complete rewrite - Different tech stack from web - More maintenance **Recommendation:** Start with **Tauri** for MVP --- ## 3. Asset Bundling Strategy ### Bundled Assets (Standalone) ``` app/assets/ ├── icons/ │ ├── items/ │ ├── ui/ │ ├── status/ │ └── actions/ ├── images/ │ ├── locations/ │ ├── npcs/ │ └── interactables/ ├── data/ │ ├── items.json │ ├── npcs.json │ ├── locations.json │ └── interactables.json └── sounds/ (future) ``` ### Caching Strategy **On First Launch:** 1. Check local cache version 2. If outdated, download latest assets 3. Store in local cache with version tag 4. Future launches use cache **Update Mechanism:** ```json { "version": "1.2.0", "assets": { "icons": "v1.2.0", "images": "v1.1.5", "data": "v1.2.0" }, "cdn_url": "https://cdn.echoesoftheash.com" } ``` **Hybrid Approach (Best):** 1. **Bundle core assets** (~50MB): - Essential UI icons - Starting location images - Common item icons 2. **Lazy load on demand**: - High-level location images - Rare item icons - NPC portraits 3. **Cache everything locally** 4. **Check for updates daily** --- ## 4. Steam Integration Technical Details ### Steamworks SDK Integration **1. Setup:** ```bash # Download Steamworks SDK # https://partner.steamgames.com/ # Install in project steamworks-sdk/ ├── sdk/ │ ├── public/ │ │ └── steam/ │ │ └── steam_api.h │ └── redistributable_bin/ │ ├── steam_api.dll (Windows) │ ├── libsteam_api.so (Linux) │ └── libsteam_api.dylib (Mac) └── tools/ ``` **2. Client-Side (Game Launch):** ```cpp // Initialize Steam API if (!SteamAPI_Init()) { return ERROR_STEAM_NOT_RUNNING; } // Get Steam ID CSteamID steamID = SteamUser()->GetSteamID(); uint64 steamID64 = steamID.ConvertToUint64(); // Request auth ticket HAuthTicket hAuthTicket; uint8 rgubTicket[1024]; uint32 pcbTicket; hAuthTicket = SteamUser()->GetAuthSessionTicket( rgubTicket, sizeof(rgubTicket), &pcbTicket ); // Convert to hex string and send to server std::string ticket = BytesToHex(rgubTicket, pcbTicket); ``` **3. Server-Side Validation:** ```python import requests async def validate_steam_ticket(steam_id: str, ticket: str) -> bool: """Validate Steam auth ticket with Steam API""" url = "https://api.steampowered.com/ISteamUserAuth/AuthenticateUserTicket/v1/" params = { "key": STEAM_WEB_API_KEY, # Get from Steamworks Partner "appid": YOUR_STEAM_APP_ID, "ticket": ticket } response = requests.get(url, params=params) data = response.json() if data['response']['params']['result'] == 'OK': validated_steam_id = data['response']['params']['steamid'] return validated_steam_id == steam_id return False ``` ### Steam Build Configuration **1. Depot Configuration** (`depot_build_1001.vdf`): ```vdf "DepotBuildConfig" { "DepotID" "1001" "ContentRoot" "..\build\steam\" "FileMapping" { "LocalPath" "*" "DepotPath" "." "recursive" "1" } "FileExclusion" "*.pdb" } ``` **2. App Build Script** (`app_build_1000.vdf`): ```vdf "AppBuild" { "AppID" "1000" // Your Steam App ID "Desc" "Echoes of the Ashes Build" "BuildOutput" "..\output\" "ContentRoot" "..\build\" "SetLive" "default" "Depots" { "1001" "depot_build_1001.vdf" } } ``` **3. Upload Script:** ```bash #!/bin/bash # build_and_upload_steam.sh # Build the game npm run build:steam # Upload to Steam steamcmd +login $STEAM_USERNAME +run_app_build app_build_1000.vdf +quit ``` --- ## 5. Build Variants ### Configuration System **config/builds.json:** ```json { "web": { "api_url": "https://api.echoesoftheash.com", "bundled_assets": false, "steam_enabled": false, "premium_required": false }, "steam": { "api_url": "https://api.echoesoftheash.com", "bundled_assets": true, "steam_enabled": true, "premium_required": false, // Validated by ownership "app_id": "1000000" }, "standalone": { "api_url": "https://api.echoesoftheash.com", "bundled_assets": true, "steam_enabled": false, "premium_required": false, // Check via API "auto_update": true } } ``` ### Build Commands ```json { "scripts": { "build:web": "BUILD_TARGET=web vite build", "build:steam": "BUILD_TARGET=steam tauri build --target steam", "build:standalone": "BUILD_TARGET=standalone tauri build" } } ``` --- ## 6. Premium Enforcement ### XP Gain Restriction ```python async def award_xp(player_id: int, xp_amount: int): player = await db.get_player_by_id(player_id) # Check premium status is_premium = await is_player_premium(player) if not is_premium and player['level'] >= 10: return { "success": False, "message": "Free trial limited to level 10. Upgrade to premium to continue!", "xp_gained": 0, "upgrade_url": "/premium/upgrade" } # Award XP normally new_xp = player['xp'] + xp_amount await db.update_player(player_id, xp=new_xp) # ... level up logic ... ``` ### Helper Functions ```python async def is_player_premium(player: dict) -> bool: """Check if player has premium access""" # Steam users are always premium if player['account_type'] == 'steam': return True # NULL = lifetime premium if player['premium_expires_at'] is None: # Check if they ever had premium (distinguish from never-premium) # Could add a 'premium_granted_at' field return False # Or check another field # Check if premium expired from datetime import datetime if player['premium_expires_at'] > datetime.utcnow(): return True return False async def grant_premium(player_id: int, duration_days: int = None): """Grant premium access to a player""" if duration_days is None: # Lifetime premium await db.update_player(player_id, premium_expires_at=None) else: # Time-limited premium from datetime import datetime, timedelta expires_at = datetime.utcnow() + timedelta(days=duration_days) await db.update_player(player_id, premium_expires_at=expires_at) ``` --- ## 7. Implementation Roadmap ### Phase 1: Foundation (Current Sprint) - [x] Add database columns (steam_id, email, premium_expires_at, account_type) - [ ] Update registration to require email - [ ] Add premium check helper functions - [ ] Implement XP gain restriction for non-premium level 10+ - [ ] Create icon folder structure ### Phase 2: Premium System (Next Sprint) - [ ] Payment integration (Stripe/PayPal) - [ ] Premium upgrade endpoint - [ ] Premium status UI indicators - [ ] Email verification system - [ ] Password reset flow ### Phase 3: Steam Integration - [ ] Set up Steamworks partner account - [ ] Integrate Steamworks SDK - [ ] Implement Steam authentication - [ ] Create Steam build pipeline - [ ] Test Steam authentication flow ### Phase 4: Standalone Client - [ ] Choose framework (Tauri recommended) - [ ] Set up project structure - [ ] Implement asset bundling - [ ] Add auto-update mechanism - [ ] Create installers (Windows, Mac, Linux) ### Phase 5: Polish & Launch - [ ] Replace emojis with custom icons - [ ] Optimize asset loading - [ ] Add caching layer - [ ] Performance testing - [ ] Beta testing - [ ] Official launch --- ## 8. Recommended Tech Stack ### For Standalone Executable: **Tauri** **Why Tauri:** - Smaller bundle size (~3-5MB vs 100MB+ for Electron) - Uses system WebView (Chromium on Windows, Safari on Mac) - Rust backend = better security - Built-in auto-updater - Native system integration - Active development **Project Structure:** ``` echoes-desktop/ ├── src-tauri/ # Rust backend │ ├── src/ │ │ ├── main.rs # Entry point │ │ ├── steam.rs # Steam integration │ │ └── storage.rs # Local storage │ ├── icons/ │ └── tauri.conf.json ├── src/ # Frontend (reuse existing PWA) │ ├── components/ │ ├── hooks/ │ └── main.tsx ├── assets/ # Bundled assets │ ├── icons/ │ ├── images/ │ └── data/ └── package.json ``` **Installation:** ```bash npm create tauri-app # Choose: React + TypeScript ``` --- ## 9. Cost Estimates ### Development - Steam Steamworks SDK: **Free** (requires $100 one-time app submission fee) - Payment Processing: **2.9% + $0.30 per transaction** (Stripe) - CDN for assets: **~$5-20/month** (CloudFlare, AWS CloudFront) ### Infrastructure - Current server: **Existing** - Steam bandwidth: **Free** (Valve covers) - Asset CDN: **$10-50/month** (depending on traffic) --- ## 10. Monetization Strategy ### Pricing Options **Option A: One-time Purchase** - **$9.99** - Full game unlock - No subscription, lifetime access - Recommended for indie games **Option B: Freemium with Optional Premium** - **Free**: Level 1-10, unlimited play - **$4.99**: Full unlock - Easier onboarding **Option C: Steam-Only Paid** - **$14.99** on Steam (full game) - Free web version with level cap - Drive Steam sales **Recommendation:** **Option C** - Premium on Steam, free trial on web --- ## Next Immediate Steps 1. **Update registration endpoint** (add email requirement) 2. **Add premium helper functions** to codebase 3. **Implement XP restriction** for level 10+ free users 4. **Create payment integration** (Stripe) 5. **Design icon system** and start replacing emojis 6. **Set up Steamworks partner account** (long lead time) 7. **Prototype Tauri desktop app** (weekend project) Would you like me to start implementing any specific part of this plan?