Refactor Merchandise and shelves

Shelves are now saved in a special section in the InteriorRefList and must be loaded dynamically when the shop is loaded.

Merchandise for the shop is loaded once and saved in the private merchandise chest for the shop. Optimize updating merchandise to refresh the shelves from the response of the update instead of refetching the merchandise.

A single shelf can be loaded and paged indpedent from other shelves in the cell.
This commit is contained in:
Tyler Hallada 2021-01-23 20:10:42 -05:00
parent d1849735b2
commit f79ea29f3a
3 changed files with 408 additions and 315 deletions

View File

@ -52,17 +52,24 @@ bool ClearMerchandise(RE::TESObjectREFR* merchant_shelf) {
if (merchant_shelf) {
RE::TESObjectCELL* cell = merchant_shelf->GetParentCell();
RE::FormID shelf_form_id = merchant_shelf->GetFormID();
logger::info(FMT_STRING("ClearMerchandise shelf_form_id: {:x}"), (uint32_t)shelf_form_id);
for (auto entry = cell->references.begin(); entry != cell->references.end();)
{
RE::TESObjectREFR* ref = (*entry).get();
logger::info(FMT_STRING("ClearMerchandise ref form_id: {:x}, disabled: {:d}, marked for deletion: {:d}, deleted: {:d}"), (uint32_t)ref->GetFormID(), ref->IsDisabled(), ref->IsMarkedForDeletion(), ref->IsDeleted());
RE::TESBoundObject* base = ref->GetBaseObject();
if (base) {
RE::FormID form_id = base->GetFormID();
RE::TESObjectREFR* linked_activator_ref = ref->GetLinkedRef(activator_keyword);
if (form_id == ACTIVATOR_STATIC) {
std::pair<uint32_t, const char*> id_parts = get_local_form_id_and_mod_name(base);
logger::info(FMT_STRING("ClearMerchandise base local_form_id: {:x}, mod_name: {}"), (uint32_t)id_parts.first, id_parts.second);
if (id_parts.first == ACTIVATOR_STATIC && strcmp(id_parts.second, MOD_NAME) == 0) {
logger::info("ClearMerchandise found activator ref");
RE::TESObjectREFR* shelf_linked_ref = ref->GetLinkedRef(shelf_keyword);
if (shelf_linked_ref) {
logger::info(FMT_STRING("ClearMerchandise activator ref shelf_linked_ref: {:x}"), (uint32_t)shelf_linked_ref->GetFormID());
} else {
logger::info("ClearMerchandise activator ref no shelf_linked_ref!");
}
if (shelf_linked_ref && shelf_linked_ref->GetFormID() == shelf_form_id) {
logger::info("ClearMerchandise activator ref is linked with cleared shelf");
logger::info(FMT_STRING("ClearMerchandise deleting existing activator ref: {:x}"), (uint32_t)ref->GetFormID());
@ -114,9 +121,9 @@ bool ClearAllMerchandise(RE::TESObjectCELL* cell) {
logger::info(FMT_STRING("ClearAllMerchandise ref form_id: {:x}, disabled: {:d}, marked for deletion: {:d}, deleted: {:d}"), (uint32_t)ref->GetFormID(), ref->IsDisabled(), ref->IsMarkedForDeletion(), ref->IsDeleted());
RE::TESBoundObject* base = ref->GetBaseObject();
if (base) {
RE::FormID form_id = base->GetFormID();
RE::TESObjectREFR* linked_activator_ref = ref->GetLinkedRef(activator_keyword);
if (form_id == ACTIVATOR_STATIC) {
std::pair<uint32_t, const char*> id_parts = get_local_form_id_and_mod_name(base);
if (id_parts.first == ACTIVATOR_STATIC && strcmp(id_parts.second, MOD_NAME) == 0) {
logger::info("ClearAllMerchandise found activator ref");
RE::TESObjectREFR * shelf_linked_ref = ref->GetLinkedRef(shelf_keyword);
if (shelf_linked_ref) {
@ -157,11 +164,11 @@ bool ClearAllMerchandise(RE::TESObjectCELL* cell) {
return true;
}
void FillShelves(
void FillShelf(
RE::TESObjectCELL* cell,
std::vector<RE::TESObjectREFR*> merchant_shelves,
RE::TESObjectREFR* merchant_shelf,
RE::TESObjectREFR* merchant_chest,
int total_merchandise,
int page,
SKSE::RegistrationMap<bool> successReg,
SKSE::RegistrationMap<RE::BSFixedString> failReg
) {
@ -174,7 +181,6 @@ void FillShelves(
REL::ID extra_linked_ref_vtbl(static_cast<std::uint64_t>(229564));
RE::BGSKeyword* shelf_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_SHELF, MOD_NAME);
RE::BGSKeyword* chest_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_CHEST, MOD_NAME);
RE::BGSKeyword* item_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_ITEM, MOD_NAME);
RE::BGSKeyword* activator_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_ACTIVATOR, MOD_NAME);
RE::BGSKeyword* toggle_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_TOGGLE, MOD_NAME);
@ -182,36 +188,28 @@ void FillShelves(
RE::BGSKeyword* prev_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_PREV, MOD_NAME);
RE::TESForm* activator_static = data_handler->LookupForm(ACTIVATOR_STATIC, MOD_NAME);
logger::info(FMT_STRING("FillShelves activator_static is defined: {:d}"), activator_static != nullptr);
if (!ClearAllMerchandise(cell)) {
logger::error("FillShelves ClearAllMerchandise returned a fail code");
failReg.SendEvent(RE::BSFixedString("Failed to clear existing merchandise from shelves"));
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
return;
}
RE::InventoryChanges* inventory_changes = merchant_chest->GetInventoryChanges();
if (inventory_changes == nullptr) {
logger::info("FillShelves container empty, nothing to save");
logger::info("FillShelf container empty, nothing to save");
successReg.SendEvent(false);
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
return;
}
for (auto shelf = merchant_shelves.begin(); shelf != merchant_shelves.end(); ++shelf) {
RE::TESObjectREFR* merchant_shelf = (*shelf);
RE::BSSimpleList<RE::InventoryEntryData*>* entries = inventory_changes->entryList;
int total_merchandise = 0;
for (auto entry = entries->begin(); entry != entries->end(); ++entry) {
total_merchandise += 1;
}
// I'm abusing the ExtraCount ExtraData type for storing the current page number state of the shelf
RE::ExtraCount* extra_page_num = merchant_shelf->extraList.GetByType<RE::ExtraCount>();
if (!extra_page_num) {
extra_page_num = (RE::ExtraCount*)RE::BSExtraData::Create(sizeof(RE::ExtraCount), RE::Offset::ExtraCount::Vtbl.address());
merchant_shelf->extraList.Add(extra_page_num);
}
extra_page_num->count = 1;
logger::info(FMT_STRING("FillShelves set shelf page to: {:d}"), merchant_shelf->extraList.GetCount());
}
// I'm abusing the ExtraCannotWear ExtraData type as a boolean marker which stores whether the shelf is in a loaded or cleared state
// The presense of ExtraCannotWear == loaded, its absence == cleared
@ -221,39 +219,41 @@ void FillShelves(
extra_is_loaded = (RE::ExtraCannotWear*)RE::BSExtraData::Create(sizeof(RE::ExtraCannotWear), RE::Offset::ExtraCannotWear::Vtbl.address());
merchant_shelf->extraList.Add(extra_is_loaded);
}
logger::info(FMT_STRING("FillShelves set loaded: {:d}"), merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>() != nullptr);
logger::info(FMT_STRING("FillShelf set loaded: {:d}"), merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>() != nullptr);
if (merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>() == nullptr) {
logger::warn(FMT_STRING("FillShelves skipping merchant shelf {:x} that is unloaded"), (uint32_t)merchant_shelf->GetFormID());
continue;
logger::warn(FMT_STRING("FillShelf skipping merchant shelf {:x} that is unloaded"), (uint32_t)merchant_shelf->GetFormID());
return;
}
RE::BSSimpleList<RE::InventoryEntryData*>* entries = inventory_changes->entryList;
int max_page = std::ceil((float)(total_merchandise) / (float)9);
int load_page = merchant_shelf->extraList.GetCount();
int load_page = page;
if (page == 0) {
load_page = merchant_shelf->extraList.GetCount();
}
if (load_page > max_page) {
load_page = max_page;
}
extra_page_num->count = load_page;
logger::info(FMT_STRING("FillShelf set shelf page to: {:d}"), merchant_shelf->extraList.GetCount());
RE::TESObjectCELL * cell = merchant_shelf->GetParentCell();
// Calculate the actual barter price using the same formula Skyrim uses in the barter menu
// Formula from: http://en.uesp.net/wiki/Skyrim:Speech#Prices
// Allure perk is not counted because merchandise has no gender and is asexual
RE::GameSettingCollection* game_settings = RE::GameSettingCollection::GetSingleton();
float f_barter_min = game_settings->GetSetting("fBarterMin")->GetFloat();
float f_barter_max = game_settings->GetSetting("fBarterMax")->GetFloat();
logger::info(FMT_STRING("FillShelves fBarterMin: {:.2f}, fBarterMax: {:.2f}"), f_barter_min, f_barter_max);
logger::info(FMT_STRING("FillShelf fBarterMin: {:.2f}, fBarterMax: {:.2f}"), f_barter_min, f_barter_max);
RE::PlayerCharacter* player = RE::PlayerCharacter::GetSingleton();
float speech_skill = player->GetActorValue(RE::ActorValue::kSpeech);
float speech_skill_advance = player->GetActorValue(RE::ActorValue::kSpeechcraftSkillAdvance);
float speech_skill_modifier = player->GetActorValue(RE::ActorValue::kSpeechcraftModifier);
float speech_skill_power_modifier = player->GetActorValue(RE::ActorValue::kSpeechcraftPowerModifier);
logger::info(FMT_STRING("FillShelves speech_skill: {:.2f}, speech_skill_advance: {:.2f}, speech_skill_modifier: {:.2f}, speech_skill_power_modifier: {:.2f}"), speech_skill, speech_skill_advance, speech_skill_modifier, speech_skill_power_modifier);
logger::info(FMT_STRING("FillShelf speech_skill: {:.2f}, speech_skill_advance: {:.2f}, speech_skill_modifier: {:.2f}, speech_skill_power_modifier: {:.2f}"), speech_skill, speech_skill_advance, speech_skill_modifier, speech_skill_power_modifier);
float price_factor = f_barter_max - (f_barter_max - f_barter_min) * std::min(speech_skill, 100.f) / 100.f;
logger::info(FMT_STRING("FillShelves price_factor: {:.2f}"), price_factor);
logger::info(FMT_STRING("FillShelf price_factor: {:.2f}"), price_factor);
float buy_haggle = 1;
float sell_haggle = 1;
@ -261,20 +261,20 @@ void FillShelves(
HaggleVisitor buy_visitor = HaggleVisitor(reinterpret_cast<RE::Actor*>(player));
player->ForEachPerkEntry(RE::BGSEntryPoint::ENTRY_POINTS::kModBuyPrices, buy_visitor);
buy_haggle = buy_visitor.GetResult();
logger::info(FMT_STRING("FillShelves buy_haggle: {:.2f}"), buy_haggle);
logger::info(FMT_STRING("FillShelf buy_haggle: {:.2f}"), buy_haggle);
}
if (player->HasPerkEntries(RE::BGSEntryPoint::ENTRY_POINTS::kModSellPrices)) {
HaggleVisitor sell_visitor = HaggleVisitor(reinterpret_cast<RE::Actor*>(player));
player->ForEachPerkEntry(RE::BGSEntryPoint::ENTRY_POINTS::kModSellPrices, sell_visitor);
sell_haggle = sell_visitor.GetResult();
logger::info(FMT_STRING("FillShelves sell_haggle: {:.2f}"), sell_haggle);
logger::info(FMT_STRING("FillShelf sell_haggle: {:.2f}"), sell_haggle);
}
logger::info(FMT_STRING("FillShelves 1 - speech_power_mod: {:.2f}"), (1.f - speech_skill_power_modifier / 100.f));
logger::info(FMT_STRING("FillShelves 1 - speech_mod: {:.2f}"), (1.f - speech_skill_modifier / 100.f));
logger::info(FMT_STRING("FillShelf 1 - speech_power_mod: {:.2f}"), (1.f - speech_skill_power_modifier / 100.f));
logger::info(FMT_STRING("FillShelf 1 - speech_mod: {:.2f}"), (1.f - speech_skill_modifier / 100.f));
float buy_price_modifier = buy_haggle * (1.f - speech_skill_power_modifier / 100.f) * (1.f - speech_skill_modifier / 100.f);
logger::info(FMT_STRING("FillShelves buy_price_modifier: {:.2f}"), buy_price_modifier);
logger::info(FMT_STRING("FillShelf buy_price_modifier: {:.2f}"), buy_price_modifier);
float sell_price_modifier = sell_haggle * (1.f + speech_skill_power_modifier / 100.f) * (1.f + speech_skill_modifier / 100.f);
logger::info(FMT_STRING("FillShelves buy_price_modifier: {:.2f}"), sell_price_modifier);
logger::info(FMT_STRING("FillShelf buy_price_modifier: {:.2f}"), sell_price_modifier);
RE::NiPoint3 shelf_position = merchant_shelf->data.location;
RE::NiPoint3 shelf_angle = merchant_shelf->data.angle;
@ -282,7 +282,7 @@ void FillShelves(
int count = 0;
for (auto entry = entries->begin(); entry != entries->end(); ++entry) {
logger::info(FMT_STRING("FillShelves container iterator count: {:d}"), count);
logger::info(FMT_STRING("FillShelf container iterator count: {:d}"), count);
if (count < (load_page - 1) * 9 || count >= (load_page - 1) * 9 + 9) {
count++;
continue;
@ -298,13 +298,13 @@ void FillShelves(
uint16_t bound_x = boundMax.x > boundMin.x ? boundMax.x - boundMin.x : boundMin.x - boundMax.x;
uint16_t bound_y = boundMax.y > boundMin.y ? boundMax.y - boundMin.y : boundMin.y - boundMax.y;
uint16_t bound_z = boundMax.z > boundMin.z ? boundMax.z - boundMin.z : boundMin.z - boundMax.z;
logger::info(FMT_STRING("FillShelves ref bounds width: {:d}, length: {:d}, height: {:d}"), bound_x, bound_y, bound_z);
logger::info(FMT_STRING("FillShelf ref bounds width: {:d}, length: {:d}, height: {:d}"), bound_x, bound_y, bound_z);
RE::TESObjectREFR* activator_ref = PlaceAtMe_Native(a_vm, 0, merchant_shelf, activator_static, 1, false, false);
RE::NiPoint3 bound_min = ref->GetBoundMin();
RE::NiPoint3 bound_max = ref->GetBoundMax();
logger::info(FMT_STRING("FillShelves ref bounds min: {:.2f} {:.2f} {:.2f}, max: {:.2f} {:.2f} {:.2f}"), bound_min.x, bound_min.y, bound_min.z, bound_max.x, bound_max.y, bound_max.z);
logger::info(FMT_STRING("FillShelf ref bounds min: {:.2f} {:.2f} {:.2f}, max: {:.2f} {:.2f} {:.2f}"), bound_min.x, bound_min.y, bound_min.z, bound_max.x, bound_max.y, bound_max.z);
RE::ExtraLinkedRef* activator_extra_linked_ref = RE::BSExtraData::Create<RE::ExtraLinkedRef>(extra_linked_ref_vtbl.address());
activator_extra_linked_ref->linkedRefs.push_back({shelf_keyword, merchant_shelf});
@ -325,7 +325,7 @@ void FillShelves(
}
if (max_over_bound > 0) {
scale = ((float)34 / (float)(max_over_bound + 34)) * (float)100;
logger::info(FMT_STRING("FillShelves new scale: {:.2f} {:d} (max_over_bound: {:d}"), scale, static_cast<uint16_t>(scale), max_over_bound);
logger::info(FMT_STRING("FillShelf new scale: {:.2f} {:d} (max_over_bound: {:d}"), scale, static_cast<uint16_t>(scale), max_over_bound);
ref->refScale = static_cast<uint16_t>(scale);
activator_ref->refScale = static_cast<uint16_t>(scale);
}
@ -359,19 +359,19 @@ void FillShelves(
} else if (count % 9 == 8) {
ref_position = RE::NiPoint3(40 + x_imbalance, y_imbalance, 20 + z_imbalance);
}
logger::info(FMT_STRING("FillShelves relative position x: {:.2f}, y: {:.2f}, z: {:.2f}"), ref_position.x, ref_position.y, ref_position.z);
logger::info(FMT_STRING("FillShelf relative position x: {:.2f}, y: {:.2f}, z: {:.2f}"), ref_position.x, ref_position.y, ref_position.z);
ref_position = rotate_point(ref_position, rotation_matrix);
logger::info(FMT_STRING("FillShelves relative rotated position x: {:.2f}, y: {:.2f}, z: {:.2f}"), ref_position.x, ref_position.y, ref_position.z);
logger::info(FMT_STRING("FillShelf relative rotated position x: {:.2f}, y: {:.2f}, z: {:.2f}"), ref_position.x, ref_position.y, ref_position.z);
ref_position = RE::NiPoint3(shelf_position.x + ref_position.x, shelf_position.y + ref_position.y, shelf_position.z + ref_position.z);
logger::info(FMT_STRING("FillShelves absolute rotated position x: {:.2f}, y: {:.2f}, z: {:.2f}"), ref_position.x, ref_position.y, ref_position.z);
logger::info(FMT_STRING("FillShelf absolute rotated position x: {:.2f}, y: {:.2f}, z: {:.2f}"), ref_position.x, ref_position.y, ref_position.z);
MoveTo_Native(ref, ref->CreateRefHandle(), cell, cell->worldSpace, ref_position - RE::NiPoint3(10000, 10000, 10000), ref_angle);
MoveTo_Native(activator_ref, activator_ref->CreateRefHandle(), cell, cell->worldSpace, ref_position, ref_angle);
activator_extra_linked_ref->linkedRefs.push_back({item_keyword, ref});
ref->extraList.Add(item_extra_linked_ref);
int32_t buy_price = std::round(ref->GetGoldValue() * buy_price_modifier * price_factor);
logger::info(FMT_STRING("FillShelves buy_price: {:d}"), buy_price);
logger::info(FMT_STRING("FillShelf buy_price: {:d}"), buy_price);
// I'm abusing the ExtraCount ExtraData type for storing the quantity and price of the merchandise the activator_ref is linked to
RE::ExtraCount* extra_quantity_price = activator_ref->extraList.GetByType<RE::ExtraCount>();
@ -386,14 +386,14 @@ void FillShelves(
activator_ref->SetDisplayName(name, true);
} else {
logger::warn("FillShelves skipping container inventory entry which has no base form!");
logger::warn("FillShelf skipping container inventory entry which has no base form!");
}
count++;
}
RE::TESObjectREFR* next_ref = merchant_shelf->GetLinkedRef(next_keyword);
if (!next_ref) {
logger::error("FillShelves next_ref is null!");
logger::error("FillShelf next_ref is null!");
failReg.SendEvent("Could not find the shelf's next button");
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
@ -401,7 +401,7 @@ void FillShelves(
}
RE::TESObjectREFR* prev_ref = merchant_shelf->GetLinkedRef(prev_keyword);
if (!prev_ref) {
logger::error("FillShelves prev_ref is null!");
logger::error("FillShelf prev_ref is null!");
failReg.SendEvent("Could not find the shelf's previous button");
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
@ -420,6 +420,66 @@ void FillShelves(
prev_ref->SetDisplayName(fmt::format("Back to page {:d}", load_page - 1).c_str(), true);
}
}
void LoadShelfPageTask(
RE::TESObjectCELL* cell,
RE::TESObjectREFR* merchant_shelf,
RE::TESObjectREFR* merchant_chest,
int page
) {
if (!merchant_chest) {
logger::error("LoadShelfPageTask merchant_chest is null!");
return;
}
if (!merchant_shelf) {
logger::error("LoadShelfPageTask merchant_shelf is null!");
return;
}
auto task = SKSE::GetTaskInterface();
task->AddTask([cell, merchant_shelf, merchant_chest, page]() {
// Since this method is running asyncronously in a thread, set up a callback on the trigger ref that will receive an event with the result
SKSE::RegistrationMap<bool> successReg = SKSE::RegistrationMap<bool>();
successReg.Register(merchant_chest, RE::BSFixedString("OnLoadShelfPageSuccess"));
SKSE::RegistrationMap<RE::BSFixedString> failReg = SKSE::RegistrationMap<RE::BSFixedString>();
failReg.Register(merchant_chest, RE::BSFixedString("OnLoadShelfPageFail"));
if (!ClearMerchandise(merchant_shelf)) {
logger::error("LoadShelfPageTask ClearMerchandise returned a fail code");
failReg.SendEvent(RE::BSFixedString("Failed to clear existing merchandise from shelf"));
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
return;
}
FillShelf(cell, merchant_shelf, merchant_chest, page, successReg, failReg);
successReg.SendEvent(true);
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
});
}
void FillShelves(
RE::TESObjectCELL* cell,
std::vector<RE::TESObjectREFR*> merchant_shelves,
RE::TESObjectREFR* merchant_chest,
SKSE::RegistrationMap<bool> successReg,
SKSE::RegistrationMap<RE::BSFixedString> failReg
) {
if (!ClearAllMerchandise(cell)) {
logger::error("FillShelves ClearAllMerchandise returned a fail code");
failReg.SendEvent(RE::BSFixedString("Failed to clear existing merchandise from shelves"));
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
return;
}
for (auto shelf = merchant_shelves.begin(); shelf != merchant_shelves.end(); ++shelf) {
RE::TESObjectREFR* merchant_shelf = (*shelf);
FillShelf(cell, merchant_shelf, merchant_chest, 0, successReg, failReg);
}
}
void LoadMerchTask(
@ -469,7 +529,7 @@ void LoadMerchTask(
}
logger::info(FMT_STRING("LoadMerchTask added: {:d} items to merchant_chest"), count);
FillShelves(cell, merchant_shelves, merchant_chest, count, successReg, failReg);
FillShelves(cell, merchant_shelves, merchant_chest, successReg, failReg);
} else {
const char* error = result.AsErr();
logger::error(FMT_STRING("LoadMerchTask get_merchandise_list error: {}"), error);
@ -566,58 +626,72 @@ void LoadMerchandiseByShopIdImpl(
// }
//}
//
//bool LoadNextMerchandise(
// RE::StaticFunctionTag*,
// RE::BSFixedString api_url,
// RE::BSFixedString api_key,
// int32_t shop_id,
// RE::TESObjectREFR* merchant_shelf
//) {
// if (!merchant_shelf) {
// logger::error("LoadNextMerchandise merchant_shelf is null!");
// return false;
// }
// int page = merchant_shelf->extraList.GetCount();
//
// RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
// if (extra_is_loaded) {
// // Only advance the page if shelf is in loaded state, else just load the (first) page
// page = page + 1;
// }
//
// std::thread thread(LoadMerchandiseByShopIdImpl, api_url, api_key, shop_id, merchant_shelf, page, false);
// thread.detach();
// return true;
//}
//
//bool LoadPrevMerchandise(
// RE::StaticFunctionTag*,
// RE::BSFixedString api_url,
// RE::BSFixedString api_key,
// int32_t shop_id,
// RE::TESObjectREFR* merchant_shelf
//) {
// if (!merchant_shelf) {
// logger::error("LoadPrevMerchandise merchant_shelf is null!");
// return false;
// }
// int page = merchant_shelf->extraList.GetCount();
//
// if (page == 1) { // no-op on first page
// return true;
// }
//
// RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
// if (extra_is_loaded) {
// // Only advance the page if shelf is in loaded state, else just load the (first) page
// page = page - 1;
// }
//
// std::thread thread(LoadMerchandiseByShopIdImpl, api_url, api_key, shop_id, merchant_shelf, page, false);
// thread.detach();
// return true;
//}
//
bool LoadNextMerchandise(
RE::StaticFunctionTag*,
RE::TESObjectREFR* merchant_shelf
) {
if (!merchant_shelf) {
logger::error("LoadNextMerchandise merchant_shelf is null!");
return false;
}
int page = merchant_shelf->extraList.GetCount();
RE::TESDataHandler* data_handler = RE::TESDataHandler::GetSingleton();
RE::BGSKeyword* chest_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_CHEST, MOD_NAME);
RE::TESObjectREFR* merchant_chest = merchant_shelf->GetLinkedRef(chest_keyword);
if (!merchant_chest) {
logger::error("LoadNextMerchandise merchant_chest is null!");
return false;
}
RE::TESObjectCELL* cell = merchant_shelf->GetParentCell();
RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
if (extra_is_loaded) {
// Only advance the page if shelf is in loaded state, else just load the (first) page
page = page + 1;
}
std::thread thread(LoadShelfPageTask, cell, merchant_shelf, merchant_chest, page);
thread.detach();
return true;
}
bool LoadPrevMerchandise(
RE::StaticFunctionTag*,
RE::TESObjectREFR* merchant_shelf
) {
if (!merchant_shelf) {
logger::error("LoadPrevMerchandise merchant_shelf is null!");
return false;
}
int page = merchant_shelf->extraList.GetCount();
RE::TESDataHandler* data_handler = RE::TESDataHandler::GetSingleton();
RE::BGSKeyword* chest_keyword = data_handler->LookupForm<RE::BGSKeyword>(KEYWORD_CHEST, MOD_NAME);
RE::TESObjectREFR* merchant_chest = merchant_shelf->GetLinkedRef(chest_keyword);
if (!merchant_chest) {
logger::error("LoadPrevMerchandise merchant_chest is null!");
return false;
}
RE::TESObjectCELL* cell = merchant_shelf->GetParentCell();
RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
if (page == 1 && extra_is_loaded) { // no-op on first page and already loaded
return true;
}
if (extra_is_loaded) {
// Only advance the page if shelf is in loaded state, else just load the (first) page
page = page - 1;
}
std::thread thread(LoadShelfPageTask, cell, merchant_shelf, merchant_chest, page);
thread.detach();
return true;
}
bool LoadMerchandiseByShopId(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
@ -662,7 +736,8 @@ bool LoadMerchandiseByShopId(
// thread.detach();
// return true;
//}
//
// TODO: Am I going to actually use this method?
bool ReplaceMerch3D(RE::StaticFunctionTag*, RE::TESObjectREFR* merchant_shelf) {
logger::info("Entered ReplaceMerch3D");
@ -747,7 +822,14 @@ bool ReplaceAllMerch3D(RE::StaticFunctionTag*, RE::TESObjectCELL* cell) {
return true;
}
void CreateMerchandiseListImpl(RE::BSFixedString api_url, RE::BSFixedString api_key, int32_t shop_id, RE::TESObjectREFR* merchant_chest) {
void CreateMerchandiseListImpl(
RE::BSFixedString api_url,
RE::BSFixedString api_key,
int32_t shop_id,
RE::TESObjectCELL* cell,
std::vector<RE::TESObjectREFR*> merchant_shelves,
RE::TESObjectREFR* merchant_chest
) {
logger::info("Entered CreateMerchandiseListImpl");
RE::TESDataHandler* data_handler = RE::TESDataHandler::GetSingleton();
std::vector<RawMerchandise> merch_records;
@ -757,7 +839,7 @@ void CreateMerchandiseListImpl(RE::BSFixedString api_url, RE::BSFixedString api_
return;
}
SKSE::RegistrationMap<bool, int> successReg = SKSE::RegistrationMap<bool, int>();
SKSE::RegistrationMap<bool> successReg = SKSE::RegistrationMap<bool>();
successReg.Register(merchant_chest, RE::BSFixedString("OnCreateMerchandiseSuccess"));
SKSE::RegistrationMap<RE::BSFixedString> failReg = SKSE::RegistrationMap<RE::BSFixedString>();
failReg.Register(merchant_chest, RE::BSFixedString("OnCreateMerchandiseFail"));
@ -765,7 +847,7 @@ void CreateMerchandiseListImpl(RE::BSFixedString api_url, RE::BSFixedString api_
RE::InventoryChanges* inventory_changes = merchant_chest->GetInventoryChanges();
if (inventory_changes == nullptr) {
logger::info("CreateMerchandiseList container empty, nothing to save");
successReg.SendEvent(false, -1);
successReg.SendEvent(false);
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
return;
@ -813,17 +895,18 @@ void CreateMerchandiseListImpl(RE::BSFixedString api_url, RE::BSFixedString api_
if (count == 0) {
logger::info("CreateMerchandiseList container inventory changes empty, nothing to save");
successReg.SendEvent(false, -1);
successReg.SendEvent(false);
successReg.Unregister(merchant_chest);
failReg.Unregister(merchant_chest);
return;
}
FFIResult<int32_t> result = update_merchandise_list(api_url.c_str(), api_key.c_str(), shop_id, &merch_records[0], merch_records.size());
FFIResult<RawMerchandiseVec> result = update_merchandise_list(api_url.c_str(), api_key.c_str(), shop_id, &merch_records[0], merch_records.size());
LoadMerchTask(result, cell, merchant_shelves, merchant_chest);
if (result.IsOk()) {
int32_t merchandise_list_id = result.AsOk();
logger::info(FMT_STRING("CreateMerchandiseList success: {}"), merchandise_list_id);
successReg.SendEvent(true, merchandise_list_id);
logger::info("CreateMerchandiseList success");
successReg.SendEvent(true);
} else {
const char* error = result.AsErr();
logger::error(FMT_STRING("CreateMerchandiseList failure: {}"), error);
@ -833,7 +916,15 @@ void CreateMerchandiseListImpl(RE::BSFixedString api_url, RE::BSFixedString api_
failReg.Unregister(merchant_chest);
}
bool CreateMerchandiseList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, int32_t shop_id, RE::TESObjectREFR* merchant_chest) {
bool CreateMerchandiseList(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
int32_t shop_id,
RE::TESObjectCELL* cell,
std::vector<RE::TESObjectREFR*> merchant_shelves,
RE::TESObjectREFR* merchant_chest
) {
logger::info("Entered CreateMerchandiseList");
if (!merchant_chest) {
@ -841,7 +932,7 @@ bool CreateMerchandiseList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE
return false;
}
std::thread thread(CreateMerchandiseListImpl, api_url, api_key, shop_id, merchant_chest);
std::thread thread(CreateMerchandiseListImpl, api_url, api_key, shop_id, cell, merchant_shelves, merchant_chest);
thread.detach();
return true;
}

View File

@ -7,20 +7,14 @@
// int32_t shop_id,
// RE::TESObjectREFR* merchant_shelf
//);
//bool LoadNextMerchandise(
// RE::StaticFunctionTag*,
// RE::BSFixedString api_url,
// RE::BSFixedString api_key,
// int32_t shop_id,
// RE::TESObjectREFR* merchant_shelf
//);
//bool LoadPrevMerchandise(
// RE::StaticFunctionTag*,
// RE::BSFixedString api_url,
// RE::BSFixedString api_key,
// int32_t shop_id,
// RE::TESObjectREFR* merchant_shelf
//);
bool LoadNextMerchandise(
RE::StaticFunctionTag*,
RE::TESObjectREFR* merchant_shelf
);
bool LoadPrevMerchandise(
RE::StaticFunctionTag*,
RE::TESObjectREFR* merchant_shelf
);
bool LoadMerchandiseByShopId(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
@ -39,6 +33,14 @@ bool LoadMerchandiseByShopId(
//);
bool ReplaceMerch3D(RE::StaticFunctionTag*, RE::TESObjectREFR* merchant_shelf);
bool ReplaceAllMerch3D(RE::StaticFunctionTag*, RE::TESObjectCELL* cell);
bool CreateMerchandiseList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, int32_t shop_id, RE::TESObjectREFR* merchant_chest);
bool CreateMerchandiseList(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
int32_t shop_id,
RE::TESObjectCELL* cell,
std::vector<RE::TESObjectREFR*> merchant_shelves,
RE::TESObjectREFR* merchant_chest
);
int GetMerchandiseQuantity(RE::StaticFunctionTag*, RE::TESObjectREFR* activator);
int GetMerchandisePrice(RE::StaticFunctionTag*, RE::TESObjectREFR* activator);

View File

@ -24,8 +24,8 @@ bool RegisterFuncs(RE::BSScript::IVirtualMachine* a_vm)
a_vm->RegisterFunction("Load", "BRInteriorRefList", LoadInteriorRefList);
a_vm->RegisterFunction("LoadByShopId", "BRInteriorRefList", LoadInteriorRefListByShopId);
//a_vm->RegisterFunction("Toggle", "BRMerchandiseList", ToggleMerchandise);
//a_vm->RegisterFunction("NextPage", "BRMerchandiseList", LoadNextMerchandise);
//a_vm->RegisterFunction("PrevPage", "BRMerchandiseList", LoadPrevMerchandise);
a_vm->RegisterFunction("NextPage", "BRMerchandiseList", LoadNextMerchandise);
a_vm->RegisterFunction("PrevPage", "BRMerchandiseList", LoadPrevMerchandise);
a_vm->RegisterFunction("Load", "BRMerchandiseList", LoadMerchandiseByShopId);
//a_vm->RegisterFunction("Refresh", "BRMerchandiseList", RefreshMerchandise);
a_vm->RegisterFunction("Replace3D", "BRMerchandiseList", ReplaceMerch3D);