mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-07-20 10:04:07 +00:00
307 lines
8.7 KiB
TypeScript
307 lines
8.7 KiB
TypeScript
import { GameState, OptionId, PerkId } from "./types";
|
|
import {
|
|
catchRateBest,
|
|
catchRateGood,
|
|
choicePerGold,
|
|
choicePerSilver,
|
|
levelTimeBest,
|
|
levelTimeGood,
|
|
missesBest,
|
|
missesGood,
|
|
upPerGold,
|
|
upPerSilver,
|
|
} from "./pure_functions";
|
|
import { t } from "./i18n/i18n";
|
|
import { icons } from "./loadGameData";
|
|
import { requiredAsyncAlert } from "./asyncAlert";
|
|
import {
|
|
escapeAttribute,
|
|
getPossibleUpgrades,
|
|
levelsListHTMl,
|
|
max_levels,
|
|
pickedUpgradesHTMl,
|
|
renderMaxLevel,
|
|
upgradeLevelAndMaxDisplay,
|
|
} from "./game_utils";
|
|
import { getFirstUnlockable, getNearestUnlockHTML } from "./openScorePanel";
|
|
import { isOptionOn } from "./options";
|
|
import { getWorstFPSAndReset } from "./fps";
|
|
import {
|
|
getCurrentMaxCoins,
|
|
getSettingValue,
|
|
setSettingValue,
|
|
} from "./settings";
|
|
import { toast } from "./toast";
|
|
|
|
export async function openUpgradesPicker(gameState: GameState) {
|
|
if (gameState.perks.chill) return;
|
|
const catchRate =
|
|
gameState.levelCoughtCoins / (gameState.levelSpawnedCoins || 1);
|
|
|
|
let medals = [];
|
|
let upgradePoints = 1;
|
|
let extraChoices = 0;
|
|
|
|
let hasMedals = 0;
|
|
|
|
function challengeResult(
|
|
name: String,
|
|
description: String,
|
|
medal: "gold" | "silver" | "no",
|
|
) {
|
|
let choices = 0,
|
|
up = 0;
|
|
if (medal === "gold") {
|
|
choices += choicePerGold;
|
|
up += upPerGold;
|
|
} else if (medal === "silver") {
|
|
choices += choicePerSilver;
|
|
up += upPerSilver;
|
|
}
|
|
if (medal !== "no") {
|
|
hasMedals++;
|
|
}
|
|
|
|
extraChoices += choices;
|
|
upgradePoints += up;
|
|
medals.push(`<div class="upgrade" data-tooltip="${escapeAttribute(description)}">
|
|
${icons["icon:" + medal + "_medal"]}
|
|
<p>
|
|
<strong>${name}</strong><br/>
|
|
${
|
|
up || choices
|
|
? t("level_up.challenges.gain", { up, choices })
|
|
: t("level_up.challenges.no_gain")
|
|
}
|
|
|
|
</p>
|
|
</div>`);
|
|
}
|
|
|
|
challengeResult(
|
|
t("level_up.challenges.levelTime.name", {
|
|
value: Math.ceil(gameState.levelTime / 1000),
|
|
}),
|
|
t("level_up.challenges.levelTime.description", {
|
|
silver: levelTimeGood,
|
|
gold: levelTimeBest,
|
|
}),
|
|
(gameState.levelTime < levelTimeBest * 1000 && "gold") ||
|
|
(gameState.levelTime < levelTimeGood * 1000 && "silver") ||
|
|
"no",
|
|
);
|
|
|
|
challengeResult(
|
|
t("level_up.challenges.catchRateGood.name", {
|
|
value: Math.floor(catchRate * 100),
|
|
caught: gameState.levelCoughtCoins,
|
|
total: gameState.levelSpawnedCoins,
|
|
}),
|
|
t("level_up.challenges.catchRateGood.description", {
|
|
silver: catchRateGood,
|
|
gold: catchRateBest,
|
|
}),
|
|
(catchRate > catchRateBest / 100 && "gold") ||
|
|
(catchRate > catchRateGood / 100 && "silver") ||
|
|
"no",
|
|
);
|
|
|
|
challengeResult(
|
|
gameState.levelMisses
|
|
? t("level_up.challenges.levelMisses.name", {
|
|
value: gameState.levelMisses,
|
|
})
|
|
: t("level_up.challenges.levelMisses.none"),
|
|
t("level_up.challenges.levelMisses.description", {
|
|
silver: missesGood,
|
|
gold: missesBest,
|
|
}),
|
|
(gameState.levelMisses < missesBest && "gold") ||
|
|
(gameState.levelMisses < missesGood && "silver") ||
|
|
"no",
|
|
);
|
|
|
|
if (hasMedals == 0) {
|
|
medals.length = 0;
|
|
} else if (hasMedals == 1) {
|
|
medals.unshift(t("level_up.challenges.earned_medal", { count: hasMedals }));
|
|
} else {
|
|
medals.unshift(
|
|
t("level_up.challenges.earned_medal_plural", { count: hasMedals }),
|
|
);
|
|
}
|
|
|
|
let sorted = getPossibleUpgrades(gameState)
|
|
.map((u) => ({
|
|
...u,
|
|
score: Math.random() + (gameState.lastOffered[u.id] || 0),
|
|
}))
|
|
.sort((a, b) => a.score - b.score)
|
|
.filter((u) => gameState.perks[u.id] < u.max + gameState.perks.limitless);
|
|
let recommendation = settingsChangeRecommendations();
|
|
while (true && !gameState.perks.chill) {
|
|
// refresh the list if you pick extra one_more_choice
|
|
const offered = sorted.slice(
|
|
0,
|
|
3 + extraChoices + gameState.perks.one_more_choice,
|
|
);
|
|
offered.forEach((u) => {
|
|
dontOfferTooSoon(gameState, u.id);
|
|
});
|
|
|
|
const unlockable = getFirstUnlockable(gameState);
|
|
let unlockRelatedUpgradesOffered = 0;
|
|
|
|
const upgradesActions = offered.map((u) => {
|
|
let unlockHint = "";
|
|
let className = "";
|
|
if (isOptionOn("level_unlocks_hints")) {
|
|
if (unlockable?.forbidden?.includes(u.id) && !gameState.perks[u.id]) {
|
|
unlockRelatedUpgradesOffered++;
|
|
className += " forbidden";
|
|
unlockHint = t("level_up.forbidden", {
|
|
levelName: unlockable?.l.name || "",
|
|
});
|
|
}
|
|
if (unlockable?.required?.includes(u.id)) {
|
|
unlockRelatedUpgradesOffered++;
|
|
className += " required";
|
|
unlockHint = t("level_up.required", {
|
|
levelName: unlockable?.l.name || "",
|
|
});
|
|
}
|
|
}
|
|
return {
|
|
value: u.id,
|
|
disabled: gameState.perks[u.id] >= u.max + gameState.perks.limitless,
|
|
text:
|
|
u.name +
|
|
(gameState.perks[u.id]
|
|
? upgradeLevelAndMaxDisplay(u, gameState)
|
|
: ""),
|
|
icon: icons["icon:" + u.id],
|
|
help: u.help(gameState.perks[u.id] || 1),
|
|
tooltip: unlockHint + u.fullHelp(gameState.perks[u.id] || 1),
|
|
className,
|
|
actionLabel: gameState.perks[u.id] ? "upgrade" : "pick",
|
|
};
|
|
});
|
|
|
|
const choice = await requiredAsyncAlert<
|
|
PerkId | { changeSettings: Record<string, any> }
|
|
>({
|
|
title: t("level_up.title", {
|
|
level: gameState.currentLevel,
|
|
max: renderMaxLevel(gameState),
|
|
}),
|
|
content: [
|
|
t("level_up.upgrade_perks", {
|
|
coins: gameState.levelCoughtCoins,
|
|
count: upgradePoints,
|
|
}),
|
|
...upgradesActions,
|
|
levelsListHTMl(gameState, gameState.currentLevel),
|
|
unlockRelatedUpgradesOffered ? getNearestUnlockHTML(gameState) : "",
|
|
recommendation,
|
|
...medals,
|
|
pickedUpgradesHTMl(gameState),
|
|
`<div id="level-recording-container"></div>`,
|
|
],
|
|
});
|
|
|
|
if (applySettingsChangeReco(choice)) {
|
|
recommendation = "";
|
|
} else {
|
|
upgradePoints--;
|
|
gameState.perks[choice]++;
|
|
gameState.runStatistics.upgrades_picked++;
|
|
if (!upgradePoints) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function dontOfferTooSoon(gameState: GameState, id: PerkId) {
|
|
gameState.lastOffered[id] = Math.round(Date.now() / 1000);
|
|
}
|
|
|
|
export function applySettingsChangeReco(choice: unknown) {
|
|
if (!choice) return;
|
|
if (typeof choice == "object" && "changeSettings" in choice) {
|
|
for (let key in choice.changeSettings) {
|
|
setSettingValue(key, choice.changeSettings[key]);
|
|
}
|
|
toast(t("settings.suggestions.applied"));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
export function settingsChangeRecommendations() {
|
|
const { worstFPS, coinsForLag } = getWorstFPSAndReset();
|
|
const maxCoinsSetting = getSettingValue("max_coins", 2);
|
|
|
|
if (worstFPS > 55) return "";
|
|
if (coinsForLag > 200 && getCurrentMaxCoins() > 200) {
|
|
// Limit the coins
|
|
const limit = Math.floor(Math.log2(coinsForLag / 200));
|
|
if (limit < maxCoinsSetting) {
|
|
return {
|
|
icon: icons["icon:slow"],
|
|
text: t("settings.suggestions.reduce_coins", {
|
|
max: Math.pow(2, limit) * 200,
|
|
}),
|
|
value: {
|
|
changeSettings: { max_coins: limit },
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
if (isOptionOn("record"))
|
|
return {
|
|
icon: icons["icon:slow"],
|
|
text: t("settings.suggestions.record"),
|
|
value: {
|
|
changeSettings: { "breakout-settings-enable-record": false },
|
|
},
|
|
};
|
|
|
|
if (isOptionOn("basic")) return "";
|
|
|
|
if (
|
|
isOptionOn("smooth_lighting") ||
|
|
isOptionOn("precise_lighting") ||
|
|
!isOptionOn("probabilistic_lighting") ||
|
|
isOptionOn("contrast")
|
|
)
|
|
return {
|
|
icon: icons["icon:slow"],
|
|
text: t("settings.suggestions.simpler_lights"),
|
|
value: {
|
|
changeSettings: {
|
|
"breakout-settings-enable-smooth_lighting": false,
|
|
"breakout-settings-enable-precise_lighting": false,
|
|
"breakout-settings-enable-probabilistic_lighting": true,
|
|
"breakout-settings-enable-contrast": false,
|
|
},
|
|
},
|
|
};
|
|
|
|
if (isOptionOn("extra_bright"))
|
|
return {
|
|
icon: icons["icon:slow"],
|
|
text: t("settings.suggestions.reduce_brightness"),
|
|
value: {
|
|
changeSettings: { "breakout-settings-enable-extra_bright": false },
|
|
},
|
|
};
|
|
|
|
return {
|
|
icon: icons["icon:slow"],
|
|
text: t("settings.suggestions.basic_mode"),
|
|
value: {
|
|
changeSettings: { "breakout-settings-enable-basic": true },
|
|
},
|
|
};
|
|
}
|