#pragma once // Quests: the quest table (one entry per quest, from the scripts) and the quest // manager's quest book (the hero's accepted quests). docs/quests.md. #include #include #include namespace game { // Quest table g_quests: vector begin/end (0xaa9e98 / 0xaa9e9c), 0x124 bytes per quest. constexpr uintptr_t kQuestsBegin = 0x00aa9e98; constexpr uintptr_t kQuestsEnd = 0x00aa9e9c; constexpr size_t kQuestSize = 0x124; constexpr size_t kQuestId = 0x00; constexpr size_t kQuestName = 0x04; // internal name, ANSI constexpr size_t kQuestTriggerScript = 0x10c; // run by Quest_Trigger 0x46de10 constexpr size_t kQuestSuccessScript = 0x118; // run by Quest_End on success constexpr size_t kQuestFailureScript = 0x11c; // run by Quest_End on failure constexpr size_t kQuestFlags = 0x120; // Quest flags (+0x120); QuestDone tests 2, QuestInProgress 4. constexpr uint32_t kQuestTriggered = 0x1; constexpr uint32_t kQuestDone = 0x2; constexpr uint32_t kQuestInProgress = 0x4; constexpr uint32_t kQuestBusy = 0x8; // while Quest_Trigger runs the scripts inline uint8_t* QuestsBegin() { return *reinterpret_cast(kQuestsBegin); } inline uint8_t* QuestsEnd() { return *reinterpret_cast(kQuestsEnd); } inline size_t QuestCount() { uint8_t* begin = QuestsBegin(); return begin && QuestsEnd() > begin ? (QuestsEnd() - begin) / kQuestSize : 0; } inline uint8_t* Quest(int index) { return QuestsBegin() + index * kQuestSize; } inline int32_t& QuestId(uint8_t* quest) { return *reinterpret_cast(quest + kQuestId); } inline uint32_t& QuestFlags(uint8_t* quest) { return *reinterpret_cast(quest + kQuestFlags); } inline const char* QuestName(const uint8_t* quest) { return reinterpret_cast(quest + kQuestName); } // Table index of quest `id`, -1 if unknown. inline int QuestIndex(int32_t id) { for (size_t i = 0, n = QuestCount(); i < n; ++i) { if (QuestId(Quest(static_cast(i))) == id) { return static_cast(i); } } return -1; } // The quest manager, a static object (script interpreter; ecx of 0x475970 and // of Quest_End 0x46ced0). Its quest book is a vector of plain 0x174-byte entries // (serialized field by field, Quest_End 0x46d9d7..); per player a selected // entry index at +0x3a8 + player * 8 (Quest_End sets it to the count = none). inline uint8_t* QuestManager() { return reinterpret_cast(0x00aaaf00); } constexpr size_t kBookBegin = 0x424; constexpr size_t kBookEnd = 0x428; constexpr size_t kBookEntrySize = 0x174; constexpr size_t kBookState = 0x04; constexpr size_t kBookQuestId = 0x08; constexpr size_t kBookSelected = 0x3a8; constexpr size_t kBookSelectedStride = 8; constexpr int kBookPlayers = 16; // +0x3a4.. 16 pairs up to the book at +0x424 // Book entry states (+4); Quest_End writes 100 / 101. constexpr int32_t kBookActive = 2; constexpr int32_t kBookSolved = 100; constexpr int32_t kBookFailed = 101; inline uint8_t*& BookBegin() { return *reinterpret_cast(QuestManager() + kBookBegin); } inline uint8_t*& BookEnd() { return *reinterpret_cast(QuestManager() + kBookEnd); } inline size_t BookCount() { return BookBegin() && BookEnd() > BookBegin() ? (BookEnd() - BookBegin()) / kBookEntrySize : 0; } inline uint8_t* BookEntry(size_t index) { return BookBegin() + index * kBookEntrySize; } inline int32_t BookState(const uint8_t* entry) { return *reinterpret_cast(entry + kBookState); } inline int32_t BookQuestId(const uint8_t* entry) { return *reinterpret_cast(entry + kBookQuestId); } // Removes book entry `index`; the players' selected indices are kept on their // entries (or "none" = the new count). inline void EraseBookEntry(size_t index) { const size_t count = BookCount(); if (index >= count) { return; } std::memmove(BookEntry(index), BookEntry(index + 1), (count - index - 1) * kBookEntrySize); BookEnd() -= kBookEntrySize; for (int player = 0; player < kBookPlayers; ++player) { auto& selected = *reinterpret_cast(QuestManager() + kBookSelected + player * kBookSelectedStride); if (selected == index || selected >= count) { selected = static_cast(count - 1); } else if (selected > index) { --selected; } } } } // namespace game