zinc-j
本项目 .j 文件中的 Zinc(//! zinc ... //! endzinc)语法与约定:库/结构体写法、匿名函数(无闭包)最佳实践、回调参数传递、资源释放/置空、以及 JASS->Zinc 迁移要点。
SKILL.md.codex/skills/zinc-j/SKILL.md
GitHubZinc(本项目)语法与约定
必须遵守
- 用
//! zinc///! endzinc包裹 Zinc 代码块。 - 禁用
++/--;用+= 1/-= 1。 - 禁用
break/continue;用if分支或布尔开关改写。 - 数组声明一律用
type name[];,不要写大小(例如integer a[10]是错的)。 - Zinc 的数组维度里不要写表达式(例如
arr[HERO_COUNT + 1][5]);需要先把值算成常量再使用,或直接用固定上界。 - 二维数组在底层是线性存储;只要索引映射不冲突(例如
a[6][5]下(1,5)与(2,1)映射不同),可以按项目约定安全使用,不必为了“从 1 开始”额外预留整段[1]。 - 返回类型用
-> type写在参数列表后;无返回可省略。 - 局部变量统一在函数/匿名函数开头声明:先基础类型,后句柄类型。
- 函数定义顺序不再作为日常写法的硬性约束:
vjassc会处理上方函数直接调用下方函数的场景。 - 仍需兼容
jasshelper时,如果它只因函数顺序报错而vjassc能过,优先在该调用点改成xxxx.evaluate(...),少做大范围函数重排。 - 本地场景(例如 UI 本地执行)禁止
execute,只能用evaluate,否则有 OOS 风险。 - 仅在本文件内引用的固定长文案/标签,不要另起一个只
return "..."的函数;优先按CenterUp.j的GetResumeLabel()风格用函数式#define,并在文件末尾配套#undef。
匿名函数(无闭包)
- 匿名函数内不能直接访问外层局部变量;需要显式传参。
- 传参优先用结构体/库的
static成员变量,并在回调结束后立刻清理为默认值(null/0/"")。 - 临时触发器回调用
CreateTrigger+TriggerAddCondition+TriggerEvaluate,随后DestroyTrigger并置null。
句柄生命周期(资源释放)
- 创建出来的句柄资源(
timer/trigger/group/effect/location/...):按对应Destroy*/Remove*API 释放,并在作用域末尾= null。 - 可分配结构体(通过
allocate()/create()取得实例)析构只写method onDestroy();外部必须调用实例的.destroy(),使子句柄清理与结构体回收一起执行。 - 不要把
destroy1()当作可分配结构体的析构函数:它只是普通方法,既不会自动触发onDestroy(),也不会回收结构体实例。destroy1()仅可用于struct X []这类静态管理器的内部 UI 清理流程。 - 不要自定义
destroy()。
结构体/方法写法
- 静态方法:
static method foo(...) -> type { ... } - 实例方法:
method foo(...) -> type { ... } - 纯静态“管理器”可用
struct X [] { ... }(避免create/destroy)。
跨位置调用与性能约束
execute:不带返回值(底层是TriggerExecute触发 action)。evaluate:可用于需要返回值或不需要返回值的调用场景。jasshelper会把evaluate降成 trigger/condition 包装,存在额外资源开销;vjassc通常能把这类调用智能降成直接调用。- 不要为了旧的函数顺序习惯重排大段代码;只有在需要通过
jasshelper或解决循环调用链时才引入evaluate/execute。
玩家工具与日志名
- 判断地图有效玩家或在线用户时,直接调用 War3Lib
PlayerUtils.cfg注入的IsValidPlayer(player)/IsOnlineUser(player);不要在地图模块内重复定义,也不要重复手写GetPlayerSlotState(...) == PLAYER_SLOT_STATE_PLAYING && GetPlayerController(...) == MAP_CONTROL_USER。 - Xlimon 中通过
DzWriteLog、CrainaxLogTrace/Debug/Info/Warn/Error或项目日志包装函数记录玩家名时,统一写PlayerName[GetConvertedPlayerId(p)](已有pid/index时优先直接用对应数组下标);禁止用GetPlayerName(p),避免日志出现平台前缀(如[初雪境#0])。该约束只针对日志,不要机械改动 UI 文案、权限判断、随机种子等非日志语境。
JASS -> Zinc 迁移要点
- JASS 默认
public;Zinc 默认private:需要对外可见的函数/全局变量要显式public。 loop/exitwhen/endloop优先改写为 Zinc 的范围for (a <= i <= b)或常规 for。- timer/trigger 的一次性逻辑优先改成匿名回调。
Hash 表键位
- 没有必须隔离生命周期或键空间的理由时,不要在 Xlimon 本地新建
InitHashtable();优先复用 War3Lib 的共享表(如HASH_ITEM/HASH_UNIT/HASH_BIGINT),并把新增子键跨仓定义到对应的D:/War3/Library/War3Lib/Jass/core/table/Hash_*Define.j文件,避免以后生成代码又在本地创建重复小表。 - 只有确实无法通过共享表完成的任务,才新建独立 hashtable;例如需要独立 flush 整个 child 且不能逐键清理,或键空间/生命周期必须与共享表隔离。
- 新增或迁移
HASH_UNIT/HASH_ITEM/HASH_BIGINT子键时,先检查对应D:/War3/Library/War3Lib/Jass/core/table/Hash_*Define.j内是否已有相同数值;同一个 hashtable 内数值冲突会导致完全不同系统互相覆盖。 Hash_UnitDefine.j、Hash_ItemDefine.j、Hash_BIDefine.j是不同 hashtable 的键名空间;跨表数值相同通常不是问题,但同文件内数值和宏名都必须唯一。- 修改共享键位前,先搜索旧宏名的全部
Save*/Load*/RemoveSaved*调用,确认应该移动新键还是保留既有系统键位。 - 可用以下命令快速检查三张表内重复数值和重复宏名:
$files = 'D:\War3\Library\War3Lib\Jass\core\table\Hash_UnitDefine.j','D:\War3\Library\War3Lib\Jass\core\table\Hash_ItemDefine.j','D:\War3\Library\War3Lib\Jass\core\table\Hash_BIDefine.j'; foreach ($file in $files) { Write-Host "FILE $file"; $defs = Select-String -Path $file -Pattern '^\s*#define\s+(\S+)\s+(-?\d+)\b' | ForEach-Object { [pscustomobject]@{ Line=$_.LineNumber; Name=$_.Matches[0].Groups[1].Value; Value=[int64]$_.Matches[0].Groups[2].Value; Text=$_.Line.Trim() } }; $defs | Group-Object Value | Where-Object Count -gt 1 | ForEach-Object { Write-Host " DUP VALUE $($_.Name)"; $_.Group | ForEach-Object { Write-Host (" L{0}: {1}" -f $_.Line, $_.Text) } }; $defs | Group-Object Name | Where-Object Count -gt 1 | ForEach-Object { Write-Host " DUP NAME $($_.Name)"; $_.Group | ForEach-Object { Write-Host (" L{0}: {1}" -f $_.Line, $_.Text) } } }
References(按需加载)
references/jass-to-zinc.mdreferences/anonymous-functions.mdreferences/callback-params.mdreferences/method-syntax.mdreferences/function-order-and-indirect-call.md