跳到主要内容

状态模型

state.data 就是这名玩家此刻能看到的全部内容,每来一条新消息就顶掉上一份。

如何读取状态

规则客户端行为
收到新消息替换上一份 PlayerState,不要合并数组。
读取对象先看 kind,再按该类型读取字段。
判断归属controlled: true 表示己方,false 表示当前可见的敌方。
某个字段缺失它的值未知或不适用。服务端不会发送 null
最小状态消息
{
"type": "state",
"data": {
"status": "ACTIVE",
"resources": 20,
"population": 1,
"population_tier": 0,
"upkeep_next_tick": 0,
"champion_beacon": {"position": [0, 0]},
"objects": [
{
"kind": "CORE",
"id": "2ea3c3dc-42b0-4b92-9754-7558bd4ff834",
"controlled": true,
"position": [12, 8],
"hp": 5,
"shield": 5,
"state": "NORMAL"
},
{
"kind": "UNIT",
"id": "9d3e4941-2816-4a39-a220-df8cd95e877d",
"controlled": true,
"position": [11, 8],
"hp": 2,
"unit_type": "WORKER",
"cargo": 0
}
],
"events": []
}
}

需要机器可读的定义,看 AsyncAPI schema

PlayerState

字段格式必需含义
status"ACTIVE""RESPAWNING"玩家有存活的 Core,还是正在等待重生。
respawn_at_tick正 int64仅重生中下一次尝试重生的 Tick。
resources非负整数Core 里存的资源;Worker 身上的 cargo 另算。
population非负整数存活的己方 Unit 数,不含 Core。
population_tier非负整数floor(population / 20)
upkeep_next_tick非负整数当前人口对应的 tier × (tier + 1) / 2
champion_beaconobject公开位置,以及可见时的携带状态。
objectsarray己方实体,加上当前可见的地形和敌方实体。
eventsarray发给这名玩家的结算结果。

没有内容时,objectsevents 是空数组 [],而不是干脆不出现。RESPAWNING 期间资源和人口字段照样在,但在 CORE_RESPAWNED 到来之前,你可能一个 Core 都没有。

Champion Beacon

位置永远公开,其余的就看你能不能看见了。

视野外

{
"position": [120, 85]
}

你只知道它在哪儿,别的一概不知——躺在地上还是被人拿着,都看不出来。

可见且在地面

{
"position": [120, 85],
"status": "GROUND"
}

这时候没有 carrier_id

可见且被携带

{
"position": [120, 85],
"status": "CARRIED",
"carrier_id": "9d3e4941-2816-4a39-a220-df8cd95e877d"
}

carrier_id 指的是携带它的那个 Core 或 Unit。如果下一份状态里没有 statuscarrier_id,把旧值丢掉,别留着接着用。

世界对象

objects 里每一项都以 kind 开头。

kind表示身份
"CORE"一个 Coreid
"UNIT"一个 Worker、Vanguard 或 Rangerid
"OBSTACLE"所有可见障碍格单个坐标
"RESOURCE"所有可见资源格单个坐标
按 kind 分发
for (const object of state.objects) {
if (object.kind === 'CORE') handleCore(object);
else if (object.kind === 'UNIT') handleUnit(object);
else handleTerrain(object);
}

地形

{
"kind": "OBSTACLE",
"positions": [[4, 7], [4, 8], [5, 8]]
}
字段格式含义
kind"OBSTACLE""RESOURCE"地形类型。
positions非空 [x, y] 数组可见格,先按 x、再按 y 排序。

同一种可见地形的所有格子都合并成一项。某个 kind 整个不出现,就说明当前一格都 看不见。地形没有 id、没有 controlled、没有 HP,也没有资源数量。

Core

正常 Core
{
"kind": "CORE",
"id": "2ea3c3dc-42b0-4b92-9754-7558bd4ff834",
"controlled": true,
"position": [12, 8],
"hp": 5,
"shield": 4,
"state": "NORMAL"
}
迁移中的 Core
{
"kind": "CORE",
"id": "2ea3c3dc-42b0-4b92-9754-7558bd4ff834",
"controlled": true,
"position": [12, 8],
"hp": 5,
"shield": 4,
"state": "MOVING",
"move_direction": "RIGHT",
"move_progress": 2,
"move_required_ticks": 4,
"destination": [13, 8]
}
字段格式必需
kind"CORE"
idUUID
controlledboolean
position[x, y]是;迁移期间仍是起点
hp非负整数
shield非负整数
state"NORMAL""MOVING"
move_direction方向字符串仅迁移中
move_progress正整数仅迁移中
move_required_ticks正整数仅迁移中;当前为 4
destination[x, y]仅迁移中

正常状态的 Core 一个移动字段都没有。看得见的敌方 Core,暴露的字段和你自己的一样。

Unit

己方 Worker
{
"kind": "UNIT",
"id": "9d3e4941-2816-4a39-a220-df8cd95e877d",
"controlled": true,
"position": [11, 8],
"hp": 2,
"unit_type": "WORKER",
"cargo": 1
}
字段格式必需
kind"UNIT"
idUUID
controlledboolean
position[x, y]
hp非负整数
unit_type"WORKER""VANGUARD""RANGER"
cargo非负整数仅己方 Worker

敌方 Worker 的 cargo 对你不可见。Vanguard 和 Ranger 则根本不带 cargo 字段, 自己的也不带。

视野

数据何时出现隐藏字段
己方 Core 和 Unit始终对象格式里的字段全都可见
敌方 Core 和 Unit所在格当前可见所有者身份;敌方 Worker 的 cargo
地形所在格当前可见资源数量
Beacon 位置始终
Beacon 状态和携带者Beacon 所在格当前可见视野外时两个字段都没有

这里没有任何「上次看见」的时间戳,所以你记住的地形要单独存一份,别和服务端当前 状态混在一起。

更新状态

每收到一份新状态,就把实体映射重建一次:

const entities = new Map();

for (const object of nextState.objects) {
if (object.kind === 'CORE' || object.kind === 'UNIT') {
entities.set(object.id, object);
}
}

服务端发对象的顺序是确定的:

  1. 障碍批次;
  2. 资源批次;
  3. 己方 Core;
  4. 按 UUID 排序的己方 Unit;
  5. 按 UUID 排序的可见敌方 Core;
  6. 按 UUID 排序的可见敌方 Unit。

空的分组会直接跳过——正因为如此,数组下标永远不能当成对象的身份。