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
@@ -3,6 +3,7 @@
// (docs/PORT-PLAN.md).
#include "UserInterface/StdAfx.h"
#include "UserInterface/PythonBackground.h"
#include "UserInterface/InstanceBase.h"
#include "EterLib/StateManager.h"
#include "../../PlatformStub.h"
@@ -107,16 +108,143 @@ CPythonBackground::~CPythonBackground()
MT_PLATFORM_STUB();
}
auto CPythonBackground::CheckAdvancing(CInstanceBase *) -> bool
// 40250 PythonBackground.cpp:547-608, verbatim. No map objects are registered with CCullingManager until
// CMapOutdoor is ported, so the checkers see no opponent and advancing is never blocked.
struct CollisionChecker
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
bool isBlocked;
CInstanceBase* pInstance;
CollisionChecker(CInstanceBase* pInstance) : pInstance(pInstance), isBlocked(false) {}
void operator () (CGraphicObjectInstance* pOpponent)
{
if (isBlocked)
return;
if (!pOpponent)
return;
if (pInstance->IsBlockObject(*pOpponent))
isBlocked=true;
}
};
struct CollisionAdjustChecker
{
bool isBlocked;
CInstanceBase* pInstance;
CollisionAdjustChecker(CInstanceBase* pInstance) : pInstance(pInstance), isBlocked(false) {}
void operator () (CGraphicObjectInstance* pOpponent)
{
if (!pOpponent)
return;
if (pInstance->AvoidObject(*pOpponent))
isBlocked=true;
}
};
bool CPythonBackground::CheckAdvancing(CInstanceBase * pInstance)
{
if (!IsMapReady())
return true;
Vector3d center;
float radius;
pInstance->GetGraphicThingInstanceRef().GetBoundingSphere(center,radius);
CCullingManager & rkCullingMgr = CCullingManager::Instance();
CollisionAdjustChecker kCollisionAdjustChecker(pInstance);
rkCullingMgr.ForInRange(center, radius, &kCollisionAdjustChecker);
if (kCollisionAdjustChecker.isBlocked)
{
CollisionChecker kCollisionChecker(pInstance);
rkCullingMgr.ForInRange(center, radius, &kCollisionChecker);
if (kCollisionChecker.isBlocked)
{
pInstance->BlockMovement();
return true;
}
else
{
pInstance->NEW_MoveToDestPixelPositionDirection(pInstance->NEW_GetDstPixelPositionRef());
}
return false;
}
return false;
}
auto CPythonBackground::GetPickingPointWithRayOnlyTerrain(const CRay &, D3DXVECTOR3 *) -> bool
namespace
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
// 40250 CMapOutdoor::__PickTerrainHeight (MapOutdoor.cpp:448), marching the ray against the terrain height.
// PORT: CMapOutdoor is not ported; the terrain is the native CMapManager height map, and GetTerrainNum/
// GetTerrainPointer become "the map is ready" (GetHeight reads the height tile under the point).
bool PickTerrainHeight(CMapManager& rkMap, float& fPos, const D3DXVECTOR3& v3Start, const D3DXVECTOR3& v3End, float fStep,
float fRayRange, float fLimitRange, D3DXVECTOR3* pv3Pick)
{
D3DXVECTOR3 v3CurPos;
float fRayRangeInv=1.0f/fRayRange;
while (fPos < fRayRange && fPos<fLimitRange)
{
D3DXVec3Lerp(&v3CurPos, &v3Start, &v3End, fPos*fRayRangeInv);
float fMultiplier = 1.0f;
if (rkMap.IsMapReady())
{
// PORT: PR_FLOAT_TO_INT is x87 fistp (round to nearest) corrected down, i.e. floor.
int ix = int(std::floor(v3CurPos.x)), iy = int(std::floor(fabs(v3CurPos.y)));
float fMapHeight = rkMap.GetHeight(float(ix), float(iy));
if ( fMapHeight >= v3CurPos.z)
{
*pv3Pick = v3CurPos;
return true;
}
else
{
fMultiplier = fMAX(1.0f, 0.01f * ( v3CurPos.z - fMapHeight ) );
}
}
fPos += fStep * fMultiplier;
}
return false;
}
}
// 40250 CMapOutdoor::GetPickingPointWithRayOnlyTerrain (MapOutdoor.cpp:549).
bool CPythonBackground::GetPickingPointWithRayOnlyTerrain(const CRay & rRay, D3DXVECTOR3 * v3IntersectPt)
{
bool bTerrainPick = false;
D3DXVECTOR3 v3TerrainPick;
D3DXVECTOR3 v3Start, v3End, v3Dir;
float fRayRange;
rRay.GetStartPoint(&v3Start);
rRay.GetDirection(&v3Dir, &fRayRange);
rRay.GetEndPoint(&v3End);
float fPos = 0.0f;
bTerrainPick=true;
if (!PickTerrainHeight(*this, fPos, v3Start, v3End, 5.0f, fRayRange, 5000.0f, &v3TerrainPick))
if (!PickTerrainHeight(*this, fPos, v3Start, v3End, 10.0f, fRayRange, 10000.0f, &v3TerrainPick))
if (!PickTerrainHeight(*this, fPos, v3Start, v3End, 100.0f, fRayRange, 100000.0f, &v3TerrainPick))
bTerrainPick=false;
if (bTerrainPick)
{
*v3IntersectPt = v3TerrainPick;
return true;
}
return false;
}
// 40250 CMapOutdoor::GetPickingPointWithRay (MapOutdoor.cpp:484).
// PORT: the object pick (FGetPickingPoint over CCullingManager) needs CMapOutdoor's area objects; only the
// terrain half runs until the map is ported.
bool CPythonBackground::GetPickingPointWithRay(const CRay & rRay, D3DXVECTOR3 * v3IntersectPt)
{
return GetPickingPointWithRayOnlyTerrain(rRay, v3IntersectPt);
}
// 40250 PythonBackground.cpp:173 (CPythonApplication::RenderGame's SetPerspective); m_pkMap stays null
@@ -207,8 +335,8 @@ void CPythonBackground::Initialize()
CMapManager::Initialize();
}
auto CPythonBackground::GetPickingPoint(D3DXVECTOR3 *) -> bool
// 40250 PythonBackground.cpp:263 → CMapOutdoor::GetPickingPoint (MapOutdoor.cpp:443): the cursor ray.
bool CPythonBackground::GetPickingPoint(D3DXVECTOR3 * v3IntersectPt)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
return GetPickingPointWithRay(ms_Ray, v3IntersectPt);
}