minigame 3add more design 1.6

This commit is contained in:
2026-05-04 17:56:06 +00:00
parent b95c54f57e
commit b481e585a3
3 changed files with 156 additions and 35 deletions
@@ -24,6 +24,9 @@ English: Upload Stack / tower assets under **`TowerBlock`** (no spaces). **`Towe
| `result-check.png` (ถ้ามี) | ไอคอนติ๊ก «ภารกิจสำเร็จ» บนสรุป — ไม่มีจะใช้ของ gauntlet |
| `sling.png` | เชือกเหวี่ยงบล็อกในเกม (ยืดจากจุดแขวนถึงบล็อก) — แนวตั้งในไฟล์ = แนวสาย; ไม่มีไฟล์จะใช้เส้นประแทน |
| `heart-1.png` | โชว์กลางเหนือกองเมื่อ **พลาด** (Tower live) — ไม่มีจะ fallback ข้อความ `♥ 1` |
| `life-full.png` | HUD มุมขวา: หัวใจ **เต็ม** ในแถบชีวิต (คู่ `life-hit.png`) — ไม่มีจะ fallback เป็น `heart-1.png` |
| `life-bar.png` | HUD: สไตล์ข้อความ **SYSTEM INTEGRITY** + วงเล็บ — วาง **ซ้ายของอวตาร** ในแผง cyan (mock รูป2) |
| `life-hit.png` | หัวใจ **หาย / โดนตี** ในแถบชีวิต (คู่ `life-full.png`) |
| `btn-drop.png` | ปุ่ม **DROP** มุมขวาล่างใน `play.html` (เทียบ Space / Enter) |
| `score+10.png` | Tower live: โชว์เหนือบล็อกเมื่อได้ **+10** |
| `score+20.png` | Tower live: เหนือบล็อกเมื่อ **×2 (+20)** หรือ **progress ถึง ~100%**; ถ้า perfect คู่ (support เต็ม) จะโชว์บนชั้นบน **และ** ชั้นก่อนหน้า |
+148 -34
View File
@@ -324,8 +324,12 @@
let stackTowerScorePlus20Img = null;
let stackTowerScoreLightImg = null;
let stackTowerScoreX2Img = null;
/** พลาดแล้วโชว์ heart-1.png กลางเหนือกอง (mock Tower) */
/** พลาดแล้วโชว์ heart-1.png กลางเหนือกอง (mock Tower) — heart เต็มใน HUD ใช้ไฟล์เดียวกัน */
let stackTowerHeartMinusImg = null;
/** HUD มุมขวา: `life-bar.png` + `life-full.png` / `life-hit.png` (mock รูป2) */
let stackTowerLifeBarImg = null;
let stackTowerLifeHitImg = null;
let stackTowerLifeFullImg = null;
/** @type {{ until: number } | null} */
let stackTowerHeartMinusFx = null;
const STACK_TOWER_HEART_MINUS_FX_MS = 1500;
@@ -4188,6 +4192,102 @@
return im && im.complete && im.naturalWidth > 0 ? im : null;
}
function ensureStackTowerLifeHudImagesPlay() {
if (!stackTowerLifeBarImg) {
stackTowerLifeBarImg = new Image();
stackTowerLifeBarImg.decoding = 'async';
stackTowerLifeBarImg.src = stackTowerAssetUrl('life-bar.png');
}
if (!stackTowerLifeHitImg) {
stackTowerLifeHitImg = new Image();
stackTowerLifeHitImg.decoding = 'async';
stackTowerLifeHitImg.src = stackTowerAssetUrl('life-hit.png');
}
if (!stackTowerLifeFullImg) {
stackTowerLifeFullImg = new Image();
stackTowerLifeFullImg.decoding = 'async';
stackTowerLifeFullImg.src = stackTowerAssetUrl('life-full.png');
}
}
/**
* นทระหวางวงเลบบน life-bar.png (normalized 01) ปรบถ asset เปลยนเลยเอาต
* UV rect on life-bar.png for heart row between brackets (tune if art changes).
*/
const STACK_TOWER_LIFE_BAR_HEART_SLOT_UV = { u0: 0.11, u1: 0.89, v0: 0.40, v1: 0.86 };
/**
* วาด life-bar + วใจ life-full (เต) / life-hit (หาย) ในกรอบ dest นความสงจรงทวาด
*/
function drawStackTowerLifeIntegrityBarPlay(ctx, destX, destY, maxW, maxH, lifeSlots, livesRem) {
ensureStackTowerLifeHudImagesPlay();
const bar = stackTowerLifeBarImg;
const fullIm = (stackTowerLifeFullImg && stackTowerLifeFullImg.complete && stackTowerLifeFullImg.naturalWidth > 0)
? stackTowerLifeFullImg
: ensureStackTowerHeartMinusImgPlay();
const hitIm = stackTowerLifeHitImg && stackTowerLifeHitImg.complete && stackTowerLifeHitImg.naturalWidth > 0
? stackTowerLifeHitImg
: null;
const slots = Math.max(1, Math.min(20, Math.floor(lifeSlots) || 1));
const lives = Math.max(0, Math.min(slots, Math.floor(livesRem) || 0));
if (!bar || !bar.complete || !bar.naturalWidth || maxW <= 4 || maxH <= 4) {
ctx.save();
ctx.fillStyle = '#bb9af7';
ctx.font = '600 9px ui-sans-serif, system-ui, sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
let heartStr = '';
for (let i = 0; i < slots; i++) heartStr += i < lives ? '♥ ' : '♡ ';
ctx.fillText('SYSTEM INTEGRITY ' + heartStr.trim(), destX, destY + maxH * 0.5);
ctx.restore();
return Math.min(maxH, 28);
}
const bw0 = bar.naturalWidth;
const bh0 = bar.naturalHeight;
const scale = Math.min(maxW / bw0, maxH / bh0, 1.35);
const bw = bw0 * scale;
const bh = bh0 * scale;
const bx = destX;
const by = destY + Math.max(0, (maxH - bh) * 0.5);
ctx.save();
try {
ctx.drawImage(bar, 0, 0, bw0, bh0, bx, by, bw, bh);
} catch (e) { /* ignore */ }
const uv = STACK_TOWER_LIFE_BAR_HEART_SLOT_UV;
const ix = bx + uv.u0 * bw;
const iy = by + uv.v0 * bh;
const iwSlot = Math.max(4, (uv.u1 - uv.u0) * bw);
const ihSlot = Math.max(4, (uv.v1 - uv.v0) * bh);
const slotW = iwSlot / slots;
const iconMax = Math.min(slotW * 0.82, ihSlot * 0.88, 36);
for (let i = 0; i < slots; i++) {
const cx = ix + slotW * (i + 0.5);
const cy = iy + ihSlot * 0.5;
const useFull = i < lives;
const im = useFull ? fullIm : hitIm;
if (im && im.complete && im.naturalWidth > 0 && im.naturalHeight > 0) {
const ih = iconMax;
const iw = (ih * im.naturalWidth) / im.naturalHeight;
try {
ctx.drawImage(im, 0, 0, im.naturalWidth, im.naturalHeight, cx - iw / 2, cy - ih / 2, iw, ih);
} catch (e2) { /* ignore */ }
} else {
ctx.fillStyle = useFull ? '#9ece6a' : '#565f89';
ctx.font = 'bold ' + Math.round(Math.max(10, iconMax * 0.72)) + 'px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(useFull ? '♥' : '♡', cx, cy);
}
}
ctx.restore();
return bh;
}
function triggerStackTowerHeartMinusFxPlay() {
if (!isStackTowerMissionHudActivePlay()) return;
stackTowerHeartMinusFx = { until: performance.now() + STACK_TOWER_HEART_MINUS_FX_MS };
@@ -7989,6 +8089,10 @@
stackPreviewHudLog = [];
}
ensureStackTowerSlingImagePlay();
if (towerMission || previewFillBots) {
ensureStackTowerLifeHudImagesPlay();
ensureStackTowerHeartMinusImgPlay();
}
}
function stackEaseDrop(t) {
@@ -14236,18 +14340,29 @@
drawRows(rows);
}
const rightW = 136;
const framePad = 10;
const bigAv = Math.min(72, Math.max(52, ch * 0.10));
const avBox = bigAv + 10;
const integrityMinW = 120;
const rightW = Math.min(300, Math.max(236, Math.round(cw * 0.34)));
let rightX = cw - pad - rightW;
const topY = 8;
if (rightX < pad + boardW + 14) {
rightX = Math.max(pad, cw - pad - rightW);
}
const bigAv = Math.min(78, Math.max(56, ch * 0.11));
const turnPanelH = bigAv + 118;
const integrityW = Math.max(integrityMinW, rightW - framePad * 2 - avBox - 10);
const innerTop = topY + framePad;
const integrityX = rightX + framePad;
const integrityH = avBox + 2;
const avFrameX = integrityX + integrityW + 10;
const avFrameY = innerTop;
const footerH = 26;
const turnPanelH = framePad + integrityH + 10 + footerH + framePad;
ctx.fillStyle = 'rgba(8, 12, 28, 0.9)';
roundRectPath(rightX, topY, rightW, turnPanelH, 8);
ctx.fill();
ctx.strokeStyle = 'rgba(187, 154, 247, 0.55)';
ctx.strokeStyle = 'rgba(0, 230, 255, 0.55)';
ctx.lineWidth = 1.5;
roundRectPath(rightX, topY, rightW, turnPanelH, 8);
ctx.stroke();
@@ -14257,39 +14372,34 @@
curEntry = order.find((e) => e.seat === currentSeat) || null;
}
const curV = curEntry ? resolveEntryVisual(curEntry) : resolveEntryVisual({ kind: 'human', seat: 1 });
const framePad = 8;
const avCx = rightX + rightW / 2;
const bigFrameTop = topY + framePad - 2;
const bigFrameH = bigAv + 8;
ctx.strokeStyle = 'rgba(255, 120, 200, 0.75)';
ctx.lineWidth = 2;
roundRectPath(rightX + framePad - 2, topY + framePad - 2, rightW - (framePad - 2) * 2, bigAv + 8, 6);
ctx.stroke();
drawHudAvatar(avCx, bigFrameTop + bigFrameH - 4, bigAv, curV.characterId, curV.playTint);
ctx.fillStyle = '#f5c2e7';
ctx.font = 'bold 10px ui-sans-serif, system-ui, sans-serif';
drawStackTowerLifeIntegrityBarPlay(ctx, integrityX, innerTop - 2, integrityW, integrityH + 4, lifeSlots, livesRem);
const avCx = avFrameX + avBox / 2;
ctx.save();
ctx.shadowColor = 'rgba(0, 234, 255, 0.5)';
ctx.shadowBlur = 12;
ctx.strokeStyle = 'rgba(0, 234, 255, 0.92)';
ctx.lineWidth = 2;
roundRectPath(avFrameX - 1, avFrameY - 1, avBox + 2, avBox + 2, 6);
ctx.stroke();
ctx.restore();
ctx.strokeStyle = 'rgba(0, 234, 255, 0.45)';
ctx.lineWidth = 1;
roundRectPath(avFrameX - 1, avFrameY - 1, avBox + 2, avBox + 2, 6);
ctx.stroke();
drawHudAvatar(avCx, avFrameY + avBox - 5, bigAv, curV.characterId, curV.playTint);
const footY = innerTop + integrityH + 12;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText('ACTIVE NODE', rightX + rightW / 2, topY + bigAv + 22);
ctx.fillStyle = '#7dfcff';
ctx.font = 'bold 11px ' + mono;
ctx.fillText('P' + currentSeat + ' · ' + curV.name.slice(0, 10), rightX + rightW / 2, topY + bigAv + 38);
ctx.fillStyle = '#bb9af7';
ctx.font = '600 9px ui-sans-serif, system-ui, sans-serif';
ctx.fillText('SYSTEM INTEGRITY', rightX + rightW / 2, topY + bigAv + 58);
ctx.font = '14px sans-serif';
let heartStr = '';
for (let i = 0; i < lifeSlots; i++) {
heartStr += i < livesRem ? '♥ ' : '♡ ';
}
ctx.fillStyle = livesRem <= 1 ? '#f7768e' : '#9ece6a';
ctx.fillText(heartStr.trim(), rightX + rightW / 2, topY + bigAv + 74);
ctx.fillStyle = 'rgba(125, 252, 255, 0.85)';
ctx.fillStyle = livesRem <= 1 ? '#f7768e' : 'rgba(125, 252, 255, 0.88)';
ctx.font = monoSm;
ctx.fillText('TEAM ' + teamScore + ' · COMBO x' + combo, rightX + rightW / 2, topY + bigAv + 96);
ctx.fillText(
'P' + currentSeat + ' · ' + curV.name.slice(0, 9) + ' · TEAM ' + teamScore + ' · COMBO x' + combo,
rightX + rightW / 2,
footY,
);
const termW = Math.min(380, Math.max(220, cw * 0.42));
const logLineH = 12;
@@ -14503,6 +14613,10 @@
'play-cyber-hud--question-mission',
!!(isQuizQuestionMissionHudActivePlay() || isStackTowerMissionHudActivePlay()),
);
root.classList.toggle(
'play-cyber-hud--stack-tower-canvas-hud',
!!(previewFillBots && stackMini && isStack() && isStackTowerMissionUiMapPlay()),
);
root.classList.toggle('play-cyber-hud--gauntlet-crown-strip', !!(on && isGauntletCrownHeistMapPlay()));
const timeLabelEl = root.querySelector('.play-cyber-time-label');
if (timeLabelEl) timeLabelEl.style.display = isQuizCarry() ? 'none' : '';
+5 -1
View File
@@ -1674,6 +1674,10 @@
#play-cyber-hud.play-cyber-hud--question-mission .play-cyber-preview-line {
display: none !important;
}
/* Stack Tower: แผงโปรไฟล์วาดบน canvas — ซ่อนกล่อง portrait DOM ขวา ไม่ให้ซ้ำกับอวตารในแผง SYSTEM INTEGRITY */
#play-cyber-hud.play-cyber-hud--stack-tower-canvas-hud .play-cyber-self {
display: none !important;
}
/* Last Light (mno9kb07): แถบคะแนนแนวนอน — score.png แล้วตามด้วยสูงสุด 6 ช่อง (อวาตาร์ | ชื่อบน / คะแนนล่าง) */
#play-cyber-hud.play-cyber-hud--gauntlet-crown-strip .play-cyber-hint {
display: none !important;
@@ -2749,7 +2753,7 @@
</div>
<script src="/Game/socket.io/socket.io.js"></script>
<script src="js/version.js?v=0.0258"></script>
<script src="js/play.js?v=0.0258"></script>
<script src="js/play.js?v=0.0259"></script>
<div class="version-tag">v —</div>
</body>
</html>