mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-22 04:56:15 -04:00
Starting adventure mode
This commit is contained in:
parent
a134821a94
commit
0ada53a063
13 changed files with 818 additions and 197 deletions
|
@ -972,7 +972,7 @@
|
|||
{
|
||||
"name": "icon:unlocks",
|
||||
"size": 7,
|
||||
"bricks": "eeee___e__e___e__e______llll___llll___llll___llll",
|
||||
"bricks": "eeee___e__e___e__e______ctCb___Gbsc___tOGO___OCbs",
|
||||
"svg": null,
|
||||
"color": ""
|
||||
},
|
||||
|
@ -1017,5 +1017,33 @@
|
|||
"bricks": "___W____y___W_y______W___y____W_y______W___y____W______W_W_WWW_WW_W_WWWWWW_W_WWWW",
|
||||
"svg": null,
|
||||
"color": ""
|
||||
},
|
||||
{
|
||||
"name": "icon:premium",
|
||||
"size": 11,
|
||||
"bricks": "________________y_________yey_________y______yy_yey_yy_yeeyeeeyeeyyeeyeeeyeey_yeyeeeyey___yyyyyyy____yyyyyyy_____________",
|
||||
"svg": 11,
|
||||
"color": ""
|
||||
},
|
||||
{
|
||||
"name": "icon:premium_active",
|
||||
"size": 11,
|
||||
"bricks": "__y____y___y____y____y_y__yby__y______y______yy_yty_yy_ybbytttybbyybbytttybby_ybytttyby___yyyyyyy____yyyyyyy_____________",
|
||||
"svg": null,
|
||||
"color": ""
|
||||
},
|
||||
{
|
||||
"name": "icon:adventure_mode",
|
||||
"size": 17,
|
||||
"bricks": "________aaaaaaaaa_______a_____________aaaabbbbbbbbb___a___t___________a_____ttttttttt__a________________a_____sssssssss__a____s_________WWWsssssrrrrrrrrr__c____R___________c_____RRRRRRRRR__c________________c_____kkkkkkkkk___c___k_____________ccccGGGGGGGGG_______c_________________ccccccccc",
|
||||
"svg": null,
|
||||
"color": ""
|
||||
},
|
||||
{
|
||||
"name": "icon:7_levels_run",
|
||||
"size": 7,
|
||||
"bricks": "___a______at__cGCa_b_c_____ycGCa_b____at_____a___",
|
||||
"svg": null,
|
||||
"color": ""
|
||||
}
|
||||
]
|
||||
]
|
78
src/game.ts
78
src/game.ts
|
@ -62,6 +62,8 @@ import {
|
|||
} from "./asyncAlert";
|
||||
import { isOptionOn, options, toggleOption } from "./options";
|
||||
import { hashCode } from "./getLevelBackground";
|
||||
import {hasUncaughtExceptionCaptureCallback} from "process";
|
||||
import {premiumMenuEntry} from "./premium";
|
||||
|
||||
export function play() {
|
||||
if (gameState.running) return;
|
||||
|
@ -453,17 +455,17 @@ async function openScorePanel() {
|
|||
},
|
||||
);
|
||||
|
||||
async function openMainMenu() {
|
||||
export async function openMainMenu() {
|
||||
pause(true);
|
||||
|
||||
const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold));
|
||||
const actions: AsyncAlertAction<() => void>[] = [
|
||||
{
|
||||
text: t("main_menu.settings_title"),
|
||||
help: t("main_menu.settings_help"),
|
||||
icon: icons["icon:settings"],
|
||||
value() {
|
||||
openSettingsMenu();
|
||||
{
|
||||
icon: icons["icon:7_levels_run"],
|
||||
text: t("main_menu.normal"),
|
||||
help: t("main_menu.normal_help"),
|
||||
value: () => {
|
||||
restart({ levelToAvoid: currentLevelInfo(gameState).name });
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -522,20 +524,23 @@ async function openMainMenu() {
|
|||
},
|
||||
},
|
||||
|
||||
premiumMenuEntry(gameState)
|
||||
,
|
||||
//
|
||||
// {
|
||||
// icon: icons["icon:continue"],
|
||||
// text: t("main_menu.resume"),
|
||||
// help: t("main_menu.resume_help"),
|
||||
// value() {},
|
||||
// },
|
||||
{
|
||||
icon: icons["icon:restart"],
|
||||
text: t("score_panel.restart"),
|
||||
help: t("score_panel.restart_help"),
|
||||
value: () => {
|
||||
restart({ levelToAvoid: currentLevelInfo(gameState).name });
|
||||
text: t("main_menu.settings_title"),
|
||||
help: t("main_menu.settings_help"),
|
||||
icon: icons["icon:settings"],
|
||||
value() {
|
||||
openSettingsMenu();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: icons["icon:continue"],
|
||||
text: t("main_menu.resume"),
|
||||
help: t("main_menu.resume_help"),
|
||||
value() {},
|
||||
},
|
||||
];
|
||||
|
||||
const cb = await asyncAlert<() => void>({
|
||||
|
@ -770,7 +775,7 @@ async function openSettingsMenu() {
|
|||
],
|
||||
allowClose: true,
|
||||
});
|
||||
if (pick && pick !== getCurrentLang() && (await confirmRestart())) {
|
||||
if (pick && pick !== getCurrentLang() && (await confirmRestart(gameState))) {
|
||||
setSettingValue("lang", pick);
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -794,11 +799,11 @@ async function openSettingsMenu() {
|
|||
},
|
||||
});
|
||||
|
||||
actions.push({
|
||||
text: t("main_menu.resume"),
|
||||
help: t("main_menu.resume_help"),
|
||||
value() {},
|
||||
});
|
||||
// actions.push({
|
||||
// text: t("main_menu.resume"),
|
||||
// help: t("main_menu.resume_help"),
|
||||
// value() {},
|
||||
// });
|
||||
const cb = await asyncAlert<() => void>({
|
||||
title: t("main_menu.settings_title"),
|
||||
text: t("main_menu.settings_help"),
|
||||
|
@ -816,10 +821,10 @@ async function openUnlocksList() {
|
|||
const actions = [
|
||||
...upgrades
|
||||
.sort((a, b) => a.threshold - b.threshold)
|
||||
.map(({ name, id, threshold, icon, fullHelp }) => ({
|
||||
.map(({ name, id, threshold, icon, help }) => ({
|
||||
text: name,
|
||||
help:
|
||||
ts >= threshold ? fullHelp : t("unlocks.unlocks_at", { threshold }),
|
||||
// help:
|
||||
// ts >= threshold ? help(1) : t("unlocks.unlocks_at", { threshold }),
|
||||
disabled: ts < threshold,
|
||||
value: { perks: { [id]: 1 } } as RunParams,
|
||||
icon,
|
||||
|
@ -830,12 +835,12 @@ async function openUnlocksList() {
|
|||
const available = ts >= l.threshold;
|
||||
return {
|
||||
text: l.name,
|
||||
help: available
|
||||
? t("unlocks.level_description", {
|
||||
size: l.size,
|
||||
bricks: l.bricks.filter((i) => i).length,
|
||||
})
|
||||
: t("unlocks.unlocks_at", { threshold: l.threshold }),
|
||||
// help: available
|
||||
// ? t("unlocks.level_description", {
|
||||
// size: l.size,
|
||||
// bricks: l.bricks.filter((i) => i).length,
|
||||
// })
|
||||
// : t("unlocks.unlocks_at", { threshold: l.threshold }),
|
||||
disabled: !available,
|
||||
value: { level: l.name } as RunParams,
|
||||
icon: icons[l.name],
|
||||
|
@ -857,15 +862,16 @@ Click an item above to start a run with it.
|
|||
</p>`,
|
||||
actions,
|
||||
allowClose: true,
|
||||
actionsAsGrid:true
|
||||
});
|
||||
if (tryOn) {
|
||||
if (await confirmRestart()) {
|
||||
if (await confirmRestart(gameState)) {
|
||||
restart(tryOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function confirmRestart() {
|
||||
export async function confirmRestart(gameState) {
|
||||
if (!gameState.currentLevel) return true;
|
||||
|
||||
return asyncAlert({
|
||||
|
@ -961,7 +967,7 @@ document.addEventListener("keyup", async (e) => {
|
|||
} else if (e.key.toLowerCase() === "s" && !alertsOpen) {
|
||||
openScorePanel().then();
|
||||
} else if (e.key.toLowerCase() === "r" && !alertsOpen) {
|
||||
if (await confirmRestart()) {
|
||||
if (await confirmRestart(gameState)) {
|
||||
restart({ levelToAvoid: currentLevelInfo(gameState).name });
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -922,6 +922,36 @@
|
|||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>normal</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>normal_help</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>pointer_lock</name>
|
||||
<description/>
|
||||
|
@ -1379,6 +1409,206 @@
|
|||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>premium</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>adventure_mode</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>adventure_mode_help</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>back</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>back_help</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>buy</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>buy_disabled_help</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>buy_help</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>enter</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>enter_help</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>help</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>help_google</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>short_help</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>title</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>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>sandbox</name>
|
||||
<children>
|
||||
|
|
|
@ -57,6 +57,8 @@
|
|||
"main_menu.max_particles_help": "Limits the number of particles show on screen for visual effect. ",
|
||||
"main_menu.mobile": "Mobile mode",
|
||||
"main_menu.mobile_help": "Leaves space under the puck.",
|
||||
"main_menu.normal": "New 7 levels run",
|
||||
"main_menu.normal_help": "Start a quick run with random perk",
|
||||
"main_menu.pointer_lock": "Mouse pointer lock",
|
||||
"main_menu.pointer_lock_help": "Locks and hides the mouse cursor.",
|
||||
"main_menu.record": "Record gameplay videos",
|
||||
|
@ -65,7 +67,7 @@
|
|||
"main_menu.reset": "Reset Game",
|
||||
"main_menu.reset_cancel": "No",
|
||||
"main_menu.reset_confirm": "Yes",
|
||||
"main_menu.reset_help": "Erase high score and statistics",
|
||||
"main_menu.reset_help": "Erase high score, license and statistics",
|
||||
"main_menu.reset_instruction": "You will loose all progress you made in the game, are you sure ?",
|
||||
"main_menu.resume": "Resume",
|
||||
"main_menu.resume_help": "Return to your run",
|
||||
|
@ -80,13 +82,26 @@
|
|||
"main_menu.sounds": "Game sounds",
|
||||
"main_menu.sounds_help": "Can slow down some phones.",
|
||||
"main_menu.title": "Breakout 71",
|
||||
"main_menu.unlocks": "Starting perk",
|
||||
"main_menu.unlocks": "Unlocked content",
|
||||
"main_menu.unlocks_help": "Try perks and levels you unlocked",
|
||||
"play.close_modale_window_tooltip": "close ",
|
||||
"play.current_lvl": "L{{level}}/{{max}}",
|
||||
"play.menu_label": "menu",
|
||||
"play.missed_ball": "miss",
|
||||
"play.mobile_press_to_play": "Press and hold here to play",
|
||||
"premium.adventure_mode": "Adventure mode",
|
||||
"premium.adventure_mode_help": "Start a new game in adventure mode",
|
||||
"premium.back": "Back",
|
||||
"premium.back_help": "Return to main menu",
|
||||
"premium.buy": "Buy a license key",
|
||||
"premium.buy_disabled_help": "Coming soon",
|
||||
"premium.buy_help": "You'll be taken to a stripe form to pay and will receive the license by email. Come back to enter it here after.",
|
||||
"premium.enter": "Enter license key",
|
||||
"premium.enter_help": "Paste the license in the window that opens",
|
||||
"premium.help": "Buy a license for Breakout 71 to unlock infinite mode and support development. It costs 4.99€ and lasts forever. You can use it on multiple devices, but please don't share it online. ",
|
||||
"premium.help_google": "While I do plan to offer premium licenses through google play, I haven't gotten around it yet, so there's no buy link here. If you already have a license key, you can enter it below. ",
|
||||
"premium.short_help": "Play as long as possible",
|
||||
"premium.title": "Adventure mode",
|
||||
"sandbox.help": "Test any perk combination",
|
||||
"sandbox.instructions": "Select perks below and press \"start run\" to try them out in a test run. Scores and stats are not recorded.",
|
||||
"sandbox.start": "Start test run",
|
||||
|
|
|
@ -57,6 +57,8 @@
|
|||
"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_help": "Laisse un espace sous le palet.",
|
||||
"main_menu.normal": "",
|
||||
"main_menu.normal_help": "",
|
||||
"main_menu.pointer_lock": "Verrouillage du pointeur",
|
||||
"main_menu.pointer_lock_help": "Cache aussi le curseur de la souris.",
|
||||
"main_menu.record": "Enregistrer des vidéos de jeu",
|
||||
|
@ -65,7 +67,7 @@
|
|||
"main_menu.reset": "Réinitialiser le jeu",
|
||||
"main_menu.reset_cancel": "Non",
|
||||
"main_menu.reset_confirm": "Oui",
|
||||
"main_menu.reset_help": "Effacer les scores et statistiques",
|
||||
"main_menu.reset_help": "Effacer les scores, statistiques et licences",
|
||||
"main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?",
|
||||
"main_menu.resume": "Retourner à la partie",
|
||||
"main_menu.resume_help": "Continuer la partie en cours",
|
||||
|
@ -80,13 +82,26 @@
|
|||
"main_menu.sounds": "Sons du jeu",
|
||||
"main_menu.sounds_help": "Ralentis certains téléphones.",
|
||||
"main_menu.title": "Breakout 71",
|
||||
"main_menu.unlocks": "Améliorations et niveaux",
|
||||
"main_menu.unlocks": "Contenu débloqué",
|
||||
"main_menu.unlocks_help": "Essayez les éléments débloqués",
|
||||
"play.close_modale_window_tooltip": "Fermer",
|
||||
"play.current_lvl": "Niveau {{level}}/{{max}}",
|
||||
"play.menu_label": "Menu",
|
||||
"play.missed_ball": "raté",
|
||||
"play.mobile_press_to_play": "Gardez le doigt ici pour jouer",
|
||||
"premium.adventure_mode": "",
|
||||
"premium.adventure_mode_help": "",
|
||||
"premium.back": "",
|
||||
"premium.back_help": "",
|
||||
"premium.buy": "",
|
||||
"premium.buy_disabled_help": "",
|
||||
"premium.buy_help": "",
|
||||
"premium.enter": "",
|
||||
"premium.enter_help": "",
|
||||
"premium.help": "",
|
||||
"premium.help_google": "",
|
||||
"premium.short_help": "",
|
||||
"premium.title": "",
|
||||
"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.start": "Démarrer la partie de test",
|
||||
|
|
|
@ -12,7 +12,7 @@ export function levelIconHTML(
|
|||
levelSize: number,
|
||||
color: string,
|
||||
) {
|
||||
const size = 40;
|
||||
const size = 46;
|
||||
const c = levelIconHTMLCanvas;
|
||||
const ctx = levelIconHTMLCanvasCtx;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
|
||||
const perks = { ...makeEmptyPerksMap(upgrades), ...(params?.perks || {}) };
|
||||
|
||||
const gameState: GameState = {
|
||||
const gameState: GameState= {
|
||||
runLevels,
|
||||
currentLevel: 0,
|
||||
upgradesOfferedFor: -1,
|
||||
|
@ -42,6 +42,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
ballStickToPuck: true,
|
||||
puckPosition: 400,
|
||||
lastPuckPosition: 400,
|
||||
lastPuckMove: 0,
|
||||
pauseTimeout: null,
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
|
@ -99,6 +100,11 @@ export function newGameState(params: RunParams): GameState {
|
|||
needsRender: true,
|
||||
autoCleanUses: 0,
|
||||
...defaultSounds(),
|
||||
|
||||
isAdventureMode:!!params?.adventure,
|
||||
adventurePath:'',
|
||||
seed:'Seed'+Math.random()
|
||||
|
||||
};
|
||||
resetBalls(gameState);
|
||||
|
||||
|
|
161
src/premium.ts
Normal file
161
src/premium.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
import {GameState} from "./types";
|
||||
import {icons} from "./loadGameData";
|
||||
import {t} from "./i18n/i18n";
|
||||
import {getSettingValue, setSettingValue} from "./settings";
|
||||
import {asyncAlert} from "./asyncAlert";
|
||||
import {confirmRestart, openMainMenu, restart} from "./game";
|
||||
|
||||
const publicKeyString = `-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyGQJgs6gxa0Fd86TuZ2q
|
||||
rGQ5ArSn8ug4VIKezru1QhIEkXeOT1lYXOLEryWaVUwXfOa9sVlKAGJY5y0TarAY
|
||||
NF2m67ME8yzNPIoZWbKXutJ3CSCXNTjAqAxHgz7H+qxbNGZXAXw+ta8+PuZDzcCI
|
||||
LbXT1u3/i0ahhA2Erdpv9XQBazKZt5AKzU31XhEEFh1jXZyk9D4XbatYXtvEwaJx
|
||||
eSWmjSxJ6SJb6oH2mwm8V4E0PxYVIa0yX3cPgGuR0pZPMleOTc6o0T24I2AUQb0d
|
||||
FckdFrr5U8bFIf/nwncMYVVNgt1vh88EuzWLjpc52nLrdOkVQNpiCN2uMgBBXQB7
|
||||
iseIfdkGF0A4DBn8qdieDvaSY8zeRW/nAce4FNBidU1SebNRnIU9f/XpA493lJW+
|
||||
Y/zXQBbmX/uSmeZDP4fjhKZv0Qa0ZeGzZiTdBKKb0BlIg/VYFFsqPytUVVyesO4J
|
||||
RCASTIjXW61E7PQKir5qIXwkQDlzJ+bpZ3PHyAvspRrBaDxIYvEEw14evpuqOgS+
|
||||
v/IlgPe+CWSvZa9xxnQl/aWZrOrD7syu6KKCbgUyXEm+Alp0YT3e6nwjn0qiM/cj
|
||||
dZpWPx3O+rZbRQb0gHcvN4+n2Y7fWAeC9mxVZtADqvVr/GTumMbLj7DdhWtt1Ogu
|
||||
4EcvkQ5SKCL0JC93DyctjOMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
|
||||
function pemToArrayBuffer(pem: string) {
|
||||
const b64 = pem
|
||||
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
||||
.replace(/-----END PUBLIC KEY-----/, '')
|
||||
.replace(/\s+/g, '');
|
||||
const binaryDerString = atob(b64);
|
||||
const binaryDer = new Uint8Array(binaryDerString.length);
|
||||
for (let i = 0; i < binaryDerString.length; i++) {
|
||||
binaryDer[i] = binaryDerString.charCodeAt(i);
|
||||
}
|
||||
return binaryDer.buffer;
|
||||
}
|
||||
|
||||
async function getPriceId(key: string, pem: string) {
|
||||
// Split the key into its components
|
||||
const [priceId, timestamp, signature] = key.split(':');
|
||||
const data = `${priceId}:${timestamp}`;
|
||||
|
||||
const publicKeyBuffer = pemToArrayBuffer(pem);
|
||||
|
||||
|
||||
const publicKey = await crypto.subtle.importKey(
|
||||
'spki',
|
||||
publicKeyBuffer,
|
||||
{
|
||||
name: 'RSA-PSS',
|
||||
hash: 'SHA-256',
|
||||
},
|
||||
true,
|
||||
['verify']
|
||||
);
|
||||
|
||||
|
||||
// Verify the signature using ECDSA
|
||||
const isValid = await crypto.subtle.verify(
|
||||
{
|
||||
name: 'RSA-PSS',
|
||||
saltLength: 32,
|
||||
},
|
||||
publicKey,
|
||||
new Uint8Array(Array.from(atob(signature), c => c.charCodeAt(0))),
|
||||
new TextEncoder().encode(data)
|
||||
);
|
||||
if (!isValid) throw new Error("Invalid key signature")
|
||||
|
||||
return priceId;
|
||||
}
|
||||
|
||||
let premium = false
|
||||
const gamePriceId = 'price_1R6YaEGRf74lr2EkSo2GPvuO'
|
||||
checkKey(getSettingValue('license', '')).then()
|
||||
|
||||
async function checkKey(key: string) {
|
||||
if (!key) return 'No key'
|
||||
try {
|
||||
|
||||
if (gamePriceId !== await getPriceId(key, publicKeyString)) {
|
||||
return 'Wrong product'
|
||||
}
|
||||
premium = true
|
||||
return ''
|
||||
|
||||
} catch (e) {
|
||||
return 'Could not upgrade : ' + e.message
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function isPremium() {
|
||||
return premium
|
||||
}
|
||||
|
||||
export function premiumMenuEntry(gameState: GameState) {
|
||||
if (isPremium()) {
|
||||
return {
|
||||
icon: icons["icon:adventure_mode"],
|
||||
text: t("premium.adventure_mode"),
|
||||
help: t("premium.adventure_mode_help"),
|
||||
value: async () => {
|
||||
if (await confirmRestart(gameState)) {
|
||||
restart({
|
||||
adventure: true
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
icon: icons["icon:premium"],
|
||||
text: t("premium.title"),
|
||||
help: t("premium.short_help"),
|
||||
value: () => openPremiumMenu(''),
|
||||
}
|
||||
}
|
||||
|
||||
async function openPremiumMenu(text) {
|
||||
const isGooglePlayInstall = new URLSearchParams(location.search).get('source') === 'com.android.vending'
|
||||
|
||||
const cb = await asyncAlert({
|
||||
title: t("premium.title"),
|
||||
text: text || (isGooglePlayInstall && t("premium.help_google")) || t("premium.help"),
|
||||
actions: [
|
||||
{
|
||||
text: t("premium.buy"),
|
||||
disabled: isGooglePlayInstall,
|
||||
help: isGooglePlayInstall ? t("premium.buy_disabled_help") : t("premium.buy_help"),
|
||||
value() {
|
||||
window.open('https://licenses.lecaro.me/buy/price_1R6YaEGRf74lr2EkSo2GPvuO', '_blank');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t("premium.enter"),
|
||||
help: t("premium.enter_help"),
|
||||
async value() {
|
||||
const value = (prompt('Please paste your license key') || '').replace(/\s+/g, '')
|
||||
const problem = await checkKey(value)
|
||||
if (problem) {
|
||||
openPremiumMenu(problem).then()
|
||||
} else {
|
||||
setSettingValue('license', value)
|
||||
openMainMenu().then()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t("premium.back"),
|
||||
help: t("premium.back_help"),
|
||||
value() {
|
||||
openMainMenu().then()
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
})
|
||||
if (cb) cb()
|
||||
|
||||
}
|
|
@ -538,7 +538,7 @@ export function renderAllBricks() {
|
|||
let redBecauseOfReach =
|
||||
gameState.perks.reach &&
|
||||
countBricksAbove(gameState, index) &&
|
||||
!countBricksBelow(gameState, index);
|
||||
!countBricksBelow(gameState, index) ;
|
||||
|
||||
let redBorder =
|
||||
(gameState.ballsColor !== color &&
|
||||
|
|
4
src/types.d.ts
vendored
4
src/types.d.ts
vendored
|
@ -266,12 +266,16 @@ export type GameState = {
|
|||
coinCatch: { vol: number; x: number };
|
||||
colorChange: { vol: number; x: number };
|
||||
};
|
||||
isAdventureMode:boolean,
|
||||
adventurePath:string,
|
||||
seed:string
|
||||
};
|
||||
|
||||
export type RunParams = {
|
||||
level?: string;
|
||||
levelToAvoid?: string;
|
||||
perks?: Partial<PerksMap>;
|
||||
adventure?:boolean;
|
||||
};
|
||||
export type OptionDef = {
|
||||
default: boolean;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue