You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

351 lines
11 KiB

#pragma once
#include <SimpleIni.h>
static std::unordered_map<RE::TESForm*, std::uint32_t> supplyCount = {};
static bool bSuppliesFetched = false;
static constexpr RE::ITEM_REMOVE_REASON iRemoveReason = RE::ITEM_REMOVE_REASON::kStoreInContainer;
inline void LoadINI(std::map<std::string, bool>* settings, const char* iniPath)
{
for (auto it = settings->begin(); it != settings->end(); it++) {
logger::info("[DEFAULT] {} = {}", it->first, it->second);
}
if (!std::filesystem::exists(iniPath)) {
logger::warn("{} does not exist, using default values.", iniPath);
return;
}
try {
CSimpleIniA ini;
ini.SetUnicode(false);
ini.SetMultiKey(false);
ini.LoadFile(iniPath);
std::list<CSimpleIniA::Entry> keysList;
ini.GetAllKeys("", keysList);
bool bUpdateINI = false;
for (auto it = settings->begin(); it != settings->end(); it++) {
bool bExists = false;
for (const auto& k : keysList) {
if (it->first == k.pItem) {
settings->insert_or_assign(k.pItem, ini.GetBoolValue("", k.pItem, settings->at(k.pItem)));
logger::info("[INI] {} = {}", k.pItem, settings->at(k.pItem));
bExists = true;
break;
}
}
if (!bExists) {
ini.SetBoolValue("", it->first.c_str(), it->second);
bUpdateINI = true;
}
}
if (bUpdateINI) {
logger::info("New settings detected, adding to EnderalSEEasyCrafting.ini");
ini.SaveFile(iniPath);
}
} catch (const std::exception& e) {
logger::error("ERROR: {}", e.what());
}
}
inline const std::map<std::string, bool> GetSettings(bool bInit = false)
{
static std::map<std::string, bool> settings;
if (bInit) {
settings.insert({ "LoadSuppliesEverywhere", false });
settings.insert({ "StoreInventorySupplies", true });
settings.insert({ "ShowFetchingNotification", true });
settings.insert({ "ShowReturningNotification", false });
LoadINI(&settings, "Data/SKSE/Plugins/EnderalSEEasyCrafting.ini");
}
return settings;
}
inline RE::TESObjectREFR* GetChest()
{
return RE::TESForm::LookupByID<RE::TESObjectREFR>(0x5C132);
}
inline void NotifyFetching(const char* a_notification)
{
if (GetSettings().at("ShowFetchingNotification")) {
RE::DebugNotification(a_notification, nullptr, false);
}
}
inline void FetchSuppliesByType(RE::FormType formType)
{
const auto chest = GetChest();
const auto targetRef = RE::PlayerCharacter::GetSingleton();
const auto inv = chest->GetInventory([formType](RE::TESBoundObject& a_form) {
if (!a_form.Is(formType)) {
return false;
}
if (formType == RE::FormType::Misc && a_form.As<RE::BGSKeywordForm>()->GetNumKeywords() == 0) {
return false;
}
return true;
});
for (const auto& [item, data] : inv) {
const auto& [count, entry] = data;
if (count > 0) {
supplyCount.insert({ item, count });
chest->RemoveItem(item, count, iRemoveReason, nullptr, targetRef, 0, 0);
}
}
}
inline void FetchByWorkbench(RE::TESFurniture* workbench)
{
if (!workbench) {
logger::warn("Invalid workbench argument in FetchByWorkbench");
return;
}
std::unordered_set<RE::FormID> checkedItems;
const auto chest = GetChest();
const auto playerRef = RE::PlayerCharacter::GetSingleton();
const auto inv = chest->GetInventory();
const auto keywords = workbench->As<RE::BGSKeywordForm>();
for (const auto& recipe : RE::TESDataHandler::GetSingleton()->GetFormArray<RE::BGSConstructibleObject>()) {
if (recipe->benchKeyword && keywords->HasKeyword(recipe->benchKeyword) && recipe->requiredItems.numContainerObjects > 0) {
for (int i = 0; i < recipe->requiredItems.numContainerObjects; i++) {
const auto component = recipe->requiredItems.GetContainerObjectAt(i).value();
const auto formID = (component && component->obj) ? component->obj->formID : NULL;
if (formID && !checkedItems.contains(formID)) {
checkedItems.insert(formID);
const auto it = inv.find(component->obj);
if (it != inv.end()) {
supplyCount.insert({ it->first, it->second.first });
chest->RemoveItem(it->first, it->second.first, iRemoveReason, nullptr, playerRef, 0, 0);
}
}
}
}
}
}
inline void FetchSuppliesByForm(RE::TESForm* a_form)
{
if (!a_form) {
return;
}
const auto chest = GetChest();
const auto targetRef = RE::PlayerCharacter::GetSingleton();
const auto inv = chest->GetInventory([&](RE::TESBoundObject& a_xform) {
return a_form == &a_xform;
});
for (const auto& [item, data] : inv) {
const auto& [count, entry] = data;
if (count > 0) {
supplyCount.insert({ item, count });
chest->RemoveItem(item, count, iRemoveReason, nullptr, targetRef, 0, 0);
}
}
}
inline bool IsQuestItem(RE::TESForm* a_form, RE::TESQuest* questElixir, RE::TESQuest* questCuthbert)
{
if (questElixir) {
if (a_form->formID == 0x34D22 || a_form->formID == 0x727E0 || a_form->formID == 0x3AD6F || a_form->formID == 0x59B86) {
return true;
}
}
if (questCuthbert) {
if (a_form->formID == 0x891AF || a_form->formID == 0x34C5D || a_form->formID == 0x34D32) {
return true;
}
}
return false;
}
inline void ReturnSupplies()
{
if (!bSuppliesFetched) {
return;
}
if (GetSettings().at("ShowReturningNotification")) {
RE::DebugNotification("Returning supplies...");
}
const auto chest = GetChest();
const auto playerRef = RE::PlayerCharacter::GetSingleton();
auto questElixir = RE::TESDataHandler::GetSingleton()->LookupForm<RE::TESQuest>(0x1C82F, "Enderal - Forgotten Stories.esm"); // FS_NQ05
auto questCuthbert = RE::TESDataHandler::GetSingleton()->LookupForm<RE::TESQuest>(0x1CA09, "Enderal - Forgotten Stories.esm"); // FS_NQ07
if (questElixir->currentStage != 35) {
questElixir = nullptr;
}
if (questCuthbert->currentStage != 115) {
questCuthbert = nullptr;
}
for (auto& item : supplyCount) {
if (!IsQuestItem(item.first, questElixir, questCuthbert)) {
playerRef->RemoveItem(item.first->As<RE::TESBoundObject>(), item.second, iRemoveReason, nullptr, chest, 0, 0);
}
}
supplyCount.clear();
if (GetSettings().at("StoreInventorySupplies")) {
const auto kOreIngot = RE::TESForm::LookupByID(0x914EC)->As<RE::BGSKeyword>(); // VendorItemOreIngot
const auto kAnimalPart = RE::TESForm::LookupByID(0x914EB)->As<RE::BGSKeyword>(); // VendorItemAnimalPart
const auto kAnimalHide = RE::TESForm::LookupByID(0x914EA)->As<RE::BGSKeyword>(); // VendorItemAnimalHide
const auto kFirewood = RE::TESForm::LookupByID(0xBECD7)->As<RE::BGSKeyword>(); // VendorItemFireword
const auto kTool = RE::TESForm::LookupByID(0x914EE)->As<RE::BGSKeyword>(); // VendorItemTool
const auto kClutter = RE::TESForm::LookupByID(0x914E9)->As<RE::BGSKeyword>(); // VendorItemClutter
const auto kFoodRaw = RE::TESForm::LookupByID(0xA0E56)->As<RE::BGSKeyword>(); // VendorItemFoodRaw
std::unordered_set<RE::FormID> checkedItems;
std::unordered_set<RE::TESForm*> suppliesToStore;
for (const auto& recipe : RE::TESDataHandler::GetSingleton()->GetFormArray<RE::BGSConstructibleObject>()) {
if (recipe->requiredItems.numContainerObjects > 0) {
for (int i = 0; i < recipe->requiredItems.numContainerObjects; i++) {
const auto component = recipe->requiredItems.GetContainerObjectAt(i).value();
const auto formID = (component && component->obj) ? component->obj->formID : NULL;
if (formID && !checkedItems.contains(formID)) {
checkedItems.insert(formID);
const auto keywords = component->obj->As<RE::BGSKeywordForm>();
if (component->obj->Is(RE::FormType::Misc) && !component->obj->IsGold() && !component->obj->IsLockpick()) {
if (keywords->HasKeyword(kOreIngot) || keywords->HasKeyword(kAnimalPart) || keywords->HasKeyword(kAnimalHide) || keywords->HasKeyword(kFirewood) || keywords->HasKeyword(kTool) || keywords->HasKeyword(kClutter) || keywords->HasKeyword(kFoodRaw)) {
suppliesToStore.insert(component->obj);
}
} else if (component->obj->Is(RE::FormType::AlchemyItem) && keywords->HasKeyword(kFoodRaw)) {
suppliesToStore.insert(component->obj);
}
}
}
}
}
const auto inv = playerRef->GetInventory([&](RE::TESBoundObject& a_form) {
return a_form.Is(RE::FormType::Ingredient) || suppliesToStore.contains(&a_form);
});
for (const auto& [item, data] : inv) {
const auto& [count, entry] = data;
if (count > 0 && !entry->IsFavorited() && !entry->IsQuestObject() && !IsQuestItem(item, questElixir, questCuthbert)) {
playerRef->RemoveItem(item, count, iRemoveReason, nullptr, chest, 0, 0);
}
}
}
std::thread([]() {
std::this_thread::sleep_for(std::chrono::milliseconds(3));
SKSE::GetTaskInterface()->AddTask([]() {
const auto spell = RE::TESForm::LookupByID(0x824AF);
if (spell) {
RE::PlayerCharacter::GetSingleton()->RemoveSpell(spell->As<RE::SpellItem>());
spell->As<RE::SpellItem>()->effects[0]->effectItem.magnitude = 1;
}
});
}).detach();
bSuppliesFetched = false;
}
inline bool FetchSupplies(RE::TESFurniture* a_furn)
{
if (!a_furn || bSuppliesFetched) {
return false;
}
supplyCount.clear();
const auto chestAct = RE::TESForm::LookupByID(0x2EDD7);
const auto playerRef = RE::PlayerCharacter::GetSingleton();
bool bAllowFetch = false;
if (GetSettings().at("LoadSuppliesEverywhere") || playerRef->GetParentCell()->GetFactionOwner() || playerRef->GetParentCell()->GetActorOwner() || a_furn->As<RE::BGSKeywordForm>()->HasKeywordString("_00E_Phasmalist_CraftingSummoningWorkbench")) {
bAllowFetch = true;
} else {
RE::TES::GetSingleton()->ForEachReferenceInRange(playerRef, 4000, [&chestAct, &bAllowFetch](RE::TESObjectREFR& b_ref) {
if (chestAct == b_ref.GetBaseObject()) {
bAllowFetch = true;
return RE::BSContainer::ForEachResult::kStop;
}
return RE::BSContainer::ForEachResult::kContinue;
});
}
if (!bAllowFetch) {
return false;
}
bSuppliesFetched = true;
const auto spellForm = RE::TESForm::LookupByID(0x824AF);
if (spellForm) {
const auto spell = spellForm->As<RE::SpellItem>();
playerRef->RemoveSpell(spell);
spell->effects[0]->effectItem.magnitude = 30000;
playerRef->AddSpell(spell);
}
std::thread([a_furn]() {
std::this_thread::sleep_for(std::chrono::milliseconds(3));
SKSE::GetTaskInterface()->AddTask([a_furn]() {
NotifyFetching("Fetching supplies...");
switch (a_furn->workBenchData.benchType.underlying()) {
case (int)RE::TESFurniture::WorkBenchData::BenchType::kAlchemy:
case (int)RE::TESFurniture::WorkBenchData::BenchType::kAlchemyExperiment:
FetchSuppliesByType(RE::FormType::Ingredient);
break;
case (int)RE::TESFurniture::WorkBenchData::BenchType::kEnchanting:
case (int)RE::TESFurniture::WorkBenchData::BenchType::kEnchantingExperiment:
FetchSuppliesByType(RE::FormType::SoulGem);
break;
default:
FetchByWorkbench(a_furn);
}
});
}).detach();
return true;
}
inline bool GetSuppliesFetched()
{
return bSuppliesFetched;
}
inline void FetchSpectralizingSupplies()
{
if (GetSettings().at("ShowFetchingNotification")) {
RE::DebugNotification("Fetching supplies...");
}
bSuppliesFetched = true;
supplyCount.clear();
FetchSuppliesByType(RE::FormType::SoulGem);
FetchSuppliesByForm(RE::TESForm::LookupByID(0x34CDD)); // Bone Meal
FetchSuppliesByForm(RE::TESForm::LookupByID(0x3AD63)); // Ectoplasm
}