diff --git a/src/ObjectProperties.cpp b/src/ObjectProperties.cpp index 8ba9744..fd38f81 100644 --- a/src/ObjectProperties.cpp +++ b/src/ObjectProperties.cpp @@ -1,7 +1,7 @@ #include "ObjectProperties.h" -RandValueParams::RandValueParams(CHANCE_TYPE a_type, const RE::TESObjectREFR* a_ref) : - rng(a_type, a_ref) +RandValueParams::RandValueParams(Chance a_chance, const RE::TESObjectREFR* a_ref) : + rng(a_chance, a_ref) {} FloatRange::FloatRange(const std::string& a_str) @@ -139,15 +139,15 @@ bool ObjectProperties::IsValid() const return location || rotation || refScale || recordFlagsSet != 0 || recordFlagsUnset != 0; } -void ObjectProperties::SetChanceType(CHANCE_TYPE a_type) +void ObjectProperties::SetChance(Chance a_chance) { - chanceType = a_type; + chance = a_chance; } void ObjectProperties::SetTransform(RE::TESObjectREFR* a_refr) const { if (location || rotation || refScale) { - RandValueParams params(chanceType, a_refr); + RandValueParams params(chance, a_refr); if (location) { location->SetTransform(a_refr->data.location, params); } diff --git a/src/ObjectProperties.h b/src/ObjectProperties.h index d2ddae5..1bb9370 100644 --- a/src/ObjectProperties.h +++ b/src/ObjectProperties.h @@ -4,7 +4,7 @@ struct RandValueParams { - RandValueParams(CHANCE_TYPE a_type, const RE::TESObjectREFR* a_ref); + RandValueParams(Chance a_chance, const RE::TESObjectREFR* a_ref); BOS_RNG rng{}; bool clamp{ false }; @@ -72,7 +72,7 @@ class ObjectProperties bool IsValid() const; - void SetChanceType(CHANCE_TYPE a_type); + void SetChance(Chance a_chance); void SetTransform(RE::TESObjectREFR* a_refr) const; void SetRecordFlags(RE::TESObjectREFR* a_refr) const; @@ -80,7 +80,7 @@ class ObjectProperties void assign_record_flags(const std::string& a_str, bool a_unsetFlag); // members - CHANCE_TYPE chanceType{ CHANCE_TYPE::kRefHash }; + Chance chance{}; std::optional location{ std::nullopt }; std::optional rotation{ std::nullopt }; diff --git a/src/PCH.h b/src/PCH.h index 01e6a62..f16b1d2 100644 --- a/src/PCH.h +++ b/src/PCH.h @@ -45,9 +45,12 @@ template using Map = ankerl::unordered_dense::map; template using Set = ankerl::unordered_dense::set; +template +using OrderedSet = std::set; using FormIDSet = Set; using FormIDOrSet = std::variant; +using FormIDOrderedSet = OrderedSet; template using FormIDMap = Map; diff --git a/src/RNG.cpp b/src/RNG.cpp index 2c3b332..d3f05bc 100644 --- a/src/RNG.cpp +++ b/src/RNG.cpp @@ -1,7 +1,7 @@ #include "RNG.h" -BOS_RNG::BOS_RNG(CHANCE_TYPE a_type, const RE::TESObjectREFR* a_ref) : - type(a_type) +BOS_RNG::BOS_RNG(Chance a_chance, const RE::TESObjectREFR* a_ref) : + type(a_chance.chanceType) { switch (type) { case CHANCE_TYPE::kRefHash: @@ -27,11 +27,19 @@ BOS_RNG::BOS_RNG(CHANCE_TYPE a_type, const RE::TESObjectREFR* a_ref) : } } break; + case CHANCE_TYPE::kRandom: + seed = a_chance.seed; + break; default: break; } } +BOS_RNG::BOS_RNG(Chance a_chance) : + type(a_chance.chanceType), + seed(a_chance.seed) + {} + Chance::Chance(const std::string& a_str) { if (distribution::is_valid_entry(a_str)) { @@ -43,8 +51,11 @@ Chance::Chance(const std::string& a_str) } else { chanceType = CHANCE_TYPE::kRefHash; } + if (srell::cmatch match; srell::regex_search(a_str.c_str(), match, regex::generic)) { - chanceValue = string::to_num(match[1].str()); + const auto chanceOptions = string::split(match[1].str(), ","); + chanceValue = string::to_num(chanceOptions[0]); + seed = chanceOptions.size() > 1 ? string::to_num(chanceOptions[1]) : 0; } } } @@ -53,7 +64,7 @@ Chance::Chance(const std::string& a_str) bool Chance::PassedChance(const RE::TESObjectREFR* a_ref) const { if (chanceValue < 100.0f) { - BOS_RNG rng(chanceType, a_ref); + BOS_RNG rng(*this, a_ref); if (const auto rngValue = rng.generate(0.0f, 100.0f); rngValue > chanceValue) { return false; } diff --git a/src/RNG.h b/src/RNG.h index f019091..1793f86 100644 --- a/src/RNG.h +++ b/src/RNG.h @@ -1,22 +1,38 @@ #pragma once -struct BOS_RNG +enum class CHANCE_TYPE +{ + kRandom, + kRefHash, + kLocationHash +}; + +struct Chance { public: - enum class CHANCE_TYPE - { - kRandom, - kRefHash, - kLocationHash - }; + Chance() = default; + explicit Chance(const std::string& a_str); + bool PassedChance(const RE::TESObjectREFR* a_ref) const; + + // members + CHANCE_TYPE chanceType{ CHANCE_TYPE::kRefHash }; + float chanceValue{ 100.0f }; + std::uint64_t seed{ 0 }; + +}; + +struct BOS_RNG +{ +public: BOS_RNG() = default; - BOS_RNG(CHANCE_TYPE a_type, const RE::TESObjectREFR* a_ref); + BOS_RNG(Chance a_chance, const RE::TESObjectREFR* a_ref); + BOS_RNG(Chance a_chance); template T generate(T a_min, T a_max) const { - if (type == CHANCE_TYPE::kRandom) { + if (type == CHANCE_TYPE::kRandom && seed == 0) { return SeedRNG().generate(a_min, a_max); } return SeedRNG(seed).generate(a_min, a_max); @@ -24,20 +40,5 @@ struct BOS_RNG // members CHANCE_TYPE type; - std::uint64_t seed; -}; - -using CHANCE_TYPE = BOS_RNG::CHANCE_TYPE; - -struct Chance -{ -public: - Chance() = default; - explicit Chance(const std::string& a_str); - - bool PassedChance(const RE::TESObjectREFR* a_ref) const; - - // members - CHANCE_TYPE chanceType{ CHANCE_TYPE::kRefHash }; - float chanceValue{ 100.0f }; + std::uint64_t seed { 0 }; }; diff --git a/src/SwapData.cpp b/src/SwapData.cpp index d0b99c5..3871562 100644 --- a/src/SwapData.cpp +++ b/src/SwapData.cpp @@ -8,7 +8,7 @@ namespace FormSwap record(a_input.record), path(a_input.path) { - properties.SetChanceType(chance.chanceType); + properties.SetChance(chance); } bool ObjectData::HasValidProperties(const RE::TESObjectREFR* a_ref) const @@ -49,7 +49,7 @@ namespace FormSwap auto& set = std::get(formIDSet); const auto setEnd = std::distance(set.begin(), set.end()) - 1; - const auto randIt = BOS_RNG(chance.chanceType, a_ref).generate(0, setEnd); + const auto randIt = BOS_RNG(chance, a_ref).generate(0, setEnd); return RE::TESForm::LookupByID(*std::next(set.begin(), randIt)); } @@ -92,6 +92,33 @@ namespace FormSwap } else { logger::error("\t\t\t\tfail : [{}] (SWAP formID not found)", a_str); } + } else if (const auto baseFormIDs = util::GetFormIDOrderedSet(formPair[0]); !baseFormIDs.empty()) { + if (auto swapFormIDs = util::GetFormIDOrderedSet(formPair[1]); !swapFormIDs.empty()) { + if (baseFormIDs.size() > swapFormIDs.size()) { + logger::error("\t\t\t\tfail : [{}] (SWAP formID set must be equal or larger than BASE formID set)", a_str); + return; + } + auto properties = formPair.size() > 2 ? formPair[2] : std::string{}; + auto chance = formPair.size() > 3 ? formPair[3] : std::string{}; + + auto a_chance = Chance(chance); + auto a_rng = BOS_RNG(a_chance); + + // randomly assign each baseFormID to a unique swapFormID + for (auto itBaseFormID : baseFormIDs) { + const auto setEnd = std::distance(swapFormIDs.begin(), swapFormIDs.end()) - 1; + const auto randIt = a_rng.generate(0, setEnd); + auto swapFormID = swapFormIDs.extract(*std::next(swapFormIDs.begin(), randIt)); + if (swapFormID) { + const Input input(properties, std::string{}, a_str, a_path); + SwapFormData swapFormData(swapFormID.value(), input); + + a_func(itBaseFormID, swapFormData); + } + } + } else { + logger::error("\t\t\t\tfail : [{}] (SWAP formID set not found)", a_str); + } } else { logger::error("\t\t\t\tfail : [{}] (BASE formID not found)", a_str); } diff --git a/src/Util.cpp b/src/Util.cpp index d3c357f..d54039c 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -55,4 +55,21 @@ namespace util return GetFormID(a_str); } } + + FormIDOrderedSet GetFormIDOrderedSet(const std::string& a_str) + { + FormIDOrderedSet set; + if (a_str.contains(",")) { + const auto IDStrs = string::split(a_str, ","); + for (auto& IDStr : IDStrs) { + if (auto formID = GetFormID(IDStr); formID != 0) { + set.emplace(formID); + } else { + logger::error("\t\t\tfailed to process {} (formID not found)", IDStr); + } + } + return set; + } + return set; + } } diff --git a/src/Util.h b/src/Util.h index 06a8912..dd84f1e 100644 --- a/src/Util.h +++ b/src/Util.h @@ -13,4 +13,5 @@ namespace util RE::FormID GetFormID(const std::string& a_str); FormIDOrSet GetSwapFormID(const std::string& a_str); + FormIDOrderedSet GetFormIDOrderedSet(const std::string& a_str); }