Civic Attitude Modifier

Alright, I've come to tackle this beast once and for all. I've learned tons since I initially began this (I basically knew nothing about C++ then, and well I am still not an expert by any standards, I can at least read code and get an idea of what it is doing...)

I've rewritten this as a readpass3. I've done 3-4 readpass3's successfully by myself. This one is different because of the second part of it; the text string that will get used. I've got everything *nearly* working; I just have one compiler error I can't solve, then hopefully, I'll be good to go to test it for real.

In the Read(...) function in CvInfos, here's my error:

Code:
	if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"CivicAttitudeChanges"))
	{
		if (pXML->SkipToNextVal())
		{
			int iTemp = 0;
			CvString szTemp = "";
			int iNumSibs = gDLL->getXMLIFace()->GetNumChildren(pXML->GetXML());

			if (gDLL->getXMLIFace()->SetToChild(pXML->GetXML()))
			{
				if (iNumSibs > 0)
				{
					for (int i=0;i<iNumSibs;i++)
					{
						if (pXML->GetChildXmlVal(szTextVal))
						{
							m_aszCivicAttitudeforPass3.push_back(szTextVal);
							pXML->GetNextXmlVal(&iTemp);
							m_aiCivicAttitudeforPass3.push_back(iTemp);
							
							m_aszCivicAttitudeReasonforPass3.push_back(szTextVal);
							[COLOR="Red"]pXML->GetNextXmlVal(&szTemp);[/COLOR]
							m_aszCivicAttitudeReasonValueforPass3.push_back(szTemp);
							
							gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
						}

						if (!gDLL->getXMLIFace()->NextSibling(pXML->GetXML()))
						{
							break;
						}
					}

					gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
				}
			}
		}

		gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
	}

The error message is:

error C2664: 'bool CvXMLLoadUtility::GetNextXmlVal(std::string &,char *)' : cannot convert parameter 1 from 'CvString *' to 'std::string &' A reference that is not to 'const' cannot be bound to a non-lvalue

It sounds like a casting issue of some sort, but I have no idea how to remedy it.
 
I don't do much with strings, so you'd have to look this up online with some guides for C++. But it boils down to a typecasting kind of thing. If you do a .cString() or something along those lines to szTemp it'll probably fix it, but I don't know which type of commands precisely are available for your use. But the basic gist is that you are trying to combine two different types of strings (like a float and an int being meshed, it don't work). In this SPECIFIC case, one of the string types is a rigid size (The CvString I think), and the other is variable/mutable.

In VS, normally if you add a . to the end of a variable and there ARE . options, it'll pop up a list of them. So type in your szTemp. and see what pops up as available, one of them should cast it to another type of string automatically.
 
Hmm. I changed the line to this:
Code:
pXML->GetNextXmlVal(&szTemp.GetCString());

But I get a new error:
"error C2102: '&' requires l-value"
 
Compare what you are doing with what it does in other places to get text.

The first example I found in that same file was in CvTechInfo::readPass2. In there it uses "CvString szTextVal;" and a line that says "if (!pXML->GetNextXmlVal(szTextVal))". No initialization of the CvString, and no "&" in the call. Searching the rest of the file for similar things shows that this is the way it is done all over the place. They work, so do it that way.
 
Maybe I'm just dense, but isn't the "if (!pXML->GetNextXmlVal(szTextVal))" just checking that there is a next XML tag, not loading the text?
 
It's the same function. If the argument wasn't the correct type and passed in the right way it would cause the compiler to give errors. Thus the way they are calling it must be right.

If you want some spot in the code where it isn't just part of such a check, there is one in CvEventTriggerInfo::read on line 21047 of the original BtS file and 2 more in CvEventInfo::read at lines 22095 and 22136.
 
You just needed to remove the & (taking the address). Since a CvString is an object, that function takes a reference (not a pointer) to it.

Code:
pXML->GetNextXmlVal([s][COLOR="Red"]&[/COLOR][/s]szTemp);

Oh, I see God-Emperor got that already. The reason that works in the if() test is that the function reads the next value and returns true if there was one. If there is no next value, it doesn't touch the string and it returns false.
 
Hrm, I was looking at the wrong line (even though you marked the right one in red, yeah.. not sure how either).

One thing I noticed on an aside:

Code:
m_aszCivicAttitudeReasonforPass3.push_back(szTextVal);
							pXML->GetNextXmlVal(&szTemp);
							m_aszCivicAttitudeReasonValueforPass3.push_back(szTemp);

The variables seem backwards to me. You are loading szTemp before you actually read it, and you are loading a string into your VALUE named variable. I think you mean to do Value with iTemp before you read in szTemp, and then do non-Value after reading in szTemp.

Looking in my own CvInfos, it seems that EF and GE out to be correct, just drop the & and you are golden.
 
Thanks guys, I guess I just needed to remember that references != locations in memory.

(It's working perfectly now)
 
The key is that references are new and operate on nearly the same principle as why memory addresses are often used: to be able to write to a location passed by the caller. Oddly, Firaxis could have made the int and bool parameters into references so you wouldn't have to pass the memory address yourself but elected not to. I have no idea why.
 
The key is that references are new and operate on nearly the same principle as why memory addresses are often used: to be able to write to a location passed by the caller.

Yeah, I actually knew this and have used it for arrays and the like, but just managed to forget that strings are references too...
 
There's no rule about it. The reason you don't add a & here to take the address is due to the function's signature. The function could have easily taken a pointer to a CvString instead. There's no way for you to know except get the error or look up the function first. :)
 
I'm trying to rewrite the way the civic effects appear in the diplomacy screen, but it isn't working. My code seems fine, but I'm still not used to dealing with CvWStrings, I guess.

Here's what is supposed to happen, for example, if you hover over slavery, and it has a -5 hit with all players with Democracy, and a +3 with Monarchy.

(bullet) Damages relations with: Greece (-5), Russia (-5), America (-5)
(bullet) Improves relations with: Turkey (+3), Malinese (+3)

It's a bit more complex than that, since there could be a lot more than 2 civics at play, anyway, here is my code:

Code:
if (bPlayerContext && GC.getGameINLINE().getActivePlayer() != NO_PLAYER)
	{
		CivicTypes eTargetCivic;
		CvWString szPlayers;
		CvWString szEnemies;
		int* paiPlayerDiplomacyChanges;
		paiPlayerDiplomacyChanges = new int[MAX_PLAYERS];
		szPlayers.Format(L"");
		szEnemies.Format(L"");
		CvPlayer& kPlayer = GET_PLAYER(GC.getGameINLINE().getActivePlayer());
		for (int iJ = 0; iJ < GC.getNumCivicInfos(); iJ++)
		{
			eTargetCivic = ((CivicTypes)iJ);
			int iAttitudeChange = GC.getCivicInfo(eCivic).getCivicAttitudeChange(eTargetCivic) + GC.getCivicInfo(eTargetCivic).getCivicAttitudeChange(eCivic);
						
			if (iAttitudeChange != 0)
			{
				for (int iK = 0; iK < MAX_PLAYERS; iK++)
				{
					paiPlayerDiplomacyChanges[iK] = 0;
					if (GET_PLAYER((PlayerTypes)iK).isAlive())
					{
						if (GET_TEAM(kPlayer.getTeam()).isHasMet(GET_PLAYER((PlayerTypes)iK).getTeam()))
						{
							for (int iL = 0; iL < GC.getNumCivicOptionInfos(); iL++)
							{
								if (GET_PLAYER((PlayerTypes)iK).getCivics((CivicOptionTypes)iL) == eTargetCivic)
								{
									paiPlayerDiplomacyChanges[iK] += iAttitudeChange;
									break;
								}
							}
						}
					}
				}
				bFirst = true;
				bool bFriendly = false;
				bool bEnemy = false;
				bool bEnemiesFirst = true;
				for (int iJ = 0; iJ < MAX_PLAYERS; iJ++)
				{
					if (paiPlayerDiplomacyChanges[iJ] > 0)
					{
						bFriendly = true;
						szPlayers.append(CvWString::format(L"<link=literal>%s</link> (+%d)", gDLL->getText(GC.getCivilizationInfo(GET_PLAYER((PlayerTypes)iJ).getCivilizationType()).getShortDescription()), paiPlayerDiplomacyChanges[iJ]));
						if (!bFirst)
							szPlayers.append(CvWString::format(L", "));
						bFirst = false;
					}
					else if (paiPlayerDiplomacyChanges[iJ] < 0)
					{
						bEnemy = true;
						szEnemies.append(CvWString::format(L"<link=literal>%s</link> (%d)", gDLL->getText(GC.getCivilizationInfo(GET_PLAYER((PlayerTypes)iJ).getCivilizationType()).getShortDescription()), paiPlayerDiplomacyChanges[iJ]));
						if (!bFirst)
							szEnemies.append(CvWString::format(L", "));
						bEnemiesFirst = false;
					}
				}
				if (bFriendly)
				{
					szHelpText.append(CvWString::format(L"%s%c%s", NEWLINE, gDLL->getSymbolID(BULLET_CHAR), szPlayers));
				}
				if (bEnemy)
				{
					szHelpText.append(CvWString::format(L"%s%c%s", NEWLINE, gDLL->getSymbolID(BULLET_CHAR), szEnemies));
				}
			}
		}
			
		SAFE_DELETE_ARRAY(paiPlayerDiplomacyChanges);
	}

It compiles fine, but in the civic screen, when I try it, it just shows an icon bullet with no text after it. And yes, I did purposely meet some players, and gave them all a civic that would affect this...
 
I don't see where you add the "Damages relations with" portion of the text key. That ought to come just before you append the Civilization Description, in an if (bFIrst) check. That way if there IS something, it will add the text before it does the first name up, and then never again. If there is never anything, it won't add the text at all.

Honestly not seeing what the issue is right now. This may be one you need to walk through step by step with the debugger and watch the string get built. If the bullet is appearing, it IS seeing at least 1 case to flag the boolean, so maybe the names aren't appending properly. But if it is seeing more than just 1, you should at least see the commas.

Also, I think if you use %D it will include the + for you automatically on the positive ones. Just less to update at any future change that somehow blends the function to a single statement.

Oh, it also seems that you should initialize the strings a bit later in the code chunk so that it will be reset when you check a new civic (should set them to "" within the civic loop, probably right after the if (iAttitudeChange != 0) check). I could see you wanting to have all modifiers toward all civics listed in a single line, but if that was the case then your bFirst should also initialize earlier (basically if the string and the flag don't both initialize at the same time, it won't work out quite right)
 
A few quick notes as I need to get to sleep.

  • CvString and CvWString start empty (""). No need to reset them unless you are clearing stuff you added previously.
  • Since MAX_PLAYERS is a constant, you don't need to use "new" with the array or delete it after.
  • You might want to use MAX_CIV_PLAYERS so it doesn't include the barbs. Not a biggie though.
  • I don't quite understand your logic here or what you want to have appear. What I expect you want doesn't match the code. I suggest writing psuedocode to make it clear what you want.
  • Make sure you take into account losing whatever attitude modifier there is for the civic you'll be leaving.
 
A few quick notes as I need to get to sleep.

  1. CvString and CvWString start empty (""). No need to reset them unless you are clearing stuff you added previously.
  2. Since MAX_PLAYERS is a constant, you don't need to use "new" with the array or delete it after.
  3. You might want to use MAX_CIV_PLAYERS so it doesn't include the barbs. Not a biggie though.
  4. I don't quite understand your logic here or what you want to have appear. What I expect you want doesn't match the code. I suggest writing psuedocode to make it clear what you want.
  5. Make sure you take into account losing whatever attitude modifier there is for the civic you'll be leaving.

1.) Handy to know. I assumed they started with some random garbage, so I was trying to be safe. ;)

2.) Like the array in CvUnit:Nuke? Gotcha.

3.) I don't understand it either. A bad sign. I rewrote it, and I think it's much better. Tell me what you think.

4.) Good Point.

I don't see where you add the "Damages relations with" portion of the text key. That ought to come just before you append the Civilization Description, in an if (bFIrst) check. That way if there IS something, it will add the text before it does the first name up, and then never again. If there is never anything, it won't add the text at all.

I was planning on adding that last. I hate filling out new text keys... XML is so tedious...

Also, I think if you use %D it will include the + for you automatically on the positive ones. Just less to update at any future change that somehow blends the function to a single statement.

Did not know that. Handy though.

Okay, well thanks for your advice guys. I did finally get it working the way I wanted it to. See what you think of the much revised code, and point out any trouble spots.

For the curious, here is what the in-game civics screen looks like:
Spoiler :


And the code:

Code:
if (bPlayerContext && GC.getGameINLINE().getActivePlayer() != NO_PLAYER)
	{
		CvPlayer& kPlayer = GET_PLAYER(GC.getGameINLINE().getActivePlayer());	
		CivicTypes eTargetCivic;
		CivicTypes eCurrentCivic = kPlayer.getCivics(GC.getCivicInfo(eCivic).getCivicOption());
		int aiPlayerDiplomacyChanges[MAX_CIV_PLAYERS];
		
		for (int iK = 0; iK < MAX_PLAYERS; iK++)
		{
			aiPlayerDiplomacyChanges[iK] = 0;
			if (GET_PLAYER((PlayerTypes)iK).isAlive() && GET_PLAYER((PlayerTypes)iK).getID() != kPlayer.getID())
			{
				if (GET_TEAM(kPlayer.getTeam()).isHasMet(GET_PLAYER((PlayerTypes)iK).getTeam()))
				{
					for (int iL = 0; iL < GC.getNumCivicOptionInfos(); iL++)
					{
						eTargetCivic = GET_PLAYER((PlayerTypes)iK).getCivics((CivicOptionTypes)iL);
						//Total attitude change is the total from both sides of the XML minus the total from our current civic setup.
						aiPlayerDiplomacyChanges[iK] += GC.getCivicInfo(eTargetCivic).getCivicAttitudeChange(eCivic) + GC.getCivicInfo(eCivic).getCivicAttitudeChange(eTargetCivic) - GC.getCivicInfo(eCurrentCivic).getCivicAttitudeChange(eTargetCivic) - GC.getCivicInfo(eTargetCivic).getCivicAttitudeChange(eCurrentCivic);
					}
				}
			}
		}
		bFirst = true;
		bool bEnemiesFirst = true;
		int iPlayerCount = 0;
		int iEnemyCount = 0;
		CvWString szPlayers;
		CvWString szEnemies;
		szEnemies.Format(L"");
		szPlayers.Format(L"");
		for (int iJ = 0; iJ < MAX_CIV_PLAYERS; iJ++)
		{
			if (aiPlayerDiplomacyChanges[iJ] > 0)
			{
				if (!bFirst)
					szPlayers.append(CvWString::format(L", "));
				szPlayers.append(CvWString::format(L"<link=literal>%s</link> (%D)", GC.getCivilizationInfo(GET_PLAYER((PlayerTypes)iJ).getCivilizationType()).getShortDescription(), aiPlayerDiplomacyChanges[iJ]));
				bFirst = false;
				//Resolution Scaling
				iPlayerCount++;
				if (iEnemyCount > XResolution / 10)
				{
					szPlayers.append(NEWLINE);
					iEnemyCount = 0;
				}
			}
			else if (aiPlayerDiplomacyChanges[iJ] < 0)
			{
				if (!bEnemiesFirst)
					szEnemies.append(CvWString::format(L", "));
				szEnemies.append(CvWString::format(L"<link=literal>%s</link> (%d)", GC.getCivilizationInfo(GET_PLAYER((PlayerTypes)iJ).getCivilizationType()).getShortDescription(), aiPlayerDiplomacyChanges[iJ]));
				bEnemiesFirst = false;
				//Resolution Scaling
				iEnemyCount++;
				if (iEnemyCount > XResolution / 10)
				{
					szEnemies.append(NEWLINE);
					iEnemyCount = 0;
				}
			}
		}
		if (!bFirst)
		{
			szHelpText.append(NEWLINE);
			szHelpText.append(gDLL->getSymbolID(BULLET_CHAR));
			szHelpText.append(gDLL->getText("TXT_KEY_CIVIC_BOOSTS_DIPLOMACY"));
			szHelpText.append(szPlayers);
		}
		if (!bEnemiesFirst)
		{
			szHelpText.append(NEWLINE);
			szHelpText.append(gDLL->getSymbolID(BULLET_CHAR));
			szHelpText.append(gDLL->getText("TXT_KEY_CIVIC_HURTS_DIPLOMACY"));
			szHelpText.append(szEnemies);
		}
	}
 
Now that I have the civic screen code working nicely, I have another area I want to clean up. The text hover shows up fine for the leader heads, but if I use the same TXT_KEY for two different changes, it appears twice, instead of combining... so my next question, how do I compare CvWStrings to see if the contents are the same?
 
I'm pretty sure that the == operator is overloaded for this class.

Code:
CvWString foo = "foo";
CvWString bar = "bar";

if (foo == bar) {
    ...
}
 
I don't understand why my code isn't working...
Code:
if (iPass == 0)
		{
			CivicTypes eTargetCivic;
			CivicTypes eCivic;
			int* paiCivicAttitudeChanges = new int[GC.getNumCivicOptionInfos()];
			CvWString* paszCivicAttitudeReasons = new CvWString[GC.getNumCivicOptionInfos()];
			for (int iJ = 0; iJ < GC.getNumCivicOptionInfos(); iJ++)
			{
				eTargetCivic = GET_PLAYER(eTargetPlayer).getCivics((CivicOptionTypes)iJ);
				for (int iK = 0; iK < GC.getNumCivicOptionInfos(); iK++)
				{
					eCivic = GET_PLAYER(ePlayer).getCivics((CivicOptionTypes)iK);
					iAttitudeChange = GC.getCivicInfo(eTargetCivic).getCivicAttitudeChange(eCivic);
					paiCivicAttitudeChanges[iJ] = iAttitudeChange;
					if (iAttitudeChange != 0)
					{
						paszCivicAttitudeReasons[iJ] = GC.getCivicInfo(eTargetCivic).getCivicAttitudeReason(eCivic);
					}
					else
					{
						paszCivicAttitudeReasons[iJ] = "";
					}
				}
			}
			[COLOR="Lime"]for (int iJ = 0; iJ < GC.getNumCivicOptionInfos(); iJ++)
			{
				if (paszCivicAttitudeReasons[iJ] != (CvWString)"")
				{
					for (int iK = 0; iK < GC.getNumCivicOptionInfos(); iK++)
					{
						if (iJ != iK)
						{
							if (paszCivicAttitudeReasons[iJ] == paszCivicAttitudeReasons[iK])
							{
								paszCivicAttitudeReasons[iK] = "";
								paiCivicAttitudeChanges[iJ] += paiCivicAttitudeChanges[iK];
								paiCivicAttitudeChanges[iK] = 0;
							}
						}
					}
				}
			}[/COLOR]
			for (int iJ = 0; iJ < GC.getNumCivicOptionInfos(); iJ++)
			{
				if (paiCivicAttitudeChanges[iJ] != 0 && paszCivicAttitudeReasons[iJ] != (CvWString)"")
				{
					szBuffer.append(NEWLINE);
					szTempBuffer.Format(SETCOLR L"%s" ENDCOLR, TEXT_COLOR((paiCivicAttitudeChanges[iJ] > 0) ? "COLOR_POSITIVE_TEXT" : "COLOR_NEGATIVE_TEXT"), gDLL->getText(paszCivicAttitudeReasons[iJ], paiCivicAttitudeChanges[iJ]).GetCString());
					szBuffer.append(szTempBuffer);
				}
			}
			SAFE_DELETE_ARRAY(paiCivicAttitudeChanges);
			SAFE_DELETE_ARRAY(paszCivicAttitudeReasons);
		}

I commented out the code in the middle (green), but it has no effect, it simply isn't showing up. If I change the final if check to only check to see that the paszCivicAttitudeReason[iJ] isn't the empty string, the text shows up, but with a 0. However, when I watch the code in VS, I can see it being assigned values...

What I am doing wrong?
 
Top Bottom