WIP port 4-d quest dialogue modules

This commit is contained in:
shenlei
2026-09-24 09:53:02 +09:00
parent a12e7fea42
commit ee7a9cd3e2
22 changed files with 2713 additions and 205 deletions
@@ -603,6 +603,19 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
keeper_update.attack_speed = 100;
if (!c.Send(keeper) || !c.Send(keeper_info) || !c.Send(keeper_update))
return Fail(name + ": send shopkeeper");
GCCharacterAdd elder = keeper;
elder.vid = kQuestVID;
elder.x = kQuestX;
elder.y = kQuestY;
elder.race = kQuestRace;
GCCharAddInfo elder_info = zeroed<GCCharAddInfo>();
elder_info.header = HDR_GC_CHAR_ADDITIONAL_INFO;
elder_info.vid = kQuestVID;
std::strncpy(elder_info.name, "Oldster", sizeof(elder_info.name) - 1);
GCCharacterUpdate elder_update = keeper_update;
elder_update.vid = kQuestVID;
if (!c.Send(elder) || !c.Send(elder_info) || !c.Send(elder_update))
return Fail(name + ": send quest NPC");
Event(name + ":burst_sent");
// A ping after the burst proves that GamePhase consumed every packet above and can reply.
@@ -619,6 +632,20 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
bool shopping = false;
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)
// CQuestManager::SendScript: packet_script {header, size, skin, src_size} and the script text.
auto send_script = [&](const std::string& script) {
struct
{
DynHead head;
std::uint8_t skin;
std::uint16_t src_size;
} __attribute__((packed)) head = {{HDR_GC_SCRIPT, static_cast<std::uint16_t>(sizeof(head) + script.size())}, 1 /* QUEST_SKIN_NORMAL */,
static_cast<std::uint16_t>(script.size())};
std::string bytes(reinterpret_cast<const char*>(&head), sizeof(head));
bytes += script;
return c.Send(bytes.data(), bytes.size());
};
for (;;)
{
std::uint8_t packet[1024] = {};
@@ -759,6 +786,11 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
CGOnClick click;
std::memcpy(&click, packet, sizeof(click));
Event(name + ":click " + std::to_string(click.vid));
// 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");
if (click.vid == kQuestVID)
quest_page = 1;
if (click.vid == kShopVID && !shopping)
{
shopping = true;
@@ -780,6 +812,25 @@ bool FakeLoginServer::ServeGame(Connection& c, int index)
return Fail(name + ": send GC_SHOP START");
}
}
// CInputMain::ScriptAnswer: an answer above 250 is the [NEXT] button (CQuestManager::Resume), the rest pick
// a [QUESTION] choice (CQuestManager::Select, 0-based).
if (header == HDR_CG_SCRIPT_ANSWER)
{
const std::uint8_t answer = packet[1];
Event(name + ":answer " + std::to_string(answer));
if (quest_page == 1 && answer == 0)
{
quest_page = 2;
if (!send_script("Long ago a dragon slept under this village.[ENTER][NEXT]"))
return Fail(name + ": send GC_SCRIPT next");
}
else if (quest_page == 2 && answer > 250)
{
quest_page = 3;
if (!send_script("It sleeps there still.[ENTER][DONE]"))
return Fail(name + ": send GC_SCRIPT done");
}
}
// CInputMain::Shop: END → CShop::RemoveGuest (GC_SHOP END); BUY → CShop::Buy (PointChange(POINT_GOLD), then
// CItem::AddToCharacter's GC_ITEM_SET into the first empty cell); SELL2 → CShopManager::Sell (the cell empties
// with the 42-byte header-20 packet, then PointChange(POINT_GOLD)).
+4
View File
@@ -37,6 +37,10 @@ public:
static constexpr std::int32_t kShopX = kMobX + 400, kShopY = kMobY;
static constexpr std::uint32_t kShopItemVnum = 27001, kShopItemPrice = 50;
static constexpr std::int32_t kStartGold = 10000, kShopSellPrice = 9;
// An old man (npclist 20009) 3 m east of the store whose click runs a quest dialogue the way CQuestManager
// sends it: say + select ([QUESTION]), then a [NEXT] page, then a [DONE] page.
static constexpr std::uint32_t kQuestVID = 0x00045678, kQuestRace = 20009;
static constexpr std::int32_t kQuestX = kShopX + 300, kQuestY = kShopY;
~FakeLoginServer();