minigame 3add more design 1.1
This commit is contained in:
@@ -358,8 +358,8 @@
|
||||
<p class="legend" id="editor-status">บันทึกแล้วจะได้รหัสฉาก ใช้รหัสนี้ตอนสร้างห้องในล็อบบี้</p>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/version.js?v=0.0249"></script>
|
||||
<script src="js/editor.js?v=0.0249"></script>
|
||||
<script src="js/version.js?v=0.0253"></script>
|
||||
<script src="js/editor.js?v=0.0253"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+141
-17
@@ -196,10 +196,17 @@
|
||||
}
|
||||
|
||||
function pickStackBlockHeavyVisual(seat) {
|
||||
const pct = Math.max(0, Math.min(100, Math.floor(Number(playStackHeavyBlockPercent) || 0)));
|
||||
if (pct <= 0) return false;
|
||||
const slot = Math.max(0, Math.min(5, (Math.floor(Number(seat)) || 1) - 1));
|
||||
const hU = normalizeGauntletAssetUrlForPlay(playStackBlockHeavyUrls[slot] || '');
|
||||
/** Tower mnn93hpi: เกิน 50% ของ Progress 100% (ตาม stackTowerProgressBlocks) → ใช้รูป heavy จาก admin — นอก live ไม่สุ่ม heavy */
|
||||
if (isStackTowerMissionUiMapPlay()) {
|
||||
if (!(stackTowerMissionPhase === 'live' && stackMini)) return false;
|
||||
const p = Number(stackMini.progressPct);
|
||||
if (!Number.isFinite(p) || p <= 50) return false;
|
||||
return !!hU;
|
||||
}
|
||||
const pct = Math.max(0, Math.min(100, Math.floor(Number(playStackHeavyBlockPercent) || 0)));
|
||||
if (pct <= 0) return false;
|
||||
if (!hU) return false;
|
||||
if (Math.random() * 100 >= pct) return false;
|
||||
return true;
|
||||
@@ -280,6 +287,13 @@
|
||||
let stackFall = null;
|
||||
/** Tower / Stack — เชือกเหวี่ยงจาก `img/TowerBlock/sling.png` */
|
||||
let stackTowerSlingImg = null;
|
||||
/** Tower mnn93hpi — perfect stack FX (`img/TowerBlock/score-light.png`, `scorex2.png`) */
|
||||
let stackTowerScoreLightImg = null;
|
||||
let stackTowerScoreX2Img = null;
|
||||
const STACK_TOWER_PERFECT_GLOW_MS = 1500;
|
||||
const STACK_TOWER_PERFECT_X2_MS = 2200;
|
||||
/** ทับบล็อกก่อนหน้า + ชั้นใหม่: support เต็มแทบไม่มีเพี้ยน */
|
||||
const STACK_TOWER_PERFECT_SUPPORT_MIN = 0.998;
|
||||
let lastStackTickMs = performance.now();
|
||||
let lastStackTowerScrollBgTickMs = performance.now();
|
||||
let mapBackgroundImg = null;
|
||||
@@ -3999,6 +4013,60 @@
|
||||
return im && im.complete && im.naturalWidth > 0 ? im : null;
|
||||
}
|
||||
|
||||
function ensureStackTowerPerfectFxImagesPlay() {
|
||||
if (!stackTowerScoreLightImg) {
|
||||
stackTowerScoreLightImg = new Image();
|
||||
stackTowerScoreLightImg.decoding = 'async';
|
||||
stackTowerScoreLightImg.src = stackTowerAssetUrl('score-light.png');
|
||||
}
|
||||
if (!stackTowerScoreX2Img) {
|
||||
stackTowerScoreX2Img = new Image();
|
||||
stackTowerScoreX2Img.decoding = 'async';
|
||||
stackTowerScoreX2Img.src = stackTowerAssetUrl('scorex2.png');
|
||||
}
|
||||
return { light: stackTowerScoreLightImg, x2: stackTowerScoreX2Img };
|
||||
}
|
||||
|
||||
/** วาด score-light ทับขอบบล็อก (หลังสไปรต์ชั้น) */
|
||||
function drawStackTowerPerfectGlowOverlayPlay(ctx, sx, sy, drawW, drawH, lightImg, nowT, untilT) {
|
||||
if (!lightImg || !lightImg.complete || !lightImg.naturalWidth || drawW <= 0 || drawH <= 0) return;
|
||||
const life = Math.max(0, Math.min(1, (untilT - nowT) / STACK_TOWER_PERFECT_GLOW_MS));
|
||||
const pulse = 0.38 + 0.52 * Math.sin((1 - life) * Math.PI);
|
||||
const nw = lightImg.naturalWidth;
|
||||
const nh = lightImg.naturalHeight;
|
||||
const pad = 1.14 + (1 - life) * 0.06;
|
||||
const gw = drawW * pad;
|
||||
const gh = drawH * pad;
|
||||
const gx = sx + (drawW - gw) * 0.5;
|
||||
const gy = sy + (drawH - gh) * 0.5;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = Math.min(0.94, pulse);
|
||||
ctx.globalCompositeOperation = 'screen';
|
||||
try {
|
||||
ctx.drawImage(lightImg, 0, 0, nw, nh, gx, gy, gw, gh);
|
||||
} catch (e) { /* ignore */ }
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/** x2 ขวาชั้นบน — ทับจุดเดียวกับ mock (แสง + ไอคอน) */
|
||||
function drawStackTowerPerfectX2OverlayPlay(ctx, x2Img, sx, sy, drawW, drawH) {
|
||||
if (!x2Img || !x2Img.complete || !x2Img.naturalWidth || drawW <= 0 || drawH <= 0) return;
|
||||
const iw = x2Img.naturalWidth;
|
||||
const ih = x2Img.naturalHeight;
|
||||
const targetH = Math.max(drawH * 0.92, Math.min(drawH * 1.35, 72));
|
||||
const scale = targetH / ih;
|
||||
const dw = iw * scale;
|
||||
const dh = ih * scale;
|
||||
const cx = sx + drawW + drawW * 0.06;
|
||||
const cy = sy + drawH * 0.5;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.98;
|
||||
try {
|
||||
ctx.drawImage(x2Img, 0, 0, iw, ih, cx - dw * 0.48, cy - dh * 0.5, dw, dh);
|
||||
} catch (e) { /* ignore */ }
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* เชือกจากจุดหนีบบน (จอ) ลงจุดยึดบล็อก — รูป sling แนวตั้ง (แกน Y = สาย) ยืดตามความยาว
|
||||
*/
|
||||
@@ -7621,6 +7689,7 @@
|
||||
nextDropVisualDirty: true,
|
||||
pendingDropSeat: 1,
|
||||
pendingDropHeavy: false,
|
||||
perfectStackX2Fx: null,
|
||||
};
|
||||
if (previewFillBots) {
|
||||
me.stackPreviewHumanPts = 0;
|
||||
@@ -7650,6 +7719,13 @@
|
||||
const towerLive = isStackTowerMissionUiMapPlay() && stackTowerMissionPhase === 'live';
|
||||
if (hit.miss) {
|
||||
stackMini.combo = 0;
|
||||
stackMini.perfectStackX2Fx = null;
|
||||
if (stackMini.layers && stackMini.layers.length) {
|
||||
for (let gi = 0; gi < stackMini.layers.length; gi++) {
|
||||
const lay = stackMini.layers[gi];
|
||||
if (lay && lay.perfectGlowUntil != null) delete lay.perfectGlowUntil;
|
||||
}
|
||||
}
|
||||
if (towerLive && stackMini.teamMissesLeft != null) {
|
||||
stackMini.teamMissesLeft = Math.max(0, stackMini.teamMissesLeft - 1);
|
||||
} else {
|
||||
@@ -7658,6 +7734,7 @@
|
||||
stackMini.score = Math.max(0, stackMini.score - 2);
|
||||
stackMini.lives = 3;
|
||||
stackMini.layers = [];
|
||||
stackMini.perfectStackX2Fx = null;
|
||||
stackMini.widthTiles = stackMini.initialWidthTiles;
|
||||
const lb = getStackAreaBoundsPlay(mapData.stackLandArea, mapData.width || 20, mapData.height || 15);
|
||||
stackMini.topCenterX = lb ? lb.cx : (mapData.width || 20) * 0.5;
|
||||
@@ -7671,6 +7748,26 @@
|
||||
const seatLay = layerVisualMeta && layerVisualMeta.seat != null ? layerVisualMeta.seat : getStackDropActorSeat();
|
||||
const heavyLay = !!(layerVisualMeta && layerVisualMeta.heavy);
|
||||
stackMini.layers.push({ w: W, cx: hit.placedCx, seat: seatLay, heavy: heavyLay });
|
||||
const nAfter = stackMini.layers.length;
|
||||
const perfectFull =
|
||||
towerLive &&
|
||||
hit.perfect &&
|
||||
!hit.miss &&
|
||||
nAfter >= 2 &&
|
||||
hit.supportRatio != null &&
|
||||
hit.supportRatio >= STACK_TOWER_PERFECT_SUPPORT_MIN;
|
||||
if (perfectFull) {
|
||||
const gUntil = performance.now() + STACK_TOWER_PERFECT_GLOW_MS;
|
||||
stackMini.layers[nAfter - 1].perfectGlowUntil = gUntil;
|
||||
stackMini.layers[nAfter - 2].perfectGlowUntil = gUntil;
|
||||
ensureStackTowerPerfectFxImagesPlay();
|
||||
stackMini.perfectStackX2Fx = {
|
||||
until: performance.now() + STACK_TOWER_PERFECT_X2_MS,
|
||||
topLayerIndex: nAfter - 1,
|
||||
};
|
||||
} else if (towerLive) {
|
||||
stackMini.perfectStackX2Fx = null;
|
||||
}
|
||||
stackMini.score += hit.pts;
|
||||
if (towerLive && Number.isFinite(Number(hit.progressDelta)) && (hit.progressDelta || 0) > 0) {
|
||||
let np = Number(stackMini.progressPct) + Number(hit.progressDelta);
|
||||
@@ -8047,6 +8144,7 @@
|
||||
function drawStackMinigame(ctx, worldToScreen, zoom) {
|
||||
if (!stackMini) return;
|
||||
const m = stackMini;
|
||||
const towerHudLive = isStackTowerMissionHudActivePlay();
|
||||
ensureStackNextDropVisual();
|
||||
const layerWorldH = m.layerWorldH || Math.max(14, tileSize * 0.3);
|
||||
const stripH = m.blockStripH || Math.max(16, tileSize * 0.32);
|
||||
@@ -8082,6 +8180,36 @@
|
||||
const seatL = L.seat != null ? L.seat : 1;
|
||||
const heavyL = !!L.heavy;
|
||||
drawStackBlockSpriteOrHue(ctx, sx, sy, drawW, drawH, seatL, heavyL, hue, {});
|
||||
if (towerHudLive && L.perfectGlowUntil != null) {
|
||||
const ntGlow = performance.now();
|
||||
if (ntGlow < L.perfectGlowUntil) {
|
||||
const fxImG = ensureStackTowerPerfectFxImagesPlay();
|
||||
if (fxImG.light) {
|
||||
drawStackTowerPerfectGlowOverlayPlay(ctx, sx, sy, drawW, drawH, fxImG.light, ntGlow, L.perfectGlowUntil);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (towerHudLive && m.perfectStackX2Fx) {
|
||||
const fxX2 = m.perfectStackX2Fx;
|
||||
const ntX = performance.now();
|
||||
if (ntX >= fxX2.until) {
|
||||
m.perfectStackX2Fx = null;
|
||||
} else {
|
||||
const ti = fxX2.topLayerIndex;
|
||||
if (ti >= 0 && ti < m.layers.length) {
|
||||
const Ltop = m.layers[ti];
|
||||
const lwT = (Ltop.w != null ? Ltop.w : m.initialWidthTiles) * tileSize;
|
||||
const cxWT = Ltop.cx * tileSize;
|
||||
const ybT = floorY - (ti + 1) * layerWorldH;
|
||||
const xlT = cxWT - lwT / 2;
|
||||
const [sxT, syT] = worldToScreen(xlT, ybT);
|
||||
const drawWT = lwT * zoom;
|
||||
const drawHT = layerWorldH * zoom;
|
||||
const fxImX = ensureStackTowerPerfectFxImagesPlay();
|
||||
if (fxImX.x2) drawStackTowerPerfectX2OverlayPlay(ctx, fxImX.x2, sxT, syT, drawWT, drawHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m.settling) {
|
||||
const s = m.settling;
|
||||
@@ -8154,15 +8282,6 @@
|
||||
ctx.lineWidth = 1;
|
||||
ctx.restore();
|
||||
}
|
||||
const [fsx, fsy] = worldToScreen(0, floorY);
|
||||
const [fex, fey] = worldToScreen(mapData.width * tileSize, floorY);
|
||||
ctx.strokeStyle = 'rgba(247, 118, 190, 0.65)';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(fsx, fsy);
|
||||
ctx.lineTo(fex, fey);
|
||||
ctx.stroke();
|
||||
ctx.lineWidth = 1;
|
||||
}
|
||||
|
||||
function applyGauntletTimingFromServer(payload) {
|
||||
@@ -13895,7 +14014,7 @@
|
||||
const qText = document.getElementById('play-cyber-quiz-mission-q-text');
|
||||
const qPlaque = document.getElementById('play-cyber-quiz-mission-q-plaque');
|
||||
const tb = root ? root.querySelector('.play-cyber-time-block') : null;
|
||||
const on = isQuizQuestionMissionHudActivePlay();
|
||||
const on = isQuizQuestionMissionHudActivePlay() || isStackTowerMissionHudActivePlay();
|
||||
if (isQuizQuestionMissionUiMapPlay()) {
|
||||
if (qBand) {
|
||||
qBand.classList.add('is-hidden');
|
||||
@@ -14031,7 +14150,10 @@
|
||||
const gw = document.querySelector('.game-wrap');
|
||||
if (gw) gw.classList.toggle('play-cyber-active', !!on);
|
||||
if (!on || !root) return;
|
||||
root.classList.toggle('play-cyber-hud--question-mission', !!isQuizQuestionMissionHudActivePlay());
|
||||
root.classList.toggle(
|
||||
'play-cyber-hud--question-mission',
|
||||
!!(isQuizQuestionMissionHudActivePlay() || isStackTowerMissionHudActivePlay()),
|
||||
);
|
||||
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' : '';
|
||||
@@ -14289,6 +14411,8 @@
|
||||
const jumpMissionHud = isJumpSurvive() && isJumpSurviveMissionUiMapPlay();
|
||||
const quizMissionHud = isQuizQuestionMissionHudActivePlay();
|
||||
const stackMissionHud = isStackTowerMissionHudActivePlay();
|
||||
/** แถบ SCORE แบบ mock mng8a80o (แนวนอน อวาตาร์+ชื่อ | คะแนน) — ใช้กับภารกิจคำถาม + Stack Tower mnn93hpi */
|
||||
const cyberQmMockHud = quizMissionHud || stackMissionHud;
|
||||
const stackTeamPts = Math.max(0, Number(stackMini && stackMini.score) || 0);
|
||||
const rows = [];
|
||||
rows.push({
|
||||
@@ -14368,22 +14492,22 @@
|
||||
const row = hudRows[r];
|
||||
const li = document.createElement('li');
|
||||
li.className = 'play-cyber-score-row' + (row.isMe ? ' is-me' : '') + (row.eliminated ? ' is-out' : '')
|
||||
+ (quizMissionHud ? ' play-cyber-score-row--qm-mock' : '')
|
||||
+ (cyberQmMockHud ? ' play-cyber-score-row--qm-mock' : '')
|
||||
+ (crownStripHud ? ' play-cyber-score-row--crown-strip' : '');
|
||||
const av = document.createElement('img');
|
||||
av.className = 'play-cyber-score-av' + (quizMissionHud ? ' play-cyber-score-av--qm' : '')
|
||||
av.className = 'play-cyber-score-av' + (cyberQmMockHud ? ' play-cyber-score-av--qm' : '')
|
||||
+ (crownStripHud ? ' play-cyber-score-av--crown-strip' : '');
|
||||
av.alt = '';
|
||||
setCyberHudScoreAvatarImg(av, row);
|
||||
const sc = document.createElement('span');
|
||||
sc.className = 'play-cyber-score-val' + (quizMissionHud ? ' play-cyber-qm-score' : '')
|
||||
sc.className = 'play-cyber-score-val' + (cyberQmMockHud ? ' play-cyber-qm-score' : '')
|
||||
+ (crownStripHud ? ' play-cyber-crown-strip-val' : '');
|
||||
if (isGauntlet() || isSpaceShooter() || isBalloonBoss() || isQuizCarry() || jumpMissionHud || quizMissionHud || stackMissionHud) {
|
||||
sc.textContent = String(row.score);
|
||||
} else {
|
||||
sc.textContent = row.eliminated ? '—' : String(r + 1);
|
||||
}
|
||||
if (quizMissionHud) {
|
||||
if (cyberQmMockHud) {
|
||||
if (row.id === leaderId && leaderId != null) {
|
||||
const crown = document.createElement('span');
|
||||
crown.className = 'play-cyber-lead-crown';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// ทุกครั้งที่มีการเพิ่มหรือเปลี่ยน ให้เพิ่ม v +0.0001
|
||||
// หลังแก้ค่าแล้วต้อง copy ไป path ที่ Nginx ชี้ (หรือรัน copy-frogger-files-only.sh) ถึงจะเห็นบนเว็บ
|
||||
window.APP_VERSION = '0.0249';
|
||||
window.APP_VERSION = '0.0253';
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var t = document.querySelector('.version-tag');
|
||||
if (t) t.textContent = 'v ' + window.APP_VERSION;
|
||||
|
||||
@@ -2713,8 +2713,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0249"></script>
|
||||
<script src="js/play.js?v=0.0249"></script>
|
||||
<script src="js/version.js?v=0.0253"></script>
|
||||
<script src="js/play.js?v=0.0253"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user