From c41248b585ca4eb8f35c5c4df84f456710215c8a Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Fri, 14 Apr 2006 03:14:43 +0000 Subject: [PATCH] # Updates to actor prop-switching system Forced variant names to lowercase. Allowed empty prop model names to remove inherited props. Removed a little code duplication. Entity: Changed confusing (and probably incorrect) loop/STL logic, to use slightly more confusing syntax instead. This was SVN commit r3761. --- source/graphics/ObjectBase.cpp | 112 ++++++++++++++++++++++++---- source/graphics/ObjectBase.h | 18 ++++- source/graphics/ObjectEntry.cpp | 95 +++-------------------- source/graphics/ObjectEntry.h | 2 +- source/graphics/ObjectManager.cpp | 6 +- source/graphics/Unit.cpp | 6 +- source/simulation/BaseFormation.cpp | 4 +- source/simulation/Entity.cpp | 44 ++++++----- 8 files changed, 158 insertions(+), 129 deletions(-) diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index dc067073e0..7aacf0119c 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -5,6 +5,7 @@ #include "ObjectManager.h" #include "XML/Xeromyces.h" #include "CLogger.h" +#include "lib/timer.h" #define LOG_CATEGORY "graphics" @@ -16,7 +17,7 @@ CObjectBase::CObjectBase() bool CObjectBase::Load(const char* filename) { - m_Variants.clear(); + m_VariantGroups.clear(); CStr filePath ("art/actors/"); filePath += filename; @@ -69,27 +70,25 @@ bool CObjectBase::Load(const char* filename) // of elements, to avoid wasteful copying/reallocation later. { // Count the variants in each group - std::vector variantSizes; + std::vector variantGroupSizes; XERO_ITER_EL(root, child) { if (child.getNodeName() == el_group) { - variantSizes.push_back(0); - XERO_ITER_EL(child, variant) - ++variantSizes.back(); + variantGroupSizes.push_back(child.getChildNodes().Count); } } - m_Variants.resize(variantSizes.size()); + m_VariantGroups.resize(variantGroupSizes.size()); // Set each vector to match the number of variants - for (size_t i = 0; i < variantSizes.size(); ++i) - m_Variants[i].resize(variantSizes[i]); + for (size_t i = 0; i < variantGroupSizes.size(); ++i) + m_VariantGroups[i].resize(variantGroupSizes[i]); } // (This XML-reading code is rather worryingly verbose...) - std::vector >::iterator currentGroup = m_Variants.begin(); + std::vector >::iterator currentGroup = m_VariantGroups.begin(); XERO_ITER_EL(root, child) { @@ -104,7 +103,7 @@ bool CObjectBase::Load(const char* filename) XERO_ITER_ATTR(variant, attr) { if (attr.Name == at_name) - currentVariant->m_VariantName = attr.Value; + currentVariant->m_VariantName = CStr(attr.Value).LowerCase(); else if (attr.Name == at_frequency) currentVariant->m_Frequency = CStr(attr.Value).ToInt(); @@ -134,9 +133,13 @@ bool CObjectBase::Load(const char* filename) XERO_ITER_ATTR(anim_element, ae) { if (ae.Name == at_name) + { anim.m_AnimName = ae.Value; + } else if (ae.Name == at_file) + { anim.m_FileName = "art/animation/" + CStr(ae.Value); + } else if (ae.Name == at_speed) { anim.m_Speed = CStr(ae.Value).ToInt() / 100.f; @@ -209,8 +212,14 @@ bool CObjectBase::Load(const char* filename) return true; } +TIMER_ADD_CLIENT(tc_CalculateVariationKey) + std::vector CObjectBase::CalculateVariationKey(const std::vector >& selections) { + TIMER_ACCRUE(tc_CalculateVariationKey); + // (TODO: see CObjectManager::FindObjectVariation for an opportunity to + // call this function a bit less frequently) + // Calculate a complete list of choices, one per group, based on the // supposedly-complete selections (i.e. not making random choices at this // stage). @@ -223,8 +232,8 @@ std::vector CObjectBase::CalculateVariationKey(const std::vector chosenProps; - for (std::vector >::iterator grp = m_Variants.begin(); - grp != m_Variants.end(); + for (std::vector >::iterator grp = m_VariantGroups.begin(); + grp != m_VariantGroups.end(); ++grp) { // Ignore groups with nothing inside. (A warning will have been @@ -274,7 +283,10 @@ std::vector CObjectBase::CalculateVariationKey(const std::vector::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) { - chosenProps[it->m_PropPointName] = it->m_ModelName; + if (it->m_ModelName.Length()) + chosenProps[it->m_PropPointName] = it->m_ModelName; + else + chosenProps.erase(it->m_PropPointName); } } @@ -292,6 +304,70 @@ std::vector CObjectBase::CalculateVariationKey(const std::vector& variationKey) +{ + Variation variation; + + // variationKey should correspond with m_Variants, giving the id of the + // chosen variant from each group. (Except variationKey has some bits stuck + // on the end for props, but we don't care about those in here.) + + std::vector >::iterator grp = m_VariantGroups.begin(); + std::vector::const_iterator match = variationKey.begin(); + for ( ; + grp != m_VariantGroups.end() && match != variationKey.end(); + ++grp, ++match) + { + // Ignore groups with nothing inside. (A warning will have been + // emitted by the loading code.) + if (grp->size() == 0) + continue; + + size_t id = *match; + if (id >= grp->size()) + { + // This should be impossible + debug_warn("BuildVariation: invalid variant id"); + continue; + } + + // Get the matched variant + CObjectBase::Variant& var ((*grp)[id]); + + // Apply its data: + + if (var.m_TextureFilename.Length()) + variation.texture = var.m_TextureFilename; + + if (var.m_ModelFilename.Length()) + variation.model = var.m_ModelFilename; + + if (var.m_Color.Length()) + variation.color = var.m_Color; + + for (std::vector::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) + { + if (it->m_ModelName.Length()) + variation.props[it->m_PropPointName] = *it; + else + variation.props.erase(it->m_PropPointName); + } + + // If one variant defines one animation called e.g. "attack", and this + // variant defines two different animations with the same name, the one + // original should be erased, and replaced by the two new ones. + // + // So, erase all existing animations which are overridden by this variant: + for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) + variation.anims.erase(it->m_AnimName); + // and then insert the new ones: + for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) + variation.anims.insert(make_pair(it->m_AnimName, *it)); + } + + return variation; +} + std::set CObjectBase::CalculateRandomVariation(const std::set& initialSelections) { std::set selections = initialSelections; @@ -308,8 +384,8 @@ std::set CObjectBase::CalculateRandomVariation(const std::set& ini // When choosing randomly, make use of each variant's frequency. If all // variants have frequency 0, treat them as if they were 1. - for (std::vector >::iterator grp = m_Variants.begin(); - grp != m_Variants.end(); + for (std::vector >::iterator grp = m_VariantGroups.begin(); + grp != m_VariantGroups.end(); ++grp) { // Ignore groups with nothing inside. (A warning will have been @@ -383,7 +459,10 @@ std::set CObjectBase::CalculateRandomVariation(const std::set& ini CObjectBase::Variant& var ((*grp)[match]); for (std::vector::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) { - chosenProps[it->m_PropPointName] = it->m_ModelName; + if (it->m_ModelName.Length()) + chosenProps[it->m_PropPointName] = it->m_ModelName; + else + chosenProps.erase(it->m_PropPointName); } } @@ -394,6 +473,7 @@ std::set CObjectBase::CalculateRandomVariation(const std::set& ini if (prop) { std::set propSelections = prop->CalculateRandomVariation(selections); + // selections = union(propSelections, selections) std::set newSelections; std::set_union(propSelections.begin(), propSelections.end(), selections.begin(), selections.end(), diff --git a/source/graphics/ObjectBase.h b/source/graphics/ObjectBase.h index 43d0e97f83..5fe958b63e 100644 --- a/source/graphics/ObjectBase.h +++ b/source/graphics/ObjectBase.h @@ -38,7 +38,7 @@ public: struct Variant { Variant() : m_Frequency(0) {} - CStr m_VariantName; + CStr m_VariantName; // lowercase name int m_Frequency; CStr m_ModelFilename; CStr m_TextureFilename; @@ -48,12 +48,21 @@ public: std::vector m_Props; }; + struct Variation + { + CStr texture; + CStr model; + CStr color; + std::map props; + std::multimap anims; + }; + CObjectBase(); - std::vector< std::vector > m_Variants; - std::vector CalculateVariationKey(const std::vector >& selections); + const Variation BuildVariation(const std::vector& variationKey); + std::set CalculateRandomVariation(const std::set& initialSelections); bool Load(const char* filename); @@ -73,6 +82,9 @@ public: // the material file CStr m_Material; + +private: + std::vector< std::vector > m_VariantGroups; }; diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index fefc634ecf..a31a4cdc58 100755 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -37,106 +37,29 @@ CObjectEntry::~CObjectEntry() } -bool CObjectEntry::BuildVariation(const std::vector >& selections) +bool CObjectEntry::BuildVariation(const std::vector >& selections, const std::vector& variationKey) { - CStr chosenTexture; - CStr chosenModel; - CStr chosenColor; - std::map chosenProps; - std::multimap chosenAnims; - - for (std::vector >::iterator grp = m_Base->m_Variants.begin(); - grp != m_Base->m_Variants.end(); - ++grp) - { - // Ignore groups with nothing inside. (A warning will have been - // emitted by the loading code.) - if (grp->size() == 0) - continue; - - int match = -1; // -1 => none found yet - - // If there's only a single variant, choose that one - if (grp->size() == 1) - { - match = 0; - } - else - { - // Determine the first variant that matches the provided strings, - // starting with the highest priority selections set: - - for (std::vector >::const_iterator selset = selections.begin(); selset < selections.end(); ++selset) - { - debug_assert(grp->size() < 256); // else they won't fit in 'choices' - - for (size_t i = 0; i < grp->size(); ++i) - { - if (selset->count((*grp)[i].m_VariantName)) - { - match = (u8)i; - break; - } - } - - // Stop after finding the first match - if (match != -1) - break; - } - - // If no match, just choose the first - if (match == -1) - match = 0; - } - - // Get the matched variant - CObjectBase::Variant& var ((*grp)[match]); - - // Apply its data: - - if (var.m_TextureFilename.Length()) - chosenTexture = var.m_TextureFilename; - - if (var.m_ModelFilename.Length()) - chosenModel = var.m_ModelFilename; - - if (var.m_Color.Length()) - chosenColor = var.m_Color; - - for (std::vector::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) - chosenProps[it->m_PropPointName] = *it; - - // If one variant defines one animation called e.g. "attack", and this - // variant defines two different animations with the same name, the one - // original should be erased, and replaced by the two new ones. - // - // So, erase all existing animations which are overridden by this variant: - for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) - chosenAnims.erase(chosenAnims.lower_bound(it->m_AnimName), chosenAnims.upper_bound(it->m_AnimName)); - // and then insert the new ones: - for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) - chosenAnims.insert(make_pair(it->m_AnimName, *it)); - } + CObjectBase::Variation variation = m_Base->BuildVariation(variationKey); // Copy the chosen data onto this model: - m_TextureName = chosenTexture; - m_ModelName = chosenModel; + m_TextureName = variation.texture; + m_ModelName = variation.model; - if (chosenColor.Length()) + if (variation.color.Length()) { std::stringstream str; - str << chosenColor; + str << variation.color; int r, g, b; if (! (str >> r >> g >> b)) // Any trailing data is ignored - LOG(ERROR, LOG_CATEGORY, "Invalid RGB colour '%s'", chosenColor.c_str()); + LOG(ERROR, LOG_CATEGORY, "Invalid RGB colour '%s'", variation.color.c_str()); else m_Color = CColor(r/255.0f, g/255.0f, b/255.0f, 1.0f); } std::vector props; - for (std::map::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it) + for (std::map::iterator it = variation.props.begin(); it != variation.props.end(); ++it) props.push_back(it->second); // Build the model: @@ -169,7 +92,7 @@ bool CObjectEntry::BuildVariation(const std::vector >& selection m_Model->CalcObjectBounds(); // load the animations - for (std::multimap::iterator it = chosenAnims.begin(); it != chosenAnims.end(); ++it) + for (std::multimap::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it) { CStr name = it->first.LowerCase(); diff --git a/source/graphics/ObjectEntry.h b/source/graphics/ObjectEntry.h index a47750c0bd..2d286acf09 100755 --- a/source/graphics/ObjectEntry.h +++ b/source/graphics/ObjectEntry.h @@ -18,7 +18,7 @@ public: CObjectEntry(int type, CObjectBase* base); ~CObjectEntry(); - bool BuildVariation(const std::vector >& selections); + bool BuildVariation(const std::vector >& selections, const std::vector& variationKey); // Base actor. Contains all the things that don't change between // different variations of the actor. diff --git a/source/graphics/ObjectManager.cpp b/source/graphics/ObjectManager.cpp index 87c911c773..955842da5f 100755 --- a/source/graphics/ObjectManager.cpp +++ b/source/graphics/ObjectManager.cpp @@ -109,7 +109,11 @@ CObjectEntry* CObjectManager::FindObjectVariation(CObjectBase* base, const std:: CObjectEntry* obj = new CObjectEntry(0, base); // TODO: type ? - if (! obj->BuildVariation(selections)) + // TODO (for some efficiency): use the pre-calculated choices for this object, + // which has already worked out what to do for props, instead of passing the + // selections into BuildVariation and having it recalculate the props' choices. + + if (! obj->BuildVariation(selections, choices)) { DeleteObject(obj); return NULL; diff --git a/source/graphics/Unit.cpp b/source/graphics/Unit.cpp index 9a11d99d6b..aecd76b721 100644 --- a/source/graphics/Unit.cpp +++ b/source/graphics/Unit.cpp @@ -92,13 +92,15 @@ void CUnit::SetPlayerID(int id) void CUnit::SetEntitySelection(const CStrW& selection) { + CStrW selection_lc = selection.LowerCase(); + // If we've already selected this, don't do anything - if (m_EntitySelections.find(selection) != m_EntitySelections.end()) + if (m_EntitySelections.find(selection_lc) != m_EntitySelections.end()) return; // Just allow one selection at a time m_EntitySelections.clear(); - m_EntitySelections.insert(selection); + m_EntitySelections.insert(selection_lc); ReloadObject(); } diff --git a/source/simulation/BaseFormation.cpp b/source/simulation/BaseFormation.cpp index ea2e162981..4027251a9f 100644 --- a/source/simulation/BaseFormation.cpp +++ b/source/simulation/BaseFormation.cpp @@ -141,7 +141,7 @@ bool CBaseFormation::loadXML(CStr filename) if( order <= 0 ) { - LOG( ERROR, LOG_CATEGORY, "CBaseFormation::LoadXML: Invalid (negative number or 0) order defined in formation file %s. The game will try to continue anyway.", filename.c_str() ); + LOG( ERROR, LOG_CATEGORY, "CBaseFormation::LoadXML: Invalid (negative number or 0) order defined in formation file %s. The game will try to continue anyway.", filename.c_str() ); continue; } --order; //We need this to be in line with arrays, so start at 0 @@ -173,7 +173,7 @@ bool CBaseFormation::loadXML(CStr filename) { if ( m_slots.find(i) == m_slots.end() ) { - LOG( ERROR, LOG_CATEGORY, "CBaseFormation::LoadXML: Missing orders in %s. Load failed.", filename.c_str() ); + LOG( ERROR, LOG_CATEGORY, "CBaseFormation::LoadXML: Missing orders in %s. Load failed.", filename.c_str() ); return false; } else diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index b602806bc3..ae6298fba9 100755 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -740,36 +740,43 @@ void CEntity::DispatchNotification( CEntityOrder order, int type ) CEventNotification evt( order, type ); DispatchEvent( &evt ); } + +struct isListenerSender +{ + CEntity* sender; + isListenerSender(CEntity* sender) : sender(sender) {} + bool operator()(CEntityListener& listener) + { + return listener.m_sender == sender; + } +}; + int CEntity::DestroyNotifier( CEntity* target ) { if (target->m_listeners.empty() || !m_destroyNotifiers) return 0; //Stop listening - for ( size_t i=0; i < target->m_listeners.size(); i++ ) - { - if ( target->m_listeners[i].m_sender == this ) - target->m_listeners.erase(target->m_listeners.begin() + i); - } - int removed=0; + // (Don't just loop and use 'erase', because modifying the deque while + // looping over it is a bit dangerous) + std::deque::iterator newEnd = std::remove_if( + target->m_listeners.begin(), target->m_listeners.end(), + isListenerSender(this)); + target->m_listeners.erase(newEnd, target->m_listeners.end()); + //Get rid of our copy - for ( size_t i=0; i < target->m_notifiers.size(); i++ ) - { - if ( m_notifiers[i] == target ) - { - m_notifiers.erase(m_notifiers.begin() + i); - ++removed; - } - } + std::vector::iterator newEnd2 = std::remove_if( + m_notifiers.begin(), m_notifiers.end(), + bind2nd(std::equal_to(), target)); + int removed = std::distance(newEnd2, m_notifiers.end()); + m_notifiers.erase(newEnd2, m_notifiers.end()); return removed; } void CEntity::DestroyAllNotifiers() { debug_assert(m_destroyNotifiers); //Make them stop listening to us - if ( m_notifiers.empty() ) - return; - for ( size_t i=0; i( argv[0] ); (int&)notify.m_type = ToPrimitive( argv[1] ); bool tmpDestroyNotifiers = ToPrimitive( argv[2] ); + // TODO: ??? This local variable overrides the member variable of the same name... bool m_destroyNotifiers = !ToPrimitive( argv[3] ); if (target == this)