2V2-e.2: Godot consumer of the 3D draw commands, offline screenshot

python_3d_surface.gd turns RenderGame's recorded draws into meshes under a
camera read back from SetPerspective; textures come from the 40250 packs.
GetFarClip and the CMapManager ctor ported verbatim. Until the environment
is ported, lit draws with no light/ambient are shown unshaded (stand-in).
script/python_game_render_test.sh runs system.py login -> select -> game
against port_fake_login_server (or --live via MT_LIVE_*) and screenshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 20:35:36 +09:00
co-authored by Claude Opus 5.5
parent 5bfa13c5cc
commit 1fe8a4b643
12 changed files with 630 additions and 6 deletions
@@ -12,9 +12,12 @@ auto CMapManager::GetHeight(float, float) -> float
return mt_platform_stub_return<float>();
}
CMapManager::CMapManager()
CMapManager::CMapManager() : mc_pcurEnvironmentData(NULL)
{
MT_PLATFORM_STUB();
m_pkMap = NULL;
m_isSoftwareTilingEnableReserved=false;
// Initialize();
}
CMapManager::~CMapManager()
@@ -76,6 +76,9 @@ auto CPythonBackground::GetWarpMapName() -> const char *
CPythonBackground::CPythonBackground()
{
MT_PLATFORM_STUB();
// PORT: the one member the stand-in's ported GetFarClip reads (PythonBackground.cpp:210); the
// view distance sets and Initialize() come with the unit.
m_eViewDistanceNum=0;
}
CPythonBackground::~CPythonBackground()
@@ -95,10 +98,20 @@ auto CPythonBackground::GetPickingPointWithRayOnlyTerrain(const CRay &, D3DXVECT
return mt_platform_stub_return<bool>();
}
auto CPythonBackground::GetFarClip() -> float
// 40250 PythonBackground.cpp:173 (CPythonApplication::RenderGame's SetPerspective); m_pkMap stays null
// until the map is ported, so this is the 50000 far clip.
float CPythonBackground::GetFarClip()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<float>();
if (!m_pkMap)
return 50000.0f;
if (m_ViewDistanceSet[m_eViewDistanceNum].m_fFarClip==0.0f)
{
TraceError("CPythonBackground::GetFarClip m_eViewDistanceNum=%d", m_eViewDistanceNum);
m_ViewDistanceSet[m_eViewDistanceNum].m_fFarClip=25600.0f;
}
return m_ViewDistanceSet[m_eViewDistanceNum].m_fFarClip;
}
auto CPythonBackground::Render() -> void
+6
View File
@@ -219,6 +219,12 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
add_test(NAME port.login_flow
COMMAND $<TARGET_FILE:port_login_flow_test> ${MT_40250_CLIENT} ${MT_PYTHON_STDLIB_ZIP})
set_tests_properties(port.login_flow PROPERTIES SKIP_RETURN_CODE 77)
# 2V2-e: the same fake server as a process, for the Godot-driven render run
# (script/python_game_render_test.sh).
add_executable(port_fake_login_server ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_fake_login_server_main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_login_flow_server.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../net/classic/classic_cipher.cpp)
target_link_libraries(port_fake_login_server PRIVATE port_platform)
# 2V1-e: the same path against a live 40250 server; skipped unless MT_LIVE_* is set (see the test).
add_test(NAME port.login_live
COMMAND $<TARGET_FILE:port_login_flow_test> ${MT_40250_CLIENT} ${MT_PYTHON_STDLIB_ZIP} live)
+109
View File
@@ -9,12 +9,18 @@
// unit, which has to stay compilable next to godot-cpp.
#include "platform/ScriptLib/PythonBoot.h"
#include "platform/EterLib/UIRenderCommands.h"
#include "platform/EterLib/RenderCommands3D.h"
#include <string>
#endif
#include <godot_cpp/classes/image.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/packed_color_array.hpp>
#include <godot_cpp/variant/packed_float32_array.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/packed_vector2_array.hpp>
#include <godot_cpp/variant/packed_vector3_array.hpp>
using namespace godot;
@@ -117,6 +123,107 @@ Ref<Image> Metin2PythonHost::memory_texture(const String &name, int64_t known_re
return Image::create_from_data(texture.width, texture.height, false, Image::FORMAT_RGBA8, rgba);
}
namespace {
PackedFloat32Array floats(const float *values, int count) {
PackedFloat32Array out;
out.resize(count);
for (int i = 0; i < count; ++i)
out[i] = values[i];
return out;
}
Color argb_color(std::uint32_t argb) {
return Color(((argb >> 16) & 255) / 255.0f, ((argb >> 8) & 255) / 255.0f, (argb & 255) / 255.0f,
((argb >> 24) & 255) / 255.0f);
}
} // namespace
Array Metin2PythonHost::render3d_draws() {
Array out;
for (const Render3DDraw &draw : Render3DDraws()) {
Dictionary item;
item["world"] = floats(draw.world, 16);
item["view"] = floats(draw.view, 16);
item["proj"] = floats(draw.proj, 16);
item["texture0"] = String::utf8(draw.texture0.c_str());
item["texture1"] = String::utf8(draw.texture1.c_str());
item["pretransformed"] = draw.pretransformed;
item["lines"] = draw.lines;
PackedVector3Array positions;
positions.resize(static_cast<int64_t>(draw.positions.size() / 3));
for (int64_t i = 0; i < positions.size(); ++i)
positions[i] = Vector3(draw.positions[i * 3], draw.positions[i * 3 + 1], draw.positions[i * 3 + 2]);
item["positions"] = positions;
if (!draw.rhw.empty())
item["rhw"] = floats(draw.rhw.data(), static_cast<int>(draw.rhw.size()));
if (!draw.normals.empty()) {
PackedVector3Array normals;
normals.resize(static_cast<int64_t>(draw.normals.size() / 3));
for (int64_t i = 0; i < normals.size(); ++i)
normals[i] = Vector3(draw.normals[i * 3], draw.normals[i * 3 + 1], draw.normals[i * 3 + 2]);
item["normals"] = normals;
}
auto uvs = [](const std::vector<float> &values) {
PackedVector2Array out;
out.resize(static_cast<int64_t>(values.size() / 2));
for (int64_t i = 0; i < out.size(); ++i)
out[i] = Vector2(values[i * 2], values[i * 2 + 1]);
return out;
};
if (!draw.uv0.empty())
item["uv0"] = uvs(draw.uv0);
if (!draw.uv1.empty())
item["uv1"] = uvs(draw.uv1);
if (!draw.diffuse.empty()) {
PackedColorArray colors;
colors.resize(static_cast<int64_t>(draw.diffuse.size()));
for (int64_t i = 0; i < colors.size(); ++i)
colors[i] = argb_color(draw.diffuse[i]);
item["diffuse"] = colors;
}
PackedInt32Array indices;
indices.resize(static_cast<int64_t>(draw.indices.size()));
for (int64_t i = 0; i < indices.size(); ++i)
indices[i] = static_cast<int32_t>(draw.indices[i]);
item["indices"] = indices;
item["alpha_blend"] = static_cast<int64_t>(draw.alpha_blend);
item["src_blend"] = static_cast<int64_t>(draw.src_blend);
item["dest_blend"] = static_cast<int64_t>(draw.dest_blend);
item["alpha_test"] = static_cast<int64_t>(draw.alpha_test);
item["alpha_ref"] = static_cast<int64_t>(draw.alpha_ref);
item["alpha_func"] = static_cast<int64_t>(draw.alpha_func);
item["cull_mode"] = static_cast<int64_t>(draw.cull_mode);
item["z_enable"] = static_cast<int64_t>(draw.z_enable);
item["z_write"] = static_cast<int64_t>(draw.z_write);
item["z_func"] = static_cast<int64_t>(draw.z_func);
item["lighting"] = static_cast<int64_t>(draw.lighting);
item["texture_factor"] = static_cast<int64_t>(draw.texture_factor);
item["fog_enable"] = static_cast<int64_t>(draw.fog_enable);
item["color_op"] = static_cast<int64_t>(draw.color_op[0]);
item["alpha_op"] = static_cast<int64_t>(draw.alpha_op[0]);
item["material_diffuse"] = Color(draw.material_diffuse[0], draw.material_diffuse[1],
draw.material_diffuse[2], draw.material_diffuse[3]);
item["material_ambient"] = Color(draw.material_ambient[0], draw.material_ambient[1],
draw.material_ambient[2], draw.material_ambient[3]);
item["material_emissive"] = Color(draw.material_emissive[0], draw.material_emissive[1],
draw.material_emissive[2], draw.material_emissive[3]);
item["light0"] = draw.light0;
item["light0_direction"] = Vector3(draw.light0_direction[0], draw.light0_direction[1],
draw.light0_direction[2]);
item["light0_diffuse"] = Color(draw.light0_diffuse[0], draw.light0_diffuse[1], draw.light0_diffuse[2],
draw.light0_diffuse[3]);
item["light0_ambient"] = Color(draw.light0_ambient[0], draw.light0_ambient[1], draw.light0_ambient[2],
draw.light0_ambient[3]);
item["ambient"] = argb_color(draw.ambient);
out.push_back(item);
}
return out;
}
void Metin2PythonHost::stop() {
PythonBoot::Stop();
}
@@ -149,6 +256,7 @@ void Metin2PythonHost::ui_key(int, bool) {}
void Metin2PythonHost::ui_update() {}
Array Metin2PythonHost::ui_render_commands() { return Array(); }
Ref<Image> Metin2PythonHost::memory_texture(const String &, int64_t) { return Ref<Image>(); }
Array Metin2PythonHost::render3d_draws() { return Array(); }
void Metin2PythonHost::stop() {
}
@@ -171,6 +279,7 @@ void Metin2PythonHost::_bind_methods() {
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_render_commands"), &Metin2PythonHost::ui_render_commands);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("memory_texture", "name", "known_revision"),
&Metin2PythonHost::memory_texture, DEFVAL(-1));
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("render3d_draws"), &Metin2PythonHost::render3d_draws);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("stop"), &Metin2PythonHost::stop);
}
+4
View File
@@ -43,6 +43,10 @@ public:
// The pixels of a "mem:<id>@<revision>" image command (the CGraphicFontTexture glyph pages) as an
// RGBA8 Image; null when the texture is gone or its revision is still `known_revision`.
static godot::Ref<godot::Image> memory_texture(const godot::String &name, int64_t known_revision);
// The 3D draw calls of the last ui_update() (RenderCommands3D.h), one Dictionary each: "world",
// "view", "proj" (PackedFloat32Array, D3D row-vector layout), vertex arrays, "indices", "texture0"
// and the blend/depth/cull/lighting state.
static godot::Array render3d_draws();
static void stop();
protected: