Android build v1.1.2
This commit is contained in:
+30
-100
@@ -1,5 +1,14 @@
|
||||
import starterProfile from './offline-starter-profile.json'
|
||||
import { bundledCatalogHash } from './offline-catalog-meta'
|
||||
import {
|
||||
catchUpExperienceReward as catchUpExperienceRewardForTarget,
|
||||
coinDropQuantity,
|
||||
experienceForLevel,
|
||||
roguelikeCoinItemLevel,
|
||||
scaledCurrentLevelExperience,
|
||||
scaledPvpBossExperience,
|
||||
scaledPvpFightExperience,
|
||||
} from './shared/rewardRules.mjs'
|
||||
import type {
|
||||
Account,
|
||||
AuthSession,
|
||||
@@ -37,7 +46,7 @@ export interface GameRepository {
|
||||
durationSeconds: number,
|
||||
options?: {
|
||||
bossesCleared?: number
|
||||
experienceMode?: 'default' | 'pvp-boss-quarter-level' | 'pvp-fight-twelfth-level' | 'pvp-stadium-round-win-quarter-level' | 'pvp-stadium-round-loss-tenth-level' | 'pvp-stadium-match-half-level'
|
||||
experienceMode?: 'default' | 'pvp-boss-quarter-level' | 'pvp-fight-twelfth-level' | 'pvp-match-win-half-level' | 'pvp-stadium-round-win-quarter-level' | 'pvp-stadium-round-loss-tenth-level' | 'pvp-stadium-match-half-level'
|
||||
fightsCleared?: number
|
||||
lootSourceEncounterId?: number
|
||||
roguelikeStage?: number
|
||||
@@ -115,6 +124,7 @@ const catalogBundleKey = 'chronicle.catalog.bundleHash.v1'
|
||||
const authTokenKey = 'chronicle.authToken.v1'
|
||||
const offlineAccount = { id: -1, username: 'Offline' }
|
||||
const ABILITY_SLOT_COUNT = 6
|
||||
let activeCatalogCache: CatalogCache | null = null
|
||||
|
||||
function clone<T>(value: T): T {
|
||||
return structuredClone(value)
|
||||
@@ -303,6 +313,7 @@ function readCatalogCache(): CatalogCache | null {
|
||||
if (localStorage.getItem(catalogBundleKey) !== bundledCatalogHash) {
|
||||
localStorage.removeItem(catalogCacheKey)
|
||||
localStorage.setItem(catalogBundleKey, bundledCatalogHash)
|
||||
activeCatalogCache = null
|
||||
return null
|
||||
}
|
||||
const serialized = localStorage.getItem(catalogCacheKey)
|
||||
@@ -319,10 +330,13 @@ function readCatalogCache(): CatalogCache | null {
|
||||
function writeCatalogCache(cache: CatalogCache) {
|
||||
localStorage.setItem(catalogBundleKey, bundledCatalogHash)
|
||||
localStorage.setItem(catalogCacheKey, JSON.stringify(cache))
|
||||
activeCatalogCache = cache
|
||||
}
|
||||
|
||||
function activeCatalog(): CatalogCache {
|
||||
return readCatalogCache() ?? bundledCatalog()
|
||||
if (activeCatalogCache) return activeCatalogCache
|
||||
activeCatalogCache = readCatalogCache() ?? bundledCatalog()
|
||||
return activeCatalogCache
|
||||
}
|
||||
|
||||
function buildProfile(save: OfflineSave): CharacterProfile {
|
||||
@@ -454,10 +468,6 @@ function selectUpgradeRecipe(
|
||||
return candidates.find((recipe) => recipe.item.itemLevel === nextItemLevel)
|
||||
}
|
||||
|
||||
function experienceForLevel(level: number) {
|
||||
return (level - 1) * (level - 1) * 100
|
||||
}
|
||||
|
||||
function catchUpExperienceReward(
|
||||
baseReward: number,
|
||||
currentExperience: number,
|
||||
@@ -465,11 +475,11 @@ function catchUpExperienceReward(
|
||||
targetLevel: number,
|
||||
) {
|
||||
if (targetLevel <= currentLevel) return baseReward
|
||||
const targetExperience = experienceForLevel(targetLevel)
|
||||
const gap = Math.max(0, targetExperience - currentExperience)
|
||||
if (gap <= 0) return baseReward
|
||||
const doubledBase = Math.min(baseReward, Math.ceil(gap / 2))
|
||||
return doubledBase * 2 + (baseReward - doubledBase)
|
||||
return catchUpExperienceRewardForTarget(
|
||||
baseReward,
|
||||
currentExperience,
|
||||
experienceForLevel(targetLevel),
|
||||
)
|
||||
}
|
||||
|
||||
function highestOtherClassLevel(save: OfflineSave) {
|
||||
@@ -479,77 +489,6 @@ function highestOtherClassLevel(save: OfflineSave) {
|
||||
.reduce((highest, [, character]) => Math.max(highest, character.level), 0)
|
||||
}
|
||||
|
||||
function scaledPvpBossExperience(
|
||||
startingExperience: number,
|
||||
startingLevel: number,
|
||||
bossesCleared: number,
|
||||
maxLevel: number,
|
||||
targetLevel = startingLevel,
|
||||
) {
|
||||
let experience = startingExperience
|
||||
let level = startingLevel
|
||||
const maxExperience = experienceForLevel(maxLevel)
|
||||
for (let bossIndex = 0; bossIndex < bossesCleared && experience < maxExperience; bossIndex += 1) {
|
||||
const currentLevelFloor = experienceForLevel(level)
|
||||
const nextLevelExperience = level >= maxLevel
|
||||
? maxExperience
|
||||
: experienceForLevel(level + 1)
|
||||
const levelBand = Math.max(1, nextLevelExperience - currentLevelFloor)
|
||||
const rewardRate = targetLevel > level ? 0.5 : 0.25
|
||||
experience = Math.min(maxExperience, experience + Math.round(levelBand * rewardRate))
|
||||
while (level < maxLevel && experienceForLevel(level + 1) <= experience) {
|
||||
level += 1
|
||||
}
|
||||
}
|
||||
return { experience, level }
|
||||
}
|
||||
|
||||
function scaledPvpFightExperience(
|
||||
startingExperience: number,
|
||||
startingLevel: number,
|
||||
fightsCleared: number,
|
||||
maxLevel: number,
|
||||
targetLevel = startingLevel,
|
||||
) {
|
||||
let experience = startingExperience
|
||||
let level = startingLevel
|
||||
const maxExperience = experienceForLevel(maxLevel)
|
||||
for (let fightIndex = 0; fightIndex < fightsCleared && experience < maxExperience; fightIndex += 1) {
|
||||
const currentLevelFloor = experienceForLevel(level)
|
||||
const nextLevelExperience = level >= maxLevel
|
||||
? maxExperience
|
||||
: experienceForLevel(level + 1)
|
||||
const levelBand = Math.max(1, nextLevelExperience - currentLevelFloor)
|
||||
const rewardRate = targetLevel > level ? 1 / 6 : 1 / 12
|
||||
experience = Math.min(maxExperience, experience + Math.round(levelBand * rewardRate))
|
||||
while (level < maxLevel && experienceForLevel(level + 1) <= experience) {
|
||||
level += 1
|
||||
}
|
||||
}
|
||||
return { experience, level }
|
||||
}
|
||||
|
||||
function scaledCurrentLevelExperience(
|
||||
startingExperience: number,
|
||||
startingLevel: number,
|
||||
maxLevel: number,
|
||||
rate: number,
|
||||
) {
|
||||
let experience = startingExperience
|
||||
let level = startingLevel
|
||||
const maxExperience = experienceForLevel(maxLevel)
|
||||
const currentLevelFloor = experienceForLevel(level)
|
||||
const nextLevelExperience = level >= maxLevel
|
||||
? maxExperience
|
||||
: experienceForLevel(level + 1)
|
||||
const levelBand = Math.max(1, nextLevelExperience - currentLevelFloor)
|
||||
experience = Math.min(maxExperience, experience + Math.round(levelBand * rate))
|
||||
while (level < maxLevel && experienceForLevel(level + 1) <= experience) {
|
||||
level += 1
|
||||
}
|
||||
return { experience, level }
|
||||
}
|
||||
|
||||
function talentEffectCapacity(level: number) {
|
||||
return Math.min(4, Math.max(0, Math.floor(level / 5)))
|
||||
}
|
||||
@@ -631,17 +570,6 @@ function rollWeightedLootEntry<T extends { dropWeight: number }>(entries: T[]):
|
||||
return entries[entries.length - 1]
|
||||
}
|
||||
|
||||
function coinDropQuantity() {
|
||||
const roll = Math.random()
|
||||
if (roll < 0.15) return 3
|
||||
if (roll < 0.5) return 2
|
||||
return 1
|
||||
}
|
||||
|
||||
function roguelikeCoinItemLevel(stage: number) {
|
||||
return Math.min(25, 5 + Math.max(0, Math.floor(stage / 5)) * 5)
|
||||
}
|
||||
|
||||
function awardRoguelikeCoin(
|
||||
profile: CharacterProfile,
|
||||
sourceEncounterId: number | undefined,
|
||||
@@ -1197,13 +1125,15 @@ function createLocalRepository(store: LocalSaveStore): GameRepository {
|
||||
profile.maxLevel,
|
||||
highestOtherClassLevel(save),
|
||||
)
|
||||
: options?.experienceMode === 'pvp-stadium-round-win-quarter-level'
|
||||
? scaledCurrentLevelExperience(previousExperience, previousLevel, profile.maxLevel, 0.25)
|
||||
: options?.experienceMode === 'pvp-stadium-round-loss-tenth-level'
|
||||
? scaledCurrentLevelExperience(previousExperience, previousLevel, profile.maxLevel, 0.1)
|
||||
: options?.experienceMode === 'pvp-stadium-match-half-level'
|
||||
? scaledCurrentLevelExperience(previousExperience, previousLevel, profile.maxLevel, 0.5)
|
||||
: null
|
||||
: options?.experienceMode === 'pvp-match-win-half-level'
|
||||
? scaledCurrentLevelExperience(previousExperience, previousLevel, profile.maxLevel, 0.5)
|
||||
: options?.experienceMode === 'pvp-stadium-round-win-quarter-level'
|
||||
? scaledCurrentLevelExperience(previousExperience, previousLevel, profile.maxLevel, 0.25)
|
||||
: options?.experienceMode === 'pvp-stadium-round-loss-tenth-level'
|
||||
? scaledCurrentLevelExperience(previousExperience, previousLevel, profile.maxLevel, 0.1)
|
||||
: options?.experienceMode === 'pvp-stadium-match-half-level'
|
||||
? scaledCurrentLevelExperience(previousExperience, previousLevel, profile.maxLevel, 0.5)
|
||||
: null
|
||||
const baseRoguelikeReward = Math.round(dungeon.experienceReward * difficulty.experienceMultiplier * (encountersCleared / 3))
|
||||
const newExperience = scaledReward
|
||||
? scaledReward.experience
|
||||
|
||||
Reference in New Issue
Block a user