# Canvas adapter for the 40250 CWindowManager. The window tree remains native; # this Control forwards Godot input and draws the native UI command stream. extends Control const AssetRoot = preload("res://asset_root.gd") const UiAssets = preload("res://ui/ui_assets.gd") const KEY_TO_DIK := { KEY_ESCAPE: 0x01, KEY_1: 0x02, KEY_2: 0x03, KEY_3: 0x04, KEY_4: 0x05, KEY_5: 0x06, KEY_6: 0x07, KEY_7: 0x08, KEY_8: 0x09, KEY_9: 0x0a, KEY_0: 0x0b, KEY_BACKSPACE: 0x0e, KEY_TAB: 0x0f, KEY_Q: 0x10, KEY_W: 0x11, KEY_E: 0x12, KEY_R: 0x13, KEY_T: 0x14, KEY_Y: 0x15, KEY_U: 0x16, KEY_I: 0x17, KEY_O: 0x18, KEY_P: 0x19, KEY_ENTER: 0x1c, KEY_A: 0x1e, KEY_S: 0x1f, KEY_D: 0x20, KEY_F: 0x21, KEY_G: 0x22, KEY_H: 0x23, KEY_J: 0x24, KEY_K: 0x25, KEY_L: 0x26, KEY_Z: 0x2c, KEY_X: 0x2d, KEY_C: 0x2e, KEY_V: 0x2f, KEY_B: 0x30, KEY_N: 0x31, KEY_M: 0x32, KEY_SPACE: 0x39, KEY_UP: 0xc8, KEY_LEFT: 0xcb, KEY_RIGHT: 0xcd, KEY_DOWN: 0xd0, } # True when this surface started system.py itself (run_app) and so owns the interpreter's lifetime. var _owns_app := false # 40250 WinMain → RunMainScript: start the interpreter, give it the canvas size (app.Create reads it), # run system.py until it parks in app.Loop(). Returns "" or the error RunApp reported. func run_app() -> String: if not AssetRoot.pack_ready(): return "no 40250 Client/pack at %s" % AssetRoot.client_path() var err: String = Metin2PythonHost.start("") if err != "": return err _sync_size() err = Metin2PythonHost.run_main_script("") if err != "": Metin2PythonHost.stop() return err _owns_app = true return "" func _exit_tree() -> void: if _owns_app: _owns_app = false Metin2PythonHost.stop() func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) mouse_filter = Control.MOUSE_FILTER_STOP _sync_size() func _notification(what: int) -> void: if what == NOTIFICATION_RESIZED and is_inside_tree(): _sync_size() func _sync_size() -> void: if ClassDB.class_exists("Metin2PythonHost"): # Before the first layout pass a full-rect Control is still 0x0; it will fill the viewport. var canvas := size if size.x > 0 and size.y > 0 else get_viewport_rect().size Metin2PythonHost.set_ui_size(int(canvas.x), int(canvas.y)) func _process(_delta: float) -> void: # app.Exit()/Abort(): Loop() returned and RunApp() finished; 40250 WinMain then ends the process. if _owns_app and not Metin2PythonHost.is_app_looping(): _owns_app = false Metin2PythonHost.stop() get_tree().quit(0) return if Metin2PythonHost.is_running(): Metin2PythonHost.ui_update() queue_redraw() func _gui_input(event: InputEvent) -> void: if not Metin2PythonHost.is_running(): return if event is InputEventMouseMotion: Metin2PythonHost.ui_mouse_move(int(event.position.x), int(event.position.y)) elif event is InputEventMouseButton: if event.button_index >= MOUSE_BUTTON_LEFT and event.button_index <= MOUSE_BUTTON_MIDDLE: Metin2PythonHost.ui_mouse_button(event.button_index, event.pressed, int(event.position.x), int(event.position.y)) func _unhandled_key_input(event: InputEvent) -> void: if event is InputEventKey and Metin2PythonHost.is_running(): var dik: int = KEY_TO_DIK.get(event.physical_keycode, 0) if dik != 0: Metin2PythonHost.ui_key(dik, event.pressed) func _draw() -> void: if not Metin2PythonHost.is_running(): return for command in Metin2PythonHost.ui_render_commands(): var argb: int = command["argb"] var color := Color8((argb >> 16) & 255, (argb >> 8) & 255, argb & 255, (argb >> 24) & 255) var p1 := Vector2(command["x1"], command["y1"]) var p2 := Vector2(command["x2"], command["y2"]) var clip := Rect2(Vector2(command["clip_x1"], command["clip_y1"]), Vector2(command["clip_x2"] - command["clip_x1"], command["clip_y2"] - command["clip_y1"])) if command["kind"] == "bar": var visible := Rect2(p1, p2 - p1).intersection(clip) if visible.has_area(): draw_rect(visible, color, true) elif command["kind"] == "line": var segment := _clip_line(p1, p2, clip) if segment.size() == 2: draw_line(segment[0], segment[1], color) elif command["kind"] == "text": var font: Font = ThemeDB.fallback_font if font != null: var baseline := p1 + Vector2(0, font.get_ascent(14)) if clip.has_point(p1): draw_string(font, baseline, command["text"], HORIZONTAL_ALIGNMENT_LEFT, clip.end.x - p1.x, 14, color) elif command["kind"] == "image": var texture: Texture2D = UiAssets.load_tex(AssetRoot.path(), command["text"]) if texture != null: var clipped := _clip_quad(command["quad"], command["uv"], clip) if clipped[0].size() >= 3: draw_polygon(clipped[0], PackedColorArray([color]), clipped[1], texture) # 40250's image quad (TL, TR, BL, BR) clipped to the viewport. The quad is a # parallelogram, so texture coordinates are an affine function of position and are recomputed for # the clipped outline. Returns [points, uvs]; empty points when nothing is visible. # PORT: 40250's SCREEN / COLOR_DODGE / MODULATE blend states (command["blend"]) are not applied yet; # the canvas draws every quad with normal alpha blending. func _clip_quad(quad: PackedVector2Array, uv: PackedVector2Array, clip: Rect2) -> Array: var outline := PackedVector2Array([quad[0], quad[1], quad[3], quad[2]]) var edge_u := quad[1] - quad[0] var edge_v := quad[2] - quad[0] var det := edge_u.cross(edge_v) if is_zero_approx(det): return [PackedVector2Array(), PackedVector2Array()] var rect := PackedVector2Array([clip.position, Vector2(clip.end.x, clip.position.y), clip.end, Vector2(clip.position.x, clip.end.y)]) var pieces := Geometry2D.intersect_polygons(outline, rect) if pieces.is_empty(): return [PackedVector2Array(), PackedVector2Array()] var points: PackedVector2Array = pieces[0] var uvs := PackedVector2Array() for point in points: var d := point - quad[0] var a := d.cross(edge_v) / det var b := edge_u.cross(d) / det uvs.push_back(uv[0] + (uv[1] - uv[0]) * a + (uv[2] - uv[0]) * b) return [points, uvs] func _clip_line(a: Vector2, b: Vector2, clip: Rect2) -> PackedVector2Array: var delta := b - a var p := [-delta.x, delta.x, -delta.y, delta.y] var q := [a.x - clip.position.x, clip.end.x - a.x, a.y - clip.position.y, clip.end.y - a.y] var enter := 0.0 var leave := 1.0 for i in range(4): if is_zero_approx(p[i]): if q[i] < 0.0: return PackedVector2Array() else: var t: float = q[i] / p[i] if p[i] < 0.0: enter = maxf(enter, t) else: leave = minf(leave, t) if enter > leave: return PackedVector2Array() return PackedVector2Array([a + delta * enter, a + delta * leave])