diff --git a/Generals/Code/GameEngine/Include/Common/Player.h b/Generals/Code/GameEngine/Include/Common/Player.h index 722af4d604..4fbec6701e 100644 --- a/Generals/Code/GameEngine/Include/Common/Player.h +++ b/Generals/Code/GameEngine/Include/Common/Player.h @@ -289,8 +289,8 @@ class Player : public Snapshot /// return t iff the player has all sciences that are prereqs for knowing the given science Bool hasPrereqsForScience(ScienceType t) const; - Bool hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ); ///< does player have totally done and produced upgrade - Bool hasUpgradeComplete( UpgradeMaskType testMask ); ///< does player have totally done and produced upgrade + Bool hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) const; ///< does player have totally done and produced upgrade + Bool hasUpgradeComplete( UpgradeMaskType testMask ) const; ///< does player have totally done and produced upgrade UpgradeMaskType getCompletedUpgradeMask() const { return m_upgradesCompleted; } ///< get list of upgrades that are completed Bool hasUpgradeInProduction( const UpgradeTemplate *upgradeTemplate ); ///< does player have this upgrade in progress right now Upgrade *addUpgrade( const UpgradeTemplate *upgradeTemplate, diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/Generals/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index 8994262dc8..76fdff185c 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h @@ -56,6 +56,7 @@ class HackInternetAIInterface; class AssaultTransportAIInterface; enum AIStateType CPP_11(: Int); +enum HordeActionType CPP_11(: Int); enum ObjectID CPP_11(: Int); @@ -547,7 +548,11 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface void setAttitude( AttitudeType tude ); ///< set the behavior modifier for this agent // Common AI "status" effects ------------------------------------------------------------------- - void evaluateMoraleBonus( void ); + Bool hasNationalism() const; + Bool hasFanaticism() const; + void evaluateMoraleBonus( Bool inHorde, Bool allowNationalism, HordeActionType type ); + void evaluateNationalismBonusClassic( Bool inHorde, Bool allowNationalism ); + void evaluateNationalismBonus( Bool inHorde, Bool allowNationalism ); #ifdef ALLOW_DEMORALIZE // demoralization ... what a nifty word to write. diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h b/Generals/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h index e1a4f2adad..65a4660852 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h @@ -41,17 +41,38 @@ class UpgradeTemplate; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix xezon 15/09/2025 Adds a new horde action to select a fixed implementation. +// +// TheSuperHackers @todo If we want more control over the horde setup, then we need to implement +// filters for separate weapon bonuses and required upgrades. But adding more behavior modules will +// be a performance concern. Example INI setup: +// +// Behavior = HordeUpdate ModuleTag +// Action = NATIONALISM +// ;ApplyWeaponBonus = NATIONALISM +// UpgradeRequired = Upgrade_Nationalism +// End +// enum HordeActionType CPP_11(: Int) { - HORDEACTION_HORDE = 0, + HORDEACTION_HORDE, ///< Classic action, applies the Horde bonus correctly, but Nationalism and Fanaticism bonuses are not removed after leaving horde. + HORDEACTION_HORDE_FIXED, ///< Applies the Horde, Nationalism and Fanaticism bonuses correctly. - HORDEACTION_COUNT + HORDEACTION_COUNT, + +#if RETAIL_COMPATIBLE_CRC + HORDEACTION_DEFAULT = HORDEACTION_HORDE, +#else + HORDEACTION_DEFAULT = HORDEACTION_HORDE_FIXED, ///< Does not change unmodified retail game behavior, because all its horde update modules explicitly set Action = HORDE. +#endif }; #ifdef DEFINE_HORDEACTION_NAMES static const char *const TheHordeActionTypeNames[] = { "HORDE", + "HORDE_FIXED", + NULL }; static_assert(ARRAY_SIZE(TheHordeActionTypeNames) == HORDEACTION_COUNT + 1, "Incorrect array size"); @@ -62,15 +83,15 @@ class HordeUpdateModuleData : public ModuleData { public: UnsignedInt m_updateRate; ///< how often to recheck our horde status - KindOfMaskType m_kindof; ///< the kind(s) of units that count towards horde-ness + KindOfMaskType m_kindof; ///< the kind(s) of units that count towards hordeness Int m_minCount; ///< min count to get "horde" status - Real m_minDist; ///< min dist to contribute to horde-ness + Real m_minDist; ///< min dist to contribute to hordeness Bool m_alliesOnly; ///< if true, only allied units count towards hordeness Bool m_exactMatch; ///< if true, only exact same type of units count towards hordeness Real m_rubOffRadius;///< If I am this close to another guy who is a true hordesman, it'll rub off on me - HordeActionType m_action; ///< what to do if we get horde-ness - Bool m_allowedNationalism; ///< Nationalism is hard ocded. Yeah! Add to the goodness with this flag instead of rewriting after Alpha. - std::vector m_flagSubObjNames; ///< name(s) of the flag subobj + HordeActionType m_action; ///< what to do if we get hordeness + Bool m_allowedNationalism; ///< Nationalism is hard coded. Yeah! Add to the goodness with this flag instead of rewriting after Alpha. + std::vector m_flagSubObjNames; ///< name(s) of the flag sub obj HordeUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -87,7 +108,7 @@ class HordeUpdateInterface virtual Bool hasFlag() const = 0; virtual Bool isTrueHordeMember() const = 0; virtual Bool isAllowedNationalism() const = 0; - + virtual HordeActionType getHordeActionType() const = 0; }; //------------------------------------------------------------------------------------------------- @@ -104,11 +125,13 @@ class HordeUpdate : public UpdateModule, public HordeUpdateInterface HordeUpdateInterface *getHordeUpdateInterface() { return this; } virtual void onDrawableBoundToObject(); + virtual UpdateSleepTime update(); ///< update this object's AI + virtual Bool isInHorde() const { return m_inHorde; } + virtual Bool hasFlag() const { return m_hasFlag; } virtual Bool isTrueHordeMember() const { return m_trueHordeMember && m_inHorde; } virtual Bool isAllowedNationalism() const; - virtual Bool hasFlag() const { return m_hasFlag; } - virtual UpdateSleepTime update(); ///< update this object's AI + virtual HordeActionType getHordeActionType() const { return getHordeUpdateModuleData()->m_action; }; protected: @@ -117,8 +140,8 @@ class HordeUpdate : public UpdateModule, public HordeUpdateInterface private: UnsignedInt m_lastHordeRefreshFrame; //Just like it sounds - Bool m_inHorde; //I amy be a trueMember, or I may merely inherit hordehood from a neighbor who is - Bool m_trueHordeMember; //meaning, I have enough hordesman near me to qualify + Bool m_inHorde; //I may be a true member, or I may merely inherit hordehood from a neighbor who is + Bool m_trueHordeMember; //meaning, I have enough hordesmen near me to qualify Bool m_hasFlag; }; diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index 8622ceb81a..0c098e793f 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2511,7 +2511,7 @@ Upgrade *Player::findUpgrade( const UpgradeTemplate *upgradeTemplate ) //================================================================================================= /** Does the player have this completed upgrade */ //================================================================================================= -Bool Player::hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) +Bool Player::hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) const { UpgradeMaskType testMask = upgradeTemplate->getUpgradeMask(); return hasUpgradeComplete( testMask ); @@ -2522,7 +2522,7 @@ Bool Player::hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) Does the player have this completed upgrade. This form is exposed so Objects can do quick lookups. */ //================================================================================================= -Bool Player::hasUpgradeComplete( UpgradeMaskType testMask ) +Bool Player::hasUpgradeComplete( UpgradeMaskType testMask ) const { return m_upgradesCompleted.testForAll( testMask ); } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index b3339d6dfa..e515ff54a4 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -4426,126 +4426,155 @@ setTmpValue(now); // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void AIUpdateInterface::evaluateMoraleBonus( void ) +Bool AIUpdateInterface::hasNationalism() const { - Object *us = getObject(); -#ifdef ALLOW_DEMORALIZE - Bool demoralized = isDemoralized(); -#endif - Bool horde = FALSE; - Bool nationalism = FALSE; - Bool fanaticism = FALSE; - - Player *player = us->getControllingPlayer(); - - // do we have nationalism - ///@todo Find a better way to represent nationalism without hardcoding here (CBD) - static const UpgradeTemplate *nationalismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Nationalism" ); - if (nationalismTemplate != NULL) + if (const Player *player = getObject()->getControllingPlayer()) { - if( player && player->hasUpgradeComplete( nationalismTemplate ) ) - nationalism = TRUE; - } - - // do we have fanaticism - ///@todo Find a better way to represent fanaticism without hardcoding here (MAL) - static const UpgradeTemplate *fanaticismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Fanaticism" ); - if (fanaticismTemplate != NULL) - { - if( player && player->hasUpgradeComplete( fanaticismTemplate ) ) - fanaticism = TRUE; + ///@todo Find a better way to represent nationalism without hard coding here (CBD) + static const UpgradeTemplate *nationalismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Nationalism" ); + if (nationalismTemplate != NULL) + { + return player->hasUpgradeComplete( nationalismTemplate ); + } } + return false; +} - // are we in a horde - HordeUpdateInterface *hui; - for( BehaviorModule** u = us->getBehaviorModules(); *u; ++u ) +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool AIUpdateInterface::hasFanaticism() const +{ + if (const Player *player = getObject()->getControllingPlayer()) { - - hui = (*u)->getHordeUpdateInterface(); - if( hui && hui->isInHorde() ) + ///@todo Find a better way to represent fanaticism without hard coding here (MAL) + static const UpgradeTemplate *fanaticismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Fanaticism" ); + if (fanaticismTemplate != NULL) { - horde = TRUE; - - if( !hui->isAllowedNationalism() ) - { - // Sorry CBD and MAL, but the cancer has spread to the lymph nodes. After Alpha, just pump full of painkillers. - nationalism = FALSE; - fanaticism = FALSE; - } + return player->hasUpgradeComplete( fanaticismTemplate ); } - } + return false; +} +// ------------------------------------------------------------------------------------------------ +// TheSuperHackers @refactor The implementation has been improved and simplified. +// ------------------------------------------------------------------------------------------------ +void AIUpdateInterface::evaluateMoraleBonus( Bool inHorde, Bool allowNationalism, HordeActionType type ) +{ #ifdef ALLOW_DEMORALIZE - // if we are are not demoralized we can have horde and nationalism effects - if( demoralized == FALSE ) -#endif + + // if we are demoralized, then we can not have horde and nationalism effects + if( isDemoralized() ) { + Object *us = getObject(); -#ifdef ALLOW_DEMORALIZE // demoralized - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); -#endif + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); - //Lorenzen temporarily disabled, since it fights with the horde buff - //Drawable *draw = us->getDrawable(); - //if ( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) - // draw->setTerrainDecal(TERRAIN_DECAL_NONE); + // we cannot have horde bonuses + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); - // horde - if( horde ) + Drawable *draw = us->getDrawable(); + if( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) { - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); - + draw->setTerrainDecal(TERRAIN_DECAL_DEMORALIZED); } - else - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); - // nationalism - if( nationalism ) - { - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); - // fanaticism - if ( fanaticism ) - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM );// FOR THE NEW GC INFANTRY GENERAL - else - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); - } - else - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + return; + } + + // demoralized + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); +#endif // ALLOW_DEMORALIZE + //Lorenzen temporarily disabled, since it fights with the horde buff + //Object *us = getObject(); + //Drawable *draw = us->getDrawable(); + //if ( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) + // draw->setTerrainDecal(TERRAIN_DECAL_NONE); + switch (type) + { + case HORDEACTION_HORDE: + evaluateNationalismBonusClassic(inHorde, allowNationalism); + break; + +#if !RETAIL_COMPATIBLE_CRC + case HORDEACTION_HORDE_FIXED: + evaluateNationalismBonus(inHorde, allowNationalism); + break; +#endif + } +} + +// ------------------------------------------------------------------------------------------------ +// TheSuperHackers @info The classic Nationalism Bonus implementation. +// Is not great, because Nationalism and Fanaticism bonuses are not disabled when leaving the horde. +// ------------------------------------------------------------------------------------------------ +void AIUpdateInterface::evaluateNationalismBonusClassic( Bool inHorde, Bool allowNationalism ) +{ + Object *us = getObject(); + + if( inHorde ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); } -#ifdef ALLOW_DEMORALIZE else { + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + } - // demoralized - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); + if( allowNationalism && hasNationalism() ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); - // we cannot have horde bonus condition - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); - Drawable *draw = us->getDrawable(); - if( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) + if ( hasFanaticism() ) { - draw->setTerrainDecal(TERRAIN_DECAL_DEMORALIZED); + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); } - - // we cannot have nationalism bonus condition + else + { + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); + } + } + else + { us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); - } -#endif +} -/* - UnicodeString msg; - msg.format( L"'%S' Horde=%d,Nationalism=%d,Demoralized=%d", - us->getTemplate()->getName().str(), horde, nationalism, demoralized ); - TheInGameUI->message( msg ); -*/ +// ------------------------------------------------------------------------------------------------ +// TheSuperHackers @bugfix The fixed Nationalism Bonus implementation. +// Nationalism and Fanaticism are now tied to the horde status. +// And Fanaticism is no longer dependent on Nationalism. +// ------------------------------------------------------------------------------------------------ +void AIUpdateInterface::evaluateNationalismBonus( Bool inHorde, Bool allowNationalism ) +{ + Object *us = getObject(); + if( inHorde ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + + if( allowNationalism && hasNationalism() ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + } + + if( allowNationalism && hasFanaticism() ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); + } + } + else + { + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); + } } #ifdef ALLOW_DEMORALIZE @@ -4562,12 +4591,16 @@ void AIUpdateInterface::setDemoralized( UnsignedInt durationInFrames ) if( (prevDemoralizedFrames == 0 && m_demoralizedFramesLeft > 0) || (prevDemoralizedFrames > 0 && m_demoralizedFramesLeft == 0) ) { - // evaluate demoralization, nationalism, and horde effect as they are all intertwined - evaluateMoraleBonus(); - + Object *us = getObject(); + for( BehaviorModule** u = us->getBehaviorModules(); *u; ++u ) + { + if ( HordeUpdateInterface *hui = (*u)->getHordeUpdateInterface() ) + { + evaluateMoraleBonus( hui->isInHorde(), hui->isAllowedNationalism(), hui->getHordeActionType() ); + } + } } - } #endif diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp index a74ec7b172..73505c6863 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp @@ -113,15 +113,15 @@ class PartitionFilterHordeMember : public PartitionFilter const Int DEFAULT_UPDATE_RATE = LOGICFRAMES_PER_SECOND; //------------------------------------------------------------------------------------------------- -HordeUpdateModuleData::HordeUpdateModuleData() : - m_updateRate(DEFAULT_UPDATE_RATE), - m_minCount(0), - m_minDist(0.0f), - m_rubOffRadius(20.0f), - m_alliesOnly(true), - m_exactMatch(false), - m_allowedNationalism(TRUE), - m_action(HORDEACTION_HORDE) +HordeUpdateModuleData::HordeUpdateModuleData() + : m_updateRate(DEFAULT_UPDATE_RATE) + , m_minCount(0) + , m_minDist(0.0f) + , m_rubOffRadius(20.0f) + , m_alliesOnly(true) + , m_exactMatch(false) + , m_allowedNationalism(TRUE) + , m_action(HORDEACTION_DEFAULT) { } @@ -190,36 +190,22 @@ Bool HordeUpdate::isAllowedNationalism() const // ------------------------------------------------------------------------------------------------ void HordeUpdate::joinOrLeaveHorde(SimpleObjectIterator *iter, Bool join) { - Bool prevInHorde = m_inHorde; - - m_inHorde = join; - - - - - const HordeUpdateModuleData* d = getHordeUpdateModuleData(); - switch (d->m_action) + // give/remove bonus effects + if( m_inHorde != join ) { - case HORDEACTION_HORDE: - { - - // give/remove bonus effects - if( prevInHorde != m_inHorde ) - { - AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); + m_inHorde = join; - if( ai ) - ai->evaluateMoraleBonus(); - else - DEBUG_CRASH(( "HordeUpdate::joinOrLeaveHorde - We (%s) must have an AI to benefit from horde", - getObject()->getTemplate()->getName().str() )); - } - - } - break; + if( AIUpdateInterface *ai = getObject()->getAIUpdateInterface() ) + { + const HordeUpdateModuleData* md = getHordeUpdateModuleData(); + ai->evaluateMoraleBonus(m_inHorde, md->m_allowedNationalism, md->m_action); + } + else + { + DEBUG_CRASH(( "HordeUpdate::joinOrLeaveHorde - We (%s) must have an AI to benefit from horde", + getObject()->getTemplate()->getName().str() )); + } } - - } //------------------------------------------------------------------------------------------------- @@ -266,8 +252,8 @@ UpdateSleepTime HordeUpdate::update( void ) Bool wasInHorde = m_inHorde; // This is a sticky situation, where refreshing the model state (like from default to damaged, for example) - // will rebuild the terraindecal and set its size to the default size.... since Vehicles have a special size, - // we want to keep it fresh, here, but not do the horde-ing test every frame... + // will rebuild the terrain decal and set its size to the default size.... since Vehicles have a special size, + // we want to keep it fresh, here, but not do the hording test every frame... Bool isInfantry = ( obj->isKindOf(KINDOF_INFANTRY) ); if ( isInfantry || (TheGameLogic->getFrame() > m_lastHordeRefreshFrame + md->m_updateRate) ) { @@ -308,7 +294,7 @@ UpdateSleepTime HordeUpdate::update( void ) AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); if( ai ) - ai->evaluateMoraleBonus(); + ai->evaluateMoraleBonus(m_inHorde, md->m_allowedNationalism, md->m_action); } @@ -318,12 +304,12 @@ UpdateSleepTime HordeUpdate::update( void ) // This is a sticky situation, where refreshing the model state (like from default to damaged, for example) - // will rebuild the terraindecal and set its size to the default size.... since Vehicles have a special size, - // we want to keep it fresh, here, but not do the horde-ing test every frame... - // This is a weak solution, in that It causes this update to fight the defualt behavior of the modelstate methods, - // But in the interest of not breaking the modelstate changing logic for five hundred other units a week before golden, + // will rebuild the terrain decal and set its size to the default size.... since Vehicles have a special size, + // we want to keep it fresh, here, but not do the hording test every frame... + // This is a weak solution, in that It causes this update to fight the default behavior of the model state methods, + // But in the interest of not breaking the model state changing logic for five hundred other units a week before golden, // this is my solution... If anyone gets this note on the next project... please please please, fix the resetting of the - // shadows/terraindecals in W3DModelDraw. THanks, ML + // shadows/terrain decals in W3DModelDraw. Thanks, ML Drawable* draw = getObject()->getDrawable(); if ( draw && ! obj->isEffectivelyDead() ) @@ -406,10 +392,7 @@ void HordeUpdate::xfer( Xfer *xfer ) // extend base class UpdateModule::xfer( xfer ); - // in horder xfer->xferBool( &m_inHorde ); - - // has flag xfer->xferBool( &m_hasFlag ); } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Player.h b/GeneralsMD/Code/GameEngine/Include/Common/Player.h index 8dd358a09a..bfe2f34a27 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Player.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Player.h @@ -309,8 +309,8 @@ class Player : public Snapshot /// return t iff the player has all sciences that are prereqs for knowing the given science Bool hasPrereqsForScience(ScienceType t) const; - Bool hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ); ///< does player have totally done and produced upgrade - Bool hasUpgradeComplete( UpgradeMaskType testMask ); ///< does player have totally done and produced upgrade + Bool hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) const; ///< does player have totally done and produced upgrade + Bool hasUpgradeComplete( UpgradeMaskType testMask ) const; ///< does player have totally done and produced upgrade UpgradeMaskType getCompletedUpgradeMask() const { return m_upgradesCompleted; } ///< get list of upgrades that are completed Bool hasUpgradeInProduction( const UpgradeTemplate *upgradeTemplate ); ///< does player have this upgrade in progress right now Upgrade *addUpgrade( const UpgradeTemplate *upgradeTemplate, diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index 491bd0e02e..3f5a9d4e9e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h @@ -57,6 +57,7 @@ class AssaultTransportAIInterface; class JetAIUpdate; enum AIStateType CPP_11(: Int); +enum HordeActionType CPP_11(: Int); enum ObjectID CPP_11(: Int); @@ -560,7 +561,11 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface void setAttitude( AttitudeType tude ); ///< set the behavior modifier for this agent // Common AI "status" effects ------------------------------------------------------------------- - void evaluateMoraleBonus( void ); + Bool hasNationalism() const; + Bool hasFanaticism() const; + void evaluateMoraleBonus( Bool inHorde, Bool allowNationalism, HordeActionType type ); + void evaluateNationalismBonusClassic( Bool inHorde, Bool allowNationalism ); + void evaluateNationalismBonus( Bool inHorde, Bool allowNationalism ); #ifdef ALLOW_DEMORALIZE // demoralization ... what a nifty word to write. diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h index e114dc31e1..9ec900ce0a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/HordeUpdate.h @@ -41,17 +41,38 @@ class UpgradeTemplate; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix xezon 15/09/2025 Adds a new horde action to select a fixed implementation. +// +// TheSuperHackers @todo If we want more control over the horde setup, then we need to implement +// filters for separate weapon bonuses and required upgrades. But adding more behavior modules will +// be a performance concern. Example INI setup: +// +// Behavior = HordeUpdate ModuleTag +// Action = NATIONALISM +// ;ApplyWeaponBonus = NATIONALISM +// UpgradeRequired = Upgrade_Nationalism +// End +// enum HordeActionType CPP_11(: Int) { - HORDEACTION_HORDE = 0, + HORDEACTION_HORDE, ///< Classic action, applies the Horde bonus correctly, but Nationalism and Fanaticism bonuses are not removed after leaving horde. + HORDEACTION_HORDE_FIXED, ///< Applies the Horde, Nationalism and Fanaticism bonuses correctly. - HORDEACTION_COUNT + HORDEACTION_COUNT, + +#if RETAIL_COMPATIBLE_CRC + HORDEACTION_DEFAULT = HORDEACTION_HORDE, +#else + HORDEACTION_DEFAULT = HORDEACTION_HORDE_FIXED, ///< Does not change unmodified retail game behavior, because all its horde update modules explicitly set Action = HORDE. +#endif }; #ifdef DEFINE_HORDEACTION_NAMES static const char *const TheHordeActionTypeNames[] = { "HORDE", + "HORDE_FIXED", + NULL }; static_assert(ARRAY_SIZE(TheHordeActionTypeNames) == HORDEACTION_COUNT + 1, "Incorrect array size"); @@ -62,15 +83,15 @@ class HordeUpdateModuleData : public ModuleData { public: UnsignedInt m_updateRate; ///< how often to recheck our horde status - KindOfMaskType m_kindof; ///< the kind(s) of units that count towards horde-ness + KindOfMaskType m_kindof; ///< the kind(s) of units that count towards hordeness Int m_minCount; ///< min count to get "horde" status - Real m_minDist; ///< min dist to contribute to horde-ness + Real m_minDist; ///< min dist to contribute to hordeness Bool m_alliesOnly; ///< if true, only allied units count towards hordeness Bool m_exactMatch; ///< if true, only exact same type of units count towards hordeness Real m_rubOffRadius;///< If I am this close to another guy who is a true hordesman, it'll rub off on me - HordeActionType m_action; ///< what to do if we get horde-ness - Bool m_allowedNationalism; ///< Nationalism is hard ocded. Yeah! Add to the goodness with this flag instead of rewriting after Alpha. - std::vector m_flagSubObjNames; ///< name(s) of the flag subobj + HordeActionType m_action; ///< what to do if we get hordeness + Bool m_allowedNationalism; ///< Nationalism is hard coded. Yeah! Add to the goodness with this flag instead of rewriting after Alpha. + std::vector m_flagSubObjNames; ///< name(s) of the flag sub obj HordeUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -87,7 +108,7 @@ class HordeUpdateInterface virtual Bool hasFlag() const = 0; virtual Bool isTrueHordeMember() const = 0; virtual Bool isAllowedNationalism() const = 0; - + virtual HordeActionType getHordeActionType() const = 0; }; //------------------------------------------------------------------------------------------------- @@ -104,11 +125,13 @@ class HordeUpdate : public UpdateModule, public HordeUpdateInterface HordeUpdateInterface *getHordeUpdateInterface() { return this; } virtual void onDrawableBoundToObject(); + virtual UpdateSleepTime update(); ///< update this object's AI + virtual Bool isInHorde() const { return m_inHorde; } + virtual Bool hasFlag() const { return m_hasFlag; } virtual Bool isTrueHordeMember() const { return m_trueHordeMember && m_inHorde; } virtual Bool isAllowedNationalism() const; - virtual Bool hasFlag() const { return m_hasFlag; } - virtual UpdateSleepTime update(); ///< update this object's AI + virtual HordeActionType getHordeActionType() const { return getHordeUpdateModuleData()->m_action; }; protected: @@ -117,8 +140,8 @@ class HordeUpdate : public UpdateModule, public HordeUpdateInterface private: UnsignedInt m_lastHordeRefreshFrame; //Just like it sounds - Bool m_inHorde; //I amy be a trueMember, or I may merely inherit hordehood from a neighbor who is - Bool m_trueHordeMember; //meaning, I have enough hordesman near me to qualify + Bool m_inHorde; //I may be a true member, or I may merely inherit hordehood from a neighbor who is + Bool m_trueHordeMember; //meaning, I have enough hordesmen near me to qualify Bool m_hasFlag; }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index 8aa80e2b47..9aae3bb315 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2992,7 +2992,7 @@ Upgrade *Player::findUpgrade( const UpgradeTemplate *upgradeTemplate ) //================================================================================================= /** Does the player have this completed upgrade */ //================================================================================================= -Bool Player::hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) +Bool Player::hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) const { UpgradeMaskType testMask = upgradeTemplate->getUpgradeMask(); return hasUpgradeComplete( testMask ); @@ -3003,7 +3003,7 @@ Bool Player::hasUpgradeComplete( const UpgradeTemplate *upgradeTemplate ) Does the player have this completed upgrade. This form is exposed so Objects can do quick lookups. */ //================================================================================================= -Bool Player::hasUpgradeComplete( UpgradeMaskType testMask ) +Bool Player::hasUpgradeComplete( UpgradeMaskType testMask ) const { return m_upgradesCompleted.testForAll( testMask ); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index b87bf464c4..2200bfd823 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -4678,126 +4678,155 @@ setTmpValue(now); // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void AIUpdateInterface::evaluateMoraleBonus( void ) +Bool AIUpdateInterface::hasNationalism() const { - Object *us = getObject(); -#ifdef ALLOW_DEMORALIZE - Bool demoralized = isDemoralized(); -#endif - Bool horde = FALSE; - Bool nationalism = FALSE; - Bool fanaticism = FALSE; - - Player *player = us->getControllingPlayer(); - - // do we have nationalism - ///@todo Find a better way to represent nationalism without hardcoding here (CBD) - static const UpgradeTemplate *nationalismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Nationalism" ); - if (nationalismTemplate != NULL) + if (const Player *player = getObject()->getControllingPlayer()) { - if( player && player->hasUpgradeComplete( nationalismTemplate ) ) - nationalism = TRUE; - } - - // do we have fanaticism - ///@todo Find a better way to represent fanaticism without hardcoding here (MAL) - static const UpgradeTemplate *fanaticismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Fanaticism" ); - if (fanaticismTemplate != NULL) - { - if( player && player->hasUpgradeComplete( fanaticismTemplate ) ) - fanaticism = TRUE; + ///@todo Find a better way to represent nationalism without hard coding here (CBD) + static const UpgradeTemplate *nationalismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Nationalism" ); + if (nationalismTemplate != NULL) + { + return player->hasUpgradeComplete( nationalismTemplate ); + } } + return false; +} - // are we in a horde - HordeUpdateInterface *hui; - for( BehaviorModule** u = us->getBehaviorModules(); *u; ++u ) +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool AIUpdateInterface::hasFanaticism() const +{ + if (const Player *player = getObject()->getControllingPlayer()) { - - hui = (*u)->getHordeUpdateInterface(); - if( hui && hui->isInHorde() ) + ///@todo Find a better way to represent fanaticism without hard coding here (MAL) + static const UpgradeTemplate *fanaticismTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_Fanaticism" ); + if (fanaticismTemplate != NULL) { - horde = TRUE; - - if( !hui->isAllowedNationalism() ) - { - // Sorry CBD and MAL, but the cancer has spread to the lymph nodes. After Alpha, just pump full of painkillers. - nationalism = FALSE; - fanaticism = FALSE; - } + return player->hasUpgradeComplete( fanaticismTemplate ); } - } + return false; +} +// ------------------------------------------------------------------------------------------------ +// TheSuperHackers @refactor The implementation has been improved and simplified. +// ------------------------------------------------------------------------------------------------ +void AIUpdateInterface::evaluateMoraleBonus( Bool inHorde, Bool allowNationalism, HordeActionType type ) +{ #ifdef ALLOW_DEMORALIZE - // if we are are not demoralized we can have horde and nationalism effects - if( demoralized == FALSE ) -#endif + + // if we are demoralized, then we can not have horde and nationalism effects + if( isDemoralized() ) { + Object *us = getObject(); -#ifdef ALLOW_DEMORALIZE // demoralized - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); -#endif + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); - //Lorenzen temporarily disabled, since it fights with the horde buff - //Drawable *draw = us->getDrawable(); - //if ( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) - // draw->setTerrainDecal(TERRAIN_DECAL_NONE); + // we cannot have horde bonuses + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); - // horde - if( horde ) + Drawable *draw = us->getDrawable(); + if( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) { - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); - + draw->setTerrainDecal(TERRAIN_DECAL_DEMORALIZED); } - else - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); - // nationalism - if( nationalism ) - { - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); - // fanaticism - if ( fanaticism ) - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM );// FOR THE NEW GC INFANTRY GENERAL - else - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); - } - else - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + return; + } + + // demoralized + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); +#endif // ALLOW_DEMORALIZE + //Lorenzen temporarily disabled, since it fights with the horde buff + //Object *us = getObject(); + //Drawable *draw = us->getDrawable(); + //if ( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) + // draw->setTerrainDecal(TERRAIN_DECAL_NONE); + switch (type) + { + case HORDEACTION_HORDE: + evaluateNationalismBonusClassic(inHorde, allowNationalism); + break; + +#if !RETAIL_COMPATIBLE_CRC + case HORDEACTION_HORDE_FIXED: + evaluateNationalismBonus(inHorde, allowNationalism); + break; +#endif + } +} + +// ------------------------------------------------------------------------------------------------ +// TheSuperHackers @info The classic Nationalism Bonus implementation. +// Is not great, because Nationalism and Fanaticism bonuses are not disabled when leaving the horde. +// ------------------------------------------------------------------------------------------------ +void AIUpdateInterface::evaluateNationalismBonusClassic( Bool inHorde, Bool allowNationalism ) +{ + Object *us = getObject(); + + if( inHorde ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); } -#ifdef ALLOW_DEMORALIZE else { + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + } - // demoralized - us->setWeaponBonusCondition( WEAPONBONUSCONDITION_DEMORALIZED ); + if( allowNationalism && hasNationalism() ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); - // we cannot have horde bonus condition - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); - Drawable *draw = us->getDrawable(); - if( draw && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) + if ( hasFanaticism() ) { - draw->setTerrainDecal(TERRAIN_DECAL_DEMORALIZED); + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); } - - // we cannot have nationalism bonus condition + else + { + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); + } + } + else + { us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); - us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); - } -#endif +} -/* - UnicodeString msg; - msg.format( L"'%S' Horde=%d,Nationalism=%d,Demoralized=%d", - us->getTemplate()->getName().str(), horde, nationalism, demoralized ); - TheInGameUI->message( msg ); -*/ +// ------------------------------------------------------------------------------------------------ +// TheSuperHackers @bugfix The fixed Nationalism Bonus implementation. +// Nationalism and Fanaticism are now tied to the horde status. +// And Fanaticism is no longer dependent on Nationalism. +// ------------------------------------------------------------------------------------------------ +void AIUpdateInterface::evaluateNationalismBonus( Bool inHorde, Bool allowNationalism ) +{ + Object *us = getObject(); + if( inHorde ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + + if( allowNationalism && hasNationalism() ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + } + + if( allowNationalism && hasFanaticism() ) + { + us->setWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); + } + } + else + { + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_HORDE ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ); + us->clearWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ); + } } #ifdef ALLOW_DEMORALIZE @@ -4814,12 +4843,16 @@ void AIUpdateInterface::setDemoralized( UnsignedInt durationInFrames ) if( (prevDemoralizedFrames == 0 && m_demoralizedFramesLeft > 0) || (prevDemoralizedFrames > 0 && m_demoralizedFramesLeft == 0) ) { - // evaluate demoralization, nationalism, and horde effect as they are all intertwined - evaluateMoraleBonus(); - + Object *us = getObject(); + for( BehaviorModule** u = us->getBehaviorModules(); *u; ++u ) + { + if ( HordeUpdateInterface *hui = (*u)->getHordeUpdateInterface() ) + { + evaluateMoraleBonus( hui->isInHorde(), hui->isAllowedNationalism(), hui->getHordeActionType() ); + } + } } - } #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp index 5ce9ffd7c7..8198bf0039 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HordeUpdate.cpp @@ -113,15 +113,15 @@ class PartitionFilterHordeMember : public PartitionFilter const Int DEFAULT_UPDATE_RATE = LOGICFRAMES_PER_SECOND; //------------------------------------------------------------------------------------------------- -HordeUpdateModuleData::HordeUpdateModuleData() : - m_updateRate(DEFAULT_UPDATE_RATE), - m_minCount(0), - m_minDist(0.0f), - m_rubOffRadius(20.0f), - m_alliesOnly(true), - m_exactMatch(false), - m_allowedNationalism(TRUE), - m_action(HORDEACTION_HORDE) +HordeUpdateModuleData::HordeUpdateModuleData() + : m_updateRate(DEFAULT_UPDATE_RATE) + , m_minCount(0) + , m_minDist(0.0f) + , m_rubOffRadius(20.0f) + , m_alliesOnly(true) + , m_exactMatch(false) + , m_allowedNationalism(TRUE) + , m_action(HORDEACTION_DEFAULT) { } @@ -190,36 +190,22 @@ Bool HordeUpdate::isAllowedNationalism() const // ------------------------------------------------------------------------------------------------ void HordeUpdate::joinOrLeaveHorde(SimpleObjectIterator *iter, Bool join) { - Bool prevInHorde = m_inHorde; - - m_inHorde = join; - - - - - const HordeUpdateModuleData* d = getHordeUpdateModuleData(); - switch (d->m_action) + // give/remove bonus effects + if( m_inHorde != join ) { - case HORDEACTION_HORDE: - { - - // give/remove bonus effects - if( prevInHorde != m_inHorde ) - { - AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); + m_inHorde = join; - if( ai ) - ai->evaluateMoraleBonus(); - else - DEBUG_CRASH(( "HordeUpdate::joinOrLeaveHorde - We (%s) must have an AI to benefit from horde", - getObject()->getTemplate()->getName().str() )); - } - - } - break; + if( AIUpdateInterface *ai = getObject()->getAIUpdateInterface() ) + { + const HordeUpdateModuleData* md = getHordeUpdateModuleData(); + ai->evaluateMoraleBonus(m_inHorde, md->m_allowedNationalism, md->m_action); + } + else + { + DEBUG_CRASH(( "HordeUpdate::joinOrLeaveHorde - We (%s) must have an AI to benefit from horde", + getObject()->getTemplate()->getName().str() )); + } } - - } //------------------------------------------------------------------------------------------------- @@ -266,8 +252,8 @@ UpdateSleepTime HordeUpdate::update( void ) Bool wasInHorde = m_inHorde; // This is a sticky situation, where refreshing the model state (like from default to damaged, for example) - // will rebuild the terraindecal and set its size to the default size.... since Vehicles have a special size, - // we want to keep it fresh, here, but not do the horde-ing test every frame... + // will rebuild the terrain decal and set its size to the default size.... since Vehicles have a special size, + // we want to keep it fresh, here, but not do the hording test every frame... Bool isInfantry = ( obj->isKindOf(KINDOF_INFANTRY) ); if ( isInfantry || (TheGameLogic->getFrame() > m_lastHordeRefreshFrame + md->m_updateRate) ) { @@ -308,7 +294,7 @@ UpdateSleepTime HordeUpdate::update( void ) AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); if( ai ) - ai->evaluateMoraleBonus(); + ai->evaluateMoraleBonus(m_inHorde, md->m_allowedNationalism, md->m_action); } @@ -318,12 +304,12 @@ UpdateSleepTime HordeUpdate::update( void ) // This is a sticky situation, where refreshing the model state (like from default to damaged, for example) - // will rebuild the terraindecal and set its size to the default size.... since Vehicles have a special size, - // we want to keep it fresh, here, but not do the horde-ing test every frame... - // This is a weak solution, in that It causes this update to fight the defualt behavior of the modelstate methods, - // But in the interest of not breaking the modelstate changing logic for five hundred other units a week before golden, + // will rebuild the terrain decal and set its size to the default size.... since Vehicles have a special size, + // we want to keep it fresh, here, but not do the hording test every frame... + // This is a weak solution, in that It causes this update to fight the default behavior of the model state methods, + // But in the interest of not breaking the model state changing logic for five hundred other units a week before golden, // this is my solution... If anyone gets this note on the next project... please please please, fix the resetting of the - // shadows/terraindecals in W3DModelDraw. THanks, ML + // shadows/terrain decals in W3DModelDraw. Thanks, ML Drawable* draw = getObject()->getDrawable(); if ( draw && ! obj->isEffectivelyDead() ) @@ -416,10 +402,7 @@ void HordeUpdate::xfer( Xfer *xfer ) // extend base class UpdateModule::xfer( xfer ); - // in horder xfer->xferBool( &m_inHorde ); - - // has flag xfer->xferBool( &m_hasFlag ); }