minigame 5 add more design 1.5

This commit is contained in:
2026-05-06 03:41:01 +00:00
parent ca572a4589
commit f6c24af772
7 changed files with 157 additions and 122 deletions
+12
View File
@@ -21,13 +21,17 @@ GAME_SRV_HASH_BEFORE="$(hash_game_server_js "$GAME_SERVER_JS")"
# ไม่ rsync ทับ Game/data/maps — ฉากที่สร้าง/บันทึกบนเซิร์ฟ (Node saveMap) มักใหม่กว่าใน repo
# หลัง deploy คัดลอกเฉพาะ *.json ที่มีใน repo แต่ยังไม่มีบนเซิร์ฟ (seed รหัสใหม่) ไม่แตะไฟล์ที่มีอยู่แล้ว
# ไม่ทับ Game/data/game-timing.json — ค่า Minigame / รูปแพลตฟอร์มที่บันทึกจาก Admin บนเซิร์ฟต้องอยู่หลัง deploy
MAPS_SRC="$SRC/Game/data/maps"
MAPS_DST="$DST/Game/data/maps"
TIMING_SRC="$SRC/Game/data/game-timing.json"
TIMING_DST="$DST/Game/data/game-timing.json"
rsync -a \
--checksum \
--exclude 'node_modules' \
--exclude 'Game/data/maps/' \
--exclude 'Game/data/game-timing.json' \
"$SRC" "$DST"
chown -R www-data:www-data "$DST"
@@ -44,6 +48,14 @@ if [[ -d "$MAPS_SRC" ]]; then
shopt -u nullglob
chown -R www-data:www-data "$MAPS_DST"
fi
if [[ -f "$TIMING_SRC" ]]; then
mkdir -p "$(dirname "$TIMING_DST")"
if [[ ! -f "$TIMING_DST" ]]; then
cp -a "$TIMING_SRC" "$TIMING_DST"
echo "OK: seeded game-timing.json (missing on server)"
fi
chown www-data:www-data "$TIMING_DST" 2>/dev/null || true
fi
# รูปป้าย quiz_carry — ให้เขียน/ลบได้ชัดเจน (FTP/Node); setgid ให้ไฟล์ใหม่ได้กลุ่ม www-data
PLAQUE="$DST/Game/public/img/quiz-carry-plaque-assets"
mkdir -p "$PLAQUE"
+3 -3
View File
@@ -42,17 +42,17 @@
{
"url": "/Game/img/Jumper/platfrom-1.png",
"w": 1392,
"h": 136
"h": 60
},
{
"url": "/Game/img/Jumper/platfrom-2.png",
"w": 389,
"h": 77
"h": 33
},
{
"url": "/Game/img/Jumper/platfrom-3.png",
"w": 212,
"h": 77
"h": 33
}
],
"spaceShooterMissionTimeSec": 0,
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -362,7 +362,7 @@
</div>
</div>
<script src="js/version.js?v=0.0258"></script>
<script src="js/editor.js?v=0.0318"></script>
<script src="js/editor.js?v=0.0319"></script>
<div class="version-tag">v —</div>
</body>
</html>
+42 -29
View File
@@ -1299,6 +1299,14 @@
}
}
function editorJumpSurviveVariantAtCell(x, y) {
if (!jumpSurvivePlatformVariantArea[y]) return 1;
let v = Math.floor(Number(jumpSurvivePlatformVariantArea[y][x]));
if (!Number.isFinite(v) || v < 1) v = 1;
if (v > 3) v = 3;
return v;
}
function ensureJumpSurviveHazardArea() {
if (!jumpSurviveHazardArea.length || jumpSurviveHazardArea.length !== height) {
const existing = jumpSurviveHazardArea.slice().map((r) => r && r.slice());
@@ -2349,35 +2357,40 @@
}
}
if (gtDraw === 'jump_survive' && jumpSurvivePlatformArea[y] && jumpSurvivePlatformArea[y][x] === 1) {
let v = jumpSurvivePlatformVariantArea[y] ? Math.floor(Number(jumpSurvivePlatformVariantArea[y][x])) : 1;
if (!Number.isFinite(v) || v < 1) v = 1;
if (v > 3) v = 3;
const cfg = editorJumpSurvivePlatformTilesCfg[v - 1] || { url: '', w: 0, h: 0 };
const im = editorJumpSurvivePlatformTileImgs[v - 1];
const px0 = tx + 3;
const py0 = ty + 3;
const pw = tileSize - 6;
const ph = tileSize - 6;
let dw = cfg.w > 0 ? cfg.w : pw;
let dh = cfg.h > 0 ? cfg.h : ph;
const maxDim = tileSize * 4;
if (dw > maxDim) dw = maxDim;
if (dh > maxDim) dh = maxDim;
const dx = tx + (tileSize - dw) / 2;
const dy = ty + tileSize - dh - 2;
if (im && im.complete && im.naturalWidth > 0) {
ctx.drawImage(im, dx, dy, dw, dh);
ctx.strokeStyle = 'rgba(160, 255, 220, 0.92)';
ctx.lineWidth = 2;
ctx.strokeRect(dx, dy, dw, dh);
ctx.lineWidth = 1;
} else {
ctx.fillStyle = 'rgba(125, 235, 200, 0.48)';
ctx.fillRect(px0, py0, pw, ph);
ctx.strokeStyle = 'rgba(160, 255, 220, 0.92)';
ctx.lineWidth = 2;
ctx.strokeRect(px0, py0, pw, ph);
ctx.lineWidth = 1;
const vHere = editorJumpSurviveVariantAtCell(x, y);
const isRunContinuation = x > 0 && jumpSurvivePlatformArea[y][x - 1] === 1 && editorJumpSurviveVariantAtCell(x - 1, y) === vHere;
if (!isRunContinuation) {
let runLen = 1;
while (x + runLen < width && jumpSurvivePlatformArea[y][x + runLen] === 1
&& editorJumpSurviveVariantAtCell(x + runLen, y) === vHere) runLen += 1;
const cfg = editorJumpSurvivePlatformTilesCfg[vHere - 1] || { url: '', w: 0, h: 0 };
const im = editorJumpSurvivePlatformTileImgs[vHere - 1];
const segWpx = runLen * tileSize;
let dw = runLen > 1 ? segWpx : (cfg.w > 0 ? cfg.w : tileSize - 6);
let dh = cfg.h > 0 ? cfg.h : tileSize - 6;
if (runLen === 1) {
const maxDim = tileSize * 4;
if (dw > maxDim) dw = maxDim;
if (dh > maxDim) dh = maxDim;
}
const dx = tx;
const dy = ty + tileSize - dh - 2;
if (im && im.complete && im.naturalWidth > 0) {
ctx.drawImage(im, dx, dy, dw, dh);
ctx.strokeStyle = 'rgba(160, 255, 220, 0.92)';
ctx.lineWidth = 2;
ctx.strokeRect(dx, dy, dw, dh);
ctx.lineWidth = 1;
} else {
const innerW = runLen > 1 ? segWpx - 4 : tileSize - 6;
const innerH = tileSize - 6;
ctx.fillStyle = 'rgba(125, 235, 200, 0.48)';
ctx.fillRect(tx + 3, ty + 3, innerW, innerH);
ctx.strokeStyle = 'rgba(160, 255, 220, 0.92)';
ctx.lineWidth = 2;
ctx.strokeRect(tx + 3, ty + 3, innerW, innerH);
ctx.lineWidth = 1;
}
}
}
if (gtDraw === 'jump_survive' && jumpSurviveHazardArea[y] && jumpSurviveHazardArea[y][x] === 1) {
+97 -87
View File
@@ -10600,10 +10600,11 @@
md.jumpSurviveHazardArea = rows;
}
/** โซนตาย jump_survive — กริดคงที่ในโลก (ไม่เลื่อนกับแพลตฟอร์ม) */
/** โซนตาย jump_survive — กริดคงที่ในโลก (ไม่เลื่อนกับแพลตฟอร์ม) · ช่องที่มีแพลตฟอร์มอยู่ด้วย = ไม่นับ hazard (ยืนบนแพลตฟอร์มชนะ) */
function jumpSurviveOverlapsHazardArea(md, left, top, right, bottom) {
const ha = md && md.jumpSurviveHazardArea;
if (!ha) return false;
const pa = md && md.jumpSurvivePlatformArea;
const ts = tileSize;
const w = md.width || 20, h = md.height || 15;
const x0 = Math.max(0, Math.floor(left / ts));
@@ -10612,7 +10613,9 @@
const y1 = Math.min(h - 1, Math.floor((bottom - 1e-6) / ts));
for (let ty = y0; ty <= y1; ty++) {
for (let tx = x0; tx <= x1; tx++) {
if (ha[ty] && ha[ty][tx] === 1) return true;
if (!ha[ty] || ha[ty][tx] !== 1) continue;
if (pa && pa[ty] && pa[ty][tx] === 1) continue;
return true;
}
}
return false;
@@ -12150,6 +12153,7 @@
/**
* กสกระโดดรวม (เล + บอท preview)
* ตายจาก: hazard วาง, บระหวาง solid+object wall, หรอตกออกนอกขอบโลกแมป (ไมใชขอบกลอง)
* @returns {{ pxc: number, feetY: number, vy: number, onGround: boolean, died: boolean }}
*/
function jumpSurvivePhysicsIntegrate(md, opts) {
@@ -12159,8 +12163,6 @@
pxc0, feetY0, vy0, wasOnGround,
jumpQueued,
moveLeft, moveRight,
camCenterY,
halfViewH,
} = opts;
const w = md.width || 20, h = md.height || 15;
const ts = tileSize;
@@ -12245,20 +12247,15 @@
return { pxc, feetY, vy, onGround, died: true };
}
headTop = feetY - bh;
if (jumpSurviveOverlapsEdgeKillPlatforms(md, pxc - bw, headTop, pxc + bw, feetY, jumpSurvivePlatformScrollPx)) {
return { pxc, feetY, vy, onGround, died: true };
}
headTop = feetY - bh;
if (jumpSurviveOverlapsHazardArea(md, pxc - bw, headTop, pxc + bw, feetY)) {
return { pxc, feetY, vy, onGround, died: true };
}
headTop = feetY - bh;
const topKill = camCenterY - halfViewH - ts * 0.35;
const botKill = camCenterY + halfViewH + ts * 0.85;
if (feetY < topKill || feetY > botKill || headTop < -ts * 2) {
/* ไม่ใช้ขอบกล้อง (cam topKill/botKill) — ตายเฉพาะ hazard ที่วาง + ตกออกนอกโลกแมปจริง (ไม่ตายในอากาศระหว่างแพลตฟอร์ม) */
const mapHpx = h * ts;
if (feetY > mapHpx + ts * 8 || headTop < -ts * 32) {
return { pxc, feetY, vy, onGround, died: true };
}
@@ -12299,8 +12296,10 @@
const y1 = Math.floor((bottom - 1e-6) / ts);
for (let ty = y0; ty <= y1; ty++) {
for (let tx = x0; tx <= x1; tx++) {
if (tx < 0 || tx >= w || ty >= h) return true;
if (tx < 0 || tx >= w) return true;
if (ty < 0) continue;
/* แพลตฟอร์มวนแนวตั้ง (period = h*ts) — worldY เกิน h*ts เป็นปกติ ห้ามถือ ty>=h เป็น “ซีลพื้นแมป” ไม่งั้น solid+กำแพงข้าง = ตายคาแอร์ */
if (ty >= h) continue;
const ob = md.objects && md.objects[ty] && md.objects[ty][tx];
if (ob === 1) return true;
}
@@ -12577,8 +12576,6 @@
jumpQueued: jumpQ,
moveLeft,
moveRight,
camCenterY: jumpSurviveCamCenterY,
halfViewH,
});
if (r.died) {
@@ -12652,8 +12649,6 @@
jumpQueued: jq,
moveLeft: !!(keys['ArrowLeft'] || keys['KeyA']),
moveRight: !!(keys['ArrowRight'] || keys['KeyD']),
camCenterY: jumpSurviveCamCenterY,
halfViewH,
});
if (r.died) {
@@ -16085,86 +16080,101 @@
}
}
}
if (isJumpSurvive() && showGrid && mapData.jumpSurvivePlatformArea) {
const pa = mapData.jumpSurvivePlatformArea;
const sc = jumpSurvivePlatformScrollPx;
const tsz = tileSize;
const period = h * tsz;
const visTop = worldMinY;
const visBot = worldMaxY;
const eps = 1e-6;
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
if (!pa[py] || pa[py][px] !== 1) continue;
const vIdx = jumpSurvivePlatformVariantIndexAtPlay(mapData, px, py);
const cfg = playJumpSurvivePlatformTiles[vIdx - 1] || { url: '', w: 0, h: 0 };
const jumpPlatU = normalizeGauntletAssetUrlForPlay(cfg.url || '');
const jumpPlatRec = jumpPlatU ? ensureGauntletAssetImage(jumpPlatU) : null;
const jumpPlatImg = jumpPlatRec && jumpPlatRec.ready && jumpPlatRec.img && jumpPlatRec.img.naturalWidth > 0 ? jumpPlatRec.img : null;
const tileWorldLeft = px * tsz;
const tileWorldRight = tileWorldLeft + tsz;
if (tileWorldRight <= worldMinX || tileWorldLeft >= worldMaxX) continue;
const rawTop = py * tsz - sc;
const kMin = Math.ceil((visTop - rawTop - tsz + eps) / period);
const kMax = Math.floor((visBot - rawTop - eps) / period);
if (kMax < kMin) continue;
for (let kk = kMin; kk <= kMax; kk++) {
const platTop = rawTop + kk * period;
if (platTop + tsz <= visTop || platTop >= visBot) continue;
const [psx, psy] = worldToScreen(tileWorldLeft, platTop);
const psz = tsz * zDraw;
let dw = cfg.w > 0 ? cfg.w * zDraw : psz - 4;
let dh = cfg.h > 0 ? cfg.h * zDraw : psz - 4;
}
if (isJumpSurvive() && mapData.jumpSurvivePlatformArea) {
const pa = mapData.jumpSurvivePlatformArea;
const sc = jumpSurvivePlatformScrollPx;
const tsz = tileSize;
const period = h * tsz;
const visTop = worldMinY;
const visBot = worldMaxY;
const eps = 1e-6;
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
if (!pa[py] || pa[py][px] !== 1) continue;
const vIdx = jumpSurvivePlatformVariantIndexAtPlay(mapData, px, py);
if (px > 0 && pa[py][px - 1] === 1 && jumpSurvivePlatformVariantIndexAtPlay(mapData, px - 1, py) === vIdx) {
continue;
}
let runLen = 1;
while (px + runLen < w && pa[py][px + runLen] === 1
&& jumpSurvivePlatformVariantIndexAtPlay(mapData, px + runLen, py) === vIdx) runLen += 1;
const cfg = playJumpSurvivePlatformTiles[vIdx - 1] || { url: '', w: 0, h: 0 };
const jumpPlatU = normalizeGauntletAssetUrlForPlay(cfg.url || '');
const jumpPlatRec = jumpPlatU ? ensureGauntletAssetImage(jumpPlatU) : null;
const jumpPlatImg = jumpPlatRec && jumpPlatRec.ready && jumpPlatRec.img && jumpPlatRec.img.naturalWidth > 0 ? jumpPlatRec.img : null;
const tileWorldLeft = px * tsz;
const tileWorldRight = (px + runLen) * tsz;
if (tileWorldRight <= worldMinX || tileWorldLeft >= worldMaxX) continue;
const rawTop = py * tsz - sc;
const kMin = Math.ceil((visTop - rawTop - tsz + eps) / period);
const kMax = Math.floor((visBot - rawTop - eps) / period);
if (kMax < kMin) continue;
for (let kk = kMin; kk <= kMax; kk++) {
const platTop = rawTop + kk * period;
if (platTop + tsz <= visTop || platTop >= visBot) continue;
const [psx, psy] = worldToScreen(tileWorldLeft, platTop);
const psz = tsz * zDraw;
const segScreenW = runLen * psz;
let dw;
let dh = cfg.h > 0 ? cfg.h * zDraw : psz - 4;
if (runLen > 1) {
dw = segScreenW;
} else {
dw = cfg.w > 0 ? cfg.w * zDraw : psz - 4;
const maxDim = psz * 4;
if (dw > maxDim) dw = maxDim;
if (dh > maxDim) dh = maxDim;
const marginBot = 2;
const dx = psx + (psz - dw) / 2;
const dy = psy + psz - dh - marginBot;
if (jumpPlatImg) {
}
const maxDh = psz * 4;
if (dh > maxDh) dh = maxDh;
const marginBot = 2;
const dx = runLen > 1 ? psx : psx + (psz - dw) / 2;
const dy = psy + psz - dh - marginBot;
if (jumpPlatImg) {
ctx.save();
try { ctx.imageSmoothingEnabled = true; } catch (e) { /* ignore */ }
ctx.drawImage(jumpPlatImg, dx, dy, dw, dh);
ctx.restore();
ctx.strokeStyle = 'rgba(0, 255, 240, 0.4)';
ctx.lineWidth = 1.5;
ctx.strokeRect(dx, dy, dw, dh);
ctx.lineWidth = 1;
} else {
const fillW = runLen > 1 ? segScreenW - 6 : psz - 6;
ctx.fillStyle = 'rgba(0, 40, 52, 0.72)';
ctx.fillRect(psx + 3, psy + 3, fillW, psz - 6);
ctx.strokeStyle = 'rgba(0, 255, 240, 0.55)';
ctx.lineWidth = 2;
ctx.strokeRect(psx + 3, psy + 3, fillW, psz - 6);
ctx.strokeStyle = 'rgba(120, 255, 255, 0.35)';
ctx.lineWidth = 1;
ctx.strokeRect(psx + 5, psy + 5, fillW - 4, psz - 10);
const grad = ctx.createLinearGradient(psx, psy, psx, psy + psz);
grad.addColorStop(0, 'rgba(0, 255, 240, 0.12)');
grad.addColorStop(0.5, 'rgba(0, 0, 0, 0)');
grad.addColorStop(1, 'rgba(0, 180, 200, 0.08)');
ctx.fillStyle = grad;
ctx.fillRect(psx + 4, psy + 4, fillW - 2, psz - 8);
ctx.lineWidth = 1;
if (zDraw >= 1.15 && psz > 22) {
ctx.save();
try { ctx.imageSmoothingEnabled = true; } catch (e) { /* ignore */ }
ctx.drawImage(jumpPlatImg, dx, dy, dw, dh);
ctx.font = `600 ${Math.max(7, Math.min(11, psz * 0.14))}px "Share Tech Mono", ui-monospace, monospace`;
ctx.fillStyle = 'rgba(180, 255, 255, 0.5)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const cx = psx + (runLen > 1 ? segScreenW * 0.5 : psz * 0.5);
ctx.fillText('DATA', cx, psy + psz * 0.38);
ctx.font = `500 ${Math.max(5, Math.min(8, psz * 0.1))}px "Share Tech Mono", ui-monospace, monospace`;
ctx.fillStyle = 'rgba(120, 230, 255, 0.38)';
ctx.fillText('/SEC/BLOCK', cx, psy + psz * 0.62);
ctx.restore();
ctx.strokeStyle = 'rgba(0, 255, 240, 0.4)';
ctx.lineWidth = 1.5;
ctx.strokeRect(dx, dy, dw, dh);
ctx.lineWidth = 1;
} else {
ctx.fillStyle = 'rgba(0, 40, 52, 0.72)';
ctx.fillRect(psx + 3, psy + 3, psz - 6, psz - 6);
ctx.strokeStyle = 'rgba(0, 255, 240, 0.55)';
ctx.lineWidth = 2;
ctx.strokeRect(psx + 3, psy + 3, psz - 6, psz - 6);
ctx.strokeStyle = 'rgba(120, 255, 255, 0.35)';
ctx.lineWidth = 1;
ctx.strokeRect(psx + 5, psy + 5, psz - 10, psz - 10);
const grad = ctx.createLinearGradient(psx, psy, psx, psy + psz);
grad.addColorStop(0, 'rgba(0, 255, 240, 0.12)');
grad.addColorStop(0.5, 'rgba(0, 0, 0, 0)');
grad.addColorStop(1, 'rgba(0, 180, 200, 0.08)');
ctx.fillStyle = grad;
ctx.fillRect(psx + 4, psy + 4, psz - 8, psz - 8);
ctx.lineWidth = 1;
if (zDraw >= 1.15 && psz > 22) {
ctx.save();
ctx.font = `600 ${Math.max(7, Math.min(11, psz * 0.14))}px "Share Tech Mono", ui-monospace, monospace`;
ctx.fillStyle = 'rgba(180, 255, 255, 0.5)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('DATA', psx + psz * 0.5, psy + psz * 0.38);
ctx.font = `500 ${Math.max(5, Math.min(8, psz * 0.1))}px "Share Tech Mono", ui-monospace, monospace`;
ctx.fillStyle = 'rgba(120, 230, 255, 0.38)';
ctx.fillText('/SEC/BLOCK', psx + psz * 0.5, psy + psz * 0.62);
ctx.restore();
}
}
}
}
}
}
}
if (isJumpSurvive() && mapData.jumpSurviveHazardArea) {
if (isJumpSurvive() && showGrid && mapData.jumpSurviveHazardArea) {
const ha = mapData.jumpSurviveHazardArea;
const tsz = tileSize;
for (let py = startTileY; py <= endTileY; py++) {
+1 -1
View File
@@ -3026,7 +3026,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.0318"></script>
<script src="js/play.js?v=0.0323"></script>
<div class="version-tag">v —</div>
</body>
</html>