A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
664 lines
23 KiB
TypeScript
664 lines
23 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Multiplayer Feature Wizard
|
|
*
|
|
* Interactive guide for learning about and testing
|
|
* Breakpilot Drive multiplayer features (Matrix Chat & Jitsi Video)
|
|
*/
|
|
|
|
import { useState } from 'react'
|
|
import Link from 'next/link'
|
|
import AdminLayout from '@/components/admin/AdminLayout'
|
|
|
|
// ========================================
|
|
// Types
|
|
// ========================================
|
|
|
|
type WizardStep =
|
|
| 'welcome'
|
|
| 'game-modes'
|
|
| 'matrix-chat'
|
|
| 'jitsi-video'
|
|
| 'go-services'
|
|
| 'unity-integration'
|
|
| 'demo'
|
|
| 'summary'
|
|
|
|
interface StepInfo {
|
|
id: WizardStep
|
|
title: string
|
|
description: string
|
|
}
|
|
|
|
// ========================================
|
|
// Step Configuration
|
|
// ========================================
|
|
|
|
const STEPS: StepInfo[] = [
|
|
{ id: 'welcome', title: 'Willkommen', description: 'Multiplayer-Uebersicht' },
|
|
{ id: 'game-modes', title: 'Spielmodi', description: 'Co-Op, Challenge, Klasse' },
|
|
{ id: 'matrix-chat', title: 'Matrix Chat', description: 'Echtzeit-Kommunikation' },
|
|
{ id: 'jitsi-video', title: 'Jitsi Video', description: 'Video-Konferenzen' },
|
|
{ id: 'go-services', title: 'Go Services', description: 'Backend-Integration' },
|
|
{ id: 'unity-integration', title: 'Unity', description: 'WebGL Bridge' },
|
|
{ id: 'demo', title: 'Demo', description: 'Live-Test' },
|
|
{ id: 'summary', title: 'Zusammenfassung', description: 'Naechste Schritte' },
|
|
]
|
|
|
|
// ========================================
|
|
// Educational Content
|
|
// ========================================
|
|
|
|
const EDUCATION_CONTENT: Record<WizardStep, { title: string; content: string; tips: string[] }> = {
|
|
'welcome': {
|
|
title: 'Multiplayer fuer Breakpilot Drive',
|
|
content: `Das Multiplayer-System ermoeglicht kooperatives und kompetitives Spielen
|
|
zwischen Schuelern. Es basiert auf zwei bewaehrten Open-Source-Technologien:
|
|
|
|
- **Matrix Synapse** fuer Echtzeit-Chat
|
|
- **Jitsi Meet** fuer Video-Kommunikation
|
|
|
|
Diese Integration ermoeglicht verschiedene Spielmodi, von 1v1-Challenges
|
|
bis hin zu klassenweiten Wettbewerben.`,
|
|
tips: [
|
|
'Matrix ist ein dezentrales Chat-Protokoll mit End-to-End-Verschluesselung',
|
|
'Jitsi ist eine Open-Source-Alternative zu Zoom/Teams',
|
|
'Beide Systeme sind DSGVO-konform und selbst-gehostet'
|
|
]
|
|
},
|
|
'game-modes': {
|
|
title: 'Multiplayer-Spielmodi',
|
|
content: `Breakpilot Drive unterstuetzt vier verschiedene Multiplayer-Modi:
|
|
|
|
**Solo** - Einzelspieler ohne Netzwerk
|
|
|
|
**Co-Op** - 2-4 Spieler arbeiten zusammen
|
|
- Gemeinsame Strecke
|
|
- Team-Chat
|
|
- Optionales Video
|
|
|
|
**Challenge** - 1v1 Wettbewerb
|
|
- Gleiche Quiz-Fragen
|
|
- Live-Punktestand
|
|
- Video-Chat empfohlen
|
|
|
|
**Klassenrennen** - Alle gegen alle
|
|
- Lehrer als Moderator
|
|
- Klassen-Chat
|
|
- Live-Leaderboard`,
|
|
tips: [
|
|
'Co-Op ist ideal fuer Lerngruppen und Foerderunterricht',
|
|
'Challenges motivieren durch direkten Wettbewerb',
|
|
'Klassenrennen eignen sich als Abschluss einer Lerneinheit'
|
|
]
|
|
},
|
|
'matrix-chat': {
|
|
title: 'Matrix Chat Integration',
|
|
content: `Matrix wird fuer die Echtzeit-Kommunikation verwendet:
|
|
|
|
**Raum-Typen:**
|
|
- Team-Raeume (Co-Op, privat)
|
|
- Challenge-Raeume (1v1, temporaer)
|
|
- Klassen-Raeume (alle Schueler, Lehrer moderiert)
|
|
|
|
**Features:**
|
|
- Spieler-Beitritt/Austritt Benachrichtigungen
|
|
- Score-Updates in Echtzeit
|
|
- Achievement-Ankuendigungen
|
|
- End-to-End-Verschluesselung optional
|
|
|
|
**Game Events:**
|
|
- player_joined, player_left
|
|
- game_started, game_ended
|
|
- score_update, quiz_answered
|
|
- achievement, challenge_won`,
|
|
tips: [
|
|
'Matrix-Raeume werden automatisch erstellt und archiviert',
|
|
'Power Levels kontrollieren wer schreiben darf',
|
|
'Custom Events (breakpilot.game.*) fuer Spiellogik'
|
|
]
|
|
},
|
|
'jitsi-video': {
|
|
title: 'Jitsi Video Integration',
|
|
content: `Jitsi ermoeglicht Video-Kommunikation waehrend des Spiels:
|
|
|
|
**Konfiguration pro Modus:**
|
|
- Co-Op: Audio an, Video optional
|
|
- Challenge: Audio/Video empfohlen
|
|
- Klassenrennen: Lehrer-Video, Schueler stumm
|
|
|
|
**Sicherheit:**
|
|
- JWT-basierte Authentifizierung
|
|
- Lobby fuer Klassenraeume
|
|
- Kein Recording fuer Minderjaehrige
|
|
|
|
**Unity-Embedding:**
|
|
- Kompaktes Overlay (320x240px)
|
|
- Minimale UI (nur Mikro, Kamera, Auflegen)
|
|
- Automatisches Verbinden bei Spielstart`,
|
|
tips: [
|
|
'Jitsi-Container erscheint als Overlay im Spiel',
|
|
'Audio hat Prioritaet - Video ist optional',
|
|
'Lehrer koennen Schueler stummschalten'
|
|
]
|
|
},
|
|
'go-services': {
|
|
title: 'Backend Go Services',
|
|
content: `Die Multiplayer-Logik ist in Go implementiert:
|
|
|
|
**Matrix Service** (game_rooms.go)
|
|
- CreateGameTeamRoom() - Co-Op Raeume
|
|
- CreateGameChallengeRoom() - 1v1 Raeume
|
|
- CreateGameClassRaceRoom() - Klassen-Raeume
|
|
- SendGameEvent() - Event Broadcasting
|
|
|
|
**Jitsi Service** (game_meetings.go)
|
|
- CreateCoopMeeting() - Team Video
|
|
- CreateChallengeMeeting() - 1v1 Video
|
|
- CreateClassRaceMeeting() - Klassen-Video
|
|
- JWT-Token Generierung
|
|
|
|
**Pfade:**
|
|
- consent-service/internal/services/matrix/game_rooms.go
|
|
- consent-service/internal/services/jitsi/game_meetings.go`,
|
|
tips: [
|
|
'Services erweitern bestehende Matrix/Jitsi Integration',
|
|
'Validierung erfolgt vor Raum-Erstellung',
|
|
'Cleanup wird automatisch bei Spielende ausgefuehrt'
|
|
]
|
|
},
|
|
'unity-integration': {
|
|
title: 'Unity WebGL Integration',
|
|
content: `Die Unity-seitige Integration besteht aus zwei Teilen:
|
|
|
|
**MultiplayerManager.cs**
|
|
- Singleton fuer Session-Verwaltung
|
|
- Events fuer UI-Updates
|
|
- Score/Achievement Broadcasting
|
|
- Editor-Simulation fuer Entwicklung
|
|
|
|
**MultiplayerPlugin.jslib**
|
|
- JavaScript Bridge fuer WebGL
|
|
- WebSocket-Verbindung zu Backend
|
|
- Jitsi External API Integration
|
|
- Automatisches Container-Management
|
|
|
|
**Pfade:**
|
|
- Assets/Scripts/Network/MultiplayerManager.cs
|
|
- Assets/Plugins/WebGL/MultiplayerPlugin.jslib`,
|
|
tips: [
|
|
'Im Editor werden Multiplayer-Events simuliert',
|
|
'jslib-Funktionen nur im WebGL Build verfuegbar',
|
|
'Jitsi-Container wird dynamisch erstellt'
|
|
]
|
|
},
|
|
'demo': {
|
|
title: 'Live-Demo',
|
|
content: `Teste die Multiplayer-Komponenten:
|
|
|
|
**Matrix-Verbindung:**
|
|
- Pruefe ob Matrix Synapse erreichbar ist
|
|
- Teste Raum-Erstellung
|
|
- Sende Test-Nachricht
|
|
|
|
**Jitsi-Verbindung:**
|
|
- Pruefe ob Jitsi Meet erreichbar ist
|
|
- Teste Meeting-Link Generierung
|
|
- Pruefe JWT-Validierung
|
|
|
|
**Hinweis:** Fuer vollstaendige Tests muss das
|
|
Unity WebGL Build laufen und mit dem Backend verbunden sein.`,
|
|
tips: [
|
|
'Matrix laeuft auf Port 8008',
|
|
'Jitsi laeuft auf Port 8443',
|
|
'Beide Services muessen in Docker laufen'
|
|
]
|
|
},
|
|
'summary': {
|
|
title: 'Zusammenfassung & Naechste Schritte',
|
|
content: `Du hast gelernt:
|
|
|
|
✓ Vier Multiplayer-Modi (Solo, Co-Op, Challenge, Klasse)
|
|
✓ Matrix fuer Chat und Game Events
|
|
✓ Jitsi fuer Video-Kommunikation
|
|
✓ Go Backend Services
|
|
✓ Unity WebGL Integration
|
|
|
|
**Naechste Schritte:**
|
|
1. Backend-Services starten (docker-compose up)
|
|
2. Unity WebGL Build erstellen
|
|
3. Multiplayer im Admin Panel testen
|
|
4. Phase 9: Mobile App Delivery`,
|
|
tips: [
|
|
'Dokumentation in docs/breakpilot-drive/multiplayer.md',
|
|
'Tests in consent-service/*_test.go',
|
|
'Unity Tests ueber Test Runner'
|
|
]
|
|
},
|
|
}
|
|
|
|
// ========================================
|
|
// Components
|
|
// ========================================
|
|
|
|
function WizardStepper({ steps, currentStep, onStepClick }: {
|
|
steps: StepInfo[]
|
|
currentStep: WizardStep
|
|
onStepClick: (step: WizardStep) => void
|
|
}) {
|
|
const currentIndex = steps.findIndex(s => s.id === currentStep)
|
|
|
|
return (
|
|
<div className="flex items-center gap-1 overflow-x-auto pb-2">
|
|
{steps.map((step, index) => {
|
|
const isActive = step.id === currentStep
|
|
const isCompleted = index < currentIndex
|
|
const isClickable = index <= currentIndex + 1
|
|
|
|
return (
|
|
<button
|
|
key={step.id}
|
|
onClick={() => isClickable && onStepClick(step.id)}
|
|
disabled={!isClickable}
|
|
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
|
|
isActive
|
|
? 'bg-indigo-600 text-white'
|
|
: isCompleted
|
|
? 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200'
|
|
: isClickable
|
|
? 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
|
: 'bg-gray-50 text-gray-400 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs ${
|
|
isActive ? 'bg-white/20' : isCompleted ? 'bg-indigo-200' : 'bg-gray-200'
|
|
}`}>
|
|
{isCompleted ? '✓' : index + 1}
|
|
</span>
|
|
<span className="hidden md:inline">{step.title}</span>
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function EducationCard({ title, content, tips }: { title: string; content: string; tips: string[] }) {
|
|
return (
|
|
<div className="bg-white rounded-xl shadow-lg p-6">
|
|
<h2 className="text-xl font-bold text-gray-900 mb-4">{title}</h2>
|
|
<div className="prose prose-sm max-w-none mb-6">
|
|
{content.split('\n\n').map((paragraph, i) => (
|
|
<p key={i} className="text-gray-700 whitespace-pre-line mb-3">
|
|
{paragraph.split('**').map((part, j) =>
|
|
j % 2 === 1 ? <strong key={j}>{part}</strong> : part
|
|
)}
|
|
</p>
|
|
))}
|
|
</div>
|
|
<div className="bg-indigo-50 rounded-lg p-4">
|
|
<h3 className="text-sm font-semibold text-indigo-800 mb-2">Tipps:</h3>
|
|
<ul className="space-y-1">
|
|
{tips.map((tip, i) => (
|
|
<li key={i} className="flex items-start gap-2 text-sm text-indigo-700">
|
|
<span className="text-indigo-500 mt-0.5">•</span>
|
|
{tip}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function GameModeDemo() {
|
|
const modes = [
|
|
{ name: 'Solo', icon: '🎮', players: '1', features: ['Einzelspieler', 'Offline moeglich', 'Eigenes Tempo'] },
|
|
{ name: 'Co-Op', icon: '🤝', players: '2-4', features: ['Team-Chat', 'Gemeinsame Strecke', 'Video optional'] },
|
|
{ name: 'Challenge', icon: '⚔️', players: '2', features: ['1v1 Wettbewerb', 'Live-Score', 'Video empfohlen'] },
|
|
{ name: 'Klassenrennen', icon: '🏁', players: '∞', features: ['Alle gegen alle', 'Lehrer-Moderation', 'Leaderboard'] },
|
|
]
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
|
|
{modes.map((mode) => (
|
|
<div key={mode.name} className="bg-gradient-to-br from-indigo-50 to-purple-50 rounded-lg p-4 border border-indigo-100">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<span className="text-3xl">{mode.icon}</span>
|
|
<div>
|
|
<h4 className="font-bold text-gray-900">{mode.name}</h4>
|
|
<p className="text-sm text-gray-500">{mode.players} Spieler</p>
|
|
</div>
|
|
</div>
|
|
<ul className="space-y-1">
|
|
{mode.features.map((feature, i) => (
|
|
<li key={i} className="text-sm text-gray-600 flex items-center gap-2">
|
|
<span className="text-indigo-500">✓</span> {feature}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function ServiceStatusDemo() {
|
|
const [matrixStatus, setMatrixStatus] = useState<'idle' | 'checking' | 'online' | 'offline'>('idle')
|
|
const [jitsiStatus, setJitsiStatus] = useState<'idle' | 'checking' | 'online' | 'offline'>('idle')
|
|
|
|
const checkMatrix = async () => {
|
|
setMatrixStatus('checking')
|
|
try {
|
|
// In production, this would check the actual Matrix server
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
setMatrixStatus('online') // Mock - assume online
|
|
} catch {
|
|
setMatrixStatus('offline')
|
|
}
|
|
}
|
|
|
|
const checkJitsi = async () => {
|
|
setJitsiStatus('checking')
|
|
try {
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
setJitsiStatus('online') // Mock
|
|
} catch {
|
|
setJitsiStatus('offline')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="mt-6 space-y-4">
|
|
<h3 className="font-semibold text-gray-800">Service-Status pruefen:</h3>
|
|
|
|
<div className="flex gap-4">
|
|
<div className="flex-1 bg-white rounded-lg border border-gray-200 p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xl">💬</span>
|
|
<span className="font-medium">Matrix Synapse</span>
|
|
</div>
|
|
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
|
matrixStatus === 'online' ? 'bg-green-100 text-green-700' :
|
|
matrixStatus === 'offline' ? 'bg-red-100 text-red-700' :
|
|
matrixStatus === 'checking' ? 'bg-yellow-100 text-yellow-700' :
|
|
'bg-gray-100 text-gray-600'
|
|
}`}>
|
|
{matrixStatus === 'online' ? 'Online' :
|
|
matrixStatus === 'offline' ? 'Offline' :
|
|
matrixStatus === 'checking' ? 'Pruefe...' : 'Unbekannt'}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-gray-500 mb-3">Port 8008</p>
|
|
<button
|
|
onClick={checkMatrix}
|
|
disabled={matrixStatus === 'checking'}
|
|
className="w-full px-3 py-2 bg-indigo-600 text-white rounded-lg text-sm hover:bg-indigo-700 disabled:opacity-50"
|
|
>
|
|
Status pruefen
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex-1 bg-white rounded-lg border border-gray-200 p-4">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xl">📹</span>
|
|
<span className="font-medium">Jitsi Meet</span>
|
|
</div>
|
|
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
|
jitsiStatus === 'online' ? 'bg-green-100 text-green-700' :
|
|
jitsiStatus === 'offline' ? 'bg-red-100 text-red-700' :
|
|
jitsiStatus === 'checking' ? 'bg-yellow-100 text-yellow-700' :
|
|
'bg-gray-100 text-gray-600'
|
|
}`}>
|
|
{jitsiStatus === 'online' ? 'Online' :
|
|
jitsiStatus === 'offline' ? 'Offline' :
|
|
jitsiStatus === 'checking' ? 'Pruefe...' : 'Unbekannt'}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-gray-500 mb-3">Port 8443</p>
|
|
<button
|
|
onClick={checkJitsi}
|
|
disabled={jitsiStatus === 'checking'}
|
|
className="w-full px-3 py-2 bg-indigo-600 text-white rounded-lg text-sm hover:bg-indigo-700 disabled:opacity-50"
|
|
>
|
|
Status pruefen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function CodePreview({ title, code, language }: { title: string; code: string; language: string }) {
|
|
return (
|
|
<div className="mt-6 bg-gray-900 rounded-lg overflow-hidden">
|
|
<div className="flex items-center justify-between px-4 py-2 bg-gray-800">
|
|
<span className="text-sm text-gray-400">{title}</span>
|
|
<span className="text-xs px-2 py-1 bg-gray-700 text-gray-300 rounded">{language}</span>
|
|
</div>
|
|
<pre className="p-4 text-sm text-gray-300 overflow-x-auto">
|
|
<code>{code}</code>
|
|
</pre>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ========================================
|
|
// Main Component
|
|
// ========================================
|
|
|
|
export default function MultiplayerWizardPage() {
|
|
const [currentStep, setCurrentStep] = useState<WizardStep>('welcome')
|
|
const currentStepIndex = STEPS.findIndex(s => s.id === currentStep)
|
|
const education = EDUCATION_CONTENT[currentStep]
|
|
|
|
const goToNext = () => {
|
|
const nextIndex = currentStepIndex + 1
|
|
if (nextIndex < STEPS.length) {
|
|
setCurrentStep(STEPS[nextIndex].id)
|
|
}
|
|
}
|
|
|
|
const goToPrevious = () => {
|
|
const prevIndex = currentStepIndex - 1
|
|
if (prevIndex >= 0) {
|
|
setCurrentStep(STEPS[prevIndex].id)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<AdminLayout
|
|
title="Multiplayer Wizard"
|
|
description="Lerne die Multiplayer-Features von Breakpilot Drive kennen"
|
|
>
|
|
{/* Back Link */}
|
|
<div className="mb-6">
|
|
<Link
|
|
href="/admin/game"
|
|
className="inline-flex items-center gap-2 text-gray-600 hover:text-gray-900"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
Zurueck zum Game Dashboard
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Stepper */}
|
|
<div className="mb-8">
|
|
<WizardStepper
|
|
steps={STEPS}
|
|
currentStep={currentStep}
|
|
onStepClick={setCurrentStep}
|
|
/>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Main Content */}
|
|
<div className="lg:col-span-2">
|
|
<EducationCard
|
|
title={education.title}
|
|
content={education.content}
|
|
tips={education.tips}
|
|
/>
|
|
|
|
{/* Step-specific content */}
|
|
{currentStep === 'game-modes' && <GameModeDemo />}
|
|
|
|
{currentStep === 'demo' && <ServiceStatusDemo />}
|
|
|
|
{currentStep === 'matrix-chat' && (
|
|
<CodePreview
|
|
title="game_rooms.go - Raum erstellen"
|
|
language="Go"
|
|
code={`func (s *MatrixService) CreateGameTeamRoom(
|
|
ctx context.Context,
|
|
config GameRoomConfig,
|
|
) (*CreateRoomResponse, error) {
|
|
roomName := fmt.Sprintf("Breakpilot Drive - Team %s",
|
|
config.SessionID[:8])
|
|
|
|
req := CreateRoomRequest{
|
|
Name: roomName,
|
|
Visibility: "private",
|
|
Preset: "private_chat",
|
|
// ... power levels, encryption
|
|
}
|
|
|
|
return s.CreateRoom(ctx, req)
|
|
}`}
|
|
/>
|
|
)}
|
|
|
|
{currentStep === 'jitsi-video' && (
|
|
<CodePreview
|
|
title="game_meetings.go - Meeting erstellen"
|
|
language="Go"
|
|
code={`func (s *JitsiService) CreateChallengeMeeting(
|
|
ctx context.Context,
|
|
config GameMeetingConfig,
|
|
challengerName string,
|
|
opponentName string,
|
|
) (*GameMeetingLink, error) {
|
|
meeting := Meeting{
|
|
RoomName: fmt.Sprintf("bp-challenge-%s",
|
|
config.SessionID[:8]),
|
|
Subject: fmt.Sprintf("Challenge: %s vs %s",
|
|
challengerName, opponentName),
|
|
Config: &MeetingConfig{
|
|
StartWithAudioMuted: false,
|
|
RequireDisplayName: true,
|
|
},
|
|
}
|
|
|
|
return s.CreateMeetingLink(ctx, meeting)
|
|
}`}
|
|
/>
|
|
)}
|
|
|
|
{currentStep === 'unity-integration' && (
|
|
<CodePreview
|
|
title="MultiplayerManager.cs - Session erstellen"
|
|
language="C#"
|
|
code={`public void CreateSession(
|
|
GameMode mode,
|
|
string displayName,
|
|
Action<MultiplayerSession> onSuccess,
|
|
Action<string> onError
|
|
) {
|
|
localPlayer = new Player {
|
|
id = Guid.NewGuid().ToString(),
|
|
displayName = displayName,
|
|
isHost = true,
|
|
isReady = false
|
|
};
|
|
|
|
state = MultiplayerState.Connecting;
|
|
StartCoroutine(CreateSessionCoroutine(
|
|
mode, onSuccess, onError));
|
|
}`}
|
|
/>
|
|
)}
|
|
|
|
{/* Navigation */}
|
|
<div className="flex justify-between mt-8">
|
|
<button
|
|
onClick={goToPrevious}
|
|
disabled={currentStepIndex === 0}
|
|
className="px-6 py-3 rounded-lg font-medium bg-gray-100 text-gray-700 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
Zurueck
|
|
</button>
|
|
<button
|
|
onClick={goToNext}
|
|
disabled={currentStepIndex === STEPS.length - 1}
|
|
className="px-6 py-3 rounded-lg font-medium bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{currentStepIndex === STEPS.length - 1 ? 'Fertig' : 'Weiter'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Sidebar */}
|
|
<div className="space-y-6">
|
|
{/* Progress */}
|
|
<div className="bg-white rounded-xl shadow p-6">
|
|
<h3 className="font-semibold text-gray-900 mb-4">Fortschritt</h3>
|
|
<div className="relative pt-1">
|
|
<div className="flex mb-2 items-center justify-between">
|
|
<span className="text-xs font-semibold text-indigo-600">
|
|
Schritt {currentStepIndex + 1} von {STEPS.length}
|
|
</span>
|
|
<span className="text-xs font-semibold text-indigo-600">
|
|
{Math.round(((currentStepIndex + 1) / STEPS.length) * 100)}%
|
|
</span>
|
|
</div>
|
|
<div className="overflow-hidden h-2 mb-4 text-xs flex rounded bg-indigo-100">
|
|
<div
|
|
style={{ width: `${((currentStepIndex + 1) / STEPS.length) * 100}%` }}
|
|
className="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-indigo-600 transition-all duration-300"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Architecture Overview */}
|
|
<div className="bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl shadow p-6 text-white">
|
|
<h3 className="font-semibold mb-4">Architektur</h3>
|
|
<div className="text-sm space-y-2 font-mono">
|
|
<div className="bg-white/10 rounded px-2 py-1">Unity WebGL</div>
|
|
<div className="text-center text-indigo-200">↓ JS Bridge</div>
|
|
<div className="bg-white/10 rounded px-2 py-1">Matrix + Jitsi</div>
|
|
<div className="text-center text-indigo-200">↓ WebSocket/API</div>
|
|
<div className="bg-white/10 rounded px-2 py-1">Go Backend</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Links */}
|
|
<div className="bg-white rounded-xl shadow p-6">
|
|
<h3 className="font-semibold text-gray-900 mb-4">Wichtige Dateien</h3>
|
|
<ul className="space-y-2 text-sm">
|
|
<li className="text-gray-600">
|
|
<span className="text-indigo-600">Go:</span> consent-service/.../matrix/game_rooms.go
|
|
</li>
|
|
<li className="text-gray-600">
|
|
<span className="text-indigo-600">Go:</span> consent-service/.../jitsi/game_meetings.go
|
|
</li>
|
|
<li className="text-gray-600">
|
|
<span className="text-indigo-600">C#:</span> Assets/Scripts/Network/MultiplayerManager.cs
|
|
</li>
|
|
<li className="text-gray-600">
|
|
<span className="text-indigo-600">JS:</span> Assets/Plugins/WebGL/MultiplayerPlugin.jslib
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AdminLayout>
|
|
)
|
|
}
|