Files
echoes-of-the-ash/old/STEAM_AND_PREMIUM_PLAN.md
2025-11-27 16:27:01 +01:00

565 lines
13 KiB
Markdown

# 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?