179 lines
4.7 KiB
TypeScript
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,
|
|
}
|
|
}
|