zyydi_PinyinSearch

This WoW addon zyydi_PinyinSearch enables searching Chinese names via Pinyin without switching IME for bags, banks, talents and more, with polyphone support, while handing control back to native search when Chinese is typed.

zyydi Pinyin Search (zyydi_PinyinSearch)

Add pinyin support to the search boxes in the World of Warcraft CN client — directly type pinyin to search for Chinese item names without switching input methods.

Inspired by Minecraft's JustEnoughCharacters (JEI Pinyin Search).

  • v1.1.0 Compatible with third-party bag addons (EllesmereUI Bags / Bank and other addons that render grid cells manually)
  • v1.2.0 Fixed multiple bugs. Auction house support was planned, but auction house searches are sent to the server and results returned to the client, which is quite tricky; exploring workarounds.
  • v1.3.0 Added support for Achievement panel, Talents, and Spellbook; fixed spammy error messages when refreshing containers.

Supported Interfaces

表格

Interface Mode Description
Bags / Bank / Warband Bank / Guild Bank Filter-as-you-type Works exactly like Chinese input; just type pinyin
Talent Page / Spellbook / PvP Talents Filter-as-you-type Same as above. Talent search uses pure Lua filtering with overridden matching logic
Auction House Pinyin → Chinese name autocomplete Real-time filtering is not feasible
Achievement Panel Pinyin → Chinese name autocomplete Same as above. Achievement filtering runs on the C++ side

Search Examples

表格

Input Matches Notes
zlys Healing Potion Initials only
zhiliao Healing Potion Full pinyin (can start from any character)
zlyaoshui Healing Potion Mixed initials + full pinyin
yaoshui Healing Potion Starts from a middle character
wanggu Kingdom Partial pinyin for the last character
ypazdlxdy A Vial of Dirty Vicious Poison Long string of initials
治疗 Healing Potion Native Blizzard search unchanged when Chinese text is entered
  • Polyphonic character support: can be matched with either xue or xie.
  • Optional fuzzy phoneme matching: z/zh, c/ch, s/sh, in/ing, en/eng, an/ang, n/l, f/h, r/l (disabled by default to avoid false matches).
  • Only activates when the input box contains no Chinese characters; native Blizzard search resumes immediately once Chinese is typed, so it should not interfere.
  • For bags: restores hidden matches instead of hiding items. Items matched by the native filter are always kept; only items missed by native filtering but matched by pinyin are restored.

Installation

  1. Copy the entire zyydi_PinyinSearch folder into World of Warcraft\_retail_\Interface\AddOns\
  2. Launch the game, go to the character selection screen, click AddOns in the bottom-left corner and enable it.
  3. Open your bags and directly type pinyin into the search box.

Talents / Spellbook (Works out of the box)

Talent search uses Blizzard's pure Lua filter (Blizzard_SpellSearch). All talent nodes, PvP talents and spellbook entries eventually call these two functions:

SpellSearchUtil.DoesStringContain(parentString, substring)  -- Check if name contains search term
SpellSearchUtil.DoStringsMatch(string1, string2)            -- Check for exact match

We override these functions directly, so pinyin filtering on the talent page works instantly, with the same experience as Chinese input. No configuration required.

Covers: Class talent tree, Hero Talents, PvP Talents, Spellbook search box.

Auction House Usage

Auction house search runs server-side (C_AuctionHouse.SendBrowseQuery). The client cannot modify matching rules, so pinyin input returns zero results on the server. Therefore, the plan uses "pinyin → Chinese name autocomplete", which is not fully functional yet:

  1. Open Auction House; addon builds candidate index in background (see below)
  2. Type pinyin in search box → dropdown lists matching Chinese item names
  3. Press Enter to search with the top match; use Tab / ↓ to switch candidates, or click to select
  4. Selected Chinese name fills the search box and triggers native search, identical to manual Chinese input

Index Sources (Priority Order)

表格

Source Description
Auctionator Price Database Keys in Auctionator.Database.db are itemIDs; item names can be retrieved directly. No 15-minute cooldown, reuses Auctionator’s existing index. Enabled by default; trigger manually with /zyps ahuse
Ride-along Full Scan Harvest data unconditionally on REPLICATE_ITEM_LIST_UPDATE — meaning we collect items when Auctionator runs its own full scan, with no extra cooldown. This fixes a critical bug in v1.3.0 (previous version only collected data on our own scans, which almost never fired)
Self-initiated Scan Fallback if above sources are unavailable: C_AuctionHouse.ReplicateItems(), 15-minute cooldown
Browse Results Append new entries whenever browse results are received

The index only contains currently listed items; it is accurate and compact (several thousand to tens of thousands of entries). /zyps ah to view status, /zyps ahscan to trigger manual full scan.

Achievement Panel Usage

Achievement filtering also runs on the C++ side (SetAchievementSearchStringGetFilteredAchievementID), so autocomplete is the only option:

  1. Open the Achievement panel (click the search box to build index in background, all from client cache; no scanning required)
  2. Type pinyin → dropdown lists matching achievement names → Enter / Click → Chinese name fills search box and uses native search.

/zyps ach to view index status, /zyps achbuild to rebuild index.

Commands

表格

Command Function
/zyps Open settings panel (also available in System → Options → AddOns)
/zyps on / /zyps off Toggle enable / disable
/zyps test zlys Run matching test on bag items and print matched list
/zyps fuzzy Quick toggle for z/zh c/ch s/sh fuzzy phoneme matching
/zyps status View hook and pinyin table status for all modules
/zyps ah Auction house candidate index status (item count, source, age)
/zyps ahuse Immediately import item names from Auctionator index
/zyps ahscan Manually trigger full scan (must be at Auction House NPC)
/zyps ach / /zyps achbuild Achievement index status / rebuild index
/zyps talent Talent / Spellbook search hook status
/zypinyin / /pinyinsearch Alias for above

Implementation Details

Bags: Intercept at data source

Blizzard bag search comparisons are handled on the C++ side (C_Container.GetContainerItemInfo() directly returns isFiltered). Lua cannot change native matching rules. The addon uses an approach of intercept results + restore matches, hooked at the data source. This works for both native Blizzard bags and third-party bag addons, as long as they read isFiltered.

Three layers of hooks as fallbacks:

  1. Data Source (Primary) Override C_Container.GetContainerItemInfo(bag, slot)
    • If native isFiltered == true (item would be hidden) AND pinyin matches → override to false to display item
    • Covers Blizzard Bags / Bank / Warband Bank, EllesmereUI Bags / Bank, and any addon calling this API
    • EllesmereUI uses fully custom grid cells and does not inherit ItemButtonMixin, so button-level hooks alone have no effect — this was why v1.0 failed to work.
  2. Search Term Hook: hooksecurefunc(C_Container, "SetItemSearch", ...) to capture current search text. Additionally scan global EditBoxes whose names contain bag/bank/inventory/container + search and attach OnTextChanged handlers, for addons that do not call SetItemSearch.
  3. Button Layer (Fallback) Override ItemButtonMixin:SetMatchesSearch(matches), used for Guild Bank and similar cases.

Autocomplete Engine

Auction House and Achievement panel share the dropdown engine in Suggest.lua: Type pinyin → 80ms debounce → fetch candidates → render dropdown → Enter / Tab / ↓ / Click to select. Enter key is handled before native processing (native search receives raw pinyin and returns nothing). Each keystroke checks whether the Enter script has been overwritten by other addons and restores it if needed.

Other Details

  • Item names prefer ContainerItemInfo.itemName (available in 12.x), fall back to extracting [...] from item hyperlinks.
  • Matching uses linear DP: each Chinese character can consume query string either by full pinyin or initial, supports starting match at any character, and partial pinyin for the final character. ~2–4 ms for 200 items with one character typed.
  • Candidate lookup is two-stage: fast pre-filter using precomputed initial / full-pinyin strings for each name with string.find. Mixed input falls back to DP after prefix pruning.
  • Hot path only uses one upvalue boolean check (benchmark: 20000 calls to GetContainerItemInfo: ~0.28s without search term, ~0.40s with active search).
  • Debug output deduplication: same message only printed once per 60 seconds; repeated errors logged once only (ns.WarnOnce).

Pinyin Data

  • Data source: pinyin.txt from mozillazg/pinyin-data
  • Covers 26713 CJK characters (CJK Unified Ideographs Extension A + Compatibility Ideographs + 〇), stores up to 3 pronunciations per character.
  • Storage: stored as pinyin → character string, reversed at runtime. Data file size ~111 KB.
  • Regenerate data:
python tools/generate_pinyin_data.py <path to pinyin.txt>
# Default reads ../pinyin-data-master/pinyin.txt, outputs ../PinyinData.lua

File Structure

zyydi_PinyinSearch/
├─ zyydi_PinyinSearch.toc   Addon manifest
├─ PinyinData.lua           Generated pinyin table (do not edit manually)
├─ Matcher.lua              Pinyin matching engine (full pinyin / initials / mixed / partial final char / fuzzy phonemes / name keys)
├─ Core.lua                 Three-tier bag hooks + item name parsing + refresh handling
├─ Suggest.lua              Reusable pinyin-to-Chinese autocomplete dropdown (shared by Auction House / Achievements)
├─ AuctionHouse.lua         Auction House: index source management + candidate list + search trigger after selection
├─ Achievements.lua         Achievements: index + candidates + native search after selection
├─ SpellSearch.lua          Talents / Spellbook: override SpellSearchUtil matching for filter-as-you-type
├─ Options.lua              Settings panel + chat commands
└─ tools/
   ├─ generate_pinyin_data.py   Data generation script
   └─ test/                     Node.js unit tests (fengari Lua VM + luaparse)

Self-Testing

Requires Node.js (luaparse / fengari installed to ~/.workbuddy/binaries/node/workspace):

cd tools/test
NODE_PATH=<workspace>/node_modules node check_syntax.js        # Syntax check for 8 Lua files
NODE_PATH=<workspace>/node_modules node test_matcher.js        # 27 matching algorithm test cases
NODE_PATH=<workspace>/node_modules node test_integration.js    # Bag pipeline + no spam on refresh
NODE_PATH=<workspace>/node_modules node test_modules.js        # Autocomplete / Auction House / Achievements / Talents

Test coverage: data source isFiltered, button layer, toggle behavior, restore-only invariant, hot path performance, hidden container skip and error deduplication, autocomplete dropdown (sorting / enter / click / tab / tooltip / restore overridden handlers), Auctionator index reuse, ride-along full scan, achievement index, talent matching override.

Known Limitations / Future Work

  • Auction House and Achievements only support autocomplete, not filter-as-you-type — bottleneck is server-side / C++ implementation, not addon limitation.
  • Self-initiated ReplicateItems has a 15-minute cooldown; almost unnoticeable when Auctionator is present.
  • Item names missing from Auctionator’s index will only be populated after client receives the data (second pass indexing).
  • Bag addons that implement fully custom filtering without reading isFiltered or calling SetItemSearch cannot be adapted.
  • Search for Transmog / Mount / Pet collections does not use ItemButtonMixin and requires separate implementation (planned for later versions).
  • Fuzzy phoneme matching is disabled globally by default; enable in settings if you want forgiving typo tolerance.

zyydi的拼音搜索 (zyydi_PinyinSearch)

给魔兽世界国服客户端的搜索框加拼音支持——直接敲拼音就能搜到中文名称,不用切输入法。 灵感来源: Minecraft 的 JustEnoughCharacters(JEI 拼音搜索)

  • v1.1.0 兼容第三方背包插件(EllesmereUI Bags / Bank 等自己画格子的插件)
  • v1.2.0 修复了若干bug,想要做拍卖行支持,但是拍卖行是搜索之后直接到服务器端然后返回客户端,有点麻烦,在找邪道。
  • v1.3.0 支持成就面板天赋法术书;修复刷新容器时刷屏的报错

各界面支持情况

界面 方式 说明
背包 / 银行 / 战团银行 / 公会银行 边打边过滤 和打中文一模一样,只需要敲拼音
天赋页 / 法术书 / PvP 天赋 边打边过滤 同上,天赋搜索是纯 Lua 过滤,直接接管判定
拍卖行 拼音 → 中文名补全 支持不了一点
成就面板 拼音 → 中文名补全 同上,成就搜索也是 C++ 侧过滤

能搜什么

输入 能搜到 说明
zlys 治疗药水 首字母
zhiliao 治疗药水 全拼(可从任意字开始)
zlyaoshui 治疗药水 首字母 + 全拼混打
yaoshui 治疗药水 从中间某个字开始
wanggu 王国 最后一个字只打一半拼音
ypazdlxdy 一瓶肮脏的烈性毒液 一长串首字母
治疗 治疗药水 中文照常走系统原生搜索(不干预)
  • 支持多音字: 可以用 xue 也可以用 xie 搜到
  • 可选模糊音:z/zh、c/ch、s/sh、in/ing、en/eng、an/ang、n/l、f/h、r/l(默认关闭,避免误匹配)
  • 只在输入框里不含中文时接管;一打中文就交回暴雪原生搜索,理论上不会帮倒忙
  • 背包里只做"救回"不做"隐藏":系统认为匹配的物品一定保留,只把系统漏掉、拼音命中的放出来

安装

  1. 把整个 zyydi_PinyinSearch 文件夹复制到 World of Warcraft\_retail_\Interface\AddOns\
  2. 进游戏,角色选择界面左下角「插件」里勾选启用
  3. 打开背包,直接在搜索框里打拼音

天赋 / 法术书(开箱即用)

天赋搜索是暴雪用 纯 Lua 写的过滤器(Blizzard_SpellSearch), 所有天赋节点、PvP 天赋、法术书条目最终都走这两个函数:

SpellSearchUtil.DoesStringContain(parentString, substring)  -- 名字里是否包含搜索词
SpellSearchUtil.DoStringsMatch(string1, string2)            -- 是否完全等于搜索词

直接接管它们就行,所以天赋页打拼音是即时过滤,跟打中文体验一致,什么都不用配置。 覆盖:职业天赋页、英雄天赋、PvP 天赋、法术书搜索框。

拍卖行怎么用

拍卖行搜索是服务端做的(C_AuctionHouse.SendBrowseQuery),客户端改不了匹配规则, 敲拼音服务端一个都搜不到。所以计划走「拼音 → 中文名补全」,目前用不了一点:

  1. 打开拍卖行,插件自动建候选索引(见下)
  2. 搜索框打拼音 → 下拉列出候选中文物品名
  3. 回车用最匹配的那个搜,Tab / ↓ 切换候选,点击也行
  4. 选中的中文名填进搜索框并发起搜索,之后和手打中文完全一样

索引来源(按优先级)

来源 说明
Auctionator 的价格库 Auctionator.Database.db 的 key 就是 itemID,取出来补名字即可。不用等 15 分钟冷却,等于白嫖它积累的索引。默认开启,手动触发 /zyps ahuse
搭车全量扫描 REPLICATE_ITEM_LIST_UPDATE 事件无条件收割——也就是 Auctionator 自己发起全量扫描时我们顺手拿一份,不额外吃冷却。这是 v1.3.0 修掉的关键 bug(之前只在自己发起扫描时才收,等于永远收不到)
自己发起扫描 拿不到上面两种时的兜底:C_AuctionHouse.ReplicateItems(),15 分钟冷却
浏览结果 每次浏览结果返回时顺手补录

索引只含真实在售物品,又准又小(几千到几万条)。/zyps ah 看状态, /zyps ahscan 手动全量扫描。

成就面板怎么用

成就搜索也是 C++ 侧过滤(SetAchievementSearchStringGetFilteredAchievementID), 同样只能补全:

  1. 打开成就面板(点一下搜索框就会后台建索引,全部走客户端缓存,不用扫描)
  2. 打拼音 → 下拉列出候选成就名 → 回车/点击 → 中文名填进搜索框,走原生搜索

/zyps ach 看索引状态,/zyps achbuild 重建。

指令

指令 作用
/zyps 打开设置面板(挂在系统「选项 → 插件」里)
/zyps on / /zyps off 开关
/zyps test zlys 对背包里的物品跑一遍匹配测试,打印命中列表
/zyps fuzzy 快速切换 z/zh c/ch s/sh 模糊音
/zyps status 查看各模块钩子与拼音表状态
/zyps ah 拍卖行候选索引状态(多少件、来源、多久前建的)
/zyps ahuse 立刻从 Auctionator 的索引取物品名
/zyps ahscan 手动发起全量扫描(需站在拍卖行前)
/zyps ach / /zyps achbuild 成就索引状态 / 重建
/zyps talent 天赋 / 法术书搜索钩子状态
/zypinyin / /pinyinsearch 同上

实现原理

背包:在数据源头拦截

暴雪的背包搜索是 C++ 侧做比较(C_Container.GetContainerItemInfo() 直接返回 isFiltered), Lua 改不了它的匹配规则。所以插件用「拦截结果 + 救回」的思路,并且在数据源头拦截—— 这样不管是暴雪原生背包还是第三方背包,只要它读 isFiltered 就都能生效。

三层钩子,逐层兜底:

  1. 数据源头(主):覆写 C_Container.GetContainerItemInfo(bag, slot)
    • 系统判 isFiltered == true(要藏)+ 拼音命中 → 翻成 false 放出来
    • 覆盖:暴雪背包/银行/战团银行、EllesmereUI Bags / Bank、以及任何调用这个 API 的插件
    • EllesmereUI 用的是完全自定义的格子,不继承 ItemButtonMixin, 所以只挂按钮层对它完全无效——这就是 v1.0 不生效的原因
  2. 查询词hooksecurefunc(C_Container, "SetItemSearch", ...) 拿到当前搜索词; 另外扫描全局里名字含 bag/bank/inventory/container + search 的 EditBox 补挂 OnTextChanged,防止某个插件不调 SetItemSearch
  3. 按钮层(兜底):覆写 ItemButtonMixin:SetMatchesSearch(matches),公会银行等靠这条

补全引擎

拍卖行和成就共用 Suggest.lua 里的下拉引擎:打拼音 → 防抖 80ms → 取候选 → 渲染下拉 → 回车 / Tab / ↓ / 点击选中。回车是抢在原生之前处理的(原生拿到的是拼音,搜出来是空的), 并且每次输入都会检查回车脚本有没有被别的插件顶掉、顶掉了就补回来。

其它细节

  • 物品名优先用 ContainerItemInfo.itemName(12.x 自带),退回 hyperlink 里的 [...]
  • 匹配用一次线性 DP:每个汉字可用「完整拼音」或「首字母」消耗查询串, 允许从任意字开始、末字允许只打一半。200 件物品敲一个字母约 2-4 ms
  • 候选查询两层:先用每个名字预计算好的首字母串 / 全拼串做一次 string.find 快筛, 只有混合输入才按首字符剪枝后回落 DP
  • 热路径只有一个 upvalue 布尔判断(实测 GetContainerItemInfo 20000 次: 无查询 ≈ 0.28s,有查询 ≈ 0.40s)
  • 调试输出会自动折叠:同一条消息 60 秒内只打一次,重复报错只记一次(ns.WarnOnce

拼音数据

  • 数据源:mozillazg/pinyin-datapinyin.txt
  • 覆盖 26713 个汉字(CJK 基本区 + 扩展 A + 兼容表意文字 + 〇),每个字最多保留 3 个读音
  • 存储方式是「拼音 → 汉字串」再在运行时反转,数据文件只有 ~111 KB
  • 重新生成:
python tools/generate_pinyin_data.py <pinyin.txt 路径>
# 默认读取 ../pinyin-data-master/pinyin.txt,输出 ../PinyinData.lua

文件结构

zyydi_PinyinSearch/
├─ zyydi_PinyinSearch.toc   插件描述
├─ PinyinData.lua           生成的拼音表(勿手改)
├─ Matcher.lua              拼音匹配引擎(全拼/首字母/混合/末字前缀/模糊音/名字键)
├─ Core.lua                 背包三层钩子 + 物品名解析 + 刷新
├─ Suggest.lua              通用「拼音→中文名」补全下拉(拍卖行/成就共用)
├─ AuctionHouse.lua         拍卖行:索引来源 + 候选 + 选中后发起搜索
├─ Achievements.lua         成就:索引 + 候选 + 选中后走原生搜索
├─ SpellSearch.lua          天赋/法术书:接管 SpellSearchUtil 判定(边打边过滤)
├─ Options.lua              设置面板 + 聊天指令
└─ tools/
   ├─ generate_pinyin_data.py   数据生成脚本
   └─ test/                     Node 环境的单元测试(fengari Lua VM + luaparse)

自测

需要 Node(已装 luaparse / fengari~/.workbuddy/binaries/node/workspace):

cd tools/test
NODE_PATH=<workspace>/node_modules node check_syntax.js        # 语法(8 个 Lua 文件)
NODE_PATH=<workspace>/node_modules node test_matcher.js        # 匹配算法 27 条
NODE_PATH=<workspace>/node_modules node test_integration.js    # 背包链路 + 刷新不刷屏
NODE_PATH=<workspace>/node_modules node test_modules.js        # 补全/拍卖行/成就/天赋

覆盖:数据源头 isFiltered、按钮层、开关行为、只救不藏不变量、热路径性能、 隐藏容器跳过与报错折叠、补全下拉(排序/回车/点击/Tab/提示行/被顶掉后补回)、 Auctionator 索引复用、搭车全量扫描、成就索引、天赋判定接管。

已知限制 / 后续可做

  • 拍卖行、成就只能做补全,做不到"边打字边过滤"——瓶颈在服务端 / C++,不是插件
  • 自己发起的 ReplicateItems 有 15 分钟冷却;有 Auctionator 时基本感觉不到
  • Auctionator 索引里未缓存的物品名要等客户端拉到数据后才补得上(第二批补录)
  • 完全自建过滤逻辑、既不读 isFiltered 也不调 SetItemSearch 的背包插件无法通用适配
  • 幻化/坐骑/宠物收藏的搜索不走 ItemButtonMixin,需要另外单独适配(后续版本)
  • 模糊音默认全关,想要"打错也能搜到"就去设置里开

The zyydi_PinyinSearch Team

profile avatar
Owner
  • 1
    Projects
  • 3
    Downloads