minigame 5 jumper 1.2

This commit is contained in:
2026-05-01 06:33:38 +00:00
parent 2f77cc1669
commit a22f73ec5f
10 changed files with 243 additions and 47 deletions
+10 -1
View File
@@ -2618,6 +2618,11 @@
var jsm = Number(data.jumpSurviveJumpHeightMult);
inp.value = String(Number.isFinite(jsm) ? Math.round(Math.max(0.5, Math.min(4, jsm)) * 100) / 100 : 1.5);
}
var inpT = el('jump-survive-mission-sec');
if (inpT) {
var jss = Number(data.jumpSurviveMissionTimeSec);
inpT.value = String(Number.isFinite(jss) && jss > 0 ? Math.floor(jss) : 0);
}
setMsg('jump-survive-timing-msg', '', '');
})
.catch(function (e) {
@@ -2630,9 +2635,13 @@
var jumpSurvMult = inp ? parseFloat(String(inp.value)) : 1.5;
if (Number.isNaN(jumpSurvMult)) jumpSurvMult = 1.5;
jumpSurvMult = Math.round(Math.max(0.5, Math.min(4, jumpSurvMult)) * 100) / 100;
var limJump = el('jump-survive-mission-sec') ? parseInt(String(el('jump-survive-mission-sec').value), 10) : 0;
if (Number.isNaN(limJump) || limJump < 0) limJump = 0;
limJump = limJump <= 0 ? 0 : Math.max(10, Math.min(7200, limJump));
gameTimingFetch('GET')
.then(function (data) {
data.jumpSurviveJumpHeightMult = jumpSurvMult;
data.jumpSurviveMissionTimeSec = limJump;
return gameTimingFetch('PUT', data);
})
.then(function () {
@@ -2672,7 +2681,7 @@
return gameTimingFetch('PUT', data);
})
.then(function () {
setMsg('game-timing-msg', 'บันทึกแล้ว — พรมแดง tick·กระโดด / รูปอุปสรรค์ · กระโดดให้รอดตั้งที่แท็บกระโดดให้รอด · ค่า Stack แก้ที่แท็บ Stack เท่านั้น · จำกัดเวลาใช้กับรอบถัดไปที่โฮสต์เริ่มเกม', 'ok');
setMsg('game-timing-msg', 'บันทึกแล้ว — พรมแดง tick·กระโดด / รูปอุปสรรค์ · มินิเกมกระโดดขึ้นแท่นตั้งที่แท็บ Minigame-5 · ค่า Stack แก้ที่แท็บ Stack เท่านั้น · จำกัดเวลาพรมแดงใช้กับรอบถัดไปที่โฮสต์เริ่มเกม', 'ok');
})
.catch(function (e) {
setMsg('game-timing-msg', e.message || 'บันทึกไม่ได้', 'error');
+9 -2
View File
@@ -443,7 +443,7 @@
<section id="tab-panel-jump-survive" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-jump-survive">
<h2>กระโดดให้รอด — ตั้งค่าเกม</h2>
<p class="muted">คนละโหมดกับ <strong>พรมแดง (Gauntlet)</strong> · เก็บที่ <code>/Game/data/game-timing.json</code> ฟิลด์ <code>jumpSurviveJumpHeightMult</code> · ผู้เล่นได้ค่าใหม่เมื่อโหลดหน้าเล่น / sync timing จากเซิร์ฟเวอร์ · ถ้า API 404 ให้รีสตาร์ท Node ที่รัน <code>Game/server.js</code></p>
<p class="muted">คนละโหมดกับ <strong>พรมแดง (Gauntlet)</strong> · เก็บที่ <code>/Game/data/game-timing.json</code> ฟิลด์ <code>jumpSurviveJumpHeightMult</code> และ <code>jumpSurviveMissionTimeSec</code> · ผู้เล่นได้ค่าใหม่เมื่อโหลดหน้าเล่น / <code>GET /api/game-timing</code> · ถ้า API 404 ให้รีสตาร์ท Node ที่รัน <code>Game/server.js</code></p>
<fieldset class="quiz-timing-fieldset">
<legend>ความสูงกระโดด</legend>
<div class="form-grid form-inline quiz-timing-grid">
@@ -451,6 +451,13 @@
</div>
</fieldset>
<p class="muted" style="margin-top:-0.25rem;margin-bottom:0.65rem">ใช้กับฉากประเภท <strong>กระโดดให้รอด</strong> เท่านั้น · ถ้าในแมปตั้ง <code>jumpSurviveJumpImpulse</code> &gt; 0 จะใช้ค่านั้นแทน (override) · <em>English:</em> Jump height multiplier vs character height; per-map impulse overrides.</p>
<fieldset class="quiz-timing-fieldset">
<legend>จำกัดเวลารอบ (มินิเกม / ภารกิจกระโดดขึ้นแท่น)</legend>
<div class="form-grid form-inline quiz-timing-grid">
<label title="0 = ใช้ค่าเริ่ม 60 วินาทีในเกม · ตั้งอย่างน้อย 10 วินาทีถ้าต้องการกำหนดเอง">เวลารอบ (วินาที) <input type="number" id="jump-survive-mission-sec" min="0" max="7200" step="5" value="0"></label>
</div>
</fieldset>
<p class="muted" style="margin-top:-0.25rem;margin-bottom:0.65rem">นับจากเริ่มรอบจริง (หลังนับถอยหลัง) · ถ้าในแมปตั้ง <code>jumpSurviveTimeSec</code> &gt; 0 จะใช้ค่าบนแมปแทนทั้งหมด · ไม่ใช่ค่าเดียวกับ &quot;จำกัดเวลาเกม&quot; ของพรมแดง · <em>English:</em> Per-map <code>jumpSurviveTimeSec</code> overrides this global default.</p>
<div class="quiz-admin-actions">
<button type="button" class="btn btn-primary" id="btn-jump-survive-save">บันทึก</button>
</div>
@@ -459,7 +466,7 @@
<section id="tab-panel-game-timing" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-game-timing">
<h2>เวลาเกม — พรมแดงสุดท้าย (Gauntlet)</h2>
<p class="muted">เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/game-timing.json</code> · หลังบันทึกห้องที่กำลังเล่นจะปรับความถี่ tick ทันที · ค่าจะส่งไปยังผู้เล่นผ่าน <code>gauntlet-sync</code> · <strong>จำกัดเวลา</strong>นับจากตอนโฮสต์เริ่มเกมพรมแดง — หมดเวลาแล้วเกมจบและส่งทุกคนกลับฉากล็อบบี้บนเซิร์ฟเวอร์ · ถ้าขึ้น Not Found ให้<strong>รีสตาร์ท Node</strong>ที่รัน <code>Game/server.js</code> (เช่น <code>pm2 restart</code>) หลัง deploy · <strong>กระโดดให้รอด</strong>ตั้งที่แท็บชื่อเดียวกับโหมด</p>
<p class="muted">เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/game-timing.json</code> · หลังบันทึกห้องที่กำลังเล่นจะปรับความถี่ tick ทันที · ค่าจะส่งไปยังผู้เล่นผ่าน <code>gauntlet-sync</code> · <strong>จำกัดเวลา</strong>นับจากตอนโฮสต์เริ่มเกมพรมแดง — หมดเวลาแล้วเกมจบและส่งทุกคนกลับฉากล็อบบี้บนเซิร์ฟเวอร์ · ถ้าขึ้น Not Found ให้<strong>รีสตาร์ท Node</strong>ที่รัน <code>Game/server.js</code> (เช่น <code>pm2 restart</code>) หลัง deploy · <strong>เวลามินิเกมกระโดดขึ้นแท่น</strong>ตั้งที่แท็บ Minigame-5</p>
<fieldset class="quiz-timing-fieldset">
<legend>พารามิเตอร์</legend>
<div class="form-grid form-inline quiz-timing-grid">
+2 -1
View File
@@ -16,5 +16,6 @@
"gauntletLaserLineWidthPx": 0,
"stackSwingHz": 0.1,
"stackBlockWidthTiles": 3.2,
"jumpSurviveJumpHeightMult": 1.5
"jumpSurviveJumpHeightMult": 1.5,
"jumpSurviveMissionTimeSec": 0
}
@@ -55,6 +55,11 @@
<input type="number" id="jumpSurviveJumpHeightMult" min="0.5" max="4" step="0.05" value="1.5">
<p class="hint">สูตร: ความสูงขึ้นสุด ≈ ค่านี้ × (ความสูงตัวเป็น pixel จากช่องบนแมป) · ถ้าในแมปตั้ง <code>jumpSurviveJumpImpulse</code> &gt; 0 จะใช้ค่านั้นแทน (override)</p>
</div>
<div class="row">
<label for="jumpSurviveMissionTimeSec">jumpSurviveMissionTimeSec — เวลารอบมินิเกมกระโดดขึ้นแท่น (วินาที · 0 = เกมใช้ค่าเริ่ม 60)</label>
<input type="number" id="jumpSurviveMissionTimeSec" min="0" max="7200" step="5" value="0">
<p class="hint">ถ้าในแมปตั้ง <code>jumpSurviveTimeSec</code> &gt; 0 จะใช้ค่าบนแมปแทน · ไม่ใช่ค่าเดียวกับพรมแดง</p>
</div>
</div>
<div id="panel-gauntlet" class="panel" role="tabpanel" aria-labelledby="tab-gauntlet" hidden>
@@ -119,6 +124,8 @@
function fillForm() {
document.getElementById('jumpSurviveJumpHeightMult').value = timing.jumpSurviveJumpHeightMult != null ? timing.jumpSurviveJumpHeightMult : 1.5;
const jms = Number(timing.jumpSurviveMissionTimeSec);
document.getElementById('jumpSurviveMissionTimeSec').value = Number.isFinite(jms) && jms > 0 ? Math.floor(jms) : 0;
document.getElementById('gauntletTickMs').value = timing.gauntletTickMs != null ? timing.gauntletTickMs : 150;
document.getElementById('gauntletJumpTicks').value = timing.gauntletJumpTicks != null ? timing.gauntletJumpTicks : 5;
document.getElementById('gauntletTimeLimitSec').value = timing.gauntletTimeLimitSec != null ? timing.gauntletTimeLimitSec : 0;
@@ -129,9 +136,13 @@
function readForm() {
const jm = parseFloat(document.getElementById('jumpSurviveJumpHeightMult').value, 10);
let jmt = parseInt(document.getElementById('jumpSurviveMissionTimeSec').value, 10);
if (Number.isNaN(jmt) || jmt < 0) jmt = 0;
jmt = jmt <= 0 ? 0 : Math.max(10, Math.min(7200, jmt));
return {
...timing,
jumpSurviveJumpHeightMult: Number.isFinite(jm) ? jm : 1.5,
jumpSurviveMissionTimeSec: jmt,
gauntletTickMs: parseInt(document.getElementById('gauntletTickMs').value, 10),
gauntletJumpTicks: parseInt(document.getElementById('gauntletJumpTicks').value, 10),
gauntletTimeLimitSec: parseInt(document.getElementById('gauntletTimeLimitSec').value, 10),
@@ -5,5 +5,6 @@ Upload files here; path in browser: /Game/img/Jumper/<filename> (public URL)
- **popup-Howto.png** — พื้นหลัง HOW TO ก่อนเริ่ม (ข้อความวิธีเล่นใส่ในรูปนี้ — หน้าเกมไม่ซ้อนข้อความ HTML)
- **popup-result.png** — พื้นหลังสรุปภารกิจตอนจบ
- **stamp-sacrifice.png** — ตราประทับ “ผู้เสียสละ” บนผู้เล่นที่ตกรอบ (optional; ถ้าโหลดไม่ได้ใช้ result-Lose_stamp)
- **result-timeup.png** — หมดเวลา (embed: หลังกดรับหลักฐาน ก่อน result-complete/gameover; ถ้าไม่มีไฟล์จะ fallback ไป quiz-carry/timeup-txt.png)
โฟลเดอร์นี้สร้างจาก repo + deploy — ถ้า FTP ยังอัปโหลดไม่ได้ ให้เช็คสิทธิ์ parent
+94 -20
View File
@@ -690,7 +690,9 @@
let playStackBlockWidthTiles = null;
/** กระโดดให้รอด: ความสูงกระโดด = ทวีคูณ × ความสูงตัว (ch×tile) — จาก GET /api/game-timing */
let playJumpSurviveJumpHeightMult = 1.5;
/** 0 = ไม่จำกัด — จาก game-timing / gauntlet-sync */
/** จำกัดเวลารอบภารกิจกระโดดขึ้นแท่น (วินาที) — จาก GET /api/game-timing; 0 = ใช้ค่าเริ่ม 60 ในเกม */
let playJumpSurviveMissionTimeSec = 0;
/** 0 = ไม่จำกัด — จาก game-timing / gauntlet-sync (พรมแดง Gauntlet เท่านั้น) */
let gauntletRuntimeTimeLimitSec = 0;
/** เวลาสิ้นสุดรอบ (epoch ms) จากเซิร์ฟเวอร์ — null = ไม่จับเวลา */
let gauntletEndsAtMs = null;
@@ -2505,8 +2507,8 @@
function jumpSurviveTimeLimitSecForMission() {
const m = Number(mapData && mapData.jumpSurviveTimeSec);
if (Number.isFinite(m) && m > 0 && m < 7200) return Math.floor(m);
const g = Number(gauntletRuntimeTimeLimitSec);
if (Number.isFinite(g) && g > 0 && g < 7200) return Math.floor(g);
const j = Number(playJumpSurviveMissionTimeSec);
if (Number.isFinite(j) && j > 0 && j < 7200) return Math.floor(j);
return 60;
}
@@ -3994,14 +3996,23 @@
el.setAttribute('aria-hidden', 'true');
}
/** (embed preview) timeup-txt
/** (embed preview) timeup (Jumper mnptfts2 = img/Jumper/result-timeup.png)
* @param {function():boolean} [embedResultOkFn] าไม = ใชคะแนน quiz_carry (คนตอบถ); งเม crown เพอเลอก complete vs gameover */
function showQuizCarryTimeupOnDeskLayer(embedResultOkFn) {
const el = document.getElementById('quiz-carry-timeup-desk-layer');
if (!el) return;
const img = document.getElementById('quiz-carry-timeup-txt-img');
if (img) {
img.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
if (isJumpSurviveMissionUiMapPlay()) {
img.onerror = function () {
this.onerror = null;
this.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
};
img.src = jumperAssetUrl('result-timeup.png') + '?v=' + String(Date.now());
} else {
img.onerror = null;
img.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
}
}
el.classList.remove('is-hidden');
el.setAttribute('aria-hidden', 'false');
@@ -5360,6 +5371,14 @@
playJumpSurviveJumpHeightMult = Math.round(Math.max(0.5, Math.min(4, jm)) * 100) / 100;
}
}
if (Object.prototype.hasOwnProperty.call(payload, 'jumpSurviveMissionTimeSec')) {
const jt = Number(payload.jumpSurviveMissionTimeSec);
if (Number.isFinite(jt) && jt > 0) {
playJumpSurviveMissionTimeSec = Math.max(10, Math.min(7200, Math.floor(jt)));
} else {
playJumpSurviveMissionTimeSec = 0;
}
}
if (isStack() && stackMini) reapplyStackMiniSizingFromGlobals();
}
@@ -7654,7 +7673,8 @@
const impulse = Math.sqrt(Math.max(1e-6, 2 * gFull * mult * charHpixels));
jumpV0 = -Math.min(impulse, ts * 36);
}
const moveX = (Number(md.jumpSurviveMovePxPerSec) > 0 ? Number(md.jumpSurviveMovePxPerSec) : 200) * dt;
let moveX = (Number(md.jumpSurviveMovePxPerSec) > 0 ? Number(md.jumpSurviveMovePxPerSec) : 200) * dt;
if (isJumpSurviveMissionUiMapPlay()) moveX *= 1.35;
if (jumpQueued && onGround) {
vy = jumpV0;
@@ -7670,7 +7690,7 @@
const headY0 = feetY - bh;
const tryL = nx - bw;
const tryR = nx + bw;
if (!jumpSurviveOverlapsSolid(md, tryL, headY0, tryR, feetY)) {
if (!jumpSurviveOverlapsSolidForHorzMove(md, tryL, headY0, tryR, feetY)) {
pxc = nx;
}
@@ -7794,6 +7814,19 @@
return jumpSurviveOverlapsPlatforms(md, left, top, right, bottom, jumpSurvivePlatformScrollPx);
}
/**
* ชนแนวนอน: objects เตมกลอง; แพลตฟอรมไมบชวงปลายเทาเลกนอย
* (าใชกลองเตมกบแพลตฟอร ขณะยนบนแพลตฟอรมมกทบชองแนวต เดนซายขวาถกปฏเสธทงกอน)
*/
function jumpSurviveOverlapsSolidForHorzMove(md, left, top, right, bottom) {
if (jumpSurviveOverlapsObjectSolids(md, left, top, right, bottom)) return true;
const ts = tileSize;
const footSlop = ts * 0.16;
const platformBottom = bottom - footSlop;
if (platformBottom <= top + ts * 0.05) return false;
return jumpSurviveOverlapsPlatforms(md, left, top, right, platformBottom, jumpSurvivePlatformScrollPx);
}
/** Head/body overlaps กำแพง (objects=1) เท่านั้น — ใช้ตรวจชนฝาบนแล้วตาย */
function jumpSurviveOverlapsObjectWall(md, left, top, right, bottom) {
const ts = tileSize;
@@ -7849,6 +7882,20 @@
return { px: sx / n, py: sy / n };
}
/** มีแพลตฟอร์มใต้จุดเล็กน้อยข้างหน้าแนว wishDir หรือไม่ — กันบอทเดินหลุมแล้วตกจอ */
function jumpSurviveBotHasFloorAhead(md, pxc, feetY, wishDir, scrollPx) {
if (!wishDir) return true;
const ts = tileSize;
const bw = ts * 0.34;
const probeX = pxc + wishDir * (bw + ts * 0.82);
const pad = ts * 0.24;
const left = probeX - pad;
const right = probeX + pad;
const top = feetY - ts * 0.45;
const bottom = feetY + ts * 8;
return jumpSurviveOverlapsPlatforms(md, left, top, right, bottom, scrollPx);
}
function stepJumpSurvivePreviewBots(dt, halfViewH) {
if (!previewFillBots || !mapData || !isJumpSurvive()) return;
const md = mapData;
@@ -7868,28 +7915,55 @@
let pxc = (o.x + cw * 0.5) * ts;
let feetY = (o.y + ch) * ts;
let jumpQ = false;
if (o.jumpSurviveOnGround && Date.now() >= o.jumpSurviveNextJumpAi) {
o.jumpSurviveNextJumpAi = Date.now() + 320 + Math.floor(Math.random() * 1100);
const tier = o.botTier || 'avg';
const jp = tier === 'sharp' ? 0.5 : tier === 'weak' ? 0.25 : 0.36;
if (Math.random() < jp) jumpQ = true;
}
const camCy = jumpSurviveCamCenterY;
const mission = isJumpSurviveMissionUiMapPlay();
const lowDanger = feetY > camCy + halfViewH - ts * 8.5;
if (Date.now() >= o.jumpSurviveWishNext) {
o.jumpSurviveWishNext = Date.now() + 380 + Math.floor(Math.random() * 700);
const wMin = mission && lowDanger ? 180 : 380;
const wSpan = mission && lowDanger ? 480 : 700;
o.jumpSurviveWishNext = Date.now() + wMin + Math.floor(Math.random() * wSpan);
const tier = o.botTier || 'avg';
if (pxc < centerPx - ts * 1.8) o.jumpSurviveWishX = 1;
if (mission && lowDanger && Math.random() < 0.55) {
o.jumpSurviveWishX = 0;
} else if (pxc < centerPx - ts * 1.8) o.jumpSurviveWishX = 1;
else if (pxc > centerPx + ts * 1.8) o.jumpSurviveWishX = -1;
else if (tier === 'sharp') {
o.jumpSurviveWishX = Math.random() < 0.55 ? (Math.random() < 0.5 ? -1 : 1) : 0;
const roam = mission ? 0.42 : 0.55;
o.jumpSurviveWishX = Math.random() < roam ? (Math.random() < 0.5 ? -1 : 1) : 0;
} else {
o.jumpSurviveWishX = Math.random() < 0.4 ? (Math.random() < 0.5 ? -1 : 1) : 0;
const roam = mission ? 0.28 : 0.4;
o.jumpSurviveWishX = Math.random() < roam ? (Math.random() < 0.5 ? -1 : 1) : 0;
}
}
const moveLeft = o.jumpSurviveWishX < 0;
const moveRight = o.jumpSurviveWishX > 0;
let moveLeft = o.jumpSurviveWishX < 0;
let moveRight = o.jumpSurviveWishX > 0;
let hazardJump = false;
if (o.jumpSurviveOnGround) {
const sc = jumpSurvivePlatformScrollPx;
if (moveRight && !jumpSurviveBotHasFloorAhead(md, pxc, feetY, 1, sc)) {
o.jumpSurviveWishX = Math.random() < 0.65 ? -1 : 0;
hazardJump = true;
} else if (moveLeft && !jumpSurviveBotHasFloorAhead(md, pxc, feetY, -1, sc)) {
o.jumpSurviveWishX = Math.random() < 0.65 ? 1 : 0;
hazardJump = true;
}
moveLeft = o.jumpSurviveWishX < 0;
moveRight = o.jumpSurviveWishX > 0;
}
let jumpQ = false;
if (hazardJump) o.jumpSurviveNextJumpAi = Math.min(o.jumpSurviveNextJumpAi, Date.now());
if (o.jumpSurviveOnGround && Date.now() >= o.jumpSurviveNextJumpAi) {
o.jumpSurviveNextJumpAi = Date.now() + 300 + Math.floor(Math.random() * (hazardJump ? 520 : 1100));
const tier = o.botTier || 'avg';
let jp = tier === 'sharp' ? 0.5 : tier === 'weak' ? 0.25 : 0.36;
if (mission) jp += 0.12;
if (lowDanger) jp += 0.22;
jp = Math.min(0.9, jp);
if (hazardJump || Math.random() < jp) jumpQ = true;
}
const r = jumpSurvivePhysicsIntegrate(md, {
dt,
+1 -1
View File
@@ -1,6 +1,6 @@
// ทุกครั้งที่มีการเพิ่มหรือเปลี่ยน ให้เพิ่ม v +0.0001
// หลังแก้ค่าแล้วต้อง copy ไป path ที่ Nginx ชี้ (หรือรัน copy-frogger-files-only.sh) ถึงจะเห็นบนเว็บ
window.APP_VERSION = '0.0180';
window.APP_VERSION = '0.0182';
document.addEventListener('DOMContentLoaded', function () {
var t = document.querySelector('.version-tag');
if (t) t.textContent = 'v ' + window.APP_VERSION;
+2 -2
View File
@@ -1899,8 +1899,8 @@
</div>
</div>
<script src="/Game/socket.io/socket.io.js"></script>
<script src="js/version.js?v=0.0180"></script>
<script src="js/play.js?v=0.206"></script>
<script src="js/version.js?v=0.0182"></script>
<script src="js/play.js?v=0.210"></script>
<div class="version-tag">v —</div>
</body>
</html>
+94 -20
View File
@@ -690,7 +690,9 @@
let playStackBlockWidthTiles = null;
/** กระโดดให้รอด: ความสูงกระโดด = ทวีคูณ × ความสูงตัว (ch×tile) — จาก GET /api/game-timing */
let playJumpSurviveJumpHeightMult = 1.5;
/** 0 = ไม่จำกัด — จาก game-timing / gauntlet-sync */
/** จำกัดเวลารอบภารกิจกระโดดขึ้นแท่น (วินาที) — จาก GET /api/game-timing; 0 = ใช้ค่าเริ่ม 60 ในเกม */
let playJumpSurviveMissionTimeSec = 0;
/** 0 = ไม่จำกัด — จาก game-timing / gauntlet-sync (พรมแดง Gauntlet เท่านั้น) */
let gauntletRuntimeTimeLimitSec = 0;
/** เวลาสิ้นสุดรอบ (epoch ms) จากเซิร์ฟเวอร์ — null = ไม่จับเวลา */
let gauntletEndsAtMs = null;
@@ -2505,8 +2507,8 @@
function jumpSurviveTimeLimitSecForMission() {
const m = Number(mapData && mapData.jumpSurviveTimeSec);
if (Number.isFinite(m) && m > 0 && m < 7200) return Math.floor(m);
const g = Number(gauntletRuntimeTimeLimitSec);
if (Number.isFinite(g) && g > 0 && g < 7200) return Math.floor(g);
const j = Number(playJumpSurviveMissionTimeSec);
if (Number.isFinite(j) && j > 0 && j < 7200) return Math.floor(j);
return 60;
}
@@ -3994,14 +3996,23 @@
el.setAttribute('aria-hidden', 'true');
}
/** (embed preview) timeup-txt
/** (embed preview) timeup (Jumper mnptfts2 = img/Jumper/result-timeup.png)
* @param {function():boolean} [embedResultOkFn] าไม = ใชคะแนน quiz_carry (คนตอบถ); งเม crown เพอเลอก complete vs gameover */
function showQuizCarryTimeupOnDeskLayer(embedResultOkFn) {
const el = document.getElementById('quiz-carry-timeup-desk-layer');
if (!el) return;
const img = document.getElementById('quiz-carry-timeup-txt-img');
if (img) {
img.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
if (isJumpSurviveMissionUiMapPlay()) {
img.onerror = function () {
this.onerror = null;
this.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
};
img.src = jumperAssetUrl('result-timeup.png') + '?v=' + String(Date.now());
} else {
img.onerror = null;
img.src = BASE + '/img/quiz-carry/timeup-txt.png?v=' + String(Date.now());
}
}
el.classList.remove('is-hidden');
el.setAttribute('aria-hidden', 'false');
@@ -5360,6 +5371,14 @@
playJumpSurviveJumpHeightMult = Math.round(Math.max(0.5, Math.min(4, jm)) * 100) / 100;
}
}
if (Object.prototype.hasOwnProperty.call(payload, 'jumpSurviveMissionTimeSec')) {
const jt = Number(payload.jumpSurviveMissionTimeSec);
if (Number.isFinite(jt) && jt > 0) {
playJumpSurviveMissionTimeSec = Math.max(10, Math.min(7200, Math.floor(jt)));
} else {
playJumpSurviveMissionTimeSec = 0;
}
}
if (isStack() && stackMini) reapplyStackMiniSizingFromGlobals();
}
@@ -7654,7 +7673,8 @@
const impulse = Math.sqrt(Math.max(1e-6, 2 * gFull * mult * charHpixels));
jumpV0 = -Math.min(impulse, ts * 36);
}
const moveX = (Number(md.jumpSurviveMovePxPerSec) > 0 ? Number(md.jumpSurviveMovePxPerSec) : 200) * dt;
let moveX = (Number(md.jumpSurviveMovePxPerSec) > 0 ? Number(md.jumpSurviveMovePxPerSec) : 200) * dt;
if (isJumpSurviveMissionUiMapPlay()) moveX *= 1.35;
if (jumpQueued && onGround) {
vy = jumpV0;
@@ -7670,7 +7690,7 @@
const headY0 = feetY - bh;
const tryL = nx - bw;
const tryR = nx + bw;
if (!jumpSurviveOverlapsSolid(md, tryL, headY0, tryR, feetY)) {
if (!jumpSurviveOverlapsSolidForHorzMove(md, tryL, headY0, tryR, feetY)) {
pxc = nx;
}
@@ -7794,6 +7814,19 @@
return jumpSurviveOverlapsPlatforms(md, left, top, right, bottom, jumpSurvivePlatformScrollPx);
}
/**
* ชนแนวนอน: objects เตมกลอง; แพลตฟอรมไมบชวงปลายเทาเลกนอย
* (าใชกลองเตมกบแพลตฟอร ขณะยนบนแพลตฟอรมมกทบชองแนวต เดนซายขวาถกปฏเสธทงกอน)
*/
function jumpSurviveOverlapsSolidForHorzMove(md, left, top, right, bottom) {
if (jumpSurviveOverlapsObjectSolids(md, left, top, right, bottom)) return true;
const ts = tileSize;
const footSlop = ts * 0.16;
const platformBottom = bottom - footSlop;
if (platformBottom <= top + ts * 0.05) return false;
return jumpSurviveOverlapsPlatforms(md, left, top, right, platformBottom, jumpSurvivePlatformScrollPx);
}
/** Head/body overlaps กำแพง (objects=1) เท่านั้น — ใช้ตรวจชนฝาบนแล้วตาย */
function jumpSurviveOverlapsObjectWall(md, left, top, right, bottom) {
const ts = tileSize;
@@ -7849,6 +7882,20 @@
return { px: sx / n, py: sy / n };
}
/** มีแพลตฟอร์มใต้จุดเล็กน้อยข้างหน้าแนว wishDir หรือไม่ — กันบอทเดินหลุมแล้วตกจอ */
function jumpSurviveBotHasFloorAhead(md, pxc, feetY, wishDir, scrollPx) {
if (!wishDir) return true;
const ts = tileSize;
const bw = ts * 0.34;
const probeX = pxc + wishDir * (bw + ts * 0.82);
const pad = ts * 0.24;
const left = probeX - pad;
const right = probeX + pad;
const top = feetY - ts * 0.45;
const bottom = feetY + ts * 8;
return jumpSurviveOverlapsPlatforms(md, left, top, right, bottom, scrollPx);
}
function stepJumpSurvivePreviewBots(dt, halfViewH) {
if (!previewFillBots || !mapData || !isJumpSurvive()) return;
const md = mapData;
@@ -7868,28 +7915,55 @@
let pxc = (o.x + cw * 0.5) * ts;
let feetY = (o.y + ch) * ts;
let jumpQ = false;
if (o.jumpSurviveOnGround && Date.now() >= o.jumpSurviveNextJumpAi) {
o.jumpSurviveNextJumpAi = Date.now() + 320 + Math.floor(Math.random() * 1100);
const tier = o.botTier || 'avg';
const jp = tier === 'sharp' ? 0.5 : tier === 'weak' ? 0.25 : 0.36;
if (Math.random() < jp) jumpQ = true;
}
const camCy = jumpSurviveCamCenterY;
const mission = isJumpSurviveMissionUiMapPlay();
const lowDanger = feetY > camCy + halfViewH - ts * 8.5;
if (Date.now() >= o.jumpSurviveWishNext) {
o.jumpSurviveWishNext = Date.now() + 380 + Math.floor(Math.random() * 700);
const wMin = mission && lowDanger ? 180 : 380;
const wSpan = mission && lowDanger ? 480 : 700;
o.jumpSurviveWishNext = Date.now() + wMin + Math.floor(Math.random() * wSpan);
const tier = o.botTier || 'avg';
if (pxc < centerPx - ts * 1.8) o.jumpSurviveWishX = 1;
if (mission && lowDanger && Math.random() < 0.55) {
o.jumpSurviveWishX = 0;
} else if (pxc < centerPx - ts * 1.8) o.jumpSurviveWishX = 1;
else if (pxc > centerPx + ts * 1.8) o.jumpSurviveWishX = -1;
else if (tier === 'sharp') {
o.jumpSurviveWishX = Math.random() < 0.55 ? (Math.random() < 0.5 ? -1 : 1) : 0;
const roam = mission ? 0.42 : 0.55;
o.jumpSurviveWishX = Math.random() < roam ? (Math.random() < 0.5 ? -1 : 1) : 0;
} else {
o.jumpSurviveWishX = Math.random() < 0.4 ? (Math.random() < 0.5 ? -1 : 1) : 0;
const roam = mission ? 0.28 : 0.4;
o.jumpSurviveWishX = Math.random() < roam ? (Math.random() < 0.5 ? -1 : 1) : 0;
}
}
const moveLeft = o.jumpSurviveWishX < 0;
const moveRight = o.jumpSurviveWishX > 0;
let moveLeft = o.jumpSurviveWishX < 0;
let moveRight = o.jumpSurviveWishX > 0;
let hazardJump = false;
if (o.jumpSurviveOnGround) {
const sc = jumpSurvivePlatformScrollPx;
if (moveRight && !jumpSurviveBotHasFloorAhead(md, pxc, feetY, 1, sc)) {
o.jumpSurviveWishX = Math.random() < 0.65 ? -1 : 0;
hazardJump = true;
} else if (moveLeft && !jumpSurviveBotHasFloorAhead(md, pxc, feetY, -1, sc)) {
o.jumpSurviveWishX = Math.random() < 0.65 ? 1 : 0;
hazardJump = true;
}
moveLeft = o.jumpSurviveWishX < 0;
moveRight = o.jumpSurviveWishX > 0;
}
let jumpQ = false;
if (hazardJump) o.jumpSurviveNextJumpAi = Math.min(o.jumpSurviveNextJumpAi, Date.now());
if (o.jumpSurviveOnGround && Date.now() >= o.jumpSurviveNextJumpAi) {
o.jumpSurviveNextJumpAi = Date.now() + 300 + Math.floor(Math.random() * (hazardJump ? 520 : 1100));
const tier = o.botTier || 'avg';
let jp = tier === 'sharp' ? 0.5 : tier === 'weak' ? 0.25 : 0.36;
if (mission) jp += 0.12;
if (lowDanger) jp += 0.22;
jp = Math.min(0.9, jp);
if (hazardJump || Math.random() < jp) jumpQ = true;
}
const r = jumpSurvivePhysicsIntegrate(md, {
dt,
+19
View File
@@ -538,6 +538,8 @@ function defaultGameTiming() {
stackBlockWidthTiles: null,
/** กระโดดให้รอด: ความสูงกระโดด = ทวีคูณ × ความสูงตัวละคร (tile) — 0.5–4 ค่าเริ่ม 1.5 */
jumpSurviveJumpHeightMult: 1.5,
/** จำกัดเวลารอบภารกิจ/มินิเกมกระโดดขึ้นแท่น (วินาที) — 0 = ไคลเอนต์ใช้ 60 วิ; ไม่ใช่ค่าเดียวกับพรมแดง */
jumpSurviveMissionTimeSec: 0,
};
}
@@ -561,6 +563,13 @@ function clampJumpSurviveJumpHeightMult(n) {
return Math.round(Math.max(0.5, Math.min(4, v)) * 100) / 100;
}
/** กระโดดขึ้นแท่น / ภารกิจ Jumper: จำกัดเวลารอบ (วินาที) — 0 = ไม่ตั้งในไฟล์ ให้ไคลเอนต์ใช้ค่าเริ่ม 60; ค่าอื่น = 107200 */
function clampJumpSurviveMissionTimeSec(n) {
const v = Number(n);
if (Number.isNaN(v) || v <= 0) return 0;
return Math.max(10, Math.min(7200, Math.floor(v)));
}
function clampGauntletTickMs(n) {
const v = Number(n);
if (Number.isNaN(v)) return GAUNTLET_DEFAULT_TICK_MS;
@@ -669,6 +678,9 @@ function loadGameTiming() {
jumpSurviveJumpHeightMult: Object.prototype.hasOwnProperty.call(j, 'jumpSurviveJumpHeightMult')
? clampJumpSurviveJumpHeightMult(j.jumpSurviveJumpHeightMult)
: 1.5,
jumpSurviveMissionTimeSec: Object.prototype.hasOwnProperty.call(j, 'jumpSurviveMissionTimeSec')
? clampJumpSurviveMissionTimeSec(j.jumpSurviveMissionTimeSec)
: 0,
};
}
} catch (e) { console.error('loadGameTiming', e.message); }
@@ -709,6 +721,12 @@ function saveGameTimingToFile(d) {
const jumpMult = Object.prototype.hasOwnProperty.call(d, 'jumpSurviveJumpHeightMult')
? clampJumpSurviveJumpHeightMult(d.jumpSurviveJumpHeightMult)
: clampJumpSurviveJumpHeightMult(prevMult);
const prevJumpMissionSec = Object.prototype.hasOwnProperty.call(prev, 'jumpSurviveMissionTimeSec')
? prev.jumpSurviveMissionTimeSec
: 0;
const jumpMissionSec = Object.prototype.hasOwnProperty.call(d, 'jumpSurviveMissionTimeSec')
? clampJumpSurviveMissionTimeSec(d.jumpSurviveMissionTimeSec)
: clampJumpSurviveMissionTimeSec(prevJumpMissionSec);
const out = {
gauntletTickMs: clampGauntletTickMs(d.gauntletTickMs),
gauntletJumpTicks: clampGauntletJumpTicks(d.gauntletJumpTicks),
@@ -717,6 +735,7 @@ function saveGameTimingToFile(d) {
stackSwingHz: stackHz,
stackBlockWidthTiles: stackBlockW,
jumpSurviveJumpHeightMult: jumpMult,
jumpSurviveMissionTimeSec: jumpMissionSec,
};
fs.writeFileSync(GAME_TIMING_PATH, JSON.stringify(out, null, 2), 'utf8');
runtimeGameTiming = out;