mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 20:16:16 -04:00
Looping works, almost
This commit is contained in:
parent
46f87556e1
commit
3d5547e786
12 changed files with 1116 additions and 918 deletions
|
@ -158,7 +158,6 @@ There's also an easy mode for kids (slower ball).
|
||||||
- [colin] piggy bank - bricks absorb coins that fall onto it, and release them back as they are broken, with added value : equivalent to Asceticism
|
- [colin] piggy bank - bricks absorb coins that fall onto it, and release them back as they are broken, with added value : equivalent to Asceticism
|
||||||
- [colin] ball coins - coins share the same physics as coins and bounce on walls and bricks : really hard to balance with speeds and all
|
- [colin] ball coins - coins share the same physics as coins and bounce on walls and bricks : really hard to balance with speeds and all
|
||||||
- non brick-shaped bricks, tilted bricks,moving blocks : very difficult because of engine optimisations
|
- non brick-shaped bricks, tilted bricks,moving blocks : very difficult because of engine optimisations
|
||||||
- transparents coins, why ?
|
|
||||||
- 3 random perks immediately, or maybe "all level get twice as many upgrades, but they are applied randomly, and you aren't told which ones you have."
|
- 3 random perks immediately, or maybe "all level get twice as many upgrades, but they are applied randomly, and you aren't told which ones you have."
|
||||||
- coins repulse coins, could get really laggy ?
|
- coins repulse coins, could get really laggy ?
|
||||||
- russian roulette: 5/6 chances to get a free upgrade, 1/6 chance of game over. Not really fun
|
- russian roulette: 5/6 chances to get a free upgrade, 1/6 chance of game over. Not really fun
|
||||||
|
|
129
dist/index.html
vendored
129
dist/index.html
vendored
File diff suppressed because one or more lines are too long
|
@ -27,19 +27,21 @@ export const debuffs = [
|
||||||
help: (lvl: number) => t("debuffs.interference.help", { lvl }),
|
help: (lvl: number) => t("debuffs.interference.help", { lvl }),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "fragility",
|
||||||
|
max: 5,
|
||||||
|
name: (lvl: number) => t("debuffs.fragility.help", { percent:lvl*20 }),
|
||||||
|
help: (lvl: number) => t("debuffs.fragility.help", { percent:lvl*20 }),
|
||||||
|
},
|
||||||
] as const as Debuff[];
|
] as const as Debuff[];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Possible challenges :
|
Possible challenges :
|
||||||
|
|
||||||
- interference : telekinesis works backward for lvl/2 seconds every 5 seconds (show timer ?)
|
|
||||||
- exclusion : one of your current perks (except the kept one) is banned
|
- exclusion : one of your current perks (except the kept one) is banned
|
||||||
- fireworks : some bricks are explosive, you're not told which ones
|
- fireworks : some bricks are explosive, you're not told which ones
|
||||||
-
|
|
||||||
|
|
||||||
- graphical effects like trail, contrast, blur to make it harder to see what's going on
|
|
||||||
- ball creates a draft behind itself that blows coins in odd patterns
|
- ball creates a draft behind itself that blows coins in odd patterns
|
||||||
- bricks are invisible
|
|
||||||
- downward wind
|
- downward wind
|
||||||
- side wind
|
- side wind
|
||||||
- add red anti-coins that apply downgrades
|
- add red anti-coins that apply downgrades
|
||||||
|
|
14
src/game.ts
14
src/game.ts
|
@ -1008,22 +1008,26 @@ restart(
|
||||||
(window.location.search.includes("stressTest") && {
|
(window.location.search.includes("stressTest") && {
|
||||||
level: "Bird",
|
level: "Bird",
|
||||||
perks: {
|
perks: {
|
||||||
// sapper: 1,
|
// sapper: 5,
|
||||||
// bigger_explosions: 20,
|
// bigger_explosions: 20,
|
||||||
// // unbounded: 1,
|
// // unbounded: 1,
|
||||||
// // pierce_color: 1,
|
// // pierce_color: 1,
|
||||||
// pierce: 1,
|
// pierce: 1,
|
||||||
|
streak_shots:1,
|
||||||
// multiball: 6,
|
// multiball: 6,
|
||||||
// base_combo: 7,
|
// base_combo: 7,
|
||||||
telekinesis: 2,
|
// telekinesis: 2,
|
||||||
yoyo: 2,
|
// yoyo: 2,
|
||||||
|
pierce:10,
|
||||||
// metamorphosis: 1,
|
// metamorphosis: 1,
|
||||||
// implosions: 1,
|
// implosions: 1,
|
||||||
// sturdy_bricks:5
|
// sturdy_bricks:5
|
||||||
|
extra_life:3
|
||||||
},
|
},
|
||||||
debuffs:{
|
debuffs:{
|
||||||
|
// fragility:3
|
||||||
|
negative_coins:1
|
||||||
|
// interference:20,
|
||||||
}
|
}
|
||||||
}) ||
|
}) ||
|
||||||
{},
|
{},
|
||||||
|
|
|
@ -143,12 +143,12 @@ export function gameOver(title: string, intro: string) {
|
||||||
|
|
||||||
export function getHistograms() {
|
export function getHistograms() {
|
||||||
let runStats = "";
|
let runStats = "";
|
||||||
// TODO separate adventure and normal runs
|
|
||||||
try {
|
try {
|
||||||
// Stores only top 100 runs
|
// Stores only top 100 runs
|
||||||
let runsHistory = JSON.parse(
|
let runsHistory = JSON.parse(
|
||||||
localStorage.getItem("breakout_71_runs_history") || "[]",
|
localStorage.getItem("breakout_71_runs_history") || "[]",
|
||||||
) as RunHistoryItem[];
|
) as RunHistoryItem[];
|
||||||
|
|
||||||
runsHistory.sort((a, b) => a.score - b.score).reverse();
|
runsHistory.sort((a, b) => a.score - b.score).reverse();
|
||||||
runsHistory = runsHistory.slice(0, 100);
|
runsHistory = runsHistory.slice(0, 100);
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,16 @@ import {icons, upgrades} from "./loadGameData";
|
||||||
import {addToTotalScore, getCurrentMaxCoins, getCurrentMaxParticles,} from "./settings";
|
import {addToTotalScore, getCurrentMaxCoins, getCurrentMaxParticles,} from "./settings";
|
||||||
import {background} from "./render";
|
import {background} from "./render";
|
||||||
import {gameOver} from "./gameOver";
|
import {gameOver} from "./gameOver";
|
||||||
import {brickIndex, fitSize, gameState, hasBrick, hitsSomething, openShortRunUpgradesPicker, pause,} from "./game";
|
import {
|
||||||
|
brickIndex,
|
||||||
|
fitSize,
|
||||||
|
gameState,
|
||||||
|
hasBrick,
|
||||||
|
hitsSomething,
|
||||||
|
openShortRunUpgradesPicker,
|
||||||
|
pause,
|
||||||
|
play,
|
||||||
|
} from "./game";
|
||||||
import {stopRecording} from "./recording";
|
import {stopRecording} from "./recording";
|
||||||
import {isOptionOn} from "./options";
|
import {isOptionOn} from "./options";
|
||||||
import {isPremium} from "./premium";
|
import {isPremium} from "./premium";
|
||||||
|
@ -324,6 +333,16 @@ export function explosionAt(
|
||||||
if (gameState.perks.zen) {
|
if (gameState.perks.zen) {
|
||||||
resetCombo(gameState, x, y);
|
resetCombo(gameState, x, y);
|
||||||
}
|
}
|
||||||
|
if(gameState.debuffs.fragility){
|
||||||
|
resetCombo(gameState, x, y);
|
||||||
|
forEachLiveOne(gameState.coins, (coin, index)=>{
|
||||||
|
// Also destroys cursed coins
|
||||||
|
if(Math.random()<gameState.debuffs.fragility/5){
|
||||||
|
destroy(gameState.coins, index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function explodeBrick(
|
export function explodeBrick(
|
||||||
|
@ -335,14 +354,14 @@ export function explodeBrick(
|
||||||
const color = gameState.bricks[index];
|
const color = gameState.bricks[index];
|
||||||
if (!color) return;
|
if (!color) return;
|
||||||
|
|
||||||
if (color === "black" || color === "transparent") {
|
if (color === "black") {
|
||||||
const x = brickCenterX(gameState, index),
|
const x = brickCenterX(gameState, index),
|
||||||
y = brickCenterY(gameState, index);
|
y = brickCenterY(gameState, index);
|
||||||
|
|
||||||
if (color === "transparent") {
|
// if (color === "transparent") {
|
||||||
schedulGameSound(gameState, "void", x, 1);
|
// schedulGameSound(gameState, "void", x, 1);
|
||||||
resetCombo(gameState, x, y);
|
// resetCombo(gameState, x, y);
|
||||||
}
|
// }
|
||||||
setBrick(gameState, index, "");
|
setBrick(gameState, index, "");
|
||||||
explosionAt(gameState, index, x, y, ball);
|
explosionAt(gameState, index, x, y, ball);
|
||||||
} else if (color) {
|
} else if (color) {
|
||||||
|
@ -534,7 +553,6 @@ export function schedulGameSound(
|
||||||
export function addToScore(gameState: GameState, coin: Coin) {
|
export function addToScore(gameState: GameState, coin: Coin) {
|
||||||
gameState.score += coin.points;
|
gameState.score += coin.points;
|
||||||
gameState.lastScoreIncrease = gameState.levelTime;
|
gameState.lastScoreIncrease = gameState.levelTime;
|
||||||
|
|
||||||
addToTotalScore(gameState, coin.points);
|
addToTotalScore(gameState, coin.points);
|
||||||
if (
|
if (
|
||||||
gameState.score > gameState.highScore &&
|
gameState.score > gameState.highScore &&
|
||||||
|
@ -557,11 +575,7 @@ export function addToScore(gameState: GameState, coin: Coin) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coin.points > 0) {
|
|
||||||
schedulGameSound(gameState, "coinCatch", coin.x, 1);
|
schedulGameSound(gameState, "coinCatch", coin.x, 1);
|
||||||
} else {
|
|
||||||
resetCombo(gameState, coin.x, coin.y);
|
|
||||||
}
|
|
||||||
gameState.runStatistics.score += coin.points;
|
gameState.runStatistics.score += coin.points;
|
||||||
if (gameState.perks.asceticism) {
|
if (gameState.perks.asceticism) {
|
||||||
resetCombo(gameState, coin.x, coin.y);
|
resetCombo(gameState, coin.x, coin.y);
|
||||||
|
@ -571,6 +585,7 @@ export function addToScore(gameState: GameState, coin: Coin) {
|
||||||
export async function gotoNextLoop(gameState: GameState) {
|
export async function gotoNextLoop(gameState: GameState) {
|
||||||
pause(false)
|
pause(false)
|
||||||
gameState.loop++
|
gameState.loop++
|
||||||
|
gameState.runStatistics.loops++
|
||||||
gameState.runLevels = getRunLevels(gameState.totalScoreAtRunStart, {})
|
gameState.runLevels = getRunLevels(gameState.totalScoreAtRunStart, {})
|
||||||
gameState.upgradesOfferedFor = -1
|
gameState.upgradesOfferedFor = -1
|
||||||
// Add random debuf
|
// Add random debuf
|
||||||
|
@ -1015,7 +1030,7 @@ export function gameStateTick(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratio = 1 - (gameState.perks.viscosity * 0.03 + 0.005) * frames;
|
const ratio = 1 - ((coin.color==='crimson' ? 2:gameState.perks.viscosity)* 0.03 + 0.005) * frames;
|
||||||
|
|
||||||
coin.vy *= ratio;
|
coin.vy *= ratio;
|
||||||
coin.vx *= ratio;
|
coin.vx *= ratio;
|
||||||
|
@ -1061,7 +1076,16 @@ export function gameStateTick(
|
||||||
// a bit of margin to be nice , negative in case it's a negative coin
|
// a bit of margin to be nice , negative in case it's a negative coin
|
||||||
gameState.puckHeight * (coin.points ? 1 : -1)
|
gameState.puckHeight * (coin.points ? 1 : -1)
|
||||||
) {
|
) {
|
||||||
|
if(coin.points) {
|
||||||
addToScore(gameState, coin);
|
addToScore(gameState, coin);
|
||||||
|
}else if(gameState.perks.extra_life && gameState.balls.length){
|
||||||
|
justLostALife(gameState, gameState.balls[0], coin.x,coin.y)
|
||||||
|
}else{
|
||||||
|
gameOver(
|
||||||
|
t('gameOver.because_cursed_coin'),
|
||||||
|
t('gameOver.because_cursed_coin_intro')
|
||||||
|
)
|
||||||
|
}
|
||||||
destroy(gameState.coins, coinIndex);
|
destroy(gameState.coins, coinIndex);
|
||||||
} else if (coin.y > gameState.canvasHeight + coinRadius) {
|
} else if (coin.y > gameState.canvasHeight + coinRadius) {
|
||||||
destroy(gameState.coins, coinIndex);
|
destroy(gameState.coins, coinIndex);
|
||||||
|
@ -1317,12 +1341,15 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
||||||
ball.vx +=
|
ball.vx +=
|
||||||
((gameState.puckPosition - ball.x) / 1000) *
|
((gameState.puckPosition - ball.x) / 1000) *
|
||||||
delta *
|
delta *
|
||||||
gameState.perks.telekinesis;
|
gameState.perks.telekinesis
|
||||||
|
* interferenceFactor(gameState)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
if (isYoyoActive(gameState, ball)) {
|
if (isYoyoActive(gameState, ball)) {
|
||||||
speedLimitDampener += 3;
|
speedLimitDampener += 3;
|
||||||
ball.vx +=
|
ball.vx +=
|
||||||
((gameState.puckPosition - ball.x) / 1000) * delta * gameState.perks.yoyo;
|
((gameState.puckPosition - ball.x) / 1000) * delta * gameState.perks.yoyo
|
||||||
|
* interferenceFactor(gameState);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
ball.vx * ball.vx + ball.vy * ball.vy <
|
ball.vx * ball.vx + ball.vy * ball.vy <
|
||||||
|
@ -1466,31 +1493,8 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
||||||
schedulGameSound(gameState, "wallBeep", ball.x, 1);
|
schedulGameSound(gameState, "wallBeep", ball.x, 1);
|
||||||
} else {
|
} else {
|
||||||
ball.vy *= -1;
|
ball.vy *= -1;
|
||||||
|
justLostALife(gameState, ball, ball.x,ball.y)
|
||||||
|
|
||||||
gameState.perks.extra_life -= 1;
|
|
||||||
if (gameState.perks.extra_life < 0) {
|
|
||||||
gameState.perks.extra_life = 0;
|
|
||||||
} else if (gameState.perks.sacrifice) {
|
|
||||||
gameState.bricks.forEach(
|
|
||||||
(color, index) => color && explodeBrick(gameState, index, ball, true),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
schedulGameSound(gameState, "lifeLost", ball.x, 1);
|
|
||||||
if (!isOptionOn("basic")) {
|
|
||||||
for (let i = 0; i < 10; i++)
|
|
||||||
makeParticle(
|
|
||||||
gameState,
|
|
||||||
ball.x,
|
|
||||||
ball.y,
|
|
||||||
Math.random() * gameState.baseSpeed * 3,
|
|
||||||
gameState.baseSpeed * 3,
|
|
||||||
"red",
|
|
||||||
false,
|
|
||||||
gameState.coinSize / 2,
|
|
||||||
150,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (gameState.perks.streak_shots) {
|
if (gameState.perks.streak_shots) {
|
||||||
resetCombo(gameState, ball.x, ball.y);
|
resetCombo(gameState, ball.x, ball.y);
|
||||||
|
@ -1667,6 +1671,34 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function justLostALife(gameState:GameState, ball:Ball, x:number,y:number){
|
||||||
|
gameState.perks.extra_life -= 1;
|
||||||
|
if (gameState.perks.extra_life < 0) {
|
||||||
|
gameState.perks.extra_life = 0;
|
||||||
|
} else if (gameState.perks.sacrifice) {
|
||||||
|
gameState.bricks.forEach(
|
||||||
|
(color, index) => color && explodeBrick(gameState, index, ball, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
schedulGameSound(gameState, "lifeLost", ball.x, 1);
|
||||||
|
|
||||||
|
if (!isOptionOn("basic")) {
|
||||||
|
for (let i = 0; i < 10; i++)
|
||||||
|
makeParticle(
|
||||||
|
gameState,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
Math.random() * gameState.baseSpeed * 3,
|
||||||
|
gameState.baseSpeed * 3,
|
||||||
|
"red",
|
||||||
|
false,
|
||||||
|
gameState.coinSize / 2,
|
||||||
|
150,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function makeCoin(
|
function makeCoin(
|
||||||
gameState: GameState,
|
gameState: GameState,
|
||||||
x: number,
|
x: number,
|
||||||
|
@ -1676,9 +1708,9 @@ function makeCoin(
|
||||||
color = "gold",
|
color = "gold",
|
||||||
points = 1,
|
points = 1,
|
||||||
) {
|
) {
|
||||||
if (gameState.debuffs.negative_coins > Math.random() * 100) {
|
if (gameState.debuffs.negative_coins *points> Math.random() * 10000) {
|
||||||
points = 0;
|
points = 0;
|
||||||
color = "transparent";
|
color = "crimson";
|
||||||
}
|
}
|
||||||
append(gameState.coins, (p: Partial<Coin>) => {
|
append(gameState.coins, (p: Partial<Coin>) => {
|
||||||
p.x = x;
|
p.x = x;
|
||||||
|
@ -1698,6 +1730,13 @@ function makeCoin(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function interferenceFactor(gameState:GameState){
|
||||||
|
if(!gameState.debuffs.interference) return 1
|
||||||
|
const cycleLength = (7+gameState.debuffs.interference)*1000
|
||||||
|
const position = gameState.levelTime % cycleLength
|
||||||
|
return position>7000 ? -1 :1
|
||||||
|
}
|
||||||
|
|
||||||
function makeParticle(
|
function makeParticle(
|
||||||
gameState: GameState,
|
gameState: GameState,
|
||||||
x: number,
|
x: number,
|
||||||
|
|
|
@ -55,8 +55,7 @@ export function getPossibleUpgrades(gameState: GameState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function max_levels(gameState: GameState) {
|
export function max_levels(gameState: GameState) {
|
||||||
// TODO
|
|
||||||
return 2
|
|
||||||
return 7 + gameState.perks.extra_levels;
|
return 7 + gameState.perks.extra_levels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,12 +73,10 @@ export function pickedUpgradesHTMl(gameState: GameState) {
|
||||||
|
|
||||||
export function debuffsHTMl(gameState: GameState):string {
|
export function debuffsHTMl(gameState: GameState):string {
|
||||||
const banned = upgrades.filter(u=>gameState.bannedPerks[u.id]).map(u=>u.name).join(', ')
|
const banned = upgrades.filter(u=>gameState.bannedPerks[u.id]).map(u=>u.name).join(', ')
|
||||||
|
let list = debuffs.filter(d=>gameState.debuffs[d.id]).map(d=>d.name(gameState.debuffs[d.id], banned)).join(' ');
|
||||||
|
|
||||||
let list = debuffs.filter(d=>gameState.debuffs[d.id]).map(d=>d.name(gameState.debuffs[d.id], banned)).join(', ');
|
|
||||||
|
|
||||||
if (!list) return "";
|
if (!list) return "";
|
||||||
return `<p>${t("score_panel.bebuffs_list")} : ${list}</p>`;
|
return `<p>${t("score_panel.bebuffs_list")} ${list}</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function levelsListHTMl(gameState: GameState) {
|
export function levelsListHTMl(gameState: GameState) {
|
||||||
|
|
|
@ -116,7 +116,27 @@
|
||||||
</translation>
|
</translation>
|
||||||
<translation>
|
<translation>
|
||||||
<language>fr-FR</language>
|
<language>fr-FR</language>
|
||||||
<approved>true</approved>
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
</children>
|
||||||
|
</folder_node>
|
||||||
|
<folder_node>
|
||||||
|
<name>fragility</name>
|
||||||
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>help</name>
|
||||||
|
<description/>
|
||||||
|
<comment/>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-FR</language>
|
||||||
|
<approved>false</approved>
|
||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
@ -176,7 +196,7 @@
|
||||||
</translation>
|
</translation>
|
||||||
<translation>
|
<translation>
|
||||||
<language>fr-FR</language>
|
<language>fr-FR</language>
|
||||||
<approved>true</approved>
|
<approved>false</approved>
|
||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
@ -187,6 +207,36 @@
|
||||||
<folder_node>
|
<folder_node>
|
||||||
<name>gameOver</name>
|
<name>gameOver</name>
|
||||||
<children>
|
<children>
|
||||||
|
<concept_node>
|
||||||
|
<name>because_cursed_coin</name>
|
||||||
|
<description/>
|
||||||
|
<comment/>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-FR</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>because_cursed_coin_intro</name>
|
||||||
|
<description/>
|
||||||
|
<comment/>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-FR</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>cumulative_total</name>
|
<name>cumulative_total</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
|
@ -3,11 +3,14 @@
|
||||||
"confirmRestart.text": "You're about to start a new run, is that really what you wanted ?",
|
"confirmRestart.text": "You're about to start a new run, is that really what you wanted ?",
|
||||||
"confirmRestart.title": "Start a new run ?",
|
"confirmRestart.title": "Start a new run ?",
|
||||||
"confirmRestart.yes": "Restart game",
|
"confirmRestart.yes": "Restart game",
|
||||||
"debuffs.banned.description": "{{lvl}} banned perk(s) : {{banned}}",
|
"debuffs.banned.description": "{{lvl}} banned perk(s) : {{banned}}.",
|
||||||
"debuffs.banned.help": "{{perk}} is banned for this run",
|
"debuffs.banned.help": "{{perk}} is banned for this run.",
|
||||||
"debuffs.interference.help": "Telekinesis and yo-yo stop working for {{lvl}}s every 7s",
|
"debuffs.fragility.help": "Explosions destroy {{percent}} of coins and reset combo.",
|
||||||
"debuffs.more_bombs.help": "{{lvl}} bricks replaced by bombs",
|
"debuffs.interference.help": "Telekinesis and yo-yo glitch for {{lvl}}s every 7s.",
|
||||||
"debuffs.negative_coins.help": "{{lvl}}% of coins spawn void and break the combo if caught",
|
"debuffs.more_bombs.help": "{{lvl}} bricks replaced by bombs.",
|
||||||
|
"debuffs.negative_coins.help": "{{lvl}}/10000 of coins spawn cursed, blinking red. Game over if you catch them.",
|
||||||
|
"gameOver.because_cursed_coin": "Game over",
|
||||||
|
"gameOver.because_cursed_coin_intro": "You cough a cursed coin (bright red coins) and didn't have a extra life to spare. ",
|
||||||
"gameOver.cumulative_total": "Your total cumulative score went from {{startTs}} to {{endTs}}.",
|
"gameOver.cumulative_total": "Your total cumulative score went from {{startTs}} to {{endTs}}.",
|
||||||
"gameOver.lost.summary": "You dropped the ball after catching {{score}} coins.",
|
"gameOver.lost.summary": "You dropped the ball after catching {{score}} coins.",
|
||||||
"gameOver.lost.title": "Game Over",
|
"gameOver.lost.title": "Game Over",
|
||||||
|
@ -68,7 +71,7 @@
|
||||||
"main_menu.max_particles_help": "Limits the number of particles show on screen for visual effect. ",
|
"main_menu.max_particles_help": "Limits the number of particles show on screen for visual effect. ",
|
||||||
"main_menu.mobile": "Mobile mode",
|
"main_menu.mobile": "Mobile mode",
|
||||||
"main_menu.mobile_help": "Leaves space under the puck.",
|
"main_menu.mobile_help": "Leaves space under the puck.",
|
||||||
"main_menu.normal": "New 7 levels run",
|
"main_menu.normal": "New run",
|
||||||
"main_menu.normal_help": "Start a quick run with random starting perk",
|
"main_menu.normal_help": "Start a quick run with random starting perk",
|
||||||
"main_menu.pointer_lock": "Mouse pointer lock",
|
"main_menu.pointer_lock": "Mouse pointer lock",
|
||||||
"main_menu.pointer_lock_help": "Locks and hides the mouse cursor.",
|
"main_menu.pointer_lock_help": "Locks and hides the mouse cursor.",
|
||||||
|
@ -122,7 +125,7 @@
|
||||||
"sandbox.start": "Start test run",
|
"sandbox.start": "Start test run",
|
||||||
"sandbox.title": "Sandbox mode",
|
"sandbox.title": "Sandbox mode",
|
||||||
"sandbox.unlocks_at": "Unlocks at total score {{score}}",
|
"sandbox.unlocks_at": "Unlocks at total score {{score}}",
|
||||||
"score_panel.bebuffs_list": "Debuffs :",
|
"score_panel.bebuffs_list": "De-buffs :",
|
||||||
"score_panel.test_run": "This is a test run, score is not recorded permanently",
|
"score_panel.test_run": "This is a test run, score is not recorded permanently",
|
||||||
"score_panel.title": "{{score}} points at level {{level}}/{{max}} ",
|
"score_panel.title": "{{score}} points at level {{level}}/{{max}} ",
|
||||||
"score_panel.title_looped": "{{score}} points at level {{level}}/{{max}} of loop {{loop}}",
|
"score_panel.title_looped": "{{score}} points at level {{level}}/{{max}} of loop {{loop}}",
|
||||||
|
|
|
@ -3,11 +3,14 @@
|
||||||
"confirmRestart.text": "Vous êtes sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?",
|
"confirmRestart.text": "Vous êtes sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?",
|
||||||
"confirmRestart.title": "Démarrer une nouvelle partie ?",
|
"confirmRestart.title": "Démarrer une nouvelle partie ?",
|
||||||
"confirmRestart.yes": "Commencer une nouvelle partie",
|
"confirmRestart.yes": "Commencer une nouvelle partie",
|
||||||
"debuffs.banned.description": "{{lvl}} amélioration(s) bannie(s) : {{banned}}",
|
"debuffs.banned.description": "{{lvl}} amélioration(s) bannie(s) : {{banned}}.",
|
||||||
"debuffs.banned.help": "",
|
"debuffs.banned.help": "{{perk}} est banni pour cette course.",
|
||||||
"debuffs.interference.help": "",
|
"debuffs.fragility.help": "Les explosions détruisent {{percent}} pièces et réinitialisent le combo.",
|
||||||
"debuffs.more_bombs.help": "",
|
"debuffs.interference.help": "Télékinésie et problème de yo-yo pendant {{lvl}}s toutes les 7 s.",
|
||||||
"debuffs.negative_coins.help": "",
|
"debuffs.more_bombs.help": "{{lvl}} briques remplacées par des bombes.",
|
||||||
|
"debuffs.negative_coins.help": "{{lvl}}/10000 pièces apparaissent maudites et clignotent en rouge. La partie est terminée si vous les attrapez.",
|
||||||
|
"gameOver.because_cursed_coin": "Jeu terminé",
|
||||||
|
"gameOver.because_cursed_coin_intro": "Vous avez craché une pièce maudite (pièces rouge vif) et vous n'aviez pas de vie supplémentaire à revendre.",
|
||||||
"gameOver.cumulative_total": "Votre score total cumulé est passé de {{startTs}} à {{endTs}}.",
|
"gameOver.cumulative_total": "Votre score total cumulé est passé de {{startTs}} à {{endTs}}.",
|
||||||
"gameOver.lost.summary": "Vous avez fait tomber la balle après avoir attrapé {{score}} pièces.",
|
"gameOver.lost.summary": "Vous avez fait tomber la balle après avoir attrapé {{score}} pièces.",
|
||||||
"gameOver.lost.title": "Balle perdue",
|
"gameOver.lost.title": "Balle perdue",
|
||||||
|
@ -43,12 +46,12 @@
|
||||||
"level_up.unlocked_level": " (Niveau)",
|
"level_up.unlocked_level": " (Niveau)",
|
||||||
"level_up.unlocked_perk": " (Amélioration)",
|
"level_up.unlocked_perk": " (Amélioration)",
|
||||||
"level_up.upgrade_perk_to_level": " niveau {{level}}",
|
"level_up.upgrade_perk_to_level": " niveau {{level}}",
|
||||||
"loop.instructions": "",
|
"loop.instructions": "Tous vos avantages seront supprimés, sauf un que vous pouvez choisir ci-dessous. Chaque option comporte un danger supplémentaire qui apparaîtra à tous les niveaux.",
|
||||||
"loop.title": "",
|
"loop.title": "Boucle de départ {{loop}}",
|
||||||
"main_menu.basic": "Graphismes simplifiés",
|
"main_menu.basic": "Graphismes simplifiés",
|
||||||
"main_menu.basic_help": "Meilleures performances.",
|
"main_menu.basic_help": "Meilleures performances.",
|
||||||
"main_menu.colorful_coins": "",
|
"main_menu.colorful_coins": "Pièces colorées",
|
||||||
"main_menu.colorful_coins_help": "",
|
"main_menu.colorful_coins_help": "Les pièces apparaissent toujours de la couleur de la brique",
|
||||||
"main_menu.download_save_file": "Sauvegarder mes progrès",
|
"main_menu.download_save_file": "Sauvegarder mes progrès",
|
||||||
"main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde",
|
"main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde",
|
||||||
"main_menu.footer_html": " <p> \n<span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span>\n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donner</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a>\n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> \n<span>v.{{appVersion}}</span>\n</p>",
|
"main_menu.footer_html": " <p> \n<span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span>\n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donner</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a>\n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> \n<span>v.{{appVersion}}</span>\n</p>",
|
||||||
|
@ -68,8 +71,8 @@
|
||||||
"main_menu.max_particles_help": "Limite le nombre de particules affichées à l'écran pour les effets visuels",
|
"main_menu.max_particles_help": "Limite le nombre de particules affichées à l'écran pour les effets visuels",
|
||||||
"main_menu.mobile": "Mode mobile",
|
"main_menu.mobile": "Mode mobile",
|
||||||
"main_menu.mobile_help": "Laisse un espace sous le palet.",
|
"main_menu.mobile_help": "Laisse un espace sous le palet.",
|
||||||
"main_menu.normal": "Nouvelle partie rapide",
|
"main_menu.normal": "Nouvelle partie",
|
||||||
"main_menu.normal_help": "Jouez 7 niveaux avec un avantage de départ aléatoire",
|
"main_menu.normal_help": "Avec un avantage de départ aléatoire",
|
||||||
"main_menu.pointer_lock": "Verrouillage du pointeur",
|
"main_menu.pointer_lock": "Verrouillage du pointeur",
|
||||||
"main_menu.pointer_lock_help": "Cache aussi le curseur de la souris.",
|
"main_menu.pointer_lock_help": "Cache aussi le curseur de la souris.",
|
||||||
"main_menu.record": "Enregistrer des vidéos de jeu",
|
"main_menu.record": "Enregistrer des vidéos de jeu",
|
||||||
|
@ -90,8 +93,8 @@
|
||||||
"main_menu.settings_title": "Paramètre",
|
"main_menu.settings_title": "Paramètre",
|
||||||
"main_menu.show_fps": "Compteur de FPS",
|
"main_menu.show_fps": "Compteur de FPS",
|
||||||
"main_menu.show_fps_help": "Surveiller la perf du jeu",
|
"main_menu.show_fps_help": "Surveiller la perf du jeu",
|
||||||
"main_menu.show_stats": "",
|
"main_menu.show_stats": "Afficher les statistiques en temps réel",
|
||||||
"main_menu.show_stats_help": "",
|
"main_menu.show_stats_help": "Pièces, temps, rebonds, ratés",
|
||||||
"main_menu.sounds": "Sons du jeu",
|
"main_menu.sounds": "Sons du jeu",
|
||||||
"main_menu.sounds_help": "Ralentis certains téléphones.",
|
"main_menu.sounds_help": "Ralentis certains téléphones.",
|
||||||
"main_menu.title": "Breakout 71",
|
"main_menu.title": "Breakout 71",
|
||||||
|
@ -114,9 +117,9 @@
|
||||||
"premium.help_google": "Bien que je prévoie de proposer des licences premium via Google Play, je n'ai pas encore eu l'occasion de le faire ; il n'y a donc pas de lien d'achat ici. Si vous possédez déjà une clé de licence, vous pouvez la saisir ci-dessous.",
|
"premium.help_google": "Bien que je prévoie de proposer des licences premium via Google Play, je n'ai pas encore eu l'occasion de le faire ; il n'y a donc pas de lien d'achat ici. Si vous possédez déjà une clé de licence, vous pouvez la saisir ci-dessous.",
|
||||||
"premium.per_hours": "Vous avez passé {{hours}} heures à jouer",
|
"premium.per_hours": "Vous avez passé {{hours}} heures à jouer",
|
||||||
"premium.per_hours_help": "Donnez 4.99€ pour être premium",
|
"premium.per_hours_help": "Donnez 4.99€ pour être premium",
|
||||||
"premium.thanks": "",
|
"premium.thanks": "Vous êtes premium, merci !",
|
||||||
"premium.thanks_help": "",
|
"premium.thanks_help": "Copiez votre clé de licence",
|
||||||
"premium.title": "",
|
"premium.title": "Débloquez la boucle avec Premium",
|
||||||
"sandbox.help": "Tester n'importe quelle combinaison d'améliorations",
|
"sandbox.help": "Tester n'importe quelle combinaison d'améliorations",
|
||||||
"sandbox.instructions": "Sélectionnez les amélioration ci-dessous et appuyez sur \"Démarrer la partie de test\" pour les tester. Les scores et les statistiques ne seront pas enregistrés.",
|
"sandbox.instructions": "Sélectionnez les amélioration ci-dessous et appuyez sur \"Démarrer la partie de test\" pour les tester. Les scores et les statistiques ne seront pas enregistrés.",
|
||||||
"sandbox.start": "Démarrer la partie de test",
|
"sandbox.start": "Démarrer la partie de test",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { baseCombo, forEachLiveOne, liveCount } from "./gameStateMutators";
|
import {baseCombo, forEachLiveOne, interferenceFactor, liveCount} from "./gameStateMutators";
|
||||||
import {
|
import {
|
||||||
brickCenterX,
|
brickCenterX,
|
||||||
brickCenterY,
|
brickCenterY,
|
||||||
|
@ -62,6 +62,7 @@ export function render(gameState: GameState) {
|
||||||
|
|
||||||
ctx.globalCompositeOperation = "screen";
|
ctx.globalCompositeOperation = "screen";
|
||||||
ctx.globalAlpha = 0.6;
|
ctx.globalAlpha = 0.6;
|
||||||
|
|
||||||
forEachLiveOne(gameState.coins, (coin) => {
|
forEachLiveOne(gameState.coins, (coin) => {
|
||||||
drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y);
|
drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y);
|
||||||
});
|
});
|
||||||
|
@ -161,7 +162,6 @@ export function render(gameState: GameState) {
|
||||||
// Coins
|
// Coins
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
forEachLiveOne(gameState.coins, (coin) => {
|
forEachLiveOne(gameState.coins, (coin) => {
|
||||||
|
|
||||||
ctx.globalCompositeOperation = 'source-over'
|
ctx.globalCompositeOperation = 'source-over'
|
||||||
// ctx.globalCompositeOperation =
|
// ctx.globalCompositeOperation =
|
||||||
// coin.color === "gold" || level.color ? "source-over" : "screen";
|
// coin.color === "gold" || level.color ? "source-over" : "screen";
|
||||||
|
@ -192,8 +192,40 @@ export function render(gameState: GameState) {
|
||||||
ball.y,
|
ball.y,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (gameState.debuffs.negative_coins) {
|
||||||
|
// Render crimson coins very bright
|
||||||
|
ctx.globalCompositeOperation = 'source-over'
|
||||||
|
ctx.globalAlpha = 0.8
|
||||||
|
const red = Math.floor(gameState.levelTime / 100) % 2 > 0
|
||||||
|
forEachLiveOne(gameState.coins, (coin) => {
|
||||||
|
if (coin.color !== 'crimson') return
|
||||||
|
drawBall(
|
||||||
|
ctx,
|
||||||
|
red ? 'red' : 'black',
|
||||||
|
coin.size * 3,
|
||||||
|
coin.x,
|
||||||
|
coin.y
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ctx.globalAlpha = 1
|
||||||
|
forEachLiveOne(gameState.coins, (coin) => {
|
||||||
|
if (coin.color !== 'crimson') return
|
||||||
|
drawCoin(
|
||||||
|
ctx,
|
||||||
|
!red ? 'red' : 'black',
|
||||||
|
coin.size,
|
||||||
|
coin.x,
|
||||||
|
coin.y,
|
||||||
|
'red',
|
||||||
|
coin.a
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
renderAllBricks();
|
renderAllBricks();
|
||||||
|
|
||||||
|
@ -257,9 +289,18 @@ export function render(gameState: GameState) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isTelekinesisActive(gameState, ball) || isYoyoActive(gameState, ball)) {
|
if (isTelekinesisActive(gameState, ball) || isYoyoActive(gameState, ball)) {
|
||||||
ctx.strokeStyle = gameState.puckColor;
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(gameState.puckPosition, gameState.gameZoneHeight);
|
ctx.moveTo(gameState.puckPosition, gameState.gameZoneHeight);
|
||||||
|
if (interferenceFactor(gameState) == -1) {
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.strokeStyle = 'red'
|
||||||
|
ctx.setLineDash(redBorderDash)
|
||||||
|
ctx.lineDashOffset = getDashOffset(gameState)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ctx.strokeStyle = gameState.puckColor;
|
||||||
|
}
|
||||||
ctx.bezierCurveTo(
|
ctx.bezierCurveTo(
|
||||||
gameState.puckPosition,
|
gameState.puckPosition,
|
||||||
gameState.gameZoneHeight,
|
gameState.gameZoneHeight,
|
||||||
|
@ -269,6 +310,9 @@ export function render(gameState: GameState) {
|
||||||
ball.y,
|
ball.y,
|
||||||
);
|
);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.setLineDash(emptyArray)
|
||||||
}
|
}
|
||||||
if (gameState.perks.clairvoyant && gameState.ballStickToPuck) {
|
if (gameState.perks.clairvoyant && gameState.ballStickToPuck) {
|
||||||
ctx.strokeStyle = gameState.ballsColor;
|
ctx.strokeStyle = gameState.ballsColor;
|
||||||
|
@ -471,7 +515,7 @@ function drawStraightLine(
|
||||||
ctx.lineTo(x2, y2);
|
ctx.lineTo(x2, y2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
if (mode == "red") {
|
if (mode == "red") {
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash(emptyArray);
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
}
|
}
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
|
@ -552,7 +596,7 @@ export function renderAllBricks() {
|
||||||
!countBricksBelow(gameState, index);
|
!countBricksBelow(gameState, index);
|
||||||
|
|
||||||
let redBorder =
|
let redBorder =
|
||||||
color === "transparent" ||
|
color === "crimson" ||
|
||||||
(gameState.ballsColor !== color &&
|
(gameState.ballsColor !== color &&
|
||||||
color !== "black" &&
|
color !== "black" &&
|
||||||
redBorderOnBricksWithWrongColor) ||
|
redBorderOnBricksWithWrongColor) ||
|
||||||
|
@ -831,7 +875,7 @@ export function drawBrick(
|
||||||
|
|
||||||
canctx.fillStyle = color;
|
canctx.fillStyle = color;
|
||||||
|
|
||||||
canctx.setLineDash(offset !== -1 ? redBorderDash : []);
|
canctx.setLineDash(offset !== -1 ? redBorderDash : emptyArray);
|
||||||
canctx.lineDashOffset = offset;
|
canctx.lineDashOffset = offset;
|
||||||
canctx.strokeStyle = offset !== -1 ? "red" : color;
|
canctx.strokeStyle = offset !== -1 ? "red" : color;
|
||||||
canctx.lineJoin = "round";
|
canctx.lineJoin = "round";
|
||||||
|
@ -941,6 +985,7 @@ export const scoreDisplay = document.getElementById(
|
||||||
) as HTMLButtonElement;
|
) as HTMLButtonElement;
|
||||||
const menuLabel = document.getElementById("menuLabel") as HTMLButtonElement;
|
const menuLabel = document.getElementById("menuLabel") as HTMLButtonElement;
|
||||||
|
|
||||||
|
const emptyArray = [];
|
||||||
const redBorderDash = [5, 5];
|
const redBorderDash = [5, 5];
|
||||||
|
|
||||||
export function getDashOffset(gameState: GameState) {
|
export function getDashOffset(gameState: GameState) {
|
||||||
|
|
3
src/types.d.ts
vendored
3
src/types.d.ts
vendored
|
@ -137,6 +137,7 @@ interface LightFlash extends BaseFlash {
|
||||||
export type RunStats = {
|
export type RunStats = {
|
||||||
started: number;
|
started: number;
|
||||||
levelsPlayed: number;
|
levelsPlayed: number;
|
||||||
|
loops: number;
|
||||||
runTime: number;
|
runTime: number;
|
||||||
coins_spawned: number;
|
coins_spawned: number;
|
||||||
score: number;
|
score: number;
|
||||||
|
@ -292,7 +293,7 @@ export type RunParams = {
|
||||||
level?: string;
|
level?: string;
|
||||||
levelToAvoid?: string;
|
levelToAvoid?: string;
|
||||||
perks?: Partial<PerksMap>;
|
perks?: Partial<PerksMap>;
|
||||||
debuffs?: boolean;
|
debuffs?: Partial<DebuffsMap>;
|
||||||
};
|
};
|
||||||
export type OptionDef = {
|
export type OptionDef = {
|
||||||
default: boolean;
|
default: boolean;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue