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
+170
View File
@@ -0,0 +1,170 @@
import type { Iwt2BossEntityState } from '../sim'
export type BossRenderMotion = {
x: number
y: number
scaleX: number
scaleY: number
rotation: number
tint?: number
}
type BossMotionProfile = {
idleFrequency: number
idleBob: number
moveFrequency: number
moveBob: number
impactScale: number
}
const DEFAULT_PROFILE: BossMotionProfile = {
idleFrequency: 1.6,
idleBob: 2.1,
moveFrequency: 5.4,
moveBob: 3,
impactScale: 0.035,
}
const BOSS_PROFILES: Partial<Record<Iwt2BossEntityState['bossId'], BossMotionProfile>> = {
bulldrome: {
idleFrequency: 1.35,
idleBob: 2.3,
moveFrequency: 5.9,
moveBob: 3.2,
impactScale: 0.045,
},
'yian-kut-ku': {
idleFrequency: 2.2,
idleBob: 2.8,
moveFrequency: 6.4,
moveBob: 3.4,
impactScale: 0.032,
},
'obsidian-ram-golem': {
idleFrequency: 1.05,
idleBob: 1.6,
moveFrequency: 3.7,
moveBob: 2.5,
impactScale: 0.052,
},
}
const WARM_TINT = 0xffc88a
const IMPACT_TINT = 0xff9b5f
export function bossRenderMotion(entity: Iwt2BossEntityState, timeSeconds: number): BossRenderMotion {
const profile = BOSS_PROFILES[entity.bossId] ?? DEFAULT_PROFILE
const phase = String(entity.attackPhase)
const speed = Math.hypot(entity.velocity.x, entity.velocity.y)
const moving = speed > 8 || phase === 'relocating'
const facingX = entity.facing.x < -0.05 ? -1 : 1
const motion: BossRenderMotion = {
x: 0,
y: 0,
scaleX: 1,
scaleY: 1,
rotation: 0,
}
if (isActionPhase(phase, 'windup')) return windupMotion(motion, entity, phase, timeSeconds, facingX)
if (isBurstPhase(phase)) return burstMotion(motion, timeSeconds, facingX)
if (isActionPhase(phase, 'recover')) return recoverMotion(motion, profile, entity, timeSeconds)
if (moving) return movingMotion(motion, profile, entity, timeSeconds, facingX)
return idleMotion(motion, profile, timeSeconds)
}
function idleMotion(motion: BossRenderMotion, profile: BossMotionProfile, timeSeconds: number): BossRenderMotion {
const wave = Math.sin(timeSeconds * profile.idleFrequency * Math.PI * 2)
return {
...motion,
y: -Math.max(0, wave) * profile.idleBob,
scaleY: 1 + Math.max(0, wave) * 0.025,
scaleX: 1 - Math.max(0, wave) * 0.01,
}
}
function movingMotion(
motion: BossRenderMotion,
profile: BossMotionProfile,
entity: Iwt2BossEntityState,
timeSeconds: number,
facingX: number,
): BossRenderMotion {
const speedFactor = Math.min(1, Math.hypot(entity.velocity.x, entity.velocity.y) / 180)
const stride = Math.sin(timeSeconds * profile.moveFrequency * Math.PI * 2)
const lift = Math.abs(stride) * profile.moveBob
return {
...motion,
y: -lift,
scaleX: 1 + 0.012 * speedFactor,
scaleY: 1 - 0.009 * Math.abs(stride),
rotation: facingX * 0.025 * speedFactor,
}
}
function windupMotion(
motion: BossRenderMotion,
entity: Iwt2BossEntityState,
phase: string,
timeSeconds: number,
facingX: number,
): BossRenderMotion {
const pulse = 0.5 + Math.sin(timeSeconds * Math.PI * 9) * 0.5
const isSlam = phase.includes('slam') || phase.includes('quake') || phase.includes('shatter')
return {
...motion,
x: -facingX * (isSlam ? 1.5 : 3),
y: isSlam ? -2 : 2,
scaleX: 1.035,
scaleY: 0.965,
rotation: -facingX * 0.02,
tint: pulse > 0.45 || entity.phaseSecondsRemaining < 0.18 ? WARM_TINT : undefined,
}
}
function burstMotion(motion: BossRenderMotion, timeSeconds: number, facingX: number): BossRenderMotion {
const shake = Math.sin(timeSeconds * Math.PI * 38) * 1.8
return {
...motion,
x: facingX * 3 + shake,
y: Math.cos(timeSeconds * Math.PI * 42) * 1.2,
scaleX: 1.045,
scaleY: 0.96,
rotation: facingX * 0.018,
tint: WARM_TINT,
}
}
function recoverMotion(
motion: BossRenderMotion,
profile: BossMotionProfile,
entity: Iwt2BossEntityState,
timeSeconds: number,
): BossRenderMotion {
const settle = Math.max(0, Math.min(1, entity.phaseSecondsRemaining / 0.45))
const impactPulse = Math.abs(Math.sin(timeSeconds * Math.PI * 11)) * settle
const pop = profile.impactScale * impactPulse
return {
...motion,
y: -2 * impactPulse,
scaleX: 1 + pop,
scaleY: 1 + pop * 0.45,
tint: impactPulse > 0.45 ? IMPACT_TINT : undefined,
}
}
function isActionPhase(phase: string, suffix: string): boolean {
return phase.toLowerCase().includes(suffix)
}
function isBurstPhase(phase: string): boolean {
const normalized = phase.toLowerCase()
return (
normalized.includes('charging')
|| normalized.includes('swooping')
|| normalized.includes('burrowing')
|| normalized.includes('ricocheting')
|| normalized.includes('flying')
)
}