diff --git a/projects/sasch40/frontend/public/assets/characters/markus/portrait.jpg b/projects/sasch40/frontend/public/assets/characters/markus/portrait.jpg index 2b836e2..06d6cee 100644 Binary files a/projects/sasch40/frontend/public/assets/characters/markus/portrait.jpg and b/projects/sasch40/frontend/public/assets/characters/markus/portrait.jpg differ diff --git a/projects/sasch40/frontend/public/assets/scenes/hinter_den_bergen.jpg b/projects/sasch40/frontend/public/assets/scenes/hinter_den_bergen.jpg index adf5bc4..0a2ad99 100644 Binary files a/projects/sasch40/frontend/public/assets/scenes/hinter_den_bergen.jpg and b/projects/sasch40/frontend/public/assets/scenes/hinter_den_bergen.jpg differ diff --git a/projects/sasch40/frontend/public/assets/scenes/schankraum.jpg b/projects/sasch40/frontend/public/assets/scenes/schankraum.jpg index dba335e..c3d256e 100644 Binary files a/projects/sasch40/frontend/public/assets/scenes/schankraum.jpg and b/projects/sasch40/frontend/public/assets/scenes/schankraum.jpg differ diff --git a/projects/sasch40/frontend/scripts/playtest.mjs b/projects/sasch40/frontend/scripts/playtest.mjs index 828cd7b..9ba417e 100644 --- a/projects/sasch40/frontend/scripts/playtest.mjs +++ b/projects/sasch40/frontend/scripts/playtest.mjs @@ -8,6 +8,15 @@ if (!fs.existsSync(SCREENSHOTS_DIR)) { fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true }); } +async function takeScreenshot(page, filename) { + try { + await page.screenshot({ path: path.join(SCREENSHOTS_DIR, filename), timeout: 20000 }); + } catch (err) { + console.warn("⚠️ Warning: Screenshot " + filename + " timed out or failed:", err.message); + } +} + + async function runPlaytest() { console.log('🚀 Starting Comprehensive Chrome Playtest for Saschas Kronkorken Kreuzzug...'); @@ -18,11 +27,11 @@ async function runPlaytest() { const browser = await puppeteer.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: 'new', + protocolTimeout: 60000, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', - '--disable-gpu', '--window-size=412,869' ] }); @@ -64,7 +73,7 @@ async function runPlaytest() { // 1. INTRO / PREQUEL SCREEN console.log('📸 01: Capturing Intro Screen...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '01_intro_screen.png') }); + await takeScreenshot(page, '01_intro_screen.png'); // Click Start Button console.log('🖱️ Clicking Start Button...'); @@ -73,7 +82,7 @@ async function runPlaytest() { // 2. PROLOGUE SCREEN (Balcony Scene) console.log('📸 02a: Capturing Prologue Screen (NAT opening)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02a_prologue_nat.png') }); + await takeScreenshot(page, '02a_prologue_nat.png'); // Click first choice -> Flo Maier arrives const natChoice = await page.$('.choice-btn'); @@ -81,7 +90,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 800)); console.log('📸 02b: Capturing Flo Maier Feierdeifl arrives...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02b_prologue_flo_feierdeifl.png') }); + await takeScreenshot(page, '02b_prologue_flo_feierdeifl.png'); // Test Game Over branch: "Ja freilich, zünd ois o, Flo!" const fireChoice = await page.$('.choice-btn:nth-child(1)'); @@ -89,25 +98,33 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 800)); console.log('📸 02c: Capturing Flo Maier Game Over (fiery castle)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02c_prologue_flo_game_over.png') }); + await takeScreenshot(page, '02c_prologue_flo_game_over.png'); // Reset via Löschzug const resetFireBtn = await page.$('.game-over-btn'); - if (resetFireBtn) await resetFireBtn.click(); - await new Promise(r => setTimeout(r, 800)); + if (resetFireBtn) { + await resetFireBtn.click(); + await new Promise(r => setTimeout(r, 1000)); + } // Skip NAT to Flo again + await page.waitForSelector('.choice-btn', { timeout: 4000 }).catch(() => null); const natChoice2 = await page.$('.choice-btn'); - if (natChoice2) await natChoice2.click(); - await new Promise(r => setTimeout(r, 600)); + if (natChoice2) { + await natChoice2.click(); + await new Promise(r => setTimeout(r, 800)); + } // Decline Flo Maier -> Viktor arrives + await page.waitForSelector('.choice-btn:nth-child(2)', { timeout: 4000 }).catch(() => null); const declineFloBtn = await page.$('.choice-btn:nth-child(2)'); - if (declineFloBtn) await declineFloBtn.click(); - await new Promise(r => setTimeout(r, 800)); + if (declineFloBtn) { + await declineFloBtn.click(); + await new Promise(r => setTimeout(r, 1000)); + } console.log('📸 02d: Capturing Viktor Partyvampir arrives...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02d_prologue_viktor_vampire.png') }); + await takeScreenshot(page, '02d_prologue_viktor_vampire.png'); // Test Game Over branch: "Ja geil, mach mi unsterblich!" const biteChoice = await page.$('.choice-btn:nth-child(1)'); @@ -115,26 +132,37 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 800)); console.log('📸 02e: Capturing Viktor Game Over (glitter dust in sun)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02e_prologue_viktor_game_over.png') }); + await takeScreenshot(page, '02e_prologue_viktor_game_over.png'); // Reset via Knoblauch const resetVampBtn = await page.$('.game-over-btn'); - if (resetVampBtn) await resetVampBtn.click(); - await new Promise(r => setTimeout(r, 800)); + if (resetVampBtn) { + await resetVampBtn.click(); + await new Promise(r => setTimeout(r, 1000)); + } // Advance to Mop safely: NAT -> Flo (decline) -> Viktor (decline) -> Mop + await page.waitForSelector('.choice-btn', { timeout: 4000 }).catch(() => null); const natC = await page.$('.choice-btn'); - if (natC) await natC.click(); - await new Promise(r => setTimeout(r, 500)); + if (natC) { + await natC.click(); + await new Promise(r => setTimeout(r, 600)); + } + await page.waitForSelector('.choice-btn:nth-child(2)', { timeout: 4000 }).catch(() => null); const floC = await page.$('.choice-btn:nth-child(2)'); - if (floC) await floC.click(); - await new Promise(r => setTimeout(r, 500)); + if (floC) { + await floC.click(); + await new Promise(r => setTimeout(r, 600)); + } + await page.waitForSelector('.choice-btn:nth-child(2)', { timeout: 4000 }).catch(() => null); const vikC = await page.$('.choice-btn:nth-child(2)'); - if (vikC) await vikC.click(); - await new Promise(r => setTimeout(r, 800)); + if (vikC) { + await vikC.click(); + await new Promise(r => setTimeout(r, 800)); + } console.log('📸 02f: Capturing Mop Dialogue (Sister Rescue Quest)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02f_prologue_mop.png') }); + await takeScreenshot(page, '02f_prologue_mop.png'); // Advance to Akt II (Doodle Jump Schwesternrettung) const startAkt2Btn = await page.$('.choice-btn'); @@ -143,7 +171,7 @@ async function runPlaytest() { // 3. AKT II: DOODLE JUMP SCHWESTERNRETTUNG console.log('📸 03a: Capturing Akt II Doodle Jump Start Modal...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03a_doodle_jump_intro_modal.png') }); + await takeScreenshot(page, '03a_doodle_jump_intro_modal.png'); // Click Start Act Button on modal const startDoodleBtn = await page.$('.start-act-btn'); @@ -154,7 +182,7 @@ async function runPlaytest() { // Capture Gameplay (Bouncing up platforms) console.log('📸 03b: Capturing Doodle Jump Gameplay...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03b_doodle_jump_gameplay.png') }); + await takeScreenshot(page, '03b_doodle_jump_gameplay.png'); // Trigger Sister 1 Rescue (Saskia) await page.evaluate(() => { @@ -180,7 +208,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 1000)); console.log('📸 05: Capturing Tavern Schankraum (seamless oak floor)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05_tavern_schankraum.png') }); + await takeScreenshot(page, '05_tavern_schankraum.png'); // Test Double-Click / Double-Tap on Trapdoor to unlock Party Bunker console.log('🚪 Testing double-click / double-tap on oak floor trapdoor...'); @@ -193,7 +221,7 @@ async function runPlaytest() { await trapdoor.click(); await new Promise(r => setTimeout(r, 1000)); console.log('📸 12b: Capturing Secret VIP Party Bunker...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12b_tavern_party_bunker_vip.png') }); + await takeScreenshot(page, '12b_tavern_party_bunker_vip.png'); // Click Pü in Party Bunker const pueBtn = await page.$('.hotspot-pue'); @@ -201,7 +229,7 @@ async function runPlaytest() { await pueBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 13: Capturing Pü Dialog...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '13_tavern_pue_dialog.png') }); + await takeScreenshot(page, '13_tavern_pue_dialog.png'); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); @@ -213,7 +241,7 @@ async function runPlaytest() { await backToSchankraum.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 12: Capturing Schankraum with revealed Luke zum Party Bunker...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12_tavern_party_bunker_trapdoor.png') }); + await takeScreenshot(page, '12_tavern_party_bunker_trapdoor.png'); } } @@ -224,7 +252,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 800)); console.log('📸 05b: Capturing Nebenraum (Atelier & Mirror)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05b_tavern_nebenraum.png') }); + await takeScreenshot(page, '05b_tavern_nebenraum.png'); // Test Zuluman Mirror in Nebenraum const mirrorBtn = await page.$('.hotspot-mirror-nebenraum'); @@ -232,7 +260,7 @@ async function runPlaytest() { await mirrorBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 14: Capturing Zuluman Mirror...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '14_tavern_zuluman_mirror.png') }); + await takeScreenshot(page, '14_tavern_zuluman_mirror.png'); const closeBtn = await page.$('.mirror-box .action-btn'); if (closeBtn) await closeBtn.click(); await new Promise(r => setTimeout(r, 400)); @@ -245,14 +273,14 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 800)); console.log('📸 08: Capturing Marktplatz...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_marktplatz.png') }); + await takeScreenshot(page, '08_tavern_marktplatz.png'); const natBtn = await page.$('.hotspot-nat'); if (natBtn) { await natBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 09b: Capturing NAT Dialog...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09b_tavern_nat_dialog.png') }); + await takeScreenshot(page, '09b_tavern_nat_dialog.png'); // Click "🐗 Zum Wildschwein-Duell!" -> opens INTRO MODAL const startBoarIntroBtn = await page.$('.dialog-actions .action-btn'); @@ -260,7 +288,7 @@ async function runPlaytest() { await startBoarIntroBtn.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 09c: Capturing Wildschwein Duel INTRO modal...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09c_tavern_boar_duel_intro.png') }); + await takeScreenshot(page, '09c_tavern_boar_duel_intro.png'); // Start Duel from Intro const startBoarDuelBtn = await page.$('.start-duel-action-btn'); @@ -274,7 +302,7 @@ async function runPlaytest() { await keilerBtn.click(); await new Promise(r => setTimeout(r, 600)); console.log('📸 09d: Capturing Boar Duel Round Clash (verifying unhighlighted buttons)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09d_tavern_boar_duel_round.png') }); + await takeScreenshot(page, '09d_tavern_boar_duel_round.png'); } // Close modal via ✕ @@ -294,7 +322,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 1000)); console.log('📸 10: Capturing Hinterland (Mane & Hansi departing toward Münchanien)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '10_tavern_hinterland.png') }); + await takeScreenshot(page, '10_tavern_hinterland.png'); // Click Hansi to hear about departure and evening concert const hansiBtn = await page.$('.hotspot-hansi'); @@ -302,25 +330,12 @@ async function runPlaytest() { await hansiBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 11: Capturing Hansi Departure Dialog...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11_tavern_hansi_dialog.png') }); + await takeScreenshot(page, '11_tavern_hansi_dialog.png'); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } - // Test Münchanien Signpost Hint Dialog - console.log('🪧 Testing Münchanien Signpost Hint...'); - const signBtn = await page.$('.hotspot-sign-munchanien'); - if (signBtn) { - await signBtn.click(); - await new Promise(r => setTimeout(r, 700)); - console.log('📸 11b: Capturing Münchanien Signpost Hint Modal...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11b_tavern_munchanien_hint.png') }); - const hintCloseBtn = await page.$('.intro-modal-box .action-btn'); - if (hintCloseBtn) await hintCloseBtn.click(); - await new Promise(r => setTimeout(r, 400)); - } - // Test Hinter den Bergen (Taiwan Valley & Papa Markus) console.log('🏮 Navigating to Hinter den Bergen...'); const hdbDoor = await page.$('.hotspot-door-hinter-den-bergen'); @@ -333,7 +348,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 1000)); console.log('📸 11c: Capturing Hinter den Bergen Scene (Taiwan Alpine Skyline & Papa Markus)...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11c_tavern_hinter_den_bergen.png') }); + await takeScreenshot(page, '11c_tavern_hinter_den_bergen.png'); // Click Markus for Liar's Dice const markusBtn = await page.$('.hotspot-markus'); @@ -341,7 +356,7 @@ async function runPlaytest() { await markusBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 11d: Capturing Papa Markus Dialog...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11d_tavern_markus_dialog.png') }); + await takeScreenshot(page, '11d_tavern_markus_dialog.png'); // Click "🎲 Auf geht's Papa, Liar's Dice spielen!" -> opens intro modal const startDiceIntroBtn = await page.$('.dialog-actions .action-btn'); @@ -349,7 +364,7 @@ async function runPlaytest() { await startDiceIntroBtn.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 11e: Capturing Liar\'s Dice INTRO Modal...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11e_tavern_liars_dice_intro.png') }); + await takeScreenshot(page, '11e_tavern_liars_dice_intro.png'); // Start Liar's Dice Match const startMatchBtn = await page.$('.start-duel-action-btn'); @@ -357,7 +372,7 @@ async function runPlaytest() { await startMatchBtn.click(); await new Promise(r => setTimeout(r, 1200)); console.log('📸 11f: Capturing Liar\'s Dice Initial Round View...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11f_tavern_liars_dice_gameplay.png') }); + await takeScreenshot(page, '11f_tavern_liars_dice_gameplay.png'); // Submit player bid const bidBtn = await page.$('.bid-action-btn'); @@ -365,7 +380,7 @@ async function runPlaytest() { await bidBtn.click(); await new Promise(r => setTimeout(r, 1500)); // wait for Markus response console.log('📸 11g: Capturing Liar\'s Dice Markus Counter-Bid...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11g_tavern_liars_dice_markus_turn.png') }); + await takeScreenshot(page, '11g_tavern_liars_dice_markus_turn.png'); } // Call bluff @@ -374,7 +389,7 @@ async function runPlaytest() { await bluffBtn.click(); await new Promise(r => setTimeout(r, 1200)); // wait for shot animation console.log('📸 11h: Capturing Liar\'s Dice Bluff Resolution & Gaoliang Shot...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11h_tavern_liars_dice_bluff_call.png') }); + await takeScreenshot(page, '11h_tavern_liars_dice_bluff_call.png'); } // Close modal @@ -401,14 +416,14 @@ async function runPlaytest() { await duelBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 15a: Capturing Gstanzl Duel INTRO modal...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '15a_tavern_gstanzl_intro.png') }); + await takeScreenshot(page, '15a_tavern_gstanzl_intro.png'); const startGstanzlBtn = await page.$('.start-duel-action-btn'); if (startGstanzlBtn) { await startGstanzlBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 15b: Capturing Gstanzl Duel Round...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '15b_tavern_gstanzl_duel.png') }); + await takeScreenshot(page, '15b_tavern_gstanzl_duel.png'); // Close via ✕ const closeDuelBtn = await page.$('.duel-box .duel-close-btn'); @@ -430,7 +445,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 1000)); console.log('📸 16: Capturing Lute Hero Start Screen Modal...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '16_lute_hero_start_modal.png') }); + await takeScreenshot(page, '16_lute_hero_start_modal.png'); // Start Concert const startConcertBtn = await page.$('.start-act-btn'); @@ -452,7 +467,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 300)); console.log('📸 17: Capturing Castle Stage Rock Concert Gameplay...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '17_lute_hero_gameplay.png') }); + await takeScreenshot(page, '17_lute_hero_gameplay.png'); // 6. AKT V: FINALE & CERTIFICATE console.log('👑 Advancing to Finale Certificate (Act V)...'); @@ -470,7 +485,7 @@ async function runPlaytest() { await new Promise(r => setTimeout(r, 1500)); console.log('📸 18: Capturing Certificate Screen...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '18_certificate_screen.png') }); + await takeScreenshot(page, '18_certificate_screen.png'); // Test Settings Modal (Gear icon ⚙️) console.log('⚙️ Testing Settings Modal (Pause, Mute, Reset)...'); @@ -479,7 +494,7 @@ async function runPlaytest() { await settingsBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 19: Capturing Settings & Pause Modal...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '19_settings_pause_modal.png') }); + await takeScreenshot(page, '19_settings_pause_modal.png'); // Click Resume const resumeBtn = await page.$('.resume-btn'); @@ -496,7 +511,7 @@ async function runPlaytest() { await restartBtn.click(); await new Promise(r => setTimeout(r, 1000)); console.log('📸 20: Capturing post-restart Intro screen...'); - await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '20_post_restart_intro.png') }); + await takeScreenshot(page, '20_post_restart_intro.png'); } // Verify all images loaded properly diff --git a/projects/sasch40/frontend/src/components/TavernView.vue b/projects/sasch40/frontend/src/components/TavernView.vue index 1389cae..c21c7b8 100644 --- a/projects/sasch40/frontend/src/components/TavernView.vue +++ b/projects/sasch40/frontend/src/components/TavernView.vue @@ -325,12 +325,6 @@ function openCharacterDialog(charId: string) { } } -// Münchanien Hint Modal -const munchanienHintActive = ref(false) -function openMunchanienHint() { - sound.playDialogueBlip() - munchanienHintActive.value = true -} // Liar's Dice (Chui Niu / 吹牛) State & Logic const liarsDiceIntroOpen = ref(false) @@ -710,13 +704,7 @@ function proceedToExpress() { - - - @@ -1046,24 +1034,6 @@ function proceedToExpress() { - -
-