2V2-e.1: 3D draw-command channel — recording D3D8 device, StateManager, GrpBase, camera

EterLib/StateManager.cpp and GrpBase.cpp ported verbatim. CGraphicDevice::Create builds a platform
IDirect3DDevice8 that keeps the device state and records every Draw* call as a Render3DDraw
(vertices decoded by FVF, strips/fans expanded, textures, world/view/proj, blend/depth/light state).
The 40250 camera (__UpdateCamera, SetCenterPosition, ...) and the Process order
(__UpdateCamera -> OnCameraUpdate -> OnUIUpdate -> Begin/SetInterfaceRenderState/OnUIRender/End)
are in place; CScreen Begin/End, CPythonGraphic render states and CCullingManager::Process matrix
updates are verbatim. port.login_flow asserts a textured, lit character draw under the RH
perspective with every vertex on screen; offline and live pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 20:15:54 +09:00
co-authored by Claude Opus 5.5
parent 668f48d6ec
commit 5bfa13c5cc
27 changed files with 3616 additions and 777 deletions
@@ -10,6 +10,8 @@
#include "EterLib/NetDevice.h"
#include "../PlatformStub.h"
#include "../EterLib/UIRenderCommands.h"
#include "../EterLib/RenderCommands3D.h"
#include "EterLib/GrpDevice.h"
#include "../ScriptLib/PythonBoot.h"
// 2V0-d link surface; 2V0-f adds the application lifecycle (Create/Loop/Process/Exit) that
@@ -53,17 +55,12 @@ void CPythonApplication::SetMinFog(float) { MT_PLATFORM_STUB(); }
// this returns it. PORT: no member to hold it; the timer only advances in Process(), so reading it
// here gives the same value.
float CPythonApplication::GetGlobalTime() { return CTimer::Instance().GetCurrentSecond(); }
// Camera (2V3).
void CPythonApplication::SetEventCamera(const SCameraSetting&) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetDefaultCamera() { MT_PLATFORM_STUB(); }
// In 40250 this stores m_v3CenterPosition for the game view (introselect.py sets it on the character list).
void CPythonApplication::SetCenterPosition(float, float, float) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetFrameSkip(bool) { MT_PLATFORM_STUB(); }
// PORT: 40250 creates the Win32 window, the D3D8 device, cursors, sound, keyboard, the net device,
// fonts and the IME here. The Godot window and its canvas are the device on this platform and they
// already exist when the host starts the script, so the window size is the canvas size
// (PythonBoot::SetUISize) rather than the width/height passed from metin2.cfg. What remains of
// Create belongs to the subsystems that are not ported yet (sound 2V4, net 2V1, game render 2V3).
// fonts and the IME here. The Godot window and its canvas already exist when the host starts the
// script, so the window size is the canvas size (PythonBoot::SetUISize) rather than the width/height
// passed from metin2.cfg, and the graphic device is the recording device (RecordingDevice.h) at that
// size. Cursors, sound, keyboard, fonts and the IME are not created here yet.
bool CPythonApplication::Create(PyObject*, const char*, int, int, int)
{
unsigned width = 0, height = 0;
@@ -76,6 +73,38 @@ bool CPythonApplication::Create(PyObject*, const char*, int, int, int)
return false;
}
// In 40250 m_grpDevice is a member created here (PythonApplication.cpp:909).
static CGraphicDevice s_grpDevice;
int iRet = s_grpDevice.Create(NULL, width, height, true, 32, 0);
switch (iRet)
{
case CGraphicDevice::CREATE_OK:
break;
case CGraphicDevice::CREATE_REFRESHRATE:
break;
case CGraphicDevice::CREATE_GET_DEVICE_CAPS:
PyErr_SetString(PyExc_RuntimeError, "GetDevCaps failed");
TraceError("CreateDevice: GetDevCaps failed");
return false;
case CGraphicDevice::CREATE_GET_DEVICE_CAPS2:
PyErr_SetString(PyExc_RuntimeError, "GetDevCaps2 failed");
TraceError("CreateDevice: GetDevCaps2 failed");
return false;
default:
// PORT: the recording device returns only the cases above; the rest is 40250's fallback.
if (iRet & CGraphicDevice::CREATE_OK)
break;
TraceError("CreateDevice: Unknown Error!");
PyErr_SetString(PyExc_RuntimeError, "UNKNOWN_ERROR");
return false;
}
// In 40250 m_netDevice is a member created here (PythonApplication.cpp:1242).
static CNetworkDevice s_netDevice;
if (!s_netDevice.Create())
@@ -205,15 +234,43 @@ bool CPythonApplication::Process()
// transfer is not ported yet (GuildMarkDownloader.cpp/GuildMarkUploader.cpp).
CAccountConnector::Instance().Process();
//!@# Alt+Tab 중 SetTransfor 에서 튕김 현상 해결을 위해 - [levites]
//if (m_isActivateWnd)
__UpdateCamera();
CResourceManager::Instance().Update();
OnCameraUpdate();
OnUIUpdate();
// PORT: the render block without the lost-device restore, ClearDepthBuffer, Show and the render-time
// statistics (Godot clears and presents the frame). The frame's UI and 3D command lists restart here.
CCullingManager::Instance().Update();
UIRenderBeginFrame();
OnUIRender();
Render3DBeginFrame();
CPythonGraphic& rkGraphic = CPythonGraphic::Instance();
if (rkGraphic.Begin())
{
rkGraphic.SetInterfaceRenderState();
OnUIRender();
rkGraphic.End();
}
return true;
}
// 40250 PythonApplicationEvent.cpp:5
void CPythonApplication::OnCameraUpdate()
{
if ( CPythonBackground::Instance().IsMapReady() )
{
CCamera* pkCameraMgr = CCameraManager::Instance().GetCurrentCamera();
if (pkCameraMgr)
pkCameraMgr->Update();
}
}
// 40250 PythonApplicationEvent.cpp:15-25
void CPythonApplication::OnUIUpdate()
{
@@ -233,30 +290,11 @@ bool CPythonApplication::GetLiarCursorOn() { MT_PLATFORM_STUB(); return false; }
void CPythonApplication::SetCursorMode(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetMouseHandler(PyObject*) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetGlobalCenterPosition(LONG, LONG) { MT_PLATFORM_STUB(); }
void CPythonApplication::SetCamera(float, float, float, float) { MT_PLATFORM_STUB(); }
void CPythonApplication::GetCamera(float* distance, float* pitch, float* rotation, float* height) {
MT_PLATFORM_STUB();
if (distance) *distance = 0;
if (pitch) *pitch = 0;
if (rotation) *rotation = 0;
if (height) *height = 0;
}
void CPythonApplication::RotateCamera(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::PitchCamera(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::ZoomCamera(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::MovieRotateCamera(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::MoviePitchCamera(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::MovieZoomCamera(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::MovieResetCamera() { MT_PLATFORM_STUB(); }
float CPythonApplication::GetRotation() { MT_PLATFORM_STUB(); return 0; }
float CPythonApplication::GetPitch() { MT_PLATFORM_STUB(); return 0; }
void CPythonApplication::SetFPS(int) { MT_PLATFORM_STUB(); }
time_t CPythonApplication::GetServerTime() { MT_PLATFORM_STUB(); return 0; }
time_t CPythonApplication::GetServerTimeStamp() { MT_PLATFORM_STUB(); return 0; }
void CPythonApplication::SetConnectData(const char*, int) { MT_PLATFORM_STUB(); }
void CPythonApplication::GetConnectData(std::string& ip, int& port) { MT_PLATFORM_STUB(); ip.clear(); port = 0; }
void CPythonApplication::EnableSpecialCameraMode() { MT_PLATFORM_STUB(); }
void CPythonApplication::SetCameraSpeed(int) { MT_PLATFORM_STUB(); }
void CPythonApplication::SaveCameraSetting(const char*) { MT_PLATFORM_STUB(); }
bool CPythonApplication::LoadCameraSetting(const char*) { MT_PLATFORM_STUB(); return false; }
void CPythonApplication::SetForceSightRange(int) { MT_PLATFORM_STUB(); }