Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A bunch of new features #26

Merged
merged 61 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
f7124a1
Added parsing of ExclusionGroup.
adya Mar 2, 2024
73149be
Implemented ExclusionGroups logic.
adya Mar 10, 2024
d616f29
Refactored form lookup to reuse the same logic for both form filters …
adya Mar 12, 2024
d0c60a1
maintenance
adya Mar 10, 2024
03cdbe5
Finalized Exclusion groups feature.
adya Mar 12, 2024
31edb4f
Fixed first Keyword wasn't preserving relative order.
adya Mar 12, 2024
db83300
maintenance
adya Mar 12, 2024
a8b3dc9
Reverted no longer needed change.
adya Mar 12, 2024
5a95589
Replaced term "exclusion group" with "exclusive group".
adya Mar 12, 2024
1e407a1
Merge pull request #19 from powerof3/feature/exclusive-groups
adya Mar 12, 2024
1bf640d
Added support for random count value.
adya Mar 13, 2024
1b99281
maintenance
adya Mar 14, 2024
f944a82
Merged IndexOrCount into a single variant.
adya Mar 14, 2024
e5eaac4
maintenance
adya Mar 14, 2024
f80555f
Removed unnecessary safety checks to keep maximum performance 😊
adya Mar 14, 2024
7a91d0b
Merge pull request #20 from powerof3/feature/random-item-count
adya Mar 14, 2024
8bb55e5
WIP: Refactored distribution code to reuse in linked distribution.
adya Mar 18, 2024
0065c94
Introduced Linked Distribution initial outline.
adya Mar 18, 2024
933ac13
maintenance
adya Mar 18, 2024
827b34b
Implemented Linked Distribution.
adya Mar 19, 2024
1926498
Implemented Linked Distribution parsing.
adya Mar 24, 2024
a9840f0
Reversed linked items tree that is logged after lookup.
adya Mar 24, 2024
52ac89c
maintenance
adya Mar 24, 2024
489af26
Merge pull request #21 from powerof3/feature/linked-items
adya Mar 25, 2024
7dd0328
Merge branch 'master' into stable
adya Mar 25, 2024
fabda7f
Updated CommonLibSSE to latest.
adya Mar 25, 2024
88e2eba
maintenance
adya Mar 25, 2024
ca2a1d2
Added a new generic record type "Form".
adya Mar 25, 2024
e6d0286
Added a new exception to detect when hinted Form type doesn't match t…
adya Mar 25, 2024
a122403
Added support for all LinkedForm types and type inferring.
adya Mar 25, 2024
51178fa
Fixed indices for packages when inferring form type.
adya Mar 25, 2024
de12de1
maintenance
adya Mar 25, 2024
30d8fa7
Added support for linked death items.
adya Mar 26, 2024
719ef95
maintenance
adya Mar 26, 2024
78dbffb
Added type inference to regular entries.
adya Mar 29, 2024
31216b3
maintenance
adya Mar 29, 2024
7747d4b
Added manual counter for processed forms to properly count inferred f…
adya Mar 30, 2024
a91a1f0
Merge pull request #27 from powerof3/feature/22-allow-specifying-form…
adya Mar 30, 2024
f254f84
Added support for fractional chances.
adya Mar 30, 2024
e155778
maintenance
adya Mar 30, 2024
a879a82
Fixed small typo in comment 😊 clean up.
adya Mar 30, 2024
ef8594d
Merge pull request #30 from powerof3/29-fractional-chances
adya Mar 30, 2024
7a31302
Improved logging of type errors during parsing and lookup.
adya Mar 30, 2024
f545690
maintenance
adya Mar 30, 2024
bde29a0
Isolated Exclusive Groups feature's code in respective files.
adya Mar 30, 2024
d2f9688
Introduced an alias for Path parameter.
adya Mar 30, 2024
bb018e2
Introduced Scope for linked forms.
adya Mar 31, 2024
b2e692c
maintenance
adya Mar 31, 2024
f54db3a
Implemented Local scope for linked forms.
adya Mar 31, 2024
6ff3eda
maintenance
adya Mar 31, 2024
b36708c
Implemented Global scope for linked forms.
adya Mar 31, 2024
f16ce03
maintenance
adya Mar 31, 2024
f54e8a5
Merge pull request #31 from powerof3/23-local-and-global-linked-items
adya Mar 31, 2024
78f3468
Renamed ExclusiveGroup types to not be confused with Linked Forms.
adya Mar 31, 2024
cb7e295
Implemented a way to overwrite outfits with linked outfits.
adya Mar 31, 2024
6c0adc0
Remove redundant RE::FormTypeToString calls.
adya Apr 3, 2024
830e42a
Removed skin from type inferring. Added missing leveled spell inferring.
adya Apr 6, 2024
3dbc218
Added also an NPC's baseTemplateForm to list of IDs.
adya Apr 6, 2024
6def751
Allowed specifying LevSpell entries directly in Spell.
adya Apr 7, 2024
85afb05
maintenance
adya Apr 7, 2024
2f8bc13
Merge pull request #39 from powerof3/36-merge-levspell-into-spell-entry
adya Apr 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions SPID/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ if (COPY_BUILD)
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PROJECT_NAME}> ${SkyrimPath}/SKSE/Plugins/
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PDB_FILE:${PROJECT_NAME}> ${SkyrimPath}/SKSE/Plugins/
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PROJECT_NAME}> ${SkyrimPath}/Data/SKSE/Plugins/
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PDB_FILE:${PROJECT_NAME}> ${SkyrimPath}/Data/SKSE/Plugins/
)
else ()
message(
Expand Down
2 changes: 2 additions & 0 deletions SPID/cmake/headerlist.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ set(headers ${headers}
include/Distribute.h
include/DistributeManager.h
include/DistributePCLevelMult.h
include/ExclusiveGroups.h
include/FormData.h
include/KeywordDependencies.h
include/LinkedDistribution.h
include/LogBuffer.h
include/LookupConfigs.h
include/LookupFilters.h
Expand Down
2 changes: 2 additions & 0 deletions SPID/cmake/sourcelist.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ set(sources ${sources}
src/Distribute.cpp
src/DistributeManager.cpp
src/DistributePCLevelMult.cpp
src/ExclusiveGroups.cpp
src/FormData.cpp
src/KeywordDependencies.cpp
src/LinkedDistribution.cpp
src/LogBuffer.cpp
src/LookupConfigs.cpp
src/LookupFilters.cpp
Expand Down
35 changes: 33 additions & 2 deletions SPID/include/Defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ struct Range
return value >= min && value <= max;
}

[[nodiscard]] bool IsExact() const
{
return min == max;
}

[[nodiscard]] T GetRandom() const
{
return IsExact() ? min : RNG().generate<T>(min, max);
}

// members
T min{ std::numeric_limits<T>::min() };
T max{ std::numeric_limits<T>::max() };
Expand Down Expand Up @@ -83,8 +93,29 @@ struct Traits
std::optional<bool> teammate{};
};

using IdxOrCount = std::int32_t;
using Chance = std::uint32_t;
using Path = std::string;

using Index = std::int32_t;
using Count = std::int32_t;
using RandomCount = Range<Count>;
using IndexOrCount = std::variant<Index, RandomCount>;

/// <summary>
/// A chance that is represented as a decimal value between 0 and 1.
/// For example, 0.5 would be 50%.
///
/// This one is used in a processed Data for filtering.
/// </summary>
using DecimalChance = double;

/// <summary>
/// A chance that is represented as a percent value between 0 and 100.
/// It also can be decimal, but would describe fraction of a percent.
/// So that 0.5 would be 0.5%.
///
/// This is used during parsing of INI files.
/// </summary>
using PercentChance = double;

/// A standardized way of converting any object to string.
///
Expand Down
180 changes: 68 additions & 112 deletions SPID/include/Distribute.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace Distribute
auto result = a_formData.filters.PassedFilters(a_npcData);

if (result != Filter::Result::kPass) {
if (result == Filter::Result::kFailRNG && hasLevelFilters) {
if (hasLevelFilters && result == Filter::Result::kFailRNG) {
pcLevelMultManager->InsertRejectedEntry(a_input, distributedFormID, index);
}
return false;
Expand Down Expand Up @@ -70,89 +70,84 @@ namespace Distribute
void add_item(RE::Actor* a_actor, RE::TESBoundObject* a_item, std::uint32_t a_itemCount);
}

using namespace Forms;

#pragma region Packages, Death Items
// old method (distributing one by one)
// for now, only packages/death items use this
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
const PCLevelMult::Input& a_input,
std::function<bool(Form*, IdxOrCount)> a_callback)
const NPCData& a_npcData,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<bool(Form*, IndexOrCount)> a_callback,
DistributedForms* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);

for (auto& formData : vec) {
if (detail::passed_filters(a_npcData, a_input, formData)) {
for (auto& formData : forms) {
if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData)) {
if (accumulatedForms) {
accumulatedForms->insert({ formData.form, formData.path });
}
a_callback(formData.form, formData.idxOrCount);
++formData.npcCount;
}
}
}
#pragma endregion

// outfits/sleep outfits
// skins
#pragma region Outfits, Sleep Outfits, Skins
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
const PCLevelMult::Input& a_input,
std::function<bool(Form*)> a_callback)
const NPCData& a_npcData,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<bool(Form*)> a_callback,
DistributedForms* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);

for (auto& formData : vec) { // Vector is reversed in FinishLookupForms
if (detail::passed_filters(a_npcData, a_input, formData) && a_callback(formData.form)) {
++formData.npcCount;
break;
}
}
}

// outfits/sleep outfits
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
std::function<bool(Form*)> a_callback)
{
auto& vec = a_distributables.GetForms(false);

for (auto& formData : vec) { // Vector is reversed in FinishLookupForms
if (detail::passed_filters(a_npcData, formData) && a_callback(formData.form)) {
for (auto& formData : forms) { // Vector is reversed in FinishLookupForms
if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData) && a_callback(formData.form)) {
if (accumulatedForms) {
accumulatedForms->insert({ formData.form, formData.path });
}
++formData.npcCount;
break;
}
}
}
#pragma endregion

// items
#pragma region Items
// countable items
template <class Form>
void for_each_form(
const NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
const PCLevelMult::Input& a_input,
std::function<bool(std::map<Form*, IdxOrCount>&)> a_callback)
const NPCData& a_npcData,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<bool(std::map<Form*, Count>&)> a_callback,
DistributedForms* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);
std::map<Form*, Count> collectedForms{};

if (vec.empty()) {
return;
}

std::map<Form*, IdxOrCount> collectedForms{};

for (auto& formData : vec) {
if (detail::passed_filters(a_npcData, a_input, formData)) {
for (auto& formData : forms) {
if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData)) {
// TODO: Safe guard getting RandomCount and if for any reason there is a PackageIndex, default it to count = 1
auto count = std::get<RandomCount>(formData.idxOrCount).GetRandom();
if (auto leveledItem = formData.form->As<RE::TESLevItem>()) {
auto level = a_npcData.GetLevel();
RE::BSScrapArray<RE::CALCED_OBJECT> calcedObjects{};

leveledItem->CalculateCurrentFormList(level, formData.idxOrCount, calcedObjects, 0, true);
leveledItem->CalculateCurrentFormList(level, count, calcedObjects, 0, true);
for (auto& calcObj : calcedObjects) {
collectedForms[static_cast<RE::TESBoundObject*>(calcObj.form)] += calcObj.count;
if (accumulatedForms) {
accumulatedForms->insert({ calcObj.form, formData.path });
}
}
} else {
collectedForms[formData.form] += formData.idxOrCount;
collectedForms[formData.form] += count;
if (accumulatedForms) {
accumulatedForms->insert({ formData.form, formData.path });
}
}
++formData.npcCount;
}
Expand All @@ -162,53 +157,56 @@ namespace Distribute
a_callback(collectedForms);
}
}
#pragma endregion

#pragma region Spells, Perks, Shouts, Keywords
// spells, perks, shouts, keywords
// forms that can be added to
template <class Form>
void for_each_form(
NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
Forms::DataVec<Form>& forms,
const PCLevelMult::Input& a_input,
std::function<void(const std::vector<Form*>&)> a_callback)
std::function<void(const std::vector<Form*>&)> a_callback,
DistributedForms* accumulatedForms = nullptr)
{
auto& vec = a_distributables.GetForms(a_input.onlyPlayerLevelEntries);

if (vec.empty()) {
return;
}

const auto npc = a_npcData.GetNPC();

std::vector<Form*> collectedForms{};
Set<RE::FormID> collectedFormIDs{};
Set<RE::FormID> collectedLeveledFormIDs{};

collectedForms.reserve(vec.size());
collectedFormIDs.reserve(vec.size());
collectedLeveledFormIDs.reserve(vec.size());
collectedForms.reserve(forms.size());
collectedFormIDs.reserve(forms.size());
collectedLeveledFormIDs.reserve(forms.size());

for (auto& formData : vec) {
for (auto& formData : forms) {
auto form = formData.form;
auto formID = form->GetFormID();
if (collectedFormIDs.contains(formID)) {
continue;
}
if constexpr (std::is_same_v<RE::BGSKeyword, Form>) {
if (detail::passed_filters(a_npcData, a_input, formData) && a_npcData.InsertKeyword(form->GetFormEditorID())) {
if (!a_npcData.HasMutuallyExclusiveForm(form) && detail::passed_filters(a_npcData, a_input, formData) && a_npcData.InsertKeyword(form->GetFormEditorID())) {
collectedForms.emplace_back(form);
collectedFormIDs.emplace(formID);
if (formData.filters.HasLevelFilters()) {
collectedLeveledFormIDs.emplace(formID);
}
if (accumulatedForms) {
accumulatedForms->insert({ form, formData.path });
}
++formData.npcCount;
}
} else {
if (detail::passed_filters(a_npcData, a_input, formData) && !detail::has_form(npc, form) && collectedFormIDs.emplace(formID).second) {
if (!a_npcData.HasMutuallyExclusiveForm(form) && detail::passed_filters(a_npcData, a_input, formData) && !detail::has_form(npc, form) && collectedFormIDs.emplace(formID).second) {
collectedForms.emplace_back(form);
if (formData.filters.HasLevelFilters()) {
collectedLeveledFormIDs.emplace(formID);
}
if (accumulatedForms) {
accumulatedForms->insert({ form, formData.path });
}
++formData.npcCount;
}
}
Expand All @@ -221,52 +219,10 @@ namespace Distribute
}
}
}
#pragma endregion

template <class Form>
void for_each_form(
NPCData& a_npcData,
Forms::Distributables<Form>& a_distributables,
std::function<void(const std::vector<Form*>&)> a_callback)
{
const auto& vec = a_distributables.GetForms(false);

if (vec.empty()) {
return;
}

const auto npc = a_npcData.GetNPC();

std::vector<Form*> collectedForms{};
Set<RE::FormID> collectedFormIDs{};

collectedForms.reserve(vec.size());
collectedFormIDs.reserve(vec.size());

for (auto& formData : vec) {
auto form = formData.form;
auto formID = form->GetFormID();
if (collectedFormIDs.contains(formID)) {
continue;
}
if constexpr (std::is_same_v<RE::BGSKeyword, Form>) {
if (detail::passed_filters(a_npcData, formData) && a_npcData.InsertKeyword(form->GetFormEditorID())) {
collectedForms.emplace_back(form);
collectedFormIDs.emplace(formID);
++formData.npcCount;
}
} else {
if (detail::passed_filters(a_npcData, formData) && !detail::has_form(npc, form) && collectedFormIDs.emplace(formID).second) {
collectedForms.emplace_back(form);
++formData.npcCount;
}
}
}

if (!collectedForms.empty()) {
a_callback(collectedForms);
}
}
void Distribute(NPCData& npcData, const PCLevelMult::Input& input);
void Distribute(NPCData& npcData, bool onlyLeveledEntries);

void Distribute(NPCData& a_npcData, const PCLevelMult::Input& a_input);
void Distribute(NPCData& a_npcData, bool a_onlyLeveledEntries);
void DistributeDeathItems(NPCData& npcData, const PCLevelMult::Input& input);
}
Loading
Loading