Android build v1.1.23

This commit is contained in:
Warren H
2026-07-05 22:48:56 -04:00
parent 956c9e32f9
commit 9708ba9e40
106 changed files with 4294 additions and 458 deletions
+59 -11
View File
@@ -51,6 +51,14 @@ function ensureRuntimeMigrations(database) {
SET last_saved_at = COALESCE(NULLIF(last_saved_at, ''), created_at, CURRENT_TIMESTAMP)
WHERE last_saved_at IS NULL OR last_saved_at = ''
`).run()
database.exec(`
CREATE TABLE IF NOT EXISTS account_iwt2_saves (
account_id INTEGER PRIMARY KEY REFERENCES accounts(id) ON DELETE CASCADE,
save_json TEXT NOT NULL,
character_level INTEGER NOT NULL DEFAULT 1,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)
`)
}
function sqliteDateFromMs(value) {
@@ -1384,6 +1392,46 @@ function importSyncSave(database, accountId, activeCharacterId, payload) {
}
}
function readIwt2SyncSave(database, accountId) {
const row = database.prepare(`
SELECT save_json AS saveJson
FROM account_iwt2_saves
WHERE account_id = ?
`).get(accountId)
if (!row) return { save: null }
try {
return { save: JSON.parse(row.saveJson) }
} catch {
return { save: null }
}
}
function importIwt2SyncSave(database, accountId, payload) {
const save = payload?.save
if (
!save
|| typeof save !== 'object'
|| Number(save.version) !== 2
|| typeof save.updatedAt !== 'number'
|| !save.character
|| typeof save.character !== 'object'
|| typeof save.character.level !== 'number'
) {
throw new Error('The IWT2 save snapshot is invalid.')
}
const savedAt = sqliteDateFromMs(save.updatedAt)
const characterLevel = clampInteger(save.character.level, 1, 1, 1000000)
database.prepare(`
INSERT INTO account_iwt2_saves (account_id, save_json, character_level, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(account_id) DO UPDATE SET
save_json = excluded.save_json,
character_level = excluded.character_level,
updated_at = excluded.updated_at
`).run(accountId, JSON.stringify(save), characterLevel, savedAt)
return readIwt2SyncSave(database, accountId)
}
function itemById(database, itemId) {
return database.prepare(`
SELECT
@@ -1544,11 +1592,6 @@ function rollEncounterLoot(database, characterId, encounterId, difficultyId, run
`).get(encounterId, difficultyId)
if (!context) throw new Error('That loot table is not available.')
const character = database.prepare('SELECT level FROM characters WHERE id = ?').get(characterId)
if (character.level < context.unlockLevel) {
throw new Error(`${context.difficultyName} unlocks at level ${context.unlockLevel}.`)
}
const entries = database.prepare(`
SELECT
encounter_loot.drop_weight AS dropWeight,
@@ -2249,9 +2292,6 @@ function completeDungeon(database, characterId, accountId, dungeonId, difficulty
JOIN classes ON classes.id = characters.class_id
WHERE characters.id = ?
`).get(characterId)
if (character.level < dungeon.unlockLevel) {
throw new Error(`${dungeon.difficultyName} unlocks at level ${dungeon.unlockLevel}.`)
}
const maxLevel = Number(
database.prepare("SELECT value FROM game_settings WHERE key = 'max_level'").get()?.value ?? 25,
)
@@ -2483,9 +2523,6 @@ function completeRoguelike(database, characterId, accountId, runMetrics) {
FROM characters
WHERE characters.id = ?
`).get(characterId)
if (character.level < dungeon.unlockLevel) {
throw new Error(`${dungeon.difficultyName} unlocks at level ${dungeon.unlockLevel}.`)
}
const maxLevel = Number(
database.prepare("SELECT value FROM game_settings WHERE key = 'max_level'").get()?.value ?? 25,
)
@@ -3137,6 +3174,17 @@ export async function handleApiRequest(request, response, next) {
return
}
if (request.url === '/api/iwt2/sync-save' && request.method === 'GET') {
sendJson(response, 200, readIwt2SyncSave(database, session.accountId))
return
}
if (request.url === '/api/iwt2/sync-save' && request.method === 'PUT') {
const payload = await readJson(request, 512 * 1024)
sendJson(response, 200, importIwt2SyncSave(database, session.accountId, payload))
return
}
if (request.url === '/api/profile' && request.method === 'GET') {
sendJson(response, 200, getProfile(database, session.characterId, session.accountId))
return