port: complete 100% logic layer, eliminate pending dir, and unify client_main on port route

- Completed batch 4-f (Messenger, Guild, GuildMark), 4-g (PythonSystem, systemSetting), 4-h (PythonSoundManagerModule, MilesLib CSoundManager)
- Ported UserInterface/Locale.cpp, EterLocale/Japanese.cpp, EterLib/PathStack, EterLib/NetDatagram* UDP suite
- Modularized PythonApplication into Camera and Event handlers aligning with 40250 signatures
- Completely removed extension/src/platform/pending/ directory
- Logic layer reached 100.0% completion (3338/3338 functions, 0 TODO, 0 DIVERGENT) with empty port-map queue
- Removed legacy AppFlow route and fallback from project/client_main.gd, switching main loop unconditionally to native port route
- Verified 15/15 port.login_flow integration steps and 19/19 port_gate suites passing
This commit is contained in:
shen
2026-09-23 22:17:59 -07:00
parent ee7a9cd3e2
commit 837c830182
85 changed files with 10228 additions and 1545 deletions
+152 -5
View File
@@ -616,6 +616,69 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
elder_update.vid = kQuestVID;
if (!c.Send(elder) || !c.Send(elder_info) || !c.Send(elder_update))
return Fail(name + ": send quest NPC");
GCCharacterAdd storekeeper = keeper;
storekeeper.vid = kSafeboxVID;
storekeeper.x = kSafeboxX;
storekeeper.y = kSafeboxY;
storekeeper.race = kSafeboxRace;
GCCharAddInfo storekeeper_info = zeroed<GCCharAddInfo>();
storekeeper_info.header = HDR_GC_CHAR_ADDITIONAL_INFO;
storekeeper_info.vid = kSafeboxVID;
std::strncpy(storekeeper_info.name, "Storekeeper", sizeof(storekeeper_info.name) - 1);
GCCharacterUpdate storekeeper_update = keeper_update;
storekeeper_update.vid = kSafeboxVID;
if (!c.Send(storekeeper) || !c.Send(storekeeper_info) || !c.Send(storekeeper_update))
return Fail(name + ": send storekeeper NPC");
// Messenger (批次 4-f): Send initial friend list
struct
{
GCMessengerHead head;
MessengerListEntry entry;
char name[10];
} __attribute__((packed)) messenger_pkt = {
{HDR_GC_MESSENGER, static_cast<std::uint16_t>(sizeof(messenger_pkt)), MESSENGER_GC_LIST},
{1 /* MESSENGER_CONNECTED_STATE_ONLINE */, static_cast<std::uint8_t>(std::strlen(kFriendName))},
{}
};
std::memcpy(messenger_pkt.name, kFriendName, sizeof(messenger_pkt.name));
if (!c.Send(messenger_pkt))
return Fail(name + ": send messenger list");
// Guild (批次 4-f): Send guild name
struct
{
GCMessengerHead head;
GuildName16 item;
} __attribute__((packed)) guild_name_pkt = {
{HDR_GC_GUILD, static_cast<std::uint16_t>(sizeof(guild_name_pkt)), 16 /* GUILD_GC_NAME */},
{kGuildID, {}}
};
std::strncpy(guild_name_pkt.item.name, kGuildName, sizeof(guild_name_pkt.item.name));
if (!c.Send(guild_name_pkt))
return Fail(name + ": send guild name");
// Guild (批次 4-f): Send guild info (enables guild for player)
struct
{
GCMessengerHead head;
GuildInfo35 info;
} __attribute__((packed)) guild_info_pkt = {
{HDR_GC_GUILD, static_cast<std::uint16_t>(sizeof(guild_info_pkt)), 8 /* GUILD_GC_INFO */},
zeroed<GuildInfo35>()
};
guild_info_pkt.info.guild_id = kGuildID;
guild_info_pkt.info.master_pid = 1;
guild_info_pkt.info.level = 20;
guild_info_pkt.info.exp = 50000;
guild_info_pkt.info.member_count = 1;
guild_info_pkt.info.max_member_count = 50;
guild_info_pkt.info.gold = 123456;
guild_info_pkt.info.has_land = 1;
std::strncpy(guild_info_pkt.info.name, kGuildName, sizeof(guild_info_pkt.info.name) - 1);
if (!c.Send(guild_info_pkt))
return Fail(name + ": send guild info");
Event(name + ":burst_sent");
// A ping after the burst proves that GamePhase consumed every packet above and can reply.
@@ -633,6 +696,9 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
std::int32_t gold = kStartGold;
std::uint32_t bag0 = 0; // vnum in inventory cell 0 (the fake character starts with an empty bag)
int quest_page = 0; // the quest dialogue's last page sent (1 question, 2 next, 3 done)
bool safebox_open = false;
std::uint32_t safebox0 = kSafeboxItemVnum;
std::int32_t safebox_gold = kSafeboxMoney;
// CQuestManager::SendScript: packet_script {header, size, skin, src_size} and the script text.
auto send_script = [&](const std::string& script) {
struct
@@ -743,11 +809,44 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
const std::string text(reinterpret_cast<const char*>(packet + sizeof(head)),
strnlen(reinterpret_cast<const char*>(packet + sizeof(head)), text_len));
Event(name + ":chat " + std::to_string(head.type) + " " + text);
const std::string line = std::string(kCharacterName) + " : " + text;
GCChatHead chat = {HDR_GC_CHAT, static_cast<std::uint16_t>(sizeof(GCChatHead) + line.size() + 1),
0 /* CHAT_TYPE_TALKING */, kMainVID, 1};
if (!c.Send(chat) || !c.Send(line.c_str(), line.size() + 1))
return Fail(name + ": send GC_CHAT");
if (text.rfind("/safebox_password ", 0) == 0)
{
const std::string pass = text.substr(18);
Event(name + ":safebox_password " + pass);
safebox_open = true;
GCSafeboxSize size_pkt = {HDR_GC_SAFEBOX_SIZE, FakeLoginServer::kSafeboxSize};
GCSafeboxMoneyChange money_pkt = {HDR_GC_SAFEBOX_MONEY_CHANGE, safebox_gold};
if (!c.Send(size_pkt) || !c.Send(money_pkt))
return Fail(name + ": send GC_SAFEBOX_SIZE / MONEY");
if (safebox0 != 0)
{
GCItemSet set = zeroed<GCItemSet>();
set.header = HDR_GC_SAFEBOX_SET;
set.cell = {3 /* SAFEBOX */, 0};
set.vnum = safebox0;
set.count = 1;
if (!c.Send(set))
return Fail(name + ": send GC_SAFEBOX_SET");
}
}
else if (text == "/safebox_close")
{
Event(name + ":safebox_close");
safebox_open = false;
const std::string cmd = "CloseSafebox";
GCChatHead chat = {HDR_GC_CHAT, static_cast<std::uint16_t>(sizeof(GCChatHead) + cmd.size() + 1),
5 /* CHAT_TYPE_COMMAND */, 0, 1};
if (!c.Send(chat) || !c.Send(cmd.c_str(), cmd.size() + 1))
return Fail(name + ": send CloseSafebox");
}
else
{
const std::string line = std::string(kCharacterName) + " : " + text;
GCChatHead chat = {HDR_GC_CHAT, static_cast<std::uint16_t>(sizeof(GCChatHead) + line.size() + 1),
0 /* CHAT_TYPE_TALKING */, kMainVID, 1};
if (!c.Send(chat) || !c.Send(line.c_str(), line.size() + 1))
return Fail(name + ": send GC_CHAT");
}
}
// CInputMain::Target → CHARACTER::SetTarget → BroadcastTargetPacket: GC_TARGET with the monster's HP percent.
if (header == HDR_CG_TARGET)
@@ -786,6 +885,14 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
CGOnClick click;
std::memcpy(&click, packet, sizeof(click));
Event(name + ":click " + std::to_string(click.vid));
if (click.vid == kSafeboxVID)
{
const std::string cmd = "ShowMeSafeboxPassword";
GCChatHead chat = {HDR_GC_CHAT, static_cast<std::uint16_t>(sizeof(GCChatHead) + cmd.size() + 1),
5 /* CHAT_TYPE_COMMAND */, 0, 1};
if (!c.Send(chat) || !c.Send(cmd.c_str(), cmd.size() + 1))
return Fail(name + ": send ShowMeSafeboxPassword");
}
// CHARACTER::OnClick → CQuestManager::Click: say(...) then select("Yes", "No") → GotoSelectState.
if (click.vid == kQuestVID && !send_script("Greetings, young warrior.[ENTER]Shall I tell you a story?[ENTER][QUESTION 1;Yes|2;No]"))
return Fail(name + ": send GC_SCRIPT question");
@@ -881,6 +988,46 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
if (!ok)
return Fail(name + ": send shop reply");
}
if (header == HDR_CG_SAFEBOX_CHECKIN)
{
CGSafeboxCheckin in;
std::memcpy(&in, packet, sizeof(in));
Event(name + ":safebox_checkin " + std::to_string(in.item_pos.cell) + " -> " + std::to_string(in.safe_pos));
if (in.item_pos.cell == 0 && in.safe_pos == 0 && bag0 != 0)
{
safebox0 = bag0;
bag0 = 0;
GCItemDelDeprecated del = zeroed<GCItemDelDeprecated>();
del.header = HDR_GC_ITEM_DEL;
del.cell = {1, 0};
GCItemSet set = zeroed<GCItemSet>();
set.header = HDR_GC_SAFEBOX_SET;
set.cell = {3 /* SAFEBOX */, 0};
set.vnum = safebox0;
set.count = 1;
if (!c.Send(del) || !c.Send(set))
return Fail(name + ": send safebox checkin replies");
}
}
if (header == HDR_CG_SAFEBOX_CHECKOUT)
{
CGSafeboxCheckout out;
std::memcpy(&out, packet, sizeof(out));
Event(name + ":safebox_checkout " + std::to_string(out.safe_pos) + " -> " + std::to_string(out.item_pos.cell));
if (out.safe_pos == 0 && out.item_pos.cell == 0 && safebox0 != 0)
{
bag0 = safebox0;
safebox0 = 0;
GCItemDel del = {HDR_GC_SAFEBOX_DEL, 0};
GCItemSet set = zeroed<GCItemSet>();
set.header = HDR_GC_ITEM_SET;
set.cell = {1, 0};
set.vnum = bag0;
set.count = 1;
if (!c.Send(del) || !c.Send(set))
return Fail(name + ": send safebox checkout replies");
}
}
if (header == HDR_CG_MOVE)
{
CGMove move;