565 lines
13 KiB
Markdown
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?
|