port 4-a: text input through CPythonIME (login typed, in-game chat)
- UserInterface/PythonIME(+Module) ported verbatim; ime module stubs removed, initime() after initdbg() - EterLib/IME.cpp in platform/: 40250 buffer/WMChar bodies, IMM/TSF processors no-op; committed multi-byte text via PythonBoot::UIChar emulating GCS_RESULTSTR - GameApplicationAdapter RunIME* -> CWindowManager; Godot keys -> ui_ime_key/ui_char - port.login_flow/login_live type the account through the IME; chat CG_CHAT round-trip on the fake server - python_game_render_test logs in with Godot key events Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1bda4390d6
commit
6ea0fa9889
@@ -29,6 +29,25 @@ func _check(ok: bool, message: String) -> void:
|
||||
func _py(source: String) -> bool:
|
||||
return Metin2PythonHost.run_line(source) == ""
|
||||
|
||||
func _key(keycode: Key, unicode: int, pressed: bool) -> void:
|
||||
var event := InputEventKey.new()
|
||||
event.keycode = keycode
|
||||
event.physical_keycode = keycode
|
||||
event.unicode = unicode
|
||||
event.pressed = pressed
|
||||
root.push_input(event)
|
||||
|
||||
func _press(keycode: Key) -> void:
|
||||
_key(keycode, 0, true)
|
||||
_key(keycode, 0, false)
|
||||
|
||||
func _type(text: String) -> void:
|
||||
for i in text.length():
|
||||
var ch := text.unicode_at(i)
|
||||
var keycode := OS.find_keycode_from_string(text[i].to_upper())
|
||||
_key(keycode, ch, true)
|
||||
_key(keycode, ch, false)
|
||||
|
||||
func _pump_until(seconds: float, condition: Callable) -> bool:
|
||||
var deadline := Time.get_ticks_msec() + int(seconds * 1000.0)
|
||||
while Time.get_ticks_msec() < deadline:
|
||||
@@ -83,13 +102,24 @@ func run() -> void:
|
||||
"assert isinstance(_stream.curPhaseWindow, introLogin.LoginWindow)"))
|
||||
_check(at_login, "login window")
|
||||
|
||||
# LoginWindow.Connect with the account from the environment.
|
||||
# The account from the environment is typed into the login board as key events (WM_KEYDOWN/WM_CHAR
|
||||
# through PythonUISurface → CPythonIME); Return runs __OnClickLoginButton → LoginWindow.Connect.
|
||||
_check(_py("_env = os.environ\n"
|
||||
+ "_slot = int(_env.get('MT_LIVE_SLOT', '0'))\n"
|
||||
+ "_stream.SetConnectInfo(_env['MT_LIVE_HOST'], int(_env.get('MT_LIVE_GAME_PORT', '13000')), "
|
||||
+ "_env['MT_LIVE_HOST'], int(_env.get('MT_LIVE_AUTH_PORT', '11000')))\n"
|
||||
+ "_stream.curPhaseWindow.Connect(_env['MT_LIVE_LOGIN'], _env['MT_LIVE_PASSWORD'])\n"
|
||||
+ "del _env"), "LoginWindow.Connect")
|
||||
+ "_w = _stream.curPhaseWindow\n"
|
||||
+ "_w._LoginWindow__OpenLoginBoard()\n"
|
||||
+ "_w.idEditLine.SetText('')\n"
|
||||
+ "_w.pwdEditLine.SetText('')\n"
|
||||
+ "_w.idEditLine.SetFocus()"), "login board open")
|
||||
_type(OS.get_environment("MT_LIVE_LOGIN"))
|
||||
_press(KEY_TAB)
|
||||
_type(OS.get_environment("MT_LIVE_PASSWORD"))
|
||||
_check(_py("assert _w.idEditLine.GetText() == _env['MT_LIVE_LOGIN']\n"
|
||||
+ "assert _w.pwdEditLine.GetText() == _env['MT_LIVE_PASSWORD']"), "account typed through the IME")
|
||||
_check(_py("del _env, _w"), "login locals dropped")
|
||||
_press(KEY_ENTER)
|
||||
var selecting := await _pump_until(30, func(): return _py(
|
||||
"assert isinstance(_stream.curPhaseWindow, introSelect.SelectCharacterWindow)"))
|
||||
_check(selecting, "character select")
|
||||
|
||||
@@ -20,6 +20,19 @@ const KEY_TO_DIK := {
|
||||
KEY_RIGHT: 0xcd, KEY_DOWN: 0xd0,
|
||||
}
|
||||
|
||||
# Win32 virtual-key codes of the keys EditLine.OnIMEKeyDown handles (WM_KEYDOWN → OnIMEKeyDown);
|
||||
# letters and digits use their ASCII code, as VK_A..VK_Z / VK_0..VK_9 do.
|
||||
const KEY_TO_VK := {
|
||||
KEY_BACKSPACE: 0x08, KEY_TAB: 0x09, KEY_ENTER: 0x0d, KEY_KP_ENTER: 0x0d,
|
||||
KEY_ESCAPE: 0x1b, KEY_SPACE: 0x20, KEY_END: 0x23, KEY_HOME: 0x24,
|
||||
KEY_LEFT: 0x25, KEY_UP: 0x26, KEY_RIGHT: 0x27, KEY_DOWN: 0x28, KEY_DELETE: 0x2e,
|
||||
}
|
||||
|
||||
# The control characters Windows turns these keys into (WM_CHAR); Godot reports them with unicode 0.
|
||||
const KEY_TO_CHAR := {
|
||||
KEY_BACKSPACE: 0x08, KEY_TAB: 0x09, KEY_ENTER: 0x0d, KEY_KP_ENTER: 0x0d, KEY_ESCAPE: 0x1b,
|
||||
}
|
||||
|
||||
# "mem:<id>" → [revision, ImageTexture]: the CGraphicFontTexture glyph pages, refetched when the
|
||||
# "@<revision>" of a command's name moves on (a glyph was added to the page).
|
||||
var _memory_textures := {}
|
||||
@@ -98,6 +111,23 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||
var dik: int = KEY_TO_DIK.get(event.physical_keycode, 0)
|
||||
if dik != 0:
|
||||
Metin2PythonHost.ui_key(dik, event.pressed)
|
||||
if event.pressed:
|
||||
_ime_key(event)
|
||||
|
||||
# 40250 gets every key press twice more through the window procedure: WM_KEYDOWN (a VK code, for
|
||||
# OnIMEKeyDown) and then WM_CHAR (the typed character, for CPythonIME). Held keys repeat both.
|
||||
func _ime_key(event: InputEventKey) -> void:
|
||||
var keycode: int = event.keycode
|
||||
var vk: int = KEY_TO_VK.get(keycode, 0)
|
||||
if vk == 0 and ((keycode >= KEY_A and keycode <= KEY_Z) or (keycode >= KEY_0 and keycode <= KEY_9)):
|
||||
vk = keycode
|
||||
if vk != 0:
|
||||
Metin2PythonHost.ui_ime_key(vk)
|
||||
var ch: int = event.unicode
|
||||
if ch == 0:
|
||||
ch = KEY_TO_CHAR.get(keycode, 0)
|
||||
if ch != 0:
|
||||
Metin2PythonHost.ui_char(ch)
|
||||
|
||||
# The command stream is drawn into pooled child canvas items in order: a run of plain commands
|
||||
# shares one item; a masked image (CPythonMiniMap's terrain tiles under minimap_image_filter.dds)
|
||||
|
||||
Reference in New Issue
Block a user