// Failed quests can be restarted (request of 2026-10-17): a failed quest is not // final, the quest giver offers it again. // // Measured (docs/quests.md): a failed quest keeps flags 3 (triggered + done) in // the quest table and its quest book entry gets state 101; Quest_Trigger // (0x46de10) does nothing for a quest with flag 1, so the giver never offers it // again. The game itself repeats only the generic quests (ids 2600..3599, // Quest_End copies them under a new id). // // The mod: after Quest_End failed a quest (its failure script has run), the // quest's flags are cleared and its book entries removed, as if it had never // been triggered. Quests that failed before the mod are reset the same way once // after loading. #include #include #include "game/engine/events.h" #include "game/engine/quests.h" #include "re/hook.h" #include "re/log.h" namespace mods::questfail { namespace { constexpr uint32_t kResetFlags = game::kQuestTriggered | game::kQuestDone | game::kQuestInProgress | game::kQuestBusy; bool g_resetAfterLoad = false; // The quest at table `index` as if it had never been triggered. void Restart(int index) { uint8_t* quest = game::Quest(index); const int32_t id = game::QuestId(quest); re::Log("questfail: restarting quest %d '%s' (flags %x)", id, game::QuestName(quest), game::QuestFlags(quest)); game::QuestFlags(quest) &= ~kResetFlags; for (size_t i = game::BookCount(); i-- > 0;) { if (game::BookQuestId(game::BookEntry(i)) == id) { game::EraseBookEntry(i); } } } void RestartFailedInBook() { for (size_t i = game::BookCount(); i-- > 0;) { const uint8_t* entry = game::BookEntry(i); if (game::BookState(entry) != game::kBookFailed) { continue; } const int index = game::QuestIndex(game::BookQuestId(entry)); if (index >= 0) { Restart(index); } i = std::min(i, game::BookCount()); } } void OnTick(uint8_t*) { if (g_resetAfterLoad) { g_resetAfterLoad = false; RestartFailedInBook(); } } void OnLoaded() { g_resetAfterLoad = true; } } // namespace // Quest_End (0x46ced0, quest manager thiscall (index, ?, success), ret 0xc): // LoseQuest passes success 0, ExitQuest 1. void __fastcall Quest_End(uint8_t* self, void* edx, int index, int a2, char success); RE_HOOK(0x0046ced0, Quest_End); void __fastcall Quest_End(uint8_t* self, void* edx, int index, int a2, char success) { Quest_End_original(self, edx, index, a2, success); if (!success && index >= 0 && static_cast(index) < game::QuestCount()) { Restart(index); } } namespace { struct Register { Register() { game::OnEngineTick(&OnTick); game::OnSaveGameLoaded(&OnLoaded); } } g_register; } // namespace } // namespace mods::questfail