Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg +++ ps/trunk/binaries/data/config/default.cfg @@ -131,6 +131,9 @@ ; 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 Index: ps/trunk/binaries/data/mods/public/gui/options/options.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.json +++ ps/trunk/binaries/data/mods/public/gui/options/options.json @@ -223,6 +223,17 @@ ] }, { + "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", "tooltip": "Number of shader effects. REQUIRES GAME RESTART", Index: ps/trunk/source/graphics/ObjectBase.cpp =================================================================== --- ps/trunk/source/graphics/ObjectBase.cpp +++ ps/trunk/source/graphics/ObjectBase.cpp @@ -534,6 +534,8 @@ // 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 @@ // 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(); - - // Choose a random number in the interval [0..totalFreq) - int randNum = boost::random::uniform_int_distribution(0, totalFreq-1)(rng); + if (allZero) + totalFreq = (int)grp->size(); - // 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 @@ // 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); } } Index: ps/trunk/source/graphics/ObjectManager.h =================================================================== --- ps/trunk/source/graphics/ObjectManager.h +++ ps/trunk/source/graphics/ObjectManager.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -54,6 +55,16 @@ std::vector ActorVariation; }; + /** + * Governs how random variants are selected by ObjectBase + */ + enum class VariantDiversity + { + NONE, + LIMITED, + FULL + }; + public: // constructor, destructor @@ -96,6 +107,8 @@ */ 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 @@ */ 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 @@ u8 m_QualityLevel = 100; std::unique_ptr m_QualityHook; + VariantDiversity m_VariantDiversity = VariantDiversity::FULL; + std::unique_ptr m_VariantDiversityHook; + template struct Hotloadable { Index: ps/trunk/source/graphics/ObjectManager.cpp =================================================================== --- ps/trunk/source/graphics/ObjectManager.cpp +++ ps/trunk/source/graphics/ObjectManager.cpp @@ -52,6 +52,7 @@ 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 @@ return cmpTerrain->GetCTerrain(); } +CObjectManager::VariantDiversity CObjectManager::GetVariantDiversity() const +{ + return m_VariantDiversity; +} + void CObjectManager::UnloadObjects() { m_Objects.clear(); @@ -191,3 +197,36 @@ // 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); +}