13 KiB
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
account_type ENUM('web', 'steam')
- web: Email/password registration, optional premium upgrade
- steam: Auto-premium via Steam ownership verification
Premium System
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
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:
- User launches game via Steam
- Game requests Steam session ticket via Steamworks SDK
- Client sends ticket to game server
- Server validates ticket with Steam API
- Server creates/logs in user with
steam_id - 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:
- Check local cache version
- If outdated, download latest assets
- Store in local cache with version tag
- Future launches use cache
Update Mechanism:
{
"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):
-
Bundle core assets (~50MB):
- Essential UI icons
- Starting location images
- Common item icons
-
Lazy load on demand:
- High-level location images
- Rare item icons
- NPC portraits
-
Cache everything locally
-
Check for updates daily
4. Steam Integration Technical Details
Steamworks SDK Integration
1. Setup:
# 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):
// 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:
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):
"DepotBuildConfig"
{
"DepotID" "1001"
"ContentRoot" "..\build\steam\"
"FileMapping"
{
"LocalPath" "*"
"DepotPath" "."
"recursive" "1"
}
"FileExclusion" "*.pdb"
}
2. App Build Script (app_build_1000.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:
#!/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:
{
"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
{
"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
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
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)
- 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:
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
- Update registration endpoint (add email requirement)
- Add premium helper functions to codebase
- Implement XP restriction for level 10+ free users
- Create payment integration (Stripe)
- Design icon system and start replacing emojis
- Set up Steamworks partner account (long lead time)
- Prototype Tauri desktop app (weekend project)
Would you like me to start implementing any specific part of this plan?