HOW TO: Add Hidden Nationality to your Mod

Kael

Deity
Joined
May 6, 2002
Messages
17,402
Location
Ohio
Note: This is no longer required with BtS, all you have to do is set the bHiddenNationality attribute to do this

. .. .. .. .. .This article will require a fair understanding of how to modify the SDK. The goal is to create a Hidden Nationality feature for your mod with the following attributes:

1. Hidden Nationality will be marked with a promotion (so it can be added and removed dynamically).

2. Hidden Nationality units will be able to attack other units without declaring war.

3. Hidden Nationality units will be able to be attacked without declaring war.

4. Hidden Nationality units won't be able to attack units of their own civ.

5. The AI will view Hidden Nationality units of other civs as enemies regardless of their real relations with the owning civ.

6. The flags of Hidden Nationality units won't be displayed.

7. The owning civilization of Hidden Nationality units in text tags.

8. Hidden Nationality units can ignore borders.

9. Hidden Nationality units can't capture units (it would reveal their nationality).


. .. .. .. .. .Hidden Nationality could definitely do more than that, but I think that gives us a decent place to start (I'm curious to see how others are able to improve it).


Step 1: Create a Hidden Nationality promotion

. .. .. .. .. .This step is little more than the name suggests. Because I want this article to be as simple as possible I will assume that you create a promotion called "PROMOTION_HIDDEN_NATIONALITY".

. .. .. .. .. .Those who are more familiar with the SDK may want to create a new schema attribute that represents Hidden Nationality so we don't have to query directly for the Promotion each time. But I trust if you are considering the need for that optimization you probably understand how to do it.


Step 2: Create a isHiddenNationality() function

. .. .. .. .. .We need a way to check and see if the unit should have Hidden Nationality or not. Below we have a very simple function that returns true if the unit has the Hidden Nationality promotion and false if they don’t. This is added to CvUnit.cpp and is a new function.

CvUnit.cpp

Code:
//FfH: Added by Kael 09/02/2006
bool CvUnit::isHiddenNationality() const
{
    if (isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_HIDDEN_NATIONALITY")))
    {
        return true;
    }
return false;
}
//FfH: End Add

. .. .. .. .. .The following will also need to be added to CvUnit.h to define the new function:

CvUnit.h

Code:
//FfH: Added by Kael 09/02/2006
	bool isHiddenNationality() const;
//FfH: End Add


. .. .. .. .. .The above function is very simple, but it is a good place to apply any qualifications you may want. For example, in the isHiddenNationality function I use I always have it return false if the unit is in its owners cities. This keeps other civs units from being able to attack them when they are in a city (so you can keep your friends from killing them when you have them garrisoned). Check the spoiler tag for the example code.

Spoiler :

Code:
//FfH: Added by Kael 09/02/2006
bool CvUnit::isHiddenNationality() const
{
    if (isHasPromotion((PromotionTypes)GC.getInfoTypeForString("PROMOTION_HIDDEN_NATIONALITY")))
    {
        CvPlot* pPlot;
        CvCity* pCity;

        pPlot = plot();
        if (pPlot->isCity())
        {
            pCity = pPlot->getPlotCity();
            if (pCity->getOwner() == getOwner())
            {
                return false;
            }
        }
        return true;
    }
	return false;
}
//FfH: End Add



Step 3: Forcing attacks when a Hidden Nationality unit moves on another unit

. .. .. .. .. .For this we are going to monitor the move function. If the unit being moved has hidden nationality and is moving into a plot with visible units that belong to other civs it will force an attack on that plot. Note that this replace the current 'setXY(pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true, (bShow && pPlot->isVisibleToWatchingHuman()));' line.


CvUnit.cpp
void CvUnit::move(CvPlot* pPlot, bool bShow)

Code:
//FfH: Modified by Kael 10/13/2006
//	setXY(pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true, (bShow && pPlot->isVisibleToWatchingHuman()));
    if (isHiddenNationality())
    {
        if (pPlot->isVisibleOtherUnit(getOwnerINLINE()))
        {
            attack(pPlot,false);
        }
        if (!(pPlot->isVisibleOtherUnit(getOwnerINLINE())) || pPlot->getNumUnits() == 1)
        {
            setXY(pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true, (bShow && pPlot->isVisibleToWatchingHuman()));
        }
	}
    else
    {
        setXY(pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true, (bShow && pPlot->isVisibleToWatchingHuman()));
    }
//FfH: End Modify


Step 4: So that the AI views hidden nationality units as enemies

. .. .. .. .. .This is my favorite part, mostly because I had code everywhere to try to get the AI to respond correctly to Hidden Nationality units. Tons of checks in the AI code that said "if the viewed unit is an enemy or is a hidden nationality unit do whatever". But by changing this function the AI always considers Hidden Nationality of other civs as enemies.

CvGameCoreUtils.cpp

Code:
bool PUF_isEnemy(const CvUnit* pUnit, int iData1, int iData2)
{
	FAssertMsg(iData1 != -1, "Invalid data argument, should be >= 0");

//FfH: Added by Kael 09/11/2006
	if (pUnit->isHiddenNationality())
    {
        if (iData1 != pUnit->getOwner())
        {
            return true;
        }
    }
//FfH: End Add

	return atWar(GET_PLAYER((PlayerTypes)iData1).getTeam(), pUnit->getTeam());
}


Step 5: Changing the attributes of Hidden Nationality units

. .. .. .. .. .Changes to the following functions will give Hidden Nationality units their ability to travel through rivals areas and keep then from being able to capture enemy units.

. .. .. .. .. .First a minor change to the isRivalTerritory() functionin CvUnit.cpp, fairly simple.

Code:
bool CvUnit::isRivalTerritory() const
{

//FfH: Added by Kael 09/21/2006
    if (isHiddenNationality())
    {
        return true;
    }
//FfH: End Add

	return GC.getUnitInfo(getUnitType()).isRivalTerritory();
}

Then a similar change to the isNoCapture function (also in CvUnit.cpp).

Code:
bool CvUnit::isNoCapture() const
{

//FfH: Added by Kael 09/21/2006
    if (isHiddenNationality())
    {
        return true;
    }
//FfH: End Add

	return GC.getUnitInfo(getUnitType()).isNoCapture();
}


Step 6: Keeping human players from being able to see a Hidden Nationality units

. .. .. .. .. .The first change is so combat messages don't mention the owning civ for hidden nationality units. Notice there are two changes in the following section (both replacing the normal combat text messages with new ones that don't mention the owning civ.

. .. .. .. .. .Note that for these to work you will need two definitions in your xml text files:

TXT_KEY_MISC_YOU_KILLED_ENEMY_UNIT_HN
. .. .. .. .. .While defending, your %s1_UnitName has killed a %s2_EnUName!
TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED_HN
. .. .. .. .. .While defending, your %s1_UnitName was destroyed by a %s2_EnUName!

CvUnit.cpp
void CvUnit::updateCombat(bool bQuick)

Code:
			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_DIED_ATTACKING", getNameKey(), pDefender->getNameKey());
			gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitDefeatScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_KILLED_ENEMY_UNIT", pDefender->getNameKey(), getNameKey(), GET_PLAYER(getOwnerINLINE()).getCivilizationAdjectiveKey());

//FfH: Added by Kael 09/22/2006
            if (isHiddenNationality())
            {
                szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_KILLED_ENEMY_UNIT_HN", pDefender->getNameKey(), getNameKey());
            }
//FfH: End Add

			gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitVictoryScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());

			// report event to Python, along with some other key state
			gDLL->getEventReporterIFace()->combatResult(pDefender, this);
		}
		else if (pDefender->isDead())
		{
			if (pDefender->isBarbarian())
			{
				GET_PLAYER(getOwnerINLINE()).changeWinsVsBarbs(1);
			}


                if (pPlot->findHighestCultureTeam() != pDefender->getTeam())
                {
                    GET_TEAM(pDefender->getTeam()).changeWarWeariness(getTeam(), GC.getDefineINT("WW_UNIT_KILLED_DEFENDING"));
                }
                if (pPlot->findHighestCultureTeam() != getTeam())
                {
                    GET_TEAM(getTeam()).changeWarWeariness(pDefender->getTeam(), GC.getDefineINT("WW_KILLED_UNIT_ATTACKING"));
                }
                GET_TEAM(getTeam()).AI_changeWarSuccess(pDefender->getTeam(), GC.getDefineINT("WAR_SUCCESS_ATTACKING"));

			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_DESTROYED_ENEMY", getNameKey(), pDefender->getNameKey());
			gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitVictoryScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
			szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED", pDefender->getNameKey(), getNameKey(), GET_PLAYER(getOwnerINLINE()).getCivilizationAdjectiveKey());

//FfH: Added by Kael 09/22/2006
            if (isHiddenNationality())
            {
                szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED_HN", pDefender->getNameKey(), getNameKey(), GET_PLAYER(getOwnerINLINE()).getCivilizationAdjectiveKey());
            }
//FfH: End Add

			gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer,GC.getEraInfo(GC.getGameINLINE().getCurrentEra()).getAudioUnitDefeatScript(), MESSAGE_TYPE_INFO, NULL, (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE());

. .. .. .. .. .The following keeps the units players name from appearing in the side menu:

CvGameTextMgr.cpp
void CvGameTextMgr::setUnitHelp(CvWString &szString, const CvUnit* pUnit, bool bOneLine, bool bShort)

Code:
//FfH: Modified by Kael
//	if (pUnit->getOwnerINLINE() != GC.getGameINLINE().getActivePlayer())
	if (pUnit->getOwnerINLINE() != GC.getGameINLINE().getActivePlayer() && pUnit->isHiddenNationality() == false)
//FfH: End Modify

. .. .. .. .. .And on of the most difficult (so many crashes playing with this), this is the code that keeps the flag from being displayed on Hidden Nationality units.

So that flags are hidden on hidden nationality units
version: 0.16

CvPlot.cpp
void CvPlot::updateFlagSymbol()

Code:
//FfH: Modified by Kael 09/02/2006
//		ePlayer = pCenterUnit->getOwnerINLINE();
        if (pCenterUnit->isHiddenNationality())
        {
            ePlayer = NO_PLAYER;
        }
        else
        {
            ePlayer = pCenterUnit->getOwnerINLINE();
        }
//FfH: End Modify


Step 7: Some fixes

. .. .. .. .. .As you can expect from changes like this we broke some stuff. The game wasn't really designed to include Hidden Nationality units. Here are the fixes I have had to apply. I will add to this section if new issues are reported.

. .. .. .. .. .First of all there was a situation where if a Hidden Nationality unit tries to move into a stack that includes some units from his civ, and some from another he will attack the stack. If his civs unit is the best defender in the stack he will attack one of the players own units. I made the following to getBestDefender to resolve this (it keeps a unit with the same owner from ever being returned as the best defender).

CvPlot.cpp
CvUnit* CvPlot::getBestDefender(PlayerTypes eOwner, PlayerTypes eAttackingPlayer, const CvUnit* pAttacker, bool bTestAtWar, bool bTestPotentialEnemy, bool bTestCanMove) const

Code:
//FfH: Modified by Kael 10/09/2006
//                              if (pLoopUnit->isBetterDefenderThan(pBestUnit, pAttacker))
//                              {
//                                  pBestUnit = pLoopUnit;
//                              }
                                if (pLoopUnit->isBetterDefenderThan(pBestUnit, pAttacker) && (pAttacker == NULL || (pAttacker->getOwner() != pLoopUnit->getOwner())))
                                {
                                    pBestUnit = pLoopUnit;
                                }
//FfH: End Modify

. .. .. .. .. .We also noticed that Hidden Nationality units would attack the stack and then move into it, regardless of existence of other units in that stack. I may the following two errors to resolve this, looking at them now they may be redundant.

CvUnit.cpp
bool CvUnit::canAdvance(const CvPlot* pPlot, int iThreshold) const

Code:
//FfH: Added by Kael 10/13/2006
    if (isHiddenNationality())
    {
        if (pPlot->isVisibleOtherUnit(getOwnerINLINE()))
        {
            return false;
        }
    }
//FfH: End Add

CvUnit.cpp
void CvUnit::updateCombat(bool bQuick)

Code:
//FfH: Modified by Kael 10/13/2006
//                getGroup()->groupMove(pPlot, true, ((bAdvance) ? this : NULL));
                if (isHiddenNationality())
                {
                    if (!(pPlot->isVisibleOtherUnit(getOwnerINLINE())))
                    {
                        getGroup()->groupMove(pPlot, true, ((true) ? this : NULL));
                    }
                }
                else
                {
                    getGroup()->groupMove(pPlot, true, ((bAdvance) ? this : NULL));
                }
//FfH: End Modify
 
This is GREAT!! I was hoping that someone could come up with the code to do this. Any chance someone could put this out as a ModComp for those of us who don't have the programming skills to modify the SDK ourselves?
 
I've not tried this yet but have questions.

1. What happens when a unit with hidden nationality captures a city (can they in fact?), will the city go to the nation of the hidden unit, thus exposing the attacker?

2. Will that nation get a diplomacy penalty?

Very helpful tutorial, thanks.
 
cf_nz said:
I've not tried this yet but have questions.

1. What happens when a unit with hidden nationality captures a city (can they in fact?), will the city go to the nation of the hidden unit, thus exposing the attacker?

2. Will that nation get a diplomacy penalty?

Very helpful tutorial, thanks.

A unit with hidden nationality can't capture. That applies to enemy units and cities. He will just walk into the city and the city will stay under control of the old owner.

In FfH we have an ability on all Hidden Nationality units so they can "Declare Nationality" so they would be able to Dlecare and take the city if the owner wanted. But once a unit declares he can never switch back (and of course the player would need to declare war to get it).
 
Just one thing I noticed, have you tested when a hidden unit is in their own city and then attacks something next to the city?

I believe isHiddenNationality() will return false in that instance.

Dale
 
Okay, I'm implementing pirates into AoD scenario. I've done everything as mentioned here. Problems:

- Pirate in own city doesn't "attack" other nations ship.
- The "do you want to declare war" option still comes up.
- AI doesn't attack HN units.

Basically, combat is still acting as normal.

Dale
 
Dale said:
Okay, I'm implementing pirates into AoD scenario. I've done everything as mentioned here. Problems:

- Pirate in own city doesn't "attack" other nations ship.
- The "do you want to declare war" option still comes up.
- AI doesn't attack HN units.

Basically, combat is still acting as normal.

Dale

Yeah, you are probably using the isHiddenNationalty() function in the spoiler tag instead of the main one listed in the artile. Thats my personal one where I don't want units in cities to look like they are hidden nationality.

You probably want the units to always be hidden nationality (as it is in the main isHiddenNationality() definition). I guess it depends on what you want an allied unit to be able to do when he pulls into port and see's a pirate ship parked there.

If you like having hidden units be able to hide in allied cities (as the spoiler version does) you may want to consider changing it so a unit is always hidden nationality on its owners turn. There may be some issue there with simotaneous turns, but outside of that you should be able to attack as hidden nationality but still keep the "defensive cities" function.
 
Also just noticed in the combat log (not the message bank on the right) the owner of the HN unit is still listed.

Here's how to get around the "Do you want to declare war" prompt and avoid war being declared:

CvSelectionGroup::groupDeclareWar(..)

Code:
if(isHiddenNationality())
{
     return false;
}

I think I'm going to go the direction of my original idea, and that is to use a mission like I did for ranged bombard. I know it works, and I'd like pirates to still be able to be used in conventional warfare.

Dale
 
Dale said:
Also just noticed in the combat log (not the message bank on the right) the owner of the HN unit is still listed.

Here's how to get around the "Do you want to declare war" prompt and avoid war being declared:

CvSelectionGroup::groupDeclareWar(..)

Code:
if(isHiddenNationality())
{
     return false;
}

I think I'm going to go the direction of my original idea, and that is to use a mission like I did for ranged bombard. I know it works, and I'd like pirates to still be able to be used in conventional warfare.

Dale

If im understanding what your saying correctly that code shouldnt work. Because when a unit is inside one of their own cities the unit isn't HN. I think if you test with a unit that isn't in a city it would work as you expect. If that is the case how would you like it to function when the unit is in a city?
 
I'd like the pirate to be a pirate at all times. :)

This is how I what the pirates to work:
- Pirates are hidden nationality (these bits in the code work like a charm)
- Pirates can raid other ships/towns without having to declare war (this failed as it still prompted to declare war on the nation)
- Pirates can be captured by other nations without declaring war (this failed as it prompted to declare war on the pirate owner)
- Pirates can enter friendly cities without raiding them (this works)
- Pirates are "safe" in friendly cities (this works)
- Pirates can raid ships from a city (this fails as the pirate loses hidden nationality in a friendly city)
- Pirates can still do conventional warfare

I know a mission will provide all the functionality I want, including the choice to battle normally. Also, I've got 95% of the code already done (for ranged bombardment). Hence I'll got down the mission path.

Just thought you'd want to know the issues I came across using your code for FfH since you've got HN units. :)

Dale

BTW, I was thinking on the way to work that to elliminate the owner name from the combat logs, have a look through the interface python scripts. I'm pretty sure I remember seeing that stuff in there not the DLL.
 
Well, I can sort of see why attacking from a city could be left as is because if you think about it, a pirate ship sailing directly out of one of your ports and attacking a merchant vessel is going to look quite bad for you... all you have to do is sail it out away from the city one tile and then attack, no?
 
Tell the pirates of Tortuga and Port Royale in 1640 that. ;)

Anyways, it's all about choice. And since a pirate can dock at friendly nations cities there's no guarentee that the pirate comes from that cities owner.

Also, it places a restriction on the concept, and I don't like imposing restrictions when it doesn't need to be. You'd also need to plug the whole in the AI code as well, and that's limiting the AI which as we all know is a bad thing.

Anyways, I've got my code running now. Just cleaning it up. :)

Dale
 
And beautifully! I've got it in my update for AoD which'll be off to the beta testers soon. :)
 
hun+celt said:
This is GREAT!! I was hoping that someone could come up with the code to do this. Any chance someone could put this out as a ModComp for those of us who don't have the programming skills to modify the SDK ourselves?

I'd like to give this a bump. Would love to have this in a mod comp so that I can add it in and start building land-based hidden nationality units (I'm SDK illiterate).
 
So I take it, that there is not a stand-alone mod for Hidden Nationality out by anyone yet?

Any source code from AoD or FfH regarding this code?

No?

/warms up fingers
 
So I take it, that there is not a stand-alone mod for Hidden Nationality out by anyone yet?

Any source code from AoD or FfH regarding this code?

No?

/warms up fingers

All of the source from FfH has been released and you can find it here: . It has the hidden nationality stuff in there as well as a bunch of other stuff.

If you would like to make a stand alone Hidden Nationality mod, you are more than welcome too.
 
I just have a request to include it in a custom mod for a friend. Did you comment the code for this seperately?

That is, if I can get past this weird syntax error at compile, on known good code. Ripping the 'ol hair out.
 
Top Bottom