Files
mtgodot-poc/extension/src/platform/UserInterface/PythonApplication.cpp
T
shenleiandClaude Opus 5.5 62b6e23bfc port 2V2-b: GamePhase dispatch consumes the Entergame burst
PythonNetworkStreamPhaseGame/PhaseGameActor/PhaseGameItem/Command,
NetworkActorManager and PythonChat/PythonChatModule are ported verbatim and
dispatched at runtime; the 2V0 chat stub module is removed and initChat()
runs before initnet() as in 40250. InstanceBase, PythonCharacterManager and
GameLib ActorInstance/RaceData/RaceManager compile but stay TODO until
2V2-c/d wire them.

Units not ported yet get their singletons from PythonBoot's GameSingletons
(CPythonApplication member order) and platform/pending stand-ins generated
by platform_stub.py pending; const char* stand-ins return "".

port.login_flow's fake server now sends the CInputLogin::Entergame order
(actor insert, NPC positions, PHASE_GAME, land list, time, channel, greet
notice, ping) and a TraceError observer asserts no unknown packet header.
port.login_live passes against the real server for 10 s of game phase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 18:20:57 +09:00

306 lines
13 KiB
C++

#include "UserInterface/StdAfx.h"
#include "UserInterface/PythonApplication.h"
#include "UserInterface/MarkManager.h"
#include "GameLib/GameUtil.h"
#include "EterLib/Camera.h"
#include "EterBase/Timer.h"
#include "EterLib/ResourceManager.h"
#include "UserInterface/PythonNetworkStream.h"
#include "UserInterface/AccountConnector.h"
#include "EterLib/NetDevice.h"
#include "../PlatformStub.h"
#include "../EterLib/UIRenderCommands.h"
#include "../ScriptLib/PythonBoot.h"
// 2V0-d link surface; 2V0-f adds the application lifecycle (Create/Loop/Process/Exit) that
// prototype.RunApp() drives. The rest of the members still wait for the game slices (2V1-2V3).
//
// PORT: there is no CPythonApplication object yet (its members are the whole game: background,
// network stream, player, ...), so these functions only use the singletons PythonBoot owns
// (UI::CWindowManager, CTimer, CResourceManager) and never touch a data member.
CPythonApplication* CPythonApplication::ms_pInstance = nullptr;
void CPythonApplication::ShowWebPage(const char*, const RECT&) { MT_PLATFORM_STUB(); }
void CPythonApplication::MoveWebPage(const RECT&) { MT_PLATFORM_STUB(); }
void CPythonApplication::HideWebPage() { MT_PLATFORM_STUB(); }
bool CPythonApplication::IsWebPageMode() { MT_PLATFORM_STUB(); return false; }
void CPythonApplication::GetInfo(UINT, std::string* out) { MT_PLATFORM_STUB(); if (out) out->clear(); }
// In 40250, Loop() is: while (1) { if (IsMessage()) { if (!MessageProcess()) break; } else { if (!Process()) break; } }
// PORT: Godot owns the message pump and forwards input to the window manager between frames
// (PythonBoot::UIMouseMove & co.), so the loop here only waits for the host's next frame. It runs
// on PythonBoot's script thread; WM_QUIT is PythonBoot::AppRequestQuit.
void CPythonApplication::Loop()
{
while (PythonBoot::AppLoopWait())
{
if (!Process())
break;
}
}
// In 40250 this is PostQuitMessage(0).
void CPythonApplication::Exit() { PythonBoot::AppRequestQuit(); }
// 40250 also writes a TraceError banner first; the caller (system.py) has already logged the reason.
void CPythonApplication::Abort()
{
TraceError("============================================================================================================");
TraceError("Abort!!!!\n\n");
PythonBoot::AppRequestQuit();
}
void CPythonApplication::SetMinFog(float) { MT_PLATFORM_STUB(); }
// In 40250 Process() copies CTimer::GetCurrentSecond() into m_fGlobalTime right after Advance() and
// 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).
bool CPythonApplication::Create(PyObject*, const char*, int, int, int)
{
unsigned width = 0, height = 0;
UIRenderGetSize(&width, &height);
if (!width || !height)
{
// appCreate returns NULL on failure; RunApp turns this into the original LogBox.
TraceError("CreateDevice: GraphicDevice create failed");
PyErr_SetString(PyExc_RuntimeError, "CREATE_DEVICE");
return false;
}
// In 40250 m_netDevice is a member created here (PythonApplication.cpp:1242).
static CNetworkDevice s_netDevice;
if (!s_netDevice.Create())
{
TraceError("NetDevice::Create failed");
PyErr_SetString(PyExc_RuntimeError, "CREATE_NETWORK");
return false;
}
return true;
}
void CPythonApplication::UpdateGame() { MT_PLATFORM_STUB(); }
void CPythonApplication::RenderGame() { MT_PLATFORM_STUB(); }
// 40250 PythonApplication.cpp:399. Kept in order: timer, network (2V1), input polling (Godot pushes
// input instead), camera (2V3), resource GC, mouse/UI update, then the render block's interface pass.
// PORT: the frame-skip bookkeeping and the game render pass are left out; the Godot frame clock
// decides when this runs, and RenderGame belongs to 2V3.
bool CPythonApplication::Process()
{
ELTimer_SetFrameMSec();
CTimer& rkTimer=CTimer::Instance();
rkTimer.Advance();
// Network I/O
CPythonNetworkStream::Instance().Process();
// PORT: m_kGuildMarkUploader/m_kGuildMarkDownloader.Process() follow here in 40250; the guild mark
// transfer is not ported yet (GuildMarkDownloader.cpp/GuildMarkUploader.cpp).
CAccountConnector::Instance().Process();
CResourceManager::Instance().Update();
OnUIUpdate();
UIRenderBeginFrame();
OnUIRender();
return true;
}
// 40250 PythonApplicationEvent.cpp:15-25
void CPythonApplication::OnUIUpdate()
{
UI::CWindowManager& rkUIMgr=UI::CWindowManager::Instance();
rkUIMgr.Update();
}
void CPythonApplication::OnUIRender()
{
UI::CWindowManager& rkUIMgr=UI::CWindowManager::Instance();
rkUIMgr.Render();
}
BOOL CPythonApplication::SetCursorNum(int) { MT_PLATFORM_STUB(); return FALSE; }
void CPythonApplication::SetCursorVisible(BOOL, bool) { MT_PLATFORM_STUB(); }
BOOL CPythonApplication::GetCursorVisible() { MT_PLATFORM_STUB(); return FALSE; }
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(); }
void CCamera::SetResistance(float) { MT_PLATFORM_STUB(); }
void CCamera::SetCameraMaxDistance(float) { MT_PLATFORM_STUB(); }
void CMovieMan::PlayLogo(const char*) { MT_PLATFORM_STUB(); }
float GetDegreeDifference(float, float) { MT_PLATFORM_STUB(); return 0; }
int GetRotatingDirection(float, float) { MT_PLATFORM_STUB(); return 0; }
BOOL PERF_CHECKER_RENDER_GAME = FALSE;
BOOL SKILL_EFFECT_UPGRADE_ENABLE = FALSE;
BOOL USE_WEAPON_SPECULAR = FALSE;
D3DXCOLOR g_fSpecularColor;
double g_specularSpd = 0;
bool LoadLocaleData(const char*) { MT_PLATFORM_STUB(); return false; }
void SetGuildSymbolPath(const char*) { MT_PLATFORM_STUB(); }
void TextTail_SetLivingTime(long) { MT_PLATFORM_STUB(); }
namespace {
// The supplied 40250 Client/locale.cfg selects "en" (code page 1252).
// Keep these mutable for app.ForceSetLocale, as Locale.cpp does.
std::string locale_name = "en";
std::string locale_path = "locale/en";
}
const char* LocaleService_GetName() { return "EUROPE"; }
bool LocaleService_IsCHEONMA() { MT_PLATFORM_STUB(); return false; }
void LocaleService_SetCHEONMA(bool) { MT_PLATFORM_STUB(); }
unsigned LocaleService_GetCodePage() { return 1252; }
const char* LocaleService_GetLocaleName() { return locale_name.c_str(); }
const char* LocaleService_GetLocalePath() { return locale_path.c_str(); }
void LocaleService_ForceSetLocale(const char* name, const char* path) {
locale_name = name ? name : "";
locale_path = path ? path : "";
}
// 40250 UserInterface/Locale.cpp:9-31, 168-172 and 393-423 (the non-Japan branch), verbatim: what the network
// slice (批次 2V1) and InsultChecker call.
const char* LSS_YMIR = "YMIR";
const char* LSS_JAPAN = "JAPAN";
const char* LSS_ENGLISH = "ENGLISH";
const char* LSS_HONGKONG = "HONGKONG";
const char* LSS_TAIWAN = "TAIWAN";
const char* LSS_NEWCIBN = "NEWCIBN";
const char* LSS_EUROPE = "EUROPE";
const char* LSS_GLOBAL = "GLOBAL";
#ifndef LSS_SECURITY_KEY
#define LSS_SECURITY_KEY "testtesttesttest"
#endif
std::string __SECURITY_KEY_STRING__ = LSS_SECURITY_KEY;
const char* LocaleService_GetSecurityKey()
{
return __SECURITY_KEY_STRING__.c_str();
}
bool LocaleService_IsYMIR() { return (stricmp( LocaleService_GetName(), LSS_YMIR ) == 0) || (stricmp( LocaleService_GetLocaleName(), "ymir" ) == 0); }
bool LocaleService_IsJAPAN() { return (stricmp( LocaleService_GetName(), LSS_JAPAN ) == 0) || (stricmp( LocaleService_GetLocaleName(), "japan" ) == 0); }
bool LocaleService_IsENGLISH() { return (stricmp( LocaleService_GetName(), LSS_ENGLISH ) == 0); }
bool LocaleService_IsEUROPE() { return (stricmp( LocaleService_GetName(), LSS_EUROPE ) == 0); }
bool LocaleService_IsHONGKONG() { return (stricmp( LocaleService_GetName(), LSS_HONGKONG ) == 0); }
bool LocaleService_IsTAIWAN() { return (stricmp( LocaleService_GetName(), LSS_TAIWAN ) == 0); }
bool LocaleService_IsNEWCIBN() { return (stricmp( LocaleService_GetName(), LSS_NEWCIBN ) == 0); }
BOOL LocaleService_IsLeadByte( const char chByte )
{
return (((unsigned char) chByte) & 0x80) != 0;
}
int LocaleService_StringCompareCI( LPCSTR szStringLeft, LPCSTR szStringRight, size_t sizeLength )
{
return strnicmp( szStringLeft, szStringRight, sizeLength );
}
// 40250 UserInterface/GameType.cpp:9-73 (DEFAULT_FONT), verbatim.
// PORT: the default names are CP949 literals in 40250; ui.py sets the locale font (app.SetDefaultFontName)
// before anything asks for it.
static std::string gs_strDefaultFontName = "굴림체:12.fnt";
static std::string gs_strDefaultItalicFontName = "굴림체:12i.fnt";
static CResource* gs_pkDefaultFont = NULL;
static CResource* gs_pkDefaultItalicFont = NULL;
static bool gs_isReloadDefaultFont = false;
void DefaultFont_Startup()
{
gs_pkDefaultFont = NULL;
}
void DefaultFont_Cleanup()
{
if (gs_pkDefaultFont)
gs_pkDefaultFont->Release();
}
void DefaultFont_SetName(const char * c_szFontName)
{
gs_strDefaultFontName = c_szFontName;
gs_strDefaultFontName += ".fnt";
gs_strDefaultItalicFontName = c_szFontName;
if(strchr(c_szFontName, ':'))
gs_strDefaultItalicFontName += "i";
gs_strDefaultItalicFontName += ".fnt";
gs_isReloadDefaultFont = true;
}
bool ReloadDefaultFonts()
{
CResourceManager& rkResMgr = CResourceManager::Instance();
gs_isReloadDefaultFont = false;
CResource* pkNewFont = rkResMgr.GetResourcePointer(gs_strDefaultFontName.c_str());
pkNewFont->AddReference();
if (gs_pkDefaultFont)
gs_pkDefaultFont->Release();
gs_pkDefaultFont = pkNewFont;
CResource* pkNewItalicFont = rkResMgr.GetResourcePointer(gs_strDefaultItalicFontName.c_str());
pkNewItalicFont->AddReference();
if (gs_pkDefaultItalicFont)
gs_pkDefaultItalicFont->Release();
gs_pkDefaultItalicFont = pkNewItalicFont;
return true;
}
CResource* DefaultFont_GetResource()
{
if (!gs_pkDefaultFont || gs_isReloadDefaultFont)
ReloadDefaultFonts();
return gs_pkDefaultFont;
}
CResource* DefaultItalicFont_GetResource()
{
if (!gs_pkDefaultItalicFont || gs_isReloadDefaultFont)
ReloadDefaultFonts();
return gs_pkDefaultItalicFont;
}