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 dont. This is added to CvUnit.cpp and is a new function.
CvUnit.cpp
. .. .. .. .. .The following will also need to be added to CvUnit.h to define the new function:
CvUnit.h
. .. .. .. .. .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.
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)
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
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.
Then a similar change to the isNoCapture function (also in CvUnit.cpp).
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)
. .. .. .. .. .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)
. .. .. .. .. .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()
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
. .. .. .. .. .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
CvUnit.cpp
void CvUnit::updateCombat(bool bQuick)
. .. .. .. .. .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 dont. 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