# Game Playing ## Overview Game playing enables users to play Flash and HTML5 games directly in their web browser. Flash games use the Ruffle WebAssembly emulator, while HTML5 games run natively. The player supports fullscreen mode, loading indicators, and error handling. ## Architecture **Backend Components:** - Game Service: Provides game data and launch configuration - Game Content Routes: Serves game files via `/game-proxy/*` and `/game-zip/*` - HTTP Proxy Server: Handles Flash content delivery - GameZip Server: Mounts and serves files from ZIP archives **Frontend Components:** - `GamePlayer`: Main player component (reusable) - `RufflePlayer`: Ruffle Flash emulator wrapper - `GamePlayerView`: Full-page player route with breadcrumb navigation - `GameDetailView`: Game details with embedded player and breadcrumb navigation - `GamePlayerDialog`: Modal player overlay - `Breadcrumbs`: Navigation bar with back button and context trail ## Supported Platforms - **Flash Games:** Played using Ruffle emulator (WebAssembly) - **HTML5 Games:** Played natively in iframe - **Animations:** Flash animations (library: "theatre") ## Game Launch Process **Step 1: Fetch Launch Data** ``` GET /api/games/:id/launch ``` Backend: 1. Fetch game from flashpoint.sqlite 2. Check if game has data on disk 3. If ZIP not present locally, start background download from gameDataSources 4. Mount ZIP file (awaits mount completion) 5. Extract launch command from game metadata 6. Construct content URL via game-service proxy 7. Return launch configuration with `downloading` status **Step 2: Check Download Status** Frontend receives response with `downloading: true` if game ZIP is being downloaded: ```typescript // GamePlayerView uses conditional polling const { data: launchData } = useGameLaunchData(id!); // Polls every 2 seconds if downloading, stops when complete useQuery({ queryKey: ['game', id, 'launch'], queryFn: () => gamesApi.getGameLaunchData(id), refetchInterval: (query) => query.state.data?.downloading ? 2000 : false, }); ``` - If `downloading: true` - show download progress UI - If `downloading: false` - proceed to player initialization **Step 3: Initialize Player** - Check if game can play in browser (canPlayInBrowser) - Select appropriate player (Ruffle vs iframe) - Mount player component **Step 4: Load Content** - **Flash:** Ruffle loads SWF from proxy URL - **HTML5:** Iframe loads game page from proxy URL - Game service proxies requests to local files or CDN **Step 5: Track Session** - POST /api/play/start with gameId - Backend creates play session record - Returns sessionId **Step 6: Play** - User interacts with game - All game assets served through proxy **Step 7: End Session** - User closes player or navigates away - POST /api/play/end with sessionId - Backend calculates duration and updates stats ## API Endpoints #### GET /api/games/:id/launch Get game launch configuration. **Response (ready to play):** ```json { "gameId": "550e8400-e29b-41d4-a716-446655440000", "title": "Super Mario Flash", "platform": "Flash", "launchCommand": "http://uploads.ungrounded.net/396000/396724_DAplayer_V6.swf", "contentUrl": "http://localhost:3100/game-proxy/http://uploads.ungrounded.net/396000/396724_DAplayer_V6.swf", "applicationPath": ":http: :message:", "playMode": "Single Player", "canPlayInBrowser": true, "downloading": false } ``` **Response (downloading):** ```json { "gameId": "550e8400-e29b-41d4-a716-446655440000", "title": "Super Mario Flash", "platform": "Flash", "launchCommand": "...", "contentUrl": "...", "canPlayInBrowser": true, "downloading": true } ``` When `downloading: true`, frontend should: 1. Show download progress UI to user 2. Poll this endpoint every 2 seconds 3. Proceed to player when `downloading` becomes false **For games without content:** ```json { "gameId": "...", "title": "Some Windows Game", "platform": "Windows", "launchCommand": "game.exe", "contentUrl": "", "canPlayInBrowser": false, "downloading": false } ``` ## Flash Game Content URL Resolution Flash games may have launch commands pointing to HTML wrapper pages instead of direct `.swf` files. The frontend automatically detects and resolves these: **SWF Extraction from HTML Wrappers:** The `useResolvedContentUrl` hook in `GamePlayer.tsx` handles HTML wrapper resolution for Flash games: 1. Detects when Flash game has HTML content URL 2. Fetches the HTML wrapper page 3. Extracts SWF reference using 9 regex patterns: - Embed tags (``) - Object tags (``) - Param tags (``) - SWFObject v1 (`swf = new SWFObject("game.swf")`) - SWFObject v2 (`swf.addVariable("..."`)`) - Adobe AC_FL_RunContent - Generic SWF references in quotes - Relative and absolute path handling 4. Passes resolved SWF URL to Ruffle player **Example:** ``` Original launch command: "Game.html" → Fetch HTML wrapper page → Extract: → Resolved URL: "/game-proxy/Game.swf" → Pass to Ruffle ``` This allows playing Flash games even when Flashpoint provides HTML wrapper pages. **Flash SWF Filter:** Only Flash games with `.swf` launchCommands appear in browse/search results. Games with HTML wrappers or other content are excluded from the "Flash" platform filter but can still be played via direct URL if you know their game ID. ## Ruffle Integration Ruffle is a Flash Player emulator written in Rust and compiled to WebAssembly. **Installation:** - Installed from npm package `@ruffle-rs/ruffle` - Files copied to `frontend/public/ruffle/` during postinstall - Includes: ruffle.js, ruffle_web.wasm, ruffle_web.js **RufflePlayer Component:** ```typescript interface RufflePlayerProps { swfUrl: string; // URL to SWF file width?: string; // Player width height?: string; // Player height className?: string; // CSS classes scaleMode?: ScaleMode; // Scaling behavior onLoadError?: (error: Error) => void; onLoadSuccess?: () => void; } type ScaleMode = 'exactfit' | 'noborder' | 'showall' | 'noscale'; ``` **Ruffle Configuration:** ```typescript const ruffleConfig = { autoplay: 'auto', backgroundColor: '#000000', letterbox: 'fullscreen', warnOnUnsupportedContent: true, logLevel: 'warn', showSwfDownload: false, }; ``` ## HTML5 Game Loading HTML5 games load in a sandboxed iframe: ```typescript