Poorly programmed victory competition.

Slyceth

Warlord
Joined
Jul 5, 2021
Messages
169
I've been trying some games without Epic Speed (Noticed immediately that combat and war is broken, considering you upgrade units incredibly quickly at Standard speed, to the point that by the time you arrive or take one city, your unit is superseded, but by a different class)

The biggest problem though is that victory competition only gives you a very glitchy (-190/-290) negative diplomacy penalty. This lasts for 1 turn at most, and just makes AI's break friendship bonds for a few turns then reconsider again. It's just a pain to deal with.

I instead suggest that you make AI's envy the top spots of the leaderboard. If Inca is on top with 2900 points, while the second place is merely at 1500 points, they shouldn't pick on the player who is in last place if they want any chance at winning.
 
Current code:
Spoiler :

Code:
int CvDiplomacyAI::GetVictoryDisputeLevelScore(PlayerTypes ePlayer) const
{
    int iOpinionWeight = 0;

    // Don't stack!
    if ((int)GetVictoryBlockLevel(ePlayer) > (int)GetVictoryDisputeLevel(ePlayer))
        return 0;

    switch (GetVictoryDisputeLevel(ePlayer))
    {
    case DISPUTE_LEVEL_FIERCE:
        iOpinionWeight += /*40*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_FIERCE);
        break;
    case DISPUTE_LEVEL_STRONG:
        iOpinionWeight += /*30*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_STRONG);
        break;
    case DISPUTE_LEVEL_WEAK:
        iOpinionWeight += /*20*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_WEAK);
        break;
    case DISPUTE_LEVEL_NONE:
        iOpinionWeight = /*0*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_NONE);
        break;
    }

    if (iOpinionWeight > 0)
    {
        iOpinionWeight += GET_PLAYER(ePlayer).GetCurrentEra() * /*4*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_PER_ERA);
        iOpinionWeight *= GetVictoryCompetitiveness();
        iOpinionWeight *= GET_PLAYER(ePlayer).isHuman() ? GET_PLAYER(ePlayer).getHandicapInfo().getVictoryDisputePercent() : GC.getGame().getHandicapInfo().getVictoryDisputePercent();
        iOpinionWeight /= 250;
    }

    return iOpinionWeight;
}

int CvDiplomacyAI::GetVictoryBlockLevelScore(PlayerTypes ePlayer) const
{
    int iOpinionWeight = 0;

    // Don't stack!
    if ((int)GetVictoryDisputeLevel(ePlayer) >= (int)GetVictoryBlockLevel(ePlayer))
        return 0;

    switch (GetVictoryBlockLevel(ePlayer))
    {
    case BLOCK_LEVEL_FIERCE:
        iOpinionWeight += /*40*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_BLOCK_FIERCE);
        break;
    case BLOCK_LEVEL_STRONG:
        iOpinionWeight += /*30*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_BLOCK_STRONG);
        break;
    case BLOCK_LEVEL_WEAK:
        iOpinionWeight += /*20*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_BLOCK_WEAK);
        break;
    case BLOCK_LEVEL_NONE:
        iOpinionWeight = /*0*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_BLOCK_NONE);
        break;
    }

    if (iOpinionWeight > 0)
    {
        iOpinionWeight += GET_PLAYER(ePlayer).GetCurrentEra() * /*4*/ GD_INT_GET(OPINION_WEIGHT_VICTORY_BLOCK_PER_ERA);
        iOpinionWeight *= GetVictoryCompetitiveness();
        iOpinionWeight *= GET_PLAYER(ePlayer).isHuman() ? GET_PLAYER(ePlayer).getHandicapInfo().getVictoryBlockPercent() : GC.getGame().getHandicapInfo().getVictoryBlockPercent();
        iOpinionWeight /= 500;
    }

    return iOpinionWeight;
}
(...)

/// Updates what our level of Dispute is with all players over Victory
void CvDiplomacyAI::DoUpdateVictoryDisputeLevels()
{
    if (GetPlayer()->isHuman())
        return;

    //Don't do this at the start of the game.
    if (GC.getGame().getGameTurn() <= 150)
        return;

    AIGrandStrategyTypes eMyGrandStrategy = GetPlayer()->GetGrandStrategyAI()->GetActiveGrandStrategy();
    bool bDontCare = !IsCompetingForVictory() || eMyGrandStrategy == NO_AIGRANDSTRATEGY || GetPlayer()->GetGrandStrategyAI()->GetGrandStrategyPriority(eMyGrandStrategy) <= 500;
    int iGameEra = GC.getGame().getCurrentEra();

    if (bDontCare)
    {
        for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++)
        {
            PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop;
            SetVictoryDisputeLevel(eLoopPlayer, DISPUTE_LEVEL_NONE);
        }
        return;
    }

    // Loop through all (valid) Players
    for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++)
    {
        PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop;

        if (IsPlayerValid(eLoopPlayer) && GET_PLAYER(eLoopPlayer).isMajorCiv())
        {
            AIGrandStrategyTypes eTheirGrandStrategy = GetPlayer()->GetGrandStrategyAI()->GetGuessOtherPlayerActiveGrandStrategy(eLoopPlayer);
            if (eTheirGrandStrategy == NO_AIGRANDSTRATEGY)
            {
                SetVictoryDisputeLevel(eLoopPlayer, DISPUTE_LEVEL_NONE);
                continue;
            }
            if (!IsAtWar(eLoopPlayer) && GetCivOpinion(eLoopPlayer) == CIV_OPINION_ALLY)
            {
                SetVictoryDisputeLevel(eLoopPlayer, DISPUTE_LEVEL_NONE);
                continue;
            }

            DisputeLevelTypes eDisputeLevel = DISPUTE_LEVEL_NONE;
            int iVictoryDisputeWeight = 0;

            // Does the other player's (estimated) Grand Strategy match our own?
            if (GetPlayer()->GetGrandStrategyAI()->GetGuessOtherPlayerActiveGrandStrategy(eLoopPlayer) == eMyGrandStrategy)
            {
                switch (GetPlayer()->GetGrandStrategyAI()->GetGuessOtherPlayerActiveGrandStrategyConfidence(eLoopPlayer))
                {
                case GUESS_CONFIDENCE_POSITIVE:
                    iVictoryDisputeWeight = /*25*/ GD_INT_GET(VICTORY_DISPUTE_GRAND_STRATEGY_MATCH_POSITIVE);
                    break;
                case GUESS_CONFIDENCE_LIKELY:
                    iVictoryDisputeWeight = /*15*/ GD_INT_GET(VICTORY_DISPUTE_GRAND_STRATEGY_MATCH_LIKELY);
                    break;
                case GUESS_CONFIDENCE_UNSURE:
                    iVictoryDisputeWeight = /*5*/ GD_INT_GET(VICTORY_DISPUTE_GRAND_STRATEGY_MATCH_UNSURE);
                    break;
                }
            }

            // Reduce competitiveness in earlier eras
            iVictoryDisputeWeight -= (6 - iGameEra);

            if (iVictoryDisputeWeight > 0)
            {
                int DifficultyModifier = GET_PLAYER(eLoopPlayer).isHuman() ? GET_PLAYER(eLoopPlayer).getHandicapInfo().getVictoryDisputeMod() : GC.getGame().getHandicapInfo().getVictoryDisputeMod();

                // Add weight for Player's competitiveness (1 - 10)
                iVictoryDisputeWeight *= GetVictoryCompetitiveness();
                iVictoryDisputeWeight += DifficultyModifier;

                // Now see what our new Dispute Level should be
                if (iVictoryDisputeWeight >= /*80*/ GD_INT_GET(VICTORY_DISPUTE_FIERCE_THRESHOLD))
                    eDisputeLevel = DISPUTE_LEVEL_FIERCE;
                else if (iVictoryDisputeWeight >= /*50*/ GD_INT_GET(VICTORY_DISPUTE_STRONG_THRESHOLD))
                    eDisputeLevel = DISPUTE_LEVEL_STRONG;
                else if (iVictoryDisputeWeight >= /*30*/ GD_INT_GET(VICTORY_DISPUTE_WEAK_THRESHOLD))
                    eDisputeLevel = DISPUTE_LEVEL_WEAK;
            }

            // Actually set the Level
            SetVictoryDisputeLevel(eLoopPlayer, eDisputeLevel);
        }
        else
        {
            SetVictoryDisputeLevel(eLoopPlayer, DISPUTE_LEVEL_NONE);
        }
    }
}

/// Updates what our level of Dispute is with all players over Victory
void CvDiplomacyAI::DoUpdateVictoryBlockLevels()
{
    if (GetPlayer()->isHuman())
        return;

    //Don't do this at the start of the game.
    if (GC.getGame().getGameTurn() <= 150)
        return;

    AIGrandStrategyTypes eMyGrandStrategy = GetPlayer()->GetGrandStrategyAI()->GetActiveGrandStrategy();
    bool bDontCare = !IsCompetingForVictory() || eMyGrandStrategy == NO_AIGRANDSTRATEGY || GetPlayer()->GetGrandStrategyAI()->GetGrandStrategyPriority(eMyGrandStrategy) <= 500;
    int iGameEra = GC.getGame().getCurrentEra();

    if (bDontCare)
    {
        for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++)
        {
            PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop;
            SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
        }
        return;
    }

    AIGrandStrategyTypes eConquestGrandStrategy = (AIGrandStrategyTypes) GC.getInfoTypeForString("AIGRANDSTRATEGY_CONQUEST");
    AIGrandStrategyTypes eCultureGrandStrategy = (AIGrandStrategyTypes) GC.getInfoTypeForString("AIGRANDSTRATEGY_CULTURE");
    AIGrandStrategyTypes eUNGrandStrategy = (AIGrandStrategyTypes) GC.getInfoTypeForString("AIGRANDSTRATEGY_UNITED_NATIONS");
    AIGrandStrategyTypes eSpaceshipGrandStrategy = (AIGrandStrategyTypes) GC.getInfoTypeForString("AIGRANDSTRATEGY_SPACESHIP");

    // Loop through all (valid) Players
    for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++)
    {
        PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop;

        if (IsPlayerValid(eLoopPlayer) && GET_PLAYER(eLoopPlayer).isMajorCiv())
        {
            AIGrandStrategyTypes eTheirGrandStrategy = GetPlayer()->GetGrandStrategyAI()->GetGuessOtherPlayerActiveGrandStrategy(eLoopPlayer);
            if (eTheirGrandStrategy == NO_AIGRANDSTRATEGY)
            {
                SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
                continue;
            }
            if (!IsAtWar(eLoopPlayer) && GetCivOpinion(eLoopPlayer) >= CIV_OPINION_FRIEND)
            {
                SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
                continue;
            }

            BlockLevelTypes eBlockLevel = BLOCK_LEVEL_NONE;
            int iVictoryBlockWeight = 0;

            CvLeague* pLeague = GC.getGame().GetGameLeagues()->GetActiveLeague();
            bool bLeagueCompetitor = false;
            bool bSpaceRace = false;
            bool bCulture = false;
            bool bWar = false;

            if (pLeague != NULL)
            {
                int iVotes = pLeague->CalculateStartingVotesForMember(eLoopPlayer);
                int iNeededVotes = GC.getGame().GetVotesNeededForDiploVictory();

                if (iNeededVotes > 0)
                {
                    // 33% there? Close!
                    if (iVotes >= (iNeededVotes / 3))
                    {
                        bLeagueCompetitor = true;
                    }
                }
            }

            int iProjectCount = GET_TEAM(GET_PLAYER(eLoopPlayer).getTeam()).GetSSProjectCount();
            if (iProjectCount > 0)
            {
                bSpaceRace = true;
                iVictoryBlockWeight += iProjectCount * 10;
            }
            else
            {
                int iTheirTechNum = GET_TEAM(GET_PLAYER(eLoopPlayer).getTeam()).GetTeamTechs()->GetNumTechsKnown();
                int iNumOtherPlayers = 0;
                int iNumPlayersAheadInTech = 0;

                for (int iOtherPlayerLoop = 0; iOtherPlayerLoop < MAX_MAJOR_CIVS; iOtherPlayerLoop++)
                {
                    PlayerTypes eOtherPlayer = (PlayerTypes) iOtherPlayerLoop;

                    if (GET_PLAYER(eOtherPlayer).getTeam() == GET_PLAYER(eLoopPlayer).getTeam())
                        continue;

                    if (!IsPlayerValid(eOtherPlayer))
                        continue;

                    iNumOtherPlayers++;
                    int iNumTechs = GET_TEAM(GET_PLAYER(eOtherPlayer).getTeam()).GetTeamTechs()->GetNumTechsKnown();
                    if (iTheirTechNum > iNumTechs)
                    {
                        iNumPlayersAheadInTech++;
                    }
                }
                if (iNumPlayersAheadInTech >= iNumOtherPlayers)
                {
                    bSpaceRace = true;
                }
                if (GetTechBlockLevel(eLoopPlayer) >= BLOCK_LEVEL_STRONG)
                {
                    bSpaceRace = true;
                }
            }

            if (GetWarmongerThreat(eLoopPlayer) >= THREAT_SEVERE || (GetPlayerNumMajorsConquered(eLoopPlayer) >= (GC.getGame().countMajorCivsEverAlive() / 3)))
            {
                bWar = true;
            }

            if (IsPlayerWonderSpammer(eLoopPlayer) || GET_PLAYER(eLoopPlayer).GetCulture()->GetNumCivsInfluentialOn() > 1)
            {
                bCulture = true;
            }
            else if (GetPolicyBlockLevel(eLoopPlayer) >= BLOCK_LEVEL_STRONG)
            {
                bCulture = true;
            }

            if ((eConquestGrandStrategy == eTheirGrandStrategy) && !bWar)
            {
                SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
                continue;
            }
            if ((eCultureGrandStrategy == eTheirGrandStrategy) && !bCulture)
            {
                SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
                continue;
            }
            if ((eUNGrandStrategy == eTheirGrandStrategy) && !bLeagueCompetitor)
            {
                SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
                continue;
            }
            if ((eSpaceshipGrandStrategy == eTheirGrandStrategy) && !bSpaceRace)
            {
                SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
                continue;
            }

            // Does the other player's (estimated) Grand Strategy differ from ours? If so, how positive are we about this?
            if (eTheirGrandStrategy != eMyGrandStrategy)
            {
                switch (GetPlayer()->GetGrandStrategyAI()->GetGuessOtherPlayerActiveGrandStrategyConfidence(eLoopPlayer))
                {
                case GUESS_CONFIDENCE_POSITIVE:
                    iVictoryBlockWeight += /*20*/ GD_INT_GET(VICTORY_BLOCK_GRAND_STRATEGY_DIFFERENCE_POSITIVE);
                    break;
                case GUESS_CONFIDENCE_LIKELY:
                    iVictoryBlockWeight += /*15*/ GD_INT_GET(VICTORY_BLOCK_GRAND_STRATEGY_DIFFERENCE_LIKELY);
                    break;
                case GUESS_CONFIDENCE_UNSURE:
                    iVictoryBlockWeight += /*5*/ GD_INT_GET(VICTORY_BLOCK_GRAND_STRATEGY_DIFFERENCE_UNSURE);
                    break;
                }
            }

            if (iVictoryBlockWeight > 0)
            {
                int DifficultyModifier = GET_PLAYER(eLoopPlayer).isHuman() ? GET_PLAYER(eLoopPlayer).getHandicapInfo().getVictoryBlockMod() : GC.getGame().getHandicapInfo().getVictoryBlockMod();

                // Add weight for Player's victory competitiveness, meanness and diplobalance desires (1 - 10)
                // Average of each is 5, and era goes up by one throughout game.
                iVictoryBlockWeight += GetVictoryCompetitiveness() + GetMeanness() + GetDiploBalance() + iGameEra;
                iVictoryBlockWeight += DifficultyModifier;

                // Now see what our new Block Level should be
                if (iVictoryBlockWeight >= /*40*/ GD_INT_GET(VICTORY_BLOCK_FIERCE_THRESHOLD))
                {
                    eBlockLevel = BLOCK_LEVEL_FIERCE;
                }
                else if (iVictoryBlockWeight >= /*30*/ GD_INT_GET(VICTORY_BLOCK_STRONG_THRESHOLD))
                {            
                    eBlockLevel = BLOCK_LEVEL_STRONG;
                }
                else if (iVictoryBlockWeight >= /*20*/ GD_INT_GET(VICTORY_BLOCK_WEAK_THRESHOLD))
                {        
                    eBlockLevel = BLOCK_LEVEL_WEAK;
                }
            }

            // Actually set the new level
            SetVictoryBlockLevel(eLoopPlayer, eBlockLevel);
        }
        else
        {
            SetVictoryBlockLevel(eLoopPlayer, BLOCK_LEVEL_NONE);
        }
    }
}

Feel free to suggest improvements.
 
ChatGPT generated improvements for void CvDiplomacyAI :: DoUpdateVictoryDisputeLevels(). Just to test if AI is any help. What do you think?

  1. Magic Numbers: There are several "magic numbers" in the code, such as 150, 6, 80, 50, and 30, which can make it difficult to understand the purpose of the code. It would be better to define these values as constants or variables with meaningful names.
  2. Code Duplication: There is some code duplication in the if statements that set the Dispute Level to DISPUTE_LEVEL_NONE. You can simplify this by moving the code to a separate function and calling it when necessary.
  3. Inconsistent Naming Convention: The variable DifficultyModifier uses PascalCase naming convention, whereas other variables use camelCase naming convention. It's recommended to use a consistent naming convention throughout the codebase.
  4. Efficiency: The loop iterates over all major civilizations, even if they are not valid players. It would be more efficient to loop only over the valid players instead of iterating over all major civilizations and checking if they are valid.
  5. Commented Out Code: There is a commented-out code that should be removed to avoid confusion.
  6. Redundant Check: In the if (!IsAtWar(eLoopPlayer) && GetCivOpinion(eLoopPlayer) == CIV_OPINION_ALLY) statement, the condition GetCivOpinion(eLoopPlayer) == CIV_OPINION_ALLY is redundant because the IsAtWar(eLoopPlayer) check already excludes allies.
 
I've been trying some games without Epic Speed (Noticed immediately that combat and war is broken, considering you upgrade units incredibly quickly at Standard speed, to the point that by the time you arrive or take one city, your unit is superseded, but by a different class)

The biggest problem though is that victory competition only gives you a very glitchy (-190/-290) negative diplomacy penalty. This lasts for 1 turn at most, and just makes AI's break friendship bonds for a few turns then reconsider again. It's just a pain to deal with.

I instead suggest that you make AI's envy the top spots of the leaderboard. If Inca is on top with 2900 points, while the second place is merely at 1500 points, they shouldn't pick on the player who is in last place if they want any chance at winning.
This have always been an issue with AI ignoring other run away AIs (its way better now than it used to be) part was due to broken diplo AI that have been fixed.
But if you dont find any specific bugs its difficult to translate soft ideas/judgement into code, there is also the fact that points and winning doesnt always add up.
 
ChatGPT generated improvements for void CvDiplomacyAI :: DoUpdateVictoryDisputeLevels(). Just to test if AI is any help. What do you think?

  1. Magic Numbers: There are several "magic numbers" in the code, such as 150, 6, 80, 50, and 30, which can make it difficult to understand the purpose of the code. It would be better to define these values as constants or variables with meaningful names.
  2. Code Duplication: There is some code duplication in the if statements that set the Dispute Level to DISPUTE_LEVEL_NONE. You can simplify this by moving the code to a separate function and calling it when necessary.
  3. Inconsistent Naming Convention: The variable DifficultyModifier uses PascalCase naming convention, whereas other variables use camelCase naming convention. It's recommended to use a consistent naming convention throughout the codebase.
  4. Efficiency: The loop iterates over all major civilizations, even if they are not valid players. It would be more efficient to loop only over the valid players instead of iterating over all major civilizations and checking if they are valid.
  5. Commented Out Code: There is a commented-out code that should be removed to avoid confusion.
  6. Redundant Check: In the if (!IsAtWar(eLoopPlayer) && GetCivOpinion(eLoopPlayer) == CIV_OPINION_ALLY) statement, the condition GetCivOpinion(eLoopPlayer) == CIV_OPINION_ALLY is redundant because the IsAtWar(eLoopPlayer) check already excludes allies.
Nice! I like the first 3 points. It's just a refactor, though. Wouldn't improve gameplay.
 
ChatGPT generated improvements for void CvDiplomacyAI :: DoUpdateVictoryDisputeLevels(). Just to test if AI is any help. What do you think?

  1. Magic Numbers: There are several "magic numbers" in the code, such as 150, 6, 80, 50, and 30, which can make it difficult to understand the purpose of the code. It would be better to define these values as constants or variables with meaningful names.
  2. Code Duplication: There is some code duplication in the if statements that set the Dispute Level to DISPUTE_LEVEL_NONE. You can simplify this by moving the code to a separate function and calling it when necessary.
  3. Inconsistent Naming Convention: The variable DifficultyModifier uses PascalCase naming convention, whereas other variables use camelCase naming convention. It's recommended to use a consistent naming convention throughout the codebase.
  4. Efficiency: The loop iterates over all major civilizations, even if they are not valid players. It would be more efficient to loop only over the valid players instead of iterating over all major civilizations and checking if they are valid.
  5. Commented Out Code: There is a commented-out code that should be removed to avoid confusion.
  6. Redundant Check: In the if (!IsAtWar(eLoopPlayer) && GetCivOpinion(eLoopPlayer) == CIV_OPINION_ALLY) statement, the condition GetCivOpinion(eLoopPlayer) == CIV_OPINION_ALLY is redundant because the IsAtWar(eLoopPlayer) check already excludes allies.
These are not useful, unfortunately:
1 - These are defines that are programmed this way to be customizable via SQL/XML.
2 - Not worth adding a new function for two lines of code that are repeated a few times.
3 - Not up to professional standards, perhaps, but no impact on the code's usefulness.
4 - This is done all over the code, because whether a player is valid or not depends on various conditions (are they alive, have we met them, do they have > 0 cities) that can change. I suppose this could be cached somehow, but it does open up the possibility for even more desyncs in multiplayer, bugs in general, and would be a lot of work.
5 - These are to show the default value of the defines to make the code more readable.
6 - No it doesn't. It is possible to be at war and also for the AI to have 160 opinion. Most commonly seen if war begins because of a Defensive Pact.

I appreciate the effort. None of these are focused on gameplay/balance, though - perhaps you could try with a different prompt.
 
Last edited:
Top Bottom