Build 29068563

This commit is contained in:
Renan LE CARO 2025-04-08 14:03:38 +02:00
parent 6ef13f2d19
commit df8bfbb350
25 changed files with 384 additions and 336 deletions

View file

@ -39,11 +39,13 @@ New players get confused as to which upgrades they have and why a side became re
## To do
- As soon as level condition is reached, lock it in and tell the user
- change fortunate ball to work more like coin magnet, carrying the balls around to catch them at next puck bounce
## Done
- review the "next unlocks" in score and game over
- As soon as upgrade condition is reached, toast
- As soon as level condition is reached, lock it in and tell the user
- extra life only saves your last ball, max 7 instead of 3
- Don't use "RAZ" in French explanations.
- explain ghost coin's slow down effect

View file

@ -29,8 +29,8 @@ android {
applicationId = "me.lecaro.breakout"
minSdk = 21
targetSdk = 34
versionCode = 29068232
versionName = "29068232"
versionCode = 29068563
versionName = "29068563"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true

File diff suppressed because one or more lines are too long

217
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
// The version of the cache.
const VERSION = "29068232";
const VERSION = "29068563";
// The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`;

25
src/addToTotalScore.ts Normal file
View file

@ -0,0 +1,25 @@
import { GameState } from "./types";
import { icons, upgrades } from "./loadGameData";
import { schedulGameSound } from "./gameStateMutators";
import { toast } from "./toast";
import { t } from "./i18n/i18n";
import { getTotalScore, setSettingValue } from "./settings";
export function addToTotalScore(gameState: GameState, points: number) {
if (gameState.creative) return;
const pastScore = getTotalScore();
const newScore = pastScore + points;
setSettingValue("breakout_71_total_score", newScore);
// Check unlocked upgrades
upgrades.forEach((u) => {
if (u.threshold > pastScore && u.threshold <= newScore) {
schedulGameSound(gameState, "colorChange", 0, 1);
toast(
icons["icon:" + u.id] +
"<strong>" +
t("gameOver.unlocked_perk") +
"</strong>",
);
}
});
}

View file

@ -442,8 +442,8 @@
},
{
"name": "icon:base_combo",
"size": 5,
"bricks": "ttttttytytttttttytytttttt",
"size": 7,
"bricks": "________bbbbb__bybyb__bbbbb__bybyb__bbbbb________",
"svg": null
},
{

View file

@ -1 +1 @@
"29068232"
"29068563"

View file

@ -513,34 +513,31 @@ h2.histogram-title strong {
}
}
.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;
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;
animation: toast forwards;
}
@keyframes toast {
0%{
0%,
100% {
opacity: 0;
transform:translate(-20px, 0);
transform: translate(-20px, -20px) scale(0.5);
}
10%,90%{
10%,
90% {
opacity: 0.8;
transform: none;
}
100%{
opacity: 0;
transform:translate(20px, 0);
}
}
}

View file

@ -74,7 +74,7 @@ import { getHistory } from "./gameOver";
import { generateSaveFileContent } from "./generateSaveFileContent";
import { runHistoryViewerMenuEntry } from "./runHistoryViewer";
import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
import {monitorLevelsUnlocks} from "./monitorLevelsUnlocks";
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
export async function play() {
if (await applyFullScreenChoice()) return;
@ -298,8 +298,8 @@ export async function openUpgradesPicker(gameState: GameState) {
`,
...actions,
getNearestUnlockHTML(gameState),
pickedUpgradesHTMl(gameState),
getNearestUnlockHTML(gameState),
`<div id="level-recording-container"></div>`,
],
@ -424,7 +424,7 @@ setInterval(() => {
}, 1000);
setInterval(() => {
monitorLevelsUnlocks(gameState)
monitorLevelsUnlocks(gameState);
}, 500);
window.addEventListener("visibilitychange", () => {

View file

@ -31,11 +31,7 @@ import {
import { t } from "./i18n/i18n";
import { icons } from "./loadGameData";
import {
addToTotalScore,
getCurrentMaxCoins,
getCurrentMaxParticles,
} from "./settings";
import { getCurrentMaxCoins, getCurrentMaxParticles } from "./settings";
import { background } from "./render";
import { gameOver } from "./gameOver";
import {
@ -50,6 +46,7 @@ import {
import { stopRecording } from "./recording";
import { isOptionOn } from "./options";
import { clamp, comboKeepingRate } from "./pure_functions";
import { addToTotalScore } from "./addToTotalScore";
export function setMousePos(gameState: GameState, x: number) {
gameState.puckPosition = x;
@ -1508,7 +1505,8 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
ball.y > ylimit &&
ball.vy > 0 &&
(ballIsUnderPuck ||
(gameState.balls.length<2 && gameState.perks.extra_life &&
(gameState.balls.length < 2 &&
gameState.perks.extra_life &&
ball.y > ylimit + gameState.puckHeight / 2))
) {
if (ballIsUnderPuck) {

View file

@ -1,9 +1,18 @@
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";
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";
import { getTotalScore } from "./settings";
export function describeLevel(level: Level) {
let bricks = 0,
@ -74,7 +83,7 @@ export function getRowColIndex(gameState: GameState, row: number, col: number) {
export function getPossibleUpgrades(gameState: GameState) {
return upgrades
.filter((u) => gameState.totalScoreAtRunStart >= u.threshold)
.filter((u) => getTotalScore() >= u.threshold)
.filter((u) => !u?.requires || gameState.perks[u?.requires]);
}
@ -272,8 +281,8 @@ export function highScoreText() {
}
let excluded: Set<PerkId>;
function isExcluded(id:PerkId){
if(!excluded) {
function isExcluded(id: PerkId) {
if (!excluded) {
excluded = new Set([
"extra_levels",
"extra_life",
@ -287,7 +296,7 @@ function isExcluded(id:PerkId){
if (u.requires) excluded.add(u.requires);
});
}
return excluded.has(id)
return excluded.has(id);
}
export function getLevelUnlockCondition(levelIndex: number) {
@ -297,7 +306,6 @@ export function getLevelUnlockCondition(levelIndex: number) {
minScore = Math.max(-1000 + 100 * levelIndex, 0);
if (levelIndex > 20) {
const possibletargets = rawUpgrades
.slice(0, Math.floor(levelIndex / 2))
.map((u) => u)

View file

@ -2262,21 +2262,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>continue_to_unlock</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>get_upgrades_to_unlock</name>
<description/>
@ -2337,21 +2322,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>title_looped</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>upcoming_levels</name>
<description/>

View file

@ -20,7 +20,7 @@
"gameOver.stats.level_reached": "Level reached",
"gameOver.stats.total_score": "Total score",
"gameOver.stats.upgrades_applied": "Upgrades applied",
"gameOver.unlocked_perk": "You just unlocked a perk",
"gameOver.unlocked_perk": "Upgrade unlocked",
"gameOver.unlocked_perk_plural": "You just unlocked {{count}} perks",
"gameOver.win.summary": "This game is over. You stashed {{score}} coins. ",
"gameOver.win.title": "You completed this game",
@ -143,13 +143,11 @@
"play.stats.levelMisses": "Missed shots, where you hit nothing",
"play.stats.levelTime": "Level time",
"play.stats.levelWallBounces": "Wall bounces",
"score_panel.close_to_unlock": "You could unlock a level at the end of this run:",
"score_panel.continue_to_unlock": "You are about to unlock level \"{{level}}\"",
"score_panel.close_to_unlock": "Next level unlock :",
"score_panel.get_upgrades_to_unlock": "Get {{missingUpgrades}} and score {{points}} more points to unlock level \"{{level}}\"",
"score_panel.rerolls_count": "You have accumulated {{rerolls}} rerolls",
"score_panel.score_to_unlock": "Score {{points}} more points to unlock level \"{{level}}\"",
"score_panel.title": "{{score}} points at level {{level}}/{{max}} ",
"score_panel.title_looped": "{{score}} points at level {{level}}/{{max}} of loop {{loop}}",
"score_panel.upcoming_levels": "Upcoming levels :",
"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.",

View file

@ -20,7 +20,7 @@
"gameOver.stats.level_reached": "Niveau atteint",
"gameOver.stats.total_score": "Score total",
"gameOver.stats.upgrades_applied": "Mises à jour appliquées",
"gameOver.unlocked_perk": "Vous avez débloqué une amélioration",
"gameOver.unlocked_perk": "Amélioration débloquée",
"gameOver.unlocked_perk_plural": "Vous avez débloqué {{count}} améliorations",
"gameOver.win.summary": "Cette partie est terminée. Vous avez accumulé {{score}} pièces. ",
"gameOver.win.title": "Vous avez terminé cette partie",
@ -143,13 +143,11 @@
"play.stats.levelMisses": "Tirs ratés, ou vous n'avez touché aucune brique",
"play.stats.levelTime": "Durée du niveau",
"play.stats.levelWallBounces": "Rebonds sur les murs",
"score_panel.close_to_unlock": "Vous pourriez débloquer un niveau à la fin de cette partie :",
"score_panel.continue_to_unlock": "Vous êtes sur le point de débloquer le niveau « {{level}} »",
"score_panel.get_upgrades_to_unlock": "Obtenez {{missingUpgrades}} et marquez {{points}} points supplémentaires pour débloquer le niveau « {{level}} »",
"score_panel.close_to_unlock": "Prochain niveau débloqué : ",
"score_panel.get_upgrades_to_unlock": "Obtenez {{missingUpgrades}} et attrapez {{points}} pièces supplémentaires pour débloquer le niveau « {{level}} »",
"score_panel.rerolls_count": "Vous avez accumulé {{rerolls}} rerolls",
"score_panel.score_to_unlock": "Marquez {{points}} points supplémentaires pour débloquer le niveau « {{level}} »",
"score_panel.score_to_unlock": "Attrapez {{points}} pièces supplémentaires pour débloquer le niveau « {{level}} »",
"score_panel.title": "{{score}} points au niveau {{level}}/{{max}} ",
"score_panel.title_looped": "{{score}} points au niveau {{level}}/{{max}} ",
"score_panel.upcoming_levels": "Niveaux de la parties : ",
"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.",

View file

@ -2,8 +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";
import { getLevelUnlockCondition, reasonLevelIsLocked } from "./game_utils";
import { allLevels } from "./loadGameData";
// The page will be reloaded if any migrations were run
let migrationsRun = 0;
@ -19,16 +19,16 @@ 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 = "";
}
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, () => {
@ -103,25 +103,27 @@ migrate("compact_runs_data", () => {
localStorage.setItem("breakout_71_runs_history", JSON.stringify(runsHistory));
});
migrate("set_breakout_71_unlocked_levels"+_appVersion, () => {
migrate("set_breakout_71_unlocked_levels" + _appVersion, () => {
// We want to lock any level unlocked by an app upgrade too
let runsHistory = JSON.parse(
let runsHistory = JSON.parse(
localStorage.getItem("breakout_71_runs_history") || "[]",
) as RunHistoryItem[];
let breakout_71_unlocked_levels = JSON.parse(
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)
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));
});
localStorage.setItem(
"breakout_71_unlocked_levels",
JSON.stringify(breakout_71_unlocked_levels),
);
});
afterMigration()
afterMigration();

View file

@ -1,41 +1,50 @@
import {GameState, UpgradeLike} from "./types";
import {getSettingValue, setSettingValue} from "./settings";
import {allLevels, icons} from "./loadGameData";
import { getLevelUnlockCondition} from "./game_utils";
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";
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[])
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;
export function monitorLevelsUnlocks(gameState: GameState) {
if (gameState.creative) return;
if(!list){
list=allLevels.map((l,li)=>({
name:l.name,li,l,
...getLevelUnlockCondition(li)
}))
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]),
);
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)
})
}
toast(icons[name] + "<strong>" + t("unlocks.just_unlocked") + "</strong>");
schedulGameSound(gameState, "colorChange", 0, 1);
});
}

View file

@ -42,14 +42,13 @@ 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) => totalScoreAtRunStart >= u.threshold && isStartingPerk(u),
(u) => getTotalScore() >= u.threshold && isStartingPerk(u),
);
randomGift =
@ -108,7 +107,6 @@ export function newGameState(params: RunParams): GameState {
coinSize: 14,
puckHeight: 20,
totalScoreAtRunStart,
pauseUsesDuringRun: 0,
keyboardPuckSpeed: 0,
lastTick: performance.now(),

View file

@ -11,6 +11,8 @@ import {
import { getCreativeModeWarning, getHistory } from "./gameOver";
import { pause } from "./game";
import { allLevels, icons } from "./loadGameData";
import { firstWhere } from "./pure_functions";
import { getSettingValue, getTotalScore } from "./settings";
export async function openScorePanel(gameState: GameState) {
pause(true);
@ -37,31 +39,34 @@ export async function openScorePanel(gameState: GameState) {
export function getNearestUnlockHTML(gameState: GameState) {
if (gameState.creative) return "";
const unlockable = allLevels
.map((l, li) => {
const { minScore, forbidden, required } = getLevelUnlockCondition(li);
return {
l,
li,
minScore,
forbidden,
required,
missing: required.filter((u) => !gameState?.perks?.[u.id]),
reason: reasonLevelIsLocked(li, getHistory(), false),
};
})
.filter(
({ reason, forbidden, missing }) =>
// Level needs to be locked
reason &&
// we can't have a forbidden perk
!forbidden.find((u) => gameState?.perks?.[u.id]) &&
// All required upgrades need to be unlocked
!missing.find((u) => u.threshold > gameState.totalScoreAtRunStart),
);
const unlocked = new Set(getSettingValue("breakout_71_unlocked_levels", []));
const firstUnlockable = firstWhere(allLevels, (l, li) => {
if (unlocked.has(l.name)) return;
const reason = reasonLevelIsLocked(li, getHistory(), false);
if (!reason) return;
const firstUnlockable =
unlockable.find(({ missing }) => !missing.length) || unlockable[0];
const { minScore, forbidden, required } = getLevelUnlockCondition(li);
const missing = required.filter((u) => !gameState?.perks?.[u.id]);
// we can't have a forbidden perk
if (forbidden.find((u) => gameState?.perks?.[u.id])) {
return;
}
// All required upgrades need to be unlocked
if (missing.find((u) => u.threshold > getTotalScore())) {
return;
}
return {
l,
li,
minScore,
forbidden,
required,
missing,
reason,
};
});
if (!firstUnlockable) return "";
let missingPoints = firstUnlockable.minScore - gameState.score;
@ -74,12 +79,8 @@ export function getNearestUnlockHTML(gameState: GameState) {
points: missingPoints,
level: firstUnlockable.l.name,
})) ||
(missingPoints > 0 &&
t("score_panel.score_to_unlock", {
points: missingPoints,
level: firstUnlockable.l.name,
})) ||
t("score_panel.continue_to_unlock", {
t("score_panel.score_to_unlock", {
points: missingPoints,
level: firstUnlockable.l.name,
});

View file

@ -65,3 +65,13 @@ export function miniMarkDown(md: string) {
)
.join("\n");
}
export function firstWhere<Input, Output>(
arr: Input[],
mapper: (item: Input, index: number) => Output | undefined,
): Output | undefined {
for (let i = 0; i < arr.length; i++) {
const result = mapper(arr[i], i);
if (typeof result !== "undefined") return result;
}
}

View file

@ -1,7 +1,5 @@
// Settings
import { GameState } from "./types";
let cachedSettings: { [key: string]: unknown } = {};
export function getSettingValue<T>(key: string, defaultValue: T) {
@ -29,11 +27,6 @@ export function getTotalScore() {
return getSettingValue("breakout_71_total_score", 0);
}
export function addToTotalScore(gameState: GameState, points: number) {
if (!gameState.creative)
setSettingValue("breakout_71_total_score", getTotalScore() + points);
}
export function getCurrentMaxCoins() {
return Math.pow(2, getSettingValue("max_coins", 1)) * 200;
}

View file

@ -17,7 +17,6 @@ export function startingPerkMenuButton() {
};
}
export function isStartingPerk(u: Upgrade): boolean {
return getSettingValue("start_with_" + u.id, u.giftable);
}

View file

@ -1,8 +1,17 @@
export function toast(html){
const div =document.createElement('div')
div.classList='toast'
div.innerHTML=html
document.body.appendChild(div)
// TOast
// setTimeout(()=>div.remove() , 500 )
}
let onScreen = 0;
export function toast(html) {
const div = document.createElement("div");
div.classList = "toast";
div.innerHTML = html;
const lasts = 1500 + onScreen * 200;
div.style.animationDuration = lasts + "ms";
div.style.top = 40 + onScreen * 50 + "px";
document.body.appendChild(div);
onScreen++;
setTimeout(() => {
div.remove();
onScreen--;
}, lasts);
}

12
src/types.d.ts vendored
View file

@ -1,5 +1,5 @@
import {rawUpgrades} from "./upgrades";
import {options} from "./options";
import { rawUpgrades } from "./upgrades";
import { options } from "./options";
export type colorString = string;
@ -258,7 +258,6 @@ export type GameState = {
ballSize: number;
coinSize: number;
puckHeight: number;
totalScoreAtRunStart: number;
pauseUsesDuringRun: number;
keyboardPuckSpeed: number;
lastTick: number;
@ -296,4 +295,9 @@ export type OptionDef = {
help: string;
};
export type OptionId = keyof typeof options;
export type UpgradeLike = { id: PerkId; name: string; requires: string };
export type UpgradeLike = {
id: PerkId;
name: string;
requires: string;
threshold: number;
};

View file

@ -17,20 +17,9 @@ export const rawUpgrades = [
: t("upgrades.extra_life.help_plural", { lvl }),
fullHelp: t("upgrades.extra_life.fullHelp"),
},
{
requires: "",
threshold: 0,
id: "streak_shots",
giftable: true,
max: 1,
name: t("upgrades.streak_shots.name"),
help: (lvl: number) => t("upgrades.streak_shots.help", { lvl }),
fullHelp: t("upgrades.streak_shots.fullHelp"),
},
{
requires: "",
threshold: 0,
id: "base_combo",
giftable: true,
@ -42,7 +31,6 @@ export const rawUpgrades = [
},
{
requires: "",
threshold: 0,
giftable: false,
id: "slow_down",
@ -53,7 +41,6 @@ export const rawUpgrades = [
},
{
requires: "",
threshold: 0,
giftable: false,
id: "bigger_puck",
@ -64,7 +51,6 @@ export const rawUpgrades = [
},
{
requires: "",
threshold: 0,
giftable: false,
id: "viscosity",
@ -76,8 +62,32 @@ export const rawUpgrades = [
},
{
requires: "",
threshold: 50,
giftable: false,
id: "skip_last",
max: 7,
name: t("upgrades.skip_last.name"),
help: (lvl: number) =>
lvl == 1
? t("upgrades.skip_last.help")
: t("upgrades.skip_last.help_plural", { lvl }),
fullHelp: t("upgrades.skip_last.fullHelp"),
},
{
requires: "",
threshold: 100,
id: "streak_shots",
giftable: true,
max: 1,
name: t("upgrades.streak_shots.name"),
help: (lvl: number) => t("upgrades.streak_shots.help", { lvl }),
fullHelp: t("upgrades.streak_shots.fullHelp"),
},
threshold: 0,
{
requires: "",
threshold: 200,
id: "left_is_lava",
giftable: true,
max: 1,
@ -89,7 +99,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 0,
threshold: 300,
id: "right_is_lava",
giftable: true,
max: 1,
@ -100,7 +110,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 0,
threshold: 400,
id: "top_is_lava",
giftable: true,
max: 1,
@ -111,20 +121,6 @@ export const rawUpgrades = [
{
requires: "",
threshold: 0,
giftable: false,
id: "skip_last",
max: 7,
name: t("upgrades.skip_last.name"),
help: (lvl: number) =>
lvl == 1
? t("upgrades.skip_last.help")
: t("upgrades.skip_last.help_plural", { lvl }),
fullHelp: t("upgrades.skip_last.fullHelp"),
},
{
requires: "",
threshold: 500,
id: "telekinesis",
giftable: true,
@ -138,8 +134,7 @@ export const rawUpgrades = [
},
{
requires: "",
threshold: 1000,
threshold: 700,
giftable: false,
id: "coin_magnet",
max: 3,
@ -153,7 +148,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 1500,
threshold: 800,
id: "multiball",
giftable: true,
max: 6,
@ -164,7 +159,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 2000,
threshold: 1000,
giftable: false,
id: "smaller_puck",
max: 2,
@ -177,8 +172,7 @@ export const rawUpgrades = [
},
{
requires: "",
threshold: 3000,
threshold: 1500,
id: "pierce",
giftable: false,
max: 3,
@ -189,7 +183,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 4000,
threshold: 2000,
id: "picky_eater",
giftable: true,
max: 1,
@ -200,7 +194,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 5000,
threshold: 2500,
giftable: false,
id: "metamorphosis",
max: 1,
@ -211,7 +205,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 6000,
threshold: 3000,
id: "compound_interest",
giftable: true,
max: 1,
@ -221,7 +215,7 @@ export const rawUpgrades = [
},
{
requires: "",
threshold: 7000,
threshold: 4000,
id: "hot_start",
giftable: true,
max: 3,
@ -236,7 +230,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 9000,
threshold: 6000,
id: "sapper",
giftable: false,
max: 7,
@ -250,7 +244,7 @@ export const rawUpgrades = [
{
requires: "",
threshold: 11000,
threshold: 9000,
id: "bigger_explosions",
giftable: false,
max: 1,