This commit is contained in:
Renan LE CARO 2025-04-18 17:15:47 +02:00
parent 530e94f704
commit d43dd90a86
23 changed files with 268 additions and 152 deletions

View file

@ -1,5 +1,5 @@
// The version of the cache.
const VERSION = "29080170";
const VERSION = "29083143";
// The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`;

View file

@ -98,7 +98,11 @@ export async function openCreativeModePerksPicker() {
} else if ("bricks" in choice) {
setSettingValue("creativeModePerks", creativeModePerks);
if (await confirmRestart(gameState)) {
restart({ perks: creativeModePerks, level: choice });
restart({
perks: creativeModePerks,
level: choice,
isCreativeRun: true,
});
}
return;
} else if (choice) {

View file

@ -1 +1 @@
"29080170"
"29083143"

View file

@ -113,7 +113,7 @@ export async function play() {
export function pause(playerAskedForPause: boolean) {
if (!gameState.running) return;
if (gameState.pauseTimeout) return;
if (gameState.computer_controlled) return;
if (gameState.startParams.computer_controlled) return;
const stop = () => {
gameState.running = false;
@ -451,7 +451,8 @@ export function tick() {
if (gameState.running) {
gameState.levelTime += timeDeltaMs * frames;
gameState.runStatistics.runTime += timeDeltaMs * frames;
gameStateTick(gameState, frames);
const steps = isOptionOn("precise_physics") ? 4 : 1;
for (let i = 0; i < steps; i++) gameStateTick(gameState, frames / steps);
}
if (gameState.running || gameState.needsRender) {
@ -665,13 +666,14 @@ async function openSettingsMenu() {
},
});
for (const key of Object.keys(options) as OptionId[]) {
if (options[key])
if (options[key]) {
actions.push({
icon: isOptionOn(key)
? icons["icon:checkmark_checked"]
: icons["icon:checkmark_unchecked"],
? icons["icon:checkmark_checked"]
: icons["icon:checkmark_unchecked"],
text: options[key].name,
help: options[key].help,
disabled : (key=='extra_bright' && isOptionOn('basic')) || (key=='contrast' && isOptionOn('basic')) || false,
value: () => {
toggleOption(key);
fitSize(gameState);
@ -679,6 +681,7 @@ async function openSettingsMenu() {
openSettingsMenu();
},
});
}
}
actions.push({
icon: icons["icon:download"],
@ -1030,7 +1033,7 @@ document.addEventListener("keyup", async (e) => {
!alertsOpen &&
pageLoad < Date.now() - 500
) {
if (gameState.computer_controlled) {
if (gameState.startParams.computer_controlled) {
return startComputerControlledGame();
}
// When doing ctrl + R in dev to refresh, i don't want to instantly restart a run

View file

@ -15,6 +15,7 @@ import { asyncAlert } from "./asyncAlert";
import { rawUpgrades } from "./upgrades";
import { run } from "jest";
import { editRawLevelList } from "./levelEditor";
import { openCreativeModePerksPicker } from "./creative";
export function addToTotalPlayTime(ms: number) {
setSettingValue(
@ -32,8 +33,14 @@ export function gameOver(title: string, intro: string) {
stopRecording();
addToTotalPlayTime(gameState.runStatistics.runTime);
if (typeof gameState.isEditorTrialRun === "number") {
editRawLevelList(gameState.isEditorTrialRun);
if (typeof gameState.startParams.isEditorTrialRun === "number") {
editRawLevelList(gameState.startParams.isEditorTrialRun);
restart({});
return;
}
if (typeof gameState.startParams.isCreativeRun) {
openCreativeModePerksPicker();
restart({});
return;
}

View file

@ -54,7 +54,7 @@ import { addToTotalScore } from "./addToTotalScore";
import { hashCode } from "./getLevelBackground";
export function setMousePos(gameState: GameState, x: number) {
if (gameState.computer_controlled) return;
if (gameState.startParams.computer_controlled) return;
gameState.puckPosition = x;
// Sets the puck position, and updates the ball position if they are supposed to follow it
@ -638,7 +638,7 @@ export function schedulGameSound(
) {
if (!vol) return;
if (!isOptionOn("sound")) return;
if (gameState.computer_controlled) return;
if (gameState.startParams.computer_controlled) return;
x ??= gameState.offsetX + gameState.gameZoneWidth / 2;
const ex = gameState.aboutToPlaySound[sound] as { vol: number; x: number };
@ -991,7 +991,7 @@ export function gameStateTick(
frames = 1,
) {
// Ai movement of puck
if (gameState.computer_controlled) computerControl(gameState);
if (gameState.startParams.computer_controlled) computerControl(gameState);
gameState.runStatistics.max_combo = Math.max(
gameState.runStatistics.max_combo,
@ -1067,7 +1067,7 @@ export function gameStateTick(
// instant win condition
(gameState.levelTime && !remainingBricks && !liveCount(gameState.coins))
) {
if (gameState.computer_controlled) {
if (gameState.startParams.computer_controlled) {
startComputerControlledGame();
} else if (gameState.currentLevel + 1 < max_levels(gameState)) {
setLevel(gameState, gameState.currentLevel + 1);
@ -1737,7 +1737,7 @@ export function ballTick(gameState: GameState, ball: Ball, frames: number) {
ball.destroyed = true;
gameState.runStatistics.balls_lost++;
if (!gameState.balls.find((b) => !b.destroyed)) {
if (gameState.computer_controlled) {
if (gameState.startParams.computer_controlled) {
startComputerControlledGame();
} else {
gameOver(

View file

@ -319,7 +319,6 @@ function isExcluded(id: PerkId) {
}
export function getLevelUnlockCondition(levelIndex: number) {
// Returns "" if level is unlocked, otherwise a string explaining how to unlock it
let required: UpgradeLike[] = [],
forbidden: UpgradeLike[] = [],
minScore = Math.max(-1000 + 100 * levelIndex, 0);

View file

@ -190,6 +190,8 @@
"settings.mobile_help": "يترك مساحة تحت المجداف.",
"settings.pointer_lock": "قفل مؤشر الماوس",
"settings.pointer_lock_help": "يقوم بقفل وإخفاء مؤشر الماوس.",
"settings.precise_physics": "",
"settings.precise_physics_help": "",
"settings.record": "تسجيل مقاطع فيديو للعبة",
"settings.record_download": "تنزيل الفيديو ({{size}} ميجابايت)",
"settings.record_help": "احصل على فيديو لكل مستوى.",

View file

@ -6787,6 +6787,76 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>precise_physics</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>precise_physics_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>record</name>
<description/>

View file

@ -190,6 +190,8 @@
"settings.mobile_help": "Lässt Platz unter dem Paddel.",
"settings.pointer_lock": "Mauszeigersperre",
"settings.pointer_lock_help": "Sperrt und versteckt den Mauszeiger.",
"settings.precise_physics": "",
"settings.precise_physics_help": "",
"settings.record": "Spielvideos aufnehmen",
"settings.record_download": "Video herunterladen ({{size}} MB)",
"settings.record_help": "Holen Sie sich ein Video von jedem Level.",

View file

@ -190,6 +190,8 @@
"settings.mobile_help": "Leaves space under the paddle.",
"settings.pointer_lock": "Mouse pointer lock",
"settings.pointer_lock_help": "Locks and hides the mouse cursor.",
"settings.precise_physics": "More precise physics",
"settings.precise_physics_help": "Compute fast ball motion in smaller steps, might reduce performance",
"settings.record": "Record gameplay videos",
"settings.record_download": "Download video ({{size}} MB)",
"settings.record_help": "Get a video of each level.",

View file

@ -190,6 +190,8 @@
"settings.mobile_help": "Deja espacio debajo de la paleta.",
"settings.pointer_lock": "Bloqueo del puntero del ratón",
"settings.pointer_lock_help": "Bloquea y oculta el cursor del mouse.",
"settings.precise_physics": "",
"settings.precise_physics_help": "",
"settings.record": "Grabar vídeos de juego",
"settings.record_download": "Descargar vídeo ({{size}} MB)",
"settings.record_help": "Obtenga un vídeo de cada nivel.",

View file

@ -190,6 +190,8 @@
"settings.mobile_help": "Laisse un espace sous la raquette.",
"settings.pointer_lock": "Verrouillage du pointeur",
"settings.pointer_lock_help": "Cache aussi le curseur de la souris.",
"settings.precise_physics": "",
"settings.precise_physics_help": "",
"settings.record": "Enregistrer des vidéos de jeu",
"settings.record_download": "Télécharger la vidéo ({{size}} MB)",
"settings.record_help": "Obtenez une vidéo de chaque niveau.",

View file

@ -190,6 +190,8 @@
"settings.mobile_help": "Оставляет место под лопаткой.",
"settings.pointer_lock": "Блокировка указателя мыши",
"settings.pointer_lock_help": "Фиксирует и скрывает курсор мыши.",
"settings.precise_physics": "",
"settings.precise_physics_help": "",
"settings.record": "Запись видеороликов игрового процесса",
"settings.record_download": "Скачать видео ({{size}} МБ)",
"settings.record_help": "Получите видеозапись каждого уровня.",

View file

@ -190,6 +190,8 @@
"settings.mobile_help": "Kürek altında boşluk bırakır.",
"settings.pointer_lock": "Fare işaretçisi kilidi",
"settings.pointer_lock_help": "Fare imlecini kilitler ve gizler.",
"settings.precise_physics": "",
"settings.precise_physics_help": "",
"settings.record": "Oyun videolarını kaydedin",
"settings.record_download": "Videoyu indir ({{size}} MB)",
"settings.record_help": "Her seviyenin videosunu edinin.",

View file

@ -65,6 +65,7 @@ export function newGameState(params: RunParams): GameState {
const runLevels = getRunLevels(params, randomGift);
const gameState: GameState = {
startParams: params,
runLevels,
level: runLevels[0],
currentLevel: 0,
@ -141,9 +142,7 @@ export function newGameState(params: RunParams): GameState {
creative:
params?.computer_controlled ||
sumOfValues(params.perks) > 1 ||
(params.level && !params.level.name.startsWith("icon:")),
computer_controlled: params?.computer_controlled || false,
isEditorTrialRun: params?.isEditorTrialRun,
(params.level && !params.level.name.startsWith("icon:"))
};
resetBalls(gameState);

View file

@ -55,6 +55,11 @@ export const options = {
name: t("settings.kid"),
help: t("settings.kid_help"),
},
precise_physics: {
default: true,
name: t("settings.precise_physics"),
help: t("settings.precise_physics_help"),
},
// Could not get the sharing to work without loading androidx and all the modern android things so for now I'll just disable sharing in the android app
record: {
default: false,

View file

@ -75,7 +75,7 @@ export function render(gameState: GameState) {
: 1;
startWork("render:scoreDisplay");
scoreDisplay.innerHTML =
(isOptionOn("show_fps") || gameState.computer_controlled
(isOptionOn("show_fps") || gameState.startParams.computer_controlled
? `
<span>
${Math.floor((liveCount(gameState.coins) / getCurrentMaxCoins()) * 100)} %
@ -105,7 +105,7 @@ export function render(gameState: GameState) {
`<span class="score" data-tooltip="${t("play.score_tooltip")}">$${gameState.score}</span>`;
scoreDisplay.className =
(gameState.computer_controlled && "computer_controlled") ||
(gameState.startParams.computer_controlled && "computer_controlled") ||
(gameState.lastScoreIncrease > gameState.levelTime - 500 && "active") ||
"";
// Clear
@ -578,7 +578,7 @@ export function render(gameState: GameState) {
startWork("render:text_under_puck");
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
if (isOptionOn("mobile-mode") && gameState.computer_controlled) {
if (isOptionOn("mobile-mode") && gameState.startParams.computer_controlled) {
drawText(
ctx,
"breakout.lecaro.me?autoplay",
@ -1155,7 +1155,7 @@ export function getDashOffset(gameState: GameState) {
let wakeLock = null,
wakeLockPending = false;
function askForWakeLock(gameState: GameState) {
if (gameState.computer_controlled && !wakeLock && !wakeLockPending) {
if (gameState.startParams.computer_controlled && !wakeLock && !wakeLockPending) {
wakeLockPending = true;
try {
navigator.wakeLock.request("screen").then((lock) => {

4
src/types.d.ts vendored
View file

@ -281,8 +281,7 @@ export type GameState = {
};
rerolls: number;
creative: boolean;
computer_controlled: boolean;
isEditorTrialRun?: number;
startParams: RunParams;
};
export type RunParams = {
@ -292,6 +291,7 @@ export type RunParams = {
perks?: Partial<PerksMap>;
computer_controlled?: boolean;
isEditorTrialRun?: number;
isCreativeRun?: boolean;
};
export type OptionDef = {
default: boolean;