16 KiB
16 KiB
Account & Player Separation - Major Refactor Plan
Overview
Separate authentication (accounts) from gameplay (characters/players) to support:
- Multiple characters per account
- Free tier: 1 character
- Premium tier: Up to 10 characters
- Character customization at creation
- Email-based login (no username)
1. New Database Schema
Accounts Table (Authentication)
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255), -- NULL for Steam/OAuth
steam_id VARCHAR(255) UNIQUE, -- Steam integration
account_type VARCHAR(20) DEFAULT 'web', -- 'web', 'steam'
premium_expires_at TIMESTAMP, -- NULL = lifetime premium
email_verified BOOLEAN DEFAULT FALSE,
email_verification_token VARCHAR(255),
password_reset_token VARCHAR(255),
password_reset_expires TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP,
CONSTRAINT check_account_type CHECK (account_type IN ('web', 'steam'))
);
CREATE INDEX idx_accounts_email ON accounts(email);
CREATE INDEX idx_accounts_steam_id ON accounts(steam_id);
Characters Table (Gameplay)
CREATE TABLE characters (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
name VARCHAR(100) UNIQUE NOT NULL, -- Character name (unique across all players)
avatar_data TEXT, -- JSON for avatar customization
-- RPG Stats
level INTEGER DEFAULT 1,
xp INTEGER DEFAULT 0,
hp INTEGER DEFAULT 100,
max_hp INTEGER DEFAULT 100,
stamina INTEGER DEFAULT 100,
max_stamina INTEGER DEFAULT 100,
-- Base Attributes (start with 0, player allocates 20 points)
strength INTEGER DEFAULT 0,
agility INTEGER DEFAULT 0,
endurance INTEGER DEFAULT 0,
intellect INTEGER DEFAULT 0,
unspent_points INTEGER DEFAULT 20, -- Initial stat points to allocate
-- Game State
location_id VARCHAR(255) DEFAULT 'cabin',
is_dead BOOLEAN DEFAULT FALSE,
last_movement_time REAL DEFAULT 0,
-- Timestamps
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT check_unspent_points CHECK (unspent_points >= 0)
);
CREATE INDEX idx_characters_account_id ON characters(account_id);
CREATE INDEX idx_characters_name ON characters(name);
CREATE INDEX idx_characters_location_id ON characters(location_id);
Character Limits
-- Enforce character limits via application logic:
-- Free accounts: MAX 1 character
-- Premium accounts: MAX 10 characters
2. Avatar System
Avatar Data Structure (JSON)
{
"preset": "warrior", // Optional preset
"body": {
"skin_tone": "#f5d6c6",
"build": "athletic" // slim, athletic, heavy
},
"hair": {
"style": "short",
"color": "#3d2817"
},
"face": {
"eyes": "blue",
"facial_hair": "none"
},
"equipped_display": {
"helmet": "iron_helmet", // Shows equipped items on avatar
"armor": "leather_chest",
"weapon": "iron_sword"
}
}
Avatar Options
Phase 1 (MVP): Simple presets
- 10 preset avatars (warrior, mage, rogue, etc.)
- Color variations
Phase 2 (Future): Dynamic avatar
- Shows equipped armor/weapons
- Customizable features
- Level-based cosmetic unlocks
3. Migration Script
migrate_account_player_separation.py
"""
Major migration: Separate accounts from characters
1. Create accounts table
2. Create characters table
3. Migrate existing players to new structure
4. Update all foreign keys
5. Drop old players table (after backup)
"""
Steps:
1. Backup current players table
2. Create accounts table
3. For each existing player:
- Create account with email (generate if missing)
- Create character from player data
- Migrate inventory, equipment, stats, etc.
4. Update all foreign key references:
- inventory.player_id -> character_id
- equipment.player_id -> character_id
- dropped_items references
- combat references
- etc.
5. Test thoroughly
6. Drop old players table
4. Authentication Flow Changes
Registration (Email-based)
POST /api/auth/register
{
"email": "player@example.com",
"password": "securepass"
}
Response:
{
"access_token": "...",
"account": {
"id": 1,
"email": "player@example.com",
"account_type": "web",
"is_premium": false,
"characters": [] // Empty on first register
},
"needs_character_creation": true
}
Login (Email-based)
POST /api/auth/login
{
"email": "player@example.com",
"password": "securepass"
}
Response:
{
"access_token": "...",
"account": {...},
"characters": [
{
"id": 1,
"name": "Aragorn",
"level": 15,
"avatar_data": {...},
"last_played_at": "2025-11-09T..."
}
]
}
Character Selection
POST /api/character/select
{
"character_id": 1
}
Response:
{
"character": {...full character data...},
"location": {...},
"inventory": [...],
"equipment": {...}
}
Character Creation
POST /api/character/create
{
"name": "Aragorn",
"avatar": {
"preset": "warrior",
...
},
"stats": {
"strength": 8,
"agility": 5,
"endurance": 4,
"intellect": 3
} // Must total 20 points
}
Validation:
- Free users: Check character count < 1
- Premium users: Check character count < 10
- Name must be unique
- Stats must total exactly 20
5. JWT Token Structure
Old (Current)
{
"player_id": 1,
"exp": 1699564800
}
New
{
"account_id": 1,
"character_id": 5, // Set after character selection
"account_type": "web",
"is_premium": false,
"exp": 1699564800
}
Flow:
- Login → Get token with
account_id, nocharacter_id - Select character → New token with
character_id - All game endpoints require
character_idin token
6. UI Changes Required
A. Login/Register Screen Redesign
Current: Simple form New: Modern authentication UI
<AuthScreen>
<Tabs>
<Tab label="Login">
<EmailInput />
<PasswordInput />
<Button>Login</Button>
<Link>Forgot Password?</Link>
</Tab>
<Tab label="Register">
<EmailInput />
<PasswordInput />
<PasswordConfirmInput />
<Checkbox>I agree to Terms</Checkbox>
<Button>Create Account</Button>
</Tab>
</Tabs>
<Divider />
<SteamLoginButton /> // Future
</AuthScreen>
Design:
- Dark fantasy theme
- Animated background (subtle fire/ash effects)
- Elden Ring / Dark Souls inspired
- Responsive (mobile-first)
B. Character Selection Screen
<CharacterSelection>
<Header>
<AccountInfo email={account.email} />
<PremiumBadge if={isPremium} />
</Header>
<CharacterGrid>
{characters.map(char => (
<CharacterCard
key={char.id}
name={char.name}
level={char.level}
avatar={char.avatar_data}
lastPlayed={char.last_played_at}
onClick={() => selectCharacter(char.id)}
/>
))}
{canCreateMore && (
<CreateCharacterCard
onClick={() => setShowCreation(true)}
/>
)}
</CharacterGrid>
{!isPremium && characters.length >= 1 && (
<UpgradeBanner>
Upgrade to Premium for 9 more character slots!
</UpgradeBanner>
)}
</CharacterSelection>
C. Character Creation Screen
<CharacterCreation>
<Step1_Name>
<Input
placeholder="Enter character name"
validation={checkNameUnique}
/>
</Step1_Name>
<Step2_Avatar>
<AvatarPreview avatar={selectedAvatar} />
<AvatarPresets>
{presets.map(preset => (
<PresetCard
key={preset.id}
image={preset.thumbnail}
label={preset.name}
onClick={() => setAvatar(preset)}
/>
))}
</AvatarPresets>
</Step2_Avatar>
<Step3_Stats>
<StatAllocator
remaining={pointsRemaining}
stats={stats}
onAllocate={(stat, amount) => allocateStat(stat, amount)}
/>
<StatsPreview>
<Stat name="Strength" value={stats.strength} />
<Stat name="Agility" value={stats.agility} />
<Stat name="Endurance" value={stats.endurance} />
<Stat name="Intellect" value={stats.intellect} />
</StatsPreview>
<PointsRemaining>{pointsRemaining} / 20</PointsRemaining>
</Step3_Stats>
<Actions>
<Button onClick={handleBack}>Back</Button>
<Button
onClick={handleCreate}
disabled={!isValid}
primary
>
Create Character
</Button>
</Actions>
</CharacterCreation>
7. Steam Integration Specifics
Do You Need Two Executables?
Answer: NO, one executable with runtime detection
// At app startup
const config = {
isSteam: checkSteamRuntime(), // Detect Steam overlay
apiUrl: process.env.API_URL || 'https://api.game.com',
steamAppId: process.env.STEAM_APP_ID
};
if (config.isSteam) {
// Initialize Steamworks
await initSteamworks();
// Auto-login with Steam
const steamTicket = await getSteamAuthTicket();
const authResponse = await api.post('/api/auth/steam/login', {
steam_ticket: steamTicket
});
// Skip email/password login, go straight to character selection
} else {
// Show email/password login
}
Build Configuration:
{
"builds": {
"web": {
"platform": "web",
"steamworks": false
},
"steam-windows": {
"platform": "windows",
"steamworks": true,
"steam_app_id": "1000000"
},
"steam-linux": {
"platform": "linux",
"steamworks": true
},
"standalone-windows": {
"platform": "windows",
"steamworks": false
}
}
}
8. Tauri Build Setup
Project Structure
echoes-desktop/
├── src-tauri/
│ ├── src/
│ │ ├── main.rs
│ │ ├── steam.rs # Steamworks integration
│ │ ├── auth.rs # Authentication logic
│ │ └── storage.rs # Local storage/cache
│ ├── icons/
│ ├── Cargo.toml
│ └── tauri.conf.json
├── src/ # Frontend (React)
│ ├── components/
│ ├── screens/
│ │ ├── Auth.tsx # Login/Register
│ │ ├── CharacterSelect.tsx
│ │ ├── CharacterCreate.tsx
│ │ └── Game.tsx
│ └── main.tsx
├── assets/ # Bundled assets
└── package.json
Installation Steps
# 1. Install Tauri CLI
cargo install tauri-cli
# 2. Create Tauri project
npm create tauri-app
# 3. Configure build
tauri.conf.json
{
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:5173",
"distDir": "../dist"
},
"package": {
"productName": "Echoes of the Ashes",
"version": "1.0.0"
},
"tauri": {
"allowlist": {
"all": false,
"fs": {
"scope": ["$APPDATA/echoes-of-ashes/*"]
},
"http": {
"scope": ["https://api.echoesoftheash.com/*"]
}
},
"bundle": {
"active": true,
"targets": ["msi", "app", "deb"], // Windows, Mac, Linux
"identifier": "com.echoesoftheash.game",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": ["assets/*"], // Bundle game assets
"externalBin": ["bin/steamworks"], // Steam DLL
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": "default-src 'self'; connect-src 'self' https://api.echoesoftheash.com"
},
"updater": {
"active": true,
"endpoints": [
"https://releases.echoesoftheash.com/{{target}}/{{current_version}}"
],
"dialog": true,
"pubkey": "YOUR_PUBLIC_KEY"
}
}
}
Build Commands
{
"scripts": {
"dev": "vite",
"build": "vite build",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build",
"tauri:build:steam": "STEAM_ENABLED=true tauri build",
"tauri:build:standalone": "STEAM_ENABLED=false tauri build"
}
}
Steamworks Integration (Rust)
// src-tauri/src/steam.rs
use steamworks::Client;
pub struct SteamManager {
client: Option<Client>,
}
impl SteamManager {
pub fn new(app_id: u32) -> Result<Self, String> {
match Client::init_app(app_id) {
Ok((client, _single)) => {
Ok(Self { client: Some(client) })
}
Err(e) => Err(format!("Failed to init Steam: {:?}", e))
}
}
pub fn get_steam_id(&self) -> Option<u64> {
self.client.as_ref().map(|c| {
c.user().steam_id().raw()
})
}
pub fn get_auth_session_ticket(&self) -> Option<Vec<u8>> {
// Implementation
None
}
}
9. Implementation Phases
Phase 1: Database Refactor (Week 1)
- Create migration script
- Test migration on dev database
- Create accounts + characters tables
- Migrate existing data
- Update all FK references
- Test thoroughly
Phase 2: Auth System (Week 1-2)
- Email-based login/register
- JWT with account_id + character_id
- Character selection endpoint
- Character creation endpoint
- Character limit enforcement
Phase 3: UI Redesign (Week 2-3)
- New login/register screen
- Character selection screen
- Character creation screen
- Avatar system (presets)
- Stat allocation UI
Phase 4: Steam Integration (Week 3-4)
- Set up Steamworks SDK
- Steam authentication backend
- Steam auto-login flow
- Test on Steam
Phase 5: Tauri Desktop (Week 4-5)
- Set up Tauri project
- Asset bundling
- Build pipeline
- Steam runtime detection
- Auto-updater
- Test builds (Win/Mac/Linux)
Phase 6: Testing & Polish (Week 5-6)
- End-to-end testing
- Performance optimization
- Bug fixes
- Documentation
- Beta release
10. Breaking Changes & Risks
Database
- MAJOR: Complete schema change
- Risk: Data loss if migration fails
- Mitigation: Full backup before migration, rollback plan
Authentication
- MAJOR: Login now uses email, not username
- Risk: Existing users can't login
- Mitigation: Send email to all users about change
API
- MAJOR: Most endpoints change from player_id to character_id
- Risk: All API clients break
- Mitigation: Version API (v2), deprecate v1
Frontend
- MAJOR: Complete auth flow redesign
- Risk: UX confusion
- Mitigation: Tutorial on first login after update
11. Rollback Plan
If migration fails:
- Restore database from backup
- Revert code changes
- Restart containers with old version
- Investigate issue
- Fix and retry
Backup Strategy:
# Before migration
docker exec echoes_of_the_ashes_db pg_dump -U postgres gamedb > backup_$(date +%Y%m%d).sql
# Restore if needed
docker exec -i echoes_of_the_ashes_db psql -U postgres gamedb < backup_20251109.sql
Next Steps
- Review this plan - Confirm approach
- Create detailed migration script - Handle all edge cases
- Set up dev environment - Test migration there first
- Implement Phase 1 - Database refactor
- Update authentication - Email-based login
- Build UI screens - Character selection/creation
- Integrate Steam - Steamworks SDK
- Create Tauri build - Desktop client
Estimated Timeline: 6 weeks full-time
Do you want me to start implementing Phase 1 (database refactor)?