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

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 },
}
}