211 lines
5.6 KiB
TypeScript
211 lines
5.6 KiB
TypeScript
/**
|
|
* D2GS S->C Skill List, Active Skill, and Skill Cast Packet Decoders (1.13c).
|
|
* Verified against D2Client.dll:
|
|
* - 0x21 UpdateSkill (12B, 0x6fb5c910)
|
|
* - 0x22 UpdateSkillStatus / ItemSkillBonus (12B, 0x6fb5c800)
|
|
* - 0x23 AssignSkill (13B, 0x6fb5c780)
|
|
* - 0x4C UnitUseSkillOnTarget (16B, 0x6fb5f1d0)
|
|
* - 0x4D UnitUseSkill (17B, 0x6fb5f160)
|
|
* - 0x6C NPCAttack (16B, 0x6fb5f750)
|
|
* - 0x7B AssignSkillHotkey (8B, 0x6fb5c6d0)
|
|
* - 0x94 BaseSkillLevels (variable 6 + 3*pkt[1], 0x6fb5c880)
|
|
* - 0x99 SkillTriggeredOnTarget (16B, 0x6fb5f0f0)
|
|
* - 0x9A SkillTriggeredOnLocation (17B, 0x6fb5f090)
|
|
*/
|
|
|
|
import { ByteReader } from '../../core/byte-reader.ts'
|
|
import type { Hand, SkillLevel, 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
|
|
}
|
|
|
|
/** 0x21 UpdateSkill (12B, D2Client.dll 0x6fb5c910) */
|
|
export function decode0x21UpdateSkill(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x21
|
|
r.u16LE() // unk
|
|
const unitId = r.u32LE()
|
|
const skill = r.u16LE()
|
|
const baseLevel = r.u8()
|
|
const bonus = r.u8()
|
|
r.u8()
|
|
return {
|
|
type: 'SkillList',
|
|
unitId,
|
|
skills: [{ skillId: skill, level: baseLevel, bonus }],
|
|
}
|
|
}
|
|
|
|
/** 0x22 UpdateSkillStatus (12B, D2Client.dll 0x6fb5c800) */
|
|
export function decode0x22UpdateSkillStatus(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x22
|
|
r.u16LE() // unk
|
|
const unitId = r.u32LE()
|
|
const skill = r.u16LE()
|
|
const amount = r.u8()
|
|
r.u16LE()
|
|
return {
|
|
type: 'SkillList',
|
|
unitId,
|
|
skills: [{ skillId: skill, level: amount }],
|
|
}
|
|
}
|
|
|
|
/** 0x23 AssignSkill (13B, D2Client.dll 0x6fb5c780) */
|
|
export function decode0x23AssignSkill(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x23
|
|
r.u8() // unitType (0)
|
|
const unitId = r.u32LE()
|
|
const handByte = r.u8()
|
|
const skill = r.u16LE()
|
|
const itemId = r.u32LE()
|
|
const hand: Hand = handByte === 0 ? 'right' : 'left'
|
|
return {
|
|
type: 'ActiveSkill',
|
|
unitId,
|
|
hand,
|
|
skill,
|
|
itemId,
|
|
}
|
|
}
|
|
|
|
/** 0x4C UnitUseSkillOnTarget (16B, D2Client.dll 0x6fb5f1d0) */
|
|
export function decode0x4CSkillOnTarget(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x4C
|
|
const casterType = clampUnitType(r.u8())
|
|
const casterId = r.u32LE()
|
|
const skill = r.u16LE()
|
|
const level = r.u8()
|
|
const targetType = clampUnitType(r.u8())
|
|
const targetId = r.u32LE()
|
|
r.u16LE()
|
|
return {
|
|
type: 'SkillCast',
|
|
caster: { unitType: casterType, id: casterId },
|
|
skill,
|
|
level,
|
|
target: { unitType: targetType, id: targetId },
|
|
}
|
|
}
|
|
|
|
/** 0x4D UnitUseSkill (17B, D2Client.dll 0x6fb5f160) */
|
|
export function decode0x4DSkillOnLocation(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x4D
|
|
const casterType = clampUnitType(r.u8())
|
|
const casterId = r.u32LE()
|
|
const skill = r.u32LE() & 0xffff
|
|
const level = r.u8()
|
|
const x = r.u16LE()
|
|
const y = r.u16LE()
|
|
r.u16LE()
|
|
return {
|
|
type: 'SkillCast',
|
|
caster: { unitType: casterType, id: casterId },
|
|
skill,
|
|
level,
|
|
target: { x, y },
|
|
}
|
|
}
|
|
|
|
/** 0x6C NPCAttack (16B, D2Client.dll 0x6fb5f750) */
|
|
export function decode0x6CNpcAttack(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x6C
|
|
const casterId = r.u32LE()
|
|
const skill = r.u16LE()
|
|
const targetId = r.u32LE()
|
|
const targetType = clampUnitType(r.u8())
|
|
const x = r.u16LE()
|
|
const y = r.u16LE()
|
|
return {
|
|
type: 'SkillCast',
|
|
caster: { unitType: 1, id: casterId },
|
|
skill,
|
|
level: 1,
|
|
target: targetId !== 0 ? { unitType: targetType, id: targetId } : { x, y },
|
|
}
|
|
}
|
|
|
|
/** 0x7B AssignSkillHotkey (8B, D2Client.dll 0x6fb5c6d0) */
|
|
export function decode0x7BAssignSkillHotkey(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x7B
|
|
const slot = r.u8()
|
|
const skill = r.u8() | ((r.u8() & 0x7f) << 8)
|
|
const hand: Hand = (pkt[3]! & 0x80) !== 0 ? 'left' : 'right'
|
|
const itemId = r.u32LE()
|
|
return {
|
|
type: 'ActiveSkill',
|
|
unitId: slot,
|
|
hand,
|
|
skill,
|
|
itemId,
|
|
}
|
|
}
|
|
|
|
/** 0x94 BaseSkillLevels (variable 6 + 3 * pkt[1], D2Client.dll 0x6fb5c880) */
|
|
export function decode0x94BaseSkillLevels(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x94
|
|
const count = r.u8()
|
|
const unitId = r.u32LE()
|
|
const skills: SkillLevel[] = []
|
|
for (let i = 0; i < count && r.remaining >= 3; i++) {
|
|
const skillId = r.u16LE()
|
|
const level = r.u8()
|
|
skills.push({ skillId, level })
|
|
}
|
|
return {
|
|
type: 'SkillList',
|
|
unitId,
|
|
skills,
|
|
}
|
|
}
|
|
|
|
/** 0x99 SkillTriggeredOnTarget (16B, D2Client.dll 0x6fb5f0f0) */
|
|
export function decode0x99SkillTriggeredOnTarget(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x99
|
|
const casterType = clampUnitType(r.u8())
|
|
const casterId = r.u32LE()
|
|
const targetType = clampUnitType(r.u8())
|
|
const targetId = r.u32LE()
|
|
const skill = r.u16LE()
|
|
const level = r.u8()
|
|
r.u16LE()
|
|
return {
|
|
type: 'SkillCast',
|
|
caster: { unitType: casterType, id: casterId },
|
|
skill,
|
|
level,
|
|
target: { unitType: targetType, id: targetId },
|
|
}
|
|
}
|
|
|
|
/** 0x9A SkillTriggeredOnLocation (17B, D2Client.dll 0x6fb5f090) */
|
|
export function decode0x9ASkillTriggeredOnLocation(pkt: Uint8Array): ServerEvent {
|
|
const r = new ByteReader(pkt)
|
|
r.u8() // 0x9A
|
|
const casterType = clampUnitType(r.u8())
|
|
const casterId = r.u32LE()
|
|
const x = r.u16LE()
|
|
const y = r.u16LE()
|
|
r.u16LE()
|
|
const skill = r.u16LE()
|
|
const level = r.u8()
|
|
r.u16LE()
|
|
return {
|
|
type: 'SkillCast',
|
|
caster: { unitType: casterType, id: casterId },
|
|
skill,
|
|
level,
|
|
target: { x, y },
|
|
}
|
|
}
|