minigame 7 Mega Virus 1.4
This commit is contained in:
+301
-25
@@ -29,6 +29,8 @@
|
||||
const QUIZ_QUESTION_MISSION_MAP_ID = 'mng8a80o';
|
||||
/** Stack ซ้อนตึก — ฉากภารกิจ (HOWTO → นับถอยหลัง → เล่น → สรุป) รูปใน `/Game/img/TowerBlock` */
|
||||
const STACK_TOWER_MISSION_MAP_ID = 'mnn93hpi';
|
||||
/** Mega Virus — balloon_boss ฉากภารกิจ (flow เดียวกับ crown / mno9kb07) รูปใน `/Game/img/MegaVirus` */
|
||||
const BALLOON_BOSS_MISSION_MAP_ID = 'mnq1eml7';
|
||||
/** ครบชั้นนี้ขึ้นไป — ยกจุดแกว่ง/เชือกขึ้น (world px) ป้องกันชนกองสูง (เฉพาะ Tower mission) */
|
||||
const STACK_TOWER_SWING_LIFT_FROM_LAYER = 9;
|
||||
/** Stack — เกณฑ์ใกล้ความจริง: ทับมากขึ้น, จำกัดการเยื้องจากกลางฐาน, ทับน้อยแล้วโคลงง่ายขึ้น */
|
||||
@@ -3428,9 +3430,16 @@
|
||||
function isGauntletCrownHeistMapPlay() {
|
||||
return isGauntletFaceRightMapMno9kb07();
|
||||
}
|
||||
function isMegaVirusMissionShellMapPlay() {
|
||||
return !!(mapData && mapData.gameType === 'balloon_boss' && currentPlayMapId() === BALLOON_BOSS_MISSION_MAP_ID);
|
||||
}
|
||||
/** Crown gauntlet หรือ Mega Virus — lobby HOWTO / Ready / นับถอยหลัง / held run */
|
||||
function usesCrownLobbyShellPlay() {
|
||||
return isGauntletCrownHeistMapPlay() || isMegaVirusMissionShellMapPlay();
|
||||
}
|
||||
let gauntletCrownHowtoVisible = false;
|
||||
function isGauntletCrownPregameBlockingPlay() {
|
||||
if (!isGauntletCrownHeistMapPlay()) return false;
|
||||
if (!usesCrownLobbyShellPlay()) return false;
|
||||
if (gauntletCrownRunHeldRemote) return true;
|
||||
if (gauntletCrownPregamePhase != null && gauntletCrownPregamePhase !== 'live') return true;
|
||||
return false;
|
||||
@@ -4227,6 +4236,29 @@
|
||||
return BASE + '/img/ViolentCrime/' + String(file || '').replace(/^\//, '');
|
||||
}
|
||||
|
||||
function megaVirusAssetUrl(file) {
|
||||
return BASE + '/img/MegaVirus/' + String(file || '').replace(/^\//, '');
|
||||
}
|
||||
|
||||
function applyMegaVirusMissionPanelImages() {
|
||||
const howtoBg = document.querySelector('#gauntlet-crown-howto-overlay .gch-bg');
|
||||
if (howtoBg) {
|
||||
howtoBg.src = megaVirusAssetUrl('popup-Howto.png');
|
||||
howtoBg.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/popup-Howto.png';
|
||||
};
|
||||
}
|
||||
const resBg = document.querySelector('#gauntlet-crown-mission-overlay .gcm-bg');
|
||||
if (resBg) {
|
||||
resBg.src = megaVirusAssetUrl('popup-result.png');
|
||||
resBg.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/popup-result.png';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function applySpaceShooterMissionPanelImages() {
|
||||
const howtoBg = document.querySelector('#gauntlet-crown-howto-overlay .gch-bg');
|
||||
if (howtoBg) {
|
||||
@@ -4475,6 +4507,149 @@
|
||||
};
|
||||
}
|
||||
|
||||
function balloonBossMergePreviewBotsMission(mission) {
|
||||
if (!mission || mission.uiSkin !== 'mega_virus' || !previewFillBots || !others || typeof others.forEach !== 'function') return mission;
|
||||
const seen = new Set();
|
||||
(mission.ranked || []).forEach(function (r) {
|
||||
if (r && r.id != null) seen.add(String(r.id));
|
||||
});
|
||||
const baseRows = (mission.ranked || []).map(function (r) {
|
||||
return {
|
||||
id: r.id,
|
||||
nickname: r.nickname,
|
||||
characterId: r.characterId,
|
||||
baseScore: Math.max(0, Number(r.baseScore) || 0),
|
||||
eliminated: !!r.eliminated,
|
||||
rankBonus: 0,
|
||||
finalScore: Math.max(0, Number(r.finalScore != null ? r.finalScore : r.baseScore) || 0),
|
||||
};
|
||||
});
|
||||
others.forEach(function (o, id) {
|
||||
if (!o || !isPreviewBotId(id)) return;
|
||||
const sid = String(id);
|
||||
if (seen.has(sid)) return;
|
||||
seen.add(sid);
|
||||
baseRows.push({
|
||||
id: id,
|
||||
nickname: (o.nickname && String(o.nickname).trim()) ? String(o.nickname).trim() : sid,
|
||||
characterId: o.characterId ?? null,
|
||||
baseScore: Math.max(0, Number(o.balloonBossScore) || 0),
|
||||
eliminated: !!o.balloonBossEliminated,
|
||||
rankBonus: 0,
|
||||
finalScore: Math.max(0, Number(o.balloonBossScore) || 0),
|
||||
});
|
||||
});
|
||||
baseRows.sort(function (a, b) {
|
||||
if (b.baseScore !== a.baseScore) return b.baseScore - a.baseScore;
|
||||
return String(a.nickname).localeCompare(String(b.nickname), 'th') || String(a.id).localeCompare(String(b.id));
|
||||
});
|
||||
const top = baseRows.slice(0, 5);
|
||||
const ranked = top.map(function (row, idx) {
|
||||
const pos = idx + 1;
|
||||
const bs = Math.max(0, Number(row.baseScore) || 0);
|
||||
return {
|
||||
id: row.id,
|
||||
nickname: row.nickname,
|
||||
characterId: row.characterId,
|
||||
baseScore: bs,
|
||||
eliminated: !!row.eliminated,
|
||||
rank: pos,
|
||||
rankLabel: pos === 1 ? '1st' : pos === 2 ? '2nd' : pos === 3 ? '3rd' : String(pos),
|
||||
rankBonus: 0,
|
||||
finalScore: bs,
|
||||
};
|
||||
});
|
||||
const totalParts = ranked.map(function (r) { return r.baseScore; });
|
||||
const totalSum = totalParts.reduce(function (s, n) { return s + n; }, 0);
|
||||
const n2 = ranked.length || 1;
|
||||
const averageScore = Math.floor(totalSum / n2);
|
||||
const maxHp = balloonBossMaxHpPlay();
|
||||
const progress = Math.min(1, totalSum / Math.max(1, maxHp));
|
||||
const score100 = Math.min(100, Math.floor(progress * 100));
|
||||
let grade = 'C';
|
||||
if (progress >= 1) grade = 'A';
|
||||
else if (score100 >= 50) grade = 'B';
|
||||
else if (totalSum <= 0) grade = 'F';
|
||||
const rewardCard = grade === 'F' ? null : gauntletCrownRollRewardCardLocal(grade);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: rewardCard,
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'mega_virus',
|
||||
survivorCount: baseRows.filter(function (r) { return !r.eliminated; }).length,
|
||||
participantCount: baseRows.length,
|
||||
balloonBossEndReason: mission.balloonBossEndReason,
|
||||
};
|
||||
}
|
||||
|
||||
function balloonBossBuildMissionPayloadPlay(reason) {
|
||||
const rows = [];
|
||||
if (myId != null) {
|
||||
rows.push({
|
||||
id: myId,
|
||||
nickname: ((me.nickname || nick || 'คุณ').trim() || 'คุณ'),
|
||||
characterId: me.characterId ?? null,
|
||||
baseScore: Math.max(0, me.balloonBossScore | 0),
|
||||
eliminated: !!me.balloonBossEliminated,
|
||||
});
|
||||
}
|
||||
others.forEach((o, id) => {
|
||||
rows.push({
|
||||
id: id,
|
||||
nickname: (o && o.nickname) ? String(o.nickname).trim() : id,
|
||||
characterId: o && o.characterId != null ? o.characterId : null,
|
||||
baseScore: Math.max(0, o.balloonBossScore | 0),
|
||||
eliminated: !!(o && o.balloonBossEliminated),
|
||||
});
|
||||
});
|
||||
rows.sort((a, b) => b.baseScore - a.baseScore
|
||||
|| String(a.nickname).localeCompare(String(b.nickname), 'th')
|
||||
|| String(a.id).localeCompare(String(b.id)));
|
||||
const top = rows.slice(0, 5);
|
||||
const ranked = top.map((row, idx) => {
|
||||
const pos = idx + 1;
|
||||
const bs = Math.max(0, Number(row.baseScore) || 0);
|
||||
return {
|
||||
id: row.id,
|
||||
nickname: row.nickname,
|
||||
characterId: row.characterId,
|
||||
baseScore: bs,
|
||||
eliminated: !!row.eliminated,
|
||||
rank: pos,
|
||||
rankLabel: pos === 1 ? '1st' : pos === 2 ? '2nd' : pos === 3 ? '3rd' : String(pos),
|
||||
rankBonus: 0,
|
||||
finalScore: bs,
|
||||
};
|
||||
});
|
||||
const totalParts = ranked.map((r) => r.baseScore);
|
||||
const totalSum = totalParts.reduce((s, n) => s + n, 0);
|
||||
const n2 = ranked.length || 1;
|
||||
const averageScore = Math.floor(totalSum / n2);
|
||||
const maxHp = balloonBossMaxHpPlay();
|
||||
const progress = Math.min(1, totalSum / Math.max(1, maxHp));
|
||||
const score100 = Math.min(100, Math.floor(progress * 100));
|
||||
let grade = 'C';
|
||||
if (reason === 'victory' || progress >= 1) grade = 'A';
|
||||
else if (score100 >= 50) grade = 'B';
|
||||
else if (totalSum <= 0) grade = 'F';
|
||||
const rewardCard = grade === 'F' ? null : gauntletCrownRollRewardCardLocal(grade);
|
||||
return {
|
||||
ranked: ranked,
|
||||
totalSum: totalSum,
|
||||
averageScore: averageScore,
|
||||
grade: grade,
|
||||
rewardCard: rewardCard,
|
||||
totalParts: totalParts,
|
||||
uiSkin: 'mega_virus',
|
||||
survivorCount: rows.filter((r) => !r.eliminated).length,
|
||||
participantCount: rows.length,
|
||||
balloonBossEndReason: reason,
|
||||
};
|
||||
}
|
||||
|
||||
function endSpaceShooterMissionRound() {
|
||||
if (!isSpaceShooterMissionUiMapPlay() || spaceShooterGameEnded) return;
|
||||
spaceShooterGameEnded = true;
|
||||
@@ -7434,7 +7609,7 @@
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'gauntletCrownRunHeld')) {
|
||||
gauntletCrownRunHeldRemote = !!payload.gauntletCrownRunHeld;
|
||||
if (!gauntletCrownRunHeldRemote && isGauntletCrownHeistMapPlay()) {
|
||||
if (!gauntletCrownRunHeldRemote && usesCrownLobbyShellPlay()) {
|
||||
gauntletCrownPregamePhase = 'live';
|
||||
}
|
||||
}
|
||||
@@ -7920,7 +8095,7 @@
|
||||
function updateGauntletCrownHowtoHud() {
|
||||
const st = document.getElementById('gauntlet-crown-howto-status');
|
||||
const btn = document.getElementById('btn-gch-ready');
|
||||
if (!btn || !isGauntletCrownHeistMapPlay() || gauntletCrownPregamePhase !== 'howto') return;
|
||||
if (!btn || !usesCrownLobbyShellPlay() || gauntletCrownPregamePhase !== 'howto') return;
|
||||
const humans = quizCarryPregameHumanIds();
|
||||
const tot = Math.max(1, quizCarryPregameTotalPlayers());
|
||||
const num = gauntletCrownPregameReadyNumerator();
|
||||
@@ -7939,7 +8114,7 @@
|
||||
}
|
||||
|
||||
function beginGauntletCrownCountdownThenRun() {
|
||||
if (!isGauntletCrownHeistMapPlay()) return;
|
||||
if (!usesCrownLobbyShellPlay()) return;
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
clearTimeout(gauntletCrownCountdownTimer);
|
||||
gauntletCrownCountdownTimer = null;
|
||||
@@ -7950,6 +8125,24 @@
|
||||
gauntletCrownHowtoVisible = false;
|
||||
const cd = document.getElementById('gauntlet-crown-countdown');
|
||||
const numEl = document.getElementById('gauntlet-crown-countdown-num');
|
||||
function applyMegaBalloonLiveStartPlay() {
|
||||
balloonBossSessionStartMs = performance.now();
|
||||
balloonBossLastTickMs = performance.now();
|
||||
balloonBossGameEnded = false;
|
||||
balloonBossPendingShots = [];
|
||||
balloonBossPlayerBullets = [];
|
||||
balloonBossBossBullets = [];
|
||||
balloonBossBossFireAcc = 0;
|
||||
me.balloonBossScore = 0;
|
||||
me.balloonBossBalloons = balloonBossBalloonsStartPlay();
|
||||
me.balloonBossEliminated = false;
|
||||
others.forEach((o) => {
|
||||
o.balloonBossScore = 0;
|
||||
o.balloonBossBalloons = balloonBossBalloonsStartPlay();
|
||||
o.balloonBossEliminated = false;
|
||||
});
|
||||
if (mapData) applyBalloonBossSpawnLayoutPlay();
|
||||
}
|
||||
const runFinish = () => {
|
||||
if (cd) cd.classList.add('is-hidden');
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
@@ -7958,10 +8151,14 @@
|
||||
}
|
||||
if (socket && socket.connected) {
|
||||
socket.emit('gauntlet-crown-begin-run', (res) => {
|
||||
if (res && res.ok) gauntletCrownPregamePhase = 'live';
|
||||
if (res && res.ok) {
|
||||
gauntletCrownPregamePhase = 'live';
|
||||
if (isMegaVirusMissionShellMapPlay()) applyMegaBalloonLiveStartPlay();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
gauntletCrownPregamePhase = 'live';
|
||||
if (isMegaVirusMissionShellMapPlay()) applyMegaBalloonLiveStartPlay();
|
||||
}
|
||||
};
|
||||
if (!cd || !numEl) {
|
||||
@@ -7985,8 +8182,9 @@
|
||||
}
|
||||
|
||||
function showGauntletCrownHowtoOverlay() {
|
||||
if (!isGauntletCrownHeistMapPlay()) return;
|
||||
if (!usesCrownLobbyShellPlay()) return;
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
if (isMegaVirusMissionShellMapPlay()) applyMegaVirusMissionPanelImages();
|
||||
const cd = document.getElementById('gauntlet-crown-countdown');
|
||||
if (cd) cd.classList.add('is-hidden');
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
@@ -8020,6 +8218,10 @@
|
||||
const g = String(mission.grade || '').trim().toUpperCase().charAt(0);
|
||||
return g !== 'F';
|
||||
}
|
||||
if (mission.uiSkin === 'mega_virus') {
|
||||
const g = String(mission.grade || '').trim().toUpperCase().charAt(0);
|
||||
return g !== 'F';
|
||||
}
|
||||
const g = String(mission.grade || '').trim().toUpperCase().charAt(0);
|
||||
return g !== 'F';
|
||||
}
|
||||
@@ -8053,7 +8255,7 @@
|
||||
*/
|
||||
function gauntletCrownBuildDisplayMission(mission) {
|
||||
if (!mission || !Array.isArray(mission.ranked)) return mission;
|
||||
if (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower') return mission;
|
||||
if (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower' || mission.uiSkin === 'mega_virus') return mission;
|
||||
if (!(previewFillBots && isGauntletCrownHeistMapPlay() && others && typeof others.forEach === 'function')) return mission;
|
||||
|
||||
const seen = new Set();
|
||||
@@ -8142,6 +8344,8 @@
|
||||
disp = quizQuestionMissionMergePreviewBotsMission(disp) || disp;
|
||||
} else if (mission.uiSkin === 'stack_tower') {
|
||||
disp = stackTowerMissionMergePreviewBotsMission(disp) || disp;
|
||||
} else if (mission.uiSkin === 'mega_virus') {
|
||||
disp = balloonBossMergePreviewBotsMission(disp) || disp;
|
||||
}
|
||||
if (!disp || !Array.isArray(disp.ranked)) return;
|
||||
rowEl.innerHTML = '';
|
||||
@@ -8206,10 +8410,11 @@
|
||||
nick.textContent = r.nickname || '—';
|
||||
const sc = document.createElement('div');
|
||||
sc.className = 'gcm-sc';
|
||||
const jumpPlain = mission && (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower');
|
||||
const jumpPlain = mission && (mission.uiSkin === 'jumper' || mission.uiSkin === 'violent_crime' || mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower' || mission.uiSkin === 'mega_virus');
|
||||
const botScorePlain = (previewFillBots && isGauntletCrownHeistMapPlay() && isPreviewBotId(r.id))
|
||||
|| (previewFillBots && mission && mission.uiSkin === 'question_mission' && isPreviewBotId(r.id))
|
||||
|| (previewFillBots && mission && mission.uiSkin === 'stack_tower' && isPreviewBotId(r.id));
|
||||
|| (previewFillBots && mission && mission.uiSkin === 'stack_tower' && isPreviewBotId(r.id))
|
||||
|| (previewFillBots && mission && mission.uiSkin === 'mega_virus' && isPreviewBotId(r.id));
|
||||
sc.textContent = (jumpPlain || botScorePlain)
|
||||
? String(Math.max(0, Number(r.baseScore) || 0))
|
||||
: String(r.baseScore || 0) + '(+' + String(r.rankBonus || 0) + ')';
|
||||
@@ -8240,6 +8445,9 @@
|
||||
} else if (mission && mission.uiSkin === 'stack_tower') {
|
||||
const pc = Math.max(0, Math.floor(Number(disp.participantCount) || 0));
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · ผู้เล่น ' + pc + ' → เกรด';
|
||||
} else if (mission && mission.uiSkin === 'mega_virus') {
|
||||
const pc = Math.max(0, Math.floor(Number(disp.participantCount) || 0));
|
||||
totalEl.innerHTML = 'ความเสียหายรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · ผู้เล่น ' + pc + ' → เกรด';
|
||||
} else {
|
||||
totalEl.innerHTML = 'คะแนนรวม <span class="gcm-total-nums">(' + parts.join('+') + ') = ' + sum + '</span> · เฉลี่ย ' + avg + ' → เกรด';
|
||||
}
|
||||
@@ -8260,9 +8468,10 @@
|
||||
row.style.gap = '8px';
|
||||
const chk = document.createElement('img');
|
||||
const stackTS = mission && mission.uiSkin === 'stack_tower';
|
||||
chk.src = stackTS ? stackTowerAssetUrl('result-check.png') : (BASE + '/img/gauntlet-assets/result-check.png');
|
||||
const megaVs = mission && mission.uiSkin === 'mega_virus';
|
||||
chk.src = stackTS ? stackTowerAssetUrl('result-check.png') : (megaVs ? megaVirusAssetUrl('result-check.png') : (BASE + '/img/gauntlet-assets/result-check.png'));
|
||||
chk.alt = '';
|
||||
if (stackTS) {
|
||||
if (stackTS || megaVs) {
|
||||
chk.onerror = function () {
|
||||
this.onerror = null;
|
||||
this.src = BASE + '/img/gauntlet-assets/result-check.png';
|
||||
@@ -8295,7 +8504,7 @@
|
||||
}
|
||||
const gcmHead = document.getElementById('gcm-heading');
|
||||
if (gcmHead) {
|
||||
if (mission && (mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower')) gcmHead.classList.add('sr-only');
|
||||
if (mission && (mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower' || mission.uiSkin === 'mega_virus')) gcmHead.classList.add('sr-only');
|
||||
else gcmHead.classList.remove('sr-only');
|
||||
}
|
||||
ov.classList.remove('is-hidden');
|
||||
@@ -8309,7 +8518,7 @@
|
||||
if (previewMode && editorEmbedReturn) {
|
||||
ov.classList.add('is-hidden');
|
||||
if (gcmHead) gcmHead.classList.remove('sr-only');
|
||||
if (mission && (mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower')) {
|
||||
if (mission && (mission.uiSkin === 'question_mission' || mission.uiSkin === 'stack_tower' || mission.uiSkin === 'mega_virus')) {
|
||||
cancelQuizCarryResultEndAfterTimeup();
|
||||
hideQuizCarryTimeupOnDeskLayer();
|
||||
hideQuizCarryResultEndLayer();
|
||||
@@ -9447,6 +9656,8 @@
|
||||
function balloonBossRemainingSecPlay() {
|
||||
const lim = balloonBossTimeLimitSecPlay();
|
||||
if (lim <= 0) return null;
|
||||
if (isMegaVirusMissionShellMapPlay() && isGauntletCrownPregameBlockingPlay()) return lim;
|
||||
if (!balloonBossSessionStartMs) return lim;
|
||||
const elapsed = (performance.now() - balloonBossSessionStartMs) / 1000;
|
||||
return Math.max(0, Math.ceil(lim - elapsed));
|
||||
}
|
||||
@@ -9521,6 +9732,11 @@
|
||||
balloonBossPendingShots = [];
|
||||
balloonBossPlayerBullets = [];
|
||||
balloonBossBossBullets = [];
|
||||
if (isMegaVirusMissionShellMapPlay()) {
|
||||
applyMegaVirusMissionPanelImages();
|
||||
showGauntletCrownMissionOverlay(balloonBossBuildMissionPayloadPlay(reason));
|
||||
return;
|
||||
}
|
||||
const ov = document.getElementById('gauntlet-ended-overlay');
|
||||
const msgEl = document.getElementById('gauntlet-ended-message');
|
||||
const titleEl = document.getElementById('gauntlet-ended-title');
|
||||
@@ -9597,6 +9813,7 @@
|
||||
|
||||
function balloonBossTickFrame() {
|
||||
if (!mapData || mapData.gameType !== 'balloon_boss') return;
|
||||
if (isMegaVirusMissionShellMapPlay() && isGauntletCrownPregameBlockingPlay()) return;
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
const ts = tileSize;
|
||||
const mw = w * ts, mh = h * ts;
|
||||
@@ -10884,8 +11101,13 @@
|
||||
if (mapData.gameType !== 'quiz_carry') {
|
||||
quizCarryWalkSpeedMultActive = QUIZ_CARRY_WALK_SPEED_MULT;
|
||||
}
|
||||
if (res && res.mapId != null && String(res.mapId).trim() !== '') {
|
||||
playSessionMapId = String(res.mapId).trim();
|
||||
{
|
||||
const qMap = (playMapIdFromQuery || '').trim();
|
||||
const dataId = mapData && mapData.id != null && String(mapData.id).trim() !== '' ? String(mapData.id).trim() : '';
|
||||
const resMap = res && res.mapId != null && String(res.mapId).trim() !== '' ? String(res.mapId).trim() : '';
|
||||
if (qMap) playSessionMapId = qMap;
|
||||
else if (dataId) playSessionMapId = dataId;
|
||||
else if (resMap) playSessionMapId = resMap;
|
||||
}
|
||||
if (res && res.hostId != null) playHostId = res.hostId;
|
||||
if (!isQuizQuestionMissionUiMapPlay()) {
|
||||
@@ -11208,6 +11430,20 @@
|
||||
const gccR = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccR) gccR.classList.add('is-hidden');
|
||||
setTimeout(function () { showGauntletCrownHowtoOverlay(); }, 50);
|
||||
} else if (mapData.gameType === 'balloon_boss' && isMegaVirusMissionShellMapPlay()) {
|
||||
hideConflictingOverlaysForGauntletCrown();
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
clearTimeout(gauntletCrownCountdownTimer);
|
||||
gauntletCrownCountdownTimer = null;
|
||||
}
|
||||
const gccRm = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccRm) gccRm.classList.add('is-hidden');
|
||||
if (res && Object.prototype.hasOwnProperty.call(res, 'gauntletCrownRunHeld')) {
|
||||
applyGauntletTimingFromServer({ gauntletCrownRunHeld: res.gauntletCrownRunHeld });
|
||||
}
|
||||
setTimeout(function () { showGauntletCrownHowtoOverlay(); }, 50);
|
||||
} else if (mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay()) {
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
clearTimeout(gauntletCrownCountdownTimer);
|
||||
@@ -11385,7 +11621,7 @@
|
||||
}
|
||||
});
|
||||
socket.on('gauntlet-crown-lobby-started', () => {
|
||||
if (!isGauntletCrownHeistMapPlay()) return;
|
||||
if (!usesCrownLobbyShellPlay()) return;
|
||||
beginGauntletCrownCountdownThenRun();
|
||||
});
|
||||
socket.on('user-move', (data) => {
|
||||
@@ -11573,7 +11809,12 @@
|
||||
});
|
||||
|
||||
socket.on('gauntlet-sync', (data) => {
|
||||
if (!mapData || mapData.gameType !== 'gauntlet' || !data) return;
|
||||
if (!data || typeof data !== 'object') return;
|
||||
if (isMegaVirusMissionShellMapPlay()) {
|
||||
applyGauntletTimingFromServer(data);
|
||||
return;
|
||||
}
|
||||
if (!mapData || mapData.gameType !== 'gauntlet') return;
|
||||
applyGauntletTimingFromServer(data);
|
||||
if (Array.isArray(data.obstacles)) {
|
||||
gauntletObstacles = data.obstacles;
|
||||
@@ -11795,10 +12036,12 @@
|
||||
}
|
||||
} else {
|
||||
gauntletEndsAtMs = null;
|
||||
const megaShellGs = mapData.gameType === 'balloon_boss' && String(ev.mapId || '').trim() === BALLOON_BOSS_MISSION_MAP_ID;
|
||||
const skipHideCrownShell = (mapData.gameType === 'jump_survive' && isJumpSurviveMissionUiMapPlay())
|
||||
|| (mapData.gameType === 'space_shooter' && isSpaceShooterMissionUiMapPlay())
|
||||
|| (mapData.gameType === 'quiz' && isQuizQuestionMissionUiMapPlay())
|
||||
|| (mapData.gameType === 'stack' && isStackTowerMissionUiMapPlay());
|
||||
|| (mapData.gameType === 'stack' && isStackTowerMissionUiMapPlay())
|
||||
|| megaShellGs;
|
||||
if (!skipHideCrownShell) {
|
||||
const hto3 = document.getElementById('gauntlet-crown-howto-overlay');
|
||||
if (hto3) hto3.classList.add('is-hidden');
|
||||
@@ -11927,7 +12170,6 @@
|
||||
balloonBossHitFx = [];
|
||||
balloonBossLastTickMs = performance.now();
|
||||
balloonBossPlayerFireCd = 0;
|
||||
balloonBossSessionStartMs = performance.now();
|
||||
balloonBossLastMoveEmit = 0;
|
||||
balloonBossBossFireAcc = 0;
|
||||
me.balloonBossScore = 0;
|
||||
@@ -11938,10 +12180,36 @@
|
||||
o.balloonBossBalloons = balloonBossBalloonsStartPlay();
|
||||
o.balloonBossEliminated = false;
|
||||
});
|
||||
fetch(BASE + '/api/game-timing?_=' + Date.now(), { cache: 'no-store' })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((t) => { if (t) applyGauntletTimingFromServer(t); })
|
||||
.catch(() => {});
|
||||
if (String(ev.mapId || '').trim() === BALLOON_BOSS_MISSION_MAP_ID) {
|
||||
balloonBossSessionStartMs = 0;
|
||||
if (Object.prototype.hasOwnProperty.call(ev, 'gauntletCrownRunHeld')) {
|
||||
applyGauntletTimingFromServer({ gauntletCrownRunHeld: ev.gauntletCrownRunHeld });
|
||||
}
|
||||
fetch(BASE + '/api/game-timing?_=' + Date.now(), { cache: 'no-store' })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((t) => {
|
||||
if (t) applyGauntletTimingFromServer(t);
|
||||
if (Object.prototype.hasOwnProperty.call(ev, 'gauntletCrownRunHeld')) {
|
||||
applyGauntletTimingFromServer({ gauntletCrownRunHeld: ev.gauntletCrownRunHeld });
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
gauntletCrownPregamePhase = null;
|
||||
gauntletCrownLobbyReadyMap = {};
|
||||
if (gauntletCrownCountdownTimer) {
|
||||
clearTimeout(gauntletCrownCountdownTimer);
|
||||
gauntletCrownCountdownTimer = null;
|
||||
}
|
||||
const gccMv = document.getElementById('gauntlet-crown-countdown');
|
||||
if (gccMv) gccMv.classList.add('is-hidden');
|
||||
setTimeout(function () { showGauntletCrownHowtoOverlay(); }, 60);
|
||||
} else {
|
||||
balloonBossSessionStartMs = performance.now();
|
||||
fetch(BASE + '/api/game-timing?_=' + Date.now(), { cache: 'no-store' })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((t) => { if (t) applyGauntletTimingFromServer(t); })
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
if (mapData.gameType === 'quiz_battle') {
|
||||
if (!mapData.quizBattleDomeArea) mapData.quizBattleDomeArea = [];
|
||||
@@ -13124,7 +13392,9 @@
|
||||
timeVal.textContent = rem != null ? String(rem) : '∞';
|
||||
}
|
||||
} else if (isBalloonBoss()) {
|
||||
if (balloonBossGameEnded) {
|
||||
if (isMegaVirusMissionShellMapPlay() && isGauntletCrownPregameBlockingPlay()) {
|
||||
timeVal.textContent = '···';
|
||||
} else if (balloonBossGameEnded) {
|
||||
timeVal.textContent = '0';
|
||||
} else {
|
||||
const rem = balloonBossRemainingSecPlay();
|
||||
@@ -14124,6 +14394,12 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isBalloonBoss() && isMegaVirusMissionShellMapPlay() && isGauntletCrownPregameBlockingPlay() && mapData && !isChatFocused()) {
|
||||
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((isSpaceShooter() || isBalloonBoss()) && mapData && !isChatFocused()) {
|
||||
if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) {
|
||||
e.preventDefault();
|
||||
@@ -14500,7 +14776,7 @@
|
||||
beginStackTowerMissionCountdownThenRun();
|
||||
return;
|
||||
}
|
||||
if (!isGauntletCrownHeistMapPlay()) return;
|
||||
if (!usesCrownLobbyShellPlay()) return;
|
||||
if (gauntletCrownPregamePhase !== 'howto') return;
|
||||
if (myId == null || !isMePlayHost()) return;
|
||||
const humans = quizCarryPregameHumanIds();
|
||||
|
||||
+64
-7
@@ -2895,11 +2895,43 @@ const GAUNTLET_LASER_SPAWN_CHANCE = 0.14;
|
||||
const GAUNTLET_LANE_ROW_SPAWN_CHANCE = 0.34;
|
||||
/** Last Light / museum gauntlet — scoring, mission UI, how-to flow */
|
||||
const GAUNTLET_CROWN_HEIST_MAP_ID = 'mno9kb07';
|
||||
/** Mega Virus — balloon_boss ฉากภารกิจ (flow เดียวกับ crown) */
|
||||
const BALLOON_BOSS_MISSION_MAP_ID = 'mnq1eml7';
|
||||
|
||||
function isGauntletCrownHeistMapBySpace(space) {
|
||||
return !!(space && space.mapId && String(space.mapId) === GAUNTLET_CROWN_HEIST_MAP_ID);
|
||||
}
|
||||
|
||||
function isBalloonBossMissionShellSpace(space) {
|
||||
if (!space || !space.mapId || String(space.mapId) !== BALLOON_BOSS_MISSION_MAP_ID) return false;
|
||||
const md = (space.mapId && maps.get(space.mapId)) || space.mapData;
|
||||
return !!(md && md.gameType === 'balloon_boss');
|
||||
}
|
||||
|
||||
function isCrownLobbyShellSpace(space) {
|
||||
return isGauntletCrownHeistMapBySpace(space) || isBalloonBossMissionShellSpace(space);
|
||||
}
|
||||
|
||||
function newBalloonBossShellGauntletRunState() {
|
||||
return {
|
||||
obstacles: [],
|
||||
nextObsId: 1,
|
||||
spawnAcc: 0,
|
||||
nextSpawnIn: 999,
|
||||
crownRunHeld: true,
|
||||
timerId: null,
|
||||
endsAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
function emitBalloonBossMissionShellGauntletSync(sid, space) {
|
||||
if (!isBalloonBossMissionShellSpace(space)) return;
|
||||
const gr = space.gauntletRun;
|
||||
io.to(sid).emit('gauntlet-sync', {
|
||||
gauntletCrownRunHeld: !!(gr && gr.crownRunHeld),
|
||||
});
|
||||
}
|
||||
|
||||
function rollGauntletCrownRewardCard(grade) {
|
||||
const r = Math.random();
|
||||
if (grade === 'A') {
|
||||
@@ -3671,6 +3703,11 @@ io.on('connection', (socket) => {
|
||||
io.to(spaceId).emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
|
||||
}
|
||||
startGauntletTicker(spaceId, space);
|
||||
} else if (isBalloonBossMissionShellSpace(space)) {
|
||||
if (!space.gauntletCrownLobbyReady || typeof space.gauntletCrownLobbyReady !== 'object') space.gauntletCrownLobbyReady = {};
|
||||
space.gauntletCrownLobbyReady[socket.id] = false;
|
||||
if (!space.gauntletRun) space.gauntletRun = newBalloonBossShellGauntletRunState();
|
||||
io.to(spaceId).emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
|
||||
}
|
||||
const peersList = [...space.peers.values()];
|
||||
const mapData = mdJoin;
|
||||
@@ -3715,6 +3752,9 @@ io.on('connection', (socket) => {
|
||||
if (mdJoin.gameType === 'gauntlet' && space.gauntletRun) {
|
||||
joinCb.gauntletEndsAt = space.gauntletRun.endsAt != null ? space.gauntletRun.endsAt : null;
|
||||
}
|
||||
if (isBalloonBossMissionShellSpace(space) && space.gauntletRun) {
|
||||
joinCb.gauntletCrownRunHeld = !!space.gauntletRun.crownRunHeld;
|
||||
}
|
||||
if (typeof cb === 'function') cb(joinCb);
|
||||
socket.to(spaceId).emit('user-joined', peer);
|
||||
if (space.voiceInits) {
|
||||
@@ -3977,6 +4017,13 @@ io.on('connection', (socket) => {
|
||||
gauntletEliminated: !!p.gauntletEliminated,
|
||||
}));
|
||||
}
|
||||
if (md && isBalloonBossMissionShellSpace(space)) {
|
||||
if (!space.gauntletCrownLobbyReady || typeof space.gauntletCrownLobbyReady !== 'object') space.gauntletCrownLobbyReady = {};
|
||||
space.peers.forEach((_p, id) => {
|
||||
space.gauntletCrownLobbyReady[id] = false;
|
||||
});
|
||||
space.gauntletRun = newBalloonBossShellGauntletRunState();
|
||||
}
|
||||
}
|
||||
const gameStartPayload = {
|
||||
mapId: mapId || undefined,
|
||||
@@ -3985,6 +4032,10 @@ io.on('connection', (socket) => {
|
||||
peersSnap: peersSnap || undefined,
|
||||
};
|
||||
if (peersSnap) gameStartPayload.gauntletEndsAt = gauntletEndsAtEmit != null ? gauntletEndsAtEmit : null;
|
||||
if (mapId && maps.has(mapId) && isBalloonBossMissionShellSpace(space)) {
|
||||
const grs = space.gauntletRun;
|
||||
gameStartPayload.gauntletCrownRunHeld = !!(grs && grs.crownRunHeld);
|
||||
}
|
||||
io.to(sid).emit('game-start', gameStartPayload);
|
||||
return reply({ ok: true });
|
||||
}
|
||||
@@ -4014,7 +4065,7 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
if (space.gauntletCrownLobbyReady && typeof space.gauntletCrownLobbyReady === 'object') {
|
||||
delete space.gauntletCrownLobbyReady[socket.id];
|
||||
if (isGauntletCrownHeistMapBySpace(space)) {
|
||||
if (isCrownLobbyShellSpace(space)) {
|
||||
io.to(sid).emit('gauntlet-crown-lobby-sync', { readyMap: { ...space.gauntletCrownLobbyReady } });
|
||||
}
|
||||
}
|
||||
@@ -4057,14 +4108,20 @@ io.on('connection', (socket) => {
|
||||
const gr = space.gauntletRun;
|
||||
if (!gr) return reply({ ok: false });
|
||||
const md = (space.mapId && maps.get(space.mapId)) || space.mapData;
|
||||
if (!md || md.gameType !== 'gauntlet' || !isGauntletCrownHeistMapBySpace(space)) return reply({ ok: false });
|
||||
const okGauntletCrown = md && md.gameType === 'gauntlet' && isGauntletCrownHeistMapBySpace(space);
|
||||
const okBalloonShell = md && md.gameType === 'balloon_boss' && isBalloonBossMissionShellSpace(space);
|
||||
if (!okGauntletCrown && !okBalloonShell) return reply({ ok: false });
|
||||
const previewRoom = !!(space.previewEditorTest
|
||||
|| (space.isPrivate && String(space.spaceName || '').toLowerCase() === 'preview'));
|
||||
if (!previewRoom && space.hostId !== socket.id) return reply({ ok: false, error: 'host' });
|
||||
if (!gr.crownRunHeld) return reply({ ok: true, already: true });
|
||||
gr.crownRunHeld = false;
|
||||
ensureGauntletEndsAtIfNeeded(space);
|
||||
emitGauntletSync(sid, space);
|
||||
if (okGauntletCrown) {
|
||||
ensureGauntletEndsAtIfNeeded(space);
|
||||
emitGauntletSync(sid, space);
|
||||
} else {
|
||||
emitBalloonBossMissionShellGauntletSync(sid, space);
|
||||
}
|
||||
return reply({ ok: true });
|
||||
});
|
||||
|
||||
@@ -4134,7 +4191,7 @@ io.on('connection', (socket) => {
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return;
|
||||
if (!isGauntletCrownHeistMapBySpace(space)) return;
|
||||
if (!isCrownLobbyShellSpace(space)) return;
|
||||
if (!space.gauntletCrownLobbyReady || typeof space.gauntletCrownLobbyReady !== 'object') {
|
||||
space.gauntletCrownLobbyReady = {};
|
||||
}
|
||||
@@ -4148,7 +4205,7 @@ io.on('connection', (socket) => {
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return;
|
||||
if (!isGauntletCrownHeistMapBySpace(space)) return;
|
||||
if (!isCrownLobbyShellSpace(space)) return;
|
||||
if (!space.gauntletCrownLobbyReady || typeof space.gauntletCrownLobbyReady !== 'object') space.gauntletCrownLobbyReady = {};
|
||||
for (const id of space.peers.keys()) {
|
||||
if (space.gauntletCrownLobbyReady[id] === undefined) space.gauntletCrownLobbyReady[id] = false;
|
||||
@@ -4162,7 +4219,7 @@ io.on('connection', (socket) => {
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || !space.peers.has(socket.id)) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
if (!isGauntletCrownHeistMapBySpace(space)) return reply({ ok: false, error: 'ไม่ใช่แมป crown' });
|
||||
if (!isCrownLobbyShellSpace(space)) return reply({ ok: false, error: 'ไม่ใช่แมป crown / Mega Virus ภารกิจ' });
|
||||
if (space.hostId !== socket.id) return reply({ ok: false, error: 'เฉพาะโฮสต์กด START' });
|
||||
const gr = space.gauntletRun;
|
||||
if (!gr || !gr.crownRunHeld) return reply({ ok: false, error: 'เริ่มแล้ว' });
|
||||
|
||||
Reference in New Issue
Block a user