mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-23 21:46:15 -04:00
Build 29049575
This commit is contained in:
parent
e3e61b12b8
commit
08640fa389
16 changed files with 1735 additions and 1847 deletions
|
@ -25,10 +25,6 @@ There's also an easy mode for kids (slower ball).
|
|||
- people assume unbounded allows for wrap around
|
||||
- popups not scrollable sometimes
|
||||
- fdroid build
|
||||
- deal with too many upgrades :
|
||||
- disable some upgrades to remove them from the pool
|
||||
- reroll mechanic
|
||||
- extra option that just adds 10% to score
|
||||
- coin magnet and viscosity : only one level ~2.5
|
||||
- Boost Ascetism : give +2 or even +3 combo per brick destroyed
|
||||
- wind : move coins based on puck movement not position
|
||||
|
|
|
@ -11,8 +11,8 @@ android {
|
|||
applicationId = "me.lecaro.breakout"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 29048147
|
||||
versionName = "29048147"
|
||||
versionCode = 29049575
|
||||
versionName = "29049575"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
|
|
File diff suppressed because one or more lines are too long
149
dist/index.html
vendored
149
dist/index.html
vendored
|
@ -631,7 +631,6 @@ var _newGameState = require("./newGameState");
|
|||
var _asyncAlert = require("./asyncAlert");
|
||||
var _options = require("./options");
|
||||
var _getLevelBackground = require("./getLevelBackground");
|
||||
var _premium = require("./premium");
|
||||
function play() {
|
||||
if (gameState.running) return;
|
||||
gameState.running = true;
|
||||
|
@ -754,8 +753,8 @@ async function openShortRunUpgradesPicker(gameState) {
|
|||
count: gameState.rerolls
|
||||
}),
|
||||
help: (0, _i18N.t)("level_up.reroll_help"),
|
||||
value: 'reroll',
|
||||
icon: (0, _loadGameData.icons)['icon:reroll']
|
||||
value: "reroll",
|
||||
icon: (0, _loadGameData.icons)["icon:reroll"]
|
||||
});
|
||||
if (!actions.length) break;
|
||||
let textAfterButtons = `
|
||||
|
@ -790,7 +789,7 @@ async function openShortRunUpgradesPicker(gameState) {
|
|||
allowClose: false,
|
||||
textAfterButtons
|
||||
});
|
||||
if (upgradeId === 'reroll') {
|
||||
if (upgradeId === "reroll") {
|
||||
repeats++;
|
||||
gameState.rerolls--;
|
||||
} else {
|
||||
|
@ -962,14 +961,7 @@ async function openMainMenu() {
|
|||
}
|
||||
}
|
||||
},
|
||||
(0, _premium.premiumMenuEntry)(gameState),
|
||||
//
|
||||
// {
|
||||
// icon: icons["icon:continue"],
|
||||
// text: t("main_menu.resume"),
|
||||
// help: t("main_menu.resume_help"),
|
||||
// value() {},
|
||||
// },
|
||||
// premiumMenuEntry(gameState),
|
||||
{
|
||||
text: (0, _i18N.t)("main_menu.settings_title"),
|
||||
help: (0, _i18N.t)("main_menu.settings_help"),
|
||||
|
@ -1343,7 +1335,7 @@ restart(window.location.search.includes("stressTest") ? {
|
|||
} : {});
|
||||
tick();
|
||||
|
||||
},{"./loadGameData":"l1B4x","./sounds":"dQKPV","./game_utils":"cEeac","./PWA/sw_loader":"2n0gK","./i18n/i18n":"eNPRm","./settings":"5blfu","./gameStateMutators":"9ZeQl","./render":"9AS2t","./recording":"godmD","./newGameState":"aQN6X","./asyncAlert":"rSqLY","./options":"d5NoS","./getLevelBackground":"7OIPf","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./premium":"4GEPs"}],"l1B4x":[function(require,module,exports,__globalThis) {
|
||||
},{"./loadGameData":"l1B4x","./sounds":"dQKPV","./game_utils":"cEeac","./PWA/sw_loader":"2n0gK","./i18n/i18n":"eNPRm","./settings":"5blfu","./gameStateMutators":"9ZeQl","./render":"9AS2t","./recording":"godmD","./newGameState":"aQN6X","./asyncAlert":"rSqLY","./options":"d5NoS","./getLevelBackground":"7OIPf","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"l1B4x":[function(require,module,exports,__globalThis) {
|
||||
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
|
||||
parcelHelpers.defineInteropFlag(exports);
|
||||
parcelHelpers.export(exports, "appVersion", ()=>appVersion);
|
||||
|
@ -1384,7 +1376,7 @@ const upgrades = (0, _upgrades.rawUpgrades).map((u)=>({
|
|||
}));
|
||||
|
||||
},{"./data/palette.json":"ktRBU","./data/levels.json":"8JSUc","./data/version.json":"iyP6E","./upgrades":"1u3Dx","./getLevelBackground":"7OIPf","./levelIcon":"6rQoT","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"iyP6E":[function(require,module,exports,__globalThis) {
|
||||
module.exports = JSON.parse("\"29048147\"");
|
||||
module.exports = JSON.parse("\"29049575\"");
|
||||
|
||||
},{}],"1u3Dx":[function(require,module,exports,__globalThis) {
|
||||
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
|
||||
|
@ -4385,8 +4377,8 @@ function newGameState(params) {
|
|||
autoCleanUses: 0,
|
||||
...(0, _gameUtils.defaultSounds)(),
|
||||
isAdventureMode: !!params?.adventure,
|
||||
adventurePath: '',
|
||||
seed: 'Seed' + Math.random(),
|
||||
adventurePath: "",
|
||||
seed: "Seed" + Math.random(),
|
||||
rerolls: 0
|
||||
};
|
||||
(0, _gameStateMutators.resetBalls)(gameState);
|
||||
|
@ -4400,130 +4392,7 @@ function newGameState(params) {
|
|||
return gameState;
|
||||
}
|
||||
|
||||
},{"./settings":"5blfu","./loadGameData":"l1B4x","./game_utils":"cEeac","./gameStateMutators":"9ZeQl","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"4GEPs":[function(require,module,exports,__globalThis) {
|
||||
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
|
||||
parcelHelpers.defineInteropFlag(exports);
|
||||
parcelHelpers.export(exports, "isPremium", ()=>isPremium);
|
||||
parcelHelpers.export(exports, "premiumMenuEntry", ()=>premiumMenuEntry);
|
||||
var _loadGameData = require("./loadGameData");
|
||||
var _i18N = require("./i18n/i18n");
|
||||
var _settings = require("./settings");
|
||||
var _asyncAlert = require("./asyncAlert");
|
||||
var _game = require("./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) {
|
||||
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, pem) {
|
||||
// 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((0, _settings.getSettingValue)('license', '')).then();
|
||||
async function checkKey(key) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
function isPremium() {
|
||||
return premium;
|
||||
}
|
||||
function premiumMenuEntry(gameState) {
|
||||
if (isPremium()) return {
|
||||
icon: (0, _loadGameData.icons)["icon:adventure_mode"],
|
||||
text: (0, _i18N.t)("premium.adventure_mode"),
|
||||
help: (0, _i18N.t)("premium.adventure_mode_help"),
|
||||
value: async ()=>{
|
||||
if (await (0, _game.confirmRestart)(gameState)) (0, _game.restart)({
|
||||
adventure: true
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
icon: (0, _loadGameData.icons)["icon:premium"],
|
||||
text: (0, _i18N.t)("premium.title"),
|
||||
help: (0, _i18N.t)("premium.short_help"),
|
||||
value: ()=>openPremiumMenu('')
|
||||
};
|
||||
}
|
||||
async function openPremiumMenu(text) {
|
||||
const isGooglePlayInstall = new URLSearchParams(location.search).get('source') === 'com.android.vending';
|
||||
const cb = await (0, _asyncAlert.asyncAlert)({
|
||||
title: (0, _i18N.t)("premium.title"),
|
||||
text: text || isGooglePlayInstall && (0, _i18N.t)("premium.help_google") || (0, _i18N.t)("premium.help"),
|
||||
actions: [
|
||||
{
|
||||
text: (0, _i18N.t)("premium.buy"),
|
||||
disabled: isGooglePlayInstall,
|
||||
help: isGooglePlayInstall ? (0, _i18N.t)("premium.buy_disabled_help") : (0, _i18N.t)("premium.buy_help"),
|
||||
value () {
|
||||
window.open('https://licenses.lecaro.me/buy/price_1R6YaEGRf74lr2EkSo2GPvuO', '_blank');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: (0, _i18N.t)("premium.enter"),
|
||||
help: (0, _i18N.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 {
|
||||
(0, _settings.setSettingValue)('license', value);
|
||||
(0, _game.openMainMenu)().then();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
text: (0, _i18N.t)("premium.back"),
|
||||
help: (0, _i18N.t)("premium.back_help"),
|
||||
value () {
|
||||
(0, _game.openMainMenu)().then();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
if (cb) cb();
|
||||
}
|
||||
|
||||
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./loadGameData":"l1B4x","./i18n/i18n":"eNPRm","./settings":"5blfu","./asyncAlert":"rSqLY","./game":"edeGs"}]},["gVqJ6","67XFf"], "67XFf", "parcelRequire94c2")
|
||||
},{"./settings":"5blfu","./loadGameData":"l1B4x","./game_utils":"cEeac","./gameStateMutators":"9ZeQl","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["gVqJ6","67XFf"], "67XFf", "parcelRequire94c2")
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// The version of the cache.
|
||||
const VERSION = "29048147";
|
||||
const VERSION = "29049575";
|
||||
|
||||
// The name of the cache
|
||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
import {GameState} from "./types";
|
||||
import { GameState } from "./types";
|
||||
|
||||
export async function openAdventureRunUpgradesPicker(gameState: GameState) {
|
||||
let options=3
|
||||
const catchRate =
|
||||
let options = 3;
|
||||
const catchRate =
|
||||
(gameState.score - gameState.levelStartScore) /
|
||||
(gameState.levelSpawnedCoins || 1);
|
||||
|
||||
|
||||
if (gameState.levelWallBounces == 0) {
|
||||
options++;
|
||||
}
|
||||
if (gameState.levelTime < 30 * 1000) {
|
||||
options++
|
||||
options++;
|
||||
}
|
||||
if (catchRate === 1) {
|
||||
options++
|
||||
options++;
|
||||
}
|
||||
if (gameState.levelMisses === 0) {
|
||||
options++
|
||||
options++;
|
||||
}
|
||||
|
||||
const choices = []
|
||||
for( let difficulty=0; difficulty<options;difficulty++){
|
||||
choices.push({
|
||||
|
||||
})
|
||||
const choices = [];
|
||||
for (let difficulty = 0; difficulty < options; difficulty++) {
|
||||
choices.push({});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1053,4 +1053,4 @@
|
|||
"svg": null,
|
||||
"color": ""
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -1 +1 @@
|
|||
"29048147"
|
||||
"29049575"
|
||||
|
|
105
src/game.ts
105
src/game.ts
|
@ -1,4 +1,4 @@
|
|||
import {allLevels, appVersion, icons, upgrades} from "./loadGameData";
|
||||
import { allLevels, appVersion, icons, upgrades } from "./loadGameData";
|
||||
import {
|
||||
Ball,
|
||||
Coin,
|
||||
|
@ -11,11 +11,17 @@ import {
|
|||
TextFlash,
|
||||
Upgrade,
|
||||
} from "./types";
|
||||
import {getAudioContext, playPendingSounds} from "./sounds";
|
||||
import {currentLevelInfo, getRowColIndex, levelsListHTMl, max_levels, pickedUpgradesHTMl,} from "./game_utils";
|
||||
import { getAudioContext, playPendingSounds } from "./sounds";
|
||||
import {
|
||||
currentLevelInfo,
|
||||
getRowColIndex,
|
||||
levelsListHTMl,
|
||||
max_levels,
|
||||
pickedUpgradesHTMl,
|
||||
} from "./game_utils";
|
||||
|
||||
import "./PWA/sw_loader";
|
||||
import {getCurrentLang, t} from "./i18n/i18n";
|
||||
import { getCurrentLang, t } from "./i18n/i18n";
|
||||
import {
|
||||
cycleMaxCoins,
|
||||
cycleMaxParticles,
|
||||
|
@ -34,13 +40,29 @@ import {
|
|||
setLevel,
|
||||
setMousePos,
|
||||
} from "./gameStateMutators";
|
||||
import {backgroundCanvas, ctx, gameCanvas, render, scoreDisplay,} from "./render";
|
||||
import {pauseRecording, recordOneFrame, resumeRecording, startRecordingGame,} from "./recording";
|
||||
import {newGameState} from "./newGameState";
|
||||
import {alertsOpen, asyncAlert, AsyncAlertAction, closeModal,} from "./asyncAlert";
|
||||
import {isOptionOn, options, toggleOption} from "./options";
|
||||
import {hashCode} from "./getLevelBackground";
|
||||
import {premiumMenuEntry} from "./premium";
|
||||
import {
|
||||
backgroundCanvas,
|
||||
ctx,
|
||||
gameCanvas,
|
||||
render,
|
||||
scoreDisplay,
|
||||
} from "./render";
|
||||
import {
|
||||
pauseRecording,
|
||||
recordOneFrame,
|
||||
resumeRecording,
|
||||
startRecordingGame,
|
||||
} from "./recording";
|
||||
import { newGameState } from "./newGameState";
|
||||
import {
|
||||
alertsOpen,
|
||||
asyncAlert,
|
||||
AsyncAlertAction,
|
||||
closeModal,
|
||||
} from "./asyncAlert";
|
||||
import { isOptionOn, options, toggleOption } from "./options";
|
||||
import { hashCode } from "./getLevelBackground";
|
||||
import { premiumMenuEntry } from "./premium";
|
||||
|
||||
export function play() {
|
||||
if (gameState.running) return;
|
||||
|
@ -174,52 +196,50 @@ export async function openShortRunUpgradesPicker(gameState: GameState) {
|
|||
|
||||
if (gameState.levelWallBounces == 0) {
|
||||
repeats++;
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
wallHitsGain = t("level_up.plus_one_upgrade");
|
||||
} else if (gameState.levelWallBounces < 5) {
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
wallHitsGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
if (gameState.levelTime < 30 * 1000) {
|
||||
repeats++;
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
timeGain = t("level_up.plus_one_upgrade");
|
||||
} else if (gameState.levelTime < 60 * 1000) {
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
timeGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
if (catchRate === 1) {
|
||||
repeats++;
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
catchGain = t("level_up.plus_one_upgrade");
|
||||
} else if (catchRate > 0.9) {
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
catchGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
if (gameState.levelMisses === 0) {
|
||||
repeats++;
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
missesGain = t("level_up.plus_one_upgrade");
|
||||
} else if (gameState.levelMisses <= 3) {
|
||||
gameState.rerolls++
|
||||
gameState.rerolls++;
|
||||
missesGain = t("level_up.plus_one_choice");
|
||||
}
|
||||
|
||||
while (repeats--) {
|
||||
const actions = pickRandomUpgrades(
|
||||
gameState,
|
||||
3 +
|
||||
gameState.perks.one_more_choice -
|
||||
gameState.perks.instant_upgrade,
|
||||
3 + gameState.perks.one_more_choice - gameState.perks.instant_upgrade,
|
||||
);
|
||||
|
||||
if(gameState.rerolls){
|
||||
if (gameState.rerolls) {
|
||||
actions.push({
|
||||
text: t("level_up.reroll",{count:gameState.rerolls}),
|
||||
text: t("level_up.reroll", { count: gameState.rerolls }),
|
||||
help: t("level_up.reroll_help"),
|
||||
value: 'reroll',
|
||||
icon: icons['icon:reroll']
|
||||
})
|
||||
value: "reroll",
|
||||
icon: icons["icon:reroll"],
|
||||
});
|
||||
}
|
||||
if (!actions.length) break;
|
||||
let textAfterButtons = `
|
||||
|
@ -242,7 +262,7 @@ export async function openShortRunUpgradesPicker(gameState: GameState) {
|
|||
t("level_up.compliment_good")) ||
|
||||
t("level_up.compliment_advice");
|
||||
|
||||
const upgradeId = (await asyncAlert<PerkId|'reroll'>({
|
||||
const upgradeId = (await asyncAlert<PerkId | "reroll">({
|
||||
title:
|
||||
t("level_up.pick_upgrade_title") +
|
||||
(repeats ? " (" + (repeats + 1) + ")" : ""),
|
||||
|
@ -267,10 +287,10 @@ export async function openShortRunUpgradesPicker(gameState: GameState) {
|
|||
textAfterButtons,
|
||||
})) as PerkId;
|
||||
|
||||
if(upgradeId==='reroll'){
|
||||
repeats++
|
||||
gameState.rerolls--
|
||||
}else{
|
||||
if (upgradeId === "reroll") {
|
||||
repeats++;
|
||||
gameState.rerolls--;
|
||||
} else {
|
||||
gameState.perks[upgradeId]++;
|
||||
if (upgradeId === "instant_upgrade") {
|
||||
repeats += 2;
|
||||
|
@ -280,7 +300,6 @@ export async function openShortRunUpgradesPicker(gameState: GameState) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
gameCanvas.addEventListener("mouseup", (e) => {
|
||||
if (e.button !== 0) return;
|
||||
if (gameState.running) {
|
||||
|
@ -450,7 +469,7 @@ export async function openMainMenu() {
|
|||
|
||||
const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold));
|
||||
const actions: AsyncAlertAction<() => void>[] = [
|
||||
{
|
||||
{
|
||||
icon: icons["icon:7_levels_run"],
|
||||
text: t("main_menu.normal"),
|
||||
help: t("main_menu.normal_help"),
|
||||
|
@ -514,15 +533,7 @@ export async function openMainMenu() {
|
|||
},
|
||||
},
|
||||
|
||||
premiumMenuEntry(gameState)
|
||||
,
|
||||
//
|
||||
// {
|
||||
// icon: icons["icon:continue"],
|
||||
// text: t("main_menu.resume"),
|
||||
// help: t("main_menu.resume_help"),
|
||||
// value() {},
|
||||
// },
|
||||
// premiumMenuEntry(gameState),
|
||||
{
|
||||
text: t("main_menu.settings_title"),
|
||||
help: t("main_menu.settings_help"),
|
||||
|
@ -765,7 +776,11 @@ async function openSettingsMenu() {
|
|||
],
|
||||
allowClose: true,
|
||||
});
|
||||
if (pick && pick !== getCurrentLang() && (await confirmRestart(gameState))) {
|
||||
if (
|
||||
pick &&
|
||||
pick !== getCurrentLang() &&
|
||||
(await confirmRestart(gameState))
|
||||
) {
|
||||
setSettingValue("lang", pick);
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -852,7 +867,7 @@ Click an item above to start a run with it.
|
|||
</p>`,
|
||||
actions,
|
||||
allowClose: true,
|
||||
actionsAsGrid:true
|
||||
actionsAsGrid: true,
|
||||
});
|
||||
if (tryOn) {
|
||||
if (await confirmRestart(gameState)) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -963,7 +963,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -978,7 +978,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1453,7 +1453,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1468,7 +1468,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1483,7 +1483,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1498,7 +1498,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1513,7 +1513,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1528,7 +1528,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1543,7 +1543,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1558,7 +1558,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1573,7 +1573,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1588,7 +1588,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1603,7 +1603,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1618,7 +1618,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
@ -1633,7 +1633,7 @@
|
|||
</translation>
|
||||
<translation>
|
||||
<language>fr-FR</language>
|
||||
<approved>false</approved>
|
||||
<approved>true</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
|
|
|
@ -21,7 +21,7 @@ describe("json data checks", () => {
|
|||
.split("")
|
||||
.filter((b) => b !== "_" && b !== "black")
|
||||
.filter((a, b, c) => c.indexOf(a) === b);
|
||||
return uniqueBricks.length > 5;
|
||||
return uniqueBricks.length > 5 && !l.name.startsWith("icon:");
|
||||
})
|
||||
.map((l) => l.name);
|
||||
expect(levelsWithManyBrickColors).toEqual([]);
|
||||
|
|
|
@ -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,
|
||||
|
@ -101,10 +101,10 @@ export function newGameState(params: RunParams): GameState {
|
|||
autoCleanUses: 0,
|
||||
...defaultSounds(),
|
||||
|
||||
isAdventureMode:!!params?.adventure,
|
||||
adventurePath:'',
|
||||
seed:'Seed'+Math.random(),
|
||||
rerolls:0
|
||||
isAdventureMode: !!params?.adventure,
|
||||
adventurePath: "",
|
||||
seed: "Seed" + Math.random(),
|
||||
rerolls: 0,
|
||||
};
|
||||
resetBalls(gameState);
|
||||
|
||||
|
|
246
src/premium.ts
246
src/premium.ts
|
@ -1,9 +1,9 @@
|
|||
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";
|
||||
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
|
||||
|
@ -18,144 +18,148 @@ RCASTIjXW61E7PQKir5qIXwkQDlzJ+bpZ3PHyAvspRrBaDxIYvEEw14evpuqOgS+
|
|||
v/IlgPe+CWSvZa9xxnQl/aWZrOrD7syu6KKCbgUyXEm+Alp0YT3e6nwjn0qiM/cj
|
||||
dZpWPx3O+rZbRQb0gHcvN4+n2Y7fWAeC9mxVZtADqvVr/GTumMbLj7DdhWtt1Ogu
|
||||
4EcvkQ5SKCL0JC93DyctjOMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
-----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;
|
||||
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}`;
|
||||
// Split the key into its components
|
||||
const [priceId, timestamp, signature] = key.split(":");
|
||||
const data = `${priceId}:${timestamp}`;
|
||||
|
||||
const publicKeyBuffer = pemToArrayBuffer(pem);
|
||||
const publicKeyBuffer = pemToArrayBuffer(pem);
|
||||
|
||||
const publicKey = await crypto.subtle.importKey(
|
||||
"spki",
|
||||
publicKeyBuffer,
|
||||
{
|
||||
name: "RSA-PSS",
|
||||
hash: "SHA-256",
|
||||
},
|
||||
true,
|
||||
["verify"],
|
||||
);
|
||||
|
||||
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");
|
||||
|
||||
|
||||
// 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;
|
||||
return priceId;
|
||||
}
|
||||
|
||||
let premium = false
|
||||
const gamePriceId = 'price_1R6YaEGRf74lr2EkSo2GPvuO'
|
||||
checkKey(getSettingValue('license', '')).then()
|
||||
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
|
||||
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
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
if (isPremium()) {
|
||||
return {
|
||||
icon: icons["icon:premium"],
|
||||
text: t("premium.title"),
|
||||
help: t("premium.short_help"),
|
||||
value: () => openPremiumMenu(''),
|
||||
}
|
||||
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()
|
||||
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 &&
|
||||
|
|
10
src/types.d.ts
vendored
10
src/types.d.ts
vendored
|
@ -266,17 +266,17 @@ export type GameState = {
|
|||
coinCatch: { vol: number; x: number };
|
||||
colorChange: { vol: number; x: number };
|
||||
};
|
||||
isAdventureMode:boolean;
|
||||
adventurePath:string;
|
||||
seed:string;
|
||||
rerolls:number;
|
||||
isAdventureMode: boolean;
|
||||
adventurePath: string;
|
||||
seed: string;
|
||||
rerolls: number;
|
||||
};
|
||||
|
||||
export type RunParams = {
|
||||
level?: string;
|
||||
levelToAvoid?: string;
|
||||
perks?: Partial<PerksMap>;
|
||||
adventure?:boolean;
|
||||
adventure?: boolean;
|
||||
};
|
||||
export type OptionDef = {
|
||||
default: boolean;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue