[NFP] Start an era behind?

Joined
Mar 12, 2019
Messages
100
I'm looking to create a mod where the AI starts one era ahead of the human player. The AI would get all of the same bonuses that if you would normally start the game in that advanced era. My first line of though was to do this manually by giving them a settler, techs, etc. but there has to be a way to use a lua script or something to do this since this is implemented in the game. Could I just change some built-in flag for the AI? Thanks!
 
Thanks for the reply! I've been doing a bit more research about this and I found that there is a parameter called LOC_GAME_START_ERA, here is how it is in the SetupParameters.xml file:

Code:
<Row ParameterId="GameStartEra" Name="LOC_GAME_START_ERA" Description="" Domain="StandardEras" DefaultValue="ERA_ANCIENT" ConfigurationGroup="Game" ConfigurationId="GAME_START_ERA" ValueNameConfigurationId="GAME_START_ERA_NAME" Hash="1" GroupId="GameOptions" SortIndex="60"/>

However, I don't see an option to distinguish between a player and an AI, except for maybe the GroupId? My current line of thinking is to just have the user start the game in a later era then have a mod which deletes the extra units, techs, civics, faith, etc. that a player gets at the start of the game to be comparable with an ancient era start.
 
By "a mod where the AI starts one era ahead of the human player", do you mean that they will just get all the Civics/Techs, of the Era where you start, already unlocked? or that the AI won't spawn (they won't be in the game) till the next Era? I suspect you mean the first one, and if that's the case, then I would go with what Raen suggested (for all the Techs and Civics, the code will be quite long as for making requirements for each era to unlock it's Civics/Techs individually). But if you mean the later one, then you will have to use lua for that.
 
The first one. I would like the AI to get all of their bonuses, etc. just as they would if you started the game in a later era while the human only gets the initial settler and warrior.

I think that I figured out an easy way to do this. I can just create a mod which will edit the Eras.xml file. The file contains all of the starting information:

Code:
    <MajorStartingUnits>
        <!--
            District - District type of district for OnDistrictCreated.  Civ specific districts may be used for targeted bonuses.
                        If a civ specific district is not given specific starting units, it will use the starting units for its base district type.
            OnDistrictCreated - Unit spawns when the district given by District is created.  
        -->
        <Row Era="ERA_ANCIENT" Unit="UNIT_SETTLER"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_WARRIOR" NotStartTile="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_SETTLER" AiOnly="true" MinDifficulty="DIFFICULTY_EMPEROR" DifficultyDelta="0.5" OnDistrictCreated="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_WARRIOR" AiOnly="true" MinDifficulty="DIFFICULTY_KING" DifficultyDelta="1" NotStartTile="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_BUILDER" AiOnly="true" MinDifficulty="DIFFICULTY_KING" DifficultyDelta="0.5" OnDistrictCreated="true"/>

Now I can just add additional items and use the AiOnly="true" flag.
 
After looking LeeS's mod, ReallyAdvancedSetupLite, I was able to get it to work. Giving players additional technologies was a complete pain! My only issue is that I still cannot just give the civic boosts to the AI. Since the AI gets all spearman in the classical era, I had to remove all of the MajorStartingUnits that the human and AI get within the Era.xml file, just so the human doesn't get the starting spearman. I had to look at my older mod on how to just give the human a warrior in the lua script.

But this code is able to:
  • Give the AI all the techs and civics in the ancient era
  • Give the AI additional spearman, settlers, etc.
  • Have the AI's first city have a starting population of 4
  • Change the AI's gold to 110 and faith to 10
Still missing:
  • Just applying the civic boosts to the AI only. I'm thinking that the StartingBoostedCivics doesn't have an AiOnly option...
  • Giving the minor civs their spearman, etc. This should be really easy though.

Here are the codes:


Code:
<GameData>
    <MajorStartingUnits>
        <Delete Era="ERA_ANCIENT"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_SETTLER"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_BUILDER" AiOnly="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_SCOUT" NotStartTile="true" AiOnly="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_SPEARMAN" AiOnly="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_SETTLER" AiOnly="true" MinDifficulty="DIFFICULTY_EMPEROR" DifficultyDelta="0.5" OnDistrictCreated="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_SPEARMAN" AiOnly="true" MinDifficulty="DIFFICULTY_KING" DifficultyDelta="1" NotStartTile="true"/>
        <Row Era="ERA_ANCIENT" Unit="UNIT_BUILDER" AiOnly="true" MinDifficulty="DIFFICULTY_KING" DifficultyDelta="0.5" OnDistrictCreated="true"/>
    </MajorStartingUnits>
    <StartingBoostedTechnologies>
        <Row Era="ERA_ANCIENT" Technology="TECH_CELESTIAL_NAVIGATION"/>
        <Row Era="ERA_ANCIENT" Technology="TECH_HORSEBACK_RIDING" AiOnly="true"/>
        <Row Era="ERA_ANCIENT" Technology="TECH_IRON_WORKING" AiOnly="true"/>
    </StartingBoostedTechnologies>
    <StartingBoostedCivics>
        <Row Era="ERA_ANCIENT" Civic="CIVIC_DRAMA_POETRY" AiOnly="true"/>
        <Row Era="ERA_ANCIENT" Civic="CIVIC_POLITICAL_PHILOSOPHY" AiOnly="true"/>
    </StartingBoostedCivics>
    <!-- In the base game:-->
    <!-- Ancient era, AI gets: Settler x2, Warrior x2, builder  -->
    <!--Classical era, AI gets: Settler x2, Spearman x2  -->
   
</GameData>

Code:
local AIGold = 110
local AIFaith = 10

function FinishCivic(sCivic, pPlayer, iPlayer)
    -- Dunno what this is for, probably checking to see how much culture has already been
    -- researched for the civic?
    local iCivic = DB.MakeHash(sCivic)
    if iCivic ~= nil then
        local pCulture = pPlayer:GetCulture()
        if pCulture ~= nil then
            -- Finding the civic cost, then applying it
            local iCivicCost = pCulture:GetCultureCost(iCivic)
            pCulture:SetCulturalProgress(iCivic, iCivicCost)
        end
    end
    return nil
end

function GetTechID(sTech)
    local tTechData = GameInfo.Technologies[sTech]
    if tTechData == nil then
        print("GetTechID: invalid sTech, returning -1")
        return -1
    end
    return tTechData.Index
end

function FinishTech(sTech, pPlayer, iPlayer)
    local iTech = GetTechID(sTech)
    if iTech >= 0 then
        local pPlayerTechs = pPlayer:GetTechs()
        if pPlayerTechs ~= nil then
            local iTechCost = pPlayerTechs:GetResearchCost(iTech)
            if (iTechCost ~= nil) and (iTechCost > 0) then
                pPlayerTechs:SetResearchProgress(iTech, iTechCost)
                return true
            end
        end
    end
    return nil
end


function OnCityInit(iPlayer, iCityID, iX, iY)
    local pCities = Players[iPlayer]:GetCities()
    local pCity = pCities:FindID(iCityID)
    if pCity ~= nil then
        if (pCity == pCities:GetCapitalCity()) then
            -- Add additional population to the city
            pCity:ChangePopulation(3)
        end
    end
end





function Initilize(iPlayer, iCityID, iX, iY)

    local pPlayer = Players[iPlayer]
   
        -- Is this the start turn?
        --if Game.GetCurrentGameTurn() == GameConfiguration.GetStartTurn() then

        -- Is the player human? If yes, skip. They don't get any advantage!
    if not pPlayer:IsHuman() then


        pPlayer:GetTreasury():SetGoldBalance(AIGold)
        pPlayer:GetReligion():SetFaithBalance(AIFaith)
        OnCityInit(iPlayer, iCityID, iX, iY)


        -- Give the AI all of the ancient era civics
        FinishCivic("CIVIC_CODE_OF_LAWS", pPlayer, iPlayer)
        FinishCivic("CIVIC_CRAFTSMANSHIP", pPlayer, iPlayer)
        FinishCivic("CIVIC_FOREIGN_TRADE", pPlayer, iPlayer)
        FinishCivic("CIVIC_MILITARY_TRADITION", pPlayer, iPlayer)
        FinishCivic("CIVIC_STATE_WORKFORCE", pPlayer, iPlayer)
        FinishCivic("CIVIC_EARLY_EMPIRE", pPlayer, iPlayer)
        FinishCivic("CIVIC_MYSTICISM", pPlayer, iPlayer)

        -- Give the AI all of the ancient era techs
        FinishTech("TECH_POTTERY", pPlayer, iPlayer)
        FinishTech("TECH_ANIMAL_HUSBANDRY", pPlayer, iPlayer)
        FinishTech("TECH_MINING", pPlayer, iPlayer)
        FinishTech("TECH_SAILING", pPlayer, iPlayer)
        FinishTech("TECH_ASTROLOGY", pPlayer, iPlayer)
        FinishTech("TECH_IRRIGATION", pPlayer, iPlayer)
        FinishTech("TECH_WRITING", pPlayer, iPlayer)
        FinishTech("TECH_ARCHERY", pPlayer, iPlayer)
        FinishTech("TECH_MASONRY", pPlayer, iPlayer)
        FinishTech("TECH_BRONZE_WORKING", pPlayer, iPlayer)
        FinishTech("TECH_THE_WHEEL", pPlayer, iPlayer)
    end

end



-- Function called once a city is founded. Calls initilization if it is the capital city
function OEB_CityFounded(iPlayer, iCityID)
    local pCities = Players[iPlayer]:GetCities()
    local pCity = pCities:FindID(iCityID)
    if pCity ~= nil then
        if (pCity == pCities:GetCapitalCity()) and (pCities:GetCount() == 1) then
            Initilize(iPlayer, iCityID, pCity:GetX(), pCity:GetY())
        end
    end
end

-- Give the human a warrior, I guess....
function GiveStupidHumanWarrior(iPlayer)
    -- Is this the start turn?
    if Game.GetCurrentGameTurn() == GameConfiguration.GetStartTurn() then

        local SpawnTurn = 0
        local playerUnits
        local pPlayer = Players[iPlayer]
        local unitPlot

        if pPlayer:IsHuman() then
            playerUnits = pPlayer:GetUnits()
            for i, unit in playerUnits:Members() do
                local unitTypeName = UnitManager.GetTypeName(unit)
                if "LOC_UNIT_SETTLER_NAME" == unitTypeName then
                    SpawnTurn = 1;
                    unitPlot = Map.GetPlot(unit:GetX(), unit:GetY());      
                end
            end
        end      

        if SpawnTurn == 1 then
            local lastPlot = unitPlot
            local unitIndex = GameInfo.Units["UNIT_WARRIOR"].Index

            for direction = 1, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
                adjacentPlot = Map.GetAdjacentPlot(lastPlot:GetX(), lastPlot:GetY(), direction);
                if (adjacentPlot ~= nil) and not (adjacentPlot:IsWater() or adjacentPlot:IsImpassable()) then
                    playerUnits:Create(unitIndex, adjacentPlot:GetX(), adjacentPlot:GetY())
                    lastPlot = adjacentPlot;
                    break      
                end
            end
            -- Remove this event handler so it does not get called on every game turn afterwards
            Events.PlayerTurnActivated.Remove(GiveStupidHumanWarrior)
        end
    end
end


-- Function to inilize OEB_CityFounded function, which runs whenever a city is founded
function OnLoadScreenClose()
    Events.CityInitialized.Add(OEB_CityFounded)
--Events.PlayerTurnActivated.Add(OnPlayerTurnActivated);
end

Events.LoadScreenClose.Add(OnLoadScreenClose)
-- Add the function to be called when a player turn is activated (AI or Human)
Events.PlayerTurnActivated.Add(GiveStupidHumanWarrior)
 
Some of the bloaty-looking functions in that script were written before we had a better understanding of what is possible via lua. I wrote most of that literally within the first week after Civ6 was originally released. The functions still work but I don't know that I'd write any of it the same way today knowing what I've learned since then.

This is in fact pulling the current turn number and comparing it to that game's start turn number
Code:
if Game.GetCurrentGameTurn() == GameConfiguration.GetStartTurn() then
When you start a game for example in the Renaissance Era the game start turn is not "1" or "0", it is like 200 or something depending on what the selected player game-speed is (Quick, Marathon, etc).

The reason certain event "adds" are contained within a LoadScreenClose function is that otherwise they fire when a saved game is loaded as the game is reloading the map. Events.CityInitialized fires in this case for every city the game re-adds to the map when loading a saved game. By delaying all these Hook-ups until LoadScreenClose, the code does not trigger as a saved map is reloaded for every city unit etc placed on the map as part of the reloading process. Events.LoadScreenClose() fires when the human player clicks Begin Journey or Continue Journey once everything is fully "in game".

It's not a good idea to do a "remove" of function X within the code of function X. In Civ5 this tended to usually cause instant CTD so I'd avoid code like that. I would give the warrior as part of the Capital City founding event. Then there would be no need for the GiveStupidHumanWarrior function.

Code:
if (pCity == pCities:GetOriginalCapitalCity()) and (pCities:GetCount() == 1) then
     if Players[iPlayer]:IsHuman() then
          Players[iPlayer]:GetUnits():Create(GameInfo.Units["UNIT_WARRIOR"].Index, pCity:GetX(), pCity:GetY())
     else
          Initilize(iPlayer, iCityID, pCity:GetX(), pCity:GetY())
     end
end
 
Last edited:
Thanks for the feedback! So if I get what you are saying is the game will re-run the OEB_CityFounded function every time a city is founded in the game, but not add the population since they will have more than once city when they reload the game. Running the function is unnecessary, after the game is created.

The GiveStupidHumanWarrior function came about since I wanted the AI to spawn in the classical era with five spearmen and the human to spawn in the ancient era with only one warrior. This function could be completely eliminated if there was some sort of HumanOnly tag in the xml file, similar to the AiOnly="true" tags. Is there a HumanOnly tag? The only problem that I have with giving the warrior after they found the city is that is not how the original game works, you get the warrior to see more of the starting terrain.

As a side note, I need to add a special exception to give Kupe a warrior since he spawns in the ocean.

Code:
-- Give the human a warrior, I guess....
function GiveStupidHumanWarrior(iPlayer)
    -- Is this the start turn?
    if Game.GetCurrentGameTurn() == GameConfiguration.GetStartTurn() then

        local SpawnTurn = 0
        local playerUnits
        local pPlayer = Players[iPlayer]
        local unitPlot
        local pPlayerCivName = PlayerConfigurations[iPlayer]:GetCivilizationTypeName()


        if pPlayer:IsHuman() then
            playerUnits = pPlayer:GetUnits()
            for i, unit in playerUnits:Members() do
                local unitTypeName = UnitManager.GetTypeName(unit)
                if "LOC_UNIT_SETTLER_NAME" == unitTypeName then
                    SpawnTurn = 1;
                    unitPlot = Map.GetPlot(unit:GetX(), unit:GetY());       
                end
            end
        end       

        if SpawnTurn == 1 then
            local lastPlot = unitPlot
            local unitIndex = GameInfo.Units["UNIT_WARRIOR"].Index

            for direction = 1, DirectionTypes.NUM_DIRECTION_TYPES - 1, 1 do
                adjacentPlot = Map.GetAdjacentPlot(lastPlot:GetX(), lastPlot:GetY(), direction)
                if (adjacentPlot ~= nil) and not (adjacentPlot:IsWater() or adjacentPlot:IsImpassable()) and not (pPlayerCivName == "CIVILIZATION_MAORI") then
                    playerUnits:Create(unitIndex, adjacentPlot:GetX(), adjacentPlot:GetY())
                    lastPlot = adjacentPlot
                    break
                end
                -- Add an exception to Kupe, since he spawns in the ocean
                if pPlayerCivName == "CIVILIZATION_MAORI" and (adjacentPlot ~= nil) and not adjacentPlot:IsImpassable() then
                    playerUnits:Create(unitIndex, adjacentPlot:GetX(), adjacentPlot:GetY())
                    lastPlot = adjacentPlot
                    break
                end
            end
            -- Remove this event handler so it does not get called on every game turn afterwards
            Events.PlayerTurnActivated.Remove(GiveStupidHumanWarrior)
        end
    end
end
 
Top Bottom