minigame 5 jumper 1.2
This commit is contained in:
+10
-1
@@ -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');
|
||||
|
||||
@@ -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> > 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> > 0 จะใช้ค่าบนแมปแทนทั้งหมด · ไม่ใช่ค่าเดียวกับ "จำกัดเวลาเกม" ของพรมแดง · <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">
|
||||
|
||||
@@ -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> > 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> > 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
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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; ค่าอื่น = 10–7200 */
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user