fix(client): face skill-cast targets with the 1.13c dir64

ClientUnit.direction is a dir64 since the facing fix, but the SkillCast
handler from the online gameplay loops still stored an 8-direction atan2
index (0 = +X). 0..7 passes assertDir64, so a casting unit silently drew
a near-south row. Face the cast target through dir64FromPoints, the same
1.13c direction table as movement; a target underfoot keeps the facing.
This commit is contained in:
troytt 2026-10-02 06:37:16 +00:00
parent dd775c8156
commit 8fcd2238cc
2 changed files with 18 additions and 7 deletions

View File

@ -18,6 +18,7 @@ import {
type UnitType,
} from '../../netproto/index.ts'
import { onlineItemDataToUiInventoryItem } from '../../common/items/item-bridge.ts'
import { dir64FromPoints } from '../../common/units/direction.ts'
import { ClientUnit, makeUnitKey, type UnitCastState } from './client-unit.ts'
import { ClientInventoryManager, type ClientItemContainers } from './inventory.ts'
import { MutableSelfPlayerState, type SelfPlayerState } from './self.ts'
@ -218,13 +219,9 @@ export class ClientWorld {
private applySkillCastToUnit(unit: ClientUnit, skillId: number, targetX: number, targetY: number): void {
unit.targetX = targetX
unit.targetY = targetY
const dx = targetX - unit.x
const dy = targetY - unit.y
if (dx !== 0 || dy !== 0) {
const angle = Math.atan2(dy, dx)
const normalized = ((angle + Math.PI * 2) % (Math.PI * 2)) / (Math.PI * 2)
unit.direction = Math.floor(normalized * 8) % 8
}
// `ClientUnit.direction` is a dir64 (`common/units/direction.ts`): face the target through the
// same 1.13c direction table as movement; a target on the unit's own position keeps the facing.
unit.direction = dir64FromPoints(unit.x, unit.y, targetX, targetY, unit.direction)
const isAttack = PHYSICAL_ATTACK_SKILL_IDS.has(skillId)
const mode = unit.unitType === 'monster' ? (isAttack ? 4 : 7) : isAttack ? 7 : 10
unit.mode = mode

View File

@ -261,3 +261,17 @@ describe('remote unit facing (RemoteEntityInterpolator)', () => {
expect(snapped).toMatchObject({ x: 5003.1, moving: false, dir64: glide.dir64 })
})
})
describe('skill-cast facing (ClientWorld)', () => {
it('turns the caster toward the target as a dir64 and keeps it for a target underfoot', () => {
const world = new ClientWorld()
world.apply({ type: 'UnitAssign', unit: { unitType: 1, id: 40, classId: 5, x: 5000, y: 5000 } })
const castAt = (x: number, y: number): number => {
world.apply({ type: 'SkillCast', caster: { unitType: 1, id: 40 }, skill: 0, level: 1, target: { x, y } })
return world.getUnit('monster', 40)!.direction
}
expect(castAt(5010, 5000)).toBe(56) // world +X: screen south-east
expect(castAt(5000, 5000)).toBe(56)
expect(castAt(4990, 4990)).toBe(32) // world −X−Y: screen north
})
})