From 2f9a91c0bd104e21d5b70f6e3f91c7a772718179 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Mon, 2 Oct 2023 19:07:21 +0600 Subject: [PATCH 01/88] wip --- libespm/include/libespm/LVLI.h | 37 +----- libespm/include/libespm/LVLN.h | 18 +++ libespm/include/libespm/LeveledListBase.h | 49 ++++++++ libespm/include/libespm/NPC_.h | 2 + libespm/include/libespm/Records.h | 1 + libespm/src/{LVLI.cpp => LeveledListBase.cpp} | 4 +- libespm/src/NPC_.cpp | 3 + .../cpp/server_guest_lib/LeveledListUtils.cpp | 111 ++++++++++++++++-- .../cpp/server_guest_lib/LeveledListUtils.h | 5 + unit/EspmTest.cpp | 1 + unit/LeveledListUtilsTest.cpp | 86 ++++++++++++++ 11 files changed, 270 insertions(+), 47 deletions(-) create mode 100644 libespm/include/libespm/LVLN.h create mode 100644 libespm/include/libespm/LeveledListBase.h rename libespm/src/{LVLI.cpp => LeveledListBase.cpp} (91%) diff --git a/libespm/include/libespm/LVLI.h b/libespm/include/libespm/LVLI.h index f1c9ebc2f2..c157e0b849 100644 --- a/libespm/include/libespm/LVLI.h +++ b/libespm/include/libespm/LVLI.h @@ -1,50 +1,17 @@ #pragma once -#include "RecordHeader.h" +#include "LeveledListBase.h" #pragma pack(push, 1) namespace espm { -class LVLI final : public RecordHeader +class LVLI final : public LeveledListBase { public: static constexpr auto kType = "LVLI"; - - enum LeveledItemFlags - { - AllLevels = 0x01, //(sets it to calculate for all entries < player level, - // choosing randomly from all the entries under) - Each = 0x02, // (sets it to repeat a check every time the list is called - // (if it's called multiple times), otherwise it will use the - // same result for all counts.) - UseAll = 0x04, // (use all entries when the list is called) - SpecialLoot = 0x08, - }; - - struct Entry - { - char type[4] = { 'L', 'V', 'L', 'O' }; - uint16_t dataSize = 0; - uint32_t level = 0; - uint32_t formId = 0; - uint32_t count = 0; - }; - - struct Data - { - const char* editorId = ""; - uint8_t chanceNone = 0; - uint8_t leveledItemFlags = 0; - uint32_t chanceNoneGlobalId = 0; - uint8_t numEntries = 0; - const Entry* entries = nullptr; - }; - - Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept; }; static_assert(sizeof(LVLI) == sizeof(RecordHeader)); -static_assert(sizeof(LVLI::Entry) == 18); } diff --git a/libespm/include/libespm/LVLN.h b/libespm/include/libespm/LVLN.h new file mode 100644 index 0000000000..b1d6947144 --- /dev/null +++ b/libespm/include/libespm/LVLN.h @@ -0,0 +1,18 @@ +#pragma once +#include "LeveledListBase.h" + +#pragma pack(push, 1) + +namespace espm { + +class LVLN final : public LeveledListBase +{ +public: + static constexpr auto kType = "LVLN"; +}; + +static_assert(sizeof(LVLN) == sizeof(RecordHeader)); + +} + +#pragma pack(pop) diff --git a/libespm/include/libespm/LeveledListBase.h b/libespm/include/libespm/LeveledListBase.h new file mode 100644 index 0000000000..fd68136d70 --- /dev/null +++ b/libespm/include/libespm/LeveledListBase.h @@ -0,0 +1,49 @@ +#pragma once +#include "RecordHeader.h" + +#pragma pack(push, 1) + +namespace espm { + +class LeveledListBase : public RecordHeader +{ +public: + enum LeveledItemFlags + { + AllLevels = 0x01, //(sets it to calculate for all entries < player level, + // choosing randomly from all the entries under) + Each = 0x02, // (sets it to repeat a check every time the list is called + // (if it's called multiple times), otherwise it will use the + // same result for all counts.) + UseAll = 0x04, // (use all entries when the list is called) + SpecialLoot = 0x08, + }; + + struct Entry + { + char type[4] = { 'L', 'V', 'L', 'O' }; + uint16_t dataSize = 0; + uint32_t level = 0; + uint32_t formId = 0; + uint32_t count = 0; + }; + + struct Data + { + const char* editorId = ""; + uint8_t chanceNone = 0; + uint8_t leveledItemFlags = 0; + uint32_t chanceNoneGlobalId = 0; + uint8_t numEntries = 0; + const Entry* entries = nullptr; + }; + + Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept; +}; + +static_assert(sizeof(LeveledListBase) == sizeof(RecordHeader)); +static_assert(sizeof(LeveledListBase::Entry) == 18); + +} + +#pragma pack(pop) diff --git a/libespm/include/libespm/NPC_.h b/libespm/include/libespm/NPC_.h index 39819178d1..85996ce766 100644 --- a/libespm/include/libespm/NPC_.h +++ b/libespm/include/libespm/NPC_.h @@ -37,6 +37,8 @@ class NPC_ final : public RecordHeader int16_t magickaOffset = 0; int16_t staminaOffset = 0; ObjectBounds objectBounds = {}; + uint32_t baseTemplate = 0; + uint16_t templateDataFlags = 0; }; Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept; diff --git a/libespm/include/libespm/Records.h b/libespm/include/libespm/Records.h index 2710cfa810..a3fbe4a1f5 100644 --- a/libespm/include/libespm/Records.h +++ b/libespm/include/libespm/Records.h @@ -26,3 +26,4 @@ #include "TREE.h" #include "WEAP.h" #include "WRLD.h" +#include "LVLN.h" \ No newline at end of file diff --git a/libespm/src/LVLI.cpp b/libespm/src/LeveledListBase.cpp similarity index 91% rename from libespm/src/LVLI.cpp rename to libespm/src/LeveledListBase.cpp index 67f7fa1414..c9d8663e05 100644 --- a/libespm/src/LVLI.cpp +++ b/libespm/src/LeveledListBase.cpp @@ -1,10 +1,10 @@ -#include "libespm/LVLI.h" +#include "libespm/LeveledListBase.h" #include "libespm/RecordHeaderAccess.h" #include namespace espm { -LVLI::Data LVLI::GetData( +LeveledListBase::Data LeveledListBase::GetData( CompressedFieldsCache& compressedFieldsCache) const noexcept { Data result; diff --git a/libespm/src/NPC_.cpp b/libespm/src/NPC_.cpp index 0309ec71e7..f3243ebcee 100644 --- a/libespm/src/NPC_.cpp +++ b/libespm/src/NPC_.cpp @@ -29,6 +29,7 @@ NPC_::Data NPC_::GetData( result.magickaOffset = *reinterpret_cast(data + 4); result.staminaOffset = *reinterpret_cast(data + 6); result.healthOffset = *reinterpret_cast(data + 20); + result.templateDataFlags = *reinterpret_cast(data + 18); } else if (!std::memcmp(type, "RNAM", 4)) { result.race = *reinterpret_cast(data); @@ -39,6 +40,8 @@ NPC_::Data NPC_::GetData( } } else if (!std::memcmp(type, "SPLO", 4)) { result.spells.emplace(*reinterpret_cast(data)); + } else if (!std::memcmp(type, "TPLT", 4)) { + result.baseTemplate = (*reinterpret_cast(data)); } }, compressedFieldsCache); diff --git a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp index 8ef8a5544e..0f279600f7 100644 --- a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp +++ b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp @@ -1,5 +1,8 @@ #include "LeveledListUtils.h" #include +#include +#include +#include std::vector LeveledListUtils::EvaluateList( const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, @@ -7,7 +10,14 @@ std::vector LeveledListUtils::EvaluateList( { espm::CompressedFieldsCache dummyCache; - auto leveledList = espm::Convert(lookupRes.rec); + const espm::LeveledListBase* leveledList = nullptr; + if (lookupRes.rec->GetType() == espm::LVLI::kType || + lookupRes.rec->GetType() == espm::LVLN::kType || + lookupRes.rec->GetType() == + "LVSP") /* for the future leveled spell implementation */ { + leveledList = + reinterpret_cast(lookupRes.rec); + } if (!leveledList) { return {}; } @@ -16,22 +26,25 @@ std::vector LeveledListUtils::EvaluateList( std::vector res; int chanceNone = data.chanceNoneGlobalId ? 100 : data.chanceNone; - if (chanceNoneOverride) + if (chanceNoneOverride) { chanceNone = *chanceNoneOverride; + } - std::random_device rd; - std::mt19937 mt(rd()); std::uniform_real_distribution dist(0.0, 100.0); + std::random_device rd; + std::mt19937 mt(rd()); bool none = dist(mt) < chanceNone; if (!none) { - std::vector entriesAllowed; + std::vector entriesAllowed; for (size_t i = 0; i < data.numEntries; ++i) { - if (!pcLevel || data.entries[i].level <= pcLevel) + if (!pcLevel || data.entries[i].level <= pcLevel) { entriesAllowed.push_back(&data.entries[i]); + } } - bool useRandomEntry = !(data.leveledItemFlags & espm::LVLI::UseAll); + bool useRandomEntry = + !(data.leveledItemFlags & espm::LeveledListBase::UseAll); if (useRandomEntry && !entriesAllowed.empty()) { std::uniform_int_distribution dist(0, entriesAllowed.size() - 1); auto entry = entriesAllowed[dist(mt)]; @@ -54,9 +67,18 @@ std::map LeveledListUtils::EvaluateListRecurse( { espm::CompressedFieldsCache dummyCache; - auto leveledList = espm::Convert(lookupRes.rec); + const espm::LeveledListBase* leveledList = nullptr; + if (lookupRes.rec->GetType() == espm::LVLI::kType || + lookupRes.rec->GetType() == espm::LVLN::kType || + lookupRes.rec->GetType() == + "LVSP") /* for the future leveled spell implementation */ { + leveledList = + reinterpret_cast(lookupRes.rec); + } + bool calcForEach = leveledList && - (leveledList->GetData(dummyCache).leveledItemFlags & espm::LVLI::Each); + (leveledList->GetData(dummyCache).leveledItemFlags & + espm::LeveledListBase::Each); if (calcForEach && countMult != 1) { std::map res; @@ -76,7 +98,10 @@ std::map LeveledListUtils::EvaluateListRecurse( if (!eLookupRes.rec) { continue; } - if (eLookupRes.rec->GetType() == espm::LVLI::kType) { + if (eLookupRes.rec->GetType() == espm::LVLI::kType || + eLookupRes.rec->GetType() == espm::LVLN::kType || + eLookupRes.rec->GetType() == + "LVSP") /* for the future leveled spell implementation */ { auto childRes = EvaluateListRecurse(br, eLookupRes, 1, pcLevel); for (auto& p : childRes) { res[p.first] += p.second; @@ -95,3 +120,69 @@ std::map LeveledListUtils::EvaluateListRecurse( return res; } + +std::vector LeveledListUtils::EvaluateTemplateChain( + const espm::CombineBrowser& br, const espm::LookupResult& headNpc, + uint32_t pcLevel) +{ + espm::CompressedFieldsCache dummyCache; + std::vector res; + + auto cursor = espm::Convert(headNpc.rec); + auto cursorFileIdx = headNpc.fileIdx; + const auto cursorToGlobalId = [&](uint32_t rawId) { + auto lookupRes = espm::LookupResult(&br, cursor, cursorFileIdx); + return lookupRes.ToGlobalId(rawId); + }; + if (!cursor) { + spdlog::error("EvaluateTemplateChain: NPC_ expected"); + return res; + } + + res.push_back(headNpc.ToGlobalId(headNpc.rec->GetId())); + + while (1) { + espm::NPC_::Data data = cursor->GetData(dummyCache); + if (!data.baseTemplate) { + break; + } + + uint32_t templateId = cursorToGlobalId(data.baseTemplate); + espm::LookupResult template_ = br.LookupById(templateId); + + if (!template_.rec) { + spdlog::error("EvaluateTemplateChain: Not found template record"); + return res; + } + + if (auto npc = espm::Convert(template_.rec)) { + res.push_back(templateId); + cursor = npc; + cursorFileIdx = template_.fileIdx; + } else if (auto lvln = espm::Convert(template_.rec)) { + std::map countByFormId = + EvaluateListRecurse(br, template_, 1, pcLevel); + if (countByFormId.empty()) { + spdlog::error( + "EvaluateTemplateChain: EvaluateListRecurse returned empty map"); + return res; + } + if (countByFormId.size() > 1) { + spdlog::warn("EvaluateTemplateChain: EvaluateListRecurse returned " + "more than 1 result, omitting other results"); + } + auto [templateIdNpc, _] = *countByFormId.begin(); + auto npcLookupRes = br.LookupById(templateIdNpc); + auto npc = espm::Convert(npcLookupRes.rec); + + res.push_back(templateIdNpc); + cursor = npc; + cursorFileIdx = npcLookupRes.fileIdx; + } else { + spdlog::error("EvaluateTemplateChain: Not found template record"); + return res; + } + } + + return res; +} diff --git a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h index 84f85f3b5d..aca7af2a46 100644 --- a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h +++ b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h @@ -12,6 +12,7 @@ struct Entry uint32_t count = 0; }; +// It seems that pcLevel=0 makes it thinking that pcLevel=maximum possible pc level std::vector EvaluateList(const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, uint32_t pcLevel = 0, @@ -21,4 +22,8 @@ std::map EvaluateListRecurse( const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, uint32_t countMult = 1, uint32_t pcLevel = 0, uint8_t* chanceNoneOverride = nullptr); + +std::vector EvaluateTemplateChain( + const espm::CombineBrowser& br, const espm::LookupResult& headNpc, + uint32_t pcLevel); } diff --git a/unit/EspmTest.cpp b/unit/EspmTest.cpp index 2099f4101a..b6b22bd3ef 100644 --- a/unit/EspmTest.cpp +++ b/unit/EspmTest.cpp @@ -278,6 +278,7 @@ TEST_CASE("Loads Npc", "[espm]") REQUIRE(npc->GetData(compressedFieldsCache).defaultOutfitId == 0x1697c); REQUIRE(npc->GetData(compressedFieldsCache).sleepOutfitId == 0x0); REQUIRE(npc->GetData(compressedFieldsCache).objects.size() == 16); + REQUIRE(npc->GetData(compressedFieldsCache).templateDataFlags == 0); } TEST_CASE("Loads Weapon", "[espm]") diff --git a/unit/LeveledListUtilsTest.cpp b/unit/LeveledListUtilsTest.cpp index c67a5ad0b0..5716c9be57 100644 --- a/unit/LeveledListUtilsTest.cpp +++ b/unit/LeveledListUtilsTest.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include extern espm::Loader l; @@ -161,3 +162,88 @@ TEST_CASE("Evaluate LItemWeaponDaggerTown", "[espm]") REQUIRE(res.size() == 1); REQUIRE(res[0x1397e] == 1000); } + +TEST_CASE("Evaluate LCharHorse", "[espm]") +{ + auto LCharHorse = 0x68d6f; + auto& br = l.GetBrowser(); + auto leveledList = br.LookupById(LCharHorse); + + std::map results; + + for (int i = 0; i < 1000000; ++i) { + auto res = EvaluateListRecurse(br, leveledList, 1); + REQUIRE(res.size() == 1); + + auto pair = *res.begin(); + results[pair.first] += pair.second; + if (results.size() == 5) { + break; + } + } + + REQUIRE(results.size() == 5); + REQUIRE(results[0x68d6e] > 0); + REQUIRE(results[0x68d6d] > 0); + REQUIRE(results[0x68d6b] > 0); + REQUIRE(results[0x68d5b] > 0); + REQUIRE(results[0x68d07] > 0); +} + +TEST_CASE("Evaluate LvlHorse template", "[espm]") +{ + auto LvlHorse = 0x68d71; + auto& br = l.GetBrowser(); + auto horse = br.LookupById(LvlHorse); + + std::map results; + + for (int i = 0; i < 1000000; ++i) { + auto res = EvaluateTemplateChain(br, horse, 1); + REQUIRE(res.size() == 2); + REQUIRE(res[0] == LvlHorse); + + results[res[1]] += 1; + if (results.size() == 5) { + break; + } + } + + REQUIRE(results.size() == 5); + REQUIRE(results[0x68d6e] > 0); + REQUIRE(results[0x68d6d] > 0); + REQUIRE(results[0x68d6b] > 0); + REQUIRE(results[0x68d5b] > 0); + REQUIRE(results[0x68d07] > 0); +} + +TEST_CASE("Evaluate LvlMudcrab template", "[espm]") +{ + auto LvlMudcrab = 0x8cacc; + auto& br = l.GetBrowser(); + auto mudcrab = br.LookupById(LvlMudcrab); + + std::map countByFormId; + + for (int i = 0; i < 100'000; i++) { + constexpr int kPcLevel = 5; + auto chain = EvaluateTemplateChain(br, mudcrab, kPcLevel); + REQUIRE(chain.size() == 2); + REQUIRE(chain[0] == 0x8cacc); + ++countByFormId[chain[1]]; + } + + // formId e4010 level 1 + // formId e4010 level 1 + // formId e4011 level 1 + // formId e4011 level 5 + // So with level 5 should be 50/50 + + REQUIRE(countByFormId.size() == 2); + + int64_t countA = static_cast(countByFormId.begin()->second); + int64_t countB = static_cast((--countByFormId.end())->second); + + // usually diff is less than 100, but we don't want to fail tests randomly + REQUIRE(std::abs(countA - countB) < 2000); +} From c4c60d95057a2ce2a58488e597c889fc2058db1a Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Mon, 2 Oct 2023 19:09:24 +0600 Subject: [PATCH 02/88] format --- libespm/include/libespm/Records.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libespm/include/libespm/Records.h b/libespm/include/libespm/Records.h index a3fbe4a1f5..9f0c992cb3 100644 --- a/libespm/include/libespm/Records.h +++ b/libespm/include/libespm/Records.h @@ -26,4 +26,4 @@ #include "TREE.h" #include "WEAP.h" #include "WRLD.h" -#include "LVLN.h" \ No newline at end of file +#include "LVLN.h" From cfc325b9e71947bf5eeb3732cf9f409614b8b4cd Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Tue, 3 Oct 2023 07:44:56 +0600 Subject: [PATCH 03/88] less noisy papurus test --- .../cpp/server_guest_lib/LeveledListUtils.cpp | 132 +++++++++++------- .../cpp/server_guest_lib/LeveledListUtils.h | 61 +++++--- unit/LeveledListUtilsTest.cpp | 28 ++-- unit/VirtualMachineTest.cpp | 26 ++-- 4 files changed, 155 insertions(+), 92 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp index 0f279600f7..a8be761a6a 100644 --- a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp +++ b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp @@ -1,8 +1,8 @@ #include "LeveledListUtils.h" +#include #include #include #include -#include std::vector LeveledListUtils::EvaluateList( const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, @@ -122,67 +122,103 @@ std::map LeveledListUtils::EvaluateListRecurse( } std::vector LeveledListUtils::EvaluateTemplateChain( - const espm::CombineBrowser& br, const espm::LookupResult& headNpc, + const espm::CombineBrowser& browser, const espm::LookupResult& headNpc, uint32_t pcLevel) { - espm::CompressedFieldsCache dummyCache; - std::vector res; - - auto cursor = espm::Convert(headNpc.rec); - auto cursorFileIdx = headNpc.fileIdx; - const auto cursorToGlobalId = [&](uint32_t rawId) { - auto lookupRes = espm::LookupResult(&br, cursor, cursorFileIdx); - return lookupRes.ToGlobalId(rawId); - }; - if (!cursor) { + std::vector result; + auto npcCursor = ConvertToNpc(headNpc); + if (!npcCursor) { spdlog::error("EvaluateTemplateChain: NPC_ expected"); - return res; + return result; } - res.push_back(headNpc.ToGlobalId(headNpc.rec->GetId())); + uint32_t cursorFileIdx = headNpc.fileIdx; + result.push_back(headNpc.ToGlobalId(headNpc.rec->GetId())); - while (1) { - espm::NPC_::Data data = cursor->GetData(dummyCache); - if (!data.baseTemplate) { - break; + while (true) { + uint32_t templateId = GetBaseTemplateId(npcCursor, cursorFileIdx, browser); + if (templateId == 0) { + break; // End if no base template } - uint32_t templateId = cursorToGlobalId(data.baseTemplate); - espm::LookupResult template_ = br.LookupById(templateId); + espm::LookupResult templateResult = browser.LookupById(templateId); - if (!template_.rec) { + if (!templateResult.rec) { spdlog::error("EvaluateTemplateChain: Not found template record"); - return res; + return result; } - if (auto npc = espm::Convert(template_.rec)) { - res.push_back(templateId); - cursor = npc; - cursorFileIdx = template_.fileIdx; - } else if (auto lvln = espm::Convert(template_.rec)) { - std::map countByFormId = - EvaluateListRecurse(br, template_, 1, pcLevel); - if (countByFormId.empty()) { - spdlog::error( - "EvaluateTemplateChain: EvaluateListRecurse returned empty map"); - return res; - } - if (countByFormId.size() > 1) { - spdlog::warn("EvaluateTemplateChain: EvaluateListRecurse returned " - "more than 1 result, omitting other results"); - } - auto [templateIdNpc, _] = *countByFormId.begin(); - auto npcLookupRes = br.LookupById(templateIdNpc); - auto npc = espm::Convert(npcLookupRes.rec); + UpdateCursorAndResult(templateId, templateResult, npcCursor, cursorFileIdx, + result, browser, pcLevel); + } - res.push_back(templateIdNpc); - cursor = npc; - cursorFileIdx = npcLookupRes.fileIdx; - } else { - spdlog::error("EvaluateTemplateChain: Not found template record"); - return res; + return result; +} + +const espm::NPC_* LeveledListUtils::ConvertToNpc( + const espm::LookupResult& lookupResult) +{ + return espm::Convert(lookupResult.rec); +} + +uint32_t LeveledListUtils::GetBaseTemplateId( + const espm::NPC_* cursor, uint32_t fileIdx, + const espm::CombineBrowser& browser) +{ + espm::CompressedFieldsCache dummyCache; + auto data = cursor->GetData(dummyCache); + if (!data.baseTemplate) { + return 0; // Indicates there's no base template + } + + auto lookupRes = espm::LookupResult(&browser, cursor, fileIdx); + return lookupRes.ToGlobalId(data.baseTemplate); +} + +void LeveledListUtils::UpdateCursorAndResult( + uint32_t templateId, espm::LookupResult& templateResult, + const espm::NPC_*& cursor, uint32_t& cursorFileIdx, + std::vector& result, const espm::CombineBrowser& browser, + uint32_t pcLevel) +{ + if (auto npc = espm::Convert(templateResult.rec)) { + result.push_back(templateId); + cursor = npc; + cursorFileIdx = templateResult.fileIdx; + } else if (auto lvln = espm::Convert(templateResult.rec)) { + auto selectedNpcId = + EvaluateAndSelectNpcId(browser, templateResult, pcLevel); + if (selectedNpcId == 0) { + return; } + + auto npcLookupRes = browser.LookupById(selectedNpcId); + cursor = espm::Convert(npcLookupRes.rec); + result.push_back(selectedNpcId); + cursorFileIdx = npcLookupRes.fileIdx; + } else { + spdlog::error("EvaluateTemplateChain: Not found template record"); } +} - return res; +uint32_t LeveledListUtils::EvaluateAndSelectNpcId( + const espm::CombineBrowser& browser, + const espm::LookupResult& templateResult, uint32_t pcLevel) +{ + auto countByFormId = + LeveledListUtils::EvaluateListRecurse(browser, templateResult, 1, pcLevel); + + if (countByFormId.empty()) { + spdlog::error( + "EvaluateTemplateChain: EvaluateListRecurse returned empty map"); + return 0; + } + + if (countByFormId.size() > 1) { + spdlog::warn("EvaluateTemplateChain: EvaluateListRecurse returned more " + "than 1 result, omitting other results"); + } + + // Return the id of the first npc in the map + return countByFormId.begin()->first; } diff --git a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h index aca7af2a46..744f2cb6fb 100644 --- a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h +++ b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.h @@ -5,25 +5,46 @@ #include #include -namespace LeveledListUtils { -struct Entry +class LeveledListUtils { - uint32_t formId = 0; - uint32_t count = 0; -}; +public: + struct Entry + { + uint32_t formId = 0; + uint32_t count = 0; + }; + + // It seems that pcLevel=0 makes it thinking that pcLevel=maximum possible pc + // level + static std::vector EvaluateList( + const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, + uint32_t pcLevel = 0, uint8_t* chanceNoneOverride = nullptr); + + static std::map EvaluateListRecurse( + const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, + uint32_t countMult = 1, uint32_t pcLevel = 0, + uint8_t* chanceNoneOverride = nullptr); + + static std::vector EvaluateTemplateChain( + const espm::CombineBrowser& br, const espm::LookupResult& headNpc, + uint32_t pcLevel); -// It seems that pcLevel=0 makes it thinking that pcLevel=maximum possible pc level -std::vector EvaluateList(const espm::CombineBrowser& br, - const espm::LookupResult& lookupRes, - uint32_t pcLevel = 0, - uint8_t* chanceNoneOverride = nullptr); - -std::map EvaluateListRecurse( - const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, - uint32_t countMult = 1, uint32_t pcLevel = 0, - uint8_t* chanceNoneOverride = nullptr); - -std::vector EvaluateTemplateChain( - const espm::CombineBrowser& br, const espm::LookupResult& headNpc, - uint32_t pcLevel); -} +private: + static const espm::NPC_* ConvertToNpc( + const espm::LookupResult& lookupResult); + + static uint32_t GetBaseTemplateId(const espm::NPC_* cursor, uint32_t fileIdx, + const espm::CombineBrowser& browser); + + static void UpdateCursorAndResult(uint32_t templateId, + espm::LookupResult& templateResult, + const espm::NPC_*& cursor, + uint32_t& cursorFileIdx, + std::vector& result, + const espm::CombineBrowser& browser, + uint32_t pcLevel); + + static uint32_t EvaluateAndSelectNpcId( + const espm::CombineBrowser& browser, + const espm::LookupResult& templateResult, uint32_t pcLevel); +}; diff --git a/unit/LeveledListUtilsTest.cpp b/unit/LeveledListUtilsTest.cpp index 5716c9be57..7a9a33d50d 100644 --- a/unit/LeveledListUtilsTest.cpp +++ b/unit/LeveledListUtilsTest.cpp @@ -10,7 +10,6 @@ extern espm::Loader l; -using namespace LeveledListUtils; using namespace std::chrono_literals; TEST_CASE("Bug", "[espm]") @@ -48,8 +47,8 @@ TEST_CASE("Bug", "[espm]") for (auto lvli : leveled) { for (int i = 0; i < 100; ++i) { const int pcLevel = 1, count = 1; - auto result = - EvaluateListRecurse(l.GetBrowser(), lvli, count, pcLevel, nullptr); + auto result = LeveledListUtils::EvaluateListRecurse( + l.GetBrowser(), lvli, count, pcLevel, nullptr); } } finished = true; @@ -65,7 +64,7 @@ TEST_CASE("Evaluate LItemFoodCabbage75", "[espm]") int totalItems = 0; for (int i = 0; i < 100000; ++i) { - auto res = EvaluateList(br, leveledList); + auto res = LeveledListUtils::EvaluateList(br, leveledList); if (res.size() > 0) { REQUIRE(res.size() == 1); REQUIRE(res[0].formId == FoodCabbage); @@ -86,14 +85,14 @@ TEST_CASE("Evaluate recurse LItemFoodCabbage", "[espm]") auto leveledList = br.LookupById(LItemFoodCabbage); for (int i = 0; i < 100; i++) { - auto res = EvaluateListRecurse(br, leveledList); + auto res = LeveledListUtils::EvaluateListRecurse(br, leveledList); REQUIRE(res.size() == 1); REQUIRE(res[FoodCabbage] >= 1); REQUIRE(res[FoodCabbage] <= 5); } // "Calculate for each" flag is not set - auto r = EvaluateListRecurse(br, leveledList, 10); + auto r = LeveledListUtils::EvaluateListRecurse(br, leveledList, 10); REQUIRE(r.size() == 1); REQUIRE(r[FoodCabbage] % 10 == 0); } @@ -108,7 +107,7 @@ TEST_CASE("Evaluate LootGoldChange25", "[espm]") int n[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; for (int i = 0; i < 100000; ++i) { - auto res = EvaluateList(br, leveledList); + auto res = LeveledListUtils::EvaluateList(br, leveledList); if (res.size() == 0) { n[0]++; } @@ -132,7 +131,7 @@ TEST_CASE("Evaluate DeathItemDraugr", "[espm]") auto& br = l.GetBrowser(); auto leveledList = br.LookupById(DeathItemDraugr); - auto res = EvaluateList(br, leveledList); + auto res = LeveledListUtils::EvaluateList(br, leveledList); REQUIRE(res.size() == 5); } @@ -143,12 +142,12 @@ TEST_CASE("Evaluate LItemWeaponDaggerTown", "[espm]") auto leveledList = br.LookupById(LItemWeaponDaggerTown); // Normally this leveled list returns one of daggers (iron, steel, etc) - auto res = EvaluateListRecurse(br, leveledList, 1); + auto res = LeveledListUtils::EvaluateListRecurse(br, leveledList, 1); REQUIRE(res.size() == 1); // But if we increase count, we will get a lot of different daggers // The reason is "Each" flag is set - res = EvaluateListRecurse(br, leveledList, 1000); + res = LeveledListUtils::EvaluateListRecurse(br, leveledList, 1000); REQUIRE(res.size() == 5); REQUIRE(res[0x1397e] > 100); @@ -158,7 +157,7 @@ TEST_CASE("Evaluate LItemWeaponDaggerTown", "[espm]") REQUIRE(res[0x1399e] > 30); // PlayerCharacter's level is 1. Only IronDagger should be generated - res = EvaluateListRecurse(br, leveledList, 1000, 1); + res = LeveledListUtils::EvaluateListRecurse(br, leveledList, 1000, 1); REQUIRE(res.size() == 1); REQUIRE(res[0x1397e] == 1000); } @@ -172,7 +171,7 @@ TEST_CASE("Evaluate LCharHorse", "[espm]") std::map results; for (int i = 0; i < 1000000; ++i) { - auto res = EvaluateListRecurse(br, leveledList, 1); + auto res = LeveledListUtils::EvaluateListRecurse(br, leveledList, 1); REQUIRE(res.size() == 1); auto pair = *res.begin(); @@ -199,7 +198,7 @@ TEST_CASE("Evaluate LvlHorse template", "[espm]") std::map results; for (int i = 0; i < 1000000; ++i) { - auto res = EvaluateTemplateChain(br, horse, 1); + auto res = LeveledListUtils::EvaluateTemplateChain(br, horse, 1); REQUIRE(res.size() == 2); REQUIRE(res[0] == LvlHorse); @@ -227,7 +226,8 @@ TEST_CASE("Evaluate LvlMudcrab template", "[espm]") for (int i = 0; i < 100'000; i++) { constexpr int kPcLevel = 5; - auto chain = EvaluateTemplateChain(br, mudcrab, kPcLevel); + auto chain = + LeveledListUtils::EvaluateTemplateChain(br, mudcrab, kPcLevel); REQUIRE(chain.size() == 2); REQUIRE(chain[0] == 0x8cacc); ++countByFormId[chain[1]]; diff --git a/unit/VirtualMachineTest.cpp b/unit/VirtualMachineTest.cpp index 70ce808f0d..98c772cef9 100644 --- a/unit/VirtualMachineTest.cpp +++ b/unit/VirtualMachineTest.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace fs = std::filesystem; @@ -45,16 +46,20 @@ std::shared_ptr CreateVirtualMachine() auto vm = std::make_shared(vector); - std::shared_ptr assertId(new int(1)); + std::shared_ptr assertId = std::make_shared(1); + std::shared_ptr ss = + std::make_shared(); + vm->RegisterFunction("", "Print", FunctionType::GlobalFunction, [=](VarValue self, const std::vector args) { if (args.size() >= 1) { - std::string showString = (const char*)args[0]; - std::cout << std::endl - << "[!] Papyrus says: " << showString - << std::endl - << std::endl; - (*assertId) = 1; + std::string showString = + static_cast(args[0]); + *ss << std::endl + << "[!] Papyrus says: " << showString + << std::endl + << std::endl; + *assertId = 1; } return VarValue::None(); }); @@ -62,15 +67,16 @@ std::shared_ptr CreateVirtualMachine() vm->RegisterFunction("", "Assert", FunctionType::GlobalFunction, [=](VarValue self, std::vector args) { if (args.size() >= 1) { - bool success = (bool)args[0]; + bool success = static_cast(args[0]); std::string message = "\t Assertion " + std::string(success ? "succeed" : "failed") + " (" + std::to_string(*assertId) + ")"; - (*assertId)++; + ++*assertId; if (!success) { + std::cout << ss->str(); throw std::runtime_error(message); } - std::cout << message << std::endl; + *ss << message << std::endl; } return VarValue::None(); }); From d658e0e7b585a67d7fff8ceadaf06c23457e45f0 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Tue, 3 Oct 2023 10:33:53 +0600 Subject: [PATCH 04/88] deliver template chain to the client & fix hosted.includes(remoteId + 0x100000000) --- skymp5-client/src/view/formView.ts | 10 +++-- .../cpp/server_guest_lib/MpActor.cpp | 45 +++++++++++++++++++ skymp5-server/cpp/server_guest_lib/MpActor.h | 2 + .../cpp/server_guest_lib/MpChangeForms.cpp | 33 +++++++++++++- .../cpp/server_guest_lib/MpChangeForms.h | 3 +- 5 files changed, 87 insertions(+), 6 deletions(-) diff --git a/skymp5-client/src/view/formView.ts b/skymp5-client/src/view/formView.ts index b3880d1f63..f0e2176b70 100644 --- a/skymp5-client/src/view/formView.ts +++ b/skymp5-client/src/view/formView.ts @@ -187,6 +187,9 @@ export class FormView implements View { private applyAll(refr: ObjectReference, model: FormModel) { let forcedWeapDrawn: boolean | null = null; + // @ts-ignore + // printConsole(model.templateChain); + if (PlayerCharacterDataHolder.getCrosshairRefId() === this.refrId) { this.lastHarvestedApply = 0; this.lastOpenApply = 0; @@ -228,8 +231,8 @@ export class FormView implements View { let alreadyHosted = false; if (Array.isArray(hosted)) { const remoteId = localIdToRemoteId(this.refrId); - - if (hosted.includes(remoteId) || hosted.includes(remoteId + 0x100000000)) { + + if (hosted.includes(remoteId) || hosted.includes(remoteId + 0x100000000)) { alreadyHosted = true; } // printConsole("remoteId=", remoteId.toString(16), "hosted=", hosted.map(x => x.toString(16))); @@ -300,7 +303,7 @@ export class FormView implements View { let alreadyHosted = false; if (Array.isArray(hosted)) { const remoteId = localIdToRemoteId(ac.getFormID()); - if (hosted.includes(remoteId)) { + if (hosted.includes(remoteId) || hosted.includes(remoteId + 0x100000000)) { alreadyHosted = true; } } @@ -454,6 +457,7 @@ export class FormView implements View { getMovement(Game.getPlayer() as Actor).worldOrCell ) { tryHost(remoteId); + printConsole(remoteId.toString(16)) return true; } } diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index 5ab9072c8e..9b80e688b9 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -6,6 +6,7 @@ #include "EspmGameObject.h" #include "FormCallbacks.h" #include "GetBaseActorValues.h" +#include "LeveledListUtils.h" #include "MathUtils.h" #include "MpChangeForms.h" #include "MsgType.h" @@ -141,6 +142,18 @@ void MpActor::VisitProperties(const PropertiesVisitor& visitor, nlohmann::json(changeForm.learnedSpells.GetLearnedSpells()) .dump() .c_str()); + + if (!changeForm.templateChain.empty()) { + // should be faster than nlohmann::json + std::string jsonDump = "["; + for (auto& element : changeForm.templateChain) { + jsonDump += std::to_string(element.ToFormId(GetParent()->espmFiles)); + jsonDump += ','; + } + jsonDump.pop_back(); // comma + jsonDump += "]"; + visitor("templateChain", jsonDump.data()); + } } void MpActor::Disable() @@ -564,6 +577,37 @@ bool MpActor::CanActorValueBeRestored(espm::ActorValue av) return true; } +void MpActor::EnsureTemplateChainEvaluated(espm::Loader& loader) +{ + constexpr auto kPcLevel = 0; + + auto worldState = GetParent(); + if (!worldState) { + return; + } + + auto baseId = GetBaseId(); + if (baseId == 0x7 || baseId == 0) { + return; + } + + if (!ChangeForm().templateChain.empty()) { + return; + } + + EditChangeForm([&](MpChangeFormREFR& changeForm) { + auto headNpc = loader.GetBrowser().LookupById(baseId); + std::vector res = LeveledListUtils::EvaluateTemplateChain( + loader.GetBrowser(), headNpc, kPcLevel); + std::vector templateChain(res.size()); + std::transform( + res.begin(), res.end(), templateChain.begin(), [&](uint32_t formId) { + return FormDesc::FromFormId(formId, worldState->espmFiles); + }); + changeForm.templateChain = std::move(templateChain); + }); +} + std::chrono::steady_clock::time_point MpActor::GetLastRestorationTime( espm::ActorValue av) const noexcept { @@ -615,6 +659,7 @@ void MpActor::Init(WorldState* worldState, uint32_t formId, bool hasChangeForm) if (worldState->HasEspm()) { EnsureBaseContainerAdded(GetParent()->GetEspm()); + EnsureTemplateChainEvaluated(GetParent()->GetEspm()); } } diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index 98eb7d7464..7ef5a6095a 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -149,6 +149,8 @@ class MpActor : public MpObjectReference std::chrono::steady_clock::time_point timePoint); bool CanActorValueBeRestored(espm::ActorValue av); + void EnsureTemplateChainEvaluated(espm::Loader& loader); + protected: void BeforeDestroy() override; void Init(WorldState* parent, uint32_t formId, bool hasChangeForm) override; diff --git a/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp b/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp index b7d78d0f84..3e17bac991 100644 --- a/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp @@ -1,6 +1,23 @@ #include "MpChangeForms.h" #include "JsonUtils.h" +namespace { +std::vector ToStringArray(const std::vector& formDescs) +{ + std::vector res(formDescs.size()); + std::transform(formDescs.begin(), formDescs.end(), res.begin(), + [](const FormDesc& v) { return v.ToString(); }); + return res; +} +std::vector ToFormDescsArray(const std::vector& strings) +{ + std::vector res(strings.size()); + std::transform(strings.begin(), strings.end(), res.begin(), + [](const std::string& v) { return FormDesc::FromString(v); }); + return res; +} +} + nlohmann::json MpChangeForm::ToJson(const MpChangeForm& changeForm) { auto res = nlohmann::json::object(); @@ -55,6 +72,10 @@ nlohmann::json MpChangeForm::ToJson(const MpChangeForm& changeForm) res["spawnDelay"] = changeForm.spawnDelay; res["effects"] = changeForm.activeMagicEffects.ToJson(); + + if (!changeForm.templateChain.empty()) { + res["templateChain"] = ToStringArray(changeForm.templateChain); + } return res; } @@ -74,7 +95,8 @@ MpChangeForm MpChangeForm::JsonToChangeForm(simdjson::dom::element& element) consoleCommandsAllowed("consoleCommandsAllowed"), spawnPointPos("spawnPoint_pos"), spawnPointRot("spawnPoint_rot"), spawnPointCellOrWorldDesc("spawnPoint_cellOrWorldDesc"), - spawnDelay("spawnDelay"), effects("effects"); + spawnDelay("spawnDelay"), effects("effects"), + templateChain("templateChain"); MpChangeForm res; ReadEx(element, recType, &res.recType); @@ -172,6 +194,13 @@ MpChangeForm MpChangeForm::JsonToChangeForm(simdjson::dom::element& element) ReadEx(element, effects, &jTmp); res.activeMagicEffects = ActiveMagicEffectsMap::FromJson(jTmp); } + + if (element.at_pointer(templateChain.GetData()).error() == + simdjson::error_code::SUCCESS) { + std::vector data; + ReadVector(element, templateChain, &data); + res.templateChain = ToFormDescsArray(data); + } return res; } @@ -187,7 +216,7 @@ size_t LearnedSpells::Count() const noexcept bool LearnedSpells::IsSpellLearned(const Data::key_type baseId) const { - return _learnedSpellIds.contains(baseId); + return _learnedSpellIds.count(baseId) != 0; } std::vector LearnedSpells::GetLearnedSpells() diff --git a/skymp5-server/cpp/server_guest_lib/MpChangeForms.h b/skymp5-server/cpp/server_guest_lib/MpChangeForms.h index e5f0c1fd08..7ba8961b1d 100644 --- a/skymp5-server/cpp/server_guest_lib/MpChangeForms.h +++ b/skymp5-server/cpp/server_guest_lib/MpChangeForms.h @@ -88,6 +88,7 @@ class MpChangeFormREFR { 0.f, 0.f, 72.f }, FormDesc::Tamriel() }; float spawnDelay = 25.0f; + std::vector templateChain; // Please update 'ActorTest.cpp' when adding new Actor-related rows @@ -101,7 +102,7 @@ class MpChangeFormREFR baseContainerAdded, nextRelootDatetime, isDisabled, profileId, isRaceMenuOpen, isDead, consoleCommandsAllowed, appearanceDump, equipmentDump, actorValues.ToTuple(), spawnPoint, dynamicFields, - spawnDelay, learnedSpells); + spawnDelay, learnedSpells, templateChain); } static nlohmann::json ToJson(const MpChangeFormREFR& changeForm); From 541822870c8c094dc7bfd45d69c57c078c7d9bbe Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Wed, 4 Oct 2023 05:33:43 +0600 Subject: [PATCH 05/88] wip --- skymp5-client/src/view/formView.ts | 63 +++++++-- .../codegen/convert-files/FunctionsDump.txt | 19 +++ .../codegen/convert-files/skyrimPlatform.ts | 1 + .../src/platform_se/pex/TESModPlatform.pex | Bin 2276 -> 2377 bytes .../src/platform_se/psc/TESModPlatform.psc | 2 + .../skyrim_platform/PapyrusTESModPlatform.cpp | 130 ++++++++++++++---- .../skyrim_platform/PapyrusTESModPlatform.h | 4 + 7 files changed, 181 insertions(+), 38 deletions(-) diff --git a/skymp5-client/src/view/formView.ts b/skymp5-client/src/view/formView.ts index f0e2176b70..9da5447db1 100644 --- a/skymp5-client/src/view/formView.ts +++ b/skymp5-client/src/view/formView.ts @@ -93,16 +93,42 @@ export class FormView implements View { } } } else { + // @ts-ignore + const templateChain: number[] | undefined = model.templateChain; + + let baselvl = Game.getFormEx(this.getLeveledBase(templateChain)); + let baseNormal = Game.getFormEx(+(model.baseId as number)); + //let baseNormal = null; + let baseAppearance = Game.getFormEx(this.getAppearanceBasedBase()); + + // printConsole(baselvl, baseNormal,baseAppearance ) + const base = - Game.getFormEx(+(model.baseId as number)) || - Game.getFormEx(this.getAppearanceBasedBase()); + baselvl || + baseNormal || + baseAppearance; if (!base) return; let refr = ObjectReference.from(Game.getFormEx(this.refrId)); - const respawnRequired = - !refr || - !refr.getBaseObject() || - (refr.getBaseObject() as Form).getFormID() !== base.getFormID(); + + let respawnRequired = false; + + if (!refr) { + respawnRequired = true; + printConsole("1"); + } + else if (!refr.getBaseObject()) { + respawnRequired = true; + printConsole("2"); + } + else if ((refr.getBaseObject() as Form).getFormID() !== base.getFormID()) { + respawnRequired = true; + printConsole(`${(refr.getBaseObject() as Form).getFormID().toString(16)} ${base.getFormID().toString(16)}`); + } + + + // @ts-ignore + // printConsole(`${!refr} ${!refr || !refr.getBaseObject()} ${!refr || (refr.getBaseObject() as Form).getFormID() !== base.getFormID()}`); if (respawnRequired) { this.destroy(); @@ -239,12 +265,6 @@ export class FormView implements View { } setDefaultAnimsDisabled(this.refrId, alreadyHosted ? false : true); - // if (model.baseId === 0x7 || !model.baseId) { - // setDefaultAnimsDisabled(this.refrId, true); - // } - // else { - // setDefaultAnimsDisabled(this.refrId, false); - // } if (alreadyHosted) { Actor.from(refr)?.clearKeepOffsetFromActor(); } @@ -439,6 +459,24 @@ export class FormView implements View { return this.appearanceBasedBaseId; } + private getLeveledBase(templateChain: number[] | undefined): number { + if (templateChain === undefined) return 0; + + const str = templateChain.join(','); + + if (this.leveledBaseId === 0) { + const leveledBase = TESModPlatform.evaluateLeveledNpc(str); + if (!leveledBase) { + printConsole("Failed to evaluate leveled npc", str); + } + this.leveledBaseId = leveledBase?.getFormID() || 0; + printConsole(this.leveledBaseId.toString(16)) + } + + printConsole(this.leveledBaseId.toString(16)); + return this.leveledBaseId; + } + private getDefaultEquipState() { return { lastNumChanges: 0, lastEqMoment: 0 }; }; @@ -484,6 +522,7 @@ export class FormView implements View { private appearanceState = this.getDefaultAppearanceState(); private eqState = this.getDefaultEquipState(); private appearanceBasedBaseId = 0; + private leveledBaseId = 0; private isOnScreen = false; private lastPcWorldOrCell = 0; private lastWorldOrCell = 0; diff --git a/skyrim-platform/src/platform_se/codegen/convert-files/FunctionsDump.txt b/skyrim-platform/src/platform_se/codegen/convert-files/FunctionsDump.txt index c02b091bbf..b857664f6f 100644 --- a/skyrim-platform/src/platform_se/codegen/convert-files/FunctionsDump.txt +++ b/skyrim-platform/src/platform_se/codegen/convert-files/FunctionsDump.txt @@ -21451,6 +21451,25 @@ }, "useLongSignature": true }, + { + "$comment": "hand-crafted entry", + "arguments": [ + { + "name": "commaSeparatedListOfIds", + "type": { + "rawType": "String" + } + } + ], + "isLatent": false, + "name": "EvaluateLeveledNpc", + "offset": "25744236640", + "returnType": { + "objectTypeName": "ActorBase", + "rawType": "Object" + }, + "useLongSignature": true + }, { "arguments": [ { diff --git a/skyrim-platform/src/platform_se/codegen/convert-files/skyrimPlatform.ts b/skyrim-platform/src/platform_se/codegen/convert-files/skyrimPlatform.ts index 400d1eace5..ebc70d0866 100644 --- a/skyrim-platform/src/platform_se/codegen/convert-files/skyrimPlatform.ts +++ b/skyrim-platform/src/platform_se/codegen/convert-files/skyrimPlatform.ts @@ -3229,6 +3229,7 @@ export declare class TESModPlatform extends PapyrusObject { static addItemEx(containerRefr: ObjectReference | null, item: Form | null, countDelta: number, health: number, enchantment: Enchantment | null, maxCharge: number, removeEnchantmentOnUnequip: boolean, chargePercent: number, textDisplayData: string, soul: number, poison: Potion | null, poisonCount: number): void static clearTintMasks(targetActor: Actor | null): void static createNpc(): ActorBase | null + static evaluateLeveledNpc(commaSeparatedListOfIds: string): ActorBase | null static getNthVtableElement(pointer: Form | null, pointerOffset: number, elementIndex: number): number static getSkinColor(base: ActorBase | null): ColorForm | null static isPlayerRunningEnabled(): boolean diff --git a/skyrim-platform/src/platform_se/pex/TESModPlatform.pex b/skyrim-platform/src/platform_se/pex/TESModPlatform.pex index 26c93d27c83b57518fc997943a954bf06afe87f3..c0f565efd28967c32b4549c1080f089119295450 100644 GIT binary patch literal 2377 zcmb7EXw^ATQ7@0^47SmWS3PUbvVwIbY zVTRLK$EZjg(9M;h2&`4j^q8qBZiY%|On5CAD#eAV>14t9!a};TxGIfIslO=&r}?*C z=;O!)J#KwOx+eEu@xauKOkCmsLDY?Sm^qyg{5bQvuwb3-GDVY8G(?!~pn2S-vJ^}F zAQGu;u5o0;Qas?%5nLVjxQ!m0X&Y(Hk~D^}#*W!(t*d08ZvBr{MpAV{9`P;SRok*H zb&^N&73NfFB}ameBRv5pKj3jpe%n88EBHy*;DCN-UX4CeM@p1>8yV zEa@Que?j@hFr<7_7|O93U>3Lw%mMd+`@jR>AuvyQHgm^zGq#(hYKNOW&IVE}D|ReU zM4SpNFj7%rF&X^6m0d;+Y?|J#Vf!&}#JrDW()d6|!f-|Y3aXB#vT3W^)!a~-r)EtN z@)Nq+)F{tWuC|6kpoSX4dJ4&3k}{^UEwRH$)MbpFi^-7q^!J(OR7^ryN@!hi+}GwH zplaiY#Y0Ti`4RRCt?XJL`+e5o1E!z|*CTCO-Daq%h=anQgHZAUhItJ(nF_a(jH{$i zcqKC)MWoMYrdwl?%A)O^(JNbAM}PA*&O!uW^rBMil*VnnLQI!Z^)`~0sjM@5&StnH zH0$Et?fazyRy0GJuE(4yYretsp{9~y>VTW{UdrvE?l~R#=5&D7(C@>HpWs3AHZwh% zvSui(>>2ndHN0rsuk)@!X2Y;)c)xK%Q+WG^MFLl3E7p%7rpZ7Kg<0mYVfYU?i{Y|_ z0`4(kk{T#A&P|Gac2e)zAnCf|eLRM9+LqW6{I6j&z)p!xo2$Tr|IL;aq$QrWDP2C-aif8Ad>QXF& z!!{e7s-aGvwx+_ql_h`a%iWpR^d7!Jk9_;LS~B5C)sdPbw;h>vNqr5#7T7Dd3!p?e+uH)PV8ngPe zdDlG}M-GZKnF*)_3y;bW<*#`(<mz=yy`z{h}XHTuLYD$}Q!d$@(m}(?9>1N literal 2276 zcmb7E+gjU35FXhw*kEHIXWFLjF+C*>A%TP*!5DA|;9>|#+O%CuYpf#gs;f1IK19E- zZ`8{^MIWMVIdTYt{qZ}a+5JX)Tl%e#>8@1$ zJ5lUWu_fb(hw_A~YmN5S{%-5`*3$Of`NhX{L{qN@AWw0F>$YZ^Q)xqLnZns^dBXR2 zSM5t=3v?g_r>e*cN~bi45gw{U2!bE6M0lZX1v?{lqOQR$2?Be%zPe_88%s<(j1rfcpUu3 zH#zeorgXfb6g#8o)Vd!CYcYZAAgSpeh?sRbP1Qrrl=)(t#m6y~TZ6b~RjL^6P%4p; zgVQ%P?>A1VdJy^8bmP}S5G7}w!dhI{rO+%8T+!@mD81tri_X*_Zk&J)B$r~99|c*q z2`L)FPcu(?QuqnQETpks;QL%qX~ol0t+JStyCVgsalwae9Fy`*ul%X4g@z1Kovybn-*7|>HDhL#aa+YX#O>Z+-@ufs4HlUaf!xSGPz8Xsa`jGz%4MB zA)(vtBqc`$1r^Qw1m~tw@4VYxW$Kxa!TzoFf$&VWn(i@OQyixkF%(|&1_~XaYjJY1 z$CsDWR(tenL_7?%*W)H6h-)%wrka*X2K++2pc`;M%TYA_m;NS}mrX}LWhiva!!F%V zS{&ddR_iL+r`!J{O3U<@mz(*EZs4EgP|o}U4ai&I zT2QcH%z|+XiWZbCn6O~dg0ck_3#KfnS}<+Fj0Lk6T(aP@1y?M%YQZ%Nu3KOZh2hYg zdFAL0hu$=*QU1X@LFcmr5+>-aRL=;YX}W0*4!un^6HD&MSfY36U3w4C`}6^QNVn)C z$j6XRAfG}$gP1Xn;AFu(*u716=yMqEW*IA^j5(UeChld9v5SrcTA=%xu_!$Y7^3=$4T02qYoU~LZlg2M#eHd0gEQ(HllXG*e;$H@OM~Z4|*G! z_MsopQ#!=kj1oPwr*-H#NHgQ|C~J;hAo}}klT#NRhT(); + auto npc = TESModPlatform::CreateForm(); if (!npc) { return nullptr; } - enum - { - AADeleteWhenDoneTestJeremyRegular = 0x0010D13E - }; - const auto srcNpc = - RE::TESForm::LookupByID(AADeleteWhenDoneTestJeremyRegular); - assert(srcNpc); - assert(srcNpc->formType.get() == RE::FormType::NPC); + const auto srcNpc = RE::TESForm::LookupByID(npcId); if (!srcNpc || srcNpc->formType != RE::FormType::NPC) { return nullptr; } @@ -249,21 +247,29 @@ RE::TESNPC* TESModPlatform::CreateNpc(IVM* vm, StackID stackId, npc_->actorData.actorBaseFlags.set(RE::ACTOR_BASE_DATA::Flag::kUnique); npc_->actorData.actorBaseFlags.set(RE::ACTOR_BASE_DATA::Flag::kSimpleActor); - // Clear AI Packages to prevent idle animations with Furniture - enum - { - DoNothing = 0x654e2, - DefaultMoveToCustom02IgnoreCombat = 0x6af62 - }; - // ignore combat && no - auto doNothing = RE::TESForm::LookupByID(DoNothing); - // combat alert - auto flagsSource = - RE::TESForm::LookupByID(DefaultMoveToCustom02IgnoreCombat); - - doNothing->packData = flagsSource->packData; - npc_->aiPackages.packages.clear(); - npc_->aiPackages.packages.push_front(doNothing); + switch (aiPackagesMode) { + case AiPackagesMode::ReplaceWithDoNothing: { + // Clear AI Packages to prevent idle animations with Furniture + enum + { + DoNothing = 0x654e2, + DefaultMoveToCustom02IgnoreCombat = 0x6af62 + }; + // ignore combat && no combat alert + auto doNothing = RE::TESForm::LookupByID(DoNothing); + auto flagsSource = RE::TESForm::LookupByID( + DefaultMoveToCustom02IgnoreCombat); + + doNothing->packData = flagsSource->packData; + npc_->aiPackages.packages.clear(); + npc_->aiPackages.packages.push_front(doNothing); + break; + } + case AiPackagesMode::KeepOriginal: + break; + default: + break; + } auto sourceFaceData = npc_->faceData; npc_->faceData = new RE::TESNPC::FaceData; @@ -275,6 +281,72 @@ RE::TESNPC* TESModPlatform::CreateNpc(IVM* vm, StackID stackId, return npc; } +static void FillFormsArray(std::vector& leak, + std::vector cursorStack) +{ + leak.reserve(cursorStack.size()); + for (auto it = cursorStack.rbegin(); it != cursorStack.rend(); ++it) { + leak.push_back(*it); + } +} + +RE::TESNPC* TESModPlatform::CreateNpc(IVM* vm, StackID stackId, + RE::StaticFunctionTag*) +{ + constexpr uint32_t kAADeleteWhenDoneTestJeremyRegular = 0x0010D13E; + return CloneNpc(kAADeleteWhenDoneTestJeremyRegular, + AiPackagesMode::ReplaceWithDoNothing); +} + +RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( + IVM* vm, StackID stackId, RE::StaticFunctionTag*, + FixedString commaSeparatedListOfIds) +{ + auto str = std::string(commaSeparatedListOfIds.data()); + + std::vector formIds; + formIds.reserve(10); + + std::istringstream iss(str); + std::string id; + while (std::getline(iss, id, ',')) { + auto formId = static_cast(atoll(id.data())); + const auto srcNpc = RE::TESForm::LookupByID(formId); + if (!srcNpc || srcNpc->formType != RE::FormType::NPC) { + return nullptr; + } + formIds.push_back(formId); + } + + std::vector cursorStack; + cursorStack.reserve(10); + + for (auto it = formIds.rbegin(); it != formIds.rend(); ++it) { + uint32_t formId = *it; + // TODO: replace with DoNothing for humanoids? and how to determine + // humanoids in case of leveled? + auto copiedNpc = CloneNpc(formId, AiPackagesMode::KeepOriginal); + if (!copiedNpc) { + return nullptr; + } + + if (cursorStack.size() > 0) { + cursorStack.back()->baseTemplateForm = copiedNpc; + auto leak = new std::vector(); + leak->reserve(10); + FillFormsArray(*leak, cursorStack); + cursorStack.back()->templateForms = leak->data(); + } + cursorStack.push_back(copiedNpc); + } + + if (cursorStack.empty()) { + return nullptr; + } + + return cursorStack.back(); +} + void TESModPlatform::SetNpcSex(IVM* vm, StackID stackId, RE::StaticFunctionTag*, RE::TESNPC* npc, int32_t sex) @@ -914,6 +986,12 @@ bool TESModPlatform::Register(IVM* vm) RE::StaticFunctionTag*>( "CreateNpc", "TESModPlatform", CreateNpc)); + vm->BindNativeMethod( + new RE::BSScript::NativeFunction( + "EvaluateLeveledNpc", "TESModPlatform", EvaluateLeveledNpc)); + vm->BindNativeMethod( new RE::BSScript::NativeFunction Date: Wed, 4 Oct 2023 19:45:18 +0600 Subject: [PATCH 06/88] LCharHorse works!!! --- skymp5-client/src/view/formView.ts | 14 ++++++++++---- .../skyrim_platform/PapyrusTESModPlatform.cpp | 12 +++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/skymp5-client/src/view/formView.ts b/skymp5-client/src/view/formView.ts index 9da5447db1..1deeff867f 100644 --- a/skymp5-client/src/view/formView.ts +++ b/skymp5-client/src/view/formView.ts @@ -94,7 +94,13 @@ export class FormView implements View { } } else { // @ts-ignore - const templateChain: number[] | undefined = model.templateChain; + let templateChain: number[] | undefined = model.templateChain; + + // There is no place for random/leveling in 1-sized chain + // Just spawn an NPC, do not generate a temporary TESNPC form + if (templateChain?.length === 1) { + templateChain = undefined; + } let baselvl = Game.getFormEx(this.getLeveledBase(templateChain)); let baseNormal = Game.getFormEx(+(model.baseId as number)); @@ -115,15 +121,15 @@ export class FormView implements View { if (!refr) { respawnRequired = true; - printConsole("1"); + // printConsole("1"); } else if (!refr.getBaseObject()) { respawnRequired = true; - printConsole("2"); + // printConsole("2"); } else if ((refr.getBaseObject() as Form).getFormID() !== base.getFormID()) { respawnRequired = true; - printConsole(`${(refr.getBaseObject() as Form).getFormID().toString(16)} ${base.getFormID().toString(16)}`); + // printConsole(`${(refr.getBaseObject() as Form).getFormID().toString(16)} ${base.getFormID().toString(16)}`); } diff --git a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp index 6e7e8c6995..e97c6db078 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp @@ -281,11 +281,11 @@ static RE::TESNPC* CloneNpc(uint32_t npcId, AiPackagesMode aiPackagesMode) return npc; } -static void FillFormsArray(std::vector& leak, +static void FillFormsArray(std::vector& leak, std::vector cursorStack) { leak.reserve(cursorStack.size()); - for (auto it = cursorStack.rbegin(); it != cursorStack.rend(); ++it) { + for (auto it = cursorStack.begin(); it != cursorStack.end(); ++it) { leak.push_back(*it); } } @@ -331,11 +331,13 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( } if (cursorStack.size() > 0) { - cursorStack.back()->baseTemplateForm = copiedNpc; - auto leak = new std::vector(); + copiedNpc->baseTemplateForm = cursorStack.back(); + + auto leak = new std::vector(); leak->reserve(10); FillFormsArray(*leak, cursorStack); - cursorStack.back()->templateForms = leak->data(); + + copiedNpc->CopyFromTemplateForms(leak->data()); } cursorStack.push_back(copiedNpc); } From 2880c67aabfbbbf3c7b7197930a65bda486bc305 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 5 Oct 2023 00:33:21 +0600 Subject: [PATCH 07/88] crashes --- .../skyrim_platform/PapyrusTESModPlatform.cpp | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp index e97c6db078..87f18aa856 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp @@ -281,7 +281,8 @@ static RE::TESNPC* CloneNpc(uint32_t npcId, AiPackagesMode aiPackagesMode) return npc; } -static void FillFormsArray(std::vector& leak, +template +static void FillFormsArray(VectorT& leak, std::vector cursorStack) { leak.reserve(cursorStack.size()); @@ -330,14 +331,40 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( return nullptr; } + { + std::stringstream ss; + ss << std::hex << "working on " << formId << "\n"; + auto str = ss.str(); + OutputDebugStringA(str.data()); + } + if (cursorStack.size() > 0) { + OutputDebugStringA("cursorStack.size() > 0\n"); + str = "cursorStack.size() is " + std::to_string(cursorStack.size()) + "\n"; + OutputDebugStringA(str.data()); + for (auto v : cursorStack) { + std::stringstream ss; + ss << " - " << v; + str = ss.str(); + OutputDebugStringA(str.data()); + } copiedNpc->baseTemplateForm = cursorStack.back(); - auto leak = new std::vector(); - leak->reserve(10); + // auto leak = new std::vector(); + // leak->reserve(10); + auto leak = new RE::BSTArray(); FillFormsArray(*leak, cursorStack); - copiedNpc->CopyFromTemplateForms(leak->data()); + // copiedNpc->CopyFromTemplateForms(leak->data()); + + auto leak2 = new RE::BSTArray(); + leak2->resize(100, 0); + + copiedNpc->templateForms = reinterpret_cast(leak2); + copiedNpc->CopyFromTemplateForms(reinterpret_cast(leak)); + } + else { + OutputDebugStringA("cursorStack.size() == 0\n"); } cursorStack.push_back(copiedNpc); } From 3455a9575d24a06483b3be882f44c55122225e41 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 5 Oct 2023 03:32:38 +0600 Subject: [PATCH 08/88] wip --- skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp | 3 ++- .../src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp index 318f3ddd36..098e3a5bac 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp @@ -359,7 +359,8 @@ void LoadGame::WriteChangeForm(std::shared_ptr save, std::copy(compressed.begin(), compressed.end(), changeForm.data.begin()); // fix offsets - const auto diff = (int64_t)previousSize - (int64_t)compressed.size(); + const auto diff = static_cast(previousSize) - + static_cast(compressed.size()); save->fileLocationTable.formIDArrayCountOffset -= diff; save->fileLocationTable.unknownTable3Offset -= diff; save->fileLocationTable.globalDataTable3Offset -= diff; diff --git a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp index 87f18aa856..c65cb470e4 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp @@ -344,7 +344,7 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( OutputDebugStringA(str.data()); for (auto v : cursorStack) { std::stringstream ss; - ss << " - " << v; + ss << " - " << v << std::endl; str = ss.str(); OutputDebugStringA(str.data()); } From dabf4fd5ea5d83d358e42f860487119b129b7f52 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 5 Oct 2023 03:35:14 +0600 Subject: [PATCH 09/88] tweak --- .../src/platform_se/skyrim_platform/LoadGame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp index 098e3a5bac..2339c9cb7a 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp @@ -293,10 +293,10 @@ SaveFile_::PlayerLocation LoadGame::CreatePlayerLocation( const std::array& pos, const SaveFile_::RefID& world) { SaveFile_::PlayerLocation r; - r.nextObjectId = 4278195454; + r.nextObjectId = 0xFF0014FE; r.worldspace1 = world; - r.coorX = (int)pos[0] / 4096; - r.coorY = (int)pos[1] / 4096; + r.coorX = static_cast(pos[0]) / 4096; + r.coorY = static_cast(pos[1]) / 4096; r.worldspace2 = world; r.posX = pos[0]; r.posY = pos[1]; From 29439ebd76412f8369eca820b47545c35a717f1f Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 5 Oct 2023 03:41:06 +0600 Subject: [PATCH 10/88] loadgame maintainance again --- .../src/platform_se/skyrim_platform/LoadGame.cpp | 10 +++++----- .../src/platform_se/skyrim_platform/LoadGame.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp index 2339c9cb7a..ca6b7e21a0 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.cpp @@ -147,9 +147,9 @@ void LoadGame::ModifyPluginInfo(std::shared_ptr& save) throw NullPointerException("dataHandler"); } - for (auto it = dataHandler->files.begin(); it != dataHandler->files.end(); - ++it) - newPlugins.push_back(std::string((*it)->fileName)); + for (auto& file : dataHandler->files) { + newPlugins.push_back(std::string(file->fileName)); + } save->OverwritePluginInfo(newPlugins); } @@ -366,7 +366,7 @@ void LoadGame::WriteChangeForm(std::shared_ptr save, save->fileLocationTable.globalDataTable3Offset -= diff; } -std::wstring LoadGame::StringToWstring(std::string s) +std::wstring LoadGame::StringToWstring(const std::string& s) { std::wstring ws(s.size(), L' '); auto n = std::mbstowcs(&ws[0], s.c_str(), s.size()); @@ -381,7 +381,7 @@ std::string LoadGame::GenerateGuid() throw std::runtime_error("CoCreateGuid failed"); } - char name[MAX_PATH] = { 0 }; + char name[37] = { 0 }; // Size adjusted for GUID string sprintf_s( name, "%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", diff --git a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h index b93aca3380..9da1341f29 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h +++ b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h @@ -46,7 +46,7 @@ class LoadGame static std::wstring GetPathToMyDocuments(); private: - static std::wstring StringToWstring(std::string s); + static std::wstring StringToWstring(const std::string &s); static std::string GenerateGuid(); From 34a5cb3d064695dc01536ed12e965798c285607b Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 5 Oct 2023 03:48:05 +0600 Subject: [PATCH 11/88] . --- docs/release/dev/sp-added-evaluate-lvl-character.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/release/dev/sp-added-evaluate-lvl-character.md diff --git a/docs/release/dev/sp-added-evaluate-lvl-character.md b/docs/release/dev/sp-added-evaluate-lvl-character.md new file mode 100644 index 0000000000..4469b3da68 --- /dev/null +++ b/docs/release/dev/sp-added-evaluate-lvl-character.md @@ -0,0 +1 @@ +Added experimental `TESModPlatform.EvaluateLeveledNpc` native. It is unstable and shouldn't be used in user plugins. This native is required for SkyMP. From a40449a7ec6663224293d2638f6bab994460fdf5 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:47:41 +0600 Subject: [PATCH 12/88] fmt --- .../cpp/server_guest_lib/MpObjectReference.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 933b99caba..285ff232ad 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1364,8 +1364,9 @@ std::vector GetInventoryObjects( espm::CompressedFieldsCache& compressedFieldsCache) { auto baseContainer = espm::Convert(lookupRes.rec); - if (baseContainer) + if (baseContainer) { return baseContainer->GetData(compressedFieldsCache).objects; + } auto baseNpc = espm::Convert(lookupRes.rec); if (baseNpc) { @@ -1387,8 +1388,9 @@ void MpObjectReference::AddContainerObject( auto map = LeveledListUtils::EvaluateListRecurse( espm.GetBrowser(), formLookupRes, kCountMult, kPlayerCharacterLevel, chanceNoneOverride.get()); - for (auto& p : map) + for (auto& p : map) { (*itemsToAdd)[p.first] += p.second; + } } else { (*itemsToAdd)[entry.formId] += entry.count; } @@ -1396,12 +1398,14 @@ void MpObjectReference::AddContainerObject( void MpObjectReference::EnsureBaseContainerAdded(espm::Loader& espm) { - if (ChangeForm().baseContainerAdded) + if (ChangeForm().baseContainerAdded) { return; + } auto worldState = GetParent(); - if (!worldState) + if (!worldState) { return; + } auto lookupRes = espm.GetBrowser().LookupById(GetBaseId()); From 7800f11e5de8149af65904bf13918ac761f763d0 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:48:20 +0600 Subject: [PATCH 13/88] fmt --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 285ff232ad..7d4076a210 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1436,8 +1436,9 @@ void MpObjectReference::EnsureBaseContainerAdded(espm::Loader& espm) } std::vector entries; - for (auto& p : itemsToAdd) + for (auto& p : itemsToAdd) { entries.push_back({ p.first, p.second }); + } AddItems(entries); if (!ChangeForm().baseContainerAdded) { @@ -1500,8 +1501,9 @@ void MpObjectReference::SendPropertyTo(const IMessageBase& preparedPropMsg, void MpObjectReference::BeforeDestroy() { - if (this->occupant && this->occupantDestroySink) + if (this->occupant && this->occupantDestroySink) { this->occupant->RemoveEventSink(this->occupantDestroySink); + } // Move far far away calling OnTriggerExit, unsubscribing, etc SetPos({ -1'000'000'000, 0, 0 }); From ed3798ce7ffb0ae05d9bd030fa5e0ecf97ec54f2 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:49:10 +0600 Subject: [PATCH 14/88] fmt --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 7d4076a210..f81f4bf2e0 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1141,11 +1141,11 @@ void MpObjectReference::ProcessActivate(MpObjectReference& activationSource) auto teleportWorldOrCell = destination.ToGlobalId( GetWorldOrCell(loader.GetBrowser(), destinationRecord)); - static const auto g_pi = std::acos(-1.f); + static const auto kPi = std::acos(-1.f); const auto& pos = teleport->pos; - const float rot[] = { teleport->rotRadians[0] / g_pi * 180, - teleport->rotRadians[1] / g_pi * 180, - teleport->rotRadians[2] / g_pi * 180 }; + const float rot[] = { teleport->rotRadians[0] / kPi * 180, + teleport->rotRadians[1] / kPi * 180, + teleport->rotRadians[2] / kPi * 180 }; TeleportMessage msg; msg.idx = activationSource.GetIdx(); From 6be0ef0828e9654cb4547d4e747c43afb1663cd0 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:49:45 +0600 Subject: [PATCH 15/88] fmt --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index f81f4bf2e0..738095e854 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1025,14 +1025,6 @@ void MpObjectReference::Init(WorldState* parent, uint32_t formId, { MpForm::Init(parent, formId, hasChangeForm); - // It crashed during sparsepp hashmap indexing. - // Not sure why. And not sure why this code actually been here. - // It seems that MoveOnGrid will be caled later. - /*if (!IsDisabled()) { - auto& gridInfo = GetParent()->grids[ChangeForm().worldOrCell]; - MoveOnGrid(*gridInfo.grid); - }*/ - // We should queue created form for saving as soon as it is initialized const auto mode = (!hasChangeForm && formId >= 0xff000000) ? Mode::RequestSave From 5f887d7d749620c26991512123ca17613293e30e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:50:57 +0600 Subject: [PATCH 16/88] fmt --- .../cpp/server_guest_lib/MpObjectReference.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 738095e854..922e2f96fc 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -812,8 +812,8 @@ void MpObjectReference::Unsubscribe(MpObjectReference* emitter, const std::set& MpObjectReference::GetListeners() const { - static const std::set g_emptyListeners; - return listeners ? *listeners : g_emptyListeners; + static const std::set kEmptyListeners; + return listeners ? *listeners : kEmptyListeners; } const std::set& MpObjectReference::GetActorListeners() const noexcept @@ -823,8 +823,8 @@ const std::set& MpObjectReference::GetActorListeners() const noexcept const std::set& MpObjectReference::GetEmitters() const { - static const std::set g_emptyEmitters; - return emitters ? *emitters : g_emptyEmitters; + static const std::set kEmptyEmitters; + return emitters ? *emitters : kEmptyEmitters; } void MpObjectReference::RequestReloot( @@ -867,10 +867,11 @@ std::shared_ptr> MpObjectReference::GetNextRelootMoment() const { std::shared_ptr> res; - if (ChangeForm().nextRelootDatetime) + if (ChangeForm().nextRelootDatetime) { res.reset(new std::chrono::time_point( std::chrono::system_clock::from_time_t( ChangeForm().nextRelootDatetime))); + } return res; } From d4e4a04196e3880c64134b874a1aca3b528c926b Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:51:20 +0600 Subject: [PATCH 17/88] fmt --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 922e2f96fc..97e610df96 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -797,8 +797,9 @@ void MpObjectReference::Unsubscribe(MpObjectReference* emitter, const bool hasPrimitive = emitter->HasPrimitive(); - if (!hasPrimitive) + if (!hasPrimitive) { emitter->callbacks->unsubscribe(emitter, listener); + } emitter->listeners->erase(listener); if (actorListener) { emitter->actorListeners.erase(actorListener); From caa85fb5f38036c031e08842bac04e27ed7c3416 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:52:22 +0600 Subject: [PATCH 18/88] fmt --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 97e610df96..20b2585020 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -775,12 +775,14 @@ void MpObjectReference::Subscribe(MpObjectReference* emitter, emitter->actorListeners.insert(actorListener); } listener->emitters->insert(emitter); - if (!hasPrimitive) + if (!hasPrimitive) { emitter->callbacks->subscribe(emitter, listener); + } if (hasPrimitive) { - if (!listener->emittersWithPrimitives) + if (!listener->emittersWithPrimitives) { listener->emittersWithPrimitives.reset(new std::map); + } listener->emittersWithPrimitives->insert({ emitter->GetFormId(), false }); } } From 36ca59961b6d28ea0f81afc499ca56ca4e2747c3 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 6 Oct 2023 22:52:54 +0600 Subject: [PATCH 19/88] fmt --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 20b2585020..fce2ad9c0e 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -753,8 +753,9 @@ void MpObjectReference::Subscribe(MpObjectReference* emitter, { auto actorEmitter = dynamic_cast(emitter); auto actorListener = dynamic_cast(listener); - if (!actorEmitter && !actorListener) + if (!actorEmitter && !actorListener) { return; + } // I don't know how often Subscrbe is called but I suppose // it is to be invoked quite frequently. In this case, each From 459607bfd4929f37c2bffe6019c3e2c94e84dc58 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 7 Oct 2023 02:57:23 +0600 Subject: [PATCH 20/88] fuu go back --- skymp5-client/src/modelSource/model.ts | 1 + skymp5-client/src/view/formView.ts | 35 ++++--------------- .../skyrim_platform/PapyrusTESModPlatform.cpp | 15 ++++++-- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/skymp5-client/src/modelSource/model.ts b/skymp5-client/src/modelSource/model.ts index eb3c0f6701..e7fd873a3d 100644 --- a/skymp5-client/src/modelSource/model.ts +++ b/skymp5-client/src/modelSource/model.ts @@ -19,6 +19,7 @@ export interface FormModel { inventory?: Inventory; isHostedByOther?: boolean; isDead?: boolean; + templateChain?: number[]; // Assigned locally isMyClone?: boolean; diff --git a/skymp5-client/src/view/formView.ts b/skymp5-client/src/view/formView.ts index 1deeff867f..4f595496e5 100644 --- a/skymp5-client/src/view/formView.ts +++ b/skymp5-client/src/view/formView.ts @@ -93,8 +93,7 @@ export class FormView implements View { } } } else { - // @ts-ignore - let templateChain: number[] | undefined = model.templateChain; + let templateChain = model.templateChain; // There is no place for random/leveling in 1-sized chain // Just spawn an NPC, do not generate a temporary TESNPC form @@ -102,40 +101,25 @@ export class FormView implements View { templateChain = undefined; } - let baselvl = Game.getFormEx(this.getLeveledBase(templateChain)); - let baseNormal = Game.getFormEx(+(model.baseId as number)); - //let baseNormal = null; - let baseAppearance = Game.getFormEx(this.getAppearanceBasedBase()); - - // printConsole(baselvl, baseNormal,baseAppearance ) - - const base = - baselvl || - baseNormal || - baseAppearance; - if (!base) return; + // TODO: getLeveledBase crashes too often ATM + let base = null; //Game.getFormEx(this.getLeveledBase(templateChain)); + if (base === null) base = Game.getFormEx(model.baseId || NaN); + if (base === null) base = Game.getFormEx(this.getAppearanceBasedBase()); + if (base === null) return; let refr = ObjectReference.from(Game.getFormEx(this.refrId)); let respawnRequired = false; - if (!refr) { respawnRequired = true; - // printConsole("1"); } else if (!refr.getBaseObject()) { respawnRequired = true; - // printConsole("2"); } else if ((refr.getBaseObject() as Form).getFormID() !== base.getFormID()) { respawnRequired = true; - // printConsole(`${(refr.getBaseObject() as Form).getFormID().toString(16)} ${base.getFormID().toString(16)}`); } - - // @ts-ignore - // printConsole(`${!refr} ${!refr || !refr.getBaseObject()} ${!refr || (refr.getBaseObject() as Form).getFormID() !== base.getFormID()}`); - if (respawnRequired) { this.destroy(); refr = (Game.getPlayer() as Actor).placeAtMe( @@ -219,9 +203,6 @@ export class FormView implements View { private applyAll(refr: ObjectReference, model: FormModel) { let forcedWeapDrawn: boolean | null = null; - // @ts-ignore - // printConsole(model.templateChain); - if (PlayerCharacterDataHolder.getCrosshairRefId() === this.refrId) { this.lastHarvestedApply = 0; this.lastOpenApply = 0; @@ -267,7 +248,6 @@ export class FormView implements View { if (hosted.includes(remoteId) || hosted.includes(remoteId + 0x100000000)) { alreadyHosted = true; } - // printConsole("remoteId=", remoteId.toString(16), "hosted=", hosted.map(x => x.toString(16))); } setDefaultAnimsDisabled(this.refrId, alreadyHosted ? false : true); @@ -476,10 +456,8 @@ export class FormView implements View { printConsole("Failed to evaluate leveled npc", str); } this.leveledBaseId = leveledBase?.getFormID() || 0; - printConsole(this.leveledBaseId.toString(16)) } - printConsole(this.leveledBaseId.toString(16)); return this.leveledBaseId; } @@ -501,7 +479,6 @@ export class FormView implements View { getMovement(Game.getPlayer() as Actor).worldOrCell ) { tryHost(remoteId); - printConsole(remoteId.toString(16)) return true; } } diff --git a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp index c65cb470e4..a882007a33 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp @@ -305,6 +305,12 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( { auto str = std::string(commaSeparatedListOfIds.data()); + thread_local std::unordered_map g_cache; + auto &cachedNpc = g_cache[str]; + if (cachedNpc) { + return cachedNpc; + } + std::vector formIds; formIds.reserve(10); @@ -349,6 +355,7 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( OutputDebugStringA(str.data()); } copiedNpc->baseTemplateForm = cursorStack.back(); + //copiedNpc->baseTemplateForm = nullptr; // auto leak = new std::vector(); // leak->reserve(10); @@ -360,8 +367,9 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( auto leak2 = new RE::BSTArray(); leak2->resize(100, 0); - copiedNpc->templateForms = reinterpret_cast(leak2); - copiedNpc->CopyFromTemplateForms(reinterpret_cast(leak)); + //copiedNpc->templateForms = reinterpret_cast(leak2); + + copiedNpc->CopyFromTemplateForms(reinterpret_cast(leak)); } else { OutputDebugStringA("cursorStack.size() == 0\n"); @@ -373,7 +381,8 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( return nullptr; } - return cursorStack.back(); + cachedNpc = cursorStack.back(); + return cachedNpc; } void TESModPlatform::SetNpcSex(IVM* vm, StackID stackId, From d4206bdeea331986211b918f55e8471bb5da50c8 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 7 Oct 2023 07:35:39 +0600 Subject: [PATCH 21/88] it seems template race/npc health works now --- libespm/include/libespm/NPC_.h | 38 +++++++++ .../cpp/server_guest_lib/ActionListener.cpp | 4 +- .../cpp/server_guest_lib/CropRegeneration.cpp | 3 +- .../server_guest_lib/GetBaseActorValues.cpp | 81 ++++++++++++++++--- .../cpp/server_guest_lib/GetBaseActorValues.h | 3 +- .../cpp/server_guest_lib/MpActor.cpp | 26 +++--- skymp5-server/cpp/server_guest_lib/MpActor.h | 1 + unit/ChangeValuesTest.cpp | 2 +- unit/CropRegenerationTest.cpp | 2 +- unit/GetBaseActorValuesTest.cpp | 2 +- 10 files changed, 136 insertions(+), 26 deletions(-) diff --git a/libespm/include/libespm/NPC_.h b/libespm/include/libespm/NPC_.h index 85996ce766..048ca59356 100644 --- a/libespm/include/libespm/NPC_.h +++ b/libespm/include/libespm/NPC_.h @@ -19,6 +19,44 @@ class NPC_ final : public RecordHeader int8_t rank = 0; }; + enum TemplateFlags : uint16_t + { + // (Destructible Object; Traits tab, including race, gender, height, + // weight, voice type, death item; Sounds tab; Animation tab; Character Gen + // tabs) + UseTraits = 0x01, + // (Stats tab, including level, autocalc, skills, health/magicka/stamina, + // speed, bleedout, class) + UseStats = 0x02, + // (both factions and assigned crime faction) + UseFactions = 0x04, + // (both spells and perks) + UseSpelllist = 0x08, + // (AI Data tab, including aggression/confidence/morality, combat style and + // gift filter) + UseAIData = 0x10, + // (only the basic Packages listed on the AI Packages tab; rest of tab + // controlled by Def Pack List) + UseAIPackages = 0x20, + // Unused? + Unused = 0x40, + // (including name and short name, and flags for Essential, Protected, + // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter) + UseBaseData = 0x80, + // (Inventory tab, including all outfits and geared-up item -- but not + // death item) + UseInventory = 0x100, + // Scripts + UseScript = 0x200, + // (the dropdown-selected package lists on the AI Packages tab) + UseDefPackList = 0x400, + // (Attack Data tab, including override from behavior graph race, events, + // and data) + UseAttackData = 0x800, + // Keywords + UseKeywords = 0x1000 + }; + struct Data { uint32_t defaultOutfitId = 0; diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp index 3a98e1f88a..effd1f7e00 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp @@ -634,7 +634,9 @@ float CalculateCurrentHealthPercentage(const MpActor& actor, float damage, uint32_t baseId = actor.GetBaseId(); uint32_t raceId = actor.GetRaceId(); WorldState* espmProvider = actor.GetParent(); - float baseHealth = GetBaseActorValues(espmProvider, baseId, raceId).health; + float baseHealth = + GetBaseActorValues(espmProvider, baseId, raceId, actor.GetTemplateChain()) + .health; if (outBaseHealth) { *outBaseHealth = baseHealth; diff --git a/skymp5-server/cpp/server_guest_lib/CropRegeneration.cpp b/skymp5-server/cpp/server_guest_lib/CropRegeneration.cpp index 69bca8cfe8..4ec68f0c08 100644 --- a/skymp5-server/cpp/server_guest_lib/CropRegeneration.cpp +++ b/skymp5-server/cpp/server_guest_lib/CropRegeneration.cpp @@ -12,7 +12,8 @@ BaseActorValues GetValues(MpActor* actor) auto appearance = actor->GetAppearance(); uint32_t raceId = appearance ? appearance->raceId : 0; auto worldState = actor->GetParent(); - return GetBaseActorValues(worldState, baseId, raceId); + return GetBaseActorValues(worldState, baseId, raceId, + actor->GetTemplateChain()); } } diff --git a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp index afe2d2a134..09a69df211 100644 --- a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp +++ b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp @@ -27,39 +27,99 @@ void BaseActorValues::VisitBaseActorValues(BaseActorValues& baseActorValues, std::to_string(changeForm.actorValues.magickaPercentage).c_str()); } +namespace { +template +auto EvaluateTemplate(WorldState* worldState, uint32_t baseId, + const std::vector& templateChain, + const Callback& callback) +{ + const std::vector chainDefault = { FormDesc::FromFormId( + baseId, worldState->espmFiles) }; + const std::vector& chain = + templateChain.size() > 0 ? templateChain : chainDefault; + + for (auto it = chain.begin(); it != chain.end(); it++) { + auto templateChainElement = it->ToFormId(worldState->espmFiles); + auto npcLookupResult = + worldState->GetEspm().GetBrowser().LookupById(templateChainElement); + auto npc = espm::Convert(npcLookupResult.rec); + auto npcData = npc->GetData(worldState->GetEspmCache()); + + if (npcData.baseTemplate == 0) { + return callback(npcLookupResult, npcData); + } + + if (!(npcData.templateDataFlags & TemplateFlag)) { + return callback(npcLookupResult, npcData); + } + } + + std::stringstream ss; + ss << "EvaluateTemplate failed: baseId=" << std::hex << baseId + << ", templateChain="; + + for (size_t i = 0; i < templateChain.size(); ++i) { + ss << templateChain[i].ToString(); + if (i != templateChain.size() - 1) { + ss << ","; + } + } + + ss << ", templateFlag=" << TemplateFlag; + + throw std::runtime_error(ss.str()); +} +} + BaseActorValues GetBaseActorValues(WorldState* worldState, uint32_t baseId, - uint32_t raceIdOverride) + uint32_t raceIdOverride, + const std::vector& templateChain) { + auto npcData = espm::GetData(baseId, worldState); - uint32_t raceID = raceIdOverride ? raceIdOverride : npcData.race; - auto raceData = espm::GetData(raceID, worldState); + + uint32_t raceId = raceIdOverride + ? raceIdOverride + : EvaluateTemplate( + worldState, baseId, templateChain, + [](const auto& npcLookupResult, const auto& npcData) { + return npcLookupResult.ToGlobalId(npcData.race); + }); + auto raceData = espm::GetData(raceId, worldState); + + espm::NPC_::Data attributesNpcData = EvaluateTemplate( + worldState, baseId, templateChain, + [](const auto&, const auto& npcData) { return npcData; }); BaseActorValues actorValues; - actorValues.health = raceData.startingHealth + npcData.healthOffset; + actorValues.health = + raceData.startingHealth + attributesNpcData.healthOffset; if (actorValues.health <= 0) { spdlog::warn("GetBaseActorValues {:x} {:x} - Negative Health found: " "startingHealth={}, healthOffset={}, defaulting to 100", baseId, raceIdOverride, raceData.startingHealth, - npcData.healthOffset); + attributesNpcData.healthOffset); actorValues.health = 100.f; } - actorValues.magicka = raceData.startingMagicka + npcData.magickaOffset; + actorValues.magicka = + raceData.startingMagicka + attributesNpcData.magickaOffset; if (actorValues.magicka <= 0) { spdlog::warn("GetBaseActorValues {:x} {:x} - Negative Magicka found: " "startingMagicka={}, magickaOffset={}, defaulting to 100", baseId, raceIdOverride, raceData.startingMagicka, - npcData.magickaOffset); + attributesNpcData.magickaOffset); actorValues.magicka = 100.f; } - actorValues.stamina = raceData.startingStamina + npcData.staminaOffset; + actorValues.stamina = + raceData.startingStamina + attributesNpcData.staminaOffset; if (actorValues.stamina <= 0) { spdlog::warn("GetBaseActorValues {:x} {:x} - Negative Stamina found: " "startingStamina={}, staminaOffset={}, defaulting to 100", baseId, raceIdOverride, raceData.startingStamina, - npcData.staminaOffset); + attributesNpcData.staminaOffset); actorValues.stamina = 100.f; } @@ -69,7 +129,8 @@ BaseActorValues GetBaseActorValues(WorldState* worldState, uint32_t baseId, spdlog::trace( "GetBaseActorValues {:x} {:x} - startingHealth={}, healthOffset={}", - baseId, raceIdOverride, raceData.startingHealth, npcData.healthOffset); + baseId, raceIdOverride, raceData.startingHealth, + attributesNpcData.healthOffset); return actorValues; } diff --git a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h index 4a683b49ca..8cf96c48b4 100644 --- a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h +++ b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h @@ -17,4 +17,5 @@ struct BaseActorValues : public ActorValues }; BaseActorValues GetBaseActorValues(WorldState* worldState, uint32_t baseId, - uint32_t raceIdOverride); + uint32_t raceIdOverride, + const std::vector& templateChain); diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index 9b80e688b9..a2697ea98f 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -123,7 +123,8 @@ void MpActor::VisitProperties(const PropertiesVisitor& visitor, // this "if" is needed for unit testing: tests can call VisitProperties // without espm attached, which will cause tests to fail if (worldState && worldState->HasEspm()) { - baseActorValues = GetBaseActorValues(worldState, baseId, raceId); + baseActorValues = GetBaseActorValues(worldState, baseId, raceId, + ChangeForm().templateChain); } MpChangeForm changeForm = GetChangeForm(); @@ -295,8 +296,8 @@ void MpActor::ApplyChangeForm(const MpChangeForm& newChangeForm) // this check is added only for test as a workaround. It is to be redone // in the nearest future. TODO if (GetParent() && GetParent()->HasEspm()) { - changeForm.actorValues = - GetBaseActorValues(GetParent(), GetBaseId(), GetRaceId()); + changeForm.actorValues = GetBaseActorValues( + GetParent(), GetBaseId(), GetRaceId(), changeForm.templateChain); } }, Mode::NoRequestSave); @@ -464,6 +465,10 @@ espm::ObjectBounds MpActor::GetBounds() const return espm::GetData(GetBaseId(), GetParent()).objectBounds; } +const std::vector &MpActor::GetTemplateChain() const { + return ChangeForm().templateChain; +} + void MpActor::SendAndSetDeathState(bool isDead, bool shouldTeleport) { float attribute = isDead ? 0.f : 1.f; @@ -753,7 +758,8 @@ void MpActor::DamageActorValue(espm::ActorValue av, float value) BaseActorValues MpActor::GetBaseValues() { - return GetBaseActorValues(GetParent(), GetBaseId(), GetRaceId()); + return GetBaseActorValues(GetParent(), GetBaseId(), GetRaceId(), + ChangeForm().templateChain); } BaseActorValues MpActor::GetMaximumValues() @@ -872,8 +878,8 @@ void MpActor::ApplyMagicEffect(espm::Effects::Effect& effect, bool hasSweetpie, return; } MpChangeForm changeForm = GetChangeForm(); - BaseActorValues baseValues = - GetBaseActorValues(GetParent(), GetBaseId(), GetRaceId()); + BaseActorValues baseValues = GetBaseActorValues( + GetParent(), GetBaseId(), GetRaceId(), changeForm.templateChain); const ActiveMagicEffectsMap& activeEffects = changeForm.activeMagicEffects; const float baseValue = baseValues.GetValue(av); const uint32_t formId = GetFormId(); @@ -949,8 +955,8 @@ void MpActor::ApplyMagicEffects(std::vector& effects, void MpActor::RemoveMagicEffect(const espm::ActorValue actorValue) noexcept { - const ActorValues baseActorValues = - GetBaseActorValues(GetParent(), GetBaseId(), GetRaceId()); + const ActorValues baseActorValues = GetBaseActorValues( + GetParent(), GetBaseId(), GetRaceId(), ChangeForm().templateChain); const float baseActorValue = baseActorValues.GetValue(actorValue); SetActorValue(actorValue, baseActorValue); EditChangeForm([actorValue](MpChangeForm& changeForm) { @@ -960,8 +966,8 @@ void MpActor::RemoveMagicEffect(const espm::ActorValue actorValue) noexcept void MpActor::RemoveAllMagicEffects() noexcept { - const ActorValues baseActorValues = - GetBaseActorValues(GetParent(), GetBaseId(), GetRaceId()); + const ActorValues baseActorValues = GetBaseActorValues( + GetParent(), GetBaseId(), GetRaceId(), ChangeForm().templateChain); SetActorValues(baseActorValues); EditChangeForm( [](MpChangeForm& changeForm) { changeForm.activeMagicEffects.Clear(); }); diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index 7ef5a6095a..afb8e377ea 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -35,6 +35,7 @@ class MpActor : public MpObjectReference uint32_t GetRaceId() const; bool IsWeaponDrawn() const; espm::ObjectBounds GetBounds() const; + const std::vector &GetTemplateChain() const; void SetRaceMenuOpen(bool isOpen); void SetAppearance(const Appearance* newAppearance); diff --git a/unit/ChangeValuesTest.cpp b/unit/ChangeValuesTest.cpp index 77af1b9f46..258f424af2 100644 --- a/unit/ChangeValuesTest.cpp +++ b/unit/ChangeValuesTest.cpp @@ -54,7 +54,7 @@ TEST_CASE("OnChangeValues call is cropping percentage values", auto appearance = ac.GetAppearance(); uint32_t raceId = appearance ? appearance->raceId : 0; BaseActorValues baseValues = - GetBaseActorValues(&p.worldState, baseId, raceId); + GetBaseActorValues(&p.worldState, baseId, raceId, {}); ActionListener::RawMessageData msgData; msgData.userId = 0; diff --git a/unit/CropRegenerationTest.cpp b/unit/CropRegenerationTest.cpp index 7aa207b825..85790a27cb 100644 --- a/unit/CropRegenerationTest.cpp +++ b/unit/CropRegenerationTest.cpp @@ -104,7 +104,7 @@ TEST_CASE("CropHealthRegeneration, CropMagickaRegeneration and " auto appearance = ac.GetAppearance(); uint32_t raceId = appearance ? appearance->raceId : 0; BaseActorValues baseValues = - GetBaseActorValues(&p.worldState, baseId, raceId); + GetBaseActorValues(&p.worldState, baseId, raceId, {}); ac.SetPercentages({ 0.0f, 0.0f, 0.0f }); diff --git a/unit/GetBaseActorValuesTest.cpp b/unit/GetBaseActorValuesTest.cpp index 4d44b73b46..c399d1e7c3 100644 --- a/unit/GetBaseActorValuesTest.cpp +++ b/unit/GetBaseActorValuesTest.cpp @@ -23,7 +23,7 @@ TEST_CASE("GetBaseActorValues works correctly", "[GetBaseActorValues]") auto appearance = ac.GetAppearance(); uint32_t raceId = appearance ? appearance->raceId : 0; BaseActorValues baseValues = - GetBaseActorValues(&p.worldState, baseId, raceId); + GetBaseActorValues(&p.worldState, baseId, raceId, {}); REQUIRE(baseValues.health == 100.f); REQUIRE(baseValues.stamina == 100.f); From 5a05fe238a4dc1a43781121e9830325b14eba4b6 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 8 Oct 2023 07:14:43 +0600 Subject: [PATCH 22/88] leveled draugrs now have armor and weapons --- skymp5-scripts/pex/ActiveMagicEffect.pex | Bin 604 -> 614 bytes .../cpp/server_guest_lib/EvaluateTemplate.h | 49 ++++++++++++++++++ .../server_guest_lib/GetBaseActorValues.cpp | 49 ++---------------- .../cpp/server_guest_lib/Inventory.cpp | 23 ++++++-- .../cpp/server_guest_lib/Inventory.h | 1 + .../cpp/server_guest_lib/MpActor.cpp | 14 +++-- .../server_guest_lib/MpObjectReference.cpp | 49 ++++++++++++------ .../src/platform_se/pex/TESModPlatform.pex | Bin 2377 -> 2357 bytes unit/TemplateInventoryTest.cpp | 31 +++++++++++ unit/papyrus_test_files/pex/AAATestObject.pex | Bin 791 -> 797 bytes unit/papyrus_test_files/pex/LatentTest.pex | Bin 1063 -> 1069 bytes unit/papyrus_test_files/pex/OpcodesTest.pex | Bin 12583 -> 12589 bytes 12 files changed, 145 insertions(+), 71 deletions(-) create mode 100644 skymp5-server/cpp/server_guest_lib/EvaluateTemplate.h create mode 100644 unit/TemplateInventoryTest.cpp diff --git a/skymp5-scripts/pex/ActiveMagicEffect.pex b/skymp5-scripts/pex/ActiveMagicEffect.pex index 0904cb6fcfe9a48a372f7a5e8c956b9ab7e06552..fcd4d8d3b1ef3e247d762115bf564878b625b6f4 100644 GIT binary patch delta 155 zcmcb^@{C2`SNMT@%uEc73_y^oIK5}0fP?~HPHKK$W=cVRaY1TMei;M5kE_3*r%Ql; zaDc0kiIKrXJ9iPbjLejj)I0|62B>90s +#include +#include + +template +auto EvaluateTemplate(WorldState* worldState, uint32_t baseId, + const std::vector& templateChain, + const Callback& callback) +{ + const std::vector chainDefault = { FormDesc::FromFormId( + baseId, worldState->espmFiles) }; + const std::vector& chain = + templateChain.size() > 0 ? templateChain : chainDefault; + + for (auto it = chain.begin(); it != chain.end(); it++) { + auto templateChainElement = it->ToFormId(worldState->espmFiles); + auto npcLookupResult = + worldState->GetEspm().GetBrowser().LookupById(templateChainElement); + auto npc = espm::Convert(npcLookupResult.rec); + auto npcData = npc->GetData(worldState->GetEspmCache()); + + if (npcData.baseTemplate == 0) { + return callback(npcLookupResult, npcData); + } + + if (!(npcData.templateDataFlags & TemplateFlag)) { + return callback(npcLookupResult, npcData); + } + } + + std::stringstream ss; + ss << "EvaluateTemplate failed: baseId=" << std::hex << baseId + << ", templateChain="; + + for (size_t i = 0; i < templateChain.size(); ++i) { + ss << templateChain[i].ToString(); + if (i != templateChain.size() - 1) { + ss << ","; + } + } + + ss << ", templateFlag=" << TemplateFlag; + + throw std::runtime_error(ss.str()); +} diff --git a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp index 09a69df211..73d0932cfa 100644 --- a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp +++ b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp @@ -1,6 +1,6 @@ #include "GetBaseActorValues.h" +#include "EvaluateTemplate.h" #include "WorldState.h" - #include void BaseActorValues::VisitBaseActorValues(BaseActorValues& baseActorValues, @@ -27,50 +27,7 @@ void BaseActorValues::VisitBaseActorValues(BaseActorValues& baseActorValues, std::to_string(changeForm.actorValues.magickaPercentage).c_str()); } -namespace { -template -auto EvaluateTemplate(WorldState* worldState, uint32_t baseId, - const std::vector& templateChain, - const Callback& callback) -{ - const std::vector chainDefault = { FormDesc::FromFormId( - baseId, worldState->espmFiles) }; - const std::vector& chain = - templateChain.size() > 0 ? templateChain : chainDefault; - - for (auto it = chain.begin(); it != chain.end(); it++) { - auto templateChainElement = it->ToFormId(worldState->espmFiles); - auto npcLookupResult = - worldState->GetEspm().GetBrowser().LookupById(templateChainElement); - auto npc = espm::Convert(npcLookupResult.rec); - auto npcData = npc->GetData(worldState->GetEspmCache()); - - if (npcData.baseTemplate == 0) { - return callback(npcLookupResult, npcData); - } - - if (!(npcData.templateDataFlags & TemplateFlag)) { - return callback(npcLookupResult, npcData); - } - } - - std::stringstream ss; - ss << "EvaluateTemplate failed: baseId=" << std::hex << baseId - << ", templateChain="; - - for (size_t i = 0; i < templateChain.size(); ++i) { - ss << templateChain[i].ToString(); - if (i != templateChain.size() - 1) { - ss << ","; - } - } - - ss << ", templateFlag=" << TemplateFlag; - - throw std::runtime_error(ss.str()); -} -} - +// TODO: implement auto-calc flag BaseActorValues GetBaseActorValues(WorldState* worldState, uint32_t baseId, uint32_t raceIdOverride, const std::vector& templateChain) @@ -105,7 +62,7 @@ BaseActorValues GetBaseActorValues(WorldState* worldState, uint32_t baseId, actorValues.magicka = raceData.startingMagicka + attributesNpcData.magickaOffset; - if (actorValues.magicka <= 0) { + if (actorValues.magicka < 0) { // zero magicka is ok, negative isn't spdlog::warn("GetBaseActorValues {:x} {:x} - Negative Magicka found: " "startingMagicka={}, magickaOffset={}, defaulting to 100", baseId, raceIdOverride, raceData.startingMagicka, diff --git a/skymp5-server/cpp/server_guest_lib/Inventory.cpp b/skymp5-server/cpp/server_guest_lib/Inventory.cpp index 1669622036..5bd42d7f94 100644 --- a/skymp5-server/cpp/server_guest_lib/Inventory.cpp +++ b/skymp5-server/cpp/server_guest_lib/Inventory.cpp @@ -78,28 +78,41 @@ Inventory& Inventory::RemoveItems(const std::vector& entries) return *this; } +Inventory& Inventory::WornAll() noexcept +{ + for (auto& entry : entries) { + entry.extra.worn = Inventory::Worn::Right; + } + return *this; +} + bool Inventory::HasItem(uint32_t baseId) const { - for (auto& entry : entries) - if (entry.baseId == baseId) + for (auto& entry : entries) { + if (entry.baseId == baseId) { return true; + } + } return false; } uint32_t Inventory::GetItemCount(uint32_t baseId) const { uint32_t sum = 0; - for (auto& entry : entries) - if (entry.baseId == baseId) + for (auto& entry : entries) { + if (entry.baseId == baseId) { sum += entry.count; + } + } return sum; } uint32_t Inventory::GetTotalItemCount() const { uint32_t sum = 0; - for (auto& entry : entries) + for (auto& entry : entries) { sum += entry.count; + } return sum; } diff --git a/skymp5-server/cpp/server_guest_lib/Inventory.h b/skymp5-server/cpp/server_guest_lib/Inventory.h index e88ebfab1d..75179643e6 100644 --- a/skymp5-server/cpp/server_guest_lib/Inventory.h +++ b/skymp5-server/cpp/server_guest_lib/Inventory.h @@ -82,6 +82,7 @@ class Inventory Inventory& AddItem(uint32_t baseId, uint32_t count); Inventory& AddItems(const std::vector& entries); Inventory& RemoveItems(const std::vector& entries); + Inventory &WornAll() noexcept; bool HasItem(uint32_t baseId) const; uint32_t GetItemCount(uint32_t baseId) const; uint32_t GetTotalItemCount() const; diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index a2697ea98f..7e8c7a4c4d 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -465,7 +465,8 @@ espm::ObjectBounds MpActor::GetBounds() const return espm::GetData(GetBaseId(), GetParent()).objectBounds; } -const std::vector &MpActor::GetTemplateChain() const { +const std::vector& MpActor::GetTemplateChain() const +{ return ChangeForm().templateChain; } @@ -658,13 +659,20 @@ void MpActor::BeforeDestroy() UnsubscribeFromAll(); } +// ActionListener.cpp +void RecalculateWorn(MpObjectReference& refr); + void MpActor::Init(WorldState* worldState, uint32_t formId, bool hasChangeForm) { MpObjectReference::Init(worldState, formId, hasChangeForm); if (worldState->HasEspm()) { - EnsureBaseContainerAdded(GetParent()->GetEspm()); - EnsureTemplateChainEvaluated(GetParent()->GetEspm()); + auto& espm = worldState->GetEspm(); + EnsureTemplateChainEvaluated(espm); + EnsureBaseContainerAdded(espm); // template chain needed here + + // equip best weapon (TODO: implement "gearedUpWeapons" flag) + RecalculateWorn(*this); } } diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index fce2ad9c0e..101fc14c30 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1,6 +1,7 @@ #include "MpObjectReference.h" #include "ChangeFormGuard.h" #include "EspmGameObject.h" +#include "EvaluateTemplate.h" #include "FormCallbacks.h" #include "LeveledListUtils.h" #include "MpActor.h" @@ -881,8 +882,7 @@ MpObjectReference::GetNextRelootMoment() const MpChangeForm MpObjectReference::GetChangeForm() const { - MpChangeForm res; - static_cast(res) = ChangeForm(); + MpChangeForm res = ChangeForm(); if (GetParent() && !GetParent()->espmFiles.empty()) { res.formDesc = FormDesc::FromFormId(GetFormId(), GetParent()->espmFiles); @@ -1335,16 +1335,22 @@ void MpObjectReference::SendOpenContainer(uint32_t targetId) } std::vector GetOutfitObjects( - const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, - espm::CompressedFieldsCache& compressedFieldsCache) + WorldState* worldState, const std::vector& templateChain, + const espm::LookupResult& lookupRes) { + auto& compressedFieldsCache = worldState->GetEspmCache(); + std::vector res; if (auto baseNpc = espm::Convert(lookupRes.rec)) { - auto data = baseNpc->GetData(compressedFieldsCache); - - auto outfitId = lookupRes.ToGlobalId(data.defaultOutfitId); - auto outfit = espm::Convert(br.LookupById(outfitId).rec); + auto baseId = lookupRes.ToGlobalId(lookupRes.rec->GetId()); + auto outfitId = EvaluateTemplate( + worldState, baseId, templateChain, + [](const auto& npcLookupRes, const auto& npcData) { + return npcLookupRes.ToGlobalId(npcData.defaultOutfitId); + }); + auto outfit = espm::Convert( + worldState->GetEspm().GetBrowser().LookupById(outfitId).rec); auto outfitData = outfit ? outfit->GetData(compressedFieldsCache) : espm::OTFT::Data(); @@ -1357,9 +1363,11 @@ std::vector GetOutfitObjects( } std::vector GetInventoryObjects( - const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, - espm::CompressedFieldsCache& compressedFieldsCache) + WorldState* worldState, const std::vector& templateChain, + const espm::LookupResult& lookupRes) { + auto& compressedFieldsCache = worldState->GetEspmCache(); + auto baseContainer = espm::Convert(lookupRes.rec); if (baseContainer) { return baseContainer->GetData(compressedFieldsCache).objects; @@ -1367,7 +1375,10 @@ std::vector GetInventoryObjects( auto baseNpc = espm::Convert(lookupRes.rec); if (baseNpc) { - return baseNpc->GetData(compressedFieldsCache).objects; + auto baseId = lookupRes.ToGlobalId(lookupRes.rec->GetId()); + return EvaluateTemplate( + worldState, baseId, templateChain, + [](const auto&, const auto& npcData) { return npcData.objects; }); } return {}; @@ -1404,25 +1415,29 @@ void MpObjectReference::EnsureBaseContainerAdded(espm::Loader& espm) return; } + auto actor = dynamic_cast(this); + const std::vector kEmptyTemplateChain; + const std::vector& templateChain = + actor ? actor->GetTemplateChain() : kEmptyTemplateChain; + auto lookupRes = espm.GetBrowser().LookupById(GetBaseId()); std::map itemsToAdd, itemsToEquip; - auto inventoryObjects = GetInventoryObjects(espm.GetBrowser(), lookupRes, - worldState->GetEspmCache()); + auto inventoryObjects = + GetInventoryObjects(GetParent(), templateChain, lookupRes); for (auto& entry : inventoryObjects) { AddContainerObject(entry, &itemsToAdd); } - auto outfitObjects = - GetOutfitObjects(espm.GetBrowser(), lookupRes, worldState->GetEspmCache()); + auto outfitObjects = GetOutfitObjects(GetParent(), templateChain, lookupRes); for (auto& entry : outfitObjects) { AddContainerObject(entry, &itemsToAdd); AddContainerObject(entry, &itemsToEquip); } - if (auto actor = dynamic_cast(this)) { + if (actor) { Equipment eq; - for (auto p : itemsToEquip) { + for (auto& p : itemsToEquip) { Inventory::Entry e; e.baseId = p.first; e.count = p.second; diff --git a/skyrim-platform/src/platform_se/pex/TESModPlatform.pex b/skyrim-platform/src/platform_se/pex/TESModPlatform.pex index c0f565efd28967c32b4549c1080f089119295450..93ca2e03a0fd03a15995159480e1dfdec1a73e49 100644 GIT binary patch literal 2357 zcmb7E`Ci*b5FXhwV6buGkdUKIoL=db6bQNKk%BS7EnpXOB^W_D)u?X3RU|MTyBjvT_^+rR%o)tyFXQ~B*s=&p+T z_Xe>?(;+Jv_yZLWI8;Ye*=V%3nyc+rr`=dsT$rPPrr+F#XcNy^ceKz<#b-*Z5v*>i zBi`n2w4>T84s@U-<(GY*svXw*ECx!hM&eka0;Wrdu%&x@S{#Jj2)WNv)0Jk7+Dp<33s3I!XBSz}2fk)NGkqC!aZ154o)98xGhIfZgXmSL%g~wD$ z)n{Q+#kvThQQVnrj)UKLorOOTk&c(6NSxAzk#;ALR%14ZeHF=+Ffh{8+;5yvWq07C_QtQnV32(OG;*=7E2%{wIil)HsJz#0 zF*uEeapMRSA-Iw&d>EwBCY5YRe-wGjQ_@dp7a@)H0^esz?v|2FuBn#_l8>7{<(r_o z2qmT}$Cbw^Tp_uTWl;|E9r}2RTnINE?f>yT+VyJ{O zHbgb0k0@@0UZYCu;1~8Z`i>)(IB3`7Fr)%12z{Ca@uA-tm=18!8YXAs~lJ35H&#QODcFc{sm9tW zxm;7Db=}lLu09>$8xigR$AdV-h}!~nO@ugybs>E!9GG^ZV(+Zw-N+C#>#2TUbQoQa zcJw!bShu=OKc>Z`=V}pq92~J0S`mVsrUx1{??4>%k_D#c8b@sXjef8%oa< z2v?!4M?Bi7lGCbbg(cVU&+&q8fbB^@nlOzq`mZc4naUhp=D5^$vFB;-ui2ouHy&iP|o}{4ai&IT2QcH%z|+XCM+mgFloV* z1tkm07ED`EvEYIQ7cHn-aLIxh3ocu5#e!K2u3B)-0`qkk4&5-X9KGex+h&Q8AICdE zXR`w~s&q58!~GVX8r?STbTugG7qj_4O`w%lq=M9VWfF7n{E@lRY9;M2$OqoOEIeMHLiuA-* z?a&g!m@y7x`YCz(9N+f^6kn!0E}Rc#65AY;!0E0e3b~nQk-nm*^fjJkTA@0vQUkIE zc?MaBG$Ce8XPa_l!he(Iy^*us+<(qVPK3onF KdYQt3clTc%o8cA! literal 2377 zcmb7EXw^ATQ7@0^47SmWS3PUbvVwIbY zVTRLK$EZjg(9M;h2&`4j^q8qBZiY%|On5CAD#eAV>14t9!a};TxGIfIslO=&r}?*C z=;O!)J#KwOx+eEu@xauKOkCmsLDY?Sm^qyg{5bQvuwb3-GDVY8G(?!~pn2S-vJ^}F zAQGu;u5o0;Qas?%5nLVjxQ!m0X&Y(Hk~D^}#*W!(t*d08ZvBr{MpAV{9`P;SRok*H zb&^N&73NfFB}ameBRv5pKj3jpe%n88EBHy*;DCN-UX4CeM@p1>8yV zEa@Que?j@hFr<7_7|O93U>3Lw%mMd+`@jR>AuvyQHgm^zGq#(hYKNOW&IVE}D|ReU zM4SpNFj7%rF&X^6m0d;+Y?|J#Vf!&}#JrDW()d6|!f-|Y3aXB#vT3W^)!a~-r)EtN z@)Nq+)F{tWuC|6kpoSX4dJ4&3k}{^UEwRH$)MbpFi^-7q^!J(OR7^ryN@!hi+}GwH zplaiY#Y0Ti`4RRCt?XJL`+e5o1E!z|*CTCO-Daq%h=anQgHZAUhItJ(nF_a(jH{$i zcqKC)MWoMYrdwl?%A)O^(JNbAM}PA*&O!uW^rBMil*VnnLQI!Z^)`~0sjM@5&StnH zH0$Et?fazyRy0GJuE(4yYretsp{9~y>VTW{UdrvE?l~R#=5&D7(C@>HpWs3AHZwh% zvSui(>>2ndHN0rsuk)@!X2Y;)c)xK%Q+WG^MFLl3E7p%7rpZ7Kg<0mYVfYU?i{Y|_ z0`4(kk{T#A&P|Gac2e)zAnCf|eLRM9+LqW6{I6j&z)p!xo2$Tr|IL;aq$QrWDP2C-aif8Ad>QXF& z!!{e7s-aGvwx+_ql_h`a%iWpR^d7!Jk9_;LS~B5C)sdPbw;h>vNqr5#7T7Dd3!p?e+uH)PV8ngPe zdDlG}M-GZKnF*)_3y;bW<*#`(<mz=yy`z{h}XHTuLYD$}Q!d$@(m}(?9>1N diff --git a/unit/TemplateInventoryTest.cpp b/unit/TemplateInventoryTest.cpp new file mode 100644 index 0000000000..e342f85689 --- /dev/null +++ b/unit/TemplateInventoryTest.cpp @@ -0,0 +1,31 @@ +#include "MsgType.h" +#include "ServerState.h" +#include "TestUtils.hpp" +#include +#include + +PartOne& GetPartOne(); + +TEST_CASE("MS13BanditCampfire01 in BleakFalls should have inventory/equipment " + "derived from NPC template", + "[TemplateInventory]") +{ + PartOne& p = GetPartOne(); + p.worldState.npcSettings["Skyrim.esm"].spawnInInterior = true; + p.worldState.npcEnabled = true; + uint32_t actorId = 0x39fe4; + auto& bandit = p.worldState.GetFormAt(actorId); + + REQUIRE(bandit.GetEquipment().inv.ToJson().size() > 0); + + int numWeaps = 0; + for (auto entry : bandit.GetEquipment().inv.entries) { + auto lookupRes = + p.worldState.GetEspm().GetBrowser().LookupById(entry.baseId); + if (lookupRes.rec && lookupRes.rec->GetType() == "WEAP") { + numWeaps++; + } + } + + REQUIRE(numWeaps == 1); +} diff --git a/unit/papyrus_test_files/pex/AAATestObject.pex b/unit/papyrus_test_files/pex/AAATestObject.pex index 2cd7742b1eab2a7a2fc1e597e438702a9da95dd4..e95b15d4b9e7484c2f83e1bbdce92906d79c8daa 100644 GIT binary patch delta 280 zcmbQvHkVD{SNMT@%uEc73_y^oIDO$n0TBhhoYefh%#?!s;)2wi{4xf9A6I`rPnQ7y z-~d-66C;C(cC*>pf=h}r^U^2VGiq_MTUiB{B$lKym`pBY)Si5rk&~T))h#DKv1H;6 zHvvv7E5H1_)Ud=N2A1N~oV3aLjQX5l`%*LtBPZWwTwBk~z{kK3asdP|N-;7BFt9W5 zFbKkgKr}0Z5X7AfK#CX00n$K~ybLTrmBJ8}j0_?`vltmffkMpUj126I3}QeYGf0*f XXev-1D9VN;%D~3J$HO2F(GDg7h@>k= delta 279 zcmbQsHl0o2SNMT@%uEc73_y^`WS2fsKtzfoAiuaEH7CD}f#1b7*gM2OK-b&c*TB%s zYog2S$zhDHifq9pMVWc&44hV0e))N+VTnZyEXAogX$38SC8-miX|b`o<>V)p zOkT~X&%|Idc`c*1P-1yvr9xU!elFO$go|I9CqHIfThGG4!@vh}00Yn+{0ss>QV=M_ zEY8Tl&d4AH`Fts3>oq>;sK^#d0026a7aR2}S diff --git a/unit/papyrus_test_files/pex/LatentTest.pex b/unit/papyrus_test_files/pex/LatentTest.pex index cd36ff7aa774d368c684931175d6199e9a8b50f1..154d46af2152205f3c8dec25a5690d3b667abae7 100644 GIT binary patch delta 398 zcmZ3^v6jQ}SNMT@%uEc73_y^oIDH`lpHE^*YF)`esMu+ zPJS5!zmKcGpQlTJe{g`Sk%^JPMC)C`>{eDKsksG)44hV0e))N+VTnbPIT+0+S2F5P zwr8}Oyn@l6B}Jn!a`JD+FO!(n>bV&BAvS`nWn>Tqa# G2?GGq_$)pE delta 398 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 D&=E#} literal 12583 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 ZYnIkIMXz~(d-9#H=NwwGk Date: Sun, 8 Oct 2023 08:38:42 +0600 Subject: [PATCH 23/88] reformat --- .../platform_se/skyrim_platform/LoadGame.h | 2 +- .../skyrim_platform/PapyrusTESModPlatform.cpp | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h index 9da1341f29..94acefc249 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h +++ b/skyrim-platform/src/platform_se/skyrim_platform/LoadGame.h @@ -46,7 +46,7 @@ class LoadGame static std::wstring GetPathToMyDocuments(); private: - static std::wstring StringToWstring(const std::string &s); + static std::wstring StringToWstring(const std::string& s); static std::string GenerateGuid(); diff --git a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp index a882007a33..485ebf7df3 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/PapyrusTESModPlatform.cpp @@ -282,8 +282,7 @@ static RE::TESNPC* CloneNpc(uint32_t npcId, AiPackagesMode aiPackagesMode) } template -static void FillFormsArray(VectorT& leak, - std::vector cursorStack) +static void FillFormsArray(VectorT& leak, std::vector cursorStack) { leak.reserve(cursorStack.size()); for (auto it = cursorStack.begin(); it != cursorStack.end(); ++it) { @@ -305,8 +304,8 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( { auto str = std::string(commaSeparatedListOfIds.data()); - thread_local std::unordered_map g_cache; - auto &cachedNpc = g_cache[str]; + thread_local std::unordered_map g_cache; + auto& cachedNpc = g_cache[str]; if (cachedNpc) { return cachedNpc; } @@ -346,7 +345,8 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( if (cursorStack.size() > 0) { OutputDebugStringA("cursorStack.size() > 0\n"); - str = "cursorStack.size() is " + std::to_string(cursorStack.size()) + "\n"; + str = + "cursorStack.size() is " + std::to_string(cursorStack.size()) + "\n"; OutputDebugStringA(str.data()); for (auto v : cursorStack) { std::stringstream ss; @@ -355,11 +355,11 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( OutputDebugStringA(str.data()); } copiedNpc->baseTemplateForm = cursorStack.back(); - //copiedNpc->baseTemplateForm = nullptr; + // copiedNpc->baseTemplateForm = nullptr; // auto leak = new std::vector(); // leak->reserve(10); - auto leak = new RE::BSTArray(); + auto leak = new RE::BSTArray(); FillFormsArray(*leak, cursorStack); // copiedNpc->CopyFromTemplateForms(leak->data()); @@ -367,11 +367,11 @@ RE::TESNPC* TESModPlatform::EvaluateLeveledNpc( auto leak2 = new RE::BSTArray(); leak2->resize(100, 0); - //copiedNpc->templateForms = reinterpret_cast(leak2); + // copiedNpc->templateForms = reinterpret_cast(leak2); - copiedNpc->CopyFromTemplateForms(reinterpret_cast(leak)); - } - else { + copiedNpc->CopyFromTemplateForms( + reinterpret_cast(leak)); + } else { OutputDebugStringA("cursorStack.size() == 0\n"); } cursorStack.push_back(copiedNpc); From 00b33811d11e61af680e5e4d33546bb29d9b8c4a Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 8 Oct 2023 09:23:33 +0600 Subject: [PATCH 24/88] reform --- libespm/include/libespm/Records.h | 2 +- .../cpp/server_guest_lib/Inventory.h | 2 +- .../server_guest_lib/MpObjectReference.cpp | 38 +++++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/libespm/include/libespm/Records.h b/libespm/include/libespm/Records.h index 9f0c992cb3..12fc118da9 100644 --- a/libespm/include/libespm/Records.h +++ b/libespm/include/libespm/Records.h @@ -16,6 +16,7 @@ #include "KYWD.h" #include "LIGH.h" #include "LVLI.h" +#include "LVLN.h" #include "MGEF.h" #include "NAVM.h" #include "NPC_.h" @@ -26,4 +27,3 @@ #include "TREE.h" #include "WEAP.h" #include "WRLD.h" -#include "LVLN.h" diff --git a/skymp5-server/cpp/server_guest_lib/Inventory.h b/skymp5-server/cpp/server_guest_lib/Inventory.h index 75179643e6..55f3eccd2d 100644 --- a/skymp5-server/cpp/server_guest_lib/Inventory.h +++ b/skymp5-server/cpp/server_guest_lib/Inventory.h @@ -82,7 +82,7 @@ class Inventory Inventory& AddItem(uint32_t baseId, uint32_t count); Inventory& AddItems(const std::vector& entries); Inventory& RemoveItems(const std::vector& entries); - Inventory &WornAll() noexcept; + Inventory& WornAll() noexcept; bool HasItem(uint32_t baseId) const; uint32_t GetItemCount(uint32_t baseId) const; uint32_t GetTotalItemCount() const; diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 101fc14c30..5a8323ad6b 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1242,12 +1242,14 @@ void MpObjectReference::UnsubscribeFromAll() void MpObjectReference::InitScripts() { auto baseId = GetBaseId(); - if (!baseId || !GetParent()->espm) + if (!baseId || !GetParent()->espm) { return; + } auto scriptStorage = GetParent()->GetScriptStorage(); - if (!scriptStorage) + if (!scriptStorage) { return; + } auto& compressedFieldsCache = GetParent()->GetEspmCache(); @@ -1257,14 +1259,36 @@ void MpObjectReference::InitScripts() auto base = br.LookupById(baseId); auto refr = br.LookupById(GetFormId()); for (auto record : { base.rec, refr.rec }) { - if (!record) + if (!record) { continue; - espm::ScriptData scriptData; - record->GetScriptData(&scriptData, compressedFieldsCache); + } + + std::optional scriptData; + + if (record == base.rec && record->GetType() == "NPC_") { + auto baseId = base.ToGlobalId(base.rec->GetId()); + if (auto actor = dynamic_cast(this)) { + auto& templateChain = actor->GetTemplateChain(); + scriptData = EvaluateTemplate( + GetParent(), baseId, templateChain, + [&compressedFieldsCache](const auto& npcLookupRes, + const auto& npcData) { + espm::ScriptData scriptData; + npcLookupRes.rec->GetScriptData(&scriptData, + compressedFieldsCache); + return scriptData; + }); + } + } + + if (!scriptData) { + scriptData = espm::ScriptData(); + record->GetScriptData(&*scriptData, compressedFieldsCache); + } auto& scriptsInStorage = GetParent()->GetScriptStorage()->ListScripts(false); - for (auto& script : scriptData.scripts) { + for (auto& script : scriptData->scripts) { if (scriptsInStorage.count( { script.scriptName.begin(), script.scriptName.end() })) { @@ -1279,7 +1303,7 @@ void MpObjectReference::InitScripts() } if (!scriptNames.empty()) { - pImpl->scriptState.reset(new ScriptState); + pImpl->scriptState = std::make_unique(); std::vector scriptInfo; for (auto& scriptName : scriptNames) { From 183e2388c9256883f4ab3eb4585b4cf8ba576345 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 8 Oct 2023 09:24:19 +0600 Subject: [PATCH 25/88] loadtemplscript --- unit/TemplateScriptTest.cpp | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 unit/TemplateScriptTest.cpp diff --git a/unit/TemplateScriptTest.cpp b/unit/TemplateScriptTest.cpp new file mode 100644 index 0000000000..b37a04ea86 --- /dev/null +++ b/unit/TemplateScriptTest.cpp @@ -0,0 +1,52 @@ +#include "MsgType.h" +#include "ScriptStorage.h" +#include "ServerState.h" +#include "TestUtils.hpp" +#include +#include + +PartOne& GetPartOne(); + +namespace { +class MyScriptStorage : public IScriptStorage +{ + std::vector GetScriptPex(const char* scriptName) override + { + if (scriptName == std::string("masterambushscript")) { + throw std::runtime_error("OK"); + } + return {}; + } + + const std::set& ListScripts(bool forceReloadScripts) override + { + static const std::set kSet = { "masterambushscript" }; + return kSet; + } +}; +} + +TEST_CASE("MS13FrostbiteSpiderREF in BleakFalls should have scripts " + "derived from NPC template", + "[TemplateScript]") +{ + PartOne& p = GetPartOne(); + + p.worldState.npcSettings["Skyrim.esm"].spawnInInterior = true; + p.worldState.npcEnabled = true; + + p.worldState.AttachScriptStorage(std::make_shared()); + + std::string what; + try { + uint32_t actorId = 0x3a1e1; + auto& spider = p.worldState.GetFormAt(actorId); + spider.SendPapyrusEvent("OnLoad"); + } catch (std::exception& e) { + what = e.what(); + } + + REQUIRE(what == "OK"); + + // REQUIRE(spider.HasScript("masterambushscript")); +} From 2c3dfc72876b6cfdea09e5127a2c303874548d0b Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 8 Oct 2023 11:29:44 +0600 Subject: [PATCH 26/88] activation parents: beginning --- libespm/include/libespm/REFR.h | 8 ++++++++ libespm/src/REFR.cpp | 7 +++++++ .../cpp/server_guest_lib/MpObjectReference.cpp | 16 ++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/libespm/include/libespm/REFR.h b/libespm/include/libespm/REFR.h index e1c557b97b..0dc7ffd4b6 100644 --- a/libespm/include/libespm/REFR.h +++ b/libespm/include/libespm/REFR.h @@ -23,6 +23,12 @@ class REFR final : public RecordHeader float rotRadians[3]; }; + struct ActivationParentInfo + { + uint32_t refrId = 0; + float delay = 0.f; + }; + struct Data { uint32_t baseId = 0; @@ -31,6 +37,8 @@ class REFR final : public RecordHeader const DoorTeleport* teleport = nullptr; const float* boundsDiv2 = nullptr; uint32_t count = 0; + uint8_t isParentActivationOnly = 0; + std::vector activationParents; }; Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept; diff --git a/libespm/src/REFR.cpp b/libespm/src/REFR.cpp index 6b9988379e..24cf089a3a 100644 --- a/libespm/src/REFR.cpp +++ b/libespm/src/REFR.cpp @@ -22,6 +22,13 @@ REFR::Data REFR::GetData( result.boundsDiv2 = reinterpret_cast(data); } else if (!std::memcmp(type, "XCNT", 4)) { result.count = *reinterpret_cast(data); + } else if (!std::memcmp(type, "XAPD", 4)) { + result.isParentActivationOnly = + *reinterpret_cast(data); + } else if (!std::memcmp(type, "XAPR", 4)) { + ActivationParentInfo info; + info = *reinterpret_cast(data); + result.activationParents.push_back(info); } }, compressedFieldsCache); diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 5a8323ad6b..058832a85f 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -291,6 +291,22 @@ void MpObjectReference::Activate(MpObjectReference& activationSource, { if (auto worldState = activationSource.GetParent(); worldState->HasEspm()) { CheckInteractionAbility(activationSource); + + auto refrId = GetFormId(); + if (refrId < 0xff000000) { + auto data = espm::GetData(refrId, worldState); + auto it = std::find_if( + data.activationParents.begin(), data.activationParents.end(), + [&](const espm::REFR::ActivationParentInfo& info) { + return info.refrId == activationSource.GetFormId(); + }); + if (it == data.activationParents.end()) { + if (data.isParentActivationOnly) { + throw std::runtime_error( + "Only activation parents can activate this object"); + } + } + } } bool activationBlockedByMpApi = MpApiOnActivate(activationSource); From f13061c24ab8b5f41e687f789b99fccaae165a8e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 8 Oct 2023 13:15:51 +0600 Subject: [PATCH 27/88] tons of papyrus boilerplate --- libespm/include/libespm/REFR.h | 2 + libespm/src/REFR.cpp | 7 + .../cpp/server_guest_lib/PapyrusActor.cpp | 31 +++- .../cpp/server_guest_lib/PapyrusActor.h | 3 + .../cpp/server_guest_lib/PapyrusCell.cpp | 15 ++ .../cpp/server_guest_lib/PapyrusCell.h | 18 +++ .../PapyrusObjectReference.cpp | 134 ++++++++++++++++++ .../server_guest_lib/PapyrusObjectReference.h | 10 ++ .../cpp/server_guest_lib/WorldState.cpp | 2 + unit/ActivateParentTest.cpp | 48 +++++++ viet/include/Promise.h | 12 ++ 11 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 skymp5-server/cpp/server_guest_lib/PapyrusCell.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/PapyrusCell.h create mode 100644 unit/ActivateParentTest.cpp diff --git a/libespm/include/libespm/REFR.h b/libespm/include/libespm/REFR.h index 0dc7ffd4b6..9133740901 100644 --- a/libespm/include/libespm/REFR.h +++ b/libespm/include/libespm/REFR.h @@ -39,6 +39,8 @@ class REFR final : public RecordHeader uint32_t count = 0; uint8_t isParentActivationOnly = 0; std::vector activationParents; + uint32_t linkedRefKeywordId = 0; + uint32_t linkedRefId = 0; }; Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept; diff --git a/libespm/src/REFR.cpp b/libespm/src/REFR.cpp index 24cf089a3a..ef74689fbe 100644 --- a/libespm/src/REFR.cpp +++ b/libespm/src/REFR.cpp @@ -29,6 +29,13 @@ REFR::Data REFR::GetData( ActivationParentInfo info; info = *reinterpret_cast(data); result.activationParents.push_back(info); + } else if (!std::memcmp(type, "XLKR", 4)) { + [[likely]] if (dataSize == 8) { + result.linkedRefKeywordId = *reinterpret_cast(data); + result.linkedRefId = *reinterpret_cast(data + 4); + } else if (dataSize == 4) { + result.linkedRefId = *reinterpret_cast(data); + } } }, compressedFieldsCache); diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusActor.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusActor.cpp index d20ff5b36b..b182539a6f 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/PapyrusActor.cpp @@ -37,9 +37,35 @@ VarValue PapyrusActor::RestoreActorValue( { espm::ActorValue attributeName = ConvertToAV(static_cast(arguments[0])); - float modifire = static_cast(arguments[1]); + float modifier = static_cast(arguments[1]); + if (auto actor = GetFormPtr(self)) { + actor->RestoreActorValue(attributeName, modifier); + } + return VarValue(); +} + +VarValue PapyrusActor::SetActorValue(VarValue self, + const std::vector& arguments) +{ if (auto actor = GetFormPtr(self)) { - actor->RestoreActorValue(attributeName, modifire); + + // TODO: fix that at least for important AVs like attributes + // SpSnippet impl helps scripted draugrs attack, nothing more (Aggression + // var) + spdlog::warn("SetActorValue executes locally at this moment. Results will " + "not affect server calculations"); + + auto it = actor->GetParent()->hosters.find(actor->GetFormId()); + + auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); + + // spsnippet don't support auto sending to host. so determining current + // hoster explicitly + SpSnippet(GetName(), "SetActorValue", serializedArgs.data(), + actor->GetFormId()) + .Execute(it == actor->GetParent()->hosters.end() + ? actor + : &actor->GetParent()->GetFormAt(it->second)); } return VarValue(); } @@ -240,6 +266,7 @@ void PapyrusActor::Register( AddMethod(vm, "PlayIdle", &PapyrusActor::PlayIdle); AddMethod(vm, "GetSitState", &PapyrusActor::GetSitState); AddMethod(vm, "RestoreActorValue", &PapyrusActor::RestoreActorValue); + AddMethod(vm, "SetActorValue", &PapyrusActor::SetActorValue); AddMethod(vm, "DamageActorValue", &PapyrusActor::DamageActorValue); AddMethod(vm, "IsEquipped", &PapyrusActor::IsEquipped); AddMethod(vm, "GetActorValuePercentage", diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusActor.h b/skymp5-server/cpp/server_guest_lib/PapyrusActor.h index 5967f60854..7718c6f19f 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusActor.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusActor.h @@ -18,6 +18,9 @@ class PapyrusActor final : public IPapyrusClass VarValue RestoreActorValue(VarValue self, const std::vector& arguments); + + VarValue SetActorValue(VarValue self, + const std::vector& arguments); VarValue DamageActorValue(VarValue self, const std::vector& arguments); diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusCell.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusCell.cpp new file mode 100644 index 0000000000..473c4eec81 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/PapyrusCell.cpp @@ -0,0 +1,15 @@ +#include "PapyrusCell.h" + +VarValue PapyrusCell::IsAttached(VarValue self, + const std::vector& arguments) +{ + return VarValue(true); // stub +} + +void PapyrusCell::Register(VirtualMachine& vm, + std::shared_ptr policy) +{ + compatibilityPolicy = policy; + + AddMethod(vm, "IsAttached", &PapyrusCell::IsAttached); +} diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusCell.h b/skymp5-server/cpp/server_guest_lib/PapyrusCell.h new file mode 100644 index 0000000000..d824a1052f --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/PapyrusCell.h @@ -0,0 +1,18 @@ +#pragma once +#include "EspmGameObject.h" +#include "IPapyrusClass.h" +#include "SpSnippetFunctionGen.h" + +class PapyrusCell final : public IPapyrusClass +{ +public: + const char* GetName() override { return "cell"; } + + VarValue IsAttached(VarValue self, + const std::vector& arguments); + + void Register(VirtualMachine& vm, + std::shared_ptr policy) override; + + std::shared_ptr compatibilityPolicy; +}; diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp index b86682524d..137bce6085 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp @@ -7,6 +7,7 @@ #include "MpFormGameObject.h" #include "MpObjectReference.h" #include "SpSnippetFunctionGen.h" +#include "TimeUtils.h" #include "WorldState.h" #include "papyrus-vm/Structures.h" #include @@ -401,6 +402,51 @@ VarValue PapyrusObjectReference::PlayAnimation( return VarValue::None(); } +VarValue PapyrusObjectReference::PlayAnimationAndWait( + VarValue self, const std::vector& arguments) +{ + if (auto selfRefr = GetFormPtr(self)) { + if (arguments.size() < 1) { + throw std::runtime_error("PlayAnimation requires at least 2 arguments"); + } + + auto funcName = "PlayAnimationAndWait"; + auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); + + std::vector> promises; + + for (auto listener : selfRefr->GetListeners()) { + auto targetRefr = dynamic_cast(listener); + if (targetRefr) { + auto promise = SpSnippet(GetName(), funcName, serializedArgs.data(), + selfRefr->GetFormId()) + .Execute(targetRefr); + promises.push_back(promise); + } + } + + if (promises.empty()) { + // No listeners found + return VarValue::None(); + } + + // Add 15 seconds timeout (protection against script freeze by user) + // Code based on PapyrusUtility::Wait implementation + auto time = Viet::TimeUtils::To(15.f); + auto timerPromise = selfRefr->GetParent()->SetTimer(time); + auto resultPromise = Viet::Promise(); + timerPromise + .Then([resultPromise](Viet::Void) { + resultPromise.Resolve(VarValue::None()); + }) + .Catch([resultPromise](const char* e) { resultPromise.Reject(e); }); + promises.push_back(resultPromise); + + return VarValue(Viet::Promise::Any(promises)); + } + return VarValue::None(); +} + VarValue PapyrusObjectReference::PlayGamebryoAnimation( VarValue self, const std::vector& arguments) { @@ -481,6 +527,88 @@ VarValue PapyrusObjectReference::SetOpen( return VarValue::None(); } +VarValue PapyrusObjectReference::Is3DLoaded(VarValue, + const std::vector&) +{ + // stub + return VarValue(true); +} + +namespace LinkedRefUtils { +static VarValue GetLinkedRef(VarValue self) +{ + if (auto selfRefr = GetFormPtr(self)) { + auto lookupRes = selfRefr->GetParent()->GetEspm().GetBrowser().LookupById( + selfRefr->GetFormId()); + auto data = + espm::GetData(selfRefr->GetFormId(), selfRefr->GetParent()); + if (data.linkedRefId) { + auto& linkedRef = selfRefr->GetParent()->GetFormAt( + lookupRes.ToGlobalId(data.linkedRefId)); + return VarValue(std::make_shared(&linkedRef)); + } + } + + return VarValue::None(); +} +} + +VarValue PapyrusObjectReference::GetLinkedRef( + VarValue self, const std::vector& arguments) +{ + // TODO: implement keyword argument + // https://ck.uesp.net/wiki/GetLinkedRef_-_ObjectReference + if (arguments.size() > 0 && arguments[0] != VarValue::None()) { + spdlog::warn( + "GetLinkedRef doesn't support Keyword argument at this moment"); + } + + return LinkedRefUtils::GetLinkedRef(self); +} + +VarValue PapyrusObjectReference::GetNthLinkedRef( + VarValue self, const std::vector& arguments) +{ + if (arguments.empty()) { + spdlog::error("GetNthLinkedRef - expected at least 1 argument"); + return VarValue::None(); + } + int n = static_cast(arguments[0]); + if (n < 0) { + return VarValue::None(); + } + + VarValue cursor = self; + for (int i = 0; i < n; ++i) { + cursor = LinkedRefUtils::GetLinkedRef(cursor); + } + return cursor; +} + +VarValue PapyrusObjectReference::GetParentCell(VarValue self, + const std::vector&) +{ + if (auto selfRefr = GetFormPtr(self)) { + auto cellOrWorld = + selfRefr->GetCellOrWorld().ToFormId(selfRefr->GetParent()->espmFiles); + auto lookupRes = + selfRefr->GetParent()->GetEspm().GetBrowser().LookupById(cellOrWorld); + if (lookupRes.rec == nullptr) { + spdlog::warn("GetParentCell - nullptr cell/world found"); + } else if (lookupRes.rec->GetType() == espm::WRLD::kType) { + // TODO: support. at least you can use WRLD + x/y to find exterior cell + spdlog::warn( + "GetParentCell - exterior cells are not supported at this moment"); + } else if (lookupRes.rec->GetType() == espm::CELL::kType) { + return VarValue(std::make_shared(lookupRes)); + } else { + spdlog::warn("GetParentCell - bad record type {}", + lookupRes.rec->GetType().ToString()); + } + } + return VarValue::None(); +} + void PapyrusObjectReference::Register( VirtualMachine& vm, std::shared_ptr policy) { @@ -510,8 +638,14 @@ void PapyrusObjectReference::Register( AddMethod(vm, "SetPosition", &PapyrusObjectReference::SetPosition); AddMethod(vm, "GetBaseObject", &PapyrusObjectReference::GetBaseObject); AddMethod(vm, "PlayAnimation", &PapyrusObjectReference::PlayAnimation); + AddMethod(vm, "PlayAnimationAndWait", + &PapyrusObjectReference::PlayAnimationAndWait); AddMethod(vm, "PlayGamebryoAnimation", &PapyrusObjectReference::PlayGamebryoAnimation); AddMethod(vm, "MoveTo", &PapyrusObjectReference::MoveTo); AddMethod(vm, "SetOpen", &PapyrusObjectReference::SetOpen); + AddMethod(vm, "Is3DLoaded", &PapyrusObjectReference::Is3DLoaded); + AddMethod(vm, "GetLinkedRef", &PapyrusObjectReference::GetLinkedRef); + AddMethod(vm, "GetNthLinkedRef", &PapyrusObjectReference::GetNthLinkedRef); + AddMethod(vm, "GetParentCell", &PapyrusObjectReference::GetParentCell); } diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h index 65974a9d8f..a167499a9c 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h @@ -38,12 +38,22 @@ class PapyrusObjectReference final const std::vector& arguments); VarValue PlayAnimation(VarValue self, const std::vector& arguments); + VarValue PlayAnimationAndWait(VarValue self, + const std::vector& arguments); VarValue PlayGamebryoAnimation(VarValue self, const std::vector& arguments); VarValue MoveTo(VarValue self, const std::vector& arguments); VarValue SetOpen(VarValue self, const std::vector& arguments); + VarValue Is3DLoaded(VarValue self, const std::vector& arguments); + + VarValue GetLinkedRef(VarValue self, const std::vector& arguments); + + VarValue GetNthLinkedRef(VarValue self, const std::vector& arguments); + + VarValue GetParentCell(VarValue self, const std::vector& arguments); + void Register(VirtualMachine& vm, std::shared_ptr policy) override; }; diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index fe6605caab..5090e65867 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -7,6 +7,7 @@ #include "MpFormGameObject.h" #include "MpObjectReference.h" #include "PapyrusActor.h" +#include "PapyrusCell.h" #include "PapyrusDebug.h" #include "PapyrusEffectShader.h" #include "PapyrusForm.h" @@ -738,6 +739,7 @@ VirtualMachine& WorldState::GetPapyrusVm() pImpl->classes.emplace_back(std::make_unique()); pImpl->classes.emplace_back(std::make_unique()); pImpl->classes.emplace_back(std::make_unique()); + pImpl->classes.emplace_back(std::make_unique()); for (auto& cl : pImpl->classes) { cl->Register(*pImpl->vm, pImpl->policy); } diff --git a/unit/ActivateParentTest.cpp b/unit/ActivateParentTest.cpp new file mode 100644 index 0000000000..137ba57800 --- /dev/null +++ b/unit/ActivateParentTest.cpp @@ -0,0 +1,48 @@ +#include "MsgType.h" +#include "ServerState.h" +#include "TestUtils.hpp" +#include +#include + +PartOne& GetPartOne(); + +TEST_CASE( + "trapwallwood (54b15) in BleakFalls shouldn't be activatable by actors", + "[ActivateParentTest]") +{ + PartOne& p = GetPartOne(); + + p.worldState.npcSettings["Skyrim.esm"].spawnInInterior = true; + p.worldState.npcEnabled = true; + + auto& trapWallWood = p.worldState.GetFormAt(0x54b15); + + // Bandit will try to activate trapwallwood + uint32_t actorId = 0x39fe4; + auto& bandit = p.worldState.GetFormAt(actorId); + bandit.SetPos(trapWallWood.GetPos()); + + REQUIRE_THROWS_WITH(trapWallWood.Activate(bandit), + "Only activation parents can activate this object"); +} + +TEST_CASE("trapwallwood (54b15) in BleakFalls should be activatable by " + "activation parents", + "[ActivateParentTest]") +{ + PartOne& p = GetPartOne(); + + p.worldState.npcSettings["Skyrim.esm"].spawnInInterior = true; + p.worldState.npcEnabled = true; + + auto& trapWallWood = p.worldState.GetFormAt(0x54b15); + auto& plate = p.worldState.GetFormAt(0x567f2); + + auto activationParents = + espm::GetData(0x54b15, &p.worldState).activationParents; + REQUIRE(activationParents.size() == 1); + REQUIRE(activationParents[0].refrId == plate.GetFormId()); + REQUIRE(activationParents[0].delay == 0); + + // TODO: test (and implement) activation parenting +} diff --git a/viet/include/Promise.h b/viet/include/Promise.h index 86543828f9..12651c4615 100644 --- a/viet/include/Promise.h +++ b/viet/include/Promise.h @@ -99,6 +99,18 @@ class Promise return res; } + static Promise Any(const std::vector>& promises) + { + Promise res; + + for (const auto& promise : promises) { + promise.Then([res](const T& val) { res.Resolve(val); }) + .Catch([res](const char* err) { res.Reject(err); }); + } + + return res; + } + operator AnyPromise() { return AnyPromise(*this); } private: From 29773d13fce24b754be99e5b0ac95e4f14504115 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 8 Oct 2023 17:43:45 +0600 Subject: [PATCH 28/88] not so bad in activating childs --- .../server_guest_lib/MpObjectReference.cpp | 56 ++++++++++++++++++- .../cpp/server_guest_lib/MpObjectReference.h | 1 + .../cpp/server_guest_lib/WorldState.h | 2 + unit/ActivateParentTest.cpp | 42 +++++++++++++- 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 058832a85f..a4123c195e 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -13,6 +13,7 @@ #include "ScopedTask.h" #include "ScriptStorage.h" #include "ScriptVariablesHolder.h" +#include "TimeUtils.h" #include "WorldState.h" #include "libespm/GroupUtils.h" #include "libespm/Utils.h" @@ -292,13 +293,16 @@ void MpObjectReference::Activate(MpObjectReference& activationSource, if (auto worldState = activationSource.GetParent(); worldState->HasEspm()) { CheckInteractionAbility(activationSource); + // Block if only activation parents can activate this auto refrId = GetFormId(); - if (refrId < 0xff000000) { + if (refrId < 0xff000000 && !dynamic_cast(this)) { + auto lookupRes = worldState->GetEspm().GetBrowser().LookupById(refrId); auto data = espm::GetData(refrId, worldState); auto it = std::find_if( data.activationParents.begin(), data.activationParents.end(), [&](const espm::REFR::ActivationParentInfo& info) { - return info.refrId == activationSource.GetFormId(); + return lookupRes.ToGlobalId(info.refrId) == + activationSource.GetFormId(); }); if (it == data.activationParents.end()) { if (data.isParentActivationOnly) { @@ -314,6 +318,7 @@ void MpObjectReference::Activate(MpObjectReference& activationSource, if (!activationBlockedByMpApi && (!activationBlocked || defaultProcessingOnly)) { ProcessActivate(activationSource); + ActivateChilds(); } else { spdlog::trace( "Activation of form {:#x} has been blocked. Reasons: " @@ -1057,6 +1062,21 @@ void MpObjectReference::Init(WorldState* parent, uint32_t formId, FormDesc::FromFormId(formId, GetParent()->espmFiles); }, mode); + + auto refrId = GetFormId(); + if (parent->HasEspm() && refrId < 0xff000000 && + !dynamic_cast(this)) { + auto lookupRes = parent->GetEspm().GetBrowser().LookupById(refrId); + auto data = espm::GetData(refrId, parent); + for (auto& info : data.activationParents) { + auto activationParent = lookupRes.ToGlobalId(info.refrId); + + // Using WorldState for that, because we don't want search (potentially + // load) other references during OnInit + parent->activationChildsByActivationParent[activationParent].insert( + { refrId, info.delay }); + } + } } bool MpObjectReference::IsLocationSavingNeeded() const @@ -1211,6 +1231,38 @@ void MpObjectReference::ProcessActivate(MpObjectReference& activationSource) } } +void MpObjectReference::ActivateChilds() +{ + auto worldState = GetParent(); + if (!worldState) { + return; + } + + auto myFormId = GetFormId(); + + for (auto& pair : worldState->activationChildsByActivationParent[myFormId]) { + auto childRefrId = pair.first; + auto delay = pair.second; + + auto delayMs = Viet::TimeUtils::To(delay); + worldState->SetTimer(delayMs).Then([worldState, childRefrId, + myFormId](Viet::Void) { + auto childRefr = std::dynamic_pointer_cast( + worldState->LookupFormById(childRefrId)); + if (!childRefr) { + spdlog::warn("MpObjectReference::ActivateChilds {:x} - Bad/missing " + "activation child {:x}", + myFormId, childRefrId); + return; + } + + // Not sure about activationSource and defaultProcessingOnly in this + // case I'll try to keep vanilla scripts working + childRefr->Activate(worldState->GetFormAt(myFormId)); + }); + } +} + bool MpObjectReference::MpApiOnActivate(MpObjectReference& caster) { simdjson::dom::parser parser; diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.h b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h index 6a3eb07e0f..dfe0b728da 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.h +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h @@ -193,6 +193,7 @@ class MpObjectReference void CheckInteractionAbility(MpObjectReference& ac); bool IsLocationSavingNeeded() const; void ProcessActivate(MpObjectReference& activationSource); + void ActivateChilds(); bool MpApiOnActivate(MpObjectReference& caster); bool everSubscribedOrListened = false; diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.h b/skymp5-server/cpp/server_guest_lib/WorldState.h index cfa285f5d4..a7551345a6 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.h +++ b/skymp5-server/cpp/server_guest_lib/WorldState.h @@ -217,6 +217,8 @@ class WorldState std::shared_ptr logger; std::vector> listeners; std::unordered_map hosters; + std::unordered_map> + activationChildsByActivationParent; std::vector> lastMovUpdateByIdx; diff --git a/unit/ActivateParentTest.cpp b/unit/ActivateParentTest.cpp index 137ba57800..4fa313892f 100644 --- a/unit/ActivateParentTest.cpp +++ b/unit/ActivateParentTest.cpp @@ -25,6 +25,32 @@ TEST_CASE( REQUIRE_THROWS_WITH(trapWallWood.Activate(bandit), "Only activation parents can activate this object"); } +namespace { +class MyListener : public PartOneListener +{ +public: + void OnConnect(Networking::UserId userId) {} + void OnDisconnect(Networking::UserId userId) {} + void OnCustomPacket(Networking::UserId userId, + const simdjson::dom::element& content) + { + } + bool OnMpApiEvent(const char* eventName, + std::optional, + std::optional formId) + { + if (eventName == std::string("onActivate")) { + if (!formId) { + throw std::runtime_error("ActivateParentTest.cpp - null formId"); + } + numActivates[*formId]++; + } + return true; + } + + std::map numActivates; +}; +} TEST_CASE("trapwallwood (54b15) in BleakFalls should be activatable by " "activation parents", @@ -32,6 +58,9 @@ TEST_CASE("trapwallwood (54b15) in BleakFalls should be activatable by " { PartOne& p = GetPartOne(); + auto listener = std::make_shared(); + p.AddListener(listener); + p.worldState.npcSettings["Skyrim.esm"].spawnInInterior = true; p.worldState.npcEnabled = true; @@ -44,5 +73,16 @@ TEST_CASE("trapwallwood (54b15) in BleakFalls should be activatable by " REQUIRE(activationParents[0].refrId == plate.GetFormId()); REQUIRE(activationParents[0].delay == 0); - // TODO: test (and implement) activation parenting + REQUIRE(listener->numActivates[trapWallWood.GetFormId()] == 0); + REQUIRE(listener->numActivates[plate.GetFormId()] == 0); + + plate.Activate(plate); + + REQUIRE(listener->numActivates[trapWallWood.GetFormId()] == 0); + REQUIRE(listener->numActivates[plate.GetFormId()] == 1); + + p.Tick(); // tick timers, child activations are deferred + + REQUIRE(listener->numActivates[trapWallWood.GetFormId()] == 1); + REQUIRE(listener->numActivates[plate.GetFormId()] == 1); } From 64c15431a5775748bb44a6e0f15aaaa047dc1d3e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 8 Oct 2023 19:09:31 +0600 Subject: [PATCH 29/88] fix setopen. now one scripted door works --- .../src/papyrus-vm-lib/VirtualMachine.cpp | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp b/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp index fe6496a1ce..b5594f804e 100644 --- a/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp +++ b/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp @@ -150,22 +150,6 @@ VarValue VirtualMachine::CallMethod( return VarValue::None(); } - const char* nativeClass = selfObj->GetParentNativeScript(); - const char* base = nativeClass; - while (1) { - if (auto f = nativeFunctions[ToLower(base)][ToLower(methodName)]) { - auto self = VarValue(selfObj); - self.SetMetaStackIdHolder(stackIdHolder); - return f(self, arguments); - } - auto it = allLoadedScripts.find(base); - if (it == allLoadedScripts.end()) - break; - base = it->second.fn()->objectTable[0].parentClassName.data(); - if (!base[0]) - break; - } - for (auto& activeScript : selfObj->activePexInstances) { FunctionInfo functionInfo; @@ -185,6 +169,23 @@ VarValue VirtualMachine::CallMethod( } } + // natives have to be after non-natives (Bethesda overrides native functions in some scripts) + const char* nativeClass = selfObj->GetParentNativeScript(); + const char* base = nativeClass; + while (1) { + if (auto f = nativeFunctions[ToLower(base)][ToLower(methodName)]) { + auto self = VarValue(selfObj); + self.SetMetaStackIdHolder(stackIdHolder); + return f(self, arguments); + } + auto it = allLoadedScripts.find(base); + if (it == allLoadedScripts.end()) + break; + base = it->second.fn()->objectTable[0].parentClassName.data(); + if (!base[0]) + break; + } + std::string e = "Method not found - '"; e += base; e += (base[0] ? "." : "") + std::string(methodName) + "'"; From 4d78c795627d7fbff624c42fb1a477336e996bf8 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Mon, 9 Oct 2023 08:14:02 +0600 Subject: [PATCH 30/88] make OnCellLoad alias for OnInit --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index a4123c195e..02921c4e1e 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -787,6 +787,7 @@ void MpObjectReference::Subscribe(MpObjectReference* emitter, listener->GetChangeForm().profileId != -1) { emitter->pImpl->onInitEventSent = true; emitter->SendPapyrusEvent("OnInit"); + emitter->SendPapyrusEvent("OnCellLoad"); } const bool hasPrimitive = emitter->HasPrimitive(); From bf64d0ba87f37978f08e514890269f9262727d51 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Tue, 10 Oct 2023 14:27:11 +0600 Subject: [PATCH 31/88] get pillar puzzle to work --- papyrus-vm/include/papyrus-vm/Structures.h | 18 +- .../src/papyrus-vm-lib/ActivePexInstance.cpp | 197 +++++++++++++----- papyrus-vm/src/papyrus-vm-lib/IGameObject.cpp | 2 +- papyrus-vm/src/papyrus-vm-lib/VarValue.cpp | 1 + .../src/papyrus-vm-lib/VirtualMachine.cpp | 11 +- skymp5-scripts/pex/ActiveMagicEffect.pex | Bin 614 -> 614 bytes .../cpp/server_guest_lib/EspmGameObject.cpp | 16 ++ .../cpp/server_guest_lib/EspmGameObject.h | 6 + skymp5-server/cpp/server_guest_lib/MpForm.cpp | 26 ++- skymp5-server/cpp/server_guest_lib/MpForm.h | 11 +- .../cpp/server_guest_lib/MpFormGameObject.cpp | 27 ++- .../cpp/server_guest_lib/MpFormGameObject.h | 14 +- .../server_guest_lib/MpObjectReference.cpp | 36 ++-- .../PapyrusObjectReference.cpp | 16 +- .../server_guest_lib/PapyrusObjectReference.h | 2 + .../src/platform_se/pex/TESModPlatform.pex | Bin 2357 -> 2357 bytes unit/VarValueTest.cpp | 27 +++ unit/VirtualMachineTest.cpp | 12 ++ unit/papyrus_test_files/pex/AAATestObject.pex | Bin 797 -> 797 bytes unit/papyrus_test_files/pex/LatentTest.pex | Bin 1069 -> 1069 bytes unit/papyrus_test_files/pex/OpcodesTest.pex | Bin 12589 -> 12589 bytes 21 files changed, 347 insertions(+), 75 deletions(-) diff --git a/papyrus-vm/include/papyrus-vm/Structures.h b/papyrus-vm/include/papyrus-vm/Structures.h index 8bb32fad22..89316f2f23 100644 --- a/papyrus-vm/include/papyrus-vm/Structures.h +++ b/papyrus-vm/include/papyrus-vm/Structures.h @@ -31,8 +31,12 @@ class IGameObject bool HasScript(const char* name) const; -private: - std::vector> activePexInstances; +protected: + virtual const std::vector>& + ListActivePexInstances() const = 0; + + virtual void AddScript( + std::shared_ptr sctipt) noexcept = 0; }; enum class FunctionType @@ -462,6 +466,16 @@ class ActivePexInstance std::string nameNeedScript, VarValue activeInstanceOwner, const std::shared_ptr& mapForFillProperties); + static VarValue TryCastToBaseClass( + VirtualMachine& vm, const std::string& resultTypeName, + VarValue* scriptToCastOwner, std::vector& locals, + std::vector& outClassesStack); + + static VarValue TryCastMultipleInheritance( + VirtualMachine& vm, const std::string& resultTypeName, + VarValue* scriptToCastOwner, + std::vector& locals); + bool _IsValid = false; std::string childrenName; diff --git a/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp b/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp index 46707c412c..e90e5f9020 100644 --- a/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp +++ b/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp @@ -447,14 +447,39 @@ void ActivePexInstance::ExecuteOpCode(ExecutionContext* ctx, uint8_t op, IsSelfStr(*args[1]) ? activeInstanceOwner : *args[1]); if (!object) object = static_cast(activeInstanceOwner); - if (object && object->activePexInstances.size() > 0) { - auto inst = object->activePexInstances.back(); + if (object && object->ListActivePexInstances().size() > 0) { + auto inst = object->ListActivePexInstances().back(); Object::PropInfo* runProperty = GetProperty(*inst, nameProperty, Object::PropInfo::kFlags_Read); if (runProperty != nullptr) { *args[2] = inst->StartFunction(runProperty->readHandler, argsForCall, ctx->stackIdHolder); + spdlog::trace("propget function called"); + } else { + auto& instProps = inst->sourcePex.fn()->objectTable[0].properties; + auto it = + std::find_if(instProps.begin(), instProps.end(), + [&](const Object::PropInfo& propInfo) { + return !Utils::stricmp(propInfo.name.data(), + nameProperty.data()); + }); + if (it == instProps.end()) { + spdlog::trace("propget do nothing: prop {} not found", + nameProperty); + } else { + VarValue* var = inst->variables->GetVariableByName( + it->autoVarName.data(), *inst->sourcePex.fn()); + if (var) { + *args[2] = *var; + } else { + spdlog::trace("propget do nothing: variable {} not found", + it->autoVarName); + } + } } + spdlog::trace("propget propName={} object={} result={}", + args[0]->ToString(), args[1]->ToString(), + args[2]->ToString()); } } else { throw std::runtime_error( @@ -469,14 +494,39 @@ void ActivePexInstance::ExecuteOpCode(ExecutionContext* ctx, uint8_t op, IsSelfStr(*args[1]) ? activeInstanceOwner : *args[1]); if (!object) object = static_cast(activeInstanceOwner); - if (object && object->activePexInstances.size() > 0) { - auto inst = object->activePexInstances.back(); + if (object && object->ListActivePexInstances().size() > 0) { + auto inst = object->ListActivePexInstances().back(); Object::PropInfo* runProperty = GetProperty(*inst, nameProperty, Object::PropInfo::kFlags_Write); if (runProperty != nullptr) { inst->StartFunction(runProperty->writeHandler, argsForCall, ctx->stackIdHolder); + spdlog::trace("propset function called"); + } else { + auto& instProps = inst->sourcePex.fn()->objectTable[0].properties; + auto it = + std::find_if(instProps.begin(), instProps.end(), + [&](const Object::PropInfo& propInfo) { + return !Utils::stricmp(propInfo.name.data(), + nameProperty.data()); + }); + if (it == instProps.end()) { + spdlog::trace("propset do nothing: prop {} not found", + nameProperty); + } else { + VarValue* var = inst->variables->GetVariableByName( + it->autoVarName.data(), *inst->sourcePex.fn()); + if (var) { + *var = *args[2]; + } else { + spdlog::trace("propset do nothing: variable {} not found", + it->autoVarName); + } + } } + spdlog::trace("propset propName={} object={} result={}", + args[0]->ToString(), args[1]->ToString(), + args[2]->ToString()); } } else { throw std::runtime_error( @@ -802,67 +852,120 @@ uint8_t ActivePexInstance::GetArrayTypeByElementType(uint8_t type) return returnType; } -void ActivePexInstance::CastObjectToObject(VarValue* result, - VarValue* scriptToCastOwner, - std::vector& locals) +VarValue ActivePexInstance::TryCastToBaseClass( + VirtualMachine& vm, const std::string& resultTypeName, + VarValue* scriptToCastOwner, std::vector& locals, + std::vector& outClassesStack) { - std::string objectToCastTypeName = scriptToCastOwner->objectType; - const std::string& resultTypeName = result->objectType; + auto object = static_cast(*scriptToCastOwner); + if (!object) { + return VarValue::None(); + } - if (scriptToCastOwner->GetType() != VarValue::kType_Object || - *scriptToCastOwner == VarValue::None()) { - *result = VarValue::None(); - if (spdlog::should_log(spdlog::level::trace)) { - spdlog::trace("CastObjectToObject {} -> {} (object is null)", - scriptToCastOwner->ToString(), result->ToString()); + std::string scriptName = object->GetParentNativeScript(); + outClassesStack.push_back(scriptName); + while (true) { + if (scriptName.empty()) { + break; + } + + if (!Utils::stricmp(resultTypeName.data(), scriptName.data())) { + return *scriptToCastOwner; } - return; + + // TODO: Test this with attention + // Here is the case when i.e. variable with type 'Form' casts to + // 'ObjectReference' while it's actually an Actor + + auto myScriptPex = vm.GetPexByName(scriptName); + + if (!myScriptPex.fn) { + spdlog::error("Script not found: {}", scriptName); + break; + } + + scriptName = myScriptPex.fn()->objectTable[0].parentClassName; + outClassesStack.push_back(scriptName); } - std::vector classesStack; + return VarValue::None(); +} +VarValue ActivePexInstance::TryCastMultipleInheritance( + VirtualMachine& vm, const std::string& resultTypeName, + VarValue* scriptToCastOwner, std::vector& locals) +{ auto object = static_cast(*scriptToCastOwner); - if (object) { - std::string scriptName = object->GetParentNativeScript(); - classesStack.push_back(scriptName); - while (1) { - if (scriptName.empty()) { - break; - } + if (!object) { + return VarValue::None(); + } - if (!Utils::stricmp(resultTypeName.data(), scriptName.data())) { - *result = *scriptToCastOwner; - if (spdlog::should_log(spdlog::level::trace)) { - spdlog::trace("CastObjectToObject {} -> {} (match found: {})", - scriptToCastOwner->ToString(), result->ToString(), - resultTypeName); - } - return; - } + // TODO: support cast to base class in parallel inheritance chain + // i.e. X extends Y, in script Z we cast smth to X while it is Y + // Current code would fail in this case. I guess it'd be better to find such + // cases in game, then implement. + for (auto& script : object->ListActivePexInstances()) { + if (Utils::stricmp(script->GetSourcePexName().data(), + resultTypeName.data()) == 0) { + return script->activeInstanceOwner; + } + } - // TODO: Test this with attention - // Here is the case when i.e. variable with type 'Form' casts to - // 'ObjectReference' while it's actually an Actor + return VarValue::None(); +} - auto myScriptPex = parentVM->GetPexByName(scriptName); +void ActivePexInstance::CastObjectToObject(VarValue* result, + VarValue* scriptToCastOwner, + std::vector& locals) +{ + static const VarValue kNone = VarValue::None(); - if (!myScriptPex.fn) { - spdlog::error("Script not found: {}", scriptName); - break; - } + if (scriptToCastOwner->GetType() != VarValue::kType_Object) { + *result = kNone; + return spdlog::trace( + "CastObjectToObject {} -> {} (object is not an object)", + scriptToCastOwner->ToString(), result->ToString()); + } + + if (*scriptToCastOwner == kNone) { + *result = kNone; + return spdlog::trace("CastObjectToObject {} -> {} (object is None)", + scriptToCastOwner->ToString(), result->ToString()); + } + + const std::string& resultTypeName = result->objectType; - scriptName = myScriptPex.fn()->objectTable[0].parentClassName; - classesStack.push_back(scriptName); + VarValue tmp; + std::vector outClassesStack; + + if (tmp == kNone) { + tmp = TryCastToBaseClass(*parentVM, resultTypeName, scriptToCastOwner, + locals, outClassesStack); + if (tmp != kNone) { + spdlog::trace("CastObjectToObject {} -> {} (base class found: {})", + scriptToCastOwner->ToString(), tmp.ToString(), + resultTypeName); } } - *result = VarValue::None(); - if (spdlog::should_log(spdlog::level::trace)) { + if (tmp == kNone) { + tmp = TryCastMultipleInheritance(*parentVM, resultTypeName, + scriptToCastOwner, locals); + if (tmp != kNone) { + spdlog::trace( + "CastObjectToObject {} -> {} (multiple inheritance found: {})", + scriptToCastOwner->ToString(), tmp.ToString(), resultTypeName); + } + } + + if (tmp == kNone) { spdlog::trace( "CastObjectToObject {} -> {} (match not found, wanted {}, stack is {})", - scriptToCastOwner->ToString(), result->ToString(), resultTypeName, - fmt::join(classesStack, ", ")); + scriptToCastOwner->ToString(), tmp.ToString(), resultTypeName, + fmt::join(outClassesStack, ", ")); } + + *result = tmp; } bool ActivePexInstance::HasParent(ActivePexInstance* script, diff --git a/papyrus-vm/src/papyrus-vm-lib/IGameObject.cpp b/papyrus-vm/src/papyrus-vm-lib/IGameObject.cpp index 436f5b0a4e..55283f0daa 100644 --- a/papyrus-vm/src/papyrus-vm-lib/IGameObject.cpp +++ b/papyrus-vm/src/papyrus-vm-lib/IGameObject.cpp @@ -3,7 +3,7 @@ bool IGameObject::HasScript(const char* scriptName) const { - for (auto& instance : activePexInstances) { + for (auto& instance : ListActivePexInstances()) { const std::string& sourcePexName = instance->GetSourcePexName(); if (!Utils::stricmp(sourcePexName.data(), scriptName)) { return true; diff --git a/papyrus-vm/src/papyrus-vm-lib/VarValue.cpp b/papyrus-vm/src/papyrus-vm-lib/VarValue.cpp index da1788dad9..5693d96f50 100644 --- a/papyrus-vm/src/papyrus-vm-lib/VarValue.cpp +++ b/papyrus-vm/src/papyrus-vm-lib/VarValue.cpp @@ -574,6 +574,7 @@ VarValue& VarValue::operator=(const VarValue& arg2) data.string = stringHolder->data(); } else { stringHolder.reset(); + // data.string ptr is copied by 'data = arg2.data;' line in this case } return *this; diff --git a/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp b/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp index b5594f804e..1d11d6720f 100644 --- a/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp +++ b/papyrus-vm/src/papyrus-vm-lib/VirtualMachine.cpp @@ -80,7 +80,9 @@ void VirtualMachine::AddObject(std::shared_ptr self, } } - self->activePexInstances = scriptsForObject; + for (auto& script : scriptsForObject) { + self->AddScript(script); + } gameObjectsHolder.insert(self); } @@ -89,7 +91,7 @@ void VirtualMachine::SendEvent(std::shared_ptr self, const std::vector& arguments, OnEnter enter) { - for (auto& scriptInstance : self->activePexInstances) { + for (auto& scriptInstance : self->ListActivePexInstances()) { auto name = scriptInstance->GetActiveStateName(); auto fn = scriptInstance->GetFunctionByName( @@ -150,7 +152,7 @@ VarValue VirtualMachine::CallMethod( return VarValue::None(); } - for (auto& activeScript : selfObj->activePexInstances) { + for (auto& activeScript : selfObj->ListActivePexInstances()) { FunctionInfo functionInfo; if (!Utils::stricmp(methodName, "GotoState") || @@ -169,7 +171,8 @@ VarValue VirtualMachine::CallMethod( } } - // natives have to be after non-natives (Bethesda overrides native functions in some scripts) + // natives have to be after non-natives (Bethesda overrides native functions + // in some scripts) const char* nativeClass = selfObj->GetParentNativeScript(); const char* base = nativeClass; while (1) { diff --git a/skymp5-scripts/pex/ActiveMagicEffect.pex b/skymp5-scripts/pex/ActiveMagicEffect.pex index fcd4d8d3b1ef3e247d762115bf564878b625b6f4..3e36ce0a665b36ae7258c339ddbe3a137d0b9c59 100644 GIT binary patch delta 192 zcmaFH@{C2`SNMT@%uEc73_y^oBGoieU?vVl;9%ecnE?TeQj82-49pDN3_MT)240{F vMg~5h5VJTV11lo~Kaj@^lH~=eWn|z5in1VyGOz-b3P5y2NEV=gAd&z8BK;Ug delta 191 zcmaFH@{C2`SNMT@%uEc73_y^oIK5}0z?6yiycOAkONuh{(iu3dto-uxQo|C97+8u^ zbJ7^tt*nAe5=&AiuGV5SnOw-IJvoiBr-Ye-gMkxd1Ow0{E(UHO$paK(7H4E&Wn|z5 t@|Zz#Kn5RB9w^F!B+9@9)W8pthX6(?pfUzlpnd_E5Qt_03J4+z008z}8Iu42 diff --git a/skymp5-server/cpp/server_guest_lib/EspmGameObject.cpp b/skymp5-server/cpp/server_guest_lib/EspmGameObject.cpp index 85f74c72dc..93aa797d05 100644 --- a/skymp5-server/cpp/server_guest_lib/EspmGameObject.cpp +++ b/skymp5-server/cpp/server_guest_lib/EspmGameObject.cpp @@ -2,6 +2,7 @@ #include #include +#include EspmGameObject::EspmGameObject(const espm::LookupResult& record_) : record(record_) @@ -336,3 +337,18 @@ const char* EspmGameObject::GetStringID() } return v->data(); } + +const std::vector>& +EspmGameObject::ListActivePexInstances() const +{ + static const std::vector> + kEmptyScriptsArray; + return kEmptyScriptsArray; +} + +void EspmGameObject::AddScript( + std::shared_ptr sctipt) noexcept +{ + spdlog::critical("EspmGameObject::AddScript is not supported"); + std::terminate(); +} diff --git a/skymp5-server/cpp/server_guest_lib/EspmGameObject.h b/skymp5-server/cpp/server_guest_lib/EspmGameObject.h index d9beb2d942..d2448e4245 100644 --- a/skymp5-server/cpp/server_guest_lib/EspmGameObject.h +++ b/skymp5-server/cpp/server_guest_lib/EspmGameObject.h @@ -12,6 +12,12 @@ class EspmGameObject : public IGameObject const char* GetStringID() override; const espm::LookupResult record; + +protected: + const std::vector>& + ListActivePexInstances() const override; + + void AddScript(std::shared_ptr sctipt) noexcept override; }; const espm::LookupResult& GetRecordPtr(const VarValue& papyrusObject); diff --git a/skymp5-server/cpp/server_guest_lib/MpForm.cpp b/skymp5-server/cpp/server_guest_lib/MpForm.cpp index 7236a8e28a..ac6b1ffde2 100644 --- a/skymp5-server/cpp/server_guest_lib/MpForm.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpForm.cpp @@ -26,12 +26,36 @@ void MpForm::SendPapyrusEvent(const char* eventName, const VarValue* arguments, VarValue MpForm::ToVarValue() const { + // TODO: consider using owning VarValue instead of non-owning return VarValue(ToGameObject().get()); } std::shared_ptr MpForm::ToGameObject() const { - if (!gameObject) + if (!gameObject) { gameObject.reset(new MpFormGameObject(const_cast(this))); + } return gameObject; } + +const std::vector>& +MpForm::ListActivePexInstances() const +{ + return activePexInstances; +} + +void MpForm::AddScript( + const std::shared_ptr& script) noexcept +{ + const std::string& name = script->GetSourcePexName(); + auto it = std::find_if(activePexInstances.begin(), activePexInstances.end(), + [&](const std::shared_ptr& item) { + return item->GetSourcePexName() == name; + }); + if (it != activePexInstances.end()) { + spdlog::warn("MpForm::AddScript {:x} - Already added script name {}", + GetFormId(), name); + return; + } + activePexInstances.push_back(script); +} diff --git a/skymp5-server/cpp/server_guest_lib/MpForm.h b/skymp5-server/cpp/server_guest_lib/MpForm.h index 48ceaf5d0f..0feb7f8afa 100644 --- a/skymp5-server/cpp/server_guest_lib/MpForm.h +++ b/skymp5-server/cpp/server_guest_lib/MpForm.h @@ -1,17 +1,20 @@ #pragma once #include "NiPoint3.h" -#include "papyrus-vm/Structures.h" #include #include #include #include +#include class WorldState; class IGameObject; +class ActivePexInstance; +struct VarValue; class MpForm { friend class WorldState; + friend class MpFormGameObject; public: MpForm(); @@ -68,7 +71,13 @@ class MpForm uint32_t id = 0; WorldState* parent = nullptr; mutable GameObjectPtr gameObject; + std::vector> activePexInstances; protected: virtual void BeforeDestroy(){}; + + const std::vector>& + ListActivePexInstances() const; + + void AddScript(const std::shared_ptr &script) noexcept; }; diff --git a/skymp5-server/cpp/server_guest_lib/MpFormGameObject.cpp b/skymp5-server/cpp/server_guest_lib/MpFormGameObject.cpp index e3e120829d..119c567ada 100644 --- a/skymp5-server/cpp/server_guest_lib/MpFormGameObject.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpFormGameObject.cpp @@ -16,8 +16,9 @@ MpForm* MpFormGameObject::GetFormPtr() const noexcept { if (parent) { bool formStillValid = parent->LookupFormById(formId).get() == form; - if (!formStillValid) + if (!formStillValid) { return nullptr; + } } return form; } @@ -47,3 +48,27 @@ const char* MpFormGameObject::GetStringID() } return v->data(); } + +const std::vector>& +MpFormGameObject::ListActivePexInstances() const +{ + static const std::vector> + kEmptyScriptsArray; + + auto form = GetFormPtr(); + if (!form) { + return kEmptyScriptsArray; + } + return form->ListActivePexInstances(); +} + +void MpFormGameObject::AddScript( + std::shared_ptr script) noexcept +{ + auto form = GetFormPtr(); + if (!form) { + spdlog::critical("MpFormGameObject::AddScript - Invalid form"); + std::terminate(); + } + return form->AddScript(script); +} diff --git a/skymp5-server/cpp/server_guest_lib/MpFormGameObject.h b/skymp5-server/cpp/server_guest_lib/MpFormGameObject.h index e4f141f107..962bf83057 100644 --- a/skymp5-server/cpp/server_guest_lib/MpFormGameObject.h +++ b/skymp5-server/cpp/server_guest_lib/MpFormGameObject.h @@ -13,6 +13,11 @@ class MpFormGameObject : public IGameObject const char* GetStringID() override; + const std::vector>& + ListActivePexInstances() const override; + + void AddScript(std::shared_ptr script) noexcept override; + private: WorldState* const parent; MpForm* const form; @@ -22,11 +27,16 @@ class MpFormGameObject : public IGameObject template inline T* GetFormPtr(const VarValue& papyrusObject) { - if (papyrusObject.GetType() != VarValue::kType_Object) + if (papyrusObject.GetType() != VarValue::kType_Object) { return nullptr; + } + auto gameObject = static_cast(papyrusObject); + auto mpFormGameObject = dynamic_cast(gameObject); - if (!mpFormGameObject) + if (!mpFormGameObject) { return nullptr; + } + return dynamic_cast(mpFormGameObject->GetFormPtr()); } diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 02921c4e1e..8a811316f6 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -293,9 +293,13 @@ void MpObjectReference::Activate(MpObjectReference& activationSource, if (auto worldState = activationSource.GetParent(); worldState->HasEspm()) { CheckInteractionAbility(activationSource); + // Pillars puzzle Bleak Falls Barrow + bool workaroundBypassParentsCheck = &activationSource == this; + // Block if only activation parents can activate this auto refrId = GetFormId(); - if (refrId < 0xff000000 && !dynamic_cast(this)) { + if (!workaroundBypassParentsCheck && refrId < 0xff000000 && + !dynamic_cast(this)) { auto lookupRes = worldState->GetEspm().GetBrowser().LookupById(refrId); auto data = espm::GetData(refrId, worldState); auto it = std::find_if( @@ -640,12 +644,14 @@ void MpObjectReference::AddItem(uint32_t baseId, uint32_t count) }); SendInventoryUpdate(); - auto baseItem = VarValue(static_cast(baseId)); - auto itemCount = VarValue(static_cast(count)); - auto itemReference = VarValue((IGameObject*)nullptr); - auto sourceContainer = VarValue((IGameObject*)nullptr); - VarValue args[4] = { baseItem, itemCount, itemReference, sourceContainer }; - SendPapyrusEvent("OnItemAdded", args, 4); + // TODO: No one used it due to incorrect baseItem which should be object, + // not id. Needs to be revised. Seems to also be buggy + // auto baseItem = VarValue(static_cast(baseId)); + // auto itemCount = VarValue(static_cast(count)); + // auto itemReference = VarValue((IGameObject*)nullptr); + // auto sourceContainer = VarValue((IGameObject*)nullptr); + // VarValue args[4] = { baseItem, itemCount, itemReference, sourceContainer + // }; SendPapyrusEvent("OnItemAdded", args, 4); } void MpObjectReference::AddItems(const std::vector& entries) @@ -658,14 +664,14 @@ void MpObjectReference::AddItems(const std::vector& entries) SendInventoryUpdate(); } - for (const auto& entri : entries) { - auto baseItem = VarValue(static_cast(entri.baseId)); - auto itemCount = VarValue(static_cast(entri.count)); - auto itemReference = VarValue((IGameObject*)nullptr); - auto sourceContainer = VarValue((IGameObject*)nullptr); - VarValue args[4] = { baseItem, itemCount, itemReference, sourceContainer }; - SendPapyrusEvent("OnItemAdded", args, 4); - } + // for (const auto& entri : entries) { + // auto baseItem = VarValue(static_cast(entri.baseId)); + // auto itemCount = VarValue(static_cast(entri.count)); + // auto itemReference = VarValue((IGameObject*)nullptr); + // auto sourceContainer = VarValue((IGameObject*)nullptr); + // VarValue args[4] = { baseItem, itemCount, itemReference, sourceContainer + // }; SendPapyrusEvent("OnItemAdded", args, 4); + // } } void MpObjectReference::RemoveItem(uint32_t baseId, uint32_t count, diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp index 137bce6085..7e829238bc 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp @@ -294,7 +294,9 @@ VarValue PapyrusObjectReference::Activate( } auto akActivator = GetFormPtr(arguments[0]); if (!akActivator) { - throw std::runtime_error("Activate didn't recognize akActivator"); + spdlog::warn("Activate didn't recognize akActivator"); + // workaround for defaultPillarPuzzlelever script + akActivator = selfRefr; } bool abDefaultProcessingOnly = static_cast(arguments[1].CastToBool()); @@ -609,6 +611,17 @@ VarValue PapyrusObjectReference::GetParentCell(VarValue self, return VarValue::None(); } +VarValue PapyrusObjectReference::GetOpenState(VarValue self, + const std::vector&) +{ + if (auto selfRefr = GetFormPtr(self)) { + if (selfRefr->GetBaseType() == "DOOR") { + return selfRefr->IsOpen() ? VarValue(1) : VarValue(3); + } + } + return VarValue(0); +} + void PapyrusObjectReference::Register( VirtualMachine& vm, std::shared_ptr policy) { @@ -648,4 +661,5 @@ void PapyrusObjectReference::Register( AddMethod(vm, "GetLinkedRef", &PapyrusObjectReference::GetLinkedRef); AddMethod(vm, "GetNthLinkedRef", &PapyrusObjectReference::GetNthLinkedRef); AddMethod(vm, "GetParentCell", &PapyrusObjectReference::GetParentCell); + AddMethod(vm, "GetOpenState", &PapyrusObjectReference::GetOpenState); } diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h index a167499a9c..7419ad9869 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h @@ -54,6 +54,8 @@ class PapyrusObjectReference final VarValue GetParentCell(VarValue self, const std::vector& arguments); + VarValue GetOpenState(VarValue self, const std::vector& arguments); + void Register(VirtualMachine& vm, std::shared_ptr policy) override; }; diff --git a/skyrim-platform/src/platform_se/pex/TESModPlatform.pex b/skyrim-platform/src/platform_se/pex/TESModPlatform.pex index 93ca2e03a0fd03a15995159480e1dfdec1a73e49..1eb3eef8ee85b71192bea55e1a99b3cab23eb6df 100644 GIT binary patch delta 1061 zcmZ8f*-{fh6g@o)5ZQ4-Llue;#RYdjaAQ#*3Zg_r+;B4Kz(_JfO{NOVmwd#0iDmKN zo6nZt;1}o*@Z_0E7fo@y&P@88dv4#{p1q#Ep0541pMOjP0t`8no&VOgFD7lrje-!C z7s)EDxge~fr6SAGg5#H9%>+ROwl8<{C75|XLTluNn=+cFDb5+Ab!#^WeY7OcMY>>B zgOWru9R=^Jgz4|N$Ryd}j8o&jCcmK94XRbAAa|V5iDYTft3}J@e5r<}LKJ%bCNi@@ zB?!}AtYTuz)V4i8C1HiK9HMi%_*uHqO8n1Q>b8rOz}-g14gARQd>O`n&1^^e!FGj? zc-{2kU*=%^!x-*xT$bE&yf7^pPg&!!Z4bx4t%-QoTKzs^XGOgi{f$YPFmaaa90qV6 zgURsFqeX-C0EUoDh2vTfxIkADHpO)4Cfab3n_=P-ahbS6TqQ<`Ys7Wp26A=WqR5`k}ATXxLHcc8B$6ZV)q3ftHR#!?ZvsCW_lem|*lfivW zK43-?9Yfyfh2i6rXB^uZ~CGrEkxGxh&ZA0`)h z5lNiTq?KO65<7WLEUW%c>4Y}CU`NT_zGT7*PcLx2LOq97p7EN*8eMNFuH!A<@ts67 z-)bx--qZbo#78Bz(5S>T8SVPK#@siskt&@>g-@zPUyX{>a5tU?yC`ZxWCdJgnN`vx i_^I|oHVA<-EdraQ%wB;$fi3RLL~W;;I$4%EVE+SphKol4 delta 1048 zcmZ8d%Tg0T6g@rN0Y*R(A81k~qNt#v_y7f;JVa1b1QGE8PLd8x5oV~#SfC5@5wo$} z_ycY%i@l%W%B3s6fR$$^O-=N6olN&R_nv!mx7)Yd+jbUy{E`*~7`p4z=WlI0BBv(& zU>zAxZ%!2j3D4JPOzfGkwWMP$T-PIZTf)+HR}6iG{yI6T?!$+ zjRNc>anz}3$co-VBaP?W4;pISFKXChe%TL_mMYJr$X314A8WDGuB^FUBBFJ-e=w^{ z0hAl+_2{E?8yn`r!1Y$5-?DpHh3_|L%GvU04)s>}F3>YhxQeDSXPI=oA~N~1Us!{^ z>bOD8Sp`l(M_;XMG@=HhpK3n(qK2YP)$_eClNHr=9IGuy!a^s{adgGI!jV)EI6)zf z48>%X5-m8%M>lbbI8B@(dWc@)EOCxFPxKKNh&T?^yIw?pHDM2>tc6Rsj4MPOE%m}x z3}Dds>`w)OYldt{Nelfv*NtFfXm@OZVcNveNKqvHB_wX}AKoN$%apX$d&Ql1o9yA; ztRS(0N;7TT#vR<{8Nn#VFphh~1aY63B&LWs_L`z9TU&_zADG66Q@a*Cq}?#pF0T(gaye_7t~xZB~9XRR!J{ za$ic;rd=}4TK^Z8v79t%V&y+;YEJhi@YalytFYcl3e1lrk+0na3QQ9yl9C+)IRVX= djMY|w5_PiKIu==`t{A7}W6@zXB?Plee*vO_hoAre diff --git a/unit/VarValueTest.cpp b/unit/VarValueTest.cpp index 5efb4eacf3..65541226fa 100644 --- a/unit/VarValueTest.cpp +++ b/unit/VarValueTest.cpp @@ -20,6 +20,19 @@ TEST_CASE("operator= for owning objects", "[VarValue]") { class MyObject : public IGameObject { + const std::vector>& + ListActivePexInstances() const override + { + static const std::vector> + kEmptyScripts; + return kEmptyScripts; + } + + void AddScript(std::shared_ptr sctipt) noexcept override + { + spdlog::critical("VarValueTest.cpp: AddScript not implemented"); + std::terminate(); + } }; VarValue var; @@ -42,6 +55,20 @@ TEST_CASE("operator== for objects", "[VarValue]") return false; } + const std::vector>& + ListActivePexInstances() const override + { + static const std::vector> + kEmptyScripts; + return kEmptyScripts; + } + + void AddScript(std::shared_ptr sctipt) noexcept override + { + spdlog::critical("VarValueTest.cpp: AddScript not implemented"); + std::terminate(); + } + private: int i; }; diff --git a/unit/VirtualMachineTest.cpp b/unit/VirtualMachineTest.cpp index 98c772cef9..f814390fc1 100644 --- a/unit/VirtualMachineTest.cpp +++ b/unit/VirtualMachineTest.cpp @@ -93,8 +93,20 @@ class TestObject : public IGameObject { public: std::string myId = "0x006AFF2E"; + std::vector> scripts; const char* GetStringID() override { return myId.c_str(); }; + + const std::vector>& + ListActivePexInstances() const override + { + return scripts; + } + + void AddScript(std::shared_ptr sctipt) noexcept override + { + scripts.push_back(sctipt); + } }; class MyScriptVariablesHolder : public ScriptVariablesHolder diff --git a/unit/papyrus_test_files/pex/AAATestObject.pex b/unit/papyrus_test_files/pex/AAATestObject.pex index e95b15d4b9e7484c2f83e1bbdce92906d79c8daa..63616a651040fc2ab6c62fbe1d543ed0a43cfc8a 100644 GIT binary patch delta 35 pcmbQsHkVD{SNMT@%uEc73_y^oBCWAe;5;Kss?s#J&5s$a7y+}y38DZ1 delta 35 qcmbQsHkVD{SNMT@%uEc73_y^oIDO$pf%A+kDH?^5n;$b;F#-U?ehS9` diff --git a/unit/papyrus_test_files/pex/LatentTest.pex b/unit/papyrus_test_files/pex/LatentTest.pex index 154d46af2152205f3c8dec25a5690d3b667abae7..4a51cb28a90039038819b30a256a7855ae3af5d2 100644 GIT binary patch literal 1069 zcmb7CO>fgc5S@*kujZqHHk1&cJ|NKxsDxg~4b)IoDs8185J%VEG>g*RU~dxfi})M- z5`G9L-gxaa?G0O=ee-ta?eo}wzyAK?l}HdS`J#V7?X%EIZ9mJ@zMiKMwP(_370*pN zmou}Z?X%Iv`RVcH#rSeGI2s(%K5gB$K&5sf?bt$u>LJT{A~nI!*Hs5F&14&3sVnnr(+*GlKAMvrvtaAm6ESZZoSM#su3qr(~bI7*Ut zsx4KALo09QZ%Bkx9}aorOPCb&A&H2@6}@9i+#J|xXoGa6Y&4ZAGL;7A>bs0=U=30k zU>=d0C>aN7_O>-GSGkq3O_aVSKb5lypWx`1ceN8~7l~$R8AOXD!O(N1SED5-iHxLL zLVq=t><13AlL7|LSPV0C^dIHnD*PEt5_6M%@c7`zH)`OU2`cdqz`!ejUx0D}Dg~$( zphl>#(^f80=X4u)2L%E&`LvtgOz=_JPLr1swTRjRJafOWW@sI`z6`O9&22%m;|OL2 zJwWRBfCW{Nah1q|C^D?~_Q5>C18?`VtA(})xrffgc5PfSqA8yjL^pmzg%?U1`5_%ywP(xLzw3UKD936Yp7^SM`1s^G5XOWp? zOtE+oe~m`DzQMEqs4^P$fypy^(VSMJspP_n1cjW8$_r1Qe))tJ-I;*LKL9(va{}kooKtsB z!#Pb*eu$MyBxJZo&pIUtq7h)LdYIr=Wu(oT}sdFFnQExqN=`E?R2S)bC^3EZQX zSLgy9^{_#3lU{$1us~g4gAd{@H=lZK61z_Z4Oi?_F01n5QL9!Zrtz!Z%nEE146J;E z5G#vv6AqF6Cr%7C+(y%BTd5s_)uc6n9ae#@4DP~Z6g8(%CR2W!h+Qspt3||p2iSVp o!~QKHXp#fEpDjGFq`*VUpoT>Gy8>;2N3K16tpkb^+xmR diff --git a/unit/papyrus_test_files/pex/OpcodesTest.pex b/unit/papyrus_test_files/pex/OpcodesTest.pex index b2da626b1c2a4e27b396b56915ff449bfd58bc9c..7d668ca3de3a6f2d094cb952c0f9887b9518ff72 100644 GIT binary patch literal 12589 zcmb_i36xaTnf`BeRqu4O%Hl#H8pR;e3j&QmH%k*lX`~4zppxqD*HBPYwN=$LZA9B> zBteZD18M}pEpba?8;ughkmO`BIVa0FPEJmeF}uleoN=<8nPev8eE)y%ef_HL>!K%T z`lGAv@-N^0?|+x~n!jFo@}J8hqzFIWIj5gb^&NfPnI12<$;;*E_2s&$F6Cv?$)3JU zuFp$l_E7z0t2@@OUA3X3bHnP!=EnJSl4=4vuv59l%Xj7zd5@~rWbzqZEN{!@ylkFg z8?woCo@&{Cdw;q+pUkAGHj`fAZBM4PXmuvNI^Cno(aRIbG?leyGE}xAk)>&Cd%Sc$ z*_-sT8@*g6)zA8Di>gFHisRZ7-T6#5nMg^sVr@FFMcPxDgf3Qh=D}mT7VqfuvI!{2 zps9MJm+#MNqt#%Y+-@9Pxg(kKw4t_aHZds0^0mG4UAZ!m)28cICQ_*li7a?ZPwj?m z1`hICz9uWyHz!j4h>uT{%0Xyl0>eo5wxxSkr*a-uwk?G8 zEy1xuA6051Td$^?UY>~c{kyxoEX8D2(56Q#T+6i$8VA4Lb}ws0YqT+~P$6Hi#^f4l z+#U|;ri`93s`hsGMDri?cK#I4g20TLz7ORy2;Jut*rM<1yDb1f^*n|4DkGQdcs{M^LSZ<`6)qL!jE%qWaDgOQ9!%LUWjnBgGGW^5WCHTiJ`wY7aB@AO0 zH>+>HT~rPJo+CF0_Rech?`s83MzhgsHes9BkAXFA?K} z7@j~>M_;9{1K$vQlfFsc0$vq-SA-@KrtACkL*O3-uhDDtkAm0fb^0gZN5GE-KOx3X z1wSK=H|P!ex!@P{3;Lzt*Ys=p4e(pRoAf6A4*0#`EqaUoMesJgP5&x*hu)!o2mT=V zBmI&71pHa>A3}2yo=E7w=x>7mAwW`CApxjDMO8Uap(+8S7V>2Z(L`0JCa6ikWHm)i z1*QWt49-$BfmtGa8qqd&u9^c}0L%q01TF&R0}VivpjkDm1%icYp;`pA0840bBue0-J#k2)3v#>MFrC>Ke5TNB~_x56}y22a>=}AO-9O zGC&`Ym0?UJTB7!-y}+R0I;C-gx+C>_N)EsaJPZwp>kud43|zArSUV-r!Ys~@Q!13v+N2D~9% zGl*){uhegVH-X;+Zvk%u|0V*bBX!iDq<97rPW=V=EAZbM(s3qHgny)R7Pi7jMWhno zyqihb8)Nidnk%>vO>y0{m|6r&u+`2|bT;QGerEO4Y+b_V6gr2_<<SXZ4uLLHCweqqBiSn1+BEjRT$4|JIo3&Z-*HfeFe?Ifurb5#B7aj z;_Ca1{c@NWIddpcjct%tQZvv|c_t2{fbv?y^7Ii|MQep9Rz6D8tTYd%k}gGc9l9^W z5H82>dgyn6zy>hB0>8_r!lLk+(q+|BTCcUD zRtt@+B{RaZYzijVg^FD+y~bp;^%zm8Scfkbp-x2|;XA1D9ml)z59zfWdClacJ#Nt!h&|boAFpmgrE@4A6U3N+yS|WTb z+Qys`UXU&rt(FL93W*Jn7^mn8lu+wH>2!QW=t_T<{JeErBDj^Eg-q9r311(Sr4v03 zB~Gm}ESGa8b18yS8%+ohK~J-cZdXbh%O9v zvLoINk7tO-1bbBS0f$|Rbc5rvD?}p_(1?slqb#6NJ|>N5Kx5jNG-3gby<^fSAJK^X zz}3(#fnfL0BsbU|g58T?S32U`bqkW*^MlPKd$|U)p5wAtx0D+V-o-@tf)c_3emih> zor^~j|A)_zow#%cG9)4yvI~SO9KtF1+ok9{=I8F>yRdf8Q?y76EkecNzsoVX8xL3k z?@{rdhW8AZ_91lbj>@%gkj4{QIEys4j*Oz_A~iH>wPvWPeGW5L>r*se3-!qixI!4= zLPZy7AuhQh*xf{1mJTljP?d%Q7-H-&O;QV!HH>o&2+r)q47cQddnGr=e@Wv590THN98&Ua6nP3 zyhd^cH~?~cqpcspGs^Oll9P}{!?BP}asZd;z0tNs{5;|9tmUy-Ema1X4_BM-?4x{Ja@UWwQTciulDw8 zgM-l(D7#`}5*#SH>fRw_IUa7pi6lD)-UkuJjl{pvx*9U&lryj)Xw|To2@KY8u~y}ot`=Tr z1_qBMhH5?hB8Yau5vt=0s}l;V6AP=83agU~t5XWAxXT9}!B?<4t*{FI!Cu^Sf>q3a zu!?{Lt4MCYisyO%sUkujLFHy?xCNgP+}&DMe>s?)>MH zkD?NxTQRNsQL03=ZdLSAnGKBZRxXtBs{m5+v=YO1w%-b1#QJu=Uj)#D>BELEzHDumdc=rmFSNN!`+E~wBo?m^$FHavYfxpe@!%Z41K1HRHZu9EJ%JTtCGFuFv5NWgLx2dT90DqIEGiR z58++z!)Sj5zuO#dkDje)v7*D#wqyK;0>5DWFdD%62wy|rMb%+_WcJ^sg+1v(_7Q`R z7L;eg4@#&#VmzWG0PY^Mo~9!VFGVgk02o6rY*2` zBp49XaVCTWq*yB&)EYI6#ah*4(qbVQJ7!^oDxCanOzOp2fBCz#-VJJv8kS;TW7EQk zf%y%IGCGc8%tcv@Mr7784haWv0m`U9#C6WJc_?H0p*WA5_;nYo9}T++a_*qxF=URG zMKx6QF*iXmm+*8L+U#Y_SrgD1Th0_wjDTWk*N_MarLXfDrptTP!(e~PRd4BXN(Xf~ zu%UjC0b1uXvYP~TMqL&~^BcjRx2FU*yK>p=9>+%a1U9uN@%&J{PZgd z$Jwy$FBc&UVi^Cz#pJ;>{K;C(L|adqr`J)<)G2ya|DoZFD2>#fLyegzLi=;Bdq|@1 zc`Y&=a_98NA>-u9IORAF_4bw&8;P0`8&w*Q5fbC5sJQHH|0!b3V$s$i6T@&bV~Ftt z!)CM@xBa8Bwf(1H%VMQ6Lidk88r^@&jKL%8NdB6q{J?2FZ2N_6#i&!nVumd!xv>rO zgVLyGu<}RPxJ!4~_UqWWZfw7feMvW^V#{VsrnH8QM)yl>e_moVnqOeIcme202xSPs2wLcS!mD6Qc7}ivXHb%X%3~OO?uj-P3ial_ui+cxlbcC zr}JrM?(*;VzyG`MjsCdt+%HNZqzEr>`r+eLyQaG%)9K~bd%66T-MJ2`NqL!cva>sr z>-JKa?Noc+@-?ehE?c{%ZSC^<#`;-wj;aDYkW;?G%eUndd58r9LeM2JEgZTJ{QYj2tnt&V0u9kG?@>I^F%GP8yXChhE=5=IjP)fHXI}rw| z;-Mr*Ad#X`EiEmqZ%zAFuOm-m(mkmZ9QQVR+0{MU+Py55pi8w)O?tJ~d0m^fBcc@@ zTf958Ipyt{Olo}=hOspW4^2T(v-xI+I#lsxhI-c}bG7N7o|Nb?F0u`ER3(y3 zbSC&AS>|;m;G-5BT>=eMs;#9MqRvssnFZ8_8UHK%wV=_3fgrV=qN_7$k5n5~G&ON^ z0~V~jsVNUD>gM^a`7|!BUD1ZcIS-~&Nz0O@6vf9fisrmj7mbNyUxAWMFf-&llV>FC5o(+uHDAijl>l*x4BaJpPur@|QjYt^i8~*x6 zKejp-4Y>~GprK1`Ig97FB=T{+&vWQ_@s6G>mxFjtsv$iqMkA!@vV)Ri727&& z#ME?4jkcqRJns(MHAHraX4B{$T>jhgUiZ>W4@yp!#`t>&sV^4?tG?cMp?<&xp|CFK zJt9L_@{4L%d7!VI4Dwhf2Ue_sVp1n*k_qj=mB;9*m7Hl`lISoceu6mZ&}rYpR|c1O z8-+Tm^f!92joQ4NE37F2R~d8s>*`$#7hVycj`t$`;p=Mr@yH%y=1j3_#N^f-zbe)H zRjI)@wxQ0N-QY*80TD~4JH4G$lEU@VRuZ`|7*k^Fw`4t!qOF;pEXoA0N^sh!Ztyd$ zVK$8o=7Bj5)S3{iY&CL6i73B2e{iwL?MUWv9p-A0%*8Wu36JM9Qao_(>fi%Urzs!h zX>W(FA7e$PJ{j2UWcFRX-FFQrv^v=a)9C^xCyWBtS(dfVFGfK!IVyI}xbr5OhO?fc z2p=7}QDPfW+lbjlscn=IMQJ&;3Ye*!KNe$JQ3X*gt)aDaJ*}fQT2D972HHqB(oJ+T z-9oq0Z9-PbwC&QY;-)9fY65?fw$dGf6m1hQ!6>3qx|6bk9Q6o&4bgb|IPDhfA;w<8 zCy236u%8&c!ca@F`5rn5dA*!LT(>H-{3BFC=rtbi62>x1##uAq6`}9NLZv=1BoAkGWx9BbU5%6Q+CxV|6 z<7a}Olg8WhHvLlYEBY1vo#5B>Yx)iF4}y2-9r{P$w}N-+UHT`%d-NXtv*3MtpZ*p2 zH^J}dcl3MU-v$36W{$%h3H=xSN$@`eND3wz|418}3@W_7c=RdBnyU2OsqKs(S0bOD=zB(N1o z0o#BK&<$k8jY&ic)ONKK*d_S5(%7r^s(l9g)qd5hK4ox^xaDNZwkJn7~d7Vq25q`CHTIWF$G0Ly`_Gveggay_&M;l zOid-KR)4R41H1$L7I+tU5BL`$I1j0#elN}Qk#On{z#oDC){uz{h$8%w&V?w2k+MiR zz;jaCy)4hE%Mg3^X) zuE(O^fcFM)Z-lrTv2r)zz1UHAbB&_57~yk$40V@oKo|CIkk)#{2dxc`*&CQk@|bDa z9C*+cN_K4R95RDdh zqF^MZblh54OALz`9ze*NDHN&*#r$DqXf5F~*p3L@UPOkrbkVSOXo_%Ow2VuNXhFKb zTTKyeDJX6P#VAELp@rT?w9dm<1ebW54*oWAMCGrJC zr~_LS`x9F=!Kud?(T;Xl!Dt6*iICdzGR+a!iQ{uXdsMcf2hCluU^BL4tHZJdDm_IX z7WH~IP^CP?pi9x50yYe4wmD!^dqUYZEBc5g+RUxqBJkoBQEuX|A#w&01RdRNP=nk>j=3yrX0A6W6q~Z4hHrKnlt5`h9 zo(zz6x@39;Vr>~n;w~nz=_9h~w_?+mIiz<$^j1Ze%4$2Jw_-zHs;FKQ)uZE*I3iOx zpIwF{HV`sxUk>)=Vc&Siz8;j`^QH9WeEazFo>!y?9Q*Pi99+IEhr1QSI7$K> z=Gqac&0!o|a;%QYBeII)Y&mw0h=cdE;fVae<RT*<6gIy9-OT8?ExuitfUotjt8TxlzK0dQx&m zjbTIXwgmpV>=pvdz*3&YCp7KGBcwoLV3VO+3nuuCi?nPxLKixN-7$l^V+VJ~4epK~ z+?_DE3lD;lp)1&(Jh+Qk2WR0xu#0PVu#4Rt>{bW5R2}p>CSLDBZ1*C@ym)-eRN*Y_ zCm>M@EFO*!Dn6v|;iz)3_L_D|a|P0NFSx2C_<9OuuaK+!0+$On!hwa+J9Zy3Wj{=e zI;MRR7U^@#oFJEa~rb=7vX81 zq5~X^2nG*Cm!92+bMyi231|+uev~t3%gd&kbP3un znTUi96kT?BALogLh6h|)*xMWB=faw-H{^WKi>&L7How`>U}-|+2o0;SH10LKmt%u) zX$l=2*C0I-sBHJpR5x|*#a=mx^!YTts~wil;5u@jqB=$QVjp}OU(k6FElE>H=RpwN z%Tu+up*MWRB-FYR924%xqoTl8DwW-S2o^s8iyy@IL->9e-(wxtM_|r_a%X!;(F5To zA9A=pi<E{Y|47(A>EEve$DcMD{v#9ky*P8T(hC!!U#5{Dh*%*#i|_ zypM?aFkrcO$mMb@d=k&91M8!&K_+Jd$@dpDHb|zIu@8F?Cc}T%8d3~O*nl-|4eGP=I(DuaJFjD3R86tivKW&o&0*f?d5P^Wi+H2$3ly5CX=1R@ zRLJ$>1g;S$QEZ;U1N&!j4Eq8e`ag#s6Q0NT7xCWYi22fo6wOz3BHD6{pNrv*^7uRk zuzyc-ZT|pWCpafB`m?w$RP>DOJLqUZdm8khh29s9Mzq2==E=CG6%*Mubh+*3$4Q8j zsqxbxTj((`1iQJb3}op^1O&Ee3CtY{2806?8jwP+Xpn2jFcxxEj);qi#CNR1fT>XO zrxCFia{b}Y;`$)SHDp){b&ZS*mk_LPNZMiIS@>L##TZ0peSt&50c=DY{pYwJLw_dP zSbi|hp>ABsk_>#ez*(m@^$Y^WY&faZBos!5P%$Zb)uz7hQA#H3)c)5pVp6uMI= zaxde1fy43&mboAQ!4h%+>Bns-5@fBSQ|!9}l_oxgpUs?w?MLZnK@L;D&t&dJ169P# zy&R?;N!%Y(I92ycq73d9J~fT$rzM3?rsP+-{uqr^3hCbk3!IZ*^&Ba_P{)>{QtG&JC|a5h?-{otBM{Bz_I; z2*nGM1LGjYrnWZWr*nn>h8Uq)e;H=h8JKl8)U2=hW}UHSomF%uLgweRz}`CRm=*tD D&=E#} From 6ccc1d93d0085ac3ec5633e1e1d0a7626066fdb3 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Tue, 10 Oct 2023 16:28:10 +0600 Subject: [PATCH 32/88] add bsa script storage --- cmake/link_vcpkg_dependencies.cmake | 3 ++ skymp5-server/cpp/addon/ScampServer.cpp | 12 +++++ .../cpp/server_guest_lib/ScriptStorage.cpp | 46 +++++++++++++++++++ .../cpp/server_guest_lib/ScriptStorage.h | 16 +++++++ vcpkg.json | 1 + 5 files changed, 78 insertions(+) diff --git a/cmake/link_vcpkg_dependencies.cmake b/cmake/link_vcpkg_dependencies.cmake index b7c36c5fb8..94f0b4c332 100644 --- a/cmake/link_vcpkg_dependencies.cmake +++ b/cmake/link_vcpkg_dependencies.cmake @@ -57,5 +57,8 @@ function(link_vcpkg_dependencies) find_package(OpenSSL REQUIRED) target_link_libraries(${target} PUBLIC OpenSSL::SSL OpenSSL::Crypto) + + find_package(bsa CONFIG REQUIRED) + target_link_libraries(${target} PUBLIC bsa::bsa) endforeach() endfunction() diff --git a/skymp5-server/cpp/addon/ScampServer.cpp b/skymp5-server/cpp/addon/ScampServer.cpp index e016e7e485..8a55a7f636 100644 --- a/skymp5-server/cpp/addon/ScampServer.cpp +++ b/skymp5-server/cpp/addon/ScampServer.cpp @@ -278,6 +278,15 @@ ScampServer::ScampServer(const Napi::CallbackInfo& info) } } + std::vector> bsaScriptStorages; + if (serverSettings["archives"].is_array()) { + for (auto archive : serverSettings["archives"]) { + std::string archivePath = archive.get(); + bsaScriptStorages.push_back( + std::make_shared(archivePath.data())); + } + } + if (serverSettings["lang"] != nullptr) { logger->info("Run localization provider"); localizationProvider = std::make_shared( @@ -309,6 +318,9 @@ ScampServer::ScampServer(const Napi::CallbackInfo& info) std::vector> scriptStorages; scriptStorages.push_back(std::make_shared( (espm::fs::path(dataDir) / "scripts").string())); + for (auto scriptStorage : bsaScriptStorages) { + scriptStorages.push_back(scriptStorage); + } scriptStorages.push_back(std::make_shared()); auto scriptStorage = std::make_shared(scriptStorages); diff --git a/skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp b/skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp index 52eb446406..e878642996 100644 --- a/skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp +++ b/skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp @@ -1,4 +1,5 @@ #include "ScriptStorage.h" +#include #include #include #include @@ -102,6 +103,7 @@ AssetsScriptStorage::AssetsScriptStorage() const uint8_t* begin = reinterpret_cast(file.begin()); const uint8_t* end = begin + file.size(); std::vector pex(begin, end); + auto nameWithoutExtension = std::filesystem::path(entry.filename()).stem().string(); scripts.insert( @@ -139,6 +141,50 @@ const std::set& AssetsScriptStorage::ListScripts(bool) return scripts; } +BsaArchiveScriptStorage::BsaArchiveScriptStorage(const char* bsaPath) +{ + bsa.read(bsaPath); +} + +std::vector BsaArchiveScriptStorage::GetScriptPex( + const char* scriptName) +{ + auto it = scriptPex.find(scriptName); + if (it == scriptPex.end()) { + spdlog::trace("BsaArchiveScriptStorage::GetScriptPex - Not found {}", + scriptName); + return {}; + } + spdlog::trace("BsaArchiveScriptStorage::GetScriptPex - Found {}", + scriptName); + return it->second; +} + +const std::set& BsaArchiveScriptStorage::ListScripts( + bool forceReloadScripts) +{ + if (scripts.empty()) { + auto bsaScripts = *bsa["scripts"]; + for (auto it = bsaScripts.begin(); it != bsaScripts.end(); it++) { + auto fileName = it->first.name(); + + const std::byte* data = it->second.data(); + size_t size = it->second.size(); + auto pex = + std::vector(reinterpret_cast(data), + reinterpret_cast(data) + size); + + auto nameWithoutExtension = + std::filesystem::path(fileName).stem().string(); + scripts.insert( + { nameWithoutExtension.begin(), nameWithoutExtension.end() }); + scriptPex[{ nameWithoutExtension.begin(), nameWithoutExtension.end() }] = + pex; + } + } + return scripts; +} + CombinedScriptStorage::CombinedScriptStorage( std::vector> scriptStorages) { diff --git a/skymp5-server/cpp/server_guest_lib/ScriptStorage.h b/skymp5-server/cpp/server_guest_lib/ScriptStorage.h index 9c882ca1d5..3fbc917139 100644 --- a/skymp5-server/cpp/server_guest_lib/ScriptStorage.h +++ b/skymp5-server/cpp/server_guest_lib/ScriptStorage.h @@ -1,5 +1,6 @@ #pragma once #include "papyrus-vm/CIString.h" +#include #include #include #include @@ -45,6 +46,21 @@ class AssetsScriptStorage : public IScriptStorage CIMap> scriptPex; }; +class BsaArchiveScriptStorage : public IScriptStorage +{ +public: + BsaArchiveScriptStorage(const char* pathToBsa); + + std::vector GetScriptPex(const char* scriptName) override; + + const std::set& ListScripts(bool forceReloadScripts) override; + +private: + std::set scripts; + CIMap> scriptPex; + bsa::tes4::archive bsa; +}; + class CombinedScriptStorage : public IScriptStorage { public: diff --git a/vcpkg.json b/vcpkg.json index 98a6af4e8a..5166218942 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -19,6 +19,7 @@ "node-addon-api", "bshoshany-thread-pool", "makeid", + "rsm-bsa", { "name": "frida-gum", "platform": "windows" From c97a87d67eb9a82cf5a872e7c3e85f7f143225c7 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Tue, 10 Oct 2023 17:42:09 +0600 Subject: [PATCH 33/88] disable placeatme --- .../src/papyrus-vm-lib/ActivePexInstance.cpp | 27 +++++++++++-------- .../server_guest_lib/MpObjectReference.cpp | 1 + .../PapyrusObjectReference.cpp | 8 +++++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp b/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp index e90e5f9020..b00b292d9f 100644 --- a/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp +++ b/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp @@ -398,6 +398,8 @@ void ActivePexInstance::ExecuteOpCode(ExecutionContext* ctx, uint8_t op, try { if (functionName == nameOnBeginState || functionName == nameOnEndState) { + // TODO: consider using CallMethod here. I'm afraid that this event + // will pollute other scripts attached to an object parentVM->SendEvent(this, functionName.c_str(), argsForCall); break; } else { @@ -405,8 +407,11 @@ void ActivePexInstance::ExecuteOpCode(ExecutionContext* ctx, uint8_t op, auto res = parentVM->CallMethod(nullableGameObject, functionName.c_str(), argsForCall, ctx->stackIdHolder); - if (EnsureCallResultIsSynchronous(res, ctx)) + spdlog::trace("callmethod object={} funcName={} result={}", + object->ToString(), functionName, res.ToString()); + if (EnsureCallResultIsSynchronous(res, ctx)) { *args[2] = res; + } } } catch (std::exception& e) { if (auto handler = parentVM->GetExceptionHandler()) @@ -724,20 +729,20 @@ VarValue& ActivePexInstance::GetIndentifierValue( if (treatStringsAsIdentifiers && value.GetType() == VarValue::kType_String) { auto& res = GetVariableValueByName(&locals, valueAsString); - if (spdlog::should_log(spdlog::level::trace)) { - spdlog::trace("GetIndentifierValue {}: {} = {}", - this->sourcePex.fn()->source, valueAsString, - res.ToString()); - } + // if (spdlog::should_log(spdlog::level::trace)) { + // spdlog::trace("GetIndentifierValue {}: {} = {}", + // this->sourcePex.fn()->source, valueAsString, + // res.ToString()); + // } return res; } if (value.GetType() == VarValue::kType_Identifier) { auto& res = GetVariableValueByName(&locals, valueAsString); - if (spdlog::should_log(spdlog::level::trace)) { - spdlog::trace("GetIndentifierValue {}: {} = {}", - this->sourcePex.fn()->source, valueAsString, - res.ToString()); - } + // if (spdlog::should_log(spdlog::level::trace)) { + // spdlog::trace("GetIndentifierValue {}: {} = {}", + // this->sourcePex.fn()->source, valueAsString, + // res.ToString()); + // } return res; } } diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 8a811316f6..d950ac933a 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -794,6 +794,7 @@ void MpObjectReference::Subscribe(MpObjectReference* emitter, emitter->pImpl->onInitEventSent = true; emitter->SendPapyrusEvent("OnInit"); emitter->SendPapyrusEvent("OnCellLoad"); + emitter->SendPapyrusEvent("OnLoad"); } const bool hasPrimitive = emitter->HasPrimitive(); diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp index 7e829238bc..cdfc3d85b5 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp @@ -637,7 +637,13 @@ void PapyrusObjectReference::Register( AddMethod(vm, "GetItemCount", &PapyrusObjectReference::GetItemCount); AddMethod(vm, "GetAnimationVariableBool", &PapyrusObjectReference::GetAnimationVariableBool); - AddMethod(vm, "PlaceAtMe", &PapyrusObjectReference::PlaceAtMe); + + // Temporary disabled. Original scripts in dungeons pollute the server with + // uncountable placed forms. and the server has no idea how to recycle all + // that + + // AddMethod(vm, "PlaceAtMe", &PapyrusObjectReference::PlaceAtMe); + AddMethod(vm, "SetAngle", &PapyrusObjectReference::SetAngle); AddMethod(vm, "Enable", &PapyrusObjectReference::Enable); AddMethod(vm, "Disable", &PapyrusObjectReference::Disable); From 2453877e70c7ef26447eaf496d035bd3be6edc9c Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Wed, 11 Oct 2023 11:15:33 +0600 Subject: [PATCH 34/88] no exterior scripts plz --- .../server_guest_lib/MpObjectReference.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index d950ac933a..fbc0b99f0b 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1378,6 +1378,25 @@ void MpObjectReference::InitScripts() } } + // A hardcoded hack to remove all scripts except SweetPie scripts from + // exterior objects + if (GetFormId() < 0x05000000) { + auto cellOrWorld = GetCellOrWorld().ToFormId(GetParent()->espmFiles); + auto lookupRes = + GetParent()->GetEspm().GetBrowser().LookupById(cellOrWorld); + if (lookupRes.rec && lookupRes.rec->GetType() == "WRLD") { + spdlog::info("Skipping non-Sweet scripts for exterior form {:x}"); + scriptNames.erase(std::remove_if(scriptNames.begin(), scriptNames.end(), + [](const std::string& val) { + auto kPrefix = "Sweet"; + bool startsWith = val.size() >= 5 && + !memcmp(kPrefix, val.data(), 5); + return !startsWith; + }), + scriptNames.end()); + } + } + if (!scriptNames.empty()) { pImpl->scriptState = std::make_unique(); From 2270de405ed9732023fa6144793d1cdaf530261a Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Wed, 11 Oct 2023 19:14:09 +0600 Subject: [PATCH 35/88] implement spsnippet placeatme for explosions & hack door in BleakFalls &mpre --- .../src/extensions/objectReferenceEx.ts | 7 +- skymp5-client/src/spSnippet.ts | 2 +- skymp5-client/src/view/modelApplyUtils.ts | 24 +++- .../server_guest_lib/MpObjectReference.cpp | 7 ++ .../PapyrusObjectReference.cpp | 109 +++++++++++------- .../cpp/server_guest_lib/PapyrusSound.cpp | 41 +++++++ .../cpp/server_guest_lib/PapyrusSound.h | 15 +++ .../cpp/server_guest_lib/PapyrusUtility.cpp | 18 ++- .../cpp/server_guest_lib/PapyrusUtility.h | 4 +- .../cpp/server_guest_lib/WorldState.cpp | 2 + 10 files changed, 179 insertions(+), 50 deletions(-) create mode 100644 skymp5-server/cpp/server_guest_lib/PapyrusSound.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/PapyrusSound.h diff --git a/skymp5-client/src/extensions/objectReferenceEx.ts b/skymp5-client/src/extensions/objectReferenceEx.ts index 72f59adce5..62d8f1c78a 100644 --- a/skymp5-client/src/extensions/objectReferenceEx.ts +++ b/skymp5-client/src/extensions/objectReferenceEx.ts @@ -34,8 +34,13 @@ export class ObjectReferenceEx { const t = base.getType(); const isItem = FormTypeEx.isItem(t); + // BlackFallsBarrow02, door isn't opening via SetOpen so we're hacking it. + // Not blocking activation & asking parent to activate until will be in the correct state + // See also modelApplyUtils.ts + const caveGSecretDoor01 = 0x6f703; + // You can also block for t === FormType.Flora || t === FormType.Tree, but I don't think it's necessary. - if (t === FormType.Container || isItem || t === FormType.NPC || t === FormType.Door) { + if (t === FormType.Container || isItem || t === FormType.NPC || (t === FormType.Door && self.getBaseObject()?.getFormID() !== caveGSecretDoor01)) { self.blockActivation(true); } else { self.blockActivation(false); diff --git a/skymp5-client/src/spSnippet.ts b/skymp5-client/src/spSnippet.ts index 4e93a3f2df..2b39d8adbe 100644 --- a/skymp5-client/src/spSnippet.ts +++ b/skymp5-client/src/spSnippet.ts @@ -32,7 +32,7 @@ const runMethod = async (snippet: Snippet): Promise => { const selfCasted = spAny[snippet.class].from(self); if (!selfCasted) throw new Error( - `Form ${selfId.toString(16)} is not instance of ${snippet.class}` + `Form ${selfId.toString(16)} is not instance of ${snippet.class}, form type is ${self.getType()}` ); const f = selfCasted[snippet.function]; return await f.apply( diff --git a/skymp5-client/src/view/modelApplyUtils.ts b/skymp5-client/src/view/modelApplyUtils.ts index 15b8af8d22..816b38b843 100644 --- a/skymp5-client/src/view/modelApplyUtils.ts +++ b/skymp5-client/src/view/modelApplyUtils.ts @@ -7,11 +7,31 @@ export class ModelApplyUtils { static applyModelInventory(refr: ObjectReference, inventory: Inventory) { applyInventory(refr, inventory, false, true); } - + static applyModelIsOpen(refr: ObjectReference, isOpen: boolean) { refr.setOpen(isOpen); + + // See also objectReferenceEx.ts + const caveGSecretDoor01 = 0x6f703; + + // TODO: add more activators to support more cells + const parentActivatorId = 0x460ca; + + if (refr.getBaseObject()?.getFormID() === caveGSecretDoor01) { + const openOrOpening = [1, 2].includes(refr.getOpenState()); + if (openOrOpening) { + if (!isOpen) { + refr.activate(ObjectReference.from(Game.getForm(parentActivatorId)), false); + } + } + if (!openOrOpening) { + if (isOpen) { + refr.activate(ObjectReference.from(Game.getForm(parentActivatorId)), false); + } + } + } } - + static applyModelIsHarvested(refr: ObjectReference, isHarvested: boolean) { const base = refr.getBaseObject(); if (base) { diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index fbc0b99f0b..1188077bc8 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -290,6 +290,13 @@ void MpObjectReference::VisitProperties(const PropertiesVisitor& visitor, void MpObjectReference::Activate(MpObjectReference& activationSource, bool defaultProcessingOnly) { + if (spdlog::should_log(spdlog::level::trace)) { + for (auto& script : ListActivePexInstances()) { + spdlog::trace("MpObjectReference::Activate {:x} - found script {}", + GetFormId(), script->GetSourcePexName()); + } + } + if (auto worldState = activationSource.GetParent(); worldState->HasEspm()) { CheckInteractionAbility(activationSource); diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp index cdfc3d85b5..99301a7751 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp @@ -188,48 +188,81 @@ VarValue PapyrusObjectReference::GetAnimationVariableBool( return VarValue(false); } +namespace { +void PlaceAtMeSpSnippet(MpObjectReference* self, + const std::vector& arguments) +{ + auto funcName = "PlaceAtMe"; + auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); + for (auto listener : self->GetListeners()) { + auto targetRefr = dynamic_cast(listener); + if (targetRefr) { + SpSnippet("ObjectReference", funcName, serializedArgs.data(), + self->GetFormId()) + .Execute(targetRefr); + } + } +} +} + VarValue PapyrusObjectReference::PlaceAtMe( VarValue self, const std::vector& arguments) { auto selfRefr = GetFormPtr(self); - if (selfRefr && arguments.size() >= 4) { - auto akFormToPlace = GetRecordPtr(arguments[0]); - int aiCount = static_cast(arguments[1].CastToInt()); - bool abForcePersist = static_cast(arguments[2].CastToBool()); - bool abInitiallyDisabled = static_cast(arguments[3].CastToBool()); - - if (akFormToPlace.rec) { - auto baseId = akFormToPlace.ToGlobalId(akFormToPlace.rec->GetId()); - - LocationalData locationalData = { - selfRefr->GetPos(), - { 0, 0, selfRefr->GetAngle().z }, // TODO: fix Degrees/radians mismatch - selfRefr->GetCellOrWorld() - }; - FormCallbacks callbacks = selfRefr->GetCallbacks(); - std::string type = akFormToPlace.rec->GetType().ToString(); - - std::unique_ptr newRefr; - - if (akFormToPlace.rec->GetType() == "NPC_") { - auto actor = new MpActor(locationalData, callbacks, baseId); - newRefr.reset(actor); - } else { - newRefr.reset( - new MpObjectReference(locationalData, callbacks, baseId, type)); - } + if (!selfRefr || arguments.size() < 4) { + return VarValue::None(); + } - auto worldState = selfRefr->GetParent(); - auto newRefrId = worldState->GenerateFormId(); - worldState->AddForm(std::move(newRefr), newRefrId); + auto akFormToPlace = GetRecordPtr(arguments[0]); + int aiCount = static_cast(arguments[1].CastToInt()); + bool abForcePersist = static_cast(arguments[2].CastToBool()); + bool abInitiallyDisabled = static_cast(arguments[3].CastToBool()); - auto& refr = worldState->GetFormAt(newRefrId); - refr.ForceSubscriptionsUpdate(); - return VarValue(std::make_shared(&refr)); - } + if (!akFormToPlace.rec) { + return VarValue::None(); } - return VarValue::None(); + + bool isExplosion = akFormToPlace.rec->GetType() == "EXPL"; + if (isExplosion) { + PlaceAtMeSpSnippet(selfRefr, arguments); + + // TODO: return pseudo-reference or even create real server-side form? + return VarValue::None(); + } + + auto baseId = akFormToPlace.ToGlobalId(akFormToPlace.rec->GetId()); + + float angleZDegrees = selfRefr->GetAngle().z; + float angleZRadians = angleZDegrees; // TODO: fix Degrees/radians mismatch + // TODO: support angleX, angleY + LocationalData locationalData = { selfRefr->GetPos(), + { 0, 0, angleZRadians }, + selfRefr->GetCellOrWorld() }; + FormCallbacks callbacks = selfRefr->GetCallbacks(); + std::string type = akFormToPlace.rec->GetType().ToString(); + + std::unique_ptr newRefr; + + if (akFormToPlace.rec->GetType() == "NPC_") { + auto actor = new MpActor(locationalData, callbacks, baseId); + newRefr.reset(actor); + } else { + newRefr.reset( + new MpObjectReference(locationalData, callbacks, baseId, type)); + } + + auto worldState = selfRefr->GetParent(); + auto newRefrId = worldState->GenerateFormId(); + worldState->AddForm(std::move(newRefr), newRefrId); + + auto& refr = worldState->GetFormAt(newRefrId); + refr.ForceSubscriptionsUpdate(); + + if (abInitiallyDisabled) { + refr.Disable(); + } + return VarValue(std::make_shared(&refr)); } VarValue PapyrusObjectReference::SetAngle( @@ -637,13 +670,7 @@ void PapyrusObjectReference::Register( AddMethod(vm, "GetItemCount", &PapyrusObjectReference::GetItemCount); AddMethod(vm, "GetAnimationVariableBool", &PapyrusObjectReference::GetAnimationVariableBool); - - // Temporary disabled. Original scripts in dungeons pollute the server with - // uncountable placed forms. and the server has no idea how to recycle all - // that - - // AddMethod(vm, "PlaceAtMe", &PapyrusObjectReference::PlaceAtMe); - + AddMethod(vm, "PlaceAtMe", &PapyrusObjectReference::PlaceAtMe); AddMethod(vm, "SetAngle", &PapyrusObjectReference::SetAngle); AddMethod(vm, "Enable", &PapyrusObjectReference::Enable); AddMethod(vm, "Disable", &PapyrusObjectReference::Disable); diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusSound.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusSound.cpp new file mode 100644 index 0000000000..4610fbec17 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/PapyrusSound.cpp @@ -0,0 +1,41 @@ +#include "PapyrusSound.h" + +#include "EspmGameObject.h" +#include "MpFormGameObject.h" +#include "MpObjectReference.h" +#include "SpSnippetFunctionGen.h" + +VarValue PapyrusSound::Play(VarValue self, + const std::vector& arguments) +{ + auto sound = GetRecordPtr(self); + if (!sound.rec) { + throw std::runtime_error("Self not found"); + } + + auto selfId = sound.ToGlobalId(sound.rec->GetId()); + + if (auto refr = GetFormPtr(arguments[0])) { + if (arguments.size() < 1) { + throw std::runtime_error("Play requires at least 1 argument"); + } + auto funcName = "Play"; + auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); + for (auto listener : refr->GetListeners()) { + auto targetRefr = dynamic_cast(listener); + if (targetRefr) { + SpSnippet(GetName(), funcName, serializedArgs.data(), selfId) + .Execute(targetRefr); + } + } + } + return VarValue::None(); +} + +void PapyrusSound::Register( + VirtualMachine& vm, std::shared_ptr policy) +{ + compatibilityPolicy = policy; + + AddMethod(vm, "Play", &PapyrusSound::Play); +} diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusSound.h b/skymp5-server/cpp/server_guest_lib/PapyrusSound.h new file mode 100644 index 0000000000..feb28c5329 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/PapyrusSound.h @@ -0,0 +1,15 @@ +#pragma once +#include "IPapyrusClass.h" + +class PapyrusSound final : public IPapyrusClass +{ +public: + const char* GetName() override { return "sound"; } + + VarValue Play(VarValue slef, const std::vector& arguments); + + void Register(VirtualMachine& vm, + std::shared_ptr policy) override; + + std::shared_ptr compatibilityPolicy; +}; diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusUtility.cpp index 6ed143726f..c112bb11c8 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.cpp +++ b/skymp5-server/cpp/server_guest_lib/PapyrusUtility.cpp @@ -28,15 +28,24 @@ VarValue PapyrusUtility::Wait(VarValue self, return VarValue(resultPromise); } -static std::mt19937 kGenerator{ std::random_device{}() }; +static std::mt19937 g_generator{ std::random_device{}() }; -VarValue PapyrusUtility::RandomInt( - VarValue self, const std::vector& arguments) const noexcept +VarValue PapyrusUtility::RandomInt(VarValue self, + const std::vector& arguments) { int32_t min = static_cast(arguments[0].CastToInt()); int32_t max = static_cast(arguments[1].CastToInt()); std::uniform_int_distribution<> distribute{ min, max }; - return VarValue(distribute(kGenerator)); + return VarValue(distribute(g_generator)); +} + +VarValue PapyrusUtility::RandomFloat(VarValue self, + const std::vector& arguments) +{ + double min = static_cast(arguments[0].CastToFloat()); + double max = static_cast(arguments[1].CastToFloat()); + std::uniform_real_distribution<> distribute{ min, max }; + return VarValue(distribute(g_generator)); } void PapyrusUtility::Register( @@ -47,4 +56,5 @@ void PapyrusUtility::Register( AddStatic(vm, "Wait", &PapyrusUtility::Wait); AddStatic(vm, "RandomInt", &PapyrusUtility::RandomInt); + AddStatic(vm, "RandomFloat", &PapyrusUtility::RandomFloat); } diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h b/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h index bac9930b01..4d1e931840 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h @@ -8,7 +8,9 @@ class PapyrusUtility final : public IPapyrusClass VarValue Wait(VarValue self, const std::vector& arguments); VarValue RandomInt(VarValue slef, - const std::vector& arguments) const noexcept; + const std::vector& arguments); + VarValue RandomFloat(VarValue slef, + const std::vector& arguments); void Register(VirtualMachine& vm, std::shared_ptr policy) override; diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index 5090e65867..18100c9c24 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -17,6 +17,7 @@ #include "PapyrusMessage.h" #include "PapyrusObjectReference.h" #include "PapyrusSkymp.h" +#include "PapyrusSound.h" #include "PapyrusUtility.h" #include "ScopedTask.h" #include "ScriptStorage.h" @@ -740,6 +741,7 @@ VirtualMachine& WorldState::GetPapyrusVm() pImpl->classes.emplace_back(std::make_unique()); pImpl->classes.emplace_back(std::make_unique()); pImpl->classes.emplace_back(std::make_unique()); + pImpl->classes.emplace_back(std::make_unique()); for (auto& cl : pImpl->classes) { cl->Register(*pImpl->vm, pImpl->policy); } From d0134fdc7447c9c15c656a5b1cd2e3658246b4b9 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 12 Oct 2023 00:34:01 +0600 Subject: [PATCH 36/88] remove placeatme explosion for now & animation saves/restores in most cases --- skymp5-client/src/modelSource/model.ts | 1 + skymp5-client/src/modelSource/remoteServer.ts | 19 ++++++++++++++++++- .../services/blockPapyrusEventsService.ts | 4 +--- .../cpp/server_guest_lib/MpChangeForms.cpp | 16 +++++++++++++++- .../cpp/server_guest_lib/MpChangeForms.h | 5 ++++- .../server_guest_lib/MpObjectReference.cpp | 19 +++++++++++++++++-- .../cpp/server_guest_lib/MpObjectReference.h | 2 ++ .../PapyrusObjectReference.cpp | 11 ++++++++++- 8 files changed, 68 insertions(+), 9 deletions(-) diff --git a/skymp5-client/src/modelSource/model.ts b/skymp5-client/src/modelSource/model.ts index e7fd873a3d..64bd679eb7 100644 --- a/skymp5-client/src/modelSource/model.ts +++ b/skymp5-client/src/modelSource/model.ts @@ -20,6 +20,7 @@ export interface FormModel { isHostedByOther?: boolean; isDead?: boolean; templateChain?: number[]; + lastAnimation?: string; // Assigned locally isMyClone?: boolean; diff --git a/skymp5-client/src/modelSource/remoteServer.ts b/skymp5-client/src/modelSource/remoteServer.ts index 97bf0f19c6..c4c600523d 100644 --- a/skymp5-client/src/modelSource/remoteServer.ts +++ b/skymp5-client/src/modelSource/remoteServer.ts @@ -289,6 +289,23 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget { refr, !!msg.props['isHarvested'], ); + const animation = msg.props.lastAnimation; + if (typeof animation === "string") { + // let res1 = refr.playAnimation(animation); + // printConsole("res1", res1); + const refrid = refr.getFormID(); + + (async () => { + for (let i = 0; i < 5; i ++) { + + // retry. pillars in bleakfalls are not reliable for some reason + let res2 = ObjectReference.from(Game.getFormEx(refrid))?.playAnimation(animation); + printConsole("res2", res2); + if (res2) break; + await Utility.wait(2); + } + })(); + } } } else { printConsole('Failed to apply model to', refrId.toString(16)); @@ -704,7 +721,7 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget { // TODO: emit event instead of sending directly to avoid type cast and dependency on network module this.send(message as unknown as Record, true); }) - .catch((e) => printConsole('!!! SpSnippet failed', e)); + .catch((e) => printConsole('!!! SpSnippet ' + msg.class + ' ' + msg.function + ' failed', e)); }); } diff --git a/skymp5-client/src/services/services/blockPapyrusEventsService.ts b/skymp5-client/src/services/services/blockPapyrusEventsService.ts index cfd8e74561..0643559d8e 100644 --- a/skymp5-client/src/services/services/blockPapyrusEventsService.ts +++ b/skymp5-client/src/services/services/blockPapyrusEventsService.ts @@ -8,9 +8,7 @@ export class BlockPapyrusEventsService extends ClientListener { } private onceTick() { - if (typeof this.sp.blockPapyrusEvents === "function") { - this.sp.blockPapyrusEvents(true); - } + this.sp.blockPapyrusEvents(true); } private onceUpdate() { diff --git a/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp b/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp index 3e17bac991..ec275a355d 100644 --- a/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp @@ -76,6 +76,12 @@ nlohmann::json MpChangeForm::ToJson(const MpChangeForm& changeForm) if (!changeForm.templateChain.empty()) { res["templateChain"] = ToStringArray(changeForm.templateChain); } + + // TODO: uncomment when add script vars save feature + // if (changeForm.lastAnimation.has_value()) { + // res["lastAnimation"] = *changeForm.lastAnimation; + // } + return res; } @@ -96,7 +102,7 @@ MpChangeForm MpChangeForm::JsonToChangeForm(simdjson::dom::element& element) spawnPointPos("spawnPoint_pos"), spawnPointRot("spawnPoint_rot"), spawnPointCellOrWorldDesc("spawnPoint_cellOrWorldDesc"), spawnDelay("spawnDelay"), effects("effects"), - templateChain("templateChain"); + templateChain("templateChain"), lastAnimation("lastAnimation"); MpChangeForm res; ReadEx(element, recType, &res.recType); @@ -201,6 +207,14 @@ MpChangeForm MpChangeForm::JsonToChangeForm(simdjson::dom::element& element) ReadVector(element, templateChain, &data); res.templateChain = ToFormDescsArray(data); } + + if (element.at_pointer(lastAnimation.GetData()).error() == + simdjson::error_code::SUCCESS) { + const char* tmp; + ReadEx(element, lastAnimation, &tmp); + res.lastAnimation = tmp; + } + return res; } diff --git a/skymp5-server/cpp/server_guest_lib/MpChangeForms.h b/skymp5-server/cpp/server_guest_lib/MpChangeForms.h index 7ba8961b1d..ede423adf0 100644 --- a/skymp5-server/cpp/server_guest_lib/MpChangeForms.h +++ b/skymp5-server/cpp/server_guest_lib/MpChangeForms.h @@ -90,6 +90,9 @@ class MpChangeFormREFR float spawnDelay = 25.0f; std::vector templateChain; + // Used for PlayAnimation (object reference) + std::optional lastAnimation; + // Please update 'ActorTest.cpp' when adding new Actor-related rows DynamicFields dynamicFields; @@ -102,7 +105,7 @@ class MpChangeFormREFR baseContainerAdded, nextRelootDatetime, isDisabled, profileId, isRaceMenuOpen, isDead, consoleCommandsAllowed, appearanceDump, equipmentDump, actorValues.ToTuple(), spawnPoint, dynamicFields, - spawnDelay, learnedSpells, templateChain); + spawnDelay, learnedSpells, templateChain, lastAnimation); } static nlohmann::json ToJson(const MpChangeFormREFR& changeForm); diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 1188077bc8..27b5e8ef14 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -269,15 +269,23 @@ bool MpObjectReference::GetTeleportFlag() const void MpObjectReference::VisitProperties(const PropertiesVisitor& visitor, VisitPropertiesMode mode) { - if (IsHarvested()) + if (IsHarvested()) { visitor("isHarvested", "true"); - if (IsOpen()) + } + if (IsOpen()) { visitor("isOpen", "true"); + } if (mode == VisitPropertiesMode::All && !GetInventory().IsEmpty()) { auto inventoryDump = GetInventory().ToJson().dump(); visitor("inventory", inventoryDump.data()); } + if (ChangeForm().lastAnimation.has_value()) { + std::string lastAnimationAsJson = "\"" + *ChangeForm().lastAnimation + + "\""; + visitor("lastAnimation", lastAnimationAsJson.data()); + } + // Property flags (isVisibleByOwner, isVisibleByNeighbor) should be checked // by a visitor auto& dynamicFields = ChangeForm().dynamicFields.GetAsJson(); @@ -851,6 +859,13 @@ void MpObjectReference::Unsubscribe(MpObjectReference* emitter, } } +void MpObjectReference::SetLastAnimation(const std::string& lastAnimation) +{ + EditChangeForm([&](MpChangeForm& changeForm) { + changeForm.lastAnimation = lastAnimation; + }); +} + const std::set& MpObjectReference::GetListeners() const { static const std::set kEmptyListeners; diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.h b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h index dfe0b728da..fa9c96af90 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.h +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h @@ -139,6 +139,8 @@ class MpObjectReference static void Unsubscribe(MpObjectReference* emitter, MpObjectReference* listener); + void SetLastAnimation(const std::string& lastAnimation); + const std::set& GetListeners() const; const std::set& GetEmitters() const; diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp index 99301a7751..a74b7318e7 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp @@ -225,7 +225,10 @@ VarValue PapyrusObjectReference::PlaceAtMe( bool isExplosion = akFormToPlace.rec->GetType() == "EXPL"; if (isExplosion) { - PlaceAtMeSpSnippet(selfRefr, arguments); + // Well sp snippet fails ATM. and I don't want to overpollute clients and + // network with those placeatme s for now + + // PlaceAtMeSpSnippet(selfRefr, arguments); // TODO: return pseudo-reference or even create real server-side form? return VarValue::None(); @@ -423,6 +426,9 @@ VarValue PapyrusObjectReference::PlayAnimation( if (arguments.size() < 1) { throw std::runtime_error("PlayAnimation requires at least 1 argument"); } + const char* animation = static_cast(arguments[0]); + selfRefr->SetLastAnimation(animation); + auto funcName = "PlayAnimation"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetListeners()) { @@ -445,6 +451,9 @@ VarValue PapyrusObjectReference::PlayAnimationAndWait( throw std::runtime_error("PlayAnimation requires at least 2 arguments"); } + const char* animation = static_cast(arguments[0]); + selfRefr->SetLastAnimation(animation); + auto funcName = "PlayAnimationAndWait"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); From 454f016afaccc2c9b525a2fe5e273cde8c1a8e4e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 12 Oct 2023 14:44:20 +0600 Subject: [PATCH 37/88] enable aggro & disable unique npcs --- libespm/include/libespm/NPC_.h | 1 + libespm/src/NPC_.cpp | 3 ++- skymp5-client/src/view/formView.ts | 13 +++++++++++++ skymp5-server/cpp/server_guest_lib/WorldState.cpp | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/libespm/include/libespm/NPC_.h b/libespm/include/libespm/NPC_.h index 048ca59356..d8a8b3a0a0 100644 --- a/libespm/include/libespm/NPC_.h +++ b/libespm/include/libespm/NPC_.h @@ -68,6 +68,7 @@ class NPC_ final : public RecordHeader std::vector factions; bool isEssential = false; + bool isUnique = false; bool isProtected = false; uint32_t race = 0; diff --git a/libespm/src/NPC_.cpp b/libespm/src/NPC_.cpp index f3243ebcee..997d9c36d5 100644 --- a/libespm/src/NPC_.cpp +++ b/libespm/src/NPC_.cpp @@ -24,7 +24,8 @@ NPC_::Data NPC_::GetData( } else if (!std::memcmp(type, "ACBS", 4)) { const uint32_t flags = *reinterpret_cast(data); - result.isEssential = !!(flags & 0x02); + result.isEssential = !!(flags & 0x2); + result.isUnique = !!(flags & 0x20); result.isProtected = !!(flags & 0x800); result.magickaOffset = *reinterpret_cast(data + 4); result.staminaOffset = *reinterpret_cast(data + 6); diff --git a/skymp5-client/src/view/formView.ts b/skymp5-client/src/view/formView.ts index 4f595496e5..947a2e3b34 100644 --- a/skymp5-client/src/view/formView.ts +++ b/skymp5-client/src/view/formView.ts @@ -137,6 +137,19 @@ export class FormView implements View { model.movement?.rot[2] || 0 ); } + else { + const race = Actor.from(refr)?.getRace()?.getFormID(); + const draugrRace = 0xd53; + const falmerRace = 0x131f4; + const chaurusRace = 0x131eb; + const frostbiteSpiderRaceGiant = 0x4e507; + const frostbiteSpiderRaceLarge = 0x53477; + + // potential masterambushscript + if (race === draugrRace || race === falmerRace || race === chaurusRace || race === frostbiteSpiderRaceGiant || race === frostbiteSpiderRaceLarge) { + Actor.from(refr)?.setActorValue("Aggression", 2); + } + } modWcProtection(refr.getFormID(), 1); // TODO: reset all states? diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index 18100c9c24..660ae7c097 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -309,7 +309,7 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, auto npcData = reinterpret_cast(base.rec)->GetData(cache); - if (npcData.isEssential || npcData.isProtected) { + if (npcData.isEssential || npcData.isProtected || npcData.isUnique) { return false; } From 0f9e92185744c684d88112f0bb0b07d0c7f740b1 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 12 Oct 2023 18:59:31 +0600 Subject: [PATCH 38/88] i want fix npc death --- skymp5-client/src/modelSource/remoteServer.ts | 4 ---- skymp5-client/src/services/services/sendInputsService.ts | 5 ++++- skymp5-client/src/sync/movementGet.ts | 6 ++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/skymp5-client/src/modelSource/remoteServer.ts b/skymp5-client/src/modelSource/remoteServer.ts index c4c600523d..653498d2ec 100644 --- a/skymp5-client/src/modelSource/remoteServer.ts +++ b/skymp5-client/src/modelSource/remoteServer.ts @@ -291,16 +291,12 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget { ); const animation = msg.props.lastAnimation; if (typeof animation === "string") { - // let res1 = refr.playAnimation(animation); - // printConsole("res1", res1); const refrid = refr.getFormID(); (async () => { for (let i = 0; i < 5; i ++) { - // retry. pillars in bleakfalls are not reliable for some reason let res2 = ObjectReference.from(Game.getFormEx(refrid))?.playAnimation(animation); - printConsole("res2", res2); if (res2) break; await Utility.wait(2); } diff --git a/skymp5-client/src/services/services/sendInputsService.ts b/skymp5-client/src/services/services/sendInputsService.ts index c0e8172621..c0d3175a7c 100644 --- a/skymp5-client/src/services/services/sendInputsService.ts +++ b/skymp5-client/src/services/services/sendInputsService.ts @@ -49,7 +49,7 @@ export class SendInputsService extends ClientListener { this.controller.emitter.emit("sendMessage", { message: { t: MsgType.OnEquip, baseId: event.baseObj.getFormID() }, - reliability: "unreliable" + reliability: "unreliable" }); } } @@ -108,6 +108,9 @@ export class SendInputsService extends ClientListener { data: getMovement(owner, form), _refrId }; + if (_refrId) /*not a player character*/{ + this.sp.printConsole("isDead", message.data.isDead); + } this.controller.emitter.emit("sendMessageWithRefrId", { message, reliability: "unreliable" diff --git a/skymp5-client/src/sync/movementGet.ts b/skymp5-client/src/sync/movementGet.ts index 8b4d0e95d9..70d39d9ad4 100644 --- a/skymp5-client/src/sync/movementGet.ts +++ b/skymp5-client/src/sync/movementGet.ts @@ -1,5 +1,5 @@ import { FormModel } from '../modelSource/model'; -import { ObjectReference, Actor, TESModPlatform } from "skyrimPlatform"; +import { ObjectReference, Actor, TESModPlatform, printConsole } from "skyrimPlatform"; import { NiPoint3, Movement, RunMode } from "./movement"; import { ObjectReferenceEx } from '../extensions/objectReferenceEx'; @@ -69,6 +69,8 @@ export const getMovement = (refr: ObjectReference, form?: FormModel): Movement = const worldOrCell = refr.getWorldSpace() || refr.getParentCell(); + const isDead = form?.isDead ?? false; + return { worldOrCell: worldOrCell?.getFormID() || 0, pos, @@ -81,7 +83,7 @@ export const getMovement = (refr: ObjectReference, form?: FormModel): Movement = isSneaking: !!(ac && isSneaking(ac)), isBlocking: !!(ac && ac.getAnimationVariableBool("IsBlocking")), isWeapDrawn: !!(ac && ac.isWeaponDrawn()), - isDead: form?.isDead ?? false, + isDead: isDead, healthPercentage: healthPercentage || 0, lookAt, speed From d0c4d18c9497aea44fa05b2f9c3618816de91acf Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 12 Oct 2023 19:09:26 +0600 Subject: [PATCH 39/88] will not fix. will just leave huge respawn timer for now. better think more about code structure then fix From c0457d600d80d69e67daa5ee69d73274d4bd5457 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 13 Oct 2023 15:59:11 +0600 Subject: [PATCH 40/88] different respawn position and delay for npcs --- libespm/include/libespm/ACHR.h | 5 +++ libespm/include/libespm/REFR.h | 2 +- libespm/src/ACHR.cpp | 12 +++++++ .../cpp/server_guest_lib/MpActor.cpp | 36 +++++++++++++++++-- skymp5-server/cpp/server_guest_lib/MpActor.h | 4 +-- .../cpp/server_guest_lib/MpChangeForms.h | 5 +++ .../cpp/server_guest_lib/WorldState.cpp | 14 ++++++-- 7 files changed, 70 insertions(+), 8 deletions(-) diff --git a/libespm/include/libespm/ACHR.h b/libespm/include/libespm/ACHR.h index 73b499ea31..7469dd2dec 100644 --- a/libespm/include/libespm/ACHR.h +++ b/libespm/include/libespm/ACHR.h @@ -1,5 +1,6 @@ #pragma once #include "RecordHeader.h" +#include "REFR.h" #pragma pack(push, 1) @@ -11,6 +12,10 @@ class ACHR final : public RecordHeader static constexpr auto kType = "ACHR"; bool StartsDead() const noexcept; + + struct Data : public REFR::Data {}; + + Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept; }; static_assert(sizeof(ACHR) == sizeof(RecordHeader)); diff --git a/libespm/include/libespm/REFR.h b/libespm/include/libespm/REFR.h index 9133740901..994c29caa3 100644 --- a/libespm/include/libespm/REFR.h +++ b/libespm/include/libespm/REFR.h @@ -5,7 +5,7 @@ namespace espm { -class REFR final : public RecordHeader +class REFR : public RecordHeader { public: static constexpr auto kType = "REFR"; diff --git a/libespm/src/ACHR.cpp b/libespm/src/ACHR.cpp index c0f36b0937..4bda8ccd93 100644 --- a/libespm/src/ACHR.cpp +++ b/libespm/src/ACHR.cpp @@ -9,4 +9,16 @@ bool ACHR::StartsDead() const noexcept return this->flags & 0x200; } +ACHR::Data ACHR::GetData( + CompressedFieldsCache& compressedFieldsCache) const noexcept +{ + REFR::Data data = + reinterpret_cast(this)->GetData(compressedFieldsCache); + + ACHR::Data res; + static_cast(res) = data; + + return res; +} + } diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index 7e8c7a4c4d..ec76480917 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -731,20 +731,52 @@ void MpActor::SetSpawnPoint(const LocationalData& position) [&](MpChangeForm& changeForm) { changeForm.spawnPoint = position; }); } +// WorldState.cpp +const NiPoint3& GetPos(const espm::REFR::LocationalData* locationalData); +NiPoint3 GetRot(const espm::REFR::LocationalData* locationalData); +uint32_t GetWorldOrCell(const espm::CombineBrowser& br, + const espm::LookupResult& refrLookupRes); + LocationalData MpActor::GetSpawnPoint() const { + auto formId = GetFormId(); + + bool createdAsPlayer = formId >= 0xff000000 && GetBaseId() <= 0x7; + if (!createdAsPlayer) { + if (auto worldState = GetParent(); worldState && worldState->HasEspm()) { + auto data = espm::GetData(formId, worldState); + auto lookupRes = worldState->GetEspm().GetBrowser().LookupById(formId); + const NiPoint3& pos = ::GetPos(data.loc); + NiPoint3 rot = ::GetRot(data.loc); + uint32_t worldOrCell = + ::GetWorldOrCell(worldState->GetEspm().GetBrowser(), lookupRes); + return LocationalData{ + pos, rot, FormDesc::FromFormId(worldOrCell, worldState->espmFiles) + }; + } + } return ChangeForm().spawnPoint; } const float MpActor::GetRespawnTime() const { + bool createdAsPlayer = GetFormId() >= 0xff000000 && GetBaseId() <= 0x7; + if (!createdAsPlayer) { + + // todo: comment it out + return 5; + + static const auto kOneHour = 60.f * 60.f; + return kOneHour; + } return ChangeForm().spawnDelay; } -void MpActor::SetRespawnTime(float time) +void MpActor::SetRespawnTime(float time, bool save) { EditChangeForm( - [&](MpChangeForm& changeForm) { changeForm.spawnDelay = time; }); + [&](MpChangeForm& changeForm) { changeForm.spawnDelay = time; }, + save ? Mode::RequestSave : Mode::NoRequestSave); } void MpActor::SetIsDead(bool isDead) diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index afb8e377ea..c2edceeb87 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -35,7 +35,7 @@ class MpActor : public MpObjectReference uint32_t GetRaceId() const; bool IsWeaponDrawn() const; espm::ObjectBounds GetBounds() const; - const std::vector &GetTemplateChain() const; + const std::vector& GetTemplateChain() const; void SetRaceMenuOpen(bool isOpen); void SetAppearance(const Appearance* newAppearance); @@ -94,7 +94,7 @@ class MpActor : public MpObjectReference void SetSpawnPoint(const LocationalData& position); LocationalData GetSpawnPoint() const; const float GetRespawnTime() const; - void SetRespawnTime(float time); + void SetRespawnTime(float time, bool save = true); void SetIsDead(bool isDead); diff --git a/skymp5-server/cpp/server_guest_lib/MpChangeForms.h b/skymp5-server/cpp/server_guest_lib/MpChangeForms.h index ede423adf0..9ad5fb9ba7 100644 --- a/skymp5-server/cpp/server_guest_lib/MpChangeForms.h +++ b/skymp5-server/cpp/server_guest_lib/MpChangeForms.h @@ -84,10 +84,15 @@ class MpChangeFormREFR // values in skymp due to poor design std::string appearanceDump, equipmentDump; ActorValues actorValues; + + // Used only for player characters. See GetSpawnPoint LocationalData spawnPoint = { { 133857, -61130, 14662 }, { 0.f, 0.f, 72.f }, FormDesc::Tamriel() }; + + // Used only for player characters. See GetSpawnDelay float spawnDelay = 25.0f; + std::vector templateChain; // Used for PlayAnimation (object reference) diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index 660ae7c097..d3a67801df 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -29,19 +29,26 @@ #include #include -namespace { -inline const NiPoint3& GetPos(const espm::REFR::LocationalData* locationalData) +const NiPoint3& GetPos(const espm::REFR::LocationalData* locationalData) { return *reinterpret_cast(locationalData->pos); } -inline NiPoint3 GetRot(const espm::REFR::LocationalData* locationalData) +NiPoint3 GetRot(const espm::REFR::LocationalData* locationalData) { static const auto g_pi = std::acos(-1.f); return { locationalData->rotRadians[0] / g_pi * 180.f, locationalData->rotRadians[1] / g_pi * 180.f, locationalData->rotRadians[2] / g_pi * 180.f }; } + +uint32_t GetWorldOrCell(const espm::CombineBrowser& br, + const espm::LookupResult& refrLookupRes) +{ + auto mapping = br.GetCombMapping(refrLookupRes.fileIdx); + uint32_t worldOrCell = + espm::utils::GetMappedId(GetWorldOrCell(br, refrLookupRes.rec), *mapping); + return worldOrCell; } struct WorldState::Impl @@ -437,6 +444,7 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, new MpActor(formLocationalData, formCallbacksFactory(), baseId)); } AddForm(std::move(form), formId, true); + // Do not TriggerFormInitEvent here, doing it later after changeForm apply } From 4834e8c5300e999c196e4ef49be0dad24720c78a Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 16:55:24 +0600 Subject: [PATCH 41/88] . --- .../services/consoleCommandsService.ts | 2 + .../services/services/sendInputsService.ts | 6 +- skymp5-server/cpp/addon/ScampServer.cpp | 23 +- .../cpp/server_guest_lib/ScriptStorage.cpp | 215 ------------------ .../cpp/server_guest_lib/ScriptStorage.h | 79 ------- .../script_storages/AssetsScriptStorage.cpp | 56 +++++ .../script_storages/AssetsScriptStorage.h | 16 ++ .../BsaArchiveScriptStorage.cpp | 51 +++++ .../script_storages/BsaArchiveScriptStorage.h | 17 ++ .../script_storages/CombinedScriptStorage.cpp | 32 +++ .../script_storages/CombinedScriptStorage.h | 21 ++ .../DirectoryScriptStorage.cpp | 52 +++++ .../script_storages/DirectoryScriptStorage.h | 16 ++ .../script_storages/IScriptStorage.h | 15 ++ .../script_storages/ScriptStorageFactory.cpp | 29 +++ .../script_storages/ScriptStorageFactory.h | 13 ++ .../script_storages/ScriptStorageUtils.cpp | 45 ++++ .../script_storages/ScriptStorageUtils.h | 14 ++ unit/PartOne_ActivateTest.cpp | 2 +- 19 files changed, 386 insertions(+), 318 deletions(-) delete mode 100644 skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp delete mode 100644 skymp5-server/cpp/server_guest_lib/ScriptStorage.h create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.h create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.h create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.h create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.h create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/IScriptStorage.h create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h diff --git a/skymp5-client/src/services/services/consoleCommandsService.ts b/skymp5-client/src/services/services/consoleCommandsService.ts index b01b14e05e..9d153d4dd0 100644 --- a/skymp5-client/src/services/services/consoleCommandsService.ts +++ b/skymp5-client/src/services/services/consoleCommandsService.ts @@ -86,6 +86,8 @@ export class ConsoleCommandsService extends ClientListener { reliability: "reliable" }); + this.sp.printConsole(args); + // Meant to be shown to user, not for logging this.sp.printConsole("sent"); return false; diff --git a/skymp5-client/src/services/services/sendInputsService.ts b/skymp5-client/src/services/services/sendInputsService.ts index c0d3175a7c..02b66750cb 100644 --- a/skymp5-client/src/services/services/sendInputsService.ts +++ b/skymp5-client/src/services/services/sendInputsService.ts @@ -108,9 +108,9 @@ export class SendInputsService extends ClientListener { data: getMovement(owner, form), _refrId }; - if (_refrId) /*not a player character*/{ - this.sp.printConsole("isDead", message.data.isDead); - } + // if (_refrId) /*not a player character*/{ + // this.sp.printConsole("isDead", message.data.isDead); + // } this.controller.emitter.emit("sendMessageWithRefrId", { message, reliability: "unreliable" diff --git a/skymp5-server/cpp/addon/ScampServer.cpp b/skymp5-server/cpp/addon/ScampServer.cpp index 8a55a7f636..d385dec2c5 100644 --- a/skymp5-server/cpp/addon/ScampServer.cpp +++ b/skymp5-server/cpp/addon/ScampServer.cpp @@ -14,12 +14,12 @@ #include "PacketHistoryWrapper.h" #include "PapyrusUtils.h" #include "ScampServerListener.h" -#include "ScriptStorage.h" #include "SettingsUtils.h" #include "formulas/SweetPieDamageFormula.h" #include "formulas/TES5DamageFormula.h" #include "libespm/IterateFields.h" #include "property_bindings/PropertyBindingFactory.h" +#include "script_storages/ScriptStorageFactory.h" #include #include #include @@ -278,15 +278,6 @@ ScampServer::ScampServer(const Napi::CallbackInfo& info) } } - std::vector> bsaScriptStorages; - if (serverSettings["archives"].is_array()) { - for (auto archive : serverSettings["archives"]) { - std::string archivePath = archive.get(); - bsaScriptStorages.push_back( - std::make_shared(archivePath.data())); - } - } - if (serverSettings["lang"] != nullptr) { logger->info("Run localization provider"); localizationProvider = std::make_shared( @@ -315,16 +306,8 @@ ScampServer::ScampServer(const Napi::CallbackInfo& info) partOne->SetDamageFormula(std::make_unique()); } - std::vector> scriptStorages; - scriptStorages.push_back(std::make_shared( - (espm::fs::path(dataDir) / "scripts").string())); - for (auto scriptStorage : bsaScriptStorages) { - scriptStorages.push_back(scriptStorage); - } - scriptStorages.push_back(std::make_shared()); - auto scriptStorage = - std::make_shared(scriptStorages); - partOne->worldState.AttachScriptStorage(scriptStorage); + partOne->worldState.AttachScriptStorage( + ScriptStorageFactory::Create(serverSettings)); partOne->AttachEspm(espm); partOne->animationSystem.Init(&partOne->worldState); diff --git a/skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp b/skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp deleted file mode 100644 index e878642996..0000000000 --- a/skymp5-server/cpp/server_guest_lib/ScriptStorage.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "ScriptStorage.h" -#include -#include -#include -#include -#include -#include - -namespace { -std::string GetFileName(const std::string& path) -{ - std::string s = path; - while (s.find('/') != s.npos || s.find('\\') != s.npos) { - while (s.find('/') != s.npos) - s = { s.begin() + s.find('/') + 1, s.end() }; - while (s.find('\\') != s.npos) - s = { s.begin() + s.find('\\') + 1, s.end() }; - } - return s; -} - -std::string RemoveExtension(std::string s) -{ - const std::regex e(".*\\.pex"); - if (std::regex_match(s, e)) { - s = { s.begin(), s.end() - strlen(".pex") }; - return s; - } - return ""; -} - -std::set GetScriptsInDirectory(std::string pexDir) -{ - std::set scripts; - - for (auto& p : std::filesystem::directory_iterator(pexDir)) { - if (p.is_directory()) - continue; - - std::string s = GetFileName(p.path().string()); - if (auto fileNameWe = RemoveExtension(s); !fileNameWe.empty()) - scripts.insert({ fileNameWe.begin(), fileNameWe.end() }); - } - - return scripts; -} -} - -DirectoryScriptStorage::DirectoryScriptStorage(const std::string& pexDirPath_) - : pexDir(pexDirPath_) -{ - scripts = GetScriptsInDirectory(pexDir); -} - -std::vector DirectoryScriptStorage::GetScriptPex( - const char* scriptName) -{ - const auto path = - std::filesystem::path(pexDir) / (scriptName + std::string(".pex")); - - if (!std::filesystem::exists(path)) { - spdlog::trace("DirectoryScriptStorage::GetScriptPex - Not found {} (file " - "doesn't exist)", - scriptName); - return {}; - } - - std::ifstream f(path, std::ios::binary); - if (!f.is_open()) { - throw std::runtime_error(path.string() + " is failed to open"); - } - std::vector buffer(std::istreambuf_iterator(f), {}); - - if (buffer.empty()) { - spdlog::trace( - "DirectoryScriptStorage::GetScriptPex - Not found {} (file is empty)", - scriptName); - return {}; - } - - spdlog::trace("DirectoryScriptStorage::GetScriptPex - Found {}", scriptName); - return buffer; -} - -const std::set& DirectoryScriptStorage::ListScripts( - bool forceReloadScripts) -{ - if (forceReloadScripts) { - scripts = GetScriptsInDirectory(pexDir); - } - return scripts; -} - -CMRC_DECLARE(server_standard_scripts); - -AssetsScriptStorage::AssetsScriptStorage() -{ - try { - auto fileSystem = cmrc::server_standard_scripts::get_filesystem(); - for (auto&& entry : fileSystem.iterate_directory("standard_scripts")) { - cmrc::file file = - fileSystem.open("standard_scripts/" + entry.filename()); - const uint8_t* begin = reinterpret_cast(file.begin()); - const uint8_t* end = begin + file.size(); - std::vector pex(begin, end); - - auto nameWithoutExtension = - std::filesystem::path(entry.filename()).stem().string(); - scripts.insert( - { nameWithoutExtension.begin(), nameWithoutExtension.end() }); - scriptPex[{ nameWithoutExtension.begin(), nameWithoutExtension.end() }] = - pex; - } - } catch (std::exception& e) { - auto dir = - cmrc::server_standard_scripts::get_filesystem().iterate_directory(""); - std::stringstream ss; - ss << e.what() << std::endl << std::endl; - ss << "Root directory contents is: " << std::endl; - for (auto&& entry : dir) { - ss << entry.filename() << std::endl; - } - throw std::runtime_error(ss.str()); - } -} - -std::vector AssetsScriptStorage::GetScriptPex(const char* scriptName) -{ - auto it = scriptPex.find(scriptName); - if (it == scriptPex.end()) { - spdlog::trace("AssetsScriptStorage::GetScriptPex - Not found {}", - scriptName); - return {}; - } - spdlog::trace("AssetsScriptStorage::GetScriptPex - Found {}", scriptName); - return it->second; -} - -const std::set& AssetsScriptStorage::ListScripts(bool) -{ - return scripts; -} - -BsaArchiveScriptStorage::BsaArchiveScriptStorage(const char* bsaPath) -{ - bsa.read(bsaPath); -} - -std::vector BsaArchiveScriptStorage::GetScriptPex( - const char* scriptName) -{ - auto it = scriptPex.find(scriptName); - if (it == scriptPex.end()) { - spdlog::trace("BsaArchiveScriptStorage::GetScriptPex - Not found {}", - scriptName); - return {}; - } - spdlog::trace("BsaArchiveScriptStorage::GetScriptPex - Found {}", - scriptName); - return it->second; -} - -const std::set& BsaArchiveScriptStorage::ListScripts( - bool forceReloadScripts) -{ - if (scripts.empty()) { - auto bsaScripts = *bsa["scripts"]; - for (auto it = bsaScripts.begin(); it != bsaScripts.end(); it++) { - auto fileName = it->first.name(); - - const std::byte* data = it->second.data(); - size_t size = it->second.size(); - auto pex = - std::vector(reinterpret_cast(data), - reinterpret_cast(data) + size); - - auto nameWithoutExtension = - std::filesystem::path(fileName).stem().string(); - scripts.insert( - { nameWithoutExtension.begin(), nameWithoutExtension.end() }); - scriptPex[{ nameWithoutExtension.begin(), nameWithoutExtension.end() }] = - pex; - } - } - return scripts; -} - -CombinedScriptStorage::CombinedScriptStorage( - std::vector> scriptStorages) -{ - this->scriptStorages = std::move(scriptStorages); -} - -std::vector CombinedScriptStorage::GetScriptPex( - const char* scriptName) -{ - for (auto& storage : scriptStorages) { - auto result = storage->GetScriptPex(scriptName); - if (!result.empty()) { - return result; - } - } - return {}; -} - -const std::set& CombinedScriptStorage::ListScripts( - bool forceReloadScripts) -{ - scripts.clear(); - for (auto& storage : scriptStorages) { - auto& result = storage->ListScripts(forceReloadScripts); - scripts.insert(result.begin(), result.end()); - } - return scripts; -} diff --git a/skymp5-server/cpp/server_guest_lib/ScriptStorage.h b/skymp5-server/cpp/server_guest_lib/ScriptStorage.h deleted file mode 100644 index 3fbc917139..0000000000 --- a/skymp5-server/cpp/server_guest_lib/ScriptStorage.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once -#include "papyrus-vm/CIString.h" -#include -#include -#include -#include -#include -#include -#include - -class IScriptStorage -{ -public: - virtual ~IScriptStorage() = default; - - virtual std::vector GetScriptPex(const char* scriptName) = 0; - - virtual const std::set& ListScripts(bool forceReloadScripts) = 0; -}; - -class DirectoryScriptStorage : public IScriptStorage -{ -public: - DirectoryScriptStorage(const std::string& pexDir_); - - std::vector GetScriptPex(const char* scriptName) override; - - const std::set& ListScripts(bool forceReloadScripts) override; - -private: - const std::string pexDir; - std::set scripts; -}; - -class AssetsScriptStorage : public IScriptStorage -{ -public: - AssetsScriptStorage(); - - std::vector GetScriptPex(const char* scriptName) override; - - const std::set& ListScripts(bool forceReloadScripts) override; - -private: - std::set scripts; - CIMap> scriptPex; -}; - -class BsaArchiveScriptStorage : public IScriptStorage -{ -public: - BsaArchiveScriptStorage(const char* pathToBsa); - - std::vector GetScriptPex(const char* scriptName) override; - - const std::set& ListScripts(bool forceReloadScripts) override; - -private: - std::set scripts; - CIMap> scriptPex; - bsa::tes4::archive bsa; -}; - -class CombinedScriptStorage : public IScriptStorage -{ -public: - // Load order matters. But unlike mods, scriptStorages.front() will be - // checked first - CombinedScriptStorage( - std::vector> scriptStorages); - - std::vector GetScriptPex(const char* scriptName) override; - - const std::set& ListScripts(bool forceReloadScripts) override; - -private: - std::vector> scriptStorages; - std::set scripts; -}; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.cpp new file mode 100644 index 0000000000..de4774a054 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.cpp @@ -0,0 +1,56 @@ +#include "AssetsScriptStorage.h" + +#include +#include +#include +#include + +CMRC_DECLARE(server_standard_scripts); + +AssetsScriptStorage::AssetsScriptStorage() +{ + try { + auto fileSystem = cmrc::server_standard_scripts::get_filesystem(); + for (auto&& entry : fileSystem.iterate_directory("standard_scripts")) { + cmrc::file file = + fileSystem.open("standard_scripts/" + entry.filename()); + const uint8_t* begin = reinterpret_cast(file.begin()); + const uint8_t* end = begin + file.size(); + std::vector pex(begin, end); + + auto nameWithoutExtension = + std::filesystem::path(entry.filename()).stem().string(); + scripts.insert( + { nameWithoutExtension.begin(), nameWithoutExtension.end() }); + scriptPex[{ nameWithoutExtension.begin(), nameWithoutExtension.end() }] = + pex; + } + } catch (std::exception& e) { + auto dir = + cmrc::server_standard_scripts::get_filesystem().iterate_directory(""); + std::stringstream ss; + ss << e.what() << std::endl << std::endl; + ss << "Root directory contents is: " << std::endl; + for (auto&& entry : dir) { + ss << entry.filename() << std::endl; + } + throw std::runtime_error(ss.str()); + } +} + +std::vector AssetsScriptStorage::GetScriptPex(const char* scriptName) +{ + auto it = scriptPex.find(scriptName); + if (it == scriptPex.end()) { + spdlog::trace("AssetsScriptStorage::GetScriptPex - Not found {}", + scriptName); + return {}; + } + spdlog::trace("AssetsScriptStorage::GetScriptPex - Found {}", scriptName); + return it->second; +} + +const std::set& AssetsScriptStorage::ListScripts(bool) +{ + return scripts; +} diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.h b/skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.h new file mode 100644 index 0000000000..b8cd5678d0 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/AssetsScriptStorage.h @@ -0,0 +1,16 @@ +#pragma once +#include "IScriptStorage.h" + +class AssetsScriptStorage : public IScriptStorage +{ +public: + AssetsScriptStorage(); + + std::vector GetScriptPex(const char* scriptName) override; + + const std::set& ListScripts(bool forceReloadScripts) override; + +private: + std::set scripts; + CIMap> scriptPex; +}; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp new file mode 100644 index 0000000000..a21bf62336 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp @@ -0,0 +1,51 @@ +#include "BsaArchiveScriptStorage.h" + +#include +#include + +BsaArchiveScriptStorage::BsaArchiveScriptStorage(const char* bsaPath_) +{ + this->bsaPath = bsaPath_; +} + +std::vector BsaArchiveScriptStorage::GetScriptPex( + const char* scriptName) +{ + auto it = scriptPex.find(scriptName); + if (it == scriptPex.end()) { + spdlog::trace("BsaArchiveScriptStorage::GetScriptPex - Not found {}", + scriptName); + return {}; + } + spdlog::trace("BsaArchiveScriptStorage::GetScriptPex - Found {}", + scriptName); + return it->second; +} + +const std::set& BsaArchiveScriptStorage::ListScripts( + bool forceReloadScripts) +{ + if (scripts.empty() || forceReloadScripts) { + scripts.clear(); + bsa::tes4::archive bsa; + bsa.read(bsaPath); + auto bsaScripts = *bsa["scripts"]; + for (auto it = bsaScripts.begin(); it != bsaScripts.end(); it++) { + auto fileName = it->first.name(); + + const std::byte* data = it->second.data(); + size_t size = it->second.size(); + auto pex = + std::vector(reinterpret_cast(data), + reinterpret_cast(data) + size); + + auto nameWithoutExtension = + std::filesystem::path(fileName).stem().string(); + scripts.insert( + { nameWithoutExtension.begin(), nameWithoutExtension.end() }); + scriptPex[{ nameWithoutExtension.begin(), nameWithoutExtension.end() }] = + pex; + } + } + return scripts; +} diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.h b/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.h new file mode 100644 index 0000000000..9ae084c16e --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.h @@ -0,0 +1,17 @@ +#pragma once +#include "IScriptStorage.h" + +class BsaArchiveScriptStorage : public IScriptStorage +{ +public: + BsaArchiveScriptStorage(const char* pathToBsa); + + std::vector GetScriptPex(const char* scriptName) override; + + const std::set& ListScripts(bool forceReloadScripts) override; + +private: + std::set scripts; + CIMap> scriptPex; + std::string bsaPath; +}; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.cpp new file mode 100644 index 0000000000..a8c5e08f61 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.cpp @@ -0,0 +1,32 @@ +#include "CombinedScriptStorage.h" + +CombinedScriptStorage::CombinedScriptStorage( + std::vector> scriptStorages) +{ + this->scriptStorages = std::move(scriptStorages); +} + +std::vector CombinedScriptStorage::GetScriptPex( + const char* scriptName) +{ + for (auto& storage : scriptStorages) { + auto result = storage->GetScriptPex(scriptName); + if (!result.empty()) { + return result; + } + } + return {}; +} + +const std::set& CombinedScriptStorage::ListScripts( + bool forceReloadScripts) +{ + if (scripts.empty() || forceReloadScripts) { + scripts.clear(); + for (auto& storage : scriptStorages) { + auto& result = storage->ListScripts(forceReloadScripts); + scripts.insert(result.begin(), result.end()); + } + } + return scripts; +} diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.h b/skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.h new file mode 100644 index 0000000000..c9e1f54f62 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/CombinedScriptStorage.h @@ -0,0 +1,21 @@ +#pragma once +#include "IScriptStorage.h" + +#include + +class CombinedScriptStorage : public IScriptStorage +{ +public: + // Load order matters. But unlike mods, scriptStorages.front() will be + // checked first + CombinedScriptStorage( + std::vector> scriptStorages); + + std::vector GetScriptPex(const char* scriptName) override; + + const std::set& ListScripts(bool forceReloadScripts) override; + +private: + std::vector> scriptStorages; + std::set scripts; +}; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.cpp new file mode 100644 index 0000000000..b54c6a7fe7 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.cpp @@ -0,0 +1,52 @@ +#include "DirectoryScriptStorage.h" +#include "ScriptStorageUtils.h" + +#include +#include +#include +#include + +DirectoryScriptStorage::DirectoryScriptStorage(const std::string& pexDirPath_) + : pexDir(pexDirPath_) +{ + scripts = ScriptStorageUtils::GetScriptsInDirectory(pexDir); +} + +std::vector DirectoryScriptStorage::GetScriptPex( + const char* scriptName) +{ + const auto path = + std::filesystem::path(pexDir) / (scriptName + std::string(".pex")); + + if (!std::filesystem::exists(path)) { + spdlog::trace("DirectoryScriptStorage::GetScriptPex - Not found {} (file " + "doesn't exist)", + scriptName); + return {}; + } + + std::ifstream f(path, std::ios::binary); + if (!f.is_open()) { + throw std::runtime_error(path.string() + " is failed to open"); + } + std::vector buffer(std::istreambuf_iterator(f), {}); + + if (buffer.empty()) { + spdlog::trace( + "DirectoryScriptStorage::GetScriptPex - Not found {} (file is empty)", + scriptName); + return {}; + } + + spdlog::trace("DirectoryScriptStorage::GetScriptPex - Found {}", scriptName); + return buffer; +} + +const std::set& DirectoryScriptStorage::ListScripts( + bool forceReloadScripts) +{ + if (forceReloadScripts) { + scripts = ScriptStorageUtils::GetScriptsInDirectory(pexDir); + } + return scripts; +} diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.h b/skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.h new file mode 100644 index 0000000000..d46ed75969 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/DirectoryScriptStorage.h @@ -0,0 +1,16 @@ +#pragma once +#include "IScriptStorage.h" + +class DirectoryScriptStorage : public IScriptStorage +{ +public: + DirectoryScriptStorage(const std::string& pexDir_); + + std::vector GetScriptPex(const char* scriptName) override; + + const std::set& ListScripts(bool forceReloadScripts) override; + +private: + const std::string pexDir; + std::set scripts; +}; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/IScriptStorage.h b/skymp5-server/cpp/server_guest_lib/script_storages/IScriptStorage.h new file mode 100644 index 0000000000..2e261d7ebe --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/IScriptStorage.h @@ -0,0 +1,15 @@ +#pragma once +#include "papyrus-vm/CIString.h" +#include +#include +#include + +class IScriptStorage +{ +public: + virtual ~IScriptStorage() = default; + + virtual std::vector GetScriptPex(const char* scriptName) = 0; + + virtual const std::set& ListScripts(bool forceReloadScripts) = 0; +}; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp new file mode 100644 index 0000000000..19d92d7f9d --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp @@ -0,0 +1,29 @@ +#include "ScriptStorageFactory.h" + +#include + +std::shared_ptr ScriptStorageFactory::Create( + nlohmann::json serverSettings) +{ + std::string dataDir = serverSettings["dataDir"]; + + std::vector> bsaScriptStorages; + if (serverSettings.contains("archives") && + serverSettings.at("archives").is_array()) { + for (auto archive : serverSettings.at("archives")) { + std::string archivePath = archive.get(); + bsaScriptStorages.push_back( + std::make_shared(archivePath.data())); + } + } + + std::vector> scriptStorages; + scriptStorages.push_back(std::make_shared( + (std::filesystem::path(dataDir) / "scripts").string())); + for (auto& scriptStorage : bsaScriptStorages) { + scriptStorages.push_back(scriptStorage); + } + scriptStorages.push_back(std::make_shared()); + return std::dynamic_pointer_cast( + std::make_shared(scriptStorages)); +} diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h new file mode 100644 index 0000000000..d6d9995157 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h @@ -0,0 +1,13 @@ +#pragma once +#include "AssetsScriptStorage.h" +#include "BsaArchiveScriptStorage.h" +#include "CombinedScriptStorage.h" +#include "DirectoryScriptStorage.h" + +#include + +class ScriptStorageFactory +{ +public: + static std::shared_ptr Create(nlohmann::json serverSettings); +}; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp new file mode 100644 index 0000000000..951e190a60 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp @@ -0,0 +1,45 @@ +#include "ScriptStorageUtils.h" + +#include +#include + +std::string ScriptStorageUtils::GetFileName(const std::string& path) +{ + std::string s = path; + while (s.find('/') != s.npos || s.find('\\') != s.npos) { + while (s.find('/') != s.npos) + s = { s.begin() + s.find('/') + 1, s.end() }; + while (s.find('\\') != s.npos) + s = { s.begin() + s.find('\\') + 1, s.end() }; + } + return s; +} + +std::string ScriptStorageUtils::RemoveExtension(std::string s) +{ + const std::regex e(".*\\.pex"); + if (std::regex_match(s, e)) { + s = { s.begin(), s.end() - strlen(".pex") }; + return s; + } + return ""; +} + +std::set ScriptStorageUtils::GetScriptsInDirectory( + const std::string& pexDir) +{ + std::set scripts; + + for (auto& p : std::filesystem::directory_iterator(pexDir)) { + if (p.is_directory()) { + continue; + } + + std::string s = GetFileName(p.path().string()); + if (auto fileNameWe = RemoveExtension(s); !fileNameWe.empty()) { + scripts.insert({ fileNameWe.begin(), fileNameWe.end() }); + } + } + + return scripts; +} diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h new file mode 100644 index 0000000000..c9a2a97347 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h @@ -0,0 +1,14 @@ +#pragma once + +#include "papyrus-vm/CIString.h" +#include +#include + +namespace ScriptStorageUtils { + +std::string GetFileName(const std::string& path); + +std::string RemoveExtension(std::string s); + +std::set GetScriptsInDirectory(const std::string &pexDir); +} diff --git a/unit/PartOne_ActivateTest.cpp b/unit/PartOne_ActivateTest.cpp index f0d4791915..0b441c5f85 100644 --- a/unit/PartOne_ActivateTest.cpp +++ b/unit/PartOne_ActivateTest.cpp @@ -1,5 +1,5 @@ -#include "ScriptStorage.h" #include "TestUtils.hpp" +#include "script_storages/DirectoryScriptStorage.h" using Catch::Matchers::ContainsSubstring; From 33a67fa6cfb76690528bfcb369238906bda12b9e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 19:19:00 +0600 Subject: [PATCH 42/88] enable mp disable command for npcs & stop sending spsnippets to npcs --- skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp | 4 +++- skymp5-server/cpp/server_guest_lib/SpSnippet.cpp | 10 ++++++++++ skymp5-server/cpp/server_guest_lib/SpSnippet.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp index fd88cd8201..142bae1196 100644 --- a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp +++ b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp @@ -124,8 +124,10 @@ void ExecuteDisable(MpActor& caller, ? caller : caller.GetParent()->GetFormAt(targetId); - if (target.GetFormId() >= 0xff000000) + if (target.GetFormId() >= 0xff000000 || + dynamic_cast(&target) != nullptr) { target.Disable(); + } } void ExecuteMp(MpActor& caller, diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp b/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp index 26618b18c6..090f8978d0 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp +++ b/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp @@ -14,6 +14,16 @@ SpSnippet::SpSnippet(const char* cl_, const char* func_, const char* args_, Viet::Promise SpSnippet::Execute(MpActor* actor) { + auto worldState = actor->GetParent(); + bool createdAsPlayer = + actor->GetFormId() >= 0xff000000 && actor->GetBaseId() <= 0x7; + if (!createdAsPlayer) { + // Return promise that never resolves in this case + // TODO: somehow detect user instead as this breaks potential feature of + // transferring user into an npc actor + return Viet::Promise(); + } + Viet::Promise promise; auto snippetIdx = actor->NextSnippetIndex(promise); diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippet.h b/skymp5-server/cpp/server_guest_lib/SpSnippet.h index c53e43169e..ff39b3bb74 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippet.h +++ b/skymp5-server/cpp/server_guest_lib/SpSnippet.h @@ -11,6 +11,7 @@ class SpSnippet public: SpSnippet(const char* cl_, const char* func_, const char* args_, uint32_t selfId_ = 0); + Viet::Promise Execute(MpActor* actor); private: From 076c326b38b962225cfaed59bba8f7803a54f782 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 19:45:08 +0600 Subject: [PATCH 43/88] polishing --- .../src/papyrus-vm-lib/ActivePexInstance.cpp | 10 -------- .../services/consoleCommandsService.ts | 2 -- .../services/services/sendInputsService.ts | 3 --- skymp5-client/src/sync/movementGet.ts | 6 ++--- .../cpp/server_guest_lib/Inventory.cpp | 8 ------- .../cpp/server_guest_lib/Inventory.h | 1 - .../cpp/server_guest_lib/LeveledListUtils.cpp | 24 +++++++++---------- 7 files changed, 14 insertions(+), 40 deletions(-) diff --git a/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp b/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp index b00b292d9f..b694e9c9e0 100644 --- a/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp +++ b/papyrus-vm/src/papyrus-vm-lib/ActivePexInstance.cpp @@ -729,20 +729,10 @@ VarValue& ActivePexInstance::GetIndentifierValue( if (treatStringsAsIdentifiers && value.GetType() == VarValue::kType_String) { auto& res = GetVariableValueByName(&locals, valueAsString); - // if (spdlog::should_log(spdlog::level::trace)) { - // spdlog::trace("GetIndentifierValue {}: {} = {}", - // this->sourcePex.fn()->source, valueAsString, - // res.ToString()); - // } return res; } if (value.GetType() == VarValue::kType_Identifier) { auto& res = GetVariableValueByName(&locals, valueAsString); - // if (spdlog::should_log(spdlog::level::trace)) { - // spdlog::trace("GetIndentifierValue {}: {} = {}", - // this->sourcePex.fn()->source, valueAsString, - // res.ToString()); - // } return res; } } diff --git a/skymp5-client/src/services/services/consoleCommandsService.ts b/skymp5-client/src/services/services/consoleCommandsService.ts index 9d153d4dd0..b01b14e05e 100644 --- a/skymp5-client/src/services/services/consoleCommandsService.ts +++ b/skymp5-client/src/services/services/consoleCommandsService.ts @@ -86,8 +86,6 @@ export class ConsoleCommandsService extends ClientListener { reliability: "reliable" }); - this.sp.printConsole(args); - // Meant to be shown to user, not for logging this.sp.printConsole("sent"); return false; diff --git a/skymp5-client/src/services/services/sendInputsService.ts b/skymp5-client/src/services/services/sendInputsService.ts index 02b66750cb..a378cc0592 100644 --- a/skymp5-client/src/services/services/sendInputsService.ts +++ b/skymp5-client/src/services/services/sendInputsService.ts @@ -108,9 +108,6 @@ export class SendInputsService extends ClientListener { data: getMovement(owner, form), _refrId }; - // if (_refrId) /*not a player character*/{ - // this.sp.printConsole("isDead", message.data.isDead); - // } this.controller.emitter.emit("sendMessageWithRefrId", { message, reliability: "unreliable" diff --git a/skymp5-client/src/sync/movementGet.ts b/skymp5-client/src/sync/movementGet.ts index 70d39d9ad4..8b4d0e95d9 100644 --- a/skymp5-client/src/sync/movementGet.ts +++ b/skymp5-client/src/sync/movementGet.ts @@ -1,5 +1,5 @@ import { FormModel } from '../modelSource/model'; -import { ObjectReference, Actor, TESModPlatform, printConsole } from "skyrimPlatform"; +import { ObjectReference, Actor, TESModPlatform } from "skyrimPlatform"; import { NiPoint3, Movement, RunMode } from "./movement"; import { ObjectReferenceEx } from '../extensions/objectReferenceEx'; @@ -69,8 +69,6 @@ export const getMovement = (refr: ObjectReference, form?: FormModel): Movement = const worldOrCell = refr.getWorldSpace() || refr.getParentCell(); - const isDead = form?.isDead ?? false; - return { worldOrCell: worldOrCell?.getFormID() || 0, pos, @@ -83,7 +81,7 @@ export const getMovement = (refr: ObjectReference, form?: FormModel): Movement = isSneaking: !!(ac && isSneaking(ac)), isBlocking: !!(ac && ac.getAnimationVariableBool("IsBlocking")), isWeapDrawn: !!(ac && ac.isWeaponDrawn()), - isDead: isDead, + isDead: form?.isDead ?? false, healthPercentage: healthPercentage || 0, lookAt, speed diff --git a/skymp5-server/cpp/server_guest_lib/Inventory.cpp b/skymp5-server/cpp/server_guest_lib/Inventory.cpp index 5bd42d7f94..7294888e49 100644 --- a/skymp5-server/cpp/server_guest_lib/Inventory.cpp +++ b/skymp5-server/cpp/server_guest_lib/Inventory.cpp @@ -78,14 +78,6 @@ Inventory& Inventory::RemoveItems(const std::vector& entries) return *this; } -Inventory& Inventory::WornAll() noexcept -{ - for (auto& entry : entries) { - entry.extra.worn = Inventory::Worn::Right; - } - return *this; -} - bool Inventory::HasItem(uint32_t baseId) const { for (auto& entry : entries) { diff --git a/skymp5-server/cpp/server_guest_lib/Inventory.h b/skymp5-server/cpp/server_guest_lib/Inventory.h index 55f3eccd2d..e88ebfab1d 100644 --- a/skymp5-server/cpp/server_guest_lib/Inventory.h +++ b/skymp5-server/cpp/server_guest_lib/Inventory.h @@ -82,7 +82,6 @@ class Inventory Inventory& AddItem(uint32_t baseId, uint32_t count); Inventory& AddItems(const std::vector& entries); Inventory& RemoveItems(const std::vector& entries); - Inventory& WornAll() noexcept; bool HasItem(uint32_t baseId) const; uint32_t GetItemCount(uint32_t baseId) const; uint32_t GetTotalItemCount() const; diff --git a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp index a8be761a6a..06013aaa02 100644 --- a/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp +++ b/skymp5-server/cpp/server_guest_lib/LeveledListUtils.cpp @@ -4,6 +4,15 @@ #include #include +namespace { +bool IsLeveledType(const espm::LookupResult& lookupRes) noexcept +{ + espm::Type type = lookupRes.rec->GetType(); + return type == espm::LVLI::kType || type == espm::LVLN::kType || + type == "LVSP" /* for the future leveled spell implementation */; +} +} + std::vector LeveledListUtils::EvaluateList( const espm::CombineBrowser& br, const espm::LookupResult& lookupRes, uint32_t pcLevel, uint8_t* chanceNoneOverride) @@ -11,10 +20,7 @@ std::vector LeveledListUtils::EvaluateList( espm::CompressedFieldsCache dummyCache; const espm::LeveledListBase* leveledList = nullptr; - if (lookupRes.rec->GetType() == espm::LVLI::kType || - lookupRes.rec->GetType() == espm::LVLN::kType || - lookupRes.rec->GetType() == - "LVSP") /* for the future leveled spell implementation */ { + if (IsLeveledType(lookupRes)) { leveledList = reinterpret_cast(lookupRes.rec); } @@ -68,10 +74,7 @@ std::map LeveledListUtils::EvaluateListRecurse( espm::CompressedFieldsCache dummyCache; const espm::LeveledListBase* leveledList = nullptr; - if (lookupRes.rec->GetType() == espm::LVLI::kType || - lookupRes.rec->GetType() == espm::LVLN::kType || - lookupRes.rec->GetType() == - "LVSP") /* for the future leveled spell implementation */ { + if (IsLeveledType(lookupRes)) { leveledList = reinterpret_cast(lookupRes.rec); } @@ -98,10 +101,7 @@ std::map LeveledListUtils::EvaluateListRecurse( if (!eLookupRes.rec) { continue; } - if (eLookupRes.rec->GetType() == espm::LVLI::kType || - eLookupRes.rec->GetType() == espm::LVLN::kType || - eLookupRes.rec->GetType() == - "LVSP") /* for the future leveled spell implementation */ { + if (IsLeveledType(eLookupRes)) { auto childRes = EvaluateListRecurse(br, eLookupRes, 1, pcLevel); for (auto& p : childRes) { res[p.first] += p.second; From a320cfc4f9d1b4dd06c28b1394ea0a5ecd5344dc Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 19:55:15 +0600 Subject: [PATCH 44/88] recalculateworn -> equipbestweapon --- .../cpp/server_guest_lib/ActionListener.cpp | 76 +++---------------- .../cpp/server_guest_lib/MpActor.cpp | 67 ++++++++++++++-- skymp5-server/cpp/server_guest_lib/MpActor.h | 2 + 3 files changed, 75 insertions(+), 70 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp index effd1f7e00..11dd9d1d2c 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp @@ -226,67 +226,6 @@ void ActionListener::OnUpdateEquipment( actor->SetEquipment(simdjson::minify(data)); } -void RecalculateWorn(MpObjectReference& refr) -{ - if (!refr.GetParent()->HasEspm()) { - return; - } - auto& loader = refr.GetParent()->GetEspm(); - auto& cache = refr.GetParent()->GetEspmCache(); - - auto ac = dynamic_cast(&refr); - if (!ac) { - return; - } - - const Equipment eq = ac->GetEquipment(); - - Equipment newEq; - newEq.numChanges = eq.numChanges + 1; - for (auto& entry : eq.inv.entries) { - bool isEquipped = entry.extra.worn != Inventory::Worn::None; - bool isWeap = - espm::GetRecordType(entry.baseId, refr.GetParent()) == espm::WEAP::kType; - if (isEquipped && isWeap) { - continue; - } - newEq.inv.AddItems({ entry }); - } - - const Inventory inv = ac->GetInventory(); - Inventory::Entry bestEntry; - int16_t bestDamage = -1; - for (auto& entry : inv.entries) { - if (entry.baseId) { - auto lookupRes = loader.GetBrowser().LookupById(entry.baseId); - if (auto weap = espm::Convert(lookupRes.rec)) { - if (!bestEntry.count || - weap->GetData(cache).weapData->damage > bestDamage) { - bestEntry = entry; - bestDamage = weap->GetData(cache).weapData->damage; - } - } - } - } - - if (bestEntry.count > 0) { - bestEntry.extra.worn = Inventory::Worn::Right; - newEq.inv.AddItems({ bestEntry }); - } - - ac->SetEquipment(newEq.ToJson().dump()); - for (auto listener : ac->GetListeners()) { - auto actor = dynamic_cast(listener); - if (!actor) { - continue; - } - UpdateEquipmentMessage msg; - msg.data = newEq.ToJson(); - msg.idx = ac->GetIdx(); - actor->SendToUser(msg, true); - } -} - void ActionListener::OnActivate(const RawMessageData& rawMsgData, uint32_t caster, uint32_t target) { @@ -318,7 +257,11 @@ void ActionListener::OnActivate(const RawMessageData& rawMsgData, caster == 0x14 ? *ac : partOne.worldState.GetFormAt(caster)); if (hosterId) { - RecalculateWorn(partOne.worldState.GetFormAt(caster)); + auto actor = std::dynamic_pointer_cast( + partOne.worldState.LookupFormById(caster)); + if (actor) { + actor->EquipBestWeapon(); + } } } @@ -517,10 +460,15 @@ void ActionListener::OnHostAttempt(const RawMessageData& rawMsgData, me->GetFormId()); hoster = me->GetFormId(); remote.UpdateHoster(hoster); - RecalculateWorn(remote); + + auto remoteAsActor = dynamic_cast(&remote); + + if (remoteAsActor) { + remoteAsActor->EquipBestWeapon(); + } uint64_t longFormId = remote.GetFormId(); - if (dynamic_cast(&remote) && longFormId < 0xff000000) { + if (remoteAsActor && longFormId < 0xff000000) { longFormId += 0x100000000; } diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index ec76480917..1cdd979451 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -25,6 +25,7 @@ #include "ChangeValuesMessage.h" #include "TeleportMessage.h" +#include "UpdateEquipmentMessage.h" struct MpActor::Impl { @@ -90,6 +91,63 @@ void MpActor::SetConsoleCommandsAllowedFlag(bool newValue) }); } +void MpActor::EquipBestWeapon() +{ + if (!GetParent()->HasEspm()) { + return; + } + + auto& loader = GetParent()->GetEspm(); + auto& cache = GetParent()->GetEspmCache(); + + const Equipment eq = GetEquipment(); + + Equipment newEq; + newEq.numChanges = eq.numChanges + 1; + for (auto& entry : eq.inv.entries) { + bool isEquipped = entry.extra.worn != Inventory::Worn::None; + bool isWeap = + espm::GetRecordType(entry.baseId, GetParent()) == espm::WEAP::kType; + if (isEquipped && isWeap) { + continue; + } + newEq.inv.AddItems({ entry }); + } + + const Inventory& inv = GetInventory(); + Inventory::Entry bestEntry; + int16_t bestDamage = -1; + for (auto& entry : inv.entries) { + if (entry.baseId) { + auto lookupRes = loader.GetBrowser().LookupById(entry.baseId); + if (auto weap = espm::Convert(lookupRes.rec)) { + if (!bestEntry.count || + weap->GetData(cache).weapData->damage > bestDamage) { + bestEntry = entry; + bestDamage = weap->GetData(cache).weapData->damage; + } + } + } + } + + if (bestEntry.count > 0) { + bestEntry.extra.worn = Inventory::Worn::Right; + newEq.inv.AddItems({ bestEntry }); + } + + SetEquipment(newEq.ToJson().dump()); + for (auto listener : GetListeners()) { + auto actor = dynamic_cast(listener); + if (!actor) { + continue; + } + UpdateEquipmentMessage msg; + msg.data = newEq.ToJson(); + msg.idx = GetIdx(); + actor->SendToUser(msg, true); + } +} + void MpActor::SetRaceMenuOpen(bool isOpen) { EditChangeForm( @@ -659,9 +717,6 @@ void MpActor::BeforeDestroy() UnsubscribeFromAll(); } -// ActionListener.cpp -void RecalculateWorn(MpObjectReference& refr); - void MpActor::Init(WorldState* worldState, uint32_t formId, bool hasChangeForm) { MpObjectReference::Init(worldState, formId, hasChangeForm); @@ -671,8 +726,8 @@ void MpActor::Init(WorldState* worldState, uint32_t formId, bool hasChangeForm) EnsureTemplateChainEvaluated(espm); EnsureBaseContainerAdded(espm); // template chain needed here - // equip best weapon (TODO: implement "gearedUpWeapons" flag) - RecalculateWorn(*this); + // TODO: implement "gearedUpWeapons" flag + EquipBestWeapon(); } } @@ -765,7 +820,7 @@ const float MpActor::GetRespawnTime() const // todo: comment it out return 5; - + static const auto kOneHour = 60.f * 60.f; return kOneHour; } diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index c2edceeb87..61b8a5fa5d 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -126,6 +126,8 @@ class MpActor : public MpObjectReference bool GetConsoleCommandsAllowedFlag() const; void SetConsoleCommandsAllowedFlag(bool newValue); + void EquipBestWeapon(); + private: struct Impl; std::shared_ptr pImpl; From bac538e6c1773e5dd19e8b3941aa4f0351494270 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 20:50:52 +0600 Subject: [PATCH 45/88] LocationalDataUtils --- .../server_guest_lib/LocationalDataUtils.cpp | 29 +++++++++++++++ .../server_guest_lib/LocationalDataUtils.h | 14 ++++++++ .../cpp/server_guest_lib/MpActor.cpp | 15 +++----- .../cpp/server_guest_lib/WorldState.cpp | 36 +++++-------------- 4 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 skymp5-server/cpp/server_guest_lib/LocationalDataUtils.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/LocationalDataUtils.h diff --git a/skymp5-server/cpp/server_guest_lib/LocationalDataUtils.cpp b/skymp5-server/cpp/server_guest_lib/LocationalDataUtils.cpp new file mode 100644 index 0000000000..c4748b2aad --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/LocationalDataUtils.cpp @@ -0,0 +1,29 @@ +#include "LocationalDataUtils.h" +#include "NiPoint3.h" + +#include "libespm/GroupUtils.h" +#include "libespm/Utils.h" + +const NiPoint3& LocationalDataUtils::GetPos( + const espm::REFR::LocationalData* locationalData) +{ + return *reinterpret_cast(locationalData->pos); +} + +NiPoint3 LocationalDataUtils::GetRot( + const espm::REFR::LocationalData* locationalData) +{ + static const auto kPi = std::acos(-1.f); + return { locationalData->rotRadians[0] / kPi * 180.f, + locationalData->rotRadians[1] / kPi * 180.f, + locationalData->rotRadians[2] / kPi * 180.f }; +} + +uint32_t LocationalDataUtils::GetWorldOrCell( + const espm::CombineBrowser& br, const espm::LookupResult& refrLookupRes) +{ + auto mapping = br.GetCombMapping(refrLookupRes.fileIdx); + uint32_t worldOrCell = espm::utils::GetMappedId( + espm::GetWorldOrCell(br, refrLookupRes.rec), *mapping); + return worldOrCell; +} diff --git a/skymp5-server/cpp/server_guest_lib/LocationalDataUtils.h b/skymp5-server/cpp/server_guest_lib/LocationalDataUtils.h new file mode 100644 index 0000000000..b8f1a23206 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/LocationalDataUtils.h @@ -0,0 +1,14 @@ +#pragma once +#include "libespm/CombineBrowser.h" +#include "libespm/LookupResult.h" +#include "libespm/REFR.h" +#include + +class NiPoint3; + +namespace LocationalDataUtils { +const NiPoint3& GetPos(const espm::REFR::LocationalData* locationalData); +NiPoint3 GetRot(const espm::REFR::LocationalData* locationalData); +uint32_t GetWorldOrCell(const espm::CombineBrowser& br, + const espm::LookupResult& refrLookupRes); +} diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index 1cdd979451..c5a3766483 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -7,6 +7,7 @@ #include "FormCallbacks.h" #include "GetBaseActorValues.h" #include "LeveledListUtils.h" +#include "LocationalDataUtils.h" #include "MathUtils.h" #include "MpChangeForms.h" #include "MsgType.h" @@ -786,12 +787,6 @@ void MpActor::SetSpawnPoint(const LocationalData& position) [&](MpChangeForm& changeForm) { changeForm.spawnPoint = position; }); } -// WorldState.cpp -const NiPoint3& GetPos(const espm::REFR::LocationalData* locationalData); -NiPoint3 GetRot(const espm::REFR::LocationalData* locationalData); -uint32_t GetWorldOrCell(const espm::CombineBrowser& br, - const espm::LookupResult& refrLookupRes); - LocationalData MpActor::GetSpawnPoint() const { auto formId = GetFormId(); @@ -801,10 +796,10 @@ LocationalData MpActor::GetSpawnPoint() const if (auto worldState = GetParent(); worldState && worldState->HasEspm()) { auto data = espm::GetData(formId, worldState); auto lookupRes = worldState->GetEspm().GetBrowser().LookupById(formId); - const NiPoint3& pos = ::GetPos(data.loc); - NiPoint3 rot = ::GetRot(data.loc); - uint32_t worldOrCell = - ::GetWorldOrCell(worldState->GetEspm().GetBrowser(), lookupRes); + const NiPoint3& pos = LocationalDataUtils::GetPos(data.loc); + NiPoint3 rot = LocationalDataUtils::GetRot(data.loc); + uint32_t worldOrCell = LocationalDataUtils::GetWorldOrCell( + worldState->GetEspm().GetBrowser(), lookupRes); return LocationalData{ pos, rot, FormDesc::FromFormId(worldOrCell, worldState->espmFiles) }; diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index d3a67801df..fc53789cb6 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -2,6 +2,8 @@ #include "FormCallbacks.h" #include "HeuristicPolicy.h" #include "ISaveStorage.h" +#include "IScriptStorage.h" +#include "LocationalDataUtils.h" #include "MpActor.h" #include "MpChangeForms.h" #include "MpFormGameObject.h" @@ -20,7 +22,6 @@ #include "PapyrusSound.h" #include "PapyrusUtility.h" #include "ScopedTask.h" -#include "ScriptStorage.h" #include "Timer.h" #include "libespm/GroupUtils.h" #include "papyrus-vm/Reader.h" @@ -29,28 +30,6 @@ #include #include -const NiPoint3& GetPos(const espm::REFR::LocationalData* locationalData) -{ - return *reinterpret_cast(locationalData->pos); -} - -NiPoint3 GetRot(const espm::REFR::LocationalData* locationalData) -{ - static const auto g_pi = std::acos(-1.f); - return { locationalData->rotRadians[0] / g_pi * 180.f, - locationalData->rotRadians[1] / g_pi * 180.f, - locationalData->rotRadians[2] / g_pi * 180.f }; -} - -uint32_t GetWorldOrCell(const espm::CombineBrowser& br, - const espm::LookupResult& refrLookupRes) -{ - auto mapping = br.GetCombMapping(refrLookupRes.fileIdx); - uint32_t worldOrCell = - espm::utils::GetMappedId(GetWorldOrCell(br, refrLookupRes.rec), *mapping); - return worldOrCell; -} - struct WorldState::Impl { std::unordered_map changes; @@ -411,10 +390,12 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, reinterpret_cast(existing->second.get()); if (locationalData) { - existingAsRefr->SetPosAndAngleSilent(GetPos(locationalData), - GetRot(locationalData)); + existingAsRefr->SetPosAndAngleSilent( + LocationalDataUtils::GetPos(locationalData), + LocationalDataUtils::GetRot(locationalData)); - assert(existingAsRefr->GetPos() == NiPoint3(GetPos(locationalData))); + assert(existingAsRefr->GetPos() == + NiPoint3(LocationalDataUtils::GetPos(locationalData))); } } else { @@ -432,7 +413,8 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, auto typeStr = t.ToString(); std::unique_ptr form; LocationalData formLocationalData = { - GetPos(locationalData), GetRot(locationalData), + LocationalDataUtils::GetPos(locationalData), + LocationalDataUtils::GetRot(locationalData), FormDesc::FromFormId(worldOrCell, espmFiles) }; if (!isNpc) { From 72ca39a9e95dd74cbb921aa1e3c737d6daa45ed6 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 21:10:04 +0600 Subject: [PATCH 46/88] create method IsCreatedAsPlayer --- skymp5-server/cpp/server_guest_lib/MpActor.cpp | 15 +++++++-------- skymp5-server/cpp/server_guest_lib/MpActor.h | 1 + skymp5-server/cpp/server_guest_lib/SpSnippet.cpp | 4 +--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index c5a3766483..b0ecf7921b 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -529,6 +529,11 @@ const std::vector& MpActor::GetTemplateChain() const return ChangeForm().templateChain; } +bool MpActor::IsCreatedAsPlayer() const +{ + return GetFormId() >= 0xff000000 && GetBaseId() <= 0x7; +} + void MpActor::SendAndSetDeathState(bool isDead, bool shouldTeleport) { float attribute = isDead ? 0.f : 1.f; @@ -791,8 +796,7 @@ LocationalData MpActor::GetSpawnPoint() const { auto formId = GetFormId(); - bool createdAsPlayer = formId >= 0xff000000 && GetBaseId() <= 0x7; - if (!createdAsPlayer) { + if (!IsCreatedAsPlayer()) { if (auto worldState = GetParent(); worldState && worldState->HasEspm()) { auto data = espm::GetData(formId, worldState); auto lookupRes = worldState->GetEspm().GetBrowser().LookupById(formId); @@ -810,12 +814,7 @@ LocationalData MpActor::GetSpawnPoint() const const float MpActor::GetRespawnTime() const { - bool createdAsPlayer = GetFormId() >= 0xff000000 && GetBaseId() <= 0x7; - if (!createdAsPlayer) { - - // todo: comment it out - return 5; - + if (!IsCreatedAsPlayer()) { static const auto kOneHour = 60.f * 60.f; return kOneHour; } diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index 61b8a5fa5d..e61e9357b2 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -36,6 +36,7 @@ class MpActor : public MpObjectReference bool IsWeaponDrawn() const; espm::ObjectBounds GetBounds() const; const std::vector& GetTemplateChain() const; + bool IsCreatedAsPlayer() const; void SetRaceMenuOpen(bool isOpen); void SetAppearance(const Appearance* newAppearance); diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp b/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp index 090f8978d0..a9f99b4417 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp +++ b/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp @@ -15,9 +15,7 @@ SpSnippet::SpSnippet(const char* cl_, const char* func_, const char* args_, Viet::Promise SpSnippet::Execute(MpActor* actor) { auto worldState = actor->GetParent(); - bool createdAsPlayer = - actor->GetFormId() >= 0xff000000 && actor->GetBaseId() <= 0x7; - if (!createdAsPlayer) { + if (!actor->IsCreatedAsPlayer()) { // Return promise that never resolves in this case // TODO: somehow detect user instead as this breaks potential feature of // transferring user into an npc actor From 351fd2becc60686b058c5aed126a479a93faa4cc Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 21:19:14 +0600 Subject: [PATCH 47/88] . --- skymp5-server/cpp/server_guest_lib/MpActor.cpp | 5 ++--- skymp5-server/cpp/server_guest_lib/MpActor.h | 2 +- unit/TemplateScriptTest.cpp | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index b0ecf7921b..6ae4c436f3 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -821,11 +821,10 @@ const float MpActor::GetRespawnTime() const return ChangeForm().spawnDelay; } -void MpActor::SetRespawnTime(float time, bool save) +void MpActor::SetRespawnTime(float time) { EditChangeForm( - [&](MpChangeForm& changeForm) { changeForm.spawnDelay = time; }, - save ? Mode::RequestSave : Mode::NoRequestSave); + [&](MpChangeForm& changeForm) { changeForm.spawnDelay = time; }); } void MpActor::SetIsDead(bool isDead) diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index e61e9357b2..f981f263e2 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -95,7 +95,7 @@ class MpActor : public MpObjectReference void SetSpawnPoint(const LocationalData& position); LocationalData GetSpawnPoint() const; const float GetRespawnTime() const; - void SetRespawnTime(float time, bool save = true); + void SetRespawnTime(float time); void SetIsDead(bool isDead); diff --git a/unit/TemplateScriptTest.cpp b/unit/TemplateScriptTest.cpp index b37a04ea86..c26cfd9684 100644 --- a/unit/TemplateScriptTest.cpp +++ b/unit/TemplateScriptTest.cpp @@ -47,6 +47,4 @@ TEST_CASE("MS13FrostbiteSpiderREF in BleakFalls should have scripts " } REQUIRE(what == "OK"); - - // REQUIRE(spider.HasScript("masterambushscript")); } From 3c49bcb5470aaf296d2ffac76c3845ec3cbcf89e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 22:17:17 +0600 Subject: [PATCH 48/88] fix inc --- skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 6 +++--- skymp5-server/cpp/server_guest_lib/WorldState.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 27b5e8ef14..d0cdcf1eb4 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -11,7 +11,6 @@ #include "PapyrusObjectReference.h" #include "Primitive.h" #include "ScopedTask.h" -#include "ScriptStorage.h" #include "ScriptVariablesHolder.h" #include "TimeUtils.h" #include "WorldState.h" @@ -19,6 +18,7 @@ #include "libespm/Utils.h" #include "papyrus-vm/Reader.h" #include "papyrus-vm/VirtualMachine.h" +#include "script_storages/IScriptStorage.h" #include #include @@ -281,8 +281,8 @@ void MpObjectReference::VisitProperties(const PropertiesVisitor& visitor, } if (ChangeForm().lastAnimation.has_value()) { - std::string lastAnimationAsJson = "\"" + *ChangeForm().lastAnimation + - "\""; + std::string lastAnimationAsJson = + "\"" + *ChangeForm().lastAnimation + "\""; visitor("lastAnimation", lastAnimationAsJson.data()); } diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index fc53789cb6..ba45251507 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -2,7 +2,6 @@ #include "FormCallbacks.h" #include "HeuristicPolicy.h" #include "ISaveStorage.h" -#include "IScriptStorage.h" #include "LocationalDataUtils.h" #include "MpActor.h" #include "MpChangeForms.h" @@ -25,6 +24,7 @@ #include "Timer.h" #include "libespm/GroupUtils.h" #include "papyrus-vm/Reader.h" +#include "script_storages/IScriptStorage.h" #include #include #include From 90b1dc5763f5e6c6b9553399392378cedf55a273 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 22:40:03 +0600 Subject: [PATCH 49/88] upload logs for ubuntu --- .github/workflows/pr-ubuntu-docker.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index 12f5dcb404..d8344c0ea0 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -96,6 +96,14 @@ jobs: run: | cd /src \ && ./build.sh --build + continue-on-error: true + + - name: Upload vcpkg failure logs + if: failure() + uses: actions/upload-artifact@v2 + with: + name: install-x64-linux-dbg-out.log + path: /src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log - name: Prepare dist.tar.gz uses: addnab/docker-run-action@v3 From 40a8adb34723b29aa8d62308e22707fa10bb2c55 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 22:53:56 +0600 Subject: [PATCH 50/88] remove --clean-after-build --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 043048c73c..fb94704ee7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,12 @@ endif() set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_CURRENT_LIST_DIR}/overlay_triplets") set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/overlay_ports") -set(VCPKG_INSTALL_OPTIONS --no-print-usage --clean-after-build) + +if(WIN32) + set(VCPKG_INSTALL_OPTIONS --no-print-usage --clean-after-build) +else() + set(VCPKG_INSTALL_OPTIONS --no-print-usage) # no clean-after-build to retrieve vcpkg failure logs in Ubuntu CI +endif() if("$ENV{CI}" STREQUAL "true" AND WIN32) # The same submodule but moved to a larger disk in Windows CI. See action files: From 805a60ba33a57049803869d1fad47cd411830e80 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 23:16:51 +0600 Subject: [PATCH 51/88] yaml --- .github/workflows/pr-ubuntu-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index d8344c0ea0..7ddc1bcb91 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -72,6 +72,7 @@ jobs: options: | -v ${{github.workspace}}:/src -v ${{github.workspace}}/.cmake-js:/home/skymp/.cmake-js + -v /src/vcpkg/buildtrees:/src/vcpkg/buildtrees -u skymp run: | cd /src \ @@ -96,7 +97,6 @@ jobs: run: | cd /src \ && ./build.sh --build - continue-on-error: true - name: Upload vcpkg failure logs if: failure() From e2e68d3995131e7dbcea90ab181e377672fdc7a8 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 14 Oct 2023 23:29:32 +0600 Subject: [PATCH 52/88] . --- .github/workflows/pr-ubuntu-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index 7ddc1bcb91..357e0e6181 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -72,7 +72,7 @@ jobs: options: | -v ${{github.workspace}}:/src -v ${{github.workspace}}/.cmake-js:/home/skymp/.cmake-js - -v /src/vcpkg/buildtrees:/src/vcpkg/buildtrees + -v /src/vcpkg/buildtrees/rsm-bsa:/src/vcpkg/buildtrees/rsm-bsa -u skymp run: | cd /src \ From ee98f61c0a5f628abd9ded5648ffc5e6a7775582 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 01:04:38 +0600 Subject: [PATCH 53/88] . --- .github/workflows/pr-ubuntu-docker.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index 357e0e6181..3018092133 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -66,20 +66,33 @@ jobs: chown -R skymp:skymp /src /home/skymp/.cmake-js - name: CMake Configure + id: cmake_configure uses: addnab/docker-run-action@v3 with: image: ${{ env.SKYMP_VCPKG_DEPS_IMAGE }} options: | -v ${{github.workspace}}:/src -v ${{github.workspace}}/.cmake-js:/home/skymp/.cmake-js - -v /src/vcpkg/buildtrees/rsm-bsa:/src/vcpkg/buildtrees/rsm-bsa -u skymp + --name configure_container run: | cd /src \ && ./build.sh --configure \ -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ -DUNIT_DATA_DIR="/src/skyrim_data_files" + - name: Copy log file from container to workspace + if: steps.cmake_configure.outcome == 'failure' + run: | + docker cp configure_container:/src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log ${{github.workspace}}/install-x64-linux-dbg-out.log + + - name: Upload vcpkg failure logs + if: steps.cmake_configure.outcome == 'failure' + uses: actions/upload-artifact@v2 + with: + name: install-x64-linux-dbg-out.log + path: /src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log + - name: Upload compile_commands.json uses: actions/upload-artifact@v3 with: @@ -97,13 +110,6 @@ jobs: run: | cd /src \ && ./build.sh --build - - - name: Upload vcpkg failure logs - if: failure() - uses: actions/upload-artifact@v2 - with: - name: install-x64-linux-dbg-out.log - path: /src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log - name: Prepare dist.tar.gz uses: addnab/docker-run-action@v3 From dcb21f9a977328009b9ed2087d7ed8c5d9b0af34 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 01:11:05 +0600 Subject: [PATCH 54/88] . --- .github/workflows/pr-ubuntu-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index 3018092133..549c3ee727 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -82,12 +82,12 @@ jobs: -DUNIT_DATA_DIR="/src/skyrim_data_files" - name: Copy log file from container to workspace - if: steps.cmake_configure.outcome == 'failure' + if: failure() run: | docker cp configure_container:/src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log ${{github.workspace}}/install-x64-linux-dbg-out.log - name: Upload vcpkg failure logs - if: steps.cmake_configure.outcome == 'failure' + if: failure() uses: actions/upload-artifact@v2 with: name: install-x64-linux-dbg-out.log From b60805062dc4f269c53795a75370a46f54b7ee69 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 01:24:03 +0600 Subject: [PATCH 55/88] . --- .github/workflows/pr-ubuntu-docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index 549c3ee727..59e9e02b0a 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -84,6 +84,7 @@ jobs: - name: Copy log file from container to workspace if: failure() run: | + docker exec configure_container chmod +r /src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log docker cp configure_container:/src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log ${{github.workspace}}/install-x64-linux-dbg-out.log - name: Upload vcpkg failure logs From 18c55b02466780dc58cbb411ae96f7ef7073a876 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 01:26:06 +0600 Subject: [PATCH 56/88] . --- .github/workflows/pr-ubuntu-docker.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index 59e9e02b0a..54789d5063 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -84,8 +84,7 @@ jobs: - name: Copy log file from container to workspace if: failure() run: | - docker exec configure_container chmod +r /src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log - docker cp configure_container:/src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log ${{github.workspace}}/install-x64-linux-dbg-out.log + sudo docker cp configure_container:/src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log ${{github.workspace}}/install-x64-linux-dbg-out.log - name: Upload vcpkg failure logs if: failure() From 9b6066a9759c58dd6a92f05097139547285d6c4f Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 01:28:56 +0600 Subject: [PATCH 57/88] . --- .github/workflows/pr-ubuntu-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-ubuntu-docker.yml b/.github/workflows/pr-ubuntu-docker.yml index 54789d5063..7073767f5b 100644 --- a/.github/workflows/pr-ubuntu-docker.yml +++ b/.github/workflows/pr-ubuntu-docker.yml @@ -91,7 +91,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: install-x64-linux-dbg-out.log - path: /src/vcpkg/buildtrees/rsm-bsa/install-x64-linux-dbg-out.log + path: ${{github.workspace}}/install-x64-linux-dbg-out.log - name: Upload compile_commands.json uses: actions/upload-artifact@v3 From da000c360e156e40558accc0a42ac43435794c26 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 02:47:01 +0600 Subject: [PATCH 58/88] rsm-bsa_patch --- overlay_ports/rsm-bsa/portfile.cmake | 38 +++++++++++++++++++ .../rsm-bsa/variant-emplace-fix.patch | 22 +++++++++++ overlay_ports/rsm-bsa/vcpkg.json | 33 ++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 overlay_ports/rsm-bsa/portfile.cmake create mode 100644 overlay_ports/rsm-bsa/variant-emplace-fix.patch create mode 100644 overlay_ports/rsm-bsa/vcpkg.json diff --git a/overlay_ports/rsm-bsa/portfile.cmake b/overlay_ports/rsm-bsa/portfile.cmake new file mode 100644 index 0000000000..7cd9475522 --- /dev/null +++ b/overlay_ports/rsm-bsa/portfile.cmake @@ -0,0 +1,38 @@ +vcpkg_check_linkage(ONLY_STATIC_LIBRARY) +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO Ryan-rsm-McKenzie/bsa + REF 4.1.0 + SHA512 c488a4f7cffa59064baafd429cf118a8f8a7b5594a0bd49a0ed468572b37af2e7428a83ad83cc7b13b556744a444cb7b8a4591c7018e49cadb1c5d42ae780f51 + HEAD_REF master + PATCHES variant-emplace-fix.patch +) + +if (VCPKG_TARGET_IS_LINUX) + message(WARNING "Build ${PORT} requires at least gcc 10.") +endif() + +vcpkg_check_features( + OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + xmem BSA_SUPPORT_XMEM +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DBUILD_TESTING=OFF + ${FEATURE_OPTIONS} +) +vcpkg_cmake_install() +vcpkg_cmake_config_fixup( + PACKAGE_NAME bsa + CONFIG_PATH "lib/cmake/bsa" +) + +file(REMOVE_RECURSE + ${CURRENT_PACKAGES_DIR}/debug/include + ${CURRENT_PACKAGES_DIR}/debug/share +) + +file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/overlay_ports/rsm-bsa/variant-emplace-fix.patch b/overlay_ports/rsm-bsa/variant-emplace-fix.patch new file mode 100644 index 0000000000..89ab654610 --- /dev/null +++ b/overlay_ports/rsm-bsa/variant-emplace-fix.patch @@ -0,0 +1,22 @@ +From 5a9a5d0854b1fbf92ea0f0c710bacd195a2342b0 Mon Sep 17 00:00:00 2001 +From: Leonid Pospelov +Date: Sun, 15 Oct 2023 02:33:51 +0600 +Subject: [PATCH] Update common.hpp + +--- + include/bsa/detail/common.hpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/include/bsa/detail/common.hpp b/include/bsa/detail/common.hpp +index 8998d38..66e449f 100644 +--- a/include/bsa/detail/common.hpp ++++ b/include/bsa/detail/common.hpp +@@ -1074,7 +1074,7 @@ namespace bsa::components + _hash(a_hash) + { + if (a_in.has_file() && a_in.shallow_copy()) { +- _name.emplace(a_name, a_in.file()); ++ _name = name_proxied{ a_name, a_in.file() }; + } else { + if (a_in.deep_copy()) { + _name.emplace(a_name); \ No newline at end of file diff --git a/overlay_ports/rsm-bsa/vcpkg.json b/overlay_ports/rsm-bsa/vcpkg.json new file mode 100644 index 0000000000..0bf1038e16 --- /dev/null +++ b/overlay_ports/rsm-bsa/vcpkg.json @@ -0,0 +1,33 @@ +{ + "name": "rsm-bsa", + "version-semver": "4.1.0", + "description": "A C++ library for working with the Bethesda archive file format", + "homepage": "https://github.com/Ryan-rsm-McKenzie/bsa", + "documentation": "https://ryan-rsm-mckenzie.github.io/bsa/", + "license": "MIT", + "supports": "!x86 & !osx & !uwp", + "dependencies": [ + "directxtex", + "lz4", + "rsm-binary-io", + "rsm-mmio", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + }, + "zlib" + ], + "features": { + "xmem": { + "description": "Compression support for the xmem codec", + "supports": "windows", + "dependencies": [ + "reproc" + ] + } + } +} From abd3e461be6f3bc3999857fb931cb43263b38287 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 02:49:28 +0600 Subject: [PATCH 59/88] rsm-bsa --- overlay_ports/rsm-bsa/variant-emplace-fix.patch | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/overlay_ports/rsm-bsa/variant-emplace-fix.patch b/overlay_ports/rsm-bsa/variant-emplace-fix.patch index 89ab654610..5eb896c0ac 100644 --- a/overlay_ports/rsm-bsa/variant-emplace-fix.patch +++ b/overlay_ports/rsm-bsa/variant-emplace-fix.patch @@ -19,4 +19,5 @@ index 8998d38..66e449f 100644 + _name = name_proxied{ a_name, a_in.file() }; } else { if (a_in.deep_copy()) { - _name.emplace(a_name); \ No newline at end of file + _name.emplace(a_name); + \ No newline at end of file From ed9d25329c1756f94e3a11be6c77eb1fd15f47b3 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 02:59:28 +0600 Subject: [PATCH 60/88] rsm-bsa --- overlay_ports/rsm-bsa/variant-emplace-fix.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay_ports/rsm-bsa/variant-emplace-fix.patch b/overlay_ports/rsm-bsa/variant-emplace-fix.patch index 5eb896c0ac..50a7f46880 100644 --- a/overlay_ports/rsm-bsa/variant-emplace-fix.patch +++ b/overlay_ports/rsm-bsa/variant-emplace-fix.patch @@ -16,7 +16,7 @@ index 8998d38..66e449f 100644 { if (a_in.has_file() && a_in.shallow_copy()) { - _name.emplace(a_name, a_in.file()); -+ _name = name_proxied{ a_name, a_in.file() }; ++ _name = name_proxy{ a_name, a_in.file() }; } else { if (a_in.deep_copy()) { _name.emplace(a_name); From 4b37e6750c7c13e21be77d349b213a1dbf56b8d0 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 03:36:58 +0600 Subject: [PATCH 61/88] patch structural binding --- overlay_ports/rsm-bsa/portfile.cmake | 4 +- .../rsm-bsa/structural-binding.patch | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 overlay_ports/rsm-bsa/structural-binding.patch diff --git a/overlay_ports/rsm-bsa/portfile.cmake b/overlay_ports/rsm-bsa/portfile.cmake index 7cd9475522..f267c0e47b 100644 --- a/overlay_ports/rsm-bsa/portfile.cmake +++ b/overlay_ports/rsm-bsa/portfile.cmake @@ -5,7 +5,9 @@ vcpkg_from_github( REF 4.1.0 SHA512 c488a4f7cffa59064baafd429cf118a8f8a7b5594a0bd49a0ed468572b37af2e7428a83ad83cc7b13b556744a444cb7b8a4591c7018e49cadb1c5d42ae780f51 HEAD_REF master - PATCHES variant-emplace-fix.patch + PATCHES + variant-emplace-fix.patch + structural-binding.patch ) if (VCPKG_TARGET_IS_LINUX) diff --git a/overlay_ports/rsm-bsa/structural-binding.patch b/overlay_ports/rsm-bsa/structural-binding.patch new file mode 100644 index 0000000000..555356b0dd --- /dev/null +++ b/overlay_ports/rsm-bsa/structural-binding.patch @@ -0,0 +1,43 @@ +From 9efa9f4853b84c0b9f56c57dcb18df5e71c9ac9b Mon Sep 17 00:00:00 2001 +From: Leonid Pospelov +Date: Sun, 15 Oct 2023 03:32:09 +0600 +Subject: [PATCH] Update tes4.cpp + +--- + src/bsa/tes4.cpp | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/bsa/tes4.cpp b/src/bsa/tes4.cpp +index 0e98dce..70330d5 100644 +--- a/src/bsa/tes4.cpp ++++ b/src/bsa/tes4.cpp +@@ -486,7 +486,7 @@ namespace bsa::tes4 + } + const std::string_view pview{ a_path }; + +- const auto [stem, extension] = [&]() noexcept ++ const auto p = [&]() noexcept + -> std::pair { + const auto split = pview.find_last_of('.'); + if (split != std::string_view::npos) { +@@ -501,6 +501,8 @@ namespace bsa::tes4 + }; + } + }(); ++ std::string_view stem = p.first; ++ std::string_view extension = p.second; + + if (!stem.empty() && + stem.length() < 260 && +@@ -1074,7 +1076,9 @@ namespace bsa::tes4 + hashing::hash hash; + hash.read(a_in, a_header.endian()); + +- auto [size, offset] = a_in->read(); ++ auto [size_, offset_] = a_in->read(); ++ auto size = size_; ++ auto offset = offset_; + + const detail::restore_point _{ a_in }; + a_in->seek_absolute(offset & ~file::isecondary_archive); + \ No newline at end of file From a3ed3f5296463cd7b903eec17e19dc55eaa368c5 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 04:02:28 +0600 Subject: [PATCH 62/88] fixcompile --- unit/TemplateScriptTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/TemplateScriptTest.cpp b/unit/TemplateScriptTest.cpp index c26cfd9684..625ed5efa0 100644 --- a/unit/TemplateScriptTest.cpp +++ b/unit/TemplateScriptTest.cpp @@ -1,7 +1,7 @@ #include "MsgType.h" -#include "ScriptStorage.h" #include "ServerState.h" #include "TestUtils.hpp" +#include "script_storages/IScriptStorage.h" #include #include From 6847945f6c5d3017b7e1d876c770e6285ff74836 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 04:02:38 +0600 Subject: [PATCH 63/88] change respawn time --- skymp5-server/cpp/server_guest_lib/MpActor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index 6ae4c436f3..1b716979f4 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -815,8 +815,8 @@ LocationalData MpActor::GetSpawnPoint() const const float MpActor::GetRespawnTime() const { if (!IsCreatedAsPlayer()) { - static const auto kOneHour = 60.f * 60.f; - return kOneHour; + static const auto kNpcSpawnDelay = 6 * 60.f * 60.f; + return kNpcSpawnDelay; } return ChangeForm().spawnDelay; } From 252dcfd7de97de11d16ea5e71a8a54806e0670c7 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 13:00:50 +0600 Subject: [PATCH 64/88] fix papyrus test --- .../cpp/server_guest_lib/MpObjectReference.cpp | 3 ++- skymp5-server/cpp/server_guest_lib/WorldState.h | 2 ++ unit/PapyrusCompatibilityTest.cpp | 10 ++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index d0cdcf1eb4..b209306538 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1402,7 +1402,8 @@ void MpObjectReference::InitScripts() // A hardcoded hack to remove all scripts except SweetPie scripts from // exterior objects - if (GetFormId() < 0x05000000) { + if (GetParent() && GetParent()->disableVanillaScriptsInExterior && + GetFormId() < 0x05000000) { auto cellOrWorld = GetCellOrWorld().ToFormId(GetParent()->espmFiles); auto lookupRes = GetParent()->GetEspm().GetBrowser().LookupById(cellOrWorld); diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.h b/skymp5-server/cpp/server_guest_lib/WorldState.h index a7551345a6..5c3d25ad21 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.h +++ b/skymp5-server/cpp/server_guest_lib/WorldState.h @@ -229,6 +229,8 @@ class WorldState NpcSettingsEntry defaultSetting; bool enableConsoleCommandsForAll = false; + bool disableVanillaScriptsInExterior = true; + private: bool AttachEspmRecord(const espm::CombineBrowser& br, const espm::RecordHeader* record, diff --git a/unit/PapyrusCompatibilityTest.cpp b/unit/PapyrusCompatibilityTest.cpp index 910ddeb0f3..bf9300256a 100644 --- a/unit/PapyrusCompatibilityTest.cpp +++ b/unit/PapyrusCompatibilityTest.cpp @@ -8,6 +8,8 @@ PartOne& GetPartOne(); TEST_CASE("Should be able to harvest a Nirnroot", "[Papyrus][espm]") { auto& partOne = GetPartOne(); + partOne.worldState.disableVanillaScriptsInExterior = false; + auto& nirnrootRef = partOne.worldState.GetFormAt(0xa4de9); partOne.worldState.AddForm( @@ -33,6 +35,8 @@ TEST_CASE("Should be able to harvest a Nirnroot", "[Papyrus][espm]") TEST_CASE("Server crash in CallMethod", "[Papyrus][espm]") { auto& partOne = GetPartOne(); + partOne.worldState.disableVanillaScriptsInExterior = false; + auto& ref = partOne.worldState.GetFormAt(0xd8995); partOne.worldState.AddForm( std::make_unique( @@ -50,6 +54,8 @@ TEST_CASE("Server crash in CallMethod", "[Papyrus][espm]") TEST_CASE("Server crash in PropGet", "[Papyrus][espm]") { auto& partOne = GetPartOne(); + partOne.worldState.disableVanillaScriptsInExterior = false; + auto& ref = partOne.worldState.GetFormAt(0xabb6f); partOne.worldState.AddForm( std::make_unique( @@ -68,6 +74,8 @@ TEST_CASE("Server crash in PropGet", "[Papyrus][espm]") TEST_CASE("Activate auto load door in BrokenOarGrotto01", "[PartOne][espm]") { auto& partOne = GetPartOne(); + partOne.worldState.disableVanillaScriptsInExterior = false; + auto& ref = partOne.worldState.GetFormAt(87048); partOne.worldState.AddForm( @@ -89,6 +97,8 @@ TEST_CASE("Activate auto load door in BrokenOarGrotto01", "[PartOne][espm]") TEST_CASE("OnTriggerEnter crash in MovarthsLairExterior01", "[PartOne][espm]") { auto& partOne = GetPartOne(); + partOne.worldState.disableVanillaScriptsInExterior = false; + auto& ref = partOne.worldState.GetFormAt(464472); partOne.worldState.AddForm( From 21cfd5fea962498ab0ad7e3dd98a0883e82e0051 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 13:12:19 +0600 Subject: [PATCH 65/88] linelint --- .linelint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.linelint.yml b/.linelint.yml index 3ca16a0aa1..cbbc338913 100644 --- a/.linelint.yml +++ b/.linelint.yml @@ -2,6 +2,7 @@ ignore: - .git/ - '**/third_party' - 'skymp5-scripts/' + - 'overlay_ports/rsm-bsa/*.patch' rules: end-of-file: From 5e284a3e53ba1ed4afe6600eeda62f01bfdd6e44 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 14:05:35 +0600 Subject: [PATCH 66/88] clang format --- libespm/include/libespm/ACHR.h | 6 ++++-- libespm/src/NPC_.cpp | 3 ++- skymp5-server/cpp/server_guest_lib/MpForm.h | 2 +- skymp5-server/cpp/server_guest_lib/PapyrusActor.h | 2 +- skymp5-server/cpp/server_guest_lib/PapyrusCell.h | 3 +-- .../cpp/server_guest_lib/PapyrusObjectReference.h | 12 +++++++----- skymp5-server/cpp/server_guest_lib/PapyrusUtility.h | 6 ++---- .../script_storages/ScriptStorageUtils.h | 2 +- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/libespm/include/libespm/ACHR.h b/libespm/include/libespm/ACHR.h index 7469dd2dec..e0f03c687e 100644 --- a/libespm/include/libespm/ACHR.h +++ b/libespm/include/libespm/ACHR.h @@ -1,6 +1,6 @@ #pragma once -#include "RecordHeader.h" #include "REFR.h" +#include "RecordHeader.h" #pragma pack(push, 1) @@ -13,7 +13,9 @@ class ACHR final : public RecordHeader bool StartsDead() const noexcept; - struct Data : public REFR::Data {}; + struct Data : public REFR::Data + { + }; Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept; }; diff --git a/libespm/src/NPC_.cpp b/libespm/src/NPC_.cpp index 997d9c36d5..7cf9f77cf3 100644 --- a/libespm/src/NPC_.cpp +++ b/libespm/src/NPC_.cpp @@ -30,7 +30,8 @@ NPC_::Data NPC_::GetData( result.magickaOffset = *reinterpret_cast(data + 4); result.staminaOffset = *reinterpret_cast(data + 6); result.healthOffset = *reinterpret_cast(data + 20); - result.templateDataFlags = *reinterpret_cast(data + 18); + result.templateDataFlags = + *reinterpret_cast(data + 18); } else if (!std::memcmp(type, "RNAM", 4)) { result.race = *reinterpret_cast(data); diff --git a/skymp5-server/cpp/server_guest_lib/MpForm.h b/skymp5-server/cpp/server_guest_lib/MpForm.h index 0feb7f8afa..8da2fe534c 100644 --- a/skymp5-server/cpp/server_guest_lib/MpForm.h +++ b/skymp5-server/cpp/server_guest_lib/MpForm.h @@ -79,5 +79,5 @@ class MpForm const std::vector>& ListActivePexInstances() const; - void AddScript(const std::shared_ptr &script) noexcept; + void AddScript(const std::shared_ptr& script) noexcept; }; diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusActor.h b/skymp5-server/cpp/server_guest_lib/PapyrusActor.h index 7718c6f19f..8e4c4f7fe6 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusActor.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusActor.h @@ -18,7 +18,7 @@ class PapyrusActor final : public IPapyrusClass VarValue RestoreActorValue(VarValue self, const std::vector& arguments); - + VarValue SetActorValue(VarValue self, const std::vector& arguments); diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusCell.h b/skymp5-server/cpp/server_guest_lib/PapyrusCell.h index d824a1052f..78077faf6d 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusCell.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusCell.h @@ -8,8 +8,7 @@ class PapyrusCell final : public IPapyrusClass public: const char* GetName() override { return "cell"; } - VarValue IsAttached(VarValue self, - const std::vector& arguments); + VarValue IsAttached(VarValue self, const std::vector& arguments); void Register(VirtualMachine& vm, std::shared_ptr policy) override; diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h index 7419ad9869..8a5def1d00 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h @@ -39,7 +39,7 @@ class PapyrusObjectReference final VarValue PlayAnimation(VarValue self, const std::vector& arguments); VarValue PlayAnimationAndWait(VarValue self, - const std::vector& arguments); + const std::vector& arguments); VarValue PlayGamebryoAnimation(VarValue self, const std::vector& arguments); VarValue MoveTo(VarValue self, const std::vector& arguments); @@ -49,10 +49,12 @@ class PapyrusObjectReference final VarValue Is3DLoaded(VarValue self, const std::vector& arguments); VarValue GetLinkedRef(VarValue self, const std::vector& arguments); - - VarValue GetNthLinkedRef(VarValue self, const std::vector& arguments); - - VarValue GetParentCell(VarValue self, const std::vector& arguments); + + VarValue GetNthLinkedRef(VarValue self, + const std::vector& arguments); + + VarValue GetParentCell(VarValue self, + const std::vector& arguments); VarValue GetOpenState(VarValue self, const std::vector& arguments); diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h b/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h index 4d1e931840..c95d7d8ed0 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h +++ b/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h @@ -7,10 +7,8 @@ class PapyrusUtility final : public IPapyrusClass const char* GetName() override { return "utility"; } VarValue Wait(VarValue self, const std::vector& arguments); - VarValue RandomInt(VarValue slef, - const std::vector& arguments); - VarValue RandomFloat(VarValue slef, - const std::vector& arguments); + VarValue RandomInt(VarValue slef, const std::vector& arguments); + VarValue RandomFloat(VarValue slef, const std::vector& arguments); void Register(VirtualMachine& vm, std::shared_ptr policy) override; diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h index c9a2a97347..56f9245f67 100644 --- a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.h @@ -10,5 +10,5 @@ std::string GetFileName(const std::string& path); std::string RemoveExtension(std::string s); -std::set GetScriptsInDirectory(const std::string &pexDir); +std::set GetScriptsInDirectory(const std::string& pexDir); } From 59190f0c29e98d97f12fb1db30e63d6f5c8a6654 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Oct 2023 14:08:15 +0600 Subject: [PATCH 67/88] a bit better cmakelists --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb94704ee7..accb68ca7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,10 @@ endif() set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_CURRENT_LIST_DIR}/overlay_triplets") set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/overlay_ports") -if(WIN32) - set(VCPKG_INSTALL_OPTIONS --no-print-usage --clean-after-build) +if("$ENV{CI}" STREQUAL "true") + set(VCPKG_INSTALL_OPTIONS --no-print-usage) else() - set(VCPKG_INSTALL_OPTIONS --no-print-usage) # no clean-after-build to retrieve vcpkg failure logs in Ubuntu CI + set(VCPKG_INSTALL_OPTIONS --no-print-usage --clean-after-build) endif() if("$ENV{CI}" STREQUAL "true" AND WIN32) From ca18312ad07e92a4fc449e567c40b8ef76299085 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Tue, 17 Oct 2023 15:26:53 +0600 Subject: [PATCH 68/88] add bsa path early validation --- .../script_storages/BsaArchiveScriptStorage.cpp | 6 ++++++ .../script_storages/ScriptStorageFactory.cpp | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp index a21bf62336..a0691a0a3b 100644 --- a/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_storages/BsaArchiveScriptStorage.cpp @@ -1,11 +1,17 @@ #include "BsaArchiveScriptStorage.h" #include +#include #include +#include BsaArchiveScriptStorage::BsaArchiveScriptStorage(const char* bsaPath_) { this->bsaPath = bsaPath_; + if (!std::filesystem::exists(bsaPath_)) { + throw std::runtime_error( + fmt::format("BSA Archive '{}' doesn't exist", bsaPath_)); + } } std::vector BsaArchiveScriptStorage::GetScriptPex( diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp index 19d92d7f9d..80c36220ce 100644 --- a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp @@ -2,6 +2,20 @@ #include +namespace { +std::string ResolveArchivePath(const std::string& pathFromConfigStr, + const std::string& dataDir) +{ + std::filesystem::path pathFromConfig = pathFromConfigStr; + + if (pathFromConfig.is_absolute()) { + return pathFromConfig.string(); + } else { + return (dataDir / pathFromConfig).string(); + } +} +} + std::shared_ptr ScriptStorageFactory::Create( nlohmann::json serverSettings) { @@ -11,7 +25,8 @@ std::shared_ptr ScriptStorageFactory::Create( if (serverSettings.contains("archives") && serverSettings.at("archives").is_array()) { for (auto archive : serverSettings.at("archives")) { - std::string archivePath = archive.get(); + std::string archivePath = + ResolveArchivePath(archive.get(), dataDir); bsaScriptStorages.push_back( std::make_shared(archivePath.data())); } From d8eb55a1513374a19a1aa770d5e66b6af22558e7 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Wed, 18 Oct 2023 03:18:56 +0600 Subject: [PATCH 69/88] useless refactor --- .../script_storages/ScriptStorageFactory.cpp | 55 +++++++++++++------ .../script_storages/ScriptStorageFactory.h | 13 +++++ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp index 80c36220ce..242b44b8bb 100644 --- a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.cpp @@ -2,9 +2,23 @@ #include -namespace { -std::string ResolveArchivePath(const std::string& pathFromConfigStr, - const std::string& dataDir) +std::shared_ptr ScriptStorageFactory::Create( + nlohmann::json serverSettings) +{ + + std::vector> scriptStorages; + + // TODO: ensure this order is correct + AddDirectory(scriptStorages, serverSettings); + AddBsa(scriptStorages, serverSettings); + AddAssets(scriptStorages, serverSettings); + + return std::dynamic_pointer_cast( + std::make_shared(scriptStorages)); +} + +std::string ScriptStorageFactory::ResolveArchivePath( + const std::string& pathFromConfigStr, const std::string& dataDir) { std::filesystem::path pathFromConfig = pathFromConfigStr; @@ -14,31 +28,36 @@ std::string ResolveArchivePath(const std::string& pathFromConfigStr, return (dataDir / pathFromConfig).string(); } } + +void ScriptStorageFactory::AddDirectory( + std::vector>& storages, + nlohmann::json& serverSettings) +{ + std::string dataDir = serverSettings["dataDir"]; + + storages.push_back(std::make_shared( + (std::filesystem::path(dataDir) / "scripts").string())); } -std::shared_ptr ScriptStorageFactory::Create( - nlohmann::json serverSettings) +void ScriptStorageFactory::AddAssets( + std::vector>& storages, nlohmann::json&) +{ + storages.push_back(std::make_shared()); +} + +void ScriptStorageFactory::AddBsa( + std::vector>& storages, + nlohmann::json& serverSettings) { std::string dataDir = serverSettings["dataDir"]; - std::vector> bsaScriptStorages; if (serverSettings.contains("archives") && serverSettings.at("archives").is_array()) { - for (auto archive : serverSettings.at("archives")) { + for (auto& archive : serverSettings.at("archives")) { std::string archivePath = ResolveArchivePath(archive.get(), dataDir); - bsaScriptStorages.push_back( + storages.push_back( std::make_shared(archivePath.data())); } } - - std::vector> scriptStorages; - scriptStorages.push_back(std::make_shared( - (std::filesystem::path(dataDir) / "scripts").string())); - for (auto& scriptStorage : bsaScriptStorages) { - scriptStorages.push_back(scriptStorage); - } - scriptStorages.push_back(std::make_shared()); - return std::dynamic_pointer_cast( - std::make_shared(scriptStorages)); } diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h index d6d9995157..d413892f1c 100644 --- a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h @@ -5,9 +5,22 @@ #include "DirectoryScriptStorage.h" #include +#include class ScriptStorageFactory { public: static std::shared_ptr Create(nlohmann::json serverSettings); + +private: + static std::string ResolveArchivePath(const std::string& pathFromConfigStr, + const std::string& dataDir); + + static void AddDirectory( + std::vector>& storages, + nlohmann::json &serverSettings); + static void AddAssets(std::vector>& storages, + nlohmann::json &serverSettings); + static void AddBsa(std::vector>& storages, + nlohmann::json &serverSettings); }; From c4c1b60dc537c691f69a5d8c73ebc62d3363a0a9 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Wed, 18 Oct 2023 15:14:10 +0600 Subject: [PATCH 70/88] format --- .../server_guest_lib/script_storages/ScriptStorageFactory.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h index d413892f1c..a8b1505545 100644 --- a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageFactory.h @@ -18,9 +18,9 @@ class ScriptStorageFactory static void AddDirectory( std::vector>& storages, - nlohmann::json &serverSettings); + nlohmann::json& serverSettings); static void AddAssets(std::vector>& storages, - nlohmann::json &serverSettings); + nlohmann::json& serverSettings); static void AddBsa(std::vector>& storages, - nlohmann::json &serverSettings); + nlohmann::json& serverSettings); }; From 4d98632334ecadcc9d6af1dfb89f67371863df9b Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 19 Oct 2023 18:31:37 +0600 Subject: [PATCH 71/88] add stupid tracing of lookupformbyid --- .../ScriptVariablesHolder.cpp | 38 +++--- .../server_guest_lib/ScriptVariablesHolder.h | 6 +- .../cpp/server_guest_lib/WorldState.cpp | 115 +++++++++++++++++- .../cpp/server_guest_lib/WorldState.h | 9 +- 4 files changed, 140 insertions(+), 28 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp index 728f87e643..15798c8540 100644 --- a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp +++ b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp @@ -135,43 +135,49 @@ VarValue ScriptVariablesHolder::CastPrimitivePropertyValue( } auto propValueFormIdGlobal = toGlobalId(propValue.formId); spdlog::trace( - "CastPrimitivePropertyValue - Prop to global id {:x} -> {:x}", - propValue.formId, propValueFormIdGlobal); + "CastPrimitivePropertyValue {} - Prop to global id {:x} -> {:x}", + myScriptName, propValue.formId, propValueFormIdGlobal); auto& gameObject = st.objectsHolder[propValueFormIdGlobal]; if (!gameObject) { auto lookupResult = br.LookupById(propValueFormIdGlobal); if (!lookupResult.rec) { spdlog::error( - "CastPrimitivePropertyValue - Record with id {:x} not found", - propValueFormIdGlobal); + "CastPrimitivePropertyValue {} - Record with id {:x} not found", + myScriptName, propValueFormIdGlobal); } else { auto type = lookupResult.rec->GetType(); if (type == espm::REFR::kType || type == espm::ACHR::kType) { if (worldState) { - auto& form = worldState->LookupFormById(propValueFormIdGlobal); + std::stringstream traceStream; + auto& form = worldState->LookupFormById(propValueFormIdGlobal, + &traceStream); if (form != nullptr) { gameObject = std::make_shared(form.get()); - spdlog::trace("CastPrimitivePropertyValue - Created {} " + spdlog::trace("CastPrimitivePropertyValue {} - Created {} " "(MpFormGameObject) property with id {:x}", - type.ToString(), propValueFormIdGlobal); + myScriptName, type.ToString(), + propValueFormIdGlobal); } else { spdlog::warn( - "CastPrimitivePropertyValue - Unable to create {} " + "CastPrimitivePropertyValue {} - Unable to create {} " "(MpFormGameObject) property with id {:x}, form " - "not found in the world", - type.ToString(), propValueFormIdGlobal); + "not found in the world. LookupFormById trace:\n{}", + myScriptName, type.ToString(), propValueFormIdGlobal, + traceStream.str()); } } else { - spdlog::error("CastPrimitivePropertyValue - Unable to create {} " - "(MpFormGameObject) property with id {:x}, null " - "WorldState", - type.ToString(), propValueFormIdGlobal); + spdlog::error( + "CastPrimitivePropertyValue {} - Unable to create {} " + "(MpFormGameObject) property with id {:x}, null " + "WorldState", + myScriptName, type.ToString(), propValueFormIdGlobal); } } else { gameObject = std::make_shared(lookupResult); - spdlog::trace("CastPrimitivePropertyValue - Created {} " + spdlog::trace("CastPrimitivePropertyValue {} - Created {} " "(EspmGameObject) property with id {:x}", - type.ToString(), propValueFormIdGlobal); + myScriptName, type.ToString(), + propValueFormIdGlobal); } } } diff --git a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h index 5c2e8cea4d..2695a275ff 100644 --- a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h +++ b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h @@ -41,13 +41,13 @@ class ScriptVariablesHolder : public IVariablesHolder std::unordered_map> objectsHolder; }; - static VarValue CastPrimitivePropertyValue( + VarValue CastPrimitivePropertyValue( const espm::CombineBrowser& br, ScriptsCache& st, const espm::Property::Value& propValue, espm::Property::Type type, const std::function& toGlobalId, WorldState* worldState); - static void CastProperty(const espm::CombineBrowser& br, + void CastProperty(const espm::CombineBrowser& br, const espm::Property& prop, VarValue* out, ScriptsCache* scriptsCache, const std::function& toGlobalId, @@ -57,7 +57,7 @@ class ScriptVariablesHolder : public IVariablesHolder espm::LookupResult baseRecordWithScripts; espm::LookupResult refrRecordWithScripts; - const std::string myScriptName; + std::string myScriptName; const espm::CombineBrowser* const browser; std::unique_ptr> vars; VarValue state; diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index ba45251507..2aee83ad37 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -207,26 +207,55 @@ void WorldState::RequestSave(MpObjectReference& ref) } } -const std::shared_ptr& WorldState::LookupFormById(uint32_t formId) +const std::shared_ptr& WorldState::LookupFormById( + uint32_t formId, std::stringstream* optionalOutTrace) { static const std::shared_ptr kNullForm; + if (optionalOutTrace) { + *optionalOutTrace << "searching for " << std::hex << formId << std::endl; + } + auto it = forms.find(formId); if (it == forms.end()) { if (formId < 0xff000000) { - if (LoadForm(formId)) { + if (LoadForm(formId, optionalOutTrace)) { it = forms.find(formId); - return it == forms.end() ? kNullForm : it->second; + if (it != forms.end()) { + if (optionalOutTrace) { + *optionalOutTrace << "found after successful LoadForm" << std::hex + << formId << std::endl; + } + return it->second; + } + if (optionalOutTrace) { + *optionalOutTrace << "not found after successful LoadForm" + << std::hex << formId << std::endl; + } + return kNullForm; + } else { + if (optionalOutTrace) { + *optionalOutTrace << "LoadForm returned false " << std::hex << formId + << std::endl; + } } } + if (optionalOutTrace) { + *optionalOutTrace << "not found " << std::hex << formId << std::endl; + } return kNullForm; } + + if (optionalOutTrace) { + *optionalOutTrace << "found " << std::hex << formId << std::endl; + } return it->second; } bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, const espm::RecordHeader* record, - const espm::IdMapping& mapping) + const espm::IdMapping& mapping, + std::stringstream* optionalOutTrace) { auto& cache = GetEspmCache(); // this place is a hotpath. @@ -239,6 +268,10 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, espm::LookupResult base = br.LookupById(baseId); if (!base.rec) { logger->info("baseId {} {}", baseId, static_cast(base.rec)); + if (optionalOutTrace) { + *optionalOutTrace << fmt::format( + "AttachEspmRecord - base record not found {:x} \n", baseId); + } return false; } @@ -255,6 +288,10 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, if (!isNpc && !isFurniture && !isActivator && !espm::utils::IsItem(t) && !isDoor && !isContainer && !isFlor && !isTree) { + if (optionalOutTrace) { + *optionalOutTrace << fmt::format( + "AttachEspmRecord - the server skips base type {} \n", t.ToString()); + } return false; } @@ -263,6 +300,10 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, auto* achr = reinterpret_cast(record); startsDead = achr->StartsDead(); if (startsDead) { + if (optionalOutTrace) { + *optionalOutTrace << fmt::format( + "AttachEspmRecord - the server skips dead actors\n"); + } return false; // TODO: Load dead references } } @@ -275,14 +316,26 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, }; if (refr->GetFlags() & InitiallyDisabled) { + if (optionalOutTrace) { + *optionalOutTrace << fmt::format( + "AttachEspmRecord - the server skips initially disabled references\n"); + } return false; } if (refr->GetFlags() & DeletedRecord) { + if (optionalOutTrace) { + *optionalOutTrace << fmt::format( + "AttachEspmRecord - the server skips deleted references\n"); + } return false; } if (!npcEnabled && isNpc) { + if (optionalOutTrace) { + *optionalOutTrace << fmt::format( + "AttachEspmRecord - the server skips npcs: npcEnabled = false\n"); + } return false; } @@ -290,12 +343,22 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, if (NpcSourceFilesOverriden() && !IsNpcAllowed(baseId)) { spdlog::trace("Skip NPC loading, it is not allowed. baseId {:#x}", baseId); + if (optionalOutTrace) { + *optionalOutTrace + << fmt::format("Skip NPC loading, it is not allowed. baseId {:#x}", + baseId) + << std::endl; + } return false; } auto npcData = reinterpret_cast(base.rec)->GetData(cache); if (npcData.isEssential || npcData.isProtected || npcData.isUnique) { + if (optionalOutTrace) { + *optionalOutTrace << fmt::format("Skip NPC due to its flags") + << std::endl; + } return false; } @@ -319,6 +382,10 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, if (it != factionFormIds.end()) { logger->info("Skipping actor {:#x} because it's in faction {:#x}", record->GetId(), *it); + if (optionalOutTrace) { + *optionalOutTrace << fmt::format("Skip NPC due to faction") + << std::endl; + } return false; } } @@ -331,6 +398,10 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, espm::utils::GetMappedId(GetWorldOrCell(br, record), mapping); if (!worldOrCell) { logger->error("Anomaly: refr without world/cell"); + if (optionalOutTrace) { + *optionalOutTrace << fmt::format("Anomaly: refr without world/cell") + << std::endl; + } return false; } @@ -353,6 +424,14 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, "baseId " "{:#x}, espmFiles size: {}", baseId, espmFiles.size()); + if (optionalOutTrace) { + *optionalOutTrace + << fmt::format("NPC's idx is greater than espmFiles.size(). NPC's" + "baseId " + "{:#x}, espmFiles size: {}", + baseId, espmFiles.size()) + << std::endl; + } return false; } auto it = npcSettings.find(espmFiles[npcFileIdx]); @@ -378,6 +457,15 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, "rules applied in server settings: spanwInInterior={}, " "spawnInExterior={}, NPC location: exterior={}, interior={}", spawnInInterior, spawnInExterior, isExterior, isInterior); + if (optionalOutTrace) { + *optionalOutTrace + << fmt::format( + "Unable to spawn npc because of " + "rules applied in server settings: spanwInInterior={}, " + "spawnInExterior={}, NPC location: exterior={}, interior={}", + spawnInInterior, spawnInExterior, isExterior, isInterior) + << std::endl; + } return false; } } @@ -401,6 +489,11 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, } else { if (!locationalData) { logger->error("Anomaly: refr without locationalData"); + if (optionalOutTrace) { + *optionalOutTrace << fmt::format( + "Anomaly: refr without locationalData") + << std::endl; + } return false; } @@ -430,19 +523,29 @@ bool WorldState::AttachEspmRecord(const espm::CombineBrowser& br, // Do not TriggerFormInitEvent here, doing it later after changeForm apply } + if (optionalOutTrace) { + *optionalOutTrace << fmt::format("AttachEspmRecord returned true") + << std::endl; + } return true; } -bool WorldState::LoadForm(uint32_t formId) +bool WorldState::LoadForm(uint32_t formId, std::stringstream* optionalOutTrace) { bool atLeastOneLoaded = false; auto& br = GetEspm().GetBrowser(); auto lookupResults = br.LookupByIdAll(formId); for (auto& lookupRes : lookupResults) { auto mapping = br.GetCombMapping(lookupRes.fileIdx); - if (AttachEspmRecord(br, lookupRes.rec, *mapping)) { + bool attached = + AttachEspmRecord(br, lookupRes.rec, *mapping, optionalOutTrace); + if (attached) { atLeastOneLoaded = true; } + if (optionalOutTrace) { + *optionalOutTrace << "AttachEspmRecord " << (attached ? "true" : "false") + << std::endl; + } } if (atLeastOneLoaded) { diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.h b/skymp5-server/cpp/server_guest_lib/WorldState.h index 5c3d25ad21..b4769d8fc1 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.h +++ b/skymp5-server/cpp/server_guest_lib/WorldState.h @@ -109,7 +109,8 @@ class WorldState wrapper); bool RemoveEffectTimer(uint32_t timerId); - const std::shared_ptr& LookupFormById(uint32_t formId); + const std::shared_ptr& LookupFormById( + uint32_t formId, std::stringstream* optionalOutTrace = nullptr); MpForm* LookupFormByIdx(int idx); @@ -234,9 +235,11 @@ class WorldState private: bool AttachEspmRecord(const espm::CombineBrowser& br, const espm::RecordHeader* record, - const espm::IdMapping& mapping); + const espm::IdMapping& mapping, + std::stringstream* optionalOutTrace = nullptr); - bool LoadForm(uint32_t formId); + bool LoadForm(uint32_t formId, + std::stringstream* optionalOutTrace = nullptr); void TickReloot(const std::chrono::system_clock::time_point& now); void TickSaveStorage(const std::chrono::system_clock::time_point& now); void TickTimers(const std::chrono::system_clock::time_point& now); From 5d6f6d3e5316d0bfc7ab26a9ce0fa4182332ea5c Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 19 Oct 2023 18:33:35 +0600 Subject: [PATCH 72/88] add comment --- skymp5-client/src/modelSource/remoteServer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/skymp5-client/src/modelSource/remoteServer.ts b/skymp5-client/src/modelSource/remoteServer.ts index 653498d2ec..996453ee2e 100644 --- a/skymp5-client/src/modelSource/remoteServer.ts +++ b/skymp5-client/src/modelSource/remoteServer.ts @@ -316,6 +316,7 @@ export class RemoteServer implements MsgHandler, ModelSource, SendTarget { if (this.worldModel.forms.length <= i) this.worldModel.forms.length = i + 1; let movement: Movement = null as unknown as Movement; + // TODO: better check if it is an npc (not an object reference) if ((msg.refrId as number) >= 0xff000000) { movement = { pos: msg.transform.pos, From da9406d41377c059143c52065625b43da3453c94 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 19 Oct 2023 18:36:04 +0600 Subject: [PATCH 73/88] add more aggro races --- skymp5-client/src/view/formView.ts | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/skymp5-client/src/view/formView.ts b/skymp5-client/src/view/formView.ts index 947a2e3b34..8d40a83ecf 100644 --- a/skymp5-client/src/view/formView.ts +++ b/skymp5-client/src/view/formView.ts @@ -144,9 +144,19 @@ export class FormView implements View { const chaurusRace = 0x131eb; const frostbiteSpiderRaceGiant = 0x4e507; const frostbiteSpiderRaceLarge = 0x53477; + const dwarvenCenturionRace = 0x131f1; + const dwarvenSphereRace = 0x131f2; + const dwarvenSpiderRace = 0x131f3; // potential masterambushscript - if (race === draugrRace || race === falmerRace || race === chaurusRace || race === frostbiteSpiderRaceGiant || race === frostbiteSpiderRaceLarge) { + if (race === draugrRace + || race === falmerRace + || race === chaurusRace + || race === frostbiteSpiderRaceGiant + || race === frostbiteSpiderRaceLarge + || race === dwarvenCenturionRace + || race === dwarvenSphereRace + || race === dwarvenSpiderRace) { Actor.from(refr)?.setActorValue("Aggression", 2); } } @@ -156,11 +166,19 @@ export class FormView implements View { this.eqState = this.getDefaultEquipState(); this.ready = false; + + let spawnPos; + if (model.movement) { + spawnPos = model.movement.pos; + // printConsole("Spawn NPC at movement.pos"); + } + else { + spawnPos = ObjectReferenceEx.getPos(Game.getPlayer() as Actor); + printConsole("Spawn NPC at player pos"); + } new SpawnProcess( this.appearanceState.appearance, - model.movement - ? model.movement.pos - : ObjectReferenceEx.getPos(Game.getPlayer() as Actor), + spawnPos, refr.getFormID(), () => { this.ready = true; From 7c2f77c870a959c67e7ebf8b52d361bd0653e50e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 19 Oct 2023 18:40:25 +0600 Subject: [PATCH 74/88] tiny refact: extract GetEditorLocationalData func --- .../cpp/server_guest_lib/MpActor.cpp | 35 +++++++++++++------ skymp5-server/cpp/server_guest_lib/MpActor.h | 1 + 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index 1b716979f4..b25166f48a 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -797,21 +797,34 @@ LocationalData MpActor::GetSpawnPoint() const auto formId = GetFormId(); if (!IsCreatedAsPlayer()) { - if (auto worldState = GetParent(); worldState && worldState->HasEspm()) { - auto data = espm::GetData(formId, worldState); - auto lookupRes = worldState->GetEspm().GetBrowser().LookupById(formId); - const NiPoint3& pos = LocationalDataUtils::GetPos(data.loc); - NiPoint3 rot = LocationalDataUtils::GetRot(data.loc); - uint32_t worldOrCell = LocationalDataUtils::GetWorldOrCell( - worldState->GetEspm().GetBrowser(), lookupRes); - return LocationalData{ - pos, rot, FormDesc::FromFormId(worldOrCell, worldState->espmFiles) - }; - } + return GetEditorLocationalData(); } return ChangeForm().spawnPoint; } +LocationalData MpActor::GetEditorLocationalData() const +{ + auto formId = GetFormId(); + auto worldState = GetParent(); + + if (!worldState || !worldState->HasEspm()) { + throw std::runtime_error("MpActor::GetEditorLocation can only be used " + "with actors attached to a valid world state"); + } + + auto data = espm::GetData(formId, worldState); + auto lookupRes = worldState->GetEspm().GetBrowser().LookupById(formId); + + const NiPoint3& pos = LocationalDataUtils::GetPos(data.loc); + NiPoint3 rot = LocationalDataUtils::GetRot(data.loc); + uint32_t worldOrCell = LocationalDataUtils::GetWorldOrCell( + worldState->GetEspm().GetBrowser(), lookupRes); + + return LocationalData{ + pos, rot, FormDesc::FromFormId(worldOrCell, worldState->espmFiles) + }; +} + const float MpActor::GetRespawnTime() const { if (!IsCreatedAsPlayer()) { diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index f981f263e2..c3ec7d06cf 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -94,6 +94,7 @@ class MpActor : public MpObjectReference void Teleport(const LocationalData& position); void SetSpawnPoint(const LocationalData& position); LocationalData GetSpawnPoint() const; + LocationalData GetEditorLocationalData() const; const float GetRespawnTime() const; void SetRespawnTime(float time); From 0d0b6d3c45bacde2b0024dec8d32b61ec1089cfe Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 20 Oct 2023 20:35:21 +0600 Subject: [PATCH 75/88] add NetImmerse script & refactor folders --- papyrus-vm/include/papyrus-vm/Structures.h | 16 +++++-- skymp5-server/cpp/addon/ScampServer.cpp | 2 +- .../cpp/server_guest_lib/ActionListener.cpp | 2 +- .../cpp/server_guest_lib/ConsoleCommands.cpp | 4 +- .../cpp/server_guest_lib/MpActor.cpp | 3 +- .../server_guest_lib/MpObjectReference.cpp | 4 +- .../cpp/server_guest_lib/PapyrusMessage.cpp | 0 .../cpp/server_guest_lib/PapyrusSkymp.cpp | 19 -------- .../cpp/server_guest_lib/WorldState.cpp | 39 +++------------ .../cpp/server_guest_lib/WorldState.h | 2 +- .../{ => script_classes}/IPapyrusClass.h | 2 +- .../{ => script_classes}/PapyrusActor.cpp | 2 +- .../{ => script_classes}/PapyrusActor.h | 2 +- .../{ => script_classes}/PapyrusCell.cpp | 0 .../{ => script_classes}/PapyrusCell.h | 2 +- .../script_classes/PapyrusClassesFactory.cpp | 45 +++++++++++++++++ .../script_classes/PapyrusClassesFactory.h | 15 ++++++ .../{ => script_classes}/PapyrusDebug.cpp | 2 +- .../{ => script_classes}/PapyrusDebug.h | 0 .../PapyrusEffectShader.cpp | 4 +- .../PapyrusEffectShader.h | 0 .../{ => script_classes}/PapyrusForm.cpp | 4 +- .../{ => script_classes}/PapyrusForm.h | 0 .../{ => script_classes}/PapyrusFormList.cpp | 2 +- .../{ => script_classes}/PapyrusFormList.h | 0 .../{ => script_classes}/PapyrusGame.cpp | 4 +- .../{ => script_classes}/PapyrusGame.h | 0 .../{ => script_classes}/PapyrusKeyword.cpp | 0 .../{ => script_classes}/PapyrusKeyword.h | 2 +- .../script_classes/PapyrusMessage.cpp | 10 ++++ .../{ => script_classes}/PapyrusMessage.h | 7 +-- .../script_classes/PapyrusNetImmerse.cpp | 48 +++++++++++++++++++ .../script_classes/PapyrusNetImmerse.h | 19 ++++++++ .../PapyrusObjectReference.cpp | 4 +- .../PapyrusObjectReference.h | 0 .../script_classes/PapyrusSkymp.cpp | 15 ++++++ .../{ => script_classes}/PapyrusSkymp.h | 0 .../{ => script_classes}/PapyrusSound.cpp | 4 +- .../{ => script_classes}/PapyrusSound.h | 0 .../{ => script_classes}/PapyrusUtility.cpp | 0 .../{ => script_classes}/PapyrusUtility.h | 0 .../HeuristicPolicy.cpp | 25 +++++----- .../HeuristicPolicy.h | 11 ++--- .../IPapyrusCompatibilityPolicy.h | 9 ++++ .../PapyrusCompatibilityPolicyFactory.cpp | 9 ++++ .../PapyrusCompatibilityPolicyFactory.h | 12 +++++ .../{ => script_objects}/EspmGameObject.cpp | 0 .../{ => script_objects}/EspmGameObject.h | 0 .../{ => script_objects}/MpFormGameObject.cpp | 0 .../{ => script_objects}/MpFormGameObject.h | 2 +- 50 files changed, 243 insertions(+), 109 deletions(-) delete mode 100644 skymp5-server/cpp/server_guest_lib/PapyrusMessage.cpp delete mode 100644 skymp5-server/cpp/server_guest_lib/PapyrusSkymp.cpp rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/IPapyrusClass.h (94%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusActor.cpp (99%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusActor.h (97%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusCell.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusCell.h (91%) create mode 100644 skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.h rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusDebug.cpp (90%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusDebug.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusEffectShader.cpp (95%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusEffectShader.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusForm.cpp (98%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusForm.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusFormList.cpp (97%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusFormList.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusGame.cpp (98%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusGame.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusKeyword.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusKeyword.h (94%) create mode 100644 skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusMessage.h (79%) create mode 100644 skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.h rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusObjectReference.cpp (99%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusObjectReference.h (100%) create mode 100644 skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSkymp.cpp rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusSkymp.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusSound.cpp (92%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusSound.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusUtility.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_classes}/PapyrusUtility.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_compatibility_policies}/HeuristicPolicy.cpp (82%) rename skymp5-server/cpp/server_guest_lib/{ => script_compatibility_policies}/HeuristicPolicy.h (68%) rename skymp5-server/cpp/server_guest_lib/{ => script_compatibility_policies}/IPapyrusCompatibilityPolicy.h (61%) create mode 100644 skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.h rename skymp5-server/cpp/server_guest_lib/{ => script_objects}/EspmGameObject.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_objects}/EspmGameObject.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_objects}/MpFormGameObject.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => script_objects}/MpFormGameObject.h (94%) diff --git a/papyrus-vm/include/papyrus-vm/Structures.h b/papyrus-vm/include/papyrus-vm/Structures.h index 89316f2f23..9ffaac1135 100644 --- a/papyrus-vm/include/papyrus-vm/Structures.h +++ b/papyrus-vm/include/papyrus-vm/Structures.h @@ -104,15 +104,21 @@ struct VarValue static VarValue None() { return VarValue(); } - explicit operator bool() const { return this->CastToBool().data.b; } + explicit operator bool() const { return CastToBool().data.b; } - explicit operator IGameObject*() const { return this->data.id; } + explicit operator IGameObject*() const + { + return GetType() == kType_Object ? data.id : nullptr; + } - explicit operator int() const { return this->CastToInt().data.i; } + explicit operator int() const { return CastToInt().data.i; } - explicit operator double() const { return this->CastToFloat().data.f; } + explicit operator double() const { return CastToFloat().data.f; } - explicit operator const char*() const { return this->data.string; } + explicit operator const char*() const + { + return GetType() == kType_String ? data.string : ""; + } std::shared_ptr> pArray; diff --git a/skymp5-server/cpp/addon/ScampServer.cpp b/skymp5-server/cpp/addon/ScampServer.cpp index d385dec2c5..e747072b27 100644 --- a/skymp5-server/cpp/addon/ScampServer.cpp +++ b/skymp5-server/cpp/addon/ScampServer.cpp @@ -2,7 +2,6 @@ #include "AsyncSaveStorage.h" #include "Bot.h" -#include "EspmGameObject.h" #include "FileDatabase.h" #include "FormCallbacks.h" #include "GamemodeApi.h" @@ -19,6 +18,7 @@ #include "formulas/TES5DamageFormula.h" #include "libespm/IterateFields.h" #include "property_bindings/PropertyBindingFactory.h" +#include "script_objects/EspmGameObject.h" #include "script_storages/ScriptStorageFactory.h" #include #include diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp index 11dd9d1d2c..36e7e49cce 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp @@ -3,7 +3,6 @@ #include "ConsoleCommands.h" #include "CropRegeneration.h" #include "DummyMessageOutput.h" -#include "EspmGameObject.h" #include "Exceptions.h" #include "FindRecipe.h" #include "GetBaseActorValues.h" @@ -15,6 +14,7 @@ #include "UserMessageOutput.h" #include "WorldState.h" #include "papyrus-vm/Utils.h" +#include "script_objects/EspmGameObject.h" #include #include #include diff --git a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp index 142bae1196..5c0f42b04c 100644 --- a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp +++ b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp @@ -1,9 +1,9 @@ #include "ConsoleCommands.h" -#include "EspmGameObject.h" +#include "script_objects/EspmGameObject.h" #include "MpActor.h" -#include "PapyrusObjectReference.h" #include "WorldState.h" #include "papyrus-vm/Utils.h" +#include "script_classes/PapyrusObjectReference.h" ConsoleCommands::Argument::Argument() { diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index b25166f48a..5d14e6bf12 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -3,7 +3,6 @@ #include "ActorValues.h" #include "ChangeFormGuard.h" #include "CropRegeneration.h" -#include "EspmGameObject.h" #include "FormCallbacks.h" #include "GetBaseActorValues.h" #include "LeveledListUtils.h" @@ -11,12 +10,12 @@ #include "MathUtils.h" #include "MpChangeForms.h" #include "MsgType.h" -#include "PapyrusObjectReference.h" #include "ServerState.h" #include "SweetPieScript.h" #include "TimeUtils.h" #include "WorldState.h" #include "libespm/espm.h" +#include "script_objects/EspmGameObject.h" #include #include #include diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index b209306538..e942a7427c 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -1,14 +1,11 @@ #include "MpObjectReference.h" #include "ChangeFormGuard.h" -#include "EspmGameObject.h" #include "EvaluateTemplate.h" #include "FormCallbacks.h" #include "LeveledListUtils.h" #include "MpActor.h" #include "MpChangeForms.h" #include "MsgType.h" -#include "PapyrusGame.h" -#include "PapyrusObjectReference.h" #include "Primitive.h" #include "ScopedTask.h" #include "ScriptVariablesHolder.h" @@ -18,6 +15,7 @@ #include "libespm/Utils.h" #include "papyrus-vm/Reader.h" #include "papyrus-vm/VirtualMachine.h" +#include "script_objects/EspmGameObject.h" #include "script_storages/IScriptStorage.h" #include #include diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusMessage.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusMessage.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusSkymp.cpp b/skymp5-server/cpp/server_guest_lib/PapyrusSkymp.cpp deleted file mode 100644 index 648d65a79e..0000000000 --- a/skymp5-server/cpp/server_guest_lib/PapyrusSkymp.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "PapyrusSkymp.h" - -#include "HeuristicPolicy.h" -#include "MpFormGameObject.h" - -VarValue PapyrusSkymp::SetDefaultActor(VarValue self, - const std::vector& arguments) -{ - auto heuristicPolicy = std::dynamic_pointer_cast(policy); - if (!heuristicPolicy) - throw std::runtime_error( - "Current compatibility policy doesn't support SetDefaultActor"); - - if (arguments.size() >= 1) - heuristicPolicy->SetDefaultActor(self.GetMetaStackId(), - GetFormPtr(arguments[0])); - - return VarValue::None(); -} diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index 2aee83ad37..71d2585ba2 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -1,29 +1,16 @@ #include "WorldState.h" #include "FormCallbacks.h" -#include "HeuristicPolicy.h" #include "ISaveStorage.h" #include "LocationalDataUtils.h" #include "MpActor.h" #include "MpChangeForms.h" -#include "MpFormGameObject.h" #include "MpObjectReference.h" -#include "PapyrusActor.h" -#include "PapyrusCell.h" -#include "PapyrusDebug.h" -#include "PapyrusEffectShader.h" -#include "PapyrusForm.h" -#include "PapyrusFormList.h" -#include "PapyrusGame.h" -#include "PapyrusKeyword.h" -#include "PapyrusMessage.h" -#include "PapyrusObjectReference.h" -#include "PapyrusSkymp.h" -#include "PapyrusSound.h" -#include "PapyrusUtility.h" #include "ScopedTask.h" #include "Timer.h" #include "libespm/GroupUtils.h" #include "papyrus-vm/Reader.h" +#include "script_classes/PapyrusClassesFactory.h" +#include "script_compatibility_policies/PapyrusCompatibilityPolicyFactory.h" #include "script_storages/IScriptStorage.h" #include #include @@ -38,7 +25,7 @@ struct WorldState::Impl bool saveStorageBusy = false; std::shared_ptr vm; uint32_t nextId = 0xff000000; - std::shared_ptr policy; + std::shared_ptr policy; std::unordered_map changeFormsForDeferredLoad; bool chunkLoadingInProgress = false; bool formLoadingInProgress = false; @@ -53,7 +40,7 @@ WorldState::WorldState() logger.reset(new spdlog::logger("empty logger")); pImpl.reset(new Impl); - pImpl->policy.reset(new HeuristicPolicy(logger, this)); + pImpl->policy = PapyrusCompatibilityPolicyFactory::Create(this); } void WorldState::Clear() @@ -822,22 +809,8 @@ VirtualMachine& WorldState::GetPapyrusVm() } }); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - pImpl->classes.emplace_back(std::make_unique()); - for (auto& cl : pImpl->classes) { - cl->Register(*pImpl->vm, pImpl->policy); - } + pImpl->classes = + PapyrusClassesFactory::CreateAndRegister(*pImpl->vm, pImpl->policy); } } diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.h b/skymp5-server/cpp/server_guest_lib/WorldState.h index b4769d8fc1..10d1ddda96 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.h +++ b/skymp5-server/cpp/server_guest_lib/WorldState.h @@ -4,13 +4,13 @@ #include "GridElement.h" #include "MpChangeForms.h" #include "MpForm.h" -#include "MpFormGameObject.h" #include "MpObjectReference.h" #include "NiPoint3.h" #include "PartOneListener.h" #include "Timer.h" #include "libespm/Loader.h" #include "papyrus-vm/VirtualMachine.h" +#include "script_objects/MpFormGameObject.h" #include #include #include diff --git a/skymp5-server/cpp/server_guest_lib/IPapyrusClass.h b/skymp5-server/cpp/server_guest_lib/script_classes/IPapyrusClass.h similarity index 94% rename from skymp5-server/cpp/server_guest_lib/IPapyrusClass.h rename to skymp5-server/cpp/server_guest_lib/script_classes/IPapyrusClass.h index 702b0727b0..4ed87aee0a 100644 --- a/skymp5-server/cpp/server_guest_lib/IPapyrusClass.h +++ b/skymp5-server/cpp/server_guest_lib/script_classes/IPapyrusClass.h @@ -1,6 +1,6 @@ #pragma once -#include "IPapyrusCompatibilityPolicy.h" #include "papyrus-vm/VirtualMachine.h" +#include "script_compatibility_policies/IPapyrusCompatibilityPolicy.h" class IPapyrusClassBase { diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusActor.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp similarity index 99% rename from skymp5-server/cpp/server_guest_lib/PapyrusActor.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp index b182539a6f..e2297763e6 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp @@ -1,7 +1,7 @@ #include "PapyrusActor.h" #include "MpActor.h" -#include "MpFormGameObject.h" +#include "script_objects/MpFormGameObject.h" #include "SpSnippetFunctionGen.h" #include "papyrus-vm/CIString.h" diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusActor.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.h similarity index 97% rename from skymp5-server/cpp/server_guest_lib/PapyrusActor.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.h index 8e4c4f7fe6..b2fd5869e8 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusActor.h +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.h @@ -1,7 +1,7 @@ #pragma once -#include "EspmGameObject.h" #include "IPapyrusClass.h" #include "SpSnippetFunctionGen.h" +#include "script_objects/EspmGameObject.h" class PapyrusActor final : public IPapyrusClass { diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusCell.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusCell.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusCell.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusCell.cpp diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusCell.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusCell.h similarity index 91% rename from skymp5-server/cpp/server_guest_lib/PapyrusCell.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusCell.h index 78077faf6d..d7f9ce8e91 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusCell.h +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusCell.h @@ -1,7 +1,7 @@ #pragma once -#include "EspmGameObject.h" #include "IPapyrusClass.h" #include "SpSnippetFunctionGen.h" +#include "script_objects/EspmGameObject.h" class PapyrusCell final : public IPapyrusClass { diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.cpp new file mode 100644 index 0000000000..f8f246182d --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.cpp @@ -0,0 +1,45 @@ +#include "PapyrusClassesFactory.h" + +#include "PapyrusActor.h" +#include "PapyrusCell.h" +#include "PapyrusDebug.h" +#include "PapyrusEffectShader.h" +#include "PapyrusForm.h" +#include "PapyrusFormList.h" +#include "PapyrusGame.h" +#include "PapyrusKeyword.h" +#include "PapyrusMessage.h" +#include "PapyrusNetImmerse.h" +#include "PapyrusObjectReference.h" +#include "PapyrusSkymp.h" +#include "PapyrusSound.h" +#include "PapyrusUtility.h" + +std::vector> +PapyrusClassesFactory::CreateAndRegister( + VirtualMachine& vm, + const std::shared_ptr& compatibilityPolicy) +{ + std::vector> result; + + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + result.emplace_back(std::make_unique()); + + for (auto& papyrusClass : result) { + papyrusClass->Register(vm, compatibilityPolicy); + } + + return result; +} diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.h new file mode 100644 index 0000000000..955e506d5f --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusClassesFactory.h @@ -0,0 +1,15 @@ +#pragma once +#include "IPapyrusClass.h" +#include +#include + +class IPapyrusCompatibilityPolicy; +class VirtualMachine; + +class PapyrusClassesFactory +{ +public: + static std::vector> CreateAndRegister( + VirtualMachine& vm, + const std::shared_ptr& compatibilityPolicy); +}; diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusDebug.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp similarity index 90% rename from skymp5-server/cpp/server_guest_lib/PapyrusDebug.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp index dee2038f64..13b7c0013f 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusDebug.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp @@ -1,7 +1,7 @@ #include "PapyrusDebug.h" #include "MpActor.h" -#include "MpFormGameObject.h" +#include "script_objects/MpFormGameObject.h" VarValue PapyrusDebug::SendAnimationEvent( VarValue self, const std::vector& arguments) diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusDebug.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusDebug.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.h diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusEffectShader.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.cpp similarity index 95% rename from skymp5-server/cpp/server_guest_lib/PapyrusEffectShader.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.cpp index fae3ec0116..0cb3d45025 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusEffectShader.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.cpp @@ -1,8 +1,8 @@ #include "PapyrusEffectShader.h" -#include "EspmGameObject.h" -#include "MpFormGameObject.h" #include "WorldState.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" VarValue PapyrusEffectShader::Play(VarValue self, const std::vector& arguments) diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusEffectShader.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusEffectShader.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.h diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusForm.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusForm.cpp similarity index 98% rename from skymp5-server/cpp/server_guest_lib/PapyrusForm.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusForm.cpp index 74105a98aa..c3539f1935 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusForm.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusForm.cpp @@ -1,10 +1,10 @@ #include "PapyrusForm.h" -#include "EspmGameObject.h" #include "MpActor.h" -#include "MpFormGameObject.h" #include "TimeUtils.h" #include "WorldState.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" #include #include diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusForm.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusForm.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusForm.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusForm.h diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusFormList.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusFormList.cpp similarity index 97% rename from skymp5-server/cpp/server_guest_lib/PapyrusFormList.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusFormList.cpp index aadf9266b5..fcef502ffc 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusFormList.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusFormList.cpp @@ -1,6 +1,6 @@ #include "PapyrusFormList.h" -#include "EspmGameObject.h" +#include "script_objects/EspmGameObject.h" VarValue PapyrusFormList::GetSize(VarValue self, const std::vector& arguments) diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusFormList.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusFormList.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusFormList.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusFormList.h diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusGame.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp similarity index 98% rename from skymp5-server/cpp/server_guest_lib/PapyrusGame.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp index cc9ad85bac..a502c6104b 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusGame.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp @@ -1,10 +1,10 @@ #include "PapyrusGame.h" #include "PapyrusFormList.h" -#include "EspmGameObject.h" -#include "MpFormGameObject.h" #include "WorldState.h" #include "libespm/Combiner.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" VarValue PapyrusGame::IncrementStat(VarValue self, const std::vector& arguments) diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusGame.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusGame.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.h diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusKeyword.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusKeyword.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusKeyword.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusKeyword.cpp diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusKeyword.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusKeyword.h similarity index 94% rename from skymp5-server/cpp/server_guest_lib/PapyrusKeyword.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusKeyword.h index 7a5b823f93..3a249f35f0 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusKeyword.h +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusKeyword.h @@ -1,7 +1,7 @@ #pragma once -#include "EspmGameObject.h" #include "IPapyrusClass.h" #include "WorldState.h" +#include "script_objects/EspmGameObject.h" class PapyrusKeyword final : public IPapyrusClass { diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp new file mode 100644 index 0000000000..0101715428 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp @@ -0,0 +1,10 @@ +#include "PapyrusMessage.h" + +void PapyrusMessage::Register( + VirtualMachine& vm, + std::shared_ptr policy) +{ + compatibilityPolicy = policy; + + AddMethod(vm, "Show", &PapyrusMessage::Show); +} diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusMessage.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.h similarity index 79% rename from skymp5-server/cpp/server_guest_lib/PapyrusMessage.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.h index d388716ed0..90122658e6 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusMessage.h +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.h @@ -10,12 +10,7 @@ class PapyrusMessage final : public IPapyrusClass DEFINE_METHOD_SPSNIPPET(Show); void Register(VirtualMachine& vm, - std::shared_ptr policy) override - { - compatibilityPolicy = policy; - - AddMethod(vm, "Show", &PapyrusMessage::Show); - } + std::shared_ptr policy) override; std::shared_ptr compatibilityPolicy; }; diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp new file mode 100644 index 0000000000..1eff6e63a9 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp @@ -0,0 +1,48 @@ +#include "PapyrusNetImmerse.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" + +VarValue PapyrusNetImmerse::SetNodeTextureSet( + VarValue, const std::vector& arguments) +{ + if (arguments.size() != 4) { + throw std::runtime_error( + "PapyrusNetImmerse::SetNodeTextureSet - expected 4 arguments"); + } + + MpObjectReference* ref = GetFormPtr(arguments[0]); + const char* node = static_cast(arguments[1]); + espm::LookupResult tSet = GetRecordPtr(arguments[2]); + bool firstPerson = static_cast(arguments[3]); + + std::ignore = firstPerson; + + // for (auto listener : selfRefr->GetListeners()) { + // auto targetRefr = dynamic_cast(listener); + // if (targetRefr) { + // SpSnippet(GetName(), funcName, serializedArgs.data(), + // selfRefr->GetFormId()) + // .Execute(targetRefr); + // } + // } + + // TODO + + return VarValue::None(); +} + +VarValue PapyrusNetImmerse::SetNodeScale( + VarValue, const std::vector& arguments) +{ + // TODO + return VarValue::None(); +} + +void PapyrusNetImmerse::Register( + VirtualMachine& vm, std::shared_ptr policy) +{ + compatibilityPolicy = policy; + + AddStatic(vm, "SetNodeTextureSet", &PapyrusNetImmerse::SetNodeTextureSet); + AddStatic(vm, "SetNodeScale", &PapyrusNetImmerse::SetNodeScale); +} diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.h new file mode 100644 index 0000000000..309b98492c --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.h @@ -0,0 +1,19 @@ +#pragma once +#include "IPapyrusClass.h" +#include "SpSnippetFunctionGen.h" + +class PapyrusNetImmerse final : public IPapyrusClass +{ +public: + const char* GetName() override { return "netimmerse"; } + + VarValue SetNodeTextureSet(VarValue self, + const std::vector& arguments); + + VarValue SetNodeScale(VarValue self, const std::vector& arguments); + + void Register(VirtualMachine& vm, + std::shared_ptr policy) override; + + std::shared_ptr compatibilityPolicy; +}; diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp similarity index 99% rename from skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp index a74b7318e7..3d36de864a 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp @@ -1,15 +1,15 @@ #include "PapyrusObjectReference.h" -#include "EspmGameObject.h" #include "FormCallbacks.h" #include "LocationalData.h" #include "MpActor.h" -#include "MpFormGameObject.h" #include "MpObjectReference.h" #include "SpSnippetFunctionGen.h" #include "TimeUtils.h" #include "WorldState.h" #include "papyrus-vm/Structures.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" #include VarValue PapyrusObjectReference::IsHarvested( diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusObjectReference.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.h diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSkymp.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSkymp.cpp new file mode 100644 index 0000000000..9d550b1b65 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSkymp.cpp @@ -0,0 +1,15 @@ +#include "PapyrusSkymp.h" + +#include "MpActor.h" +#include "script_objects/MpFormGameObject.h" + +VarValue PapyrusSkymp::SetDefaultActor(VarValue self, + const std::vector& arguments) +{ + if (arguments.size() >= 1) { + policy->SetDefaultActor(self.GetMetaStackId(), + GetFormPtr(arguments[0])); + } + + return VarValue::None(); +} diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusSkymp.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSkymp.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusSkymp.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSkymp.h diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusSound.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.cpp similarity index 92% rename from skymp5-server/cpp/server_guest_lib/PapyrusSound.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.cpp index 4610fbec17..edd7b4e133 100644 --- a/skymp5-server/cpp/server_guest_lib/PapyrusSound.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.cpp @@ -1,9 +1,9 @@ #include "PapyrusSound.h" -#include "EspmGameObject.h" -#include "MpFormGameObject.h" #include "MpObjectReference.h" #include "SpSnippetFunctionGen.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" VarValue PapyrusSound::Play(VarValue self, const std::vector& arguments) diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusSound.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusSound.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.h diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusUtility.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusUtility.cpp rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusUtility.cpp diff --git a/skymp5-server/cpp/server_guest_lib/PapyrusUtility.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusUtility.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/PapyrusUtility.h rename to skymp5-server/cpp/server_guest_lib/script_classes/PapyrusUtility.h diff --git a/skymp5-server/cpp/server_guest_lib/HeuristicPolicy.cpp b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/HeuristicPolicy.cpp similarity index 82% rename from skymp5-server/cpp/server_guest_lib/HeuristicPolicy.cpp rename to skymp5-server/cpp/server_guest_lib/script_compatibility_policies/HeuristicPolicy.cpp index 3292207f7a..43fa70ab0a 100644 --- a/skymp5-server/cpp/server_guest_lib/HeuristicPolicy.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/HeuristicPolicy.cpp @@ -1,21 +1,21 @@ #include "HeuristicPolicy.h" -#include "MpFormGameObject.h" +#include "script_objects/MpFormGameObject.h" -HeuristicPolicy::HeuristicPolicy( - const std::shared_ptr& logger_, WorldState* worldState_) - : logger(logger_) - , worldState(worldState_) +HeuristicPolicy::HeuristicPolicy(WorldState* worldState_) + : worldState(worldState_) { } void HeuristicPolicy::SetDefaultActor(int32_t stackId, MpActor* newActor) { - if (stackId < 0) + if (stackId < 0) { throw std::runtime_error("Invalid stackId was passed to SetDefaultActor"); + } - if (stackInfo.size() <= stackId) + if (stackInfo.size() <= stackId) { stackInfo.resize(stackId + 1); + } stackInfo[stackId].ac = newActor; } @@ -23,19 +23,21 @@ MpActor* HeuristicPolicy::GetDefaultActor(const char* className, const char* funcName, int32_t stackId) const { - if (stackId < 0 || stackId >= stackInfo.size()) + if (stackId < 0 || stackId >= stackInfo.size()) { throw std::runtime_error( "Invalid stackId was passed to GetDefaultActor (" + std::to_string(stackId) + ")"); + } auto& info = stackInfo[stackId]; MpActor* actor = stackId < stackInfo.size() ? stackInfo[stackId].ac : static_cast(nullptr); - if (!actor && logger) - logger->warn("Unable to determine Actor for '{}.{}' in '{}'", className, + if (!actor) { + spdlog::warn("Unable to determine Actor for '{}.{}' in '{}'", className, funcName, info.currentEventName); + } return actor; } @@ -65,7 +67,8 @@ void HeuristicPolicy::BeforeSendPapyrusEvent(MpForm* form, actor = GetFormPtr(arguments[0]); } - if (stackInfo.size() <= stackId) + if (stackInfo.size() <= stackId) { stackInfo.resize(stackId + 1); + } stackInfo[stackId] = { actor, eventName }; } diff --git a/skymp5-server/cpp/server_guest_lib/HeuristicPolicy.h b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/HeuristicPolicy.h similarity index 68% rename from skymp5-server/cpp/server_guest_lib/HeuristicPolicy.h rename to skymp5-server/cpp/server_guest_lib/script_compatibility_policies/HeuristicPolicy.h index 3746b91d2a..1ccda337c1 100644 --- a/skymp5-server/cpp/server_guest_lib/HeuristicPolicy.h +++ b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/HeuristicPolicy.h @@ -2,28 +2,26 @@ #include "IPapyrusCompatibilityPolicy.h" #include "MpActor.h" -#include "MpFormGameObject.h" #include "papyrus-vm/Utils.h" #include "papyrus-vm/VirtualMachine.h" -#include +#include "script_objects/MpFormGameObject.h" #include class HeuristicPolicy : public IPapyrusCompatibilityPolicy { public: - explicit HeuristicPolicy(const std::shared_ptr& logger, - WorldState* worldState_); + explicit HeuristicPolicy(WorldState* worldState_); MpActor* GetDefaultActor(const char* className, const char* funcName, int32_t stackId) const override; WorldState* GetWorldState() const override; - void SetDefaultActor(int32_t stackId, MpActor* actor); + void SetDefaultActor(int32_t stackId, MpActor* actor) override; void BeforeSendPapyrusEvent(MpForm* form, const char* eventName, const VarValue* arguments, size_t argumentsCount, - int32_t stackId); + int32_t stackId) override; private: struct StackInfo @@ -32,6 +30,5 @@ class HeuristicPolicy : public IPapyrusCompatibilityPolicy const char* currentEventName = ""; }; std::vector stackInfo; - const std::shared_ptr& logger; WorldState* const worldState; }; diff --git a/skymp5-server/cpp/server_guest_lib/IPapyrusCompatibilityPolicy.h b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h similarity index 61% rename from skymp5-server/cpp/server_guest_lib/IPapyrusCompatibilityPolicy.h rename to skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h index 4e96e2c46b..24b16fe7c2 100644 --- a/skymp5-server/cpp/server_guest_lib/IPapyrusCompatibilityPolicy.h +++ b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h @@ -1,8 +1,10 @@ #pragma once #include +class MpForm; class MpActor; class WorldState; +struct VarValue; class IPapyrusCompatibilityPolicy { @@ -16,4 +18,11 @@ class IPapyrusCompatibilityPolicy int32_t stackId) const = 0; virtual WorldState* GetWorldState() const { return nullptr; } + + virtual void SetDefaultActor(int32_t stackId, MpActor* actor) = 0; + + virtual void BeforeSendPapyrusEvent(MpForm* form, const char* eventName, + const VarValue* arguments, + size_t argumentsCount, + int32_t stackId) = 0; }; diff --git a/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.cpp b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.cpp new file mode 100644 index 0000000000..d12abb5a69 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.cpp @@ -0,0 +1,9 @@ +#include "PapyrusCompatibilityPolicyFactory.h" + +#include "HeuristicPolicy.h" + +std::shared_ptr +PapyrusCompatibilityPolicyFactory::Create(WorldState* worldState) +{ + return std::make_shared(worldState); +} diff --git a/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.h b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.h new file mode 100644 index 0000000000..1538039c86 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/PapyrusCompatibilityPolicyFactory.h @@ -0,0 +1,12 @@ +#pragma once +#include "IPapyrusCompatibilityPolicy.h" +#include + +class WorldState; + +class PapyrusCompatibilityPolicyFactory +{ +public: + static std::shared_ptr Create( + WorldState* worldState); +}; diff --git a/skymp5-server/cpp/server_guest_lib/EspmGameObject.cpp b/skymp5-server/cpp/server_guest_lib/script_objects/EspmGameObject.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/EspmGameObject.cpp rename to skymp5-server/cpp/server_guest_lib/script_objects/EspmGameObject.cpp diff --git a/skymp5-server/cpp/server_guest_lib/EspmGameObject.h b/skymp5-server/cpp/server_guest_lib/script_objects/EspmGameObject.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/EspmGameObject.h rename to skymp5-server/cpp/server_guest_lib/script_objects/EspmGameObject.h diff --git a/skymp5-server/cpp/server_guest_lib/MpFormGameObject.cpp b/skymp5-server/cpp/server_guest_lib/script_objects/MpFormGameObject.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/MpFormGameObject.cpp rename to skymp5-server/cpp/server_guest_lib/script_objects/MpFormGameObject.cpp diff --git a/skymp5-server/cpp/server_guest_lib/MpFormGameObject.h b/skymp5-server/cpp/server_guest_lib/script_objects/MpFormGameObject.h similarity index 94% rename from skymp5-server/cpp/server_guest_lib/MpFormGameObject.h rename to skymp5-server/cpp/server_guest_lib/script_objects/MpFormGameObject.h index 962bf83057..d768ee39b5 100644 --- a/skymp5-server/cpp/server_guest_lib/MpFormGameObject.h +++ b/skymp5-server/cpp/server_guest_lib/script_objects/MpFormGameObject.h @@ -25,7 +25,7 @@ class MpFormGameObject : public IGameObject }; template -inline T* GetFormPtr(const VarValue& papyrusObject) +T* GetFormPtr(const VarValue& papyrusObject) { if (papyrusObject.GetType() != VarValue::kType_Object) { return nullptr; From c481aff880bbe14d306e701f498c4ecadfa56b8d Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 20 Oct 2023 22:32:02 +0600 Subject: [PATCH 76/88] create database_drivers & save_storages folders, fix tests --- papyrus-vm/include/papyrus-vm/Structures.h | 10 +--- skymp5-server/cpp/addon/PapyrusUtils.h | 4 +- skymp5-server/cpp/addon/ScampServer.cpp | 19 ++------ skymp5-server/cpp/addon/SettingsUtils.h | 48 ------------------- skymp5-server/cpp/server_guest_lib/PartOne.h | 2 +- .../ScriptVariablesHolder.cpp | 4 +- .../cpp/server_guest_lib/WorldState.cpp | 2 +- .../database_drivers/DatabaseFactory.cpp | 46 ++++++++++++++++++ .../database_drivers/DatabaseFactory.h | 11 +++++ .../{ => database_drivers}/FileDatabase.cpp | 0 .../{ => database_drivers}/FileDatabase.h | 0 .../{ => database_drivers}/IDatabase.h | 0 .../MigrationDatabase.cpp | 0 .../MigrationDatabase.h | 0 .../{ => database_drivers}/MongoDatabase.cpp | 0 .../{ => database_drivers}/MongoDatabase.h | 0 .../{ => save_storages}/AsyncSaveStorage.cpp | 0 .../{ => save_storages}/AsyncSaveStorage.h | 2 +- .../{ => save_storages}/ISaveStorage.h | 0 .../save_storages/SaveStorageFactory.cpp | 9 ++++ .../save_storages/SaveStorageFactory.h | 12 +++++ unit/HeuristicPolicyTest.cpp | 6 +-- unit/PapyrusActorTest.cpp | 2 +- unit/PapyrusDebugTest.cpp | 8 +++- unit/PapyrusFormListTest.cpp | 4 +- unit/PapyrusFormTest.cpp | 2 +- unit/PapyrusGameTest.cpp | 10 ++-- unit/PapyrusObjectReferenceTest.cpp | 4 +- unit/PapyrusSkympTest.cpp | 6 +-- unit/PapyrusUtilityTest.cpp | 6 +-- unit/SaveStorageTest.cpp | 4 +- unit/TestUtils.cpp | 12 ----- unit/TestUtils.hpp | 13 +---- 33 files changed, 120 insertions(+), 126 deletions(-) delete mode 100644 skymp5-server/cpp/addon/SettingsUtils.h create mode 100644 skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.h rename skymp5-server/cpp/server_guest_lib/{ => database_drivers}/FileDatabase.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => database_drivers}/FileDatabase.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => database_drivers}/IDatabase.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => database_drivers}/MigrationDatabase.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => database_drivers}/MigrationDatabase.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => database_drivers}/MongoDatabase.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => database_drivers}/MongoDatabase.h (100%) rename skymp5-server/cpp/server_guest_lib/{ => save_storages}/AsyncSaveStorage.cpp (100%) rename skymp5-server/cpp/server_guest_lib/{ => save_storages}/AsyncSaveStorage.h (94%) rename skymp5-server/cpp/server_guest_lib/{ => save_storages}/ISaveStorage.h (100%) create mode 100644 skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.cpp create mode 100644 skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.h diff --git a/papyrus-vm/include/papyrus-vm/Structures.h b/papyrus-vm/include/papyrus-vm/Structures.h index 9ffaac1135..c61c944616 100644 --- a/papyrus-vm/include/papyrus-vm/Structures.h +++ b/papyrus-vm/include/papyrus-vm/Structures.h @@ -106,19 +106,13 @@ struct VarValue explicit operator bool() const { return CastToBool().data.b; } - explicit operator IGameObject*() const - { - return GetType() == kType_Object ? data.id : nullptr; - } + explicit operator IGameObject*() const { return data.id; } explicit operator int() const { return CastToInt().data.i; } explicit operator double() const { return CastToFloat().data.f; } - explicit operator const char*() const - { - return GetType() == kType_String ? data.string : ""; - } + explicit operator const char*() const { return data.string; } std::shared_ptr> pArray; diff --git a/skymp5-server/cpp/addon/PapyrusUtils.h b/skymp5-server/cpp/addon/PapyrusUtils.h index 865ce994c8..9408877ddd 100644 --- a/skymp5-server/cpp/addon/PapyrusUtils.h +++ b/skymp5-server/cpp/addon/PapyrusUtils.h @@ -1,10 +1,10 @@ #pragma once -#include "EspmGameObject.h" #include "FormDesc.h" -#include "MpFormGameObject.h" #include "NapiHelper.h" #include "WorldState.h" #include "papyrus-vm/Structures.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" #include #include diff --git a/skymp5-server/cpp/addon/ScampServer.cpp b/skymp5-server/cpp/addon/ScampServer.cpp index e747072b27..a526962ff4 100644 --- a/skymp5-server/cpp/addon/ScampServer.cpp +++ b/skymp5-server/cpp/addon/ScampServer.cpp @@ -1,23 +1,19 @@ #include "ScampServer.h" -#include "AsyncSaveStorage.h" #include "Bot.h" -#include "FileDatabase.h" #include "FormCallbacks.h" #include "GamemodeApi.h" -#include "MigrationDatabase.h" -#include "MongoDatabase.h" -#include "MpFormGameObject.h" #include "NapiHelper.h" #include "NetworkingCombined.h" #include "PacketHistoryWrapper.h" #include "PapyrusUtils.h" #include "ScampServerListener.h" -#include "SettingsUtils.h" +#include "database_drivers/DatabaseFactory.h" #include "formulas/SweetPieDamageFormula.h" #include "formulas/TES5DamageFormula.h" #include "libespm/IterateFields.h" #include "property_bindings/PropertyBindingFactory.h" +#include "save_storages/SaveStorageFactory.h" #include "script_objects/EspmGameObject.h" #include "script_storages/ScriptStorageFactory.h" #include @@ -37,12 +33,6 @@ std::shared_ptr& GetLogger() return g_logger; } -std::shared_ptr CreateSaveStorage( - std::shared_ptr db, std::shared_ptr logger) -{ - return std::make_shared(db, logger); -} - std::string GetPropertyAlphabet() { std::string alphabet; @@ -346,8 +336,9 @@ ScampServer::ScampServer(const Napi::CallbackInfo& info) Napi::Value ScampServer::AttachSaveStorage(const Napi::CallbackInfo& info) { try { - partOne->AttachSaveStorage(CreateSaveStorage( - SettingsUtils::CreateDatabase(serverSettings, logger), logger)); + auto db = DatabaseFactory::Create(serverSettings, logger); + auto saveStorage = SaveStorageFactory::Create(db, logger); + partOne->AttachSaveStorage(saveStorage); } catch (std::exception& e) { throw Napi::Error::New(info.Env(), (std::string)e.what()); } diff --git a/skymp5-server/cpp/addon/SettingsUtils.h b/skymp5-server/cpp/addon/SettingsUtils.h deleted file mode 100644 index 98ef6755ca..0000000000 --- a/skymp5-server/cpp/addon/SettingsUtils.h +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include - -#include "FileDatabase.h" -#include "MigrationDatabase.h" -#include "MongoDatabase.h" - -class SettingsUtils -{ -public: - std::shared_ptr static CreateDatabase( - nlohmann::json settings, std::shared_ptr logger) - { - auto databaseDriver = settings.count("databaseDriver") - ? settings["databaseDriver"].get() - : std::string("file"); - - if (databaseDriver == "file") { - auto databaseName = settings.count("databaseName") - ? settings["databaseName"].get() - : std::string("world"); - - logger->info("Using file with name '" + databaseName + "'"); - return std::make_shared(databaseName, logger); - } - - if (databaseDriver == "mongodb") { - auto databaseName = settings.count("databaseName") - ? settings["databaseName"].get() - : std::string("db"); - - auto databaseUri = settings["databaseUri"].get(); - logger->info("Using mongodb with name '" + databaseName + "'"); - return std::make_shared(databaseUri, databaseName); - } - - if (databaseDriver == "migration") { - auto from = settings.at("databaseOld"); - auto to = settings.at("databaseNew"); - auto oldDatabase = CreateDatabase(from, logger); - auto newDatabase = CreateDatabase(to, logger); - return std::make_shared(newDatabase, oldDatabase); - } - - throw std::runtime_error("Unrecognized databaseDriver: " + databaseDriver); - } -}; diff --git a/skymp5-server/cpp/server_guest_lib/PartOne.h b/skymp5-server/cpp/server_guest_lib/PartOne.h index 59344aee40..41623adac9 100644 --- a/skymp5-server/cpp/server_guest_lib/PartOne.h +++ b/skymp5-server/cpp/server_guest_lib/PartOne.h @@ -1,7 +1,7 @@ #pragma once #include "AnimationSystem.h" #include "GamemodeApi.h" -#include "ISaveStorage.h" +#include "save_storages/ISaveStorage.h" #include "MpActor.h" #include "Networking.h" #include "NiPoint3.h" diff --git a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp index 15798c8540..2d234ad2e5 100644 --- a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp +++ b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.cpp @@ -1,10 +1,10 @@ #include "ScriptVariablesHolder.h" -#include "EspmGameObject.h" -#include "MpFormGameObject.h" #include "WorldState.h" #include "libespm/Property.h" #include "papyrus-vm/Utils.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" #include diff --git a/skymp5-server/cpp/server_guest_lib/WorldState.cpp b/skymp5-server/cpp/server_guest_lib/WorldState.cpp index 71d2585ba2..8f10bcdc0f 100644 --- a/skymp5-server/cpp/server_guest_lib/WorldState.cpp +++ b/skymp5-server/cpp/server_guest_lib/WorldState.cpp @@ -1,6 +1,5 @@ #include "WorldState.h" #include "FormCallbacks.h" -#include "ISaveStorage.h" #include "LocationalDataUtils.h" #include "MpActor.h" #include "MpChangeForms.h" @@ -9,6 +8,7 @@ #include "Timer.h" #include "libespm/GroupUtils.h" #include "papyrus-vm/Reader.h" +#include "save_storages/ISaveStorage.h" #include "script_classes/PapyrusClassesFactory.h" #include "script_compatibility_policies/PapyrusCompatibilityPolicyFactory.h" #include "script_storages/IScriptStorage.h" diff --git a/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp b/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp new file mode 100644 index 0000000000..673c8451ab --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp @@ -0,0 +1,46 @@ +#include "DatabaseFactory.h" + +#include +#include +#include + +#include "FileDatabase.h" +#include "MigrationDatabase.h" +#include "MongoDatabase.h" + +std::shared_ptr DatabaseFactory::Create( + nlohmann::json settings, std::shared_ptr logger) +{ + auto databaseDriver = settings.count("databaseDriver") + ? settings["databaseDriver"].get() + : std::string("file"); + + if (databaseDriver == "file") { + auto databaseName = settings.count("databaseName") + ? settings["databaseName"].get() + : std::string("world"); + + logger->info("Using file with name '" + databaseName + "'"); + return std::make_shared(databaseName, logger); + } + + if (databaseDriver == "mongodb") { + auto databaseName = settings.count("databaseName") + ? settings["databaseName"].get() + : std::string("db"); + + auto databaseUri = settings["databaseUri"].get(); + logger->info("Using mongodb with name '" + databaseName + "'"); + return std::make_shared(databaseUri, databaseName); + } + + if (databaseDriver == "migration") { + auto from = settings.at("databaseOld"); + auto to = settings.at("databaseNew"); + auto oldDatabase = Create(from, logger); + auto newDatabase = Create(to, logger); + return std::make_shared(newDatabase, oldDatabase); + } + + throw std::runtime_error("Unrecognized databaseDriver: " + databaseDriver); +} \ No newline at end of file diff --git a/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.h b/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.h new file mode 100644 index 0000000000..d92aebd8d7 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.h @@ -0,0 +1,11 @@ +#pragma once +#include "IDatabase.h" +#include +#include + +class DatabaseFactory +{ +public: + static std::shared_ptr Create( + nlohmann::json settings, std::shared_ptr logger); +}; diff --git a/skymp5-server/cpp/server_guest_lib/FileDatabase.cpp b/skymp5-server/cpp/server_guest_lib/database_drivers/FileDatabase.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/FileDatabase.cpp rename to skymp5-server/cpp/server_guest_lib/database_drivers/FileDatabase.cpp diff --git a/skymp5-server/cpp/server_guest_lib/FileDatabase.h b/skymp5-server/cpp/server_guest_lib/database_drivers/FileDatabase.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/FileDatabase.h rename to skymp5-server/cpp/server_guest_lib/database_drivers/FileDatabase.h diff --git a/skymp5-server/cpp/server_guest_lib/IDatabase.h b/skymp5-server/cpp/server_guest_lib/database_drivers/IDatabase.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/IDatabase.h rename to skymp5-server/cpp/server_guest_lib/database_drivers/IDatabase.h diff --git a/skymp5-server/cpp/server_guest_lib/MigrationDatabase.cpp b/skymp5-server/cpp/server_guest_lib/database_drivers/MigrationDatabase.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/MigrationDatabase.cpp rename to skymp5-server/cpp/server_guest_lib/database_drivers/MigrationDatabase.cpp diff --git a/skymp5-server/cpp/server_guest_lib/MigrationDatabase.h b/skymp5-server/cpp/server_guest_lib/database_drivers/MigrationDatabase.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/MigrationDatabase.h rename to skymp5-server/cpp/server_guest_lib/database_drivers/MigrationDatabase.h diff --git a/skymp5-server/cpp/server_guest_lib/MongoDatabase.cpp b/skymp5-server/cpp/server_guest_lib/database_drivers/MongoDatabase.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/MongoDatabase.cpp rename to skymp5-server/cpp/server_guest_lib/database_drivers/MongoDatabase.cpp diff --git a/skymp5-server/cpp/server_guest_lib/MongoDatabase.h b/skymp5-server/cpp/server_guest_lib/database_drivers/MongoDatabase.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/MongoDatabase.h rename to skymp5-server/cpp/server_guest_lib/database_drivers/MongoDatabase.h diff --git a/skymp5-server/cpp/server_guest_lib/AsyncSaveStorage.cpp b/skymp5-server/cpp/server_guest_lib/save_storages/AsyncSaveStorage.cpp similarity index 100% rename from skymp5-server/cpp/server_guest_lib/AsyncSaveStorage.cpp rename to skymp5-server/cpp/server_guest_lib/save_storages/AsyncSaveStorage.cpp diff --git a/skymp5-server/cpp/server_guest_lib/AsyncSaveStorage.h b/skymp5-server/cpp/server_guest_lib/save_storages/AsyncSaveStorage.h similarity index 94% rename from skymp5-server/cpp/server_guest_lib/AsyncSaveStorage.h rename to skymp5-server/cpp/server_guest_lib/save_storages/AsyncSaveStorage.h index 176bb26b45..0966be19a4 100644 --- a/skymp5-server/cpp/server_guest_lib/AsyncSaveStorage.h +++ b/skymp5-server/cpp/server_guest_lib/save_storages/AsyncSaveStorage.h @@ -1,6 +1,6 @@ #pragma once -#include "IDatabase.h" #include "ISaveStorage.h" +#include "database_drivers/IDatabase.h" #include #include diff --git a/skymp5-server/cpp/server_guest_lib/ISaveStorage.h b/skymp5-server/cpp/server_guest_lib/save_storages/ISaveStorage.h similarity index 100% rename from skymp5-server/cpp/server_guest_lib/ISaveStorage.h rename to skymp5-server/cpp/server_guest_lib/save_storages/ISaveStorage.h diff --git a/skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.cpp b/skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.cpp new file mode 100644 index 0000000000..7b34d4b16b --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.cpp @@ -0,0 +1,9 @@ +#include "SaveStorageFactory.h" + +#include "AsyncSaveStorage.h" + +std::shared_ptr SaveStorageFactory::Create( + std::shared_ptr db, std::shared_ptr logger) +{ + return std::make_shared(db, logger); +} diff --git a/skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.h b/skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.h new file mode 100644 index 0000000000..cb5589b047 --- /dev/null +++ b/skymp5-server/cpp/server_guest_lib/save_storages/SaveStorageFactory.h @@ -0,0 +1,12 @@ +#pragma once +#include "ISaveStorage.h" +#include "database_drivers/IDatabase.h" +#include +#include + +class SaveStorageFactory +{ +public: + static std::shared_ptr Create( + std::shared_ptr db, std::shared_ptr logger); +}; diff --git a/unit/HeuristicPolicyTest.cpp b/unit/HeuristicPolicyTest.cpp index c255283de4..6affca9091 100644 --- a/unit/HeuristicPolicyTest.cpp +++ b/unit/HeuristicPolicyTest.cpp @@ -1,15 +1,15 @@ #include "TestUtils.hpp" #include -#include "HeuristicPolicy.h" #include "MpActor.h" -#include "MpFormGameObject.h" +#include "script_compatibility_policies/HeuristicPolicy.h" +#include "script_objects/MpFormGameObject.h" TEST_CASE("HeuristicPolicy", "[HeuristicPolicy]") { auto logger = std::make_shared("empty logger"); WorldState wst; - HeuristicPolicy policy(logger, &wst); + HeuristicPolicy policy(&wst); MpActor actor(LocationalData(), FormCallbacks::DoNothing()); MpFormGameObject actorGameObject(&actor); diff --git a/unit/PapyrusActorTest.cpp b/unit/PapyrusActorTest.cpp index 3c9e294d45..d5056e236e 100644 --- a/unit/PapyrusActorTest.cpp +++ b/unit/PapyrusActorTest.cpp @@ -1,7 +1,7 @@ #include "TestUtils.hpp" #include -#include "PapyrusActor.h" +#include "script_classes/PapyrusActor.h" PartOne& GetPartOne(); diff --git a/unit/PapyrusDebugTest.cpp b/unit/PapyrusDebugTest.cpp index b5bbe67b11..2ce58344ac 100644 --- a/unit/PapyrusDebugTest.cpp +++ b/unit/PapyrusDebugTest.cpp @@ -1,7 +1,8 @@ #include "TestUtils.hpp" #include -#include "PapyrusDebug.h" +#include "script_classes/PapyrusDebug.h" +#include "script_compatibility_policies/HeuristicPolicy.h" TEST_CASE("Notification", "[Papyrus][Debug]") { @@ -15,8 +16,11 @@ TEST_CASE("Notification", "[Papyrus][Debug]") auto& ac = p.worldState.GetFormAt(0xff000000); + auto policy = new HeuristicPolicy(&p.worldState); + policy->SetDefaultActor(VarValue::AttachTestStackId().GetMetaStackId(), &ac); + PapyrusDebug debug; - debug.compatibilityPolicy.reset(new PapyrusCompatibilityPolicy(&ac)); + debug.compatibilityPolicy.reset(policy); DoConnect(p, 3); p.SetUserActor(3, 0xff000000); diff --git a/unit/PapyrusFormListTest.cpp b/unit/PapyrusFormListTest.cpp index bf0f4ec6a2..c22eafe555 100644 --- a/unit/PapyrusFormListTest.cpp +++ b/unit/PapyrusFormListTest.cpp @@ -1,8 +1,8 @@ #include "TestUtils.hpp" #include -#include "EspmGameObject.h" -#include "PapyrusFormList.h" +#include "script_classes/PapyrusFormList.h" +#include "script_objects/EspmGameObject.h" extern espm::Loader l; diff --git a/unit/PapyrusFormTest.cpp b/unit/PapyrusFormTest.cpp index 67056879e9..ad5849106b 100644 --- a/unit/PapyrusFormTest.cpp +++ b/unit/PapyrusFormTest.cpp @@ -1,7 +1,7 @@ #include "TestUtils.hpp" #include -#include "PapyrusForm.h" +#include "script_classes/PapyrusForm.h" using namespace std::chrono_literals; diff --git a/unit/PapyrusGameTest.cpp b/unit/PapyrusGameTest.cpp index 0acc37682a..33bf742668 100644 --- a/unit/PapyrusGameTest.cpp +++ b/unit/PapyrusGameTest.cpp @@ -1,10 +1,10 @@ -#include "HeuristicPolicy.h" #include "PartOne.h" #include "TestUtils.hpp" +#include "script_compatibility_policies/HeuristicPolicy.h" #include -#include "PapyrusGame.h" #include "papyrus-vm/Structures.h" +#include "script_classes/PapyrusGame.h" PartOne& GetPartOne(); @@ -13,8 +13,7 @@ TEST_CASE("GetForm", "[Papyrus][Game][espm]") PartOne& partOne = GetPartOne(); PapyrusGame game; std::shared_ptr logger; - game.compatibilityPolicy.reset( - new HeuristicPolicy(logger, &partOne.worldState)); + game.compatibilityPolicy.reset(new HeuristicPolicy(&partOne.worldState)); constexpr const uint32_t foodBarrel = 0x20570; const auto& refer = @@ -32,8 +31,7 @@ TEST_CASE("GetFormEx", "[Papyrus][Game][espm]") PartOne& partOne = GetPartOne(); PapyrusGame game; std::shared_ptr logger; - game.compatibilityPolicy.reset( - new HeuristicPolicy(logger, &partOne.worldState)); + game.compatibilityPolicy.reset(new HeuristicPolicy(&partOne.worldState)); DoConnect(partOne, 0); const uint32_t formId = partOne.CreateActor(0xff000000, { 0, 0, 0 }, 0, 0x3c); diff --git a/unit/PapyrusObjectReferenceTest.cpp b/unit/PapyrusObjectReferenceTest.cpp index 5e5959cd15..98534e2d7a 100644 --- a/unit/PapyrusObjectReferenceTest.cpp +++ b/unit/PapyrusObjectReferenceTest.cpp @@ -2,10 +2,10 @@ #include #include "ActionListener.h" -#include "EspmGameObject.h" #include "MpObjectReference.h" -#include "PapyrusObjectReference.h" #include "papyrus-vm/Structures.h" +#include "script_classes/PapyrusObjectReference.h" +#include "script_objects/EspmGameObject.h" using Catch::Matchers::ContainsSubstring; diff --git a/unit/PapyrusSkympTest.cpp b/unit/PapyrusSkympTest.cpp index 4ff3d98420..70d0f42512 100644 --- a/unit/PapyrusSkympTest.cpp +++ b/unit/PapyrusSkympTest.cpp @@ -1,8 +1,8 @@ #include "TestUtils.hpp" #include -#include "HeuristicPolicy.h" -#include "PapyrusSkymp.h" +#include "script_classes/PapyrusSkymp.h" +#include "script_compatibility_policies/HeuristicPolicy.h" TEST_CASE("SetDefaultActor should store actor per stack", "[Papyrus][Skymp]") { @@ -12,7 +12,7 @@ TEST_CASE("SetDefaultActor should store actor per stack", "[Papyrus][Skymp]") auto logger = std::make_shared("empty logger"); PapyrusSkymp skymp; - skymp.policy = std::make_shared(logger, &p.worldState); + skymp.policy = std::make_shared(&p.worldState); skymp.SetDefaultActor(VarValue::AttachTestStackId(VarValue::None(), 0), { ac.ToVarValue() }); diff --git a/unit/PapyrusUtilityTest.cpp b/unit/PapyrusUtilityTest.cpp index 3c6a07ea1f..774ec3b76d 100644 --- a/unit/PapyrusUtilityTest.cpp +++ b/unit/PapyrusUtilityTest.cpp @@ -1,15 +1,15 @@ #include "TestUtils.hpp" #include -#include "HeuristicPolicy.h" -#include "PapyrusUtility.h" +#include "script_classes/PapyrusUtility.h" +#include "script_compatibility_policies/HeuristicPolicy.h" TEST_CASE("Wait", "[Papyrus][Utility]") { WorldState wst; PapyrusUtility utility; std::shared_ptr logger; - utility.compatibilityPolicy.reset(new HeuristicPolicy(logger, &wst)); + utility.compatibilityPolicy.reset(new HeuristicPolicy(&wst)); bool waitFinished = false; diff --git a/unit/SaveStorageTest.cpp b/unit/SaveStorageTest.cpp index 254537e669..5fe990b624 100644 --- a/unit/SaveStorageTest.cpp +++ b/unit/SaveStorageTest.cpp @@ -1,8 +1,8 @@ #include "TestUtils.hpp" -#include "AsyncSaveStorage.h" -#include "FileDatabase.h" #include "MpChangeForms.h" +#include "database_drivers/FileDatabase.h" +#include "save_storages/AsyncSaveStorage.h" #include std::shared_ptr MakeSaveStorage() diff --git a/unit/TestUtils.cpp b/unit/TestUtils.cpp index 806e4581f2..f7012b245c 100644 --- a/unit/TestUtils.cpp +++ b/unit/TestUtils.cpp @@ -1,6 +1,5 @@ #include "TestUtils.hpp" #include "FormCallbacks.h" -#include "IPapyrusCompatibilityPolicy.h" #include "MpActor.h" #include "MsgType.h" #include "PartOne.h" @@ -104,14 +103,3 @@ void FakeListener::clear() { ss = std::stringstream(); } - -PapyrusCompatibilityPolicy::PapyrusCompatibilityPolicy(MpActor* ac_) - : ac(ac_) -{ -} - -MpActor* PapyrusCompatibilityPolicy::GetDefaultActor(const char*, const char*, - int32_t) const -{ - return ac; -} diff --git a/unit/TestUtils.hpp b/unit/TestUtils.hpp index 77d65faf48..0539c58c48 100644 --- a/unit/TestUtils.hpp +++ b/unit/TestUtils.hpp @@ -1,9 +1,9 @@ #pragma once #include "FormCallbacks.h" -#include "IPapyrusCompatibilityPolicy.h" #include "MpActor.h" #include "MsgType.h" #include "PartOne.h" +#include "script_compatibility_policies/IPapyrusCompatibilityPolicy.h" #include #include #include @@ -100,14 +100,3 @@ class FakeListener : public PartOne::Listener private: std::stringstream ss; }; - -class PapyrusCompatibilityPolicy : public IPapyrusCompatibilityPolicy -{ -public: - PapyrusCompatibilityPolicy(MpActor* ac_); - - MpActor* GetDefaultActor(const char*, const char*, int32_t) const override; - -private: - MpActor* const ac; -}; From 86647d37bfd1c944b81f0555736d06634c3fbe96 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 21 Oct 2023 02:32:13 +0600 Subject: [PATCH 77/88] dont spawn dead actors --- skymp5-client/src/view/formView.ts | 8 ++++++++ skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/skymp5-client/src/view/formView.ts b/skymp5-client/src/view/formView.ts index 8d40a83ecf..cac3873d5b 100644 --- a/skymp5-client/src/view/formView.ts +++ b/skymp5-client/src/view/formView.ts @@ -54,6 +54,13 @@ export class FormView implements View { } } + // Don't spawn dead actors if not already + if (model.isDead) { + if (this.refrId === 0) { + return; + } + } + // Players with different worldOrCell should be invisible if (model.movement) { const worldOrCell = ObjectReferenceEx.getWorldOrCell(Game.getPlayer() as Actor); @@ -128,6 +135,7 @@ export class FormView implements View { true, true ) as ObjectReference; + this.state = {}; delete this.wasHostedByOther; if (base.getType() !== FormType.NPC) { diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index e942a7427c..d298f54089 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -270,9 +270,15 @@ void MpObjectReference::VisitProperties(const PropertiesVisitor& visitor, if (IsHarvested()) { visitor("isHarvested", "true"); } + if (IsOpen()) { visitor("isOpen", "true"); } + + if (auto actor = dynamic_cast(this); actor && actor->IsDead()) { + visitor("isDead", "true"); + } + if (mode == VisitPropertiesMode::All && !GetInventory().IsEmpty()) { auto inventoryDump = GetInventory().ToJson().dump(); visitor("inventory", inventoryDump.data()); From 7af9f44999fdf9379b04d1b38d6a13c1247a3107 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 21 Oct 2023 02:57:12 +0600 Subject: [PATCH 78/88] fix-eof-rule --- .../cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp b/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp index 673c8451ab..4fdca7941f 100644 --- a/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp +++ b/skymp5-server/cpp/server_guest_lib/database_drivers/DatabaseFactory.cpp @@ -43,4 +43,4 @@ std::shared_ptr DatabaseFactory::Create( } throw std::runtime_error("Unrecognized databaseDriver: " + databaseDriver); -} \ No newline at end of file +} From c51dfdc58650614cd34c2afc531d647bd4e0d55f Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 21 Oct 2023 02:58:08 +0600 Subject: [PATCH 79/88] fix-inc --- skymp5-server/cpp/server_guest_lib/MpForm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpForm.cpp b/skymp5-server/cpp/server_guest_lib/MpForm.cpp index ac6b1ffde2..a3299a56cb 100644 --- a/skymp5-server/cpp/server_guest_lib/MpForm.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpForm.cpp @@ -1,7 +1,7 @@ #include "MpForm.h" -#include "MpFormGameObject.h" #include "WorldState.h" +#include "script_objects/MpFormGameObject.h" MpForm::MpForm() { From 1cd35619b922c72bbb7d1bb6635a7007bee7097e Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 21 Oct 2023 03:16:46 +0600 Subject: [PATCH 80/88] . --- skymp5-client/webpack.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skymp5-client/webpack.config.js b/skymp5-client/webpack.config.js index cb32e4be07..1b5497ba9f 100644 --- a/skymp5-client/webpack.config.js +++ b/skymp5-client/webpack.config.js @@ -9,6 +9,8 @@ module.exports = { entry: [ "./src/index.ts" ], + // SkyrimPlatform ignores embedded source maps at this moment + // devtool: "inline-source-map", devtool: false, output: { path: outDirPath, From 109c78b10fc011b0f7895d692ed5874b3ee69da5 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 21 Oct 2023 03:19:02 +0600 Subject: [PATCH 81/88] reformat --- skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp | 2 +- skymp5-server/cpp/server_guest_lib/PartOne.h | 2 +- .../cpp/server_guest_lib/ScriptVariablesHolder.h | 9 ++++----- .../server_guest_lib/script_classes/PapyrusMessage.cpp | 3 +-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp index 5c0f42b04c..79273f0954 100644 --- a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp +++ b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp @@ -1,9 +1,9 @@ #include "ConsoleCommands.h" -#include "script_objects/EspmGameObject.h" #include "MpActor.h" #include "WorldState.h" #include "papyrus-vm/Utils.h" #include "script_classes/PapyrusObjectReference.h" +#include "script_objects/EspmGameObject.h" ConsoleCommands::Argument::Argument() { diff --git a/skymp5-server/cpp/server_guest_lib/PartOne.h b/skymp5-server/cpp/server_guest_lib/PartOne.h index 41623adac9..52c702029b 100644 --- a/skymp5-server/cpp/server_guest_lib/PartOne.h +++ b/skymp5-server/cpp/server_guest_lib/PartOne.h @@ -1,7 +1,6 @@ #pragma once #include "AnimationSystem.h" #include "GamemodeApi.h" -#include "save_storages/ISaveStorage.h" #include "MpActor.h" #include "Networking.h" #include "NiPoint3.h" @@ -10,6 +9,7 @@ #include "WorldState.h" #include "formulas/IDamageFormula.h" #include "libespm/Loader.h" +#include "save_storages/ISaveStorage.h" #include #include #include diff --git a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h index 2695a275ff..7dfb245265 100644 --- a/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h +++ b/skymp5-server/cpp/server_guest_lib/ScriptVariablesHolder.h @@ -47,11 +47,10 @@ class ScriptVariablesHolder : public IVariablesHolder const std::function& toGlobalId, WorldState* worldState); - void CastProperty(const espm::CombineBrowser& br, - const espm::Property& prop, VarValue* out, - ScriptsCache* scriptsCache, - const std::function& toGlobalId, - WorldState* worldState); + void CastProperty(const espm::CombineBrowser& br, const espm::Property& prop, + VarValue* out, ScriptsCache* scriptsCache, + const std::function& toGlobalId, + WorldState* worldState); static espm::Property::Type GetElementType(espm::Property::Type arrayType); diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp index 0101715428..bb6735a93c 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusMessage.cpp @@ -1,8 +1,7 @@ #include "PapyrusMessage.h" void PapyrusMessage::Register( - VirtualMachine& vm, - std::shared_ptr policy) + VirtualMachine& vm, std::shared_ptr policy) { compatibilityPolicy = policy; From c3d385a73774f12f431d8d5eae938181267b1c6a Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 21 Oct 2023 03:19:41 +0600 Subject: [PATCH 82/88] fix inc --- skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp b/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp index 9cc55a0e58..054f176a9f 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp +++ b/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp @@ -1,9 +1,9 @@ #include "SpSnippetFunctionGen.h" -#include "EspmGameObject.h" -#include "MpFormGameObject.h" #include "SpSnippet.h" #include "WorldState.h" +#include "script_objects/EspmGameObject.h" +#include "script_objects/MpFormGameObject.h" #include #include From 1f6abd8b16ee1a3f7831421bbfc324312c85aab9 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 21 Oct 2023 03:28:24 +0600 Subject: [PATCH 83/88] add 'archives' cfg option docs --- docs/docs_server_configuration_reference.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/docs_server_configuration_reference.md b/docs/docs_server_configuration_reference.md index 99ea14c621..f7256f5290 100644 --- a/docs/docs_server_configuration_reference.md +++ b/docs/docs_server_configuration_reference.md @@ -93,6 +93,24 @@ Absolute paths work but aren't accessible via `uiPort`. External tooling wouldn' } ``` +## archives + +Specify BSA archives that will be loaded by the server. + +At this moment, used only for compiled Papyrus scripts. + +Relative/absolute paths work similar to esp/esm. + +```json5 +{ + // ... + "archives": [ + "Skyrim - Misc.bsa" + ] + // ... +} +``` + ## lang The language, the translation of which will be obtained from the string files located in Data/strings From 957f6fbc228572342f7beb6b5cf195bdb08c1ff2 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Mon, 23 Oct 2023 03:56:08 +0600 Subject: [PATCH 84/88] fix build & upload pex --- unit/MigrationDatabaseTest.cpp | 4 ++-- unit/papyrus_test_files/pex/AAATestObject.pex | Bin 797 -> 797 bytes unit/papyrus_test_files/pex/LatentTest.pex | Bin 1069 -> 1069 bytes unit/papyrus_test_files/pex/OpcodesTest.pex | Bin 12589 -> 12589 bytes 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/MigrationDatabaseTest.cpp b/unit/MigrationDatabaseTest.cpp index da72ca9abf..e771552739 100644 --- a/unit/MigrationDatabaseTest.cpp +++ b/unit/MigrationDatabaseTest.cpp @@ -1,6 +1,6 @@ -#include "MigrationDatabase.h" -#include "FileDatabase.h" +#include "database_drivers/MigrationDatabase.h" #include "TestUtils.hpp" +#include "database_drivers/FileDatabase.h" #include inline std::shared_ptr MakeDatabase(const char* directory) diff --git a/unit/papyrus_test_files/pex/AAATestObject.pex b/unit/papyrus_test_files/pex/AAATestObject.pex index 63616a651040fc2ab6c62fbe1d543ed0a43cfc8a..78f8a142ed5ccf108d374d9593c2f3630ef281ea 100644 GIT binary patch delta 207 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 literal 12589 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 Date: Mon, 23 Oct 2023 03:56:46 +0600 Subject: [PATCH 85/88] Debug.trace added --- .../script_classes/PapyrusDebug.cpp | 25 ++++++++++++++++++- .../script_classes/PapyrusDebug.h | 11 +++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp index 13b7c0013f..ba9a1ba896 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp @@ -4,7 +4,7 @@ #include "script_objects/MpFormGameObject.h" VarValue PapyrusDebug::SendAnimationEvent( - VarValue self, const std::vector& arguments) + VarValue, const std::vector& arguments) { auto targetActor = GetFormPtr(arguments[0]); if (targetActor) { @@ -14,3 +14,26 @@ VarValue PapyrusDebug::SendAnimationEvent( } return VarValue::None(); } + +VarValue PapyrusDebug::Trace(VarValue, const std::vector& arguments) +{ + const char* asTextToPrint = static_cast(arguments[0]); + int aiSeverity = static_cast(arguments[1]); + + std::ignore = aiSeverity; + + spdlog::info("{}", asTextToPrint); + + return VarValue::None(); +} + +void PapyrusDebug::Register( + VirtualMachine& vm, std::shared_ptr policy) +{ + compatibilityPolicy = policy; + + AddStatic(vm, "Notification", &PapyrusDebug::Notification); + AddStatic(vm, "MessageBox", &PapyrusDebug::MessageBox); + AddStatic(vm, "SendAnimationEvent", &PapyrusDebug::SendAnimationEvent); + AddStatic(vm, "Trace", &PapyrusDebug::Trace); +} diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.h b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.h index b9a6bd6ef4..554da0bc19 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.h +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.h @@ -13,15 +13,10 @@ class PapyrusDebug final : public IPapyrusClass VarValue SendAnimationEvent(VarValue self, const std::vector& arguments); - void Register(VirtualMachine& vm, - std::shared_ptr policy) override - { - compatibilityPolicy = policy; + VarValue Trace(VarValue self, const std::vector& arguments); - AddStatic(vm, "Notification", &PapyrusDebug::Notification); - AddStatic(vm, "MessageBox", &PapyrusDebug::MessageBox); - AddStatic(vm, "SendAnimationEvent", &PapyrusDebug::SendAnimationEvent); - } + void Register(VirtualMachine& vm, + std::shared_ptr policy) override; std::shared_ptr compatibilityPolicy; }; From dff9ba6382d789f11a2094060e7c87216d950f6b Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Wed, 25 Oct 2023 17:14:20 +0600 Subject: [PATCH 86/88] add missing header --- .../script_compatibility_policies/IPapyrusCompatibilityPolicy.h | 1 + 1 file changed, 1 insertion(+) diff --git a/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h index 24b16fe7c2..c168967ce3 100644 --- a/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h +++ b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h @@ -1,5 +1,6 @@ #pragma once #include +#include class MpForm; class MpActor; From 57f3f8efaa2acd496f03a1012ca29e5d35095fc4 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Thu, 26 Oct 2023 15:21:01 +0600 Subject: [PATCH 87/88] renamevar --- skymp5-server/cpp/server_guest_lib/ActionListener.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp index 36e7e49cce..52748519be 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp @@ -303,19 +303,19 @@ namespace { VarValue VarValueFromJson(const simdjson::dom::element& parentMsg, const simdjson::dom::element& element) { - static const auto key = JsonPointer("returnValue"); + static const auto kKey = JsonPointer("returnValue"); // TODO: DOUBLE, STRING ... switch (element.type()) { case simdjson::dom::element_type::INT64: case simdjson::dom::element_type::UINT64: { int32_t v; - ReadEx(parentMsg, key, &v); + ReadEx(parentMsg, kKey, &v); return VarValue(v); } case simdjson::dom::element_type::BOOL: { bool v; - ReadEx(parentMsg, key, &v); + ReadEx(parentMsg, kKey, &v); return VarValue(v); } case simdjson::dom::element_type::NULL_VALUE: From 19918b9aebb1574a2bda0b66776c1e4043118ae5 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 4 Nov 2023 19:30:46 +0600 Subject: [PATCH 88/88] reformat --- .../script_compatibility_policies/IPapyrusCompatibilityPolicy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h index c168967ce3..8692e18344 100644 --- a/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h +++ b/skymp5-server/cpp/server_guest_lib/script_compatibility_policies/IPapyrusCompatibilityPolicy.h @@ -1,6 +1,6 @@ #pragma once +#include #include -#include class MpForm; class MpActor;