Android build v1.1.34
This commit is contained in:
@@ -49,6 +49,11 @@ import {
|
||||
import {
|
||||
createIwt2WeightedRoguelikeBossPair,
|
||||
createUniformIwt2RoguelikeBossPair,
|
||||
enabledIwt2RoguelikeBossPool,
|
||||
IWT2_PVP_ROGUELIKE_BOSS_ROSTER_LIMIT_ENABLED,
|
||||
IWT2_PVP_ROGUELIKE_ENABLED_BOSS_IDS,
|
||||
IWT2_PVP_STADIUM_BOSS_ROSTER_LIMIT_ENABLED,
|
||||
IWT2_PVP_STADIUM_ENABLED_BOSS_IDS,
|
||||
IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED,
|
||||
IWT2_PVP_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED,
|
||||
IWT2_PVP_STADIUM_WEIGHTED_BOSS_PROGRESSION_ENABLED,
|
||||
@@ -613,11 +618,27 @@ function createRoguelikeBossPair(
|
||||
: contentType === 'stadium'
|
||||
? IWT2_PVP_STADIUM_WEIGHTED_BOSS_PROGRESSION_ENABLED
|
||||
: IWT2_PVP_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED
|
||||
const bossPool = roguelikeBossPoolFor(variant, contentType)
|
||||
|
||||
if (weightedProgressionEnabled) {
|
||||
return createIwt2WeightedRoguelikeBossPair(stage)
|
||||
return createIwt2WeightedRoguelikeBossPair(stage, Math.random, { bossPool })
|
||||
}
|
||||
return createUniformIwt2RoguelikeBossPair()
|
||||
return createUniformIwt2RoguelikeBossPair(Math.random, { bossPool })
|
||||
}
|
||||
|
||||
function roguelikeBossPoolFor(
|
||||
variant: Iwt2RoguelikeVariant,
|
||||
contentType: Iwt2RoguelikeContentType,
|
||||
): Iwt2BossId[] | undefined {
|
||||
if (variant !== 'pvp') return undefined
|
||||
if (contentType === 'stadium') {
|
||||
return IWT2_PVP_STADIUM_BOSS_ROSTER_LIMIT_ENABLED
|
||||
? enabledIwt2RoguelikeBossPool(IWT2_PVP_STADIUM_ENABLED_BOSS_IDS)
|
||||
: undefined
|
||||
}
|
||||
return IWT2_PVP_ROGUELIKE_BOSS_ROSTER_LIMIT_ENABLED
|
||||
? enabledIwt2RoguelikeBossPool(IWT2_PVP_ROGUELIKE_ENABLED_BOSS_IDS)
|
||||
: undefined
|
||||
}
|
||||
|
||||
function chooseRunChoices<T>(items: readonly T[], count: number): T[] {
|
||||
|
||||
@@ -220,11 +220,11 @@ const DEFAULT_YIAN_KUT_KU_BOSS_METADATA: Iwt2BossMetadata = {
|
||||
firePuddleRadius: 42,
|
||||
firePuddleDamage: 15,
|
||||
firePuddleSeconds: 8,
|
||||
birdWaveThresholds: [0.9, 0.4],
|
||||
birdWaveThresholds: [0.5],
|
||||
birdFlightCooldown: 10,
|
||||
birdFlightWindup: 0.85,
|
||||
birdFlightSpeed: 360,
|
||||
birdHealth: 100,
|
||||
birdHealth: 55,
|
||||
birdRadius: 16,
|
||||
birdContactDamage: 26,
|
||||
birdStunSeconds: 0.75,
|
||||
|
||||
@@ -10,9 +10,11 @@ export type Iwt2PvpGearNormalizationConfig = {
|
||||
gearLevel: Iwt2GearLevel
|
||||
}
|
||||
|
||||
export const IWT2_PVP_NORMALIZED_GEAR_LEVEL: Iwt2GearLevel = 5
|
||||
|
||||
export const IWT2_PVP_GEAR_NORMALIZATION: Iwt2PvpGearNormalizationConfig = {
|
||||
enabled: true,
|
||||
gearLevel: 5,
|
||||
gearLevel: IWT2_PVP_NORMALIZED_GEAR_LEVEL,
|
||||
}
|
||||
|
||||
export function createIwt2PvpNormalizedGearProgress(
|
||||
|
||||
@@ -3,6 +3,12 @@ import { IWT2_BOSS_METADATA, type Iwt2BossId } from './bosses'
|
||||
export const IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED = true
|
||||
export const IWT2_PVP_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED = true
|
||||
export const IWT2_PVP_STADIUM_WEIGHTED_BOSS_PROGRESSION_ENABLED = true
|
||||
export const IWT2_PVP_ROGUELIKE_BOSS_ROSTER_LIMIT_ENABLED = true
|
||||
export const IWT2_PVP_STADIUM_BOSS_ROSTER_LIMIT_ENABLED = true
|
||||
|
||||
export const IWT2_PVP_ROGUELIKE_ENABLED_BOSS_IDS: readonly Iwt2BossId[] = Object.keys(IWT2_BOSS_METADATA) as Iwt2BossId[]
|
||||
|
||||
export const IWT2_PVP_STADIUM_ENABLED_BOSS_IDS: readonly Iwt2BossId[] = IWT2_PVP_ROGUELIKE_ENABLED_BOSS_IDS
|
||||
|
||||
type Iwt2RoguelikeBossTier = 'early' | 'mid' | 'late'
|
||||
|
||||
@@ -11,6 +17,10 @@ type TierWeight = {
|
||||
weight: number
|
||||
}
|
||||
|
||||
type Iwt2RoguelikeBossPoolOptions = {
|
||||
bossPool?: readonly Iwt2BossId[]
|
||||
}
|
||||
|
||||
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'],
|
||||
@@ -52,23 +62,28 @@ export function createIwt2PveRoguelikeBossPair(
|
||||
export function createIwt2WeightedRoguelikeBossPair(
|
||||
stage: number,
|
||||
random: () => number = Math.random,
|
||||
options: Iwt2RoguelikeBossPoolOptions = {},
|
||||
): Iwt2BossId[] {
|
||||
const choices: Iwt2BossId[] = []
|
||||
const maxThreat = maxThreatForStage(stage)
|
||||
const bossPool = normalizeBossPool(options.bossPool)
|
||||
|
||||
while (choices.length < 2) {
|
||||
const next = chooseWeightedBossForStage(stage, choices, maxThreat, random)
|
||||
const next = chooseWeightedBossForStage(stage, choices, maxThreat, random, bossPool)
|
||||
if (!next) break
|
||||
choices.push(next)
|
||||
}
|
||||
|
||||
return choices.length === 2
|
||||
? choices
|
||||
: createUniformIwt2RoguelikeBossPair(random)
|
||||
: createUniformIwt2RoguelikeBossPair(random, { bossPool })
|
||||
}
|
||||
|
||||
export function createUniformIwt2RoguelikeBossPair(random: () => number = Math.random): Iwt2BossId[] {
|
||||
const pool = Object.keys(IWT2_BOSS_METADATA) as Iwt2BossId[]
|
||||
export function createUniformIwt2RoguelikeBossPair(
|
||||
random: () => number = Math.random,
|
||||
options: Iwt2RoguelikeBossPoolOptions = {},
|
||||
): Iwt2BossId[] {
|
||||
const pool = normalizeBossPool(options.bossPool)
|
||||
const choices: Iwt2BossId[] = []
|
||||
while (pool.length > 0 && choices.length < 2) {
|
||||
const index = randomIndex(pool.length, random)
|
||||
@@ -78,19 +93,29 @@ export function createUniformIwt2RoguelikeBossPair(random: () => number = Math.r
|
||||
return choices
|
||||
}
|
||||
|
||||
export function enabledIwt2RoguelikeBossPool(
|
||||
bossIds: readonly Iwt2BossId[],
|
||||
): Iwt2BossId[] {
|
||||
return normalizeBossPool(bossIds)
|
||||
}
|
||||
|
||||
function chooseWeightedBossForStage(
|
||||
stage: number,
|
||||
selected: readonly Iwt2BossId[],
|
||||
maxThreat: number,
|
||||
random: () => number,
|
||||
bossPool: readonly Iwt2BossId[],
|
||||
): Iwt2BossId | undefined {
|
||||
const selectedSet = new Set(selected)
|
||||
const bossPoolSet = new Set(bossPool)
|
||||
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
|
||||
bossPoolSet.has(bossId)
|
||||
&& !selectedSet.has(bossId)
|
||||
&& selectedThreat + threatForBoss(bossId) <= maxThreat
|
||||
)),
|
||||
}))
|
||||
.filter((entry) => entry.weight > 0 && entry.bosses.length > 0)
|
||||
@@ -110,6 +135,21 @@ function chooseWeightedBossForStage(
|
||||
return lastEntry?.bosses[randomIndex(lastEntry.bosses.length, random)]
|
||||
}
|
||||
|
||||
function normalizeBossPool(bossPool: readonly Iwt2BossId[] | undefined): Iwt2BossId[] {
|
||||
const knownBosses = Object.keys(IWT2_BOSS_METADATA) as Iwt2BossId[]
|
||||
if (!bossPool) return knownBosses
|
||||
|
||||
const knownBossSet = new Set(knownBosses)
|
||||
const normalized: Iwt2BossId[] = []
|
||||
for (const bossId of bossPool) {
|
||||
if (knownBossSet.has(bossId) && !normalized.includes(bossId)) {
|
||||
normalized.push(bossId)
|
||||
}
|
||||
}
|
||||
|
||||
return normalized.length > 0 ? normalized : knownBosses
|
||||
}
|
||||
|
||||
function tierWeightsForStage(stage: number): TierWeight[] {
|
||||
const safeStage = Math.max(1, Math.floor(stage))
|
||||
if (safeStage <= 2) {
|
||||
|
||||
@@ -52,16 +52,17 @@ type OverlayAction = 'primary' | 'requeue' | 'menu'
|
||||
type OverlayNavEntry = {
|
||||
action: OverlayAction
|
||||
row: number
|
||||
column: number
|
||||
}
|
||||
|
||||
const DEFAULT_OVERLAY_NAV_ENTRIES: OverlayNavEntry[] = [
|
||||
{ action: 'primary', row: 0 },
|
||||
{ action: 'menu', row: 1 },
|
||||
{ action: 'primary', row: 0, column: 0 },
|
||||
{ action: 'menu', row: 1, column: 0 },
|
||||
]
|
||||
const PVP_RESULT_OVERLAY_NAV_ENTRIES: OverlayNavEntry[] = [
|
||||
{ action: 'primary', row: 0 },
|
||||
{ action: 'requeue', row: 1 },
|
||||
{ action: 'menu', row: 2 },
|
||||
{ action: 'primary', row: 0, column: 0 },
|
||||
{ action: 'requeue', row: 0, column: 1 },
|
||||
{ action: 'menu', row: 0, column: 2 },
|
||||
]
|
||||
const EMPTY_ROGUELIKE_BUFFS: Iwt2RoguelikeSelfBuffId[] = []
|
||||
const IWT2_PVP_BOSS_HEALTH_MULTIPLIER = 0.7
|
||||
@@ -278,12 +279,18 @@ export function BossArenaScreen({ bossId, bossIds, difficulty, modeLabel, save,
|
||||
const active = entries.find((entry) => entry.action === current) ?? entries[0]
|
||||
const candidates = entries.filter((entry) => {
|
||||
if (entry.action === current) return false
|
||||
if (action === 'navigateUp') return entry.row < active.row
|
||||
if (action === 'navigateDown') return entry.row > active.row
|
||||
if (action === 'navigateLeft') return entry.row === active.row && entry.column < active.column
|
||||
if (action === 'navigateRight') return entry.row === active.row && entry.column > active.column
|
||||
if (action === 'navigateUp') return entry.column === active.column && entry.row < active.row
|
||||
if (action === 'navigateDown') return entry.column === active.column && entry.row > active.row
|
||||
return false
|
||||
})
|
||||
if (candidates.length === 0) return current
|
||||
candidates.sort((a, b) => Math.abs(a.row - active.row) - Math.abs(b.row - active.row))
|
||||
candidates.sort((a, b) => {
|
||||
const aDistance = Math.abs(a.row - active.row) + Math.abs(a.column - active.column)
|
||||
const bDistance = Math.abs(b.row - active.row) + Math.abs(b.column - active.column)
|
||||
return aDistance - bDistance
|
||||
})
|
||||
return candidates[0]?.action ?? current
|
||||
})
|
||||
}, [pvpRoguelike])
|
||||
|
||||
@@ -69,8 +69,8 @@ export function createInitialIwt2ArenaState(
|
||||
roguelikePressure?: Iwt2RoguelikePressureState,
|
||||
bounds: Iwt2ArenaBounds = { width: DEFAULT_ARENA_WIDTH, height: DEFAULT_ARENA_HEIGHT },
|
||||
): Iwt2ArenaState {
|
||||
const initialBossIds = bossIds?.length ? bossIds.slice(0, 2) : chooseInitialBossIds(bossId)
|
||||
const bosses = initialBossIds.map((id, index) => createBossEntity(id, index, bossHealthScale, bounds))
|
||||
const initialBossIds = bossIds?.length ? [...bossIds] : chooseInitialBossIds(bossId)
|
||||
const bosses = initialBossIds.map((id, index) => createBossEntity(id, index, initialBossIds.length, bossHealthScale, bounds))
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
time: 0,
|
||||
@@ -97,9 +97,15 @@ function chooseInitialBossIds(primaryBossId: Iwt2BossId): Iwt2BossId[] {
|
||||
return [primaryBossId, random]
|
||||
}
|
||||
|
||||
function createBossEntity(bossId: Iwt2BossId, index: number, healthScale: number, bounds: Iwt2ArenaBounds): Iwt2BossEntityState {
|
||||
function createBossEntity(
|
||||
bossId: Iwt2BossId,
|
||||
index: number,
|
||||
bossCount: number,
|
||||
healthScale: number,
|
||||
bounds: Iwt2ArenaBounds,
|
||||
): Iwt2BossEntityState {
|
||||
const bossMetadata = IWT2_BOSS_METADATA[bossId]
|
||||
const position = scaleArenaPoint(initialBossPosition(index), bounds)
|
||||
const position = scaleArenaPoint(initialBossPosition(index, bossCount), bounds)
|
||||
const maxHealth = Math.max(1, Math.round(bossMetadata.maxHealth * Math.max(0.01, healthScale)))
|
||||
return {
|
||||
id: bossId,
|
||||
@@ -136,7 +142,14 @@ function createBossEntity(bossId: Iwt2BossId, index: number, healthScale: number
|
||||
}
|
||||
}
|
||||
|
||||
function initialBossPosition(index: number) {
|
||||
function initialBossPosition(index: number, bossCount = 2) {
|
||||
if (bossCount > 2) {
|
||||
const angle = -Math.PI * 0.5 + (Math.PI * (index + 0.5)) / bossCount
|
||||
return {
|
||||
x: 690 + Math.cos(angle) * 120,
|
||||
y: DEFAULT_ARENA_HEIGHT * 0.5 + Math.sin(angle) * 165,
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: index === 0 ? 660 : 760,
|
||||
y: index === 0 ? 190 : 345,
|
||||
@@ -757,6 +770,7 @@ function advanceBossProjectile({
|
||||
radius: hitMember.radius + projectile.radius,
|
||||
}, {
|
||||
damage: projectile.damage,
|
||||
damageEventType: 'bossProjectileHit',
|
||||
sourceId: projectile.sourceId,
|
||||
time,
|
||||
})
|
||||
@@ -774,13 +788,13 @@ function advanceBossProjectile({
|
||||
bounced = true
|
||||
}
|
||||
|
||||
if (bounced) {
|
||||
if (hitMember) {
|
||||
const puddle = addFirePuddle({
|
||||
damage: IWT2_BOSS_METADATA['yian-kut-ku'].firePuddleDamage!,
|
||||
duration: IWT2_BOSS_METADATA['yian-kut-ku'].firePuddleSeconds!,
|
||||
hazards: nextHazards,
|
||||
nextHazardId: nextHazardIdValue,
|
||||
position: hitMember?.position ?? position,
|
||||
position: hitMember.position,
|
||||
radius: IWT2_BOSS_METADATA['yian-kut-ku'].firePuddleRadius!,
|
||||
sourceId: projectile.sourceId,
|
||||
time,
|
||||
|
||||
@@ -224,6 +224,7 @@ export function tickGroundHazards({
|
||||
},
|
||||
{
|
||||
damage: hazard.damage,
|
||||
damageEventType: 'groundHazardTick',
|
||||
sourceId: hazard.sourceId,
|
||||
time,
|
||||
},
|
||||
|
||||
@@ -288,6 +288,8 @@ export type Iwt2ArenaEventType =
|
||||
| 'partyAttack'
|
||||
| 'partyHealed'
|
||||
| 'partyDamaged'
|
||||
| 'bossProjectileHit'
|
||||
| 'groundHazardTick'
|
||||
| 'partyStunned'
|
||||
| 'bossChargeStart'
|
||||
| 'bossChargeHit'
|
||||
|
||||
@@ -46,7 +46,7 @@ const BIRD_COUNT = 3
|
||||
const BIRD_MELEE_RANGE = 24
|
||||
const BIRD_MELEE_COOLDOWN = 1.15
|
||||
const BIRD_FLIGHT_DAMAGE = 26
|
||||
const YIAN_FIREBALL_BOUNCES = 8
|
||||
const YIAN_FIREBALL_BOUNCES = 3
|
||||
const YIAN_SAFE_WALL_MARGIN = 118
|
||||
const YIAN_CENTER_CAST_DISTANCE = 36
|
||||
const YIAN_CENTER_CHARGE_SPEED = 430
|
||||
|
||||
Reference in New Issue
Block a user