mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-22 04:56:15 -04:00
wip
This commit is contained in:
parent
e1c20627bc
commit
6ef13f2d19
15 changed files with 289 additions and 65 deletions
|
@ -512,3 +512,35 @@ h2.histogram-title strong {
|
|||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.toast {
|
||||
position:fixed;
|
||||
left:0;
|
||||
top:40px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:10px;
|
||||
opacity:0.8;
|
||||
background:black;
|
||||
border:1px solid white;
|
||||
border-radius:2px;
|
||||
padding-right:10px;
|
||||
pointer-events: none;
|
||||
animation: toast 800ms forwards;
|
||||
}
|
||||
|
||||
@keyframes toast {
|
||||
0%{
|
||||
opacity: 0;
|
||||
transform:translate(-20px, 0);
|
||||
}
|
||||
10%,90%{
|
||||
opacity: 0.8;
|
||||
transform: none;
|
||||
}
|
||||
100%{
|
||||
opacity: 0;
|
||||
transform:translate(20px, 0);
|
||||
}
|
||||
}
|
|
@ -74,6 +74,7 @@ import { getHistory } from "./gameOver";
|
|||
import { generateSaveFileContent } from "./generateSaveFileContent";
|
||||
import { runHistoryViewerMenuEntry } from "./runHistoryViewer";
|
||||
import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
|
||||
import {monitorLevelsUnlocks} from "./monitorLevelsUnlocks";
|
||||
|
||||
export async function play() {
|
||||
if (await applyFullScreenChoice()) return;
|
||||
|
@ -422,6 +423,10 @@ setInterval(() => {
|
|||
FPSCounter = 0;
|
||||
}, 1000);
|
||||
|
||||
setInterval(() => {
|
||||
monitorLevelsUnlocks(gameState)
|
||||
}, 500);
|
||||
|
||||
window.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
pause(true);
|
||||
|
|
|
@ -1508,7 +1508,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
|
|||
ball.y > ylimit &&
|
||||
ball.vy > 0 &&
|
||||
(ballIsUnderPuck ||
|
||||
(gameState.perks.extra_life &&
|
||||
(gameState.balls.length<2 && gameState.perks.extra_life &&
|
||||
ball.y > ylimit + gameState.puckHeight / 2))
|
||||
) {
|
||||
if (ballIsUnderPuck) {
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
import {
|
||||
Ball,
|
||||
GameState,
|
||||
Level,
|
||||
PerkId,
|
||||
PerksMap,
|
||||
RunHistoryItem,
|
||||
Upgrade,
|
||||
} from "./types";
|
||||
import { icons, upgrades } from "./loadGameData";
|
||||
import { t } from "./i18n/i18n";
|
||||
import { clamp } from "./pure_functions";
|
||||
import { rawUpgrades } from "./upgrades";
|
||||
import { hashCode } from "./getLevelBackground";
|
||||
import {Ball, GameState, Level, PerkId, PerksMap, RunHistoryItem, UpgradeLike,} from "./types";
|
||||
import {icons, upgrades} from "./loadGameData";
|
||||
import {t} from "./i18n/i18n";
|
||||
import {clamp} from "./pure_functions";
|
||||
import {rawUpgrades} from "./upgrades";
|
||||
import {hashCode} from "./getLevelBackground";
|
||||
|
||||
export function describeLevel(level: Level) {
|
||||
let bricks = 0,
|
||||
|
@ -279,16 +271,10 @@ export function highScoreText() {
|
|||
return "";
|
||||
}
|
||||
|
||||
type UpgradeLike = { id: PerkId; name: string; requires: string };
|
||||
|
||||
export function getLevelUnlockCondition(levelIndex: number) {
|
||||
// Returns "" if level is unlocked, otherwise a string explaining how to unlock it
|
||||
let required: UpgradeLike[] = [],
|
||||
forbidden: UpgradeLike[] = [],
|
||||
minScore = Math.max(-1000 + 100 * levelIndex, 0);
|
||||
|
||||
if (levelIndex > 20) {
|
||||
const excluded: Set<PerkId> = new Set([
|
||||
let excluded: Set<PerkId>;
|
||||
function isExcluded(id:PerkId){
|
||||
if(!excluded) {
|
||||
excluded = new Set([
|
||||
"extra_levels",
|
||||
"extra_life",
|
||||
"one_more_choice",
|
||||
|
@ -300,11 +286,22 @@ export function getLevelUnlockCondition(levelIndex: number) {
|
|||
rawUpgrades.forEach((u) => {
|
||||
if (u.requires) excluded.add(u.requires);
|
||||
});
|
||||
}
|
||||
return excluded.has(id)
|
||||
}
|
||||
|
||||
export function getLevelUnlockCondition(levelIndex: number) {
|
||||
// Returns "" if level is unlocked, otherwise a string explaining how to unlock it
|
||||
let required: UpgradeLike[] = [],
|
||||
forbidden: UpgradeLike[] = [],
|
||||
minScore = Math.max(-1000 + 100 * levelIndex, 0);
|
||||
|
||||
if (levelIndex > 20) {
|
||||
|
||||
const possibletargets = rawUpgrades
|
||||
.slice(0, Math.floor(levelIndex / 2))
|
||||
.map((u) => u)
|
||||
.filter((u) => !excluded.has(u.id))
|
||||
.filter((u) => !isExcluded(u.id))
|
||||
.sort(
|
||||
(a, b) => hashCode(levelIndex + a.id) - hashCode(levelIndex + b.id),
|
||||
);
|
||||
|
|
|
@ -154,7 +154,7 @@
|
|||
"score_panel.upgrades_picked": "Upgrades picked so far : ",
|
||||
"unlocks.greyed_out_help": "The grayed out upgrades can be unlocked by increasing your total score. The total score increases every time you score in game, outside of test runs.",
|
||||
"unlocks.intro": "Your total score is {{ts}}. Below are all the upgrades and levels the games has to offer. Click an upgrade or level below to start a test game with it. Hint: you can set the starting upgrades in the settings.",
|
||||
"unlocks.just_unlocked": "You just unlocked a level",
|
||||
"unlocks.just_unlocked": "Level unlocked",
|
||||
"unlocks.just_unlocked_plural": "You just unlocked {{count}} levels",
|
||||
"unlocks.level": "<h2>You unlocked {{unlocked}} levels out of {{out_of}}</h2>\n<p>Here are all the game levels, click one to start a test game with that starting level. </p> ",
|
||||
"unlocks.level_description": "A {{size}}x{{size}} level with {{bricks}} bricks, {{colors}} colors and {{bombs}} bombs.",
|
||||
|
|
|
@ -154,7 +154,7 @@
|
|||
"score_panel.upgrades_picked": "Améliorations choisies jusqu'à présent :",
|
||||
"unlocks.greyed_out_help": "Les éléments grisées peuvent être débloquées en augmentant votre score total. Le score total augmente à chaque fois que vous marquez des points dans le jeu, en dehors des parties de test.",
|
||||
"unlocks.intro": "Votre score total est de {{ts}}. Vous trouverez ci-dessous toutes les améliorations et tous les niveaux que le jeu peut offrir. Cliquez sur l'un d'entre eux pour les essayer dans une partie de test. Astuce : vous pouvez choisir les améliorations de départ dans les réglages.",
|
||||
"unlocks.just_unlocked": "Vous venez de débloquer un niveau",
|
||||
"unlocks.just_unlocked": "Niveau débloqué",
|
||||
"unlocks.just_unlocked_plural": "Vous venez de débloquer {{count}} niveaux",
|
||||
"unlocks.level": "<h2>Vous avez débloqué {{unlocked}} niveaux sur {{out_of}}</h2>\n<p>Voici tous les niveaux du jeu, cliquez sur l'un d'eux pour démarrer une partie de test avec ce niveau de départ. </p> ",
|
||||
"unlocks.level_description": "Un niveau {{size}}x{{size}} avec {{bricks}} briques, {{colors}} couleurs et {{bombs}} bombes.",
|
||||
|
|
|
@ -2,6 +2,8 @@ import { RunHistoryItem } from "./types";
|
|||
|
||||
import _appVersion from "./data/version.json";
|
||||
import { generateSaveFileContent } from "./generateSaveFileContent";
|
||||
import {getLevelUnlockCondition, reasonLevelIsLocked} from "./game_utils";
|
||||
import {allLevels} from "./loadGameData";
|
||||
|
||||
// The page will be reloaded if any migrations were run
|
||||
let migrationsRun = 0;
|
||||
|
@ -17,6 +19,17 @@ function migrate(name: string, cb: () => void) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function afterMigration(){
|
||||
// Avoid a boot loop by setting the hash before reloading
|
||||
// We can't set the query string as it is used for other things
|
||||
if (migrationsRun && !window.location.hash) {
|
||||
window.location.hash = "#reloadAfterMigration";
|
||||
window.location.reload();
|
||||
}
|
||||
if (!migrationsRun) {
|
||||
window.location.hash = "";
|
||||
}
|
||||
}
|
||||
|
||||
migrate("save_data_before_upgrade_to_" + _appVersion, () => {
|
||||
localStorage.setItem(
|
||||
|
@ -90,12 +103,25 @@ migrate("compact_runs_data", () => {
|
|||
localStorage.setItem("breakout_71_runs_history", JSON.stringify(runsHistory));
|
||||
});
|
||||
|
||||
// Avoid a boot loop by setting the hash before reloading
|
||||
// We can't set the query string as it is used for other things
|
||||
if (migrationsRun && !window.location.hash) {
|
||||
window.location.hash = "#reloadAfterMigration";
|
||||
window.location.reload();
|
||||
}
|
||||
if (!migrationsRun) {
|
||||
window.location.hash = "";
|
||||
}
|
||||
migrate("set_breakout_71_unlocked_levels"+_appVersion, () => {
|
||||
// We want to lock any level unlocked by an app upgrade too
|
||||
let runsHistory = JSON.parse(
|
||||
localStorage.getItem("breakout_71_runs_history") || "[]",
|
||||
) as RunHistoryItem[];
|
||||
|
||||
let breakout_71_unlocked_levels = JSON.parse(
|
||||
localStorage.getItem("breakout_71_unlocked_levels") || "[]",
|
||||
) as string[];
|
||||
|
||||
allLevels.filter((l,li)=>!reasonLevelIsLocked(li,runsHistory,false)).forEach(l=>{
|
||||
if(!breakout_71_unlocked_levels.includes(l.name)){
|
||||
breakout_71_unlocked_levels.push(l.name)
|
||||
}
|
||||
})
|
||||
localStorage.setItem("breakout_71_unlocked_levels", JSON.stringify(breakout_71_unlocked_levels));
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
afterMigration()
|
41
src/monitorLevelsUnlocks.ts
Normal file
41
src/monitorLevelsUnlocks.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import {GameState, UpgradeLike} from "./types";
|
||||
import {getSettingValue, setSettingValue} from "./settings";
|
||||
import {allLevels, icons} from "./loadGameData";
|
||||
import { getLevelUnlockCondition} from "./game_utils";
|
||||
|
||||
import {t} from "./i18n/i18n";
|
||||
import {toast} from "./toast";
|
||||
import {schedulGameSound} from "./gameStateMutators";
|
||||
|
||||
let list: {minScore: number, forbidden: UpgradeLike[], required: UpgradeLike[]}[] ;
|
||||
let unlocked=new Set(getSettingValue('breakout_71_unlocked_levels',[]) as string[])
|
||||
|
||||
export function monitorLevelsUnlocks(gameState:GameState){
|
||||
if(gameState.creative) return;
|
||||
|
||||
if(!list){
|
||||
list=allLevels.map((l,li)=>({
|
||||
name:l.name,li,l,
|
||||
...getLevelUnlockCondition(li)
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
list.forEach(({name, minScore, forbidden, required, l})=>{
|
||||
// Already unlocked
|
||||
if(unlocked.has(name)) return
|
||||
// Score not reached yet
|
||||
if(gameState.score<minScore) return
|
||||
// We are missing a required perk
|
||||
if(required.find(id=>!gameState.perks[id])) return;
|
||||
// We have a forbidden perk
|
||||
if(forbidden.find(id=>gameState.perks[id])) return;
|
||||
// Level just got unlocked
|
||||
unlocked.add(name)
|
||||
setSettingValue('breakout_71_unlocked_levels', getSettingValue('breakout_71_unlocked_levels',[]).concat([name]))
|
||||
|
||||
toast(icons[name]+'<strong>'+t('unlocks.just_unlocked')+'</strong>')
|
||||
schedulGameSound(gameState, 'colorChange', 0, 1)
|
||||
|
||||
})
|
||||
}
|
|
@ -42,13 +42,14 @@ export function getRunLevels(
|
|||
|
||||
export function newGameState(params: RunParams): GameState {
|
||||
const highScore = getHighScore();
|
||||
const totalScoreAtRunStart=getTotalScore()
|
||||
|
||||
const perks = { ...makeEmptyPerksMap(upgrades), ...(params?.perks || {}) };
|
||||
|
||||
let randomGift: PerkId | undefined = undefined;
|
||||
if (!sumOfValues(perks)) {
|
||||
const giftable = upgrades.filter(
|
||||
(u) => highScore >= u.threshold && !u.requires && isStartingPerk(u),
|
||||
(u) => totalScoreAtRunStart >= u.threshold && isStartingPerk(u),
|
||||
);
|
||||
|
||||
randomGift =
|
||||
|
@ -106,7 +107,8 @@ export function newGameState(params: RunParams): GameState {
|
|||
ballSize: 20,
|
||||
coinSize: 14,
|
||||
puckHeight: 20,
|
||||
totalScoreAtRunStart: getTotalScore(),
|
||||
|
||||
totalScoreAtRunStart,
|
||||
pauseUsesDuringRun: 0,
|
||||
keyboardPuckSpeed: 0,
|
||||
lastTick: performance.now(),
|
||||
|
|
|
@ -17,6 +17,7 @@ export function startingPerkMenuButton() {
|
|||
};
|
||||
}
|
||||
export function isStartingPerk(u: Upgrade): boolean {
|
||||
|
||||
return getSettingValue("start_with_" + u.id, u.giftable);
|
||||
}
|
||||
|
||||
|
|
8
src/toast.ts
Normal file
8
src/toast.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export function toast(html){
|
||||
const div =document.createElement('div')
|
||||
div.classList='toast'
|
||||
div.innerHTML=html
|
||||
document.body.appendChild(div)
|
||||
// TOast
|
||||
// setTimeout(()=>div.remove() , 500 )
|
||||
}
|
5
src/types.d.ts
vendored
5
src/types.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
import { rawUpgrades } from "./upgrades";
|
||||
import { options } from "./options";
|
||||
import {rawUpgrades} from "./upgrades";
|
||||
import {options} from "./options";
|
||||
|
||||
export type colorString = string;
|
||||
|
||||
|
@ -296,3 +296,4 @@ export type OptionDef = {
|
|||
help: string;
|
||||
};
|
||||
export type OptionId = keyof typeof options;
|
||||
export type UpgradeLike = { id: PerkId; name: string; requires: string };
|
|
@ -9,7 +9,7 @@ export const rawUpgrades = [
|
|||
threshold: 0,
|
||||
giftable: false,
|
||||
id: "extra_life",
|
||||
max: 3,
|
||||
max: 7,
|
||||
name: t("upgrades.extra_life.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl === 1
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue