Added AddAllBooksToList
This commit is contained in:
parent
4d80f064b5
commit
58890efc21
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -23,6 +23,7 @@ configure_file(
|
||||
set(sources
|
||||
src/Main.cpp
|
||||
src/Papyrus.cpp
|
||||
src/BookCheck.cpp
|
||||
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.rc)
|
||||
|
||||
|
144
Source/ArtifactTrackerDLL/src/BookCheck.cpp
Normal file
144
Source/ArtifactTrackerDLL/src/BookCheck.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
#include "BookCheck.h"
|
||||
|
||||
// Mostly borrowed from Fix Note icon for SkyUI by 0xC0000005
|
||||
namespace BookCheck
|
||||
{
|
||||
std::vector<RE::TESObjectBOOK*> validBooks;
|
||||
|
||||
void PreloadBookList()
|
||||
{
|
||||
const auto dataHandler = RE::TESDataHandler::GetSingleton();
|
||||
|
||||
if (!dataHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
RE::BGSKeyword* recipeKeyword = dataHandler->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
|
||||
|
||||
for (const auto& form : dataHandler->GetFormArray<RE::TESObjectBOOK>()) {
|
||||
if (!form || form->TeachesSpell()) {
|
||||
continue;
|
||||
}
|
||||
if (form->HasKeyword(recipeKeyword) || IsBook(form)) {
|
||||
validBooks.push_back(form);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RE::TESObjectBOOK*> GetBookList()
|
||||
{
|
||||
return validBooks;
|
||||
}
|
||||
|
||||
const char* ExtractFileName(const char* const path, size_t& fileNameLength)
|
||||
{
|
||||
if (!path) {
|
||||
fileNameLength = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* ptr = path;
|
||||
const char* fileNameStart = NULL;
|
||||
|
||||
while (*ptr != 0) {
|
||||
char value = *ptr;
|
||||
|
||||
if (value == '\\' || value == '/') {
|
||||
fileNameStart = ptr + 1;
|
||||
}
|
||||
|
||||
ptr++;
|
||||
}
|
||||
|
||||
const char* result = fileNameStart ? fileNameStart : path;
|
||||
|
||||
fileNameLength = ptr - result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs a case-sensitive search for 'needle' in the specified string,
|
||||
// excluding any matches that are part of a larger word.
|
||||
bool ContainsWholeWord(const char* const haystack, const size_t haystackLength,
|
||||
const char* const needle, const size_t needleLength)
|
||||
{
|
||||
if (haystackLength >= needleLength) {
|
||||
for (size_t i = 0; i < haystackLength; i++) {
|
||||
if (haystack[i] != needle[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t j = i + 1;
|
||||
size_t k = 1;
|
||||
|
||||
while (j < haystackLength && k < needleLength && haystack[j] == needle[k]) {
|
||||
j++;
|
||||
k++;
|
||||
}
|
||||
|
||||
if (k == needleLength) {
|
||||
// Check that the match is not part of a larger word.
|
||||
|
||||
char next = haystack[j];
|
||||
|
||||
return !(next >= 'a' && next <= 'z');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StartsWith(const char* haystack, size_t haystackLength,
|
||||
const char* needle, size_t needleLength)
|
||||
{
|
||||
return haystackLength >= needleLength && memcmp(haystack, needle, needleLength) == 0;
|
||||
}
|
||||
|
||||
bool IsNote(const char* const modelFileName, size_t modelFileNameLength)
|
||||
{
|
||||
// Most items will use one of the note models from the base game, e.g. Note01.nif.
|
||||
if (ContainsWholeWord(modelFileName, modelFileNameLength, "Note", 4)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The Creation Club Fishing mod does not include 'Note' the inventory art model names for the
|
||||
// regional fishing maps, instead those items use the following format: FishMap<region>.
|
||||
if (StartsWith(modelFileName, modelFileNameLength, "FishMap", 7)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsJournal(const char* const modelFileName, size_t modelFileNameLength)
|
||||
{
|
||||
return ContainsWholeWord(modelFileName, modelFileNameLength, "Journal", 7);
|
||||
}
|
||||
|
||||
bool IsBook(RE::TESObjectBOOK* book)
|
||||
{
|
||||
if (!book || !book->inventoryModel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RE::BGSSoundDescriptorForm* pickupSound = book->As<RE::BGSPickupPutdownSounds>()->pickupSound;
|
||||
|
||||
if (pickupSound && pickupSound->formID == 0xC7A54) { // ITMNoteUp
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t modelFileNameLength;
|
||||
const char* const modelFileName = BookCheck::ExtractFileName(book->inventoryModel->GetModel(), modelFileNameLength);
|
||||
|
||||
if (modelFileNameLength == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsNote(modelFileName, modelFileNameLength) || IsJournal(modelFileName, modelFileNameLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
23
Source/ArtifactTrackerDLL/src/BookCheck.h
Normal file
23
Source/ArtifactTrackerDLL/src/BookCheck.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
// Mostly borrowed from Fix Note icon for SkyUI by 0xC0000005
|
||||
namespace BookCheck
|
||||
{
|
||||
void PreloadBookList();
|
||||
|
||||
std::vector<RE::TESObjectBOOK*> GetBookList();
|
||||
|
||||
const char* ExtractFileName(const char* const path, size_t& fileNameLength);
|
||||
|
||||
// Performs a case-sensitive search for 'needle' in the specified string,
|
||||
// excluding any matches that are part of a larger word.
|
||||
bool ContainsWholeWord(const char* const haystack, const size_t haystackLength, const char* const needle, const size_t needleLength);
|
||||
|
||||
bool StartsWith(const char* haystack, size_t haystackLength, const char* needle, size_t needleLength);
|
||||
|
||||
bool IsNote(const char* const modelFileName, size_t modelFileNameLength);
|
||||
|
||||
bool IsJournal(const char* const modelFileName, size_t modelFileNameLength);
|
||||
|
||||
bool IsBook(RE::TESObjectBOOK* book);
|
||||
}
|
@ -2,54 +2,54 @@
|
||||
|
||||
namespace Papyrus::ArtifactTracker
|
||||
{
|
||||
inline bool is_excluded(RE::TESForm* a_form, RE::TESForm* a_excludeForm = NULL)
|
||||
{
|
||||
if (!a_excludeForm) {
|
||||
return false;
|
||||
}
|
||||
inline bool is_excluded(RE::TESForm* a_form, RE::TESForm* a_excludeForm = NULL)
|
||||
{
|
||||
if (!a_excludeForm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RE::BGSListForm* list = a_excludeForm->As<RE::BGSListForm>();
|
||||
const RE::BGSListForm* list = a_excludeForm->As<RE::BGSListForm>();
|
||||
|
||||
if (list) {
|
||||
if (list->HasForm(a_form)) {
|
||||
return true;
|
||||
} else {
|
||||
bool isExcluded = false;
|
||||
if (list) {
|
||||
if (list->HasForm(a_form)) {
|
||||
return true;
|
||||
} else {
|
||||
bool isExcluded = false;
|
||||
|
||||
list->ForEachForm([&](RE::TESForm& a_exform) {
|
||||
const auto exlist = a_exform.As<RE::BGSListForm>();
|
||||
if (exlist) {
|
||||
if (exlist->forms.size() > 0) {
|
||||
if (exlist->forms[0]->Is(RE::FormType::Keyword) ? a_form->HasKeywordInList(exlist, false) : exlist->HasForm(a_form)) {
|
||||
isExcluded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto exkeyword = a_exform.As<RE::BGSKeyword>();
|
||||
if (exkeyword && a_form->As<RE::BGSKeywordForm>()->HasKeyword(exkeyword)) {
|
||||
list->ForEachForm([&](RE::TESForm& a_exform) {
|
||||
const auto exlist = a_exform.As<RE::BGSListForm>();
|
||||
if (exlist) {
|
||||
if (exlist->forms.size() > 0) {
|
||||
if (exlist->forms[0]->Is(RE::FormType::Keyword) ? a_form->HasKeywordInList(exlist, false) : exlist->HasForm(a_form)) {
|
||||
isExcluded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return isExcluded;
|
||||
}
|
||||
}
|
||||
|
||||
const RE::BGSKeyword* keyword = a_excludeForm->As<RE::BGSKeyword>();
|
||||
|
||||
if (keyword) {
|
||||
if (a_form->As<RE::BGSKeywordForm>()->HasKeyword(keyword)) {
|
||||
} else {
|
||||
const auto exkeyword = a_exform.As<RE::BGSKeyword>();
|
||||
if (exkeyword && a_form->As<RE::BGSKeywordForm>()->HasKeyword(exkeyword)) {
|
||||
isExcluded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
return isExcluded;
|
||||
}
|
||||
}
|
||||
|
||||
const RE::BGSKeyword* keyword = a_excludeForm->As<RE::BGSKeyword>();
|
||||
|
||||
if (keyword) {
|
||||
if (a_form->As<RE::BGSKeywordForm>()->HasKeyword(keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_artifact(RE::TESForm* a_form, RE::TESForm* a_excludeForm = NULL)
|
||||
{
|
||||
const auto formType = a_form->GetFormType();
|
||||
@ -124,13 +124,15 @@ namespace Papyrus::ArtifactTracker
|
||||
return 0;
|
||||
}
|
||||
|
||||
RE::BGSKeyword* recipeKeyword = RE::TESDataHandler::GetSingleton()->LookupForm<RE::BGSKeyword>(0xF5CB0, "Skyrim.esm"); // VendorItemRecipe
|
||||
|
||||
auto inv = containerRef->GetInventory([&](RE::TESBoundObject& a_exform) {
|
||||
return a_exform.GetPlayable()
|
||||
&& (
|
||||
a_exform.formType == RE::FormType::Armor
|
||||
|| (a_exform.formType == RE::FormType::Weapon && a_exform.formID != 0x000001F4)
|
||||
|| a_exform.formType == RE::FormType::Misc
|
||||
|| a_exform.formType == RE::FormType::Book
|
||||
|| (a_exform.formType == RE::FormType::Book && !a_exform.As<RE::TESObjectBOOK>()->TeachesSpell() && (a_exform.As<RE::BGSKeywordForm>()->HasKeyword(recipeKeyword) || BookCheck::IsBook(a_exform.As<RE::TESObjectBOOK>())))
|
||||
)
|
||||
&& (excludeOnlyMisc ? (a_exform.formType != RE::FormType::Misc || !is_excluded(&a_exform, a_excludeForm)) : !is_excluded(&a_exform, a_excludeForm));
|
||||
});
|
||||
@ -295,6 +297,30 @@ namespace Papyrus::ArtifactTracker
|
||||
cellItems.clear();
|
||||
}
|
||||
|
||||
inline std::int32_t AddAllBooksToList(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*,
|
||||
RE::BGSListForm* a_targetList,
|
||||
RE::TESForm* a_excludeForm = NULL)
|
||||
{
|
||||
if (!a_targetList) {
|
||||
a_vm->TraceStack("a_targetList in AddItemsOfTypeAndKeywordToList is None", a_stackID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a_excludeForm) {
|
||||
for (const auto& book : BookCheck::GetBookList()) {
|
||||
if (!is_excluded(book, a_excludeForm)) {
|
||||
a_targetList->AddForm(book);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto& book : BookCheck::GetBookList()) {
|
||||
a_targetList->AddForm(book);
|
||||
}
|
||||
}
|
||||
|
||||
return a_targetList->forms.size();
|
||||
}
|
||||
|
||||
inline void Bind(VM& a_vm)
|
||||
{
|
||||
BIND(AddAllFormsToList);
|
||||
@ -307,5 +333,7 @@ namespace Papyrus::ArtifactTracker
|
||||
logger::info("Registered HasRefInCell"sv);
|
||||
BIND(SyncCellStorage);
|
||||
logger::info("Registered SyncCellStorage"sv);
|
||||
BIND(AddAllBooksToList);
|
||||
logger::info("Registered AddAllBooksToList"sv);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "Papyrus.h"
|
||||
#include "BookCheck.h"
|
||||
|
||||
using namespace RE::BSScript;
|
||||
using namespace SKSE;
|
||||
@ -6,6 +7,7 @@ using namespace SKSE::log;
|
||||
using namespace SKSE::stl;
|
||||
|
||||
namespace {
|
||||
|
||||
void InitializeLogging() {
|
||||
auto path = logger::log_directory();
|
||||
if (!path) {
|
||||
@ -23,6 +25,18 @@ namespace {
|
||||
spdlog::set_default_logger(std::move(log));
|
||||
spdlog::set_pattern("[%l] %v"s);
|
||||
}
|
||||
|
||||
void InitializeMessaging() {
|
||||
if (!GetMessagingInterface()->RegisterListener([](MessagingInterface::Message* message) {
|
||||
|
||||
if (message->type == MessagingInterface::kDataLoaded) {
|
||||
BookCheck::PreloadBookList();
|
||||
}
|
||||
|
||||
})) {
|
||||
stl::report_and_fail("Unable to register message listener.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SKSEPluginLoad(const LoadInterface* skse) {
|
||||
@ -33,6 +47,7 @@ SKSEPluginLoad(const LoadInterface* skse) {
|
||||
log::info("{} {} is loading...", plugin->GetName(), version);
|
||||
|
||||
Init(skse);
|
||||
InitializeMessaging();
|
||||
SKSE::GetPapyrusInterface()->Register(Papyrus::Bind);
|
||||
|
||||
log::info("{} has finished loading.", plugin->GetName());
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "Papyrus.h"
|
||||
#include "BookCheck.h"
|
||||
|
||||
#include "Functions/ObjectReference.h"
|
||||
#include "Functions/ArtifactTracker.h"
|
||||
|
@ -2,6 +2,8 @@ Scriptname ETR_Functions Hidden
|
||||
|
||||
int function AddAllFormsToList(FormList targetList, int formType, Form excludeForm = None) native global
|
||||
|
||||
int function AddAllBooksToList(FormList targetList, Form excludeForm = None) native global
|
||||
|
||||
int function AddArtifactsToList(Form refOrList, FormList targetList, Form excludeForm = None, bool excludeOnlyMisc = false) native global
|
||||
|
||||
int function GetItemCountInList(FormList refList, Form baseForm) native global
|
||||
|
@ -38,11 +38,11 @@ event OnPlayerLoadGame()
|
||||
ahzmorehudie.RegisterIconFormList("dbmDisp", ETR_ItemsStored)
|
||||
endif
|
||||
|
||||
If SKSE.GetPluginVersion("QuickLootRE") >= 292
|
||||
QuickLootRE.RegisterNewItemsList(ETR_ItemsNew)
|
||||
QuickLootRE.RegisterDisplayedItemsList(ETR_ItemsStored)
|
||||
QuickLootRE.RegisterFoundItemsList(ETR_ItemsFound)
|
||||
endif
|
||||
;If SKSE.GetPluginVersion("QuickLootRE") >= 292
|
||||
; QuickLootRE.RegisterNewItemsList(ETR_ItemsNew)
|
||||
; QuickLootRE.RegisterDisplayedItemsList(ETR_ItemsStored)
|
||||
; QuickLootRE.RegisterFoundItemsList(ETR_ItemsFound)
|
||||
;endif
|
||||
|
||||
; Rebuild all lists to avoid discrepancies, stale data, and broken records
|
||||
|
||||
@ -61,9 +61,9 @@ event OnPlayerLoadGame()
|
||||
|
||||
ETR_ItemsNew.Revert()
|
||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 26, ETR_FoundAndStored)
|
||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 27, ETR_FoundAndStored)
|
||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 32, ETR_ExcludeFromNew)
|
||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 41, ETR_FoundAndStored)
|
||||
ETR_Functions.AddAllFormsToList(ETR_ItemsNew, 32, ETR_ExcludeFromNew)
|
||||
ETR_Functions.AddAllBooksToList(ETR_ItemsNew, ETR_FoundAndStored)
|
||||
|
||||
endevent
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user