port: CMapOutdoor + PythonBackground + minimap (1:1 40250)

GameLib map/area/terrain units, PRTerrainLib Terrain/TextureSet,
PythonBackground(+Module) and ColorTransitionHelper ported verbatim;
pending stand-ins and background module stubs removed. Hand edits:
BeginEnvironment's pointer tag via uintptr_t, LoadWaterMap reads the
file's 32-bit longs. Terrain draw passes stay platform no-ops (Godot
draws the terrain). RecordingDevice gains textures, buffers and render
targets; orthographic draws become UI commands so CPythonMiniMap's
masked tiles show through minimap_image_filter in python_ui_surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-24 03:50:09 +09:00
co-authored by Claude Opus 5.5
parent b69116992c
commit 2ead95756e
100 changed files with 17679 additions and 1514 deletions
+92 -3
View File
@@ -44,6 +44,15 @@ func run_app() -> String:
return ""
func _exit_tree() -> void:
for segment in _segments:
RenderingServer.free_rid(segment)
_segments.clear()
for material in _materials:
RenderingServer.free_rid(material)
_materials.clear()
if _mask_shader.is_valid():
RenderingServer.free_rid(_mask_shader)
_mask_shader = RID()
if _owns_app:
_owns_app = false
Metin2PythonHost.stop()
@@ -90,7 +99,36 @@ func _unhandled_key_input(event: InputEvent) -> void:
if dik != 0:
Metin2PythonHost.ui_key(dik, event.pressed)
# 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)
# gets its own item carrying the mask shader, since a canvas item has a single material.
const MASK_SHADER := """
shader_type canvas_item;
render_mode blend_mix;
uniform sampler2D mask_texture : repeat_disable, filter_linear;
uniform vec4 uv_rect; // su, sv, eu, ev of the image
uniform vec2 m0; // mask coordinates at the image's TL, TR and BL corners
uniform vec2 m1;
uniform vec2 m2;
void fragment() {
vec2 ab = (UV - uv_rect.xy) / max(uv_rect.zw - uv_rect.xy, vec2(1e-6));
vec4 mask = texture(mask_texture, m0 + (m1 - m0) * ab.x + (m2 - m0) * ab.y);
vec4 image = texture(TEXTURE, UV) * COLOR;
COLOR = vec4(image.rgb * mask.rgb, mask.a * COLOR.a);
}
"""
var _segments: Array[RID] = []
var _materials: Array[RID] = []
var _mask_shader := RID()
var _white: ImageTexture
func _draw() -> void:
var used := 0
var masked := 0
var plain := false
for segment in _segments:
RenderingServer.canvas_item_clear(segment)
if not Metin2PythonHost.is_running():
return
for command in Metin2PythonHost.ui_render_commands():
@@ -105,17 +143,68 @@ func _draw() -> void:
if command["kind"] == "bar":
var visible := Rect2(p1, p2 - p1).intersection(clip)
if visible.has_area():
draw_rect(visible, color, true)
if not plain:
used = _segment(used, RID())
plain = true
RenderingServer.canvas_item_add_rect(_segments[used - 1], visible, color)
elif command["kind"] == "line":
var segment := _clip_line(p1, p2, clip)
if segment.size() == 2:
draw_line(segment[0], segment[1], color)
if not plain:
used = _segment(used, RID())
plain = true
RenderingServer.canvas_item_add_line(_segments[used - 1], segment[0], segment[1], color)
elif command["kind"] == "image":
var texture: Texture2D = _texture(command["text"])
var mask: Texture2D = _texture(command["mask"]) if command.has("mask") else null
if mask != null and texture == null:
texture = _white_texture()
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)
if mask != null:
used = _segment(used, _mask_material(masked, mask, command["uv"], command["mask_uv"]))
masked += 1
plain = false
elif not plain:
used = _segment(used, RID())
plain = true
RenderingServer.canvas_item_add_polygon(_segments[used - 1], clipped[0],
PackedColorArray([color]), clipped[1], texture.get_rid())
# Opens the next pooled child item (draw order = index) with the given material; returns the count used.
func _segment(used: int, material: RID) -> int:
if used == _segments.size():
var item := RenderingServer.canvas_item_create()
RenderingServer.canvas_item_set_parent(item, get_canvas_item())
_segments.push_back(item)
var item := _segments[used]
RenderingServer.canvas_item_set_draw_index(item, used)
RenderingServer.canvas_item_set_material(item, material)
return used + 1
func _mask_material(index: int, mask: Texture2D, uv: PackedVector2Array, mask_uv: PackedVector2Array) -> RID:
if not _mask_shader.is_valid():
_mask_shader = RenderingServer.shader_create()
RenderingServer.shader_set_code(_mask_shader, MASK_SHADER)
if index == _materials.size():
var material := RenderingServer.material_create()
RenderingServer.material_set_shader(material, _mask_shader)
_materials.push_back(material)
var material := _materials[index]
RenderingServer.material_set_param(material, "mask_texture", mask.get_rid())
RenderingServer.material_set_param(material, "uv_rect", Vector4(uv[0].x, uv[0].y, uv[3].x, uv[3].y))
RenderingServer.material_set_param(material, "m0", mask_uv[0])
RenderingServer.material_set_param(material, "m1", mask_uv[1])
RenderingServer.material_set_param(material, "m2", mask_uv[2])
return material
func _white_texture() -> Texture2D:
if _white == null:
var image := Image.create(1, 1, false, Image.FORMAT_RGBA8)
image.fill(Color.WHITE)
_white = ImageTexture.create_from_image(image)
return _white
func _texture(name: String) -> Texture2D:
if not name.begins_with("mem:"):