1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

Bug Reports and Discussion

Discussion in 'More Naval AI Modmod' started by Tholal, Jan 3, 2013.

  1. Sezren

    Sezren Chieftain

    Joined:
    Jan 17, 2009
    Messages:
    74
    It happens with 2.7beta too. I figured out a culprit: The option settings. Dont think they are stored in save file. So save file would not help you? 'Quick Combat(Defense)' must be on OFF else it will not happen. I also have 'Show enemy moves' on ON, zoom to combat on OFF, stack attack on OFF - dont know if these matter.

    If you still cannot reproduce it please post again. I will try to provide a save file and all my option settings (screenshots?) then.

    I used 3 galleons & 15 flurries & 2 barb triremes now.
     
  2. Tholal

    Tholal Emperor

    Joined:
    May 19, 2009
    Messages:
    1,676
    This is the issue. Looks like it's due to where Kael placed the FFH-specific code in updateCombat(). Think I might have a solution for it.

    Thanks for providing the extra info!
     
  3. Sezren

    Sezren Chieftain

    Joined:
    Jan 17, 2009
    Messages:
    74
    Longshoremen can be cast with ships that do not have cargo if they pick 'Skeleton Crew' first. In similar fashion it can probably be cast by ships which have too much current cargo (if they have 'Skeleton Crew' active).

    Suggested fix: Increase required cargo if 'Skeleton Crew' is active:

    Code:
    def reqCrewLongshoremen(caster):
    	pPlot = caster.plot()
    	if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LONGSHOREMEN')):
    		return False
    	req_cargo = 1
    	if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SKELETON_CREW')):
    		req_cargo = req_cargo + 1
    	if caster.cargoSpaceAvailable(-1, gc.getInfoTypeForString('DOMAIN_LAND')) < req_cargo:
    		return False
    
    Tested and works for me.


    Edit: There is a similar problem with the other spells that they allow ships to have more cargo than they should have: Picking Cargo spell + loading ship + picking other spell does allow the ship to exceed its cargo limits. Probably would be better to use something other than "cargoSpaceAvailable" as that does not consider current load (edit2: wrong it does, probably was some load/save issue between .py changes which made me believe that).

    Edit2: After some testing:
    Adding this to 'reqCrewBuccaneers' and 'reqCrewNormalCrew' should completely fix it (in combination with the fix for 'reqCrewLongshoremen'):
    Code:
    	if caster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SKELETON_CREW')) and caster.isFull():
    		return False
    
     
  4. Tholal

    Tholal Emperor

    Joined:
    May 19, 2009
    Messages:
    1,676
    Thanks Sezren! I'll add your code before my next beta release.
     
  5. Sezren

    Sezren Chieftain

    Joined:
    Jan 17, 2009
    Messages:
    74
    General about withdrawal:

    Withdrawal due to promotions works on offense and on defense - ok but differs from BTS AFAIK.
    Withdrawal from iWithdrawlProbDefensive works only on defense - as intended obviously.
    Withdrawal from <iWithdrawalProb>20</iWithdrawalProb> only works on offense.
    Is this intentional? At least the displayed stats are misleading: The unit help does not distinct between iWithdrawalProb and withdrawal from promotions.
    It just adds the 2 values and displays them. Which made sense in BTS (no defensive withdrawal based on looking into the respective cpp) but not in FFH2.
    Example: Privateer with 3 promos has 70% withdraw on offense and 50% on defense. The help simply says 70%. I never even knew there was a difference before looking at this stuff in detail.

    --

    There is supposed to be a cap at 90%. The method to ensure the cap is questionable and does only work partially:
    Code:
        if (promotionInfo.getWithdrawalChange() > 0)
        {
            if (promotionInfo.getWithdrawalChange() + m_pUnitInfo->getWithdrawalProbability() + getExtraWithdrawal() > GC.getDefineINT("MAX_WITHDRAWAL_PROBABILITY"))
            {
                return false;
            }
        }
    
    Means to breach the cap:
    a) Cast "fair winds" spell.
    b) Mutation and getting 'Light' promotion.
    c) Probably upgrading a unit (not tested).
    d) The 10% from 'Advanced Tactics' are not considered (not tested).

    There are weird problems due to the questionable method:
    a) Have to wait for 'fair winds' to turn off before picking withdrawal promotion.
    b) Can mutate/upgrade after picking withdrawal promotion but not before.
    c) It can also be advantageous to have less withdrawal (as having more may make it impossible to pick the next withdrawal promotion).
    d) The method only considers offensive withdrawal but the promotions function for defensive withdrawal also. Which means defensive withdrawal is unfairly restricted due to having offensive withdrawal.

    Any reason for not changing the code so that the maximum is actually ensured and all the weirdness due to the questionable code goes away?
    Like - in combination with removing the questionable code: (not tested, cannot compile DLL)
    Code:
    int CvUnit::withdrawalProbability() const
    {
    	if (getDomainType() == DOMAIN_LAND && plot()->isWater())
    	{
    		return 0;
    	}
    
    //FfH: Added by Kael 04/06/2009
        if (getImmobileTimer() > 0)
        {
            return 0;
        }
    //FfH: End Add
    
    	// Advanced Tactics - all units have an inherent 10% withdrawal chance
    	int iWithDrawalBonus = 0;
    	if (GC.getGameINLINE().isOption(GAMEOPTION_ADVANCED_TACTICS))
    	{
    		if ((getDuration() == 0) && !m_pUnitInfo->isObject())
    		{
    			iWithDrawalBonus += 10;
    		}
    	}
    
    [B]	const int prior_code_withdraw = std::max(0, (m_pUnitInfo->getWithdrawalProbability() + getExtraWithdrawal() + iWithDrawalBonus));
    
    	// possibly too slow to read this here like that - may have to 'cache' the constant somewhere
    	// or simply use static but I do not know if that is acceptable/workable in civ 4 code
    	const int max_withdraw = GC.getDefineINT("MAX_WITHDRAWAL_PROBABILITY")); 
    
    	return std::min(max_withdraw, prior_code_withdraw);[/B]
    }
    
    And maybe also getWithdrawlProbDefensive() but AFAIK only Loki can and does (intentionally) exceed the cap and I do not know if the current behaviour (see question above) is intended.
     
  6. Sezren

    Sezren Chieftain

    Joined:
    Jan 17, 2009
    Messages:
    74
    See:
    http://forums.civfanatics.com/showpost.php?p=14050433&postcount=729
    https://bitbucket.org/Tholal/more-naval-ai/issues/65/armageddon-can-hit-player-twice

    I have looked into the .cpp (etc) code and done some testing. Not 100% sure I got everything right.

    The culprit appears to be the way global events are handled. Armageddon Blight is not affected because iGlobal is not set. Presumably (not tested) Hellfire & Wrath are also affected. Maybe some other events too.

    What appears to be happening is the following:

    1) Human player (only happens to human player but not during autoplay as that does not count as "human" in cpp terms) increases AC to 100.

    2) At the end of the humans turn the game will generate a Popup in "void CvPlayer::trigger(const EventTriggeredData& kData)".

    3) Game switches to first/next AI player and ends up in the same function. But the code is different as instead it calls "applyEvent(eEvent, kData.getID());"

    4) In applyEvent setTriggerFired is called. There the misworking happens (but not yet):

    Code:
    void CvPlayer::setTriggerFired(const EventTriggeredData& kTriggeredData, bool bOthers, bool bAnnounce)
    {
    	FAssert(kTriggeredData.m_eTrigger >= 0 && kTriggeredData.m_eTrigger < GC.getNumEventTriggerInfos());
    
    	CvEventTriggerInfo& kTrigger = GC.getEventTriggerInfo(kTriggeredData.m_eTrigger);
    
    	if (!isTriggerFired(kTriggeredData.m_eTrigger))
    	{
    		m_triggersFired.push_back(kTriggeredData.m_eTrigger);
    
    		if (bOthers)
    		{
    			if (kTrigger.isGlobal())
    			{
    				for (int i = 0; i < MAX_CIV_PLAYERS; i++)
    				{
    					if (i != getID())
    					{
    
    //						GET_PLAYER((PlayerTypes)i).setTriggerFired(kTriggeredData, false, false);
                            if (GET_PLAYER((PlayerTypes)i).isAlive())
                            {
                                GET_PLAYER((PlayerTypes)i).setTriggerFired(kTriggeredData, false, false);
                            }
    
    					}
    				}
    			}
    			else if (kTrigger.isTeam())
    			{
    				for (int i = 0; i < MAX_CIV_PLAYERS; i++)
    				{
    					if (i != getID() && getTeam() == GET_PLAYER((PlayerTypes)i).getTeam())
    					{
    						GET_PLAYER((PlayerTypes)i).setTriggerFired(kTriggeredData, false, false);
    					}
    				}
    			}
    		}
    	}
    
    //FfH: Modified by Kael 09/25/2008
    <snip comments>
        if (isAlive())
        {
    		if (!CvString(kTrigger.getPythonCallback()).empty())
            {
                long lResult;
    
                CyArgsList argsList;
                argsList.add(gDLL->getPythonIFace()->makePythonObject(&kTriggeredData));
                argsList.add(getID());	// Player ID
                gDLL->getPythonIFace()->callFunction(PYRandomEventModule, kTrigger.getPythonCallback(), argsList.makeFunctionArgs(), &lResult);
            }
        }
        GC.getGameINLINE().setEventTriggered((EventTriggerTypes)kTriggeredData.m_eTrigger, true);
    //FfH: End Modify
    <snip more code>
    
    After/during the turn of the first/next AI player this function is called for every player - including the human one. The Armageddon code is actually in the python callback. Due to "GC.getGameINLINE().setEventTriggered((EventTriggerTypes)kTriggeredData.m_eTrigger, true);" the event cannot happen again after that.

    5) However: When the next turn for the human comes the popup created in 2) will be shown to the human player. After clicking 'continue' the game goes again into applyEvent (*) and then setTriggerFired. In setTriggerFired the upper part inside "if (!isTriggerFired(kTriggeredData.m_eTrigger))" is skipped but the python callback is called again.

    The python callback code was added for FFH according to the comment. My guess is that simply moving it into the "if (!isTriggerFired(kTriggeredData.m_eTrigger))" block will fix the problem. But the code is very complicated, I do not understand all possibilities and I may be completely wrong.

    (*) This is just a deduction based on gameflow observation as I did not find the code for it.

    Edit: Proposed fix may prevent other events from firing more often than once. Not quite sure how the components in the code are all intended but I will give it another try:
    Code:
    
    void CvPlayer::setTriggerFired(const EventTriggeredData& kTriggeredData, bool bOthers, bool bAnnounce)
    {
    	FAssert(kTriggeredData.m_eTrigger >= 0 && kTriggeredData.m_eTrigger < GC.getNumEventTriggerInfos());
    
    	CvEventTriggerInfo& kTrigger = GC.getEventTriggerInfo(kTriggeredData.m_eTrigger);
    
    [B]	const int global_and_fired = kTrigger.isGlobal() && isTriggerFired(kTriggeredData.m_eTrigger);[/B]
    
    	if (!isTriggerFired(kTriggeredData.m_eTrigger))
    	{
    		m_triggersFired.push_back(kTriggeredData.m_eTrigger);
    
    		if (bOthers)
    		{
    			if (kTrigger.isGlobal())
    			{
    				for (int i = 0; i < MAX_CIV_PLAYERS; i++)
    				{
    					if (i != getID())
    					{
    
    //						GET_PLAYER((PlayerTypes)i).setTriggerFired(kTriggeredData, false, false);
                            if (GET_PLAYER((PlayerTypes)i).isAlive())
                            {
                                GET_PLAYER((PlayerTypes)i).setTriggerFired(kTriggeredData, false, false);
                            }
    
    					}
    				}
    			}
    			else if (kTrigger.isTeam())
    			{
    				for (int i = 0; i < MAX_CIV_PLAYERS; i++)
    				{
    					if (i != getID() && getTeam() == GET_PLAYER((PlayerTypes)i).getTeam())
    					{
    						GET_PLAYER((PlayerTypes)i).setTriggerFired(kTriggeredData, false, false);
    					}
    				}
    			}
    		}
    	}
    
    //FfH: Modified by Kael 09/25/2008
    //	if (!isEmpty(kTrigger.getPythonCallback()))
    //	{
    //		long lResult;
    //		CyArgsList argsList;
    //		argsList.add(gDLL->getPythonIFace()->makePythonObject(&kTriggeredData));
    //		gDLL->getPythonIFace()->callFunction(PYRandomEventModule, kTrigger.getPythonCallback(), argsList.makeFunctionArgs(), &lResult);
    //	}
        if (isAlive()[B] && !global_and_fired[/B])
        {
    		if (!CvString(kTrigger.getPythonCallback()).empty())
            {
                long lResult;
    
                CyArgsList argsList;
                argsList.add(gDLL->getPythonIFace()->makePythonObject(&kTriggeredData));
                argsList.add(getID());	// Player ID
                gDLL->getPythonIFace()->callFunction(PYRandomEventModule, kTrigger.getPythonCallback(), argsList.makeFunctionArgs(), &lResult);
            }
        }
        GC.getGameINLINE().setEventTriggered((EventTriggerTypes)kTriggeredData.m_eTrigger, true);
    //FfH: End Modify
     
  7. Tholal

    Tholal Emperor

    Joined:
    May 19, 2009
    Messages:
    1,676
    Thanks Sezren for digging into that. I gave it a try and first test looked good. Also just added your crew promotion code and it is working well so far. Haven't had a chance to look into the withdrawal stuff.
     
  8. Sezren

    Sezren Chieftain

    Joined:
    Jan 17, 2009
    Messages:
    74
    Ok good. Did you use the first version or the edited one? It should probably get more testing though - like a "beta 3". Most of the gameplay code is pretty simple and easy to understand. But these event code parts are not (easy to understand).
     
  9. ofu

    ofu Chieftain

    Joined:
    Mar 19, 2012
    Messages:
    38
    After revolution,Bannor splited into two factions,but they all called themself Bannor.When another leader asked me to attack the Bannor,I don't know which one he mentioned.

    Is there any way to make the new formed faction have different name? Like New Bannor,Fallen Bannor,or Bannor No.2?

    Or make AI always use leaders' names rather than factions' names in diplomatic window.
     
  10. jackal1234

    jackal1234 Chieftain

    Joined:
    Jul 11, 2010
    Messages:
    99
    Location:
    127.0.0.0
    Cool idea:)
     
  11. Tholal

    Tholal Emperor

    Joined:
    May 19, 2009
    Messages:
    1,676
    You can turn on Dynamic Civ Names in the BUG options (under Scoreboard)

    Diplomacy window is handled in the .exe unfortunately. Not sure that I can change that.
     
  12. ofu

    ofu Chieftain

    Joined:
    Mar 19, 2012
    Messages:
    38

    OK,thank you. And Nice work guys.Looking forward the 27.3 version.
     
  13. Sezren

    Sezren Chieftain

    Joined:
    Jan 17, 2009
    Messages:
    74
    This is in MNAI 2.6.3:

    2x use of undefined variable 'player':
    Code:
    def doOrderVsVeilTemple1(argsList):
    	iEvent = argsList[0]
    	kTriggeredData = argsList[1]
    	pPlayer = gc.getPlayer(kTriggeredData.ePlayer)
    	pCity = pPlayer.getCity(kTriggeredData.iCityId)
    	pCity.changeOccupationTimer(1)
    	iOrder = gc.getInfoTypeForString('RELIGION_THE_ORDER')
    	iVeil = gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL')
    	pCity.setNumRealBuilding(gc.getInfoTypeForString('BUILDING_TEMPLE_OF_THE_VEIL'), 0)
    	if pCity.isHolyCityByType(iVeil) == False:
    		if gc.getGame().getSorenRandNum(100, "Order vs Veil Temple 1") < 50:
    			pCity.setHasReligion(iVeil, False, False, False)
    	(loopCity, iter) = [B]player[/B].firstCity(false)
    	while(loopCity):
    		if loopCity.isHasReligion(iOrder):
    			loopCity.changeHappinessTimer(5)
    		if loopCity.isHasReligion(iVeil):
    			loopCity.changeHurryAngerTimer(5)
    		(loopCity, iter) = [B]player[/B].nextCity(iter, false)
    
    Edit: Not a bug. See post by 'platyping'. '-1' is superfluous.

    Code:
    def spellRobGrave(caster):
    	CyGame().changeGlobalCounter(1)
    	pPlot = caster.plot()
    	pPlot.setImprovementType(-1)
    	pPlayer = gc.getPlayer(caster.getOwner())
    	lList = ['LOW_GOLD', 'HIGH_GOLD', 'SPAWN_SKELETONS', 'SPAWN_SPECTRE']
    	if pPlayer.canReceiveGoody(pPlot, gc.getInfoTypeForString('GOODY_GRAVE_TECH'), caster):
    		lList = lList + ['TECH']
    	sGoody = lList[CyGame().getSorenRandNum(len(lList), "Pick Goody")[B]-1[/B]]
    	if sGoody == 'LOW_GOLD':
    		pPlayer.receiveGoody(pPlot, gc.getInfoTypeForString('GOODY_GRAVE_LOW_GOLD'), caster)
    	if sGoody == 'HIGH_GOLD':
    		pPlayer.receiveGoody(pPlot, gc.getInfoTypeForString('GOODY_GRAVE_HIGH_GOLD'), caster)
    	if sGoody == 'TECH':
    		pPlayer.receiveGoody(pPlot, gc.getInfoTypeForString('GOODY_GRAVE_TECH'), caster)
    	if sGoody == 'SPAWN_SKELETONS':
    		pPlayer.receiveGoody(pPlot, gc.getInfoTypeForString('GOODY_GRAVE_SKELETONS'), caster)
    	if sGoody == 'SPAWN_SPECTRE':
    		pPlayer.receiveGoody(pPlot, gc.getInfoTypeForString('GOODY_GRAVE_SPECTRE'), caster)
    
    IMO it should not be giving a tech anyway though as that is usually much more valuable than sanctify/raise skeleton.
     
  14. platyping

    platyping Sleeping Dragon

    Joined:
    Oct 22, 2010
    Messages:
    4,626
    Location:
    Emerald Dreams
    Assuming TECH is added to the list.
    len(lList) will be 5, where lList[0] will be LOW_GOLD and lList[4] will be TECH.

    CyGame().getSorenRandNum(len(lList), "Pick Goody") will generate a random number from 0 to 4, which is already correct.
    -1 will then reduce this to become a random number from -1 to 3.
    lList[0 to 3] will point correctly to LOW_GOLD to SPECTRE.
    lList[-1] will actually point to TECH because it is the first entry from the end.
    Thus, -1 to 3 will still end up giving equal chance to pick one from the list.

    It is not a bug, but definitely a redundant and pointless step.
     
  15. Sezren

    Sezren Chieftain

    Joined:
    Jan 17, 2009
    Messages:
    74
    Have had demon champions from hellfire appear on volcano.
    Volcano code seems to turn plot into flatlands. Hellfire code checks for flatlands. Other spawn code may be affected as well. For example Hyborem startup code uses "isPeak" which probably returns 'False' (not tested). Likelihood of it happening for Hyborem is very small of course - unlike for hellfire.

    --

    Ok, thanks. Tested it again and actually got a tech from it. Edited post.
     
  16. platyping

    platyping Sleeping Dragon

    Joined:
    Oct 22, 2010
    Messages:
    4,626
    Location:
    Emerald Dreams
    To simplify it further, lList should directly contain ["GOODY_GRAVE_LOW_GOLD", "GOODY_GRAVE_HIGH_GOLD" ... ]

    pPlayer.receiveGoody(pPlot, gc.getInfoTypeForString(sGoody), caster)

    Then there is no need of a chunk of if statements.
     
  17. Tholal

    Tholal Emperor

    Joined:
    May 19, 2009
    Messages:
    1,676
    That works. Thanks guys!
     
  18. MagisterCultuum

    MagisterCultuum Great Sage

    Joined:
    Feb 14, 2007
    Messages:
    16,332
    Location:
    Kael's head
    I just got an event saying that Beeri Bawl had agreed to allow my (Faeryl Viconia's) city of Essuria return to my control, asking if I would welcome the city back or have it become an independent Khazad empire.

    I selected welcome, but then nothign happened.

    I found this in PythonErr.log
    Code:
    Traceback (most recent call last):
    
      File "CvEventInterface", line 34, in applyEvent
    
      File "CvEventManager", line 254, in applyEvent
    
      File "Revolution", line 6181, in joinHumanHandler
    
    KeyError: 1
    ERR: Python function applyEvent failed, module CvEventInterface
    
    Note that this is in my modmod with the 2.7beta2.

    I did not realize until a few minutes ago that a third beta had been released.
     
  19. Tholal

    Tholal Emperor

    Joined:
    May 19, 2009
    Messages:
    1,676

    Thanks for the report! I'll take a look.
     
  20. 25Hour

    25Hour Some sort of lemur

    Joined:
    Mar 14, 2008
    Messages:
    181
    Is anyone else finding that the civilization switch in the "High to Low" challenge mode isn't activating?

    (Just tried out the mod today. The Revolutions integration is excellent!)
     

Share This Page