minigame 2 add more design 2.7
This commit is contained in:
File diff suppressed because one or more lines are too long
+174
-36
@@ -34,9 +34,23 @@
|
||||
const QUIZ_QUESTION_MISSION_MAP_ID = 'mng8a80o';
|
||||
/** Stack ซ้อนตึก — ฉากภารกิจ (HOWTO → นับถอยหลัง → เล่น → สรุป) รูปใน `/Game/img/TowerBlock` */
|
||||
const STACK_TOWER_MISSION_MAP_ID = 'mnn93hpi';
|
||||
/** Stack Tower: ความละเอียดบัฟเฟอร์วาดคงที่ — ย่อ/ขยายด้วย CSS เท่านั้น กัน BG กับตำแหน่งบล็อกเลื่อนตามขนาดหน้าต่าง */
|
||||
const STACK_TOWER_FIXED_RENDER_W = 1280;
|
||||
const STACK_TOWER_FIXED_RENDER_H = 720;
|
||||
/** Stack Tower: บัฟเฟอร์วาดคงที่ 16:9 — 1920×1080 */
|
||||
const STACK_TOWER_FIXED_RENDER_W = 1920;
|
||||
const STACK_TOWER_FIXED_RENDER_H = 1080;
|
||||
/** คูณ zDraw ให้มุมมองโลกเท่าเดิมเมื่อบัฟเฟอร์กว้างกว่าฐานอ้างอิง 1280w (= 1.5 ที่ 1920w) */
|
||||
const STACK_TOWER_FIXED_RENDER_Z_MUL = STACK_TOWER_FIXED_RENDER_W / 1280;
|
||||
/** เชือก Stack Tower: ปลายล่างตามเส้น (1 = ถึงจุดยึดบล็อก) */
|
||||
const STACK_TOWER_ROPE_DRAW_T1 = 1.0;
|
||||
/** ปลายบนเชือก (จอ): ยืดเหนือขอบบนแคนวาสเป็นเศษส่วนของความสูง (เช่น 0.25 = 25% นอกจอด้านบน) */
|
||||
const STACK_TOWER_ROPE_TOP_ABOVE_CANVAS_FRAC = 0.25;
|
||||
/** progress ≥ นี้: แอนิเมตซูม + เลื่อนแมป (ครั้งเดียวต่อรอบ stack) */
|
||||
const STACK_TOWER_POST50_PROGRESS_THRESH = 50;
|
||||
const STACK_TOWER_POST50_ZOOM_MUL = 1.1;
|
||||
/** เลื่อน BG แนวตั้งเท่าเศษส่วนของความสูงแคนวาส (จอ) */
|
||||
const STACK_TOWER_POST50_MAP_SHIFT_SCREEN_FRAC = 0.1;
|
||||
const STACK_TOWER_POST50_ANIM_MS = 680;
|
||||
/** หลัง 50%: เลื่อนกล้องลงในมุมมองจอเท่าเศษส่วนของความสูงแคนวาส (เช่น 0.25 = 25% ของความสูงจอ) */
|
||||
const STACK_TOWER_POST50_VIEW_SHIFT_SCREEN_FRAC = 0.25;
|
||||
/** Mega Virus — balloon_boss ฉากภารกิจ (flow เดียวกับ crown / mno9kb07) รูปใน `/Game/img/MegaVirus` */
|
||||
const BALLOON_BOSS_MISSION_MAP_ID = 'mnq1eml7';
|
||||
/** ครบชั้นนี้ขึ้นไป — ยกจุดแกว่ง/เชือกขึ้น (world px) ป้องกันชนกองสูง (เฉพาะ Tower mission) */
|
||||
@@ -1074,7 +1088,8 @@
|
||||
const scaleL = cwR / loop.naturalWidth;
|
||||
const drawHLoop = Math.max(1, Math.round(loop.naturalHeight * scaleL));
|
||||
const camFollowScreen = isStackTowerMissionUiMapPlay() ? getStackTowerBgScrollHeightBoostPx() * zPan : 0;
|
||||
const S = stackTowerScrollBgPx + camFollowScreen;
|
||||
const post50Scroll = isStackTowerMissionUiMapPlay() ? getStackTowerPost50BgScrollExtraPxPlay(chR) : 0;
|
||||
const S = stackTowerScrollBgPx + camFollowScreen + post50Scroll;
|
||||
const vp0 = S;
|
||||
const vp1 = S + chR;
|
||||
const yLimit = vp1 + drawHLoop * 2;
|
||||
@@ -1138,7 +1153,8 @@
|
||||
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 rawScroll = stackTowerScrollBgPx + getStackTowerBgScrollHeightBoostPx() * zDraw;
|
||||
const rawScroll = stackTowerScrollBgPx + getStackTowerBgScrollHeightBoostPx() * zDraw
|
||||
+ getStackTowerPost50BgScrollExtraPxPlay(chR);
|
||||
const scroll = ((rawScroll % drawHLoop) + drawHLoop) % drawHLoop;
|
||||
let y = -scroll;
|
||||
ctx.save();
|
||||
@@ -1158,6 +1174,19 @@
|
||||
stackTowerScrollBgPx += (stackTowerScrollBgSpeedPxPerSec || 40) * dtSec;
|
||||
}
|
||||
|
||||
/**
|
||||
* ค่าลบจาก screen Y ใน worldToScreen — ให้บล็อก/กริดเลื่อนตามฉาก BG ที่ stackTowerScrollBgPx เลื่อน (ซิงก์กับ drawStackTowerScrollBgFullCanvas)
|
||||
*/
|
||||
function getStackTowerWorldLayerScrollScreenOffsetYPlay(zPan) {
|
||||
if (!isStackTowerMissionUiMapPlay() || !stackTowerScrollBgDrawActive() || !stackTowerScrollBgImagesReady()) return 0;
|
||||
if (stackTowerScrollBgOff) return 0;
|
||||
const zPn = Number(zPan) > 0 ? zPan : zoom;
|
||||
const zRef = stackTowerScrollBgZoomRefPlay > 0 ? stackTowerScrollBgZoomRefPlay : zPn;
|
||||
const bgZm = Math.max(0.28, Math.min(4.25, zPn / zRef));
|
||||
const raw = stackTowerScrollBgPx / bgZm;
|
||||
return stackTowerScrollBgFlowDown ? -raw : raw;
|
||||
}
|
||||
|
||||
/** โหลดจาก mapData.gridImageLibrary — ดัชนีตรงกับ gridImageCells[y][x] */
|
||||
let mapGridImageImgs = [];
|
||||
let mapGridImageHeldImgs = [];
|
||||
@@ -2081,6 +2110,8 @@
|
||||
let stackTowerMissionDeferredPhase = null;
|
||||
let stackTowerSessionStartAt = 0;
|
||||
let stackTowerMissionEndedOnce = false;
|
||||
/** progress ข้าม 50%: เริ่มแอนิเมตซูม+เลื่อนแมป — null ยังไม่ข้าม, ตัวเลข = performance.now() ตอนเริ่ม */
|
||||
let stackTowerPost50AnimStartMs = null;
|
||||
/** แฟลชรูปผล Tower (timeup / gameover / complete) ก่อน GCM 5 วิ */
|
||||
let stackTowerResultFlashTimer = null;
|
||||
const STACK_TOWER_RESULT_FLASH_MS = 5000;
|
||||
@@ -4167,8 +4198,8 @@
|
||||
}
|
||||
/** Last Light (mno9kb07) ใน iframe เอดิเตอร์: ซูมคงที่ — ผู้เล่นปรับเองไม่ได้ */
|
||||
const PLAY_EMBED_LAST_LIGHT_FIXED_ZOOM_MUL = 1.49;
|
||||
/** Stack Tower (mnn93hpi) ใน iframe เอดิเตอร์: ซูมคงที่ 1.22 — ล้อ/ปุ่มไม่ปรับได้ */
|
||||
const PLAY_EMBED_STACK_TOWER_FIXED_ZOOM_MUL = 1.22;
|
||||
/** Stack Tower (mnn93hpi) ใน embed พรีวิว: ซูมคงที่ ×1 — ล้อ/ปุ่มไม่ปรับได้ */
|
||||
const PLAY_EMBED_STACK_TOWER_FIXED_ZOOM_MUL = 1;
|
||||
function isLastLightEmbedZoomLockedPlay() {
|
||||
return !!(previewMode && editorEmbedReturn && mapData && isGauntletCrownHeistMapPlay());
|
||||
}
|
||||
@@ -4182,6 +4213,39 @@
|
||||
playEmbedUserZoomMul = PLAY_EMBED_STACK_TOWER_FIXED_ZOOM_MUL;
|
||||
}
|
||||
}
|
||||
|
||||
/** smoothstep 0..1 */
|
||||
function stackTowerPost50SmoothstepPlay(t) {
|
||||
const x = Math.max(0, Math.min(1, t));
|
||||
return x * x * (3 - 2 * x);
|
||||
}
|
||||
|
||||
/** Tower live + progress ≥50%: 0→1 ระหว่าง STACK_TOWER_POST50_ANIM_MS */
|
||||
function getStackTowerPost50AnimEasePlay() {
|
||||
if (!isStackTowerMissionUiMapPlay() || stackTowerMissionPhase !== 'live' || !stackMini) return 0;
|
||||
const p = Number(stackMini.progressPct) || 0;
|
||||
if (p < STACK_TOWER_POST50_PROGRESS_THRESH) {
|
||||
stackTowerPost50AnimStartMs = null;
|
||||
return 0;
|
||||
}
|
||||
if (stackTowerPost50AnimStartMs == null) stackTowerPost50AnimStartMs = performance.now();
|
||||
const u = Math.min(1, Math.max(0, (performance.now() - stackTowerPost50AnimStartMs) / STACK_TOWER_POST50_ANIM_MS));
|
||||
return stackTowerPost50SmoothstepPlay(u);
|
||||
}
|
||||
|
||||
function getStackTowerPost50ZMulPlay() {
|
||||
const e = getStackTowerPost50AnimEasePlay();
|
||||
if (e <= 0) return 1;
|
||||
return 1 + (STACK_TOWER_POST50_ZOOM_MUL - 1) * e;
|
||||
}
|
||||
|
||||
function getStackTowerPost50BgScrollExtraPxPlay(ch) {
|
||||
const e = getStackTowerPost50AnimEasePlay();
|
||||
if (e <= 0) return 0;
|
||||
const chR = Math.max(1, Number(ch) || 720);
|
||||
return STACK_TOWER_POST50_MAP_SHIFT_SCREEN_FRAC * chR * e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same zDraw chain as draw() (zoom, special maps, Last Light embed lock, preview embed mul).
|
||||
* Runway end checks must not rely on lastPlayZDrawForInput alone — it can lag map/canvas or default 1.4 vs real zoom.
|
||||
@@ -4209,6 +4273,10 @@
|
||||
if (previewMode && editorEmbedReturn && mapData && !isQuizQuestionMissionHudActivePlay()) {
|
||||
zDraw *= playEmbedUserZoomMul;
|
||||
}
|
||||
if (isStackTowerMissionUiMapPlay()) {
|
||||
zDraw *= STACK_TOWER_FIXED_RENDER_Z_MUL;
|
||||
zDraw *= getStackTowerPost50ZMulPlay();
|
||||
}
|
||||
return zDraw;
|
||||
}
|
||||
let gauntletCrownHowtoVisible = false;
|
||||
@@ -4415,14 +4483,16 @@
|
||||
const dur = STACK_TOWER_SCORE_POPUP_MS;
|
||||
const imgs = ensureStackTowerScorePopupImagesPlay();
|
||||
m.scorePopups = m.scorePopups.filter((p) => p.until > now);
|
||||
const hidPop = getStackTowerVisualHiddenLayerCountPlay();
|
||||
for (let pi = 0; pi < m.scorePopups.length; pi++) {
|
||||
const p = m.scorePopups[pi];
|
||||
const li = Math.floor(Number(p.layerIndex)) || 0;
|
||||
if (li < 0 || li >= m.layers.length) continue;
|
||||
if (li < hidPop) continue;
|
||||
const L = m.layers[li];
|
||||
const lw = (L.w != null ? L.w : m.initialWidthTiles) * tileSize;
|
||||
const cxW = L.cx * tileSize;
|
||||
const yb = floorY - (li + 1) * layerWorldH;
|
||||
const yb = floorY - (li + 1 - hidPop) * layerWorldH;
|
||||
const xl = cxW - lw / 2;
|
||||
const [sx, sy] = worldToScreen(xl, yb);
|
||||
const drawW = lw * zoom;
|
||||
@@ -4614,7 +4684,8 @@
|
||||
const u = Math.max(0, Math.min(1, (nt - t0) / dur));
|
||||
const alpha = 1 - u * u;
|
||||
const worldCx = m.topCenterX * tileSize;
|
||||
const stackTopY = floorY - nLay * layerWorldH;
|
||||
const hidHm = getStackTowerVisualHiddenLayerCountPlay();
|
||||
const stackTopY = floorY - (nLay - hidHm) * layerWorldH;
|
||||
const worldCy = stackTopY - tileSize * 1.55 - u * tileSize * 0.5;
|
||||
const [sx, sy] = worldToScreen(worldCx, worldCy);
|
||||
const im = ensureStackTowerHeartMinusImgPlay();
|
||||
@@ -4716,6 +4787,7 @@
|
||||
stackTowerMissionDeferredPhase = null;
|
||||
stackTowerSessionStartAt = 0;
|
||||
stackTowerMissionEndedOnce = false;
|
||||
stackTowerPost50AnimStartMs = null;
|
||||
hideStackTowerHowtoRulesDom();
|
||||
setGauntletCrownHowtoStackTowerArtMaskPlay(false);
|
||||
if (stackTowerMissionCountdownTimer) {
|
||||
@@ -8580,6 +8652,16 @@
|
||||
return Math.min(lh * 20, tiers * lh * 0.98);
|
||||
}
|
||||
|
||||
/** progress ≥50% + ชั้นพอ: ไม่วาดครึ่งล่างของกอง — ซ้อนเฉพาะครึ่งบนจากพื้น (เฉพาะภาพ ไม่แก้ logic เกม) */
|
||||
function getStackTowerVisualHiddenLayerCountPlay() {
|
||||
if (!isStackTowerMissionHudActivePlay() || !stackMini) return 0;
|
||||
const p = Number(stackMini.progressPct) || 0;
|
||||
if (p < STACK_TOWER_POST50_PROGRESS_THRESH) return 0;
|
||||
const n = stackMini.layers ? stackMini.layers.length : 0;
|
||||
if (n < 4) return 0;
|
||||
return Math.floor(n / 2);
|
||||
}
|
||||
|
||||
/** world px ตามความสูงหอ (mnn93hpi) — ใช้เลื่อนกล้องให้บล็อก/ฉากไปกับมุมมอง */
|
||||
function getStackTowerBgScrollHeightBoostPx() {
|
||||
if (!isStackTowerMissionUiMapPlay() || !stackMini) return 0;
|
||||
@@ -8595,10 +8677,10 @@
|
||||
return Math.min(cfg.maxPx, layerCount * cfg.pxPerLayer);
|
||||
}
|
||||
|
||||
/** กล้อง stack: จุดกลางจาก land / release ของแมปเท่านั้น (Tower ไม่เลื่อน cy ตามหอ / post-50 / ตามบล็อก) */
|
||||
function getStackCameraCentersPx() {
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
const ts = tileSize;
|
||||
const mapHpx = h * ts;
|
||||
const land = getStackAreaBoundsPlay(mapData.stackLandArea, w, h);
|
||||
const rel = getStackAreaBoundsPlay(mapData.stackReleaseArea, w, h);
|
||||
let cx = (w / 2) * ts;
|
||||
@@ -8613,12 +8695,6 @@
|
||||
cx = rel.cx * ts;
|
||||
cy = rel.cy * ts;
|
||||
}
|
||||
if (isStackTowerMissionUiMapPlay() && stackMini) {
|
||||
const shift = getStackTowerBgScrollHeightBoostPx();
|
||||
if (shift > 0) {
|
||||
cy = Math.max(ts * 1.5, Math.min(mapHpx - ts * 2, cy - shift));
|
||||
}
|
||||
}
|
||||
return { px: cx, py: cy };
|
||||
}
|
||||
|
||||
@@ -8641,6 +8717,7 @@
|
||||
|
||||
function resetStackMinigameState() {
|
||||
stackMini = null;
|
||||
stackTowerPost50AnimStartMs = null;
|
||||
stackTowerHeartMinusFx = null;
|
||||
if (!mapData || !isStack()) return;
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
@@ -9158,6 +9235,26 @@
|
||||
stepPreviewStackTurnBased(Date.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* จุดบนจอที่เส้นก้าน→บล็อกตัดแนว y = -(ความสูงแคนวาส × STACK_TOWER_ROPE_TOP_ABOVE_CANVAS_FRAC)
|
||||
* — ปลายเชือกอยู่เหนือขอบบน ~25% ของความสูงจอ (มองเห็นช่วงที่เข้ามาในจอ)
|
||||
*/
|
||||
function stackTowerRopeScreenTopAnchorPlay(slingBx, slingBy, tcx, tcy, canvasH) {
|
||||
const ch = Math.max(80, Number(canvasH) || 1080);
|
||||
const marginTop = -ch * STACK_TOWER_ROPE_TOP_ABOVE_CANVAS_FRAC;
|
||||
const ddx = tcx - slingBx;
|
||||
const ddy = tcy - slingBy;
|
||||
if (Math.abs(ddy) < 1e-4) {
|
||||
return { x: tcx, y: Math.min(marginTop, Math.min(tcy, slingBy)) };
|
||||
}
|
||||
const vTop = (marginTop - slingBy) / ddy;
|
||||
if (!Number.isFinite(vTop) || vTop < 0) {
|
||||
return { x: tcx, y: tcy };
|
||||
}
|
||||
const xTop = slingBx + vTop * ddx;
|
||||
return { x: xTop, y: marginTop };
|
||||
}
|
||||
|
||||
function applyStackPreviewSpawnLayout() {
|
||||
if (!mapData || mapData.gameType !== 'stack') return;
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
@@ -9189,13 +9286,15 @@
|
||||
const floorY = m.floorWorldY;
|
||||
const swingLiftDraw = getStackTowerSwingLiftWorldPx(m.layers.length, layerWorldH);
|
||||
const swingDrawY = m.swingWorldY - swingLiftDraw;
|
||||
const hidLayers = towerHudLive ? getStackTowerVisualHiddenLayerCountPlay() : 0;
|
||||
const visualLiftWorld = hidLayers * layerWorldH;
|
||||
|
||||
const nLay = m.layers.length;
|
||||
for (let i = 0; i < nLay; i++) {
|
||||
for (let i = hidLayers; i < nLay; i++) {
|
||||
const L = m.layers[i];
|
||||
const lw = (L.w != null ? L.w : m.initialWidthTiles) * tileSize;
|
||||
const cxW = L.cx * tileSize;
|
||||
const yb = floorY - (i + 1) * layerWorldH;
|
||||
const yb = floorY - (i + 1 - hidLayers) * layerWorldH;
|
||||
const xl = cxW - lw / 2;
|
||||
const [sx, sy] = worldToScreen(xl, yb);
|
||||
const drawW = lw * zoom;
|
||||
@@ -9218,11 +9317,11 @@
|
||||
m.perfectStackX2Fx = null;
|
||||
} else {
|
||||
const ti = fxX2.topLayerIndex;
|
||||
if (ti >= 0 && ti < m.layers.length) {
|
||||
if (ti >= hidLayers && 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 ybT = floorY - (ti + 1 - hidLayers) * layerWorldH;
|
||||
const xlT = cxWT - lwT / 2;
|
||||
const [sxT, syT] = worldToScreen(xlT, ybT);
|
||||
const drawWT = lwT * zoom;
|
||||
@@ -9247,7 +9346,8 @@
|
||||
const xSlide = rotSign * ue * tileSize * 0.62;
|
||||
const cxW = s.xLeft0 + s.twWorld / 2 + xSlide;
|
||||
const cyW = s.yTop0 + s.stripH * 0.5 + yFall;
|
||||
const [cxs, cys] = worldToScreen(cxW, cyW);
|
||||
const cyWDraw = isStackTowerMissionUiMapPlay() ? cyW + visualLiftWorld : cyW;
|
||||
const [cxs, cys] = worldToScreen(cxW, cyWDraw);
|
||||
const drawW = s.twWorld * zoom;
|
||||
const drawH = s.stripH * zoom;
|
||||
const hue = (m.layers.length * 47) % 360;
|
||||
@@ -9261,8 +9361,9 @@
|
||||
} else {
|
||||
const twWorld = m.widthTiles * tileSize;
|
||||
const drawStripPx = stripH * zoom;
|
||||
const swingDrawYDraw = isStackTowerMissionUiMapPlay() ? swingDrawY + visualLiftWorld : swingDrawY;
|
||||
const cableTopY = isStackTowerMissionUiMapPlay()
|
||||
? (swingDrawY - tileSize * 6)
|
||||
? (swingDrawYDraw - tileSize * 6)
|
||||
: Math.max(0, swingDrawY - tileSize * 6);
|
||||
const craneX = m.craneWorldX != null ? m.craneWorldX : m.topCenterX * tileSize;
|
||||
let blockLeftWorld;
|
||||
@@ -9274,11 +9375,12 @@
|
||||
const u = stackEaseDropPhys(t, stackFall.sloppyN);
|
||||
blockLeftWorld = stackFall.xLeft0 + (stackFall.xLeft1 - stackFall.xLeft0) * u;
|
||||
blockTopWorld = stackFall.y0 + (stackFall.y1 - stackFall.y0) * u;
|
||||
if (isStackTowerMissionUiMapPlay()) blockTopWorld += visualLiftWorld;
|
||||
fallTiltRad = stackFall.tiltMax * Math.sin(Math.PI * u);
|
||||
} else {
|
||||
const swingXW = (m.topCenterX + m.swingAmp * Math.sin(m.phase)) * tileSize;
|
||||
blockLeftWorld = swingXW - twWorld / 2;
|
||||
blockTopWorld = swingDrawY;
|
||||
blockTopWorld = isStackTowerMissionUiMapPlay() ? swingDrawYDraw : swingDrawY;
|
||||
}
|
||||
const cxMid = blockLeftWorld + twWorld / 2;
|
||||
const [tcx, tcy] = worldToScreen(craneX, cableTopY);
|
||||
@@ -9286,14 +9388,32 @@
|
||||
let slingBx = bcx;
|
||||
let slingBy = bcy;
|
||||
if (stackFall && stackFall.releaseAttachWorldX != null) {
|
||||
const p = worldToScreen(stackFall.releaseAttachWorldX, stackFall.releaseAttachWorldY);
|
||||
const attY = stackFall.releaseAttachWorldY + (isStackTowerMissionUiMapPlay() ? visualLiftWorld : 0);
|
||||
const p = worldToScreen(stackFall.releaseAttachWorldX, attY);
|
||||
slingBx = p[0];
|
||||
slingBy = p[1];
|
||||
}
|
||||
drawStackSlingSegmentPlay(ctx, tcx, tcy, slingBx, slingBy, zoom);
|
||||
let ropeSx0 = tcx;
|
||||
let ropeSy0 = tcy;
|
||||
let ropeSx1 = slingBx;
|
||||
let ropeSy1 = slingBy;
|
||||
if (isStackTowerMissionUiMapPlay()) {
|
||||
const chCv = ctx.canvas && ctx.canvas.height ? ctx.canvas.height : STACK_TOWER_FIXED_RENDER_H;
|
||||
const topA = stackTowerRopeScreenTopAnchorPlay(slingBx, slingBy, tcx, tcy, chCv);
|
||||
ropeSx0 = topA.x;
|
||||
ropeSy0 = topA.y;
|
||||
ropeSx1 = slingBx;
|
||||
ropeSy1 = slingBy;
|
||||
const t1 = Math.max(0, Math.min(1, STACK_TOWER_ROPE_DRAW_T1));
|
||||
if (t1 < 1) {
|
||||
ropeSx1 = ropeSx0 + (slingBx - ropeSx0) * t1;
|
||||
ropeSy1 = ropeSy0 + (slingBy - ropeSy0) * t1;
|
||||
}
|
||||
}
|
||||
drawStackSlingSegmentPlay(ctx, ropeSx0, ropeSy0, ropeSx1, ropeSy1, zoom);
|
||||
const ropeAng = stackFall && stackFall.releaseRopeAng != null
|
||||
? stackFall.releaseRopeAng
|
||||
: Math.atan2(bcy - tcy, bcx - tcx);
|
||||
: Math.atan2(bcy - ropeSy0, bcx - ropeSx0);
|
||||
const swingTiltRad = ropeAng - Math.PI / 2;
|
||||
const hitMissTint = stackFall && stackFall.hit && stackFall.hit.miss;
|
||||
const seatDraw = stackFall && stackFall.actorSeat != null
|
||||
@@ -15214,7 +15334,8 @@
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
function drawHudAvatar(cx, baselineY, maxSize, characterId, playTint) {
|
||||
/** @param {{ headBust?: boolean, headSrcFrac?: number, bustLiftPx?: number }} [avatarOpts] — headBust + bustLiftPx ดันรูปขึ้นในกรอบ */
|
||||
function drawHudAvatar(cx, baselineY, maxSize, characterId, playTint, avatarOpts) {
|
||||
const timeMs = now;
|
||||
const dir = 'down';
|
||||
const rawImg = getAvatarImg(characterId, dir, timeMs, false);
|
||||
@@ -15227,16 +15348,31 @@
|
||||
iw = charImg.naturalWidth;
|
||||
ih = charImg.naturalHeight;
|
||||
}
|
||||
const headBust = !!(avatarOpts && avatarOpts.headBust);
|
||||
let sx = 0;
|
||||
let sy = 0;
|
||||
let sww = iw;
|
||||
let shh = ih;
|
||||
if (headBust && iw > 0 && ih > 0) {
|
||||
const rawF = Number(avatarOpts && avatarOpts.headSrcFrac);
|
||||
const frac = Number.isFinite(rawF) && rawF > 0.22 && rawF < 0.85 ? rawF : 0.4;
|
||||
shh = Math.max(8, Math.round(ih * frac));
|
||||
}
|
||||
const ax = cx - maxSize / 2;
|
||||
const ay = baselineY - maxSize;
|
||||
ctx.save();
|
||||
roundRectPath(ax, ay, maxSize, maxSize, 5);
|
||||
ctx.clip();
|
||||
if (iw > 0 && ih > 0) {
|
||||
const scale = Math.min(maxSize / iw, maxSize / ih, 1);
|
||||
const dw = iw * scale;
|
||||
const dh = ih * scale;
|
||||
ctx.drawImage(charImg, 0, 0, iw, ih, cx - dw / 2, baselineY - dh, dw, dh);
|
||||
const maxScale = headBust ? 2.45 : 1;
|
||||
const scale = Math.min(maxSize / sww, maxSize / shh, maxScale);
|
||||
const dw = sww * scale;
|
||||
const dh = shh * scale;
|
||||
const rawLift = headBust && avatarOpts ? Number(avatarOpts.bustLiftPx) : NaN;
|
||||
const bustLift = headBust
|
||||
? (Number.isFinite(rawLift) ? Math.max(0, Math.min(maxSize * 0.45, rawLift)) : 22)
|
||||
: 0;
|
||||
ctx.drawImage(charImg, sx, sy, sww, shh, cx - dw / 2, baselineY - dh - bustLift, dw, dh);
|
||||
} else {
|
||||
ctx.fillStyle = 'rgba(80, 200, 255, 0.35)';
|
||||
ctx.fillRect(ax, ay, maxSize, maxSize);
|
||||
@@ -15397,7 +15533,7 @@
|
||||
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);
|
||||
drawHudAvatar(avCx, avFrameY + avBox - 5, bigAv, curV.characterId, curV.playTint, towerMapHud ? { headBust: true } : undefined);
|
||||
|
||||
if (!towerMapHud) {
|
||||
const footY = innerTop + integrityH + 12;
|
||||
@@ -16209,6 +16345,7 @@
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
const w = mapData.width, h = mapData.height;
|
||||
|
||||
let zDraw = computePlayCameraZDrawPlay();
|
||||
const stackCam = isStack() ? getStackCameraCentersPx() : null;
|
||||
let camX = stackCam ? stackCam.px : me.x * tileSize;
|
||||
let camY = stackCam ? stackCam.py : me.y * tileSize;
|
||||
@@ -16216,7 +16353,6 @@
|
||||
camX = jumpSurviveCamCenterX;
|
||||
camY = jumpSurviveCamCenterY;
|
||||
}
|
||||
let zDraw = computePlayCameraZDrawPlay();
|
||||
if (isSpaceShooter() || isBalloonBoss()) {
|
||||
const mwPx = w * tileSize, mhPx = h * tileSize;
|
||||
camX = mwPx * 0.5;
|
||||
@@ -16243,12 +16379,14 @@
|
||||
? Math.max(0.38, Math.min(0.82, frac))
|
||||
: 0.56;
|
||||
const floorY0 = stackMini.floorWorldY;
|
||||
const curScreen = (floorY0 - camY) * zDraw + canvas.height * 0.5;
|
||||
const stScrollFloor = getStackTowerWorldLayerScrollScreenOffsetYPlay(zDraw);
|
||||
const curScreen = (floorY0 - camY) * zDraw + canvas.height * 0.5 - stScrollFloor;
|
||||
const desired = canvas.height * screenFrac;
|
||||
camY += (curScreen - desired) / zDraw;
|
||||
}
|
||||
}
|
||||
lastPlayZDrawForInput = zDraw;
|
||||
const stackTowerWorldScrollScreenY = getStackTowerWorldLayerScrollScreenOffsetYPlay(zDraw);
|
||||
const halfW = canvas.width / (2 * zDraw);
|
||||
const halfH = canvas.height / (2 * zDraw);
|
||||
|
||||
@@ -16350,7 +16488,7 @@
|
||||
}
|
||||
function worldToScreen(wx, wy) {
|
||||
const sx = (wx - camX) * zDraw + canvas.width / 2;
|
||||
const sy = (wy - camY) * zDraw + canvas.height / 2;
|
||||
const sy = (wy - camY) * zDraw + canvas.height / 2 - stackTowerWorldScrollScreenY;
|
||||
return [sx, sy];
|
||||
}
|
||||
|
||||
@@ -16380,7 +16518,7 @@
|
||||
}
|
||||
if (!gim || !gim.complete || !gim.naturalWidth) continue;
|
||||
const sx0 = (wx0 - camX) * zDraw + canvas.width / 2;
|
||||
const sy0 = (wy0 - camY) * zDraw + canvas.height / 2;
|
||||
const sy0 = (wy0 - camY) * zDraw + canvas.height / 2 - stackTowerWorldScrollScreenY;
|
||||
const sw = ww * zDraw;
|
||||
const sh = wh * zDraw;
|
||||
ctx.save();
|
||||
|
||||
@@ -127,14 +127,40 @@
|
||||
image-rendering: pixelated;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
/* Stack Tower (mnn93hpi) เท่านั้น — class บน html จาก play.js — แคนวาสคงที่ไม่ย่อเข้า stage (ไม่มีขอบเทารอบพิกเซล) */
|
||||
/* Stack Tower (mnn93hpi): เต็มพื้นที่ stage ยังคงสัดส่วนบัฟเฟอร์ 16:9 (object-fit) */
|
||||
html.play-stack-tower-pixel-canvas #play-canvas-stage.play-canvas-stage {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
html.play-stack-tower-pixel-canvas #play-canvas-stage #game-canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
object-fit: contain;
|
||||
object-position: center center;
|
||||
touch-action: none;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
html.play-preview-editor-embed.play-stack-tower-pixel-canvas #play-canvas-stage #game-canvas {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
align-self: stretch !important;
|
||||
}
|
||||
/* Stack Tower: portrait มุมขวา (DOM) — โฟกัสประมาณคอขึ้น */
|
||||
html.play-stack-tower-pixel-canvas .play-cyber-self-frame {
|
||||
overflow: hidden;
|
||||
}
|
||||
html.play-stack-tower-pixel-canvas .play-cyber-self-frame img#play-cyber-portrait-img {
|
||||
width: 72px;
|
||||
height: 96px;
|
||||
object-fit: cover;
|
||||
object-position: center 2%;
|
||||
margin: -22px 0 0 0;
|
||||
}
|
||||
html.play-stack-tower-pixel-canvas .play-cyber-self {
|
||||
top: max(44px, calc(env(safe-area-inset-top) + 30px)) !important;
|
||||
}
|
||||
/* หลังกดรับหลักฐานในหน้าสรุป — BG ดำ alpha ทั้งจอ + timeup ติดโซนโต๊ะเหมือน #quiz-map-question-panel */
|
||||
#quiz-carry-timeup-desk-layer {
|
||||
@@ -3176,7 +3202,7 @@
|
||||
</div>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0306"></script>
|
||||
<script src="js/play.js?v=0.0355"></script>
|
||||
<script src="js/play.js?v=0.0375"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user