文件 玩法擴展

介面與輸入

畫板上的按鈕、選項卡片能點,滑鼠停在上面會自動醒目顯示;登記熱鍵、點地面選位置、讀取滑鼠指著哪裡、知道本機玩家選取了誰。點擊、熱鍵、放技能、聊天全文、玩家離開,都會進入事件流。

畫板 畫出來的東西,現在能點了。執行環境接管遊戲視窗的輸入,外部程式可以:

能力一句話遊戲收得到嗎
可點擊的畫板項目按鈕、選項卡片、面板:點中時發出 ui.click,滑鼠停在上面自動醒目顯示點在按鈕上的那一下收不到
熱鍵登記 F5、ctrl+shift+Q 這樣的組合,按下時發出 hotkey可選擇吞掉(連同它產生的字元)
點地面點在世界上時發出 mouse.world,帶有地面座標可選擇吞掉(「點一個位置放塔」)
滑鼠位置每幀更新:螢幕像素、指著的地面點、懸停的畫板項目——
選取本機玩家選取了誰;一有變化就發出 selection.changed——

全部都是本機輸入 + 本機繪製:不進入指令流,多人局也安全。但如果回呼裡改動了世界(刷單位、改屬性),那還是只能用於單人局。

Python:g.ui

ui = g.ui                                                   # 第一次使用時,執行環境接管視窗輸入
ui.button("shop", "買一瓶藥水(50 金)", screen=(40, 300), on_click=lambda g, ev: buy(g))
c = ui.choice("升級了!選一個獎勵", [("力量 +5", "更耐打"), ("攻速 +20%", "輸出更高"), ("召喚狼", "多一個幫手")],
              pause=True, on_pick=lambda g, i: give(g, i))  # 螢幕中間一排卡片;pause=True 選擇時遊戲暫停
i = c.wait(timeout=30)                                      # 也可以阻塞等待(期間照樣處理事件,不會遺失)
ui.hotkey("F5", lambda g, ev: g.say(hero, "收到!"))        # 預設吞掉
ui.hotkey("ctrl+shift+Q", on_press=..., swallow=False)
ui.mouse(on_click, capture=True, buttons=("left", "right"))  # 擷取地面點擊:左鍵、右鍵都回報,並吞掉
xy = ui.pick_point("點地面:塔要蓋在哪?")                  # 阻塞版:下一次左鍵點地面 -> (x, y);Esc 或逾時 -> None
ui.cursor()                                                 # {'screen': (x, y), 'world': (x, y, z) 或 None, 'hover': 'shop'}
ui.toast("第 3 波來了!", seconds=3)
ui.close()                                                  # 收起自己的控制項和熱鍵;沒有別的程式在用輸入時才交還視窗輸入
g.close()                                                   # 或者整個中斷連線(也可以寫 with Game(...) as g:)

回呼的參數是 (g, ev),在你呼叫 g.events() 時觸發 —— Bot 和玩法模組的執行器每拍都會呼叫。沒有給回呼的點擊會進入 ui.clicks。回呼裡拋出例外只會記入日誌,不影響其他回呼和事件。

也可以直接使用畫板底層:g.canvas.text(..., clickable=True, hover=顏色),點擊從事件流裡接收,ev.key 就是繪製時給的 key。畫可點擊的元素會自動開啟輸入,不必先碰 g.ui。

熱鍵寫法:F1 ~ F24、A ~ Z、0 ~ 9、numpad0 ~ numpad9、space enter esc tab backspace insert delete home end pageup pagedown left up right down,前面可以加上 ctrl+、shift+、alt+。

不帶修飾鍵的字母和數字會和聊天輸入、遊戲快速鍵互相衝突。優先使用 F5 ~ F8 這類遊戲沒占用的鍵,或者組合鍵。

新事件

g.events() 裡多了這些(完整欄位見 W3P 協定):

kind什麼時候便捷欄位
ui.click可互動的畫板項目被點擊.key 畫板 key、.button('left' / 'right')、.mods 修飾鍵
ui.hover滑鼠移入 / 移出畫板項目.key(移出時是 None)
hotkey登記的熱鍵被按下.key 熱鍵寫法、.mods
mouse.world開啟地面點擊後,點在世界上.x .y 地面座標、.button、.value(1 = 被吞掉了)
selection.changed本機玩家的選取變了用 g.selection() 取得單位
spell.cast單位放了技能(技能開始冷卻).spell 四字碼、.b 等級、.value 冷卻秒數、.x .y 施法點
message螢幕訊息框裡出現一條.text 全文、.frame 哪個框、.chat(是聊天時)
player.left玩家離開或被判定落敗移除.player
game.ended離開對局——

聊天和螢幕訊息

玩家在聊天框輸入的文字,直接從 message 事件的 .chat 讀取:

for ev in g.events():
    if ev.kind == "message" and ev.chat and ev.chat["text"] == "-follow":
        ...                                    # ev.chat = {'channel': '所有人', 'sender': '玩家名稱', 'text': '-follow'}

g.messages() 另有一份獨立的游標:遊戲提示(「需要更多的農場」「不能在那裡建造」)也在裡面。寫 Bot 時用它,就知道一條命令為什麼沒做成。

從其他語言使用

  • 閘道:ui.button、ui.choice、ui.hotkey、ui.mouse、ui.cursor 這些方法在 閘道 上以同名提供。遠端無法傳入回呼函式,點擊和熱鍵從事件推送裡接收(ui.click 事件帶有 key)。
  • 直接寫入共用記憶體:先送出語意命令 input_enable(W3P 操作碼 74),執行環境開始接管輸入;在輸入區塊 Local\War3Input_<pid> 裡,你寫入熱鍵表和滑鼠開關,它回寫滑鼠位置、指著的地面點和懸停項目。畫板項目的旗標位元 0x40 表示「可互動」。配置見 W3P 協定。

好幾個程式同時使用

模組、遠見、MCP、閘道的每個工作階段可能同時在一局遊戲裡放按鈕、登記熱鍵,彼此互不干擾:

  • 每個程式登記自己的熱鍵和地面點擊開關,SDK 把大家的合併成一張表交給執行環境。同一個鍵只留一條,事件發給所有人,各自依鍵認出自己的熱鍵;
  • ui.close() 只撤掉自己的,最後一個程式離開了才交還視窗輸入;
  • 程式被強制結束、來不及收尾:執行環境每 2 秒檢查一次,登記過的程式全都結束了,就清掉它們留下的熱鍵和地面點擊攔截,它們畫的按鈕也不再攔截點擊。

實測

2026-09-25,測試實例上的實機核對 16/16:

  • 點按鈕 → ui.click + 回呼,執行環境的攔截計數 +1(遊戲沒收到這一下);點在按鈕外面不觸發;
  • F6 → hotkey;點地面 → mouse.world(被吞掉);
  • 刷出一個聖騎士、選取它 → selection.changed,g.selection() 對得上;放神聖護甲 → spell.cast('AHds', 1, 35.0);
  • 地圖文字 → message;聊天 → message,.chat 解析出發言者和內容;
  • 判定電腦落敗 → player.left;結束對局 → game.ended。

真人用滑鼠點按鈕、懸停醒目顯示也逐一看過。

邊界與注意事項

  • 位置用的是真實滑鼠:遊戲本身依系統游標讀取位置,所以懸停、cursor() 反映的是真實滑鼠。攔截只管按鍵。
  • 畫在滑鼠游標下面:魔獸把游標當作畫面的一部分,每幀畫進去。畫板和頭頂氣泡都在遊戲畫游標那一步之前繪製:蓋住遊戲介面,被游標蓋住。只有這一幀沒畫游標(隱藏或過場動畫)時,才退回到最後一步繪製。
  • 系統縮放:自己寫測試、用視窗訊息投遞點擊時,未宣告 DPI 感知的行程送過去的座標會被系統放大(150% 縮放時實測 ×1.5)。測試程式要先宣告 DPI 感知。真人點擊不受影響。
  • 第一次要預熱字型,約 1 秒。這段期間按鈕還沒畫出來,點不中。
  • 不在對局裡時不回報地面點擊:主選單和結算畫面上 mouse.world 不發出,也不吞掉。
  • 按下時被吞掉的那一下,放開前切換到別的程式或滑鼠拖出了視窗,也會復位,不會連下一次放開也吞掉。
  • 1.27 沒有建立新遊戲介面框架的函式(1.31 才有):這裡的按鈕、卡片都是執行環境畫的,樣式隨你定,但不會出現在遊戲本身的選單層級裡。