diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index d68412e..d96e86d 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -328,13 +328,17 @@ return !!(mapData && mapData.gameType === 'stack' && (currentPlayMapId() || '').trim() === STACK_TOWER_MISSION_MAP_ID); } - function stackTowerScrollBgDrawActive() { - if (!stackTowerScrollBgMapEligible() || stackTowerScrollBgOff) return false; + function stackTowerScrollBgImagesReady() { + if (!stackTowerScrollBgMapEligible()) return false; const a = stackTowerScrollBgIntroImg; const b = stackTowerScrollBgLoopImg; return !!(a && a.complete && a.naturalWidth && b && b.complete && b.naturalWidth); } + function stackTowerScrollBgDrawActive() { + return stackTowerScrollBgImagesReady(); + } + function reloadStackTowerScrollBgFromMap() { stackTowerScrollBgIntroImg = null; stackTowerScrollBgLoopImg = null; @@ -345,7 +349,6 @@ stackTowerScrollBgSpeedPxPerSec = Math.max(8, Math.min(400, Math.floor(Number(raw.speedPxPerSec)) || 40)); const dirRaw = String(raw.scrollDirection || raw.direction || 'up').toLowerCase(); stackTowerScrollBgFlowDown = (dirRaw === 'down' || dirRaw === 'top' || dirRaw === 'toptobottom'); - if (stackTowerScrollBgOff) return; const introSrc = (typeof raw.introImage === 'string' && raw.introImage.length) ? raw.introImage : STACK_TOWER_SCROLL_BG_DEFAULT_INTRO; const loopSrc = (typeof raw.loopImage === 'string' && raw.loopImage.length) ? raw.loopImage : STACK_TOWER_SCROLL_BG_DEFAULT_LOOP; let pending = 2; @@ -440,8 +443,32 @@ ctx.restore(); } + /** กล้องมองเหนือขอบบนแมป (worldMinY<0) + ใช้รูปแผนที่ — ยัด loop ท้องฟ้าเติมครึ่งบนจอ */ + function drawStackTowerLoopSkyFillAboveMap(ctx, worldMinY, zDraw, cw, ch) { + if (!isStackTowerMissionUiMapPlay() || worldMinY >= 0) return; + const loop = stackTowerScrollBgLoopImg; + if (!loop || !loop.complete || !loop.naturalWidth) return; + const cwR = Math.max(1, Math.round(cw)); + const chR = Math.max(1, Math.round(ch)); + const scaleL = cwR / loop.naturalWidth; + const drawHLoop = Math.max(1, Math.round(loop.naturalHeight * scaleL)); + const skyScreenH = Math.min(chR, Math.ceil(-worldMinY * zDraw) + 4); + const scroll = (stackTowerScrollBgPx + getStackTowerBgScrollHeightBoostPx()) % drawHLoop; + let y = -scroll; + ctx.save(); + ctx.imageSmoothingEnabled = false; + while (y < skyScreenH + drawHLoop) { + if (y + drawHLoop > 0) { + ctx.drawImage(loop, 0, 0, loop.naturalWidth, loop.naturalHeight, 0, y, cwR, drawHLoop); + } + y += drawHLoop; + } + ctx.restore(); + } + function tickStackTowerScrollBg(dtSec) { - if (!stackTowerScrollBgDrawActive()) return; + if (!stackTowerScrollBgMapEligible() || stackTowerScrollBgOff) return; + if (!stackTowerScrollBgImagesReady()) return; stackTowerScrollBgPx += (stackTowerScrollBgSpeedPxPerSec || 40) * dtSec; } @@ -6890,7 +6917,9 @@ : 0.16 * offN * (hit.perfect ? 0.1 : 1); const isHumanPreview = !!(previewActor && previewActor.human); const botIdPreview = previewActor && previewActor.botId ? previewActor.botId : null; - const cableTopYRel = Math.max(0, swingAttachY - tileSize * 6); + const cableTopYRel = isStackTowerMissionUiMapPlay() + ? (swingAttachY - tileSize * 6) + : Math.max(0, swingAttachY - tileSize * 6); const craneXRel = m.craneWorldX != null ? m.craneWorldX : m.topCenterX * tileSize; const releaseRopeAng = Math.atan2(swingAttachY - cableTopYRel, swingXW - craneXRel); stackFall = { @@ -7076,7 +7105,9 @@ const floorY = m.floorWorldY; const swingLiftDraw = getStackTowerSwingLiftWorldPx(m.layers.length, layerWorldH); const swingDrawY = m.swingWorldY - swingLiftDraw; - const guideTopY = Math.max(0, swingDrawY - tileSize * 6.5); + const guideTopY = isStackTowerMissionUiMapPlay() + ? (swingDrawY - tileSize * 6.5) + : Math.max(0, swingDrawY - tileSize * 6.5); const [gtx, gty] = worldToScreen(m.topCenterX * tileSize, guideTopY); const [gbx, gby] = worldToScreen(m.topCenterX * tileSize, floorY); ctx.strokeStyle = 'rgba(255,255,255,0.22)'; @@ -7132,7 +7163,9 @@ } else { const twWorld = m.widthTiles * tileSize; const drawStripPx = stripH * zoom; - const cableTopY = Math.max(0, swingDrawY - tileSize * 6); + const cableTopY = isStackTowerMissionUiMapPlay() + ? (swingDrawY - tileSize * 6) + : Math.max(0, swingDrawY - tileSize * 6); const craneX = m.craneWorldX != null ? m.craneWorldX : m.topCenterX * tileSize; let blockLeftWorld; let blockTopWorld; @@ -12937,6 +12970,7 @@ /* กล้องเห็นพื้นที่นอก [0,map] ได้ — ห้ามสุ่มต้นทาง drawImage เกินขอบภาพ (จะเกิดซ้ำ/เส้นตัด/ซ้อนกันที่ขอบจอ) */ ctx.fillStyle = (isSpaceShooter() || isBalloonBoss()) ? '#0a0e22' : '#1a1b26'; ctx.fillRect(0, 0, canvas.width, canvas.height); + drawStackTowerLoopSkyFillAboveMap(ctx, worldMinY, zDraw, canvas.width, canvas.height); const natW = mapBackgroundImg.naturalWidth; const natH = mapBackgroundImg.naturalHeight; const bgMinX = Math.max(0, worldMinX); diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index af9f0b4..b72e4f8 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -2009,7 +2009,7 @@ - +