Android build v1.1.29
This commit is contained in:
Binary file not shown.
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.warren.iwanttoheal"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 108
|
||||
versionName "1.1.28"
|
||||
versionCode 109
|
||||
versionName "1.1.29"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -46,6 +46,11 @@ import {
|
||||
type Iwt2RoguelikeSelfBuffId,
|
||||
type Iwt2RoguelikeVariant,
|
||||
} from './content/roguelike'
|
||||
import {
|
||||
createIwt2PveRoguelikeBossPair,
|
||||
createUniformIwt2RoguelikeBossPair,
|
||||
IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED,
|
||||
} from './content/roguelikeBossProgression'
|
||||
|
||||
type Iwt2Screen =
|
||||
| 'menu'
|
||||
@@ -522,7 +527,7 @@ function createRoguelikeRun(
|
||||
contentType: Iwt2RoguelikeContentType,
|
||||
): Iwt2RoguelikeRunState {
|
||||
return {
|
||||
bossIds: createRandomRoguelikeBossPair(),
|
||||
bossIds: createRoguelikeBossPair(variant, 1),
|
||||
buffs: [],
|
||||
contentType,
|
||||
debuffs: [],
|
||||
@@ -590,15 +595,17 @@ function applyRoguelikeChoice(
|
||||
return {
|
||||
...run,
|
||||
...nextBase,
|
||||
bossIds: createRandomRoguelikeBossPair(),
|
||||
bossIds: createRoguelikeBossPair(run.variant, run.stage + 1),
|
||||
...buildRoguelikeChoices(save, run.variant),
|
||||
stage: run.stage + 1,
|
||||
}
|
||||
}
|
||||
|
||||
function createRandomRoguelikeBossPair(): Iwt2BossId[] {
|
||||
const pool = Object.keys(IWT2_BOSS_METADATA) as Iwt2BossId[]
|
||||
return chooseRunChoices(pool, 2)
|
||||
function createRoguelikeBossPair(variant: Iwt2RoguelikeVariant, stage: number): Iwt2BossId[] {
|
||||
if (variant === 'pve' && IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED) {
|
||||
return createIwt2PveRoguelikeBossPair(stage)
|
||||
}
|
||||
return createUniformIwt2RoguelikeBossPair()
|
||||
}
|
||||
|
||||
function chooseRunChoices<T>(items: readonly T[], count: number): T[] {
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { IWT2_BOSS_METADATA, type Iwt2BossId } from './bosses'
|
||||
|
||||
export const IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED = true
|
||||
|
||||
type Iwt2RoguelikeBossTier = 'early' | 'mid' | 'late'
|
||||
|
||||
type TierWeight = {
|
||||
tier: Iwt2RoguelikeBossTier
|
||||
weight: number
|
||||
}
|
||||
|
||||
const IWT2_ROGUELIKE_BOSS_TIERS: Record<Iwt2RoguelikeBossTier, readonly Iwt2BossId[]> = {
|
||||
early: ['bulldrome', 'yian-kut-ku', 'great-jaggi', 'rathian', 'stormcoil-wyrm'],
|
||||
mid: ['khezu', 'barroth', 'tobi-kadachi', 'ember-mantis-duelist', 'crystal-bat-matriarch', 'hollowcrown-revenant'],
|
||||
late: ['rimebastion', 'cinderback-ricochet', 'obsidian-ram-golem', 'venom-orchid-hydra', 'sandglass-scorpion'],
|
||||
}
|
||||
|
||||
const IWT2_ROGUELIKE_BOSS_THREAT: Record<Iwt2RoguelikeBossTier, number> = {
|
||||
early: 1,
|
||||
mid: 2,
|
||||
late: 3,
|
||||
}
|
||||
|
||||
const IWT2_ROGUELIKE_BOSS_TIER_BY_ID: Record<Iwt2BossId, Iwt2RoguelikeBossTier> = {
|
||||
bulldrome: 'early',
|
||||
'yian-kut-ku': 'early',
|
||||
'great-jaggi': 'early',
|
||||
rathian: 'early',
|
||||
'stormcoil-wyrm': 'early',
|
||||
khezu: 'mid',
|
||||
barroth: 'mid',
|
||||
'tobi-kadachi': 'mid',
|
||||
'ember-mantis-duelist': 'mid',
|
||||
'crystal-bat-matriarch': 'mid',
|
||||
'hollowcrown-revenant': 'mid',
|
||||
rimebastion: 'late',
|
||||
'cinderback-ricochet': 'late',
|
||||
'obsidian-ram-golem': 'late',
|
||||
'venom-orchid-hydra': 'late',
|
||||
'sandglass-scorpion': 'late',
|
||||
}
|
||||
|
||||
export function createIwt2PveRoguelikeBossPair(
|
||||
stage: number,
|
||||
random: () => number = Math.random,
|
||||
): Iwt2BossId[] {
|
||||
const choices: Iwt2BossId[] = []
|
||||
const maxThreat = maxThreatForStage(stage)
|
||||
|
||||
while (choices.length < 2) {
|
||||
const next = chooseWeightedBossForStage(stage, choices, maxThreat, random)
|
||||
if (!next) break
|
||||
choices.push(next)
|
||||
}
|
||||
|
||||
return choices.length === 2
|
||||
? choices
|
||||
: createUniformIwt2RoguelikeBossPair(random)
|
||||
}
|
||||
|
||||
export function createUniformIwt2RoguelikeBossPair(random: () => number = Math.random): Iwt2BossId[] {
|
||||
const pool = Object.keys(IWT2_BOSS_METADATA) as Iwt2BossId[]
|
||||
const choices: Iwt2BossId[] = []
|
||||
while (pool.length > 0 && choices.length < 2) {
|
||||
const index = randomIndex(pool.length, random)
|
||||
const [choice] = pool.splice(index, 1)
|
||||
if (choice) choices.push(choice)
|
||||
}
|
||||
return choices
|
||||
}
|
||||
|
||||
function chooseWeightedBossForStage(
|
||||
stage: number,
|
||||
selected: readonly Iwt2BossId[],
|
||||
maxThreat: number,
|
||||
random: () => number,
|
||||
): Iwt2BossId | undefined {
|
||||
const selectedSet = new Set(selected)
|
||||
const selectedThreat = selected.reduce((total, bossId) => total + threatForBoss(bossId), 0)
|
||||
const weightedTiers = tierWeightsForStage(stage)
|
||||
.map((entry) => ({
|
||||
...entry,
|
||||
bosses: IWT2_ROGUELIKE_BOSS_TIERS[entry.tier].filter((bossId) => (
|
||||
!selectedSet.has(bossId) && selectedThreat + threatForBoss(bossId) <= maxThreat
|
||||
)),
|
||||
}))
|
||||
.filter((entry) => entry.weight > 0 && entry.bosses.length > 0)
|
||||
|
||||
const totalWeight = weightedTiers.reduce((total, entry) => total + entry.weight, 0)
|
||||
if (totalWeight <= 0) return undefined
|
||||
|
||||
let roll = safeRandom(random) * totalWeight
|
||||
for (const entry of weightedTiers) {
|
||||
roll -= entry.weight
|
||||
if (roll <= 0) {
|
||||
return entry.bosses[randomIndex(entry.bosses.length, random)]
|
||||
}
|
||||
}
|
||||
|
||||
const lastEntry = weightedTiers[weightedTiers.length - 1]
|
||||
return lastEntry?.bosses[randomIndex(lastEntry.bosses.length, random)]
|
||||
}
|
||||
|
||||
function tierWeightsForStage(stage: number): TierWeight[] {
|
||||
const safeStage = Math.max(1, Math.floor(stage))
|
||||
if (safeStage <= 2) {
|
||||
return [
|
||||
{ tier: 'early', weight: 85 },
|
||||
{ tier: 'mid', weight: 15 },
|
||||
{ tier: 'late', weight: 0 },
|
||||
]
|
||||
}
|
||||
if (safeStage === 3) {
|
||||
return [
|
||||
{ tier: 'early', weight: 60 },
|
||||
{ tier: 'mid', weight: 35 },
|
||||
{ tier: 'late', weight: 5 },
|
||||
]
|
||||
}
|
||||
if (safeStage <= 5) {
|
||||
return [
|
||||
{ tier: 'early', weight: 25 },
|
||||
{ tier: 'mid', weight: 60 },
|
||||
{ tier: 'late', weight: 15 },
|
||||
]
|
||||
}
|
||||
if (safeStage === 6) {
|
||||
return [
|
||||
{ tier: 'early', weight: 10 },
|
||||
{ tier: 'mid', weight: 50 },
|
||||
{ tier: 'late', weight: 40 },
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ tier: 'early', weight: 5 },
|
||||
{ tier: 'mid', weight: 30 },
|
||||
{ tier: 'late', weight: 65 },
|
||||
]
|
||||
}
|
||||
|
||||
function maxThreatForStage(stage: number): number {
|
||||
const safeStage = Math.max(1, Math.floor(stage))
|
||||
if (safeStage <= 2) return 3
|
||||
if (safeStage === 3) return 4
|
||||
if (safeStage <= 5) return 5
|
||||
return 6
|
||||
}
|
||||
|
||||
function threatForBoss(bossId: Iwt2BossId): number {
|
||||
return IWT2_ROGUELIKE_BOSS_THREAT[IWT2_ROGUELIKE_BOSS_TIER_BY_ID[bossId]]
|
||||
}
|
||||
|
||||
function randomIndex(length: number, random: () => number): number {
|
||||
return Math.min(length - 1, Math.floor(safeRandom(random) * length))
|
||||
}
|
||||
|
||||
function safeRandom(random: () => number): number {
|
||||
const value = random()
|
||||
return Number.isFinite(value) ? Math.min(0.999999999, Math.max(0, value)) : 0
|
||||
}
|
||||
Reference in New Issue
Block a user