Android build v1.0.34

This commit is contained in:
Warren H
2026-06-20 15:08:51 -04:00
parent 4fc15ebe9a
commit 1281be69d8
9 changed files with 1098 additions and 47 deletions
+51 -30
View File
@@ -103,6 +103,7 @@ const offlineSaveKey = 'chronicle.offlineSave.v1'
const onlineCacheKey = 'chronicle.onlineCache.v1'
const authTokenKey = 'chronicle.authToken.v1'
const offlineAccount = { id: -1, username: 'Offline' }
const ABILITY_SLOT_COUNT = 6
function clone<T>(value: T): T {
return structuredClone(value)
@@ -147,7 +148,7 @@ function upgradeV1Save(v1: { profile: CharacterProfile; lootRolls: Record<string
level: cid === p.character.classId ? p.character.level : 1,
experience: cid === p.character.classId ? p.character.experience : 0,
talentPoints: cid === p.character.classId ? p.character.talentPoints : 1,
abilitySlots: cid === p.character.classId ? [...p.abilitySlots] : [],
abilitySlots: cid === p.character.classId ? normalizeAbilitySlots(p.abilitySlots) : [],
talentRanks,
inventory: cid === p.character.classId ? clone(p.inventory) : [],
}
@@ -164,11 +165,32 @@ function upgradeV1Save(v1: { profile: CharacterProfile; lootRolls: Record<string
}
function upgradeV2Save(v2: Omit<OfflineSave, 'version' | 'completedRaidPhases'> & { version: 2 }): OfflineSave {
return {
return normalizeSaveAbilitySlots({
...v2,
version: 3,
completedRaidPhases: 0,
})
}
function normalizeAbilitySlots(abilitySlots: unknown): Array<number | null> {
const slots = Array.isArray(abilitySlots)
? abilitySlots
.slice(0, ABILITY_SLOT_COUNT)
.map((value) => {
if (value === null || value === undefined) return null
const id = Number(value)
return Number.isInteger(id) ? id : null
})
: []
while (slots.length < ABILITY_SLOT_COUNT) slots.push(null)
return slots
}
function normalizeSaveAbilitySlots(save: OfflineSave): OfflineSave {
for (const character of Object.values(save.characters)) {
character.abilitySlots = normalizeAbilitySlots(character.abilitySlots)
}
return save
}
function normalizeOfflineSave(raw: unknown): OfflineSave | null {
@@ -178,12 +200,12 @@ function normalizeOfflineSave(raw: unknown): OfflineSave | null {
profile?: CharacterProfile
lootRolls?: Record<string, LootRoll>
}
if (candidate.version === 3) return candidate as OfflineSave
if (candidate.version === 3) return normalizeSaveAbilitySlots(candidate as OfflineSave)
if (candidate.version === 2) {
return upgradeV2Save(candidate as Omit<OfflineSave, 'version' | 'completedRaidPhases'> & { version: 2 })
}
if (candidate.version === 1 && candidate.profile) {
return upgradeV1Save(candidate as { profile: CharacterProfile; lootRolls: Record<string, LootRoll> })
return normalizeSaveAbilitySlots(upgradeV1Save(candidate as { profile: CharacterProfile; lootRolls: Record<string, LootRoll> }))
}
return null
}
@@ -448,7 +470,7 @@ function mergeProfileIntoSave(profile: CharacterProfile, existingSave?: OfflineS
level: profile.character.level,
experience: profile.character.experience,
talentPoints: profile.character.talentPoints,
abilitySlots: [...profile.abilitySlots],
abilitySlots: normalizeAbilitySlots(profile.abilitySlots),
talentRanks,
inventory: clone(profile.inventory),
}
@@ -787,9 +809,9 @@ function emptyCharacterData(classId: number): CharacterData {
const inventory: Item[] = []
const startingAbilitySlots: Array<number | null> = gc.spells
.filter((s) => s.unlockLevel === 1)
.slice(0, 5)
.slice(0, ABILITY_SLOT_COUNT)
.map((s) => s.id)
while (startingAbilitySlots.length < 6) startingAbilitySlots.push(null)
while (startingAbilitySlots.length < ABILITY_SLOT_COUNT) startingAbilitySlots.push(null)
return {
level: 1,
experience: 0,
@@ -829,31 +851,30 @@ function createLocalRepository(store: LocalSaveStore): GameRepository {
},
async saveProfile(classId, abilitySlots) {
const save = requireStoredSave(store)
const static_ = clone(starterProfile) as CharacterProfile
const gameClass = static_.classes.find((candidate) => candidate.id === classId)
if (!gameClass) throw new Error('Selected class does not exist.')
const static_ = clone(starterProfile) as CharacterProfile
const gameClass = static_.classes.find((candidate) => candidate.id === classId)
if (!gameClass) throw new Error('Selected class does not exist.')
const slots = abilitySlots.slice(0, 6)
while (slots.length < 6) slots.push(null)
const selectedIds = slots.filter((id): id is number => id !== null)
if (new Set(selectedIds).size !== selectedIds.length) {
throw new Error('The same ability cannot be equipped twice.')
}
const activeChar = save.characters[save.activeClassId]
const validIds = new Set(
gameClass.spells
.filter((spell) => spell.unlockLevel <= activeChar.level)
.map((spell) => spell.id),
)
if (selectedIds.some((id) => !validIds.has(id))) {
throw new Error('One or more abilities are locked or belong to another class.')
}
const slots = normalizeAbilitySlots(abilitySlots)
const selectedIds = slots.filter((id): id is number => id !== null)
if (new Set(selectedIds).size !== selectedIds.length) {
throw new Error('The same ability cannot be equipped twice.')
}
const activeChar = save.characters[save.activeClassId]
const validIds = new Set(
gameClass.spells
.filter((spell) => spell.unlockLevel <= activeChar.level)
.map((spell) => spell.id),
)
if (selectedIds.some((id) => !validIds.has(id))) {
throw new Error('One or more abilities are locked or belong to another class.')
}
if (!save.characters[classId]) {
save.characters[classId] = emptyCharacterData(classId)
}
save.characters[classId].abilitySlots = slots
save.activeClassId = classId
if (!save.characters[classId]) {
save.characters[classId] = emptyCharacterData(classId)
}
save.characters[classId].abilitySlots = slots
save.activeClassId = classId
store.writeSave(save)
return buildProfile(save)
},