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

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:

  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

<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:

  1. Restore database from backup
  2. Revert code changes
  3. Restart containers with old version
  4. Investigate issue
  5. 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

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