Android build v1.1.23
This commit is contained in:
@@ -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')
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user