# @deademx/engine 中文文档

规范地址：https://npmcn.com/package/%40deademx/engine/

版本：5\.0\.0
npm 元数据更新时间：2026-10-26T21:52:54.594Z
中文译文更新时间：2026-11-11T20:48:55.749Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40deademx/engine
原文对照：https://npmcn.com/package/%40deademx/engine/?lang=original
项目主页：https://deadem.com/
源码仓库：https://github.com/Igor-Losev/deadem

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：734 kB，255 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=18\.0\.0
- 运行依赖：2 个
- 首次发布：2026-04-22T13:14:22.815Z
- 最新版本发布：2026-10-26T21:52:54.330Z
- 近 30 天下载量：3413（2026-10-12 至 2026-11-10，npm 官方下载统计）
- 已发布版本数：21（近 12 个月 21 个）

## 中文简介

共享源 2 为 Node.js 和浏览器演示和回放播放引擎

## 安装

```sh
npm install @deademx/engine
```

也可以使用：`pnpm add @deademx/engine`、`yarn add @deademx/engine`、`bun add @deademx/engine`

## README

# [![deadem](https://deadem.com/logo80.svg)](https://deadem.com/)  
@ deademx/引擎  

[![](https://github.com/Igor-Losev/deadem/actions/workflows/ci.yml/badge.svg)](https://github.com/Igor-Losev/deadem/actions/workflows/ci.yml) [![](https://img.shields.io/npm/v/%40deademx%2Fengine)](https://www.npmjs.com/package/@deademx/engine)

**@ deademx/引擎** 是共享源 2 的核心 [`@deademx/deadlock`](https://www.npmjs.com/package/@deademx/deadlock) (解锁), [`@deademx/cs2`](https://www.npmjs.com/package/@deademx/cs2) (评委-评委2)和 [`@deademx/dota2`](https://www.npmjs.com/package/@deademx/dota2) (Dota 2).

该引擎为与Source 2演示机合作提供常见课程. 它不自行运行,而依赖于游戏包的配置: `.proto` 游戏文件和解码器指令.

> \[注\] 所有实例都使用 `@deademx/deadlock`。其他游戏的API相同.

## 目录

-   [快速启动](https://npmcn.com/package/%40deademx/engine/#readme-quick-start)  
    玩家的名字和杀人.
-   [如何运作](https://npmcn.com/package/%40deademx/engine/#readme-how-it-works)  
    滴答 三角洲 包裹和快照.
-   [数据存在的地方](https://npmcn.com/package/%40deademx/engine/#readme-where-data-lives)  
    状态数据、无国籍数据和三角洲.
    -   [状态数据](https://npmcn.com/package/%40deademx/engine/#readme-stateful-data)  
        `Demo` 在当前勾选时.
        -   [实体](https://npmcn.com/package/%40deademx/engine/#readme-demo--entities)  
            游戏对象和手柄.
        -   [字符串表格](https://npmcn.com/package/%40deademx/engine/#readme-demo--string-tables)  
            索引字符串和数据.
        -   [用户命令](https://npmcn.com/package/%40deademx/engine/#readme-demo--user-commands)  
            玩家输入每个插槽.
    -   [无国籍数据](https://npmcn.com/package/%40deademx/engine/#readme-stateless-data)  
        只有拦截者能看见的数据.
    -   [三角洲](https://npmcn.com/package/%40deademx/engine/#readme-deltas)  
        状态数据的变化.
-   [截取器](https://npmcn.com/package/%40deademx/engine/#readme-interceptors)  
    在每个数据包之前或之后召回.
    -   [`InterceptorStage.DEMO_PACKET`](https://npmcn.com/package/%40deademx/engine/#readme-interceptorstagedemo_packet)  
        每个演示包.
    -   [`InterceptorStage.MESSAGE_PACKET`](https://npmcn.com/package/%40deademx/engine/#readme-interceptorstagemessage_packet)  
        每个消息包.
    -   [`InterceptorStage.ENTITY_PACKET`](https://npmcn.com/package/%40deademx/engine/#readme-interceptorstageentity_packet)  
        实体变化.
    -   [`InterceptorStage.STRING_TABLE`](https://npmcn.com/package/%40deademx/engine/#readme-interceptorstagestring_table)  
        字符串表格更改 .
    -   [`InterceptorStage.USER_COMMAND`](https://npmcn.com/package/%40deademx/engine/#readme-interceptorstageuser_command)  
        用户命令.
    -   [截取流](https://npmcn.com/package/%40deademx/engine/#readme-interceptor-flow)  
        调用之序.
-   [玩家](https://npmcn.com/package/%40deademx/engine/#readme-player)  
    寻取取取出并回放.
    -   [寻求](https://npmcn.com/package/%40deademx/engine/#readme-seeking)  
        州在任何时间.
    -   [状态](https://npmcn.com/package/%40deademx/engine/#readme-state)  
        国机.
    -   [导航](https://npmcn.com/package/%40deademx/engine/#readme-navigation)  
        前后一 tick.
    -   [重播](https://npmcn.com/package/%40deademx/engine/#readme-playback)  
        实时回放及其中断.
-   [HTTP 广播](https://npmcn.com/package/%40deademx/engine/#readme-http-broadcast)  
    解说活相.
-   [配置图宁( T)](https://npmcn.com/package/%40deademx/engine/#readme-configuration--tuning)  
    过滤器和事件循环中断 .
    -   [过滤器](https://npmcn.com/package/%40deademx/engine/#readme-filters)  
        只需要解码.
    -   [流调](https://npmcn.com/package/%40deademx/engine/#readme-stream-tuning)  
        `breakInterval` 和UI的反应.
-   [业绩](https://npmcn.com/package/%40deademx/engine/#readme-performance)  
    加速过滤器.
    -   [提示](https://npmcn.com/package/%40deademx/engine/#readme-tips)  
        如何是不断解说快.
-   [类型](https://npmcn.com/package/%40deademx/engine/#readme-types)  
    类型脚本和 `is()`。 。 。 .
-   [词汇](https://npmcn.com/package/%40deademx/engine/#readme-vocabulary)  
    术语和密码.
-   [有用链接](https://npmcn.com/package/%40deademx/engine/#readme-useful-links)  
    关于网络和演示的文章.
-   [许可证](https://npmcn.com/package/%40deademx/engine/#readme-license)  
    麻省理工学院.

## 快速启动

```bash
npm install @deademx/deadlock
```

```js
import { createReadStream } from 'node:fs';

import { Parser } from '@deademx/deadlock';

(async () => {
    const parser = new Parser();
    const stream = createReadStream('./match.dem');

    await parser.parse(stream);

    const demo = parser.getDemo();

    for (const controller of demo.getEntitiesByClassName('CCitadelPlayerController')) {
        console.log(controller.getField('m_iszPlayerName'), controller.getField('m_PlayerDataGlobal.m_iPlayerKills'));
    }

    await parser.dispose(); // cleanup state and resources
})();
```

## 如何运作

演示( E)`.dem`)是一对匹配的录音:游戏引擎重放的帧和网络包的序列. 解析器读取了一串字节的流,分解了帧和网络包,解压缩了他们的有效载荷,并提供了一个API来消耗已解码的数据.

演示的结构跟随服务器模拟匹配的方式: 在离散的时间步骤中称为 `ticks`. 例如"死锁"和"反冲"2通常使用64分的滴答速率,也就是说一秒内有64分的时间步,或者每15.625分一个. 在每个勾选时保存完整的世界状态是低效的,所以格式使用deltas:演示只存储自上个勾选以来的变化. 因此,一滴状态是所有三角洲应用到一滴状态的结果.

溪流有两层包. 外层由帧组成. 这个工程叫它们演示包(`DemoPacket`) (中文(简体) ). 内层由来自游戏服务器的信息组成,如实体更新,聊天和声音等. 这个项目叫他们信息包(`MessagePacket`) (中文(简体) ). 演示包共有19个类型,每个游戏包支持大约70-200个类型的信息包. 三种类型的演示包, `DEM_SIGNON_PACKET`, (中文(简体) ), `DEM_PACKET` 财务报告和审定财务报表 `DEM_FULL_PACKET`,并携带这些消息的网络包.

```text
.dem (simplified)
 ├─ tick -1
 │   ├─ DEM_FILE_HEADER
 │   ├─ DEM_SIGNON_PACKET ×N ── MessagePacket, …
 │   ├─ DEM_SEND_TABLES
 │   ├─ DEM_CLASS_INFO
 │   └─ DEM_STRING_TABLES
 ├─ DEM_PACKET       ── MessagePacket, …     first tick
 ├─ DEM_FULL_PACKET  ── MessagePacket, …     snapshot at the first tick, then about once a minute
 ├─ DEM_PACKET       ── MessagePacket, …     next recorded tick
 ├─ …
 ├─ DEM_STOP                                 last tick
 └─ DEM_FILE_INFO                            last tick
```

引擎在状态下累积了三角洲,用于三种数据: `Entities`, (中文(简体) ), `String Tables` 财务报告和审定财务报表 `User Commands`页:1.

当需要一次通行证时,按顺序阅读非常适合. 然而,对于随机访问来说,效率低下:要在比赛中间求取需要应用整个上半场的三角洲. 为了提高随机访问的效率,演示还包含一个快照(`DEM_FULL_PACKET`大约每60秒。 一个快照包含实体和字符串表格的全部状态. 一个搜索从最近的快照中应用三角洲,而不是从溪流的起始处应用.

引擎输出两种阅读方式:

-   `Parser` 尽可能快地读取一次和顺序的演示.
-   `Player` 缓冲演示,索引其快照,以实时速度(或另一种速率)提供回放,并寻求任意勾选.

两个班都一样 [拦截器](https://npmcn.com/package/%40deademx/engine/#readme-interceptors) API (英语).

## 数据存在的地方

引擎在 `Demo`,而无国籍数据,如声音事件和杀戮,则只存在于数据包中,可以通过截取器访问.

### 状态数据

状态数据由实体,字符串表格和用户命令等组成. `parser.getDemo()` 财务报告和审定财务报表 `player.getDemo()` 返回 `Demo`,它在当前勾选时保存此数据。 引擎变了 `Demo` 对象,因为它应用数据包,而从未替换.

何时 `parse()` 返回时, `Demo` 比赛结束 下面的例子以特定的勾选方式捕捉状态,并将每个玩家的10分钟净值打印入演示:

```js
import { createReadStream } from 'node:fs';

import { InterceptorStage, Parser } from '@deademx/deadlock';

const TICK = 10 * 60 * 64; // 10 minutes into the demo at 64 ticks per second

const parser = new Parser();

const interceptor = (demoPacket) => {
    if (demoPacket.tick < TICK) return;

    for (const controller of parser.getDemo().getEntitiesByClassName('CCitadelPlayerController')) {
        if (controller.getField('m_iTeamNum') < 2) continue; // spectators and SourceTV

        console.log(controller.getField('m_iszPlayerName'), controller.getField('m_PlayerDataGlobal.m_iGoldNetWorth'));
    }

    parser.unregisterPostInterceptor(InterceptorStage.DEMO_PACKET, interceptor);
};

parser.registerPostInterceptor(InterceptorStage.DEMO_PACKET, interceptor);

await parser.parse(createReadStream('./match.dem'));
```

#### 实体

实体是游戏对象,如武器,蠕动或能力,具有类名和场. 这个 `SVC_PACKET_ENTITIES` 消息包载有其更新.

在"死锁"中,玩家是两个实体. 那个卒子 `CCitadelPlayerPawn` 保持英雄的地位和健康 控制器 `CCitadelPlayerController` 名称和净值。 处理字段,通常 `m_h*`,链接实体. `demo.getEntityByHandle()` 决定:

```js
for (const pawn of demo.getEntitiesByClassName('CCitadelPlayerPawn')) {
    const controller = demo.getEntityByHandle(pawn.getField('m_hController'));

    if (controller === null) continue;

    console.log(controller.getField('m_iszPlayerName'), pawn.getField('m_iHealth'));
}
```

#### 演示 – 字符串表

字符串表是一个有索引条目的数据容器,每个有文本字符串和可选的二进制数据. `DEM_STRING_TABLES`, (中文(简体) ), `SVC_CREATE_STRING_TABLE`, (中文(简体) ), `SVC_UPDATE_STRING_TABLE` 财务报告和审定财务报表 `SVC_CLEAR_ALL_STRING_TABLES` 带着它的变化。 譬如说, `userinfo` 存储播放器名称,以及 `ActiveModifiers` 储存活的起伏和起伏.

`demo.stringTableContainer.getByType()` 返回其表格 [`StringTableType`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/enums/StringTableType.js)数字 :

```js
for (const table of demo.stringTableContainer.getTables()) {
    console.log(table.type.name, table.getEntriesCount());
}

const userInfo = demo.stringTableContainer.getByType(StringTableType.USER_INFO);

for (const entry of userInfo.getEntries()) {
    console.log(entry.id, entry.value?.name);
}
```

> \[注\] `USER_INFO` 仅在玩家连接时保留一个条目, 因此在 `parse()` 大多数条目是空的.

#### Demo – 用户命令

用户命令是玩家输入的网络代表,如按下按钮,取景角度和移动等. 这个 `SVC_USER_COMMANDS` 信息包随身携带。 Dota 2 演示中没有用户命令 .

`demo.getUserCommands()` 返回每个玩家槽的最新命令,槽 N 的控制器是实体 N + 1.

```js
for (const command of demo.getUserCommands()) {
    const controller = demo.getEntity(command.slot + 1);

    if (controller === null) continue;

    console.log(controller.getField('m_iszPlayerName'), command.state.base.viewangles);
}
```

### 无国籍数据

无国籍数据是其它数据包携带的 引擎没有保存它,只有 [拦截器](https://npmcn.com/package/%40deademx/engine/#readme-interceptors) 接受这个 在死锁中, 消息包 `CITADEL_USER_MESSAGE_DAMAGE` 拥有攻击者、受害者和损失数额。 演示包 `DEM_FILE_HEADER` 持有地图和服务器名称.

### 三角洲

三角洲是状态数据的变化. 三个拦截阶段报告三角洲: `ENTITY_PACKET` 对于实体, `STRING_TABLE` 用于字符串表格和 `USER_COMMAND` 用于用户命令。 每个事件只持有已经改变的东西,例如一个实体已经改变的字段. 这个 [截取器](https://npmcn.com/package/%40deademx/engine/#readme-interceptors) 报告部分描述了这些内容.

## 截取器

截取器就是回叫器 `InterceptorStage`. 发动机以前称为(`PRE`或之后(`POST`将一个包应用到 `Demo`这样 `PRE` 看过以前的状态 `POST` 新的一个. `Parser` 财务报告和审定财务报表 `Player` 提供四种方法:

-   `registerPreInterceptor(stage, callback)` 财务报告和审定财务报表 `registerPostInterceptor(stage, callback)` 添加拦截器.
-   `unregisterPreInterceptor(stage, callback)` 财务报告和审定财务报表 `unregisterPostInterceptor(stage, callback)` 拿开.

拦截器是同步的,引擎不会等待返回的承诺.

| 阶段 | 钩子签名 |
| --- | --- |
| `DEMO_PACKET` | (演示包 : [`DemoPacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/DemoPacket.js)(虚空) |
| `MESSAGE_PACKET` | (演示包 : [`DemoPacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/DemoPacket.js),留言包: [`MessagePacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/MessagePacket.js)(虚空) |
| `ENTITY_PACKET` | (演示包 : [`DemoPacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/DemoPacket.js),留言包: [`MessagePacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/MessagePacket.js),事件:矩阵 <[`EntityMutationEvent`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/entity/EntityMutationEvent.js)\>) 无效 |
| `STRING_TABLE` | (演示包 : [`DemoPacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/DemoPacket.js),留言包: [`MessagePacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/MessagePacket.js) QQ 无效, 事件: 阵列 <[`StringTableMutationEvent`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/tables/string/StringTableMutationEvent.js)\>) 无效 |
| `USER_COMMAND` | (演示包 : [`DemoPacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/DemoPacket.js),留言包: [`MessagePacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/MessagePacket.js),事件:矩阵 <[`UserCommandEvent`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/UserCommandEvent.js)\>) 无效 |

### `InterceptorStage.DEMO_PACKET`

每个演示包一个呼叫.

```js
import { InterceptorStage, Parser } from '@deademx/deadlock';

const parser = new Parser();

parser.registerPostInterceptor(InterceptorStage.DEMO_PACKET, (demoPacket) => {
    console.log(`Tick [ ${demoPacket.tick} ] | Type [ ${demoPacket.type.code} ]`);
});
```

### `InterceptorStage.MESSAGE_PACKET`

每个消息包一个电话.

```js
import { MessagePacketType } from '@deademx/deadlock';

parser.registerPostInterceptor(InterceptorStage.MESSAGE_PACKET, (demoPacket, messagePacket) => {
    if (!messagePacket.is(MessagePacketType.CITADEL_USER_MESSAGE_CHAT_MESSAGE)) return;

    console.log(`Tick [ ${demoPacket.tick} ] | Slot [ ${messagePacket.data.playerSlot} ] | ${messagePacket.data.text}`);
});
```

### `InterceptorStage.ENTITY_PACKET`

一通电话 `SVC_PACKET_ENTITIES` 信息,带有 [`EntityMutationEvent`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/entity/EntityMutationEvent.js) 对于每个已改变的实体.

```js
import { EntityOperation } from '@deademx/deadlock';

parser.registerPostInterceptor(InterceptorStage.ENTITY_PACKET, (demoPacket, messagePacket, events) => {
    for (const event of events) {
        if (event.operation !== EntityOperation.UPDATE) continue;
        if (event.entity.class.name !== 'CCitadelPlayerPawn') continue;

        const [health] = event.getChanges(['m_iHealth']);

        if (health !== undefined) {
            console.log(`Tick [ ${demoPacket.tick} ] | Entity [ ${event.entity.class.name}|${event.entity.index} ] | Health [ ${health} ]`);
        }
    }
});
```

| `event.operation` | 何时 | `event.getChanges()` |
| --- | --- | --- |
| `CREATE` | 实体出现 | 所有初始字段 |
| `UPDATE` | 字段变化 | 已更改字段 |
| `LEAVE` | 服务器停止发送实体 。 留在这 `Demo` 与 `entity.active` 设置为 `false` | 空 |
| `DELETE` | 该实体从 `Demo` | 空 |

### `InterceptorStage.STRING_TABLE`

一通电话 `DEM_STRING_TABLES` (光盘)和 `SVC_CREATE_STRING_TABLE`, (中文(简体) ), `SVC_UPDATE_STRING_TABLE` 或者说 `SVC_CLEAR_ALL_STRING_TABLES` (信息包),带 [`StringTableMutationEvent`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/tables/string/StringTableMutationEvent.js) 每个更改的表格.

```js
import { StringTableOperation, StringTableType } from '@deademx/deadlock';

parser.registerPreInterceptor(InterceptorStage.STRING_TABLE, (demoPacket, messagePacket, events) => {
    for (const event of events) {
        if (!event.table.is(StringTableType.USER_INFO) || event.operation !== StringTableOperation.UPDATE) continue;

        for (const entry of event.entries) {
            const before = event.table.getEntryById(entry.id)?.value ?? null; // PRE: the table still holds the previous entry
            const after = entry.value;

            if (before === null && after !== null) {
                console.log(`Tick [ ${demoPacket.tick} ] | Connected [ ${after.name} ]`);
            }

            if (before !== null && after === null) {
                console.log(`Tick [ ${demoPacket.tick} ] | Disconnected [ ${before.name} ]`);
            }
        }
    }
});
```

| `event.operation` | 何时 | `event.entries` |
| --- | --- | --- |
| `CREATE` | 创建或替换表格 | 所有条目 |
| `UPDATE` | 条目更改 | 已更改条目 |
| `REMOVE` | `SVC_CLEAR_ALL_STRING_TABLES` 删除表格 | 空 |

### `InterceptorStage.USER_COMMAND`

一通电话 `SVC_USER_COMMANDS` 或者说 `SVC_USER_COMMAND_KEYFRAME` 信息,带有 [`UserCommandEvent`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/UserCommandEvent.js) 每个命令. `event.userCommand` 是更新的命令,并且 `event.getChanges()` 返回命令更改的内容,形状为 `state`。 。 。 .

```js
parser.registerPostInterceptor(InterceptorStage.USER_COMMAND, (demoPacket, messagePacket, events) => {
    for (const event of events) {
        const { inShop } = event.getChanges(); // Deadlock: true while the shop is open

        if (inShop === true) {
            console.log(`Tick [ ${demoPacket.tick} ] | Slot [ ${event.userCommand.slot} ] | Shop opened`);
        }
    }
});
```

### 截取流

```text
PRE DEMO_PACKET
 └─ DEM_FILE_HEADER
POST DEMO_PACKET
...
PRE DEMO_PACKET
 └─ DEM_PACKET
     ├─ PRE MESSAGE_PACKET
     │   └─ NET_TICK
     ├─ POST MESSAGE_PACKET
     ├─ ...
     ├─ PRE MESSAGE_PACKET
     │   └─ SVC_PACKET_ENTITIES
     │       ├─ PRE ENTITY_PACKET
     │       │   └─ events
     │       └─ POST ENTITY_PACKET
     ├─ POST MESSAGE_PACKET
     ├─ PRE MESSAGE_PACKET
     │   └─ SVC_UPDATE_STRING_TABLE
     │       ├─ PRE STRING_TABLE
     │       │   └─ events
     │       └─ POST STRING_TABLE
     ├─ POST MESSAGE_PACKET
     ├─ PRE MESSAGE_PACKET
     │   └─ SVC_USER_COMMANDS
     │       └─ POST USER_COMMAND
     ├─ POST MESSAGE_PACKET
     └─ ...
POST DEMO_PACKET
```

## 玩家

`Parser` 从头到尾都读过一次演示. `Player` 向后搜索, 步数按勾选, 并按实时速度或任何其他速度播放演示 . `Player` 将整个演示读取为 `load()` 并索引其快照. `Player` 尚不支持 HTTP 广播 .

### 寻求

`Player` 提供 `seekToTick(tick)`,它从最近的早期快照开始,并应用三角洲直至 `tick`寻找时间随着时间的距离而增加.

```js
import { createReadStream } from 'node:fs';

import { Player } from '@deademx/deadlock';

const player = new Player();

await player.load(createReadStream('./match.dem'));
await player.seekToTick(player.getLastTick());

const controllers = player.getDemo().getEntitiesByClassName('CCitadelPlayerController');

for (const controller of controllers) {
    const name = controller.getField('m_iszPlayerName');
    const netWorth = controller.getField('m_PlayerDataGlobal.m_iGoldNetWorth');

    console.log(`[ ${name} ] | m_iGoldNetWorth [ ${netWorth} ]`);
}

await player.dispose();
```

### 状态

`Player` 随状态机:

```text
IDLE
 ├── load()        → LOADED
 └── dispose()     → DISPOSED

LOADED
 ├── play()        → PLAYING
 ├── seekToTick()  → SEEKING → LOADED
 └── dispose()     → DISPOSED

PLAYING
 ├── pause() / end → LOADED
 └── dispose()     → DISPOSED

SEEKING
 ├── (completes)   → LOADED
 └── dispose()     → DISPOSED
```

一个失败的搜索仍然返回到 `LOADED`。 。 。 .

### 导航

`Player` 提供了两种每次移动一个勾选的方法:

-   `player.nextTick()` 是同步和便宜的,因为它应用了一个三角洲。 在最后一个勾选时,它返回 `false`。 。 。 .
-   `player.prevTick()` 是同步的,成本与搜索一样高,因为它从最近的快照开始.

```js
await player.seekToTick(10 * 60 * 64); // 10 minutes into the demo at 64 ticks per second

for (let i = 0; i < 10 && player.nextTick(); i++) { // 10 ticks forward
    console.log(`Tick [ ${player.getCurrentTick()} ]`);
}
```

### 重播

`Player` 提供 `play(rate)`,实时播放演示。 这个 `rate` 参数乘以速度。 回来的承诺:

-   返回到最后一个勾选时决定 .
-   以 `PlaybackInterruptedError` 与 `reason` `'paused'` 之后 `player.pause()` 或者说 `player.seekToTick()`。 。 。 .
-   以 `PlaybackInterruptedError` 与 `reason` `'stopped'` 之后 `player.stop()`。 。 。 .
-   以 `PlaybackInterruptedError` 与 `reason` `'disposed'` 之后 `player.dispose()`。 。 。 .
-   如果包处理失败, 则以原出错拒绝 .

```js
const playback = player.play(0.5); // half speed

setTimeout(() => player.pause(), 1000);

try {
    await playback;
} catch (error) {
    console.log(error.reason); // 'paused'
}
```

## HTTP 广播

HTTP广播在片断中通过HTTP进行直播匹配. 发动机为它提供两个课:

-   `BroadcastGateway` 请求从广播服务器取出碎片.
-   `BroadcastAgent` 跟着一个匹配,下载其碎片,并将其变成一串流. 30秒后,没有新的碎片,特工停止了.

广播的结构不同于 `.dem` 文档,所以 `Parser` 需要明确来源: `DemoSource.HTTP_BROADCAST`。 。 。 .

```js
import { BroadcastAgent, BroadcastGateway, DemoSource, Parser } from '@deademx/deadlock';

const MATCH_ID = 'MATCH_IDENTIFIER_AND_SALT';

const gateway = new BroadcastGateway('dist1-ord1.steamcontent.com/tv');
const agent = new BroadcastAgent(gateway, MATCH_ID);

const parser = new Parser();

await parser.parse(agent.stream(), DemoSource.HTTP_BROADCAST);
```

默认, `agent.stream()` 在服务器报告到的碎片前启动四个碎片 `/sync`接近现场. `agent.stream(true)` 从广播的第一个片段开始.

## 配置图宁( T)

`Parser` 财务报告和审定财务报表 `Player` 带一个 `ParserConfiguration` 在建筑师。 没有一个,两个都用 `ParserConfiguration.DEFAULT`。 。 。 .

### 过滤器

滤镜是解析器性能的主要杠杆——减少解码后的数据会同时切除内存和时间.

| 选项 | 类型 | 默认 | 过滤器 |
| --- | --- | --- | --- |
| `entityClasses` | `Array<string>|null` | `null` | 哪些实体类的字段解码 |
| `messagePacketTypes` | `Array<MessagePacketType>|null` | `null` | 信件类型允许列表 |
| `messagePacketTypesExclude` | `Array<MessagePacketType>|null` | `null` | 信件类型列表 |

四个消息包总是通过消息过滤器,因为引擎内部需要它们: `SVC_SERVER_INFO`, (中文(简体) ), `SVC_CREATE_STRING_TABLE`, (中文(简体) ), `SVC_UPDATE_STRING_TABLE` 财务报告和审定财务报表 `SVC_CLEAR_ALL_STRING_TABLES`。 。 。 .

```js
import { MessagePacketType, Parser, Player, ParserConfiguration } from '@deademx/deadlock';

// Skips all entities: everything else is decoded.
const configA = new ParserConfiguration({
    messagePacketTypesExclude: [MessagePacketType.SVC_PACKET_ENTITIES]
});

// Decodes only entities, and only the fields of player pawns and controllers.
const configB = new ParserConfiguration({
    messagePacketTypes: [MessagePacketType.SVC_PACKET_ENTITIES],
    entityClasses: ['CCitadelPlayerPawn', 'CCitadelPlayerController']
});

new Parser(configA);
new Player(configB);
```

### 流调

没有休息,长时间的剖析 或寻求冻结UI。 每个 N 演示包, N 所在位置 `breakInterval`,引擎会向事件循环输出, 所以计时器,输入和渲染在两者之间运行。 一个更低的值使得一个UI更能响应,并花费了一点速度. 默认为1000.

```js
new Parser(new ParserConfiguration({ breakInterval: 100 }));
```

## 业绩

实体解码( E)`SVC_PACKET_ENTITIES`) 占了分析者的大部分作品. `SVC_USER_COMMANDS` 是一个遥远的秒。 其它东西加起来都不算什么 表格显示解析速度相对于默认配置.

| 配置 | 死锁 | 反击2 | Dota 二号 |
| --- | --- | --- | --- |
| 所有东西, `ParserConfiguration.DEFAULT` | 1× (韩语) | 1× (韩语) | 1× (韩语) |
| 一个实体类别: `SVC_PACKET_ENTITIES` + 键 `entityClasses` 使用玩家控制器 | 3.7× (中文(简体) ) | 2.8× (中文(简体) ) | 2.3× (中文(简体) ) |
| 只有玩家输入 : `SVC_USER_COMMANDS` | 2.7× (中文(简体) ) | 1.8× (中文(简体) ) | —— 说 |
| 一个聊天消息类型, 没有实体和用户命令 | 5.0× (中文(简体) ) | 3.7× (中文(简体) ) | 4.2× (中文(简体) ) |

绝对数和内存使用量在 [`@deademx/deadlock`](https://github.com/Igor-Losev/deadem/blob/main/packages/deadlock/README.md#performance), (中文(简体) ), [`@deademx/cs2`](https://github.com/Igor-Losev/deadem/blob/main/packages/cs2/README.md#performance) 财务报告和审定财务报表 [`@deademx/dota2`](https://github.com/Igor-Losev/deadem/blob/main/packages/dota2/README.md#performance)。 。 。 .

### 提示

-   过滤到底需要什么。 最大的业绩收益.
    
-   下调 `breakInterval` 在浏览器中。 默认 `1000` (原始内容存档于2018-09-21). suits Node.js. [Deadem 探测器](https://deadem.com/) 用途 `100` 保持UI的反应.
    
-   注意呼叫数。 500 MB 死锁演示可能包含 ~ 150k `InterceptorStage.DEMO_PACKET` 电话,~3M `InterceptorStage.MESSAGE_PACKET` 调用,和带有~100M字段更新的~10M实体事件. 工作繁重 `InterceptorStage.MESSAGE_PACKET` 截取器的化合物速度快,并且可以罐装性能.
    
-   使用 `InterceptorStage.ENTITY_PACKET` 只有在需要比对时, 简单的读取当前状态——玩家位置,健康等——使用 `Demo` 从一个 `InterceptorStage.DEMO_PACKET` 代之以拦截器.
    
-   样本,而不是读取每一个勾。 用户界面很少在每个勾点上需要一个值 。 每秒一次意味着"死锁"和"反冲"2中每64个虱子,"多塔"2中每30个虱子.
    
-   随所需要多读. 首选点读取通过 `entity.getField('name')` 结束 `entity.unpackFlattened()` 和田地行走者。 实体状态被存储在打入的数组中,数值只在读取时实现. 联合来文: [`Entity.getField`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/entity/Entity.js) 说明成本.
    

## 类型

`@deademx/engine` 和游戏包船 TypeScript 声明. 包装 `data` 实值 `unknown` 直至 `is()` 将其缩小为游戏中的类型 `.proto` 文档.

```ts
parser.registerPostInterceptor(InterceptorStage.MESSAGE_PACKET, (demoPacket, messagePacket) => {
    if (messagePacket.is(MessagePacketType.CITADEL_USER_MESSAGE_CHAT_MESSAGE)) {
        console.log(messagePacket.data.text); // string
    }
});
```

实体字段没有静态类型. [Deadem 探测器](https://deadem.com/) 显示每个实体的类型和价值,以及 `getField<T>()` 将预期类型取为 `T`数字 :

```ts
const health = pawn.getField<number>('m_iHealth'); // number, an unset field is undefined
```

## 词汇

| 任期 | 含义 | 代码 |
| --- | --- | --- |
| 基线 | 类的默认字段值 。 一个新的实体从它们开始. | [`ClassBaseline`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/ClassBaseline.js) |
| 类 | 例如,实体的类型 `CCitadelPlayerPawn`定义了字段. | [`Class`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/Class.js) |
| 演示 | 当前勾选时的状态:实体,字符串表格,用户命令和服务器信息. | [`Demo`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/Demo.js) |
| 演示包 | 外包的演示流,19种类型之一. | [`DemoPacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/DemoPacket.js), (中文(简体) ), [`DemoPacketType`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/enums/DemoPacketType.js) |
| 实体 | 游戏世界的一个具有类和被命名字段的对象,例如英雄或能力. | [`Entity`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/entity/Entity.js) |
| 外地 | 例如,一个实体的命名值 `m_iHealth`。点加入巢名. | [`Field`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/fields/Field.js) |
| 处理 | 以一个实体的索引和序列编号. | [`Demo.getEntityByHandle`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/Demo.js) |
| 截取器 | 在引擎应用之前或之后接收每包的回调 . | [`InterceptorStage`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/enums/InterceptorStage.js) |
| 信件包 | 演示包的内包, 例如实体更新或聊天消息 。 每个游戏包都添加自己的 `MessagePacketType` 数值. | [`MessagePacket`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/MessagePacket.js), (中文(简体) ), [`MessagePacketType`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/enums/MessagePacketType.js) |
| 解析器 | 阅读一次演示,从头到尾. | [`Parser`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/Parser.js) |
| 玩家 | 拖出一个演示,然后寻找,步骤和播放. | [`Player`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/player/Player.js) |
| 抓图 | A 中的实体状态和字符串表 `DEM_FULL_PACKET`大约一分钟一次 引擎只在搜索开始时应用. | —— 说 |
| 字符串表 | 一个有索引条目的数据容器,每个有文本字符串和可选的二进制数据. | [`StringTable`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/tables/string/StringTable.js), (中文(简体) ), [`StringTableType`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/enums/StringTableType.js) |
| 选中 | 服务器模拟的一个步骤,通常在"死锁"和"反冲"2中为64分每秒,而"多塔"2中为30分. | [`Server`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/Server.js) (单位:千美元)`tickRate`(中文(简体) ) |
| 用户命令 | 一个玩家在勾选时的输入:按钮,取景角度和运动. | [`UserCommand`](https://github.com/Igor-Losev/deadem/blob/main/packages/engine/src/data/UserCommand.js) |

## 有用链接

-   [源代码多人网络](https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking):滴答,快照和三角洲压缩.
-   [连接事件和信件](https://developer.valvesoftware.com/wiki/Networking_Events_%26_Messages):网络消息和游戏事件.
-   [联网实体](https://developer.valvesoftware.com/wiki/Networking_Entities): 实体及其联网字段.
-   [用户cmd](https://developer.valvesoftware.com/wiki/Usercmd):用户命令.
-   [反击:全球攻击性广播](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Broadcast):HTTP广播协议.
-   [DEM( 文件格式)](https://developer.valvesoftware.com/wiki/DEM_Format): 旧的演示格式头和框.
-   [解密记录工具](https://developer.valvesoftware.com/wiki/Demo_Recording_Tools):录制并播放演示.

## 许可证

[麻省理工学院](https://github.com/Igor-Losev/deadem/blob/main/LICENSE)

## 常见问题

### @deademx/engine 是什么？

共享源 2 为 Node\.js 和浏览器演示和回放播放引擎

### 如何安装 @deademx/engine？

在项目目录运行 npm install @deademx/engine，或使用 pnpm add / yarn add / bun add。

### @deademx/engine 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @deademx/engine 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @deademx/engine 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=18\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @deademx/engine 可以商用吗？

@deademx/engine 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @deademx/engine 的下载量有多少？

根据 npm 官方下载统计，@deademx/engine 在 2026/10/12 – 2026/11/10 这 30 天内被下载 3,413 次，日均约 114 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @deademx/engine 还在维护吗？

最新版本 5\.0\.0 发布于 2026/10/27（16 天前）；至今共发布 21 个版本，其中过去 12 个月发布了 21 个；首次发布于 2026/04/22。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

