FfH2 0.23 Bug Thread

With Tears of Sucellus one usually can heal (30%) a unit per turn. But it needs life mana. As Malakim you have life mana from the beginning, but you cannot heal units until you have a life mana node extra. Should not be, isn't it?
Editing: It seems to be only an "incorrect" translation in the German version. It's the Shrine of Sucellus, of course.
 
couldn't it be the opposite ??
ie : make the combat match the combat odds ... :)

because having a lvl (many) str (many) having 99,9 against a spearman and loosing (uncorrected) or having 0.01% (combat odds match combat) changes nothing :)

the issue is that it should be 99.9 and almost never loose.

from what I gathered, the issue seems to be more about the combat calculation itself than odd's calculation...

but maybe I'm totally off.
I fully agree with you, it is ununderstandable loosing regulary a fight with 99,7% or similar, even when you reload several times to try it out. It seems to me the AI plays uncorrectly for it is far from a probability calculation. And winning a fight with a 1,2% quote is crazy.
 
In version .23c

When using a unit with the fear promotion; when the unit attacks and wins combat, all defending units will retreat from the defending location and the attacking unit will move in the location; this do not occur in cities though.

I thought that fear was effective during defense: an attacking unit would break its attack against the unit with the fear promotion.
 
In version .23c

When using a unit with the fear promotion; when the unit attacks and wins combat, all defending units will retreat from the defending location and the attacking unit will move in the location; this do not occur in cities though.
This is as designed. I'm not sure where I read it - perhaps in the 'Pedia under the Fear promotion.
 
Yeah, the stone wall from the spell is temporary. If you move your adept out of the city, the wall is gone.
 
RE:Comments from my last two posts: I'd be happy to discuss those issues on another thread.

Suggested fix; let me know if you want more detail.
Spoiler :
Code:
CvUnit::updateCombat
...
			iAttackerStrength = currCombatStr(NULL, NULL, &cdAttackerDetails, true, pDefender); // Smarter Orcs
			iAttackerFirepower = currFirepower(NULL, NULL, true, pDefender); // Smarter Orcs


//FfH: Added by Kael 03/06/2007
int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
{
    return maxCombatStr(pPlot, pAttacker, pCombatDetails, true, NULL);//Smarter Orcs
}
//FfH: End Add

//FfH: Modified by Kael 12/22/2006
//int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
// BetterAI start
// maxCombatStr can be called in four different configurations
//		pPlot == NULL, pAttacker == NULL for combat when this is the attacker
//		pPlot valid, pAttacker valid for combat when this is the defender
//		pPlot valid, pAttacker == NULL (new case), when this is the defender, attacker unknown
//		pPlot valid, pAttacker == this (new case), when the defender is unknown, but we want to calc approx str
//			note, in this last case, it is expected pCombatDetails == NULL, it does not have to be, but some
//			values may be unexpectedly reversed in this case (iModifierTotal will be the negative sum)
// BetterAI end
//Smarter Orcs start
// In:	pDefender - the defending unit.
int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails, bool bAttacking, const CvUnit* pDefender) const
// Smarter Orcs end
//FfH: End Modify

{
	 // CvCity* pCity; // BetterAI removal
	int iModifier;
	int iCombat;

	FAssertMsg((pPlot == NULL) || (pPlot->getTerrainType() != NO_TERRAIN), "(pPlot == NULL) || (pPlot->getTerrainType() is not expected to be equal with NO_TERRAIN)");
	FAssertMsg((pAttacker == NULL) || (pDefender == NULL),"Improper call!"); // Smarter Orcs
...

// Following replaces the resistance code:
	// Smarter Orcs start
	int iResistCold = 0;
	int iResistDeath = 0;
	int iResistFire = 0;
	int iResistHoly = 0;
	int iResistLightning = 0;
	int iResistPoison = 0;
	int iResistUnholy = 0;
	const CvUnit *pOtherUnit;

	
	if (pAttacker != NULL)
    {
		pOtherUnit = pAttacker;
	}
	else if (pDefender != NULL)
    {
		pOtherUnit = pDefender;
	}
	else
	{
		pOtherUnit = NULL;
	}
	if (pOtherUnit != NULL)
	{
		iResistCold = pOtherUnit->getResistCold();
		iResistDeath = pOtherUnit->getResistDeath();
		iResistFire = pOtherUnit->getResistFire();
		iResistHoly = pOtherUnit->getResistHoly();
		iResistLightning = pOtherUnit->getResistLightning();
		iResistPoison = pOtherUnit->getResistPoison();
		iResistUnholy = pOtherUnit->getResistUnholy();
	}
    if (getCombatCold() != 0)
    {
        iCombat += getCombatCold() * (100 - iResistCold);
    }
    if (getCombatDeath() != 0)
    {
        iCombat += getCombatDeath() * (100 - iResistDeath);
    }
    if (getCombatFire() != 0)
    {
        iCombat += getCombatFire() * (100 - iResistFire);
    }
    if (getCombatHoly() != 0)
    {
        iCombat += getCombatHoly() * (100 - iResistHoly);
    }
    if (getCombatLightning() != 0)
    {
        iCombat += getCombatLightning() * (100 - iResistLightning);
    }
    if (getCombatPoison() != 0)
    {
        iCombat += getCombatPoison() * (100 - iResistPoison);
    }
    if (getCombatUnholy() != 0)
    {
        iCombat += getCombatUnholy() * (100 - iResistUnholy);
    }
	// Smarter Orcs end

//FfH: Added by Kael 03/06/2007
int CvUnit::currCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
{
    return currCombatStr(pPlot, pAttacker, pCombatDetails, true, NULL); // Smarter Orcs
}
//FfH: End Add

//FfH: Modified by Kael 12/22/2006
//int CvUnit::currCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
//{
//	return ((maxCombatStr(pPlot, pAttacker, pCombatDetails) * currHitPoints()) / maxHitPoints());
//}
// Smarter Orcs start
int CvUnit::currCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails, bool bAttacking, const CvUnit* pDefender) const
{
	return ((maxCombatStr(pPlot, pAttacker, pCombatDetails, bAttacking, pDefender) * currHitPoints()) / maxHitPoints());
	// Smarter Orcs end
}
//FfH: End Modify

//FfH: Added by Kael 03/06/2007
int CvUnit::currFirepower(const CvPlot* pPlot, const CvUnit* pAttacker) const
{
    return currFirepower(pPlot, pAttacker, true, NULL); // Smarter Orcs
}
//FfH: End Add

//FfH: Modified by Kael 12/22/2006
//int CvUnit::currFirepower(const CvPlot* pPlot, const CvUnit* pAttacker) const
//{
//	return ((maxCombatStr(pPlot, pAttacker) + currCombatStr(pPlot, pAttacker) + 1) / 2);
// Smarter Orcs start
int CvUnit::currFirepower(const CvPlot* pPlot, const CvUnit* pAttacker, bool bAttacking, const CvUnit* pDefender) const
{
	return ((maxCombatStr(pPlot, pAttacker, NULL, bAttacking, pDefender) + currCombatStr(pPlot, pAttacker, NULL, bAttacking, pDefender) + 1) / 2);
	// Smarter Orcs end
//FfH: End Modify
}

//BetterAI start
// this nomalizes str by firepower, useful for quick odds calcs
// the effect is that a damaged unit will have an effective str lowered by firepower/maxFirepower
// doing the algebra, this means we mulitply by 1/2(1 + currHP)/maxHP = (maxHP + currHP) / (2 * maxHP)
int CvUnit::currEffectiveStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails) const
{
	return currEffectiveStr(pPlot, pAttacker, pCombatDetails, true);
}
//Smarter Orcs start
int CvUnit::currEffectiveStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails, bool bAttacking, const CvUnit* pDefender) const // Smarter Orcs
{
	int currStr = currCombatStr(pPlot, pAttacker, pCombatDetails, bAttacking, pDefender); // Smarter Orcs

	currStr *= (maxHitPoints() + currHitPoints());
	currStr /= (2 * maxHitPoints());

	return currStr;
}
//Smarter Orcs end
//BetterAI end
//FfH: Added by Kael 03/06/2007
float CvUnit::maxCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker) const
{
    return maxCombatStrFloat(pPlot, pAttacker, true, NULL);// Smarter Orcs
}
//FfH: End Add

//FfH: Modified by Kael 12/22/2006
//float CvUnit::maxCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker) const
//{
//	return (((float)(maxCombatStr(pPlot, pAttacker))) / 100.0f);
//Smarter Orcs start
float CvUnit::maxCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker, bool bAttacking, const CvUnit* pDefender) const
{
	return (((float)(maxCombatStr(pPlot, pAttacker, NULL, bAttacking, pDefender))) / 100.0f);
	// Smarter Orcs end
//FfH: End Modify
}

//FfH: Added by Kael 03/06/2007
float CvUnit::currCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker) const
{
    return currCombatStrFloat(pPlot, pAttacker, true, NULL);//Smarter Orcs
}
//FfH: End Add

//FfH: Modified by Kael 12/22/2006
//float CvUnit::currCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker) const
//{
//	return (((float)(currCombatStr(pPlot, pAttacker))) / 100.0f);
//Smarter Orcs start
float CvUnit::currCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker, bool bAttacking, const CvUnit* pDefender) const
{
	return (((float)(currCombatStr(pPlot, pAttacker, NULL, bAttacking, pDefender))) / 100.0f);
	// Smarter Orcs end
//FfH: End Modify
}
...
bool CvGameTextMgr::setCombatPlotHelp(CvWString &szString, CvPlot* pPlot)
{
...
			szOffenseOdds.Format(L"%.2f", ((pAttacker->getDomainType() == DOMAIN_AIR) ? pAttacker->airCurrCombatStrFloat() : pAttacker->currCombatStrFloat(NULL, NULL, true, pDefender))); // Smarter Orcs
...
int CvUnitAI::AI_attackOdds(const CvPlot* pPlot, bool bPotentialEnemy) const
{
...
	iOurStrength = ((getDomainType() == DOMAIN_AIR) ? airCurrCombatStr() : currCombatStr(NULL, NULL, NULL, true, pDefender)); // Smarter Orcs
	iOurFirepower = ((getDomainType() == DOMAIN_AIR) ? iOurStrength : currFirepower(NULL, NULL, true, pDefender)); // Smarter Orcs
...
int getCombatOdds(CvUnit* pAttacker, CvUnit* pDefender)
{
...
	iAttackerStrength = pAttacker->currCombatStr(NULL, NULL, NULL, true, pDefender); // Smarter Orcs
	iAttackerFirepower = pAttacker->currFirepower(NULL, NULL, true, pDefender); // Smarter Orcs
...
// CvUnit.h:

	int currEffectiveStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails = NULL, bool bAttacker = true, const CvUnit* pDefender = NULL) const; // BetterAI // Smarter Orcs
	DllExport float maxCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker) const;																	// Exposed to Python
	DllExport float currCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker) const;																	// Exposed to Python

//FfH: Added by Kael 12/22/2006
	DllExport int maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails = NULL, bool bAttacking = true, const CvUnit* pDefender = NULL) const; // Smarter Orcs
	DllExport int currCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails = NULL, bool bAttacking = true, const CvUnit* pDefender = NULL) const; // Smarter Orcs
	DllExport int baseCombatStr(bool bAttacking = true) const;
	DllExport int currFirepower(const CvPlot* pPlot, const CvUnit* pAttacker, bool bAttacking = true, const CvUnit* pDefender = NULL) const; // Smarter Orcs
	DllExport float maxCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker, bool bAttacking = true, const CvUnit* pDefender = NULL) const; // Smarter Orcs
	DllExport float currCombatStrFloat(const CvPlot* pPlot, const CvUnit* pAttacker, bool bAttacking = true, const CvUnit* pDefender = NULL) const; // Smarter Orcs
//FfH: End Add

I should note that the main known issue with this code is that if a defender is immune to all the attacker's damage, the attack is still valid, but the attacker's strength is treated as 0.01. I am not certain that this is desired (or undesired) behavior; I merely consider it far better than ignoring the defender's resistances.
 
It is possible for a unit to gain over 100% resistance. With the current math (or the math I suggest above), any resistance above 100% is treated as a negative toward the rest of the other creature's strength (in other words, a creature with 100% fire resistance attacking a Fire Elemental would not do as well as one with 120% resistance, but is otherwise the same). I'm not certain if that is desired.
 
I'm experiencing a crash while playing Hyborem. It occurs when I research Divine Essence, but I don't know if that is the cause.

I can't upload the save here, because it is too large.

How can I sent it, and to whom?
 
Come across another error after it continues to crash from time to time. This one is found over and over in my xml.log file.

Code:
[3093.546] info type UNITCLASS_HIGH_PRIEST not found, Current XML file is: Misc/Civ4TutorialInfos.xml
[3093.546] info type UNITCLASS_MONK not found, Current XML file is: Misc/Civ4TutorialInfos.xml
[3093.546] info type UNITCLASS_CAMEL_ARCHER not found, Current XML file is: Misc/Civ4TutorialInfos.xml
[3093.546] info type BUILDING_DWARVEN_FORGE not found, Current XML file is: Misc/Civ4TutorialInfos.xml

Not quite sure why it's looking in that file, there's only a this in that file.

Code:
<?xml version="1.0" ?> 
- <!--  edited with XMLSPY v2004 rel. 2 U (http://www.xmlspy.com) by Ed Piper (Firaxis Games) 
  --> 
- <!--  Sid Meier's Civilization 4 
  --> 
- <!--  Copyright Firaxis Games 2005 
  --> 
- <!--  
  --> 
- <!--  Tutorial Infos 
  --> 
- <Civ4TutorialInfos xmlns="x-schema:CIV4TutorialSchema.xml">
- <TutorialInfo>
  <Type>TUTORIAL_INTRODUCTION</Type> 
- <TutorialMessages>
- <TutorialMessage>
  <TutorialMessageText>Welcome to Sid Meiers Civilization 4.</TutorialMessageText> 
  <TutorialMessageImage>ART_DEF_TUTORIAL_CIV4LOGO</TutorialMessageImage> 
  <TutorialMessageSound>AS2D_TUTORIAL_INTRODUCTION_1</TutorialMessageSound> 
  </TutorialMessage>
  </TutorialMessages>
  </TutorialInfo>
  </Civ4TutorialInfos>

I doubt this will fix my current problem with the constent crashing from time to time, but it sure can't harm it by trying to correct the problem.
 
Come across another error after it continues to crash from time to time. This one is found over and over in my xml.log file.

Code:
[3093.546] info type UNITCLASS_HIGH_PRIEST not found, Current XML file is: Misc/Civ4TutorialInfos.xml
[3093.546] info type UNITCLASS_MONK not found, Current XML file is: Misc/Civ4TutorialInfos.xml
[3093.546] info type UNITCLASS_CAMEL_ARCHER not found, Current XML file is: Misc/Civ4TutorialInfos.xml
[3093.546] info type BUILDING_DWARVEN_FORGE not found, Current XML file is: Misc/Civ4TutorialInfos.xml
Spoiler :

Not quite sure why it's looking in that file, there's only a this in that file.

Code:
<?xml version="1.0" ?> 
- <!--  edited with XMLSPY v2004 rel. 2 U (http://www.xmlspy.com) by Ed Piper (Firaxis Games) 
  --> 
- <!--  Sid Meier's Civilization 4 
  --> 
- <!--  Copyright Firaxis Games 2005 
  --> 
- <!--  
  --> 
- <!--  Tutorial Infos 
  --> 
- <Civ4TutorialInfos xmlns="x-schema:CIV4TutorialSchema.xml">
- <TutorialInfo>
  <Type>TUTORIAL_INTRODUCTION</Type> 
- <TutorialMessages>
- <TutorialMessage>
  <TutorialMessageText>Welcome to Sid Meiers Civilization 4.</TutorialMessageText> 
  <TutorialMessageImage>ART_DEF_TUTORIAL_CIV4LOGO</TutorialMessageImage> 
  <TutorialMessageSound>AS2D_TUTORIAL_INTRODUCTION_1</TutorialMessageSound> 
  </TutorialMessage>
  </TutorialMessages>
  </TutorialInfo>
  </Civ4TutorialInfos>

I doubt this will fix my current problem with the constent crashing from time to time, but it sure can't harm it by trying to correct the problem.
The problem (if there is one) is in other XML files. The information above suggests that CVI4UnitClassInfos.xml and CIV4BuildingInfos.xml are missing/corrupt/not loading successfully, or possibly just loading after the tutorial file.
 
I'm experiencing a crash while playing Hyborem. It occurs when I research Divine Essence, but I don't know if that is the cause.

I can't upload the save here, because it is too large.

How can I sent it, and to whom?

I'd zip it or rar it, then post it here.
 
On a wrap-around map, which most maps are, the spread of hell terrain does not wrap around. It goes to the "edge" of the map (what you see in the minimap) and then stops. This is significant when Hyborem spawns near the edge of the map.

EDIT: (a month later...) Okay, I just had a game where the spread of hell terrain did wrap around. Now I wish I had kept a savegame from the time it didn't wrap. In the game where it didn't wrap, Hyborem spawned very near the western edge of the map. Hell terrain spread far to the east, but never crossed the short water expanse to enter my lands to the west. There was no alignment or AC counter reason for it not to spread. I have no idea what caused the initial result, but I just had a game where hell terrain did wrap around the edge of the map. 'Just thought I would update this post with that information.
 
Interesting. That is one more issue at the map edge, then - I wonder why the edge causes so much trouble.
 
I've captured a wolf, and have sent it back to my capital.

Just as my wolf is entering my capital, it turns out that Garrim Gyr (with whom I have open borders) has a scout in my capital. Instead of just entering the tile, my wolf attacks Garrim Gyr's scout and dies.

I understand that my wolf had HN, and therefore could attack without fear of causing a war, but I really didn't want it to attack. Is there anyway of stopping this?... and no, I don't want to hand-hold my wolf all the way back to the capital 1 tile at a time making sure he doesn't attack anyone.
 
You can declare nationality. If you want to put him in a pen, it doesn't matter if you do or not. Otherwise, no you have to hold his paw all the way to a city.
Keep in mind, once in the city HN is off, so they are safe from attacks from others.
 
I've captured a wolf, and have sent it back to my capital.

Just as my wolf is entering my capital, it turns out that Garrim Gyr (with whom I have open borders) has a scout in my capital. Instead of just entering the tile, my wolf attacks Garrim Gyr's scout and dies.

I understand that my wolf had HN, and therefore could attack without fear of causing a war, but I really didn't want it to attack. Is there anyway of stopping this?... and no, I don't want to hand-hold my wolf all the way back to the capital 1 tile at a time making sure he doesn't attack anyone.

To what jwin said I'd add that Garrim Gyr's scout would have attacked your wolf anyway, so 'handholding' really isn't the issue.

If you want to add an animal to a carnival or create a dancing bear with it or whatever, declare nationality.

If you want to use it as a HN mercenary, treat it like any other HN mercenary. (Stack it with other HN units and keep an eye on it lest it be removed from the ecosystem by a more powerful AI unit or stack.)
 
Here's the fixed code:

Spoiler :
Code:
// Smarter Orcs start- defect fix.
//
// creates a derived artItem class which automatically registers itself with the ARTFILEMGR upon contruction.
// creates a static var of that artItem type which constructs (and registers) at startup.
// creates a getFooArtInfo() function that searches the map based on the id provided and returns the artInfo struct or null.
//
#define ART_INFO_DEFN(name) \
\
class Cv##name##ArtInfoItem : public CvArtFileMgr::ArtInfoItem \
{ \
	void init() { ARTFILEMGR.m_map##name##ArtInfos = new CvArtFileMgr::ArtInfo##name##MapType; } \
	void deInit() { SAFE_DELETE(ARTFILEMGR.m_map##name##ArtInfos); SAFE_DELETE_ARRAY(ARTFILEMGR.m_pa##name##ArtInfo); } \
	void buildMap() { BUILD_INFO_MAP(*ARTFILEMGR.m_map##name##ArtInfos, ARTFILEMGR.get##name##ArtInfo, ARTFILEMGR.getNum##name##ArtInfos()); } \
}; \
\
static Cv##name##ArtInfoItem g##name##ArtInfoItem; \
\
CvArtInfo##name##* CvArtFileMgr::get##name##ArtInfo( const char *szArtDefineTag ) const \
{ \
	FAssertMsg(szArtDefineTag, "NULL string on art info lookup?"); \
	ArtInfo##name##MapType::const_iterator it = m_map##name##ArtInfos->find( szArtDefineTag );\
	if ( it == m_map##name##ArtInfos->end() ) \
	{\
		char szErrorMsg[256]; \
		sprintf(szErrorMsg, "get##name##ArtInfo: %s was not found", szArtDefineTag); \
		FAssertMsg(false, szErrorMsg ); \
		int iCompare = strncmp( szArtDefineTag, "ERROR", strlen(szArtDefineTag) ); \
		if ( iCompare == 0 ) \
		{ \
			return NULL; \
		} \
		else \
		{ \
			return get##name##ArtInfo( "ERROR" ); \
		} \
	} \
	return it->second; \
} \
CvArtInfo##name##& CvArtFileMgr::get##name##ArtInfo(int i) { return m_pa##name##ArtInfo[i]; }
// Smarter Orcs end

The changed lines are:
Code:
		int iCompare = strncmp( szArtDefineTag, "ERROR", strlen(szArtDefineTag) ); \
		if ( iCompare == 0 ) \
The original code was attempting to compare szArtDefineTag directly with "ERROR", which (while valid in most cases in python) will always return false in C++ (save in a single contrived edge case). This would cause it to go down the error branch, which causes an infinite recursive loop, and a crash at the stack overflow.

This appears to be a vanilla defect as well.
 
edit: if you dont want the same thing to happen on a reload, make sure you've checked "Random seed on reload" or the same thing will happen every reload since it uses the same random numbers.
Thank you, Sureshot :) , for this valuable advice, now I know, why the odds are always the same.
 
Back
Top Bottom