The Expression System

Ok, attempting your second suggestion there, I'm finding this REALLY tough but I think I've done fairly well... just a few issues here.

My initial blind and completely ignorant attempt at the Read process needs tweaking...
Code:
	if(GETXML->SetToChildByTagName( pXML->GetXML(), "FreePromoTypes"))
	{
		int i = 0;
		int iNum = GETXML->NumOfChildrenByTagName( pXML->GetXML(), "PromotionType" );
		m_aFreePromoTypes.resize(iNum); // Important to keep the delayed resolution pointers correct

		if(GETXML->SetToChild( pXML->GetXML() ))
		{
			if (GETXML->LocateFirstSiblingNodeByTagName(pXML->GetXML(), "PromotionType"))
			{
				do
				{
					pXML->GetChildXmlValByName(szTextVal, "PromotionType");

					if (pXML->GetChildXmlValByName(&(m_aFreePromoTypes[i].m_pExprFreePromotionCondition), "FreePromotionCondition"))
					{
						m_aFreePromoTypes[i].m_pExprFreePromotionCondition = BoolExpr::read(pXML);
						GETXML->SetToParent(pXML->GetXML());
					}
					GC.addDelayedResolution((int*)&(m_aFreePromoTypes[i].ePromotion), szTextVal);
					i++;
				} while(GETXML->LocateNextSiblingNodeByTagName(pXML->GetXML(), "PromotionType"));
			}
			GETXML->SetToParent( pXML->GetXML() );
		}
		GETXML->SetToParent( pXML->GetXML() );
	}
I get the following in regards to this section:
Code:
1>CvInfos.cpp(28899) : error C2664: 'bool CvXMLLoadUtility::GetChildXmlValByName(std::string &,const TCHAR *,char *)' : cannot convert parameter 1 from 'BoolExpr ** ' to 'std::string &'
No idea how to correct this - just getting this far is a whole lot of guesswork.

The other section that gives me trouble is in the text manager:
Code:
		for (iI = 0; iI < GC.getBuildingInfo(eBuilding).getNumFreePromoTypes(); iI++)
		{	
			BoolExpr* pExpr = GC.getBuildingInfo(eBuilding).getFreePromoType(iI).m_pExprFreePromotionCondition;
			if (pExpr)
			{
				bool bEval = false;
				if (pCity)
				{
					for (int iJ = 0; iJ < GC.getNumUnitInfos(); iJ++)
					{
						UnitTypes eUnit = ((UnitTypes)iJ);
						bEval = pExpr->evaluate(const_cast<CvGameObjectUnit*>(eUnit->getGameObjectConst()));
					}
				}
				if (!bEval)
				{
					szBuffer.append(NEWLINE);
					szBuffer.append(gDLL->getText("TXT_KEY_REQUIRES"));
					szBuffer.append(" ");
					pExpr->buildDisplayString(szBuffer);
				}
			}
I'm getting:
Code:
1>CvGameTextMgr.cpp(21237) : error C2227: left of '->getGameObjectConst' must point to class/struct/union
1>        type is 'UnitTypes'
So apparently this is only going to evaluate a particular unit in the game if my assessments here are correct, so this really isn't going to work very well at all... perhaps better to simply list the free promotions themselves with a footnote that there could be some unexplained conditions at this time eh? Even just trying to list off the conditions themselves on each free promo would be extraordinarily taxing to the display if trying to accommodate all possible conditions.

Surprisingly I got no other errors. I'm hoping this means we're doing pretty good up to this point.
 
Well, there are several mistakes there but they are all easy to fix.

First, some info about the XML reading functions. You always point to a specific node/tag and all functions you call are relative to that node.
NumOfChildrenByTagName gives you the number of children of that node with a specific tag name. So when you call it with "PromotionType" while you are on the "FreePromoTypes" tag, then that will only return a non 0 value if there is a PromotionType with the FreePromoTypes you are on as parent. But I assume that you will have a "FreePromoType" node for every entry (in my example that was FreePromotion), not directly a PromotionType (because where would the condition be then).
The NumChildrenOfTagName does not move the reading pointer but SetToChild does. It sets the pointer to the first child node of the current node. So when you apply that while you are on the FreePromoTypes node, you will end up on the first FreePromoType node. LocateFirstSiblingNodeByTagName now moves the pointer to the first node with that name on the current level. So you have to give it FreePromoType instead of PromotionType (same as above). Since you are already there after the SetToChild, it will not do anything (but it might if the user accidently puts some other tags on that level).
The sequence of SetToChild and then LocateFirstSiblingNodeByTagName is the same as using SetToChildByTagName so you might as well use that directly.
The do while loop with LocateNextSiblingNodeByTagName now goes through all nodes with the given name, which again needs to be FreePromoType instead of PromotionType.
What you do with PromotionType within that loop body is correct. GetChildXmlValByName goes to the subnode with the given name, reads it as the type of variable you pass (string in this case) and goes back to the node on which it started (so you are not actually moving the reading pointer). You can't do the same with boolean expressions as there is no GetChildXmlValByName overload for BoolExpr. BoolExpr has the static read method that expects that the reading pointer is on the tag with the boolean expression. So instead of GetChildXmlValByName you use SetToChildByTagName to move the reading pointer to the FreePromotionCondition node and then body of the if there is already a correct call to the read method.

About the game text:
I don't think it makes sense to actually list all units for which this would be true (far too many units). You can't evaluate the expression on the unit type anyway without having an actual unit (as the unit type is not a game object, only the unit is).
As the second part of that code does, you can turn the expression into a text rendering with
pExpr->buildDisplayString(szBuffer);
and display it in brackets after the free promotion. That is how I have done it with the other expressions that are already in the game.
Best try it out and see if it looks good.
 
@AIAndy:

I've modified the CvOutcomeMission code and it seems to be crashing in this section of code.

PHP:
bool CvOutcomeMission::isPossible(CvUnit* pUnit, bool bTestVisible)
{
	CvPlayer* pOwner = &GET_PLAYER(pUnit->getOwnerINLINE());

	if (!bTestVisible)
	{
		if (pOwner->getGold() < m_iCost->evaluate(pUnit->getGameObject()))
		{
			return false;
		}

		if (m_pPlotCondition)
			if (!m_pPlotCondition->evaluate(pUnit->plot()->getGameObject()))
				return false;
	}

	if (m_pUnitCondition)
		if (!m_pUnitCondition->evaluate(pUnit->getGameObject()))
			return false;

	if (!getOutcomeList()->isPossible(*pUnit))
	{
		return false;
	}

	if (!bTestVisible)
	{
		if (!getPropertyCost()->isEmpty())
		{
			CvGameObject* pPayer = NULL;
			if ((m_ePayerType == NO_GAMEOBJECT) || (m_ePayerType == GAMEOBJECT_UNIT))
			{
				pPayer = pUnit->getGameObject();
			}
			else
			{
				pUnit->getGameObject()->foreach(m_ePayerType, boost::bind(callSetPayer, _1, &pPayer));
			}

			if (!pPayer)
			{
				return false;
			}

			if (! (*(pPayer->getProperties()) > m_PropertyCost ))
			{
				return false;
			}
		}
	}

	return true;
}
I've figured out that it always has to do with missions which kill a subdued animal (mainly butchering), and I'm wondering if there is something else I should be doing for outcomes which kill the unit.
 
I've figured out that it always has to do with missions which kill a subdued animal (mainly butchering), and I'm wondering if there is something else I should be doing for outcomes which kill the unit.
Butcher missions don't have a gold cost so they don't have an iCost tag either. If you use the usual reading code for expressions, then that means m_iCost will be a NULL pointer.
So similar to the code with m_pPlotCondition below you have to check first that the expression is not NULL before you evaluate it.

Code:
if (m_iCost)
  if (pOwner->getGold() < m_iCost->evaluate(pUnit->getGameObject()))
  {
    return false;
  }

The same needs to be done wherever m_iCost is used.
 
Butcher missions don't have a gold cost so they don't have an iCost tag either. If you use the usual reading code for expressions, then that means m_iCost will be a NULL pointer.
So similar to the code with m_pPlotCondition below you have to check first that the expression is not NULL before you evaluate it.

Code:
if (m_iCost)
  if (pOwner->getGold() < m_iCost->evaluate(pUnit->getGameObject()))
  {
    return false;
  }

The same needs to be done wherever m_iCost is used.
Or write an (probably inline) accessor method on CvUnit for m_iCost, and use that everywhere (if it's a particular issue for units):

Code:
inline int CvUnit::getCost() { return (m_iCost == NULL ? 0 : m_iCost->evaluate(getGameObject())); }
 
Or write an (probably inline) accessor method on CvUnit for m_iCost, and use that everywhere (if it's a particular issue for units):

Code:
inline int CvUnit::getCost() { return (m_iCost == NULL ? 0 : m_iCost->evaluate(getGameObject())); }
Can't do it on CvUnit as the m_iCost is on the mission, not the unit.
What I have done at several other places is to change
int CvOutcomeMission::getCost()
into
int CvOutcomeMission::getCost(CvGameObject* pObject)
and return 0 if m_iCost is NULL.
 
Well, there are several mistakes there but they are all easy to fix.

First, some info about the XML reading functions. You always point to a specific node/tag and all functions you call are relative to that node.
NumOfChildrenByTagName gives you the number of children of that node with a specific tag name. So when you call it with "PromotionType" while you are on the "FreePromoTypes" tag, then that will only return a non 0 value if there is a PromotionType with the FreePromoTypes you are on as parent. But I assume that you will have a "FreePromoType" node for every entry (in my example that was FreePromotion), not directly a PromotionType (because where would the condition be then).
The NumChildrenOfTagName does not move the reading pointer but SetToChild does. It sets the pointer to the first child node of the current node. So when you apply that while you are on the FreePromoTypes node, you will end up on the first FreePromoType node. LocateFirstSiblingNodeByTagName now moves the pointer to the first node with that name on the current level. So you have to give it FreePromoType instead of PromotionType (same as above). Since you are already there after the SetToChild, it will not do anything (but it might if the user accidently puts some other tags on that level).
The sequence of SetToChild and then LocateFirstSiblingNodeByTagName is the same as using SetToChildByTagName so you might as well use that directly.
The do while loop with LocateNextSiblingNodeByTagName now goes through all nodes with the given name, which again needs to be FreePromoType instead of PromotionType.
What you do with PromotionType within that loop body is correct. GetChildXmlValByName goes to the subnode with the given name, reads it as the type of variable you pass (string in this case) and goes back to the node on which it started (so you are not actually moving the reading pointer). You can't do the same with boolean expressions as there is no GetChildXmlValByName overload for BoolExpr. BoolExpr has the static read method that expects that the reading pointer is on the tag with the boolean expression. So instead of GetChildXmlValByName you use SetToChildByTagName to move the reading pointer to the FreePromotionCondition node and then body of the if there is already a correct call to the read method.

About the game text:
I don't think it makes sense to actually list all units for which this would be true (far too many units). You can't evaluate the expression on the unit type anyway without having an actual unit (as the unit type is not a game object, only the unit is).
As the second part of that code does, you can turn the expression into a text rendering with
pExpr->buildDisplayString(szBuffer);
and display it in brackets after the free promotion. That is how I have done it with the other expressions that are already in the game.
Best try it out and see if it looks good.

Ok, the game text made perfect sense but here's what I worked out from what you stated there:

Code:
	if(GETXML->SetToChildByTagName( pXML->GetXML(), "FreePromoTypes"))
	{
		int i = 0;
		int iNum = GETXML->NumOfChildrenByTagName( pXML->GetXML(), "FreePromoType" );
		m_aFreePromoTypes.resize(iNum); // Important to keep the delayed resolution pointers correct

		if(GETXML->SetToChild( pXML->GetXML() ))
		{
			if (GETXML->LocateFirstSiblingNodeByTagName(pXML->GetXML(), "FreePromoType"))
			{	
				do
				{
					pXML->GetChildXmlValByName(szTextVal, "PromotionType");

					if (GETXML->SetToChildByTagName(pXML->GetXML(), "FreePromotionCondition"))
					{
						m_aFreePromoTypes[i].m_pExprFreePromotionCondition = BoolExpr::read(pXML);
						GETXML->SetToParent(pXML->GetXML());
					}
					GC.addDelayedResolution((int*)&(m_aFreePromoTypes[i].ePromotion), szTextVal);
					i++;
				} while(GETXML->LocateNextSiblingNodeByTagName(pXML->GetXML(), "FreePromoType"));
			}
			GETXML->SetToParent( pXML->GetXML() );
		}
		GETXML->SetToParent( pXML->GetXML() );
	}
Is this along the lines of what you were instructing? (This surprisingly compiles ok but I don't know if it RUNS ok yet ;)
 
Unfortunately, it fails to load all xml files dependent on the building schema. Maybe something is wrong there?

Code:
	<ElementType name="PromotionType" content="textOnly"/>
	<ElementType name="FreePromoType" content="eltOnly">
		<element type="PromotionType" minOccurs="0" maxOccurs="*"/>
		<element type="FreePromotionCondition" minOccurs="0"/>
	</ElementType>
	<ElementType name="FreePromoTypes" content="eltonly">
		<element type="FreePromoType" minOccurs="0" maxOccurs="*"/>
	</ElementType>
and
Code:
	<ElementType name="FreePromotionCondition"/>
The XML Load error message is less than helpful here... just states all the building files are failing to load. (I'm running with the old loader mechanism for the debugging...)
 
Unfortunately, it fails to load all xml files dependent on the building schema. Maybe something is wrong there?

Code:
	<ElementType name="PromotionType" content="textOnly"/>
	<ElementType name="FreePromoType" content="eltOnly">
		<element type="PromotionType" minOccurs="0" maxOccurs="*"/>
		<element type="FreePromotionCondition" minOccurs="0"/>
	</ElementType>
	<ElementType name="FreePromoTypes" content="eltonly">
		<element type="FreePromoType" minOccurs="0" maxOccurs="*"/>
	</ElementType>
and
Code:
	<ElementType name="FreePromotionCondition"/>
The XML Load error message is less than helpful here... just states all the building files are failing to load. (I'm running with the old loader mechanism for the debugging...)
One common source for that error is that a tag is already defined somewhere else in the schema. Like if
<ElementType name="PromotionType" content="textOnly"/>
is twice in that schema, then that schema will always fail.
 
I've eliminated that consideration... none of the 'new' tags are declared more than their one occurrence.

Could it have something to do with
Code:
		<element type="FreePromotionCondition" minOccurs="0"/>
having a minOccurs definition?

I also had a bit of trouble trying to implement
Code:
if (GETXML->SetToChildByTagName(pXML->GetXML(), "FreePromotionCondition"))
trying to use
Code:
pXML->SetToChildByTagName(&(m_aFreePromoTypes[i].m_pExprFreePromotionCondition), "FreePromotionCondition"))
but was forced into first replacine pXML with GETXML and then pXML->GetXML() appeared to need to replace &(m_aFreePromoTypes.m_pExprFreePromotionCondition).

So that's all correct then right?

Maybe I'm doing something wrong in the sections I didn't post that still compile ok?
 
I've eliminated that consideration... none of the 'new' tags are declared more than their one occurrence.

Could it have something to do with
Code:
		<element type="FreePromotionCondition" minOccurs="0"/>
having a minOccurs definition?

I also had a bit of trouble trying to implement
Code:
if (GETXML->SetToChildByTagName(pXML->GetXML(), "FreePromotionCondition"))
trying to use
Code:
pXML->SetToChildByTagName(&(m_aFreePromoTypes[i].m_pExprFreePromotionCondition), "FreePromotionCondition"))
but was forced into first replacine pXML with GETXML and then pXML->GetXML() appeared to need to replace &(m_aFreePromoTypes.m_pExprFreePromotionCondition).

So that's all correct then right?

Maybe I'm doing something wrong in the sections I didn't post that still compile ok?

The one with GETXML->SetToChildByTagName is correct.
To make sure it is not a schema failure, have you tried loading it with the new parser? Does it still fail to load?
 
EDIT: THANK YOU for insisting that it had to be something wrong in the schema!! It was simply a mixup between eltonly and eltOnly that was giving me trouble! Hopefully everything is smooth sailing from here then... ;)
 
@hydro
So you need something like.

If x terrain is in city vicinity then requires y building.
 
@AIAndy

Ok so I need to know how to code a rather complex expression system tag. Which is ...

Cannot Build if X Terrain is in the city vicinity unless you have Y Building.

Is this possible?

I'm not familiar with the syntax, but the expression is:

(NOT X terrain) OR (Y building)
 
Code:
<ConstructCondition>
  <Or>
    <Not>
      <Has>
        <GOMType>GOM_TERRAIN</GOMType>
        <ID>TERRAIN_X</ID>
      </Has>
    </Not>
    <Has>
      <GOMType>GOM_BUILDING</GOMType>
      <ID>BUILDING_Y</ID>
    </Has>
 </Or>
</ConstructCondition>
 
Bumping This so I won't have to keep searching it.
 
Top Bottom