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

LUA Trait Help

Discussion in 'Civ5 - Creation & Customization' started by CivHawk, Mar 26, 2018.

Tags:
  1. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    After battering my head against the wall trying to come up with a way to code my in LUA, I've hit an actual wall...

    From my limited knowledge of Lua, I cobbled together this 'pretty' piece of code:

    Code:
    function CheckPlayerPeaceDeal(iPlayer)
        local pPlayer = Players[iPlayer]
        local goldFromPeaceDeal = 0
            if pPlayer.GetPeaceDuration() = 10 then
                goldFromPeaceDeal = 500
            end
        end
        return goldFromPeaceDeal
    end
    Does it look right? Have I defined all my variables?

    I did think about using some form of looping iterator, but I don't know how I would do that to cycle through the peace deals of multiple Civs...

    Any help would be greatly appreciated.
     
  2. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    GetPeaceDuration is a Game method, as in Game.GetPeaceDuration(), and is not a valid Player method.

    Game.GetPeaceDuration takes no arguments from my check on William Howard's data on what types and values of information various methods require and return. It will also probably reflect game-speed, though I have not checked for that at any time that I recall.

    Also, you need to use syntax as
    Code:
    pPlayer:IsHuman()
    instead of
    Code:
    pPlayer.IsHuman()
    The later would work but requires you to re-state the pPlayer object as the leading argument in all player methods, as in
    Code:
    pPlayer.IsHuman(pPlayer)
    Which is a bit rudundant and prone to making you have syntax errors by forgetting that redundancy.

    Game and Map methods use the "." syntax in all cases, but the player, team, unit, city, etc., methods are all better used using the ":" syntax.
     
  3. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    And to answer your "usage" question, you would want to probably hook to the Events.WarStateChanged, see http://modiki.civfanatics.com/index.php?title=Events.WarStateChanged_(Civ5_API) The arguments that are passed from the gamecore to any "listener" hooked to that event are TeamID datas rather than player ID datas because players do not actually "own" wars, techs, and certain other things: teams "own" them. So you need to determine whether the player you are interested in belongs to one of the teams involved. If not, then you need to determine which players belong to these two teams and determine if they are "neighbors" or not.

    But within 10 tiles of your capital would seem to mean that the condition would never likely be met on larger maps, so the UA would be unusable on larger maps after all that coding work.
     
  4. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    Appreciate the feedback!

    Noted. I did think that it might be too ambitious. If I make it for just any team, then a large map with lots of Civs could potentially generate a lot of gold. So that's why I wanted to keep it within a certain distance. Instead of 10 tiles from the capital, is there a rule that can check for, say, territory adjacent to mine instead?

    Ah. I've been looking at that spreadsheet that 'whoward' made... seemed to state ".GetPeaceDuration()" was the way to use it, but I'll try the other way.

    That makes sense too, thanks. I didn't realise that teams and players had different ways of looking at the same effect. Would the Events.WarStateChanged act as the 'hook' in this case (sorry, am n00b)...

    Also, do you reckon the code so far could work if I make those changes to it?
     
  5. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    This is my latest attempt, but now this is for the trait:

    Code:
    local civilizationID = GameInfoTypes["CIVILIZATION_CIV_NAME"]
    
    function PeaceDealBonus(playerID)
        local pTeam = Teams[teamID]
            for teamID = 0, GameDefines.MAX_CIV_TEAMS - 1 do          
            if pPlayer ~= nil and player:GetCivilizationType() == civilizationID then
                CivName = pTeam
                break
            end
        end
        local AliveTeamCount = pGame:CountCivTeamsAlive()
        local Gold = math.floor(2000 / AliveTeamCount)
            for CivName do
            pTeam:ChangeGold(Gold)
        end
        end
        end
    Events.WarStateChanged(false war) --could change to 'peace'
    Would you say the code above would work to satisfy that?
     
    Last edited: Mar 31, 2018
  6. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    You're not understanding how "hooks" for Civ5, and variables and functions work whether for Civ5 or just standard lua usable in any program that makes use of lua.

    This definition
    Code:
    void Events.WarStateChanged(TeamID team1, TeamID team2, bool war)
    from the page I linked you to earlier is specific to a civ5 implementation and means the following:
    1. "void" means that any function subscribed to this game event does not return any value to the gamecore for processing by the game's core execution code
    2. "Events.WarStateChanged" specifies a type of event (often referred to as a "hook") that fires information from the gamecore to any function in lua that is "subscribed" to "listen" (ie, is "hooked") to the event. Firaxis pre-determines under what game-conditions each specific event will fire (ie, what triggers it) and what data is passed as arguments to any lua function that is hooked to each specific event. There can theoretically be an infinite number of different functions all running in the same or different mods which are "hooked" to "listen" to any single pre-defined event.
    3. "(TeamID team1, TeamID team2, bool war)" tells you what arguments are passed from the gamecore to any lua function "hooked" to the event, and what type of data will be passed.
      • TeamID team1, tells us that the 1st argument will be sent data for the team ID number of the 1st player team. Since team IDs are always integer values this will therefore be an integer type of data.
      • TeamID team2, tells us that the 2nd argument will be sent data for the team ID number of the 2nd player team. Since team IDs are always integer values this will therefore be an integer type of data.
      • bool war tells us that the 3rd argument will be sent data for whether or not the change in war-state was a declaration of war, and that boolean true or false will be passed. In the case of a war being declared the data passed will be boolean true.
      • I do not now remember whether "team1" will be the team that initiated the war/peace change, or merely the lower-numbered team ID number.
    -----------------------------------------------------------------------------

    When you "subscribe" a function to a "hook" you need one of two syntax methods, but I prefer the one I will show:
    Code:
    Events.WarStateChanged.Add(SomeFunctionName)
    This will "subscribe" whatever function we define as SomeFunctionName to the event "hook", and function SomeFunctionName will be activated with the pertinent argument data every time a war-state changes in-game.
    Code:
    function SomeFunctionName(Team1, Team2, War)
    	if (War == false) then
    		print("Someone Made Peace")
    	end
    end
    Events.WarStateChanged.Add(SomeFunctionName)
    As written this does not actually do much other than print to the log whenever the change in war-status between two teams is not a declaration of war.

    -----------------------------------------------------------------------------

    Note that instead of putting in (TeamID team1, TeamID team2, bool war) with a desired value on the line that starts the function definition, I merely state variable-names this particular function will use for the three arguments the gamecore passes to the hooked function, ie, (Team1, Team2, War). You can call the variables you are going to use within the function anything you want since these names are really only data-holders for the info the game passes to the function. So I could also call the names of the three arguments
    Code:
    function SomeFunctionName(Cheeseburgers, Hamburgers, Tomato)
    So long as I use these variable names properly within the function I am making (ie, correct for the data that is passed to each argument-variable from the gamecore) all will still work. I can therefore do
    Code:
    function SomeFunctionName(Cheeseburgers, Hamburgers, Tomato)
    	if (Tomato == false) then
    		print("Someone Made Peace")
    	end
    end
    Events.WarStateChanged.Add(SomeFunctionName)
    As a general rule you should assign variable-names in your function defining line that make sense for the type of data that the gamecore passes for that argument.

    -----------------------------------------------------------------------------

    As to the code you posted:
    Code:
    local civilizationID = GameInfoTypes["CIVILIZATION_COSTA_RICA"]
    
    function PeaceDealBonus(playerID)
    	local pTeam = Teams[teamID]
    	for teamID = 0, GameDefines.MAX_CIV_TEAMS - 1 do           
    		if pPlayer ~= nil and player:GetCivilizationType() == civilizationID then
    			CostaRica = pTeam
    			break
    		end
    	end
    	local AliveTeamCount = pGame:CountCivTeamsAlive()
    	local Gold = math.floor(2000 / AliveTeamCount)
    	for CostaRica do
    		pTeam:ChangeGold(Gold)
    	end
    end
    
    Events.WarStateChanged(false war) --could change to 'peace'
    Your code will not work as written given your misunderstanding of how hooks, listener functions, and argument names work. But you also have some more basic problems:
    1. Here you are attempting to create a team object variable
      Code:
      local pTeam = Teams[teamID]
      But this cannot work since you are using an undefined variable called teamID.
    2. Here you are attempting to use two different player object variable-names (pPlayer and player), neither of which have been previously defined, and are therefore assigned a value of "nil" by lua:
      Code:
      if pPlayer ~= nil and player:GetCivilizationType() == civilizationID then
    3. Here you are defining a variable called CostaRica but it will be given a value of "nil" since you are setting it equal to another variable that will have a value of "nil" as mentioned before
      Code:
      CostaRica = pTeam
    4. This has three different incorrect usages:
      Code:
      local AliveTeamCount = pGame:CountCivTeamsAlive()
      • pGame has never been defined anywhere and therefore has a value of "nil"
      • "Game" methods do not require nor want an "object" variable, and are always stated as "Game." because there is never more than one game at a time.
      • Game methods use "." syntax instead of ":" syntax so the correct way to access that info is
        Code:
        local AliveTeamCount = Game.CountCivTeamsAlive()
    5. Here you are attempting to throw an object variable into a for loop as its "limiter":
      Code:
      for CostaRica do
      	pTeam:ChangeGold(Gold)
      end
      This is both uneccessary and would not work anyway. You would simply just state
      Code:
      pTeam:ChangeGold(Gold)
      However, see the next item:
    6. There is no "Team" method called ChangeGold() so you cannot do
      Code:
      pTeam:ChangeGold(Gold)
      Also, since as noted above, pTeam is holding a value of "nil", you cannot run any "Team" methods on it: lua will simply generate an error from attempting to "Index" a nil value. What you want to change a player's gold is the player method
      Code:
      PlayerObjectVariableName:ChangeGold(Gold)
      But you first need to have a valid player object variable name defined, with a valid value -- you then stick this valid player object variable name in place of the variable-name I called PlayerObjectVariableName for example purposes.
    7. This would cause the game to generate a fatal file syntax error because it is telling the game to assign a function called false war as a "listener" subscribed to the Events.WarStateChanged hook-event
      Code:
      Events.WarStateChanged(false war)
      No such function with that name exists within the lua file.
    -----------------------------------------------------------------------------

    Assuming I understand what you are currently going for, I would write the code as:
    Code:
    local civilizationID = GameInfoTypes["CIVILIZATION_COSTA_RICA"]
    --create boolean flag for whether peace effect applies when one of players making peace is a Costa Rica player
    local bApplyWhenCostaRicaInvolved = false
    
    --create tables and boolean to track active Costa Rica players
    local tCostaRicaPlayerIDs, tCostaRicaTeamIDs, bCostaRicaActive = {}, {}, false
    
    --get the Costa Rica player's ID and Team ID numbers and stick these in the lua tables
    for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
    	local pLoopPlayer = Players[iPlayer]
    	if pLoopPlayer:IsEverAlive() and (pLoopPlayer:GetCivilizationType() == civilizationID) then
    		local iLoopTeam = pLoopPlayer:GetTeam()
    		table.insert(tCostaRicaPlayerIDs, {PlayerID=iPlayer, TeamID=iLoopTeam})
    		if not tCostaRicaTeamIDs[iLoopTeam] then
    			tCostaRicaTeamIDs[iLoopTeam] = {}
    		end
    		table.insert(tCostaRicaTeamIDs[iLoopTeam], iPlayer)
    		bCostaRicaActive = true
    	end
    end
    function PeaceTreatySigned(Team1, Team2, War)
    	if (War == false) then
    		print("Someone Made Peace")
    		local bExecuteEffect = false
    		local Gold = math.floor(2000 / Game.CountCivTeamsAlive())
    		if (bApplyWhenCostaRicaInvolved == true) then
    			bExecuteEffect = true
    		else --this will run when (bApplyWhenCostaRicaInvolved == false)
    			if (not tCostaRicaTeamIDs[Team1]) and (not tCostaRicaTeamIDs[Team2]) then
    				bExecuteEffect = true
    			end
    		end
    		if (bExecuteEffect == true) then
    			for key,Data in ipairs(tCostaRicaPlayerIDs) do
    				local pPlayer = Players[Data.PlayerID]
    				pPlayer:ChangeGold(Gold)
    			end
    		end
    	end
    end
    function OnLoadScreenClose()
    	Events.WarStateChanged.Add(PeaceTreatySigned)
    end
    if (bCostaRicaActive == true) then
    	--delay actual event hook to the Events.LoadScreenClose so that game reload does not cause the code to fire
    	--the code will also never execute if Costa Rica is not part of the current game
    	Events.LoadScreenClose.Add(OnLoadScreenClose)
    end
    Since City-States also have Teams and follow their allies into war (and peace), you'll get multiple firings of the code if the players involved in the peace deal are allied to city-states, which may or may not be over-powered. The code as written does not account for issues related to city-states following Major players into war and peace.
     
    Troller0001 likes this.
  7. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    Thank you so much for your detailed answer, it's honestly really appreciated

    I've been developing my understanding of .lua day-by-day. But there's still a lot of stuff I don't understand the purpose of, let alone the existence of certain processes. As such, I have a few more questions if that's ok:

    1. I had no idea that you could create tables. If I understand correctly, is their purpose to store information so that future code can pull data from it?

    When is the best time to use tables and when is it not? Should tables only be used when storing integers generated by a loop? Can other things be stored in tables, like text strings, etc.?



    2. Also, for this bit of code that you mentioned:

    Code:
    --create boolean flag for whether peace effect applies when one of players making peace is a Costa Rica player
    local bApplyWhenCostaRicaInvolved = false
    If I were to change that to true, then even Costa Rica's own peace deals will also be included, as opposed to just all of the other teams deals?

    3. I also don't understand the difference between '=' and '==', if there is any. For example:

    Code:
    Line 7: local bApplyWhenCostaRicaInvolved = false
    and

    Code:
    Line 31: if (bApplyWhenCostaRicaInvolved == true) then
    Line 32:                 bExecuteEffect = true
    Is one for variable definitions and one for actual logic?

    4. Has this bit:

    Code:
    Line 29:        local bExecuteEffect = false
    been set as like a default answer, that has been overidden by the code below it?

    Code:
            if (bApplyWhenCostaRicaInvolved == true) then
                bExecuteEffect = true
    5. I do indeed intend to remove city-states from the calculation as I believe it would be OP (either that or reduce the amount of base gold earned (e.g. 1000 instead of 2000)...)

    But lets say I try to remove the city-states, would I have to create a table of all the alive city-states, like you made for the MAX_MAJOR_CIVS, and then say:

    Code:
    local bApplyWhenMinorCivIsInvolved = false
    Code:
    local tMinorCivPlayerIDs, tMinorCivTeamIDs, bMinorCivActive = {}, {}, false
    Code:
    for iPlayer = 0, GameDefines.MAX_MINOR_CIVS - 1 do
        local pLoopMinorCivPlayer = Players[iPlayer]
        if pLoopMinorCivPlayer:IsEverAlive() then
            local iLoopMinorCivTeam = pLoopMinorCivPlayer:GetTeam()
            table.insert(tMinorCivPlayerIDs, {PlayerID=iPlayer, TeamID=iLoopMinorCivTeam})
            if not tMinorCivTeamIDs[iLoopMinorCivTeam] then
                tMinorCivTeamIDs[iLoopMinorCivTeam] = {}
            end
            table.insert(tMinorCivTeamIDs[iLoopMinorCivTeam], iPlayer)
            bMinorCivActive = true
        end
    end
    
    Code:
                if (tMinorCivTeamIDs[Team2]) then
                    bExecuteEffect = false
                end
    
    Would that work?
     
  8. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    "=" sets a value

    "==" is used as the "is equal to" evaluation in "if" blocks

    changing local bApplyWhenCostaRicaInvolved = false to local bApplyWhenCostaRicaInvolved = true would indeed allow the code to add the gold when a Coasta Rica player is involved.

    Since Teams have "Team Objects" we can get the objects for both Teams involved and check whether either of the two teams involved is a Minor Civ (ie, City-State). And setting up a boolean flag like you proposed could then be used to determine whether or not such a check is made by the code. But, no, the loop you proposed would not work since it would run through all the players again including the Major Civs.

    local bExecuteEffect = false. Yes, that is exactly the purpose. Set a flag to a "default" value and then let the rest of the code override the default value when the correct conditions are met. By placing this flag's definition inside the function called PeaceTreatySigned the flag and any changes made to it are only valid during that individual execution of the function.

    I'll have to respond later to your other questions.
     
  9. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    To get a team object from a team number, we need to do as:
    Code:
    local pTeam1 = Teams[Team1]
    We can then check if the team is a minor civilization via
    Code:
    pTeam1:IsMinorCiv()
    Unless a user is specifically playing with teams enabled there is one player to each team. The exception is when "Team Play" is enabled, and usually the teams in such cases are defined as part of a scenario -- and in most such cases city-states are still assigned to their own team. So for City-States it's a reasonable assumption we can make the pTeam1:IsMinorCiv() check and not worry about team-play corner-case issues.

    To implement the ability in the code to ignore peace deals where a city state is one of the teams, I would thus change the code to:
    Spoiler :
    Code:
    local civilizationID = GameInfoTypes["CIVILIZATION_COSTA_RICA"]
    --create boolean flag for whether peace effect applies when one of players making peace is a Costa Rica player
    local bApplyWhenCostaRicaInvolved = false
    
    --create boolean flag for whether peace effect applies when one of players making peace is a City-State player
    local bApplyWhenMinorCivIsInvolved = false
    
    --create tables and boolean to track active Costa Rica players
    local tCostaRicaPlayerIDs, tCostaRicaTeamIDs, bCostaRicaActive = {}, {}, false
    
    --get the Costa Rica player's ID and Team ID numbers and stick these in the lua tables
    for iPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
    	local pLoopPlayer = Players[iPlayer]
    	if pLoopPlayer:IsEverAlive() and (pLoopPlayer:GetCivilizationType() == civilizationID) then
    		local iLoopTeam = pLoopPlayer:GetTeam()
    		table.insert(tCostaRicaPlayerIDs, {PlayerID=iPlayer, TeamID=iLoopTeam})
    		if not tCostaRicaTeamIDs[iLoopTeam] then
    			tCostaRicaTeamIDs[iLoopTeam] = {}
    		end
    		table.insert(tCostaRicaTeamIDs[iLoopTeam], iPlayer)
    		bCostaRicaActive = true
    	end
    end
    function PeaceTreatySigned(Team1, Team2, War)
    	if (War == false) then
    		print("Someone Made Peace")
    		if (bApplyWhenMinorCivIsInvolved == false) then
    			local pTeam1 = Teams[Team1]
    			local pTeam2 = Teams[Team2]
    			if pTeam1:IsMinorCiv() or pTeam2:IsMinorCiv() then
    				print("One of the Peacemakers was a Minor Civ: terminating function PeaceTreatySigned")
    				return
    			end
    		end
    		local bExecuteEffect = false
    		local Gold = math.floor(2000 / Game.CountCivTeamsAlive())
    		if (bApplyWhenCostaRicaInvolved == true) then
    			bExecuteEffect = true
    		else --this will run when (bApplyWhenCostaRicaInvolved == false)
    			if (not tCostaRicaTeamIDs[Team1]) and (not tCostaRicaTeamIDs[Team2]) then
    				bExecuteEffect = true
    			end
    		end
    		if (bExecuteEffect == true) then
    			for key,Data in ipairs(tCostaRicaPlayerIDs) do
    				local pPlayer = Players[Data.PlayerID]
    				pPlayer:ChangeGold(Gold)
    			end
    		end
    	end
    end
    function OnLoadScreenClose()
    	Events.WarStateChanged.Add(PeaceTreatySigned)
    end
    if (bCostaRicaActive == true) then
    	--delay actual event hook to the Events.LoadScreenClose so that game reload does not cause the code to fire
    	--the code will also never execute if Costa Rica is not part of the current game
    	Events.LoadScreenClose.Add(OnLoadScreenClose)
    end
    Lua tables can be used to store any data that would be convenient to expedite the rest of your code. You can store integers, booleans, text, lua sub-tables, and object variables in the "value" side of a table.

    But if at all possible you should tend to avoid storing object variables as the data pertaining to those object variables can change (or no longer exist) between the time you stick it into an lua table and the time you want to use it. For example, I could store the "pCity" object variable for a city, but then later on if that same city completes a new building I might run into a dis-synch issue between the data applying to the city in "realtime" and that which pertained when I stored the variable into the table.

    All I can say about when to use tables and when not to is that there ought to be at least two "same" pieces of information your code needs to handle (like two different Player ID#s) in order for it to be worth creating an lua table rather than a variable that only holds a single integer value, for example.
    No. Sometimes you know you have X pieces of game information your mod is adding that you want your lua code to handle processing for (like, say, six different Wonders) but you do not want your code to process for anything else. You can in that case just create a "hard-coded" lua table with the exact wonder ID data you want your code to handle. The good part of lua tables used in this way is that if you decide to add another Wonder to the list your code does things for, you just add it to the table, and likewise if you decide you want to remove one of the wonders from being processed by your code, you just remove it from the list in the table.
    Code:
    local tFreeGreatPeopleWonders = { [GameInfoTypes.BUILDING_TERRACOTTA_ARMY] = GameInfoTypes.UNIT_GREAT_GENERAL,
    	[GameInfoTypes.BUILDING_ALHAMBRA] = GameInfoTypes.UNIT_ENGINEER,
    	[GameInfoTypes.BUILDING_HAGIA_SOPHIA] = GameInfoTypes.UNIT_ENGINEER }
    I could then make the rest of my code create the specified great person (or any unit for that matter) in the city that completes these wonders.
     
  10. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    Appreciate the answer!

    Firstly, I've just had a try of the code you mentioned. Using the In Game Editor (IGE) I set all the Civs to Declare War on each other (which shouldn't change the gold, and didn't). But then I let them all declare peace naturally over the next few turns. But when that happened, I didn't get the gold. Is it because I was using IGE to artificially create war perhaps? Is there something I need to be doing with the LUA file in ModBuddy (like on the Action tab)?

    I should note I also have a 'simple trait' XML document based on the ones the Firaxis Civs use in-game to give my civ the ability to enter city-state territory without angering it. Could that be conflicting with the lua script?

    Also, I was caught off guard a bit by how you don't necessarily need to start code chunks with 'function', e.g.:

    Code:
    if (bCostaRicaActive == true) then
        --delay actual event hook to the Events.LoadScreenClose so that game reload does not cause the code to fire
        --the code will also never execute if Costa Rica is not part of the current game
        Events.LoadScreenClose.Add(OnLoadScreenClose)
    end
    Is this done because you mention something major like a hook?
     
  11. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    the lua file needs to be set-up as an InGameUIAddin. See whoward69's what ModBuddy setting for what file types tutorial

    the easiest way to be sure your file is actually loading and is loading without errors is to add a print statement at the very bottom that will then be added into the game's lua.log file when the code is successfully loaded.

    code that is not "hooked" to a event will execute when the game loads from a saved game or from starting a new game, but not thereafter for the remainder of that game-session. so since we only need to look for whether Costa Rica is part of the game once, that loop through all relevant players is not part of a function nor "hhoked" to a game event: it is executed at the "root" level of the file, executes only one time when a game-session is started, and caches info we need into the correct variables and tables.

    we can then make use of the variable bCostaRicaActive to determine whether it is even necessary for the main "guts" of the code to even execute. A mod can still be enabled when a given civ added by that mod is not part of a given game, and in such cases it is better to make our lua code not execute as this creates less processing.

    There are also certain types of event-hooks we do not want to subscribe to until after a saved game is completely loaded, and for such cases we use the Events.LoadScreenClose because it does not fire until after the map is rebuilt from a save, units have been placed, cities have been placed, etc. Events.LoadScreenClose fires when you press "Start Journey" or "Continue Journey" on the loading screen, and this button does not become available until after all the saved-game or new-game info has loaded into the game and onto the map.
     
  12. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    I followed that page you mentioned which suggesting that:

    When I built the mod and started a game with it, my In-Game Editor button disappeared from the UI. Has this happened because I have 'overwritten' it or not included it as a compatible mod (e.g. in the Associations tab within the modinfo in ModBuddy)?

    I have a couple of questions about the following code as well:

    Code:
                    print("One of the Peacemakers was a Minor Civ: terminating function PeaceTreatySigned")
                    return
    1. Does print create text in the same place as things like "[player name] have discovered that the world is round" and "It is We Love the King Day in [city name]"? If so, could I change it to:

    Code:
    print("One of the Peacemakers was a city-state - you will not receive any gold")
    2. What is the true purpose of the 'return' function? I've seen it used in contexts where I thought I wouldn't see it, and absent from contexts in which I thought I would. I understand that it's there to 'return' a value of some kind, but I don't understand when and why.

    In a block of code like that, apparently the 'return' or 'break' phrase is used at the end, as I would expect. You know, "you've ran the code to get a value - here's your value, do what you want with it". But like... does the value just sit there in the ether, at that bit of the code?

    My intended output of my mod trait was to generate gold for Costa Rica, so that's my output - that's the value I want 'returned' at the end of all this. But:

    Code:
            if (bExecuteEffect == true) then
                for key,Data in ipairs(tCostaRicaPlayerIDs) do
                    local pPlayer = Players[Data.PlayerID]
                    pPlayer:ChangeGold(Gold)
    At no point during that chunk of code is there a 'return' phrase. Which again, leads me to think that the 'return' phrase simply hold a value in the ether of the code somewhere so that a future bit of logic transforms into into an actual physical piece of output, i.e. the change of gold at the top of the screen. Have I understood this correctly?
     
  13. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    There should not have been any effect on the In-Game Editor mod. You should not need to do anything for compatibility purposes to it. I've never had the IGE button disappear from the UI as a result of any mod I've made. I would have to see the built version of the mod from within the game's Mods folder to have any better idea what is causing the IGE button to disappear.

    command
    in an lua file causes a message with the mod's lua filename and the text "Cheeseburgers" to be printed in the game's lua.log. The command is used primarily for debugging pureposes so you can see which lines are executing properly.

    return terminates the function. Where the function is not meant to send any data back up to another function or to the gamecore, you can use it as a simple "exit this function at this point" command, as I have done.

    break literally tells lua to "break off" doing anything more within a "for" or "while" loop. It is used to cease execution of the rest of loop when we have found the data we are interested in, or have executed the thing we want to execute.

    there is no return or break here
    Code:
            if (bExecuteEffect == true) then
                for key,Data in ipairs(tCostaRicaPlayerIDs) do
                    local pPlayer = Players[Data.PlayerID]
                    pPlayer:ChangeGold(Gold)
               end
            end
    because we want to run through the entire table via the defined "for" loop, rather than breaking-off in the middle of it, or terminating the entire function in the middle of it.

    You do not need to "return" a value from the function because this line
    Code:
    pPlayer:ChangeGold(Gold)
    enaxcts everything that needs to be enacted
     
    Last edited: Apr 7, 2018
  14. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    It might have something to do with a slight change to the IGE lua file to decides where the icon is place on the screen. I used the Enhanced User Interface, so I downloaded an lua extension to re-place the IGE button next to the Gold information on the top bar. Maybe that has something to do with it?

    I've uploaded a zip of my current built mod. The lua is 99% identical to the code you uploaded yourself though...
     

    Attached Files:

  15. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    You must have the full filepath as it is within the mod, so this is incorrect
    Code:
      <EntryPoints>
        <EntryPoint type="InGameUIAddin" file="Trait_Functions.lua">
          <Name>Trait_Functions</Name>
          <Description>Trait_Functions</Description>
        </EntryPoint>
      </EntryPoints>
    because the lua file is in a sub-folder called Lua/. You need Lua/Trait_Functions.lua instead of Trait_Functions.lua. If you follow William Howard's tutorial, you will see that you can select the filename from the valid list of mod files as a 1st step instead of selecting "InGameUIAddin" first. This will ensure that you have the proper file-path within the mod for the file you want to add as an "entrypoint".

    At this point your file is not even loading.

    --------------------------

    I would also diagnose that the IGE button dissappearing has to do with the EUI compatability-file since your mod is not actually doing anything lua-wise at the moment. But I have not run EUI in a very long time and cannot remember what else you might need to do in terms of disabling the "standard" EUI file that can interfere with the IGE button or result in overlap of the IGE button with EUI stuff in the top panel.
     
  16. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    Yep, that seems to have worked. I can't thank you enough dude. Unless you have a Patreon or something? I'd be happy to donate for your help

    I'm in the middle of writing a new mod too for a similar Civ which I'll have a better go at this time. If you're interested, the trait I've come up for it is:

    To write it:
    1. Hook the function to PlayerDoTurn
    2. Identify my Civ
    3. Identify the gold in my coffers
    4. Generate unhappiness (or is it classed as 'negative happiness'?), science and culture
     
  17. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    I wouldn't even try to create an "unhappiness" effect. There are no straightforward game systems by which this can be accomplished, and the only work-around methods are quite frankly a mess. There are direct lua methods to add more unhappiness but there's no straightforward manner by which to track the totals that have been implemented and also I seem to recall other people reporting that the effects can get wiped by the game re-calculating as part of turn processing and the like.

    You cannot create negative happiness via buildings: the game simply ignores negative values.
    You can do so via policies, but doing so via policies is loaded with issues, which is why the dynamic policy effects system I created requires either William Howard's VMC DLL or the CP DLL.
     
  18. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    Noted. I had another idea that instead I could have:

    I noticed that there was a method for SetHappiness in the Player section, I could set all global happiness to 0...?

    Will that have more do-ability?

    Also, I gotta ask... what's your process, dude? What's the first thing you consider when beginning to write an .lua code? Do you start by gathering all of the functions you'll need to make a trait first? Or do you take it one line at a time? What's the most useful 'methods' you typically remember or have to-hand?
     
  19. LeeS

    LeeS Imperator

    Joined:
    Jul 23, 2013
    Messages:
    5,732
    Location:
    Illinois, USA
    I'm not sure if the Happiness score is also re-calculated every turn (to override any changes made in lua) or whether the change made by lua is also "remembered" in the calculations each turn, but be aware that the Happiness and UnHappiness being referred to are the raw data for Happiness and UnHappiness rather than the end product of "Happiness" - "UnHappiness" = "Excess Happiness"

    To get your effective "Excess Happiness" that shows on the top panel you need to use:
    Code:
    Player:GetExcessHappiness()
    Negative Values reflect an Unhappy Empire. You would need to get the excess happiness and subtract that from the Player:GetHappiness() to arrive at a result for Player:SetHappiness(NewTotalValue)

    The only way I can respond to what my process is is to say I first determine whether a thing can be done, then whether or not it is worth the code-gymnastics required to implement it, and then I work out in my head the logic-path required. At this point I've written enough Civ5 code that I copy-paste and frankenstien together what I need from existing code.
     
  20. CivHawk

    CivHawk Chieftain

    Joined:
    Dec 29, 2017
    Messages:
    51
    Yeah, I've heard about an unwritten 'programmers code' that basically says you have to copypaste most of your work...

    I have a few more questions regarding my learnings...

    1. I'm not sure what 'JONSCulture' means, especially in terms of lua methods, is it different from normal 'culture' in any way?

    2. In terms of any common 'frankenstein' code you use, are you able to provide any templates? For example, I have seen that on numerous mods, people using a 'for' statement to identify their Civ, which I think would be useful to make sure the effect happens for the Civ in question. Am I thinking along the right lines here?

    3. I'm not sure how to change the current science output of a Civ. Is it done per city, per team, per player?

    4. I'm also writing lua for a unit as well with the current effect:

    I understand that I'd have to iterate through the units until I reach the New Unit and then apply the code, is that right? The only ideology-base method I can find seems to be:

    Player:GetPublicOpinionPreferredIdeology

    Is there a way I can use this method to satisfy the above logic?
     

Share This Page