mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 12:15:06 -04:00
wip
This commit is contained in:
parent
64a85200b9
commit
47ad04c49b
26 changed files with 313 additions and 247 deletions
19
Readme.md
19
Readme.md
|
@ -24,13 +24,23 @@ languages, I may add features again.
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
## To do
|
## To do
|
||||||
- redo video
|
|
||||||
- auto-detect device performance at first startup and adjust settings accordingly
|
- redo video presentation
|
||||||
- demo mode that shows device name (for phone shops to catch attention)
|
- chill game mode, to just relax your mind :
|
||||||
|
- no 7 levels limit
|
||||||
|
- no upgrades offered at the end of the level
|
||||||
|
- get a random perk
|
||||||
|
- every 7 level it's replaced by another random perk
|
||||||
|
- every 7 levels, +10 base combo and +1 piece
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
- level editor
|
- added a few levels
|
||||||
|
- autoplay mode (with wake lock and computer play)
|
||||||
|
- slower coins fall once they are past the paddle
|
||||||
|
- in game level editor
|
||||||
- allow loading newer save in outdated app (for rollback)
|
- allow loading newer save in outdated app (for rollback)
|
||||||
- game crashes when reaching level 12 (no level info in runLevels)
|
- game crashes when reaching level 12 (no level info in runLevels)
|
||||||
|
|
||||||
|
@ -382,6 +392,7 @@ languages, I may add features again.
|
||||||
|
|
||||||
## Maybe one day
|
## Maybe one day
|
||||||
- https://weblate.org/fr/ quite annoying to have merge conflicts while pushing, i'll enable it later.
|
- https://weblate.org/fr/ quite annoying to have merge conflicts while pushing, i'll enable it later.
|
||||||
|
- auto-detect device performance at first startup and adjust settings accordingly (hard to do in any sort of useful way)
|
||||||
- [jaceys] Move the restart button out of the menu, so that it is more easily accessible (will allow user to choose starting perk instead)
|
- [jaceys] Move the restart button out of the menu, so that it is more easily accessible (will allow user to choose starting perk instead)
|
||||||
- colored coins only (coins should be of the color of the ball to count, otherwise what ? i'd rather avoid negative points)
|
- colored coins only (coins should be of the color of the ball to count, otherwise what ? i'd rather avoid negative points)
|
||||||
- coins avoid ball of different color (pointless)
|
- coins avoid ball of different color (pointless)
|
||||||
|
|
|
@ -29,8 +29,8 @@ android {
|
||||||
applicationId = "me.lecaro.breakout"
|
applicationId = "me.lecaro.breakout"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 29077162
|
versionCode = 29077593
|
||||||
versionName = "29077162"
|
versionName = "29077593"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
|
|
File diff suppressed because one or more lines are too long
218
dist/index.html
vendored
218
dist/index.html
vendored
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
||||||
// The version of the cache.
|
// The version of the cache.
|
||||||
const VERSION = "29077162";
|
const VERSION = "29077593";
|
||||||
|
|
||||||
// The name of the cache
|
// The name of the cache
|
||||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||||
|
|
|
@ -933,12 +933,6 @@
|
||||||
"bricks": "__bbbb___bbggbb_bbggggbbbggggggbbggggggbbbggggbb_bbggbb___bbbb__",
|
"bricks": "__bbbb___bbggbb_bbggggbbbggggggbbggggggbbbggggbb_bbggbb___bbbb__",
|
||||||
"color": ""
|
"color": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "icon:particles",
|
|
||||||
"size": 8,
|
|
||||||
"bricks": "_b_b_b__________b_bbb_b___bbb___b_bbb__b_____b___b_b__b________b",
|
|
||||||
"color": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "icon:reset",
|
"name": "icon:reset",
|
||||||
"size": 8,
|
"size": 8,
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
"29077162"
|
"29077593"
|
||||||
|
|
|
@ -71,8 +71,8 @@ canvas:not(#game) {
|
||||||
transition: color 0.01s;
|
transition: color 0.01s;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hidden {
|
&.computer_controlled {
|
||||||
display: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
@ -521,6 +521,7 @@ h2.histogram-title strong {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -534,22 +535,18 @@ h2.histogram-title strong {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
animation: toast forwards;
|
transition: opacity 200ms, transform 200ms;
|
||||||
}
|
&.hidden{
|
||||||
|
|
||||||
@keyframes toast {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translate(-20px, -20px) scale(0.5);
|
transform: translate(-20px, -20px) scale(0.5);
|
||||||
}
|
}
|
||||||
10%,
|
&.visible{
|
||||||
90% {
|
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.gridEdit > div > span,
|
.gridEdit > div > span,
|
||||||
.palette > span {
|
.palette > span {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
27
src/game.ts
27
src/game.ts
|
@ -44,6 +44,7 @@ import {
|
||||||
import {
|
import {
|
||||||
forEachLiveOne,
|
forEachLiveOne,
|
||||||
gameStateTick,
|
gameStateTick,
|
||||||
|
liveCount,
|
||||||
normalizeGameState,
|
normalizeGameState,
|
||||||
pickRandomUpgrades,
|
pickRandomUpgrades,
|
||||||
setLevel,
|
setLevel,
|
||||||
|
@ -96,6 +97,7 @@ import { runHistoryViewerMenuEntry } from "./runHistoryViewer";
|
||||||
import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
|
import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
|
||||||
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
|
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
|
||||||
import { levelEditorMenuEntry } from "./levelEditor";
|
import { levelEditorMenuEntry } from "./levelEditor";
|
||||||
|
import {toast} from "./toast";
|
||||||
|
|
||||||
export async function play() {
|
export async function play() {
|
||||||
if (await applyFullScreenChoice()) return;
|
if (await applyFullScreenChoice()) return;
|
||||||
|
@ -470,6 +472,7 @@ export let lastMeasuredFPS = 60;
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
lastMeasuredFPS = FPSCounter;
|
lastMeasuredFPS = FPSCounter;
|
||||||
FPSCounter = 0;
|
FPSCounter = 0;
|
||||||
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
@ -768,15 +771,6 @@ async function openSettingsMenu() {
|
||||||
await openSettingsMenu();
|
await openSettingsMenu();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
actions.push({
|
|
||||||
icon: icons["icon:particles"],
|
|
||||||
text: t("settings.max_particles", { max: getCurrentMaxParticles() }),
|
|
||||||
help: t("settings.max_particles_help"),
|
|
||||||
async value() {
|
|
||||||
cycleMaxParticles();
|
|
||||||
await openSettingsMenu();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
actions.push({
|
actions.push({
|
||||||
icon: icons["icon:reset"],
|
icon: icons["icon:reset"],
|
||||||
|
@ -1022,22 +1016,29 @@ export function restart(params: RunParams) {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.location.search.includes("autoplay")) {
|
if (window.location.search.includes("autoplay")) {
|
||||||
startComputerControlledGame();
|
startComputerControlledGame();
|
||||||
|
} else if (window.location.search.includes("stress")) {
|
||||||
|
if(!isOptionOn('show_fps'))
|
||||||
|
toggleOption('show_fps')
|
||||||
|
restart({
|
||||||
|
level:allLevels.find(l=>l.name=='Worms'),
|
||||||
|
perks:{base_combo:5000, pierce:20, rainbow:3, sapper:2, etherealcoins:1}
|
||||||
|
});
|
||||||
}else {
|
}else {
|
||||||
restart({});
|
restart({});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startComputerControlledGame() {
|
export function startComputerControlledGame() {
|
||||||
const perks: Partial<PerksMap> = { base_combo: 7, pierce: 3 };
|
const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 };
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const u = sample(upgrades);
|
const u = sample(upgrades);
|
||||||
perks[u.id] = Math.floor(Math.random() * u.max) + 1;
|
|
||||||
|
perks[u.id] ||= Math.floor(Math.random() * u.max) + 1;
|
||||||
}
|
}
|
||||||
perks.superhot = 0;
|
perks.superhot = 0;
|
||||||
restart({
|
restart({
|
||||||
level: sample(allLevels)?.name,
|
level: sample(allLevels.filter((l) => l.color === "#000000")),
|
||||||
computer_controlled: true,
|
computer_controlled: true,
|
||||||
perks,
|
perks,
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
pickedUpgradesHTMl,
|
pickedUpgradesHTMl,
|
||||||
reasonLevelIsLocked,
|
reasonLevelIsLocked,
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import { getSettingValue, getTotalScore } from "./settings";
|
import {getSettingValue, getTotalScore, setSettingValue} from "./settings";
|
||||||
import { stopRecording } from "./recording";
|
import { stopRecording } from "./recording";
|
||||||
import { asyncAlert } from "./asyncAlert";
|
import { asyncAlert } from "./asyncAlert";
|
||||||
import { rawUpgrades } from "./upgrades";
|
import { rawUpgrades } from "./upgrades";
|
||||||
|
@ -18,13 +18,8 @@ import { editRawLevelList } from "./levelEditor";
|
||||||
|
|
||||||
export function addToTotalPlayTime(ms: number) {
|
export function addToTotalPlayTime(ms: number) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(
|
setSettingValue('breakout_71_total_play_time', getSettingValue('breakout_71_total_play_time',0)+ms)
|
||||||
"breakout_71_total_play_time",
|
|
||||||
JSON.stringify(
|
|
||||||
JSON.parse(localStorage.getItem("breakout_71_total_play_time") || "0") +
|
|
||||||
ms,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1160,8 +1160,12 @@ export function gameStateTick(
|
||||||
|
|
||||||
const ratio =
|
const ratio =
|
||||||
1 -
|
1 -
|
||||||
((gameState.perks.viscosity * 0.03 + 0.002) * frames) /
|
((gameState.perks.viscosity * 0.03 +
|
||||||
|
0.002 +
|
||||||
|
(coin.y > gameState.gameZoneHeight ? 0.2 : 0)) *
|
||||||
|
frames) /
|
||||||
(1 + gameState.perks.etherealcoins);
|
(1 + gameState.perks.etherealcoins);
|
||||||
|
|
||||||
if (!gameState.perks.etherealcoins) {
|
if (!gameState.perks.etherealcoins) {
|
||||||
coin.vy *= ratio;
|
coin.vy *= ratio;
|
||||||
coin.vx *= ratio;
|
coin.vx *= ratio;
|
||||||
|
@ -1214,6 +1218,9 @@ export function gameStateTick(
|
||||||
|
|
||||||
const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10;
|
const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10;
|
||||||
const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames);
|
const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames);
|
||||||
|
if(coin.previousY<gameState.gameZoneHeight && coin.y>gameState.gameZoneHeight && coin.vy>0 && speed > 20) {
|
||||||
|
schedulGameSound(gameState, "plouf", coin.x, clamp(speed, 20,100)/100*0.2);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight &&
|
coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight &&
|
||||||
|
|
|
@ -240,6 +240,7 @@ export function defaultSounds() {
|
||||||
explode: { vol: 0, x: 0 },
|
explode: { vol: 0, x: 0 },
|
||||||
lifeLost: { vol: 0, x: 0 },
|
lifeLost: { vol: 0, x: 0 },
|
||||||
coinCatch: { vol: 0, x: 0 },
|
coinCatch: { vol: 0, x: 0 },
|
||||||
|
plouf: { vol: 0, x: 0 },
|
||||||
colorChange: { vol: 0, x: 0 },
|
colorChange: { vol: 0, x: 0 },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -186,8 +186,6 @@
|
||||||
"settings.load_save_file_help": "حدد ملف الحفظ على جهازك",
|
"settings.load_save_file_help": "حدد ملف الحفظ على جهازك",
|
||||||
"settings.max_coins": " {{max}} عملات معدنية على الشاشة كحد أقصى",
|
"settings.max_coins": " {{max}} عملات معدنية على الشاشة كحد أقصى",
|
||||||
"settings.max_coins_help": "تجميلي فقط، لا يؤثر على النتيجة",
|
"settings.max_coins_help": "تجميلي فقط، لا يؤثر على النتيجة",
|
||||||
"settings.max_particles": " {{max}} جسيمات كحد أقصى",
|
|
||||||
"settings.max_particles_help": "يحدد عدد الجسيمات التي تظهر على الشاشة للتأثير البصري.",
|
|
||||||
"settings.mobile": "الوضع المحمول",
|
"settings.mobile": "الوضع المحمول",
|
||||||
"settings.mobile_help": "يترك مساحة تحت المجداف.",
|
"settings.mobile_help": "يترك مساحة تحت المجداف.",
|
||||||
"settings.pointer_lock": "قفل مؤشر الماوس",
|
"settings.pointer_lock": "قفل مؤشر الماوس",
|
||||||
|
|
|
@ -6647,76 +6647,6 @@
|
||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>max_particles</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>true</approved>
|
|
||||||
</translation>
|
|
||||||
<translation>
|
|
||||||
<language>es-CL</language>
|
|
||||||
<approved>false</approved>
|
|
||||||
</translation>
|
|
||||||
<translation>
|
|
||||||
<language>fr-FR</language>
|
|
||||||
<approved>true</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>max_particles_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>true</approved>
|
|
||||||
</translation>
|
|
||||||
<translation>
|
|
||||||
<language>es-CL</language>
|
|
||||||
<approved>false</approved>
|
|
||||||
</translation>
|
|
||||||
<translation>
|
|
||||||
<language>fr-FR</language>
|
|
||||||
<approved>true</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>
|
<concept_node>
|
||||||
<name>mobile</name>
|
<name>mobile</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
|
@ -186,8 +186,6 @@
|
||||||
"settings.load_save_file_help": "Wählen Sie eine Speicherdatei auf Ihrem Gerät",
|
"settings.load_save_file_help": "Wählen Sie eine Speicherdatei auf Ihrem Gerät",
|
||||||
"settings.max_coins": " {{max}} Münzen auf dem Bildschirm maximal",
|
"settings.max_coins": " {{max}} Münzen auf dem Bildschirm maximal",
|
||||||
"settings.max_coins_help": "Nur kosmetisch, keine Auswirkung auf das Ergebnis",
|
"settings.max_coins_help": "Nur kosmetisch, keine Auswirkung auf das Ergebnis",
|
||||||
"settings.max_particles": " {{max}} Teilchen maximal",
|
|
||||||
"settings.max_particles_help": "Begrenzt die Anzahl der auf dem Bildschirm angezeigten Partikel für visuelle Effekte.",
|
|
||||||
"settings.mobile": "Mobiler Modus",
|
"settings.mobile": "Mobiler Modus",
|
||||||
"settings.mobile_help": "Lässt Platz unter dem Paddel.",
|
"settings.mobile_help": "Lässt Platz unter dem Paddel.",
|
||||||
"settings.pointer_lock": "Mauszeigersperre",
|
"settings.pointer_lock": "Mauszeigersperre",
|
||||||
|
|
|
@ -186,8 +186,6 @@
|
||||||
"settings.load_save_file_help": "Select a save file on your device",
|
"settings.load_save_file_help": "Select a save file on your device",
|
||||||
"settings.max_coins": " {{max}} coins on screen maximum",
|
"settings.max_coins": " {{max}} coins on screen maximum",
|
||||||
"settings.max_coins_help": "Cosmetic only, no effect on score",
|
"settings.max_coins_help": "Cosmetic only, no effect on score",
|
||||||
"settings.max_particles": " {{max}} particles maximum",
|
|
||||||
"settings.max_particles_help": "Limits the number of particles show on screen for visual effect. ",
|
|
||||||
"settings.mobile": "Mobile mode",
|
"settings.mobile": "Mobile mode",
|
||||||
"settings.mobile_help": "Leaves space under the paddle.",
|
"settings.mobile_help": "Leaves space under the paddle.",
|
||||||
"settings.pointer_lock": "Mouse pointer lock",
|
"settings.pointer_lock": "Mouse pointer lock",
|
||||||
|
|
|
@ -186,8 +186,6 @@
|
||||||
"settings.load_save_file_help": "Seleccione un archivo guardado en su dispositivo",
|
"settings.load_save_file_help": "Seleccione un archivo guardado en su dispositivo",
|
||||||
"settings.max_coins": " {{max}} monedas en pantalla máximo",
|
"settings.max_coins": " {{max}} monedas en pantalla máximo",
|
||||||
"settings.max_coins_help": "Solo cosmético, sin efecto en la puntuación.",
|
"settings.max_coins_help": "Solo cosmético, sin efecto en la puntuación.",
|
||||||
"settings.max_particles": " {{max}} partículas máximo",
|
|
||||||
"settings.max_particles_help": "Limita la cantidad de partículas que se muestran en la pantalla para lograr un efecto visual.",
|
|
||||||
"settings.mobile": "Modo móvil",
|
"settings.mobile": "Modo móvil",
|
||||||
"settings.mobile_help": "Deja espacio debajo de la paleta.",
|
"settings.mobile_help": "Deja espacio debajo de la paleta.",
|
||||||
"settings.pointer_lock": "Bloqueo del puntero del ratón",
|
"settings.pointer_lock": "Bloqueo del puntero del ratón",
|
||||||
|
|
|
@ -186,8 +186,6 @@
|
||||||
"settings.load_save_file_help": "Depuis un fichier ",
|
"settings.load_save_file_help": "Depuis un fichier ",
|
||||||
"settings.max_coins": "{{max}} pièces affichées maximum",
|
"settings.max_coins": "{{max}} pièces affichées maximum",
|
||||||
"settings.max_coins_help": "Visuel uniquement, pas d'impact sur le score",
|
"settings.max_coins_help": "Visuel uniquement, pas d'impact sur le score",
|
||||||
"settings.max_particles": " {{max}} particules maximum",
|
|
||||||
"settings.max_particles_help": "Limite le nombre de particules affichées à l'écran pour les effets visuels",
|
|
||||||
"settings.mobile": "Mode mobile",
|
"settings.mobile": "Mode mobile",
|
||||||
"settings.mobile_help": "Laisse un espace sous la raquette.",
|
"settings.mobile_help": "Laisse un espace sous la raquette.",
|
||||||
"settings.pointer_lock": "Verrouillage du pointeur",
|
"settings.pointer_lock": "Verrouillage du pointeur",
|
||||||
|
|
|
@ -186,8 +186,6 @@
|
||||||
"settings.load_save_file_help": "Выберите файл сохранения на вашем устройстве",
|
"settings.load_save_file_help": "Выберите файл сохранения на вашем устройстве",
|
||||||
"settings.max_coins": " {{max}} монет на экране максимум",
|
"settings.max_coins": " {{max}} монет на экране максимум",
|
||||||
"settings.max_coins_help": "Только косметика, не влияет на результат",
|
"settings.max_coins_help": "Только косметика, не влияет на результат",
|
||||||
"settings.max_particles": " {{max}} частиц максимум",
|
|
||||||
"settings.max_particles_help": "Ограничивает количество частиц, отображаемых на экране для визуального эффекта.",
|
|
||||||
"settings.mobile": "Мобильный режим",
|
"settings.mobile": "Мобильный режим",
|
||||||
"settings.mobile_help": "Оставляет место под лопаткой.",
|
"settings.mobile_help": "Оставляет место под лопаткой.",
|
||||||
"settings.pointer_lock": "Блокировка указателя мыши",
|
"settings.pointer_lock": "Блокировка указателя мыши",
|
||||||
|
|
|
@ -186,8 +186,6 @@
|
||||||
"settings.load_save_file_help": "Cihazınızda bir kayıt dosyası seçin",
|
"settings.load_save_file_help": "Cihazınızda bir kayıt dosyası seçin",
|
||||||
"settings.max_coins": "Ekranda maksimum {{max}} jeton var",
|
"settings.max_coins": "Ekranda maksimum {{max}} jeton var",
|
||||||
"settings.max_coins_help": "Sadece kozmetik, puan üzerinde etkisi yok",
|
"settings.max_coins_help": "Sadece kozmetik, puan üzerinde etkisi yok",
|
||||||
"settings.max_particles": " {{max}} parçacık maksimum",
|
|
||||||
"settings.max_particles_help": "Görsel efekt için ekranda gösterilen parçacık sayısını sınırlar.",
|
|
||||||
"settings.mobile": "Mobil mod",
|
"settings.mobile": "Mobil mod",
|
||||||
"settings.mobile_help": "Kürek altında boşluk bırakır.",
|
"settings.mobile_help": "Kürek altında boşluk bırakır.",
|
||||||
"settings.pointer_lock": "Fare işaretçisi kilidi",
|
"settings.pointer_lock": "Fare işaretçisi kilidi",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {getSettingValue} from "./settings";
|
||||||
|
|
||||||
export function clamp(value: number, min: number, max: number) {
|
export function clamp(value: number, min: number, max: number) {
|
||||||
return Math.max(min, Math.min(value, max));
|
return Math.max(min, Math.min(value, max));
|
||||||
}
|
}
|
||||||
|
@ -8,9 +10,8 @@ export function comboKeepingRate(level: number) {
|
||||||
|
|
||||||
export function hoursSpentPlaying() {
|
export function hoursSpentPlaying() {
|
||||||
try {
|
try {
|
||||||
const timePlayed =
|
const timePlayed = getSettingValue('breakout_71_total_play_time',0)
|
||||||
localStorage.getItem("breakout_71_total_play_time") || "0";
|
return Math.floor(timePlayed / 1000 / 60 / 60);
|
||||||
return Math.floor(parseFloat(timePlayed) / 1000 / 60 / 60);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ export function render(gameState: GameState) {
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
scoreDisplay.innerHTML =
|
scoreDisplay.innerHTML =
|
||||||
(isOptionOn("show_fps")
|
(isOptionOn("show_fps") || gameState.computer_controlled
|
||||||
? `
|
? `
|
||||||
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
|
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
|
||||||
${lastMeasuredFPS} FPS
|
${lastMeasuredFPS} FPS
|
||||||
|
@ -99,7 +99,7 @@ export function render(gameState: GameState) {
|
||||||
`<span class="score" data-tooltip="${t("play.score_tooltip")}">$${gameState.score}</span>`;
|
`<span class="score" data-tooltip="${t("play.score_tooltip")}">$${gameState.score}</span>`;
|
||||||
|
|
||||||
scoreDisplay.className =
|
scoreDisplay.className =
|
||||||
(gameState.computer_controlled && "hidden") ||
|
(gameState.computer_controlled && "computer_controlled") ||
|
||||||
(gameState.lastScoreIncrease > gameState.levelTime - 500 && "active") ||
|
(gameState.lastScoreIncrease > gameState.levelTime - 500 && "active") ||
|
||||||
"";
|
"";
|
||||||
|
|
||||||
|
@ -549,6 +549,18 @@ export function render(gameState: GameState) {
|
||||||
|
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
|
|
||||||
|
if (isOptionOn("mobile-mode") && gameState.computer_controlled) {
|
||||||
|
drawText(
|
||||||
|
ctx,
|
||||||
|
"breakout.lecaro.me?autoplay",
|
||||||
|
gameState.puckColor,
|
||||||
|
gameState.puckHeight,
|
||||||
|
gameState.canvasWidth / 2,
|
||||||
|
gameState.gameZoneHeight +
|
||||||
|
(gameState.canvasHeight - gameState.gameZoneHeight) / 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (isOptionOn("mobile-mode") && !gameState.running) {
|
if (isOptionOn("mobile-mode") && !gameState.running) {
|
||||||
drawText(
|
drawText(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -561,6 +573,15 @@ export function render(gameState: GameState) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if(isOptionOn('mobile-mode')) {
|
||||||
|
// ctx.globalCompositeOperation = "source-over";
|
||||||
|
// ctx.globalAlpha = 0.5;
|
||||||
|
// ctx.fillStyle = 'black'
|
||||||
|
// ctx.fillRect(0,gameState.gameZoneHeight, gameState.canvasWidth, gameState.canvasHeight-gameState.gameZoneHeight)
|
||||||
|
// }
|
||||||
|
// ctx.globalAlpha=1
|
||||||
|
askForWakeLock(gameState);
|
||||||
|
|
||||||
if (shaked) {
|
if (shaked) {
|
||||||
ctx.resetTransform();
|
ctx.resetTransform();
|
||||||
}
|
}
|
||||||
|
@ -1111,3 +1132,23 @@ function getCoinRenderColor(gameState: GameState, coin: Coin) {
|
||||||
return coin.color;
|
return coin.color;
|
||||||
return "#ffd300";
|
return "#ffd300";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wakeLock = null,
|
||||||
|
wakeLockPending = false;
|
||||||
|
function askForWakeLock(gameState: GameState) {
|
||||||
|
if (gameState.computer_controlled && !wakeLock && !wakeLockPending) {
|
||||||
|
wakeLockPending = true;
|
||||||
|
try {
|
||||||
|
navigator.wakeLock.request("screen").then((lock) => {
|
||||||
|
wakeLock = lock;
|
||||||
|
wakeLockPending = false;
|
||||||
|
lock.addEventListener("release", () => {
|
||||||
|
// the wake lock has been released
|
||||||
|
wakeLock = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("askForWakeLock error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,17 +28,11 @@ export function getTotalScore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentMaxCoins() {
|
export function getCurrentMaxCoins() {
|
||||||
return Math.pow(2, getSettingValue("max_coins", 1)) * 200;
|
return Math.pow(2, getSettingValue("max_coins", 6)) * 200;
|
||||||
}
|
}
|
||||||
export function getCurrentMaxParticles() {
|
export function getCurrentMaxParticles() {
|
||||||
return Math.pow(2, getSettingValue("max_particles", 1)) * 200;
|
return getCurrentMaxCoins()
|
||||||
}
|
}
|
||||||
export function cycleMaxCoins() {
|
export function cycleMaxCoins() {
|
||||||
setSettingValue("max_coins", (getSettingValue("max_coins", 1) + 1) % 6);
|
setSettingValue("max_coins", (getSettingValue("max_coins", 6) + 1) % 6);
|
||||||
}
|
|
||||||
export function cycleMaxParticles() {
|
|
||||||
setSettingValue(
|
|
||||||
"max_particles",
|
|
||||||
(getSettingValue("max_particles", 1) + 1) % 6,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ export function playPendingSounds(gameState: GameState) {
|
||||||
};
|
};
|
||||||
if (ex.vol) {
|
if (ex.vol) {
|
||||||
sounds[soundName](
|
sounds[soundName](
|
||||||
Math.min(2, ex.vol),
|
Math.min(1, ex.vol),
|
||||||
pixelsToPan(gameState, ex.x),
|
pixelsToPan(gameState, ex.x),
|
||||||
gameState.combo,
|
gameState.combo,
|
||||||
);
|
);
|
||||||
|
@ -25,13 +25,21 @@ export function playPendingSounds(gameState: GameState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const sounds = {
|
export const sounds = {
|
||||||
wallBeep: (vol: number, pan: number, combo: number) => {
|
wallBeep: (volume: number, pan: number) => {
|
||||||
if (!isOptionOn("sound")) return;
|
if (!isOptionOn("sound")) return;
|
||||||
createSingleBounceSound(800, pan, vol);
|
|
||||||
|
createSingleBounceSound(800, pan, volume);
|
||||||
|
},
|
||||||
|
|
||||||
|
plouf: (volume: number, pan: number) => {
|
||||||
|
if (!isOptionOn("sound")) return;
|
||||||
|
createSingleBounceSound(240, pan, volume*0.5);
|
||||||
|
// createWaterDropSound(800, pan, volume*0.2, 0.2,'triangle')
|
||||||
},
|
},
|
||||||
|
|
||||||
comboIncreaseMaybe: (volume: number, pan: number, combo: number) => {
|
comboIncreaseMaybe: (volume: number, pan: number, combo: number) => {
|
||||||
if (!isOptionOn("sound")) return;
|
if (!isOptionOn("sound")) return;
|
||||||
|
|
||||||
let delta = 0;
|
let delta = 0;
|
||||||
if (!isNaN(lastComboPlayed)) {
|
if (!isNaN(lastComboPlayed)) {
|
||||||
if (lastComboPlayed < combo) delta = 1;
|
if (lastComboPlayed < combo) delta = 1;
|
||||||
|
@ -269,3 +277,44 @@ function createOscillator(
|
||||||
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
|
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
|
||||||
return oscillator;
|
return oscillator;
|
||||||
}
|
}
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
function createWaterDropSound(
|
||||||
|
baseFreq = 500,
|
||||||
|
pan = 0.5,
|
||||||
|
volume = 1,
|
||||||
|
duration = 0.6,
|
||||||
|
type: OscillatorType = "sine"
|
||||||
|
) {
|
||||||
|
const context = getAudioContext();
|
||||||
|
if (!context) return;
|
||||||
|
|
||||||
|
const oscillator = createOscillator(context, baseFreq, type);
|
||||||
|
const gainNode = context.createGain();
|
||||||
|
const panner = context.createStereoPanner();
|
||||||
|
|
||||||
|
// Connect nodes
|
||||||
|
oscillator.connect(gainNode);
|
||||||
|
gainNode.connect(panner);
|
||||||
|
panner.connect(context.destination);
|
||||||
|
panner.connect(audioRecordingTrack);
|
||||||
|
|
||||||
|
// Panning
|
||||||
|
panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime);
|
||||||
|
|
||||||
|
const now = context.currentTime;
|
||||||
|
|
||||||
|
// Volume envelope: soft plop -> fade out
|
||||||
|
gainNode.gain.setValueAtTime(0.0001, now);
|
||||||
|
gainNode.gain.exponentialRampToValueAtTime(0.7 * volume, now + duration/100); // Quick swell
|
||||||
|
gainNode.gain.exponentialRampToValueAtTime(0.1, now + duration/3); // Fade out
|
||||||
|
gainNode.gain.exponentialRampToValueAtTime(0.001, now + duration); // Fade out
|
||||||
|
|
||||||
|
// Pitch envelope: slight downward pitch bend to simulate water tension
|
||||||
|
oscillator.frequency.setValueAtTime(baseFreq, now);
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(baseFreq * 0.5, now + duration);
|
||||||
|
|
||||||
|
// Start and stop
|
||||||
|
oscillator.start(now);
|
||||||
|
oscillator.stop(now + duration);
|
||||||
|
}
|
26
src/toast.ts
26
src/toast.ts
|
@ -1,17 +1,17 @@
|
||||||
let onScreen = 0;
|
|
||||||
|
|
||||||
export function toast(html) {
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.classList = "toast";
|
|
||||||
div.innerHTML = html;
|
|
||||||
const lasts = 1500 + onScreen * 200;
|
|
||||||
div.style.animationDuration = lasts + "ms";
|
|
||||||
div.style.top = 40 + onScreen * 50 + "px";
|
|
||||||
|
|
||||||
|
let div= document.createElement("div");
|
||||||
|
div.classList = 'hidden toast';
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
onScreen++;
|
let timeout: NodeJS.Timeout|undefined;
|
||||||
setTimeout(() => {
|
export function toast(html) {
|
||||||
div.remove();
|
div.classList = "toast visible";
|
||||||
onScreen--;
|
div.innerHTML = html;
|
||||||
}, lasts);
|
if(timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
timeout=setTimeout(() => {
|
||||||
|
timeout=undefined
|
||||||
|
div.classList = 'hidden toast';
|
||||||
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
1
src/types.d.ts
vendored
1
src/types.d.ts
vendored
|
@ -276,6 +276,7 @@ export type GameState = {
|
||||||
explode: { vol: number; x: number };
|
explode: { vol: number; x: number };
|
||||||
lifeLost: { vol: number; x: number };
|
lifeLost: { vol: number; x: number };
|
||||||
coinCatch: { vol: number; x: number };
|
coinCatch: { vol: number; x: number };
|
||||||
|
plouf: { vol: number; x: number };
|
||||||
colorChange: { vol: number; x: number };
|
colorChange: { vol: number; x: number };
|
||||||
};
|
};
|
||||||
rerolls: number;
|
rerolls: number;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue