stuff
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import type { GameObj, PosComp, RectComp, AnchorComp, ColorComp, OpacityComp, ZComp } from 'kaplay'
|
||||
import type { KaplayInstance, FighterInstance } from './types'
|
||||
import {
|
||||
FIREBALL_SPEED, FIREBALL_WIDTH, FIREBALL_HEIGHT, FIREBALL_DAMAGE,
|
||||
HURTBOX_WIDTH, HURTBOX_HEIGHT, CROUCH_HURTBOX_HEIGHT,
|
||||
HITSTUN_SPECIAL, BLOCKSTUN_SPECIAL, CHIP_DAMAGE_RATIO,
|
||||
MAX_PROJECTILES, STAGE_LEFT, STAGE_RIGHT,
|
||||
} from './constants'
|
||||
import type { HitResult } from './combat'
|
||||
|
||||
type ProjectileObj = GameObj<PosComp | RectComp | AnchorComp | ColorComp | OpacityComp | ZComp>
|
||||
|
||||
interface Projectile {
|
||||
obj: ProjectileObj
|
||||
owner: 1 | 2
|
||||
speed: number
|
||||
damage: number
|
||||
alive: boolean
|
||||
}
|
||||
|
||||
const projectiles: Projectile[] = []
|
||||
|
||||
/**
|
||||
* Spawn a fireball projectile from the attacker's position.
|
||||
*/
|
||||
export function spawnFireball(
|
||||
k: KaplayInstance,
|
||||
fighter: FighterInstance,
|
||||
safeColor: (color: string) => ReturnType<KaplayInstance['Color']['fromHex']>,
|
||||
): void {
|
||||
// Count existing projectiles for this player
|
||||
const existing = projectiles.filter(p => p.owner === fighter.player && p.alive).length
|
||||
if (existing >= MAX_PROJECTILES) return
|
||||
|
||||
const dir = fighter.physics.facingRight ? 1 : -1
|
||||
const x = fighter.obj.pos.x + 40 * dir
|
||||
const y = fighter.obj.pos.y - 40
|
||||
|
||||
// Outer glow
|
||||
k.add([
|
||||
k.rect(FIREBALL_WIDTH + 6, FIREBALL_HEIGHT + 6),
|
||||
k.pos(x, y),
|
||||
k.anchor('center'),
|
||||
k.color(safeColor('#ff880044')),
|
||||
k.opacity(0.3),
|
||||
k.z(14),
|
||||
`fireball_glow_${fighter.player}`,
|
||||
{ speed: FIREBALL_SPEED * dir, owner: fighter.player },
|
||||
])
|
||||
|
||||
const obj = k.add([
|
||||
k.rect(FIREBALL_WIDTH, FIREBALL_HEIGHT),
|
||||
k.pos(x, y),
|
||||
k.anchor('center'),
|
||||
k.color(safeColor('#ff6600')),
|
||||
k.opacity(1),
|
||||
k.z(15),
|
||||
`fireball_${fighter.player}`,
|
||||
]) as unknown as ProjectileObj
|
||||
|
||||
const projectile: Projectile = {
|
||||
obj,
|
||||
owner: fighter.player,
|
||||
speed: FIREBALL_SPEED * dir,
|
||||
damage: FIREBALL_DAMAGE,
|
||||
alive: true,
|
||||
}
|
||||
projectiles.push(projectile)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all projectiles. Called each frame from the game loop.
|
||||
* Returns hit results for any projectile that connected.
|
||||
*/
|
||||
export function updateProjectiles(
|
||||
k: KaplayInstance,
|
||||
fighters: [FighterInstance, FighterInstance],
|
||||
dt: number,
|
||||
): { target: FighterInstance; result: HitResult }[] {
|
||||
const hits: { target: FighterInstance; result: HitResult }[] = []
|
||||
|
||||
// Update glow positions to follow their fireballs
|
||||
for (const player of [1, 2] as const) {
|
||||
const glows = k.get(`fireball_glow_${player}`) as unknown as ProjectileObj[]
|
||||
for (const glow of glows) {
|
||||
const spd = (glow as any).speed as number
|
||||
glow.pos.x += spd * dt
|
||||
if (glow.pos.x < STAGE_LEFT - 50 || glow.pos.x > STAGE_RIGHT + 50) {
|
||||
glow.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const proj of projectiles) {
|
||||
if (!proj.alive) continue
|
||||
|
||||
// Move
|
||||
proj.obj.pos.x += proj.speed * dt
|
||||
|
||||
// Off-screen cleanup
|
||||
if (proj.obj.pos.x < STAGE_LEFT - 50 || proj.obj.pos.x > STAGE_RIGHT + 50) {
|
||||
proj.obj.destroy()
|
||||
proj.alive = false
|
||||
continue
|
||||
}
|
||||
|
||||
// Check collision with opponent
|
||||
const target = fighters.find(f => f.player !== proj.owner)
|
||||
if (!target) continue
|
||||
|
||||
const isCrouching = target.combat.state === 'crouching' || target.combat.state === 'blocking'
|
||||
const hurtH = isCrouching ? CROUCH_HURTBOX_HEIGHT : HURTBOX_HEIGHT
|
||||
const tLeft = target.obj.pos.x - HURTBOX_WIDTH / 2
|
||||
const tRight = target.obj.pos.x + HURTBOX_WIDTH / 2
|
||||
const tTop = target.obj.pos.y - hurtH
|
||||
const tBottom = target.obj.pos.y
|
||||
|
||||
const pLeft = proj.obj.pos.x - FIREBALL_WIDTH / 2
|
||||
const pRight = proj.obj.pos.x + FIREBALL_WIDTH / 2
|
||||
const pTop = proj.obj.pos.y - FIREBALL_HEIGHT / 2
|
||||
const pBottom = proj.obj.pos.y + FIREBALL_HEIGHT / 2
|
||||
|
||||
if (pRight >= tLeft && pLeft <= tRight && pBottom >= tTop && pTop <= tBottom) {
|
||||
const isBlocking = target.combat.state === 'blocking' && target.physics.grounded
|
||||
const result: HitResult = isBlocking
|
||||
? {
|
||||
type: 'blocked',
|
||||
damage: Math.round(proj.damage * CHIP_DAMAGE_RATIO),
|
||||
hitstun: 0,
|
||||
blockstun: BLOCKSTUN_SPECIAL,
|
||||
knockbackX: 60,
|
||||
knockbackY: 0,
|
||||
hitbox: { offsetX: 0, offsetY: 0, width: FIREBALL_WIDTH, height: FIREBALL_HEIGHT, damage: proj.damage, hitstun: HITSTUN_SPECIAL, blockstun: BLOCKSTUN_SPECIAL, knockbackX: 120, knockbackY: 0, activeFrames: [0, 0] },
|
||||
}
|
||||
: {
|
||||
type: 'hit',
|
||||
damage: proj.damage,
|
||||
hitstun: HITSTUN_SPECIAL,
|
||||
blockstun: 0,
|
||||
knockbackX: 120,
|
||||
knockbackY: -80,
|
||||
hitbox: { offsetX: 0, offsetY: 0, width: FIREBALL_WIDTH, height: FIREBALL_HEIGHT, damage: proj.damage, hitstun: HITSTUN_SPECIAL, blockstun: BLOCKSTUN_SPECIAL, knockbackX: 120, knockbackY: -80, activeFrames: [0, 0] },
|
||||
}
|
||||
|
||||
hits.push({ target, result })
|
||||
proj.obj.destroy()
|
||||
proj.alive = false
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up dead projectiles
|
||||
for (let i = projectiles.length - 1; i >= 0; i--) {
|
||||
if (!projectiles[i].alive) projectiles.splice(i, 1)
|
||||
}
|
||||
|
||||
return hits
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all projectiles (round reset).
|
||||
*/
|
||||
export function clearAllProjectiles(k: KaplayInstance): void {
|
||||
for (const proj of projectiles) {
|
||||
if (proj.alive && proj.obj.exists()) {
|
||||
proj.obj.destroy()
|
||||
}
|
||||
}
|
||||
projectiles.length = 0
|
||||
|
||||
// Clean glow objects
|
||||
for (const player of [1, 2]) {
|
||||
for (const glow of k.get(`fireball_glow_${player}`)) {
|
||||
glow.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user