feat(netproto): encode 1.13c C2S 0x5F UpdatePlayerPos
5-byte position resync request (u8 0x5F, u16 x, u16 y) built by D2Client 0x6FAC49E0 when its position gate finds the local player out of tolerance; D2Game 0x6FC88530 answers with 0x15 when it disagrees.
This commit is contained in:
parent
531273c989
commit
ea252b0414
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* D2GS C->S Player Position Resync Packet Builder (0x5F, 1.13c).
|
||||
*/
|
||||
|
||||
import { ByteWriter } from '../../core/byte-writer.ts'
|
||||
import { validateC2sPacket } from '../framing-c2s.ts'
|
||||
|
||||
function assertU16(value: number, what: string): void {
|
||||
if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
|
||||
throw new RangeError(`0x5F UpdatePlayerPos ${what} must be a u16, got ${String(value)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 0x5F UpdatePlayerPos (5B): `u8 0x5F | u16 x | u16 y`, the client's own sub-tile.
|
||||
*
|
||||
* D2Client 0x6FAC49E0 builds it when the position gate 0x6FADA1B0 finds the local player out
|
||||
* of tolerance; D2Game handles it at 0x6FC88530 (D2MOO `PlrMsg.cpp`
|
||||
* `D2GAME_PACKETCALLBACK_Rcv0x5F_UpdatePlayerPos`), answering with `0x15` when it disagrees.
|
||||
*/
|
||||
export function encodeC2sUpdatePlayerPos(x: number, y: number): Uint8Array {
|
||||
assertU16(x, 'x')
|
||||
assertU16(y, 'y')
|
||||
const w = new ByteWriter(5)
|
||||
w.u8(0x5f)
|
||||
w.u16LE(x)
|
||||
w.u16LE(y)
|
||||
return validateC2sPacket(w.toUint8Array())
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@ import {
|
|||
encodeC2sSkillOnUnit,
|
||||
} from './c2s/skills.ts'
|
||||
import { encodeC2sTrade } from './c2s/trade.ts'
|
||||
import { encodeC2sUpdatePlayerPos } from './c2s/position.ts'
|
||||
import { D2GS_S2C_PACKET_SIZES, resolveS2cPacketSize } from './framing-s2c.ts'
|
||||
import { decode0x26ChatMessage, decode0x5AEventMessage } from './s2c/chat.ts'
|
||||
import {
|
||||
|
|
@ -436,6 +437,8 @@ export function encodeD2gsClientCommand(
|
|||
return [encodeC2sMoveToUnit(false, { unitType: cmd.unitKind, id: cmd.unitId })]
|
||||
case 'RunToTarget':
|
||||
return [encodeC2sMoveToUnit(true, { unitType: cmd.unitKind, id: cmd.unitId })]
|
||||
case 'UpdatePlayerPos':
|
||||
return [encodeC2sUpdatePlayerPos(cmd.x, cmd.y)]
|
||||
case 'SkillAt':
|
||||
return [encodeC2sSkillAt(cmd.hand, cmd.at, cmd.shift)]
|
||||
case 'CastLeftSkillOnLocation':
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export type ClientCommand =
|
|||
| { readonly type: 'MoveToUnit'; readonly run: boolean; readonly target: UnitRef }
|
||||
| { readonly type: 'WalkToTarget'; readonly unitKind: UnitType; readonly unitId: number }
|
||||
| { readonly type: 'RunToTarget'; readonly unitKind: UnitType; readonly unitId: number }
|
||||
| { readonly type: 'UpdatePlayerPos'; readonly x: number; readonly y: number }
|
||||
| { readonly type: 'SkillAt'; readonly hand: Hand; readonly at: Pt; readonly shift?: boolean | undefined }
|
||||
| { readonly type: 'CastLeftSkillOnLocation'; readonly x: number; readonly y: number }
|
||||
| { readonly type: 'CastRightSkillOnLocation'; readonly x: number; readonly y: number }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* C2S 0x5F UpdatePlayerPos (D2 1.13c): `u8 0x5F | u16 x | u16 y`, sent by D2Client 0x6FAC49E0 when
|
||||
* the position gate 0x6FADA1B0 finds the local player out of tolerance.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { encodeC2sUpdatePlayerPos } from '../../src/netproto/d2gs/c2s/position.ts'
|
||||
import { encodeD2gsClientCommand } from '../../src/netproto/d2gs/registry.ts'
|
||||
|
||||
describe('C2S 0x5F UpdatePlayerPos', () => {
|
||||
it('encodes the sub-tile as two little-endian u16 values', () => {
|
||||
expect([...encodeC2sUpdatePlayerPos(0x1234, 0xbeef)]).toEqual([0x5f, 0x34, 0x12, 0xef, 0xbe])
|
||||
expect([...encodeC2sUpdatePlayerPos(0, 0xffff)]).toEqual([0x5f, 0x00, 0x00, 0xff, 0xff])
|
||||
})
|
||||
|
||||
it('is what the UpdatePlayerPos client command sends', () => {
|
||||
const packets = encodeD2gsClientCommand({ type: 'UpdatePlayerPos', x: 5000, y: 4999 })
|
||||
expect(packets.map(p => [...p])).toEqual([[0x5f, 0x88, 0x13, 0x87, 0x13]])
|
||||
})
|
||||
|
||||
it('rejects coordinates that are not u16', () => {
|
||||
for (const bad of [-1, 0x10000, 1.5, Number.NaN]) {
|
||||
expect(() => encodeC2sUpdatePlayerPos(bad, 100)).toThrow(RangeError)
|
||||
expect(() => encodeC2sUpdatePlayerPos(100, bad)).toThrow(RangeError)
|
||||
}
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue