mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-29 16:29:13 -04:00
wip
This commit is contained in:
parent
bcf40fe667
commit
096f7d4abd
13 changed files with 1917 additions and 225 deletions
204
dist/index.html
vendored
204
dist/index.html
vendored
File diff suppressed because one or more lines are too long
|
@ -12,12 +12,12 @@ import { asyncAlert, requiredAsyncAlert } from "./asyncAlert";
|
||||||
import {
|
import {
|
||||||
describeLevel,
|
describeLevel,
|
||||||
highScoreText,
|
highScoreText,
|
||||||
reasonLevelIsLocked,
|
|
||||||
sumOfValues,
|
sumOfValues,
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import { getHistory } from "./gameOver";
|
import { getHistory } from "./gameOver";
|
||||||
import { noCreative } from "./upgrades";
|
import { noCreative } from "./upgrades";
|
||||||
import { levelIconHTML } from "./levelIcon";
|
import { levelIconHTML } from "./levelIcon";
|
||||||
|
import {reasonLevelIsLocked} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export function creativeMode(gameState: GameState) {
|
export function creativeMode(gameState: GameState) {
|
||||||
return {
|
return {
|
||||||
|
@ -46,7 +46,7 @@ export async function openCreativeModePerksPicker() {
|
||||||
while (true) {
|
while (true) {
|
||||||
const levelOptions = [
|
const levelOptions = [
|
||||||
...allLevels.map((l, li) => {
|
...allLevels.map((l, li) => {
|
||||||
const problem = reasonLevelIsLocked(li, getHistory(), true)?.text || "";
|
const problem = reasonLevelIsLocked(li, l.name,getHistory(), true)?.text || "";
|
||||||
return {
|
return {
|
||||||
icon: icons[l.name],
|
icon: icons[l.name],
|
||||||
text: l.name,
|
text: l.name,
|
||||||
|
|
1605
src/data/unlockConditions.json
Normal file
1605
src/data/unlockConditions.json
Normal file
File diff suppressed because it is too large
Load diff
38
src/data/unlockConditions.test.ts
Normal file
38
src/data/unlockConditions.test.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import conditions from "./unlockConditions.json"
|
||||||
|
import levels from "./levels.json"
|
||||||
|
import {rawUpgrades} from "../upgrades";
|
||||||
|
import {getLevelUnlockCondition} from "../get_level_unlock_condition";
|
||||||
|
import {UnlockCondition} from "../types";
|
||||||
|
|
||||||
|
describe("conditions", () => {
|
||||||
|
it("defines conditions for existing levels only", () => {
|
||||||
|
const conditionForMissingLevel=Object.keys(conditions).filter(levelName=>!levels.find(l=>l.name===levelName))
|
||||||
|
expect(conditionForMissingLevel).toEqual([]);
|
||||||
|
});
|
||||||
|
it("defines conditions with existing upgrades only", () => {
|
||||||
|
|
||||||
|
const existingIds :Set<string>= new Set(rawUpgrades.map(u=>u.id));
|
||||||
|
const missing:Set<string>=new Set();
|
||||||
|
Object.values(conditions).forEach(({required,forbidden})=>{
|
||||||
|
[...required,...forbidden].forEach(id=> {
|
||||||
|
if(!existingIds.has(id))
|
||||||
|
missing.add(id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
expect([...missing]).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defines conditions for all levels", () => {
|
||||||
|
const toAdd : Record<string,UnlockCondition>= {}
|
||||||
|
levels.filter(l=>!l.name.startsWith('icon:')).forEach((l,li)=> {
|
||||||
|
if(l.name in conditions) return
|
||||||
|
toAdd[l.name]= getLevelUnlockCondition(li, l.name)
|
||||||
|
})
|
||||||
|
if(Object.keys(toAdd).length){
|
||||||
|
console.debug('Missing hardcoded conditons\n\n'+ JSON.stringify(toAdd).slice(1,-1)+'\n\n')
|
||||||
|
}
|
||||||
|
expect(Object.keys(toAdd)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
|
@ -27,7 +27,6 @@ import {
|
||||||
levelsListHTMl,
|
levelsListHTMl,
|
||||||
max_levels,
|
max_levels,
|
||||||
pickedUpgradesHTMl,
|
pickedUpgradesHTMl,
|
||||||
reasonLevelIsLocked,
|
|
||||||
sample,
|
sample,
|
||||||
sumOfValues,
|
sumOfValues,
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
|
@ -99,6 +98,7 @@ import { getNearestUnlockHTML, openScorePanel } from "./openScorePanel";
|
||||||
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
|
import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
|
||||||
import { levelEditorMenuEntry } from "./levelEditor";
|
import { levelEditorMenuEntry } from "./levelEditor";
|
||||||
import { categories } from "./upgrades";
|
import { categories } from "./upgrades";
|
||||||
|
import {reasonLevelIsLocked} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export async function play() {
|
export async function play() {
|
||||||
if (await applyFullScreenChoice()) return;
|
if (await applyFullScreenChoice()) return;
|
||||||
|
@ -967,7 +967,7 @@ async function openUnlockedLevelsList() {
|
||||||
const levelActions = allLevels.map((l, li) => {
|
const levelActions = allLevels.map((l, li) => {
|
||||||
const lockedBecause = unlockedBefore.has(l.name)
|
const lockedBecause = unlockedBefore.has(l.name)
|
||||||
? null
|
? null
|
||||||
: reasonLevelIsLocked(li, getHistory(), true);
|
: reasonLevelIsLocked(li, l.name, getHistory(), true);
|
||||||
const percentUnlocked = lockedBecause?.reached
|
const percentUnlocked = lockedBecause?.reached
|
||||||
? `<span class="progress-inline"><span style="transform: scale(${Math.floor((lockedBecause.reached / lockedBecause.minScore) * 100) / 100},1)"></span></span>`
|
? `<span class="progress-inline"><span style="transform: scale(${Math.floor((lockedBecause.reached / lockedBecause.minScore) * 100) / 100},1)"></span></span>`
|
||||||
: "";
|
: "";
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
currentLevelInfo,
|
currentLevelInfo,
|
||||||
describeLevel,
|
describeLevel,
|
||||||
pickedUpgradesHTMl,
|
pickedUpgradesHTMl,
|
||||||
reasonLevelIsLocked,
|
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import {
|
import {
|
||||||
askForPersistentStorage,
|
askForPersistentStorage,
|
||||||
|
@ -18,6 +18,7 @@ import { stopRecording } from "./recording";
|
||||||
import { asyncAlert } from "./asyncAlert";
|
import { asyncAlert } from "./asyncAlert";
|
||||||
import { editRawLevelList } from "./levelEditor";
|
import { editRawLevelList } from "./levelEditor";
|
||||||
import { openCreativeModePerksPicker } from "./creative";
|
import { openCreativeModePerksPicker } from "./creative";
|
||||||
|
import {isLevelLocked, reasonLevelIsLocked} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export function addToTotalPlayTime(ms: number) {
|
export function addToTotalPlayTime(ms: number) {
|
||||||
setSettingValue(
|
setSettingValue(
|
||||||
|
@ -139,7 +140,7 @@ export function getHistograms(gameState: GameState) {
|
||||||
.map((l, li) => ({
|
.map((l, li) => ({
|
||||||
li,
|
li,
|
||||||
l,
|
l,
|
||||||
r: reasonLevelIsLocked(li, runsHistory, false)?.text,
|
r: reasonLevelIsLocked(li,l.name, runsHistory, false)?.text,
|
||||||
}))
|
}))
|
||||||
.filter((l) => l.r);
|
.filter((l) => l.r);
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ export function getHistograms(gameState: GameState) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const unlocked = locked.filter(
|
const unlocked = locked.filter(
|
||||||
({ li }) => !reasonLevelIsLocked(li, runsHistory, true),
|
({ li ,l}) => !isLevelLocked(li, l.name, runsHistory),
|
||||||
);
|
);
|
||||||
if (unlocked.length) {
|
if (unlocked.length) {
|
||||||
unlockedLevels = `
|
unlockedLevels = `
|
||||||
|
|
|
@ -1,19 +1,9 @@
|
||||||
import {
|
import {Ball, Coin, GameState, Level, PerkId, PerksMap,} from "./types";
|
||||||
Ball,
|
import {icons, upgrades} from "./loadGameData";
|
||||||
Coin,
|
import {t} from "./i18n/i18n";
|
||||||
GameState,
|
import {clamp} from "./pure_functions";
|
||||||
Level,
|
import {getSettingValue, getTotalScore} from "./settings";
|
||||||
PerkId,
|
import {isOptionOn} from "./options";
|
||||||
PerksMap,
|
|
||||||
RunHistoryItem,
|
|
||||||
UpgradeLike,
|
|
||||||
} from "./types";
|
|
||||||
import { icons, upgrades } from "./loadGameData";
|
|
||||||
import { t } from "./i18n/i18n";
|
|
||||||
import { clamp } from "./pure_functions";
|
|
||||||
import { hashCode } from "./getLevelBackground";
|
|
||||||
import { getSettingValue, getTotalScore } from "./settings";
|
|
||||||
import { isOptionOn } from "./options";
|
|
||||||
|
|
||||||
export function describeLevel(level: Level) {
|
export function describeLevel(level: Level) {
|
||||||
let bricks = 0,
|
let bricks = 0,
|
||||||
|
@ -300,97 +290,6 @@ export function highScoreText() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
let excluded: Set<PerkId>;
|
|
||||||
function isExcluded(id: PerkId) {
|
|
||||||
if (!excluded) {
|
|
||||||
excluded = new Set([
|
|
||||||
"extra_levels",
|
|
||||||
"extra_life",
|
|
||||||
"one_more_choice",
|
|
||||||
"shunt",
|
|
||||||
"slow_down",
|
|
||||||
]);
|
|
||||||
// Avoid excluding a perk that's needed for the required one
|
|
||||||
upgrades.forEach((u) => {
|
|
||||||
if (u.requires) excluded.add(u.requires);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return excluded.has(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLevelUnlockCondition(levelIndex: number) {
|
|
||||||
let required: UpgradeLike[] = [],
|
|
||||||
forbidden: UpgradeLike[] = [],
|
|
||||||
minScore = Math.max(-1000 + 100 * levelIndex, 0);
|
|
||||||
|
|
||||||
if (levelIndex > 20) {
|
|
||||||
const possibletargets = [...upgrades]
|
|
||||||
.slice(0, Math.floor(levelIndex / 2))
|
|
||||||
.filter((u) => !isExcluded(u.id))
|
|
||||||
.sort(
|
|
||||||
(a, b) => hashCode(levelIndex + a.id) - hashCode(levelIndex + b.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
const length = Math.min(3, Math.ceil(levelIndex / 30));
|
|
||||||
required = possibletargets.slice(0, length);
|
|
||||||
forbidden = possibletargets.slice(length, length + length);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
required,
|
|
||||||
forbidden,
|
|
||||||
minScore,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBestScoreMatching(
|
|
||||||
history: RunHistoryItem[],
|
|
||||||
required: UpgradeLike[] = [],
|
|
||||||
forbidden: UpgradeLike[] = [],
|
|
||||||
) {
|
|
||||||
return Math.max(
|
|
||||||
0,
|
|
||||||
...history
|
|
||||||
.filter(
|
|
||||||
(r) =>
|
|
||||||
!required.find((u) => !r?.perks?.[u.id]) &&
|
|
||||||
!forbidden.find((u) => r?.perks?.[u.id]),
|
|
||||||
)
|
|
||||||
.map((r) => r.score),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reasonLevelIsLocked(
|
|
||||||
levelIndex: number,
|
|
||||||
history: RunHistoryItem[],
|
|
||||||
mentionBestScore: boolean,
|
|
||||||
): null | { reached: number; minScore: number; text: string } {
|
|
||||||
const { required, forbidden, minScore } = getLevelUnlockCondition(levelIndex);
|
|
||||||
|
|
||||||
const reached = getBestScoreMatching(history, required, forbidden);
|
|
||||||
let reachedText =
|
|
||||||
reached && mentionBestScore ? t("unlocks.reached", { reached }) : "";
|
|
||||||
if (reached >= minScore) {
|
|
||||||
return null;
|
|
||||||
} else if (!required.length && !forbidden.length) {
|
|
||||||
return {
|
|
||||||
reached,
|
|
||||||
minScore,
|
|
||||||
text: t("unlocks.minScore", { minScore }) + reachedText,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
reached,
|
|
||||||
minScore,
|
|
||||||
text:
|
|
||||||
t("unlocks.minScoreWithPerks", {
|
|
||||||
minScore,
|
|
||||||
required: required.map((u) => u.name).join(", "),
|
|
||||||
forbidden: forbidden.map((u) => u.name).join(", "),
|
|
||||||
}) + reachedText,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCoinRenderColor(gameState: GameState, coin: Coin) {
|
export function getCoinRenderColor(gameState: GameState, coin: Coin) {
|
||||||
if (
|
if (
|
||||||
gameState.perks.metamorphosis ||
|
gameState.perks.metamorphosis ||
|
||||||
|
|
113
src/get_level_unlock_condition.ts
Normal file
113
src/get_level_unlock_condition.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import {PerkId, RunHistoryItem, UnlockCondition} from "./types";
|
||||||
|
import {upgrades} from "./loadGameData";
|
||||||
|
import {hashCode} from "./getLevelBackground";
|
||||||
|
import {t} from "./i18n/i18n";
|
||||||
|
|
||||||
|
import _hardCodedCondition from './data/unlockConditions.json'
|
||||||
|
|
||||||
|
const hardCodedCondition = _hardCodedCondition as Record<string, UnlockCondition>
|
||||||
|
|
||||||
|
let excluded: Set<PerkId>;
|
||||||
|
|
||||||
|
function isExcluded(id: PerkId) {
|
||||||
|
if (!excluded) {
|
||||||
|
excluded = new Set([
|
||||||
|
"extra_levels",
|
||||||
|
"extra_life",
|
||||||
|
"one_more_choice",
|
||||||
|
"shunt",
|
||||||
|
"slow_down",
|
||||||
|
]);
|
||||||
|
// Avoid excluding a perk that's needed for the required one
|
||||||
|
upgrades.forEach((u) => {
|
||||||
|
if (u.requires) excluded.add(u.requires);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return excluded.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLevelUnlockCondition(levelIndex: number, levelName:string):UnlockCondition {
|
||||||
|
|
||||||
|
if(hardCodedCondition[levelName]) return hardCodedCondition[levelName]
|
||||||
|
const result :UnlockCondition = {
|
||||||
|
required:[],
|
||||||
|
forbidden:[],
|
||||||
|
minScore : Math.max(-1000 + 100 * levelIndex, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (levelIndex > 20) {
|
||||||
|
const possibletargets = [...upgrades]
|
||||||
|
.slice(0, Math.floor(levelIndex / 2))
|
||||||
|
.filter((u) => !isExcluded(u.id))
|
||||||
|
.sort(
|
||||||
|
(a, b) => hashCode(levelIndex + a.id) - hashCode(levelIndex + b.id),
|
||||||
|
).map(u => u.id);
|
||||||
|
|
||||||
|
const length = Math.min(3, Math.ceil(levelIndex / 30));
|
||||||
|
result.required = possibletargets.slice(0, length);
|
||||||
|
result.forbidden = possibletargets.slice(length, length + length);
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBestScoreMatching(
|
||||||
|
history: RunHistoryItem[],
|
||||||
|
required: PerkId[] = [],
|
||||||
|
forbidden: PerkId[] = [],
|
||||||
|
) {
|
||||||
|
return Math.max(
|
||||||
|
0,
|
||||||
|
...history
|
||||||
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
!required.find((id) => !r?.perks?.[id]) &&
|
||||||
|
!forbidden.find((id) => r?.perks?.[id]),
|
||||||
|
)
|
||||||
|
.map((r) => r.score),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLevelLocked(
|
||||||
|
levelIndex: number,
|
||||||
|
levelName:string,
|
||||||
|
history: RunHistoryItem[]){
|
||||||
|
const {required, forbidden, minScore} = getLevelUnlockCondition(levelIndex, levelName);
|
||||||
|
return getBestScoreMatching(history, required, forbidden) < minScore
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reasonLevelIsLocked(
|
||||||
|
levelIndex: number,levelName:string,
|
||||||
|
history: RunHistoryItem[],
|
||||||
|
mentionBestScore: boolean,
|
||||||
|
): null | { reached: number; minScore: number; text: string } {
|
||||||
|
|
||||||
|
const {required, forbidden, minScore} = getLevelUnlockCondition(levelIndex, levelName);
|
||||||
|
const reached = getBestScoreMatching(history, required, forbidden);
|
||||||
|
let reachedText =
|
||||||
|
reached && mentionBestScore ? t("unlocks.reached", {reached}) : "";
|
||||||
|
if (reached >= minScore) {
|
||||||
|
return null;
|
||||||
|
} else if (!required.length && !forbidden.length) {
|
||||||
|
return {
|
||||||
|
reached,
|
||||||
|
minScore,
|
||||||
|
text: t("unlocks.minScore", {minScore}) + reachedText,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
reached,
|
||||||
|
minScore,
|
||||||
|
text:
|
||||||
|
t("unlocks.minScoreWithPerks", {
|
||||||
|
minScore,
|
||||||
|
required: required.map((u) => upgradeName(u)).join(", "),
|
||||||
|
forbidden: forbidden.map((u) => upgradeName(u)).join(", "),
|
||||||
|
}) + reachedText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function upgradeName(id:PerkId){
|
||||||
|
return upgrades.find(u=>u.id==id)!.name
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ import { RunHistoryItem } from "./types";
|
||||||
|
|
||||||
import _appVersion from "./data/version.json";
|
import _appVersion from "./data/version.json";
|
||||||
import { generateSaveFileContent } from "./generateSaveFileContent";
|
import { generateSaveFileContent } from "./generateSaveFileContent";
|
||||||
import { reasonLevelIsLocked } from "./game_utils";
|
|
||||||
import { allLevels } from "./loadGameData";
|
import { allLevels } from "./loadGameData";
|
||||||
import { toast } from "./toast";
|
import { toast } from "./toast";
|
||||||
|
import {isLevelLocked, reasonLevelIsLocked} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
// The page will be reloaded if any migrations were run
|
// The page will be reloaded if any migrations were run
|
||||||
let migrationsRun = 0;
|
let migrationsRun = 0;
|
||||||
|
@ -128,7 +128,7 @@ migrate("set_breakout_71_unlocked_levels" + _appVersion, () => {
|
||||||
) as string[];
|
) as string[];
|
||||||
|
|
||||||
allLevels
|
allLevels
|
||||||
.filter((l, li) => !reasonLevelIsLocked(li, runsHistory, false))
|
.filter((l, li) => !isLevelLocked(li,l.name, runsHistory))
|
||||||
.forEach((l) => {
|
.forEach((l) => {
|
||||||
if (!breakout_71_unlocked_levels.includes(l.name)) {
|
if (!breakout_71_unlocked_levels.includes(l.name)) {
|
||||||
breakout_71_unlocked_levels.push(l.name);
|
breakout_71_unlocked_levels.push(l.name);
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
import { GameState, UpgradeLike } from "./types";
|
import {GameState, PerkId} from "./types";
|
||||||
import { getSettingValue, setSettingValue } from "./settings";
|
import { getSettingValue, setSettingValue } from "./settings";
|
||||||
import { allLevels, icons } from "./loadGameData";
|
import { allLevels, icons } from "./loadGameData";
|
||||||
import { getLevelUnlockCondition } from "./game_utils";
|
|
||||||
|
|
||||||
import { t } from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import { toast } from "./toast";
|
import { toast } from "./toast";
|
||||||
import { schedulGameSound } from "./gameStateMutators";
|
import { schedulGameSound } from "./gameStateMutators";
|
||||||
|
import {getLevelUnlockCondition} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
let list: {
|
let list: {
|
||||||
minScore: number;
|
minScore: number;
|
||||||
forbidden: UpgradeLike[];
|
forbidden: PerkId[];
|
||||||
required: UpgradeLike[];
|
required: PerkId[];
|
||||||
}[];
|
}[];
|
||||||
let unlocked = new Set(
|
let unlocked : Set<string> |null = null
|
||||||
getSettingValue("breakout_71_unlocked_levels", []) as string[],
|
|
||||||
);
|
|
||||||
|
|
||||||
export function monitorLevelsUnlocks(gameState: GameState) {
|
export function monitorLevelsUnlocks(gameState: GameState) {
|
||||||
|
if(!unlocked){
|
||||||
|
unlocked = new Set(
|
||||||
|
getSettingValue("breakout_71_unlocked_levels", []) as string[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (gameState.creative) return;
|
if (gameState.creative) return;
|
||||||
|
|
||||||
if (!list) {
|
if (!list) {
|
||||||
|
@ -24,13 +28,13 @@ export function monitorLevelsUnlocks(gameState: GameState) {
|
||||||
name: l.name,
|
name: l.name,
|
||||||
li,
|
li,
|
||||||
l,
|
l,
|
||||||
...getLevelUnlockCondition(li),
|
...getLevelUnlockCondition(li, l.name),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
list.forEach(({ name, minScore, forbidden, required, l }) => {
|
list.forEach(({ name, minScore, forbidden, required, l }) => {
|
||||||
// Already unlocked
|
// Already unlocked
|
||||||
if (unlocked.has(name)) return;
|
if (unlocked!.has(name)) return;
|
||||||
// Score not reached yet
|
// Score not reached yet
|
||||||
if (gameState.score < minScore) return;
|
if (gameState.score < minScore) return;
|
||||||
if (!minScore) return;
|
if (!minScore) return;
|
||||||
|
@ -41,7 +45,7 @@ export function monitorLevelsUnlocks(gameState: GameState) {
|
||||||
// We have a forbidden perk
|
// We have a forbidden perk
|
||||||
if (forbidden.find((id) => gameState.perks[id])) return;
|
if (forbidden.find((id) => gameState.perks[id])) return;
|
||||||
// Level just got unlocked
|
// Level just got unlocked
|
||||||
unlocked.add(name);
|
unlocked!.add(name);
|
||||||
setSettingValue(
|
setSettingValue(
|
||||||
"breakout_71_unlocked_levels",
|
"breakout_71_unlocked_levels",
|
||||||
getSettingValue("breakout_71_unlocked_levels", []).concat([name]),
|
getSettingValue("breakout_71_unlocked_levels", []).concat([name]),
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
getHighScore,
|
getHighScore,
|
||||||
getPossibleUpgrades,
|
getPossibleUpgrades,
|
||||||
highScoreText,
|
highScoreText,
|
||||||
reasonLevelIsLocked,
|
|
||||||
makeEmptyPerksMap,
|
makeEmptyPerksMap,
|
||||||
sumOfValues,
|
sumOfValues,
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
|
@ -14,6 +13,7 @@ import { isOptionOn } from "./options";
|
||||||
import { getHistory } from "./gameOver";
|
import { getHistory } from "./gameOver";
|
||||||
import { getSettingValue, getTotalScore } from "./settings";
|
import { getSettingValue, getTotalScore } from "./settings";
|
||||||
import { isBlackListedForStart, isStartingPerk } from "./startingPerks";
|
import { isBlackListedForStart, isStartingPerk } from "./startingPerks";
|
||||||
|
import {isLevelLocked, reasonLevelIsLocked} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export function getRunLevels(
|
export function getRunLevels(
|
||||||
params: RunParams,
|
params: RunParams,
|
||||||
|
@ -26,7 +26,7 @@ export function getRunLevels(
|
||||||
const history = getHistory();
|
const history = getHistory();
|
||||||
const unlocked = allLevels.filter(
|
const unlocked = allLevels.filter(
|
||||||
(l, li) =>
|
(l, li) =>
|
||||||
unlockedBefore.has(l.name) || !reasonLevelIsLocked(li, history, false),
|
unlockedBefore.has(l.name) || !isLevelLocked(li, l.name, history),
|
||||||
);
|
);
|
||||||
const firstLevel = params?.level
|
const firstLevel = params?.level
|
||||||
? [params.level]
|
? [params.level]
|
||||||
|
|
|
@ -2,17 +2,16 @@ import { GameState } from "./types";
|
||||||
import { asyncAlert } from "./asyncAlert";
|
import { asyncAlert } from "./asyncAlert";
|
||||||
import { t } from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import {
|
import {
|
||||||
getLevelUnlockCondition,
|
|
||||||
levelsListHTMl,
|
levelsListHTMl,
|
||||||
max_levels,
|
max_levels,
|
||||||
pickedUpgradesHTMl,
|
pickedUpgradesHTMl,
|
||||||
reasonLevelIsLocked,
|
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import { getCreativeModeWarning, getHistory } from "./gameOver";
|
import { getCreativeModeWarning, getHistory } from "./gameOver";
|
||||||
import { pause } from "./game";
|
import { pause } from "./game";
|
||||||
import { allLevels, icons } from "./loadGameData";
|
import { allLevels, icons } from "./loadGameData";
|
||||||
import { firstWhere } from "./pure_functions";
|
import { firstWhere } from "./pure_functions";
|
||||||
import { getSettingValue, getTotalScore } from "./settings";
|
import { getSettingValue, getTotalScore } from "./settings";
|
||||||
|
import {getLevelUnlockCondition, reasonLevelIsLocked, upgradeName} from "./get_level_unlock_condition";
|
||||||
|
|
||||||
export async function openScorePanel(gameState: GameState) {
|
export async function openScorePanel(gameState: GameState) {
|
||||||
pause(true);
|
pause(true);
|
||||||
|
@ -42,13 +41,13 @@ export function getNearestUnlockHTML(gameState: GameState) {
|
||||||
const unlocked = new Set(getSettingValue("breakout_71_unlocked_levels", []));
|
const unlocked = new Set(getSettingValue("breakout_71_unlocked_levels", []));
|
||||||
const firstUnlockable = firstWhere(allLevels, (l, li) => {
|
const firstUnlockable = firstWhere(allLevels, (l, li) => {
|
||||||
if (unlocked.has(l.name)) return;
|
if (unlocked.has(l.name)) return;
|
||||||
const reason = reasonLevelIsLocked(li, getHistory(), false);
|
const reason = reasonLevelIsLocked(li, l.name, getHistory(), false);
|
||||||
if (!reason) return;
|
if (!reason) return;
|
||||||
|
|
||||||
const { minScore, forbidden, required } = getLevelUnlockCondition(li);
|
const { minScore, forbidden, required } = getLevelUnlockCondition(li, l.name);
|
||||||
const missing = required.filter((u) => !gameState?.perks?.[u.id]);
|
const missing = required.filter((id) => !gameState?.perks?.[id]);
|
||||||
// we can't have a forbidden perk
|
// we can't have a forbidden perk
|
||||||
if (forbidden.find((u) => gameState?.perks?.[u.id])) {
|
if (forbidden.find((id) => gameState?.perks?.[id])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ export function getNearestUnlockHTML(gameState: GameState) {
|
||||||
|
|
||||||
if (!firstUnlockable) return "";
|
if (!firstUnlockable) return "";
|
||||||
let missingPoints = Math.max(0, firstUnlockable.minScore - gameState.score);
|
let missingPoints = Math.max(0, firstUnlockable.minScore - gameState.score);
|
||||||
let missingUpgrades = firstUnlockable.missing.map((u) => u.name).join(", ");
|
let missingUpgrades = firstUnlockable.missing.map((id) => upgradeName(id)).join(", ");
|
||||||
|
|
||||||
const title =
|
const title =
|
||||||
(missingUpgrades &&
|
(missingUpgrades &&
|
||||||
|
|
9
src/types.d.ts
vendored
9
src/types.d.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
import { rawUpgrades } from "./upgrades";
|
import {rawUpgrades} from "./upgrades";
|
||||||
import { options } from "./options";
|
import {options} from "./options";
|
||||||
|
|
||||||
export type colorString = string;
|
export type colorString = string;
|
||||||
|
|
||||||
|
@ -307,3 +307,8 @@ export type UpgradeLike = {
|
||||||
requires: string;
|
requires: string;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
};
|
};
|
||||||
|
export type UnlockCondition = {
|
||||||
|
required: PerkId[];
|
||||||
|
forbidden: PerkId[];
|
||||||
|
minScore: number;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue