2V3-b: click on the ground to walk; read the terrain attribute map

CScreen::SetCursorPosition and CPythonGraphic::SetCursorPosition are the 40250
bodies, so RenderGame builds the pick ray under the cursor each frame.
CPythonBackground::GetPickingPoint* march that ray as
CMapOutdoor::__PickTerrainHeight does, against the native CMapManager height
(PR_FLOAT_TO_INT's x87 rounding becomes floor); object picking waits for
CMapOutdoor. CMapManager::isAttrOn reads the packed <map>/<xxxyyy>/attr.atr
as CMapOutdoor::isAttrOn + CTerrain::isAttrOn do, and CheckAdvancing with its
collision checkers is verbatim (no map objects are registered yet). All of
this stays in the pending/platform layer and is marked ADAPTED.

port.login_flow clicks the ground below the actor after the key walk. The
actor walks to the picked point (334 cm offline), and the fake server sees
FUNC_MOVE, then FUNC_WAIT. It also checks that the actor's cell is free and
that its terrain block has blocked cells. port.login_live against the real
server passed (key 133 cm, click 296 cm), and python_game_render_test.sh
passes offline. MT_TEST_MODE=python_ui now adds the 3D surface, so the port
route can be played by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 23:57:34 +09:00
co-authored by Claude Opus 5.5
parent ff0983d3c8
commit 8b4700f008
12 changed files with 627 additions and 46 deletions
+49
View File
@@ -48,6 +48,7 @@
#include "UserInterface/StdAfx.h"
#include "UserInterface/PythonCharacterManager.h"
#include "UserInterface/InstanceBase.h"
#include "UserInterface/PythonBackground.h"
#include "EterGrnLib/StdAfx.h"
#include "EterGrnLib/LODController.h"
#include "EterGrnLib/ModelInstance.h"
@@ -413,6 +414,54 @@ int main(int argc, char** argv)
for (const auto& e : m)
std::printf("port_login_flow_test: %s\n", e.c_str());
}
// The terrain attribute map (attr.atr) under the actor: CInstanceBase's block/water/ban-PK checks read it
// through CPythonBackground::isAttrOn. The actor stands on a free cell; its 256x256 tile has blocked cells.
if (main_instance)
{
TPixelPosition here = {};
main_instance->NEW_GetPixelPosition(&here);
CPythonBackground& bg = CPythonBackground::Instance();
CHECK(!bg.isAttrOn(here.x, here.y, CTerrainImpl::ATTRIBUTE_BLOCK));
const int tx = int(here.x) / CTerrainImpl::TERRAIN_XSIZE * CTerrainImpl::TERRAIN_XSIZE;
const int ty = int(here.y) / CTerrainImpl::TERRAIN_YSIZE * CTerrainImpl::TERRAIN_YSIZE;
int blocked = 0;
for (int y = 0; y < CTerrainImpl::TERRAIN_YSIZE; y += CTerrainImpl::HALF_CELLSCALE)
for (int x = 0; x < CTerrainImpl::TERRAIN_XSIZE; x += CTerrainImpl::HALF_CELLSCALE)
blocked += bg.isAttrOn(tx + x, ty + y, CTerrainImpl::ATTRIBUTE_BLOCK) ? 1 : 0;
std::printf("port_login_flow_test: %d blocked attribute cells in the actor's terrain\n", blocked);
CHECK(blocked > 0);
}
// 8. (批次 2V3) Click-to-move: a left click on the ground below the actor reaches game.GameWindow's
// OnMouseLeftButtonDown → player.SetMouseState(MBT_LEFT, MBS_PRESS); CPythonPlayer picks the ground
// through the cursor ray (CScreen::SetCursorPosition → CPythonBackground::GetPickingPoint) and the
// actor walks to it, sending CG_MOVE FUNC_MOVE, then FUNC_WAIT on arrival.
const size_t moves_before = server ? server->Events().size() : 0;
if (main_instance)
main_instance->NEW_GetPixelPosition(&start);
PythonBoot::UIMouseButton(1, true, 400, 520);
pump_until(0.1, [] { return false; });
PythonBoot::UIMouseButton(1, false, 400, 520);
CHECK(main_instance && pump_until(3, [&] { return walked() > 100.0f; }));
CHECK(main_instance && pump_until(5, [&] { return !main_instance->IsWalking(); }));
std::printf("port_login_flow_test: click walked %.0f cm\n", main_instance ? walked() : 0.0f);
if (server)
{
auto click_moves = [&] {
std::vector<std::string> out;
const auto events = server->Events();
for (size_t i = moves_before; i < events.size(); ++i)
if (events[i].rfind("game2:move ", 0) == 0)
out.push_back(events[i]);
return out;
};
CHECK(pump_until(2, [&] { auto m = click_moves(); return m.size() >= 2 && m.back().rfind("game2:move 0 ", 0) == 0; }));
const auto m = click_moves();
CHECK(!m.empty() && m.front().rfind("game2:move 1 0 ", 0) == 0);
for (const auto& e : m)
std::printf("port_login_flow_test: %s\n", e.c_str());
}
}
if (server)
{