An Idiots Guide to Editing the DLL

NO_GAMEOPTION is not an integer, it is an enumerator. As such, you CAN get away with using it as an integer, since there is a sequential order assigned, but you are FORBIDDEN to use an integer in place of an enumerator. You must cast it, which is what the (GameOptionTypes)iNum does.

So the game will read:

m_iReqGameOption(NO_GAMEOPTION)

as

m_iReqGameOption(-1)

Even though it prefers you don't do things that way. In this particular case, there is no harm done. But what is stored, is a -1.

The proper format for your IF statement is:
if (GC.getGameINLINE().isOption((GameOptionTypes)GC.getUnitInfo(eUnit) .getReqGameOption())).

But you want to make sure you do not make this check if getReqGameOption returns NO_GAMEOPTION, because you don't know if that will return true or false, as it doesn't exist.

So you want to use both cases really. I suspect that:

if (GC.getUnitInfo(eUnit).getNotGameOption() != NO_GAMEOPTION)

will work fine, it will treat NO_GAMEOPTION like a -1 in this case and be able to compare it with an integer. The reason that the other one doesn't work is because .isOption() requires that you pass it a GameOptionTypes variable, so will yell at you for trying to give it an integer.
 
Thanks Xienwolf.

Just a quick clarification, this looks wrong to me:
(GameOptionTypes)GC.getUnitInfo(eUnit) .getReqGameOption(), why is there no period . between GameOptionTypes and GC.getUnitInfo(eUnit)?
 
The period means that you are talking about a property of an object. getUnitInfo(eUnit) has many function inside of it, one of which is getReqGameOption(), so you use a period to "slip inside of" the UnitInfo object and get a piece of it.

But the (GameOptionTypes) is what is known as a TypeCast, it doesn't have any item inside of it, so you can't "slip inside" with a period. What it does instead is convert an item which comes after it, in this case an integer which is plucked by the code GC.getUnitInfo(eUnit).getReqGameOption(), into an enumerator for GameOptionTypes.

So GC.getUnitInfo(eUnit).getReqGameOption() is used to get something like 6

Then (GameOptionTypes)6 is used to get something like GAMEOPTION_ALLOW_ARCHERS.
 
OK, this question may seem apparent, but I'm not seeing it. How does CvInfos know to "type cast" the string passed to it from the UnitInfos XML, with the correct GameOption integer? I never told CvInfos to reference the GameOption index when I have it load the tag ReqGameOption, so how does the program know to set a string to the correct index integer value when storing it's reference variable (and why doesn't it just store it as a string in the first place)?
 
It looks for the exact string which you loaded from the XML, which was GAMEOPTION_BLAH_BLAH. If you happened to create a unit with the exact same type name, then it would cause some SERIOUS issues with this part of the code (with all getInfoTypeFor pieces). That is why we have the standard format of starting all unit names with UNIT_ and all promotion names with PROMOTION_ and so on. So the game didn't know, it actually looked at every single enumeration that exists up to that point.
 
OK, so, the preceding part of a string UNIT_, PROMOTION_, etc the dll knows to set these up in their own separate lists?
 
Not really. I edited the last post as soon as I popped it up, but I guess you had already read it. The game actually looks at absolutely every enumeration it already has created by that point. It might be smart enough to do a quick Alpha filter instead of a flat loop over everything though, so as to not waste a ton of time. I haven't looked closely at the precise code setup for that part. But it doesn't have a CLUE that you want a GameOption, so if you list UNIT_WARRIOR in this field, it will find the integer which matches up with a UNIT_WARRIOR and load that integer, even though it is probably a number FAR higher than the total number of gameoptions in the game, and it'll cause a confusing bug for you later on (many people actually run into this issue with Units and Buildings when they use a CLASS in place of a TYPE, or the other way around)
 
I'm still not getting it. Whenever I reference an object and get an index value, it's specific to that class. NO_GAMEOPTION returns -1, the first gameoption in the XML is going to be 0, etc. Same for units, or any other object list. What's confusing me is how does CvInfos know to set the integer value based on GameOptionInfos.xml?
 
In CvUnit.cpp you would make a function to track the DefensiveOnly status of your unit, and modify ::setHasPromotion to increment a tracking integer when such a promotion is gained. Then you find all locations where isDefensiveOnly() is asked, and point them at CvUnit instead of UnitInfos.

I've made some changes now to CvUnit.cpp and CvUnit.h, modeled on the no bad goodies example, and it compiles, but the changes do nothing. Units with the promotion can still attack. isOnlyDefensive appears to already be pointed at CvUnit - the function itself looks like this:
Code:
bool CvUnit::isOnlyDefensive() const
{
	return m_pUnitInfo->isOnlyDefensive();
}
That's the only time I see the function referenced to a class. How do I make the change?
 
Phungus, the DLL doesn't know to use GameOptionInfos.xml at all. It just has a tracker which says that once upon a time it loaded something with the Type identifier of "GAMEOPTION_BLAH" and it assigned a 12 to that particular item. So when you do a getInfoTypeFor("GAMEOPTION_BLAH") it looks through everything it has ever loaded. All units, all Promotion, all Terrains, all gameoptions, EVERYTHING, until it finds something which it registered as having once loaded as a match for that string. Then it hands you the integer which had been assigned to that item. Thus, if you list <PrereqGameOption>UNIT_WARRIOR</PrereqGameOption>, then the code will not give you an error immediately, it doesn't know that UNIT_WARRIOR is NOT a GameOption, it just knows that UNIT_WARRIOR had been stored once upon a time as a 43, so it gives you a 43 to store in this value. That will lead to errors later in life for you. And the precise reason it can happen is because this function has NO IDEA where to look, it just looks EVERYWHERE.


deanej: You would add an integer to CvUnit.cpp/CvUnit.h which tracks how many promotions the unit currently has with the <bOnlyDefensive> tag. Then you include that integer with this function like so:

Code:
bool CvUnit::isOnlyDefensive() const
{
	return m_pUnitInfo->isOnlyDefensive() || m_iOnlyDefensive > 0;
}

void CvUnit::changeOnlyDefensive(int iChange)
{
	m_iOnlyDefensive += iChange;
}

And in CvUnit::setHasPromotion you will add a line in there along the gist of:

Code:
changeOnlyDefensive(GC.getPromotionInfo(ePromotion).isDefensiveOnly() ? iChange : 0);

I don't remember the PRECISE format, but that should work fairly well, or you can just copy the format of the other boolean expressions already in the function.
 
Well, things just got alot more complicated. It acts as though whatever I enter in the tags is garbage, and gives asserts, and the default XML error message saying "GAMEOPTION_NO_ESPIONAGE" doesn't exist. Specifically, the assert is:
Assert Failed

File: CvGlobals.cpp
Line: 3921
Expression: strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message: info type GAMEOPTION_NO_ESPIONAGE not found, Current XML file is: xml\Buildings/CIV4BuildingInfos.xml
Which makes me think that GameOptionInfos isn't loaded yet. Any ideas as to how to get around this?
 
Gameoptions are hardcoded in the DLL, so they are ALWAYS loaded at ALL times. The catch is that since they ARE hardcoded, they may not exist at all in the lookup tables used by getInfoTypeFor... In fact, thinking about it in that light, I am almost certain that they are NOT included there.

But, since the XML file for GameOptions needs to link the description to the Option, there must be a way to figure out the number properly. I'll actually open the code to check out that link for you instead of guessing and probably leading you astray.

Well craps... the load method is the same as normal. In that case, the descriptions ARE loaded after units, so you can TRY to use Readpass3, or move the loading of gameoptions to the top of:

bool CvXMLLoadUtility::LoadPreMenuGlobals()


Normally I wouldn't advise moving something in that function, but GameOptions don't depend on anything else, so I don't see any reason NOT to load them first, so it should be safe in this particular case, and might be all you need to fix things.
 
Thanks Xienwolf, that works. I am as well a bit apprehensious about changing the loading order of the XML, but I don't see how it can hurt in this instance; as far as I can see GameOptionInfos references absolutely nothing. It's kind of weird that since these have to exist in the dll in CvEnums, that they are loaded so late in the XML anyway, I don't see any reason for it.
 
Is there a:
Code:
<ElementType name="PrereqCivic" content="textOnly"/>

Anywhere in your code? You also need that line, along with the:

Code:
	<ElementType name="PrereqCivics" content="eltOnly">
		<element type="PrereqCivic" minOccurs="0"/>
	</ElementType>

I'm trying once again to do this, but I cannot even get the schema set up. Unfortunately I could not follow the tutorial on cloning an existing array and make it work (which sucks I was able to dive right in following the boolean tutorial, and use it to create new integers); I'm currently failing at the very first step with setting up the schema. Was hoping it was the missing textOnly line, but that has not helped. Here is my current schema:

Schema
Code:
	<ElementType name="NotGameOption" content="textOnly"/>
	<ElementType name="ReqCivicOrs" content="textOnly"/>
	<ElementType name="ReqCivicOrs" content="eltOnly">
		<element type="CivicOption" minOccurs="0" maxOccurs="*"/>
	</ElementType>
...
		<element type="NotGameOption" minOccurs="0"/>
		<element type="ReqCivicOrs" minOccurs="0"/>
UnitInfos:
Code:
			<NotGameOption>NONE</NotGameOption>
			<ReqCivicOrs>
				<CivicOption>CIVIC_THEOCRACY</CivicOption>
				<CivicOption>NONE</CivicOption>
				<CivicOption>NONE</CivicOption>
			</ReqCivicOrs>
The NotGameOption tag is one of the ones I just added with your help described a couple posts above. It works fine, just referencing it here to show the placement is the same. I have also posted in Afforess's trouble with arrays thread, as I stand a good chance of running into more issues after the schema is fixed, and the array thread in the Python/SDK forum seems a good spot for such a post to be.
 
in your schema you need to replace the first reqCivicOrs with CivicOption (and make sure CivicOption isn't listed twice in the schema file)
 
Mildly amusing, but congratulations phungus. :) I decided to spend a little time tonight on writing up the next leg of the tutorial, remembered that I had a pending request for a "create your own array" item, and since the idea for the tag is the hard part I would have been able to just belt it out.

Alas, I found that the request was for precisely what you just finished, a CivicPrereq Array for units :)

So again, grats on getting it done for yourself :)
 
I'm having issues again. I'm trying to add a bNoCloaking tag to Civ4UnitInfos that if set to 1 will block a unit from being able to cloak. It's not supposed to do anything in the SDK, just act as a hook for the python code and display text for the civilopedia and build list. I've added in all the code to CvInfos, CvGameTextMgr, and CyInfoInterface, but the text does not display, and I get python exceptions when the tech for cloaking is researched.

CvInfos.cpp
Code:
m_bNoRevealMap(false),
//Star Trek - cloaking
m_bNoCloaking(false),
m_fUnitMaxSpeed(0.0f),
Code:
bool CvUnitInfo::isNoRevealMap() const
{
	return m_bNoRevealMap;
}

//Star Trek - cloaking
bool CvUnitInfo::isNoCloaking() const
{
	return m_bNoCloaking;
}

float CvUnitInfo::getUnitMaxSpeed() const
{
	return m_fUnitMaxSpeed;
}
Code:
	stream->Read(&m_bNoRevealMap);
	//Star Trek - cloaking
	stream->Read(&m_bNoCloaking);

	stream->Read(&m_fUnitMaxSpeed);
Code:
	stream->Write(m_bNoRevealMap);
	//Star Trek - cloaking
	stream->Write(m_bNoCloaking);

	stream->Write(m_fUnitMaxSpeed);
Code:
	pXML->GetChildXmlValByName(&m_bNoRevealMap,"bNoRevealMap",false);
	//Star Trek - cloaking
	pXML->GetChildXmlValByName(&m_bNoCloaking,"bNoCloaking",false);

	pXML->SetVariableListTagPair(&m_pbUpgradeUnitClass, "UnitClassUpgrades", sizeof(GC.getUnitClassInfo((UnitClassTypes)0)), GC.getNumUnitClassInfos());

CvInfos.h
Code:
	bool isNoRevealMap() const;			// Exposed to Python
	//Star Trek - cloaking
	bool isNoCloaking() const;

	float getUnitMaxSpeed() const;					// Exposed to Python
Code:
	bool m_bNoRevealMap;
	//Star Trek - cloaking
	bool m_bNoCloaking;
	int m_iLeaderPromotion;

CvGameTextMgr.cpp, in setUnitHelp
Code:
		if (pUnit->getUnitInfo().isNoRevealMap())
		{
			szString.append(NEWLINE);
			szString.append(gDLL->getText("TXT_KEY_UNIT_VISIBILITY_MOVE_RANGE"));
		}

		//Star Trek - cloaking
		if (pUnit->getUnitInfo().isNoCloaking())
		{
			szString.append(NEWLINE);
			szString.append(gDLL->getText("TXT_KEY_UNIT_CANNOT_CLOAK"));
		}

		if (!CvWString(pUnit->getUnitInfo().getHelp()).empty())
		{
			szString.append(NEWLINE);
			szString.append(pUnit->getUnitInfo().getHelp());
		}

CyInfoInterface1.cpp
Code:
		.def("isAlwaysHostile", &CvUnitInfo::isAlwaysHostile, "bool ()")
		//Star Trek - cloaking
		.def("isNoCloaking", &CvUnitInfo::isNoCloaking, "bool ()")

		.def("getUnitMaxSpeed", &CvUnitInfo::getUnitMaxSpeed, "float ()")

CvMainInterface.py
Code:
					pUnit = g_pSelectedUnit
					iUnitType = pUnit.getUnitType()
					pUnitOwner = gc.getPlayer( pUnit.getOwner( ))
					pUnitTeam = gc.getTeam(pUnit.getOwner())
					if pUnitOwner.isTurnActive( ):
						if (pUnitTeam.isHasTech(gc.getInfoTypeForString('TECH_CLOAKING_DEVICE')) and not (pUnit.isNoCloaking() or pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK')) or pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CLOAK_FIRE')))):
							screen.appendMultiListButton( "BottomButtonContainer", gc.getPromotionInfo(gc.getInfoTypeForString('PROMOTION_CLOAK')).getButton(), 0, WidgetTypes.WIDGET_GENERAL, 660, 660, False )
							screen.show( "BottomButtonContainer" )
							iCount = iCount + 1

Traceback (most recent call last):

File "CvScreensInterface", line 713, in forceScreenRedraw

File "CvMainInterface", line 811, in redraw

File "CvMainInterface", line 1599, in updateSelectionButtons

AttributeError: 'CyUnit' object has no attribute 'isNoCloaking'
ERR: Python function forceScreenRedraw failed, module CvScreensInterface

All I know is, civ's code and I do NOT get along. Ever.
 
You asked python to display:

pUnit.isNoCloacking()

You need to ask instead:

cg.getUnitInfo(pUnit.getUnitType()).isNoCloacking()


Also, your line in CvGameTextMgr will show the inability to cloak on a unit on the map, but will not show that information in the Civilopedia. You need to add that to the OTHER UnitHelp function in CvGameTextMgr (in the one which the pedia displays you won't have a pUnit to work with)
 
Top Bottom