mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-22 04:56:15 -04:00
wip
This commit is contained in:
parent
64a85200b9
commit
47ad04c49b
26 changed files with 313 additions and 247 deletions
|
@ -1,5 +1,5 @@
|
|||
// The version of the cache.
|
||||
const VERSION = "29077162";
|
||||
const VERSION = "29077593";
|
||||
|
||||
// The name of the cache
|
||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||
|
|
|
@ -933,12 +933,6 @@
|
|||
"bricks": "__bbbb___bbggbb_bbggggbbbggggggbbggggggbbbggggbb_bbggbb___bbbb__",
|
||||
"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",
|
||||
"size": 8,
|
||||
|
|
|
@ -1 +1 @@
|
|||
"29077162"
|
||||
"29077593"
|
||||
|
|
|
@ -71,8 +71,8 @@ canvas:not(#game) {
|
|||
transition: color 0.01s;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
&.computer_controlled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
span {
|
||||
|
@ -521,6 +521,7 @@ h2.histogram-title strong {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
@ -534,22 +535,18 @@ h2.histogram-title strong {
|
|||
border-radius: 2px;
|
||||
padding-right: 10px;
|
||||
pointer-events: none;
|
||||
animation: toast forwards;
|
||||
}
|
||||
|
||||
@keyframes toast {
|
||||
0%,
|
||||
100% {
|
||||
transition: opacity 200ms, transform 200ms;
|
||||
&.hidden{
|
||||
opacity: 0;
|
||||
transform: translate(-20px, -20px) scale(0.5);
|
||||
}
|
||||
10%,
|
||||
90% {
|
||||
&.visible{
|
||||
opacity: 0.8;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.gridEdit > div > span,
|
||||
.palette > span {
|
||||
display: inline-flex;
|
||||
|
|
31
src/game.ts
31
src/game.ts
|
@ -44,6 +44,7 @@ import {
|
|||
import {
|
||||
forEachLiveOne,
|
||||
gameStateTick,
|
||||
liveCount,
|
||||
normalizeGameState,
|
||||
pickRandomUpgrades,
|
||||
setLevel,
|
||||
|
@ -96,6 +97,7 @@ import { runHistoryViewerMenuEntry } from "./runHistoryViewer";
|
|||
import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
|
||||
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
|
||||
import { levelEditorMenuEntry } from "./levelEditor";
|
||||
import {toast} from "./toast";
|
||||
|
||||
export async function play() {
|
||||
if (await applyFullScreenChoice()) return;
|
||||
|
@ -470,6 +472,7 @@ export let lastMeasuredFPS = 60;
|
|||
setInterval(() => {
|
||||
lastMeasuredFPS = FPSCounter;
|
||||
FPSCounter = 0;
|
||||
|
||||
}, 1000);
|
||||
|
||||
setInterval(() => {
|
||||
|
@ -768,15 +771,6 @@ async function 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({
|
||||
icon: icons["icon:reset"],
|
||||
|
@ -1022,22 +1016,29 @@ export function restart(params: RunParams) {
|
|||
play();
|
||||
}
|
||||
}
|
||||
|
||||
if (window.location.search.includes("autoplay")) {
|
||||
if (window.location.search.includes("autoplay")) {
|
||||
startComputerControlledGame();
|
||||
} else {
|
||||
} 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 {
|
||||
restart({});
|
||||
}
|
||||
|
||||
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++) {
|
||||
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;
|
||||
restart({
|
||||
level: sample(allLevels)?.name,
|
||||
level: sample(allLevels.filter((l) => l.color === "#000000")),
|
||||
computer_controlled: true,
|
||||
perks,
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
pickedUpgradesHTMl,
|
||||
reasonLevelIsLocked,
|
||||
} from "./game_utils";
|
||||
import { getSettingValue, getTotalScore } from "./settings";
|
||||
import {getSettingValue, getTotalScore, setSettingValue} from "./settings";
|
||||
import { stopRecording } from "./recording";
|
||||
import { asyncAlert } from "./asyncAlert";
|
||||
import { rawUpgrades } from "./upgrades";
|
||||
|
@ -18,13 +18,8 @@ import { editRawLevelList } from "./levelEditor";
|
|||
|
||||
export function addToTotalPlayTime(ms: number) {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
"breakout_71_total_play_time",
|
||||
JSON.stringify(
|
||||
JSON.parse(localStorage.getItem("breakout_71_total_play_time") || "0") +
|
||||
ms,
|
||||
),
|
||||
);
|
||||
setSettingValue('breakout_71_total_play_time', getSettingValue('breakout_71_total_play_time',0)+ms)
|
||||
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -1160,8 +1160,12 @@ export function gameStateTick(
|
|||
|
||||
const ratio =
|
||||
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);
|
||||
|
||||
if (!gameState.perks.etherealcoins) {
|
||||
coin.vy *= ratio;
|
||||
coin.vx *= ratio;
|
||||
|
@ -1214,6 +1218,9 @@ export function gameStateTick(
|
|||
|
||||
const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10;
|
||||
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 (
|
||||
coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight &&
|
||||
|
|
|
@ -240,6 +240,7 @@ export function defaultSounds() {
|
|||
explode: { vol: 0, x: 0 },
|
||||
lifeLost: { vol: 0, x: 0 },
|
||||
coinCatch: { vol: 0, x: 0 },
|
||||
plouf: { vol: 0, x: 0 },
|
||||
colorChange: { vol: 0, x: 0 },
|
||||
},
|
||||
};
|
||||
|
|
|
@ -186,8 +186,6 @@
|
|||
"settings.load_save_file_help": "حدد ملف الحفظ على جهازك",
|
||||
"settings.max_coins": " {{max}} عملات معدنية على الشاشة كحد أقصى",
|
||||
"settings.max_coins_help": "تجميلي فقط، لا يؤثر على النتيجة",
|
||||
"settings.max_particles": " {{max}} جسيمات كحد أقصى",
|
||||
"settings.max_particles_help": "يحدد عدد الجسيمات التي تظهر على الشاشة للتأثير البصري.",
|
||||
"settings.mobile": "الوضع المحمول",
|
||||
"settings.mobile_help": "يترك مساحة تحت المجداف.",
|
||||
"settings.pointer_lock": "قفل مؤشر الماوس",
|
||||
|
|
|
@ -6647,76 +6647,6 @@
|
|||
</translation>
|
||||
</translations>
|
||||
</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>
|
||||
<name>mobile</name>
|
||||
<description/>
|
||||
|
|
|
@ -186,8 +186,6 @@
|
|||
"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_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_help": "Lässt Platz unter dem Paddel.",
|
||||
"settings.pointer_lock": "Mauszeigersperre",
|
||||
|
|
|
@ -186,8 +186,6 @@
|
|||
"settings.load_save_file_help": "Select a save file on your device",
|
||||
"settings.max_coins": " {{max}} coins on screen maximum",
|
||||
"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_help": "Leaves space under the paddle.",
|
||||
"settings.pointer_lock": "Mouse pointer lock",
|
||||
|
|
|
@ -186,8 +186,6 @@
|
|||
"settings.load_save_file_help": "Seleccione un archivo guardado en su dispositivo",
|
||||
"settings.max_coins": " {{max}} monedas en pantalla máximo",
|
||||
"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_help": "Deja espacio debajo de la paleta.",
|
||||
"settings.pointer_lock": "Bloqueo del puntero del ratón",
|
||||
|
|
|
@ -186,8 +186,6 @@
|
|||
"settings.load_save_file_help": "Depuis un fichier ",
|
||||
"settings.max_coins": "{{max}} pièces affichées maximum",
|
||||
"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_help": "Laisse un espace sous la raquette.",
|
||||
"settings.pointer_lock": "Verrouillage du pointeur",
|
||||
|
|
|
@ -186,8 +186,6 @@
|
|||
"settings.load_save_file_help": "Выберите файл сохранения на вашем устройстве",
|
||||
"settings.max_coins": " {{max}} монет на экране максимум",
|
||||
"settings.max_coins_help": "Только косметика, не влияет на результат",
|
||||
"settings.max_particles": " {{max}} частиц максимум",
|
||||
"settings.max_particles_help": "Ограничивает количество частиц, отображаемых на экране для визуального эффекта.",
|
||||
"settings.mobile": "Мобильный режим",
|
||||
"settings.mobile_help": "Оставляет место под лопаткой.",
|
||||
"settings.pointer_lock": "Блокировка указателя мыши",
|
||||
|
|
|
@ -186,8 +186,6 @@
|
|||
"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_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_help": "Kürek altında boşluk bırakır.",
|
||||
"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) {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
}
|
||||
|
@ -8,9 +10,8 @@ export function comboKeepingRate(level: number) {
|
|||
|
||||
export function hoursSpentPlaying() {
|
||||
try {
|
||||
const timePlayed =
|
||||
localStorage.getItem("breakout_71_total_play_time") || "0";
|
||||
return Math.floor(parseFloat(timePlayed) / 1000 / 60 / 60);
|
||||
const timePlayed = getSettingValue('breakout_71_total_play_time',0)
|
||||
return Math.floor(timePlayed / 1000 / 60 / 60);
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export function render(gameState: GameState) {
|
|||
: 1;
|
||||
|
||||
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"}">
|
||||
${lastMeasuredFPS} FPS
|
||||
|
@ -99,7 +99,7 @@ export function render(gameState: GameState) {
|
|||
`<span class="score" data-tooltip="${t("play.score_tooltip")}">$${gameState.score}</span>`;
|
||||
|
||||
scoreDisplay.className =
|
||||
(gameState.computer_controlled && "hidden") ||
|
||||
(gameState.computer_controlled && "computer_controlled") ||
|
||||
(gameState.lastScoreIncrease > gameState.levelTime - 500 && "active") ||
|
||||
"";
|
||||
|
||||
|
@ -549,6 +549,18 @@ export function render(gameState: GameState) {
|
|||
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
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) {
|
||||
drawText(
|
||||
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) {
|
||||
ctx.resetTransform();
|
||||
}
|
||||
|
@ -1111,3 +1132,23 @@ function getCoinRenderColor(gameState: GameState, coin: Coin) {
|
|||
return coin.color;
|
||||
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() {
|
||||
return Math.pow(2, getSettingValue("max_coins", 1)) * 200;
|
||||
return Math.pow(2, getSettingValue("max_coins", 6)) * 200;
|
||||
}
|
||||
export function getCurrentMaxParticles() {
|
||||
return Math.pow(2, getSettingValue("max_particles", 1)) * 200;
|
||||
return getCurrentMaxCoins()
|
||||
}
|
||||
export function cycleMaxCoins() {
|
||||
setSettingValue("max_coins", (getSettingValue("max_coins", 1) + 1) % 6);
|
||||
}
|
||||
export function cycleMaxParticles() {
|
||||
setSettingValue(
|
||||
"max_particles",
|
||||
(getSettingValue("max_particles", 1) + 1) % 6,
|
||||
);
|
||||
setSettingValue("max_coins", (getSettingValue("max_coins", 6) + 1) % 6);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export function playPendingSounds(gameState: GameState) {
|
|||
};
|
||||
if (ex.vol) {
|
||||
sounds[soundName](
|
||||
Math.min(2, ex.vol),
|
||||
Math.min(1, ex.vol),
|
||||
pixelsToPan(gameState, ex.x),
|
||||
gameState.combo,
|
||||
);
|
||||
|
@ -25,13 +25,21 @@ export function playPendingSounds(gameState: GameState) {
|
|||
}
|
||||
}
|
||||
export const sounds = {
|
||||
wallBeep: (vol: number, pan: number, combo: number) => {
|
||||
wallBeep: (volume: number, pan: number) => {
|
||||
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) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
|
||||
let delta = 0;
|
||||
if (!isNaN(lastComboPlayed)) {
|
||||
if (lastComboPlayed < combo) delta = 1;
|
||||
|
@ -269,3 +277,44 @@ function createOscillator(
|
|||
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
|
||||
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);
|
||||
onScreen++;
|
||||
setTimeout(() => {
|
||||
div.remove();
|
||||
onScreen--;
|
||||
}, lasts);
|
||||
let timeout: NodeJS.Timeout|undefined;
|
||||
export function toast(html) {
|
||||
div.classList = "toast visible";
|
||||
div.innerHTML = html;
|
||||
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 };
|
||||
lifeLost: { vol: number; x: number };
|
||||
coinCatch: { vol: number; x: number };
|
||||
plouf: { vol: number; x: number };
|
||||
colorChange: { vol: number; x: number };
|
||||
};
|
||||
rerolls: number;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue