Implement 40250 classic client port

This commit is contained in:
shenlei
2026-09-01 18:49:28 +09:00
parent 2277b1dd60
commit 8f61990a83
481 changed files with 169826 additions and 293 deletions
+127
View File
@@ -825,6 +825,20 @@ int main() {
if (f.name == "Bob") bob_now = f.online;
}
CHECK(bob_now, "messenger: LOGIN flips Bob online");
const char *invite = "messenger_auth Alice";
std::vector<uint8_t> invite_buf(sizeof(GCChat) + std::strlen(invite) + 1);
GCChat invite_head{};
invite_head.header = GC_CHAT;
invite_head.length = (uint16_t)invite_buf.size();
invite_head.type = CHAT_TYPE_COMMAND;
std::memcpy(invite_buf.data(), &invite_head, sizeof(invite_head));
std::memcpy(invite_buf.data() + sizeof(invite_head), invite, std::strlen(invite) + 1);
es.apply(GC_CHAT, invite_buf.data(), (uint16_t)invite_buf.size());
auto invites = es.drain_friend_invites();
CHECK(invites.size() == 1 && invites[0] == "Alice",
"messenger: invitation command surfaced");
CHECK(es.drain_friend_invites().empty(), "messenger: invitation queue drained");
}
// --- P8 NPC shop (GC_SHOP START / END + error) ---
@@ -1414,6 +1428,119 @@ int main() {
"cube: COMMAND lines never enter the chat log");
}
// --- 40250 command-bus callbacks outside cube -------------------------
{
auto cmd = [&](const std::string &text) {
std::vector<uint8_t> buf(sizeof(GCChat) + text.size() + 1);
GCChat h{};
h.header = GC_CHAT;
h.length = (uint16_t)buf.size();
h.type = CHAT_TYPE_COMMAND;
std::memcpy(buf.data(), &h, sizeof(h));
std::memcpy(buf.data() + sizeof(h), text.c_str(), text.size() + 1);
es.apply(GC_CHAT, buf.data(), (uint16_t)buf.size());
};
cmd("PartyRequestDenied");
cmd("ShowMeSafeboxPassword");
cmd("ShowMeMallPassword");
cmd("RefineSuceeded");
cmd("RefineFailed");
cmd("OpenPrivateShop");
cmd("MyShopPriceList 11209 250000");
cmd("setblockmode 37");
cmd("ObserverMode 1");
cmd("ObserverCount 3");
cmd("StoneDetect 9001 2 90");
cmd("StartStaminaConsume 10 77");
cmd("StopStaminaConsume 55");
cmd("sms");
cmd("combo 1");
cmd("mobile_auth");
cmd("gift");
cmd("kiss 1 2");
cmd("slap 1 2");
cmd("dance6 1");
cmd("joy 2");
auto events = es.drain_server_commands();
int denied = 0, safe = 0, mall = 0, ok = 0, fail = 0, open = 0, price = 0, block = 0;
int observer = 0, observer_count = 0, stone = 0, stamina_start = 0, stamina_stop = 0;
int mobile = 0, mobile_auth = 0, combo = 0, gift = 0;
for (const auto &ev : events) {
switch (ev.kind) {
case ServerCommandEvent::PartyRequestDenied: ++denied; break;
case ServerCommandEvent::SafeboxPasswordRequired: ++safe; break;
case ServerCommandEvent::SafeboxWrongPassword: break;
case ServerCommandEvent::MallPasswordRequired: ++mall; break;
case ServerCommandEvent::RefineSucceeded: ++ok; break;
case ServerCommandEvent::RefineFailed: ++fail; break;
case ServerCommandEvent::PrivateShopOpenRequested: ++open; break;
case ServerCommandEvent::MyShopPrice:
price += ev.value == 11209 && ev.value2 == 250000;
break;
case ServerCommandEvent::BlockModeChanged:
block += ev.value == 37;
break;
case ServerCommandEvent::ObserverModeChanged:
observer += ev.value == 1 && es.observer_mode();
break;
case ServerCommandEvent::ObserverCountChanged:
observer_count += ev.value == 3 && es.observer_count() == 3;
break;
case ServerCommandEvent::StoneDetected:
stone += ev.value == 9001 && ev.value2 == 2 && ev.value3 == 90.0f;
break;
case ServerCommandEvent::StaminaStarted:
stamina_start += ev.value == 10 && ev.value2 == 77;
break;
case ServerCommandEvent::StaminaStopped:
stamina_stop += ev.value == 0 && ev.value2 == 55;
break;
case ServerCommandEvent::MobileFlagChanged:
mobile += ev.value == 1 && es.mobile_flag();
break;
case ServerCommandEvent::MobileAuthRequired:
++mobile_auth;
break;
case ServerCommandEvent::ComboChanged:
combo += ev.value == 1 && es.combo_skill_flag();
break;
case ServerCommandEvent::GiftAvailable:
++gift;
break;
}
}
CHECK(denied == 1 && safe == 1 && mall == 1 && ok == 1 && fail == 1 && open == 1
&& price == 1 && block == 1 && observer == 1 && observer_count == 1
&& stone == 1 && stamina_start == 1 && stamina_stop == 1 && mobile == 1
&& mobile_auth == 1 && combo == 1 && gift == 1 && !es.stamina_consuming()
&& es.current_stamina() == 55,
"40250 command bus: callbacks surfaced");
auto motions = es.drain_motions();
CHECK(motions.size() == 6 && motions[0].vid == 1 && motions[0].victim_vid == 2
&& motions[0].motion == 308 && motions[1].vid == 2 && motions[1].motion == 308
&& motions[2].motion == 320 && motions[3].motion == 316
&& motions[4].motion == 330 && motions[5].motion == 349,
"40250 command bus: emotions feed the GC_MOTION stream");
es.set_now(1000);
cmd("StartStaminaConsume 10 77");
es.drain_server_commands();
es.set_now(1500);
es.tick();
CHECK(es.stamina_consuming() && es.current_stamina() == 72
&& es.points().stamina() == 72 && es.take_points_dirty(),
"40250 command bus: stamina drains at server-command rate");
es.mut_safebox_wrong_password();
bool wrong_password = false;
for (const auto &ev : es.drain_server_commands()) {
wrong_password = wrong_password || ev.kind == ServerCommandEvent::SafeboxWrongPassword;
}
CHECK(wrong_password, "40250 command bus: wrong safebox password surfaced");
cmd("CloseSafebox");
cmd("CloseMall");
CHECK(!es.safebox_open() && !es.mall_open(),
"40250 command bus: storage close callbacks update state");
}
// --- despawn ---
// --- guild land list + minimap observers ---
{