Modders Guide to FfH2

I was using the scenario counter earlier but I think it isn't active outside of Scenarios.

The scenario counter should be available anytime. The epic game doesn't use it, but I havent blocked it.
 
In scenarios, it seems to just use gc.getGame().changeScenarioCounter(1) and displays it like this:
Code:
	def getGoalTag(self, pPlayer):
		szBuffer = u"<font=2>"
		if gc.getGame().isOption(GameOptionTypes.GAMEOPTION_WB_AGAINST_THE_WALL):
			if gc.getGame().getScenarioCounter() == 0:
				szBuffer = szBuffer + CyTranslator().getColorText("TXT_KEY_WB_AGAINST_THE_WALL_GOAL_1", (), gc.getInfoTypeForString("COLOR_RED"))


I believe you could make an event that uses python to change the scenario counter and make ScenarioFunctions.py display goals even when not in a scenario.

You would probably want to disable these events during scenarios, or at least try not to use a counter number used in any scenario otherwise.


Edit: I see some people have mentioned the scenario counter already.
 
Kael,

I have a question - I am working on a modmod to add a new civ. As one of the functions, I want some of the civs units to be able to use the unitcreatefromcombat function. I have added this to the xml file for the unit and set the chance at 100% but my they don't seem to be creating the new units when sucessful in combat. Is there something more I'd need to do to enable this function to work?
 
Xienwolf - THANK YOU. Yes, I am using a module. I moved the units that are using unitcreatefromcombat over to the main FFH unitinfo.xml and that fixed it.

Thanks - I spent the last two days trying to figure out what I was doing wrong!! :D
 
Quick question: I was trying to tweak 0.40z so that trading over the ocean would become available with ocean-going vessels (optics), instead of being an end-game tech (astronomy).

This looked pretty straight forward: I moved this section from TECH_ASTRONOMY to TECH_OPTICS and figured I'd be good to go:

<TerrainTrades>
<TerrainTrade>
<TerrainType>TERRAIN_OCEAN</TerrainType>
<bTerrainTrade>1</bTerrainTrade>
</TerrainTrade>
</TerrainTrades>


I loaded up a save game, and sure enough, the ability appears now under Optics instead of under Astronomy on the tech screen. Great! Except, I have Optics, and still am not getting trade connections to anyone else (we are all on separate continents). I thought perhaps I needed to expose enough map to have a path to others' capitals, but this doesn't help either. I've discovered the Lanun capital, and I know they also have Optics because I got a message that the Black Wind had been built.

Is there more I need to do to get trade over oceans working?


thanks,

-gg
 
Trading over ocean is enabled when you GET the tech. Since you already have it now you can't get trade over oceans. Not even if you go into WB and remove/readd the tech will you get it: removing it will cause your ability to trade over oceans to become *negative* and readding it will change it back to zero.

If you really want to do it in that specific save you can remove trading over ocean from optics, load the save and remove the tech from your civ in WB, save the game, put back trade over ocean at optics, load the new save and readd the tech in WB. When you remove / add the tech you should do it for all civs who had the tech when you first moved the ability.
 
Ah, cool. I was hoping it would evaluate trade routes each time it loaded based on the XML, but oh well. Thanks for the fast reply. :)
 
Does pCity.applyBuildEffects(newUnit)give the units all the benefits as if it had been built in the city, including those granted in python? (Among other things, I'm thinking it may be better to have Barnaxus use this instead of seperate condition statements in case I add more golem boosting buildings.)
 
:)Hi Xienwolf - another question for you (sorry that your reward for being so helpful is another question - and gratitude!) I am having another wierd issue where for some reason all my units (even melee, non magic units like scouts) are all given Air2 and Air3 as a promotion option - even though they don't have channeling (or air mana, or Air 1). I haven't touched the promotions in the XML, although for my module I have added some new promotions, but nothing that would affect Air at all. I have only moved part of my files over from the module. Would using a module also maybe mess up the promotions prereqs?

I am wondering if I should scrap using a module althogether if so and just merge my changes into the main FFH folder. I am trying to find the cause of this strange error first. Thanks for any thoughts you might have from you expertise - appreciate it.
 
As Valk says, it is the module that is messing things up. You are overwriting all of the prereqs for the first couple of promotions in the base XML files.

Keep a backup copy of unmodded FfH around somewhere, and make all your changes directly to the mod instead of toying with modules, except in files where you only adjust numerical items and want to have an easy update to any new patches.
 
Great- thanks Valk and Xienwolf - I appreciate it - that's good to know. I have been driving myself crazy trying to find out what could be causing this, going back through all the files I changed and not finding anything that would do this - so it's a relief to know what the cause is (and that I am not crazy). Thanks again for your help!:goodjob:
 
Yes. That should lead you through everything you need. Mostly all in CvInfos and CvUnit (actually, relatively certain COMPLETELY within those two, and some Cy files)
OK while looking for capture code I found it was part of CvUnit::combatWon, which is all this:
Spoiler :

Code:
void CvUnit::combatWon(CvUnit* pLoser, bool bAttacking)
{
    PromotionTypes ePromotion;
    bool bConvert = false;
    int iUnit = NO_UNIT;
    CLLNode<IDInfo>* pUnitNode;
    CvUnit* pLoopUnit;
    CvPlot* pPlot;
    CvUnit* pUnit;

    for (int iI = 0; iI < GC.getNumPromotionInfos(); iI++)
    {
        if (isHasPromotion((PromotionTypes)iI))
        {
            if (GC.getPromotionInfo((PromotionTypes)iI).getFreeXPFromCombat() != 0)
            {
                changeExperience(GC.getPromotionInfo((PromotionTypes)iI).getFreeXPFromCombat(), -1, false, false, false);
            }
            if (GC.getPromotionInfo((PromotionTypes)iI).getModifyGlobalCounterOnCombat() != 0)
            {
                if (pLoser->isAlive())
                {
                    GC.getGameINLINE().changeGlobalCounter(GC.getPromotionInfo((PromotionTypes)iI).getModifyGlobalCounterOnCombat());
                }
		    }
            if (GC.getPromotionInfo((PromotionTypes)iI).isRemovedByCombat())
            {
                setHasPromotion(((PromotionTypes)iI), false);
		    }
            if (GC.getPromotionInfo((PromotionTypes)iI).getPromotionCombatApply() != NO_PROMOTION)
            {
                ePromotion = (PromotionTypes)GC.getPromotionInfo((PromotionTypes)iI).getPromotionCombatApply();
                pPlot = pLoser->plot();
                pUnitNode = pPlot->headUnitNode();
                while (pUnitNode != NULL)
                {
                    pLoopUnit = ::getUnit(pUnitNode->m_data);
                    pUnitNode = pPlot->nextUnitNode(pUnitNode);
                    if (pLoopUnit->isHasPromotion(ePromotion) == false)
                    {
                        if (pLoopUnit->isAlive() || !GC.getPromotionInfo(ePromotion).isPrereqAlive())
                        {
                            if (isEnemy(pLoopUnit->getTeam()))
                            {
                                if (pLoopUnit->canAcquirePromotion(ePromotion))
                                {
                                    if (GC.getGameINLINE().getSorenRandNum(100, "Combat Apply") <= GC.getDefineINT("COMBAT_APPLY_CHANCE"))
                                    {
                                        pLoopUnit->setHasPromotion(ePromotion, true);
                                        gDLL->getInterfaceIFace()->addMessage((PlayerTypes)pLoopUnit->getOwner(), true, GC.getEVENT_MESSAGE_TIME(), GC.getPromotionInfo(ePromotion).getDescription(), "", MESSAGE_TYPE_INFO, GC.getPromotionInfo(ePromotion).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), getX_INLINE(), getY_INLINE(), true, true);
                                        gDLL->getInterfaceIFace()->addMessage((PlayerTypes)getOwner(), true, GC.getEVENT_MESSAGE_TIME(), GC.getPromotionInfo(ePromotion).getDescription(), "", MESSAGE_TYPE_INFO, GC.getPromotionInfo(ePromotion).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), getX_INLINE(), getY_INLINE(), true, true);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (GC.getPromotionInfo((PromotionTypes)iI).getCombatCapturePercent() != 0)
            {
                if (iUnit == NO_UNIT && pLoser->isAlive())
                {
                    if (GC.getGameINLINE().getSorenRandNum(100, "Combat Capture") <= GC.getPromotionInfo((PromotionTypes)iI).getCombatCapturePercent())
                    {
                        iUnit = pLoser->getUnitType();
                        bConvert = true;
                    }
                }
            }
            if (GC.getPromotionInfo((PromotionTypes)iI).getCaptureUnitCombat() != NO_UNITCOMBAT)
            {
                if (iUnit == NO_UNIT && pLoser->getUnitCombatType() == GC.getPromotionInfo((PromotionTypes)iI).getCaptureUnitCombat())
                {
                    iUnit = pLoser->getUnitType();
                    bConvert = true;
                }
            }
		}
        if (pLoser->isHasPromotion((PromotionTypes)iI))
        {
            if (GC.getPromotionInfo((PromotionTypes)iI).getPromotionCombatApply() != NO_PROMOTION)
            {
                ePromotion = (PromotionTypes)GC.getPromotionInfo((PromotionTypes)iI).getPromotionCombatApply();
                if (isHasPromotion(ePromotion) == false)
                {
                    if (isAlive() || !GC.getPromotionInfo(ePromotion).isPrereqAlive())
                    {
                        if (pLoser->isEnemy(getTeam()))
                        {
                            if (GC.getGameINLINE().getSorenRandNum(100, "Combat Apply") <= GC.getDefineINT("COMBAT_APPLY_CHANCE"))
                            {
                                setHasPromotion(ePromotion, true);
                                gDLL->getInterfaceIFace()->addMessage((PlayerTypes)getOwner(), true, GC.getEVENT_MESSAGE_TIME(), GC.getPromotionInfo(ePromotion).getDescription(), "", MESSAGE_TYPE_INFO, GC.getPromotionInfo(ePromotion).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), getX_INLINE(), getY_INLINE(), true, true);
                                gDLL->getInterfaceIFace()->addMessage((PlayerTypes)pLoser->getOwner(), true, GC.getEVENT_MESSAGE_TIME(), GC.getPromotionInfo(ePromotion).getDescription(), "", MESSAGE_TYPE_INFO, GC.getPromotionInfo(ePromotion).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), getX_INLINE(), getY_INLINE(), true, true);
                            }
                        }
                    }
                }
		    }
        }
	}
    if (GET_PLAYER(getOwnerINLINE()).getFreeXPFromCombat() != 0)
    {
        changeExperience(GET_PLAYER(getOwnerINLINE()).getFreeXPFromCombat(), -1, false, false, false);
    }
    if (getCombatHealPercent() != 0)
    {
        if (pLoser->isAlive())
        {
            int i = getCombatHealPercent();
            if (i > getDamage())
            {
                i = getDamage();
            }
            if (i != 0)
            {
                changeDamage(-1 * i, NO_PLAYER);
            }
        }
    }
    if (m_pUnitInfo->isExplodeInCombat() && m_pUnitInfo->isSuicide())
    {
        if (bAttacking)
        {
            pPlot = pLoser->plot();
        }
        else
        {
            pPlot = plot();
        }
        if (plot()->isVisibleToWatchingHuman())
        {
            gDLL->getEngineIFace()->TriggerEffect((EffectTypes)GC.getInfoTypeForString("EFFECT_ARTILLERY_SHELL_EXPLODE"), pPlot->getPoint(), (float)(GC.getASyncRand().get(360)));
            gDLL->getInterfaceIFace()->playGeneralSound("AS3D_UN_GRENADE_EXPLODE", pPlot->getPoint());
        }
    }
    if (GC.getUnitInfo(pLoser->getUnitType()).isExplodeInCombat())
    {
        if (plot()->isVisibleToWatchingHuman())
        {
            gDLL->getEngineIFace()->TriggerEffect((EffectTypes)GC.getInfoTypeForString("EFFECT_ARTILLERY_SHELL_EXPLODE"), plot()->getPoint(), (float)(GC.getASyncRand().get(360)));
            gDLL->getInterfaceIFace()->playGeneralSound("AS3D_UN_GRENADE_EXPLODE", plot()->getPoint());
        }
    }
    if ((m_pUnitInfo->getEnslavementChance() + GET_PLAYER(getOwnerINLINE()).getEnslavementChance()) > 0)
    {
        if (getDuration() == 0 && pLoser->isAlive() && !pLoser->isAnimal() && iUnit == NO_UNIT)
        {
            if (GC.getGameINLINE().getSorenRandNum(100, "Enslavement") <= (m_pUnitInfo->getEnslavementChance() + GET_PLAYER(getOwnerINLINE()).getEnslavementChance()))
            {
                iUnit = GC.getDefineINT("SLAVE_UNIT");
            }
        }
    }
    if (m_pUnitInfo->getPromotionFromCombat() != NO_PROMOTION)
    {
        if (pLoser->isAlive())
        {
            setHasPromotion((PromotionTypes)m_pUnitInfo->getPromotionFromCombat(), true);
        }
    }
    if (getGoldFromCombat() != 0)
    {
        if (!pLoser->isAnimal())
        {
            GET_PLAYER(getOwnerINLINE()).changeGold(getGoldFromCombat());
            CvWString szBuffer = gDLL->getText("TXT_KEY_MESSAGE_GOLD_FROM_COMBAT", getGoldFromCombat()).GetCString();
            gDLL->getInterfaceIFace()->addMessage((PlayerTypes)getOwner(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_GOODY_GOLD", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_WHITE"), getX_INLINE(), getY_INLINE(), true, true);
        }
    }
    if (getDuration() > 0)
    {
        changeDuration(m_pUnitInfo->getDurationFromCombat());
    }
    if (pLoser->getDamageTypeCombat(DAMAGE_POISON) > 0 && GC.getDefineINT("POISONED_PROMOTION") != -1)
    {
        if (isAlive() && getDamage() > 0)
        {
            if (GC.getGameINLINE().getSorenRandNum(100, "Poisoned") >= getDamageTypeResist(DAMAGE_POISON))
            {
                setHasPromotion((PromotionTypes)GC.getDefineINT("POISONED_PROMOTION"), true);
            }
        }
    }
    if (m_pUnitInfo->getUnitCreateFromCombat() != NO_UNIT)
    {
        if (!pLoser->isImmuneToCapture() && pLoser->isAlive() && GC.getUnitInfo((UnitTypes)pLoser->getUnitType()).getEquipmentPromotion() == NO_PROMOTION)
        {
            if (GC.getGameINLINE().getSorenRandNum(100, "Create Unit from Combat") <= m_pUnitInfo->getUnitCreateFromCombatChance())
            {
                pUnit = GET_PLAYER(getOwnerINLINE()).initUnit((UnitTypes)m_pUnitInfo->getUnitCreateFromCombat(), plot()->getX_INLINE(), plot()->getY_INLINE());
                pUnit->setDuration(getDuration());
                if (isHiddenNationality())
                {
                    pUnit->setHasPromotion((PromotionTypes)GC.getDefineINT("HIDDEN_NATIONALITY_PROMOTION"), true);
                }
                iUnit = NO_UNIT;
            }
        }
    }
    if (iUnit != NO_UNIT)
    {
        if ((!pLoser->isImmuneToCapture() && !isNoCapture() && !pLoser->isImmortal())
          || GC.getUnitInfo((UnitTypes)pLoser->getUnitType()).getEquipmentPromotion() != NO_PROMOTION)
        {
            pUnit = GET_PLAYER(getOwnerINLINE()).initUnit((UnitTypes)iUnit, plot()->getX_INLINE(), plot()->getY_INLINE());
            if (getDuration() != 0)
            {
                pUnit->setDuration(getDuration());
            }
            if (iUnit == GC.getDefineINT("SLAVE_UNIT"))
            {
                if (pLoser->getRace() != NO_PROMOTION)
                {
                    pUnit->setHasPromotion((PromotionTypes)pLoser->getRace(), true);
                }
            }
            if (bConvert)
            {
                pLoser->setDamage(75, NO_PLAYER, false);
                pUnit->convert(pLoser);
            }
        }
    }
    if (!isEmpty(GC.getUnitInfo(getUnitType()).getPyPostCombatWon()))
    {
        CyUnit* pyCaster = new CyUnit(this);
        CyUnit* pyOpponent = new CyUnit(pLoser);
        CyArgsList argsList;
        argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCaster));	// pass in unit class
        argsList.add(gDLL->getPythonIFace()->makePythonObject(pyOpponent));	// pass in unit class
        gDLL->getPythonIFace()->callFunction(PYSpellModule, "postCombatWon", argsList.makeFunctionArgs()); //, &lResult
        delete pyCaster; // python fxn must not hold on to this pointer
        delete pyOpponent; // python fxn must not hold on to this pointer
    }
    if (!isEmpty(GC.getUnitInfo(pLoser->getUnitType()).getPyPostCombatLost()))
    {
        CyUnit* pyCaster = new CyUnit(pLoser);
        CyUnit* pyOpponent = new CyUnit(this);
        CyArgsList argsList;
        argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCaster));	// pass in unit class
        argsList.add(gDLL->getPythonIFace()->makePythonObject(pyOpponent));	// pass in unit class
        gDLL->getPythonIFace()->callFunction(PYSpellModule, "postCombatLost", argsList.makeFunctionArgs()); //, &lResult
        delete pyCaster; // python fxn must not hold on to this pointer
        delete pyOpponent; // python fxn must not hold on to this pointer
    }
    if (m_pUnitInfo->getUnitConvertFromCombat() != NO_UNIT)
    {
        if (GC.getGameINLINE().getSorenRandNum(100, "Convert Unit from Combat") <= m_pUnitInfo->getUnitConvertFromCombatChance())
        {
            pUnit = GET_PLAYER(getOwnerINLINE()).initUnit((UnitTypes)m_pUnitInfo->getUnitConvertFromCombat(), getX_INLINE(), getY_INLINE(), AI_getUnitAIType());
            pUnit->convert(this);
        }
    }
}

Now I think if I cut out everything that doesn't relate to the unit capturing code I want it looks like this:
Spoiler :
Code:
void CvUnit::combatWon(CvUnit* pLoser, bool bAttacking)
{
    PromotionTypes ePromotion;
    bool bConvert = false;
    int iUnit = NO_UNIT;
    CLLNode<IDInfo>* pUnitNode;
    CvUnit* pLoopUnit;
    CvPlot* pPlot;
    CvUnit* pUnit;

    for (int iI = 0; iI < GC.getNumPromotionInfos(); iI++)
    {
        if (isHasPromotion((PromotionTypes)iI))
        {
            if (GC.getPromotionInfo((PromotionTypes)iI).getCombatCapturePercent() != 0)
            {
                if (iUnit == NO_UNIT && pLoser->isAlive())
                {
                    if (GC.getGameINLINE().getSorenRandNum(100, "Combat Capture") <= GC.getPromotionInfo((PromotionTypes)iI).getCombatCapturePercent())
                    {
                        iUnit = pLoser->getUnitType();
                        bConvert = true;
                    }
                }
            }
		}
	}
    if (m_pUnitInfo->getUnitConvertFromCombat() != NO_UNIT)
    {
        if (GC.getGameINLINE().getSorenRandNum(100, "Convert Unit from Combat") <= m_pUnitInfo->getUnitConvertFromCombatChance())
        {
            pUnit = GET_PLAYER(getOwnerINLINE()).initUnit((UnitTypes)m_pUnitInfo->getUnitConvertFromCombat(), getX_INLINE(), getY_INLINE(), AI_getUnitAIType());
            pUnit->convert(this);
        }
    }
}
Did I take out too much or leave something else in?
 
Took out too much.

The last chunk (getUnitConvertFromCombat()) is baby spiders upgrading to giant spiders, and wolves/lions upgrading to wolf packs/lion prides.

The first chunk (getCombatCapturePercent()) is Command Promotions. Moving the same unit into your control.

I think I remember you asking about enslavement (generating slaves) originally, so you want getEnslavementChance(), which can come from the unit (Taskmasters) or the Player (Slavery Civic).

This chunk defines your iUnit variable with the unit you will create. Then you need the chunk for if (iUnit != NO_UNIT) to actually spawn it.
 
Took out too much.

The last chunk (getUnitConvertFromCombat()) is baby spiders upgrading to giant spiders, and wolves/lions upgrading to wolf packs/lion prides.
I thought that was something different but I couldn't find what it did.
The first chunk (getCombatCapturePercent()) is Command Promotions. Moving the same unit into your control.

I think I remember you asking about enslavement (generating slaves) originally...

Nope, I did want Command type promotions. Do I have that code right?
 
You still need the iUnit != NO_UNIT chunk at the end, or there will not be any new units created. For you there is only one way to have a new unit made, so you can safely combine the functions of course if you wanted to.
 
Is there an easy way to check a leader's gender in python? I added more leaders to Lord of the Balors (different ones in different versions), and all the gay sex with Sallos seems so wrong. I guess I could hardcode all male leaders like you hardcoded Varn in the python, but it would be a lot simpler if I could just find a simple gender check.
 
Top Bottom