143 lines
3.5 KiB
JavaScript
143 lines
3.5 KiB
JavaScript
const PROGRESS_KEY = 'tang-detective-progress-v1'
|
|
const SETTINGS_KEY = 'tang-detective-settings-v1'
|
|
const AUDIO_KEY = 'tang-detective-audio-progress-v1'
|
|
|
|
function defaultProgress() {
|
|
return {
|
|
completedHotspots: {},
|
|
completedChapters: [],
|
|
lastChapter: 1,
|
|
}
|
|
}
|
|
|
|
function isPlainRecord(value) {
|
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
}
|
|
|
|
function chapterId(number) {
|
|
return `S01-C${String(number).padStart(2, '0')}`
|
|
}
|
|
|
|
function eventId(number) {
|
|
return `S01-H${String(number).padStart(2, '0')}`
|
|
}
|
|
|
|
function normalizeCompletedHotspots(value) {
|
|
const source = isPlainRecord(value) ? value : {}
|
|
const normalized = {}
|
|
for (let chapterNumber = 1; chapterNumber <= 15; chapterNumber += 1) {
|
|
const id = chapterId(chapterNumber)
|
|
const stored = Array.isArray(source[id]) ? source[id] : []
|
|
const firstEvent = (chapterNumber - 1) * 4 + 1
|
|
const allowed = new Set(
|
|
Array.from({ length: 4 }, (_, index) => eventId(firstEvent + index)),
|
|
)
|
|
const clean = [...new Set(stored.filter((item) => allowed.has(item)))]
|
|
if (clean.length > 0 || Object.prototype.hasOwnProperty.call(source, id)) {
|
|
normalized[id] = clean
|
|
}
|
|
}
|
|
return normalized
|
|
}
|
|
|
|
function normalizeCompletedChapters(value) {
|
|
if (!Array.isArray(value)) return []
|
|
const allowed = new Set(
|
|
Array.from({ length: 15 }, (_, index) => chapterId(index + 1)),
|
|
)
|
|
return [...new Set(value.filter((item) => allowed.has(item)))]
|
|
}
|
|
|
|
function normalizeProgress(value) {
|
|
const source = isPlainRecord(value) ? value : {}
|
|
const lastChapter = Number(source.lastChapter)
|
|
return {
|
|
...source,
|
|
completedHotspots: normalizeCompletedHotspots(source.completedHotspots),
|
|
completedChapters: normalizeCompletedChapters(source.completedChapters),
|
|
lastChapter: Number.isInteger(lastChapter) && lastChapter >= 1 && lastChapter <= 15
|
|
? lastChapter
|
|
: 1,
|
|
}
|
|
}
|
|
|
|
function normalizeSettings(value) {
|
|
const source = isPlainRecord(value) ? value : {}
|
|
return {
|
|
...source,
|
|
fontScale: source.fontScale === 'xlarge' ? 'xlarge' : 'large',
|
|
sound: source.sound !== false,
|
|
}
|
|
}
|
|
|
|
function read(key, fallback) {
|
|
try {
|
|
const value = wx.getStorageSync(key)
|
|
return value || fallback
|
|
} catch (error) {
|
|
return fallback
|
|
}
|
|
}
|
|
|
|
function write(key, value) {
|
|
try {
|
|
wx.setStorageSync(key, value)
|
|
return true
|
|
} catch (error) {
|
|
// Storage failure must never block the text game.
|
|
return false
|
|
}
|
|
}
|
|
|
|
function getProgress() {
|
|
return normalizeProgress(read(PROGRESS_KEY, null))
|
|
}
|
|
|
|
function saveProgress(progress) {
|
|
return write(PROGRESS_KEY, normalizeProgress(progress))
|
|
}
|
|
|
|
/**
|
|
* Start the story again without deleting the reader's collected memory cards
|
|
* or accessibility preferences. Only story/level progress is reset.
|
|
*/
|
|
function resetStoryProgress() {
|
|
const current = getProgress()
|
|
const next = {
|
|
...current,
|
|
...defaultProgress(),
|
|
}
|
|
delete next.comicReaderByChapter
|
|
delete next.lastPageId
|
|
if (!saveProgress(next)) return false
|
|
write(AUDIO_KEY, {})
|
|
return true
|
|
}
|
|
|
|
function getSettings() {
|
|
return normalizeSettings(read(SETTINGS_KEY, null))
|
|
}
|
|
|
|
function saveSettings(settings) {
|
|
return write(SETTINGS_KEY, normalizeSettings(settings))
|
|
}
|
|
|
|
function getAudioProgress() {
|
|
const value = read(AUDIO_KEY, {})
|
|
return isPlainRecord(value) ? value : {}
|
|
}
|
|
|
|
function saveAudioProgress(progress) {
|
|
write(AUDIO_KEY, progress)
|
|
}
|
|
|
|
module.exports = {
|
|
getProgress,
|
|
saveProgress,
|
|
resetStoryProgress,
|
|
getSettings,
|
|
saveSettings,
|
|
getAudioProgress,
|
|
saveAudioProgress,
|
|
}
|