2V2-e.5b: feed native map height and light into game render

This commit is contained in:
shenlei
2026-09-23 22:10:29 +09:00
parent 702fef7b47
commit 5a1df57e69
13 changed files with 678 additions and 76 deletions
+2 -1
View File
@@ -10,7 +10,8 @@ if(NOT TARGET mtpython)
list(FILTER MT_PLATFORM_SOURCES EXCLUDE REGEX "/ScriptLib/")
endif()
add_library(port_platform STATIC ${MT_PLATFORM_SOURCES})
target_link_libraries(port_platform PUBLIC port_logic PRIVATE mt3p::freetype xrender::libgr2)
target_link_libraries(port_platform PUBLIC port_logic PRIVATE mt3p::freetype xrender::libgr2 xrender::formats)
target_include_directories(port_platform PRIVATE ${CMAKE_SOURCE_DIR}/formats)
foreach(src IN LISTS MT_PLATFORM_SOURCES)
mt_port_apply_defines(${src})
endforeach()
@@ -206,6 +206,12 @@ void init_2v0_gameplay_stubs();
namespace PythonBoot
{
std::string CurrentMapName()
{
if (!g_game_singletons)
return {};
return CPythonBackground::Instance().GetWarpMapName();
}
bool IsRunning()
{
@@ -36,6 +36,8 @@ bool IsRunning();
bool RunMainScript(const char* lpCmdLine, std::string* error);
bool IsAppLooping();
bool AppFrame();
// Native CPythonBackground map selection, in the local coordinate frame of RenderGame.
std::string CurrentMapName();
// Used by the platform CPythonApplication: Loop() calls AppLoopWait() before each Process() and stops
// when it returns false; Exit()/Abort() call AppRequestQuit() (40250: PostQuitMessage).
@@ -3,13 +3,81 @@
// (docs/PORT-PLAN.md).
#include "GameLib/StdAfx.h"
#include "GameLib/MapManager.h"
#include "EterPack/EterPackManager.h"
#include "EterBase/MappedFile.h"
#include "EterLib/StateManager.h"
#include "GameLib/MapUtil.h"
#include <map_setting.h>
#include <terrain_files.h>
#include <terrain_mesh.h>
#include <cmath>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include "../../PlatformStub.h"
auto CMapManager::GetHeight(float, float) -> float
namespace {
// PORT: The Godot terrain adapter owns drawing until CMapOutdoor is ported. This
// pack-backed state supplies its 40250 callers with the same height and .msenv
// data during that transition; it is not a CMapOutdoor port.
struct NativeMapState {
fmt::MapSetting setting;
std::string name;
std::map<std::pair<int, int>, fmt::HeightMap> heights;
bool ready = false;
};
std::map<const CMapManager *, NativeMapState> s_native_maps;
bool pack_bytes(const std::string &name, std::string &out)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<float>();
CMappedFile file;
LPCVOID data = nullptr;
if (!CEterPackManager::Instance().Get(file, name.c_str(), &data))
return false;
out.assign(static_cast<const char *>(data), file.Size());
return true;
}
bool load_height_tile(NativeMapState &state, int tx, int ty)
{
if (tx < 0 || ty < 0 || tx >= state.setting.map_size_x || ty >= state.setting.map_size_y)
return false;
const auto key = std::make_pair(tx, ty);
if (state.heights.find(key) != state.heights.end())
return true;
char tile[16];
snprintf(tile, sizeof(tile), "%03d%03d", tx, ty);
std::string bytes;
if (!pack_bytes(state.name + "/" + tile + "/height.raw", bytes) || bytes.size() != 131 * 131 * 2)
return false;
fmt::HeightMap map;
map.raw.resize(131 * 131);
for (size_t i = 0; i < map.raw.size(); ++i)
map.raw[i] = static_cast<unsigned char>(bytes[i * 2]) |
(static_cast<unsigned char>(bytes[i * 2 + 1]) << 8);
state.heights.emplace(key, std::move(map));
return true;
}
}
auto CMapManager::GetHeight(float fx, float fy) -> float
{
auto found = s_native_maps.find(this);
if (found == s_native_maps.end() || !found->second.ready)
return 0.0f;
NativeMapState &state = found->second;
const double x = fx, y = std::abs(double(fy));
if (x < 0 || y < 0)
return 0.0f;
const int tx = int(x / 25600.0), ty = int(y / 25600.0);
if (!load_height_tile(state, tx, ty))
return 0.0f;
const float height = float(fmt::terrain_height_at(state.heights.at({tx, ty}), x - tx * 25600.0,
y - ty * 25600.0, state.setting.height_scale));
return height;
}
CMapManager::CMapManager() : mc_pcurEnvironmentData(NULL)
@@ -22,12 +90,51 @@ CMapManager::CMapManager() : mc_pcurEnvironmentData(NULL)
CMapManager::~CMapManager()
{
MT_PLATFORM_STUB();
Destroy();
}
void CMapManager::Initialize()
{
mc_pcurEnvironmentData = NULL;
__LoadMapInfoVector();
}
void CMapManager::Create()
{
// PORT: CMapOutdoor is still rendered by Metin2World, not this native object.
}
void CMapManager::Destroy()
{
Clear();
for (auto &entry : m_EnvironmentDataMap)
delete entry.second;
m_EnvironmentDataMap.clear();
}
void CMapManager::__LoadMapInfoVector()
{
std::string bytes;
if (!pack_bytes(m_stAtlasInfoFileName, bytes) && !pack_bytes("AtlasInfo.txt", bytes))
return;
std::istringstream lines(bytes);
std::string line;
m_kVct_kMapInfo.clear();
while (std::getline(lines, line)) {
std::istringstream fields(line);
TMapInfo info{};
if (!(fields >> info.m_strName >> info.m_dwBaseX >> info.m_dwBaseY >> info.m_dwSizeX >> info.m_dwSizeY))
continue;
info.m_dwEndX = info.m_dwBaseX + info.m_dwSizeX * 25600;
info.m_dwEndY = info.m_dwBaseY + info.m_dwSizeY * 25600;
m_kVct_kMapInfo.push_back(info);
}
}
auto CMapManager::Clear() -> void
{
MT_PLATFORM_STUB();
s_native_maps.erase(this);
mc_pcurEnvironmentData = NULL;
}
auto CMapManager::AllocMap() -> CMapBase *
@@ -36,10 +143,37 @@ auto CMapManager::AllocMap() -> CMapBase *
return mt_platform_stub_return<CMapBase *>();
}
auto CMapManager::LoadMap(const std::string &, float, float, float) -> bool
auto CMapManager::LoadMap(const std::string &name, float x, float y, float) -> bool
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
NativeMapState next;
next.name = name;
std::string bytes, error;
if (!pack_bytes(name + "/setting.txt", bytes) || !fmt::parse_map_setting(bytes, next.setting, &error)) {
TraceError("native map setting failed: %s (%s)", name.c_str(), error.c_str());
return false;
}
if (!load_height_tile(next, int(x / 25600.0f), int(y / 25600.0f))) {
TraceError("native map height tile failed: %s (%d,%d)", name.c_str(), int(x / 25600.0f), int(y / 25600.0f));
return false;
}
for (auto &entry : m_EnvironmentDataMap)
delete entry.second;
m_EnvironmentDataMap.clear();
mc_pcurEnvironmentData = NULL;
if (!next.setting.environment.empty()) {
auto env = std::make_unique<TEnvironmentData>();
Environment_Init(*env);
const std::string env_path = "d:/ymir work/environment/" + next.setting.environment;
if (Environment_Load(*env, env_path.c_str())) {
m_EnvironmentDataMap[0] = env.release();
mc_pcurEnvironmentData = m_EnvironmentDataMap[0];
} else {
TraceError("native map environment failed: %s", env_path.c_str());
}
}
next.ready = true;
s_native_maps[this] = std::move(next);
return true;
}
auto CMapManager::isPhysicalCollision(const D3DXVECTOR3 &) -> bool
@@ -74,19 +208,23 @@ auto CMapManager::isAttrOn(int, int, BYTE) -> bool
auto CMapManager::BeginEnvironment() -> void
{
MT_PLATFORM_STUB();
if (!IsMapReady() || !mc_pcurEnvironmentData)
return;
STATEMANAGER.SaveRenderState(D3DRS_LIGHTING, TRUE);
STATEMANAGER.SetMaterial(&mc_pcurEnvironmentData->Material);
STATEMANAGER.SetLight(0, &mc_pcurEnvironmentData->DirLights[ENV_DIRLIGHT_BACKGROUND]);
// PORT: RecordingDevice stores SetLight but has no D3D LightEnable slot yet.
}
auto CMapManager::EndEnvironment() -> void
{
MT_PLATFORM_STUB();
if (IsMapReady() && mc_pcurEnvironmentData)
STATEMANAGER.RestoreRenderState(D3DRS_LIGHTING);
}
// 40250 MapManager.cpp:144 (CPythonApplication::OnCameraUpdate); m_pkMap stays null until LoadMap is ported.
// PORT: MapOutdoor is not allocated yet; report the pack-backed transition map's readiness.
bool CMapManager::IsMapReady()
{
if (!m_pkMap)
return false;
return m_pkMap->IsReady();
auto found = s_native_maps.find(this);
return found != s_native_maps.end() && found->second.ready;
}
@@ -3,27 +3,31 @@
// (docs/PORT-PLAN.md).
#include "UserInterface/StdAfx.h"
#include "UserInterface/PythonBackground.h"
#include "EterLib/StateManager.h"
#include "../../PlatformStub.h"
auto CPythonBackground::Destroy() -> void
{
MT_PLATFORM_STUB();
CMapManager::Destroy();
m_bVisibleGuildArea = FALSE;
}
auto CPythonBackground::Create() -> void
{
MT_PLATFORM_STUB();
CMapManager::Create();
}
auto CPythonBackground::GlobalPositionToLocalPosition(LONG &, LONG &) -> void
auto CPythonBackground::GlobalPositionToLocalPosition(LONG &x, LONG &y) -> void
{
MT_PLATFORM_STUB();
x -= m_dwBaseX;
y -= m_dwBaseY;
}
auto CPythonBackground::LocalPositionToGlobalPosition(LONG &, LONG &) -> void
auto CPythonBackground::LocalPositionToGlobalPosition(LONG &x, LONG &y) -> void
{
MT_PLATFORM_STUB();
x += m_dwBaseX;
y += m_dwBaseY;
}
auto CPythonBackground::RefreshShadowLevel() -> void
@@ -61,24 +65,41 @@ auto CPythonBackground::DeleteTargetEffect(DWORD) -> void
MT_PLATFORM_STUB();
}
auto CPythonBackground::Warp(DWORD, DWORD) -> void
auto CPythonBackground::Warp(DWORD x, DWORD y) -> void
{
MT_PLATFORM_STUB();
if (m_kVct_kMapInfo.empty())
Initialize();
auto found = std::find_if(m_kVct_kMapInfo.begin(), m_kVct_kMapInfo.end(),
[x, y](const TMapInfo &info) {
return x >= info.m_dwBaseX && x < info.m_dwEndX &&
y >= info.m_dwBaseY && y < info.m_dwEndY;
});
if (found == m_kVct_kMapInfo.end()) {
TraceError("NOT_FOUND_GLOBAL_POSITION(%u, %u)", x, y);
return;
}
if (!LoadMap(found->m_strName, float(x - found->m_dwBaseX),
float(y - found->m_dwBaseY), 0.0f)) {
TraceError("CPythonBackground::Warp map load failed: %s", found->m_strName.c_str());
return;
}
m_dwBaseX = found->m_dwBaseX;
m_dwBaseY = found->m_dwBaseY;
m_strMapName = found->m_strName;
}
auto CPythonBackground::GetWarpMapName() -> const char *
{
MT_PLATFORM_STUB();
// PORT: Keep the not-yet-ported map lookup safe for GamePhase's ShowMapName call.
return "";
return m_strMapName.c_str();
}
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;
m_dwBaseX=0;
m_dwBaseY=0;
m_bVisibleGuildArea=FALSE;
m_iXMasTreeGrade=0;
}
CPythonBackground::~CPythonBackground()
@@ -166,10 +187,22 @@ auto CPythonBackground::RenderWater() -> void
auto CPythonBackground::SetBackgroundDirLight() -> void
{
MT_PLATFORM_STUB();
if (!IsMapReady() || !mc_pcurEnvironmentData)
return;
STATEMANAGER.SetLight(0, &mc_pcurEnvironmentData->DirLights[ENV_DIRLIGHT_BACKGROUND]);
}
auto CPythonBackground::SetCharacterDirLight() -> void
{
MT_PLATFORM_STUB();
if (!IsMapReady() || !mc_pcurEnvironmentData)
return;
STATEMANAGER.SetLight(0, &mc_pcurEnvironmentData->DirLights[ENV_DIRLIGHT_CHARACTER]);
}
void CPythonBackground::Initialize()
{
std::string atlas(LocaleService_GetLocalePath());
atlas += "/AtlasInfo.txt";
SetAtlasInfoFileName(atlas.c_str());
CMapManager::Initialize();
}
+6
View File
@@ -224,6 +224,10 @@ Array Metin2PythonHost::render3d_draws() {
return out;
}
String Metin2PythonHost::current_map_name() {
return String::utf8(PythonBoot::CurrentMapName().c_str());
}
void Metin2PythonHost::stop() {
PythonBoot::Stop();
}
@@ -257,6 +261,7 @@ 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(); }
String Metin2PythonHost::current_map_name() { return String(); }
void Metin2PythonHost::stop() {
}
@@ -280,6 +285,7 @@ void Metin2PythonHost::_bind_methods() {
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("current_map_name"), &Metin2PythonHost::current_map_name);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("stop"), &Metin2PythonHost::stop);
}
+1
View File
@@ -47,6 +47,7 @@ public:
// "view", "proj" (PackedFloat32Array, D3D row-vector layout), vertex arrays, "indices", "texture0"
// and the blend/depth/cull/lighting state.
static godot::Array render3d_draws();
static godot::String current_map_name();
static void stop();
protected: