Index: ps/trunk/source/graphics/ObjectBase.h =================================================================== --- ps/trunk/source/graphics/ObjectBase.h +++ ps/trunk/source/graphics/ObjectBase.h @@ -229,6 +229,11 @@ VfsPath GetPathname() const { return m_Pathname; } + /** + * Return a list of selections specifying a particular variant in all groups, based on the seed. + */ + std::set PickSelectionsAtRandom(uint32_t seed) const; + // Interface accessible from CObjectManager / CObjectBase protected: /** Index: ps/trunk/source/graphics/ObjectBase.cpp =================================================================== --- ps/trunk/source/graphics/ObjectBase.cpp +++ ps/trunk/source/graphics/ObjectBase.cpp @@ -781,6 +781,14 @@ { } +std::set CActorDef::PickSelectionsAtRandom(uint32_t seed) const +{ + // Use the selections from the highest quality actor - this lets artists maintain compatibility (or not) + // when going to lower quality levels. + std::vector> noSelections; + return GetBase(255)->CalculateRandomRemainingSelections(seed, noSelections); +} + std::vector CActorDef::QualityLevels() const { std::vector splits; Index: ps/trunk/source/graphics/Unit.h =================================================================== --- ps/trunk/source/graphics/Unit.h +++ ps/trunk/source/graphics/Unit.h @@ -42,11 +42,10 @@ CUnit(CObjectManager& objectManager, const CActorDef& actor, uint32_t seed); public: - // Attempt to create a unit with the given actor, with a set of - // suggested selections (with the rest being randomised using the - // given random seed). + // Attempt to create a unit with the given actor. + // If specific selections are wanted, call SetEntitySelection or SetActorSelections after creation. // Returns NULL on failure. - static CUnit* Create(const CStrW& actorName, uint32_t seed, const std::set& selections, CObjectManager& objectManager); + static CUnit* Create(const CStrW& actorName, uint32_t seed, CObjectManager& objectManager); // destructor ~CUnit(); @@ -77,6 +76,9 @@ const std::set& GetActorSelections() const { return m_ActorSelections; } + /** + * Overwrite the seed-selected actor selections. Likely only useful for Atlas or debugging. + */ void SetActorSelections(const std::set& selections); private: @@ -96,9 +98,11 @@ // seed used when creating unit uint32_t m_Seed; - // actor-level selections for this unit + // Actor-level selections for this unit. This is normally set at init time, + // so that we always re-use the same aesthetic variants. + // These have lower priority than entity-level selections. std::set m_ActorSelections; - // entity-level selections for this unit + // Entity-level selections for this unit (used for e.g. animation variants). std::map m_EntitySelections; // object manager which looks after this unit's objectentry Index: ps/trunk/source/graphics/Unit.cpp =================================================================== --- ps/trunk/source/graphics/Unit.cpp +++ ps/trunk/source/graphics/Unit.cpp @@ -31,6 +31,15 @@ CUnit::CUnit(CObjectManager& objectManager, const CActorDef& actor, uint32_t seed) : m_ID(INVALID_ENTITY), m_ObjectManager(objectManager), m_Actor(actor), m_Seed(seed), m_Animation(nullptr) { + /** + * When entity selections change, we might end up with a different layout in terms of variants/groups, + * which means the random key calculation might end up with different results for the same seed. + * This is bad, as it means entities randomly change appearence when changing e.g. animation. + * To fix this, we'll initially pick a random and complete specification based on our seed, + * and then pass that as the lowest priority selections. Thus, if the actor files are properly specified, + * we can ensure that the entities will look the same no matter what happens. + */ + SetActorSelections(m_Actor.PickSelectionsAtRandom(m_Seed)); // Calls ReloadObject(). } CUnit::~CUnit() @@ -39,14 +48,13 @@ delete m_Model; } -CUnit* CUnit::Create(const CStrW& actorName, uint32_t seed, const std::set& selections, CObjectManager& objectManager) +CUnit* CUnit::Create(const CStrW& actorName, uint32_t seed, CObjectManager& objectManager) { auto [success, actor] = objectManager.FindActorDef(actorName); UNUSED2(success); CUnit* unit = new CUnit(objectManager, actor, seed); - unit->SetActorSelections(selections); // Calls ReloadObject(). if (!unit->m_Model) { delete unit; Index: ps/trunk/source/graphics/UnitManager.h =================================================================== --- ps/trunk/source/graphics/UnitManager.h +++ ps/trunk/source/graphics/UnitManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -50,7 +50,7 @@ void DeleteAll(); // creates a new unit and adds it to the world - CUnit* CreateUnit(const CStrW& actorName, uint32_t seed, const std::set& selections); + CUnit* CreateUnit(const CStrW& actorName, uint32_t seed); // return the units const std::vector& GetUnits() const { return m_Units; } Index: ps/trunk/source/graphics/UnitManager.cpp =================================================================== --- ps/trunk/source/graphics/UnitManager.cpp +++ ps/trunk/source/graphics/UnitManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -87,12 +87,12 @@ /////////////////////////////////////////////////////////////////////////////// // CreateUnit: create a new unit and add it to the world -CUnit* CUnitManager::CreateUnit(const CStrW& actorName, uint32_t seed, const std::set& selections) +CUnit* CUnitManager::CreateUnit(const CStrW& actorName, uint32_t seed) { if (! m_ObjectManager) return NULL; - CUnit* unit = CUnit::Create(actorName, seed, selections, *m_ObjectManager); + CUnit* unit = CUnit::Create(actorName, seed, *m_ObjectManager); if (unit) AddUnit(unit); return unit; Index: ps/trunk/source/simulation2/components/CCmpProjectileManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpProjectileManager.cpp +++ ps/trunk/source/simulation2/components/CCmpProjectileManager.cpp @@ -202,8 +202,7 @@ projectile.origin = launchPoint; - std::set selections; - projectile.unit = GetSimContext().GetUnitManager().CreateUnit(actorName, m_ActorSeed++, selections); + projectile.unit = GetSimContext().GetUnitManager().CreateUnit(actorName, m_ActorSeed++); if (!projectile.unit) // The error will have already been logged return currentId; @@ -302,8 +301,7 @@ quat.ToMatrix(transform); transform.Translate(m_Projectiles[i].pos); - std::set selections; - CUnit* unit = GetSimContext().GetUnitManager().CreateUnit(m_Projectiles[i].impactActorName, m_ActorSeed++, selections); + CUnit* unit = GetSimContext().GetUnitManager().CreateUnit(m_Projectiles[i].impactActorName, m_ActorSeed++); unit->GetModel().SetTransform(transform); ProjectileImpactAnimation projectileImpactAnimation; Index: ps/trunk/source/simulation2/components/CCmpVisualActor.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpVisualActor.cpp +++ ps/trunk/source/simulation2/components/CCmpVisualActor.cpp @@ -598,11 +598,10 @@ if (!GetSimContext().HasUnitManager()) return; - std::set selections; std::wstring actorName = m_ActorName; if (actorName.find(L".xml") == std::wstring::npos) actorName += L".xml"; - m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed(), selections); + m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed()); if (!m_Unit) return;