2V3-a: walk with the keyboard through the ported player chain
PythonPlayer, PythonPlayerInput{,Keyboard,Mouse}, PythonPlayerEventHandler,
PythonPlayerSkill, PythonPlayerModule and GameType are verbatim copies; the
`player` stub module and the pending PythonPlayer/GameType stand-ins are gone.
Unported callees (CPythonSkill, CItemData, CItemManager, CPythonItem, ...) are
pending stubs. 64-bit fixes: emotion icon handles use Py_BuildPointer, and
PyDict_Next takes Py_ssize_t.
The actor did not move because GrannyUpdateModelMatrix passed the model matrix
through. Walk/run motions use constant root-motion extraction
(AccumulationFlags=AccumulationExtracted, LoopTranslation e.g. (0,-300,0) for
the warrior run, same as the .msa Accumulation). libgr2 now reads both fields
from TrackGroups[0], and the runtime moves by LoopTranslation over each
control's local-clock span, blended by control weight. VDA extraction is not
implemented.
port.login_flow presses DIK_UP; the actor walks more than 100 cm, and the fake
server parses CG packets and sees CG_MOVE FUNC_MOVE, then FUNC_WAIT at the new
position. port.login_live against the real server walked 132 cm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
157f7c3bb4
commit
ff0983d3c8
@@ -509,9 +509,46 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
|
||||
return Fail(name + ": CG_PONG after the entergame burst: " + error);
|
||||
Event(name + ":burst_pong");
|
||||
|
||||
// Hold the connection until the test stops us.
|
||||
std::uint8_t byte = 0;
|
||||
while (!m_stop && c.Read(&byte, 1) == 1)
|
||||
;
|
||||
return true;
|
||||
// Then hold the connection until the test stops us, reading what the game phase sends the way
|
||||
// CInputProcessor::Process frames it (static size or TDynamicSizePacketHeader, then the sequence
|
||||
// byte). A CG_MOVE (input_main.cpp CInputMain::Move) becomes "<name>:move <func> <arg> <rot> <x> <y>".
|
||||
for (;;)
|
||||
{
|
||||
std::uint8_t packet[1024] = {};
|
||||
if (c.Read(packet, 1) != 1)
|
||||
return true;
|
||||
const std::uint8_t header = packet[0];
|
||||
size_t size = static_cast<size_t>(packet_size_cg(header));
|
||||
if (is_dynamic_cg(header) || header == HDR_CG_SYNC_POSITION)
|
||||
{
|
||||
if (c.Read(packet + 1, sizeof(DynHead) - 1) != 1)
|
||||
return true;
|
||||
DynHead head;
|
||||
std::memcpy(&head, packet, sizeof(head));
|
||||
size = head.size;
|
||||
if (size < sizeof(DynHead) || size > sizeof(packet) ||
|
||||
(size > sizeof(DynHead) && c.Read(packet + sizeof(DynHead), size - sizeof(DynHead)) != 1))
|
||||
return Fail(name + ": dynamic CG packet " + std::to_string(header) + " in the game phase");
|
||||
}
|
||||
else if (size == 0 || size > sizeof(packet))
|
||||
return Fail(name + ": unknown CG header " + std::to_string(header) + " in the game phase");
|
||||
else if (size > 1 && c.Read(packet + 1, size - 1) != 1)
|
||||
return true;
|
||||
if (is_sequence_cg(header))
|
||||
{
|
||||
std::uint8_t sequence = 0;
|
||||
if (c.Read(&sequence, 1) != 1)
|
||||
return true;
|
||||
if (sequence != SEQUENCE_TABLE[c.sequence])
|
||||
return Fail(name + ": SEQUENCE mismatch on CG header " + std::to_string(header));
|
||||
c.sequence = (c.sequence + 1) % SEQUENCE_TABLE_SIZE;
|
||||
}
|
||||
if (header == HDR_CG_MOVE)
|
||||
{
|
||||
CGMove move;
|
||||
std::memcpy(&move, packet, sizeof(move));
|
||||
Event(name + ":move " + std::to_string(move.func) + " " + std::to_string(move.arg) + " " +
|
||||
std::to_string(move.rot) + " " + std::to_string(move.x) + " " + std::to_string(move.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,6 +377,42 @@ int main(int argc, char** argv)
|
||||
return false;
|
||||
};
|
||||
CHECK(pump_until(3, character_draw));
|
||||
|
||||
// 7. (批次 2V3) Keyboard movement through the original path: the key reaches game.GameWindow's
|
||||
// onPressKeyDict, MoveUp calls player.SetSingleDIKKeyState(DIK_UP, True), CPythonPlayer turns
|
||||
// it into NEW_MoveToDirection on the main instance, and CPythonPlayerEventHandler::OnMove sends
|
||||
// CG_MOVE FUNC_MOVE. Releasing the key stops the actor (OnStop: FUNC_WAIT at the new position).
|
||||
TPixelPosition start = {};
|
||||
if (main_instance)
|
||||
main_instance->NEW_GetPixelPosition(&start);
|
||||
auto walked = [&] {
|
||||
TPixelPosition now = {};
|
||||
main_instance->NEW_GetPixelPosition(&now);
|
||||
return std::hypot(now.x - start.x, now.y - start.y);
|
||||
};
|
||||
PythonBoot::UIKey(DIK_UP, true);
|
||||
CHECK(main_instance && pump_until(3, [&] { return walked() > 100.0f; }));
|
||||
PythonBoot::UIKey(DIK_UP, false);
|
||||
CHECK(main_instance && pump_until(2, [&] { return !main_instance->IsWalking(); }));
|
||||
const float distance = main_instance ? walked() : 0.0f;
|
||||
std::printf("port_login_flow_test: DIK_UP walked %.0f cm\n", distance);
|
||||
if (server)
|
||||
{
|
||||
// The fake server records "game2:move <func> <arg> <rot> <x> <y>"; x, y are the TPixelPosition
|
||||
// (y negated back, as SendCharacterStatePacket does) plus the map base the server gave.
|
||||
auto moves = [&] {
|
||||
std::vector<std::string> out;
|
||||
for (const auto& e : server->Events())
|
||||
if (e.rfind("game2:move ", 0) == 0)
|
||||
out.push_back(e);
|
||||
return out;
|
||||
};
|
||||
CHECK(pump_until(2, [&] { auto m = moves(); return m.size() >= 2 && m.back().rfind("game2:move 0 ", 0) == 0; }));
|
||||
const auto m = 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)
|
||||
{
|
||||
@@ -394,7 +430,7 @@ int main(int argc, char** argv)
|
||||
if (e.rfind(prefix, 0) == 0)
|
||||
want.push_back(e);
|
||||
for (const auto& e : events)
|
||||
if (e.rfind(prefix, 0) == 0)
|
||||
if (e.rfind(prefix, 0) == 0 && e.find(":move ") == std::string::npos)
|
||||
got.push_back(e);
|
||||
CHECK(want == got);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user