Skip to content

Commit 48beb1a

Browse files
authored
Merge pull request #11 from Servbot91/hotornot-documentation
Major Improvement: Fixed all gauntlet and champion mode logic. Fixed button css creation
2 parents 6db87df + 65ff290 commit 48beb1a

6 files changed

Lines changed: 292 additions & 197 deletions

File tree

plugins/hot_or_not/hot_or_not.js

Lines changed: 103 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
};
1111

1212
// state.js
13+
function resetBattleState() {
14+
state.gauntletChampion = null;
15+
state.gauntletWins = 0;
16+
state.gauntletDefeated = [];
17+
state.gauntletFalling = false;
18+
state.gauntletFallingItem = null;
19+
state.gauntletChampionRank = 0;
20+
}
1321
var state;
1422
var init_state = __esm({
1523
"state.js"() {
@@ -1298,10 +1306,9 @@
12981306
state.gauntletFalling = true;
12991307
state.gauntletFallingItem = loserItem;
13001308
state.gauntletDefeated = [winnerId];
1309+
} else {
1310+
state.gauntletDefeated.push(winnerId);
13011311
}
1302-
state.gauntletChampion = winnerItem;
1303-
state.gauntletWins = 1;
1304-
state.gauntletDefeated = [loserId];
13051312
}
13061313
}
13071314
function updateChampionModeState(winnerId, winnerItem, loserId, newWinnerRating) {
@@ -1316,31 +1323,15 @@
13161323
}
13171324
}
13181325
async function handleSkip() {
1319-
const leftPerformer = state.currentPair?.left;
13201326
const rightPerformer = state.currentPair?.right;
1321-
if (!leftPerformer || !rightPerformer) {
1322-
loadNewPair();
1323-
return;
1327+
if (state.currentMode === "gauntlet" && rightPerformer) {
1328+
state.skippedId = rightPerformer.id;
1329+
console.log(`[HotOrNot] Skipping Gauntlet opponent: ${rightPerformer.name}`);
13241330
}
13251331
if (state.currentMode === "swiss") {
1326-
console.log(`[HotOrNot] Tying Swiss match: ${leftPerformer.name} vs ${rightPerformer.name}`);
1327-
try {
1328-
await handleComparison(
1329-
leftPerformer.id,
1330-
rightPerformer.id,
1331-
leftPerformer.rating100,
1332-
rightPerformer.rating100,
1333-
null,
1334-
leftPerformer,
1335-
rightPerformer,
1336-
true
1337-
// <--- The isDraw flag
1338-
);
1339-
} catch (err) {
1340-
console.error("[HotOrNot] Tie failed:", err);
1341-
}
13421332
}
1343-
loadNewPair();
1333+
const { loadNewPair: loadNewPair2 } = await Promise.resolve().then(() => (init_battle_engine(), battle_engine_exports));
1334+
loadNewPair2();
13441335
}
13451336
function applyVisualFeedback(winnerCard, loserCard, winnerRating, loserRating, outcome) {
13461337
winnerCard.classList.add("hon-winner");
@@ -1350,7 +1341,14 @@
13501341
if (loserCard) {
13511342
showRatingAnimation(loserCard, loserRating, outcome.newLoserRating, outcome.loserChange, false);
13521343
}
1353-
setTimeout(() => loadNewPair(), 1500);
1344+
setTimeout(() => {
1345+
const isVictoryVisible = document.querySelector(".hon-victory-screen");
1346+
if (!isVictoryVisible) {
1347+
loadNewPair();
1348+
} else {
1349+
console.log("[HotOrNot] Victory screen detected, cancelling next pair load.");
1350+
}
1351+
}, 1500);
13541352
}
13551353
var init_match_handler = __esm({
13561354
"match-handler.js"() {
@@ -1363,6 +1361,20 @@
13631361
});
13641362

13651363
// battle-engine.js
1364+
var battle_engine_exports = {};
1365+
__export(battle_engine_exports, {
1366+
fetchChampionPairPerformers: () => fetchChampionPairPerformers,
1367+
fetchChampionPairScenes: () => fetchChampionPairScenes,
1368+
fetchGauntletPairPerformers: () => fetchGauntletPairPerformers,
1369+
fetchGauntletPairScenes: () => fetchGauntletPairScenes,
1370+
fetchPair: () => fetchPair,
1371+
fetchSwissPairImages: () => fetchSwissPairImages,
1372+
fetchSwissPairPerformers: () => fetchSwissPairPerformers,
1373+
fetchSwissPairScenes: () => fetchSwissPairScenes,
1374+
handleMatchmakingLogic: () => handleMatchmakingLogic,
1375+
isChampionVictory: () => isChampionVictory,
1376+
loadNewPair: () => loadNewPair
1377+
});
13661378
async function fetchPair() {
13671379
const { battleType, currentMode, selectedGenders } = state;
13681380
if (currentMode === "swiss") {
@@ -1815,20 +1827,20 @@
18151827
document.removeEventListener("keydown", handleGlobalKeys);
18161828
return;
18171829
}
1818-
const hotKeys = ["ArrowLeft", "ArrowRight", " ", "Space"];
1819-
if (hotKeys.includes(e.key) || hotKeys.includes(e.code)) {
1820-
e.stopImmediatePropagation();
1830+
const isSpace = e.key === " " || e.code === "Space";
1831+
const hotKeys = ["ArrowLeft", "ArrowRight", ...isSpace ? [" ", "Space"] : []];
1832+
if (hotKeys.includes(e.key) || e.code && hotKeys.includes(e.code)) {
18211833
e.preventDefault();
1834+
e.stopImmediatePropagation();
18221835
if (e.key === "ArrowLeft") {
1823-
const leftCard = activeModal.querySelector('.hon-scene-card[data-side="left"]');
1824-
leftCard?.querySelector(".hon-scene-body")?.click();
1825-
}
1826-
if (e.key === "ArrowRight") {
1827-
const rightCard = activeModal.querySelector('.hon-scene-card[data-side="right"]');
1828-
rightCard?.querySelector(".hon-scene-body")?.click();
1829-
}
1830-
if (e.key === " " || e.code === "Space") {
1831-
document.getElementById("hon-skip-btn")?.click();
1836+
activeModal.querySelector('.hon-scene-card[data-side="left"] .hon-scene-body')?.click();
1837+
} else if (e.key === "ArrowRight") {
1838+
activeModal.querySelector('.hon-scene-card[data-side="right"] .hon-scene-body')?.click();
1839+
} else if (isSpace) {
1840+
const skipBtn = document.getElementById("hon-skip-btn") || activeModal.querySelector(".hon-gauntlet-skip");
1841+
if (skipBtn) {
1842+
skipBtn.click();
1843+
}
18321844
}
18331845
}
18341846
}
@@ -1873,29 +1885,37 @@
18731885
console.error("CRASH in _buildAndOpenModal:", err);
18741886
}
18751887
}
1876-
function openRankingModal() {
1888+
async function openRankingModal() {
18771889
try {
18781890
const path = window.location.pathname;
1879-
state.battleType = path.includes("/images") ? "images" : "performers";
18801891
const performerMatch = path.match(/\/performers\/(\d+)/);
1881-
if (performerMatch && state.currentMode === "gauntlet") {
1892+
const isSinglePerformerPage = !!performerMatch;
1893+
if (isSinglePerformerPage) {
18821894
const performerId = performerMatch[1];
1883-
Promise.resolve().then(() => (init_api_client(), api_client_exports)).then(async ({ fetchPerformerById: fetchPerformerById2 }) => {
1884-
try {
1885-
const performer = await fetchPerformerById2(performerId);
1886-
if (performer) {
1887-
state.gauntletChampion = performer;
1888-
state.gauntletWins = 0;
1889-
state.gauntletDefeated = [];
1890-
state.gauntletFalling = false;
1891-
state.gauntletFallingItem = null;
1892-
}
1893-
} catch (e) {
1894-
console.warn("[HotOrNot] Could not pre-seed performer for gauntlet:", e);
1895-
}
1895+
if (state.currentMode === "gauntlet" && state.gauntletChampion && state.gauntletChampion.id.toString() === performerId) {
1896+
console.log("[HotOrNot] Resuming existing Gauntlet run.");
18961897
_buildAndOpenModal();
1897-
});
1898-
return;
1898+
return;
1899+
}
1900+
state.battleType = "performers";
1901+
state.currentMode = "gauntlet";
1902+
const { fetchPerformerById: fetchPerformerById2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
1903+
try {
1904+
const performer = await fetchPerformerById2(performerId);
1905+
if (performer) {
1906+
state.gauntletChampion = performer;
1907+
state.gauntletWins = 0;
1908+
state.gauntletDefeated = [];
1909+
state.gauntletFalling = false;
1910+
state.gauntletFallingItem = null;
1911+
}
1912+
} catch (e) {
1913+
console.warn("[HotOrNot] Could not pre-seed performer:", e);
1914+
}
1915+
} else {
1916+
state.battleType = path.includes("/images") ? "images" : "performers";
1917+
state.currentMode = "swiss";
1918+
state.gauntletChampion = null;
18991919
}
19001920
_buildAndOpenModal();
19011921
} catch (err) {
@@ -1983,37 +2003,53 @@
19832003
});
19842004
const skipBtn = parent.querySelector("#hon-skip-btn");
19852005
if (skipBtn) {
1986-
skipBtn.style.display = state.currentMode === "swiss" ? "block" : "none";
2006+
const isSkippableMode = state.currentMode === "swiss" || state.currentMode === "gauntlet";
2007+
skipBtn.style.display = isSkippableMode ? "block" : "none";
19872008
skipBtn.onclick = () => {
1988-
if (state.currentMode === "swiss")
2009+
if (state.currentMode === "swiss" || state.currentMode === "gauntlet") {
19892010
handleSkip();
2011+
}
19902012
};
19912013
}
19922014
parent.querySelectorAll(".hon-gender-btn").forEach((btn) => {
19932015
btn.addEventListener("click", () => handleGenderToggle(btn.dataset.gender));
19942016
});
19952017
parent.querySelectorAll(".hon-mode-btn").forEach((btn) => {
1996-
btn.addEventListener("click", () => {
2018+
btn.addEventListener("click", async () => {
19972019
const newMode = btn.dataset.mode;
19982020
if (state.currentMode === newMode)
19992021
return;
20002022
state.currentMode = newMode;
2001-
state.gauntletChampion = null;
2002-
state.gauntletFalling = false;
2003-
state.gauntletWins = 0;
2004-
state.gauntletDefeated = [];
2023+
const { getPerformerIdFromUrl: getPerformerIdFromUrl2 } = await Promise.resolve().then(() => (init_ui_modal(), ui_modal_exports));
2024+
const urlPerformerId = getPerformerIdFromUrl2();
2025+
if (!urlPerformerId || state.gauntletChampion && state.gauntletChampion.id.toString() !== urlPerformerId) {
2026+
state.gauntletChampion = null;
2027+
state.gauntletWins = 0;
2028+
state.gauntletDefeated = [];
2029+
state.gauntletFalling = false;
2030+
}
20052031
const modalContent = document.querySelector(".hon-modal-content");
2006-
const mainContainer = document.getElementById("stash-main-container");
20072032
if (modalContent) {
20082033
modalContent.innerHTML = `<span class="hon-modal-close">\u2715</span>${createMainUI()}`;
20092034
attachEventListeners(modalContent);
20102035
modalContent.querySelector(".hon-modal-close").onclick = () => Promise.resolve().then(() => (init_ui_modal(), ui_modal_exports)).then((m) => m.closeRankingModal());
2011-
} else if (mainContainer) {
2012-
mainContainer.innerHTML = createMainUI();
2013-
attachEventListeners(mainContainer);
20142036
}
20152037
if (newMode === "gauntlet") {
2016-
window.showPerformerSelection();
2038+
if (urlPerformerId && !state.gauntletChampion) {
2039+
const { fetchPerformerById: fetchPerformerById2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
2040+
state.gauntletChampion = await fetchPerformerById2(urlPerformerId);
2041+
}
2042+
if (state.gauntletChampion) {
2043+
const selEl = document.getElementById("hon-performer-selection");
2044+
const compEl = document.getElementById("hon-comparison-area");
2045+
if (selEl)
2046+
selEl.style.display = "none";
2047+
if (compEl)
2048+
compEl.style.display = "";
2049+
loadNewPair();
2050+
} else {
2051+
window.showPerformerSelection();
2052+
}
20172053
} else {
20182054
loadNewPair();
20192055
}

plugins/hot_or_not/packages/battle-engine.js

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -248,36 +248,51 @@ export async function fetchChampionPairPerformers() {
248248
* ============================================
249249
*/
250250

251-
function handleMatchmakingLogic(list, type) {
252-
// 1. Handle Falling State
253-
if (state.gauntletFalling && state.gauntletFallingItem) {
254-
const fallIdx = list.findIndex(i => i.id === state.gauntletFallingItem.id);
255-
const below = list.filter((i, idx) => idx > fallIdx && !state.gauntletDefeated.includes(i.id));
256-
257-
if (below.length === 0) {
258-
return { items: [state.gauntletFallingItem], ranks: [list.length], isVictory: false, isPlacement: true, placementRank: list.length, placementRating: 1 };
259-
}
260-
const nextBelow = below[0];
261-
return { items: [state.gauntletFallingItem, nextBelow], ranks: [fallIdx + 1, list.indexOf(nextBelow) + 1], isFalling: true };
262-
}
263-
264-
// 2. No Champion - Start New
251+
export function handleMatchmakingLogic(list, type) {
252+
// ⭐ ADD THIS GUARD CLAUSE
265253
if (!state.gauntletChampion) {
266-
const challenger = list[Math.floor(Math.random() * list.length)];
267-
const lowest = [...list].sort((a, b) => (a.rating100 || 0) - (b.rating100 || 0))[0];
268-
return { items: [challenger, lowest], ranks: [list.indexOf(challenger) + 1, list.indexOf(lowest) + 1], isVictory: false };
254+
console.warn("[HotOrNot] No champion selected, picking a random starter.");
255+
const randomStarter = list[Math.floor(Math.random() * list.length)];
256+
return { items: [randomStarter, list.find(i => i.id !== randomStarter.id)], ranks: [null, null], isVictory: false };
269257
}
270258

271-
// 3. Existing Champion - Find Next
259+
// Now this line is safe
272260
const champIdx = list.findIndex(i => i.id === state.gauntletChampion.id);
273-
const opponents = list.filter((i, idx) => i.id !== state.gauntletChampion.id && !state.gauntletDefeated.includes(i.id) && (idx < champIdx || (i.rating100 || 0) >= (state.gauntletChampion.rating100 || 0)));
274261

275-
if (opponents.length === 0) return { items: [state.gauntletChampion], ranks: [1], isVictory: true };
262+
// 1. Identify "The Wall": Performers ranked HIGHER than our champion
263+
// (In a DESC sorted list, these are indices 0 through champIdx - 1)
264+
let potentialOpponents = list.filter((item, idx) =>
265+
idx < champIdx &&
266+
!state.gauntletDefeated.includes(item.id) &&
267+
item.id !== state.skippedId
268+
);
269+
270+
// 2. If we've beaten everyone above us, check for a "Victory"
271+
if (potentialOpponents.length === 0) {
272+
// If we only have 0 because of a skip, clear skip and try one last time
273+
if (state.skippedId) {
274+
state.skippedId = null;
275+
return handleMatchmakingLogic(list, type);
276+
}
277+
return { items: [state.gauntletChampion], ranks: [1], isVictory: true };
278+
}
276279

277-
const nextOpponent = opponents[opponents.length - 1]; // Closest opponent
278-
return { items: [state.gauntletChampion, nextOpponent], ranks: [champIdx + 1, list.indexOf(nextOpponent) + 1], isVictory: false };
280+
// 3. SMART SELECTION:
281+
// Instead of picking from the whole list (which includes unrated),
282+
// pick from the 5 performers ranked immediately ABOVE the champion.
283+
const proximityWindow = Math.min(5, potentialOpponents.length);
284+
const randomIdx = Math.floor(Math.random() * proximityWindow);
285+
286+
// Grab from the end of the "higher ranked" list
287+
const nextOpponent = potentialOpponents[potentialOpponents.length - 1 - randomIdx];
288+
289+
return {
290+
items: [state.gauntletChampion, nextOpponent],
291+
ranks: [champIdx + 1, list.indexOf(nextOpponent) + 1],
292+
isVictory: false
293+
};
279294
}
280295

281296
export function isChampionVictory(currentIndex, defeatedList, totalList) {
282297
return totalList.filter((item, idx) => idx < currentIndex && !defeatedList.includes(item.id)).length === 0;
283-
}
298+
}

0 commit comments

Comments
 (0)