Skip to content

Commit ddcb6ba

Browse files
committed
Add C_QuestLog.GetNumQuestObjectives(questID)
The quest's objective count — the length GetQuestDetails(questID) .requirements would have — read straight from the cache block with no Lua tables or strings materialized. nil when the quest isn't cached (same contract as GetQuestDetails). Exists because materializing full detail tables in a login-path loop is expensive at warm cache: GetQuestDetails copies description / objectives / completion text into Lua strings (~5 KB per quest). TurtleGuide's guide-load OIDX validation did exactly that for every tagged step — fine on the first (cold-cache) session, but once the quest cache was warm from questcache.wdb every subsequent login exhausted vanilla's fixed Lua memory pool and crashed the client in lmemPool.cpp:53. With this accessor the same validation costs a few dozen byte reads regardless of cache temperature.
1 parent 17cf323 commit ddcb6ba

3 files changed

Lines changed: 61 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
4848
| [MerchantFrame](docs/API.md#merchantframe) | `C_MerchantFrame.GetBuybackItemID`, `C_MerchantFrame.GetItemInfo`, `C_MerchantFrame.GetNumJunkItems`, `C_MerchantFrame.IsMerchantItemRefundable`, `C_MerchantFrame.IsSellAllJunkEnabled`, `C_MerchantFrame.SellAllJunkItems` |
4949
| [NamePlate](docs/API.md#nameplate) | `C_NamePlate.GetNamePlateForGUID`, `C_NamePlate.GetNamePlateForUnit`, `C_NamePlate.GetNamePlateGUIDs`, `C_NamePlate.GetNamePlates` |
5050
| [NameCache](docs/API.md#namecache) | `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.IsScanEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `C_PlayerCache.SetScanEnabled`, `C_PlayerInfo.GUIDIsCreature`, `C_PlayerInfo.GUIDIsGameObject`, `C_PlayerInfo.GUIDIsPet`, `C_PlayerInfo.GUIDIsPlayer`, `GetPlayerInfoByGUID`, `UnitNameFromGUID` |
51-
| [Quest](docs/API.md#quest) | `C_QuestLog.GetQuestDetails`, `C_QuestLog.GetQuestIDForLogIndex`, `C_QuestLog.GetTitleForQuestID`, `C_QuestLog.IsOnQuest`, `C_QuestLog.IsQuestDataCachedByID`, `C_QuestLog.IsUnitOnQuest`, `C_QuestLog.RequestLoadQuestByID`, `GetQuestLogLeaderBoardID` |
51+
| [Quest](docs/API.md#quest) | `C_QuestLog.GetNumQuestObjectives`, `C_QuestLog.GetQuestDetails`, `C_QuestLog.GetQuestIDForLogIndex`, `C_QuestLog.GetTitleForQuestID`, `C_QuestLog.IsOnQuest`, `C_QuestLog.IsQuestDataCachedByID`, `C_QuestLog.IsUnitOnQuest`, `C_QuestLog.RequestLoadQuestByID`, `GetQuestLogLeaderBoardID` |
5252
| [Screen](docs/API.md#screen) | `GetPhysicalScreenSize` |
5353
| [Spell](docs/API.md#spell) | `C_Spell.CancelSpellByID`, `C_Spell.CastAtCursor`, `C_Spell.CastAtUnit`, `C_Spell.CastingInfo`, `C_Spell.ChannelInfo`, `C_Spell.DoesSpellExist`, `C_Spell.GetSchoolString`, `C_Spell.GetSpellCooldown`, `C_Spell.GetSpellDescription`, `C_Spell.GetSpellInfo`, `C_Spell.GetSpellLink`, `C_Spell.GetSpellMechanicByID`, `C_Spell.GetSpellName`, `C_Spell.GetSpellPowerCost`, `C_Spell.GetSpellRadius`, `C_Spell.GetSpellReagents`, `C_Spell.GetSpellSubtext`, `C_Spell.GetSpellTexture`, `C_Spell.IsAutoAttackSpell`, `C_Spell.IsCurrentSpell`, `C_Spell.IsRangedAutoAttackSpell`, `C_Spell.IsSelfBuff`, `C_Spell.IsSpellHarmful`, `C_Spell.IsSpellHelpful`, `C_Spell.IsSpellPassive`, `C_Spell.IsSpellUsable`, `C_Spell.SpellHasRange`, `C_Spell.UnitCastingInfo`, `C_Spell.UnitChannelInfo`, `CancelSpellByName`, `CastSpellNoToggle`, `GetCraftSpellID`, `GetSpellInfo`, `GetSpellLink`, `GetSpellRadius`, `GetSpellSchool`, `IsHarmfulSpell`, `IsHelpfulSpell`, `IsPassiveSpell`, `IsPlayerSpell`, `IsSpellKnown`, `IsUsableSpell`, `SpellHasRange` |
5454
| [SpellBook](docs/API.md#spellbook) | `C_SpellBook.GetCurrentLevelSpells`, `C_SpellBook.GetSpellLevelLearned`, `C_SpellBook.GetSpellSkillLine`, `C_SpellBook.IsAutoAttackSpellBookItem`, `C_SpellBook.IsRangedAutoAttackSpellBookItem`, `FindSpellBookSlotByID` |

docs/API.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ build instructions.
291291
- [`C_QuestLog.IsUnitOnQuest(unit, questID)`](#c_questlogisunitonquestunit-questid)
292292
- [`C_QuestLog.GetTitleForQuestID(questID)`](#c_questloggettitleforquestidquestid)
293293
- [`C_QuestLog.GetQuestDetails(questID)`](#c_questloggetquestdetailsquestid)
294+
- [`C_QuestLog.GetNumQuestObjectives(questID)`](#c_questloggetnumquestobjectivesquestid)
294295
- [`C_QuestLog.IsQuestDataCachedByID(questID)`](#c_questlogisquestdatacachedbyidquestid)
295296
- [`GetQuestLogLeaderBoardID(objectiveIndex [, questIndex])`](#getquestlogleaderboardidobjectiveindex--questindex)
296297

@@ -7135,6 +7136,26 @@ Reagents for Reclaimers Inc. level 40 (no tag)
71357136
with `d.choiceItems = {{id=6793, count=1}, {id=6794, count=1}}`,
71367137
`d.rewardMoney = 3500`, `d.rewardMoneyAtMaxLevel = 1920`, etc.
71377138

7139+
### `C_QuestLog.GetNumQuestObjectives(questID)`
7140+
7141+
The quest's objective count — exactly the length
7142+
`GetQuestDetails(questID).requirements` would have — read straight from
7143+
the quest cache with **no Lua tables or strings materialized**. Returns
7144+
`nil` when the quest isn't cached (same contract as `GetQuestDetails`).
7145+
7146+
```lua
7147+
local n = C_QuestLog.GetNumQuestObjectives(1467) -- 1
7148+
```
7149+
7150+
Use this instead of `GetQuestDetails` when only the count matters —
7151+
e.g. bounds-checking authored objective indices across every step of a
7152+
guide at load time. `GetQuestDetails` copies the quest's description /
7153+
objectives / completion text into Lua strings and builds ~20 table
7154+
fields per call; harmless one at a time, but a login-path loop over
7155+
hundreds of cached quests can put real pressure on vanilla's
7156+
fixed-size Lua memory pool. This accessor costs a few dozen byte reads
7157+
regardless of cache temperature.
7158+
71387159
### `C_QuestLog.IsQuestDataCachedByID(questID)`
71397160

71407161
Returns `true` if the quest's static data is currently in the

src/quest/Details.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,43 @@ void PushRequirements(void *L, const uint8_t *data) {
134134
}
135135
}
136136

137+
// `C_QuestLog.GetNumQuestObjectives(questID)` — the quest's objective
138+
// count (the length `GetQuestDetails(questID).requirements` would have),
139+
// read straight from the cache block with zero Lua table/string
140+
// materialization. Exists so per-step validation loops (guide addons
141+
// bounds-checking objective indices at load time) stay O(1)-cheap even
142+
// with a warm quest cache — materializing full detail tables for hundreds
143+
// of quests inside a login-path loop can exhaust vanilla's fixed Lua
144+
// memory pool. Returns nil when the quest isn't cached (same contract as
145+
// GetQuestDetails).
146+
int __fastcall Script_GetNumQuestObjectives(void *L) {
147+
if (!Game::Lua::IsNumber(L, 1)) {
148+
Game::Lua::Error(L,
149+
"Usage: C_QuestLog.GetNumQuestObjectives(questID)");
150+
return 0;
151+
}
152+
const int questID = static_cast<int>(Game::Lua::ToNumber(L, 1));
153+
if (questID <= 0)
154+
return 0;
155+
const uint8_t *data = Quest::Cache::Peek(static_cast<uint32_t>(questID));
156+
if (data == nullptr)
157+
return 0;
158+
159+
int count = 0;
160+
auto *npcOrGo = reinterpret_cast<const int32_t *>(
161+
data + Quest::Cache::OFF_REQUIRED_NPC_OR_GO);
162+
auto *items = reinterpret_cast<const uint32_t *>(
163+
data + Quest::Cache::OFF_REQUIRED_ITEM);
164+
for (int i = 0; i < Quest::Cache::OBJECTIVE_COUNT; ++i) {
165+
if (npcOrGo[i] != 0)
166+
++count;
167+
if (items[i] != 0)
168+
++count;
169+
}
170+
Game::Lua::PushNumber(L, static_cast<double>(count));
171+
return 1;
172+
}
173+
137174
int __fastcall Script_GetQuestDetails(void *L) {
138175
if (!Game::Lua::IsNumber(L, 1)) {
139176
Game::Lua::Error(L, "Usage: C_QuestLog.GetQuestDetails(questID)");
@@ -245,6 +282,8 @@ int __fastcall Script_GetQuestDetails(void *L) {
245282
void RegisterLuaFunctions() {
246283
Game::Lua::RegisterTableFunction("C_QuestLog", "GetQuestDetails",
247284
&Script_GetQuestDetails);
285+
Game::Lua::RegisterTableFunction("C_QuestLog", "GetNumQuestObjectives",
286+
&Script_GetNumQuestObjectives);
248287
}
249288

250289
} // namespace

0 commit comments

Comments
 (0)