render and server locale parity fixes

This commit is contained in:
shen
2026-09-28 00:18:37 -07:00
parent b776fd071f
commit d3cf586849
20 changed files with 7853 additions and 4 deletions
@@ -6,6 +6,28 @@
#include <algorithm>
namespace {
struct Point { float x, y; };
std::vector<Point> clip_polygon(std::vector<Point> polygon, int edge, float bound) {
std::vector<Point> result;
if (polygon.empty()) return result;
auto inside = [edge, bound](Point p) {
return edge == 0 ? p.x >= bound : edge == 1 ? p.x <= bound
: edge == 2 ? p.y >= bound : p.y <= bound;
};
auto intersect = [edge, bound](Point a, Point b) {
const float t = edge < 2 ? (bound - a.x) / (b.x - a.x)
: (bound - a.y) / (b.y - a.y);
return Point{a.x + t * (b.x - a.x), a.y + t * (b.y - a.y)};
};
Point previous = polygon.back();
for (Point current : polygon) {
const bool was_inside = inside(previous), is_inside = inside(current);
if (was_inside != is_inside) result.push_back(intersect(previous, current));
if (is_inside) result.push_back(current);
previous = current;
}
return result;
}
std::vector<UIRenderCommand> commands;
std::uint64_t frame_id = 0;
int canvas_width = 0;
@@ -31,6 +53,32 @@ void UIRenderAdd(UIRenderCommand command) {
command.clip_x1 = clip_x1; command.clip_y1 = clip_y1;
command.clip_x2 = clip_x2; command.clip_y2 = clip_y2;
command.behind_3d = Render3DDraws().empty();
if (command.kind == UIRenderCommand::Bar && command.quad) {
// Cooldown fans are triangles. Clip their geometry before either renderer consumes it.
std::vector<Point> polygon{{command.qx[0], command.qy[0]},
{command.qx[1], command.qy[1]},
{command.qx[2], command.qy[2]}};
const float bounds[4] = {clip_x1, clip_x2, clip_y1, clip_y2};
for (int edge = 0; edge < 4; ++edge)
polygon = clip_polygon(std::move(polygon), edge, bounds[edge]);
for (std::size_t i = 1; i + 1 < polygon.size(); ++i) {
UIRenderCommand piece = command;
const Point tri[3] = {polygon[0], polygon[i], polygon[i + 1]};
const float area = (tri[1].x - tri[0].x) * (tri[2].y - tri[0].y) -
(tri[1].y - tri[0].y) * (tri[2].x - tri[0].x);
if (area == 0.0f) continue;
for (int k = 0; k < 4; ++k) {
piece.qx[k] = tri[k < 3 ? k : 2].x;
piece.qy[k] = tri[k < 3 ? k : 2].y;
}
piece.x1 = std::min({tri[0].x, tri[1].x, tri[2].x});
piece.y1 = std::min({tri[0].y, tri[1].y, tri[2].y});
piece.x2 = std::max({tri[0].x, tri[1].x, tri[2].x});
piece.y2 = std::max({tri[0].y, tri[1].y, tri[2].y});
commands.push_back(std::move(piece));
}
return;
}
commands.push_back(command);
}
const std::vector<UIRenderCommand>& UIRenderCommands() { return commands; }
@@ -38,6 +38,9 @@ void CMapOutdoor::RenderWater()
// PORT: see the file comment.
if (!IsNativeTerrainRenderEnabled())
return;
CGraphicTexture* pWaterTexture = m_WaterInstances[((ELTimer_GetMSec() / 70) % 30)].GetTexturePointer();
if (!pWaterTexture)
return;
//////////////////////////////////////////////////////////////////////////
// RenderState
@@ -49,7 +52,7 @@ void CMapOutdoor::RenderWater()
STATEMANAGER.SaveRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
STATEMANAGER.SaveRenderState(D3DRS_COLORVERTEX, TRUE);
STATEMANAGER.SetTexture(0, m_WaterInstances[((ELTimer_GetMSec() / 70) % 30)].GetTexturePointer()->GetD3DTexture());
STATEMANAGER.SetTexture(0, pWaterTexture->GetD3DTexture());
D3DXMatrixScaling(&matTexTransformWater, m_fWaterTexCoordBase, -m_fWaterTexCoordBase, 0.0f);
D3DXMatrixMultiply(&matTexTransformWater, &m_matViewInverse, &matTexTransformWater);
@@ -88,6 +88,8 @@ void UIIMEKeyDown(int vkey);
// While IsAppLooping(), UIUpdate is AppFrame() and UIRender keeps the commands that Process() drew.
void UIUpdate();
void UIRender();
// Completed native frame duration, including Vulkan rendering and presentation, for auto sight range.
void SetNativeRenderFrameTime(float milliseconds);
bool IsSoftwareCursorVisible();
// 40250 Main() calls Clear() and lets the launcher leave scope.
@@ -588,6 +588,11 @@ bool CSpeedTreeRT::LoadTree(const unsigned char * pBlock, unsigned int nNumBytes
return true;
}
void CSpeedTreeRT::SetSourceName(const char * pFilename)
{
m_pImpl->pTree->strName = pFilename ? pFilename : "";
}
bool CSpeedTreeRT::Compute(const float * /*pTransform*/, unsigned int nSeed, bool /*bCompositeStrips*/)
{
STreeData & tree = *m_pImpl->pTree;
@@ -72,6 +72,8 @@ float CPythonApplication::GetGlobalElapsedTime() { return CTimer::Instance().Get
static bool s_isFrameSkipDisable = false;
// m_iForceSightRange (the 40250 constructor sets -1: automatic sight range).
static int s_iForceSightRange = -1;
static float s_nativeRenderFrameTime = 0.0f;
void PythonBoot::SetNativeRenderFrameTime(float milliseconds) { s_nativeRenderFrameTime = milliseconds; }
void CPythonApplication::SetFrameSkip(bool isEnable) { s_isFrameSkipDisable = !isEnable; }
// 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 already exist when the host starts the
@@ -350,7 +352,11 @@ bool CPythonApplication::Process()
static DWORD s_dwRenderRangeTime = 0;
static DWORD s_dwRenderRangeFrame = 0;
m_dwCurRenderTime = dwRenderEndTime - dwRenderStartTime;
// The native host completes Vulkan work after this command-recording pass.
// Use its last completed frame, including presentation, for automatic sight range.
m_dwCurRenderTime = s_nativeRenderFrameTime > 0.0f
? static_cast<DWORD>(s_nativeRenderFrameTime + 0.5f)
: dwRenderEndTime - dwRenderStartTime;
s_dwRenderRangeTime += m_dwCurRenderTime;
++s_dwRenderRangeFrame;
+3
View File
@@ -222,6 +222,9 @@ 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)
# Offline locale data builder: retains 40250 proto layout and gameplay fields.
add_executable(locale_proto_patch ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/locale_proto_patch.cpp)
target_link_libraries(locale_proto_patch PRIVATE port_platform)
# 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
@@ -371,6 +371,8 @@ bool CSpeedTreeWrapper::LoadTree(const char * pszSptFile, const BYTE * c_pbBlock
return false;
}
}
// PORT: the in-memory SpeedTree replacement needs the pack path for species geometry and UVs.
m_pSpeedTree->SetSourceName(pszSptFile);
// override the lighting method stored in the spt file
#ifdef WRAPPER_USE_DYNAMIC_LIGHTING
@@ -297,6 +297,8 @@ static void operator delete[](void* pRawMemory);
bool LoadTree(const char* pFilename);
bool LoadTree(const unsigned char* pBlock, unsigned int nNumBytes);
// PORT: supply the pack path when LoadTree receives only a memory block.
void SetSourceName(const char* pFilename);
unsigned char* SaveTree(unsigned int& nNumBytes, bool bSaveLeaves = false) const;
void GetTreeSize(float& fSize, float& fVariance) const;