diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index c8ed7b1fcf..c4cf0658a1 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -131,6 +131,9 @@ sharpness = 0.3 ; Quality used for actors. max_actor_quality=200 +; Whether or not actor variants are selected randomly, possible values are "full", "limited", "none". +variant_diversity = "full" + ; Quality level of shader effects (set to 10 to display all effects) materialmgr.quality = 2.0 diff --git a/binaries/data/mods/public/gui/options/options.json b/binaries/data/mods/public/gui/options/options.json index d1875791f8..a346b4ef55 100644 --- a/binaries/data/mods/public/gui/options/options.json +++ b/binaries/data/mods/public/gui/options/options.json @@ -222,6 +222,17 @@ { "value": 200, "label": "High", "tooltip": "High quality models." } ] }, + { + "type": "dropdown", + "label": "Model appearance randomization", + "tooltip": "Randomize the appearance of entities. Disabling gives a small performance improvement.", + "config": "variant_diversity", + "list": [ + { "value": "none", "label": "None", "tooltip": "Entities will all look the same." }, + { "value": "limited", "label": "Limited", "tooltip": "Entities will be less diverse." }, + { "value": "full", "label": "Normal", "tooltip": "Entities appearance is randomized normally." } + ] + }, { "type": "slider", "label": "Shader effects", diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index 9794d80fb2..1c02337a0b 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -534,6 +534,8 @@ std::set CObjectBase::CalculateRandomRemainingSelections(rng_t& rng, const // When choosing randomly, make use of each variant's frequency. If all // variants have frequency 0, treat them as if they were 1. + CObjectManager::VariantDiversity diversity = m_ObjectManager.GetVariantDiversity(); + for (std::vector >::const_iterator grp = m_VariantGroups.begin(); grp != m_VariantGroups.end(); ++grp) @@ -583,18 +585,17 @@ std::set CObjectBase::CalculateRandomRemainingSelections(rng_t& rng, const // Someone might be silly and set all variants to have freq==0, in // which case we just pretend they're all 1 bool allZero = (totalFreq == 0); - if (allZero) totalFreq = (int)grp->size(); + if (allZero) + totalFreq = (int)grp->size(); - // Choose a random number in the interval [0..totalFreq) - int randNum = boost::random::uniform_int_distribution(0, totalFreq-1)(rng); - - // and use that to choose one of the variants + // Choose a random number in the interval [0..totalFreq) to choose one of the variants. + // If the diversity is "none", force 0 to return the first valid variant. + int randNum = diversity == CObjectManager::VariantDiversity::NONE ? 0 : boost::random::uniform_int_distribution(0, totalFreq-1)(rng); for (size_t i = 0; i < grp->size(); ++i) { randNum -= (allZero ? 1 : (*grp)[i].m_Frequency); if (randNum < 0) { - remainingSelections.insert((*grp)[i].m_VariantName); // (If this change to 'remainingSelections' interferes with earlier choices, then // we'll get some non-fatal inconsistencies that just break the randomness. But that // shouldn't happen, much.) @@ -603,12 +604,23 @@ std::set CObjectBase::CalculateRandomRemainingSelections(rng_t& rng, const // and "a" for the second, then the selection of "a" from the second group will // cause "a" to be used in the first instead of the "b"). match = (int)i; + + // In limited diversity, somewhat-randomly continue. This cuts variants to about a third, + // though not quite because we must pick a variant so the actual probability is more complex. + // (It's also dependent on actor files not containing too many 0-frequency variants) + if (diversity == CObjectManager::VariantDiversity::LIMITED && (i % 3 != 0)) + { + // Reset to 0 or we'll just pick every subsequent variant. + randNum = 0; + continue; + } break; } } - ENSURE(randNum < 0); + ENSURE(match != -1); // This should always succeed; otherwise it // wouldn't have chosen any of the variants. + remainingSelections.insert((*grp)[match].m_VariantName); } } diff --git a/source/graphics/ObjectManager.cpp b/source/graphics/ObjectManager.cpp index 4791a3998d..f567ef8809 100644 --- a/source/graphics/ObjectManager.cpp +++ b/source/graphics/ObjectManager.cpp @@ -52,6 +52,7 @@ CObjectManager::CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& RegisterFileReloadFunc(ReloadChangedFileCB, this); m_QualityHook = std::make_unique(g_ConfigDB.RegisterHookAndCall("max_actor_quality", [this]() { ActorQualityChanged(); })); + m_VariantDiversityHook = std::make_unique(g_ConfigDB.RegisterHookAndCall("variant_diversity", [this]() { VariantDiversityChanged(); })); if (!CXeromyces::AddValidator(g_VFS, "actor", "art/actors/actor.rng")) LOGERROR("CObjectManager: failed to load actor grammar file 'art/actors/actor.rng'"); @@ -143,6 +144,11 @@ CTerrain* CObjectManager::GetTerrain() return cmpTerrain->GetCTerrain(); } +CObjectManager::VariantDiversity CObjectManager::GetVariantDiversity() const +{ + return m_VariantDiversity; +} + void CObjectManager::UnloadObjects() { m_Objects.clear(); @@ -191,3 +197,36 @@ void CObjectManager::ActorQualityChanged() // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise. m_Simulation.Interpolate(0.f, 0.f, 0.f); } + +void CObjectManager::VariantDiversityChanged() +{ + CStr value; + CFG_GET_VAL("variant_diversity", value); + VariantDiversity variantDiversity = VariantDiversity::FULL; + if (value == "none") + variantDiversity = VariantDiversity::NONE; + else if (value == "limited") + variantDiversity = VariantDiversity::LIMITED; + // Otherwise assume full. + + if (variantDiversity == m_VariantDiversity) + return; + + m_VariantDiversity = variantDiversity; + + // Mark old entries as outdated so we don't reload them from the cache. + for (std::pair>& object : m_Objects) + object.second.outdated = true; + + // Reload actors. + for (std::pair>& actor : m_ActorDefs) + actor.second.outdated = true; + + // Reload visual actor components. + const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual); + for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit) + static_cast(eit->second)->Hotload(); + + // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise. + m_Simulation.Interpolate(0.f, 0.f, 0.f); +} diff --git a/source/graphics/ObjectManager.h b/source/graphics/ObjectManager.h index 3920b42271..eb0b1f92ba 100644 --- a/source/graphics/ObjectManager.h +++ b/source/graphics/ObjectManager.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -54,6 +55,16 @@ public: std::vector ActorVariation; }; + /** + * Governs how random variants are selected by ObjectBase + */ + enum class VariantDiversity + { + NONE, + LIMITED, + FULL + }; + public: // constructor, destructor @@ -96,6 +107,8 @@ public: */ CTerrain* GetTerrain(); + VariantDiversity GetVariantDiversity() const; + /** * Reload any scripts that were loaded from the given filename. * (This is used to implement hotloading.) @@ -107,6 +120,11 @@ public: */ void ActorQualityChanged(); + /** + * Reload actors. Used when changing the variant diversity. + */ + void VariantDiversityChanged(); + CMeshManager& m_MeshManager; CSkeletonAnimManager& m_SkeletonAnimManager; CSimulation2& m_Simulation; @@ -114,6 +132,9 @@ public: u8 m_QualityLevel = 100; std::unique_ptr m_QualityHook; + VariantDiversity m_VariantDiversity = VariantDiversity::FULL; + std::unique_ptr m_VariantDiversityHook; + template struct Hotloadable {