703 lines
16 KiB
Markdown
703 lines
16 KiB
Markdown
# 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)
|
|
```sql
|
|
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)
|
|
```sql
|
|
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
|
|
```sql
|
|
-- Enforce character limits via application logic:
|
|
-- Free accounts: MAX 1 character
|
|
-- Premium accounts: MAX 10 characters
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Avatar System
|
|
|
|
### Avatar Data Structure (JSON)
|
|
```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`
|
|
```python
|
|
"""
|
|
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)
|
|
```json
|
|
{
|
|
"player_id": 1,
|
|
"exp": 1699564800
|
|
}
|
|
```
|
|
|
|
### New
|
|
```json
|
|
{
|
|
"account_id": 1,
|
|
"character_id": 5, // Set after character selection
|
|
"account_type": "web",
|
|
"is_premium": false,
|
|
"exp": 1699564800
|
|
}
|
|
```
|
|
|
|
**Flow:**
|
|
1. Login → Get token with `account_id`, no `character_id`
|
|
2. Select character → New token with `character_id`
|
|
3. All game endpoints require `character_id` in token
|
|
|
|
---
|
|
|
|
## 6. UI Changes Required
|
|
|
|
### A. Login/Register Screen Redesign
|
|
|
|
**Current:** Simple form
|
|
**New:** Modern authentication UI
|
|
|
|
```tsx
|
|
<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
|
|
|
|
```tsx
|
|
<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
|
|
|
|
```tsx
|
|
<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**
|
|
|
|
```typescript
|
|
// 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:**
|
|
```json
|
|
{
|
|
"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
|
|
```bash
|
|
# 1. Install Tauri CLI
|
|
cargo install tauri-cli
|
|
|
|
# 2. Create Tauri project
|
|
npm create tauri-app
|
|
|
|
# 3. Configure build
|
|
```
|
|
|
|
### tauri.conf.json
|
|
```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
|
|
```json
|
|
{
|
|
"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)
|
|
```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:
|
|
1. Restore database from backup
|
|
2. Revert code changes
|
|
3. Restart containers with old version
|
|
4. Investigate issue
|
|
5. Fix and retry
|
|
|
|
**Backup Strategy:**
|
|
```bash
|
|
# 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
|
|
|
|
1. **Review this plan** - Confirm approach
|
|
2. **Create detailed migration script** - Handle all edge cases
|
|
3. **Set up dev environment** - Test migration there first
|
|
4. **Implement Phase 1** - Database refactor
|
|
5. **Update authentication** - Email-based login
|
|
6. **Build UI screens** - Character selection/creation
|
|
7. **Integrate Steam** - Steamworks SDK
|
|
8. **Create Tauri build** - Desktop client
|
|
|
|
**Estimated Timeline:** 6 weeks full-time
|
|
|
|
**Do you want me to start implementing Phase 1 (database refactor)?**
|