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

[NFP] Start an era behind?

Discussion in 'Mod Creation Help' started by inquisitive_otter, Jan 13, 2021.

Tags:
  1. inquisitive_otter

    inquisitive_otter Chieftain

    Joined:
    Mar 12, 2019
    Messages:
    88
    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!
     
  2. raen

    raen Coat of Arms

    Joined:
    May 12, 2003
    Messages:
    2,339
    Location:
    Portugal
    You probably know that by lua you can check if is a human player or not. As for the other part, I believe if you give them a next era tech that they advance to that next Era.
     
  3. inquisitive_otter

    inquisitive_otter Chieftain

    Joined:
    Mar 12, 2019
    Messages:
    88
    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.
     
  4. Zegangani

    Zegangani Warlord

    Joined:
    Oct 9, 2020
    Messages:
    296
    Gender:
    Male
    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.
     
  5. inquisitive_otter

    inquisitive_otter Chieftain

    Joined:
    Mar 12, 2019
    Messages:
    88
    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.
     
  6. inquisitive_otter

    inquisitive_otter Chieftain

    Joined:
    Mar 12, 2019
    Messages:
    88
    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)
     
    raen likes this.
  7. raen

    raen Coat of Arms

    Joined:
    May 12, 2003
    Messages:
    2,339
    Location:
    Portugal
  8. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,072
    Location:
    Illinois, USA
    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: Jan 20, 2021
  9. inquisitive_otter

    inquisitive_otter Chieftain

    Joined:
    Mar 12, 2019
    Messages:
    88
    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
     

Share This Page