diablo2-web/src/netproto/d2gs/s2c/movement.ts

179 lines
4.7 KiB
TypeScript

/**
* D2GS S->C Player and NPC Movement / Reassignment Packet Decoders (1.13c).
* Verified against D2Client.dll 0x6fb5f590 (0x0D), 0x6fb5f510 (0x0F), 0x6fb5f4c0 (0x10),
* 0x6fb5f640 (0x15), 0x6fb5f460 (0x67), 0x6fb5f3f0 (0x68), 0x6fb5f2b0 (0x6D), 0x6fb5d950 (0x96).
*/
import { BitReader } from '../../core/bit-reader.ts'
import { ByteReader } from '../../core/byte-reader.ts'
import type { UnitType } from '../../domain/ids.ts'
import type { ServerEvent } from '../../domain/server-event.ts'
function clampUnitType(raw: number): UnitType {
return (raw >= 0 && raw <= 5 ? raw : 0) as UnitType
}
function resolveMoveMode(rawMoveType: number): 'walk' | 'run' | 'knockback' | 'stop' {
// In D2Client.dll: 0x17 / 0x02 / 0x04 / bit 0x10 = run, 0x20 = knockback
if (rawMoveType === 0x20 || rawMoveType === 0x09) return 'knockback'
if (rawMoveType === 0x17 || rawMoveType === 0x02 || (rawMoveType & 0x02) !== 0) return 'run'
return 'walk'
}
/** 0x0D PlayerStop (13B, D2Client.dll 0x6fb5f590) */
export function decode0x0DPlayerStop(pkt: Uint8Array): ServerEvent {
const r = new ByteReader(pkt)
r.u8() // 0x0D
const unitType = clampUnitType(r.u8())
const id = r.u32LE()
r.u8() // unknown
const x = r.u16LE()
const y = r.u16LE()
r.u8() // unknown
r.u8() // lifePct
return {
type: 'UnitMove',
unitType,
id,
mode: 'stop',
to: { x, y },
}
}
/** 0x0F PlayerMove (16B, D2Client.dll 0x6fb5f510) */
export function decode0x0FPlayerMove(pkt: Uint8Array): ServerEvent {
const r = new ByteReader(pkt)
r.u8() // 0x0F
const unitType = clampUnitType(r.u8())
const id = r.u32LE()
const moveType = r.u8()
const targetX = r.u16LE()
const targetY = r.u16LE()
r.u8() // 0
const currentX = r.u16LE()
const currentY = r.u16LE()
return {
type: 'UnitMove',
unitType,
id,
mode: resolveMoveMode(moveType),
from: { x: currentX, y: currentY },
to: { x: targetX, y: targetY },
}
}
/** 0x10 PlayerMoveToTarget (16B, D2Client.dll 0x6fb5f4c0) */
export function decode0x10PlayerMoveToTarget(pkt: Uint8Array): ServerEvent {
const r = new ByteReader(pkt)
r.u8() // 0x10
const unitType = clampUnitType(r.u8())
const id = r.u32LE()
const moveType = r.u8()
const targetType = clampUnitType(r.u8())
const targetId = r.u32LE()
const currentX = r.u16LE()
const currentY = r.u16LE()
return {
type: 'UnitMove',
unitType,
id,
mode: resolveMoveMode(moveType),
from: { x: currentX, y: currentY },
to: { x: currentX, y: currentY },
target: { unitType: targetType, id: targetId },
}
}
/** 0x15 ReassignPlayer (11B, D2Client.dll 0x6fb5f640) */
export function decode0x15ReassignPlayer(pkt: Uint8Array): ServerEvent {
const r = new ByteReader(pkt)
r.u8() // 0x15
const unitType = clampUnitType(r.u8())
const id = r.u32LE()
const x = r.u16LE()
const y = r.u16LE()
const snap = r.u8() !== 0
return {
type: 'UnitReassign',
unitType,
id,
at: { x, y },
snap,
}
}
/** 0x67 NPCMove (16B, D2Client.dll 0x6fb5f460) */
export function decode0x67NpcMove(pkt: Uint8Array): ServerEvent {
const r = new ByteReader(pkt)
r.u8() // 0x67
const id = r.u32LE()
const moveType = r.u8()
const targetX = r.u16LE()
const targetY = r.u16LE()
return {
type: 'UnitMove',
unitType: 1,
id,
mode: resolveMoveMode(moveType),
to: { x: targetX, y: targetY },
}
}
/** 0x68 NPCMoveToTarget (21B, D2Client.dll 0x6fb5f3f0) */
export function decode0x68NpcMoveToTarget(pkt: Uint8Array): ServerEvent {
const r = new ByteReader(pkt)
r.u8() // 0x68
const id = r.u32LE()
const moveType = r.u8()
const currentX = r.u16LE()
const currentY = r.u16LE()
const targetType = clampUnitType(r.u8())
const targetId = r.u32LE()
return {
type: 'UnitMove',
unitType: 1,
id,
mode: resolveMoveMode(moveType),
from: { x: currentX, y: currentY },
to: { x: currentX, y: currentY },
target: { unitType: targetType, id: targetId },
}
}
/** 0x6D NPCStop (10B, D2Client.dll 0x6fb5f2b0) */
export function decode0x6DNpcStop(pkt: Uint8Array): ServerEvent {
const r = new ByteReader(pkt)
r.u8() // 0x6D
const id = r.u32LE()
const x = r.u16LE()
const y = r.u16LE()
r.u8() // lifePct
return {
type: 'UnitMove',
unitType: 1,
id,
mode: 'stop',
to: { x, y },
}
}
/** 0x96 WalkVerify (9B, D2Client.dll 0x6fb5d950 -> 0x6fb5c140) */
export function decode0x96WalkVerify(pkt: Uint8Array): ServerEvent {
const br = new BitReader(pkt)
br.readBits(8) // 0x96
const stamina = br.readBits(15)
const x = br.readBits(16)
const y = br.readBits(16)
const vx = br.readSignedBits(8)
const vy = br.readSignedBits(8)
return {
type: 'LifeMana',
life: -1,
mana: -1,
stamina,
at: { x, y },
vx,
vy,
}
}