LPAI 独立网页游戏 UGC 社区指南

联网与插件

弱联网脚本

用条件树、分组、聚合和限流查询公开玩家快照,并明确隐私与完整存档边界。

脚本系统 · 弱联网

在不开放完整存档的前提下共享玩家状态

弱联网适合排行榜、队伍/公会成员、世界状态和玩家名片。玩家主动上传插件映射的数值和动态字符串,以及所选对象类中的完整可序列化运行时对象;其他玩家只能按条件查询快照,不能读取或修改对方存档。

# 先定义公开快照契约

告诉 AI 哪些数值、字符串和对象类可以公开,玩家何时主动上传,其他玩家按什么条件查询,以及页面如何展示昵称和头像。AI 必须先配置弱联网插件映射,再编写上传/查询脚本。对象类不是逐字段白名单:弱联网会序列化该类下对象的完整可序列化运行时数据。不得上传完整存档、包含秘密的对象类或私密文本。

上传有三分钟限流,查询/聚合有三秒限流和缓存。渲染布局只展示查询结果,不能把网络返回内容当成可执行脚本或 Canvas 模块源码。

# 配置与脚本的边界

纯 AI 路径让 AI 同时维护插件映射和异步脚本;打开传统模式文档后,作者需在插件中选择公开字段,并在脚本里构造条件树、分页、排序和分组。下面的 API、返回结构、缓存和隐私边界两种路径都必须遵守。

# 先完成插件配置

在编辑器的弱联网插件中配置会变成公开数据的资源:

映射 上限 实际上传内容
数值属性或组合属性 30 个槽位 玩家当前的有限数值;已映射的数值属性还可用于筛选、排序、分组和聚合。
动态字符串 20 个槽位 解析后的玩家字符串,每次上传每项最多 1000 个字符。
对象类 20 个类 该类下玩家拥有的每个对象的全部可序列化字段,共享 20000 个字符的序列化预算。内容可能包含名称、描述、ObjectPropertyCount 和其他可序列化运行时状态。

对象映射不是字段级公开白名单。启用一个对象类前,必须审查它完整的运行时结构。对象类按照插件列表顺序序列化;共享 20000 字符预算不足时,后面的对象会被跳过,因此应把重要对象类放在前面。

脚本运行时不能自行选择上传资源。tools.uploadWeakOnline() 只读取插件映射;插件关闭、没有资源配置,或正式环境中的匿名玩家仍叫 "Player" 时返回 false。保存、删除或调整映射只影响之后的上传,不会重写历史快照;每次改映射后都必须重新上传。

# 上传快照

tools.uploadWeakOnline()
  .then((ok) => {
    if (ok) tools.showToast(tools.tr("状态已同步"), "success");
  })
  .catch(() => {
    tools.showToast(tools.tr("同步太频繁,请稍后再试"), "warning");
  });

上传是异步操作,只上传弱联网快照,不会保存本地存档,也不会触发云存档。FangZhi 和后端共同限制:同一玩家在同一项目中每 3 分钟最多上传一次。失败或限流会 reject,不能放在每帧脚本、js_move 或循环中调用。

正式运行中,匿名玩家 "Player" 不能上传;查询和聚合会返回空结果,直到玩家完成认证。编辑器测试服使用隔离的测试服项目命名空间,测试数据不会与正式数据混合。

# 查询快照

// 必须替换为 AI 从当前项目查询到的真实 ID
const levelPropertyId = "实际等级属性ID";
const powerPropertyId = "实际战力属性ID";

const result = await tools.queryWeakOnline(
  { and: [{ field: levelPropertyId, operator: "gte", value: 10 }] },
  1,
  10,
  { field: powerPropertyId, order: "desc" }
);

for (const item of result.items) {
  // username 字段为兼容旧脚本保留,实际值是公开昵称
  tools.showNotify(`${item.username}:${item.numbers[powerPropertyId] ?? 0}`);
}

签名:

tools.queryWeakOnline(filters, page?, limit?, sort?, groupBy?)

filters 是条件树,不能传任意 SQL 或字符串。每个 field 都必须是弱联网插件已启用的数值属性真实 ID,每个 value 都必须是有限数值:

const levelPropertyId = "实际等级属性ID";
const factionCodePropertyId = "实际阵营编码属性ID";

// 叶子条件
{ field: levelPropertyId, operator: "gte", value: 10 }

// 任意嵌套 and / or
{ and: [
  { field: levelPropertyId, operator: "gte", value: 10 },
  { or: [
    { field: factionCodePropertyId, operator: "eq", value: 1 },
    { field: factionCodePropertyId, operator: "eq", value: 2 }
  ] }
] }

支持的操作符:eqneqgtgteltlte{ and: [] } 表示查询全部。sort 只能按已配置的数值属性 ID 排序,例如 { field: powerPropertyId, order: "desc" };普通查询可传 { order: "random" } 随机抽取,但只支持第 1 页,不使用随机数据库排序,也不走 15 分钟结果缓存。

普通查询返回:

{
  items: [
    {
      username: "公开昵称",
      avatar: "<svg ...>",
      numbers: { "实际战力属性ID": 1200 },
      strings: { "实际称号动态字符串ID": "探索者" },
      object: { "实际伙伴对象类ID": [] },
      updatetime: "2026-01-01T00:00:00Z"
    }
  ],
  page: 1,
  limit: 10,
  hasMore: false
}

numbersstringsobject 分别以插件配置的属性 ID、动态字符串 ID、对象类 ID 为键,不是显示名称,也不是内部的 num_XX 槽位。运行时和后端只在内部把真实资源 ID 转为存储槽位。avatar 是完整 SVG 字符串;旧快照缺少字段时,数值、字符串和对象分别按 0、空字符串和空数组处理。修改插件配置不会重写历史快照。

非随机查询结果会缓存约 15 分钟;任意玩家上传新快照时,会使该项目的弱联网查询与聚合缓存失效。

每次普通查询最多返回 10 条。查询与聚合共享同一个运行时限流:任一操作完成后,同一玩家必须等待 3 秒才能再次执行这两种操作中的任意一种。

# 按队伍或公会分组

将第五个参数传入已配置的分组属性 ID:

const powerPropertyId = "实际战力属性ID";
const guildIdPropertyId = "实际公会ID属性ID";

const result = await tools.queryWeakOnline(
  { and: [] },
  1,
  10,
  { field: powerPropertyId, order: "desc" },
  guildIdPropertyId
);

for (const group of result.groups) {
  console.log(group.value, group.memberCount, group.items);
}

分组返回 { groups, page, limit, hasMore }。每组包含 { value, memberCount, items }items 固定最多 5 条详情;每页最多 10 组。组按分组值升序排列,分组模式不支持 random 排序,sort 只控制组内详情顺序。

# 聚合数值

需要统计世界平均战力、最高分或总人数时,用独立的聚合接口:

const levelPropertyId = "实际等级属性ID";
const powerPropertyId = "实际战力属性ID";

const result = await tools.aggregateWeakOnline({
  and: [{ field: levelPropertyId, operator: "gte", value: 10 }]
});

console.log(result.totalCount, result.metrics[powerPropertyId]);
// { count, min, max, avg, sum }

filters 省略或传 { and: [] } 表示匹配全部快照。每个数值属性都会返回 countminmaxavgsum;没有上传该属性时 count0,其他值为 null。聚合结果服务端缓存 15 分钟;任意玩家上传新快照会使该项目的聚合缓存失效。聚合与普通/分组查询共用同一个 3 秒间隔。

# 与传统联网能力的边界

  • 弱联网是“公开快照查询”,不是实时通信;不要用它实现即时战斗同步。
  • 查询结果只读,脚本不能修改其他玩家的属性、字符串或对象。
  • 选择对象类会公开完整可序列化运行时对象,而不是所选字段;不要映射包含私密或秘密状态的对象类。
  • 需要完整进度、多槽存档或跨设备恢复时,使用本地/云存档流程。
  • 不能通过弱联网接口上传密码、令牌、私密文本或未经玩家同意的个人信息。