Moved more variables to gamestate

This commit is contained in:
Renan LE CARO 2025-03-14 12:23:19 +01:00
parent 4fb4c97734
commit b6fe46c9bc
5 changed files with 217 additions and 209 deletions

188
dist/index.html vendored
View file

@ -494,7 +494,6 @@ parcelHelpers.export(exports, "getPossibleUpgrades", ()=>getPossibleUpgrades);
parcelHelpers.export(exports, "getUpgraderUnlockPoints", ()=>getUpgraderUnlockPoints);
parcelHelpers.export(exports, "dontOfferTooSoon", ()=>dontOfferTooSoon);
parcelHelpers.export(exports, "pickRandomUpgrades", ()=>pickRandomUpgrades);
parcelHelpers.export(exports, "restart", ()=>restart);
parcelHelpers.export(exports, "setMousePos", ()=>setMousePos);
parcelHelpers.export(exports, "brickIndex", ()=>brickIndex);
parcelHelpers.export(exports, "hasBrick", ()=>hasBrick);
@ -505,7 +504,6 @@ parcelHelpers.export(exports, "bordersHitCheck", ()=>bordersHitCheck);
parcelHelpers.export(exports, "tick", ()=>tick);
parcelHelpers.export(exports, "isTelekinesisActive", ()=>isTelekinesisActive);
parcelHelpers.export(exports, "ballTick", ()=>ballTick);
parcelHelpers.export(exports, "resetRunStatistics", ()=>resetRunStatistics);
parcelHelpers.export(exports, "getTotalScore", ()=>getTotalScore);
parcelHelpers.export(exports, "addToTotalScore", ()=>addToTotalScore);
parcelHelpers.export(exports, "addToTotalPlayTime", ()=>addToTotalPlayTime);
@ -542,6 +540,7 @@ parcelHelpers.export(exports, "findLast", ()=>findLast);
parcelHelpers.export(exports, "toggleFullScreen", ()=>toggleFullScreen);
parcelHelpers.export(exports, "setKeyPressed", ()=>setKeyPressed);
parcelHelpers.export(exports, "gameState", ()=>gameState);
parcelHelpers.export(exports, "restart", ()=>restart);
var _loadGameData = require("./loadGameData");
var _options = require("./options");
var _sounds = require("./sounds");
@ -566,7 +565,7 @@ function baseCombo() {
function resetCombo(x, y) {
const prev = gameState.combo;
gameState.combo = baseCombo();
if (!levelTime) gameState.combo += gameState.perks.hot_start * 15;
if (!gameState.levelTime) gameState.combo += gameState.perks.hot_start * 15;
if (prev > gameState.combo && gameState.perks.soft_reset) gameState.combo += Math.floor((prev - gameState.combo) / (1 + gameState.perks.soft_reset));
const lost = Math.max(0, prev - gameState.combo);
if (lost) {
@ -574,7 +573,7 @@ function resetCombo(x, y) {
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
type: "text",
text: "-" + lost,
time: levelTime,
time: gameState.levelTime,
color: "red",
x: x,
y: y,
@ -593,7 +592,7 @@ function decreaseCombo(by, x, y) {
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
type: "text",
text: "-" + lost,
time: levelTime,
time: gameState.levelTime,
color: "red",
x: x,
y: y,
@ -622,9 +621,9 @@ function pause(playerAskedForPause) {
pauseRecording();
gameState.pauseTimeout = null;
document.body.className = gameState.running ? " running " : " paused ";
}, Math.min(Math.max(0, pauseUsesDuringRun - 5) * 50, 500));
}, Math.min(Math.max(0, gameState.pauseUsesDuringRun - 5) * 50, 500));
if (playerAskedForPause) // Pausing many times in a run will make pause slower
pauseUsesDuringRun++;
gameState.pauseUsesDuringRun++;
if (document.exitPointerLock) document.exitPointerLock();
}
const background = document.createElement("img");
@ -670,7 +669,7 @@ setInterval(()=>{
}, 1000);
function recomputeTargetBaseSpeed() {
// We never want the ball to completely stop, it will move at least 3px per frame
gameState.baseSpeed = Math.max(3, gameState.gameZoneWidth / 12 / 10 + gameState.currentLevel / 3 + levelTime / 30000 - gameState.perks.slow_down * 2);
gameState.baseSpeed = Math.max(3, gameState.gameZoneWidth / 12 / 10 + gameState.currentLevel / 3 + gameState.levelTime / 30000 - gameState.perks.slow_down * 2);
}
function brickCenterX(index) {
return gameState.offsetX + (index % gameState.gridSize + 0.5) * gameState.brickWidth;
@ -688,7 +687,7 @@ function spawnExplosion(count, x, y, color, duration = 150, size = gameState.coi
count = 1;
for(let i = 0; i < count; i++)gameState.flashes.push({
type: "particle",
time: levelTime,
time: gameState.levelTime,
size,
x: x + (Math.random() - 0.5) * gameState.brickWidth / 2,
y: y + (Math.random() - 0.5) * gameState.brickWidth / 2,
@ -702,15 +701,16 @@ function spawnExplosion(count, x, y, color, duration = 150, size = gameState.coi
function addToScore(coin) {
coin.destroyed = true;
gameState.score += coin.points;
gameState.lastScoreIncrease = gameState.levelTime;
addToTotalScore(coin.points);
if (gameState.score > gameState.highScore && !isCreativeModeRun) {
if (gameState.score > gameState.highScore && !gameState.isCreativeModeRun) {
gameState.highScore = gameState.score;
localStorage.setItem("breakout-3-hs", gameState.score.toString());
}
if (!isSettingOn("basic")) gameState.flashes.push({
type: "particle",
duration: 100 + Math.random() * 50,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: coin.color,
x: coin.previousX,
@ -723,7 +723,7 @@ function addToScore(coin) {
gameState.lastPlayedCoinGrab = Date.now();
(0, _sounds.sounds).coinCatch(coin.x);
}
runStatistics.score += coin.points;
gameState.runStatistics.score += coin.points;
}
function pickedUpgradesHTMl() {
let list = "";
@ -735,11 +735,11 @@ async function openUpgradesPicker() {
let repeats = 1;
let choices = 3;
let timeGain = "", catchGain = "", missesGain = "";
if (levelTime < 30000) {
if (gameState.levelTime < 30000) {
repeats++;
choices++;
timeGain = " (+1 upgrade and choice)";
} else if (levelTime < 60000) {
} else if (gameState.levelTime < 60000) {
choices++;
timeGain = " (+1 choice)";
}
@ -771,7 +771,7 @@ async function openUpgradesPicker() {
title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""),
actions,
text: `<p>
You caught ${gameState.score - gameState.levelStartScore} coins ${catchGain} out of ${gameState.levelSpawnedCoins} in ${Math.round(levelTime / 1000)} seconds${timeGain}.
You caught ${gameState.score - gameState.levelStartScore} coins ${catchGain} out of ${gameState.levelSpawnedCoins} in ${Math.round(gameState.levelTime / 1000)} seconds${timeGain}.
You missed ${gameState.levelMisses} times ${missesGain}.
${timeGain && catchGain && missesGain && "Impressive, keep it up !" || (timeGain || catchGain || missesGain) && "Well done !" || "Try to catch all coins, never miss the bricks or clear the level under 30s to gain additional choices and upgrades."}
</p>`,
@ -780,7 +780,7 @@ async function openUpgradesPicker() {
});
gameState.perks[upgradeId]++;
if (upgradeId === "instant_upgrade") repeats += 2;
runStatistics.upgrades_picked++;
gameState.runStatistics.upgrades_picked++;
}
resetCombo(undefined, undefined);
(0, _resetBalls.resetBalls)(gameState);
@ -790,13 +790,13 @@ function setLevel(l) {
pause(false);
if (l > 0) openUpgradesPicker();
gameState.currentLevel = l;
levelTime = 0;
level_skip_last_uses = 0;
lastTickDown = levelTime;
gameState.levelTime = 0;
gameState.autoCleanUses = 0;
gameState.lastTickDown = gameState.levelTime;
gameState.levelStartScore = gameState.score;
gameState.levelSpawnedCoins = 0;
gameState.levelMisses = 0;
runStatistics.levelsPlayed++;
gameState.runStatistics.levelsPlayed++;
resetCombo(undefined, undefined);
recomputeTargetBaseSpeed();
(0, _resetBalls.resetBalls)(gameState);
@ -836,17 +836,16 @@ function getUpgraderUnlockPoints() {
});
return list.filter((o)=>o.threshold).sort((a, b)=>a.threshold - b.threshold);
}
let lastOffered = {};
function dontOfferTooSoon(id) {
lastOffered[id] = Math.round(Date.now() / 1000);
function dontOfferTooSoon(gameState, id) {
gameState.lastOffered[id] = Math.round(Date.now() / 1000);
}
function pickRandomUpgrades(count) {
let list = getPossibleUpgrades(gameState).map((u)=>({
...u,
score: Math.random() + (lastOffered[u.id] || 0)
score: Math.random() + (gameState.lastOffered[u.id] || 0)
})).sort((a, b)=>a.score - b.score).filter((u)=>gameState.perks[u.id] < u.max).slice(0, count).sort((a, b)=>a.id > b.id ? 1 : -1);
list.forEach((u)=>{
dontOfferTooSoon(u.id);
dontOfferTooSoon(gameState, u.id);
});
return list.map((u)=>({
text: u.name + (gameState.perks[u.id] ? " lvl " + (gameState.perks[u.id] + 1) : ""),
@ -855,20 +854,13 @@ function pickRandomUpgrades(count) {
help: u.help(gameState.perks[u.id] + 1)
}));
}
function restart(params) {
Object.assign(gameState, newGameState(params));
resetRunStatistics();
pauseUsesDuringRun = 0;
pauseRecording();
setLevel(0);
}
function setMousePos(x) {
gameState.needsRender = true;
gameState.puckPosition = x;
// We have borders visible, enforce them
if (gameState.puckPosition < gameState.offsetXRoundedDown + gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.puckWidth / 2;
if (gameState.puckPosition > gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2;
if (!gameState.running && !levelTime) (0, _resetBalls.putBallsAtPuck)(gameState);
if (!gameState.running && !gameState.levelTime) (0, _resetBalls.putBallsAtPuck)(gameState);
}
gameCanvas.addEventListener("mouseup", (e)=>{
if (e.button !== 0) return;
@ -980,31 +972,31 @@ function tick() {
recomputeTargetBaseSpeed();
const currentTick = performance.now();
gameState.puckWidth = gameState.gameZoneWidth / 12 * (3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck);
if (keyboardPuckSpeed) setMousePos(gameState.puckPosition + keyboardPuckSpeed);
if (gameState.keyboardPuckSpeed) setMousePos(gameState.puckPosition + gameState.keyboardPuckSpeed);
if (gameState.running) {
levelTime += currentTick - lastTick;
runStatistics.runTime += currentTick - lastTick;
runStatistics.max_combo = Math.max(runStatistics.max_combo, gameState.combo);
gameState.levelTime += currentTick - gameState.lastTick;
gameState.runStatistics.runTime += currentTick - gameState.lastTick;
gameState.runStatistics.max_combo = Math.max(gameState.runStatistics.max_combo, gameState.combo);
// How many times to compute
let delta = Math.min(4, (currentTick - lastTick) / (1000 / 60));
let delta = Math.min(4, (currentTick - gameState.lastTick) / (1000 / 60));
delta *= gameState.running ? 1 : 0;
gameState.coins = gameState.coins.filter((coin)=>!coin.destroyed);
gameState.balls = gameState.balls.filter((ball)=>!ball.destroyed);
const remainingBricks = gameState.bricks.filter((b)=>b && b !== "black").length;
if (levelTime > lastTickDown + 1000 && gameState.perks.hot_start) {
lastTickDown = levelTime;
if (gameState.levelTime > gameState.lastTickDown + 1000 && gameState.perks.hot_start) {
gameState.lastTickDown = gameState.levelTime;
decreaseCombo(gameState.perks.hot_start, gameState.puckPosition, gameState.gameZoneHeight - 2 * gameState.puckHeight);
}
if (remainingBricks <= gameState.perks.skip_last && !level_skip_last_uses) {
if (remainingBricks <= gameState.perks.skip_last && !gameState.autoCleanUses) {
gameState.bricks.forEach((type, index)=>{
if (type) explodeBrick(index, gameState.balls[0], true);
});
level_skip_last_uses++;
gameState.autoCleanUses++;
}
if (!remainingBricks && !gameState.coins.length) {
if (gameState.currentLevel + 1 < max_levels()) setLevel(gameState.currentLevel + 1);
else gameOver("Run finished with " + gameState.score + " points", "You cleared all levels for this run.");
} else if (gameState.running || levelTime) {
} else if (gameState.running || gameState.levelTime) {
let playedCoinBounce = false;
const coinRadius = Math.round(gameState.coinSize / 2);
gameState.coins.forEach((coin)=>{
@ -1057,7 +1049,7 @@ function tick() {
type: "particle",
duration: 150,
ethereal: true,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
x: gameState.offsetXRoundedDown + Math.random() * gameState.gameZoneWidthRoundedUp,
@ -1082,7 +1074,7 @@ function tick() {
const baseParticle = !isSettingOn("basic") && (gameState.combo - baseCombo()) * Math.random() > 5 && gameState.running && {
type: "particle",
duration: 100 * (Math.random() + 1),
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: "red",
ethereal: true
@ -1137,7 +1129,7 @@ function tick() {
}
render();
requestAnimationFrame(tick);
lastTick = currentTick;
gameState.lastTick = currentTick;
}
function isTelekinesisActive(ball) {
return gameState.perks.telekinesis && !ball.hitSinceBounce && ball.vy < 0;
@ -1183,7 +1175,7 @@ function ballTick(ball, delta) {
type: "particle",
duration: 250,
ethereal: true,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color,
x: brickCenterX(index) + dx * gameState.brickWidth / 2,
@ -1224,7 +1216,7 @@ function ballTick(ball, delta) {
destroyed: false,
duration: 150,
size: gameState.coinSize / 2,
time: levelTime,
time: gameState.levelTime,
x: ball.x,
y: ball.y,
vx: Math.random() * gameState.baseSpeed * 3,
@ -1237,21 +1229,21 @@ function ballTick(ball, delta) {
});
ball.hitItem = [];
if (!ball.hitSinceBounce) {
runStatistics.misses++;
gameState.runStatistics.misses++;
gameState.levelMisses++;
resetCombo(ball.x, ball.y);
gameState.flashes.push({
type: "text",
text: "miss",
duration: 500,
time: levelTime,
time: gameState.levelTime,
size: gameState.puckHeight * 1.5,
color: "red",
x: gameState.puckPosition,
y: gameState.gameZoneHeight - gameState.puckHeight * 2
});
}
runStatistics.puck_bounces++;
gameState.runStatistics.puck_bounces++;
ball.hitSinceBounce = 0;
ball.sapperUses = 0;
ball.piercedSinceBounce = 0;
@ -1264,7 +1256,7 @@ function ballTick(ball, delta) {
}
if (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 && gameState.running) {
ball.destroyed = true;
runStatistics.balls_lost++;
gameState.runStatistics.balls_lost++;
if (!gameState.balls.find((b)=>!b.destroyed)) gameOver("Game Over", "You dropped the ball after catching " + gameState.score + " coins. ");
}
const radius = gameState.ballSize / 2;
@ -1313,7 +1305,7 @@ function ballTick(ball, delta) {
gameState.flashes.push({
type: "particle",
duration: 100 * ball.sparks,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: gameState.ballsColor,
x: ball.x,
@ -1340,9 +1332,6 @@ const defaultRunStats = ()=>({
max_combo: 1,
max_level: 0
});
function resetRunStatistics() {
runStatistics = defaultRunStats();
}
function getTotalScore() {
try {
return JSON.parse(localStorage.getItem("breakout_71_total_score") || "0");
@ -1351,7 +1340,7 @@ function getTotalScore() {
}
}
function addToTotalScore(points) {
if (isCreativeModeRun) return;
if (gameState.isCreativeModeRun) return;
try {
localStorage.setItem("breakout_71_total_score", JSON.stringify(getTotalScore() + points));
} catch (e) {}
@ -1365,8 +1354,8 @@ function gameOver(title, intro) {
if (!gameState.running) return;
pause(true);
stopRecording();
addToTotalPlayTime(runStatistics.runTime);
runStatistics.max_level = gameState.currentLevel + 1;
addToTotalPlayTime(gameState.runStatistics.runTime);
gameState.runStatistics.max_level = gameState.currentLevel + 1;
let animationDelay = -300;
const getDelay = ()=>{
animationDelay += 800;
@ -1413,7 +1402,7 @@ function gameOver(title, intro) {
allowClose: true,
title,
text: `
${isCreativeModeRun ? "<p>This test run and its score are not being recorded</p>" : ""}
${gameState.isCreativeModeRun ? "<p>This test run and its score are not being recorded</p>" : ""}
<p>${intro}</p>
${unlocksInfo}
`,
@ -1439,12 +1428,12 @@ function getHistograms() {
runsHistory.sort((a, b)=>a.score - b.score).reverse();
runsHistory = runsHistory.slice(0, 100);
runsHistory.push({
...runStatistics,
...gameState.runStatistics,
perks: gameState.perks,
appVersion: (0, _loadGameData.appVersion)
});
// Generate some histogram
if (!isCreativeModeRun) localStorage.setItem("breakout_71_runs_history", JSON.stringify(runsHistory, null, 2));
if (!gameState.isCreativeModeRun) localStorage.setItem("breakout_71_runs_history", JSON.stringify(runsHistory, null, 2));
const makeHistogram = (title, getter, unit)=>{
let values = runsHistory.map((h)=>getter(h) || 0);
let min = Math.min(...values);
@ -1534,7 +1523,7 @@ function explodeBrick(index, ball, isExplosion) {
gameState.flashes.push({
type: "ball",
duration: 150,
time: levelTime,
time: gameState.levelTime,
size: gameState.brickWidth * 2,
color: "white",
x,
@ -1542,7 +1531,7 @@ function explodeBrick(index, ball, isExplosion) {
});
spawnExplosion(7 * (1 + gameState.perks.bigger_explosions), x, y, "white", 150, gameState.coinSize);
ball.hitSinceBounce++;
runStatistics.bricks_broken++;
gameState.runStatistics.bricks_broken++;
} else if (color) {
// Even if it bounces we don't want to count that as a miss
ball.hitSinceBounce++;
@ -1554,8 +1543,8 @@ function explodeBrick(index, ball, isExplosion) {
if (gameState.perks.sturdy_bricks) // +10% per level
coinsToSpawn += Math.ceil((10 + gameState.perks.sturdy_bricks) / 10 * coinsToSpawn);
gameState.levelSpawnedCoins += coinsToSpawn;
runStatistics.coins_spawned += coinsToSpawn;
runStatistics.bricks_broken++;
gameState.runStatistics.coins_spawned += coinsToSpawn;
gameState.runStatistics.bricks_broken++;
const maxCoins = gameState.MAX_COINS * (isSettingOn("basic") ? 0.5 : 1);
const spawnableCoins = gameState.coins.length > gameState.MAX_COINS ? 1 : Math.floor(maxCoins - gameState.coins.length) / 3;
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
@ -1597,7 +1586,7 @@ function explodeBrick(index, ball, isExplosion) {
gameState.flashes.push({
type: "ball",
duration: 40,
time: levelTime,
time: gameState.levelTime,
size: gameState.brickWidth,
color: color,
x,
@ -1621,6 +1610,13 @@ function render() {
const { width, height } = gameCanvas;
if (!width || !height) return;
scoreDisplay.innerText = `L${gameState.currentLevel + 1}/${max_levels()} $${gameState.score}`;
Object.assign(scoreDisplay.style, gameState.lastScoreIncrease < gameState.levelTime - 500 ? {
color: 'gold',
fontWeight: 'bold',
opacity: 1
} : {
opacity: 0.5
});
// Clear
if (!isSettingOn("basic") && !level.color && level.svg) {
// Without this the light trails everything
@ -1645,7 +1641,7 @@ function render() {
ctx.globalAlpha = 1;
gameState.flashes.forEach((flash)=>{
const { x, y, time, color, size, type, duration } = flash;
const elapsed = levelTime - time;
const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
if (type === "ball") drawFuzzyBall(ctx, color, size, x, y);
if (type === "particle") drawFuzzyBall(ctx, color, size * 3, x, y);
@ -1685,7 +1681,7 @@ function render() {
ctx.fillRect(0, 0, width, height);
gameState.flashes.forEach((flash)=>{
const { x, y, time, color, size, type, duration } = flash;
const elapsed = levelTime - time;
const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
if (type === "particle") drawBall(ctx, color, size, x, y);
});
@ -1717,10 +1713,10 @@ function render() {
ctx.globalCompositeOperation = "source-over";
renderAllBricks();
ctx.globalCompositeOperation = "screen";
gameState.flashes = gameState.flashes.filter((f)=>levelTime - f.time < f.duration && !f.destroyed);
gameState.flashes = gameState.flashes.filter((f)=>gameState.levelTime - f.time < f.duration && !f.destroyed);
gameState.flashes.forEach((flash)=>{
const { x, y, time, color, size, type, duration } = flash;
const elapsed = levelTime - time;
const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - elapsed / duration * 2));
if (type === "text") {
ctx.globalCompositeOperation = "source-over";
@ -1980,9 +1976,6 @@ function drawText(ctx, text, color, fontSize, x, y, left = false) {
}
ctx.drawImage(cachedGraphics[key], left ? x : Math.round(x - cachedGraphics[key].width / 2), Math.round(y - cachedGraphics[key].height / 2));
}
let levelTime = 0;
// Limits skip last to one use per level
let level_skip_last_uses = 0;
window.addEventListener("visibilitychange", ()=>{
if (document.hidden) pause(true);
});
@ -2089,7 +2082,7 @@ async function openScorePanel() {
const cb = await asyncAlert({
title: ` ${gameState.score} points at level ${gameState.currentLevel + 1} / ${max_levels()}`,
text: `
${isCreativeModeRun ? "<p>This is a test run, score is not recorded permanently</p>" : ""}
${gameState.isCreativeModeRun ? "<p>This is a test run, score is not recorded permanently</p>" : ""}
<p>Upgrades picked so far : </p>
<p>${pickedUpgradesHTMl()}</p>
`,
@ -2302,7 +2295,7 @@ function distanceBetween(a, b) {
return Math.sqrt(distance2(a, b));
}
function rainbowColor() {
return `hsl(${Math.round(levelTime / 4) * 2 % 360},100%,70%)`;
return `hsl(${Math.round(gameState.levelTime / 4) * 2 % 360},100%,70%)`;
}
function repulse(a, b, power, impactsBToo) {
const distance = distanceBetween(a, b);
@ -2312,7 +2305,7 @@ function repulse(a, b, power, impactsBToo) {
// Unit vector
const dx = (a.x - b.x) / distance;
const dy = (a.y - b.y) / distance;
const fact = -power * (max - distance) / (max * 1.2) / 3 * Math.min(500, levelTime) / 500;
const fact = -power * (max - distance) / (max * 1.2) / 3 * Math.min(500, gameState.levelTime) / 500;
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") {
b.vx += dx * fact;
b.vy += dy * fact;
@ -2324,7 +2317,7 @@ function repulse(a, b, power, impactsBToo) {
gameState.flashes.push({
type: "particle",
duration: 100,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
@ -2336,7 +2329,7 @@ function repulse(a, b, power, impactsBToo) {
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") gameState.flashes.push({
type: "particle",
duration: 100,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
@ -2354,7 +2347,7 @@ function attract(a, b, power) {
// Unit vector
const dx = (a.x - b.x) / distance;
const dy = (a.y - b.y) / distance;
const fact = power * (distance - min) / min * Math.min(500, levelTime) / 500;
const fact = power * (distance - min) / min * Math.min(500, gameState.levelTime) / 500;
b.vx += dx * fact;
b.vy += dy * fact;
a.vx -= dx * fact;
@ -2364,7 +2357,7 @@ function attract(a, b, power) {
gameState.flashes.push({
type: "particle",
duration: 100,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
@ -2376,7 +2369,7 @@ function attract(a, b, power) {
gameState.flashes.push({
type: "particle",
duration: 100,
time: levelTime,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
@ -2504,7 +2497,7 @@ const pressed = {
};
function setKeyPressed(key, on) {
pressed[key] = on;
keyboardPuckSpeed = (pressed.ArrowRight - pressed.ArrowLeft) * (1 + pressed.Shift * 2) * gameState.gameZoneWidth / 50;
gameState.keyboardPuckSpeed = (pressed.ArrowRight - pressed.ArrowLeft) * (1 + pressed.Shift * 2) * gameState.gameZoneWidth / 50;
}
document.addEventListener("keydown", (e)=>{
if (e.key.toLowerCase() === "f" && !e.ctrlKey && !e.metaKey) toggleFullScreen();
@ -2527,12 +2520,6 @@ document.addEventListener("keyup", (e)=>{
else return;
e.preventDefault();
});
let isCreativeModeRun = false;
let pauseUsesDuringRun = 0;
let keyboardPuckSpeed = 0;
let lastTick = performance.now();
let lastTickDown = 0;
let runStatistics = defaultRunStats();
function newGameState(params) {
const totalScoreAtRunStart = getTotalScore();
const firstLevel = params?.level ? (0, _loadGameData.allLevels).filter((l)=>l.name === params?.level) : [];
@ -2542,7 +2529,6 @@ function newGameState(params) {
...makeEmptyPerksMap(),
...params?.perks || {}
};
isCreativeModeRun = (0, _gameUtils.sumOfKeys)(perks) > 1;
const gameState = {
runLevels,
currentLevel: 0,
@ -2564,6 +2550,7 @@ function newGameState(params) {
brickWidth: 0,
needsRender: true,
score: 0,
lastScoreIncrease: -1000,
lastExplosion: -1000,
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
balls: [],
@ -2581,18 +2568,33 @@ function newGameState(params) {
ballSize: 20,
coinSize: 14,
puckHeight: 20,
totalScoreAtRunStart
totalScoreAtRunStart,
isCreativeModeRun: (0, _gameUtils.sumOfKeys)(perks) > 1,
pauseUsesDuringRun: 0,
keyboardPuckSpeed: 0,
lastTick: performance.now(),
lastTickDown: 0,
runStatistics: defaultRunStats(),
lastOffered: {},
levelTime: 0,
autoCleanUses: 0
};
(0, _resetBalls.resetBalls)(gameState);
if (!(0, _gameUtils.sumOfKeys)(gameState.perks)) {
const giftable = getPossibleUpgrades(gameState).filter((u)=>u.giftable);
const randomGift = isSettingOn("easy") && "slow_down" || giftable[Math.floor(Math.random() * giftable.length)].id;
perks[randomGift] = 1;
dontOfferTooSoon(randomGift);
dontOfferTooSoon(gameState, randomGift);
}
for (let perk of (0, _loadGameData.upgrades))if (gameState.perks[perk.id]) dontOfferTooSoon(gameState, perk.id);
return gameState;
}
const gameState = newGameState({});
function restart(params) {
Object.assign(gameState, newGameState(params));
pauseRecording();
setLevel(0);
}
restart({});
fitSize();
tick();
@ -3143,7 +3145,7 @@ const options = {
name: `Mouse pointer lock`,
help: `Locks and hides the mouse cursor.`,
afterChange: ()=>{},
disabled: ()=>!(0, _game.gameCanvas).requestPointerLock
disabled: ()=>!document.body.requestPointerLock
},
easy: {
default: false,