Update index.html
Browse files- index.html +39 -79
index.html
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
|
@@ -3821,10 +3823,6 @@ let onlineBallSpawnTimer = 0;
|
|
| 3821 |
let onlineSearchStartTime = 0;
|
| 3822 |
let onlineBotMatchTimeout = null;
|
| 3823 |
let onlineBotElo = parseInt(localStorage.getItem("onlineBotElo")) || 1500;
|
| 3824 |
-
// ONLINE BOT HARD LOCK: these are set when the bot match starts and the bot is
|
| 3825 |
-
// forced back to these exact coordinates every simulation tick.
|
| 3826 |
-
let onlineBotFixedX = null;
|
| 3827 |
-
let onlineBotFixedY = null;
|
| 3828 |
let onlineSimTick = 0;
|
| 3829 |
let onlineMatchStamp = 0;
|
| 3830 |
let onlineRound = 0, postUnsub = null, rematchSent = false, lastPostChat = 0;
|
|
@@ -3864,23 +3862,6 @@ const NET_SMOOTH = {
|
|
| 3864 |
|
| 3865 |
// Gemeinsame Paket-Verarbeitung fΓΌr Online-Gegner UND Online TV (identisch sauber)
|
| 3866 |
function feedNetPlayer(pl, st, stopMs) {
|
| 3867 |
-
// In an Online-vs-Bot match the bot is local, not a network-controlled player.
|
| 3868 |
-
// Ignore network position packets completely so smoothing/extrapolation cannot move it.
|
| 3869 |
-
if (mode === "online" && onlineVsBot && pl && pl.name !== myPlayName()) {
|
| 3870 |
-
if (onlineBotFixedX == null) onlineBotFixedX = pl.x;
|
| 3871 |
-
if (onlineBotFixedY == null) onlineBotFixedY = pl.y;
|
| 3872 |
-
pl.x = onlineBotFixedX;
|
| 3873 |
-
pl.y = onlineBotFixedY;
|
| 3874 |
-
pl.tx = onlineBotFixedX;
|
| 3875 |
-
pl.ty = onlineBotFixedY;
|
| 3876 |
-
pl.baseTx = onlineBotFixedX;
|
| 3877 |
-
pl.baseTy = onlineBotFixedY;
|
| 3878 |
-
pl.netVX = 0;
|
| 3879 |
-
pl.netVY = 0;
|
| 3880 |
-
pl.alive = st.alive !== false;
|
| 3881 |
-
pl.lastPacketAt = Date.now();
|
| 3882 |
-
return;
|
| 3883 |
-
}
|
| 3884 |
const now = Date.now();
|
| 3885 |
// Jedes Match-Update (Ball, anderer Spieler ...) liefert den Spielerstand erneut.
|
| 3886 |
// UnverΓ€nderte Daten sind KEIN neues Paket -> ignorieren (sonst Geschwindigkeit=0 / Zittern).
|
|
@@ -4828,22 +4809,6 @@ function makeLCG(seed) {
|
|
| 4828 |
// βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 4829 |
// ONLINE GAME
|
| 4830 |
// βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 4831 |
-
function lockOnlineBotPosition(bot) {
|
| 4832 |
-
if (!onlineVsBot || !bot) return;
|
| 4833 |
-
if (onlineBotFixedX == null) onlineBotFixedX = bot.x;
|
| 4834 |
-
if (onlineBotFixedY == null) onlineBotFixedY = bot.y;
|
| 4835 |
-
bot.x = onlineBotFixedX;
|
| 4836 |
-
bot.y = onlineBotFixedY;
|
| 4837 |
-
bot.tx = onlineBotFixedX;
|
| 4838 |
-
bot.ty = onlineBotFixedY;
|
| 4839 |
-
bot.baseTx = onlineBotFixedX;
|
| 4840 |
-
bot.baseTy = onlineBotFixedY;
|
| 4841 |
-
bot.netVX = 0;
|
| 4842 |
-
bot.netVY = 0;
|
| 4843 |
-
bot.vx = 0;
|
| 4844 |
-
bot.vy = 0;
|
| 4845 |
-
}
|
| 4846 |
-
|
| 4847 |
function startOnlineGame(matchData) {
|
| 4848 |
setOnlineAspect(matchData && matchData.arena);
|
| 4849 |
hideAllScreensForGame();
|
|
@@ -4891,10 +4856,6 @@ function startOnlineGame(matchData) {
|
|
| 4891 |
const mePlayer = createPlayer(meX, py, myColor, myName);
|
| 4892 |
const oppPlayer = createPlayer(oppX, py, oppColor, oppName);
|
| 4893 |
|
| 4894 |
-
// HARD LOCK the Online Bot to its spawn point.
|
| 4895 |
-
onlineBotFixedX = onlineVsBot ? oppX : null;
|
| 4896 |
-
onlineBotFixedY = onlineVsBot ? py : null;
|
| 4897 |
-
|
| 4898 |
oppPlayer.tx = oppX;
|
| 4899 |
oppPlayer.ty = py;
|
| 4900 |
// NEW: network-smoothing bookkeeping for the opponent's reconstructed
|
|
@@ -4916,7 +4877,6 @@ function startOnlineGame(matchData) {
|
|
| 4916 |
}
|
| 4917 |
|
| 4918 |
players.push(mePlayer, oppPlayer);
|
| 4919 |
-
lockOnlineBotPosition(oppPlayer);
|
| 4920 |
|
| 4921 |
document.getElementById("onlineNameMe").textContent = dn(myName);
|
| 4922 |
document.getElementById("onlineNameOpp").textContent = dn(oppName);
|
|
@@ -4979,14 +4939,12 @@ function startOnlineGame(matchData) {
|
|
| 4979 |
const oppRole = onlineRole === "p1" ? "p2" : "p1";
|
| 4980 |
|
| 4981 |
const oppState = data[oppRole + "State"];
|
| 4982 |
-
if (oppState
|
| 4983 |
const op = players.find(p => p.name !== myName);
|
| 4984 |
if (op) {
|
| 4985 |
feedNetPlayer(op, oppState);
|
| 4986 |
}
|
| 4987 |
}
|
| 4988 |
-
// Online-vs-Bot is 100% local for the bot. Firebase must NEVER be
|
| 4989 |
-
// allowed to provide a bot position/state that can move it.
|
| 4990 |
|
| 4991 |
if (!onlineIsHost && data.ballState) {
|
| 4992 |
try {
|
|
@@ -5186,11 +5144,7 @@ async function handleOnlineResult(winnerName, change) {
|
|
| 5186 |
document.getElementById("onlineHUD").style.display = "none";
|
| 5187 |
|
| 5188 |
const won = winnerName === onlineAccount.name;
|
| 5189 |
-
|
| 5190 |
-
// Every completed bot match awards exactly +30 ELO, regardless of who was
|
| 5191 |
-
// hit first. Real online matches keep the normal result calculation.
|
| 5192 |
-
const botReward = onlineVsBot ? 30 : clampEloChange(Math.round(change));
|
| 5193 |
-
const eloChange = onlineVsBot ? 30 : botReward;
|
| 5194 |
const oppName = onlineOpponent ? onlineOpponent.name : "Online Bot";
|
| 5195 |
const oppUid = onlineOpponent ? onlineOpponent.uid : ONLINE_BOT_UID;
|
| 5196 |
|
|
@@ -5235,19 +5189,7 @@ async function handleOnlineResult(winnerName, change) {
|
|
| 5235 |
|
| 5236 |
onlineAccount = sanitizeAccount(onlineAccount);
|
| 5237 |
|
| 5238 |
-
if (
|
| 5239 |
-
onlineAccount.elo += 30;
|
| 5240 |
-
onlineAccount.wins++;
|
| 5241 |
-
onlineAccount.currentStreak++;
|
| 5242 |
-
onlineAccount.totalEloGained += 30;
|
| 5243 |
-
if (onlineAccount.currentStreak > onlineAccount.bestStreak) {
|
| 5244 |
-
onlineAccount.bestStreak = onlineAccount.currentStreak;
|
| 5245 |
-
}
|
| 5246 |
-
if (onlineAccount.elo > onlineAccount.highestElo) {
|
| 5247 |
-
onlineAccount.highestElo = onlineAccount.elo;
|
| 5248 |
-
}
|
| 5249 |
-
// Deliberately NO loss/elo subtraction path for bot matches.
|
| 5250 |
-
} else if (won) {
|
| 5251 |
onlineAccount.elo += eloChange;
|
| 5252 |
onlineAccount.wins++;
|
| 5253 |
onlineAccount.currentStreak++;
|
|
@@ -5286,11 +5228,32 @@ async function handleOnlineResult(winnerName, change) {
|
|
| 5286 |
saveAccountLocal();
|
| 5287 |
updateAccountBtn();
|
| 5288 |
lastLeagueNote = "";
|
| 5289 |
-
if (fbReady) await pushAccountToFirebase(); // writes ONLY players/{myUid}
|
| 5290 |
-
if (won && fbReady) leagueRecordWin().catch(e => console.warn("league win failed", e));
|
| 5291 |
|
| 5292 |
-
|
| 5293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5294 |
|
| 5295 |
// Bot stats: written to players/{ONLINE_BOT_UID}. This is the ONE
|
| 5296 |
// place a client writes to a uid that isn't its own β acceptable
|
|
@@ -5344,7 +5307,8 @@ async function handleOnlineResult(winnerName, change) {
|
|
| 5344 |
localStorage.setItem("onlineBotElo", onlineBotElo);
|
| 5345 |
|
| 5346 |
if (fbReady) {
|
| 5347 |
-
|
|
|
|
| 5348 |
uid: ONLINE_BOT_UID,
|
| 5349 |
name: "Online Bot",
|
| 5350 |
elo: onlineBotElo,
|
|
@@ -5361,7 +5325,7 @@ async function handleOnlineResult(winnerName, change) {
|
|
| 5361 |
worstLossChange: onlineOpponent.worstLossChange ?? 0,
|
| 5362 |
worstLossOpp: onlineOpponent.worstLossOpp ?? "",
|
| 5363 |
updatedAt: Date.now()
|
| 5364 |
-
});
|
| 5365 |
}
|
| 5366 |
}
|
| 5367 |
|
|
@@ -6289,13 +6253,9 @@ function update() {
|
|
| 6289 |
|
| 6290 |
if (onlineVsBot) {
|
| 6291 |
const bot = players.find(p => p.name !== myPlayName());
|
| 6292 |
-
if (bot) {
|
| 6293 |
-
|
| 6294 |
-
// This runs every simulation tick, so AI, Firebase, smoothing,
|
| 6295 |
-
// velocity, or any other code cannot move the bot.
|
| 6296 |
-
lockOnlineBotPosition(bot);
|
| 6297 |
checkBotCollision(bot);
|
| 6298 |
-
lockOnlineBotPosition(bot);
|
| 6299 |
}
|
| 6300 |
} else {
|
| 6301 |
// CHANGED (network smoothing): replaced the fixed-rate nudge
|
|
@@ -6382,7 +6342,7 @@ function update() {
|
|
| 6382 |
for (const p of players) {
|
| 6383 |
if (!p.alive) continue;
|
| 6384 |
const humanNames = ["Player","Player 1","Player 2","YOU"];
|
| 6385 |
-
if (!humanNames.includes(p.name)
|
| 6386 |
}
|
| 6387 |
|
| 6388 |
for (const p of players) {
|
|
@@ -6437,7 +6397,7 @@ function checkOnlineCollision(me) {
|
|
| 6437 |
if (onlineVsBot) {
|
| 6438 |
const oppElo = onlineOpponent?.elo || 1500;
|
| 6439 |
const diff = onlineAccount.elo - oppElo;
|
| 6440 |
-
const change =
|
| 6441 |
|
| 6442 |
handleOnlineResult(opp ? opp.name : "", change);
|
| 6443 |
} else {
|
|
@@ -6460,7 +6420,7 @@ function checkBotCollision(bot) {
|
|
| 6460 |
const oppElo = onlineOpponent?.elo || 1500;
|
| 6461 |
|
| 6462 |
const diff = oppElo - onlineAccount.elo;
|
| 6463 |
-
const change =
|
| 6464 |
|
| 6465 |
handleOnlineResult(me.name, change);
|
| 6466 |
}
|
|
@@ -6565,7 +6525,7 @@ async function reportOnlineResult(winnerName, loserName) {
|
|
| 6565 |
const loserElo = (loserName === me.name) ? me.elo : opp.elo;
|
| 6566 |
|
| 6567 |
const diff = loserElo - winnerElo;
|
| 6568 |
-
const change =
|
| 6569 |
|
| 6570 |
// Bot-Match: es gibt keinen Match-Listener, der "done" verarbeitet -> Ergebnis direkt behandeln
|
| 6571 |
// (sonst laeuft das Spiel nach der Camping-Disqualifikation einfach weiter)
|
|
|
|
| 1 |
+
// DARKONBALLS FREEZE FIX β POST-GAME FIREBASE IS NON-BLOCKING β 2026-10-06
|
| 2 |
+
|
| 3 |
<!DOCTYPE html>
|
| 4 |
<html lang="en">
|
| 5 |
<head>
|
|
|
|
| 3823 |
let onlineSearchStartTime = 0;
|
| 3824 |
let onlineBotMatchTimeout = null;
|
| 3825 |
let onlineBotElo = parseInt(localStorage.getItem("onlineBotElo")) || 1500;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3826 |
let onlineSimTick = 0;
|
| 3827 |
let onlineMatchStamp = 0;
|
| 3828 |
let onlineRound = 0, postUnsub = null, rematchSent = false, lastPostChat = 0;
|
|
|
|
| 3862 |
|
| 3863 |
// Gemeinsame Paket-Verarbeitung fΓΌr Online-Gegner UND Online TV (identisch sauber)
|
| 3864 |
function feedNetPlayer(pl, st, stopMs) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3865 |
const now = Date.now();
|
| 3866 |
// Jedes Match-Update (Ball, anderer Spieler ...) liefert den Spielerstand erneut.
|
| 3867 |
// UnverΓ€nderte Daten sind KEIN neues Paket -> ignorieren (sonst Geschwindigkeit=0 / Zittern).
|
|
|
|
| 4809 |
// βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 4810 |
// ONLINE GAME
|
| 4811 |
// βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4812 |
function startOnlineGame(matchData) {
|
| 4813 |
setOnlineAspect(matchData && matchData.arena);
|
| 4814 |
hideAllScreensForGame();
|
|
|
|
| 4856 |
const mePlayer = createPlayer(meX, py, myColor, myName);
|
| 4857 |
const oppPlayer = createPlayer(oppX, py, oppColor, oppName);
|
| 4858 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4859 |
oppPlayer.tx = oppX;
|
| 4860 |
oppPlayer.ty = py;
|
| 4861 |
// NEW: network-smoothing bookkeeping for the opponent's reconstructed
|
|
|
|
| 4877 |
}
|
| 4878 |
|
| 4879 |
players.push(mePlayer, oppPlayer);
|
|
|
|
| 4880 |
|
| 4881 |
document.getElementById("onlineNameMe").textContent = dn(myName);
|
| 4882 |
document.getElementById("onlineNameOpp").textContent = dn(oppName);
|
|
|
|
| 4939 |
const oppRole = onlineRole === "p1" ? "p2" : "p1";
|
| 4940 |
|
| 4941 |
const oppState = data[oppRole + "State"];
|
| 4942 |
+
if (oppState) {
|
| 4943 |
const op = players.find(p => p.name !== myName);
|
| 4944 |
if (op) {
|
| 4945 |
feedNetPlayer(op, oppState);
|
| 4946 |
}
|
| 4947 |
}
|
|
|
|
|
|
|
| 4948 |
|
| 4949 |
if (!onlineIsHost && data.ballState) {
|
| 4950 |
try {
|
|
|
|
| 5144 |
document.getElementById("onlineHUD").style.display = "none";
|
| 5145 |
|
| 5146 |
const won = winnerName === onlineAccount.name;
|
| 5147 |
+
const eloChange = clampEloChange(Math.round(change));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5148 |
const oppName = onlineOpponent ? onlineOpponent.name : "Online Bot";
|
| 5149 |
const oppUid = onlineOpponent ? onlineOpponent.uid : ONLINE_BOT_UID;
|
| 5150 |
|
|
|
|
| 5189 |
|
| 5190 |
onlineAccount = sanitizeAccount(onlineAccount);
|
| 5191 |
|
| 5192 |
+
if (won) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5193 |
onlineAccount.elo += eloChange;
|
| 5194 |
onlineAccount.wins++;
|
| 5195 |
onlineAccount.currentStreak++;
|
|
|
|
| 5228 |
saveAccountLocal();
|
| 5229 |
updateAccountBtn();
|
| 5230 |
lastLeagueNote = "";
|
|
|
|
|
|
|
| 5231 |
|
| 5232 |
+
// IMPORTANT: never wait for Firebase before showing the result screen.
|
| 5233 |
+
// A slow/hung Firebase connection used to make the completed game look frozen.
|
| 5234 |
+
// Persist everything in the background instead.
|
| 5235 |
+
if (fbReady) {
|
| 5236 |
+
try {
|
| 5237 |
+
Promise.resolve(pushAccountToFirebase()).catch(e =>
|
| 5238 |
+
console.warn("post-match account save failed:", e)
|
| 5239 |
+
);
|
| 5240 |
+
} catch (e) {
|
| 5241 |
+
console.warn("post-match account save failed:", e);
|
| 5242 |
+
}
|
| 5243 |
+
}
|
| 5244 |
+
if (won && fbReady) {
|
| 5245 |
+
try {
|
| 5246 |
+
Promise.resolve(leagueRecordWin()).catch(e =>
|
| 5247 |
+
console.warn("league win failed:", e)
|
| 5248 |
+
);
|
| 5249 |
+
} catch (e) {
|
| 5250 |
+
console.warn("league win failed:", e);
|
| 5251 |
+
}
|
| 5252 |
+
}
|
| 5253 |
+
|
| 5254 |
+
// Show the result immediately. Do not make the UI wait on network operations.
|
| 5255 |
+
showEloAnimation(won ? eloChange : -eloChange);
|
| 5256 |
+
setTimeout(() => showWinnerOverlay(winnerName, won ? eloChange : -eloChange), 500);
|
| 5257 |
|
| 5258 |
// Bot stats: written to players/{ONLINE_BOT_UID}. This is the ONE
|
| 5259 |
// place a client writes to a uid that isn't its own β acceptable
|
|
|
|
| 5307 |
localStorage.setItem("onlineBotElo", onlineBotElo);
|
| 5308 |
|
| 5309 |
if (fbReady) {
|
| 5310 |
+
// Fire-and-forget: bot-stat Firebase writes must NEVER block the result screen.
|
| 5311 |
+
Promise.resolve().then(() => dbSetBotPlayer({
|
| 5312 |
uid: ONLINE_BOT_UID,
|
| 5313 |
name: "Online Bot",
|
| 5314 |
elo: onlineBotElo,
|
|
|
|
| 5325 |
worstLossChange: onlineOpponent.worstLossChange ?? 0,
|
| 5326 |
worstLossOpp: onlineOpponent.worstLossOpp ?? "",
|
| 5327 |
updatedAt: Date.now()
|
| 5328 |
+
})).catch(e => console.warn("bot stat save failed:", e));
|
| 5329 |
}
|
| 5330 |
}
|
| 5331 |
|
|
|
|
| 6253 |
|
| 6254 |
if (onlineVsBot) {
|
| 6255 |
const bot = players.find(p => p.name !== myPlayName());
|
| 6256 |
+
if (bot && bot.alive) {
|
| 6257 |
+
updateAI(bot, balls);
|
|
|
|
|
|
|
|
|
|
| 6258 |
checkBotCollision(bot);
|
|
|
|
| 6259 |
}
|
| 6260 |
} else {
|
| 6261 |
// CHANGED (network smoothing): replaced the fixed-rate nudge
|
|
|
|
| 6342 |
for (const p of players) {
|
| 6343 |
if (!p.alive) continue;
|
| 6344 |
const humanNames = ["Player","Player 1","Player 2","YOU"];
|
| 6345 |
+
if (!humanNames.includes(p.name)) updateAI(p, balls);
|
| 6346 |
}
|
| 6347 |
|
| 6348 |
for (const p of players) {
|
|
|
|
| 6397 |
if (onlineVsBot) {
|
| 6398 |
const oppElo = onlineOpponent?.elo || 1500;
|
| 6399 |
const diff = onlineAccount.elo - oppElo;
|
| 6400 |
+
const change = Math.max(1, Math.min(15, Math.round(5 + diff / 100)));
|
| 6401 |
|
| 6402 |
handleOnlineResult(opp ? opp.name : "", change);
|
| 6403 |
} else {
|
|
|
|
| 6420 |
const oppElo = onlineOpponent?.elo || 1500;
|
| 6421 |
|
| 6422 |
const diff = oppElo - onlineAccount.elo;
|
| 6423 |
+
const change = Math.max(1, Math.min(15, Math.round(5 + diff / 100)));
|
| 6424 |
|
| 6425 |
handleOnlineResult(me.name, change);
|
| 6426 |
}
|
|
|
|
| 6525 |
const loserElo = (loserName === me.name) ? me.elo : opp.elo;
|
| 6526 |
|
| 6527 |
const diff = loserElo - winnerElo;
|
| 6528 |
+
const change = clampEloChange(Math.max(1, Math.min(15, Math.round(5 + diff / 100))));
|
| 6529 |
|
| 6530 |
// Bot-Match: es gibt keinen Match-Listener, der "done" verarbeitet -> Ergebnis direkt behandeln
|
| 6531 |
// (sonst laeuft das Spiel nach der Camping-Disqualifikation einfach weiter)
|